Source code for mailman_pgp.utils.pgp

# Copyright (C) 2017 Jan Jancar
#
# This file is a part of the Mailman PGP plugin.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# this program.  If not, see <http://www.gnu.org/licenses/>.

"""Miscellaneous PGP utilities."""
from pgpy import PGPKey, PGPSignature
from pgpy.constants import SignatureType
from pgpy.errors import PGPError
from pgpy.packet import Packet, Signature
from pgpy.types import Armorable
from public import public


@public
[docs]def expired(verifications): """ :param verifications: :return: """ return any(any(sigsubj.signature.is_expired or sigsubj.by.is_expired for sigsubj in verification.good_signatures) for verification in verifications)
@public
[docs]def revoked(verifications): """ :param verifications: :return: """ return any(any(key_revoked(sigsubj.by) for sigsubj in verification.good_signatures) for verification in verifications)
@public
[docs]def verifies(verifications): """ :param verifications: :type verifications: typing.Sequence[pgpy.types.SignatureVerification] :return: bool """ return all(bool(verification) and all(not sigsubj.signature.is_expired for sigsubj in verification.good_signatures) for verification in verifications)
@public
[docs]def hashes(verifications): """ :param verifications: :return: :rtype: typing.Generator[bytes] """ for verification in verifications: for sigsubj in verification.good_signatures: data = sigsubj.signature.hashdata(sigsubj.subject) hasher = sigsubj.signature.hash_algorithm.hasher hasher.update(data) yield hasher.digest()
@public
[docs]def key_from_blob(blob): """ :param blob: :return: :rtype: pgpy.PGPKey """ key, _ = PGPKey.from_blob(blob) return key
@public
[docs]def key_from_file(file): """ :param file: :return: :rtype: pgpy.PGPKey """ key, _ = PGPKey.from_file(file) return key
@public
[docs]def revoc_from_blob(blob): """ Load a key revocation signature from an ASCII-Armored blob. :param blob: :return: :rtype: pgpy.PGPSignature """ dearm = Armorable.ascii_unarmor(blob) p = Packet(dearm['body']) if not isinstance(p, Signature): raise ValueError('Not a key revocation signature.') if p.sigtype not in (SignatureType.KeyRevocation, SignatureType.SubkeyRevocation): raise ValueError('Not a key revocation.') sig = PGPSignature() sig |= p return sig
@public
[docs]def key_revoked(key): """ :param key: :type key: pgpy.PGPKey :return: :rtype: bool """ if key.is_primary: verifier = key else: verifier = key.parent for revoc in key.revocation_signatures: try: verified = verifier.verify(key, revoc) except PGPError: continue if bool(verified): return True return False
@public
[docs]def key_flags(key): """ :param key: :type key: pgpy.PGPKey :return: :rtype: Set[pgpy.constants.KeyFlags] """ if key.is_expired: return set() if key_revoked(key): return set() usage_flags = set() uids = (uid for uid in key.userids if uid.is_primary) uids = list(uids) if len(uids) == 0: uids = key.userids for uid in uids: revoked = False for sig in uid.signatures: if sig.type is not SignatureType.CertRevocation: continue if sig.signer == key.fingerprint.keyid: try: verified = key.verify(uid, sig) except PGPError: continue if bool(verified): revoked = True if not revoked: usage_flags |= uid.selfsig.key_flags break for subkey in key.subkeys.values(): if subkey.is_expired: continue if not key_revoked(subkey): usage_flags |= subkey.usage_flags() return usage_flags
@public
[docs]def key_usable(key, flags_required): """ Check that the `key` has the `flags_required` set of KeyFlags. Checks only non-expired, non-revoked key/subkeys. Validates revocations it can, so not those made with some other designated revocation key. :param key: The key to check. :type key: pgpy.PGPKey :param flags_required: The set of flags required. :type flags_required: set :return: Whether the key has the flags_required. :rtype: bool """ if key.is_expired: return False if key_revoked(key): return False return flags_required.issubset(key_flags(key))
@public
[docs]def key_merge(privkey, new_key, signer_key=None): """ :param privkey: :type privkey: pgpy.PGPKey :param new_key: :type new_key: pgpy.PGPKey :param signer_key: :type signer_key: pgpy.PGPKey """ if privkey.pubkey.key_material != new_key.key_material: raise ValueError('You sent a wrong key.') uid_map = {} for uid in privkey.userids: for uid_other in new_key.userids: if uid == uid_other: uid_map[uid] = uid_other if len(uid_map) == 0: raise ValueError('No signed UIDs found.') uid_sigs = {} for uid, uid_other in uid_map.items(): for sig in uid_other.signatures: if sig in uid.signatures: continue if signer_key is None: uid_sigs.setdefault(uid, []).append(sig) continue if sig.signer != signer_key.fingerprint.keyid: continue # sig is a new signature, not currently on uid, and seems to # be made by the signer_key try: verification = signer_key.verify(uid, sig) if bool(verification): uid_sigs.setdefault(uid, []).append(sig) except PGPError: pass if len(uid_sigs) == 0: raise ValueError('No new certifications found.') for uid, sigs in uid_sigs.items(): for sig in sigs: uid |= sig