From 933731e4671c8ed3b7fa16bf1222e06f76eea215 Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 4 Nov 2013 15:09:40 -0200 Subject: Add verification of detached signatures. --- .../feature-4375_add-verification-of-detached-sigs | 1 + src/leap/keymanager/__init__.py | 12 +++++++---- src/leap/keymanager/keys.py | 9 +++++--- src/leap/keymanager/openpgp.py | 25 ++++++++++++++++++---- src/leap/keymanager/tests/test_keymanager.py | 14 ++++++++++-- 5 files changed, 48 insertions(+), 13 deletions(-) create mode 100644 changes/feature-4375_add-verification-of-detached-sigs diff --git a/changes/feature-4375_add-verification-of-detached-sigs b/changes/feature-4375_add-verification-of-detached-sigs new file mode 100644 index 0000000..ee09cbe --- /dev/null +++ b/changes/feature-4375_add-verification-of-detached-sigs @@ -0,0 +1 @@ + o Add verification of detached signatures. Closes #4375. diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index a550598..61213d3 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -473,15 +473,18 @@ class KeyManager(object): data, privkey, digest_algo=digest_algo, clearsign=clearsign, detach=detach, binary=binary) - 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: EncryptionKey + :param detached_sig: A detached signature. If given, C{data} is + verified using this detached signature. + :type detached_sig: str :return: The signed data. :rtype: str @@ -489,7 +492,8 @@ class KeyManager(object): leap_assert_type(pubkey, EncryptionKey) leap_assert(pubkey.__class__ in self._wrapper_map, 'Unknown key type.') leap_assert(pubkey.private is False, 'Key is not public.') - return self._wrapper_map[pubkey.__class__].verify(data, pubkey) + return self._wrapper_map[pubkey.__class__].verify( + data, pubkey, detached_sig=detached_sig) from ._version import get_versions __version__ = get_versions()['version'] diff --git a/src/leap/keymanager/keys.py b/src/leap/keymanager/keys.py index b8e88d4..ec1bfeb 100644 --- a/src/leap/keymanager/keys.py +++ b/src/leap/keymanager/keys.py @@ -340,15 +340,18 @@ class EncryptionScheme(object): pass @abstractmethod - 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: EncryptionKey + :param detached_sig: A detached signature. If given, C{data} is + verified against this sdetached signature. + :type detached_sig: str :return: The signed data. :rtype: str diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index 8ec8639..111dfaf 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -30,6 +30,7 @@ import locale 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.keymanager import errors @@ -570,15 +571,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 +590,20 @@ 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() + sfd = os.fdopen(sf, 'w') + sfd.write(detached_sig) + sfd.close() + df = _make_binary_stream(data, gpg._encoding) + result = gpg.verify_file(df, sig_file=sfname) + df.close() gpgpubkey = gpg.list_keys().pop() valid = result.valid rfprint = result.fingerprint diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index 2512604..67676e9 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -294,7 +294,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): pgp.put_ascii_key(PRIVATE_KEY) data = 'data' privkey = pgp.get_key(ADDRESS, private=True) - signed = pgp.sign(data, privkey) + signed = pgp.sign(data, privkey, detach=False) pubkey = pgp.get_key(ADDRESS, private=False) self.assertTrue(pgp.verify(signed, pubkey)) @@ -314,6 +314,16 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): encrypted_and_signed, privkey2, verify=pubkey) self.assertTrue(data, res) + def test_sign_verify_detached_sig(self): + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + pgp.put_ascii_key(PRIVATE_KEY) + data = 'data' + privkey = pgp.get_key(ADDRESS, private=True) + signature = pgp.sign(data, privkey, detach=True) + pubkey = pgp.get_key(ADDRESS, private=False) + self.assertTrue(pgp.verify(data, pubkey, detached_sig=signature)) + class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): @@ -490,7 +500,7 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): privkey = km.get_key( ADDRESS, OpenPGPKey, private=True, fetch_remote=False) # encrypt - signdata = km.sign(self.RAW_DATA, privkey) + signdata = km.sign(self.RAW_DATA, privkey, detach=False) self.assertNotEqual(self.RAW_DATA, signdata) # get public key for verifying pubkey = km.get_key( -- cgit v1.2.3 From 45f158060a4ed2f95a94d839a869a76a02d5ceab Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 4 Nov 2013 15:15:46 -0200 Subject: Remove key comment on OpenPGP key generation. --- src/leap/keymanager/openpgp.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index 111dfaf..a4dc1b8 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -27,6 +27,7 @@ import re import shutil import tempfile import locale +from contextlib import closing from gnupg import GPG from gnupg.gnupg import GPGUtilities @@ -47,6 +48,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 @@ -244,7 +249,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 " @@ -598,12 +603,10 @@ class OpenPGPScheme(EncryptionScheme): # gpg.verify_file(), which receives the data as a binary # stream and the name of a file containing the signature. sf, sfname = tempfile.mkstemp() - sfd = os.fdopen(sf, 'w') - sfd.write(detached_sig) - sfd.close() - df = _make_binary_stream(data, gpg._encoding) - result = gpg.verify_file(df, sig_file=sfname) - df.close() + 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 -- cgit v1.2.3 From 666eb414ebc4d852410699f061c4b2d505f59308 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 11 Nov 2013 16:24:28 -0300 Subject: Use 'utf-8' as default/fallback encoding. - Also reorder stdlib imports alphabetically :) --- changes/bug-4427_default-encoding-to-utf8 | 2 ++ src/leap/keymanager/openpgp.py | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 changes/bug-4427_default-encoding-to-utf8 diff --git a/changes/bug-4427_default-encoding-to-utf8 b/changes/bug-4427_default-encoding-to-utf8 new file mode 100644 index 0000000..3f7d671 --- /dev/null +++ b/changes/bug-4427_default-encoding-to-utf8 @@ -0,0 +1,2 @@ + o Default encoding to 'utf-8' in case of system encodings not set. Closes + #4427. diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index a4dc1b8..1670e1c 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -21,12 +21,14 @@ 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 @@ -516,6 +518,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): -- cgit v1.2.3 From 0050dcd11137f544c6c78d236163e19f76a24b38 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 13 Nov 2013 18:09:15 -0300 Subject: Refactor code to support parsing ascii keys. --- src/leap/keymanager/errors.py | 12 ++++++++ src/leap/keymanager/openpgp.py | 63 ++++++++++++++++++++++++++++++------------ 2 files changed, 58 insertions(+), 17 deletions(-) diff --git a/src/leap/keymanager/errors.py b/src/leap/keymanager/errors.py index 89949d2..27180db 100644 --- a/src/leap/keymanager/errors.py +++ b/src/leap/keymanager/errors.py @@ -84,3 +84,15 @@ class SignFailed(Exception): Raised when failed to sign. """ pass + + +class KeyAddressMismatch(Exception): + """ + A mismatch between addresses. + """ + + +class KeyFingerprintMismatch(Exception): + """ + A mismatch between fingerprints. + """ diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index 1670e1c..78acd6a 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -35,7 +35,7 @@ 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, @@ -303,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 @@ -325,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): """ -- cgit v1.2.3 From 875f8ed071f3dae06818eba992a8dd8ddc2298b3 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 14 Nov 2013 11:59:28 -0300 Subject: Expose openpgp methods in keymanager. - parse_ascii_key - delete_key - put_key --- src/leap/keymanager/__init__.py | 42 +++++++++++++++++++++++++++++++++++++++++ src/leap/keymanager/openpgp.py | 6 +++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index 61213d3..dbc5489 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -495,6 +495,48 @@ class KeyManager(object): return self._wrapper_map[pubkey.__class__].verify( data, pubkey, detached_sig=detached_sig) + def parse_openpgp_ascii_key(self, key_data): + """ + Parses an ascii armored key (or key pair) data and returns + the OpenPGPKey keys. + + :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 + """ + return self._wrapper_map[OpenPGPKey].parse_ascii_key(key_data) + + def delete_key(self, key): + """ + Remove C{key} from storage. + + May raise: + openpgp.errors.KeyNotFound + openpgp.errors.KeyAttributesDiffer + + :param key: The key to be removed. + :type key: EncryptionKey + """ + try: + self._wrapper_map[type(key)].delete_key(key) + except IndexError as e: + leap_assert(False, "Unsupported key type. Error {0!r}".format(e)) + + def put_key(self, key): + """ + Put C{key} in local storage. + + :param key: The key to be stored. + :type key: OpenPGPKey + """ + try: + self._wrapper_map[type(key)].put_key(key) + except IndexError as e: + leap_assert(False, "Unsupported key type. Error {0!r}".format(e)) + from ._version import get_versions __version__ = get_versions()['version'] del get_versions diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index 78acd6a..f6223d5 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -424,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) -- cgit v1.2.3 From d88afefd26592f91edbf3f5a2eea165508ca4afa Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 14 Nov 2013 12:07:30 -0300 Subject: Add changes file. --- changes/feature-expose-methods | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changes/feature-expose-methods diff --git a/changes/feature-expose-methods b/changes/feature-expose-methods new file mode 100644 index 0000000..92b30d5 --- /dev/null +++ b/changes/feature-expose-methods @@ -0,0 +1,2 @@ + o New openpgp method: parse_ascii_keys. + o Expose openpgp methods in keymanager (parse_ascii_keys, put_key, delete_key). -- cgit v1.2.3 From b4b46883df55ffeccfdda975f0b61b32436d8ee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 15 Nov 2013 10:04:03 -0300 Subject: Fold in changes --- CHANGELOG | 8 ++++++++ changes/bug-4427_default-encoding-to-utf8 | 2 -- changes/feature-4375_add-verification-of-detached-sigs | 1 - changes/feature-expose-methods | 2 -- 4 files changed, 8 insertions(+), 5 deletions(-) delete mode 100644 changes/bug-4427_default-encoding-to-utf8 delete mode 100644 changes/feature-4375_add-verification-of-detached-sigs delete mode 100644 changes/feature-expose-methods diff --git a/CHANGELOG b/CHANGELOG index dc89742..e20b86c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,11 @@ +0.3.6 Nov 15: + o Default encoding to 'utf-8' in case of system encodings not + set. Closes #4427. + o Add verification of detached signatures. Closes #4375. + o New openpgp method: parse_ascii_keys. + o Expose openpgp methods in keymanager (parse_ascii_keys, put_key, + delete_key). + 0.3.5 Nov 1: o Return unicode decrypted text to avoid encoding issues. Related to #4000. diff --git a/changes/bug-4427_default-encoding-to-utf8 b/changes/bug-4427_default-encoding-to-utf8 deleted file mode 100644 index 3f7d671..0000000 --- a/changes/bug-4427_default-encoding-to-utf8 +++ /dev/null @@ -1,2 +0,0 @@ - o Default encoding to 'utf-8' in case of system encodings not set. Closes - #4427. diff --git a/changes/feature-4375_add-verification-of-detached-sigs b/changes/feature-4375_add-verification-of-detached-sigs deleted file mode 100644 index ee09cbe..0000000 --- a/changes/feature-4375_add-verification-of-detached-sigs +++ /dev/null @@ -1 +0,0 @@ - o Add verification of detached signatures. Closes #4375. diff --git a/changes/feature-expose-methods b/changes/feature-expose-methods deleted file mode 100644 index 92b30d5..0000000 --- a/changes/feature-expose-methods +++ /dev/null @@ -1,2 +0,0 @@ - o New openpgp method: parse_ascii_keys. - o Expose openpgp methods in keymanager (parse_ascii_keys, put_key, delete_key). -- cgit v1.2.3