diff options
Diffstat (limited to 'src/leap/keymanager/openpgp.py')
-rw-r--r-- | src/leap/keymanager/openpgp.py | 107 |
1 files changed, 83 insertions, 24 deletions
diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index 8ec86391..f6223d57 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -21,17 +21,21 @@ Infrastructure for using OpenPGP keys in Key Manager. """ +import locale import logging import os import re import shutil +import sys import tempfile -import locale + +from contextlib import closing from gnupg import GPG from gnupg.gnupg import GPGUtilities +from gnupg._util import _make_binary_stream -from leap.common.check import leap_assert, leap_assert_type +from leap.common.check import leap_assert, leap_assert_type, leap_check from leap.keymanager import errors from leap.keymanager.keys import ( EncryptionKey, @@ -46,6 +50,10 @@ from leap.keymanager.keys import ( logger = logging.getLogger(__name__) +# +# A temporary GPG keyring wrapped to provide OpenPGP functionality. +# + class TempGPGWrapper(object): """ A context manager that wraps a temporary GPG keyring which only contains @@ -243,7 +251,7 @@ class OpenPGPScheme(EncryptionScheme): key_length=4096, name_real=address, name_email=address, - name_comment='Generated by LEAP Key Manager.') + name_comment='') logger.info("About to generate keys... This might take SOME time.") gpg.gen_key(params) logger.info("Keys for %s have been successfully " @@ -295,16 +303,22 @@ class OpenPGPScheme(EncryptionScheme): raise errors.KeyNotFound(address) return build_key_from_dict(OpenPGPKey, address, doc.content) - def put_ascii_key(self, key_data): + def parse_ascii_key(self, key_data): """ - Put key contained in ascii-armored C{key_data} in local storage. + Parses an ascii armored key (or key pair) data and returns + the OpenPGPKey keys. - :param key_data: The key data to be stored. + :param key_data: the key data to be parsed. :type key_data: str or unicode + + :returns: the public key and private key (if applies) for that data. + :rtype: (public, private) -> tuple(OpenPGPKey, OpenPGPKey) + the tuple may have one or both components None """ leap_assert_type(key_data, (str, unicode)) # TODO: add more checks for correct key data. leap_assert(key_data is not None, 'Data does not represent a key.') + mail_regex = '.*<([\w.-]+@[\w.-]+)>.*' with self._temporary_gpgwrapper() as gpg: # TODO: inspect result, or use decorator @@ -317,31 +331,54 @@ class OpenPGPScheme(EncryptionScheme): except IndexError: pass pubkey = gpg.list_keys(secret=False).pop() # unitary keyring + # extract adress from first uid on key - match = re.match('.*<([\w.-]+@[\w.-]+)>.*', pubkey['uids'].pop()) + match = re.match(mail_regex, pubkey['uids'].pop()) leap_assert(match is not None, 'No user address in key data.') address = match.group(1) + if privkey is not None: - match = re.match( - '.*<([\w.-]+@[\w.-]+)>.*', privkey['uids'].pop()) + match = re.match(mail_regex, privkey['uids'].pop()) leap_assert(match is not None, 'No user address in key data.') privaddress = match.group(1) - leap_assert( - address == privaddress, - 'Addresses in pub and priv key differ.') - leap_assert( - pubkey['fingerprint'] == privkey['fingerprint'], - 'Fingerprints for pub and priv key differ.') - # insert private key in storage + + # build private key openpgp_privkey = _build_key_from_gpg( - address, privkey, + privaddress, privkey, gpg.export_keys(privkey['fingerprint'], secret=True)) - self.put_key(openpgp_privkey) - # insert public key in storage + + leap_check(address == privaddress, + 'Addresses in public and private key differ.', + errors.KeyAddressMismatch) + leap_check(pubkey['fingerprint'] == privkey['fingerprint'], + 'Fingerprints for public and private key differ.', + errors.KeyFingerprintMismatch) + + # build public key openpgp_pubkey = _build_key_from_gpg( address, pubkey, gpg.export_keys(pubkey['fingerprint'], secret=False)) + + return (openpgp_pubkey, openpgp_privkey) + + def put_ascii_key(self, key_data): + """ + Put key contained in ascii-armored C{key_data} in local storage. + + :param key_data: The key data to be stored. + :type key_data: str or unicode + """ + leap_assert_type(key_data, (str, unicode)) + + try: + openpgp_pubkey, openpgp_privkey = self.parse_ascii_key(key_data) + except (errors.KeyAddressMismatch, errors.KeyFingerprintMismatch) as e: + leap_assert(False, repr(e)) + + if openpgp_pubkey is not None: self.put_key(openpgp_pubkey) + if openpgp_privkey is not None: + self.put_key(openpgp_privkey) def put_key(self, key): """ @@ -387,10 +424,14 @@ class OpenPGPScheme(EncryptionScheme): """ Remove C{key} from storage. + May raise: + errors.KeyNotFound + errors.KeyAttributesDiffer + :param key: The key to be removed. :type key: EncryptionKey """ - leap_assert(key.__class__ is OpenPGPKey, 'Wrong key type.') + leap_assert_type(key, OpenPGPKey) stored_key = self.get_key(key.address, private=key.private) if stored_key is None: raise errors.KeyNotFound(key) @@ -510,6 +551,10 @@ class OpenPGPScheme(EncryptionScheme): # https://github.com/isislovecruft/python-gnupg/\ # blob/master/gnupg/_meta.py#L121 encoding = locale.getpreferredencoding() + if encoding is None: + encoding = sys.stdin.encoding + if encoding is None: + encoding = 'utf-8' return result.data.decode(encoding, 'replace') def is_encrypted(self, data): @@ -570,15 +615,18 @@ class OpenPGPScheme(EncryptionScheme): '%s != %s' % (rfprint, kfprint)) return result.data - def verify(self, data, pubkey): + def verify(self, data, pubkey, detached_sig=None): """ - Verify signed C{data} with C{pubkey}. + Verify signed C{data} with C{pubkey}, eventually using + C{detached_sig}. :param data: The data to be verified. :type data: str - :param pubkey: The public key to be used on verification. :type pubkey: OpenPGPKey + :param detached_sig: A detached signature. If given, C{data} is + verified against this detached signature. + :type detached_sig: str :return: The ascii-armored signed data. :rtype: str @@ -586,7 +634,18 @@ class OpenPGPScheme(EncryptionScheme): leap_assert_type(pubkey, OpenPGPKey) leap_assert(pubkey.private is False) with self._temporary_gpgwrapper(pubkey) as gpg: - result = gpg.verify(data) + result = None + if detached_sig is None: + result = gpg.verify(data) + else: + # to verify using a detached sig we have to use + # gpg.verify_file(), which receives the data as a binary + # stream and the name of a file containing the signature. + sf, sfname = tempfile.mkstemp() + with os.fdopen(sf, 'w') as sfd: + sfd.write(detached_sig) + with closing(_make_binary_stream(data, gpg._encoding)) as df: + result = gpg.verify_file(df, sig_file=sfname) gpgpubkey = gpg.list_keys().pop() valid = result.valid rfprint = result.fingerprint |