From 82e6a091225664c757f56b38f806401910889773 Mon Sep 17 00:00:00 2001 From: drebs Date: Tue, 2 Jul 2013 15:30:02 -0300 Subject: Move the key manager from leap_client repo to its own repo. --- keymanager/src/leap/__init__.py | 6 + keymanager/src/leap/keymanager/__init__.py | 336 ++++++++++ keymanager/src/leap/keymanager/errors.py | 86 +++ keymanager/src/leap/keymanager/gpg.py | 397 ++++++++++++ keymanager/src/leap/keymanager/keys.py | 285 +++++++++ keymanager/src/leap/keymanager/openpgp.py | 636 +++++++++++++++++++ keymanager/src/leap/keymanager/tests/__init__.py | 0 .../src/leap/keymanager/tests/test_keymanager.py | 676 +++++++++++++++++++++ 8 files changed, 2422 insertions(+) create mode 100644 keymanager/src/leap/__init__.py create mode 100644 keymanager/src/leap/keymanager/__init__.py create mode 100644 keymanager/src/leap/keymanager/errors.py create mode 100644 keymanager/src/leap/keymanager/gpg.py create mode 100644 keymanager/src/leap/keymanager/keys.py create mode 100644 keymanager/src/leap/keymanager/openpgp.py create mode 100644 keymanager/src/leap/keymanager/tests/__init__.py create mode 100644 keymanager/src/leap/keymanager/tests/test_keymanager.py (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/__init__.py b/keymanager/src/leap/__init__.py new file mode 100644 index 00000000..f48ad105 --- /dev/null +++ b/keymanager/src/leap/__init__.py @@ -0,0 +1,6 @@ +# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages +try: + __import__('pkg_resources').declare_namespace(__name__) +except ImportError: + from pkgutil import extend_path + __path__ = extend_path(__path__, __name__) diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py new file mode 100644 index 00000000..e1f318ca --- /dev/null +++ b/keymanager/src/leap/keymanager/__init__.py @@ -0,0 +1,336 @@ +# -*- coding: utf-8 -*- +# __init__.py +# Copyright (C) 2013 LEAP +# +# 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 . + + +""" +Key Manager is a Nicknym agent for LEAP client. +""" + +import requests + +from leap.common.check import leap_assert +from leap.keymanager.errors import ( + KeyNotFound, + NoPasswordGiven, +) +from leap.keymanager.keys import ( + build_key_from_dict, + KEYMANAGER_KEY_TAG, + TAGS_PRIVATE_INDEX, +) +from leap.keymanager.openpgp import ( + OpenPGPKey, + OpenPGPScheme, +) + + +# +# The Key Manager +# + +class KeyManager(object): + + # + # server's key storage constants + # + + OPENPGP_KEY = 'openpgp' + PUBKEY_KEY = "user[public_key]" + + def __init__(self, address, nickserver_uri, soledad, session_id=None, + ca_cert_path=None, api_uri=None, api_version=None, uid=None): + """ + Initialize a Key Manager for user's C{address} with provider's + nickserver reachable in C{url}. + + :param address: The address of the user of this Key Manager. + :type address: str + :param url: The URL of the nickserver. + :type url: str + :param soledad: A Soledad instance for local storage of keys. + :type soledad: leap.soledad.Soledad + :param session_id: The session ID for interacting with the webapp API. + :type session_id: str + :param ca_cert_path: The path to the CA certificate. + :type ca_cert_path: str + :param api_uri: The URI of the webapp API. + :type api_uri: str + :param api_version: The version of the webapp API. + :type api_version: str + :param uid: The users' UID. + :type uid: str + """ + self._address = address + self._nickserver_uri = nickserver_uri + self._soledad = soledad + self._session_id = session_id + self.ca_cert_path = ca_cert_path + self.api_uri = api_uri + self.api_version = api_version + self.uid = uid + # a dict to map key types to their handlers + self._wrapper_map = { + OpenPGPKey: OpenPGPScheme(soledad), + # other types of key will be added to this mapper. + } + # the following are used to perform https requests + self._fetcher = requests + self._session = self._fetcher.session() + + # + # utilities + # + + def _key_class_from_type(self, ktype): + """ + Return key class from string representation of key type. + """ + return filter( + lambda klass: str(klass) == ktype, + self._wrapper_map).pop() + + def _get(self, uri, data=None): + """ + Send a GET request to C{uri} containing C{data}. + + :param uri: The URI of the request. + :type uri: str + :param data: The body of the request. + :type data: dict, str or file + + :return: The response to the request. + :rtype: requests.Response + """ + leap_assert( + self._ca_cert_path is not None, + 'We need the CA certificate path!') + res = self._fetcher.get(uri, data=data, verify=self._ca_cert_path) + # assert that the response is valid + res.raise_for_status() + leap_assert( + res.headers['content-type'].startswith('application/json'), + 'Content-type is not JSON.') + return res + + def _put(self, uri, data=None): + """ + Send a PUT request to C{uri} containing C{data}. + + The request will be sent using the configured CA certificate path to + verify the server certificate and the configured session id for + authentication. + + :param uri: The URI of the request. + :type uri: str + :param data: The body of the request. + :type data: dict, str or file + + :return: The response to the request. + :rtype: requests.Response + """ + leap_assert( + self._ca_cert_path is not None, + 'We need the CA certificate path!') + leap_assert( + self._session_id is not None, + 'We need a session_id to interact with webapp!') + res = self._fetcher.put( + uri, data=data, verify=self._ca_cert_path, + cookies={'_session_id': self._session_id}) + # assert that the response is valid + res.raise_for_status() + return res + + def _fetch_keys_from_server(self, address): + """ + Fetch keys bound to C{address} from nickserver and insert them in + local database. + + :param address: The address bound to the keys. + :type address: str + + @raise KeyNotFound: If the key was not found on nickserver. + """ + # request keys from the nickserver + server_keys = self._get( + self._nickserver_uri, {'address': address}).json() + # insert keys in local database + if self.OPENPGP_KEY in server_keys: + self._wrapper_map[OpenPGPKey].put_ascii_key( + server_keys['openpgp']) + + # + # key management + # + + def send_key(self, ktype): + """ + Send user's key of type C{ktype} to provider. + + Public key bound to user's is sent to provider, which will sign it and + replace any prior keys for the same address in its database. + + If C{send_private} is True, then the private key is encrypted with + C{password} and sent to server in the same request, together with a + hash string of user's address and password. The encrypted private key + will be saved in the server in a way it is publicly retrievable + through the hash string. + + :param ktype: The type of the key. + :type ktype: KeyType + + @raise KeyNotFound: If the key was not found in local database. + """ + leap_assert( + ktype is OpenPGPKey, + 'For now we only know how to send OpenPGP public keys.') + # prepare the public key bound to address + pubkey = self.get_key( + self._address, ktype, private=False, fetch_remote=False) + data = { + self.PUBKEY_KEY: pubkey.key_data + } + uri = "%s/%s/users/%s.json" % ( + self._api_uri, + self._api_version, + self._uid) + self._put(uri, data) + + def get_key(self, address, ktype, private=False, fetch_remote=True): + """ + Return a key of type C{ktype} bound to C{address}. + + First, search for the key in local storage. If it is not available, + then try to fetch from nickserver. + + :param address: The address bound to the key. + :type address: str + :param ktype: The type of the key. + :type ktype: KeyType + :param private: Look for a private key instead of a public one? + :type private: bool + + :return: A key of type C{ktype} bound to C{address}. + :rtype: EncryptionKey + @raise KeyNotFound: If the key was not found both locally and in + keyserver. + """ + leap_assert( + ktype in self._wrapper_map, + 'Unkown key type: %s.' % str(ktype)) + try: + # return key if it exists in local database + return self._wrapper_map[ktype].get_key(address, private=private) + except KeyNotFound: + # we will only try to fetch a key from nickserver if fetch_remote + # is True and the key is not private. + if fetch_remote is False or private is True: + raise + self._fetch_keys_from_server(address) + return self._wrapper_map[ktype].get_key(address, private=False) + + def get_all_keys_in_local_db(self, private=False): + """ + Return all keys stored in local database. + + :return: A list with all keys in local db. + :rtype: list + """ + return map( + lambda doc: build_key_from_dict( + self._key_class_from_type(doc.content['type']), + doc.content['address'], + doc.content), + self._soledad.get_from_index( + TAGS_PRIVATE_INDEX, + KEYMANAGER_KEY_TAG, + '1' if private else '0')) + + def refresh_keys(self): + """ + Fetch keys from nickserver and update them locally. + """ + addresses = set(map( + lambda doc: doc.address, + self.get_all_keys_in_local_db(private=False))) + for address in addresses: + # do not attempt to refresh our own key + if address == self._address: + continue + self._fetch_keys_from_server(address) + + def gen_key(self, ktype): + """ + Generate a key of type C{ktype} bound to the user's address. + + :param ktype: The type of the key. + :type ktype: KeyType + + :return: The generated key. + :rtype: EncryptionKey + """ + return self._wrapper_map[ktype].gen_key(self._address) + + # + # Setters/getters + # + + def _get_session_id(self): + return self._session_id + + def _set_session_id(self, session_id): + self._session_id = session_id + + session_id = property( + _get_session_id, _set_session_id, doc='The session id.') + + def _get_ca_cert_path(self): + return self._ca_cert_path + + def _set_ca_cert_path(self, ca_cert_path): + self._ca_cert_path = ca_cert_path + + ca_cert_path = property( + _get_ca_cert_path, _set_ca_cert_path, + doc='The path to the CA certificate.') + + def _get_api_uri(self): + return self._api_uri + + def _set_api_uri(self, api_uri): + self._api_uri = api_uri + + api_uri = property( + _get_api_uri, _set_api_uri, doc='The webapp API URI.') + + def _get_api_version(self): + return self._api_version + + def _set_api_version(self, api_version): + self._api_version = api_version + + api_version = property( + _get_api_version, _set_api_version, doc='The webapp API version.') + + def _get_uid(self): + return self._uid + + def _set_uid(self, uid): + self._uid = uid + + uid = property( + _get_uid, _set_uid, doc='The uid of the user.') diff --git a/keymanager/src/leap/keymanager/errors.py b/keymanager/src/leap/keymanager/errors.py new file mode 100644 index 00000000..89949d29 --- /dev/null +++ b/keymanager/src/leap/keymanager/errors.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +# errors.py +# Copyright (C) 2013 LEAP +# +# 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 . + + +""" +Errors and exceptions used by the Key Manager. +""" + + +class KeyNotFound(Exception): + """ + Raised when key was no found on keyserver. + """ + pass + + +class KeyAlreadyExists(Exception): + """ + Raised when attempted to create a key that already exists. + """ + pass + + +class KeyAttributesDiffer(Exception): + """ + Raised when trying to delete a key but the stored key differs from the key + passed to the delete_key() method. + """ + pass + + +class NoPasswordGiven(Exception): + """ + Raised when trying to perform some action that needs a password without + providing one. + """ + pass + + +class InvalidSignature(Exception): + """ + Raised when signature could not be verified. + """ + pass + + +class EncryptionFailed(Exception): + """ + Raised upon failures of encryption. + """ + pass + + +class DecryptionFailed(Exception): + """ + Raised upon failures of decryption. + """ + pass + + +class EncryptionDecryptionFailed(Exception): + """ + Raised upon failures of encryption/decryption. + """ + pass + + +class SignFailed(Exception): + """ + Raised when failed to sign. + """ + pass diff --git a/keymanager/src/leap/keymanager/gpg.py b/keymanager/src/leap/keymanager/gpg.py new file mode 100644 index 00000000..15c1d9f6 --- /dev/null +++ b/keymanager/src/leap/keymanager/gpg.py @@ -0,0 +1,397 @@ +# -*- coding: utf-8 -*- +# gpgwrapper.py +# Copyright (C) 2013 LEAP +# +# 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 . + + +""" +A GPG wrapper used to handle OpenPGP keys. + +This is a temporary class that will be superseded by the a revised version of +python-gnupg. +""" + + +import os +import gnupg +import re +from gnupg import ( + logger, + _is_sequence, + _make_binary_stream, +) + + +class ListPackets(): + """ + Handle status messages for --list-packets. + """ + + def __init__(self, gpg): + """ + Initialize the packet listing handling class. + + :param gpg: GPG object instance. + :type gpg: gnupg.GPG + """ + self.gpg = gpg + self.nodata = None + self.key = None + self.need_passphrase = None + self.need_passphrase_sym = None + self.userid_hint = None + + def handle_status(self, key, value): + """ + Handle one line of the --list-packets status message. + + :param key: The status message key. + :type key: str + :param value: The status message value. + :type value: str + """ + # TODO: write tests for handle_status + if key == 'NODATA': + self.nodata = True + if key == 'ENC_TO': + # This will only capture keys in our keyring. In the future we + # may want to include multiple unknown keys in this list. + self.key, _, _ = value.split() + if key == 'NEED_PASSPHRASE': + self.need_passphrase = True + if key == 'NEED_PASSPHRASE_SYM': + self.need_passphrase_sym = True + if key == 'USERID_HINT': + self.userid_hint = value.strip().split() + + +class GPGWrapper(gnupg.GPG): + """ + This is a temporary class for handling GPG requests, and should be + replaced by a more general class used throughout the project. + """ + + GNUPG_HOME = os.environ['HOME'] + "/.config/leap/gnupg" + GNUPG_BINARY = "/usr/bin/gpg" # this has to be changed based on OS + + def __init__(self, gpgbinary=GNUPG_BINARY, gnupghome=GNUPG_HOME, + verbose=False, use_agent=False, keyring=None, options=None): + """ + Initialize a GnuPG process wrapper. + + :param gpgbinary: Name for GnuPG binary executable. + :type gpgbinary: C{str} + :param gpghome: Full pathname to directory containing the public and + private keyrings. + :type gpghome: C{str} + :param keyring: Name of alternative keyring file to use. If specified, + the default keyring is not used. + :param verbose: Should some verbose info be output? + :type verbose: bool + :param use_agent: Should pass `--use-agent` to GPG binary? + :type use_agent: bool + :param keyring: Path for the keyring to use. + :type keyring: str + @options: A list of additional options to pass to the GPG binary. + :type options: list + + @raise: RuntimeError with explanation message if there is a problem + invoking gpg. + """ + gnupg.GPG.__init__(self, gnupghome=gnupghome, gpgbinary=gpgbinary, + verbose=verbose, use_agent=use_agent, + keyring=keyring, options=options) + self.result_map['list-packets'] = ListPackets + + def find_key_by_email(self, email, secret=False): + """ + Find user's key based on their email. + + :param email: Email address of key being searched for. + :type email: str + :param secret: Should we search for a secret key? + :type secret: bool + + :return: The fingerprint of the found key. + :rtype: str + """ + for key in self.list_keys(secret=secret): + for uid in key['uids']: + if re.search(email, uid): + return key + raise LookupError("GnuPG public key for email %s not found!" % email) + + def find_key_by_subkey(self, subkey, secret=False): + """ + Find user's key based on a subkey fingerprint. + + :param email: Subkey fingerprint of the key being searched for. + :type email: str + :param secret: Should we search for a secret key? + :type secret: bool + + :return: The fingerprint of the found key. + :rtype: str + """ + for key in self.list_keys(secret=secret): + for sub in key['subkeys']: + if sub[0] == subkey: + return key + raise LookupError( + "GnuPG public key for subkey %s not found!" % subkey) + + def find_key_by_keyid(self, keyid, secret=False): + """ + Find user's key based on the key ID. + + :param email: The key ID of the key being searched for. + :type email: str + :param secret: Should we search for a secret key? + :type secret: bool + + :return: The fingerprint of the found key. + :rtype: str + """ + for key in self.list_keys(secret=secret): + if keyid == key['keyid']: + return key + raise LookupError( + "GnuPG public key for keyid %s not found!" % keyid) + + def find_key_by_fingerprint(self, fingerprint, secret=False): + """ + Find user's key based on the key fingerprint. + + :param email: The fingerprint of the key being searched for. + :type email: str + :param secret: Should we search for a secret key? + :type secret: bool + + :return: The fingerprint of the found key. + :rtype: str + """ + for key in self.list_keys(secret=secret): + if fingerprint == key['fingerprint']: + return key + raise LookupError( + "GnuPG public key for fingerprint %s not found!" % fingerprint) + + def encrypt(self, data, recipient, sign=None, always_trust=True, + passphrase=None, symmetric=False): + """ + Encrypt data using GPG. + + :param data: The data to be encrypted. + :type data: str + :param recipient: The address of the public key to be used. + :type recipient: str + :param sign: Should the encrypted content be signed? + :type sign: bool + :param always_trust: Skip key validation and assume that used keys + are always fully trusted? + :type always_trust: bool + :param passphrase: The passphrase to be used if symmetric encryption + is desired. + :type passphrase: str + :param symmetric: Should we encrypt to a password? + :type symmetric: bool + + :return: An object with encrypted result in the `data` field. + :rtype: gnupg.Crypt + """ + # TODO: devise a way so we don't need to "always trust". + return gnupg.GPG.encrypt(self, data, recipient, sign=sign, + always_trust=always_trust, + passphrase=passphrase, + symmetric=symmetric, + cipher_algo='AES256') + + def decrypt(self, data, always_trust=True, passphrase=None): + """ + Decrypt data using GPG. + + :param data: The data to be decrypted. + :type data: str + :param always_trust: Skip key validation and assume that used keys + are always fully trusted? + :type always_trust: bool + :param passphrase: The passphrase to be used if symmetric encryption + is desired. + :type passphrase: str + + :return: An object with decrypted result in the `data` field. + :rtype: gnupg.Crypt + """ + # TODO: devise a way so we don't need to "always trust". + return gnupg.GPG.decrypt(self, data, always_trust=always_trust, + passphrase=passphrase) + + def send_keys(self, keyserver, *keyids): + """ + Send keys to a keyserver + + :param keyserver: The keyserver to send the keys to. + :type keyserver: str + :param keyids: The key ids to send. + :type keyids: list + + :return: A list of keys sent to server. + :rtype: gnupg.ListKeys + """ + # TODO: write tests for this. + # TODO: write a SendKeys class to handle status for this. + result = self.result_map['list'](self) + gnupg.logger.debug('send_keys: %r', keyids) + data = gnupg._make_binary_stream("", self.encoding) + args = ['--keyserver', keyserver, '--send-keys'] + args.extend(keyids) + self._handle_io(args, data, result, binary=True) + gnupg.logger.debug('send_keys result: %r', result.__dict__) + data.close() + return result + + def encrypt_file(self, file, recipients, sign=None, + always_trust=False, passphrase=None, + armor=True, output=None, symmetric=False, + cipher_algo=None): + """ + Encrypt the message read from the file-like object 'file'. + + :param file: The file to be encrypted. + :type data: file + :param recipient: The address of the public key to be used. + :type recipient: str + :param sign: Should the encrypted content be signed? + :type sign: bool + :param always_trust: Skip key validation and assume that used keys + are always fully trusted? + :type always_trust: bool + :param passphrase: The passphrase to be used if symmetric encryption + is desired. + :type passphrase: str + :param armor: Create ASCII armored output? + :type armor: bool + :param output: Path of file to write results in. + :type output: str + :param symmetric: Should we encrypt to a password? + :type symmetric: bool + :param cipher_algo: Algorithm to use. + :type cipher_algo: str + + :return: An object with encrypted result in the `data` field. + :rtype: gnupg.Crypt + """ + args = ['--encrypt'] + if symmetric: + args = ['--symmetric'] + if cipher_algo: + args.append('--cipher-algo %s' % cipher_algo) + else: + args = ['--encrypt'] + if not _is_sequence(recipients): + recipients = (recipients,) + for recipient in recipients: + args.append('--recipient "%s"' % recipient) + if armor: # create ascii-armored output - set to False for binary + args.append('--armor') + if output: # write the output to a file with the specified name + if os.path.exists(output): + os.remove(output) # to avoid overwrite confirmation message + args.append('--output "%s"' % output) + if sign: + args.append('--sign --default-key "%s"' % sign) + if always_trust: + args.append("--always-trust") + result = self.result_map['crypt'](self) + self._handle_io(args, file, result, passphrase=passphrase, binary=True) + logger.debug('encrypt result: %r', result.data) + return result + + def list_packets(self, data): + """ + List the sequence of packets. + + :param data: The data to extract packets from. + :type data: str + + :return: An object with packet info. + :rtype ListPackets + """ + args = ["--list-packets"] + result = self.result_map['list-packets'](self) + self._handle_io( + args, + _make_binary_stream(data, self.encoding), + result, + ) + return result + + def encrypted_to(self, data): + """ + Return the key to which data is encrypted to. + + :param data: The data to be examined. + :type data: str + + :return: The fingerprint of the key to which data is encrypted to. + :rtype: str + """ + # TODO: make this support multiple keys. + result = self.list_packets(data) + if not result.key: + raise LookupError( + "Content is not encrypted to a GnuPG key!") + try: + return self.find_key_by_keyid(result.key) + except: + return self.find_key_by_subkey(result.key) + + def is_encrypted_sym(self, data): + """ + Say whether some chunk of data is encrypted to a symmetric key. + + :param data: The data to be examined. + :type data: str + + :return: Whether data is encrypted to a symmetric key. + :rtype: bool + """ + result = self.list_packets(data) + return bool(result.need_passphrase_sym) + + def is_encrypted_asym(self, data): + """ + Say whether some chunk of data is encrypted to a private key. + + :param data: The data to be examined. + :type data: str + + :return: Whether data is encrypted to a private key. + :rtype: bool + """ + result = self.list_packets(data) + return bool(result.key) + + def is_encrypted(self, data): + """ + Say whether some chunk of data is encrypted to a key. + + :param data: The data to be examined. + :type data: str + + :return: Whether data is encrypted to a key. + :rtype: bool + """ + return self.is_encrypted_asym(data) or self.is_encrypted_sym(data) diff --git a/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py new file mode 100644 index 00000000..44bd587b --- /dev/null +++ b/keymanager/src/leap/keymanager/keys.py @@ -0,0 +1,285 @@ +# -*- coding: utf-8 -*- +# keys.py +# Copyright (C) 2013 LEAP +# +# 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 . + + +""" +Abstact key type and encryption scheme representations. +""" + + +try: + import simplejson as json +except ImportError: + import json # noqa +import re + + +from abc import ABCMeta, abstractmethod +from leap.common.check import leap_assert + + +# +# Dictionary keys used for storing cryptographic keys. +# + +KEY_ADDRESS_KEY = 'address' +KEY_TYPE_KEY = 'type' +KEY_ID_KEY = 'key_id' +KEY_FINGERPRINT_KEY = 'fingerprint' +KEY_DATA_KEY = 'key_data' +KEY_PRIVATE_KEY = 'private' +KEY_LENGTH_KEY = 'length' +KEY_EXPIRY_DATE_KEY = 'expiry_date' +KEY_FIRST_SEEN_AT_KEY = 'first_seen_at' +KEY_LAST_AUDITED_AT_KEY = 'last_audited_at' +KEY_VALIDATION_KEY = 'validation' +KEY_TAGS_KEY = 'tags' + + +# +# Key storage constants +# + +KEYMANAGER_KEY_TAG = 'keymanager-key' + + +# +# key indexing constants. +# + +TAGS_PRIVATE_INDEX = 'by-tags-private' +TAGS_ADDRESS_PRIVATE_INDEX = 'by-tags-address-private' +INDEXES = { + TAGS_PRIVATE_INDEX: [ + KEY_TAGS_KEY, + 'bool(%s)' % KEY_PRIVATE_KEY, + ], + TAGS_ADDRESS_PRIVATE_INDEX: [ + KEY_TAGS_KEY, + KEY_ADDRESS_KEY, + 'bool(%s)' % KEY_PRIVATE_KEY, + ] +} + + +# +# Key handling utilities +# + +def is_address(address): + """ + Return whether the given C{address} is in the form user@provider. + + :param address: The address to be tested. + :type address: str + :return: Whether C{address} is in the form user@provider. + :rtype: bool + """ + return bool(re.match('[\w.-]+@[\w.-]+', address)) + + +def build_key_from_dict(kClass, address, kdict): + """ + Build an C{kClass} key bound to C{address} based on info in C{kdict}. + + :param address: The address bound to the key. + :type address: str + :param kdict: Dictionary with key data. + :type kdict: dict + :return: An instance of the key. + :rtype: C{kClass} + """ + leap_assert( + address == kdict[KEY_ADDRESS_KEY], + 'Wrong address in key data.') + return kClass( + address, + key_id=kdict[KEY_ID_KEY], + fingerprint=kdict[KEY_FINGERPRINT_KEY], + key_data=kdict[KEY_DATA_KEY], + private=kdict[KEY_PRIVATE_KEY], + length=kdict[KEY_LENGTH_KEY], + expiry_date=kdict[KEY_EXPIRY_DATE_KEY], + first_seen_at=kdict[KEY_FIRST_SEEN_AT_KEY], + last_audited_at=kdict[KEY_LAST_AUDITED_AT_KEY], + validation=kdict[KEY_VALIDATION_KEY], # TODO: verify for validation. + ) + + +# +# Abstraction for encryption keys +# + +class EncryptionKey(object): + """ + Abstract class for encryption keys. + + A key is "validated" if the nicknym agent has bound the user address to a + public key. Nicknym supports three different levels of key validation: + + * Level 3 - path trusted: A path of cryptographic signatures can be traced + from a trusted key to the key under evaluation. By default, only the + provider key from the user's provider is a "trusted key". + * level 2 - provider signed: The key has been signed by a provider key for + the same domain, but the provider key is not validated using a trust + path (i.e. it is only registered) + * level 1 - registered: The key has been encountered and saved, it has no + signatures (that are meaningful to the nicknym agent). + """ + + __metaclass__ = ABCMeta + + def __init__(self, address, key_id=None, fingerprint=None, + key_data=None, private=None, length=None, expiry_date=None, + validation=None, first_seen_at=None, last_audited_at=None): + self.address = address + self.key_id = key_id + self.fingerprint = fingerprint + self.key_data = key_data + self.private = private + self.length = length + self.expiry_date = expiry_date + self.validation = validation + self.first_seen_at = first_seen_at + self.last_audited_at = last_audited_at + + def get_json(self): + """ + Return a JSON string describing this key. + + :return: The JSON string describing this key. + :rtype: str + """ + return json.dumps({ + KEY_ADDRESS_KEY: self.address, + KEY_TYPE_KEY: str(self.__class__), + KEY_ID_KEY: self.key_id, + KEY_FINGERPRINT_KEY: self.fingerprint, + KEY_DATA_KEY: self.key_data, + KEY_PRIVATE_KEY: self.private, + KEY_LENGTH_KEY: self.length, + KEY_EXPIRY_DATE_KEY: self.expiry_date, + KEY_VALIDATION_KEY: self.validation, + KEY_FIRST_SEEN_AT_KEY: self.first_seen_at, + KEY_LAST_AUDITED_AT_KEY: self.last_audited_at, + KEY_TAGS_KEY: [KEYMANAGER_KEY_TAG], + }) + + def __repr__(self): + """ + Representation of this class + """ + return u"<%s 0x%s (%s - %s)>" % ( + self.__class__.__name__, + self.key_id, + self.address, + "priv" if self.private else "publ") + + +# +# Encryption schemes +# + +class EncryptionScheme(object): + """ + Abstract class for Encryption Schemes. + + A wrapper for a certain encryption schemes should know how to get and put + keys in local storage using Soledad, how to generate new keys and how to + find out about possibly encrypted content. + """ + + __metaclass__ = ABCMeta + + def __init__(self, soledad): + """ + Initialize this Encryption Scheme. + + :param soledad: A Soledad instance for local storage of keys. + :type soledad: leap.soledad.Soledad + """ + self._soledad = soledad + self._init_indexes() + + def _init_indexes(self): + """ + Initialize the database indexes. + """ + # Ask the database for currently existing indexes. + db_indexes = dict(self._soledad.list_indexes()) + # Loop through the indexes we expect to find. + for name, expression in INDEXES.items(): + if name not in db_indexes: + # The index does not yet exist. + self._soledad.create_index(name, *expression) + continue + if expression == db_indexes[name]: + # The index exists and is up to date. + continue + # The index exists but the definition is not what expected, so we + # delete it and add the proper index expression. + self._soledad.delete_index(name) + self._soledad.create_index(name, *expression) + + @abstractmethod + def get_key(self, address, private=False): + """ + Get key from local storage. + + :param address: The address bound to the key. + :type address: str + :param private: Look for a private key instead of a public one? + :type private: bool + + :return: The key bound to C{address}. + :rtype: EncryptionKey + @raise KeyNotFound: If the key was not found on local storage. + """ + pass + + @abstractmethod + def put_key(self, key): + """ + Put a key in local storage. + + :param key: The key to be stored. + :type key: EncryptionKey + """ + pass + + @abstractmethod + def gen_key(self, address): + """ + Generate a new key. + + :param address: The address bound to the key. + :type address: str + + :return: The key bound to C{address}. + :rtype: EncryptionKey + """ + pass + + @abstractmethod + def delete_key(self, key): + """ + Remove C{key} from storage. + + :param key: The key to be removed. + :type key: EncryptionKey + """ + pass diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py new file mode 100644 index 00000000..d19bb2b5 --- /dev/null +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -0,0 +1,636 @@ +# -*- coding: utf-8 -*- +# openpgp.py +# Copyright (C) 2013 LEAP +# +# 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 . + +""" +Infrastructure for using OpenPGP keys in Key Manager. +""" +import logging +import os +import re +import shutil +import tempfile + +from leap.common.check import leap_assert, leap_assert_type +from leap.keymanager import errors +from leap.keymanager.keys import ( + EncryptionKey, + EncryptionScheme, + is_address, + build_key_from_dict, + KEYMANAGER_KEY_TAG, + TAGS_ADDRESS_PRIVATE_INDEX, +) +from leap.keymanager.gpg import GPGWrapper + +logger = logging.getLogger(__name__) + + +# +# gpg wrapper and decorator +# + +def temporary_gpgwrapper(keys=None): + """ + Returns a unitary gpg wrapper that implements context manager + protocol. + + :param key_data: ASCII armored key data. + :type key_data: str + + :return: a GPGWrapper instance + :rtype: GPGWrapper + """ + # TODO do here checks on key_data + return TempGPGWrapper(keys=keys) + + +def with_temporary_gpg(fun): + """ + Decorator to add a temporary gpg wrapper as context + to gpg related functions. + + Decorated functions are expected to return a function whose only + argument is a gpgwrapper instance. + """ + def wrapped(*args, **kwargs): + """ + We extract the arguments passed to the wrapped function, + run the function and do validations. + We expect that the positional arguments are `data`, + and an optional `key`. + All the rest of arguments should be passed as named arguments + to allow for a correct unpacking. + """ + if len(args) == 2: + keys = args[1] if isinstance(args[1], OpenPGPKey) else None + else: + keys = None + + # sign/verify keys passed as arguments + sign = kwargs.get('sign', None) + if sign: + keys = [keys, sign] + + verify = kwargs.get('verify', None) + if verify: + keys = [keys, verify] + + # is the wrapped function sign or verify? + fun_name = fun.__name__ + is_sign_function = True if fun_name == "sign" else False + is_verify_function = True if fun_name == "verify" else False + + result = None + + with temporary_gpgwrapper(keys) as gpg: + result = fun(*args, **kwargs)(gpg) + + # TODO: cleanup a little bit the + # validation. maybe delegate to other + # auxiliary functions for clarity. + + ok = getattr(result, 'ok', None) + + stderr = getattr(result, 'stderr', None) + if stderr: + logger.debug("%s" % (stderr,)) + + if ok is False: + raise errors.EncryptionDecryptionFailed( + 'Failed to encrypt/decrypt in %s: %s' % ( + fun.__name__, + stderr)) + + if verify is not None: + # A verify key has been passed + if result.valid is False or \ + verify.fingerprint != result.pubkey_fingerprint: + raise errors.InvalidSignature( + 'Failed to verify signature with key %s: %s' % + (verify.key_id, stderr)) + + if is_sign_function: + # Specific validation for sign function + privkey = gpg.list_keys(secret=True).pop() + rfprint = result.fingerprint + kfprint = privkey['fingerprint'] + if result.fingerprint is None: + raise errors.SignFailed( + 'Failed to sign with key %s: %s' % + (privkey['keyid'], stderr)) + leap_assert( + result.fingerprint == kfprint, + 'Signature and private key fingerprints mismatch: ' + '%s != %s' % + (rfprint, kfprint)) + + if is_verify_function: + # Specific validation for verify function + pubkey = gpg.list_keys().pop() + valid = result.valid + rfprint = result.fingerprint + kfprint = pubkey['fingerprint'] + if valid is False or rfprint != kfprint: + raise errors.InvalidSignature( + 'Failed to verify signature ' + 'with key %s.' % pubkey['keyid']) + result = result.valid + + # ok, enough checks. let's return data if available + if hasattr(result, 'data'): + result = result.data + return result + return wrapped + + +class TempGPGWrapper(object): + """ + A context manager returning a temporary GPG wrapper keyring, which + contains exactly zero or one pubkeys, and zero or one privkeys. + + Temporary unitary keyrings allow the to use GPG's facilities for exactly + one key. This function creates an empty temporary keyring and imports + C{keys} if it is not None. + """ + def __init__(self, keys=None): + """ + :param keys: OpenPGP key, or list of. + :type keys: OpenPGPKey or list of OpenPGPKeys + """ + self._gpg = None + if not keys: + keys = list() + if not isinstance(keys, list): + keys = [keys] + self._keys = keys + for key in filter(None, keys): + leap_assert_type(key, OpenPGPKey) + + def __enter__(self): + """ + Calls the unitary gpgwrapper initializer + + :return: A GPG wrapper with a unitary keyring. + :rtype: gnupg.GPG + """ + self._build_keyring() + return self._gpg + + def __exit__(self, exc_type, exc_value, traceback): + """ + Ensures the gpgwrapper is properly destroyed. + """ + # TODO handle exceptions and log here + self._destroy_keyring() + + def _build_keyring(self): + """ + Create an empty GPG keyring and import C{keys} into it. + + :param keys: List of keys to add to the keyring. + :type keys: list of OpenPGPKey + + :return: A GPG wrapper with a unitary keyring. + :rtype: gnupg.GPG + """ + privkeys = [key for key in self._keys if key and key.private is True] + publkeys = [key for key in self._keys if key and key.private is False] + # here we filter out public keys that have a correspondent + # private key in the list because the private key_data by + # itself is enough to also have the public key in the keyring, + # and we want to count the keys afterwards. + + privaddrs = map(lambda privkey: privkey.address, privkeys) + publkeys = filter( + lambda pubkey: pubkey.address not in privaddrs, publkeys) + + listkeys = lambda: self._gpg.list_keys() + listsecretkeys = lambda: self._gpg.list_keys(secret=True) + + self._gpg = GPGWrapper(gnupghome=tempfile.mkdtemp()) + leap_assert(len(listkeys()) is 0, 'Keyring not empty.') + + # import keys into the keyring: + # concatenating ascii-armored keys, which is correctly + # understood by the GPGWrapper. + + self._gpg.import_keys("".join( + [x.key_data for x in publkeys + privkeys])) + + # assert the number of keys in the keyring + leap_assert( + len(listkeys()) == len(publkeys) + len(privkeys), + 'Wrong number of public keys in keyring: %d, should be %d)' % + (len(listkeys()), len(publkeys) + len(privkeys))) + leap_assert( + len(listsecretkeys()) == len(privkeys), + 'Wrong number of private keys in keyring: %d, should be %d)' % + (len(listsecretkeys()), len(privkeys))) + + def _destroy_keyring(self): + """ + Securely erase a unitary keyring. + """ + # TODO: implement some kind of wiping of data or a more + # secure way that + # does not write to disk. + + try: + for secret in [True, False]: + for key in self._gpg.list_keys(secret=secret): + self._gpg.delete_keys( + key['fingerprint'], + secret=secret) + leap_assert(len(self._gpg.list_keys()) is 0, 'Keyring not empty!') + + except: + raise + + finally: + leap_assert(self._gpg.gnupghome != os.path.expanduser('~/.gnupg'), + "watch out! Tried to remove default gnupg home!") + shutil.rmtree(self._gpg.gnupghome) + + +# +# API functions +# + +@with_temporary_gpg +def encrypt_asym(data, key, passphrase=None, sign=None): + """ + Encrypt C{data} using public @{key} and sign with C{sign} key. + + :param data: The data to be encrypted. + :type data: str + :param pubkey: The key used to encrypt. + :type pubkey: OpenPGPKey + :param sign: The key used for signing. + :type sign: OpenPGPKey + + :return: The encrypted data. + :rtype: str + """ + leap_assert_type(key, OpenPGPKey) + leap_assert(key.private is False, 'Key is not public.') + if sign is not None: + leap_assert_type(sign, OpenPGPKey) + leap_assert(sign.private is True) + + # Here we cannot assert for correctness of sig because the sig is in + # the ciphertext. + # result.ok - (bool) indicates if the operation succeeded + # result.data - (bool) contains the result of the operation + + return lambda gpg: gpg.encrypt( + data, key.fingerprint, + sign=sign.key_id if sign else None, + passphrase=passphrase, symmetric=False) + + +@with_temporary_gpg +def decrypt_asym(data, key, passphrase=None, verify=None): + """ + Decrypt C{data} using private @{key} and verify with C{verify} key. + + :param data: The data to be decrypted. + :type data: str + :param privkey: The key used to decrypt. + :type privkey: OpenPGPKey + :param verify: The key used to verify a signature. + :type verify: OpenPGPKey + + :return: The decrypted data. + :rtype: str + + @raise InvalidSignature: Raised if unable to verify the signature with + C{verify} key. + """ + leap_assert(key.private is True, 'Key is not private.') + if verify is not None: + leap_assert_type(verify, OpenPGPKey) + leap_assert(verify.private is False) + + return lambda gpg: gpg.decrypt( + data, passphrase=passphrase) + + +@with_temporary_gpg +def is_encrypted(data): + """ + Return whether C{data} was encrypted using OpenPGP. + + :param data: The data we want to know about. + :type data: str + + :return: Whether C{data} was encrypted using this wrapper. + :rtype: bool + """ + return lambda gpg: gpg.is_encrypted(data) + + +@with_temporary_gpg +def is_encrypted_asym(data): + """ + Return whether C{data} was asymmetrically encrypted using OpenPGP. + + :param data: The data we want to know about. + :type data: str + + :return: Whether C{data} was encrypted using this wrapper. + :rtype: bool + """ + return lambda gpg: gpg.is_encrypted_asym(data) + + +@with_temporary_gpg +def sign(data, privkey): + """ + Sign C{data} with C{privkey}. + + :param data: The data to be signed. + :type data: str + + :param privkey: The private key to be used to sign. + :type privkey: OpenPGPKey + + :return: The ascii-armored signed data. + :rtype: str + """ + leap_assert_type(privkey, OpenPGPKey) + leap_assert(privkey.private is True) + + # result.fingerprint - contains the fingerprint of the key used to + # sign. + return lambda gpg: gpg.sign(data, keyid=privkey.key_id) + + +@with_temporary_gpg +def verify(data, key): + """ + Verify signed C{data} with C{pubkey}. + + :param data: The data to be verified. + :type data: str + + :param pubkey: The public key to be used on verification. + :type pubkey: OpenPGPKey + + :return: The ascii-armored signed data. + :rtype: str + """ + leap_assert_type(key, OpenPGPKey) + leap_assert(key.private is False) + + return lambda gpg: gpg.verify(data) + + +# +# Helper functions +# + + +def _build_key_from_gpg(address, key, key_data): + """ + Build an OpenPGPKey for C{address} based on C{key} from + local gpg storage. + + ASCII armored GPG key data has to be queried independently in this + wrapper, so we receive it in C{key_data}. + + :param address: The address bound to the key. + :type address: str + :param key: Key obtained from GPG storage. + :type key: dict + :param key_data: Key data obtained from GPG storage. + :type key_data: str + :return: An instance of the key. + :rtype: OpenPGPKey + """ + return OpenPGPKey( + address, + key_id=key['keyid'], + fingerprint=key['fingerprint'], + key_data=key_data, + private=True if key['type'] == 'sec' else False, + length=key['length'], + expiry_date=key['expires'], + validation=None, # TODO: verify for validation. + ) + + +# +# The OpenPGP wrapper +# + +class OpenPGPKey(EncryptionKey): + """ + Base class for OpenPGP keys. + """ + + +class OpenPGPScheme(EncryptionScheme): + """ + A wrapper for OpenPGP keys. + """ + + def __init__(self, soledad): + """ + Initialize the OpenPGP wrapper. + + :param soledad: A Soledad instance for key storage. + :type soledad: leap.soledad.Soledad + """ + EncryptionScheme.__init__(self, soledad) + + def gen_key(self, address): + """ + Generate an OpenPGP keypair bound to C{address}. + + :param address: The address bound to the key. + :type address: str + :return: The key bound to C{address}. + :rtype: OpenPGPKey + @raise KeyAlreadyExists: If key already exists in local database. + """ + # make sure the key does not already exist + leap_assert(is_address(address), 'Not an user address: %s' % address) + try: + self.get_key(address) + raise errors.KeyAlreadyExists(address) + except errors.KeyNotFound: + pass + + def _gen_key(gpg): + params = gpg.gen_key_input( + key_type='RSA', + key_length=4096, + name_real=address, + name_email=address, + name_comment='Generated by LEAP Key Manager.') + gpg.gen_key(params) + pubkeys = gpg.list_keys() + # assert for new key characteristics + leap_assert( + len(pubkeys) is 1, # a unitary keyring! + 'Keyring has wrong number of keys: %d.' % len(pubkeys)) + key = gpg.list_keys(secret=True).pop() + leap_assert( + len(key['uids']) is 1, # with just one uid! + 'Wrong number of uids for key: %d.' % len(key['uids'])) + leap_assert( + re.match('.*<%s>$' % address, key['uids'][0]) is not None, + 'Key not correctly bound to address.') + # insert both public and private keys in storage + for secret in [True, False]: + key = gpg.list_keys(secret=secret).pop() + openpgp_key = _build_key_from_gpg( + address, key, + gpg.export_keys(key['fingerprint'], secret=secret)) + self.put_key(openpgp_key) + + with temporary_gpgwrapper() as gpg: + # TODO: inspect result, or use decorator + _gen_key(gpg) + + return self.get_key(address, private=True) + + def get_key(self, address, private=False): + """ + Get key bound to C{address} from local storage. + + :param address: The address bound to the key. + :type address: str + :param private: Look for a private key instead of a public one? + :type private: bool + + :return: The key bound to C{address}. + :rtype: OpenPGPKey + @raise KeyNotFound: If the key was not found on local storage. + """ + leap_assert(is_address(address), 'Not an user address: %s' % address) + doc = self._get_key_doc(address, private) + if doc is None: + raise errors.KeyNotFound(address) + return build_key_from_dict(OpenPGPKey, address, doc.content) + + 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 + """ + leap_assert_type(key_data, str) + # TODO: add more checks for correct key data. + leap_assert(key_data is not None, 'Data does not represent a key.') + + def _put_ascii_key(gpg): + gpg.import_keys(key_data) + privkey = None + pubkey = None + + try: + privkey = gpg.list_keys(secret=True).pop() + 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()) + 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()) + 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 + openpgp_privkey = _build_key_from_gpg( + address, privkey, + gpg.export_keys(privkey['fingerprint'], secret=True)) + self.put_key(openpgp_privkey) + # insert public key in storage + openpgp_pubkey = _build_key_from_gpg( + address, pubkey, + gpg.export_keys(pubkey['fingerprint'], secret=False)) + self.put_key(openpgp_pubkey) + + with temporary_gpgwrapper() as gpg: + # TODO: inspect result, or use decorator + _put_ascii_key(gpg) + + def put_key(self, key): + """ + Put C{key} in local storage. + + :param key: The key to be stored. + :type key: OpenPGPKey + """ + doc = self._get_key_doc(key.address, private=key.private) + if doc is None: + self._soledad.create_doc_from_json(key.get_json()) + else: + doc.set_json(key.get_json()) + self._soledad.put_doc(doc) + + def _get_key_doc(self, address, private=False): + """ + Get the document with a key (public, by default) bound to C{address}. + + If C{private} is True, looks for a private key instead of a public. + + :param address: The address bound to the key. + :type address: str + :param private: Whether to look for a private key. + :type private: bool + :return: The document with the key or None if it does not exist. + :rtype: leap.soledad.document.SoledadDocument + """ + doclist = self._soledad.get_from_index( + TAGS_ADDRESS_PRIVATE_INDEX, + KEYMANAGER_KEY_TAG, + address, + '1' if private else '0') + if len(doclist) is 0: + return None + leap_assert( + len(doclist) is 1, + 'Found more than one %s key for address!' % + 'private' if private else 'public') + return doclist.pop() + + def delete_key(self, key): + """ + Remove C{key} from storage. + + :param key: The key to be removed. + :type key: EncryptionKey + """ + leap_assert(key.__class__ is OpenPGPKey, 'Wrong key type.') + stored_key = self.get_key(key.address, private=key.private) + if stored_key is None: + raise errors.KeyNotFound(key) + if stored_key.__dict__ != key.__dict__: + raise errors.KeyAttributesDiffer(key) + doc = self._get_key_doc(key.address, key.private) + self._soledad.delete_doc(doc) diff --git a/keymanager/src/leap/keymanager/tests/__init__.py b/keymanager/src/leap/keymanager/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py new file mode 100644 index 00000000..09a61838 --- /dev/null +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -0,0 +1,676 @@ +## -*- coding: utf-8 -*- +# test_keymanager.py +# Copyright (C) 2013 LEAP +# +# 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 . + + +""" +Tests for the Key Manager. +""" + + +from mock import Mock +from leap.common.testing.basetest import BaseLeapTest +from leap.soledad import Soledad +from leap.keymanager import ( + KeyManager, + openpgp, + KeyNotFound, + NoPasswordGiven, + errors, +) +from leap.keymanager.openpgp import OpenPGPKey +from leap.keymanager.keys import ( + is_address, + build_key_from_dict, +) + + +ADDRESS = 'leap@leap.se' +ADDRESS_2 = 'anotheruser@leap.se' + + +class KeyManagerUtilTestCase(BaseLeapTest): + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_is_address(self): + self.assertTrue( + is_address('user@leap.se'), + 'Incorrect address detection.') + self.assertFalse( + is_address('userleap.se'), + 'Incorrect address detection.') + self.assertFalse( + is_address('user@'), + 'Incorrect address detection.') + self.assertFalse( + is_address('@leap.se'), + 'Incorrect address detection.') + + def test_build_key_from_dict(self): + kdict = { + 'address': ADDRESS, + 'key_id': 'key_id', + 'fingerprint': 'fingerprint', + 'key_data': 'key_data', + 'private': 'private', + 'length': 'length', + 'expiry_date': 'expiry_date', + 'first_seen_at': 'first_seen_at', + 'last_audited_at': 'last_audited_at', + 'validation': 'validation', + } + key = build_key_from_dict(OpenPGPKey, ADDRESS, kdict) + self.assertEqual( + kdict['address'], key.address, + 'Wrong data in key.') + self.assertEqual( + kdict['key_id'], key.key_id, + 'Wrong data in key.') + self.assertEqual( + kdict['fingerprint'], key.fingerprint, + 'Wrong data in key.') + self.assertEqual( + kdict['key_data'], key.key_data, + 'Wrong data in key.') + self.assertEqual( + kdict['private'], key.private, + 'Wrong data in key.') + self.assertEqual( + kdict['length'], key.length, + 'Wrong data in key.') + self.assertEqual( + kdict['expiry_date'], key.expiry_date, + 'Wrong data in key.') + self.assertEqual( + kdict['first_seen_at'], key.first_seen_at, + 'Wrong data in key.') + self.assertEqual( + kdict['last_audited_at'], key.last_audited_at, + 'Wrong data in key.') + self.assertEqual( + kdict['validation'], key.validation, + 'Wrong data in key.') + + +class KeyManagerWithSoledadTestCase(BaseLeapTest): + + def setUp(self): + # mock key fetching and storing so Soledad doesn't fail when trying to + # reach the server. + Soledad._get_secrets_from_shared_db = Mock(return_value=None) + Soledad._put_secrets_in_shared_db = Mock(return_value=None) + + self._soledad = Soledad( + "leap@leap.se", + "123456", + secrets_path=self.tempdir + "/secret.gpg", + local_db_path=self.tempdir + "/soledad.u1db", + server_url='', + cert_file=None, + auth_token=None, + ) + + def tearDown(self): + km = self._key_manager() + for key in km.get_all_keys_in_local_db(): + km._wrapper_map[key.__class__].delete_key(key) + for key in km.get_all_keys_in_local_db(private=True): + km._wrapper_map[key.__class__].delete_key(key) + + def _key_manager(self, user=ADDRESS, url=''): + return KeyManager(user, url, self._soledad) + + +class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): + + def _test_openpgp_gen_key(self): + pgp = openpgp.OpenPGPScheme(self._soledad) + self.assertRaises(KeyNotFound, pgp.get_key, 'user@leap.se') + key = pgp.gen_key('user@leap.se') + self.assertIsInstance(key, openpgp.OpenPGPKey) + self.assertEqual( + 'user@leap.se', key.address, 'Wrong address bound to key.') + self.assertEqual( + '4096', key.length, 'Wrong key length.') + + def test_openpgp_put_delete_key(self): + pgp = openpgp.OpenPGPScheme(self._soledad) + self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) + pgp.put_ascii_key(PUBLIC_KEY) + key = pgp.get_key(ADDRESS, private=False) + pgp.delete_key(key) + self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) + + def test_openpgp_put_ascii_key(self): + pgp = openpgp.OpenPGPScheme(self._soledad) + self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) + pgp.put_ascii_key(PUBLIC_KEY) + key = pgp.get_key(ADDRESS, private=False) + self.assertIsInstance(key, openpgp.OpenPGPKey) + self.assertEqual( + ADDRESS, key.address, 'Wrong address bound to key.') + self.assertEqual( + '4096', key.length, 'Wrong key length.') + pgp.delete_key(key) + self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) + + def test_get_public_key(self): + pgp = openpgp.OpenPGPScheme(self._soledad) + self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) + pgp.put_ascii_key(PUBLIC_KEY) + self.assertRaises( + KeyNotFound, pgp.get_key, ADDRESS, private=True) + key = pgp.get_key(ADDRESS, private=False) + self.assertEqual(ADDRESS, key.address) + self.assertFalse(key.private) + self.assertEqual(KEY_FINGERPRINT, key.fingerprint) + pgp.delete_key(key) + self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) + + def test_openpgp_encrypt_decrypt_asym(self): + # encrypt + pgp = openpgp.OpenPGPScheme(self._soledad) + pgp.put_ascii_key(PUBLIC_KEY) + pubkey = pgp.get_key(ADDRESS, private=False) + cyphertext = openpgp.encrypt_asym('data', pubkey) + # assert + self.assertTrue(cyphertext is not None) + self.assertTrue(cyphertext != '') + self.assertTrue(cyphertext != 'data') + self.assertTrue(openpgp.is_encrypted_asym(cyphertext)) + self.assertTrue(openpgp.is_encrypted(cyphertext)) + # decrypt + self.assertRaises( + KeyNotFound, pgp.get_key, ADDRESS, private=True) + pgp.put_ascii_key(PRIVATE_KEY) + privkey = pgp.get_key(ADDRESS, private=True) + plaintext = openpgp.decrypt_asym(cyphertext, privkey) + pgp.delete_key(pubkey) + pgp.delete_key(privkey) + self.assertRaises( + KeyNotFound, pgp.get_key, ADDRESS, private=False) + self.assertRaises( + KeyNotFound, pgp.get_key, ADDRESS, private=True) + + def test_verify_with_private_raises(self): + pgp = openpgp.OpenPGPScheme(self._soledad) + pgp.put_ascii_key(PRIVATE_KEY) + data = 'data' + privkey = pgp.get_key(ADDRESS, private=True) + signed = openpgp.sign(data, privkey) + self.assertRaises( + AssertionError, + openpgp.verify, signed, privkey) + + def test_sign_with_public_raises(self): + pgp = openpgp.OpenPGPScheme(self._soledad) + pgp.put_ascii_key(PUBLIC_KEY) + data = 'data' + pubkey = pgp.get_key(ADDRESS, private=False) + self.assertRaises( + AssertionError, + openpgp.sign, data, pubkey) + + def test_verify_with_wrong_key_raises(self): + pgp = openpgp.OpenPGPScheme(self._soledad) + pgp.put_ascii_key(PRIVATE_KEY) + data = 'data' + privkey = pgp.get_key(ADDRESS, private=True) + signed = openpgp.sign(data, privkey) + pgp.put_ascii_key(PUBLIC_KEY_2) + wrongkey = pgp.get_key(ADDRESS_2) + self.assertRaises( + errors.InvalidSignature, + openpgp.verify, signed, wrongkey) + + def test_encrypt_asym_sign_with_public_raises(self): + pgp = openpgp.OpenPGPScheme(self._soledad) + pgp.put_ascii_key(PRIVATE_KEY) + data = 'data' + privkey = pgp.get_key(ADDRESS, private=True) + pubkey = pgp.get_key(ADDRESS, private=False) + self.assertRaises( + AssertionError, + openpgp.encrypt_asym, data, privkey, sign=pubkey) + + def test_decrypt_asym_verify_with_private_raises(self): + pgp = openpgp.OpenPGPScheme(self._soledad) + pgp.put_ascii_key(PRIVATE_KEY) + data = 'data' + privkey = pgp.get_key(ADDRESS, private=True) + pubkey = pgp.get_key(ADDRESS, private=False) + encrypted_and_signed = openpgp.encrypt_asym( + data, pubkey, sign=privkey) + self.assertRaises( + AssertionError, + openpgp.decrypt_asym, + encrypted_and_signed, privkey, verify=privkey) + + def test_decrypt_asym_verify_with_wrong_key_raises(self): + pgp = openpgp.OpenPGPScheme(self._soledad) + pgp.put_ascii_key(PRIVATE_KEY) + data = 'data' + privkey = pgp.get_key(ADDRESS, private=True) + pubkey = pgp.get_key(ADDRESS, private=False) + encrypted_and_signed = openpgp.encrypt_asym(data, pubkey, sign=privkey) + pgp.put_ascii_key(PUBLIC_KEY_2) + wrongkey = pgp.get_key(ADDRESS_2) + self.assertRaises( + errors.InvalidSignature, + openpgp.verify, encrypted_and_signed, wrongkey) + + def test_sign_verify(self): + pgp = openpgp.OpenPGPScheme(self._soledad) + pgp.put_ascii_key(PRIVATE_KEY) + data = 'data' + privkey = pgp.get_key(ADDRESS, private=True) + signed = openpgp.sign(data, privkey) + pubkey = pgp.get_key(ADDRESS, private=False) + self.assertTrue(openpgp.verify(signed, pubkey)) + + def test_encrypt_asym_sign_decrypt_verify(self): + pgp = openpgp.OpenPGPScheme(self._soledad) + pgp.put_ascii_key(PRIVATE_KEY) + pubkey = pgp.get_key(ADDRESS, private=False) + privkey = pgp.get_key(ADDRESS, private=True) + pgp.put_ascii_key(PRIVATE_KEY_2) + pubkey2 = pgp.get_key(ADDRESS_2, private=False) + privkey2 = pgp.get_key(ADDRESS_2, private=True) + data = 'data' + encrypted_and_signed = openpgp.encrypt_asym( + data, pubkey2, sign=privkey) + res = openpgp.decrypt_asym( + encrypted_and_signed, privkey2, verify=pubkey) + self.assertTrue(data, res) + + +class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): + + def test_get_all_keys_in_db(self): + km = self._key_manager() + km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY) + # get public keys + keys = km.get_all_keys_in_local_db(False) + self.assertEqual(len(keys), 1, 'Wrong number of keys') + self.assertEqual(ADDRESS, keys[0].address) + self.assertFalse(keys[0].private) + # get private keys + keys = km.get_all_keys_in_local_db(True) + self.assertEqual(len(keys), 1, 'Wrong number of keys') + self.assertEqual(ADDRESS, keys[0].address) + self.assertTrue(keys[0].private) + + def test_get_public_key(self): + km = self._key_manager() + km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY) + # get the key + key = km.get_key(ADDRESS, OpenPGPKey, private=False, + fetch_remote=False) + self.assertTrue(key is not None) + self.assertEqual(key.address, ADDRESS) + self.assertEqual( + key.fingerprint.lower(), KEY_FINGERPRINT.lower()) + self.assertFalse(key.private) + + def test_get_private_key(self): + km = self._key_manager() + km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY) + # get the key + key = km.get_key(ADDRESS, OpenPGPKey, private=True, + fetch_remote=False) + self.assertTrue(key is not None) + self.assertEqual(key.address, ADDRESS) + self.assertEqual( + key.fingerprint.lower(), KEY_FINGERPRINT.lower()) + self.assertTrue(key.private) + + def test_send_key_raises_key_not_found(self): + km = self._key_manager() + self.assertRaises( + KeyNotFound, + km.send_key, OpenPGPKey) + + def test_send_key(self): + """ + Test that request is well formed when sending keys to server. + """ + km = self._key_manager() + km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY) + km._fetcher.put = Mock() + # the following data will be used on the send + km.ca_cert_path = 'capath' + km.session_id = 'sessionid' + km.uid = 'myuid' + km.api_uri = 'apiuri' + km.api_version = 'apiver' + km.send_key(OpenPGPKey) + # setup expected args + data = { + km.PUBKEY_KEY: km.get_key(km._address, OpenPGPKey).key_data, + } + url = '%s/%s/users/%s.json' % ('apiuri', 'apiver', 'myuid') + km._fetcher.put.assert_called_once_with( + url, data=data, verify='capath', + cookies={'_session_id': 'sessionid'}, + ) + + def test__fetch_keys_from_server(self): + """ + Test that the request is well formed when fetching keys from server. + """ + km = self._key_manager(url='http://nickserver.domain') + + class Response(object): + status_code = 200 + headers = {'content-type': 'application/json'} + + def json(self): + return {'address': ADDRESS_2, 'openpgp': PUBLIC_KEY_2} + + def raise_for_status(self): + pass + + # mock the fetcher so it returns the key for ADDRESS_2 + km._fetcher.get = Mock( + return_value=Response()) + km.ca_cert_path = 'cacertpath' + # do the fetch + km._fetch_keys_from_server(ADDRESS_2) + # and verify the call + km._fetcher.get.assert_called_once_with( + 'http://nickserver.domain', + data={'address': ADDRESS_2}, + verify='cacertpath', + ) + + def test_refresh_keys_does_not_refresh_own_key(self): + """ + Test that refreshing keys will not attempt to refresh our own key. + """ + km = self._key_manager() + # we add 2 keys but we expect it to only refresh the second one. + km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY) + km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY_2) + # mock the key fetching + km._fetch_keys_from_server = Mock(return_value=[]) + km.ca_cert_path = '' # some bogus path so the km does not complain. + # do the refreshing + km.refresh_keys() + km._fetch_keys_from_server.assert_called_once_with( + ADDRESS_2 + ) + + def test_get_key_fetches_from_server(self): + """ + Test that getting a key successfuly fetches from server. + """ + km = self._key_manager(url='http://nickserver.domain') + + class Response(object): + status_code = 200 + headers = {'content-type': 'application/json'} + + def json(self): + return {'address': ADDRESS_2, 'openpgp': PUBLIC_KEY_2} + + def raise_for_status(self): + pass + + # mock the fetcher so it returns the key for ADDRESS_2 + km._fetcher.get = Mock(return_value=Response()) + km.ca_cert_path = 'cacertpath' + # try to key get without fetching from server + self.assertRaises( + KeyNotFound, km.get_key, ADDRESS_2, OpenPGPKey, + fetch_remote=False + ) + # try to get key fetching from server. + key = km.get_key(ADDRESS_2, OpenPGPKey) + self.assertIsInstance(key, OpenPGPKey) + self.assertEqual(ADDRESS_2, key.address) + + +# Key material for testing + +# key 24D18DDF: public key "Leap Test Key " +KEY_FINGERPRINT = "E36E738D69173C13D709E44F2F455E2824D18DDF" +PUBLIC_KEY = """ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.10 (GNU/Linux) + +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 +=MuOY +-----END PGP PUBLIC KEY BLOCK----- +""" +PRIVATE_KEY = """ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.10 (GNU/Linux) + +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 +=JTFu +-----END PGP PRIVATE KEY BLOCK----- +""" + +# key 7FEE575A: public key "anotheruser " +PUBLIC_KEY_2 = """ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.10 (GNU/Linux) + +mI0EUYwJXgEEAMbTKHuPJ5/Gk34l9Z06f+0WCXTDXdte1UBoDtZ1erAbudgC4MOR +gquKqoj3Hhw0/ILqJ88GcOJmKK/bEoIAuKaqlzDF7UAYpOsPZZYmtRfPC2pTCnXq +Z1vdeqLwTbUspqXflkCkFtfhGKMq5rH8GV5a3tXZkRWZhdNwhVXZagC3ABEBAAG0 +IWFub3RoZXJ1c2VyIDxhbm90aGVydXNlckBsZWFwLnNlPoi4BBMBAgAiBQJRjAle +AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRB/nfpof+5XWotuA/4tLN4E +gUr7IfLy2HkHAxzw7A4rqfMN92DIM9mZrDGaWRrOn3aVF7VU1UG7MDkHfPvp/cFw +ezoCw4s4IoHVc/pVlOkcHSyt4/Rfh248tYEJmFCJXGHpkK83VIKYJAithNccJ6Q4 +JE/o06Mtf4uh/cA1HUL4a4ceqUhtpLJULLeKo7iNBFGMCV4BBADsyQI7GR0wSAxz +VayLjuPzgT+bjbFeymIhjuxKIEwnIKwYkovztW+4bbOcQs785k3Lp6RzvigTpQQt +Z/hwcLOqZbZw8t/24+D+Pq9mMP2uUvCFFqLlVvA6D3vKSQ/XNN+YB919WQ04jh63 +yuRe94WenT1RJd6xU1aaUff4rKizuQARAQABiJ8EGAECAAkFAlGMCV4CGwwACgkQ +f536aH/uV1rPZQQAqCzRysOlu8ez7PuiBD4SebgRqWlxa1TF1ujzfLmuPivROZ2X +Kw5aQstxgGSjoB7tac49s0huh4X8XK+BtJBfU84JS8Jc2satlfwoyZ35LH6sDZck +I+RS/3we6zpMfHs3vvp9xgca6ZupQxivGtxlJs294TpJorx+mFFqbV17AzQ= +=Thdu +-----END PGP PUBLIC KEY BLOCK----- +""" + +PRIVATE_KEY_2 = """ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.10 (GNU/Linux) + +lQHYBFGMCV4BBADG0yh7jyefxpN+JfWdOn/tFgl0w13bXtVAaA7WdXqwG7nYAuDD +kYKriqqI9x4cNPyC6ifPBnDiZiiv2xKCALimqpcwxe1AGKTrD2WWJrUXzwtqUwp1 +6mdb3Xqi8E21LKal35ZApBbX4RijKuax/BleWt7V2ZEVmYXTcIVV2WoAtwARAQAB +AAP7BLuSAx7tOohnimEs74ks8l/L6dOcsFQZj2bqs4AoY3jFe7bV0tHr4llypb/8 +H3/DYvpf6DWnCjyUS1tTnXSW8JXtx01BUKaAufSmMNg9blKV6GGHlT/Whe9uVyks +7XHk/+9mebVMNJ/kNlqq2k+uWqJohzC8WWLRK+d1tBeqDsECANZmzltPaqUsGV5X +C3zszE3tUBgptV/mKnBtopKi+VH+t7K6fudGcG+bAcZDUoH/QVde52mIIjjIdLje +uajJuHUCAO1mqh+vPoGv4eBLV7iBo3XrunyGXiys4a39eomhxTy3YktQanjjx+ty +GltAGCs5PbWGO6/IRjjvd46wh53kzvsCAO0J97gsWhzLuFnkxFAJSPk7RRlyl7lI +1XS/x0Og6j9XHCyY1OYkfBm0to3UlCfkgirzCYlTYObCofzdKFIPDmSqHbQhYW5v +dGhlcnVzZXIgPGFub3RoZXJ1c2VyQGxlYXAuc2U+iLgEEwECACIFAlGMCV4CGwMG +CwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEH+d+mh/7ldai24D/i0s3gSBSvsh +8vLYeQcDHPDsDiup8w33YMgz2ZmsMZpZGs6fdpUXtVTVQbswOQd8++n9wXB7OgLD +izgigdVz+lWU6RwdLK3j9F+Hbjy1gQmYUIlcYemQrzdUgpgkCK2E1xwnpDgkT+jT +oy1/i6H9wDUdQvhrhx6pSG2kslQst4qjnQHYBFGMCV4BBADsyQI7GR0wSAxzVayL +juPzgT+bjbFeymIhjuxKIEwnIKwYkovztW+4bbOcQs785k3Lp6RzvigTpQQtZ/hw +cLOqZbZw8t/24+D+Pq9mMP2uUvCFFqLlVvA6D3vKSQ/XNN+YB919WQ04jh63yuRe +94WenT1RJd6xU1aaUff4rKizuQARAQABAAP9EyElqJ3dq3EErXwwT4mMnbd1SrVC +rUJrNWQZL59mm5oigS00uIyR0SvusOr+UzTtd8ysRuwHy5d/LAZsbjQStaOMBILx +77TJveOel0a1QK0YSMF2ywZMCKvquvjli4hAtWYz/EwfuzQN3t23jc5ny+GqmqD2 +3FUxLJosFUfLNmECAO9KhVmJi+L9dswIs+2Dkjd1eiRQzNOEVffvYkGYZyKxNiXF +UA5kvyZcB4iAN9sWCybE4WHZ9jd4myGB0MPDGxkCAP1RsXJbbuD6zS7BXe5gwunO +2q4q7ptdSl/sJYQuTe1KNP5d/uGsvlcFfsYjpsopasPjFBIncc/2QThMKlhoEaEB +/0mVAxpT6SrEvUbJ18z7kna24SgMPr3OnPMxPGfvNLJY/Xv/A17YfoqjmByCvsKE +JCDjopXtmbcrZyoEZbEht9mko4ifBBgBAgAJBQJRjAleAhsMAAoJEH+d+mh/7lda +z2UEAKgs0crDpbvHs+z7ogQ+Enm4EalpcWtUxdbo83y5rj4r0TmdlysOWkLLcYBk +o6Ae7WnOPbNIboeF/FyvgbSQX1POCUvCXNrGrZX8KMmd+Sx+rA2XJCPkUv98Hus6 +THx7N776fcYHGumbqUMYrxrcZSbNveE6SaK8fphRam1dewM0 +=a5gs +-----END PGP PRIVATE KEY BLOCK----- +""" +import unittest +if __name__ == "__main__": + unittest.main() -- cgit v1.2.3 From bd817552f6410f2ecd54dbbc7a1116ff5dc1659e Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 2 Aug 2013 11:16:04 +0200 Subject: Add some logs to be able to follow the key generation. --- keymanager/src/leap/keymanager/openpgp.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index d19bb2b5..9d404cb9 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -473,7 +473,7 @@ class OpenPGPScheme(EncryptionScheme): self.get_key(address) raise errors.KeyAlreadyExists(address) except errors.KeyNotFound: - pass + logger.debug('Key for %s not found' % (address,)) def _gen_key(gpg): params = gpg.gen_key_input( @@ -482,9 +482,18 @@ class OpenPGPScheme(EncryptionScheme): name_real=address, name_email=address, name_comment='Generated by LEAP Key Manager.') + logger.info("About to generate keys... This might take SOME time.") gpg.gen_key(params) + logger.info("Keys for %s have been successfully " + "generated." % (address,)) pubkeys = gpg.list_keys() + # assert for new key characteristics + + # XXX This exception is not properly catched by the soledad + # bootstrapping, so if we do not finish generating the keys + # we end with a blocked thread -- kali + leap_assert( len(pubkeys) is 1, # a unitary keyring! 'Keyring has wrong number of keys: %d.' % len(pubkeys)) -- cgit v1.2.3 From 2623cf55c275bda37ad7249e4cf33d448ea7f246 Mon Sep 17 00:00:00 2001 From: drebs Date: Wed, 7 Aug 2013 16:29:54 +0200 Subject: Support bundled GPG and change API. - Move openpgp encrypt/decrypt/sign/verify API to inside OpenPGP class. - Add encrypt/decrypt/sign/verify API to KeyManager. - Add possibility of passing custom gpg binary to KeyManager and OpenPGPScheme. - Remove "_asym" suffix from method names. - Bump version to 0.2.1. New API is *not* backwards compatible. --- keymanager/src/leap/keymanager/__init__.py | 97 ++++- keymanager/src/leap/keymanager/keys.py | 69 +++ keymanager/src/leap/keymanager/openpgp.py | 475 +++++++++------------ .../src/leap/keymanager/tests/test_keymanager.py | 124 ++++-- 4 files changed, 456 insertions(+), 309 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index e1f318ca..d24e08e4 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -22,12 +22,13 @@ Key Manager is a Nicknym agent for LEAP client. import requests -from leap.common.check import leap_assert +from leap.common.check import leap_assert, leap_assert_type from leap.keymanager.errors import ( KeyNotFound, NoPasswordGiven, ) from leap.keymanager.keys import ( + EncryptionKey, build_key_from_dict, KEYMANAGER_KEY_TAG, TAGS_PRIVATE_INDEX, @@ -52,7 +53,8 @@ class KeyManager(object): PUBKEY_KEY = "user[public_key]" def __init__(self, address, nickserver_uri, soledad, session_id=None, - ca_cert_path=None, api_uri=None, api_version=None, uid=None): + ca_cert_path=None, api_uri=None, api_version=None, uid=None, + gpgbinary=None): """ Initialize a Key Manager for user's C{address} with provider's nickserver reachable in C{url}. @@ -73,6 +75,8 @@ class KeyManager(object): :type api_version: str :param uid: The users' UID. :type uid: str + :param gpgbinary: Name for GnuPG binary executable. + :type gpgbinary: C{str} """ self._address = address self._nickserver_uri = nickserver_uri @@ -84,7 +88,7 @@ class KeyManager(object): self.uid = uid # a dict to map key types to their handlers self._wrapper_map = { - OpenPGPKey: OpenPGPScheme(soledad), + OpenPGPKey: OpenPGPScheme(soledad, gpgbinary=gpgbinary), # other types of key will be added to this mapper. } # the following are used to perform https requests @@ -334,3 +338,90 @@ class KeyManager(object): uid = property( _get_uid, _set_uid, doc='The uid of the user.') + + # + # encrypt/decrypt and sign/verify API + # + + def encrypt(self, data, pubkey, passphrase=None, sign=None): + """ + Encrypt C{data} using public @{key} and sign with C{sign} key. + + :param data: The data to be encrypted. + :type data: str + :param pubkey: The key used to encrypt. + :type pubkey: EncryptionKey + :param sign: The key used for signing. + :type sign: EncryptionKey + + :return: The encrypted data. + :rtype: str + """ + 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__].encrypt( + data, pubkey, passphrase, sign) + + def decrypt(self, data, privkey, passphrase=None, verify=None): + """ + Decrypt C{data} using private @{privkey} and verify with C{verify} key. + + :param data: The data to be decrypted. + :type data: str + :param privkey: The key used to decrypt. + :type privkey: OpenPGPKey + :param verify: The key used to verify a signature. + :type verify: OpenPGPKey + + :return: The decrypted data. + :rtype: str + + @raise InvalidSignature: Raised if unable to verify the signature with + C{verify} key. + """ + leap_assert_type(privkey, EncryptionKey) + leap_assert( + privkey.__class__ in self._wrapper_map, + 'Unknown key type.') + leap_assert(privkey.private is True, 'Key is not private.') + return self._wrapper_map[privkey.__class__].decrypt( + data, privkey, passphrase, verify) + + def sign(self, data, privkey): + """ + Sign C{data} with C{privkey}. + + :param data: The data to be signed. + :type data: str + + :param privkey: The private key to be used to sign. + :type privkey: EncryptionKey + + :return: The signed data. + :rtype: str + """ + leap_assert_type(privkey, EncryptionKey) + leap_assert( + privkey.__class__ in self._wrapper_map, + 'Unknown key type.') + leap_assert(privkey.private is True, 'Key is not private.') + return self._wrapper_map[privkey.__class__].sign(data, privkey) + + def verify(self, data, pubkey): + """ + Verify signed C{data} with C{pubkey}. + + :param data: The data to be verified. + :type data: str + + :param pubkey: The public key to be used on verification. + :type pubkey: EncryptionKey + + :return: The signed data. + :rtype: str + """ + 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) diff --git a/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py index 44bd587b..1c337451 100644 --- a/keymanager/src/leap/keymanager/keys.py +++ b/keymanager/src/leap/keymanager/keys.py @@ -283,3 +283,72 @@ class EncryptionScheme(object): :type key: EncryptionKey """ pass + + @abstractmethod + def encrypt(self, data, pubkey, passphrase=None, sign=None): + """ + Encrypt C{data} using public @{pubkey} and sign with C{sign} key. + + :param data: The data to be encrypted. + :type data: str + :param pubkey: The key used to encrypt. + :type pubkey: EncryptionKey + :param sign: The key used for signing. + :type sign: EncryptionKey + + :return: The encrypted data. + :rtype: str + """ + pass + + @abstractmethod + def decrypt(self, data, privkey, passphrase=None, verify=None): + """ + Decrypt C{data} using private @{privkey} and verify with C{verify} key. + + :param data: The data to be decrypted. + :type data: str + :param privkey: The key used to decrypt. + :type privkey: OpenPGPKey + :param verify: The key used to verify a signature. + :type verify: OpenPGPKey + + :return: The decrypted data. + :rtype: str + + @raise InvalidSignature: Raised if unable to verify the signature with + C{verify} key. + """ + pass + + @abstractmethod + def sign(self, data, privkey): + """ + Sign C{data} with C{privkey}. + + :param data: The data to be signed. + :type data: str + + :param privkey: The private key to be used to sign. + :type privkey: EncryptionKey + + :return: The signed data. + :rtype: str + """ + pass + + @abstractmethod + def verify(self, data, pubkey): + """ + Verify signed C{data} with C{pubkey}. + + :param data: The data to be verified. + :type data: str + + :param pubkey: The public key to be used on verification. + :type pubkey: EncryptionKey + + :return: The signed data. + :rtype: str + """ + pass diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 9d404cb9..1e78c703 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -15,15 +15,19 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . + """ Infrastructure for using OpenPGP keys in Key Manager. """ + + import logging import os import re import shutil import tempfile + from leap.common.check import leap_assert, leap_assert_type from leap.keymanager import errors from leap.keymanager.keys import ( @@ -36,125 +40,8 @@ from leap.keymanager.keys import ( ) from leap.keymanager.gpg import GPGWrapper -logger = logging.getLogger(__name__) - - -# -# gpg wrapper and decorator -# - -def temporary_gpgwrapper(keys=None): - """ - Returns a unitary gpg wrapper that implements context manager - protocol. - - :param key_data: ASCII armored key data. - :type key_data: str - - :return: a GPGWrapper instance - :rtype: GPGWrapper - """ - # TODO do here checks on key_data - return TempGPGWrapper(keys=keys) - - -def with_temporary_gpg(fun): - """ - Decorator to add a temporary gpg wrapper as context - to gpg related functions. - - Decorated functions are expected to return a function whose only - argument is a gpgwrapper instance. - """ - def wrapped(*args, **kwargs): - """ - We extract the arguments passed to the wrapped function, - run the function and do validations. - We expect that the positional arguments are `data`, - and an optional `key`. - All the rest of arguments should be passed as named arguments - to allow for a correct unpacking. - """ - if len(args) == 2: - keys = args[1] if isinstance(args[1], OpenPGPKey) else None - else: - keys = None - - # sign/verify keys passed as arguments - sign = kwargs.get('sign', None) - if sign: - keys = [keys, sign] - - verify = kwargs.get('verify', None) - if verify: - keys = [keys, verify] - - # is the wrapped function sign or verify? - fun_name = fun.__name__ - is_sign_function = True if fun_name == "sign" else False - is_verify_function = True if fun_name == "verify" else False - - result = None - - with temporary_gpgwrapper(keys) as gpg: - result = fun(*args, **kwargs)(gpg) - - # TODO: cleanup a little bit the - # validation. maybe delegate to other - # auxiliary functions for clarity. - - ok = getattr(result, 'ok', None) - - stderr = getattr(result, 'stderr', None) - if stderr: - logger.debug("%s" % (stderr,)) - - if ok is False: - raise errors.EncryptionDecryptionFailed( - 'Failed to encrypt/decrypt in %s: %s' % ( - fun.__name__, - stderr)) - - if verify is not None: - # A verify key has been passed - if result.valid is False or \ - verify.fingerprint != result.pubkey_fingerprint: - raise errors.InvalidSignature( - 'Failed to verify signature with key %s: %s' % - (verify.key_id, stderr)) - - if is_sign_function: - # Specific validation for sign function - privkey = gpg.list_keys(secret=True).pop() - rfprint = result.fingerprint - kfprint = privkey['fingerprint'] - if result.fingerprint is None: - raise errors.SignFailed( - 'Failed to sign with key %s: %s' % - (privkey['keyid'], stderr)) - leap_assert( - result.fingerprint == kfprint, - 'Signature and private key fingerprints mismatch: ' - '%s != %s' % - (rfprint, kfprint)) - - if is_verify_function: - # Specific validation for verify function - pubkey = gpg.list_keys().pop() - valid = result.valid - rfprint = result.fingerprint - kfprint = pubkey['fingerprint'] - if valid is False or rfprint != kfprint: - raise errors.InvalidSignature( - 'Failed to verify signature ' - 'with key %s.' % pubkey['keyid']) - result = result.valid - # ok, enough checks. let's return data if available - if hasattr(result, 'data'): - result = result.data - return result - return wrapped +logger = logging.getLogger(__name__) class TempGPGWrapper(object): @@ -166,12 +53,15 @@ class TempGPGWrapper(object): one key. This function creates an empty temporary keyring and imports C{keys} if it is not None. """ - def __init__(self, keys=None): + def __init__(self, keys=None, gpgbinary=None): """ :param keys: OpenPGP key, or list of. :type keys: OpenPGPKey or list of OpenPGPKeys + :param gpgbinary: Name for GnuPG binary executable. + :type gpgbinary: C{str} """ self._gpg = None + self._gpgbinary = gpgbinary if not keys: keys = list() if not isinstance(keys, list): @@ -221,7 +111,9 @@ class TempGPGWrapper(object): listkeys = lambda: self._gpg.list_keys() listsecretkeys = lambda: self._gpg.list_keys(secret=True) - self._gpg = GPGWrapper(gnupghome=tempfile.mkdtemp()) + self._gpg = GPGWrapper( + gnupghome=tempfile.mkdtemp(), + gpgbinary=self._gpgbinary) leap_assert(len(listkeys()) is 0, 'Keyring not empty.') # import keys into the keyring: @@ -266,144 +158,6 @@ class TempGPGWrapper(object): shutil.rmtree(self._gpg.gnupghome) -# -# API functions -# - -@with_temporary_gpg -def encrypt_asym(data, key, passphrase=None, sign=None): - """ - Encrypt C{data} using public @{key} and sign with C{sign} key. - - :param data: The data to be encrypted. - :type data: str - :param pubkey: The key used to encrypt. - :type pubkey: OpenPGPKey - :param sign: The key used for signing. - :type sign: OpenPGPKey - - :return: The encrypted data. - :rtype: str - """ - leap_assert_type(key, OpenPGPKey) - leap_assert(key.private is False, 'Key is not public.') - if sign is not None: - leap_assert_type(sign, OpenPGPKey) - leap_assert(sign.private is True) - - # Here we cannot assert for correctness of sig because the sig is in - # the ciphertext. - # result.ok - (bool) indicates if the operation succeeded - # result.data - (bool) contains the result of the operation - - return lambda gpg: gpg.encrypt( - data, key.fingerprint, - sign=sign.key_id if sign else None, - passphrase=passphrase, symmetric=False) - - -@with_temporary_gpg -def decrypt_asym(data, key, passphrase=None, verify=None): - """ - Decrypt C{data} using private @{key} and verify with C{verify} key. - - :param data: The data to be decrypted. - :type data: str - :param privkey: The key used to decrypt. - :type privkey: OpenPGPKey - :param verify: The key used to verify a signature. - :type verify: OpenPGPKey - - :return: The decrypted data. - :rtype: str - - @raise InvalidSignature: Raised if unable to verify the signature with - C{verify} key. - """ - leap_assert(key.private is True, 'Key is not private.') - if verify is not None: - leap_assert_type(verify, OpenPGPKey) - leap_assert(verify.private is False) - - return lambda gpg: gpg.decrypt( - data, passphrase=passphrase) - - -@with_temporary_gpg -def is_encrypted(data): - """ - Return whether C{data} was encrypted using OpenPGP. - - :param data: The data we want to know about. - :type data: str - - :return: Whether C{data} was encrypted using this wrapper. - :rtype: bool - """ - return lambda gpg: gpg.is_encrypted(data) - - -@with_temporary_gpg -def is_encrypted_asym(data): - """ - Return whether C{data} was asymmetrically encrypted using OpenPGP. - - :param data: The data we want to know about. - :type data: str - - :return: Whether C{data} was encrypted using this wrapper. - :rtype: bool - """ - return lambda gpg: gpg.is_encrypted_asym(data) - - -@with_temporary_gpg -def sign(data, privkey): - """ - Sign C{data} with C{privkey}. - - :param data: The data to be signed. - :type data: str - - :param privkey: The private key to be used to sign. - :type privkey: OpenPGPKey - - :return: The ascii-armored signed data. - :rtype: str - """ - leap_assert_type(privkey, OpenPGPKey) - leap_assert(privkey.private is True) - - # result.fingerprint - contains the fingerprint of the key used to - # sign. - return lambda gpg: gpg.sign(data, keyid=privkey.key_id) - - -@with_temporary_gpg -def verify(data, key): - """ - Verify signed C{data} with C{pubkey}. - - :param data: The data to be verified. - :type data: str - - :param pubkey: The public key to be used on verification. - :type pubkey: OpenPGPKey - - :return: The ascii-armored signed data. - :rtype: str - """ - leap_assert_type(key, OpenPGPKey) - leap_assert(key.private is False) - - return lambda gpg: gpg.verify(data) - - -# -# Helper functions -# - - def _build_key_from_gpg(address, key, key_data): """ Build an OpenPGPKey for C{address} based on C{key} from @@ -445,17 +199,25 @@ class OpenPGPKey(EncryptionKey): class OpenPGPScheme(EncryptionScheme): """ - A wrapper for OpenPGP keys. + A wrapper for OpenPGP keys management and use (encryption, decyption, + signing and verification). """ - def __init__(self, soledad): + def __init__(self, soledad, gpgbinary=None): """ Initialize the OpenPGP wrapper. :param soledad: A Soledad instance for key storage. :type soledad: leap.soledad.Soledad + :param gpgbinary: Name for GnuPG binary executable. + :type gpgbinary: C{str} """ EncryptionScheme.__init__(self, soledad) + self._gpgbinary = gpgbinary + + # + # Keys management + # def gen_key(self, address): """ @@ -475,7 +237,8 @@ class OpenPGPScheme(EncryptionScheme): except errors.KeyNotFound: logger.debug('Key for %s not found' % (address,)) - def _gen_key(gpg): + with self._temporary_gpgwrapper() as gpg: + # TODO: inspect result, or use decorator params = gpg.gen_key_input( key_type='RSA', key_length=4096, @@ -512,10 +275,6 @@ class OpenPGPScheme(EncryptionScheme): gpg.export_keys(key['fingerprint'], secret=secret)) self.put_key(openpgp_key) - with temporary_gpgwrapper() as gpg: - # TODO: inspect result, or use decorator - _gen_key(gpg) - return self.get_key(address, private=True) def get_key(self, address, private=False): @@ -548,7 +307,8 @@ class OpenPGPScheme(EncryptionScheme): # TODO: add more checks for correct key data. leap_assert(key_data is not None, 'Data does not represent a key.') - def _put_ascii_key(gpg): + with self._temporary_gpgwrapper() as gpg: + # TODO: inspect result, or use decorator gpg.import_keys(key_data) privkey = None pubkey = None @@ -584,10 +344,6 @@ class OpenPGPScheme(EncryptionScheme): gpg.export_keys(pubkey['fingerprint'], secret=False)) self.put_key(openpgp_pubkey) - with temporary_gpgwrapper() as gpg: - # TODO: inspect result, or use decorator - _put_ascii_key(gpg) - def put_key(self, key): """ Put C{key} in local storage. @@ -643,3 +399,182 @@ class OpenPGPScheme(EncryptionScheme): raise errors.KeyAttributesDiffer(key) doc = self._get_key_doc(key.address, key.private) self._soledad.delete_doc(doc) + + # + # Data encryption, decryption, signing and verifying + # + + def _temporary_gpgwrapper(self, keys=None): + """ + Returns a unitary gpg wrapper that implements context manager + protocol. + + :param key_data: ASCII armored key data. + :type key_data: str + :param gpgbinary: Name for GnuPG binary executable. + :type gpgbinary: C{str} + + :return: a GPGWrapper instance + :rtype: GPGWrapper + """ + # TODO do here checks on key_data + return TempGPGWrapper( + keys=keys, gpgbinary=self._gpgbinary) + + @staticmethod + def _assert_gpg_result_ok(result): + """ + Check if GPG result is 'ok' and log stderr outputs. + :param result: The GPG results + :type result: + """ + stderr = getattr(result, 'stderr', None) + if stderr: + logger.debug("%s" % (stderr,)) + if getattr(result, 'ok', None) is not True: + raise errors.EncryptionDecryptionFailed( + 'Failed to encrypt/decrypt in %s: %s' % ( + this.encrypt, + stderr)) + + def encrypt(self, data, pubkey, passphrase=None, sign=None): + """ + Encrypt C{data} using public @{pubkey} and sign with C{sign} key. + + :param data: The data to be encrypted. + :type data: str + :param pubkey: The key used to encrypt. + :type pubkey: OpenPGPKey + :param sign: The key used for signing. + :type sign: OpenPGPKey + + :return: The encrypted data. + :rtype: str + """ + leap_assert_type(pubkey, OpenPGPKey) + leap_assert(pubkey.private is False, 'Key is not public.') + keys = [pubkey] + if sign is not None: + leap_assert_type(sign, OpenPGPKey) + leap_assert(sign.private is True) + keys.append(sign) + with self._temporary_gpgwrapper(keys) as gpg: + result = gpg.encrypt( + data, pubkey.fingerprint, + sign=sign.key_id if sign else None, + passphrase=passphrase, symmetric=False) + # Here we cannot assert for correctness of sig because the sig is + # in the ciphertext. + # result.ok - (bool) indicates if the operation succeeded + # result.data - (bool) contains the result of the operation + self._assert_gpg_result_ok(result) + return result.data + + def decrypt(self, data, privkey, passphrase=None, verify=None): + """ + Decrypt C{data} using private @{privkey} and verify with C{verify} key. + + :param data: The data to be decrypted. + :type data: str + :param privkey: The key used to decrypt. + :type privkey: OpenPGPKey + :param verify: The key used to verify a signature. + :type verify: OpenPGPKey + + :return: The decrypted data. + :rtype: str + + @raise InvalidSignature: Raised if unable to verify the signature with + C{verify} key. + """ + leap_assert(privkey.private is True, 'Key is not private.') + keys = [privkey] + if verify is not None: + leap_assert_type(verify, OpenPGPKey) + leap_assert(verify.private is False) + keys.append(verify) + with self._temporary_gpgwrapper(keys) as gpg: + result = gpg.decrypt(data, passphrase=passphrase) + self._assert_gpg_result_ok(result) + # verify signature + if (verify is not None): + if result.valid is False or \ + verify.fingerprint != result.pubkey_fingerprint: + raise errors.InvalidSignature( + 'Failed to verify signature with key %s: %s' % + (verify.key_id, stderr)) + return result.data + + def is_encrypted(self, data): + """ + Return whether C{data} was asymmetrically encrypted using OpenPGP. + + :param data: The data we want to know about. + :type data: str + + :return: Whether C{data} was encrypted using this wrapper. + :rtype: bool + """ + with self._temporary_gpgwrapper() as gpg: + return gpg.is_encrypted_asym(data) + + def sign(self, data, privkey): + """ + Sign C{data} with C{privkey}. + + :param data: The data to be signed. + :type data: str + + :param privkey: The private key to be used to sign. + :type privkey: OpenPGPKey + + :return: The ascii-armored signed data. + :rtype: str + """ + leap_assert_type(privkey, OpenPGPKey) + leap_assert(privkey.private is True) + + # result.fingerprint - contains the fingerprint of the key used to + # sign. + with self._temporary_gpgwrapper(privkey) as gpg: + result = gpg.sign(data, keyid=privkey.key_id) + rfprint = privkey.fingerprint + privkey = gpg.list_keys(secret=True).pop() + kfprint = privkey['fingerprint'] + if result.fingerprint is None: + raise errors.SignFailed( + 'Failed to sign with key %s: %s' % + (privkey['keyid'], stderr)) + leap_assert( + result.fingerprint == kfprint, + 'Signature and private key fingerprints mismatch: ' + '%s != %s' % (rfprint, kfprint)) + return result.data + + def verify(self, data, pubkey): + """ + Verify signed C{data} with C{pubkey}. + + :param data: The data to be verified. + :type data: str + + :param pubkey: The public key to be used on verification. + :type pubkey: OpenPGPKey + + :return: The ascii-armored signed data. + :rtype: str + """ + leap_assert_type(pubkey, OpenPGPKey) + leap_assert(pubkey.private is False) + with self._temporary_gpgwrapper(pubkey) as gpg: + result = gpg.verify(data) + gpgpubkey = gpg.list_keys().pop() + valid = result.valid + rfprint = result.fingerprint + kfprint = gpgpubkey['fingerprint'] + # raise in case sig is invalid + if valid is False or rfprint != kfprint: + raise errors.InvalidSignature( + 'Failed to verify signature ' + 'with key %s.' % gpgpubkey['keyid']) + return True diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 09a61838..a474e32a 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -40,6 +40,7 @@ from leap.keymanager.keys import ( ADDRESS = 'leap@leap.se' ADDRESS_2 = 'anotheruser@leap.se' +GPG_BINARY_PATH = '/usr/bin/gpg' class KeyManagerUtilTestCase(BaseLeapTest): @@ -136,7 +137,8 @@ class KeyManagerWithSoledadTestCase(BaseLeapTest): km._wrapper_map[key.__class__].delete_key(key) def _key_manager(self, user=ADDRESS, url=''): - return KeyManager(user, url, self._soledad) + return KeyManager(user, url, self._soledad, + gpgbinary=GPG_BINARY_PATH) class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): @@ -152,7 +154,8 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): '4096', key.length, 'Wrong key length.') def test_openpgp_put_delete_key(self): - pgp = openpgp.OpenPGPScheme(self._soledad) + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) pgp.put_ascii_key(PUBLIC_KEY) key = pgp.get_key(ADDRESS, private=False) @@ -160,7 +163,8 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) def test_openpgp_put_ascii_key(self): - pgp = openpgp.OpenPGPScheme(self._soledad) + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) pgp.put_ascii_key(PUBLIC_KEY) key = pgp.get_key(ADDRESS, private=False) @@ -173,7 +177,8 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) def test_get_public_key(self): - pgp = openpgp.OpenPGPScheme(self._soledad) + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) pgp.put_ascii_key(PUBLIC_KEY) self.assertRaises( @@ -185,24 +190,25 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): pgp.delete_key(key) self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - def test_openpgp_encrypt_decrypt_asym(self): + def test_openpgp_encrypt_decrypt(self): # encrypt - pgp = openpgp.OpenPGPScheme(self._soledad) + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) pgp.put_ascii_key(PUBLIC_KEY) pubkey = pgp.get_key(ADDRESS, private=False) - cyphertext = openpgp.encrypt_asym('data', pubkey) + cyphertext = pgp.encrypt('data', pubkey) # assert self.assertTrue(cyphertext is not None) self.assertTrue(cyphertext != '') self.assertTrue(cyphertext != 'data') - self.assertTrue(openpgp.is_encrypted_asym(cyphertext)) - self.assertTrue(openpgp.is_encrypted(cyphertext)) + self.assertTrue(pgp.is_encrypted(cyphertext)) + self.assertTrue(pgp.is_encrypted(cyphertext)) # decrypt self.assertRaises( KeyNotFound, pgp.get_key, ADDRESS, private=True) pgp.put_ascii_key(PRIVATE_KEY) privkey = pgp.get_key(ADDRESS, private=True) - plaintext = openpgp.decrypt_asym(cyphertext, privkey) + plaintext = pgp.decrypt(cyphertext, privkey) pgp.delete_key(pubkey) pgp.delete_key(privkey) self.assertRaises( @@ -211,83 +217,91 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): KeyNotFound, pgp.get_key, ADDRESS, private=True) def test_verify_with_private_raises(self): - pgp = openpgp.OpenPGPScheme(self._soledad) + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) pgp.put_ascii_key(PRIVATE_KEY) data = 'data' privkey = pgp.get_key(ADDRESS, private=True) - signed = openpgp.sign(data, privkey) + signed = pgp.sign(data, privkey) self.assertRaises( AssertionError, - openpgp.verify, signed, privkey) + pgp.verify, signed, privkey) def test_sign_with_public_raises(self): - pgp = openpgp.OpenPGPScheme(self._soledad) + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) pgp.put_ascii_key(PUBLIC_KEY) data = 'data' pubkey = pgp.get_key(ADDRESS, private=False) self.assertRaises( AssertionError, - openpgp.sign, data, pubkey) + pgp.sign, data, pubkey) def test_verify_with_wrong_key_raises(self): - pgp = openpgp.OpenPGPScheme(self._soledad) + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) pgp.put_ascii_key(PRIVATE_KEY) data = 'data' privkey = pgp.get_key(ADDRESS, private=True) - signed = openpgp.sign(data, privkey) + signed = pgp.sign(data, privkey) pgp.put_ascii_key(PUBLIC_KEY_2) wrongkey = pgp.get_key(ADDRESS_2) self.assertRaises( errors.InvalidSignature, - openpgp.verify, signed, wrongkey) + pgp.verify, signed, wrongkey) - def test_encrypt_asym_sign_with_public_raises(self): - pgp = openpgp.OpenPGPScheme(self._soledad) + def test_encrypt_sign_with_public_raises(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) pubkey = pgp.get_key(ADDRESS, private=False) self.assertRaises( AssertionError, - openpgp.encrypt_asym, data, privkey, sign=pubkey) + pgp.encrypt, data, privkey, sign=pubkey) - def test_decrypt_asym_verify_with_private_raises(self): - pgp = openpgp.OpenPGPScheme(self._soledad) + def test_decrypt_verify_with_private_raises(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) pubkey = pgp.get_key(ADDRESS, private=False) - encrypted_and_signed = openpgp.encrypt_asym( + encrypted_and_signed = pgp.encrypt( data, pubkey, sign=privkey) self.assertRaises( AssertionError, - openpgp.decrypt_asym, + pgp.decrypt, encrypted_and_signed, privkey, verify=privkey) - def test_decrypt_asym_verify_with_wrong_key_raises(self): - pgp = openpgp.OpenPGPScheme(self._soledad) + def test_decrypt_verify_with_wrong_key_raises(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) pubkey = pgp.get_key(ADDRESS, private=False) - encrypted_and_signed = openpgp.encrypt_asym(data, pubkey, sign=privkey) + encrypted_and_signed = pgp.encrypt(data, pubkey, sign=privkey) pgp.put_ascii_key(PUBLIC_KEY_2) wrongkey = pgp.get_key(ADDRESS_2) self.assertRaises( errors.InvalidSignature, - openpgp.verify, encrypted_and_signed, wrongkey) + pgp.verify, encrypted_and_signed, wrongkey) def test_sign_verify(self): - pgp = openpgp.OpenPGPScheme(self._soledad) + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) pgp.put_ascii_key(PRIVATE_KEY) data = 'data' privkey = pgp.get_key(ADDRESS, private=True) - signed = openpgp.sign(data, privkey) + signed = pgp.sign(data, privkey) pubkey = pgp.get_key(ADDRESS, private=False) - self.assertTrue(openpgp.verify(signed, pubkey)) + self.assertTrue(pgp.verify(signed, pubkey)) - def test_encrypt_asym_sign_decrypt_verify(self): - pgp = openpgp.OpenPGPScheme(self._soledad) + def test_encrypt_sign_decrypt_verify(self): + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) pgp.put_ascii_key(PRIVATE_KEY) pubkey = pgp.get_key(ADDRESS, private=False) privkey = pgp.get_key(ADDRESS, private=True) @@ -295,9 +309,9 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): pubkey2 = pgp.get_key(ADDRESS_2, private=False) privkey2 = pgp.get_key(ADDRESS_2, private=True) data = 'data' - encrypted_and_signed = openpgp.encrypt_asym( + encrypted_and_signed = pgp.encrypt( data, pubkey2, sign=privkey) - res = openpgp.decrypt_asym( + res = pgp.decrypt( encrypted_and_signed, privkey2, verify=pubkey) self.assertTrue(data, res) @@ -448,6 +462,44 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): self.assertEqual(ADDRESS_2, key.address) +class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): + + RAW_DATA = 'data' + + def test_keymanager_openpgp_encrypt_decrypt(self): + km = self._key_manager() + # put raw private key + km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY) + # get public key + pubkey = km.get_key( + ADDRESS, OpenPGPKey, private=False, fetch_remote=False) + # encrypt + encdata = km.encrypt(self.RAW_DATA, pubkey) + self.assertNotEqual(self.RAW_DATA, encdata) + # get private key + privkey = km.get_key( + ADDRESS, OpenPGPKey, private=True, fetch_remote=False) + # decrypt + rawdata = km.decrypt(encdata, privkey) + self.assertEqual(self.RAW_DATA, rawdata) + + def test_keymanager_openpgp_sign_verify(self): + km = self._key_manager() + # put raw private keys + km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY) + # get private key for signing + privkey = km.get_key( + ADDRESS, OpenPGPKey, private=True, fetch_remote=False) + # encrypt + signdata = km.sign(self.RAW_DATA, privkey) + self.assertNotEqual(self.RAW_DATA, signdata) + # get public key for verifying + pubkey = km.get_key( + ADDRESS, OpenPGPKey, private=False, fetch_remote=False) + # decrypt + self.assertTrue(km.verify(signdata, pubkey)) + + # Key material for testing # key 24D18DDF: public key "Leap Test Key " -- cgit v1.2.3 From 66e7044bef038b5bf07fa4d6bddd50edc80e6c4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 8 Aug 2013 11:36:00 -0300 Subject: Remove options from the constructor call for gnupg.GPG --- keymanager/src/leap/keymanager/gpg.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/gpg.py b/keymanager/src/leap/keymanager/gpg.py index 15c1d9f6..b81b218f 100644 --- a/keymanager/src/leap/keymanager/gpg.py +++ b/keymanager/src/leap/keymanager/gpg.py @@ -110,9 +110,10 @@ class GPGWrapper(gnupg.GPG): @raise: RuntimeError with explanation message if there is a problem invoking gpg. """ + # XXX: options isn't always supported, so removing for the time being gnupg.GPG.__init__(self, gnupghome=gnupghome, gpgbinary=gpgbinary, verbose=verbose, use_agent=use_agent, - keyring=keyring, options=options) + keyring=keyring)#, options=options) self.result_map['list-packets'] = ListPackets def find_key_by_email(self, email, secret=False): -- cgit v1.2.3 From 4bdec73025a8d923782ee99f1096bbd1bbd93c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 8 Aug 2013 13:44:50 -0300 Subject: Do not fail if nickserver request fails --- keymanager/src/leap/keymanager/__init__.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index d24e08e4..e6122ffb 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -20,6 +20,7 @@ Key Manager is a Nicknym agent for LEAP client. """ +import logging import requests from leap.common.check import leap_assert, leap_assert_type @@ -38,6 +39,8 @@ from leap.keymanager.openpgp import ( OpenPGPScheme, ) +logger = logging.getLogger(__name__) + # # The Key Manager @@ -170,12 +173,18 @@ class KeyManager(object): @raise KeyNotFound: If the key was not found on nickserver. """ # request keys from the nickserver - server_keys = self._get( - self._nickserver_uri, {'address': address}).json() - # insert keys in local database - if self.OPENPGP_KEY in server_keys: - self._wrapper_map[OpenPGPKey].put_ascii_key( - server_keys['openpgp']) + res = None + try: + res = self._get(self._nickserver_uri, {'address': address}) + server_keys = res.json() + # insert keys in local database + if self.OPENPGP_KEY in server_keys: + self._wrapper_map[OpenPGPKey].put_ascii_key( + server_keys['openpgp']) + except Exception as e: + logger.warning("Error retrieving the keys: %r" % (e,)) + if res: + logger.warning("%s" % (res.content,)) # # key management -- cgit v1.2.3 From 3e9aa2257a292bd7ce26896dd824c57d640058e4 Mon Sep 17 00:00:00 2001 From: drebs Date: Fri, 9 Aug 2013 00:34:00 +0200 Subject: Fix error in encryption failure logging method. --- keymanager/src/leap/keymanager/openpgp.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 1e78c703..aa04ed09 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -433,9 +433,7 @@ class OpenPGPScheme(EncryptionScheme): logger.debug("%s" % (stderr,)) if getattr(result, 'ok', None) is not True: raise errors.EncryptionDecryptionFailed( - 'Failed to encrypt/decrypt in %s: %s' % ( - this.encrypt, - stderr)) + 'Failed to encrypt/decrypt: %s' % stderr) def encrypt(self, data, pubkey, passphrase=None, sign=None): """ -- cgit v1.2.3 From 79dae47bf729a04c7b633234ca634949a6172720 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 16 Aug 2013 15:29:43 -0300 Subject: Signal events from keymanager --- keymanager/src/leap/keymanager/__init__.py | 31 +++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index e6122ffb..20e44fd5 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -24,10 +24,11 @@ import logging import requests from leap.common.check import leap_assert, leap_assert_type -from leap.keymanager.errors import ( - KeyNotFound, - NoPasswordGiven, -) +from leap.common.events import signal +from leap.common.events import events_pb2 as proto + +from leap.keymanager.errors import KeyNotFound + from leap.keymanager.keys import ( EncryptionKey, build_key_from_dict, @@ -222,6 +223,7 @@ class KeyManager(object): self._api_version, self._uid) self._put(uri, data) + signal(proto.KEYMANAGER_DONE_UPLOADING_KEYS, self._address) def get_key(self, address, ktype, private=False, fetch_remote=True): """ @@ -246,15 +248,26 @@ class KeyManager(object): ktype in self._wrapper_map, 'Unkown key type: %s.' % str(ktype)) try: + signal(proto.KEYMANAGER_LOOKING_FOR_KEY, address) # return key if it exists in local database - return self._wrapper_map[ktype].get_key(address, private=private) + key = self._wrapper_map[ktype].get_key(address, private=private) + signal(proto.KEYMANAGER_KEY_FOUND, address) + + return key except KeyNotFound: + signal(proto.KEYMANAGER_KEY_NOT_FOUND, address) + # we will only try to fetch a key from nickserver if fetch_remote # is True and the key is not private. if fetch_remote is False or private is True: raise + + signal(proto.KEYMANAGER_LOOKING_FOR_KEY, address) self._fetch_keys_from_server(address) - return self._wrapper_map[ktype].get_key(address, private=False) + key = self._wrapper_map[ktype].get_key(address, private=False) + signal(proto.KEYMANAGER_KEY_FOUND, address) + + return key def get_all_keys_in_local_db(self, private=False): """ @@ -296,7 +309,11 @@ class KeyManager(object): :return: The generated key. :rtype: EncryptionKey """ - return self._wrapper_map[ktype].gen_key(self._address) + signal(proto.KEYMANAGER_STARTED_KEY_GENERATION, self._address) + key = self._wrapper_map[ktype].gen_key(self._address) + signal(proto.KEYMANAGER_FINISHED_KEY_GENERATION, self._address) + + return key # # Setters/getters -- cgit v1.2.3 From 82191866aa668e7ad294087c0365decf54588698 Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 19 Aug 2013 08:57:42 -0300 Subject: Update to new soledad package scheme. --- keymanager/src/leap/keymanager/tests/test_keymanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index a474e32a..5f232fed 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -23,7 +23,7 @@ Tests for the Key Manager. from mock import Mock from leap.common.testing.basetest import BaseLeapTest -from leap.soledad import Soledad +from leap.soledad.client import Soledad from leap.keymanager import ( KeyManager, openpgp, -- cgit v1.2.3 From 8783a07eea46e7d35c11ab1f6805fc5c715464c9 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 23 Aug 2013 12:17:20 +0200 Subject: Some packaging improvements * versioneer all the things * add parse_requirements --- keymanager/src/leap/keymanager/__init__.py | 4 + keymanager/src/leap/keymanager/_version.py | 203 +++++++++++++++++++++++++++++ 2 files changed, 207 insertions(+) create mode 100644 keymanager/src/leap/keymanager/_version.py (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 20e44fd5..05cbccaa 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -451,3 +451,7 @@ class KeyManager(object): 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) + +from ._version import get_versions +__version__ = get_versions()['version'] +del get_versions diff --git a/keymanager/src/leap/keymanager/_version.py b/keymanager/src/leap/keymanager/_version.py new file mode 100644 index 00000000..3a514e14 --- /dev/null +++ b/keymanager/src/leap/keymanager/_version.py @@ -0,0 +1,203 @@ + +IN_LONG_VERSION_PY = True +# This file helps to compute a version number in source trees obtained from +# git-archive tarball (such as those provided by githubs download-from-tag +# feature). Distribution tarballs (build by setup.py sdist) and build +# directories (produced by setup.py build) will contain a much shorter file +# that just contains the computed version number. + +# This file is released into the public domain. Generated by +# versioneer-0.7+ (https://github.com/warner/python-versioneer) + +# these strings will be replaced by git during git-archive +git_refnames = "$Format:%d$" +git_full = "$Format:%H$" + + +import subprocess +import sys + +def run_command(args, cwd=None, verbose=False): + try: + # remember shell=False, so use git.cmd on windows, not just git + p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd) + except EnvironmentError: + e = sys.exc_info()[1] + if verbose: + print("unable to run %s" % args[0]) + print(e) + return None + stdout = p.communicate()[0].strip() + if sys.version >= '3': + stdout = stdout.decode() + if p.returncode != 0: + if verbose: + print("unable to run %s (error)" % args[0]) + return None + return stdout + + +import sys +import re +import os.path + +def get_expanded_variables(versionfile_source): + # the code embedded in _version.py can just fetch the value of these + # variables. When used from setup.py, we don't want to import + # _version.py, so we do it with a regexp instead. This function is not + # used from _version.py. + variables = {} + try: + f = open(versionfile_source,"r") + for line in f.readlines(): + if line.strip().startswith("git_refnames ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + variables["refnames"] = mo.group(1) + if line.strip().startswith("git_full ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + variables["full"] = mo.group(1) + f.close() + except EnvironmentError: + pass + return variables + +def versions_from_expanded_variables(variables, tag_prefix, verbose=False): + refnames = variables["refnames"].strip() + if refnames.startswith("$Format"): + if verbose: + print("variables are unexpanded, not using") + return {} # unexpanded, so not in an unpacked git-archive tarball + refs = set([r.strip() for r in refnames.strip("()").split(",")]) + # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of + # just "foo-1.0". If we see a "tag: " prefix, prefer those. + TAG = "tag: " + tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) + if not tags: + # Either we're using git < 1.8.3, or there really are no tags. We use + # a heuristic: assume all version tags have a digit. The old git %d + # expansion behaves like git log --decorate=short and strips out the + # refs/heads/ and refs/tags/ prefixes that would let us distinguish + # between branches and tags. By ignoring refnames without digits, we + # filter out many common branch names like "release" and + # "stabilization", as well as "HEAD" and "master". + tags = set([r for r in refs if re.search(r'\d', r)]) + if verbose: + print("discarding '%s', no digits" % ",".join(refs-tags)) + if verbose: + print("likely tags: %s" % ",".join(sorted(tags))) + for ref in sorted(tags): + # sorting will prefer e.g. "2.0" over "2.0rc1" + if ref.startswith(tag_prefix): + r = ref[len(tag_prefix):] + if verbose: + print("picking %s" % r) + return { "version": r, + "full": variables["full"].strip() } + # no suitable tags, so we use the full revision id + if verbose: + print("no suitable tags, using full revision id") + return { "version": variables["full"].strip(), + "full": variables["full"].strip() } + +def versions_from_vcs(tag_prefix, versionfile_source, verbose=False): + # this runs 'git' from the root of the source tree. That either means + # someone ran a setup.py command (and this code is in versioneer.py, so + # IN_LONG_VERSION_PY=False, thus the containing directory is the root of + # the source tree), or someone ran a project-specific entry point (and + # this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the + # containing directory is somewhere deeper in the source tree). This only + # gets called if the git-archive 'subst' variables were *not* expanded, + # and _version.py hasn't already been rewritten with a short version + # string, meaning we're inside a checked out source tree. + + try: + here = os.path.abspath(__file__) + except NameError: + # some py2exe/bbfreeze/non-CPython implementations don't do __file__ + return {} # not always correct + + # versionfile_source is the relative path from the top of the source tree + # (where the .git directory might live) to this file. Invert this to find + # the root from __file__. + root = here + if IN_LONG_VERSION_PY: + for i in range(len(versionfile_source.split("/"))): + root = os.path.dirname(root) + else: + root = os.path.dirname(here) + if not os.path.exists(os.path.join(root, ".git")): + if verbose: + print("no .git in %s" % root) + return {} + + GIT = "git" + if sys.platform == "win32": + GIT = "git.cmd" + stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"], + cwd=root) + if stdout is None: + return {} + if not stdout.startswith(tag_prefix): + if verbose: + print("tag '%s' doesn't start with prefix '%s'" % (stdout, tag_prefix)) + return {} + tag = stdout[len(tag_prefix):] + stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root) + if stdout is None: + return {} + full = stdout.strip() + if tag.endswith("-dirty"): + full += "-dirty" + return {"version": tag, "full": full} + + +def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False): + if IN_LONG_VERSION_PY: + # We're running from _version.py. If it's from a source tree + # (execute-in-place), we can work upwards to find the root of the + # tree, and then check the parent directory for a version string. If + # it's in an installed application, there's no hope. + try: + here = os.path.abspath(__file__) + except NameError: + # py2exe/bbfreeze/non-CPython don't have __file__ + return {} # without __file__, we have no hope + # versionfile_source is the relative path from the top of the source + # tree to _version.py. Invert this to find the root from __file__. + root = here + for i in range(len(versionfile_source.split("/"))): + root = os.path.dirname(root) + else: + # we're running from versioneer.py, which means we're running from + # the setup.py in a source tree. sys.argv[0] is setup.py in the root. + here = os.path.abspath(sys.argv[0]) + root = os.path.dirname(here) + + # Source tarballs conventionally unpack into a directory that includes + # both the project name and a version string. + dirname = os.path.basename(root) + if not dirname.startswith(parentdir_prefix): + if verbose: + print("guessing rootdir is '%s', but '%s' doesn't start with prefix '%s'" % + (root, dirname, parentdir_prefix)) + return None + return {"version": dirname[len(parentdir_prefix):], "full": ""} + +tag_prefix = "" +parentdir_prefix = "leap.keymanager-" +versionfile_source = "src/leap/keymanager/_version.py" + +def get_versions(default={"version": "unknown", "full": ""}, verbose=False): + variables = { "refnames": git_refnames, "full": git_full } + ver = versions_from_expanded_variables(variables, tag_prefix, verbose) + if not ver: + ver = versions_from_vcs(tag_prefix, versionfile_source, verbose) + if not ver: + ver = versions_from_parentdir(parentdir_prefix, versionfile_source, + verbose) + if not ver: + ver = default + return ver + -- cgit v1.2.3 From 03ecafdc5834e64cc9f1f1493ddfaa82210ace47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 29 Aug 2013 17:41:43 -0300 Subject: Do not raise on not 2XX error codes Also accept unicode ascii keys --- keymanager/src/leap/keymanager/__init__.py | 15 ++++++++++----- keymanager/src/leap/keymanager/openpgp.py | 4 ++-- 2 files changed, 12 insertions(+), 7 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 05cbccaa..2f39ad9e 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -127,11 +127,16 @@ class KeyManager(object): self._ca_cert_path is not None, 'We need the CA certificate path!') res = self._fetcher.get(uri, data=data, verify=self._ca_cert_path) - # assert that the response is valid - res.raise_for_status() - leap_assert( - res.headers['content-type'].startswith('application/json'), - 'Content-type is not JSON.') + # Nickserver now returns 404 for key not found and 500 for + # other cases (like key too small), so we are skipping this + # check for the time being + # res.raise_for_status() + + # Responses are now text/plain, although it's json anyway, but + # this will fail when it shouldn't + # leap_assert( + # res.headers['content-type'].startswith('application/json'), + # 'Content-type is not JSON.') return res def _put(self, uri, data=None): diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index aa04ed09..7946db85 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -301,9 +301,9 @@ class OpenPGPScheme(EncryptionScheme): 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 + :type key_data: str or unicode """ - leap_assert_type(key_data, str) + 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.') -- cgit v1.2.3 From c32a1efec962b309dbe9e2904a991016ad6e575e Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 2 Sep 2013 01:04:51 +0200 Subject: assert nonnull soledad --- keymanager/src/leap/keymanager/keys.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py index 1c337451..b8e88d46 100644 --- a/keymanager/src/leap/keymanager/keys.py +++ b/keymanager/src/leap/keymanager/keys.py @@ -219,6 +219,8 @@ class EncryptionScheme(object): """ Initialize the database indexes. """ + leap_assert(self._soledad is not None, + "Cannot init indexes with null soledad") # Ask the database for currently existing indexes. db_indexes = dict(self._soledad.list_indexes()) # Loop through the indexes we expect to find. -- cgit v1.2.3 From fea2f9b9c3f2427184e957d860aca7d96e116bbf Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 30 Sep 2013 15:55:01 -0300 Subject: Update to use gnupg 1.2.2 module. --- keymanager/src/leap/keymanager/gpg.py | 398 --------------------- keymanager/src/leap/keymanager/openpgp.py | 65 ++-- .../src/leap/keymanager/tests/test_keymanager.py | 1 - 3 files changed, 33 insertions(+), 431 deletions(-) delete mode 100644 keymanager/src/leap/keymanager/gpg.py (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/gpg.py b/keymanager/src/leap/keymanager/gpg.py deleted file mode 100644 index b81b218f..00000000 --- a/keymanager/src/leap/keymanager/gpg.py +++ /dev/null @@ -1,398 +0,0 @@ -# -*- coding: utf-8 -*- -# gpgwrapper.py -# Copyright (C) 2013 LEAP -# -# 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 . - - -""" -A GPG wrapper used to handle OpenPGP keys. - -This is a temporary class that will be superseded by the a revised version of -python-gnupg. -""" - - -import os -import gnupg -import re -from gnupg import ( - logger, - _is_sequence, - _make_binary_stream, -) - - -class ListPackets(): - """ - Handle status messages for --list-packets. - """ - - def __init__(self, gpg): - """ - Initialize the packet listing handling class. - - :param gpg: GPG object instance. - :type gpg: gnupg.GPG - """ - self.gpg = gpg - self.nodata = None - self.key = None - self.need_passphrase = None - self.need_passphrase_sym = None - self.userid_hint = None - - def handle_status(self, key, value): - """ - Handle one line of the --list-packets status message. - - :param key: The status message key. - :type key: str - :param value: The status message value. - :type value: str - """ - # TODO: write tests for handle_status - if key == 'NODATA': - self.nodata = True - if key == 'ENC_TO': - # This will only capture keys in our keyring. In the future we - # may want to include multiple unknown keys in this list. - self.key, _, _ = value.split() - if key == 'NEED_PASSPHRASE': - self.need_passphrase = True - if key == 'NEED_PASSPHRASE_SYM': - self.need_passphrase_sym = True - if key == 'USERID_HINT': - self.userid_hint = value.strip().split() - - -class GPGWrapper(gnupg.GPG): - """ - This is a temporary class for handling GPG requests, and should be - replaced by a more general class used throughout the project. - """ - - GNUPG_HOME = os.environ['HOME'] + "/.config/leap/gnupg" - GNUPG_BINARY = "/usr/bin/gpg" # this has to be changed based on OS - - def __init__(self, gpgbinary=GNUPG_BINARY, gnupghome=GNUPG_HOME, - verbose=False, use_agent=False, keyring=None, options=None): - """ - Initialize a GnuPG process wrapper. - - :param gpgbinary: Name for GnuPG binary executable. - :type gpgbinary: C{str} - :param gpghome: Full pathname to directory containing the public and - private keyrings. - :type gpghome: C{str} - :param keyring: Name of alternative keyring file to use. If specified, - the default keyring is not used. - :param verbose: Should some verbose info be output? - :type verbose: bool - :param use_agent: Should pass `--use-agent` to GPG binary? - :type use_agent: bool - :param keyring: Path for the keyring to use. - :type keyring: str - @options: A list of additional options to pass to the GPG binary. - :type options: list - - @raise: RuntimeError with explanation message if there is a problem - invoking gpg. - """ - # XXX: options isn't always supported, so removing for the time being - gnupg.GPG.__init__(self, gnupghome=gnupghome, gpgbinary=gpgbinary, - verbose=verbose, use_agent=use_agent, - keyring=keyring)#, options=options) - self.result_map['list-packets'] = ListPackets - - def find_key_by_email(self, email, secret=False): - """ - Find user's key based on their email. - - :param email: Email address of key being searched for. - :type email: str - :param secret: Should we search for a secret key? - :type secret: bool - - :return: The fingerprint of the found key. - :rtype: str - """ - for key in self.list_keys(secret=secret): - for uid in key['uids']: - if re.search(email, uid): - return key - raise LookupError("GnuPG public key for email %s not found!" % email) - - def find_key_by_subkey(self, subkey, secret=False): - """ - Find user's key based on a subkey fingerprint. - - :param email: Subkey fingerprint of the key being searched for. - :type email: str - :param secret: Should we search for a secret key? - :type secret: bool - - :return: The fingerprint of the found key. - :rtype: str - """ - for key in self.list_keys(secret=secret): - for sub in key['subkeys']: - if sub[0] == subkey: - return key - raise LookupError( - "GnuPG public key for subkey %s not found!" % subkey) - - def find_key_by_keyid(self, keyid, secret=False): - """ - Find user's key based on the key ID. - - :param email: The key ID of the key being searched for. - :type email: str - :param secret: Should we search for a secret key? - :type secret: bool - - :return: The fingerprint of the found key. - :rtype: str - """ - for key in self.list_keys(secret=secret): - if keyid == key['keyid']: - return key - raise LookupError( - "GnuPG public key for keyid %s not found!" % keyid) - - def find_key_by_fingerprint(self, fingerprint, secret=False): - """ - Find user's key based on the key fingerprint. - - :param email: The fingerprint of the key being searched for. - :type email: str - :param secret: Should we search for a secret key? - :type secret: bool - - :return: The fingerprint of the found key. - :rtype: str - """ - for key in self.list_keys(secret=secret): - if fingerprint == key['fingerprint']: - return key - raise LookupError( - "GnuPG public key for fingerprint %s not found!" % fingerprint) - - def encrypt(self, data, recipient, sign=None, always_trust=True, - passphrase=None, symmetric=False): - """ - Encrypt data using GPG. - - :param data: The data to be encrypted. - :type data: str - :param recipient: The address of the public key to be used. - :type recipient: str - :param sign: Should the encrypted content be signed? - :type sign: bool - :param always_trust: Skip key validation and assume that used keys - are always fully trusted? - :type always_trust: bool - :param passphrase: The passphrase to be used if symmetric encryption - is desired. - :type passphrase: str - :param symmetric: Should we encrypt to a password? - :type symmetric: bool - - :return: An object with encrypted result in the `data` field. - :rtype: gnupg.Crypt - """ - # TODO: devise a way so we don't need to "always trust". - return gnupg.GPG.encrypt(self, data, recipient, sign=sign, - always_trust=always_trust, - passphrase=passphrase, - symmetric=symmetric, - cipher_algo='AES256') - - def decrypt(self, data, always_trust=True, passphrase=None): - """ - Decrypt data using GPG. - - :param data: The data to be decrypted. - :type data: str - :param always_trust: Skip key validation and assume that used keys - are always fully trusted? - :type always_trust: bool - :param passphrase: The passphrase to be used if symmetric encryption - is desired. - :type passphrase: str - - :return: An object with decrypted result in the `data` field. - :rtype: gnupg.Crypt - """ - # TODO: devise a way so we don't need to "always trust". - return gnupg.GPG.decrypt(self, data, always_trust=always_trust, - passphrase=passphrase) - - def send_keys(self, keyserver, *keyids): - """ - Send keys to a keyserver - - :param keyserver: The keyserver to send the keys to. - :type keyserver: str - :param keyids: The key ids to send. - :type keyids: list - - :return: A list of keys sent to server. - :rtype: gnupg.ListKeys - """ - # TODO: write tests for this. - # TODO: write a SendKeys class to handle status for this. - result = self.result_map['list'](self) - gnupg.logger.debug('send_keys: %r', keyids) - data = gnupg._make_binary_stream("", self.encoding) - args = ['--keyserver', keyserver, '--send-keys'] - args.extend(keyids) - self._handle_io(args, data, result, binary=True) - gnupg.logger.debug('send_keys result: %r', result.__dict__) - data.close() - return result - - def encrypt_file(self, file, recipients, sign=None, - always_trust=False, passphrase=None, - armor=True, output=None, symmetric=False, - cipher_algo=None): - """ - Encrypt the message read from the file-like object 'file'. - - :param file: The file to be encrypted. - :type data: file - :param recipient: The address of the public key to be used. - :type recipient: str - :param sign: Should the encrypted content be signed? - :type sign: bool - :param always_trust: Skip key validation and assume that used keys - are always fully trusted? - :type always_trust: bool - :param passphrase: The passphrase to be used if symmetric encryption - is desired. - :type passphrase: str - :param armor: Create ASCII armored output? - :type armor: bool - :param output: Path of file to write results in. - :type output: str - :param symmetric: Should we encrypt to a password? - :type symmetric: bool - :param cipher_algo: Algorithm to use. - :type cipher_algo: str - - :return: An object with encrypted result in the `data` field. - :rtype: gnupg.Crypt - """ - args = ['--encrypt'] - if symmetric: - args = ['--symmetric'] - if cipher_algo: - args.append('--cipher-algo %s' % cipher_algo) - else: - args = ['--encrypt'] - if not _is_sequence(recipients): - recipients = (recipients,) - for recipient in recipients: - args.append('--recipient "%s"' % recipient) - if armor: # create ascii-armored output - set to False for binary - args.append('--armor') - if output: # write the output to a file with the specified name - if os.path.exists(output): - os.remove(output) # to avoid overwrite confirmation message - args.append('--output "%s"' % output) - if sign: - args.append('--sign --default-key "%s"' % sign) - if always_trust: - args.append("--always-trust") - result = self.result_map['crypt'](self) - self._handle_io(args, file, result, passphrase=passphrase, binary=True) - logger.debug('encrypt result: %r', result.data) - return result - - def list_packets(self, data): - """ - List the sequence of packets. - - :param data: The data to extract packets from. - :type data: str - - :return: An object with packet info. - :rtype ListPackets - """ - args = ["--list-packets"] - result = self.result_map['list-packets'](self) - self._handle_io( - args, - _make_binary_stream(data, self.encoding), - result, - ) - return result - - def encrypted_to(self, data): - """ - Return the key to which data is encrypted to. - - :param data: The data to be examined. - :type data: str - - :return: The fingerprint of the key to which data is encrypted to. - :rtype: str - """ - # TODO: make this support multiple keys. - result = self.list_packets(data) - if not result.key: - raise LookupError( - "Content is not encrypted to a GnuPG key!") - try: - return self.find_key_by_keyid(result.key) - except: - return self.find_key_by_subkey(result.key) - - def is_encrypted_sym(self, data): - """ - Say whether some chunk of data is encrypted to a symmetric key. - - :param data: The data to be examined. - :type data: str - - :return: Whether data is encrypted to a symmetric key. - :rtype: bool - """ - result = self.list_packets(data) - return bool(result.need_passphrase_sym) - - def is_encrypted_asym(self, data): - """ - Say whether some chunk of data is encrypted to a private key. - - :param data: The data to be examined. - :type data: str - - :return: Whether data is encrypted to a private key. - :rtype: bool - """ - result = self.list_packets(data) - return bool(result.key) - - def is_encrypted(self, data): - """ - Say whether some chunk of data is encrypted to a key. - - :param data: The data to be examined. - :type data: str - - :return: Whether data is encrypted to a key. - :rtype: bool - """ - return self.is_encrypted_asym(data) or self.is_encrypted_sym(data) diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 7946db85..9d8d89a1 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -27,6 +27,8 @@ import re import shutil import tempfile +from gnupg import GPG +from gnupg.gnupg import GPGUtilities from leap.common.check import leap_assert, leap_assert_type from leap.keymanager import errors @@ -38,7 +40,6 @@ from leap.keymanager.keys import ( KEYMANAGER_KEY_TAG, TAGS_ADDRESS_PRIVATE_INDEX, ) -from leap.keymanager.gpg import GPGWrapper logger = logging.getLogger(__name__) @@ -46,15 +47,15 @@ logger = logging.getLogger(__name__) class TempGPGWrapper(object): """ - A context manager returning a temporary GPG wrapper keyring, which - contains exactly zero or one pubkeys, and zero or one privkeys. - - Temporary unitary keyrings allow the to use GPG's facilities for exactly - one key. This function creates an empty temporary keyring and imports - C{keys} if it is not None. + A context manager that wraps a temporary GPG keyring which only contains + the keys given at object creation. """ + def __init__(self, keys=None, gpgbinary=None): """ + Create an empty temporary keyring and import any given C{keys} into + it. + :param keys: OpenPGP key, or list of. :type keys: OpenPGPKey or list of OpenPGPKeys :param gpgbinary: Name for GnuPG binary executable. @@ -67,14 +68,15 @@ class TempGPGWrapper(object): if not isinstance(keys, list): keys = [keys] self._keys = keys - for key in filter(None, keys): + for key in keys: leap_assert_type(key, OpenPGPKey) def __enter__(self): """ - Calls the unitary gpgwrapper initializer + Build and return a GPG keyring containing the keys given on + object creation. - :return: A GPG wrapper with a unitary keyring. + :return: A GPG instance containing the keys given on object creation. :rtype: gnupg.GPG """ self._build_keyring() @@ -82,19 +84,16 @@ class TempGPGWrapper(object): def __exit__(self, exc_type, exc_value, traceback): """ - Ensures the gpgwrapper is properly destroyed. + Ensure the gpg is properly destroyed. """ # TODO handle exceptions and log here self._destroy_keyring() def _build_keyring(self): """ - Create an empty GPG keyring and import C{keys} into it. - - :param keys: List of keys to add to the keyring. - :type keys: list of OpenPGPKey + Create a GPG keyring containing the keys given on object creation. - :return: A GPG wrapper with a unitary keyring. + :return: A GPG instance containing the keys given on object creation. :rtype: gnupg.GPG """ privkeys = [key for key in self._keys if key and key.private is True] @@ -111,14 +110,13 @@ class TempGPGWrapper(object): listkeys = lambda: self._gpg.list_keys() listsecretkeys = lambda: self._gpg.list_keys(secret=True) - self._gpg = GPGWrapper( - gnupghome=tempfile.mkdtemp(), - gpgbinary=self._gpgbinary) + self._gpg = GPG(binary=self._gpgbinary, + homedir=tempfile.mkdtemp()) leap_assert(len(listkeys()) is 0, 'Keyring not empty.') # import keys into the keyring: # concatenating ascii-armored keys, which is correctly - # understood by the GPGWrapper. + # understood by GPG. self._gpg.import_keys("".join( [x.key_data for x in publkeys + privkeys])) @@ -135,7 +133,7 @@ class TempGPGWrapper(object): def _destroy_keyring(self): """ - Securely erase a unitary keyring. + Securely erase the keyring. """ # TODO: implement some kind of wiping of data or a more # secure way that @@ -153,9 +151,9 @@ class TempGPGWrapper(object): raise finally: - leap_assert(self._gpg.gnupghome != os.path.expanduser('~/.gnupg'), + leap_assert(self._gpg.homedir != os.path.expanduser('~/.gnupg'), "watch out! Tried to remove default gnupg home!") - shutil.rmtree(self._gpg.gnupghome) + shutil.rmtree(self._gpg.homedir) def _build_key_from_gpg(address, key, key_data): @@ -406,16 +404,16 @@ class OpenPGPScheme(EncryptionScheme): def _temporary_gpgwrapper(self, keys=None): """ - Returns a unitary gpg wrapper that implements context manager - protocol. + Return a gpg wrapper that implements the context manager protocol and + contains C{keys}. :param key_data: ASCII armored key data. :type key_data: str :param gpgbinary: Name for GnuPG binary executable. :type gpgbinary: C{str} - :return: a GPGWrapper instance - :rtype: GPGWrapper + :return: a TempGPGWrapper instance + :rtype: TempGPGWrapper """ # TODO do here checks on key_data return TempGPGWrapper( @@ -459,8 +457,9 @@ class OpenPGPScheme(EncryptionScheme): with self._temporary_gpgwrapper(keys) as gpg: result = gpg.encrypt( data, pubkey.fingerprint, - sign=sign.key_id if sign else None, - passphrase=passphrase, symmetric=False) + default_key=sign.key_id if sign else None, + passphrase=passphrase, symmetric=False, + cipher_algo='AES256') # Here we cannot assert for correctness of sig because the sig is # in the ciphertext. # result.ok - (bool) indicates if the operation succeeded @@ -492,7 +491,8 @@ class OpenPGPScheme(EncryptionScheme): leap_assert(verify.private is False) keys.append(verify) with self._temporary_gpgwrapper(keys) as gpg: - result = gpg.decrypt(data, passphrase=passphrase) + result = gpg.decrypt( + data, passphrase=passphrase, always_trust=True) self._assert_gpg_result_ok(result) # verify signature if (verify is not None): @@ -514,7 +514,8 @@ class OpenPGPScheme(EncryptionScheme): :rtype: bool """ with self._temporary_gpgwrapper() as gpg: - return gpg.is_encrypted_asym(data) + gpgutil = GPGUtilities(gpg) + return gpgutil.is_encrypted_asym(data) def sign(self, data, privkey): """ @@ -535,7 +536,7 @@ class OpenPGPScheme(EncryptionScheme): # result.fingerprint - contains the fingerprint of the key used to # sign. with self._temporary_gpgwrapper(privkey) as gpg: - result = gpg.sign(data, keyid=privkey.key_id) + result = gpg.sign(data, default_key=privkey.key_id) rfprint = privkey.fingerprint privkey = gpg.list_keys(secret=True).pop() kfprint = privkey['fingerprint'] diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 5f232fed..25126047 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -28,7 +28,6 @@ from leap.keymanager import ( KeyManager, openpgp, KeyNotFound, - NoPasswordGiven, errors, ) from leap.keymanager.openpgp import OpenPGPKey -- cgit v1.2.3 From 5e5af82141b6740067432bfc24750ffd2e0c98a3 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 2 Oct 2013 11:33:28 -0400 Subject: add sanity check for gnupg package and version --- keymanager/src/leap/keymanager/__init__.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 2f39ad9e..76be2262 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -14,11 +14,28 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . - - """ Key Manager is a Nicknym agent for LEAP client. """ +# let's do a little sanity check to see if we're using the wrong gnupg +import sys + +try: + from gnupg.gnupg import GPGUtilities + assert(GPGUtilities) # pyflakes happy + from gnupg import __version__ + from distutils.version import LooseVersion as V + assert(V(__version__) >= V('1.2.2')) + +except ImportError, AssertionError: + print "Ooops! It looks like there is a conflict in the installed version " + print "of gnupg." + print "Disclaimer: Ideally, we would need to work a patch and propose the " + print "merge to upstream. But until then do: " + print + print "% pip uninstall python-gnupg" + print "% pip install gnupg" + sys.exit(1) import logging import requests -- cgit v1.2.3 From aa8015567aa840da4874385716f11b1161a4ec5f Mon Sep 17 00:00:00 2001 From: drebs Date: Fri, 4 Oct 2013 16:30:24 -0300 Subject: Add option to choose cipher and digest algorithms. --- keymanager/src/leap/keymanager/__init__.py | 20 +++++++++++++++++--- keymanager/src/leap/keymanager/openpgp.py | 22 ++++++++++++++++++---- 2 files changed, 35 insertions(+), 7 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 76be2262..edd0ba1f 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -391,7 +391,8 @@ class KeyManager(object): # encrypt/decrypt and sign/verify API # - def encrypt(self, data, pubkey, passphrase=None, sign=None): + def encrypt(self, data, pubkey, passphrase=None, sign=None, + cipher_algo='AES256'): """ Encrypt C{data} using public @{key} and sign with C{sign} key. @@ -401,6 +402,8 @@ class KeyManager(object): :type pubkey: EncryptionKey :param sign: The key used for signing. :type sign: EncryptionKey + :param cipher_algo: The cipher algorithm to use. + :type cipher_algo: str :return: The encrypted data. :rtype: str @@ -436,7 +439,8 @@ class KeyManager(object): return self._wrapper_map[privkey.__class__].decrypt( data, privkey, passphrase, verify) - def sign(self, data, privkey): + def sign(self, data, privkey, digest_algo='SHA512', clearsign=False, + detach=True, binary=False): """ Sign C{data} with C{privkey}. @@ -445,6 +449,14 @@ class KeyManager(object): :param privkey: The private key to be used to sign. :type privkey: EncryptionKey + :param digest_algo: The hash digest to use. + :type digest_algo: str + :param clearsign: If True, create a cleartext signature. + :type clearsign: bool + :param detach: If True, create a detached signature. + :type detach: bool + :param binary: If True, do not ascii armour the output. + :type binary: bool :return: The signed data. :rtype: str @@ -454,7 +466,9 @@ class KeyManager(object): privkey.__class__ in self._wrapper_map, 'Unknown key type.') leap_assert(privkey.private is True, 'Key is not private.') - return self._wrapper_map[privkey.__class__].sign(data, privkey) + return self._wrapper_map[privkey.__class__].sign( + data, privkey, digest_algo=digest_algo, clearsign=clearsign, + detach=detach, binary=binary) def verify(self, data, pubkey): """ diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 9d8d89a1..64123318 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -433,7 +433,8 @@ class OpenPGPScheme(EncryptionScheme): raise errors.EncryptionDecryptionFailed( 'Failed to encrypt/decrypt: %s' % stderr) - def encrypt(self, data, pubkey, passphrase=None, sign=None): + def encrypt(self, data, pubkey, passphrase=None, sign=None, + cipher_algo='AES256'): """ Encrypt C{data} using public @{pubkey} and sign with C{sign} key. @@ -443,6 +444,8 @@ class OpenPGPScheme(EncryptionScheme): :type pubkey: OpenPGPKey :param sign: The key used for signing. :type sign: OpenPGPKey + :param cipher_algo: The cipher algorithm to use. + :type cipher_algo: str :return: The encrypted data. :rtype: str @@ -459,7 +462,7 @@ class OpenPGPScheme(EncryptionScheme): data, pubkey.fingerprint, default_key=sign.key_id if sign else None, passphrase=passphrase, symmetric=False, - cipher_algo='AES256') + cipher_algo=cipher_algo) # Here we cannot assert for correctness of sig because the sig is # in the ciphertext. # result.ok - (bool) indicates if the operation succeeded @@ -517,7 +520,8 @@ class OpenPGPScheme(EncryptionScheme): gpgutil = GPGUtilities(gpg) return gpgutil.is_encrypted_asym(data) - def sign(self, data, privkey): + def sign(self, data, privkey, digest_algo='SHA512', clearsign=False, + detach=True, binary=False): """ Sign C{data} with C{privkey}. @@ -526,6 +530,14 @@ class OpenPGPScheme(EncryptionScheme): :param privkey: The private key to be used to sign. :type privkey: OpenPGPKey + :param digest_algo: The hash digest to use. + :type digest_algo: str + :param clearsign: If True, create a cleartext signature. + :type clearsign: bool + :param detach: If True, create a detached signature. + :type detach: bool + :param binary: If True, do not ascii armour the output. + :type binary: bool :return: The ascii-armored signed data. :rtype: str @@ -536,7 +548,9 @@ class OpenPGPScheme(EncryptionScheme): # result.fingerprint - contains the fingerprint of the key used to # sign. with self._temporary_gpgwrapper(privkey) as gpg: - result = gpg.sign(data, default_key=privkey.key_id) + result = gpg.sign(data, default_key=privkey.key_id, + digest_algo=digest_algo, clearsign=clearsign, + detach=detach, binary=binary) rfprint = privkey.fingerprint privkey = gpg.list_keys(secret=True).pop() kfprint = privkey['fingerprint'] -- cgit v1.2.3 From 542c4a5b7bf1c95bdfcb4059e9968412b0e53d67 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 9 Oct 2013 12:23:35 -0300 Subject: bump also the gnupg sanity check --- keymanager/src/leap/keymanager/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index edd0ba1f..f2cffd56 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -25,7 +25,7 @@ try: assert(GPGUtilities) # pyflakes happy from gnupg import __version__ from distutils.version import LooseVersion as V - assert(V(__version__) >= V('1.2.2')) + assert(V(__version__) >= V('1.2.3')) except ImportError, AssertionError: print "Ooops! It looks like there is a conflict in the installed version " -- cgit v1.2.3 From abb91dd3af9aa28eb998546fbd4e3c6feaa3bdbd Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 9 Oct 2013 13:03:30 -0300 Subject: fix exception catching --- keymanager/src/leap/keymanager/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index f2cffd56..a5505981 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -27,14 +27,17 @@ try: from distutils.version import LooseVersion as V assert(V(__version__) >= V('1.2.3')) -except ImportError, AssertionError: +except (ImportError, AssertionError): + print "*******" print "Ooops! It looks like there is a conflict in the installed version " print "of gnupg." + print print "Disclaimer: Ideally, we would need to work a patch and propose the " print "merge to upstream. But until then do: " print print "% pip uninstall python-gnupg" print "% pip install gnupg" + print "*******" sys.exit(1) import logging -- cgit v1.2.3 From 9cd2ee1e1b96ad521967096424b0b9ff16ed82da Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 23 Oct 2013 10:39:28 -0300 Subject: Return unicode in order to solve encoding issues. --- keymanager/src/leap/keymanager/openpgp.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 64123318..8ec86391 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -26,6 +26,7 @@ import os import re import shutil import tempfile +import locale from gnupg import GPG from gnupg.gnupg import GPGUtilities @@ -482,7 +483,7 @@ class OpenPGPScheme(EncryptionScheme): :type verify: OpenPGPKey :return: The decrypted data. - :rtype: str + :rtype: unicode @raise InvalidSignature: Raised if unable to verify the signature with C{verify} key. @@ -504,7 +505,12 @@ class OpenPGPScheme(EncryptionScheme): raise errors.InvalidSignature( 'Failed to verify signature with key %s: %s' % (verify.key_id, stderr)) - return result.data + + # XXX: this is the encoding used by gpg module + # https://github.com/isislovecruft/python-gnupg/\ + # blob/master/gnupg/_meta.py#L121 + encoding = locale.getpreferredencoding() + return result.data.decode(encoding, 'replace') def is_encrypted(self, data): """ -- cgit v1.2.3 From 96ac727920ed50bb6c067a3b1da2b8ee7d603762 Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 4 Nov 2013 15:09:40 -0200 Subject: Add verification of detached signatures. --- keymanager/src/leap/keymanager/__init__.py | 12 +++++++---- keymanager/src/leap/keymanager/keys.py | 9 +++++--- keymanager/src/leap/keymanager/openpgp.py | 25 ++++++++++++++++++---- .../src/leap/keymanager/tests/test_keymanager.py | 14 ++++++++++-- 4 files changed, 47 insertions(+), 13 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index a5505981..61213d38 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/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/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py index b8e88d46..ec1bfeb4 100644 --- a/keymanager/src/leap/keymanager/keys.py +++ b/keymanager/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/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 8ec86391..111dfafa 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/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/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 25126047..67676e9c 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/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 54cad35e3282bc64d772d472abd093b359ad91a5 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. --- keymanager/src/leap/keymanager/openpgp.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 111dfafa..a4dc1b8b 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/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 0f10209dafb330320df387e66a1cc9083a9ae755 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 :) --- keymanager/src/leap/keymanager/openpgp.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index a4dc1b8b..1670e1c9 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/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 dc5eff74a2ae85c74bca4747217565ca626bda91 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. --- keymanager/src/leap/keymanager/errors.py | 12 ++++++ keymanager/src/leap/keymanager/openpgp.py | 63 ++++++++++++++++++++++--------- 2 files changed, 58 insertions(+), 17 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/errors.py b/keymanager/src/leap/keymanager/errors.py index 89949d29..27180dbd 100644 --- a/keymanager/src/leap/keymanager/errors.py +++ b/keymanager/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/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 1670e1c9..78acd6ad 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/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 edd27d8d9afd60e73acd5b4b509f54178d09f894 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 --- keymanager/src/leap/keymanager/__init__.py | 42 ++++++++++++++++++++++++++++++ keymanager/src/leap/keymanager/openpgp.py | 6 ++++- 2 files changed, 47 insertions(+), 1 deletion(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 61213d38..dbc54891 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/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/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 78acd6ad..f6223d57 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/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 5be02926efeb775e652c217ade02250b0cc7e3ca Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 21 Nov 2013 12:12:31 -0300 Subject: Do not check for a valid address, rely on client. An email address regexp that supports all (or almost) emails is really complex, and is_address uses a simpler one since we restrict the valid usernames and provider names. For that we rely on the email client to check whether the email address is valid or not. --- keymanager/src/leap/keymanager/openpgp.py | 1 - 1 file changed, 1 deletion(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index f6223d57..0c11d17b 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -297,7 +297,6 @@ class OpenPGPScheme(EncryptionScheme): :rtype: OpenPGPKey @raise KeyNotFound: If the key was not found on local storage. """ - leap_assert(is_address(address), 'Not an user address: %s' % address) doc = self._get_key_doc(address, private) if doc is None: raise errors.KeyNotFound(address) -- cgit v1.2.3 From dd85ddd5e45b86e60e2d346ead3d09deccbef0ed Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 21 Nov 2013 12:23:33 -0300 Subject: Remove '+' suffix in email address. This allows us to support encrypt mails to 'test_user@provider.com' even if was sent to 'test_user+optional_string@provider.com'. --- keymanager/src/leap/keymanager/openpgp.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 0c11d17b..cb46371c 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -297,6 +297,11 @@ class OpenPGPScheme(EncryptionScheme): :rtype: OpenPGPKey @raise KeyNotFound: If the key was not found on local storage. """ + # Remove the identity suffix after the '+' until the '@' + # e.g.: test_user+something@provider.com becomes test_user@probider.com + # since the key belongs to the identity without the '+' suffix. + address = re.sub(r'\+.*\@', '@', address) + doc = self._get_key_doc(address, private) if doc is None: raise errors.KeyNotFound(address) -- cgit v1.2.3 From 58a45c40ae8b5619f6c848feaec329396e09b42f Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 25 Nov 2013 11:20:55 -0200 Subject: Fix error returns on openpgp backend. Also fix: - some docstrings. - variabled referenced without previous assignement. --- keymanager/src/leap/keymanager/__init__.py | 3 +++ keymanager/src/leap/keymanager/openpgp.py | 9 +++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index dbc54891..6cfbf710 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -425,6 +425,9 @@ class KeyManager(object): :type data: str :param privkey: The key used to decrypt. :type privkey: OpenPGPKey + :param passphrase: The passphrase for the secret key used for + decryption. + :type passphrase: str :param verify: The key used to verify a signature. :type verify: OpenPGPKey diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index cb46371c..5781d67b 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -341,6 +341,7 @@ class OpenPGPScheme(EncryptionScheme): leap_assert(match is not None, 'No user address in key data.') address = match.group(1) + openpgp_privkey = None if privkey is not None: match = re.match(mail_regex, privkey['uids'].pop()) leap_assert(match is not None, 'No user address in key data.') @@ -374,6 +375,7 @@ class OpenPGPScheme(EncryptionScheme): """ leap_assert_type(key_data, (str, unicode)) + openpgp_privkey = None try: openpgp_pubkey, openpgp_privkey = self.parse_ascii_key(key_data) except (errors.KeyAddressMismatch, errors.KeyFingerprintMismatch) as e: @@ -524,6 +526,9 @@ class OpenPGPScheme(EncryptionScheme): :type data: str :param privkey: The key used to decrypt. :type privkey: OpenPGPKey + :param passphrase: The passphrase for the secret key used for + decryption. + :type passphrase: str :param verify: The key used to verify a signature. :type verify: OpenPGPKey @@ -549,7 +554,7 @@ class OpenPGPScheme(EncryptionScheme): verify.fingerprint != result.pubkey_fingerprint: raise errors.InvalidSignature( 'Failed to verify signature with key %s: %s' % - (verify.key_id, stderr)) + (verify.key_id, result.stderr)) # XXX: this is the encoding used by gpg module # https://github.com/isislovecruft/python-gnupg/\ @@ -612,7 +617,7 @@ class OpenPGPScheme(EncryptionScheme): if result.fingerprint is None: raise errors.SignFailed( 'Failed to sign with key %s: %s' % - (privkey['keyid'], stderr)) + (privkey['keyid'], result.stderr)) leap_assert( result.fingerprint == kfprint, 'Signature and private key fingerprints mismatch: ' -- cgit v1.2.3 From 4010fa3dca4ff6814a759793ce49fc76f896fe63 Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 25 Nov 2013 16:35:27 -0200 Subject: Improve exceptions names and handling. --- keymanager/src/leap/keymanager/errors.py | 6 +-- keymanager/src/leap/keymanager/openpgp.py | 70 +++++++++++++++++++------------ 2 files changed, 46 insertions(+), 30 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/errors.py b/keymanager/src/leap/keymanager/errors.py index 27180dbd..ebe4fd5e 100644 --- a/keymanager/src/leap/keymanager/errors.py +++ b/keymanager/src/leap/keymanager/errors.py @@ -58,21 +58,21 @@ class InvalidSignature(Exception): pass -class EncryptionFailed(Exception): +class EncryptError(Exception): """ Raised upon failures of encryption. """ pass -class DecryptionFailed(Exception): +class DecryptError(Exception): """ Raised upon failures of decryption. """ pass -class EncryptionDecryptionFailed(Exception): +class GPGError(Exception): """ Raised upon failures of encryption/decryption. """ diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 5781d67b..856b21e4 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -471,14 +471,18 @@ class OpenPGPScheme(EncryptionScheme): def _assert_gpg_result_ok(result): """ Check if GPG result is 'ok' and log stderr outputs. - :param result: The GPG results - :type result: + + :param result: GPG results, which have a field calld 'ok' that states + whether the gpg operation was successful or not. + :type result: object + + :raise GPGError: Raised when the gpg operation was not successful. """ stderr = getattr(result, 'stderr', None) if stderr: logger.debug("%s" % (stderr,)) if getattr(result, 'ok', None) is not True: - raise errors.EncryptionDecryptionFailed( + raise errors.GPGError( 'Failed to encrypt/decrypt: %s' % stderr) def encrypt(self, data, pubkey, passphrase=None, sign=None, @@ -497,6 +501,8 @@ class OpenPGPScheme(EncryptionScheme): :return: The encrypted data. :rtype: str + + :raise EncryptError: Raised if failed encrypting for some reason. """ leap_assert_type(pubkey, OpenPGPKey) leap_assert(pubkey.private is False, 'Key is not public.') @@ -515,8 +521,12 @@ class OpenPGPScheme(EncryptionScheme): # in the ciphertext. # result.ok - (bool) indicates if the operation succeeded # result.data - (bool) contains the result of the operation - self._assert_gpg_result_ok(result) - return result.data + try: + self._assert_gpg_result_ok(result) + return result.data + except errors.GPGError as e: + logger.error('Failed to decrypt: %s.' % str(e)) + raise error.EncryptError() def decrypt(self, data, privkey, passphrase=None, verify=None): """ @@ -535,8 +545,9 @@ class OpenPGPScheme(EncryptionScheme): :return: The decrypted data. :rtype: unicode - @raise InvalidSignature: Raised if unable to verify the signature with - C{verify} key. + :raise DecryptError: Raised if failed decrypting for some reason. + :raise InvalidSignature: Raised if unable to verify the signature with + C{verify} key. """ leap_assert(privkey.private is True, 'Key is not private.') keys = [privkey] @@ -545,26 +556,31 @@ class OpenPGPScheme(EncryptionScheme): leap_assert(verify.private is False) keys.append(verify) with self._temporary_gpgwrapper(keys) as gpg: - result = gpg.decrypt( - data, passphrase=passphrase, always_trust=True) - self._assert_gpg_result_ok(result) - # verify signature - if (verify is not None): - if result.valid is False or \ - verify.fingerprint != result.pubkey_fingerprint: - raise errors.InvalidSignature( - 'Failed to verify signature with key %s: %s' % - (verify.key_id, result.stderr)) - - # XXX: this is the encoding used by gpg module - # 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') + try: + result = gpg.decrypt( + data, passphrase=passphrase, always_trust=True) + self._assert_gpg_result_ok(result) + # verify signature + if (verify is not None): + if result.valid is False or \ + verify.fingerprint != result.pubkey_fingerprint: + raise errors.InvalidSignature( + 'Failed to verify signature with key %s: %s' % + (verify.key_id, result.stderr)) + + # XXX: this is the encoding used by gpg module + # 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') + except errors.GPGError as e: + logger.error('Failed to decrypt: %s.' % str(e)) + raise errors.DecryptError(str(e)) + def is_encrypted(self, data): """ -- cgit v1.2.3 From 90fd6f86e79e1a1abb93306a31f10c34b02ade8f Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 9 Dec 2013 17:19:39 -0400 Subject: pep8 cleanup --- keymanager/src/leap/keymanager/_version.py | 34 ++++++++++++++++++------------ keymanager/src/leap/keymanager/openpgp.py | 5 ----- 2 files changed, 20 insertions(+), 19 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/_version.py b/keymanager/src/leap/keymanager/_version.py index 3a514e14..28fca968 100644 --- a/keymanager/src/leap/keymanager/_version.py +++ b/keymanager/src/leap/keymanager/_version.py @@ -17,6 +17,7 @@ git_full = "$Format:%H$" import subprocess import sys + def run_command(args, cwd=None, verbose=False): try: # remember shell=False, so use git.cmd on windows, not just git @@ -37,10 +38,10 @@ def run_command(args, cwd=None, verbose=False): return stdout -import sys import re import os.path + def get_expanded_variables(versionfile_source): # the code embedded in _version.py can just fetch the value of these # variables. When used from setup.py, we don't want to import @@ -48,7 +49,7 @@ def get_expanded_variables(versionfile_source): # used from _version.py. variables = {} try: - f = open(versionfile_source,"r") + f = open(versionfile_source, "r") for line in f.readlines(): if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) @@ -63,12 +64,13 @@ def get_expanded_variables(versionfile_source): pass return variables + def versions_from_expanded_variables(variables, tag_prefix, verbose=False): refnames = variables["refnames"].strip() if refnames.startswith("$Format"): if verbose: print("variables are unexpanded, not using") - return {} # unexpanded, so not in an unpacked git-archive tarball + return {} # unexpanded, so not in an unpacked git-archive tarball refs = set([r.strip() for r in refnames.strip("()").split(",")]) # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. @@ -93,13 +95,14 @@ def versions_from_expanded_variables(variables, tag_prefix, verbose=False): r = ref[len(tag_prefix):] if verbose: print("picking %s" % r) - return { "version": r, - "full": variables["full"].strip() } + return {"version": r, + "full": variables["full"].strip()} # no suitable tags, so we use the full revision id if verbose: print("no suitable tags, using full revision id") - return { "version": variables["full"].strip(), - "full": variables["full"].strip() } + return {"version": variables["full"].strip(), + "full": variables["full"].strip()} + def versions_from_vcs(tag_prefix, versionfile_source, verbose=False): # this runs 'git' from the root of the source tree. That either means @@ -116,7 +119,7 @@ def versions_from_vcs(tag_prefix, versionfile_source, verbose=False): here = os.path.abspath(__file__) except NameError: # some py2exe/bbfreeze/non-CPython implementations don't do __file__ - return {} # not always correct + return {} # not always correct # versionfile_source is the relative path from the top of the source tree # (where the .git directory might live) to this file. Invert this to find @@ -141,7 +144,8 @@ def versions_from_vcs(tag_prefix, versionfile_source, verbose=False): return {} if not stdout.startswith(tag_prefix): if verbose: - print("tag '%s' doesn't start with prefix '%s'" % (stdout, tag_prefix)) + print("tag '%s' doesn't start with prefix '%s'" % + (stdout, tag_prefix)) return {} tag = stdout[len(tag_prefix):] stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root) @@ -153,7 +157,8 @@ def versions_from_vcs(tag_prefix, versionfile_source, verbose=False): return {"version": tag, "full": full} -def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False): +def versions_from_parentdir(parentdir_prefix, versionfile_source, + verbose=False): if IN_LONG_VERSION_PY: # We're running from _version.py. If it's from a source tree # (execute-in-place), we can work upwards to find the root of the @@ -163,7 +168,7 @@ def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False) here = os.path.abspath(__file__) except NameError: # py2exe/bbfreeze/non-CPython don't have __file__ - return {} # without __file__, we have no hope + return {} # without __file__, we have no hope # versionfile_source is the relative path from the top of the source # tree to _version.py. Invert this to find the root from __file__. root = here @@ -180,7 +185,8 @@ def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False) dirname = os.path.basename(root) if not dirname.startswith(parentdir_prefix): if verbose: - print("guessing rootdir is '%s', but '%s' doesn't start with prefix '%s'" % + print("guessing rootdir is '%s', but '%s' doesn't start " + "with prefix '%s'" % (root, dirname, parentdir_prefix)) return None return {"version": dirname[len(parentdir_prefix):], "full": ""} @@ -189,8 +195,9 @@ tag_prefix = "" parentdir_prefix = "leap.keymanager-" versionfile_source = "src/leap/keymanager/_version.py" + def get_versions(default={"version": "unknown", "full": ""}, verbose=False): - variables = { "refnames": git_refnames, "full": git_full } + variables = {"refnames": git_refnames, "full": git_full} ver = versions_from_expanded_variables(variables, tag_prefix, verbose) if not ver: ver = versions_from_vcs(tag_prefix, versionfile_source, verbose) @@ -200,4 +207,3 @@ def get_versions(default={"version": "unknown", "full": ""}, verbose=False): if not ver: ver = default return ver - diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 856b21e4..1e424b6a 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -14,13 +14,9 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . - - """ Infrastructure for using OpenPGP keys in Key Manager. """ - - import locale import logging import os @@ -581,7 +577,6 @@ class OpenPGPScheme(EncryptionScheme): logger.error('Failed to decrypt: %s.' % str(e)) raise errors.DecryptError(str(e)) - def is_encrypted(self, data): """ Return whether C{data} was asymmetrically encrypted using OpenPGP. -- cgit v1.2.3 From 2a04ac69203e80be714daab438a7b9bb85459fd9 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 10 Dec 2013 16:54:25 -0400 Subject: typo --- keymanager/src/leap/keymanager/openpgp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 1e424b6a..4276b19f 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -294,7 +294,7 @@ class OpenPGPScheme(EncryptionScheme): @raise KeyNotFound: If the key was not found on local storage. """ # Remove the identity suffix after the '+' until the '@' - # e.g.: test_user+something@provider.com becomes test_user@probider.com + # e.g.: test_user+something@provider.com becomes test_user@provider.com # since the key belongs to the identity without the '+' suffix. address = re.sub(r'\+.*\@', '@', address) -- cgit v1.2.3 From 06868daa749cf6ab4fc8038676f55a4500f13ec2 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 17 Dec 2013 00:45:05 -0400 Subject: memoize call to get_key --- keymanager/src/leap/keymanager/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 6cfbf710..5b1d8d96 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -46,6 +46,7 @@ import requests from leap.common.check import leap_assert, leap_assert_type from leap.common.events import signal from leap.common.events import events_pb2 as proto +from leap.common.decorators import memoized_method from leap.keymanager.errors import KeyNotFound @@ -250,6 +251,7 @@ class KeyManager(object): self._put(uri, data) signal(proto.KEYMANAGER_DONE_UPLOADING_KEYS, self._address) + @memoized_method def get_key(self, address, ktype, private=False, fetch_remote=True): """ Return a key of type C{ktype} bound to C{address}. @@ -266,9 +268,10 @@ class KeyManager(object): :return: A key of type C{ktype} bound to C{address}. :rtype: EncryptionKey - @raise KeyNotFound: If the key was not found both locally and in - keyserver. + :raise KeyNotFound: If the key was not found both locally and in + keyserver. """ + logger.debug("getting key for %s" % (address,)) leap_assert( ktype in self._wrapper_map, 'Unkown key type: %s.' % str(ktype)) -- cgit v1.2.3 From 6559c38b51c28245cdbb06641466defac607855b Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 18 Dec 2013 22:36:20 -0400 Subject: Do not memoize get_key, but a separate method. In this way we can choose which calls to get from cache and we do not mess with the call from send_key --- keymanager/src/leap/keymanager/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 5b1d8d96..aa4e2e72 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -252,6 +252,12 @@ class KeyManager(object): signal(proto.KEYMANAGER_DONE_UPLOADING_KEYS, self._address) @memoized_method + def get_key_from_cache(self, *args, **kwargs): + """ + Public interface to `get_key`, that is memoized. + """ + return self.get_key(*args, **kwargs) + def get_key(self, address, ktype, private=False, fetch_remote=True): """ Return a key of type C{ktype} bound to C{address}. -- cgit v1.2.3 From cbaf583dc68661fe4f0cc9ee9aedde6d6ec0de9a Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 9 Jan 2014 16:53:19 -0300 Subject: Use a better version handler. --- keymanager/src/leap/keymanager/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index aa4e2e72..4edcdd0c 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -23,9 +23,9 @@ import sys try: from gnupg.gnupg import GPGUtilities assert(GPGUtilities) # pyflakes happy - from gnupg import __version__ - from distutils.version import LooseVersion as V - assert(V(__version__) >= V('1.2.3')) + from gnupg import __version__ as _gnupg_version + from pkg_resources import parse_version + assert(parse_version(_gnupg_version) >= parse_version('1.2.3')) except (ImportError, AssertionError): print "*******" -- cgit v1.2.3 From 92c14ffdbcec74cb6c602fc82633a99e9b8bde40 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 17 Jan 2014 14:53:10 -0300 Subject: Do not decode decrypted data, return as str. --- keymanager/src/leap/keymanager/openpgp.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 4276b19f..961e1ed3 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -564,15 +564,7 @@ class OpenPGPScheme(EncryptionScheme): 'Failed to verify signature with key %s: %s' % (verify.key_id, result.stderr)) - # XXX: this is the encoding used by gpg module - # 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') + return result.data except errors.GPGError as e: logger.error('Failed to decrypt: %s.' % str(e)) raise errors.DecryptError(str(e)) -- cgit v1.2.3 From da9191e700895811b657a19148d1d3852e79d157 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 17 Jan 2014 15:37:00 -0300 Subject: Remove unused imports, fix typo. --- keymanager/src/leap/keymanager/openpgp.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 961e1ed3..950d022c 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -17,12 +17,10 @@ """ Infrastructure for using OpenPGP keys in Key Manager. """ -import locale import logging import os import re import shutil -import sys import tempfile from contextlib import closing @@ -522,7 +520,7 @@ class OpenPGPScheme(EncryptionScheme): return result.data except errors.GPGError as e: logger.error('Failed to decrypt: %s.' % str(e)) - raise error.EncryptError() + raise errors.EncryptError() def decrypt(self, data, privkey, passphrase=None, verify=None): """ -- cgit v1.2.3 From 9c4c725b35b66521d355a70ec0e211d43d4515c7 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 17 Jan 2014 17:47:06 -0300 Subject: Fix failing tests. Add missing MockSharedDB. Update Soledad parameters. --- keymanager/src/leap/keymanager/tests/test_keymanager.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 67676e9c..e2558e45 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -118,9 +118,21 @@ class KeyManagerWithSoledadTestCase(BaseLeapTest): Soledad._get_secrets_from_shared_db = Mock(return_value=None) Soledad._put_secrets_in_shared_db = Mock(return_value=None) + class MockSharedDB(object): + + get_doc = Mock(return_value=None) + put_doc = Mock() + lock = Mock(return_value=('atoken', 300)) + unlock = Mock(return_value=True) + + def __call__(self): + return self + + Soledad._shared_db = MockSharedDB() + self._soledad = Soledad( - "leap@leap.se", - "123456", + u"leap@leap.se", + u"123456", secrets_path=self.tempdir + "/secret.gpg", local_db_path=self.tempdir + "/soledad.u1db", server_url='', -- cgit v1.2.3 From d25eafa5f29d3953e3b928afdd74301dec84b977 Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 17 Feb 2014 17:01:29 -0300 Subject: Update auth to interact with webapp v2. Closes #5120. --- keymanager/src/leap/keymanager/__init__.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 4edcdd0c..dfe04153 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -77,7 +77,7 @@ class KeyManager(object): OPENPGP_KEY = 'openpgp' PUBKEY_KEY = "user[public_key]" - def __init__(self, address, nickserver_uri, soledad, session_id=None, + def __init__(self, address, nickserver_uri, soledad, token=None, ca_cert_path=None, api_uri=None, api_version=None, uid=None, gpgbinary=None): """ @@ -90,8 +90,8 @@ class KeyManager(object): :type url: str :param soledad: A Soledad instance for local storage of keys. :type soledad: leap.soledad.Soledad - :param session_id: The session ID for interacting with the webapp API. - :type session_id: str + :param token: The token for interacting with the webapp API. + :type token: str :param ca_cert_path: The path to the CA certificate. :type ca_cert_path: str :param api_uri: The URI of the webapp API. @@ -106,7 +106,7 @@ class KeyManager(object): self._address = address self._nickserver_uri = nickserver_uri self._soledad = soledad - self._session_id = session_id + self._token = token self.ca_cert_path = ca_cert_path self.api_uri = api_uri self.api_version = api_version @@ -180,11 +180,11 @@ class KeyManager(object): self._ca_cert_path is not None, 'We need the CA certificate path!') leap_assert( - self._session_id is not None, - 'We need a session_id to interact with webapp!') + self._token is not None, + 'We need a token to interact with webapp!') res = self._fetcher.put( uri, data=data, verify=self._ca_cert_path, - cookies={'_session_id': self._session_id}) + headers={'Authorization': 'Token token=%s' % self._token}) # assert that the response is valid res.raise_for_status() return res @@ -353,14 +353,14 @@ class KeyManager(object): # Setters/getters # - def _get_session_id(self): - return self._session_id + def _get_token(self): + return self._token - def _set_session_id(self, session_id): - self._session_id = session_id + def _set_token(self, token): + self._token = token - session_id = property( - _get_session_id, _set_session_id, doc='The session id.') + token = property( + _get_token, _set_token, doc='The session token.') def _get_ca_cert_path(self): return self._ca_cert_path -- cgit v1.2.3 From fae320b7e81036426f375f10832a38f160433238 Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 31 Mar 2014 17:59:13 -0300 Subject: Properly raise exception when key is not found on server (#5415). --- keymanager/src/leap/keymanager/__init__.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index dfe04153..41f352ec 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -197,21 +197,25 @@ class KeyManager(object): :param address: The address bound to the keys. :type address: str - @raise KeyNotFound: If the key was not found on nickserver. + :raise KeyNotFound: If the key was not found on nickserver. """ # request keys from the nickserver res = None try: res = self._get(self._nickserver_uri, {'address': address}) + res.raise_for_status() server_keys = res.json() # insert keys in local database if self.OPENPGP_KEY in server_keys: self._wrapper_map[OpenPGPKey].put_ascii_key( server_keys['openpgp']) + except requests.exceptions.HTTPError as e: + if e.response.status_code == 404: + raise KeyNotFound(address) + logger.warning("HTTP error retrieving key: %r" % (e,)) + logger.warning("%s" % (res.content,)) except Exception as e: - logger.warning("Error retrieving the keys: %r" % (e,)) - if res: - logger.warning("%s" % (res.content,)) + logger.warning("Error retrieving key: %r" % (e,)) # # key management @@ -233,7 +237,7 @@ class KeyManager(object): :param ktype: The type of the key. :type ktype: KeyType - @raise KeyNotFound: If the key was not found in local database. + :raise KeyNotFound: If the key was not found in local database. """ leap_assert( ktype is OpenPGPKey, @@ -297,7 +301,7 @@ class KeyManager(object): raise signal(proto.KEYMANAGER_LOOKING_FOR_KEY, address) - self._fetch_keys_from_server(address) + self._fetch_keys_from_server(address) # might raise KeyNotFound key = self._wrapper_map[ktype].get_key(address, private=False) signal(proto.KEYMANAGER_KEY_FOUND, address) @@ -443,7 +447,7 @@ class KeyManager(object): :return: The decrypted data. :rtype: str - @raise InvalidSignature: Raised if unable to verify the signature with + :raise InvalidSignature: Raised if unable to verify the signature with C{verify} key. """ leap_assert_type(privkey, EncryptionKey) -- cgit v1.2.3 From 93bd48dd8855e4b4c7ebccb0148fd09c4a347f65 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Fri, 25 Jul 2014 19:26:19 -0500 Subject: gpg.verify_file() gets the data as a filename not as a binary stream --- keymanager/src/leap/keymanager/openpgp.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 950d022c..46ae2aab 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -649,13 +649,17 @@ class OpenPGPScheme(EncryptionScheme): 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. + # gpg.verify_file(), which receives the name of + # files containing the date and 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) + df, dfname = tempfile.mkstemp() + with os.fdopen(df, 'w') as sdd: + sdd.write(data) + result = gpg.verify_file(dfname, sig_file=sfname) + os.unlink(sfname) + os.unlink(dfname) gpgpubkey = gpg.list_keys().pop() valid = result.valid rfprint = result.fingerprint -- cgit v1.2.3 From 008328cc0ad2738109472869f3644100ca85859d Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Fri, 25 Jul 2014 19:28:15 -0500 Subject: Fix test_send_key --- keymanager/src/leap/keymanager/tests/test_keymanager.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index e2558e45..f89dcd9b 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -147,8 +147,8 @@ class KeyManagerWithSoledadTestCase(BaseLeapTest): for key in km.get_all_keys_in_local_db(private=True): km._wrapper_map[key.__class__].delete_key(key) - def _key_manager(self, user=ADDRESS, url=''): - return KeyManager(user, url, self._soledad, + def _key_manager(self, user=ADDRESS, url='', token=None): + return KeyManager(user, url, self._soledad, token=token, gpgbinary=GPG_BINARY_PATH) @@ -387,7 +387,8 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): """ Test that request is well formed when sending keys to server. """ - km = self._key_manager() + token = "mytoken" + km = self._key_manager(token=token) km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY) km._fetcher.put = Mock() # the following data will be used on the send @@ -404,7 +405,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): url = '%s/%s/users/%s.json' % ('apiuri', 'apiver', 'myuid') km._fetcher.put.assert_called_once_with( url, data=data, verify='capath', - cookies={'_session_id': 'sessionid'}, + headers={'Authorization': 'Token token=%s' % token}, ) def test__fetch_keys_from_server(self): -- cgit v1.2.3 From d3f7c6df9ddde1e2a6311612fbe37efd9b5ddb56 Mon Sep 17 00:00:00 2001 From: drebs Date: Wed, 27 Aug 2014 12:59:28 -0300 Subject: Fix call to python-gnupg verify_file() method (#6022). --- keymanager/src/leap/keymanager/openpgp.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 46ae2aab..ee37a342 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -22,12 +22,11 @@ import os import re import shutil import tempfile +import io -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, leap_check from leap.keymanager import errors @@ -649,17 +648,13 @@ class OpenPGPScheme(EncryptionScheme): result = gpg.verify(data) else: # to verify using a detached sig we have to use - # gpg.verify_file(), which receives the name of - # files containing the date and the signature. + # 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) - df, dfname = tempfile.mkstemp() - with os.fdopen(df, 'w') as sdd: - sdd.write(data) - result = gpg.verify_file(dfname, sig_file=sfname) + result = gpg.verify_file(io.BytesIO(data), sig_file=sfname) os.unlink(sfname) - os.unlink(dfname) gpgpubkey = gpg.list_keys().pop() valid = result.valid rfprint = result.fingerprint -- cgit v1.2.3 From 792f8236e2fcdb8ba14867db2c1a527a2e5adb4b Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 15 Sep 2014 14:07:26 -0500 Subject: Update docstrings of the public API --- keymanager/src/leap/keymanager/__init__.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 41f352ec..65352a86 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -82,12 +82,12 @@ class KeyManager(object): gpgbinary=None): """ Initialize a Key Manager for user's C{address} with provider's - nickserver reachable in C{url}. + nickserver reachable in C{nickserver_uri}. - :param address: The address of the user of this Key Manager. + :param address: The email address of the user of this Key Manager. :type address: str - :param url: The URL of the nickserver. - :type url: str + :param nickserver_uri: The URI of the nickserver. + :type nickserver_uri: str :param soledad: A Soledad instance for local storage of keys. :type soledad: leap.soledad.Soledad :param token: The token for interacting with the webapp API. @@ -98,7 +98,7 @@ class KeyManager(object): :type api_uri: str :param api_version: The version of the webapp API. :type api_version: str - :param uid: The users' UID. + :param uid: The user's UID. :type uid: str :param gpgbinary: Name for GnuPG binary executable. :type gpgbinary: C{str} @@ -228,12 +228,6 @@ class KeyManager(object): Public key bound to user's is sent to provider, which will sign it and replace any prior keys for the same address in its database. - If C{send_private} is True, then the private key is encrypted with - C{password} and sent to server in the same request, together with a - hash string of user's address and password. The encrypted private key - will be saved in the server in a way it is publicly retrievable - through the hash string. - :param ktype: The type of the key. :type ktype: KeyType @@ -275,6 +269,9 @@ class KeyManager(object): :type ktype: KeyType :param private: Look for a private key instead of a public one? :type private: bool + :param fetch_remote: If key not found in local storage try to fetch + from nickserver + :type fetch_remote: bool :return: A key of type C{ktype} bound to C{address}. :rtype: EncryptionKey @@ -311,6 +308,9 @@ class KeyManager(object): """ Return all keys stored in local database. + :param private: Include private keys + :type private: bool + :return: A list with all keys in local db. :rtype: list """ @@ -416,6 +416,9 @@ class KeyManager(object): :type data: str :param pubkey: The key used to encrypt. :type pubkey: EncryptionKey + :param passphrase: The passphrase for the secret key used for the + signature. + :type passphrase: str :param sign: The key used for signing. :type sign: EncryptionKey :param cipher_algo: The cipher algorithm to use. @@ -448,7 +451,7 @@ class KeyManager(object): :rtype: str :raise InvalidSignature: Raised if unable to verify the signature with - C{verify} key. + C{verify} key. """ leap_assert_type(privkey, EncryptionKey) leap_assert( -- cgit v1.2.3 From 7f36a336362a19fe5a426a08863ff4b05b003e31 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 15 Sep 2014 14:07:58 -0500 Subject: Remove refresh_keys Never should be done a bulk upload of keys. The updating of the keys should not be a task for the user of the keymanager. Keys will be updated by the keymanager in a background worker one per one. --- keymanager/src/leap/keymanager/__init__.py | 13 ------------- keymanager/src/leap/keymanager/tests/test_keymanager.py | 17 ----------------- 2 files changed, 30 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 65352a86..4563d804 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -324,19 +324,6 @@ class KeyManager(object): KEYMANAGER_KEY_TAG, '1' if private else '0')) - def refresh_keys(self): - """ - Fetch keys from nickserver and update them locally. - """ - addresses = set(map( - lambda doc: doc.address, - self.get_all_keys_in_local_db(private=False))) - for address in addresses: - # do not attempt to refresh our own key - if address == self._address: - continue - self._fetch_keys_from_server(address) - def gen_key(self, ktype): """ Generate a key of type C{ktype} bound to the user's address. diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index f89dcd9b..932f2602 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -437,23 +437,6 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): verify='cacertpath', ) - def test_refresh_keys_does_not_refresh_own_key(self): - """ - Test that refreshing keys will not attempt to refresh our own key. - """ - km = self._key_manager() - # we add 2 keys but we expect it to only refresh the second one. - km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY) - km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY_2) - # mock the key fetching - km._fetch_keys_from_server = Mock(return_value=[]) - km.ca_cert_path = '' # some bogus path so the km does not complain. - # do the refreshing - km.refresh_keys() - km._fetch_keys_from_server.assert_called_once_with( - ADDRESS_2 - ) - def test_get_key_fetches_from_server(self): """ Test that getting a key successfuly fetches from server. -- cgit v1.2.3 From 936666b5a2c12c0d9733fb5ffcecd7f512c6b660 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 15 Sep 2014 14:13:49 -0500 Subject: Pass the cipher_algo to the encrypt wrapper --- keymanager/src/leap/keymanager/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 4563d804..fe9f02cb 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -418,7 +418,7 @@ class KeyManager(object): 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__].encrypt( - data, pubkey, passphrase, sign) + data, pubkey, passphrase, sign, cipher_algo=cipher_algo) def decrypt(self, data, privkey, passphrase=None, verify=None): """ -- cgit v1.2.3 From c6fcde7d333d1769dbdfc1c8be255378a20895d6 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Tue, 16 Sep 2014 10:55:16 -0500 Subject: Remove get_key_from_cache We only need to cache the fetch with a sort timeout. The tests that fetches keys now have to use different keys or will be cached. --- keymanager/src/leap/keymanager/__init__.py | 8 +------- keymanager/src/leap/keymanager/tests/test_keymanager.py | 8 ++++---- 2 files changed, 5 insertions(+), 11 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index fe9f02cb..37eb2233 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -189,6 +189,7 @@ class KeyManager(object): res.raise_for_status() return res + @memoized_method(invalidation=300) def _fetch_keys_from_server(self, address): """ Fetch keys bound to C{address} from nickserver and insert them in @@ -249,13 +250,6 @@ class KeyManager(object): self._put(uri, data) signal(proto.KEYMANAGER_DONE_UPLOADING_KEYS, self._address) - @memoized_method - def get_key_from_cache(self, *args, **kwargs): - """ - Public interface to `get_key`, that is memoized. - """ - return self.get_key(*args, **kwargs) - def get_key(self, address, ktype, private=False, fetch_remote=True): """ Return a key of type C{ktype} bound to C{address}. diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 932f2602..f9ba04f6 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -448,7 +448,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): headers = {'content-type': 'application/json'} def json(self): - return {'address': ADDRESS_2, 'openpgp': PUBLIC_KEY_2} + return {'address': ADDRESS, 'openpgp': PUBLIC_KEY} def raise_for_status(self): pass @@ -458,13 +458,13 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km.ca_cert_path = 'cacertpath' # try to key get without fetching from server self.assertRaises( - KeyNotFound, km.get_key, ADDRESS_2, OpenPGPKey, + KeyNotFound, km.get_key, ADDRESS, OpenPGPKey, fetch_remote=False ) # try to get key fetching from server. - key = km.get_key(ADDRESS_2, OpenPGPKey) + key = km.get_key(ADDRESS, OpenPGPKey) self.assertIsInstance(key, OpenPGPKey) - self.assertEqual(ADDRESS_2, key.address) + self.assertEqual(ADDRESS, key.address) class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): -- cgit v1.2.3 From ac0432fd35d5325110c96db065ef6eb6257a8d31 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Tue, 16 Sep 2014 11:01:43 -0500 Subject: Rename method get_all_keys_in_local_db -> get_all_keys --- keymanager/src/leap/keymanager/__init__.py | 2 +- keymanager/src/leap/keymanager/tests/test_keymanager.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 37eb2233..3888c0ad 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -298,7 +298,7 @@ class KeyManager(object): return key - def get_all_keys_in_local_db(self, private=False): + def get_all_keys(self, private=False): """ Return all keys stored in local database. diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index f9ba04f6..7192bfb1 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -142,9 +142,9 @@ class KeyManagerWithSoledadTestCase(BaseLeapTest): def tearDown(self): km = self._key_manager() - for key in km.get_all_keys_in_local_db(): + for key in km.get_all_keys(): km._wrapper_map[key.__class__].delete_key(key) - for key in km.get_all_keys_in_local_db(private=True): + for key in km.get_all_keys(private=True): km._wrapper_map[key.__class__].delete_key(key) def _key_manager(self, user=ADDRESS, url='', token=None): @@ -343,12 +343,12 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km = self._key_manager() km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY) # get public keys - keys = km.get_all_keys_in_local_db(False) + keys = km.get_all_keys(False) self.assertEqual(len(keys), 1, 'Wrong number of keys') self.assertEqual(ADDRESS, keys[0].address) self.assertFalse(keys[0].private) # get private keys - keys = km.get_all_keys_in_local_db(True) + keys = km.get_all_keys(True) self.assertEqual(len(keys), 1, 'Wrong number of keys') self.assertEqual(ADDRESS, keys[0].address) self.assertTrue(keys[0].private) -- cgit v1.2.3 From 6053b7f6689a4a168c2b394db456bb8c72e516f7 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Tue, 16 Sep 2014 11:27:40 -0500 Subject: Remove parse_openpgp_ascii_key Don't fit with the logic of the keymanager and it's not use except for some commented code in bitmask_client --- keymanager/src/leap/keymanager/__init__.py | 14 -------------- 1 file changed, 14 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 3888c0ad..bd85c2d4 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -495,20 +495,6 @@ 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. -- cgit v1.2.3 From ca81004d13ef48206b860cf4ff70e8bbdfbc61af Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Wed, 8 Oct 2014 19:28:33 -0500 Subject: Accept ascii keys on put_key --- keymanager/src/leap/keymanager/__init__.py | 9 ++++++--- keymanager/src/leap/keymanager/tests/test_keymanager.py | 11 +++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index bd85c2d4..da679acb 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -515,11 +515,14 @@ class KeyManager(object): """ Put C{key} in local storage. - :param key: The key to be stored. - :type key: OpenPGPKey + :param key: The key to be stored. It can be ascii key or an OpenPGPKey + :type key: str or OpenPGPKey """ try: - self._wrapper_map[type(key)].put_key(key) + if isinstance(key, basestring): + self._wrapper_map[OpenPGPKey].put_ascii_key(key) + else: + self._wrapper_map[type(key)].put_key(key) except IndexError as e: leap_assert(False, "Unsupported key type. Error {0!r}".format(e)) diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 7192bfb1..39b729d8 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -466,6 +466,17 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): self.assertIsInstance(key, OpenPGPKey) self.assertEqual(ADDRESS, key.address) + def test_put_key_ascii(self): + """ + Test that putting ascii key works + """ + km = self._key_manager(url='http://nickserver.domain') + + km.put_key(PUBLIC_KEY) + key = km.get_key(ADDRESS, OpenPGPKey) + self.assertIsInstance(key, OpenPGPKey) + self.assertEqual(ADDRESS, key.address) + class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): -- cgit v1.2.3 From 6e65c0fb2a36182a45ec7851b8404c406910589f Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Fri, 1 Aug 2014 10:20:27 -0500 Subject: Implement 'fetch_key' for ascii keys binary keys support is still missing --- keymanager/src/leap/keymanager/__init__.py | 31 +++++++++++++- keymanager/src/leap/keymanager/openpgp.py | 5 ++- .../src/leap/keymanager/tests/test_keymanager.py | 50 ++++++++++++++++++++++ 3 files changed, 84 insertions(+), 2 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index da679acb..c3423d9b 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -48,7 +48,7 @@ from leap.common.events import signal from leap.common.events import events_pb2 as proto from leap.common.decorators import memoized_method -from leap.keymanager.errors import KeyNotFound +from leap.keymanager.errors import KeyNotFound, KeyAttributesDiffer from leap.keymanager.keys import ( EncryptionKey, @@ -526,6 +526,35 @@ class KeyManager(object): except IndexError as e: leap_assert(False, "Unsupported key type. Error {0!r}".format(e)) + def fetch_key(self, address, uri, ktype): + """ + Fetch a public key for C{address} from the network and put it in + local storage. + + Raises C{openpgp.errors.KeyNotFound} if not valid key on C{uri}. + Raises C{openpgp.errors.KeyAttributesDiffer} if address don't match + any uid on the key. + + :param address: The email address of the key. + :type address: str + :param uri: The URI of the key. + :type uri: str + :param ktype: The type of the key. + :type ktype: KeyType + """ + res = self._get(uri) + if not res.ok: + raise KeyNotFound(uri) + + # XXX parse binary keys + pubkey, _ = self._wrapper_map[ktype].parse_ascii_key(res.content) + if pubkey is None: + raise KeyNotFound(uri) + if pubkey.address != address: + raise KeyAttributesDiffer("UID %s found, but expected %s" + % (pubkey.address, address)) + self.put_key(pubkey) + from ._version import get_versions __version__ = get_versions()['version'] del get_versions diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index ee37a342..6a825cda 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -327,7 +327,10 @@ class OpenPGPScheme(EncryptionScheme): privkey = gpg.list_keys(secret=True).pop() except IndexError: pass - pubkey = gpg.list_keys(secret=False).pop() # unitary keyring + try: + pubkey = gpg.list_keys(secret=False).pop() # unitary keyring + except IndexError: + return (None, None) # extract adress from first uid on key match = re.match(mail_regex, pubkey['uids'].pop()) diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 39b729d8..65f8f39f 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -28,6 +28,7 @@ from leap.keymanager import ( KeyManager, openpgp, KeyNotFound, + KeyAttributesDiffer, errors, ) from leap.keymanager.openpgp import OpenPGPKey @@ -477,6 +478,55 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): self.assertIsInstance(key, OpenPGPKey) self.assertEqual(ADDRESS, key.address) + def test_fetch_uri_ascii_key(self): + """ + Test that fetch key downloads the ascii key and gets included in + the local storage + """ + km = self._key_manager() + + class Response(object): + ok = True + content = PUBLIC_KEY + + km._fetcher.get = Mock(return_value=Response()) + km.ca_cert_path = 'cacertpath' + + km.fetch_key(ADDRESS, "http://site.domain/key", OpenPGPKey) + key = km.get_key(ADDRESS, OpenPGPKey) + self.assertEqual(KEY_FINGERPRINT, key.fingerprint) + + def test_fetch_uri_empty_key(self): + """ + Test that fetch key raises KeyNotFound if no key in the url + """ + km = self._key_manager() + + class Response(object): + ok = True + content = "" + + km._fetcher.get = Mock(return_value=Response()) + km.ca_cert_path = 'cacertpath' + self.assertRaises(KeyNotFound, km.fetch_key, + ADDRESS, "http://site.domain/key", OpenPGPKey) + + def test_fetch_uri_address_differ(self): + """ + Test that fetch key raises KeyAttributesDiffer if the address + don't match + """ + km = self._key_manager() + + class Response(object): + ok = True + content = PUBLIC_KEY + + km._fetcher.get = Mock(return_value=Response()) + km.ca_cert_path = 'cacertpath' + self.assertRaises(KeyAttributesDiffer, km.fetch_key, + ADDRESS_2, "http://site.domain/key", OpenPGPKey) + class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): -- cgit v1.2.3 From 66d4a0de5d405ba761943f83d2b778b9ee63707b Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Sun, 12 Oct 2014 03:21:47 -0500 Subject: Basic validation levels support --- keymanager/src/leap/keymanager/__init__.py | 96 +++++++-- keymanager/src/leap/keymanager/errors.py | 6 + keymanager/src/leap/keymanager/keys.py | 16 +- keymanager/src/leap/keymanager/openpgp.py | 3 +- keymanager/src/leap/keymanager/tests/__init__.py | 236 +++++++++++++++++++++ .../src/leap/keymanager/tests/test_keymanager.py | 234 ++------------------ .../src/leap/keymanager/tests/test_validation.py | 150 +++++++++++++ keymanager/src/leap/keymanager/validation.py | 94 ++++++++ 8 files changed, 594 insertions(+), 241 deletions(-) create mode 100644 keymanager/src/leap/keymanager/tests/test_validation.py create mode 100644 keymanager/src/leap/keymanager/validation.py (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index c3423d9b..156aaf86 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -48,7 +48,12 @@ from leap.common.events import signal from leap.common.events import events_pb2 as proto from leap.common.decorators import memoized_method -from leap.keymanager.errors import KeyNotFound, KeyAttributesDiffer +from leap.keymanager.errors import ( + KeyNotFound, + KeyAddressMismatch, + KeyNotValidUpgrade +) +from leap.keymanager.validation import ValidationLevel, can_upgrade from leap.keymanager.keys import ( EncryptionKey, @@ -208,8 +213,11 @@ class KeyManager(object): server_keys = res.json() # insert keys in local database if self.OPENPGP_KEY in server_keys: - self._wrapper_map[OpenPGPKey].put_ascii_key( - server_keys['openpgp']) + self.put_raw_key( + server_keys['openpgp'], + OpenPGPKey, + address=address, + validation=ValidationLevel.Provider_Trust) except requests.exceptions.HTTPError as e: if e.response.status_code == 404: raise KeyNotFound(address) @@ -230,7 +238,7 @@ class KeyManager(object): replace any prior keys for the same address in its database. :param ktype: The type of the key. - :type ktype: KeyType + :type ktype: subclass of EncryptionKey :raise KeyNotFound: If the key was not found in local database. """ @@ -260,7 +268,7 @@ class KeyManager(object): :param address: The address bound to the key. :type address: str :param ktype: The type of the key. - :type ktype: KeyType + :type ktype: subclass of EncryptionKey :param private: Look for a private key instead of a public one? :type private: bool :param fetch_remote: If key not found in local storage try to fetch @@ -323,7 +331,7 @@ class KeyManager(object): Generate a key of type C{ktype} bound to the user's address. :param ktype: The type of the key. - :type ktype: KeyType + :type ktype: subclass of EncryptionKey :return: The generated key. :rtype: EncryptionKey @@ -515,32 +523,74 @@ class KeyManager(object): """ Put C{key} in local storage. - :param key: The key to be stored. It can be ascii key or an OpenPGPKey - :type key: str or OpenPGPKey + :param key: The key to be stored + :type key: EncryptionKey + :raises KeyNotValidUpdate: if a key with the same uid exists and the + new one is not a valid update for it """ try: - if isinstance(key, basestring): - self._wrapper_map[OpenPGPKey].put_ascii_key(key) - else: + old_key = self._wrapper_map[type(key)].get_key(key.address, + private=key.private) + except KeyNotFound: + old_key = None + + if key.private or can_upgrade(key, old_key): + try: self._wrapper_map[type(key)].put_key(key) - except IndexError as e: - leap_assert(False, "Unsupported key type. Error {0!r}".format(e)) + except IndexError as e: + leap_assert( + False, "Unsupported key type. Error {0!r}".format(e)) + else: + raise KeyNotValidUpgrade("Key %s can not be upgraded by new key %s" + % (old_key.key_id, key.key_id)) - def fetch_key(self, address, uri, ktype): + def put_raw_key(self, key, ktype, address=None, + validation=ValidationLevel.Weak_Chain): + """ + Put C{key} in local storage. + + :param key: The ascii key to be stored + :type key: str + :param ktype: the type of the key. + :type ktype: subclass of EncryptionKey + :param address: if set used to check that the key is for this address + :type address: str + :param validation: validation level for this key + (default: 'Weak_Chain') + :type validation: ValidationLevel + + :raises KeyAddressMismatch: if address doesn't match any uid on the key + :raises KeyNotValidUpdate: if a key with the same uid exists and the + new one is not a valid update for it + """ + pubkey, _ = self._wrapper_map[ktype].parse_ascii_key(key) + if address is not None and address != pubkey.address: + raise KeyAddressMismatch("Key UID %s, but expected %s" + % (pubkey.address, address)) + + pubkey.validation = validation + self.put_key(pubkey) + + def fetch_key(self, address, uri, ktype, + validation=ValidationLevel.Weak_Chain): """ Fetch a public key for C{address} from the network and put it in local storage. - Raises C{openpgp.errors.KeyNotFound} if not valid key on C{uri}. - Raises C{openpgp.errors.KeyAttributesDiffer} if address don't match - any uid on the key. - :param address: The email address of the key. :type address: str :param uri: The URI of the key. :type uri: str - :param ktype: The type of the key. - :type ktype: KeyType + :param ktype: the type of the key. + :type ktype: subclass of EncryptionKey + :param validation: validation level for this key + (default: 'Weak_Chain') + :type validation: ValidationLevel + + :raises KeyNotFound: if not valid key on C{uri} + :raises KeyAddressMismatch: if address doesn't match any uid on the key + :raises KeyNotValidUpdate: if a key with the same uid exists and the + new one is not a valid update for it """ res = self._get(uri) if not res.ok: @@ -551,8 +601,10 @@ class KeyManager(object): if pubkey is None: raise KeyNotFound(uri) if pubkey.address != address: - raise KeyAttributesDiffer("UID %s found, but expected %s" - % (pubkey.address, address)) + raise KeyAddressMismatch("UID %s found, but expected %s" + % (pubkey.address, address)) + + pubkey.validation = validation self.put_key(pubkey) from ._version import get_versions diff --git a/keymanager/src/leap/keymanager/errors.py b/keymanager/src/leap/keymanager/errors.py index ebe4fd5e..f8965823 100644 --- a/keymanager/src/leap/keymanager/errors.py +++ b/keymanager/src/leap/keymanager/errors.py @@ -96,3 +96,9 @@ class KeyFingerprintMismatch(Exception): """ A mismatch between fingerprints. """ + + +class KeyNotValidUpgrade(Exception): + """ + Already existing key can not be upgraded with the new key + """ diff --git a/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py index ec1bfeb4..ecb0a36a 100644 --- a/keymanager/src/leap/keymanager/keys.py +++ b/keymanager/src/leap/keymanager/keys.py @@ -25,12 +25,17 @@ try: import simplejson as json except ImportError: import json # noqa +import logging import re from abc import ABCMeta, abstractmethod from leap.common.check import leap_assert +from leap.keymanager.validation import ValidationLevel, toValidationLevel + +logger = logging.getLogger(__name__) + # # Dictionary keys used for storing cryptographic keys. @@ -106,6 +111,13 @@ def build_key_from_dict(kClass, address, kdict): leap_assert( address == kdict[KEY_ADDRESS_KEY], 'Wrong address in key data.') + try: + validation = toValidationLevel(kdict[KEY_VALIDATION_KEY]) + except ValueError: + logger.error("Not valid validation level (%s) for key %s", + (kdict[KEY_VALIDATION_KEY], kdict[KEY_ID_KEY])) + validation = ValidationLevel.Weak_Chain + return kClass( address, key_id=kdict[KEY_ID_KEY], @@ -116,7 +128,7 @@ def build_key_from_dict(kClass, address, kdict): expiry_date=kdict[KEY_EXPIRY_DATE_KEY], first_seen_at=kdict[KEY_FIRST_SEEN_AT_KEY], last_audited_at=kdict[KEY_LAST_AUDITED_AT_KEY], - validation=kdict[KEY_VALIDATION_KEY], # TODO: verify for validation. + validation=validation, ) @@ -173,7 +185,7 @@ class EncryptionKey(object): KEY_PRIVATE_KEY: self.private, KEY_LENGTH_KEY: self.length, KEY_EXPIRY_DATE_KEY: self.expiry_date, - KEY_VALIDATION_KEY: self.validation, + KEY_VALIDATION_KEY: str(self.validation), KEY_FIRST_SEEN_AT_KEY: self.first_seen_at, KEY_LAST_AUDITED_AT_KEY: self.last_audited_at, KEY_TAGS_KEY: [KEYMANAGER_KEY_TAG], diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 6a825cda..57a7754a 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -38,6 +38,7 @@ from leap.keymanager.keys import ( KEYMANAGER_KEY_TAG, TAGS_ADDRESS_PRIVATE_INDEX, ) +from leap.keymanager.validation import ValidationLevel logger = logging.getLogger(__name__) @@ -183,7 +184,7 @@ def _build_key_from_gpg(address, key, key_data): private=True if key['type'] == 'sec' else False, length=key['length'], expiry_date=key['expires'], - validation=None, # TODO: verify for validation. + validation=ValidationLevel.Weak_Chain, ) diff --git a/keymanager/src/leap/keymanager/tests/__init__.py b/keymanager/src/leap/keymanager/tests/__init__.py index e69de29b..1ea33b57 100644 --- a/keymanager/src/leap/keymanager/tests/__init__.py +++ b/keymanager/src/leap/keymanager/tests/__init__.py @@ -0,0 +1,236 @@ +# -*- coding: utf-8 -*- +# test_keymanager.py +# Copyright (C) 2013 LEAP +# +# 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 . +""" +Base classes for the Key Manager tests. +""" + +from mock import Mock + +from leap.common.testing.basetest import BaseLeapTest +from leap.soledad.client import Soledad +from leap.keymanager import KeyManager + + +ADDRESS = 'leap@leap.se' +# XXX discover the gpg binary path +GPG_BINARY_PATH = '/usr/bin/gpg' + + +class KeyManagerWithSoledadTestCase(BaseLeapTest): + + def setUp(self): + # mock key fetching and storing so Soledad doesn't fail when trying to + # reach the server. + Soledad._get_secrets_from_shared_db = Mock(return_value=None) + Soledad._put_secrets_in_shared_db = Mock(return_value=None) + + class MockSharedDB(object): + + get_doc = Mock(return_value=None) + put_doc = Mock() + lock = Mock(return_value=('atoken', 300)) + unlock = Mock(return_value=True) + + def __call__(self): + return self + + Soledad._shared_db = MockSharedDB() + + self._soledad = Soledad( + u"leap@leap.se", + u"123456", + secrets_path=self.tempdir + "/secret.gpg", + local_db_path=self.tempdir + "/soledad.u1db", + server_url='', + cert_file=None, + auth_token=None, + ) + + def tearDown(self): + km = self._key_manager() + for key in km.get_all_keys(): + km._wrapper_map[key.__class__].delete_key(key) + for key in km.get_all_keys(private=True): + km._wrapper_map[key.__class__].delete_key(key) + + def _key_manager(self, user=ADDRESS, url='', token=None): + return KeyManager(user, url, self._soledad, token=token, + gpgbinary=GPG_BINARY_PATH) + + +# key 24D18DDF: public key "Leap Test Key " +KEY_FINGERPRINT = "E36E738D69173C13D709E44F2F455E2824D18DDF" +PUBLIC_KEY = """ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.10 (GNU/Linux) + +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 +=MuOY +-----END PGP PUBLIC KEY BLOCK----- +""" +PRIVATE_KEY = """ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.10 (GNU/Linux) + +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 +=JTFu +-----END PGP PRIVATE KEY BLOCK----- +""" diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 65f8f39f..1bd6a2e6 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -1,4 +1,4 @@ -## -*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # test_keymanager.py # Copyright (C) 2013 LEAP # @@ -23,12 +23,10 @@ Tests for the Key Manager. from mock import Mock from leap.common.testing.basetest import BaseLeapTest -from leap.soledad.client import Soledad from leap.keymanager import ( - KeyManager, openpgp, KeyNotFound, - KeyAttributesDiffer, + KeyAddressMismatch, errors, ) from leap.keymanager.openpgp import OpenPGPKey @@ -36,11 +34,21 @@ from leap.keymanager.keys import ( is_address, build_key_from_dict, ) +from leap.keymanager.validation import ( + ValidationLevel, + toValidationLevel +) +from leap.keymanager.tests import ( + KeyManagerWithSoledadTestCase, + ADDRESS, + KEY_FINGERPRINT, + PUBLIC_KEY, + PRIVATE_KEY, + GPG_BINARY_PATH +) -ADDRESS = 'leap@leap.se' ADDRESS_2 = 'anotheruser@leap.se' -GPG_BINARY_PATH = '/usr/bin/gpg' class KeyManagerUtilTestCase(BaseLeapTest): @@ -76,7 +84,7 @@ class KeyManagerUtilTestCase(BaseLeapTest): 'expiry_date': 'expiry_date', 'first_seen_at': 'first_seen_at', 'last_audited_at': 'last_audited_at', - 'validation': 'validation', + 'validation': str(ValidationLevel.Weak_Chain), } key = build_key_from_dict(OpenPGPKey, ADDRESS, kdict) self.assertEqual( @@ -107,52 +115,10 @@ class KeyManagerUtilTestCase(BaseLeapTest): kdict['last_audited_at'], key.last_audited_at, 'Wrong data in key.') self.assertEqual( - kdict['validation'], key.validation, + toValidationLevel(kdict['validation']), key.validation, 'Wrong data in key.') -class KeyManagerWithSoledadTestCase(BaseLeapTest): - - def setUp(self): - # mock key fetching and storing so Soledad doesn't fail when trying to - # reach the server. - Soledad._get_secrets_from_shared_db = Mock(return_value=None) - Soledad._put_secrets_in_shared_db = Mock(return_value=None) - - class MockSharedDB(object): - - get_doc = Mock(return_value=None) - put_doc = Mock() - lock = Mock(return_value=('atoken', 300)) - unlock = Mock(return_value=True) - - def __call__(self): - return self - - Soledad._shared_db = MockSharedDB() - - self._soledad = Soledad( - u"leap@leap.se", - u"123456", - secrets_path=self.tempdir + "/secret.gpg", - local_db_path=self.tempdir + "/soledad.u1db", - server_url='', - cert_file=None, - auth_token=None, - ) - - def tearDown(self): - km = self._key_manager() - for key in km.get_all_keys(): - km._wrapper_map[key.__class__].delete_key(key) - for key in km.get_all_keys(private=True): - km._wrapper_map[key.__class__].delete_key(key) - - def _key_manager(self, user=ADDRESS, url='', token=None): - return KeyManager(user, url, self._soledad, token=token, - gpgbinary=GPG_BINARY_PATH) - - class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def _test_openpgp_gen_key(self): @@ -220,7 +186,6 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): KeyNotFound, pgp.get_key, ADDRESS, private=True) pgp.put_ascii_key(PRIVATE_KEY) privkey = pgp.get_key(ADDRESS, private=True) - plaintext = pgp.decrypt(cyphertext, privkey) pgp.delete_key(pubkey) pgp.delete_key(privkey) self.assertRaises( @@ -473,7 +438,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): """ km = self._key_manager(url='http://nickserver.domain') - km.put_key(PUBLIC_KEY) + km.put_raw_key(PUBLIC_KEY, OpenPGPKey) key = km.get_key(ADDRESS, OpenPGPKey) self.assertIsInstance(key, OpenPGPKey) self.assertEqual(ADDRESS, key.address) @@ -524,7 +489,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km._fetcher.get = Mock(return_value=Response()) km.ca_cert_path = 'cacertpath' - self.assertRaises(KeyAttributesDiffer, km.fetch_key, + self.assertRaises(KeyAddressMismatch, km.fetch_key, ADDRESS_2, "http://site.domain/key", OpenPGPKey) @@ -568,169 +533,6 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): # Key material for testing -# key 24D18DDF: public key "Leap Test Key " -KEY_FINGERPRINT = "E36E738D69173C13D709E44F2F455E2824D18DDF" -PUBLIC_KEY = """ ------BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1.4.10 (GNU/Linux) - -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 -=MuOY ------END PGP PUBLIC KEY BLOCK----- -""" -PRIVATE_KEY = """ ------BEGIN PGP PRIVATE KEY BLOCK----- -Version: GnuPG v1.4.10 (GNU/Linux) - -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 -=JTFu ------END PGP PRIVATE KEY BLOCK----- -""" - # key 7FEE575A: public key "anotheruser " PUBLIC_KEY_2 = """ -----BEGIN PGP PUBLIC KEY BLOCK----- diff --git a/keymanager/src/leap/keymanager/tests/test_validation.py b/keymanager/src/leap/keymanager/tests/test_validation.py new file mode 100644 index 00000000..c7170abb --- /dev/null +++ b/keymanager/src/leap/keymanager/tests/test_validation.py @@ -0,0 +1,150 @@ +# -*- coding: utf-8 -*- +# __init__.py +# Copyright (C) 2014 LEAP +# +# 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 . +""" +Tests for the Validation Levels +""" + +from leap.keymanager.openpgp import OpenPGPKey +from leap.keymanager.errors import ( + KeyNotValidUpgrade +) +from leap.keymanager.tests import ( + KeyManagerWithSoledadTestCase, + ADDRESS, + PUBLIC_KEY, + KEY_FINGERPRINT +) +from leap.keymanager.validation import ValidationLevel + + +class ValidationLevelTestCase(KeyManagerWithSoledadTestCase): + + def test_none_old_key(self): + km = self._key_manager() + km.put_raw_key(PUBLIC_KEY, OpenPGPKey) + key = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + self.assertEqual(key.fingerprint, KEY_FINGERPRINT) + + def test_cant_upgrade(self): + km = self._key_manager() + km.put_raw_key(PUBLIC_KEY, OpenPGPKey, + validation=ValidationLevel.Provider_Trust) + self.assertRaises(KeyNotValidUpgrade, km.put_raw_key, UNRELATED_KEY, + OpenPGPKey) + + def test_fingerprint_level(self): + km = self._key_manager() + km.put_raw_key(PUBLIC_KEY, OpenPGPKey) + km.put_raw_key(UNRELATED_KEY, OpenPGPKey, + validation=ValidationLevel.Fingerprint) + key = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + self.assertEqual(key.fingerprint, UNRELATED_FINGERPRINT) + + def test_expired_key(self): + km = self._key_manager() + km.put_raw_key(EXPIRED_KEY, OpenPGPKey) + km.put_raw_key(UNRELATED_KEY, OpenPGPKey) + key = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + self.assertEqual(key.fingerprint, UNRELATED_FINGERPRINT) + + def test_expired_fail_lower_level(self): + km = self._key_manager() + km.put_raw_key(EXPIRED_KEY, OpenPGPKey, + validation=ValidationLevel.Third_Party_Endorsement) + self.assertRaises( + KeyNotValidUpgrade, + km.put_raw_key, + UNRELATED_KEY, + OpenPGPKey, + validation=ValidationLevel.Provider_Trust) + + +# Key material for testing + +# key 901FBCA5: public key "Leap Test Key " +UNRELATED_FINGERPRINT = "ABCCD9C8270B6A8D5633FAC9D04DB2E4901FBCA5" +UNRELATED_KEY = """ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +mQENBFQ9VDoBCACbKflcEhUXZULOT4Fwc2ifRUllJpusd2uX5oeDlZdZ15uLY2eF +LcxnAdIWkI/PsXimh0ev/Pf4oCynfmt02I3c2d9F0N6JXWnRiP+p098oPOcqeEqL +N3CrkH1RVnEXNeJ/Fu7tkD61SBXl1MytMfcHyhN5arg8OcVAjcmghX53+92jFhC9 +8ss87H/qEe5vEX/ahP3tiL5ULvaS4GIX+XB0O3yCVdRoRG9lqMIBP/ZqCkKrNll8 +dT12a6ByG/rWharZUeUETiM4Y+JjDUUaEC2YhNF9k52JNGanLH9LTTtlKy5WTT+E +C6T6VMAtkwcBDpkXr5sBB/N+Y1z0Fp359lIXABEBAAG0HExlYXAgVGVzdCBLZXkg +PGxlYXBAbGVhcC5zZT6JATgEEwECACIFAlQ9VDoCGwMGCwkIBwMCBhUIAgkKCwQW +AgMBAh4BAheAAAoJENBNsuSQH7ylsSUIAIxUFbkeTdHbCF/LVA2U+ktnR1iVikAY +vFK+U+Bto11/AO4Kew2eWniDch/sqLQOoSydtP42z2z3/Al3u7LhQ8bElQHPDY78 +t49qweyJi00V3vCKCdWwPJnPM5eJOIrZHCbwIgeXCsXxVNJVyziVqMuum+px1h2d +1YJZXYejT8rzwa3yBPAsGWRAWETeTvUuyjPMFa59scbnaDuY+bwQ2r/qG9m7UyHU +h2kAHC5sf1rixVOY6rLhw75gQHE/L2BZJRfVsDQqIpEMh2OgMfNbL928jncjwQvc +/IXXwSUx7y50ll+uNh+TVLf0MlUjKdHmHqnGBMlIIWojWJuKxYmOOoO5AQ0EVD1U +OgEIAM/TlhWVSI+tl5XBUAcf60RxjpHQkmdfq1i1jgwUgu/638EKzBfLcnRYX8Rn +DO9CWnHcql/4hp226fIWZN/SyReE81n7UkLDMAglhHgiezHMSH1GYVu4IlfpLVXn +brLVo83KioH5MPFWmZv5tigpU/G8dTx9yVGv1//YW2qqRYYqeIKJfapBaY/bNqyD +vYRfZo1K2brtHx4bToY6mALRF4ruV5SVZGS69e4Sh692C2pXSVbCpRhQ/2WnvkZH +leFIdmNmQN61MC1k26A620Rm+pAsXX71dln0u96xbrCgEVbi6ccfXzbFKtVmThVB +w11CLvVTviOm99TmcgpmDS4cf08AEQEAAYkBHwQYAQIACQUCVD1UOgIbDAAKCRDQ +TbLkkB+8pR+fB/0SeTcRr1duN7VYWdtng1+jO0ornIBtUraglN01dEEmiwN83DTi +J37i+nll+4is7BtiXqhumRptKh1v8UUMyFX/rjjoojCJBg5NExsiOYl3O4le68oF +3+XC+n7yrlyNmI15+3dcQmC9F6HN8EBZgrn5YPKGIOMHTGatB5PryMKg2IKiN5GZ +E0hmrOQgmcGrkeqysKACQYUHTasSk2IY1l1G5YQglqCaBh4+UC82Dmg5fTBbHjxP +YhhojkP4aD/0YW7dgql3nzYqvPCAjBH1Cf6rA9HvAJwUP9Ig/okcrrPEKm638+mG ++vNIuLqIkA4oFLBAAIrgMiQZ+NZz9uD6DJE7 +=FO7G +-----END PGP PUBLIC KEY BLOCK----- +""" + +# key A1885A7C: public key "Leap Test Key " +EXPIRED_FINGERPRINT = "7C1F68B0E14157B09B5F4ADE6F15F004A1885A7C" +EXPIRED_KEY = """ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.12 (GNU/Linux) + +mQENBBvrfd0BCADGNpspaNhsbhSjKioCWrE2MTTYC+Sdpes22RabdhQyOCWvlSbj +b8p0y3kmnMOtVBT+c22/w7eu2YBfIpS4RswgE5ypr/1kZLFQueVe/cp29GjPvLwJ +82A3EOHcmXs8rSJ76h2bnkySvbJawz9rwCcaXhpdAwC+sjWvbqiwZYEL+90I4Xp3 +acDh9vNtPxDCg5RdI0bfdIEBGgHTfsda3kWGvo1wH5SgrTRq0+EcTI7aJgkMmM/A +IhnpACE52NvGdG9eB3x7xyQFsQqK8F0XvEev2UJH4SR7vb+Z7FNTJKCy6likYbSV +wGGFuowFSESnzXuUI6PcjyuO6FUbMgeM5euFABEBAAG0HExlYXAgVGVzdCBLZXkg +PGxlYXBAbGVhcC5zZT6JAT4EEwECACgFAhvrfd0CGwMFCQABUYAGCwkIBwMCBhUI +AgkKCwQWAgMBAh4BAheAAAoJEG8V8AShiFp8VNkH/iCQcXkTfMOVlL2rQRyZtJEO +Lr5uTyyY8O6ubeNCHqZzlIopiPAsv4hIYjjMDvOfZ9R53YgmbacUm0rvh1B4MSUf +k+sa9/tequ3y44LUKp7AB6NyyLgVOU5ngl2w+bi7CgXAep3oP4joYKcU0mmSAc2S +2Gj85DVqP0kdzNs47esvyj7g1TOfdBwmLsTx/219H+w3dNBeyCQWkYCYNh7MX/Ba +SZ+P0xr4FetcOVPM3wAzUtDG7hKsgccoIXt0FWhG/nn8cETfGH+o3W/ky7Jktatx +DGDHoZJvAaG2B2ey1pAQlezr8p/O+ZVABiigHk1S+myBHyhlXzUcjhQnEG7aHZ65 +AQ0EG+t93QEIAKqRq/2sBDW4g3FU+11LhixT+GosrfVvnitz3S9k2tBXok/wYpI1 +XeA+kTHiF0LaqoaciDRvkA9DvhDbSrNM1yeuYRyZiHlTmoPZ/Fkl60oA2cyLd1L5 +sXbuipY3TEiakugdSU4rzgi0hFycm6Go6yq2G6eC6UALvD9CTMdZHw40TadG9xpm +4thYPuJ1kPH8/bkbTi9sLHoApYgL+7ssje8w4epr0qD4IGxeKwJPf/tbTRpnd8w3 +leldixHHKAutNt49p0pkXlORAHRpUmp+KMZhFvCvIPwe9o5mYtMR7sDRxjY61ZEQ +KLyKoh5wsJsaPXBjdG7cf6G/cBcwvnQVUHcAEQEAAYkBJQQYAQIADwUCG+t93QIb +DAUJAAFRgAAKCRBvFfAEoYhafOPgB/9z4YCyT/N0262HtegHykhsyykuqEeNb1LV +D9INcP+RbCX/0IjFgP4DTMPP7qqF1OBwR276maALT321Gqxc5HN5YrwxGdmoyBLm +unaQJJlD+7B1C+jnO6r4m44obvJ/NMERxVyzkXap3J2VgRIO1wNLI9I0sH6Kj5/j +Mgy06OwXDcqIc+jB4sIJ3Tnm8LZ3phJzNEm9mI8Ak0oJ7IEcMndR6DzmRt1rJQcq +K/D7hOG02zvyRhxF27U1qR1MxeU/gNnOx8q4dnVyWB+EiV1sFl4iTOyYHEsoyd7W +Osuse7+NkyUHgMXMVW7cz+nU7iO+ht2rkBtv+Z5LGlzgHTeFjKci +=WhX+ +-----END PGP PUBLIC KEY BLOCK----- +""" + +import unittest +if __name__ == "__main__": + unittest.main() diff --git a/keymanager/src/leap/keymanager/validation.py b/keymanager/src/leap/keymanager/validation.py new file mode 100644 index 00000000..6dceb787 --- /dev/null +++ b/keymanager/src/leap/keymanager/validation.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +# __init__.py +# Copyright (C) 2014 LEAP +# +# 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 . + +""" +Validation levels implementation for key managment. + +See: + https://lists.riseup.net/www/arc/leap-discuss/2014-09/msg00000.html +""" + + +from datetime import datetime +from enum import Enum + + +ValidationLevel = Enum( + "Weak_Chain", + "Provider_Trust", + "Provider_Endorsement", + "Third_Party_Endorsement", + "Third_Party_Consensus", + "Historically_Auditing", + "Known_Key", + "Fingerprint") + + +def toValidationLevel(value): + """ + Convert a string representation of a validation level into + C{ValidationLevel} + + :param value: validation level + :type value: str + :rtype: ValidationLevel + :raises ValueError: if C{value} is not a validation level + """ + for level in ValidationLevel: + if value == str(level): + return level + raise ValueError("Not valid validation level: %s" % (value,)) + + +def can_upgrade(new_key, old_key): + """ + :type new_key: EncryptionKey + :type old_key: EncryptionKey + :rtype: bool + """ + # XXX not succesfully used and strict high validation level (#6211) + # XXX implement key signature checking (#6120) + + # First contact + if old_key is None: + return True + + if new_key.address != old_key.address: + # XXX how do we map multiple IDs? (#6212) + return False + + # An update of the same key + if new_key.fingerprint == old_key.fingerprint: + # XXX wich one is newer? is that a downgrade attack? (#6210) + return True + + # Manually verified fingerprint + if new_key.validation == ValidationLevel.Fingerprint: + return True + + # Expired key and higher validation level + if old_key.expiry_date: + old_expiry_date = datetime.fromtimestamp(int(old_key.expiry_date)) + if (old_expiry_date < datetime.now() and + new_key.validation >= old_key.validation): + return True + + # No expiration date and higher validation level + elif new_key.validation >= old_key.validation: + return True + + return False -- cgit v1.2.3 From 12a1627e820ed543158678326032814daebe139d Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 20 Oct 2014 18:35:11 -0500 Subject: Update doc string of OpenPGPScheme._temporary_gpgwrapper --- keymanager/src/leap/keymanager/openpgp.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 57a7754a..e84cd297 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -452,10 +452,8 @@ class OpenPGPScheme(EncryptionScheme): Return a gpg wrapper that implements the context manager protocol and contains C{keys}. - :param key_data: ASCII armored key data. - :type key_data: str - :param gpgbinary: Name for GnuPG binary executable. - :type gpgbinary: C{str} + :param keys: keys to conform the keyring. + :type key: list(OpenPGPKey) :return: a TempGPGWrapper instance :rtype: TempGPGWrapper -- cgit v1.2.3 From 1fac28ed61bde2b8db8cff8aea45b20705a072e3 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Thu, 30 Oct 2014 21:54:32 -0600 Subject: Merge keys when updating an exisiting key This is needed to prevent roll back attacks where the attacker push us to accept a key with an old expiration date that could be use to push an untrusted key when after it's expiration. --- keymanager/src/leap/keymanager/openpgp.py | 12 ++++++ .../src/leap/keymanager/tests/test_validation.py | 43 ++++++++++++++++++++++ keymanager/src/leap/keymanager/validation.py | 1 - 3 files changed, 55 insertions(+), 1 deletion(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index e84cd297..f86b35d8 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -37,6 +37,8 @@ from leap.keymanager.keys import ( build_key_from_dict, KEYMANAGER_KEY_TAG, TAGS_ADDRESS_PRIVATE_INDEX, + KEY_FINGERPRINT_KEY, + KEY_DATA_KEY, ) from leap.keymanager.validation import ValidationLevel @@ -394,6 +396,16 @@ class OpenPGPScheme(EncryptionScheme): if doc is None: self._soledad.create_doc_from_json(key.get_json()) else: + if key.fingerprint == doc.content[KEY_FINGERPRINT_KEY]: + # in case of an update of the key merge them with gnupg + with self._temporary_gpgwrapper() as gpg: + gpg.import_keys(doc.content[KEY_DATA_KEY]) + gpg.import_keys(key.key_data) + gpgkey = gpg.list_keys(secret=key.private).pop() + key = _build_key_from_gpg( + key.address, gpgkey, + gpg.export_keys(gpgkey['fingerprint'], + secret=key.private)) doc.set_json(key.get_json()) self._soledad.put_doc(doc) diff --git a/keymanager/src/leap/keymanager/tests/test_validation.py b/keymanager/src/leap/keymanager/tests/test_validation.py index c7170abb..0f4d81ab 100644 --- a/keymanager/src/leap/keymanager/tests/test_validation.py +++ b/keymanager/src/leap/keymanager/tests/test_validation.py @@ -72,6 +72,13 @@ class ValidationLevelTestCase(KeyManagerWithSoledadTestCase): OpenPGPKey, validation=ValidationLevel.Provider_Trust) + def test_roll_back(self): + km = self._key_manager() + km.put_raw_key(EXPIRED_KEY_UPDATED, OpenPGPKey) + km.put_raw_key(EXPIRED_KEY, OpenPGPKey) + key = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + self.assertEqual(key.expiry_date, EXPIRED_KEY_NEW_EXPIRY_DATE) + # Key material for testing @@ -144,6 +151,42 @@ Osuse7+NkyUHgMXMVW7cz+nU7iO+ht2rkBtv+Z5LGlzgHTeFjKci =WhX+ -----END PGP PUBLIC KEY BLOCK----- """ +# updated expiration date +# Tue 24 Oct 2034 05:13:00 PM BST +EXPIRED_KEY_NEW_EXPIRY_DATE = "2045319180" +EXPIRED_KEY_UPDATED = """ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.12 (GNU/Linux) + +mQENBBvrfd0BCADGNpspaNhsbhSjKioCWrE2MTTYC+Sdpes22RabdhQyOCWvlSbj +b8p0y3kmnMOtVBT+c22/w7eu2YBfIpS4RswgE5ypr/1kZLFQueVe/cp29GjPvLwJ +82A3EOHcmXs8rSJ76h2bnkySvbJawz9rwCcaXhpdAwC+sjWvbqiwZYEL+90I4Xp3 +acDh9vNtPxDCg5RdI0bfdIEBGgHTfsda3kWGvo1wH5SgrTRq0+EcTI7aJgkMmM/A +IhnpACE52NvGdG9eB3x7xyQFsQqK8F0XvEev2UJH4SR7vb+Z7FNTJKCy6likYbSV +wGGFuowFSESnzXuUI6PcjyuO6FUbMgeM5euFABEBAAG0HExlYXAgVGVzdCBLZXkg +PGxlYXBAbGVhcC5zZT6JAT4EEwECACgCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4B +AheABQJUURIXBQld/ZovAAoJEG8V8AShiFp8xUcIALcAHZbaxvyhHRGOrwDddbH0 +fFDK0AqKTsIT7y4D/HLFCP5zG3Ck7qGPZdkHXZfzq8rIb+zUjW3oJIVI1IucHxG2 +T5kppa8RFCBAFlRWYf6R3isX3YL0d3QSragjoxRNPcHNU8ALHcvfSonFHBoi4fH4 +4rvgksAiT68SsdPaoXDlabx5T15evu/7T5e/DGMQVPMxiaSuSQhbOKuMk2wcFdmL +tBYHLZPa54hHPNhEDyxLgtKKph0gObk9ojKfH9kPvLveIcpS5CqTJfN/kqBz7CJW +wEeAi2iG3H1OEB25aCUdTxXSRNlGqEgcWPaWxtc1RzlARu7LB64OUZuRy4puiAG5 +AQ0EG+t93QEIAKqRq/2sBDW4g3FU+11LhixT+GosrfVvnitz3S9k2tBXok/wYpI1 +XeA+kTHiF0LaqoaciDRvkA9DvhDbSrNM1yeuYRyZiHlTmoPZ/Fkl60oA2cyLd1L5 +sXbuipY3TEiakugdSU4rzgi0hFycm6Go6yq2G6eC6UALvD9CTMdZHw40TadG9xpm +4thYPuJ1kPH8/bkbTi9sLHoApYgL+7ssje8w4epr0qD4IGxeKwJPf/tbTRpnd8w3 +leldixHHKAutNt49p0pkXlORAHRpUmp+KMZhFvCvIPwe9o5mYtMR7sDRxjY61ZEQ +KLyKoh5wsJsaPXBjdG7cf6G/cBcwvnQVUHcAEQEAAYkBJQQYAQIADwUCG+t93QIb +DAUJAAFRgAAKCRBvFfAEoYhafOPgB/9z4YCyT/N0262HtegHykhsyykuqEeNb1LV +D9INcP+RbCX/0IjFgP4DTMPP7qqF1OBwR276maALT321Gqxc5HN5YrwxGdmoyBLm +unaQJJlD+7B1C+jnO6r4m44obvJ/NMERxVyzkXap3J2VgRIO1wNLI9I0sH6Kj5/j +Mgy06OwXDcqIc+jB4sIJ3Tnm8LZ3phJzNEm9mI8Ak0oJ7IEcMndR6DzmRt1rJQcq +K/D7hOG02zvyRhxF27U1qR1MxeU/gNnOx8q4dnVyWB+EiV1sFl4iTOyYHEsoyd7W +Osuse7+NkyUHgMXMVW7cz+nU7iO+ht2rkBtv+Z5LGlzgHTeFjKci +=79Ll +-----END PGP PUBLIC KEY BLOCK----- +""" + import unittest if __name__ == "__main__": diff --git a/keymanager/src/leap/keymanager/validation.py b/keymanager/src/leap/keymanager/validation.py index 6dceb787..7d68966e 100644 --- a/keymanager/src/leap/keymanager/validation.py +++ b/keymanager/src/leap/keymanager/validation.py @@ -73,7 +73,6 @@ def can_upgrade(new_key, old_key): # An update of the same key if new_key.fingerprint == old_key.fingerprint: - # XXX wich one is newer? is that a downgrade attack? (#6210) return True # Manually verified fingerprint -- cgit v1.2.3 From fe41ebea35e17abb402b2ba0d786897ac102e760 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Thu, 30 Oct 2014 21:57:48 -0600 Subject: Remove outdated comment --- keymanager/src/leap/keymanager/keys.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py index ecb0a36a..b5c9118d 100644 --- a/keymanager/src/leap/keymanager/keys.py +++ b/keymanager/src/leap/keymanager/keys.py @@ -141,16 +141,7 @@ class EncryptionKey(object): Abstract class for encryption keys. A key is "validated" if the nicknym agent has bound the user address to a - public key. Nicknym supports three different levels of key validation: - - * Level 3 - path trusted: A path of cryptographic signatures can be traced - from a trusted key to the key under evaluation. By default, only the - provider key from the user's provider is a "trusted key". - * level 2 - provider signed: The key has been signed by a provider key for - the same domain, but the provider key is not validated using a trust - path (i.e. it is only registered) - * level 1 - registered: The key has been encountered and saved, it has no - signatures (that are meaningful to the nicknym agent). + public key. """ __metaclass__ = ABCMeta -- cgit v1.2.3 From 68565379fe73fb8a07e96925785ab9a733880b4b Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Tue, 4 Nov 2014 11:53:56 -0600 Subject: Use datetime for key expiration --- keymanager/src/leap/keymanager/keys.py | 13 +++++++++++-- keymanager/src/leap/keymanager/openpgp.py | 7 ++++++- keymanager/src/leap/keymanager/tests/test_keymanager.py | 4 ++-- keymanager/src/leap/keymanager/tests/test_validation.py | 5 +++-- keymanager/src/leap/keymanager/validation.py | 9 ++++----- 5 files changed, 26 insertions(+), 12 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py index b5c9118d..a61a8c79 100644 --- a/keymanager/src/leap/keymanager/keys.py +++ b/keymanager/src/leap/keymanager/keys.py @@ -30,6 +30,7 @@ import re from abc import ABCMeta, abstractmethod +from datetime import datetime from leap.common.check import leap_assert from leap.keymanager.validation import ValidationLevel, toValidationLevel @@ -118,6 +119,10 @@ def build_key_from_dict(kClass, address, kdict): (kdict[KEY_VALIDATION_KEY], kdict[KEY_ID_KEY])) validation = ValidationLevel.Weak_Chain + expiry_date = None + if kdict[KEY_EXPIRY_DATE_KEY]: + expiry_date = datetime.fromtimestamp(int(kdict[KEY_EXPIRY_DATE_KEY])) + return kClass( address, key_id=kdict[KEY_ID_KEY], @@ -125,7 +130,7 @@ def build_key_from_dict(kClass, address, kdict): key_data=kdict[KEY_DATA_KEY], private=kdict[KEY_PRIVATE_KEY], length=kdict[KEY_LENGTH_KEY], - expiry_date=kdict[KEY_EXPIRY_DATE_KEY], + expiry_date=expiry_date, first_seen_at=kdict[KEY_FIRST_SEEN_AT_KEY], last_audited_at=kdict[KEY_LAST_AUDITED_AT_KEY], validation=validation, @@ -167,6 +172,10 @@ class EncryptionKey(object): :return: The JSON string describing this key. :rtype: str """ + expiry_str = "" + if self.expiry_date is not None: + expiry_str = self.expiry_date.strftime("%s") + return json.dumps({ KEY_ADDRESS_KEY: self.address, KEY_TYPE_KEY: str(self.__class__), @@ -175,7 +184,7 @@ class EncryptionKey(object): KEY_DATA_KEY: self.key_data, KEY_PRIVATE_KEY: self.private, KEY_LENGTH_KEY: self.length, - KEY_EXPIRY_DATE_KEY: self.expiry_date, + KEY_EXPIRY_DATE_KEY: expiry_str, KEY_VALIDATION_KEY: str(self.validation), KEY_FIRST_SEEN_AT_KEY: self.first_seen_at, KEY_LAST_AUDITED_AT_KEY: self.last_audited_at, diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index f86b35d8..d3c305e2 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -25,6 +25,7 @@ import tempfile import io +from datetime import datetime from gnupg import GPG from gnupg.gnupg import GPGUtilities @@ -178,6 +179,10 @@ def _build_key_from_gpg(address, key, key_data): :return: An instance of the key. :rtype: OpenPGPKey """ + expiry_date = None + if key['expires']: + expiry_date = datetime.fromtimestamp(int(key['expires'])) + return OpenPGPKey( address, key_id=key['keyid'], @@ -185,7 +190,7 @@ def _build_key_from_gpg(address, key, key_data): key_data=key_data, private=True if key['type'] == 'sec' else False, length=key['length'], - expiry_date=key['expires'], + expiry_date=expiry_date, validation=ValidationLevel.Weak_Chain, ) diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 1bd6a2e6..6a877bcf 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -81,7 +81,7 @@ class KeyManagerUtilTestCase(BaseLeapTest): 'key_data': 'key_data', 'private': 'private', 'length': 'length', - 'expiry_date': 'expiry_date', + 'expiry_date': '', 'first_seen_at': 'first_seen_at', 'last_audited_at': 'last_audited_at', 'validation': str(ValidationLevel.Weak_Chain), @@ -106,7 +106,7 @@ class KeyManagerUtilTestCase(BaseLeapTest): kdict['length'], key.length, 'Wrong data in key.') self.assertEqual( - kdict['expiry_date'], key.expiry_date, + None, key.expiry_date, 'Wrong data in key.') self.assertEqual( kdict['first_seen_at'], key.first_seen_at, diff --git a/keymanager/src/leap/keymanager/tests/test_validation.py b/keymanager/src/leap/keymanager/tests/test_validation.py index 0f4d81ab..3ae873d4 100644 --- a/keymanager/src/leap/keymanager/tests/test_validation.py +++ b/keymanager/src/leap/keymanager/tests/test_validation.py @@ -18,6 +18,8 @@ Tests for the Validation Levels """ +from datetime import datetime + from leap.keymanager.openpgp import OpenPGPKey from leap.keymanager.errors import ( KeyNotValidUpgrade @@ -152,8 +154,7 @@ Osuse7+NkyUHgMXMVW7cz+nU7iO+ht2rkBtv+Z5LGlzgHTeFjKci -----END PGP PUBLIC KEY BLOCK----- """ # updated expiration date -# Tue 24 Oct 2034 05:13:00 PM BST -EXPIRED_KEY_NEW_EXPIRY_DATE = "2045319180" +EXPIRED_KEY_NEW_EXPIRY_DATE = datetime.fromtimestamp(2045319180) EXPIRED_KEY_UPDATED = """ -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1.4.12 (GNU/Linux) diff --git a/keymanager/src/leap/keymanager/validation.py b/keymanager/src/leap/keymanager/validation.py index 7d68966e..cf5b4a83 100644 --- a/keymanager/src/leap/keymanager/validation.py +++ b/keymanager/src/leap/keymanager/validation.py @@ -80,11 +80,10 @@ def can_upgrade(new_key, old_key): return True # Expired key and higher validation level - if old_key.expiry_date: - old_expiry_date = datetime.fromtimestamp(int(old_key.expiry_date)) - if (old_expiry_date < datetime.now() and - new_key.validation >= old_key.validation): - return True + if (old_key.expiry_date is not None and + old_key.expiry_date < datetime.now() and + new_key.validation >= old_key.validation): + return True # No expiration date and higher validation level elif new_key.validation >= old_key.validation: -- cgit v1.2.3 From 88b2416234ed88bc7652c452f779c95f17bf210f Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Thu, 6 Nov 2014 00:47:32 -0600 Subject: Implement the new encryption-key soledad document --- keymanager/src/leap/keymanager/__init__.py | 10 ++-- keymanager/src/leap/keymanager/keys.py | 60 +++++++++++++++------- keymanager/src/leap/keymanager/openpgp.py | 22 ++++---- .../src/leap/keymanager/tests/test_keymanager.py | 55 +++++++++++--------- 4 files changed, 89 insertions(+), 58 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 156aaf86..53dd9a78 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -319,7 +319,7 @@ class KeyManager(object): return map( lambda doc: build_key_from_dict( self._key_class_from_type(doc.content['type']), - doc.content['address'], + doc.content['address'][0], doc.content), self._soledad.get_from_index( TAGS_PRIVATE_INDEX, @@ -529,7 +529,7 @@ class KeyManager(object): new one is not a valid update for it """ try: - old_key = self._wrapper_map[type(key)].get_key(key.address, + old_key = self._wrapper_map[type(key)].get_key(key.address[0], private=key.private) except KeyNotFound: old_key = None @@ -564,7 +564,7 @@ class KeyManager(object): new one is not a valid update for it """ pubkey, _ = self._wrapper_map[ktype].parse_ascii_key(key) - if address is not None and address != pubkey.address: + if address is not None and address not in pubkey.address: raise KeyAddressMismatch("Key UID %s, but expected %s" % (pubkey.address, address)) @@ -600,9 +600,9 @@ class KeyManager(object): pubkey, _ = self._wrapper_map[ktype].parse_ascii_key(res.content) if pubkey is None: raise KeyNotFound(uri) - if pubkey.address != address: + if address not in pubkey.address: raise KeyAddressMismatch("UID %s found, but expected %s" - % (pubkey.address, address)) + % (str(pubkey.address), address)) pubkey.validation = validation self.put_key(pubkey) diff --git a/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py index a61a8c79..4952b9b3 100644 --- a/keymanager/src/leap/keymanager/keys.py +++ b/keymanager/src/leap/keymanager/keys.py @@ -27,6 +27,7 @@ except ImportError: import json # noqa import logging import re +import time from abc import ABCMeta, abstractmethod @@ -50,9 +51,11 @@ KEY_DATA_KEY = 'key_data' KEY_PRIVATE_KEY = 'private' KEY_LENGTH_KEY = 'length' KEY_EXPIRY_DATE_KEY = 'expiry_date' -KEY_FIRST_SEEN_AT_KEY = 'first_seen_at' KEY_LAST_AUDITED_AT_KEY = 'last_audited_at' +KEY_REFRESHED_AT_KEY = 'refreshed_at' KEY_VALIDATION_KEY = 'validation' +KEY_ENCR_USED_KEY = 'encr_used' +KEY_SIGN_USED_KEY = 'sign_used' KEY_TAGS_KEY = 'tags' @@ -110,7 +113,7 @@ def build_key_from_dict(kClass, address, kdict): :rtype: C{kClass} """ leap_assert( - address == kdict[KEY_ADDRESS_KEY], + address in kdict[KEY_ADDRESS_KEY], 'Wrong address in key data.') try: validation = toValidationLevel(kdict[KEY_VALIDATION_KEY]) @@ -119,24 +122,40 @@ def build_key_from_dict(kClass, address, kdict): (kdict[KEY_VALIDATION_KEY], kdict[KEY_ID_KEY])) validation = ValidationLevel.Weak_Chain - expiry_date = None - if kdict[KEY_EXPIRY_DATE_KEY]: - expiry_date = datetime.fromtimestamp(int(kdict[KEY_EXPIRY_DATE_KEY])) + expiry_date = _to_datetime(kdict[KEY_EXPIRY_DATE_KEY]) + last_audited_at = _to_datetime(kdict[KEY_LAST_AUDITED_AT_KEY]) + refreshed_at = _to_datetime(kdict[KEY_REFRESHED_AT_KEY]) return kClass( - address, + [address], key_id=kdict[KEY_ID_KEY], fingerprint=kdict[KEY_FINGERPRINT_KEY], key_data=kdict[KEY_DATA_KEY], private=kdict[KEY_PRIVATE_KEY], length=kdict[KEY_LENGTH_KEY], expiry_date=expiry_date, - first_seen_at=kdict[KEY_FIRST_SEEN_AT_KEY], - last_audited_at=kdict[KEY_LAST_AUDITED_AT_KEY], + last_audited_at=last_audited_at, + refreshed_at=refreshed_at, validation=validation, + encr_used=kdict[KEY_ENCR_USED_KEY], + sign_used=kdict[KEY_SIGN_USED_KEY], ) +def _to_datetime(unix_time): + if unix_time != 0: + return datetime.fromtimestamp(unix_time) + else: + return None + + +def _to_unix_time(date): + if date is not None: + return int(time.mktime(date.timetuple())) + else: + return 0 + + # # Abstraction for encryption keys # @@ -151,9 +170,10 @@ class EncryptionKey(object): __metaclass__ = ABCMeta - def __init__(self, address, key_id=None, fingerprint=None, - key_data=None, private=None, length=None, expiry_date=None, - validation=None, first_seen_at=None, last_audited_at=None): + def __init__(self, address, key_id="", fingerprint="", + key_data="", private=False, length=0, expiry_date=None, + validation=ValidationLevel.Weak_Chain, last_audited_at=None, + refreshed_at=None, encr_used=False, sign_used=False): self.address = address self.key_id = key_id self.fingerprint = fingerprint @@ -162,8 +182,10 @@ class EncryptionKey(object): self.length = length self.expiry_date = expiry_date self.validation = validation - self.first_seen_at = first_seen_at self.last_audited_at = last_audited_at + self.refreshed_at = refreshed_at + self.encr_used = encr_used + self.sign_used = sign_used def get_json(self): """ @@ -172,9 +194,9 @@ class EncryptionKey(object): :return: The JSON string describing this key. :rtype: str """ - expiry_str = "" - if self.expiry_date is not None: - expiry_str = self.expiry_date.strftime("%s") + expiry_date = _to_unix_time(self.expiry_date) + last_audited_at = _to_unix_time(self.last_audited_at) + refreshed_at = _to_unix_time(self.refreshed_at) return json.dumps({ KEY_ADDRESS_KEY: self.address, @@ -184,10 +206,12 @@ class EncryptionKey(object): KEY_DATA_KEY: self.key_data, KEY_PRIVATE_KEY: self.private, KEY_LENGTH_KEY: self.length, - KEY_EXPIRY_DATE_KEY: expiry_str, + KEY_EXPIRY_DATE_KEY: expiry_date, + KEY_LAST_AUDITED_AT_KEY: last_audited_at, + KEY_REFRESHED_AT_KEY: refreshed_at, KEY_VALIDATION_KEY: str(self.validation), - KEY_FIRST_SEEN_AT_KEY: self.first_seen_at, - KEY_LAST_AUDITED_AT_KEY: self.last_audited_at, + KEY_ENCR_USED_KEY: self.encr_used, + KEY_SIGN_USED_KEY: self.sign_used, KEY_TAGS_KEY: [KEYMANAGER_KEY_TAG], }) diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index d3c305e2..1160434c 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -41,7 +41,6 @@ from leap.keymanager.keys import ( KEY_FINGERPRINT_KEY, KEY_DATA_KEY, ) -from leap.keymanager.validation import ValidationLevel logger = logging.getLogger(__name__) @@ -109,9 +108,9 @@ class TempGPGWrapper(object): # itself is enough to also have the public key in the keyring, # and we want to count the keys afterwards. - privaddrs = map(lambda privkey: privkey.address, privkeys) + privaddrs = map(lambda privkey: privkey.address[0], privkeys) publkeys = filter( - lambda pubkey: pubkey.address not in privaddrs, publkeys) + lambda pubkey: pubkey.address[0] not in privaddrs, publkeys) listkeys = lambda: self._gpg.list_keys() listsecretkeys = lambda: self._gpg.list_keys(secret=True) @@ -184,14 +183,14 @@ def _build_key_from_gpg(address, key, key_data): expiry_date = datetime.fromtimestamp(int(key['expires'])) return OpenPGPKey( - address, + [address], key_id=key['keyid'], fingerprint=key['fingerprint'], key_data=key_data, private=True if key['type'] == 'sec' else False, - length=key['length'], + length=int(key['length']), expiry_date=expiry_date, - validation=ValidationLevel.Weak_Chain, + refreshed_at=datetime.now(), ) @@ -397,7 +396,7 @@ class OpenPGPScheme(EncryptionScheme): :param key: The key to be stored. :type key: OpenPGPKey """ - doc = self._get_key_doc(key.address, private=key.private) + doc = self._get_key_doc(key.address[0], private=key.private) if doc is None: self._soledad.create_doc_from_json(key.get_json()) else: @@ -408,7 +407,7 @@ class OpenPGPScheme(EncryptionScheme): gpg.import_keys(key.key_data) gpgkey = gpg.list_keys(secret=key.private).pop() key = _build_key_from_gpg( - key.address, gpgkey, + key.address[0], gpgkey, gpg.export_keys(gpgkey['fingerprint'], secret=key.private)) doc.set_json(key.get_json()) @@ -452,12 +451,11 @@ class OpenPGPScheme(EncryptionScheme): :type key: EncryptionKey """ leap_assert_type(key, OpenPGPKey) - stored_key = self.get_key(key.address, private=key.private) - if stored_key is None: + doc = self._get_key_doc(key.address[0], key.private) + if doc is None: raise errors.KeyNotFound(key) - if stored_key.__dict__ != key.__dict__: + if doc.content[KEY_FINGERPRINT_KEY] != key.fingerprint: raise errors.KeyAttributesDiffer(key) - doc = self._get_key_doc(key.address, key.private) self._soledad.delete_doc(doc) # diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 6a877bcf..4daf3465 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -21,6 +21,7 @@ Tests for the Key Manager. """ +from datetime import datetime from mock import Mock from leap.common.testing.basetest import BaseLeapTest from leap.keymanager import ( @@ -75,16 +76,18 @@ class KeyManagerUtilTestCase(BaseLeapTest): def test_build_key_from_dict(self): kdict = { - 'address': ADDRESS, - 'key_id': 'key_id', - 'fingerprint': 'fingerprint', - 'key_data': 'key_data', - 'private': 'private', - 'length': 'length', - 'expiry_date': '', - 'first_seen_at': 'first_seen_at', - 'last_audited_at': 'last_audited_at', + 'address': [ADDRESS], + 'key_id': KEY_FINGERPRINT[-16:], + 'fingerprint': KEY_FINGERPRINT, + 'key_data': PUBLIC_KEY, + 'private': False, + 'length': 4096, + 'expiry_date': 0, + 'last_audited_at': 0, + 'refreshed_at': 1311239602, 'validation': str(ValidationLevel.Weak_Chain), + 'encr_used': False, + 'sign_used': True, } key = build_key_from_dict(OpenPGPKey, ADDRESS, kdict) self.assertEqual( @@ -109,14 +112,20 @@ class KeyManagerUtilTestCase(BaseLeapTest): None, key.expiry_date, 'Wrong data in key.') self.assertEqual( - kdict['first_seen_at'], key.first_seen_at, + None, key.last_audited_at, 'Wrong data in key.') self.assertEqual( - kdict['last_audited_at'], key.last_audited_at, + datetime.fromtimestamp(kdict['refreshed_at']), key.refreshed_at, 'Wrong data in key.') self.assertEqual( toValidationLevel(kdict['validation']), key.validation, 'Wrong data in key.') + self.assertEqual( + kdict['encr_used'], key.encr_used, + 'Wrong data in key.') + self.assertEqual( + kdict['sign_used'], key.sign_used, + 'Wrong data in key.') class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): @@ -127,9 +136,9 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): key = pgp.gen_key('user@leap.se') self.assertIsInstance(key, openpgp.OpenPGPKey) self.assertEqual( - 'user@leap.se', key.address, 'Wrong address bound to key.') + ['user@leap.se'], key.address, 'Wrong address bound to key.') self.assertEqual( - '4096', key.length, 'Wrong key length.') + 4096, key.length, 'Wrong key length.') def test_openpgp_put_delete_key(self): pgp = openpgp.OpenPGPScheme( @@ -147,10 +156,10 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): pgp.put_ascii_key(PUBLIC_KEY) key = pgp.get_key(ADDRESS, private=False) self.assertIsInstance(key, openpgp.OpenPGPKey) + self.assertTrue( + ADDRESS in key.address, 'Wrong address bound to key.') self.assertEqual( - ADDRESS, key.address, 'Wrong address bound to key.') - self.assertEqual( - '4096', key.length, 'Wrong key length.') + 4096, key.length, 'Wrong key length.') pgp.delete_key(key) self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) @@ -162,7 +171,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): self.assertRaises( KeyNotFound, pgp.get_key, ADDRESS, private=True) key = pgp.get_key(ADDRESS, private=False) - self.assertEqual(ADDRESS, key.address) + self.assertTrue(ADDRESS in key.address) self.assertFalse(key.private) self.assertEqual(KEY_FINGERPRINT, key.fingerprint) pgp.delete_key(key) @@ -311,12 +320,12 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): # get public keys keys = km.get_all_keys(False) self.assertEqual(len(keys), 1, 'Wrong number of keys') - self.assertEqual(ADDRESS, keys[0].address) + self.assertTrue(ADDRESS in keys[0].address) self.assertFalse(keys[0].private) # get private keys keys = km.get_all_keys(True) self.assertEqual(len(keys), 1, 'Wrong number of keys') - self.assertEqual(ADDRESS, keys[0].address) + self.assertTrue(ADDRESS in keys[0].address) self.assertTrue(keys[0].private) def test_get_public_key(self): @@ -326,7 +335,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): key = km.get_key(ADDRESS, OpenPGPKey, private=False, fetch_remote=False) self.assertTrue(key is not None) - self.assertEqual(key.address, ADDRESS) + self.assertTrue(ADDRESS in key.address) self.assertEqual( key.fingerprint.lower(), KEY_FINGERPRINT.lower()) self.assertFalse(key.private) @@ -338,7 +347,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): key = km.get_key(ADDRESS, OpenPGPKey, private=True, fetch_remote=False) self.assertTrue(key is not None) - self.assertEqual(key.address, ADDRESS) + self.assertTrue(ADDRESS in key.address) self.assertEqual( key.fingerprint.lower(), KEY_FINGERPRINT.lower()) self.assertTrue(key.private) @@ -430,7 +439,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): # try to get key fetching from server. key = km.get_key(ADDRESS, OpenPGPKey) self.assertIsInstance(key, OpenPGPKey) - self.assertEqual(ADDRESS, key.address) + self.assertTrue(ADDRESS in key.address) def test_put_key_ascii(self): """ @@ -441,7 +450,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km.put_raw_key(PUBLIC_KEY, OpenPGPKey) key = km.get_key(ADDRESS, OpenPGPKey) self.assertIsInstance(key, OpenPGPKey) - self.assertEqual(ADDRESS, key.address) + self.assertTrue(ADDRESS in key.address) def test_fetch_uri_ascii_key(self): """ -- cgit v1.2.3 From 5d5f60afb1878aa65156fa294cc97a12bfe497d3 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 10 Nov 2014 09:50:56 -0600 Subject: Use type instead of tags to get docs in openpgp For that that now the type is the class.__name__ instead of str(class) --- keymanager/src/leap/keymanager/__init__.py | 2 +- keymanager/src/leap/keymanager/keys.py | 8 ++++---- keymanager/src/leap/keymanager/openpgp.py | 10 ++++++---- 3 files changed, 11 insertions(+), 9 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 53dd9a78..0ffb6fc7 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -134,7 +134,7 @@ class KeyManager(object): Return key class from string representation of key type. """ return filter( - lambda klass: str(klass) == ktype, + lambda klass: klass.__name__ == ktype, self._wrapper_map).pop() def _get(self, uri, data=None): diff --git a/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py index 4952b9b3..5aeb7945 100644 --- a/keymanager/src/leap/keymanager/keys.py +++ b/keymanager/src/leap/keymanager/keys.py @@ -71,14 +71,14 @@ KEYMANAGER_KEY_TAG = 'keymanager-key' # TAGS_PRIVATE_INDEX = 'by-tags-private' -TAGS_ADDRESS_PRIVATE_INDEX = 'by-tags-address-private' +TYPE_ADDRESS_PRIVATE_INDEX = 'by-type-address-private' INDEXES = { TAGS_PRIVATE_INDEX: [ KEY_TAGS_KEY, 'bool(%s)' % KEY_PRIVATE_KEY, ], - TAGS_ADDRESS_PRIVATE_INDEX: [ - KEY_TAGS_KEY, + TYPE_ADDRESS_PRIVATE_INDEX: [ + KEY_TYPE_KEY, KEY_ADDRESS_KEY, 'bool(%s)' % KEY_PRIVATE_KEY, ] @@ -200,7 +200,7 @@ class EncryptionKey(object): return json.dumps({ KEY_ADDRESS_KEY: self.address, - KEY_TYPE_KEY: str(self.__class__), + KEY_TYPE_KEY: self.__class__.__name__, KEY_ID_KEY: self.key_id, KEY_FINGERPRINT_KEY: self.fingerprint, KEY_DATA_KEY: self.key_data, diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 1160434c..38db178c 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -36,8 +36,7 @@ from leap.keymanager.keys import ( EncryptionScheme, is_address, build_key_from_dict, - KEYMANAGER_KEY_TAG, - TAGS_ADDRESS_PRIVATE_INDEX, + TYPE_ADDRESS_PRIVATE_INDEX, KEY_FINGERPRINT_KEY, KEY_DATA_KEY, ) @@ -210,6 +209,9 @@ class OpenPGPScheme(EncryptionScheme): signing and verification). """ + # type used on the soledad documents + OPENPGP_KEY_TYPE = OpenPGPKey.__name__ + def __init__(self, soledad, gpgbinary=None): """ Initialize the OpenPGP wrapper. @@ -427,8 +429,8 @@ class OpenPGPScheme(EncryptionScheme): :rtype: leap.soledad.document.SoledadDocument """ doclist = self._soledad.get_from_index( - TAGS_ADDRESS_PRIVATE_INDEX, - KEYMANAGER_KEY_TAG, + TYPE_ADDRESS_PRIVATE_INDEX, + self.OPENPGP_KEY_TYPE, address, '1' if private else '0') if len(doclist) is 0: -- cgit v1.2.3 From 4ec8d345a32b9169300974941c5bdd87d85f3915 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 10 Nov 2014 13:36:35 -0600 Subject: Implement active key document --- keymanager/src/leap/keymanager/__init__.py | 25 ++--- keymanager/src/leap/keymanager/keys.py | 25 +++++ keymanager/src/leap/keymanager/openpgp.py | 146 ++++++++++++++++++++++++----- 3 files changed, 161 insertions(+), 35 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 0ffb6fc7..562bfbfa 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -519,15 +519,24 @@ class KeyManager(object): except IndexError as e: leap_assert(False, "Unsupported key type. Error {0!r}".format(e)) - def put_key(self, key): + def put_key(self, key, address=None): """ Put C{key} in local storage. :param key: The key to be stored :type key: EncryptionKey + :param address: address for which this key will be active. If not set + all the uids will be activated + :type address: str + + :raises KeyAddressMismatch: if address doesn't match any uid on the key :raises KeyNotValidUpdate: if a key with the same uid exists and the new one is not a valid update for it """ + if address is not None and address not in key.address: + raise KeyAddressMismatch("UID %s found, but expected %s" + % (str(key.address), address)) + try: old_key = self._wrapper_map[type(key)].get_key(key.address[0], private=key.private) @@ -536,7 +545,7 @@ class KeyManager(object): if key.private or can_upgrade(key, old_key): try: - self._wrapper_map[type(key)].put_key(key) + self._wrapper_map[type(key)].put_key(key, address) except IndexError as e: leap_assert( False, "Unsupported key type. Error {0!r}".format(e)) @@ -553,7 +562,7 @@ class KeyManager(object): :type key: str :param ktype: the type of the key. :type ktype: subclass of EncryptionKey - :param address: if set used to check that the key is for this address + :param address: address for which this key will be active :type address: str :param validation: validation level for this key (default: 'Weak_Chain') @@ -564,12 +573,9 @@ class KeyManager(object): new one is not a valid update for it """ pubkey, _ = self._wrapper_map[ktype].parse_ascii_key(key) - if address is not None and address not in pubkey.address: - raise KeyAddressMismatch("Key UID %s, but expected %s" - % (pubkey.address, address)) pubkey.validation = validation - self.put_key(pubkey) + self.put_key(pubkey, address) def fetch_key(self, address, uri, ktype, validation=ValidationLevel.Weak_Chain): @@ -600,12 +606,9 @@ class KeyManager(object): pubkey, _ = self._wrapper_map[ktype].parse_ascii_key(res.content) if pubkey is None: raise KeyNotFound(uri) - if address not in pubkey.address: - raise KeyAddressMismatch("UID %s found, but expected %s" - % (str(pubkey.address), address)) pubkey.validation = validation - self.put_key(pubkey) + self.put_key(pubkey, address) from ._version import get_versions __version__ = get_versions()['version'] diff --git a/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py index 5aeb7945..d1b5d933 100644 --- a/keymanager/src/leap/keymanager/keys.py +++ b/keymanager/src/leap/keymanager/keys.py @@ -64,6 +64,8 @@ KEY_TAGS_KEY = 'tags' # KEYMANAGER_KEY_TAG = 'keymanager-key' +KEYMANAGER_ACTIVE_TAG = 'keymanager-active' +KEYMANAGER_ACTIVE_TYPE = '-active' # @@ -71,12 +73,18 @@ KEYMANAGER_KEY_TAG = 'keymanager-key' # TAGS_PRIVATE_INDEX = 'by-tags-private' +TYPE_ID_PRIVATE_INDEX = 'by-type-id-private' TYPE_ADDRESS_PRIVATE_INDEX = 'by-type-address-private' INDEXES = { TAGS_PRIVATE_INDEX: [ KEY_TAGS_KEY, 'bool(%s)' % KEY_PRIVATE_KEY, ], + TYPE_ID_PRIVATE_INDEX: [ + KEY_TYPE_KEY, + KEY_ID_KEY, + 'bool(%s)' % KEY_PRIVATE_KEY, + ], TYPE_ADDRESS_PRIVATE_INDEX: [ KEY_TYPE_KEY, KEY_ADDRESS_KEY, @@ -215,6 +223,23 @@ class EncryptionKey(object): KEY_TAGS_KEY: [KEYMANAGER_KEY_TAG], }) + def get_active_json(self, address): + """ + Return a JSON string describing this key. + + :param address: Address for wich the key is active + :type address: str + :return: The JSON string describing this key. + :rtype: str + """ + return json.dumps({ + KEY_ADDRESS_KEY: address, + KEY_TYPE_KEY: self.__class__.__name__ + KEYMANAGER_ACTIVE_TYPE, + KEY_ID_KEY: self.key_id, + KEY_PRIVATE_KEY: self.private, + KEY_TAGS_KEY: [KEYMANAGER_ACTIVE_TAG], + }) + def __repr__(self): """ Representation of this class diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 38db178c..52655d0e 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -36,9 +36,12 @@ from leap.keymanager.keys import ( EncryptionScheme, is_address, build_key_from_dict, + TYPE_ID_PRIVATE_INDEX, TYPE_ADDRESS_PRIVATE_INDEX, KEY_FINGERPRINT_KEY, KEY_DATA_KEY, + KEY_ID_KEY, + KEYMANAGER_ACTIVE_TYPE, ) @@ -193,6 +196,18 @@ def _build_key_from_gpg(address, key, key_data): ) +def _parse_address(address): + """ + Remove the identity suffix after the '+' until the '@' + e.g.: test_user+something@provider.com becomes test_user@provider.com + since the key belongs to the identity without the '+' suffix. + + :type address: str + :rtype: str + """ + return re.sub(r'\+.*\@', '@', address) + + # # The OpenPGP wrapper # @@ -210,7 +225,8 @@ class OpenPGPScheme(EncryptionScheme): """ # type used on the soledad documents - OPENPGP_KEY_TYPE = OpenPGPKey.__name__ + KEY_TYPE = OpenPGPKey.__name__ + ACTIVE_TYPE = KEY_TYPE + KEYMANAGER_ACTIVE_TYPE def __init__(self, soledad, gpgbinary=None): """ @@ -282,7 +298,7 @@ class OpenPGPScheme(EncryptionScheme): openpgp_key = _build_key_from_gpg( address, key, gpg.export_keys(key['fingerprint'], secret=secret)) - self.put_key(openpgp_key) + self.put_key(openpgp_key, address) return self.get_key(address, private=True) @@ -299,10 +315,7 @@ class OpenPGPScheme(EncryptionScheme): :rtype: OpenPGPKey @raise KeyNotFound: If the key was not found on local storage. """ - # Remove the identity suffix after the '+' until the '@' - # e.g.: test_user+something@provider.com becomes test_user@provider.com - # since the key belongs to the identity without the '+' suffix. - address = re.sub(r'\+.*\@', '@', address) + address = _parse_address(address) doc = self._get_key_doc(address, private) if doc is None: @@ -371,12 +384,15 @@ class OpenPGPScheme(EncryptionScheme): return (openpgp_pubkey, openpgp_privkey) - def put_ascii_key(self, key_data): + def put_ascii_key(self, key_data, address=None): """ 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 + :param address: address for which this key will be active. If not set + all the uids will be activated + :type address: str """ leap_assert_type(key_data, (str, unicode)) @@ -387,21 +403,41 @@ class OpenPGPScheme(EncryptionScheme): leap_assert(False, repr(e)) if openpgp_pubkey is not None: - self.put_key(openpgp_pubkey) + self.put_key(openpgp_pubkey, address) if openpgp_privkey is not None: - self.put_key(openpgp_privkey) + self.put_key(openpgp_privkey, address) - def put_key(self, key): + def put_key(self, key, address=None): """ Put C{key} in local storage. :param key: The key to be stored. :type key: OpenPGPKey + :param address: address for which this key will be active. If not set + all the uids will be activated + :type address: str """ - doc = self._get_key_doc(key.address[0], private=key.private) - if doc is None: - self._soledad.create_doc_from_json(key.get_json()) + if address is not None: + active_address = [_parse_address(address)] else: + active_address = key.address + + self._put_key_doc(key) + self._put_active_doc(key, active_address) + + def _put_key_doc(self, key): + """ + Put key document in soledad + + :type key: OpenPGPKey + """ + docs = self._soledad.get_from_index( + TYPE_ID_PRIVATE_INDEX, + self.KEY_TYPE, + key.key_id, + '1' if key.private else '0') + if len(docs) != 0: + doc = docs.pop() if key.fingerprint == doc.content[KEY_FINGERPRINT_KEY]: # in case of an update of the key merge them with gnupg with self._temporary_gpgwrapper() as gpg: @@ -412,8 +448,41 @@ class OpenPGPScheme(EncryptionScheme): key.address[0], gpgkey, gpg.export_keys(gpgkey['fingerprint'], secret=key.private)) - doc.set_json(key.get_json()) - self._soledad.put_doc(doc) + doc.set_json(key.get_json()) + self._soledad.put_doc(doc) + else: + logger.critical( + "Can't put a key whith the same key_id and different " + "fingerprint: %s, %s" + % (key.fingerprint, doc.content[KEY_FINGERPRINT_KEY])) + else: + self._soledad.create_doc_from_json(key.get_json()) + + def _put_active_doc(self, key, addresses): + """ + Put active key document in soledad + + :type key: OpenPGPKey + :type addresses: list(str) + """ + for address in addresses: + docs = self._soledad.get_from_index( + TYPE_ADDRESS_PRIVATE_INDEX, + self.ACTIVE_TYPE, + address, + '1' if key.private else '0') + if len(docs) == 1: + doc = docs.pop() + doc.set_json(key.get_active_json(address)) + self._soledad.put_doc(doc) + else: + if len(docs) > 1: + logger.error("There is more than one active key document " + "for the address %s" % (address,)) + for doc in docs: + self._soledad.delete_doc(doc) + self._soledad.create_doc_from_json( + key.get_active_json(address)) def _get_key_doc(self, address, private=False): """ @@ -428,17 +497,26 @@ class OpenPGPScheme(EncryptionScheme): :return: The document with the key or None if it does not exist. :rtype: leap.soledad.document.SoledadDocument """ - doclist = self._soledad.get_from_index( + activedoc = self._soledad.get_from_index( TYPE_ADDRESS_PRIVATE_INDEX, - self.OPENPGP_KEY_TYPE, + self.ACTIVE_TYPE, address, '1' if private else '0') - if len(doclist) is 0: + if len(activedoc) is 0: return None + leap_assert( + len(activedoc) is 1, + 'Found more than one key for address %s!' % (address,)) + + key_id = activedoc[0].content[KEY_ID_KEY] + doclist = self._soledad.get_from_index( + TYPE_ID_PRIVATE_INDEX, + self.KEY_TYPE, + key_id, + '1' if private else '0') leap_assert( len(doclist) is 1, - 'Found more than one %s key for address!' % - 'private' if private else 'public') + 'There is %d keys for id %s!' % (len(doclist), key_id)) return doclist.pop() def delete_key(self, key): @@ -447,17 +525,37 @@ class OpenPGPScheme(EncryptionScheme): May raise: errors.KeyNotFound - errors.KeyAttributesDiffer :param key: The key to be removed. :type key: EncryptionKey """ leap_assert_type(key, OpenPGPKey) - doc = self._get_key_doc(key.address[0], key.private) + activedocs = self._soledad.get_from_index( + TYPE_ID_PRIVATE_INDEX, + self.ACTIVE_TYPE, + key.key_id, + '1' if key.private else '0') + for doc in activedocs: + self._soledad.delete_doc(doc) + + docs = self._soledad.get_from_index( + TYPE_ID_PRIVATE_INDEX, + self.KEY_TYPE, + key.key_id, + '1' if key.private else '0') + if len(docs) == 0: + raise errors.KeyNotFound(key) + if len(docs) > 1: + logger.critical("There is more than one key for key_id %s" + % key.key_id) + + doc = None + for d in docs: + if d.content['fingerprint'] == key.fingerprint: + doc = d + break if doc is None: raise errors.KeyNotFound(key) - if doc.content[KEY_FINGERPRINT_KEY] != key.fingerprint: - raise errors.KeyAttributesDiffer(key) self._soledad.delete_doc(doc) # -- cgit v1.2.3 From 1d1274e57f5e466fb195c829a628f519ef51186a Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 10 Nov 2014 18:36:59 -0600 Subject: Implement multi uid support --- keymanager/src/leap/keymanager/__init__.py | 12 +-- keymanager/src/leap/keymanager/keys.py | 15 ++- keymanager/src/leap/keymanager/openpgp.py | 120 ++++++++++----------- .../src/leap/keymanager/tests/test_keymanager.py | 48 ++++----- .../src/leap/keymanager/tests/test_validation.py | 21 ++-- keymanager/src/leap/keymanager/validation.py | 4 - 6 files changed, 100 insertions(+), 120 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 562bfbfa..c64cdeab 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -319,7 +319,6 @@ class KeyManager(object): return map( lambda doc: build_key_from_dict( self._key_class_from_type(doc.content['type']), - doc.content['address'][0], doc.content), self._soledad.get_from_index( TAGS_PRIVATE_INDEX, @@ -519,26 +518,25 @@ class KeyManager(object): except IndexError as e: leap_assert(False, "Unsupported key type. Error {0!r}".format(e)) - def put_key(self, key, address=None): + def put_key(self, key, address): """ Put C{key} in local storage. :param key: The key to be stored :type key: EncryptionKey - :param address: address for which this key will be active. If not set - all the uids will be activated + :param address: address for which this key will be active :type address: str :raises KeyAddressMismatch: if address doesn't match any uid on the key :raises KeyNotValidUpdate: if a key with the same uid exists and the new one is not a valid update for it """ - if address is not None and address not in key.address: + if address not in key.address: raise KeyAddressMismatch("UID %s found, but expected %s" % (str(key.address), address)) try: - old_key = self._wrapper_map[type(key)].get_key(key.address[0], + old_key = self._wrapper_map[type(key)].get_key(address, private=key.private) except KeyNotFound: old_key = None @@ -553,7 +551,7 @@ class KeyManager(object): raise KeyNotValidUpgrade("Key %s can not be upgraded by new key %s" % (old_key.key_id, key.key_id)) - def put_raw_key(self, key, ktype, address=None, + def put_raw_key(self, key, ktype, address, validation=ValidationLevel.Weak_Chain): """ Put C{key} in local storage. diff --git a/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py index d1b5d933..2108aa26 100644 --- a/keymanager/src/leap/keymanager/keys.py +++ b/keymanager/src/leap/keymanager/keys.py @@ -109,20 +109,15 @@ def is_address(address): return bool(re.match('[\w.-]+@[\w.-]+', address)) -def build_key_from_dict(kClass, address, kdict): +def build_key_from_dict(kClass, kdict): """ - Build an C{kClass} key bound to C{address} based on info in C{kdict}. + Build an C{kClass} key based on info in C{kdict}. - :param address: The address bound to the key. - :type address: str :param kdict: Dictionary with key data. :type kdict: dict :return: An instance of the key. :rtype: C{kClass} """ - leap_assert( - address in kdict[KEY_ADDRESS_KEY], - 'Wrong address in key data.') try: validation = toValidationLevel(kdict[KEY_VALIDATION_KEY]) except ValueError: @@ -135,7 +130,7 @@ def build_key_from_dict(kClass, address, kdict): refreshed_at = _to_datetime(kdict[KEY_REFRESHED_AT_KEY]) return kClass( - [address], + kdict[KEY_ADDRESS_KEY], key_id=kdict[KEY_ID_KEY], fingerprint=kdict[KEY_FINGERPRINT_KEY], key_data=kdict[KEY_DATA_KEY], @@ -315,12 +310,14 @@ class EncryptionScheme(object): pass @abstractmethod - def put_key(self, key): + def put_key(self, key, address): """ Put a key in local storage. :param key: The key to be stored. :type key: EncryptionKey + :param address: address for which this key will be active. + :type address: str """ pass diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 52655d0e..4f965745 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -38,6 +38,7 @@ from leap.keymanager.keys import ( build_key_from_dict, TYPE_ID_PRIVATE_INDEX, TYPE_ADDRESS_PRIVATE_INDEX, + KEY_ADDRESS_KEY, KEY_FINGERPRINT_KEY, KEY_DATA_KEY, KEY_ID_KEY, @@ -110,9 +111,9 @@ class TempGPGWrapper(object): # itself is enough to also have the public key in the keyring, # and we want to count the keys afterwards. - privaddrs = map(lambda privkey: privkey.address[0], privkeys) + privids = map(lambda privkey: privkey.key_id, privkeys) publkeys = filter( - lambda pubkey: pubkey.address[0] not in privaddrs, publkeys) + lambda pubkey: pubkey.key_id not in privids, publkeys) listkeys = lambda: self._gpg.list_keys() listsecretkeys = lambda: self._gpg.list_keys(secret=True) @@ -163,16 +164,13 @@ class TempGPGWrapper(object): shutil.rmtree(self._gpg.homedir) -def _build_key_from_gpg(address, key, key_data): +def _build_key_from_gpg(key, key_data): """ - Build an OpenPGPKey for C{address} based on C{key} from - local gpg storage. + Build an OpenPGPKey based on C{key} from local gpg storage. ASCII armored GPG key data has to be queried independently in this wrapper, so we receive it in C{key_data}. - :param address: The address bound to the key. - :type address: str :param key: Key obtained from GPG storage. :type key: dict :param key_data: Key data obtained from GPG storage. @@ -183,9 +181,12 @@ def _build_key_from_gpg(address, key, key_data): expiry_date = None if key['expires']: expiry_date = datetime.fromtimestamp(int(key['expires'])) + address = [] + for uid in key['uids']: + address.append(_parse_address(uid)) return OpenPGPKey( - [address], + address, key_id=key['keyid'], fingerprint=key['fingerprint'], key_data=key_data, @@ -198,14 +199,18 @@ def _build_key_from_gpg(address, key, key_data): def _parse_address(address): """ - Remove the identity suffix after the '+' until the '@' + Remove name, '<', '>' and the identity suffix after the '+' until the '@' e.g.: test_user+something@provider.com becomes test_user@provider.com since the key belongs to the identity without the '+' suffix. :type address: str :rtype: str """ - return re.sub(r'\+.*\@', '@', address) + mail_regex = '(.*<)?([\w.-]+)(\+.*)?(@[\w.-]+)(>.*)?' + match = re.match(mail_regex, address) + if match is None: + return None + return ''.join(match.group(2, 4)) # @@ -289,15 +294,17 @@ class OpenPGPScheme(EncryptionScheme): leap_assert( len(key['uids']) is 1, # with just one uid! 'Wrong number of uids for key: %d.' % len(key['uids'])) - leap_assert( - re.match('.*<%s>$' % address, key['uids'][0]) is not None, - 'Key not correctly bound to address.') + uid_match = False + for uid in key['uids']: + if re.match('.*<%s>$' % address, uid) is not None: + uid_match = True + return + leap_assert(uid_match, 'Key not correctly bound to address.') # insert both public and private keys in storage for secret in [True, False]: key = gpg.list_keys(secret=secret).pop() openpgp_key = _build_key_from_gpg( - address, key, - gpg.export_keys(key['fingerprint'], secret=secret)) + key, gpg.export_keys(key['fingerprint'], secret=secret)) self.put_key(openpgp_key, address) return self.get_key(address, private=True) @@ -320,7 +327,10 @@ class OpenPGPScheme(EncryptionScheme): doc = self._get_key_doc(address, private) if doc is None: raise errors.KeyNotFound(address) - return build_key_from_dict(OpenPGPKey, address, doc.content) + leap_assert( + address in doc.content[KEY_ADDRESS_KEY], + 'Wrong address in key data.') + return build_key_from_dict(OpenPGPKey, doc.content) def parse_ascii_key(self, key_data): """ @@ -337,7 +347,6 @@ class OpenPGPScheme(EncryptionScheme): 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 @@ -354,44 +363,30 @@ class OpenPGPScheme(EncryptionScheme): except IndexError: return (None, None) - # extract adress from first uid on key - match = re.match(mail_regex, pubkey['uids'].pop()) - leap_assert(match is not None, 'No user address in key data.') - address = match.group(1) - openpgp_privkey = None if privkey is not None: - match = re.match(mail_regex, privkey['uids'].pop()) - leap_assert(match is not None, 'No user address in key data.') - privaddress = match.group(1) - # build private key openpgp_privkey = _build_key_from_gpg( - privaddress, privkey, + privkey, gpg.export_keys(privkey['fingerprint'], secret=True)) - - 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, + pubkey, gpg.export_keys(pubkey['fingerprint'], secret=False)) return (openpgp_pubkey, openpgp_privkey) - def put_ascii_key(self, key_data, address=None): + def put_ascii_key(self, key_data, address): """ 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 - :param address: address for which this key will be active. If not set - all the uids will be activated + :param address: address for which this key will be active :type address: str """ leap_assert_type(key_data, (str, unicode)) @@ -407,23 +402,17 @@ class OpenPGPScheme(EncryptionScheme): if openpgp_privkey is not None: self.put_key(openpgp_privkey, address) - def put_key(self, key, address=None): + def put_key(self, key, address): """ Put C{key} in local storage. :param key: The key to be stored. :type key: OpenPGPKey - :param address: address for which this key will be active. If not set - all the uids will be activated + :param address: address for which this key will be active. :type address: str """ - if address is not None: - active_address = [_parse_address(address)] - else: - active_address = key.address - self._put_key_doc(key) - self._put_active_doc(key, active_address) + self._put_active_doc(key, address) def _put_key_doc(self, key): """ @@ -445,7 +434,7 @@ class OpenPGPScheme(EncryptionScheme): gpg.import_keys(key.key_data) gpgkey = gpg.list_keys(secret=key.private).pop() key = _build_key_from_gpg( - key.address[0], gpgkey, + gpgkey, gpg.export_keys(gpgkey['fingerprint'], secret=key.private)) doc.set_json(key.get_json()) @@ -458,31 +447,30 @@ class OpenPGPScheme(EncryptionScheme): else: self._soledad.create_doc_from_json(key.get_json()) - def _put_active_doc(self, key, addresses): + def _put_active_doc(self, key, address): """ Put active key document in soledad :type key: OpenPGPKey - :type addresses: list(str) - """ - for address in addresses: - docs = self._soledad.get_from_index( - TYPE_ADDRESS_PRIVATE_INDEX, - self.ACTIVE_TYPE, - address, - '1' if key.private else '0') - if len(docs) == 1: - doc = docs.pop() - doc.set_json(key.get_active_json(address)) - self._soledad.put_doc(doc) - else: - if len(docs) > 1: - logger.error("There is more than one active key document " - "for the address %s" % (address,)) - for doc in docs: - self._soledad.delete_doc(doc) - self._soledad.create_doc_from_json( - key.get_active_json(address)) + :type addresses: str + """ + docs = self._soledad.get_from_index( + TYPE_ADDRESS_PRIVATE_INDEX, + self.ACTIVE_TYPE, + address, + '1' if key.private else '0') + if len(docs) == 1: + doc = docs.pop() + doc.set_json(key.get_active_json(address)) + self._soledad.put_doc(doc) + else: + if len(docs) > 1: + logger.error("There is more than one active key document " + "for the address %s" % (address,)) + for doc in docs: + self._soledad.delete_doc(doc) + self._soledad.create_doc_from_json( + key.get_active_json(address)) def _get_key_doc(self, address, private=False): """ diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 4daf3465..6aeb67a8 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -89,7 +89,7 @@ class KeyManagerUtilTestCase(BaseLeapTest): 'encr_used': False, 'sign_used': True, } - key = build_key_from_dict(OpenPGPKey, ADDRESS, kdict) + key = build_key_from_dict(OpenPGPKey, kdict) self.assertEqual( kdict['address'], key.address, 'Wrong data in key.') @@ -144,7 +144,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - pgp.put_ascii_key(PUBLIC_KEY) + pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) key = pgp.get_key(ADDRESS, private=False) pgp.delete_key(key) self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) @@ -153,7 +153,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - pgp.put_ascii_key(PUBLIC_KEY) + pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) key = pgp.get_key(ADDRESS, private=False) self.assertIsInstance(key, openpgp.OpenPGPKey) self.assertTrue( @@ -167,7 +167,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - pgp.put_ascii_key(PUBLIC_KEY) + pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) self.assertRaises( KeyNotFound, pgp.get_key, ADDRESS, private=True) key = pgp.get_key(ADDRESS, private=False) @@ -181,7 +181,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): # encrypt pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PUBLIC_KEY) + pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) pubkey = pgp.get_key(ADDRESS, private=False) cyphertext = pgp.encrypt('data', pubkey) # assert @@ -193,7 +193,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): # decrypt self.assertRaises( KeyNotFound, pgp.get_key, ADDRESS, private=True) - pgp.put_ascii_key(PRIVATE_KEY) + pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) privkey = pgp.get_key(ADDRESS, private=True) pgp.delete_key(pubkey) pgp.delete_key(privkey) @@ -216,7 +216,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_sign_with_public_raises(self): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PUBLIC_KEY) + pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) data = 'data' pubkey = pgp.get_key(ADDRESS, private=False) self.assertRaises( @@ -226,11 +226,11 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_verify_with_wrong_key_raises(self): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY) + pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) data = 'data' privkey = pgp.get_key(ADDRESS, private=True) signed = pgp.sign(data, privkey) - pgp.put_ascii_key(PUBLIC_KEY_2) + pgp.put_ascii_key(PUBLIC_KEY_2, ADDRESS_2) wrongkey = pgp.get_key(ADDRESS_2) self.assertRaises( errors.InvalidSignature, @@ -239,7 +239,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_encrypt_sign_with_public_raises(self): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY) + pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) data = 'data' privkey = pgp.get_key(ADDRESS, private=True) pubkey = pgp.get_key(ADDRESS, private=False) @@ -250,7 +250,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_decrypt_verify_with_private_raises(self): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY) + pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) data = 'data' privkey = pgp.get_key(ADDRESS, private=True) pubkey = pgp.get_key(ADDRESS, private=False) @@ -264,12 +264,12 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_decrypt_verify_with_wrong_key_raises(self): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY) + pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) data = 'data' privkey = pgp.get_key(ADDRESS, private=True) pubkey = pgp.get_key(ADDRESS, private=False) encrypted_and_signed = pgp.encrypt(data, pubkey, sign=privkey) - pgp.put_ascii_key(PUBLIC_KEY_2) + pgp.put_ascii_key(PUBLIC_KEY_2, ADDRESS_2) wrongkey = pgp.get_key(ADDRESS_2) self.assertRaises( errors.InvalidSignature, @@ -278,7 +278,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_sign_verify(self): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY) + pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) data = 'data' privkey = pgp.get_key(ADDRESS, private=True) signed = pgp.sign(data, privkey, detach=False) @@ -288,10 +288,10 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_encrypt_sign_decrypt_verify(self): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY) + pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) pubkey = pgp.get_key(ADDRESS, private=False) privkey = pgp.get_key(ADDRESS, private=True) - pgp.put_ascii_key(PRIVATE_KEY_2) + pgp.put_ascii_key(PRIVATE_KEY_2, ADDRESS_2) pubkey2 = pgp.get_key(ADDRESS_2, private=False) privkey2 = pgp.get_key(ADDRESS_2, private=True) data = 'data' @@ -304,7 +304,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_sign_verify_detached_sig(self): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY) + pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) data = 'data' privkey = pgp.get_key(ADDRESS, private=True) signature = pgp.sign(data, privkey, detach=True) @@ -316,7 +316,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): def test_get_all_keys_in_db(self): km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY) + km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) # get public keys keys = km.get_all_keys(False) self.assertEqual(len(keys), 1, 'Wrong number of keys') @@ -330,7 +330,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): def test_get_public_key(self): km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY) + km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) # get the key key = km.get_key(ADDRESS, OpenPGPKey, private=False, fetch_remote=False) @@ -342,7 +342,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): def test_get_private_key(self): km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY) + km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) # get the key key = km.get_key(ADDRESS, OpenPGPKey, private=True, fetch_remote=False) @@ -364,7 +364,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): """ token = "mytoken" km = self._key_manager(token=token) - km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY) + km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY, ADDRESS) km._fetcher.put = Mock() # the following data will be used on the send km.ca_cert_path = 'capath' @@ -447,7 +447,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): """ km = self._key_manager(url='http://nickserver.domain') - km.put_raw_key(PUBLIC_KEY, OpenPGPKey) + km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) key = km.get_key(ADDRESS, OpenPGPKey) self.assertIsInstance(key, OpenPGPKey) self.assertTrue(ADDRESS in key.address) @@ -509,7 +509,7 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): def test_keymanager_openpgp_encrypt_decrypt(self): km = self._key_manager() # put raw private key - km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY) + km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) # get public key pubkey = km.get_key( ADDRESS, OpenPGPKey, private=False, fetch_remote=False) @@ -526,7 +526,7 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): def test_keymanager_openpgp_sign_verify(self): km = self._key_manager() # put raw private keys - km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY) + km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) # get private key for signing privkey = km.get_key( ADDRESS, OpenPGPKey, private=True, fetch_remote=False) diff --git a/keymanager/src/leap/keymanager/tests/test_validation.py b/keymanager/src/leap/keymanager/tests/test_validation.py index 3ae873d4..400d36e8 100644 --- a/keymanager/src/leap/keymanager/tests/test_validation.py +++ b/keymanager/src/leap/keymanager/tests/test_validation.py @@ -37,47 +37,48 @@ class ValidationLevelTestCase(KeyManagerWithSoledadTestCase): def test_none_old_key(self): km = self._key_manager() - km.put_raw_key(PUBLIC_KEY, OpenPGPKey) + km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) key = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) self.assertEqual(key.fingerprint, KEY_FINGERPRINT) def test_cant_upgrade(self): km = self._key_manager() - km.put_raw_key(PUBLIC_KEY, OpenPGPKey, + km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS, validation=ValidationLevel.Provider_Trust) self.assertRaises(KeyNotValidUpgrade, km.put_raw_key, UNRELATED_KEY, - OpenPGPKey) + OpenPGPKey, ADDRESS) def test_fingerprint_level(self): km = self._key_manager() - km.put_raw_key(PUBLIC_KEY, OpenPGPKey) - km.put_raw_key(UNRELATED_KEY, OpenPGPKey, + km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) + km.put_raw_key(UNRELATED_KEY, OpenPGPKey, ADDRESS, validation=ValidationLevel.Fingerprint) key = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) self.assertEqual(key.fingerprint, UNRELATED_FINGERPRINT) def test_expired_key(self): km = self._key_manager() - km.put_raw_key(EXPIRED_KEY, OpenPGPKey) - km.put_raw_key(UNRELATED_KEY, OpenPGPKey) + km.put_raw_key(EXPIRED_KEY, OpenPGPKey, ADDRESS) + km.put_raw_key(UNRELATED_KEY, OpenPGPKey, ADDRESS) key = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) self.assertEqual(key.fingerprint, UNRELATED_FINGERPRINT) def test_expired_fail_lower_level(self): km = self._key_manager() - km.put_raw_key(EXPIRED_KEY, OpenPGPKey, + km.put_raw_key(EXPIRED_KEY, OpenPGPKey, ADDRESS, validation=ValidationLevel.Third_Party_Endorsement) self.assertRaises( KeyNotValidUpgrade, km.put_raw_key, UNRELATED_KEY, OpenPGPKey, + ADDRESS, validation=ValidationLevel.Provider_Trust) def test_roll_back(self): km = self._key_manager() - km.put_raw_key(EXPIRED_KEY_UPDATED, OpenPGPKey) - km.put_raw_key(EXPIRED_KEY, OpenPGPKey) + km.put_raw_key(EXPIRED_KEY_UPDATED, OpenPGPKey, ADDRESS) + km.put_raw_key(EXPIRED_KEY, OpenPGPKey, ADDRESS) key = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) self.assertEqual(key.expiry_date, EXPIRED_KEY_NEW_EXPIRY_DATE) diff --git a/keymanager/src/leap/keymanager/validation.py b/keymanager/src/leap/keymanager/validation.py index cf5b4a83..245013e5 100644 --- a/keymanager/src/leap/keymanager/validation.py +++ b/keymanager/src/leap/keymanager/validation.py @@ -67,10 +67,6 @@ def can_upgrade(new_key, old_key): if old_key is None: return True - if new_key.address != old_key.address: - # XXX how do we map multiple IDs? (#6212) - return False - # An update of the same key if new_key.fingerprint == old_key.fingerprint: return True -- cgit v1.2.3 From b40e61f4d7624250325378d51a289a92773e785c Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 10 Nov 2014 19:00:04 -0600 Subject: Fix comments --- keymanager/src/leap/keymanager/__init__.py | 4 ++-- keymanager/src/leap/keymanager/keys.py | 4 ++-- keymanager/src/leap/keymanager/openpgp.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index c64cdeab..e4be9c47 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -493,8 +493,8 @@ class KeyManager(object): verified using this detached signature. :type detached_sig: str - :return: The signed data. - :rtype: str + :return: signature matches + :rtype: bool """ leap_assert_type(pubkey, EncryptionKey) leap_assert(pubkey.__class__ in self._wrapper_map, 'Unknown key type.') diff --git a/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py index 2108aa26..7c732e35 100644 --- a/keymanager/src/leap/keymanager/keys.py +++ b/keymanager/src/leap/keymanager/keys.py @@ -411,7 +411,7 @@ class EncryptionScheme(object): verified against this sdetached signature. :type detached_sig: str - :return: The signed data. - :rtype: str + :return: signature matches + :rtype: bool """ pass diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 4f965745..3f298f71 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -742,8 +742,8 @@ class OpenPGPScheme(EncryptionScheme): verified against this detached signature. :type detached_sig: str - :return: The ascii-armored signed data. - :rtype: str + :return: signature matches + :rtype: bool """ leap_assert_type(pubkey, OpenPGPKey) leap_assert(pubkey.private is False) -- cgit v1.2.3 From d3a7f97ebc32434cd012f4cd5d334e775e2ead49 Mon Sep 17 00:00:00 2001 From: drebs Date: Tue, 25 Nov 2014 12:36:24 -0200 Subject: Fix test to use correct new signature for openpgp.put_ascii_key(). --- keymanager/src/leap/keymanager/tests/test_keymanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 6aeb67a8..319d2e11 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -205,7 +205,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_verify_with_private_raises(self): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY) + pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) data = 'data' privkey = pgp.get_key(ADDRESS, private=True) signed = pgp.sign(data, privkey) -- cgit v1.2.3 From ae11553bc7b7cf94aa1672e6227d75f2c287fd9b Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Thu, 20 Nov 2014 10:55:09 -0600 Subject: Use addresses instead of keys on the public API --- keymanager/src/leap/keymanager/__init__.py | 125 +++++++++++++-------- .../src/leap/keymanager/tests/test_keymanager.py | 25 ++--- 2 files changed, 85 insertions(+), 65 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index e4be9c47..1704e0b4 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -43,7 +43,7 @@ except (ImportError, AssertionError): import logging import requests -from leap.common.check import leap_assert, leap_assert_type +from leap.common.check import leap_assert from leap.common.events import signal from leap.common.events import events_pb2 as proto from leap.common.decorators import memoized_method @@ -56,7 +56,6 @@ from leap.keymanager.errors import ( from leap.keymanager.validation import ValidationLevel, can_upgrade from leap.keymanager.keys import ( - EncryptionKey, build_key_from_dict, KEYMANAGER_KEY_TAG, TAGS_PRIVATE_INDEX, @@ -395,70 +394,92 @@ class KeyManager(object): # encrypt/decrypt and sign/verify API # - def encrypt(self, data, pubkey, passphrase=None, sign=None, - cipher_algo='AES256'): + def encrypt(self, data, address, ktype, passphrase=None, sign=None, + cipher_algo='AES256', fetch_remote=True): """ - Encrypt C{data} using public @{key} and sign with C{sign} key. + Encrypt C{data} for C{address} and sign with C{sign} address. :param data: The data to be encrypted. :type data: str - :param pubkey: The key used to encrypt. - :type pubkey: EncryptionKey + :param address: The address to encrypt it for. + :type address: str + :param ktype: The type of the key. + :type ktype: subclass of EncryptionKey :param passphrase: The passphrase for the secret key used for the signature. :type passphrase: str - :param sign: The key used for signing. - :type sign: EncryptionKey + :param sign: The address to be used for signature. + :type sign: str :param cipher_algo: The cipher algorithm to use. :type cipher_algo: str + :param fetch_remote: If key not found in local storage try to fetch + from nickserver + :type fetch_remote: bool :return: The encrypted data. :rtype: str + + :raise KeyNotFound: If any of the keys was not found both locally and + in keyserver. + :raise EncryptError: Raised if failed encrypting for some reason. """ - 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__].encrypt( - data, pubkey, passphrase, sign, cipher_algo=cipher_algo) + pubkey = self.get_key(address, ktype, private=False, + fetch_remote=fetch_remote) + privkey = None + if sign is not None: + privkey = self.get_key(sign, ktype, private=True) + return self._wrapper_map[ktype].encrypt( + data, pubkey, passphrase, privkey, cipher_algo=cipher_algo) - def decrypt(self, data, privkey, passphrase=None, verify=None): + def decrypt(self, data, address, ktype, passphrase=None, verify=None, + fetch_remote=True): """ - Decrypt C{data} using private @{privkey} and verify with C{verify} key. + Decrypt C{data} using private key from @{address} and verify with + C{verify} address. :param data: The data to be decrypted. :type data: str - :param privkey: The key used to decrypt. - :type privkey: OpenPGPKey + :param address: The address to who was encrypted. + :type address: str + :param ktype: The type of the key. + :type ktype: subclass of EncryptionKey :param passphrase: The passphrase for the secret key used for decryption. :type passphrase: str - :param verify: The key used to verify a signature. - :type verify: OpenPGPKey + :param verify: The address to be used for signature. + :type verify: str + :param fetch_remote: If key for verify not found in local storage try + to fetch from nickserver + :type fetch_remote: bool :return: The decrypted data. :rtype: str + :raise KeyNotFound: If any of the keys was not found both locally and + in keyserver. + :raise DecryptError: Raised if failed decrypting for some reason. :raise InvalidSignature: Raised if unable to verify the signature with - C{verify} key. + C{verify} address. """ - leap_assert_type(privkey, EncryptionKey) - leap_assert( - privkey.__class__ in self._wrapper_map, - 'Unknown key type.') - leap_assert(privkey.private is True, 'Key is not private.') - return self._wrapper_map[privkey.__class__].decrypt( - data, privkey, passphrase, verify) + privkey = self.get_key(address, ktype, private=True) + pubkey = None + if verify is not None: + pubkey = self.get_key(verify, ktype, private=False, + fetch_remote=fetch_remote) + return self._wrapper_map[ktype].decrypt( + data, privkey, passphrase, pubkey) - def sign(self, data, privkey, digest_algo='SHA512', clearsign=False, + def sign(self, data, address, ktype, digest_algo='SHA512', clearsign=False, detach=True, binary=False): """ - Sign C{data} with C{privkey}. + Sign C{data} with C{address}. :param data: The data to be signed. :type data: str - - :param privkey: The private key to be used to sign. - :type privkey: EncryptionKey + :param address: The address to be used to sign. + :type address: EncryptionKey + :param ktype: The type of the key. + :type ktype: subclass of EncryptionKey :param digest_algo: The hash digest to use. :type digest_algo: str :param clearsign: If True, create a cleartext signature. @@ -470,36 +491,46 @@ class KeyManager(object): :return: The signed data. :rtype: str + + :raise KeyNotFound: If the key was not found both locally and + in keyserver. + :raise SignFailed: If there was any error signing. """ - leap_assert_type(privkey, EncryptionKey) - leap_assert( - privkey.__class__ in self._wrapper_map, - 'Unknown key type.') - leap_assert(privkey.private is True, 'Key is not private.') - return self._wrapper_map[privkey.__class__].sign( + privkey = self.get_key(address, ktype, private=True) + return self._wrapper_map[ktype].sign( data, privkey, digest_algo=digest_algo, clearsign=clearsign, detach=detach, binary=binary) - def verify(self, data, pubkey, detached_sig=None): + def verify(self, data, address, ktype, detached_sig=None, + fetch_remote=True): """ - Verify signed C{data} with C{pubkey}, eventually using + Verify signed C{data} with C{address}, 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 address: The address to be used to verify. + :type address: EncryptionKey + :param ktype: The type of the key. + :type ktype: subclass of EncryptionKey :param detached_sig: A detached signature. If given, C{data} is verified using this detached signature. :type detached_sig: str + :param fetch_remote: If key for verify not found in local storage try + to fetch from nickserver + :type fetch_remote: bool :return: signature matches :rtype: bool + + :raise KeyNotFound: If the key was not found both locally and + in keyserver. + :raise InvalidSignature: Raised if unable to verify the signature with + C{verify} address. """ - 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( + pubkey = self.get_key(address, ktype, private=False, + fetch_remote=fetch_remote) + return self._wrapper_map[ktype].verify( data, pubkey, detached_sig=detached_sig) def delete_key(self, key): diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 319d2e11..8ae12bff 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -218,10 +218,9 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): self._soledad, gpgbinary=GPG_BINARY_PATH) pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) data = 'data' - pubkey = pgp.get_key(ADDRESS, private=False) self.assertRaises( AssertionError, - pgp.sign, data, pubkey) + pgp.sign, data, ADDRESS, OpenPGPKey) def test_verify_with_wrong_key_raises(self): pgp = openpgp.OpenPGPScheme( @@ -510,34 +509,24 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): km = self._key_manager() # put raw private key km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) - # get public key - pubkey = km.get_key( - ADDRESS, OpenPGPKey, private=False, fetch_remote=False) # encrypt - encdata = km.encrypt(self.RAW_DATA, pubkey) + encdata = km.encrypt(self.RAW_DATA, ADDRESS, OpenPGPKey, + fetch_remote=False) self.assertNotEqual(self.RAW_DATA, encdata) - # get private key - privkey = km.get_key( - ADDRESS, OpenPGPKey, private=True, fetch_remote=False) # decrypt - rawdata = km.decrypt(encdata, privkey) + rawdata = km.decrypt(encdata, ADDRESS, OpenPGPKey) self.assertEqual(self.RAW_DATA, rawdata) def test_keymanager_openpgp_sign_verify(self): km = self._key_manager() # put raw private keys km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) - # get private key for signing - privkey = km.get_key( - ADDRESS, OpenPGPKey, private=True, fetch_remote=False) # encrypt - signdata = km.sign(self.RAW_DATA, privkey, detach=False) + signdata = km.sign(self.RAW_DATA, ADDRESS, OpenPGPKey, detach=False) self.assertNotEqual(self.RAW_DATA, signdata) - # get public key for verifying - pubkey = km.get_key( - ADDRESS, OpenPGPKey, private=False, fetch_remote=False) # decrypt - self.assertTrue(km.verify(signdata, pubkey)) + self.assertTrue(km.verify(signdata, ADDRESS, OpenPGPKey, + fetch_remote=False)) # Key material for testing -- cgit v1.2.3 From 0ca8dbbac78f3c2cd91342a4ae78d94f76495046 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Thu, 20 Nov 2014 10:56:21 -0600 Subject: Return signing key on signature verification Don't throw an exception if verification fails --- keymanager/src/leap/keymanager/__init__.py | 18 +++---- keymanager/src/leap/keymanager/errors.py | 7 --- keymanager/src/leap/keymanager/keys.py | 7 ++- keymanager/src/leap/keymanager/openpgp.py | 27 ++++------ .../src/leap/keymanager/tests/test_keymanager.py | 62 +++++++++++++++------- 5 files changed, 65 insertions(+), 56 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 1704e0b4..b2b05f4a 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -452,22 +452,21 @@ class KeyManager(object): to fetch from nickserver :type fetch_remote: bool - :return: The decrypted data. - :rtype: str + :return: The decrypted data and the signing key if signature verifies + :rtype: (unicode, EncryptionKey) :raise KeyNotFound: If any of the keys was not found both locally and in keyserver. :raise DecryptError: Raised if failed decrypting for some reason. - :raise InvalidSignature: Raised if unable to verify the signature with - C{verify} address. """ privkey = self.get_key(address, ktype, private=True) pubkey = None if verify is not None: pubkey = self.get_key(verify, ktype, private=False, fetch_remote=fetch_remote) - return self._wrapper_map[ktype].decrypt( + decrypted, signed = self._wrapper_map[ktype].decrypt( data, privkey, passphrase, pubkey) + return (decrypted, pubkey if signed else None) def sign(self, data, address, ktype, digest_algo='SHA512', clearsign=False, detach=True, binary=False): @@ -520,18 +519,17 @@ class KeyManager(object): to fetch from nickserver :type fetch_remote: bool - :return: signature matches - :rtype: bool + :return: The signing key if signature verifies else None + :rtype: EncryptionKey :raise KeyNotFound: If the key was not found both locally and in keyserver. - :raise InvalidSignature: Raised if unable to verify the signature with - C{verify} address. """ pubkey = self.get_key(address, ktype, private=False, fetch_remote=fetch_remote) - return self._wrapper_map[ktype].verify( + signed = self._wrapper_map[ktype].verify( data, pubkey, detached_sig=detached_sig) + return pubkey if signed else None def delete_key(self, key): """ diff --git a/keymanager/src/leap/keymanager/errors.py b/keymanager/src/leap/keymanager/errors.py index f8965823..c068b27f 100644 --- a/keymanager/src/leap/keymanager/errors.py +++ b/keymanager/src/leap/keymanager/errors.py @@ -51,13 +51,6 @@ class NoPasswordGiven(Exception): pass -class InvalidSignature(Exception): - """ - Raised when signature could not be verified. - """ - pass - - class EncryptError(Exception): """ Raised upon failures of encryption. diff --git a/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py index 7c732e35..0e243ba0 100644 --- a/keymanager/src/leap/keymanager/keys.py +++ b/keymanager/src/leap/keymanager/keys.py @@ -373,11 +373,10 @@ class EncryptionScheme(object): :param verify: The key used to verify a signature. :type verify: OpenPGPKey - :return: The decrypted data. - :rtype: str + :return: The decrypted data and if signature verifies + :rtype: (unicode, bool) - @raise InvalidSignature: Raised if unable to verify the signature with - C{verify} key. + :raise DecryptError: Raised if failed decrypting for some reason. """ pass diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 3f298f71..1d1de989 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -640,12 +640,10 @@ class OpenPGPScheme(EncryptionScheme): :param verify: The key used to verify a signature. :type verify: OpenPGPKey - :return: The decrypted data. - :rtype: unicode + :return: The decrypted data and if signature verifies + :rtype: (unicode, bool) :raise DecryptError: Raised if failed decrypting for some reason. - :raise InvalidSignature: Raised if unable to verify the signature with - C{verify} key. """ leap_assert(privkey.private is True, 'Key is not private.') keys = [privkey] @@ -658,15 +656,15 @@ class OpenPGPScheme(EncryptionScheme): result = gpg.decrypt( data, passphrase=passphrase, always_trust=True) self._assert_gpg_result_ok(result) + # verify signature - if (verify is not None): - if result.valid is False or \ - verify.fingerprint != result.pubkey_fingerprint: - raise errors.InvalidSignature( - 'Failed to verify signature with key %s: %s' % - (verify.key_id, result.stderr)) + sign_valid = False + if (verify is not None and + result.valid is True and + verify.fingerprint == result.pubkey_fingerprint): + sign_valid = True - return result.data + return (result.data, sign_valid) except errors.GPGError as e: logger.error('Failed to decrypt: %s.' % str(e)) raise errors.DecryptError(str(e)) @@ -764,9 +762,4 @@ class OpenPGPScheme(EncryptionScheme): valid = result.valid rfprint = result.fingerprint kfprint = gpgpubkey['fingerprint'] - # raise in case sig is invalid - if valid is False or rfprint != kfprint: - raise errors.InvalidSignature( - 'Failed to verify signature ' - 'with key %s.' % gpgpubkey['keyid']) - return True + return valid and rfprint == kfprint diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 8ae12bff..ee4462a1 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -183,11 +183,12 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): self._soledad, gpgbinary=GPG_BINARY_PATH) pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) pubkey = pgp.get_key(ADDRESS, private=False) - cyphertext = pgp.encrypt('data', pubkey) + data = 'data' + cyphertext = pgp.encrypt(data, pubkey) # assert self.assertTrue(cyphertext is not None) self.assertTrue(cyphertext != '') - self.assertTrue(cyphertext != 'data') + self.assertTrue(cyphertext != data) self.assertTrue(pgp.is_encrypted(cyphertext)) self.assertTrue(pgp.is_encrypted(cyphertext)) # decrypt @@ -195,6 +196,8 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): KeyNotFound, pgp.get_key, ADDRESS, private=True) pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) privkey = pgp.get_key(ADDRESS, private=True) + decrypted, _ = pgp.decrypt(cyphertext, privkey) + self.assertEqual(decrypted, data) pgp.delete_key(pubkey) pgp.delete_key(privkey) self.assertRaises( @@ -231,9 +234,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): signed = pgp.sign(data, privkey) pgp.put_ascii_key(PUBLIC_KEY_2, ADDRESS_2) wrongkey = pgp.get_key(ADDRESS_2) - self.assertRaises( - errors.InvalidSignature, - pgp.verify, signed, wrongkey) + self.assertFalse(pgp.verify(signed, wrongkey)) def test_encrypt_sign_with_public_raises(self): pgp = openpgp.OpenPGPScheme( @@ -260,7 +261,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): pgp.decrypt, encrypted_and_signed, privkey, verify=privkey) - def test_decrypt_verify_with_wrong_key_raises(self): + def test_decrypt_verify_with_wrong_key(self): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) @@ -270,9 +271,10 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): encrypted_and_signed = pgp.encrypt(data, pubkey, sign=privkey) pgp.put_ascii_key(PUBLIC_KEY_2, ADDRESS_2) wrongkey = pgp.get_key(ADDRESS_2) - self.assertRaises( - errors.InvalidSignature, - pgp.verify, encrypted_and_signed, wrongkey) + decrypted, validsign = pgp.decrypt(encrypted_and_signed, privkey, + verify=wrongkey) + self.assertEqual(decrypted, data) + self.assertFalse(validsign) def test_sign_verify(self): pgp = openpgp.OpenPGPScheme( @@ -296,9 +298,10 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): data = 'data' encrypted_and_signed = pgp.encrypt( data, pubkey2, sign=privkey) - res = pgp.decrypt( + res, validsign = pgp.decrypt( encrypted_and_signed, privkey2, verify=pubkey) - self.assertTrue(data, res) + self.assertEqual(data, res) + self.assertTrue(validsign) def test_sign_verify_detached_sig(self): pgp = openpgp.OpenPGPScheme( @@ -308,7 +311,8 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): 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)) + validsign = pgp.verify(data, pubkey, detached_sig=signature) + self.assertTrue(validsign) class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): @@ -509,24 +513,46 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): km = self._key_manager() # put raw private key km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) + km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY_2, ADDRESS_2) # encrypt encdata = km.encrypt(self.RAW_DATA, ADDRESS, OpenPGPKey, - fetch_remote=False) + sign=ADDRESS_2, fetch_remote=False) self.assertNotEqual(self.RAW_DATA, encdata) # decrypt - rawdata = km.decrypt(encdata, ADDRESS, OpenPGPKey) + rawdata, signingkey = km.decrypt(encdata, ADDRESS, OpenPGPKey, + verify=ADDRESS_2, fetch_remote=False) self.assertEqual(self.RAW_DATA, rawdata) + key = km.get_key(ADDRESS_2, OpenPGPKey, private=False, + fetch_remote=False) + self.assertEqual(signingkey.fingerprint, key.fingerprint) + + def test_keymanager_openpgp_encrypt_decrypt_wrong_sign(self): + km = self._key_manager() + # put raw keys + km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) + km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY_2, ADDRESS_2) + # encrypt + encdata = km.encrypt(self.RAW_DATA, ADDRESS, OpenPGPKey, + sign=ADDRESS_2, fetch_remote=False) + self.assertNotEqual(self.RAW_DATA, encdata) + # verify + rawdata, signingkey = km.decrypt(encdata, ADDRESS, OpenPGPKey, + verify=ADDRESS, fetch_remote=False) + self.assertEqual(self.RAW_DATA, rawdata) + self.assertTrue(signingkey is None) def test_keymanager_openpgp_sign_verify(self): km = self._key_manager() # put raw private keys km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) - # encrypt signdata = km.sign(self.RAW_DATA, ADDRESS, OpenPGPKey, detach=False) self.assertNotEqual(self.RAW_DATA, signdata) - # decrypt - self.assertTrue(km.verify(signdata, ADDRESS, OpenPGPKey, - fetch_remote=False)) + # verify + signingkey = km.verify(signdata, ADDRESS, OpenPGPKey, + fetch_remote=False) + key = km.get_key(ADDRESS, OpenPGPKey, private=False, + fetch_remote=False) + self.assertEqual(signingkey.fingerprint, key.fingerprint) # Key material for testing -- cgit v1.2.3 From 7094981e43794558fe3542c3d381dfe43c4fa56b Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Fri, 28 Nov 2014 10:37:00 -0600 Subject: Port to soledad new async API --- keymanager/src/leap/keymanager/__init__.py | 438 ++++++++++++++------- keymanager/src/leap/keymanager/errors.py | 6 + keymanager/src/leap/keymanager/keys.py | 86 +++- keymanager/src/leap/keymanager/openpgp.py | 366 ++++++++++------- keymanager/src/leap/keymanager/tests/__init__.py | 109 +++-- .../src/leap/keymanager/tests/test_keymanager.py | 394 ++++-------------- .../src/leap/keymanager/tests/test_openpgp.py | 250 ++++++++++++ .../src/leap/keymanager/tests/test_validation.py | 49 ++- 8 files changed, 1024 insertions(+), 674 deletions(-) create mode 100644 keymanager/src/leap/keymanager/tests/test_openpgp.py (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index b2b05f4a..7ff437e5 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -43,6 +43,8 @@ except (ImportError, AssertionError): import logging import requests +from twisted.internet import defer + from leap.common.check import leap_assert from leap.common.events import signal from leap.common.events import events_pb2 as proto @@ -51,7 +53,8 @@ from leap.common.decorators import memoized_method from leap.keymanager.errors import ( KeyNotFound, KeyAddressMismatch, - KeyNotValidUpgrade + KeyNotValidUpgrade, + UnsupportedKeyTypeError ) from leap.keymanager.validation import ValidationLevel, can_upgrade @@ -196,15 +199,20 @@ class KeyManager(object): @memoized_method(invalidation=300) def _fetch_keys_from_server(self, address): """ - Fetch keys bound to C{address} from nickserver and insert them in + Fetch keys bound to address from nickserver and insert them in local database. :param address: The address bound to the keys. :type address: str - :raise KeyNotFound: If the key was not found on nickserver. + :return: A Deferred which fires when the key is in the storage, + or which fails with KeyNotFound if the key was not found on + nickserver. + :rtype: Deferred + """ # request keys from the nickserver + d = defer.succeed(None) res = None try: res = self._get(self._nickserver_uri, {'address': address}) @@ -212,18 +220,22 @@ class KeyManager(object): server_keys = res.json() # insert keys in local database if self.OPENPGP_KEY in server_keys: - self.put_raw_key( + d = self.put_raw_key( server_keys['openpgp'], OpenPGPKey, address=address, validation=ValidationLevel.Provider_Trust) except requests.exceptions.HTTPError as e: if e.response.status_code == 404: - raise KeyNotFound(address) + d = defer.fail(KeyNotFound(address)) + else: + d = defer.fail(e) logger.warning("HTTP error retrieving key: %r" % (e,)) logger.warning("%s" % (res.content,)) except Exception as e: + d = defer.fail(e) logger.warning("Error retrieving key: %r" % (e,)) + return d # # key management @@ -231,7 +243,7 @@ class KeyManager(object): def send_key(self, ktype): """ - Send user's key of type C{ktype} to provider. + Send user's key of type ktype to provider. Public key bound to user's is sent to provider, which will sign it and replace any prior keys for the same address in its database. @@ -239,27 +251,33 @@ class KeyManager(object): :param ktype: The type of the key. :type ktype: subclass of EncryptionKey - :raise KeyNotFound: If the key was not found in local database. + :return: A Deferred which fires when the key is sent, or which fails + with KeyNotFound if the key was not found in local database. + :rtype: Deferred + + :raise UnsupportedKeyTypeError: if invalid key type """ - leap_assert( - ktype is OpenPGPKey, - 'For now we only know how to send OpenPGP public keys.') - # prepare the public key bound to address - pubkey = self.get_key( + self._assert_supported_key_type(ktype) + + def send(pubkey): + data = { + self.PUBKEY_KEY: pubkey.key_data + } + uri = "%s/%s/users/%s.json" % ( + self._api_uri, + self._api_version, + self._uid) + self._put(uri, data) + signal(proto.KEYMANAGER_DONE_UPLOADING_KEYS, self._address) + + d = self.get_key( self._address, ktype, private=False, fetch_remote=False) - data = { - self.PUBKEY_KEY: pubkey.key_data - } - uri = "%s/%s/users/%s.json" % ( - self._api_uri, - self._api_version, - self._uid) - self._put(uri, data) - signal(proto.KEYMANAGER_DONE_UPLOADING_KEYS, self._address) + d.addCallback(send) + return d def get_key(self, address, ktype, private=False, fetch_remote=True): """ - Return a key of type C{ktype} bound to C{address}. + Return a key of type ktype bound to address. First, search for the key in local storage. If it is not available, then try to fetch from nickserver. @@ -274,36 +292,47 @@ class KeyManager(object): from nickserver :type fetch_remote: bool - :return: A key of type C{ktype} bound to C{address}. - :rtype: EncryptionKey - :raise KeyNotFound: If the key was not found both locally and in - keyserver. + :return: A Deferred which fires with an EncryptionKey of type ktype + bound to address, or which fails with KeyNotFound if no key + was found neither locally or in keyserver. + :rtype: Deferred + + :raise UnsupportedKeyTypeError: if invalid key type """ + self._assert_supported_key_type(ktype) logger.debug("getting key for %s" % (address,)) leap_assert( ktype in self._wrapper_map, 'Unkown key type: %s.' % str(ktype)) - try: - signal(proto.KEYMANAGER_LOOKING_FOR_KEY, address) - # return key if it exists in local database - key = self._wrapper_map[ktype].get_key(address, private=private) - signal(proto.KEYMANAGER_KEY_FOUND, address) + signal(proto.KEYMANAGER_LOOKING_FOR_KEY, address) + def key_found(key): + signal(proto.KEYMANAGER_KEY_FOUND, address) return key - except KeyNotFound: + + def key_not_found(failure): + if not failure.check(KeyNotFound): + return failure + signal(proto.KEYMANAGER_KEY_NOT_FOUND, address) # we will only try to fetch a key from nickserver if fetch_remote # is True and the key is not private. if fetch_remote is False or private is True: - raise + return failure signal(proto.KEYMANAGER_LOOKING_FOR_KEY, address) - self._fetch_keys_from_server(address) # might raise KeyNotFound - key = self._wrapper_map[ktype].get_key(address, private=False) - signal(proto.KEYMANAGER_KEY_FOUND, address) - - return key + d = self._fetch_keys_from_server(address) + d.addCallback( + lambda _: + self._wrapper_map[ktype].get_key(address, private=False)) + d.addCallback(key_found) + return d + + # return key if it exists in local database + d = self._wrapper_map[ktype].get_key(address, private=private) + d.addCallbacks(key_found, key_not_found) + return d def get_all_keys(self, private=False): """ @@ -312,33 +341,50 @@ class KeyManager(object): :param private: Include private keys :type private: bool - :return: A list with all keys in local db. - :rtype: list - """ - return map( - lambda doc: build_key_from_dict( - self._key_class_from_type(doc.content['type']), - doc.content), - self._soledad.get_from_index( - TAGS_PRIVATE_INDEX, - KEYMANAGER_KEY_TAG, - '1' if private else '0')) + :return: A Deferred which fires with a list of all keys in local db. + :rtype: Deferred + """ + def build_keys(docs): + return map( + lambda doc: build_key_from_dict( + self._key_class_from_type(doc.content['type']), + doc.content), + docs) + + # XXX: there is no check that the soledad indexes are ready, as it + # happens with EncryptionScheme. + # The usecases right now are not problematic. This could be solve + # adding a keytype to this funciont and moving the soledad request + # to the EncryptionScheme. + d = self._soledad.get_from_index( + TAGS_PRIVATE_INDEX, + KEYMANAGER_KEY_TAG, + '1' if private else '0') + d.addCallback(build_keys) + return d def gen_key(self, ktype): """ - Generate a key of type C{ktype} bound to the user's address. + Generate a key of type ktype bound to the user's address. :param ktype: The type of the key. :type ktype: subclass of EncryptionKey - :return: The generated key. - :rtype: EncryptionKey + :return: A Deferred which fires with the generated EncryptionKey. + :rtype: Deferred + + :raise UnsupportedKeyTypeError: if invalid key type """ - signal(proto.KEYMANAGER_STARTED_KEY_GENERATION, self._address) - key = self._wrapper_map[ktype].gen_key(self._address) - signal(proto.KEYMANAGER_FINISHED_KEY_GENERATION, self._address) + self._assert_supported_key_type(ktype) - return key + def signal_finished(key): + signal(proto.KEYMANAGER_FINISHED_KEY_GENERATION, self._address) + return key + + signal(proto.KEYMANAGER_STARTED_KEY_GENERATION, self._address) + d = self._wrapper_map[ktype].gen_key(self._address) + d.addCallback(signal_finished) + return d # # Setters/getters @@ -397,7 +443,8 @@ class KeyManager(object): def encrypt(self, data, address, ktype, passphrase=None, sign=None, cipher_algo='AES256', fetch_remote=True): """ - Encrypt C{data} for C{address} and sign with C{sign} address. + Encrypt data with the public key bound to address and sign with with + the private key bound to sign address. :param data: The data to be encrypted. :type data: str @@ -412,34 +459,39 @@ class KeyManager(object): :type sign: str :param cipher_algo: The cipher algorithm to use. :type cipher_algo: str - :param fetch_remote: If key not found in local storage try to fetch + :param fetch_remote: If key is not found in local storage try to fetch from nickserver :type fetch_remote: bool - :return: The encrypted data. - :rtype: str + :return: A Deferred which fires with the encrypted data as str, or + which fails with KeyNotFound if no keys were found neither + locally or in keyserver or fails with EncryptError if failed + encrypting for some reason. + :rtype: Deferred - :raise KeyNotFound: If any of the keys was not found both locally and - in keyserver. - :raise EncryptError: Raised if failed encrypting for some reason. + :raise UnsupportedKeyTypeError: if invalid key type """ - pubkey = self.get_key(address, ktype, private=False, - fetch_remote=fetch_remote) - privkey = None - if sign is not None: - privkey = self.get_key(sign, ktype, private=True) - return self._wrapper_map[ktype].encrypt( - data, pubkey, passphrase, privkey, cipher_algo=cipher_algo) + self._assert_supported_key_type(ktype) + + def encrypt(keys): + pubkey, signkey = keys + return self._wrapper_map[ktype].encrypt( + data, pubkey, passphrase, sign=signkey, + cipher_algo=cipher_algo) + + d = self._get_keys(ktype, address, sign, fetch_remote=fetch_remote) + d.addCallback(encrypt) + return d def decrypt(self, data, address, ktype, passphrase=None, verify=None, fetch_remote=True): """ - Decrypt C{data} using private key from @{address} and verify with - C{verify} address. + Decrypt data using private key from address and verify with public key + bound to verify address. :param data: The data to be decrypted. :type data: str - :param address: The address to who was encrypted. + :param address: The address to whom data was encrypted. :type address: str :param ktype: The type of the key. :type ktype: subclass of EncryptionKey @@ -452,26 +504,59 @@ class KeyManager(object): to fetch from nickserver :type fetch_remote: bool - :return: The decrypted data and the signing key if signature verifies - :rtype: (unicode, EncryptionKey) + :return: A Deferred which fires with the decrypted data as str and the + signing EncryptionKey if signature verifies, or which fails + with KeyNotFound if no keys were found neither locally or in + keyserver or fails with DecryptError if failed decrypting for + some reason. + :rtype: Deferred - :raise KeyNotFound: If any of the keys was not found both locally and - in keyserver. - :raise DecryptError: Raised if failed decrypting for some reason. + :raise UnsupportedKeyTypeError: if invalid key type """ - privkey = self.get_key(address, ktype, private=True) - pubkey = None - if verify is not None: - pubkey = self.get_key(verify, ktype, private=False, - fetch_remote=fetch_remote) - decrypted, signed = self._wrapper_map[ktype].decrypt( - data, privkey, passphrase, pubkey) - return (decrypted, pubkey if signed else None) + self._assert_supported_key_type(ktype) + + def decrypt(keys): + pubkey, privkey = keys + decrypted, signed = self._wrapper_map[ktype].decrypt( + data, privkey, passphrase=passphrase, verify=pubkey) + return (decrypted, pubkey if signed else None) + + d = self._get_keys(ktype, verify, address, fetch_remote) + d.addCallback(decrypt) + return d + + def _get_keys(self, ktype, public, private, fetch_remote=True): + """ + Get public and private keys of ktype. + + :param ktype: The type of the key. + :type ktype: subclass of EncryptionKey + :param public: The address of the public key. + :type public: str + :param private: The address of the private key. + :type private: str + :param fetch_remote: If key for verify not found in local storage try + to fetch from nickserver + :type fetch_remote: bool + + :return: A Deferred which fires with a tuple with public and private + EncryptionKeys, or which fails with KeyNotFound if no keys + were found neither locally or in keyserver. + :rtype: Deferred + """ + dpub = defer.succeed(None) + if public is not None: + dpub = self.get_key(public, ktype, private=False, + fetch_remote=fetch_remote) + dpriv = defer.succeed(None) + if private is not None: + dpriv = self.get_key(private, ktype, private=True) + return defer.gatherResults([dpub, dpriv]) def sign(self, data, address, ktype, digest_algo='SHA512', clearsign=False, detach=True, binary=False): """ - Sign C{data} with C{address}. + Sign data with private key bound to address. :param data: The data to be signed. :type data: str @@ -488,23 +573,30 @@ class KeyManager(object): :param binary: If True, do not ascii armour the output. :type binary: bool - :return: The signed data. - :rtype: str + :return: A Deferred which fires with the signed data as str or fails + with KeyNotFound if no key was found neither locally or in + keyserver or fails with SignFailed if there was any error + signing. + :rtype: Deferred - :raise KeyNotFound: If the key was not found both locally and - in keyserver. - :raise SignFailed: If there was any error signing. + :raise UnsupportedKeyTypeError: if invalid key type """ - privkey = self.get_key(address, ktype, private=True) - return self._wrapper_map[ktype].sign( - data, privkey, digest_algo=digest_algo, clearsign=clearsign, - detach=detach, binary=binary) + self._assert_supported_key_type(ktype) + + def sign(privkey): + return self._wrapper_map[ktype].sign( + data, privkey, digest_algo=digest_algo, clearsign=clearsign, + detach=detach, binary=binary) + + d = self.get_key(address, ktype, private=True) + d.addCallback(sign) + return d def verify(self, data, address, ktype, detached_sig=None, fetch_remote=True): """ - Verify signed C{data} with C{address}, eventually using - C{detached_sig}. + Verify signed data with private key bound to address, eventually using + detached_sig. :param data: The data to be verified. :type data: str @@ -519,71 +611,90 @@ class KeyManager(object): to fetch from nickserver :type fetch_remote: bool - :return: The signing key if signature verifies else None - :rtype: EncryptionKey + :return: A Deferred which fires with the signing EncryptionKey if + signature verifies else None, or which fails with KeyNotFound + if no key was found neither locally or in keyserver. + :rtype: Deferred - :raise KeyNotFound: If the key was not found both locally and - in keyserver. + :raise UnsupportedKeyTypeError: if invalid key type """ - pubkey = self.get_key(address, ktype, private=False, - fetch_remote=fetch_remote) - signed = self._wrapper_map[ktype].verify( - data, pubkey, detached_sig=detached_sig) - return pubkey if signed else None + self._assert_supported_key_type(ktype) + + def verify(pubkey): + signed = self._wrapper_map[ktype].verify( + data, pubkey, detached_sig=detached_sig) + return pubkey if signed else None + + d = self.get_key(address, ktype, private=False, + fetch_remote=fetch_remote) + d.addCallback(verify) + return d def delete_key(self, key): """ - Remove C{key} from storage. - - May raise: - openpgp.errors.KeyNotFound - openpgp.errors.KeyAttributesDiffer + Remove key from storage. :param key: The key to be removed. :type key: EncryptionKey + + :return: A Deferred which fires when the key is deleted, or which fails + KeyNotFound if the key was not found on local storage. + :rtype: Deferred + + :raise UnsupportedKeyTypeError: if invalid key type """ - try: - self._wrapper_map[type(key)].delete_key(key) - except IndexError as e: - leap_assert(False, "Unsupported key type. Error {0!r}".format(e)) + self._assert_supported_key_type(type(key)) + return self._wrapper_map[type(key)].delete_key(key) def put_key(self, key, address): """ - Put C{key} in local storage. + Put key bound to address in local storage. :param key: The key to be stored :type key: EncryptionKey :param address: address for which this key will be active :type address: str - :raises KeyAddressMismatch: if address doesn't match any uid on the key - :raises KeyNotValidUpdate: if a key with the same uid exists and the - new one is not a valid update for it + :return: A Deferred which fires when the key is in the storage, or + which fails with KeyAddressMismatch if address doesn't match + any uid on the key or fails with KeyNotValidUpdate if a key + with the same uid exists and the new one is not a valid update + for it. + :rtype: Deferred + + :raise UnsupportedKeyTypeError: if invalid key type """ - if address not in key.address: - raise KeyAddressMismatch("UID %s found, but expected %s" - % (str(key.address), address)) + self._assert_supported_key_type(type(key)) - try: - old_key = self._wrapper_map[type(key)].get_key(address, - private=key.private) - except KeyNotFound: - old_key = None - - if key.private or can_upgrade(key, old_key): - try: - self._wrapper_map[type(key)].put_key(key, address) - except IndexError as e: - leap_assert( - False, "Unsupported key type. Error {0!r}".format(e)) - else: - raise KeyNotValidUpgrade("Key %s can not be upgraded by new key %s" - % (old_key.key_id, key.key_id)) + if address not in key.address: + return defer.fail( + KeyAddressMismatch("UID %s found, but expected %s" + % (str(key.address), address))) + + def old_key_not_found(failure): + if failure.check(KeyNotFound): + return None + else: + return failure + + def check_upgrade(old_key): + if key.private or can_upgrade(key, old_key): + return self._wrapper_map[type(key)].put_key(key, address) + else: + raise KeyNotValidUpgrade( + "Key %s can not be upgraded by new key %s" + % (old_key.key_id, key.key_id)) + + d = self._wrapper_map[type(key)].get_key(address, + private=key.private) + d.addErrback(old_key_not_found) + d.addCallback(check_upgrade) + return d def put_raw_key(self, key, ktype, address, validation=ValidationLevel.Weak_Chain): """ - Put C{key} in local storage. + Put raw key bound to address in local storage. :param key: The ascii key to be stored :type key: str @@ -595,19 +706,24 @@ class KeyManager(object): (default: 'Weak_Chain') :type validation: ValidationLevel - :raises KeyAddressMismatch: if address doesn't match any uid on the key - :raises KeyNotValidUpdate: if a key with the same uid exists and the - new one is not a valid update for it + :return: A Deferred which fires when the key is in the storage, or + which fails with KeyAddressMismatch if address doesn't match + any uid on the key or fails with KeyNotValidUpdate if a key + with the same uid exists and the new one is not a valid update + for it. + :rtype: Deferred + + :raise UnsupportedKeyTypeError: if invalid key type """ + self._assert_supported_key_type(ktype) pubkey, _ = self._wrapper_map[ktype].parse_ascii_key(key) - pubkey.validation = validation - self.put_key(pubkey, address) + return self.put_key(pubkey, address) def fetch_key(self, address, uri, ktype, validation=ValidationLevel.Weak_Chain): """ - Fetch a public key for C{address} from the network and put it in + Fetch a public key bound to address from the network and put it in local storage. :param address: The email address of the key. @@ -620,22 +736,40 @@ class KeyManager(object): (default: 'Weak_Chain') :type validation: ValidationLevel - :raises KeyNotFound: if not valid key on C{uri} - :raises KeyAddressMismatch: if address doesn't match any uid on the key - :raises KeyNotValidUpdate: if a key with the same uid exists and the - new one is not a valid update for it + :return: A Deferred which fires when the key is in the storage, or + which fails with KeyNotFound: if not valid key on uri or fails + with KeyAddressMismatch if address doesn't match any uid on + the key or fails with KeyNotValidUpdate if a key with the same + uid exists and the new one is not a valid update for it. + :rtype: Deferred + + :raise UnsupportedKeyTypeError: if invalid key type """ + self._assert_supported_key_type(ktype) + res = self._get(uri) if not res.ok: - raise KeyNotFound(uri) + return defer.fail(KeyNotFound(uri)) # XXX parse binary keys pubkey, _ = self._wrapper_map[ktype].parse_ascii_key(res.content) if pubkey is None: - raise KeyNotFound(uri) + return defer.fail(KeyNotFound(uri)) pubkey.validation = validation - self.put_key(pubkey, address) + return self.put_key(pubkey, address) + + def _assert_supported_key_type(self, ktype): + """ + Check if ktype is one of the supported key types + + :param ktype: the type of the key. + :type ktype: subclass of EncryptionKey + + :raise UnsupportedKeyTypeError: if invalid key type + """ + if ktype not in self._wrapper_map: + raise UnsupportedKeyTypeError(str(ktype)) from ._version import get_versions __version__ = get_versions()['version'] diff --git a/keymanager/src/leap/keymanager/errors.py b/keymanager/src/leap/keymanager/errors.py index c068b27f..4041837e 100644 --- a/keymanager/src/leap/keymanager/errors.py +++ b/keymanager/src/leap/keymanager/errors.py @@ -95,3 +95,9 @@ class KeyNotValidUpgrade(Exception): """ Already existing key can not be upgraded with the new key """ + + +class UnsupportedKeyTypeError(Exception): + """ + Invalid key type + """ diff --git a/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py index 0e243ba0..4e98de6c 100644 --- a/keymanager/src/leap/keymanager/keys.py +++ b/keymanager/src/leap/keymanager/keys.py @@ -33,6 +33,7 @@ import time from abc import ABCMeta, abstractmethod from datetime import datetime from leap.common.check import leap_assert +from twisted.internet import defer from leap.keymanager.validation import ValidationLevel, toValidationLevel @@ -277,21 +278,61 @@ class EncryptionScheme(object): """ leap_assert(self._soledad is not None, "Cannot init indexes with null soledad") - # Ask the database for currently existing indexes. - db_indexes = dict(self._soledad.list_indexes()) - # Loop through the indexes we expect to find. - for name, expression in INDEXES.items(): - if name not in db_indexes: - # The index does not yet exist. - self._soledad.create_index(name, *expression) - continue - if expression == db_indexes[name]: - # The index exists and is up to date. - continue - # The index exists but the definition is not what expected, so we - # delete it and add the proper index expression. - self._soledad.delete_index(name) - self._soledad.create_index(name, *expression) + + def init_idexes(indexes): + deferreds = [] + db_indexes = dict(indexes) + # Loop through the indexes we expect to find. + for name, expression in INDEXES.items(): + if name not in db_indexes: + # The index does not yet exist. + d = self._soledad.create_index(name, *expression) + deferreds.append(d) + elif expression != db_indexes[name]: + # The index exists but the definition is not what expected, + # so we delete it and add the proper index expression. + d = self._soledad.delete_index(name) + d.addCallback( + lambda _: + self._soledad.create_index(name, *expression)) + deferreds.append(d) + return defer.gatherResults(deferreds, consumeErrors=True) + + self.deferred_indexes = self._soledad.list_indexes() + self.deferred_indexes.addCallback(init_idexes) + + def _wait_indexes(self, *methods): + """ + Methods that need to wait for the indexes to be ready. + + Heavily based on + http://blogs.fluidinfo.com/terry/2009/05/11/a-mixin-class-allowing-python-__init__-methods-to-work-with-twisted-deferreds/ + + :param methods: methods that need to wait for the indexes to be ready + :type methods: tuple(str) + """ + self.waiting = [] + self.stored = {} + + def restore(_): + for method in self.stored: + setattr(self, method, self.stored[method]) + for d in self.waiting: + d.callback(None) + + def makeWrapper(method): + def wrapper(*args, **kw): + d = defer.Deferred() + d.addCallback(lambda _: self.stored[method](*args, **kw)) + self.waiting.append(d) + return d + return wrapper + + for method in methods: + self.stored[method] = getattr(self, method) + setattr(self, method, makeWrapper(method)) + + self.deferred_indexes.addCallback(restore) @abstractmethod def get_key(self, address, private=False): @@ -303,9 +344,10 @@ class EncryptionScheme(object): :param private: Look for a private key instead of a public one? :type private: bool - :return: The key bound to C{address}. - :rtype: EncryptionKey - @raise KeyNotFound: If the key was not found on local storage. + :return: A Deferred which fires with the EncryptionKey bound to + address, or which fails with KeyNotFound if the key was not + found on local storage. + :rtype: Deferred """ pass @@ -318,6 +360,9 @@ class EncryptionScheme(object): :type key: EncryptionKey :param address: address for which this key will be active. :type address: str + + :return: A Deferred which fires when the key is in the storage. + :rtype: Deferred """ pass @@ -341,6 +386,11 @@ class EncryptionScheme(object): :param key: The key to be removed. :type key: EncryptionKey + + :return: A Deferred which fires when the key is deleted, or which + fails with KeyNotFound if the key was not found on local + storage. + :rtype: Deferred """ pass diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 1d1de989..f81fb0a0 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -28,6 +28,7 @@ import io from datetime import datetime from gnupg import GPG from gnupg.gnupg import GPGUtilities +from twisted.internet import defer from leap.common.check import leap_assert, leap_assert_type, leap_check from leap.keymanager import errors @@ -243,6 +244,7 @@ class OpenPGPScheme(EncryptionScheme): :type gpgbinary: C{str} """ EncryptionScheme.__init__(self, soledad) + self._wait_indexes("get_key", "put_key") self._gpgbinary = gpgbinary # @@ -255,59 +257,67 @@ class OpenPGPScheme(EncryptionScheme): :param address: The address bound to the key. :type address: str - :return: The key bound to C{address}. - :rtype: OpenPGPKey - @raise KeyAlreadyExists: If key already exists in local database. + + :return: A Deferred which fires with the key bound to address, or fails + with KeyAlreadyExists if key already exists in local database. + :rtype: Deferred """ # make sure the key does not already exist leap_assert(is_address(address), 'Not an user address: %s' % address) - try: - self.get_key(address) - raise errors.KeyAlreadyExists(address) - except errors.KeyNotFound: - logger.debug('Key for %s not found' % (address,)) - - with self._temporary_gpgwrapper() as gpg: - # TODO: inspect result, or use decorator - params = gpg.gen_key_input( - key_type='RSA', - key_length=4096, - name_real=address, - name_email=address, - 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 " - "generated." % (address,)) - pubkeys = gpg.list_keys() - - # assert for new key characteristics - - # XXX This exception is not properly catched by the soledad - # bootstrapping, so if we do not finish generating the keys - # we end with a blocked thread -- kali - leap_assert( - len(pubkeys) is 1, # a unitary keyring! - 'Keyring has wrong number of keys: %d.' % len(pubkeys)) - key = gpg.list_keys(secret=True).pop() - leap_assert( - len(key['uids']) is 1, # with just one uid! - 'Wrong number of uids for key: %d.' % len(key['uids'])) - uid_match = False - for uid in key['uids']: - if re.match('.*<%s>$' % address, uid) is not None: - uid_match = True - return - leap_assert(uid_match, 'Key not correctly bound to address.') - # insert both public and private keys in storage - for secret in [True, False]: - key = gpg.list_keys(secret=secret).pop() - openpgp_key = _build_key_from_gpg( - key, gpg.export_keys(key['fingerprint'], secret=secret)) - self.put_key(openpgp_key, address) + def _gen_key(_): + with self._temporary_gpgwrapper() as gpg: + # TODO: inspect result, or use decorator + params = gpg.gen_key_input( + key_type='RSA', + key_length=4096, + name_real=address, + name_email=address, + 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 " + "generated." % (address,)) + pubkeys = gpg.list_keys() + + # assert for new key characteristics + + # XXX This exception is not properly catched by the soledad + # bootstrapping, so if we do not finish generating the keys + # we end with a blocked thread -- kali + + leap_assert( + len(pubkeys) is 1, # a unitary keyring! + 'Keyring has wrong number of keys: %d.' % len(pubkeys)) + key = gpg.list_keys(secret=True).pop() + leap_assert( + len(key['uids']) is 1, # with just one uid! + 'Wrong number of uids for key: %d.' % len(key['uids'])) + uid_match = False + for uid in key['uids']: + if re.match('.*<%s>$' % address, uid) is not None: + uid_match = True + return + leap_assert(uid_match, 'Key not correctly bound to address.') + # insert both public and private keys in storage + deferreds = [] + for secret in [True, False]: + key = gpg.list_keys(secret=secret).pop() + openpgp_key = _build_key_from_gpg( + key, + gpg.export_keys(key['fingerprint'], secret=secret)) + d = self.put_key(openpgp_key, address) + deferreds.append(d) + return defer.gatherResults(deferreds) + + def key_already_exists(_): + raise errors.KeyAlreadyExists(address) - return self.get_key(address, private=True) + d = self.get_key(address) + d.addCallbacks(key_already_exists, _gen_key) + d.addCallback(lambda _: self.get_key(address, private=True)) + return d def get_key(self, address, private=False): """ @@ -318,19 +328,24 @@ class OpenPGPScheme(EncryptionScheme): :param private: Look for a private key instead of a public one? :type private: bool - :return: The key bound to C{address}. - :rtype: OpenPGPKey - @raise KeyNotFound: If the key was not found on local storage. + :return: A Deferred which fires with the OpenPGPKey bound to address, + or which fails with KeyNotFound if the key was not found on + local storage. + :rtype: Deferred """ address = _parse_address(address) - doc = self._get_key_doc(address, private) - if doc is None: - raise errors.KeyNotFound(address) - leap_assert( - address in doc.content[KEY_ADDRESS_KEY], - 'Wrong address in key data.') - return build_key_from_dict(OpenPGPKey, doc.content) + def build_key(doc): + if doc is None: + raise errors.KeyNotFound(address) + leap_assert( + address in doc.content[KEY_ADDRESS_KEY], + 'Wrong address in key data.') + return build_key_from_dict(OpenPGPKey, doc.content) + + d = self._get_key_doc(address, private) + d.addCallback(build_key) + return d def parse_ascii_key(self, key_data): """ @@ -388,6 +403,9 @@ class OpenPGPScheme(EncryptionScheme): :type key_data: str or unicode :param address: address for which this key will be active :type address: str + + :return: A Deferred which fires when the OpenPGPKey is in the storage. + :rtype: Deferred """ leap_assert_type(key_data, (str, unicode)) @@ -395,12 +413,17 @@ class OpenPGPScheme(EncryptionScheme): try: openpgp_pubkey, openpgp_privkey = self.parse_ascii_key(key_data) except (errors.KeyAddressMismatch, errors.KeyFingerprintMismatch) as e: - leap_assert(False, repr(e)) + return defer.fail(e) + def put_key(_, key): + return self.put_key(key, address) + + d = defer.succeed(None) if openpgp_pubkey is not None: - self.put_key(openpgp_pubkey, address) + d.addCallback(put_key, openpgp_pubkey) if openpgp_privkey is not None: - self.put_key(openpgp_privkey, address) + d.addCallback(put_key, openpgp_privkey) + return d def put_key(self, key, address): """ @@ -410,42 +433,59 @@ class OpenPGPScheme(EncryptionScheme): :type key: OpenPGPKey :param address: address for which this key will be active. :type address: str + + :return: A Deferred which fires when the key is in the storage. + :rtype: Deferred """ - self._put_key_doc(key) - self._put_active_doc(key, address) + d = self._put_key_doc(key) + d.addCallback(lambda _: self._put_active_doc(key, address)) + return d def _put_key_doc(self, key): """ Put key document in soledad :type key: OpenPGPKey - """ - docs = self._soledad.get_from_index( + :rtype: Deferred + """ + def check_and_put(docs, key): + if len(docs) == 1: + doc = docs.pop() + if key.fingerprint == doc.content[KEY_FINGERPRINT_KEY]: + # in case of an update of the key merge them with gnupg + with self._temporary_gpgwrapper() as gpg: + gpg.import_keys(doc.content[KEY_DATA_KEY]) + gpg.import_keys(key.key_data) + gpgkey = gpg.list_keys(secret=key.private).pop() + key = _build_key_from_gpg( + gpgkey, + gpg.export_keys(gpgkey['fingerprint'], + secret=key.private)) + doc.set_json(key.get_json()) + d = self._soledad.put_doc(doc) + else: + logger.critical( + "Can't put a key whith the same key_id and different " + "fingerprint: %s, %s" + % (key.fingerprint, doc.content[KEY_FINGERPRINT_KEY])) + d = defer.fail( + errors.KeyFingerprintMismatch(key.fingerprint)) + elif len(docs) > 1: + logger.critical( + "There is more than one key with the same key_id %s" + % (key.key_id,)) + d = defer.fail(errors.KeyAttributesDiffer(key.key_id)) + else: + d = self._soledad.create_doc_from_json(key.get_json()) + return d + + d = self._soledad.get_from_index( TYPE_ID_PRIVATE_INDEX, self.KEY_TYPE, key.key_id, '1' if key.private else '0') - if len(docs) != 0: - doc = docs.pop() - if key.fingerprint == doc.content[KEY_FINGERPRINT_KEY]: - # in case of an update of the key merge them with gnupg - with self._temporary_gpgwrapper() as gpg: - gpg.import_keys(doc.content[KEY_DATA_KEY]) - gpg.import_keys(key.key_data) - gpgkey = gpg.list_keys(secret=key.private).pop() - key = _build_key_from_gpg( - gpgkey, - gpg.export_keys(gpgkey['fingerprint'], - secret=key.private)) - doc.set_json(key.get_json()) - self._soledad.put_doc(doc) - else: - logger.critical( - "Can't put a key whith the same key_id and different " - "fingerprint: %s, %s" - % (key.fingerprint, doc.content[KEY_FINGERPRINT_KEY])) - else: - self._soledad.create_doc_from_json(key.get_json()) + d.addCallback(check_and_put, key) + return d def _put_active_doc(self, key, address): """ @@ -453,24 +493,37 @@ class OpenPGPScheme(EncryptionScheme): :type key: OpenPGPKey :type addresses: str + :rtype: Deferred """ - docs = self._soledad.get_from_index( + def check_and_put(docs): + if len(docs) == 1: + doc = docs.pop() + doc.set_json(key.get_active_json(address)) + d = self._soledad.put_doc(doc) + else: + if len(docs) > 1: + logger.error("There is more than one active key document " + "for the address %s" % (address,)) + deferreds = [] + for doc in docs: + delete = self._soledad.delete_doc(doc) + deferreds.append(delete) + d = defer.gatherResults(deferreds, consumeErrors=True) + else: + d = defer.succeed(None) + + d.addCallback( + lambda _: self._soledad.create_doc_from_json( + key.get_active_json(address))) + return d + + d = self._soledad.get_from_index( TYPE_ADDRESS_PRIVATE_INDEX, self.ACTIVE_TYPE, address, '1' if key.private else '0') - if len(docs) == 1: - doc = docs.pop() - doc.set_json(key.get_active_json(address)) - self._soledad.put_doc(doc) - else: - if len(docs) > 1: - logger.error("There is more than one active key document " - "for the address %s" % (address,)) - for doc in docs: - self._soledad.delete_doc(doc) - self._soledad.create_doc_from_json( - key.get_active_json(address)) + d.addCallback(check_and_put) + return d def _get_key_doc(self, address, private=False): """ @@ -482,69 +535,94 @@ class OpenPGPScheme(EncryptionScheme): :type address: str :param private: Whether to look for a private key. :type private: bool - :return: The document with the key or None if it does not exist. - :rtype: leap.soledad.document.SoledadDocument + + :return: A Deferred which fires with the SoledadDocument with the key + or None if it does not exist. + :rtype: Deferred """ - activedoc = self._soledad.get_from_index( + def get_key_from_active_doc(activedoc): + if len(activedoc) is 0: + return None + leap_assert( + len(activedoc) is 1, + 'Found more than one key for address %s!' % (address,)) + + key_id = activedoc[0].content[KEY_ID_KEY] + d = self._soledad.get_from_index( + TYPE_ID_PRIVATE_INDEX, + self.KEY_TYPE, + key_id, + '1' if private else '0') + d.addCallback(get_doc, key_id) + return d + + def get_doc(doclist, key_id): + leap_assert( + len(doclist) is 1, + 'There is %d keys for id %s!' % (len(doclist), key_id)) + return doclist.pop() + + d = self._soledad.get_from_index( TYPE_ADDRESS_PRIVATE_INDEX, self.ACTIVE_TYPE, address, '1' if private else '0') - if len(activedoc) is 0: - return None - leap_assert( - len(activedoc) is 1, - 'Found more than one key for address %s!' % (address,)) - - key_id = activedoc[0].content[KEY_ID_KEY] - doclist = self._soledad.get_from_index( - TYPE_ID_PRIVATE_INDEX, - self.KEY_TYPE, - key_id, - '1' if private else '0') - leap_assert( - len(doclist) is 1, - 'There is %d keys for id %s!' % (len(doclist), key_id)) - return doclist.pop() + d.addCallback(get_key_from_active_doc) + return d def delete_key(self, key): """ Remove C{key} from storage. - May raise: - errors.KeyNotFound - :param key: The key to be removed. :type key: EncryptionKey + + :return: A Deferred which fires when the key is deleted, or which + fails with KeyNotFound if the key was not found on local + storage. + :rtype: Deferred """ leap_assert_type(key, OpenPGPKey) - activedocs = self._soledad.get_from_index( - TYPE_ID_PRIVATE_INDEX, - self.ACTIVE_TYPE, - key.key_id, - '1' if key.private else '0') - for doc in activedocs: - self._soledad.delete_doc(doc) - docs = self._soledad.get_from_index( + def delete_docs(activedocs): + deferreds = [] + for doc in activedocs: + d = self._soledad.delete_doc(doc) + deferreds.append(d) + return defer.gatherResults(deferreds) + + def get_key_docs(_): + return self._soledad.get_from_index( + TYPE_ID_PRIVATE_INDEX, + self.KEY_TYPE, + key.key_id, + '1' if key.private else '0') + + def delete_key(docs): + if len(docs) == 0: + raise errors.KeyNotFound(key) + if len(docs) > 1: + logger.critical("There is more than one key for key_id %s" + % key.key_id) + + doc = None + for d in docs: + if d.content['fingerprint'] == key.fingerprint: + doc = d + break + if doc is None: + raise errors.KeyNotFound(key) + return self._soledad.delete_doc(doc) + + d = self._soledad.get_from_index( TYPE_ID_PRIVATE_INDEX, - self.KEY_TYPE, + self.ACTIVE_TYPE, key.key_id, '1' if key.private else '0') - if len(docs) == 0: - raise errors.KeyNotFound(key) - if len(docs) > 1: - logger.critical("There is more than one key for key_id %s" - % key.key_id) - - doc = None - for d in docs: - if d.content['fingerprint'] == key.fingerprint: - doc = d - break - if doc is None: - raise errors.KeyNotFound(key) - self._soledad.delete_doc(doc) + d.addCallback(delete_docs) + d.addCallback(get_key_docs) + d.addCallback(delete_key) + return d # # Data encryption, decryption, signing and verifying diff --git a/keymanager/src/leap/keymanager/tests/__init__.py b/keymanager/src/leap/keymanager/tests/__init__.py index 1ea33b57..05b44871 100644 --- a/keymanager/src/leap/keymanager/tests/__init__.py +++ b/keymanager/src/leap/keymanager/tests/__init__.py @@ -18,37 +18,25 @@ Base classes for the Key Manager tests. """ -from mock import Mock +from twisted.internet.defer import gatherResults +from twisted.trial import unittest from leap.common.testing.basetest import BaseLeapTest from leap.soledad.client import Soledad from leap.keymanager import KeyManager +from leap.keymanager.openpgp import OpenPGPKey ADDRESS = 'leap@leap.se' +ADDRESS_2 = 'anotheruser@leap.se' # XXX discover the gpg binary path GPG_BINARY_PATH = '/usr/bin/gpg' -class KeyManagerWithSoledadTestCase(BaseLeapTest): +class KeyManagerWithSoledadTestCase(unittest.TestCase, BaseLeapTest): def setUp(self): - # mock key fetching and storing so Soledad doesn't fail when trying to - # reach the server. - Soledad._get_secrets_from_shared_db = Mock(return_value=None) - Soledad._put_secrets_in_shared_db = Mock(return_value=None) - - class MockSharedDB(object): - - get_doc = Mock(return_value=None) - put_doc = Mock() - lock = Mock(return_value=('atoken', 300)) - unlock = Mock(return_value=True) - - def __call__(self): - return self - - Soledad._shared_db = MockSharedDB() + self.setUpEnv() self._soledad = Soledad( u"leap@leap.se", @@ -58,14 +46,32 @@ class KeyManagerWithSoledadTestCase(BaseLeapTest): server_url='', cert_file=None, auth_token=None, + syncable=False ) def tearDown(self): km = self._key_manager() - for key in km.get_all_keys(): - km._wrapper_map[key.__class__].delete_key(key) - for key in km.get_all_keys(private=True): - km._wrapper_map[key.__class__].delete_key(key) + + def delete_keys(keys): + deferreds = [] + for key in keys: + d = km._wrapper_map[key.__class__].delete_key(key) + deferreds.append(d) + return gatherResults(deferreds) + + def get_and_delete_keys(_): + deferreds = [] + for private in [True, False]: + d = km.get_all_keys(private=private) + d.addCallback(delete_keys) + deferreds.append(d) + return gatherResults(deferreds) + + # wait for the indexes to be ready for the tear down + d = km._wrapper_map[OpenPGPKey].deferred_indexes + d.addCallback(get_and_delete_keys) + d.addCallback(lambda _: self.tearDownEnv()) + return d def _key_manager(self, user=ADDRESS, url='', token=None): return KeyManager(user, url, self._soledad, token=token, @@ -234,3 +240,62 @@ RZXoH+FTg9UAW87eqU610npOkT6cRaBxaMK/mDtGNdc= =JTFu -----END PGP PRIVATE KEY BLOCK----- """ + +# key 7FEE575A: public key "anotheruser " +PUBLIC_KEY_2 = """ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.10 (GNU/Linux) + +mI0EUYwJXgEEAMbTKHuPJ5/Gk34l9Z06f+0WCXTDXdte1UBoDtZ1erAbudgC4MOR +gquKqoj3Hhw0/ILqJ88GcOJmKK/bEoIAuKaqlzDF7UAYpOsPZZYmtRfPC2pTCnXq +Z1vdeqLwTbUspqXflkCkFtfhGKMq5rH8GV5a3tXZkRWZhdNwhVXZagC3ABEBAAG0 +IWFub3RoZXJ1c2VyIDxhbm90aGVydXNlckBsZWFwLnNlPoi4BBMBAgAiBQJRjAle +AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRB/nfpof+5XWotuA/4tLN4E +gUr7IfLy2HkHAxzw7A4rqfMN92DIM9mZrDGaWRrOn3aVF7VU1UG7MDkHfPvp/cFw +ezoCw4s4IoHVc/pVlOkcHSyt4/Rfh248tYEJmFCJXGHpkK83VIKYJAithNccJ6Q4 +JE/o06Mtf4uh/cA1HUL4a4ceqUhtpLJULLeKo7iNBFGMCV4BBADsyQI7GR0wSAxz +VayLjuPzgT+bjbFeymIhjuxKIEwnIKwYkovztW+4bbOcQs785k3Lp6RzvigTpQQt +Z/hwcLOqZbZw8t/24+D+Pq9mMP2uUvCFFqLlVvA6D3vKSQ/XNN+YB919WQ04jh63 +yuRe94WenT1RJd6xU1aaUff4rKizuQARAQABiJ8EGAECAAkFAlGMCV4CGwwACgkQ +f536aH/uV1rPZQQAqCzRysOlu8ez7PuiBD4SebgRqWlxa1TF1ujzfLmuPivROZ2X +Kw5aQstxgGSjoB7tac49s0huh4X8XK+BtJBfU84JS8Jc2satlfwoyZ35LH6sDZck +I+RS/3we6zpMfHs3vvp9xgca6ZupQxivGtxlJs294TpJorx+mFFqbV17AzQ= +=Thdu +-----END PGP PUBLIC KEY BLOCK----- +""" + +PRIVATE_KEY_2 = """ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.10 (GNU/Linux) + +lQHYBFGMCV4BBADG0yh7jyefxpN+JfWdOn/tFgl0w13bXtVAaA7WdXqwG7nYAuDD +kYKriqqI9x4cNPyC6ifPBnDiZiiv2xKCALimqpcwxe1AGKTrD2WWJrUXzwtqUwp1 +6mdb3Xqi8E21LKal35ZApBbX4RijKuax/BleWt7V2ZEVmYXTcIVV2WoAtwARAQAB +AAP7BLuSAx7tOohnimEs74ks8l/L6dOcsFQZj2bqs4AoY3jFe7bV0tHr4llypb/8 +H3/DYvpf6DWnCjyUS1tTnXSW8JXtx01BUKaAufSmMNg9blKV6GGHlT/Whe9uVyks +7XHk/+9mebVMNJ/kNlqq2k+uWqJohzC8WWLRK+d1tBeqDsECANZmzltPaqUsGV5X +C3zszE3tUBgptV/mKnBtopKi+VH+t7K6fudGcG+bAcZDUoH/QVde52mIIjjIdLje +uajJuHUCAO1mqh+vPoGv4eBLV7iBo3XrunyGXiys4a39eomhxTy3YktQanjjx+ty +GltAGCs5PbWGO6/IRjjvd46wh53kzvsCAO0J97gsWhzLuFnkxFAJSPk7RRlyl7lI +1XS/x0Og6j9XHCyY1OYkfBm0to3UlCfkgirzCYlTYObCofzdKFIPDmSqHbQhYW5v +dGhlcnVzZXIgPGFub3RoZXJ1c2VyQGxlYXAuc2U+iLgEEwECACIFAlGMCV4CGwMG +CwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEH+d+mh/7ldai24D/i0s3gSBSvsh +8vLYeQcDHPDsDiup8w33YMgz2ZmsMZpZGs6fdpUXtVTVQbswOQd8++n9wXB7OgLD +izgigdVz+lWU6RwdLK3j9F+Hbjy1gQmYUIlcYemQrzdUgpgkCK2E1xwnpDgkT+jT +oy1/i6H9wDUdQvhrhx6pSG2kslQst4qjnQHYBFGMCV4BBADsyQI7GR0wSAxzVayL +juPzgT+bjbFeymIhjuxKIEwnIKwYkovztW+4bbOcQs785k3Lp6RzvigTpQQtZ/hw +cLOqZbZw8t/24+D+Pq9mMP2uUvCFFqLlVvA6D3vKSQ/XNN+YB919WQ04jh63yuRe +94WenT1RJd6xU1aaUff4rKizuQARAQABAAP9EyElqJ3dq3EErXwwT4mMnbd1SrVC +rUJrNWQZL59mm5oigS00uIyR0SvusOr+UzTtd8ysRuwHy5d/LAZsbjQStaOMBILx +77TJveOel0a1QK0YSMF2ywZMCKvquvjli4hAtWYz/EwfuzQN3t23jc5ny+GqmqD2 +3FUxLJosFUfLNmECAO9KhVmJi+L9dswIs+2Dkjd1eiRQzNOEVffvYkGYZyKxNiXF +UA5kvyZcB4iAN9sWCybE4WHZ9jd4myGB0MPDGxkCAP1RsXJbbuD6zS7BXe5gwunO +2q4q7ptdSl/sJYQuTe1KNP5d/uGsvlcFfsYjpsopasPjFBIncc/2QThMKlhoEaEB +/0mVAxpT6SrEvUbJ18z7kna24SgMPr3OnPMxPGfvNLJY/Xv/A17YfoqjmByCvsKE +JCDjopXtmbcrZyoEZbEht9mko4ifBBgBAgAJBQJRjAleAhsMAAoJEH+d+mh/7lda +z2UEAKgs0crDpbvHs+z7ogQ+Enm4EalpcWtUxdbo83y5rj4r0TmdlysOWkLLcYBk +o6Ae7WnOPbNIboeF/FyvgbSQX1POCUvCXNrGrZX8KMmd+Sx+rA2XJCPkUv98Hus6 +THx7N776fcYHGumbqUMYrxrcZSbNveE6SaK8fphRam1dewM0 +=a5gs +-----END PGP PRIVATE KEY BLOCK----- +""" diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index ee4462a1..b8ef88ae 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -23,12 +23,12 @@ Tests for the Key Manager. from datetime import datetime from mock import Mock -from leap.common.testing.basetest import BaseLeapTest +from twisted.internet.defer import inlineCallbacks +from twisted.trial import unittest + from leap.keymanager import ( - openpgp, KeyNotFound, KeyAddressMismatch, - errors, ) from leap.keymanager.openpgp import OpenPGPKey from leap.keymanager.keys import ( @@ -42,23 +42,16 @@ from leap.keymanager.validation import ( from leap.keymanager.tests import ( KeyManagerWithSoledadTestCase, ADDRESS, + ADDRESS_2, KEY_FINGERPRINT, PUBLIC_KEY, + PUBLIC_KEY_2, PRIVATE_KEY, - GPG_BINARY_PATH + PRIVATE_KEY_2, ) -ADDRESS_2 = 'anotheruser@leap.se' - - -class KeyManagerUtilTestCase(BaseLeapTest): - - def setUp(self): - pass - - def tearDown(self): - pass +class KeyManagerUtilTestCase(unittest.TestCase): def test_is_address(self): self.assertTrue( @@ -128,227 +121,43 @@ class KeyManagerUtilTestCase(BaseLeapTest): 'Wrong data in key.') -class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): - - def _test_openpgp_gen_key(self): - pgp = openpgp.OpenPGPScheme(self._soledad) - self.assertRaises(KeyNotFound, pgp.get_key, 'user@leap.se') - key = pgp.gen_key('user@leap.se') - self.assertIsInstance(key, openpgp.OpenPGPKey) - self.assertEqual( - ['user@leap.se'], key.address, 'Wrong address bound to key.') - self.assertEqual( - 4096, key.length, 'Wrong key length.') - - def test_openpgp_put_delete_key(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) - self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) - key = pgp.get_key(ADDRESS, private=False) - pgp.delete_key(key) - self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - - def test_openpgp_put_ascii_key(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) - self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) - key = pgp.get_key(ADDRESS, private=False) - self.assertIsInstance(key, openpgp.OpenPGPKey) - self.assertTrue( - ADDRESS in key.address, 'Wrong address bound to key.') - self.assertEqual( - 4096, key.length, 'Wrong key length.') - pgp.delete_key(key) - self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - - def test_get_public_key(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) - self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) - self.assertRaises( - KeyNotFound, pgp.get_key, ADDRESS, private=True) - key = pgp.get_key(ADDRESS, private=False) - self.assertTrue(ADDRESS in key.address) - self.assertFalse(key.private) - self.assertEqual(KEY_FINGERPRINT, key.fingerprint) - pgp.delete_key(key) - self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - - def test_openpgp_encrypt_decrypt(self): - # encrypt - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) - pubkey = pgp.get_key(ADDRESS, private=False) - data = 'data' - cyphertext = pgp.encrypt(data, pubkey) - # assert - self.assertTrue(cyphertext is not None) - self.assertTrue(cyphertext != '') - self.assertTrue(cyphertext != data) - self.assertTrue(pgp.is_encrypted(cyphertext)) - self.assertTrue(pgp.is_encrypted(cyphertext)) - # decrypt - self.assertRaises( - KeyNotFound, pgp.get_key, ADDRESS, private=True) - pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) - privkey = pgp.get_key(ADDRESS, private=True) - decrypted, _ = pgp.decrypt(cyphertext, privkey) - self.assertEqual(decrypted, data) - pgp.delete_key(pubkey) - pgp.delete_key(privkey) - self.assertRaises( - KeyNotFound, pgp.get_key, ADDRESS, private=False) - self.assertRaises( - KeyNotFound, pgp.get_key, ADDRESS, private=True) - - def test_verify_with_private_raises(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) - data = 'data' - privkey = pgp.get_key(ADDRESS, private=True) - signed = pgp.sign(data, privkey) - self.assertRaises( - AssertionError, - pgp.verify, signed, privkey) - - def test_sign_with_public_raises(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) - data = 'data' - self.assertRaises( - AssertionError, - pgp.sign, data, ADDRESS, OpenPGPKey) - - def test_verify_with_wrong_key_raises(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) - data = 'data' - privkey = pgp.get_key(ADDRESS, private=True) - signed = pgp.sign(data, privkey) - pgp.put_ascii_key(PUBLIC_KEY_2, ADDRESS_2) - wrongkey = pgp.get_key(ADDRESS_2) - self.assertFalse(pgp.verify(signed, wrongkey)) - - def test_encrypt_sign_with_public_raises(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) - data = 'data' - privkey = pgp.get_key(ADDRESS, private=True) - pubkey = pgp.get_key(ADDRESS, private=False) - self.assertRaises( - AssertionError, - pgp.encrypt, data, privkey, sign=pubkey) - - def test_decrypt_verify_with_private_raises(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) - data = 'data' - privkey = pgp.get_key(ADDRESS, private=True) - pubkey = pgp.get_key(ADDRESS, private=False) - encrypted_and_signed = pgp.encrypt( - data, pubkey, sign=privkey) - self.assertRaises( - AssertionError, - pgp.decrypt, - encrypted_and_signed, privkey, verify=privkey) - - def test_decrypt_verify_with_wrong_key(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) - data = 'data' - privkey = pgp.get_key(ADDRESS, private=True) - pubkey = pgp.get_key(ADDRESS, private=False) - encrypted_and_signed = pgp.encrypt(data, pubkey, sign=privkey) - pgp.put_ascii_key(PUBLIC_KEY_2, ADDRESS_2) - wrongkey = pgp.get_key(ADDRESS_2) - decrypted, validsign = pgp.decrypt(encrypted_and_signed, privkey, - verify=wrongkey) - self.assertEqual(decrypted, data) - self.assertFalse(validsign) - - def test_sign_verify(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) - data = 'data' - privkey = pgp.get_key(ADDRESS, private=True) - signed = pgp.sign(data, privkey, detach=False) - pubkey = pgp.get_key(ADDRESS, private=False) - self.assertTrue(pgp.verify(signed, pubkey)) - - def test_encrypt_sign_decrypt_verify(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) - pubkey = pgp.get_key(ADDRESS, private=False) - privkey = pgp.get_key(ADDRESS, private=True) - pgp.put_ascii_key(PRIVATE_KEY_2, ADDRESS_2) - pubkey2 = pgp.get_key(ADDRESS_2, private=False) - privkey2 = pgp.get_key(ADDRESS_2, private=True) - data = 'data' - encrypted_and_signed = pgp.encrypt( - data, pubkey2, sign=privkey) - res, validsign = pgp.decrypt( - encrypted_and_signed, privkey2, verify=pubkey) - self.assertEqual(data, res) - self.assertTrue(validsign) - - def test_sign_verify_detached_sig(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) - data = 'data' - privkey = pgp.get_key(ADDRESS, private=True) - signature = pgp.sign(data, privkey, detach=True) - pubkey = pgp.get_key(ADDRESS, private=False) - validsign = pgp.verify(data, pubkey, detached_sig=signature) - self.assertTrue(validsign) - - class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): + @inlineCallbacks def test_get_all_keys_in_db(self): km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) + yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) # get public keys - keys = km.get_all_keys(False) + keys = yield km.get_all_keys(False) self.assertEqual(len(keys), 1, 'Wrong number of keys') self.assertTrue(ADDRESS in keys[0].address) self.assertFalse(keys[0].private) # get private keys - keys = km.get_all_keys(True) + keys = yield km.get_all_keys(True) self.assertEqual(len(keys), 1, 'Wrong number of keys') self.assertTrue(ADDRESS in keys[0].address) self.assertTrue(keys[0].private) + @inlineCallbacks def test_get_public_key(self): km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) + yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) # get the key - key = km.get_key(ADDRESS, OpenPGPKey, private=False, - fetch_remote=False) + key = yield km.get_key(ADDRESS, OpenPGPKey, private=False, + fetch_remote=False) self.assertTrue(key is not None) self.assertTrue(ADDRESS in key.address) self.assertEqual( key.fingerprint.lower(), KEY_FINGERPRINT.lower()) self.assertFalse(key.private) + @inlineCallbacks def test_get_private_key(self): km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) + yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) # get the key - key = km.get_key(ADDRESS, OpenPGPKey, private=True, - fetch_remote=False) + key = yield km.get_key(ADDRESS, OpenPGPKey, private=True, + fetch_remote=False) self.assertTrue(key is not None) self.assertTrue(ADDRESS in key.address) self.assertEqual( @@ -357,17 +166,17 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): def test_send_key_raises_key_not_found(self): km = self._key_manager() - self.assertRaises( - KeyNotFound, - km.send_key, OpenPGPKey) + d = km.send_key(OpenPGPKey) + return self.assertFailure(d, KeyNotFound) + @inlineCallbacks def test_send_key(self): """ Test that request is well formed when sending keys to server. """ token = "mytoken" km = self._key_manager(token=token) - km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY, ADDRESS) + yield km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY, ADDRESS) km._fetcher.put = Mock() # the following data will be used on the send km.ca_cert_path = 'capath' @@ -375,10 +184,11 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km.uid = 'myuid' km.api_uri = 'apiuri' km.api_version = 'apiver' - km.send_key(OpenPGPKey) + yield km.send_key(OpenPGPKey) # setup expected args + pubkey = yield km.get_key(km._address, OpenPGPKey) data = { - km.PUBKEY_KEY: km.get_key(km._address, OpenPGPKey).key_data, + km.PUBKEY_KEY: pubkey.key_data, } url = '%s/%s/users/%s.json' % ('apiuri', 'apiver', 'myuid') km._fetcher.put.assert_called_once_with( @@ -386,7 +196,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): headers={'Authorization': 'Token token=%s' % token}, ) - def test__fetch_keys_from_server(self): + def test_fetch_keys_from_server(self): """ Test that the request is well formed when fetching keys from server. """ @@ -406,15 +216,19 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km._fetcher.get = Mock( return_value=Response()) km.ca_cert_path = 'cacertpath' - # do the fetch - km._fetch_keys_from_server(ADDRESS_2) - # and verify the call - km._fetcher.get.assert_called_once_with( - 'http://nickserver.domain', - data={'address': ADDRESS_2}, - verify='cacertpath', - ) + def verify_the_call(_): + km._fetcher.get.assert_called_once_with( + 'http://nickserver.domain', + data={'address': ADDRESS_2}, + verify='cacertpath', + ) + + d = km._fetch_keys_from_server(ADDRESS_2) + d.addCallback(verify_the_call) + return d + + @inlineCallbacks def test_get_key_fetches_from_server(self): """ Test that getting a key successfuly fetches from server. @@ -435,26 +249,26 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km._fetcher.get = Mock(return_value=Response()) km.ca_cert_path = 'cacertpath' # try to key get without fetching from server - self.assertRaises( - KeyNotFound, km.get_key, ADDRESS, OpenPGPKey, - fetch_remote=False - ) + d = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + yield self.assertFailure(d, KeyNotFound) # try to get key fetching from server. - key = km.get_key(ADDRESS, OpenPGPKey) + key = yield km.get_key(ADDRESS, OpenPGPKey) self.assertIsInstance(key, OpenPGPKey) self.assertTrue(ADDRESS in key.address) + @inlineCallbacks def test_put_key_ascii(self): """ Test that putting ascii key works """ km = self._key_manager(url='http://nickserver.domain') - km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) - key = km.get_key(ADDRESS, OpenPGPKey) + yield km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) + key = yield km.get_key(ADDRESS, OpenPGPKey) self.assertIsInstance(key, OpenPGPKey) self.assertTrue(ADDRESS in key.address) + @inlineCallbacks def test_fetch_uri_ascii_key(self): """ Test that fetch key downloads the ascii key and gets included in @@ -469,8 +283,8 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km._fetcher.get = Mock(return_value=Response()) km.ca_cert_path = 'cacertpath' - km.fetch_key(ADDRESS, "http://site.domain/key", OpenPGPKey) - key = km.get_key(ADDRESS, OpenPGPKey) + yield km.fetch_key(ADDRESS, "http://site.domain/key", OpenPGPKey) + key = yield km.get_key(ADDRESS, OpenPGPKey) self.assertEqual(KEY_FINGERPRINT, key.fingerprint) def test_fetch_uri_empty_key(self): @@ -485,8 +299,8 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km._fetcher.get = Mock(return_value=Response()) km.ca_cert_path = 'cacertpath' - self.assertRaises(KeyNotFound, km.fetch_key, - ADDRESS, "http://site.domain/key", OpenPGPKey) + d = km.fetch_key(ADDRESS, "http://site.domain/key", OpenPGPKey) + return self.assertFailure(d, KeyNotFound) def test_fetch_uri_address_differ(self): """ @@ -501,120 +315,66 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km._fetcher.get = Mock(return_value=Response()) km.ca_cert_path = 'cacertpath' - self.assertRaises(KeyAddressMismatch, km.fetch_key, - ADDRESS_2, "http://site.domain/key", OpenPGPKey) + d = km.fetch_key(ADDRESS_2, "http://site.domain/key", OpenPGPKey) + return self.assertFailure(d, KeyAddressMismatch) class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): RAW_DATA = 'data' + @inlineCallbacks def test_keymanager_openpgp_encrypt_decrypt(self): km = self._key_manager() # put raw private key - km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) - km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY_2, ADDRESS_2) + yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) + yield km._wrapper_map[OpenPGPKey].put_ascii_key( + PRIVATE_KEY_2, ADDRESS_2) # encrypt - encdata = km.encrypt(self.RAW_DATA, ADDRESS, OpenPGPKey, - sign=ADDRESS_2, fetch_remote=False) + encdata = yield km.encrypt(self.RAW_DATA, ADDRESS, OpenPGPKey, + sign=ADDRESS_2, fetch_remote=False) self.assertNotEqual(self.RAW_DATA, encdata) # decrypt - rawdata, signingkey = km.decrypt(encdata, ADDRESS, OpenPGPKey, - verify=ADDRESS_2, fetch_remote=False) + rawdata, signingkey = yield km.decrypt( + encdata, ADDRESS, OpenPGPKey, verify=ADDRESS_2, fetch_remote=False) self.assertEqual(self.RAW_DATA, rawdata) - key = km.get_key(ADDRESS_2, OpenPGPKey, private=False, - fetch_remote=False) + key = yield km.get_key(ADDRESS_2, OpenPGPKey, private=False, + fetch_remote=False) self.assertEqual(signingkey.fingerprint, key.fingerprint) + @inlineCallbacks def test_keymanager_openpgp_encrypt_decrypt_wrong_sign(self): km = self._key_manager() # put raw keys - km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) - km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY_2, ADDRESS_2) + yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) + yield km._wrapper_map[OpenPGPKey].put_ascii_key( + PRIVATE_KEY_2, ADDRESS_2) # encrypt - encdata = km.encrypt(self.RAW_DATA, ADDRESS, OpenPGPKey, - sign=ADDRESS_2, fetch_remote=False) + encdata = yield km.encrypt(self.RAW_DATA, ADDRESS, OpenPGPKey, + sign=ADDRESS_2, fetch_remote=False) self.assertNotEqual(self.RAW_DATA, encdata) # verify - rawdata, signingkey = km.decrypt(encdata, ADDRESS, OpenPGPKey, - verify=ADDRESS, fetch_remote=False) + rawdata, signingkey = yield km.decrypt( + encdata, ADDRESS, OpenPGPKey, verify=ADDRESS, fetch_remote=False) self.assertEqual(self.RAW_DATA, rawdata) self.assertTrue(signingkey is None) + @inlineCallbacks def test_keymanager_openpgp_sign_verify(self): km = self._key_manager() # put raw private keys - km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) - signdata = km.sign(self.RAW_DATA, ADDRESS, OpenPGPKey, detach=False) + yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) + signdata = yield km.sign(self.RAW_DATA, ADDRESS, OpenPGPKey, + detach=False) self.assertNotEqual(self.RAW_DATA, signdata) # verify - signingkey = km.verify(signdata, ADDRESS, OpenPGPKey, + signingkey = yield km.verify(signdata, ADDRESS, OpenPGPKey, + fetch_remote=False) + key = yield km.get_key(ADDRESS, OpenPGPKey, private=False, fetch_remote=False) - key = km.get_key(ADDRESS, OpenPGPKey, private=False, - fetch_remote=False) self.assertEqual(signingkey.fingerprint, key.fingerprint) -# Key material for testing - -# key 7FEE575A: public key "anotheruser " -PUBLIC_KEY_2 = """ ------BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1.4.10 (GNU/Linux) - -mI0EUYwJXgEEAMbTKHuPJ5/Gk34l9Z06f+0WCXTDXdte1UBoDtZ1erAbudgC4MOR -gquKqoj3Hhw0/ILqJ88GcOJmKK/bEoIAuKaqlzDF7UAYpOsPZZYmtRfPC2pTCnXq -Z1vdeqLwTbUspqXflkCkFtfhGKMq5rH8GV5a3tXZkRWZhdNwhVXZagC3ABEBAAG0 -IWFub3RoZXJ1c2VyIDxhbm90aGVydXNlckBsZWFwLnNlPoi4BBMBAgAiBQJRjAle -AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRB/nfpof+5XWotuA/4tLN4E -gUr7IfLy2HkHAxzw7A4rqfMN92DIM9mZrDGaWRrOn3aVF7VU1UG7MDkHfPvp/cFw -ezoCw4s4IoHVc/pVlOkcHSyt4/Rfh248tYEJmFCJXGHpkK83VIKYJAithNccJ6Q4 -JE/o06Mtf4uh/cA1HUL4a4ceqUhtpLJULLeKo7iNBFGMCV4BBADsyQI7GR0wSAxz -VayLjuPzgT+bjbFeymIhjuxKIEwnIKwYkovztW+4bbOcQs785k3Lp6RzvigTpQQt -Z/hwcLOqZbZw8t/24+D+Pq9mMP2uUvCFFqLlVvA6D3vKSQ/XNN+YB919WQ04jh63 -yuRe94WenT1RJd6xU1aaUff4rKizuQARAQABiJ8EGAECAAkFAlGMCV4CGwwACgkQ -f536aH/uV1rPZQQAqCzRysOlu8ez7PuiBD4SebgRqWlxa1TF1ujzfLmuPivROZ2X -Kw5aQstxgGSjoB7tac49s0huh4X8XK+BtJBfU84JS8Jc2satlfwoyZ35LH6sDZck -I+RS/3we6zpMfHs3vvp9xgca6ZupQxivGtxlJs294TpJorx+mFFqbV17AzQ= -=Thdu ------END PGP PUBLIC KEY BLOCK----- -""" - -PRIVATE_KEY_2 = """ ------BEGIN PGP PRIVATE KEY BLOCK----- -Version: GnuPG v1.4.10 (GNU/Linux) - -lQHYBFGMCV4BBADG0yh7jyefxpN+JfWdOn/tFgl0w13bXtVAaA7WdXqwG7nYAuDD -kYKriqqI9x4cNPyC6ifPBnDiZiiv2xKCALimqpcwxe1AGKTrD2WWJrUXzwtqUwp1 -6mdb3Xqi8E21LKal35ZApBbX4RijKuax/BleWt7V2ZEVmYXTcIVV2WoAtwARAQAB -AAP7BLuSAx7tOohnimEs74ks8l/L6dOcsFQZj2bqs4AoY3jFe7bV0tHr4llypb/8 -H3/DYvpf6DWnCjyUS1tTnXSW8JXtx01BUKaAufSmMNg9blKV6GGHlT/Whe9uVyks -7XHk/+9mebVMNJ/kNlqq2k+uWqJohzC8WWLRK+d1tBeqDsECANZmzltPaqUsGV5X -C3zszE3tUBgptV/mKnBtopKi+VH+t7K6fudGcG+bAcZDUoH/QVde52mIIjjIdLje -uajJuHUCAO1mqh+vPoGv4eBLV7iBo3XrunyGXiys4a39eomhxTy3YktQanjjx+ty -GltAGCs5PbWGO6/IRjjvd46wh53kzvsCAO0J97gsWhzLuFnkxFAJSPk7RRlyl7lI -1XS/x0Og6j9XHCyY1OYkfBm0to3UlCfkgirzCYlTYObCofzdKFIPDmSqHbQhYW5v -dGhlcnVzZXIgPGFub3RoZXJ1c2VyQGxlYXAuc2U+iLgEEwECACIFAlGMCV4CGwMG -CwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEH+d+mh/7ldai24D/i0s3gSBSvsh -8vLYeQcDHPDsDiup8w33YMgz2ZmsMZpZGs6fdpUXtVTVQbswOQd8++n9wXB7OgLD -izgigdVz+lWU6RwdLK3j9F+Hbjy1gQmYUIlcYemQrzdUgpgkCK2E1xwnpDgkT+jT -oy1/i6H9wDUdQvhrhx6pSG2kslQst4qjnQHYBFGMCV4BBADsyQI7GR0wSAxzVayL -juPzgT+bjbFeymIhjuxKIEwnIKwYkovztW+4bbOcQs785k3Lp6RzvigTpQQtZ/hw -cLOqZbZw8t/24+D+Pq9mMP2uUvCFFqLlVvA6D3vKSQ/XNN+YB919WQ04jh63yuRe -94WenT1RJd6xU1aaUff4rKizuQARAQABAAP9EyElqJ3dq3EErXwwT4mMnbd1SrVC -rUJrNWQZL59mm5oigS00uIyR0SvusOr+UzTtd8ysRuwHy5d/LAZsbjQStaOMBILx -77TJveOel0a1QK0YSMF2ywZMCKvquvjli4hAtWYz/EwfuzQN3t23jc5ny+GqmqD2 -3FUxLJosFUfLNmECAO9KhVmJi+L9dswIs+2Dkjd1eiRQzNOEVffvYkGYZyKxNiXF -UA5kvyZcB4iAN9sWCybE4WHZ9jd4myGB0MPDGxkCAP1RsXJbbuD6zS7BXe5gwunO -2q4q7ptdSl/sJYQuTe1KNP5d/uGsvlcFfsYjpsopasPjFBIncc/2QThMKlhoEaEB -/0mVAxpT6SrEvUbJ18z7kna24SgMPr3OnPMxPGfvNLJY/Xv/A17YfoqjmByCvsKE -JCDjopXtmbcrZyoEZbEht9mko4ifBBgBAgAJBQJRjAleAhsMAAoJEH+d+mh/7lda -z2UEAKgs0crDpbvHs+z7ogQ+Enm4EalpcWtUxdbo83y5rj4r0TmdlysOWkLLcYBk -o6Ae7WnOPbNIboeF/FyvgbSQX1POCUvCXNrGrZX8KMmd+Sx+rA2XJCPkUv98Hus6 -THx7N776fcYHGumbqUMYrxrcZSbNveE6SaK8fphRam1dewM0 -=a5gs ------END PGP PRIVATE KEY BLOCK----- -""" import unittest if __name__ == "__main__": unittest.main() diff --git a/keymanager/src/leap/keymanager/tests/test_openpgp.py b/keymanager/src/leap/keymanager/tests/test_openpgp.py new file mode 100644 index 00000000..01cf3417 --- /dev/null +++ b/keymanager/src/leap/keymanager/tests/test_openpgp.py @@ -0,0 +1,250 @@ +# -*- coding: utf-8 -*- +# test_keymanager.py +# Copyright (C) 2014 LEAP +# +# 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 . + + +""" +Tests for the OpenPGP support on Key Manager. +""" + + +from twisted.internet.defer import inlineCallbacks + +from leap.keymanager import ( + KeyNotFound, + openpgp, +) +from leap.keymanager.openpgp import OpenPGPKey +from leap.keymanager.tests import ( + KeyManagerWithSoledadTestCase, + ADDRESS, + ADDRESS_2, + KEY_FINGERPRINT, + PUBLIC_KEY, + PUBLIC_KEY_2, + PRIVATE_KEY, + PRIVATE_KEY_2, + GPG_BINARY_PATH +) + + +class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): + + @inlineCallbacks + def _test_openpgp_gen_key(self): + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + yield self._assert_key_not_found(pgp, 'user@leap.se') + key = yield pgp.gen_key('user@leap.se') + self.assertIsInstance(key, openpgp.OpenPGPKey) + self.assertEqual( + ['user@leap.se'], key.address, 'Wrong address bound to key.') + self.assertEqual( + 4096, key.length, 'Wrong key length.') + + @inlineCallbacks + def test_openpgp_put_delete_key(self): + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + yield self._assert_key_not_found(pgp, ADDRESS) + yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) + key = yield pgp.get_key(ADDRESS, private=False) + yield pgp.delete_key(key) + yield self._assert_key_not_found(pgp, ADDRESS) + + @inlineCallbacks + def test_openpgp_put_ascii_key(self): + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + yield self._assert_key_not_found(pgp, ADDRESS) + yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) + key = yield pgp.get_key(ADDRESS, private=False) + self.assertIsInstance(key, openpgp.OpenPGPKey) + self.assertTrue( + ADDRESS in key.address, 'Wrong address bound to key.') + self.assertEqual( + 4096, key.length, 'Wrong key length.') + yield pgp.delete_key(key) + yield self._assert_key_not_found(pgp, ADDRESS) + + @inlineCallbacks + def test_get_public_key(self): + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + yield self._assert_key_not_found(pgp, ADDRESS) + yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) + yield self._assert_key_not_found(pgp, ADDRESS, private=True) + key = yield pgp.get_key(ADDRESS, private=False) + self.assertTrue(ADDRESS in key.address) + self.assertFalse(key.private) + self.assertEqual(KEY_FINGERPRINT, key.fingerprint) + yield pgp.delete_key(key) + yield self._assert_key_not_found(pgp, ADDRESS) + + @inlineCallbacks + def test_openpgp_encrypt_decrypt(self): + data = 'data' + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + + # encrypt + yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) + pubkey = yield pgp.get_key(ADDRESS, private=False) + cyphertext = pgp.encrypt(data, pubkey) + + self.assertTrue(cyphertext is not None) + self.assertTrue(cyphertext != '') + self.assertTrue(cyphertext != data) + self.assertTrue(pgp.is_encrypted(cyphertext)) + self.assertTrue(pgp.is_encrypted(cyphertext)) + + # decrypt + yield self._assert_key_not_found(pgp, ADDRESS, private=True) + yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) + privkey = yield pgp.get_key(ADDRESS, private=True) + decrypted, _ = pgp.decrypt(cyphertext, privkey) + self.assertEqual(decrypted, data) + + yield pgp.delete_key(pubkey) + yield pgp.delete_key(privkey) + yield self._assert_key_not_found(pgp, ADDRESS, private=False) + yield self._assert_key_not_found(pgp, ADDRESS, private=True) + + @inlineCallbacks + def test_verify_with_private_raises(self): + data = 'data' + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) + privkey = yield pgp.get_key(ADDRESS, private=True) + signed = pgp.sign(data, privkey) + self.assertRaises( + AssertionError, + pgp.verify, signed, privkey) + + @inlineCallbacks + def test_sign_with_public_raises(self): + data = 'data' + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) + self.assertRaises( + AssertionError, + pgp.sign, data, ADDRESS, OpenPGPKey) + + @inlineCallbacks + def test_verify_with_wrong_key_raises(self): + data = 'data' + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) + privkey = yield pgp.get_key(ADDRESS, private=True) + signed = pgp.sign(data, privkey) + yield pgp.put_ascii_key(PUBLIC_KEY_2, ADDRESS_2) + wrongkey = yield pgp.get_key(ADDRESS_2) + self.assertFalse(pgp.verify(signed, wrongkey)) + + @inlineCallbacks + def test_encrypt_sign_with_public_raises(self): + data = 'data' + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) + privkey = yield pgp.get_key(ADDRESS, private=True) + pubkey = yield pgp.get_key(ADDRESS, private=False) + self.assertRaises( + AssertionError, + pgp.encrypt, data, privkey, sign=pubkey) + + @inlineCallbacks + def test_decrypt_verify_with_private_raises(self): + data = 'data' + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) + privkey = yield pgp.get_key(ADDRESS, private=True) + pubkey = yield pgp.get_key(ADDRESS, private=False) + encrypted_and_signed = pgp.encrypt( + data, pubkey, sign=privkey) + self.assertRaises( + AssertionError, + pgp.decrypt, + encrypted_and_signed, privkey, verify=privkey) + + @inlineCallbacks + def test_decrypt_verify_with_wrong_key(self): + data = 'data' + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) + privkey = yield pgp.get_key(ADDRESS, private=True) + pubkey = yield pgp.get_key(ADDRESS, private=False) + encrypted_and_signed = pgp.encrypt(data, pubkey, sign=privkey) + yield pgp.put_ascii_key(PUBLIC_KEY_2, ADDRESS_2) + wrongkey = yield pgp.get_key(ADDRESS_2) + decrypted, validsign = pgp.decrypt(encrypted_and_signed, privkey, + verify=wrongkey) + self.assertEqual(decrypted, data) + self.assertFalse(validsign) + + @inlineCallbacks + def test_sign_verify(self): + data = 'data' + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) + privkey = yield pgp.get_key(ADDRESS, private=True) + signed = pgp.sign(data, privkey, detach=False) + pubkey = yield pgp.get_key(ADDRESS, private=False) + validsign = pgp.verify(signed, pubkey) + self.assertTrue(validsign) + + @inlineCallbacks + def test_encrypt_sign_decrypt_verify(self): + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + + yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) + pubkey = yield pgp.get_key(ADDRESS, private=False) + privkey = yield pgp.get_key(ADDRESS, private=True) + + yield pgp.put_ascii_key(PRIVATE_KEY_2, ADDRESS_2) + pubkey2 = yield pgp.get_key(ADDRESS_2, private=False) + privkey2 = yield pgp.get_key(ADDRESS_2, private=True) + + data = 'data' + encrypted_and_signed = pgp.encrypt( + data, pubkey2, sign=privkey) + res, validsign = pgp.decrypt( + encrypted_and_signed, privkey2, verify=pubkey) + self.assertEqual(data, res) + self.assertTrue(validsign) + + @inlineCallbacks + def test_sign_verify_detached_sig(self): + data = 'data' + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) + privkey = yield pgp.get_key(ADDRESS, private=True) + signature = yield pgp.sign(data, privkey, detach=True) + pubkey = yield pgp.get_key(ADDRESS, private=False) + validsign = pgp.verify(data, pubkey, detached_sig=signature) + self.assertTrue(validsign) + + def _assert_key_not_found(self, pgp, address, private=False): + d = pgp.get_key(address, private=private) + return self.assertFailure(d, KeyNotFound) diff --git a/keymanager/src/leap/keymanager/tests/test_validation.py b/keymanager/src/leap/keymanager/tests/test_validation.py index 400d36e8..83a02e00 100644 --- a/keymanager/src/leap/keymanager/tests/test_validation.py +++ b/keymanager/src/leap/keymanager/tests/test_validation.py @@ -19,6 +19,7 @@ Tests for the Validation Levels """ from datetime import datetime +from twisted.internet.defer import inlineCallbacks from leap.keymanager.openpgp import OpenPGPKey from leap.keymanager.errors import ( @@ -35,51 +36,57 @@ from leap.keymanager.validation import ValidationLevel class ValidationLevelTestCase(KeyManagerWithSoledadTestCase): + @inlineCallbacks def test_none_old_key(self): km = self._key_manager() - km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) - key = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + yield km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) + key = yield km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) self.assertEqual(key.fingerprint, KEY_FINGERPRINT) + @inlineCallbacks def test_cant_upgrade(self): km = self._key_manager() - km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS, - validation=ValidationLevel.Provider_Trust) - self.assertRaises(KeyNotValidUpgrade, km.put_raw_key, UNRELATED_KEY, - OpenPGPKey, ADDRESS) + yield km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS, + validation=ValidationLevel.Provider_Trust) + d = km.put_raw_key(UNRELATED_KEY, OpenPGPKey, ADDRESS) + yield self.assertFailure(d, KeyNotValidUpgrade) + @inlineCallbacks def test_fingerprint_level(self): km = self._key_manager() - km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) - km.put_raw_key(UNRELATED_KEY, OpenPGPKey, ADDRESS, - validation=ValidationLevel.Fingerprint) - key = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + yield km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) + yield km.put_raw_key(UNRELATED_KEY, OpenPGPKey, ADDRESS, + validation=ValidationLevel.Fingerprint) + key = yield km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) self.assertEqual(key.fingerprint, UNRELATED_FINGERPRINT) + @inlineCallbacks def test_expired_key(self): km = self._key_manager() - km.put_raw_key(EXPIRED_KEY, OpenPGPKey, ADDRESS) - km.put_raw_key(UNRELATED_KEY, OpenPGPKey, ADDRESS) - key = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + yield km.put_raw_key(EXPIRED_KEY, OpenPGPKey, ADDRESS) + yield km.put_raw_key(UNRELATED_KEY, OpenPGPKey, ADDRESS) + key = yield km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) self.assertEqual(key.fingerprint, UNRELATED_FINGERPRINT) + @inlineCallbacks def test_expired_fail_lower_level(self): km = self._key_manager() - km.put_raw_key(EXPIRED_KEY, OpenPGPKey, ADDRESS, - validation=ValidationLevel.Third_Party_Endorsement) - self.assertRaises( - KeyNotValidUpgrade, - km.put_raw_key, + yield km.put_raw_key( + EXPIRED_KEY, OpenPGPKey, ADDRESS, + validation=ValidationLevel.Third_Party_Endorsement) + d = km.put_raw_key( UNRELATED_KEY, OpenPGPKey, ADDRESS, validation=ValidationLevel.Provider_Trust) + yield self.assertFailure(d, KeyNotValidUpgrade) + @inlineCallbacks def test_roll_back(self): km = self._key_manager() - km.put_raw_key(EXPIRED_KEY_UPDATED, OpenPGPKey, ADDRESS) - km.put_raw_key(EXPIRED_KEY, OpenPGPKey, ADDRESS) - key = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + yield km.put_raw_key(EXPIRED_KEY_UPDATED, OpenPGPKey, ADDRESS) + yield km.put_raw_key(EXPIRED_KEY, OpenPGPKey, ADDRESS) + key = yield km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) self.assertEqual(key.expiry_date, EXPIRED_KEY_NEW_EXPIRY_DATE) -- cgit v1.2.3 From 12684222d47a670b4e7790fa63348c5d2d966b6a Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Fri, 5 Dec 2014 12:28:37 -0600 Subject: Fix key generation --- keymanager/src/leap/keymanager/openpgp.py | 8 ++------ keymanager/src/leap/keymanager/tests/test_openpgp.py | 3 +++ 2 files changed, 5 insertions(+), 6 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index f81fb0a0..c95b3817 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -282,11 +282,6 @@ class OpenPGPScheme(EncryptionScheme): pubkeys = gpg.list_keys() # assert for new key characteristics - - # XXX This exception is not properly catched by the soledad - # bootstrapping, so if we do not finish generating the keys - # we end with a blocked thread -- kali - leap_assert( len(pubkeys) is 1, # a unitary keyring! 'Keyring has wrong number of keys: %d.' % len(pubkeys)) @@ -298,8 +293,9 @@ class OpenPGPScheme(EncryptionScheme): for uid in key['uids']: if re.match('.*<%s>$' % address, uid) is not None: uid_match = True - return + break leap_assert(uid_match, 'Key not correctly bound to address.') + # insert both public and private keys in storage deferreds = [] for secret in [True, False]: diff --git a/keymanager/src/leap/keymanager/tests/test_openpgp.py b/keymanager/src/leap/keymanager/tests/test_openpgp.py index 01cf3417..e6f56e26 100644 --- a/keymanager/src/leap/keymanager/tests/test_openpgp.py +++ b/keymanager/src/leap/keymanager/tests/test_openpgp.py @@ -43,6 +43,9 @@ from leap.keymanager.tests import ( class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): + # set the trial timeout to 20min, needed by the key generation test + timeout = 1200 + @inlineCallbacks def _test_openpgp_gen_key(self): pgp = openpgp.OpenPGPScheme( -- cgit v1.2.3 From 1e3fc37172d474488f300e180e69c4adb7b396c6 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Sat, 13 Dec 2014 10:15:53 -0600 Subject: Find the gpg path instead of hard code it --- keymanager/src/leap/keymanager/tests/__init__.py | 15 ++++++++--- .../src/leap/keymanager/tests/test_openpgp.py | 29 +++++++++++----------- 2 files changed, 26 insertions(+), 18 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/tests/__init__.py b/keymanager/src/leap/keymanager/tests/__init__.py index 05b44871..7128d20d 100644 --- a/keymanager/src/leap/keymanager/tests/__init__.py +++ b/keymanager/src/leap/keymanager/tests/__init__.py @@ -18,6 +18,9 @@ Base classes for the Key Manager tests. """ +import distutils.spawn +import os.path + from twisted.internet.defer import gatherResults from twisted.trial import unittest @@ -29,14 +32,13 @@ from leap.keymanager.openpgp import OpenPGPKey ADDRESS = 'leap@leap.se' ADDRESS_2 = 'anotheruser@leap.se' -# XXX discover the gpg binary path -GPG_BINARY_PATH = '/usr/bin/gpg' class KeyManagerWithSoledadTestCase(unittest.TestCase, BaseLeapTest): def setUp(self): self.setUpEnv() + self.gpg_binary_path = self._find_gpg() self._soledad = Soledad( u"leap@leap.se", @@ -75,7 +77,14 @@ class KeyManagerWithSoledadTestCase(unittest.TestCase, BaseLeapTest): def _key_manager(self, user=ADDRESS, url='', token=None): return KeyManager(user, url, self._soledad, token=token, - gpgbinary=GPG_BINARY_PATH) + gpgbinary=self.gpg_binary_path) + + def _find_gpg(self): + gpg_path = distutils.spawn.find_executable('gpg') + if gpg_path is not None: + return os.path.realpath(gpg_path) + else: + return "/usr/bin/gpg" # key 24D18DDF: public key "Leap Test Key " diff --git a/keymanager/src/leap/keymanager/tests/test_openpgp.py b/keymanager/src/leap/keymanager/tests/test_openpgp.py index e6f56e26..5f85c74b 100644 --- a/keymanager/src/leap/keymanager/tests/test_openpgp.py +++ b/keymanager/src/leap/keymanager/tests/test_openpgp.py @@ -37,7 +37,6 @@ from leap.keymanager.tests import ( PUBLIC_KEY_2, PRIVATE_KEY, PRIVATE_KEY_2, - GPG_BINARY_PATH ) @@ -49,7 +48,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): @inlineCallbacks def _test_openpgp_gen_key(self): pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) yield self._assert_key_not_found(pgp, 'user@leap.se') key = yield pgp.gen_key('user@leap.se') self.assertIsInstance(key, openpgp.OpenPGPKey) @@ -61,7 +60,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): @inlineCallbacks def test_openpgp_put_delete_key(self): pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) yield self._assert_key_not_found(pgp, ADDRESS) yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) key = yield pgp.get_key(ADDRESS, private=False) @@ -71,7 +70,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): @inlineCallbacks def test_openpgp_put_ascii_key(self): pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) yield self._assert_key_not_found(pgp, ADDRESS) yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) key = yield pgp.get_key(ADDRESS, private=False) @@ -86,7 +85,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): @inlineCallbacks def test_get_public_key(self): pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) yield self._assert_key_not_found(pgp, ADDRESS) yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) yield self._assert_key_not_found(pgp, ADDRESS, private=True) @@ -101,7 +100,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_openpgp_encrypt_decrypt(self): data = 'data' pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) # encrypt yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) @@ -130,7 +129,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_verify_with_private_raises(self): data = 'data' pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) privkey = yield pgp.get_key(ADDRESS, private=True) signed = pgp.sign(data, privkey) @@ -142,7 +141,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_sign_with_public_raises(self): data = 'data' pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) self.assertRaises( AssertionError, @@ -152,7 +151,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_verify_with_wrong_key_raises(self): data = 'data' pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) privkey = yield pgp.get_key(ADDRESS, private=True) signed = pgp.sign(data, privkey) @@ -164,7 +163,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_encrypt_sign_with_public_raises(self): data = 'data' pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) privkey = yield pgp.get_key(ADDRESS, private=True) pubkey = yield pgp.get_key(ADDRESS, private=False) @@ -176,7 +175,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_decrypt_verify_with_private_raises(self): data = 'data' pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) privkey = yield pgp.get_key(ADDRESS, private=True) pubkey = yield pgp.get_key(ADDRESS, private=False) @@ -191,7 +190,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_decrypt_verify_with_wrong_key(self): data = 'data' pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) privkey = yield pgp.get_key(ADDRESS, private=True) pubkey = yield pgp.get_key(ADDRESS, private=False) @@ -207,7 +206,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_sign_verify(self): data = 'data' pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) privkey = yield pgp.get_key(ADDRESS, private=True) signed = pgp.sign(data, privkey, detach=False) @@ -218,7 +217,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): @inlineCallbacks def test_encrypt_sign_decrypt_verify(self): pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) pubkey = yield pgp.get_key(ADDRESS, private=False) @@ -240,7 +239,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_sign_verify_detached_sig(self): data = 'data' pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) privkey = yield pgp.get_key(ADDRESS, private=True) signature = yield pgp.sign(data, privkey, detach=True) -- cgit v1.2.3 From 7acfc0802eeda852c09dac549b26dc455554cc04 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Sun, 14 Dec 2014 22:13:57 -0600 Subject: Return the right error on signature verification --- keymanager/src/leap/keymanager/__init__.py | 79 +++++++++++----------- keymanager/src/leap/keymanager/errors.py | 7 ++ .../src/leap/keymanager/tests/test_keymanager.py | 3 +- 3 files changed, 48 insertions(+), 41 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 7ff437e5..5324d429 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -54,7 +54,8 @@ from leap.keymanager.errors import ( KeyNotFound, KeyAddressMismatch, KeyNotValidUpgrade, - UnsupportedKeyTypeError + UnsupportedKeyTypeError, + InvalidSignature ) from leap.keymanager.validation import ValidationLevel, can_upgrade @@ -479,7 +480,12 @@ class KeyManager(object): data, pubkey, passphrase, sign=signkey, cipher_algo=cipher_algo) - d = self._get_keys(ktype, address, sign, fetch_remote=fetch_remote) + dpub = self.get_key(address, ktype, private=False, + fetch_remote=fetch_remote) + dpriv = defer.succeed(None) + if sign is not None: + dpriv = self.get_key(sign, ktype, private=True) + d = defer.gatherResults([dpub, dpriv]) d.addCallback(encrypt) return d @@ -504,11 +510,12 @@ class KeyManager(object): to fetch from nickserver :type fetch_remote: bool - :return: A Deferred which fires with the decrypted data as str and the - signing EncryptionKey if signature verifies, or which fails - with KeyNotFound if no keys were found neither locally or in - keyserver or fails with DecryptError if failed decrypting for - some reason. + :return: A Deferred which fires with: + * (decripted str, signing key) if validation works + * (decripted str, KeyNotFound) if signing key not found + * (decripted str, InvalidSignature) if signature is invalid + * KeyNotFound failure if private key not found + * DecryptError failure if decription failed :rtype: Deferred :raise UnsupportedKeyTypeError: if invalid key type @@ -519,39 +526,25 @@ class KeyManager(object): pubkey, privkey = keys decrypted, signed = self._wrapper_map[ktype].decrypt( data, privkey, passphrase=passphrase, verify=pubkey) - return (decrypted, pubkey if signed else None) - - d = self._get_keys(ktype, verify, address, fetch_remote) - d.addCallback(decrypt) - return d - - def _get_keys(self, ktype, public, private, fetch_remote=True): - """ - Get public and private keys of ktype. - - :param ktype: The type of the key. - :type ktype: subclass of EncryptionKey - :param public: The address of the public key. - :type public: str - :param private: The address of the private key. - :type private: str - :param fetch_remote: If key for verify not found in local storage try - to fetch from nickserver - :type fetch_remote: bool + if pubkey is None: + signature = KeyNotFound(verify) + elif signed: + signature = pubkey + else: + signature = InvalidSignature( + 'Failed to verify signature with key %s' % + (pubkey.key_id,)) + return (decrypted, signature) - :return: A Deferred which fires with a tuple with public and private - EncryptionKeys, or which fails with KeyNotFound if no keys - were found neither locally or in keyserver. - :rtype: Deferred - """ + dpriv = self.get_key(address, ktype, private=True) dpub = defer.succeed(None) - if public is not None: - dpub = self.get_key(public, ktype, private=False, + if verify is not None: + dpub = self.get_key(verify, ktype, private=False, fetch_remote=fetch_remote) - dpriv = defer.succeed(None) - if private is not None: - dpriv = self.get_key(private, ktype, private=True) - return defer.gatherResults([dpub, dpriv]) + dpub.addErrback(lambda f: None if f.check(KeyNotFound) else f) + d = defer.gatherResults([dpub, dpriv]) + d.addCallback(decrypt) + return d def sign(self, data, address, ktype, digest_algo='SHA512', clearsign=False, detach=True, binary=False): @@ -612,8 +605,9 @@ class KeyManager(object): :type fetch_remote: bool :return: A Deferred which fires with the signing EncryptionKey if - signature verifies else None, or which fails with KeyNotFound - if no key was found neither locally or in keyserver. + signature verifies, or which fails with InvalidSignature if + signature don't verifies or fails with KeyNotFound if no key + was found neither locally or in keyserver. :rtype: Deferred :raise UnsupportedKeyTypeError: if invalid key type @@ -623,7 +617,12 @@ class KeyManager(object): def verify(pubkey): signed = self._wrapper_map[ktype].verify( data, pubkey, detached_sig=detached_sig) - return pubkey if signed else None + if signed: + return pubkey + else: + raise InvalidSignature( + 'Failed to verify signature with key %s' % + (pubkey.key_id,)) d = self.get_key(address, ktype, private=False, fetch_remote=fetch_remote) diff --git a/keymanager/src/leap/keymanager/errors.py b/keymanager/src/leap/keymanager/errors.py index 4041837e..8a9fb3c7 100644 --- a/keymanager/src/leap/keymanager/errors.py +++ b/keymanager/src/leap/keymanager/errors.py @@ -51,6 +51,13 @@ class NoPasswordGiven(Exception): pass +class InvalidSignature(Exception): + """ + Raised when signature could not be verified. + """ + pass + + class EncryptError(Exception): """ Raised upon failures of encryption. diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index b8ef88ae..dca89e8e 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -29,6 +29,7 @@ from twisted.trial import unittest from leap.keymanager import ( KeyNotFound, KeyAddressMismatch, + errors ) from leap.keymanager.openpgp import OpenPGPKey from leap.keymanager.keys import ( @@ -357,7 +358,7 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): rawdata, signingkey = yield km.decrypt( encdata, ADDRESS, OpenPGPKey, verify=ADDRESS, fetch_remote=False) self.assertEqual(self.RAW_DATA, rawdata) - self.assertTrue(signingkey is None) + self.assertTrue(isinstance(signingkey, errors.InvalidSignature)) @inlineCallbacks def test_keymanager_openpgp_sign_verify(self): -- cgit v1.2.3 From fb74dafe063683441108a198fc09d85b4d68c276 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 29 Dec 2014 00:27:27 -0600 Subject: Return a valid error from gatherResults --- keymanager/src/leap/keymanager/__init__.py | 11 +++++++---- keymanager/src/leap/keymanager/tests/test_keymanager.py | 8 ++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 5324d429..c4050fa8 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -485,8 +485,8 @@ class KeyManager(object): dpriv = defer.succeed(None) if sign is not None: dpriv = self.get_key(sign, ktype, private=True) - d = defer.gatherResults([dpub, dpriv]) - d.addCallback(encrypt) + d = defer.gatherResults([dpub, dpriv], consumeErrors=True) + d.addCallbacks(encrypt, self._extract_first_error) return d def decrypt(self, data, address, ktype, passphrase=None, verify=None, @@ -542,10 +542,13 @@ class KeyManager(object): dpub = self.get_key(verify, ktype, private=False, fetch_remote=fetch_remote) dpub.addErrback(lambda f: None if f.check(KeyNotFound) else f) - d = defer.gatherResults([dpub, dpriv]) - d.addCallback(decrypt) + d = defer.gatherResults([dpub, dpriv], consumeErrors=True) + d.addCallbacks(decrypt, self._extract_first_error) return d + def _extract_first_error(self, failure): + return failure.value.subFailure + def sign(self, data, address, ktype, digest_algo='SHA512', clearsign=False, detach=True, binary=False): """ diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index dca89e8e..86832bab 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -375,6 +375,14 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): fetch_remote=False) self.assertEqual(signingkey.fingerprint, key.fingerprint) + def test_keymanager_encrypt_key_not_found(self): + km = self._key_manager() + d = km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) + d.addCallback( + lambda _: km.encrypt(self.RAW_DATA, ADDRESS_2, OpenPGPKey, + sign=ADDRESS, fetch_remote=False)) + return self.assertFailure(d, KeyNotFound) + import unittest if __name__ == "__main__": -- cgit v1.2.3 From 679a1b90b11c9b7e06fde72e72314c757af7767e Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Fri, 19 Dec 2014 07:38:42 -0600 Subject: Fix key upgrade on no expiration date and higher validation level --- keymanager/src/leap/keymanager/validation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/validation.py b/keymanager/src/leap/keymanager/validation.py index 245013e5..87de2af8 100644 --- a/keymanager/src/leap/keymanager/validation.py +++ b/keymanager/src/leap/keymanager/validation.py @@ -82,7 +82,8 @@ def can_upgrade(new_key, old_key): return True # No expiration date and higher validation level - elif new_key.validation >= old_key.validation: + if (old_key.expiry_date is None and + new_key.validation > old_key.validation): return True return False -- cgit v1.2.3 From 61ad8463efa940347a5b50f8d25a056c68dbda0a Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Fri, 19 Dec 2014 07:40:05 -0600 Subject: On key update merge metadata correctly --- keymanager/src/leap/keymanager/openpgp.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index c95b3817..0adfc52d 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -40,8 +40,6 @@ from leap.keymanager.keys import ( TYPE_ID_PRIVATE_INDEX, TYPE_ADDRESS_PRIVATE_INDEX, KEY_ADDRESS_KEY, - KEY_FINGERPRINT_KEY, - KEY_DATA_KEY, KEY_ID_KEY, KEYMANAGER_ACTIVE_TYPE, ) @@ -447,23 +445,30 @@ class OpenPGPScheme(EncryptionScheme): def check_and_put(docs, key): if len(docs) == 1: doc = docs.pop() - if key.fingerprint == doc.content[KEY_FINGERPRINT_KEY]: + oldkey = build_key_from_dict(OpenPGPKey, doc.content) + if key.fingerprint == oldkey.fingerprint: # in case of an update of the key merge them with gnupg with self._temporary_gpgwrapper() as gpg: - gpg.import_keys(doc.content[KEY_DATA_KEY]) + gpg.import_keys(oldkey.key_data) gpg.import_keys(key.key_data) gpgkey = gpg.list_keys(secret=key.private).pop() - key = _build_key_from_gpg( + mergedkey = _build_key_from_gpg( gpgkey, gpg.export_keys(gpgkey['fingerprint'], secret=key.private)) - doc.set_json(key.get_json()) + mergedkey.validation = max( + [key.validation, oldkey.validation]) + mergedkey.last_audited_at = oldkey.last_audited_at + mergedkey.refreshed_at = key.refreshed_at + mergedkey.encr_used = key.encr_used or oldkey.encr_used + mergedkey.sign_used = key.sign_used or oldkey.sign_used + doc.set_json(mergedkey.get_json()) d = self._soledad.put_doc(doc) else: logger.critical( "Can't put a key whith the same key_id and different " "fingerprint: %s, %s" - % (key.fingerprint, doc.content[KEY_FINGERPRINT_KEY])) + % (key.fingerprint, oldkey.fingerprint)) d = defer.fail( errors.KeyFingerprintMismatch(key.fingerprint)) elif len(docs) > 1: -- cgit v1.2.3 From 0a301839f1c486e1ea3faa914dbbe33d75790b22 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Fri, 19 Dec 2014 08:15:43 -0600 Subject: Upgrade keys if not successfully used and strict high validation level --- keymanager/src/leap/keymanager/__init__.py | 23 +++- .../src/leap/keymanager/tests/test_validation.py | 118 ++++++++++++++++++--- keymanager/src/leap/keymanager/validation.py | 6 +- 3 files changed, 126 insertions(+), 21 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index c4050fa8..fdbc206d 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -476,9 +476,13 @@ class KeyManager(object): def encrypt(keys): pubkey, signkey = keys - return self._wrapper_map[ktype].encrypt( + encrypted = self._wrapper_map[ktype].encrypt( data, pubkey, passphrase, sign=signkey, cipher_algo=cipher_algo) + pubkey.encr_used = True + d = self._wrapper_map[ktype].put_key(pubkey, address) + d.addCallback(lambda _: encrypted) + return d dpub = self.get_key(address, ktype, private=False, fetch_remote=fetch_remote) @@ -529,7 +533,10 @@ class KeyManager(object): if pubkey is None: signature = KeyNotFound(verify) elif signed: - signature = pubkey + pubkey.sign_used = True + d = self._wrapper_map[ktype].put_key(pubkey, address) + d.addCallback(lambda _: (decrypted, pubkey)) + return d else: signature = InvalidSignature( 'Failed to verify signature with key %s' % @@ -621,7 +628,10 @@ class KeyManager(object): signed = self._wrapper_map[ktype].verify( data, pubkey, detached_sig=detached_sig) if signed: - return pubkey + pubkey.sign_used = True + d = self._wrapper_map[ktype].put_key(pubkey, address) + d.addCallback(lambda _: pubkey) + return d else: raise InvalidSignature( 'Failed to verify signature with key %s' % @@ -718,9 +728,12 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ self._assert_supported_key_type(ktype) - pubkey, _ = self._wrapper_map[ktype].parse_ascii_key(key) + pubkey, privkey = self._wrapper_map[ktype].parse_ascii_key(key) pubkey.validation = validation - return self.put_key(pubkey, address) + d = self.put_key(pubkey, address) + if privkey is not None: + d.addCallback(lambda _: self.put_key(privkey, address)) + return d def fetch_key(self, address, uri, ktype, validation=ValidationLevel.Weak_Chain): diff --git a/keymanager/src/leap/keymanager/tests/test_validation.py b/keymanager/src/leap/keymanager/tests/test_validation.py index 83a02e00..a8b35ca3 100644 --- a/keymanager/src/leap/keymanager/tests/test_validation.py +++ b/keymanager/src/leap/keymanager/tests/test_validation.py @@ -89,6 +89,34 @@ class ValidationLevelTestCase(KeyManagerWithSoledadTestCase): key = yield km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) self.assertEqual(key.expiry_date, EXPIRED_KEY_NEW_EXPIRY_DATE) + @inlineCallbacks + def test_not_used(self): + km = self._key_manager() + yield km.put_raw_key(UNEXPIRED_KEY, OpenPGPKey, ADDRESS, + validation=ValidationLevel.Provider_Trust) + yield km.put_raw_key(UNRELATED_KEY, OpenPGPKey, ADDRESS, + validation=ValidationLevel.Provider_Endorsement) + key = yield km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + self.assertEqual(key.fingerprint, UNRELATED_FINGERPRINT) + + @inlineCallbacks + def test_used(self): + TEXT = "some text" + + km = self._key_manager() + yield km.put_raw_key(UNEXPIRED_KEY, OpenPGPKey, ADDRESS) + yield km.encrypt(TEXT, ADDRESS, OpenPGPKey) + + km2 = self._key_manager() + yield km2.put_raw_key(UNEXPIRED_PRIVATE, OpenPGPKey, ADDRESS) + signature = yield km2.sign(TEXT, ADDRESS, OpenPGPKey) + + yield km.verify(TEXT, ADDRESS, OpenPGPKey, detached_sig=signature) + d = km.put_raw_key( + UNRELATED_KEY, OpenPGPKey, ADDRESS, + validation=ValidationLevel.Provider_Endorsement) + yield self.assertFailure(d, KeyNotValidUpgrade) + # Key material for testing @@ -162,7 +190,7 @@ Osuse7+NkyUHgMXMVW7cz+nU7iO+ht2rkBtv+Z5LGlzgHTeFjKci -----END PGP PUBLIC KEY BLOCK----- """ # updated expiration date -EXPIRED_KEY_NEW_EXPIRY_DATE = datetime.fromtimestamp(2045319180) +EXPIRED_KEY_NEW_EXPIRY_DATE = datetime.fromtimestamp(2049717872) EXPIRED_KEY_UPDATED = """ -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1.4.12 (GNU/Linux) @@ -174,27 +202,87 @@ acDh9vNtPxDCg5RdI0bfdIEBGgHTfsda3kWGvo1wH5SgrTRq0+EcTI7aJgkMmM/A IhnpACE52NvGdG9eB3x7xyQFsQqK8F0XvEev2UJH4SR7vb+Z7FNTJKCy6likYbSV wGGFuowFSESnzXuUI6PcjyuO6FUbMgeM5euFABEBAAG0HExlYXAgVGVzdCBLZXkg PGxlYXBAbGVhcC5zZT6JAT4EEwECACgCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4B -AheABQJUURIXBQld/ZovAAoJEG8V8AShiFp8xUcIALcAHZbaxvyhHRGOrwDddbH0 -fFDK0AqKTsIT7y4D/HLFCP5zG3Ck7qGPZdkHXZfzq8rIb+zUjW3oJIVI1IucHxG2 -T5kppa8RFCBAFlRWYf6R3isX3YL0d3QSragjoxRNPcHNU8ALHcvfSonFHBoi4fH4 -4rvgksAiT68SsdPaoXDlabx5T15evu/7T5e/DGMQVPMxiaSuSQhbOKuMk2wcFdmL -tBYHLZPa54hHPNhEDyxLgtKKph0gObk9ojKfH9kPvLveIcpS5CqTJfN/kqBz7CJW -wEeAi2iG3H1OEB25aCUdTxXSRNlGqEgcWPaWxtc1RzlARu7LB64OUZuRy4puiAG5 +AheABQJUlDCSBQleQLiTAAoJEG8V8AShiFp8t3QH/1eqkVIScXmqaCVeno3VSKiH +HqnxiHcEgtpNRfUlP6tLD4H6QPEpvoUI9S/8HSYi3nbDGXEX8ycKlnwxjdIqWSOW +xj91/7uQAo+dP9QaVJ6xgaAiqzN1x3JzX3Js1wTodmNV0TfmGjxwnC5up/xK7/pd +KuDP3woDsRlwy8Lgj67mkn49xfAFHo6hI6SD36UBDAC/ELq6kZaba4Kk0fEVHCEz +HX0B09ZIY9fmf305cEB3dNh6SMQgKtH0wKozaqI2UM2B+cs3z08bC+YuUUh7UJTH +yr+hI7vF4/WEeJB3fuhP3xsumLhV8P47DaJ7oivmtsDEbAJFKqvigEqNES73Xpy5 AQ0EG+t93QEIAKqRq/2sBDW4g3FU+11LhixT+GosrfVvnitz3S9k2tBXok/wYpI1 XeA+kTHiF0LaqoaciDRvkA9DvhDbSrNM1yeuYRyZiHlTmoPZ/Fkl60oA2cyLd1L5 sXbuipY3TEiakugdSU4rzgi0hFycm6Go6yq2G6eC6UALvD9CTMdZHw40TadG9xpm 4thYPuJ1kPH8/bkbTi9sLHoApYgL+7ssje8w4epr0qD4IGxeKwJPf/tbTRpnd8w3 leldixHHKAutNt49p0pkXlORAHRpUmp+KMZhFvCvIPwe9o5mYtMR7sDRxjY61ZEQ -KLyKoh5wsJsaPXBjdG7cf6G/cBcwvnQVUHcAEQEAAYkBJQQYAQIADwUCG+t93QIb -DAUJAAFRgAAKCRBvFfAEoYhafOPgB/9z4YCyT/N0262HtegHykhsyykuqEeNb1LV -D9INcP+RbCX/0IjFgP4DTMPP7qqF1OBwR276maALT321Gqxc5HN5YrwxGdmoyBLm -unaQJJlD+7B1C+jnO6r4m44obvJ/NMERxVyzkXap3J2VgRIO1wNLI9I0sH6Kj5/j -Mgy06OwXDcqIc+jB4sIJ3Tnm8LZ3phJzNEm9mI8Ak0oJ7IEcMndR6DzmRt1rJQcq -K/D7hOG02zvyRhxF27U1qR1MxeU/gNnOx8q4dnVyWB+EiV1sFl4iTOyYHEsoyd7W -Osuse7+NkyUHgMXMVW7cz+nU7iO+ht2rkBtv+Z5LGlzgHTeFjKci -=79Ll +KLyKoh5wsJsaPXBjdG7cf6G/cBcwvnQVUHcAEQEAAYkBJQQYAQIADwIbDAUCVJQw +3QUJXkC4/QAKCRBvFfAEoYhafEtiB/9hMfSFNMxtlIJDJArG4JwR7sBOatYUT858 +qZnTgGETZN8wXpeEpXWKdDdmCX9aeE9jsDNgSQ5WWpqU21bGMXh1IGjAzmqTqq3/ +ik1vALuaVfr6OqjTzrJVQujT61CGed26xpP3Zh8hLKyKa+dXnX/VpgZS42wZLPx2 +wcODfANmTfE2AhMap/RyDy21q4nau+z2hMEOKdtF8dpP+pEvzoN5ZexYP1hfT+Av +oFPyVB5YtEMfxTEshDKRPjbdgNmw4faKXd5Cbelo4YxxpO16FHb6gzIdjOX15vQ+ +KwcVXzg9xk4D3cr1mnTCops/iv6TXvcw4Wbo70rrKXwkjl8LKjOP +=sHoe -----END PGP PUBLIC KEY BLOCK----- """ +UNEXPIRED_KEY = EXPIRED_KEY_UPDATED +UNEXPIRED_PRIVATE = """ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.12 (GNU/Linux) + +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 +=dZE8 +-----END PGP PRIVATE KEY BLOCK----- +""" import unittest diff --git a/keymanager/src/leap/keymanager/validation.py b/keymanager/src/leap/keymanager/validation.py index 87de2af8..b3aff3e4 100644 --- a/keymanager/src/leap/keymanager/validation.py +++ b/keymanager/src/leap/keymanager/validation.py @@ -60,7 +60,6 @@ def can_upgrade(new_key, old_key): :type old_key: EncryptionKey :rtype: bool """ - # XXX not succesfully used and strict high validation level (#6211) # XXX implement key signature checking (#6120) # First contact @@ -86,4 +85,9 @@ def can_upgrade(new_key, old_key): new_key.validation > old_key.validation): return True + # Not successfully used and strict high validation level + if (not (old_key.sign_used and old_key.encr_used) and + new_key.validation > old_key.validation): + return True + return False -- cgit v1.2.3 From cf8b6825d15ec970a6e54aac408d007605f3bb57 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Fri, 19 Dec 2014 22:37:40 -0600 Subject: upgrade key when signed by old key --- keymanager/src/leap/keymanager/__init__.py | 2 +- keymanager/src/leap/keymanager/openpgp.py | 99 +++++++++++++--------- .../src/leap/keymanager/tests/test_validation.py | 55 ++++++++++++ keymanager/src/leap/keymanager/validation.py | 6 +- 4 files changed, 121 insertions(+), 41 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index fdbc206d..a1a59f54 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -25,7 +25,7 @@ try: assert(GPGUtilities) # pyflakes happy from gnupg import __version__ as _gnupg_version from pkg_resources import parse_version - assert(parse_version(_gnupg_version) >= parse_version('1.2.3')) + assert(parse_version(_gnupg_version) >= parse_version('1.4.0')) except (ImportError, AssertionError): print "*******" diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 0adfc52d..794a0ec2 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -163,39 +163,6 @@ class TempGPGWrapper(object): shutil.rmtree(self._gpg.homedir) -def _build_key_from_gpg(key, key_data): - """ - Build an OpenPGPKey based on C{key} from local gpg storage. - - ASCII armored GPG key data has to be queried independently in this - wrapper, so we receive it in C{key_data}. - - :param key: Key obtained from GPG storage. - :type key: dict - :param key_data: Key data obtained from GPG storage. - :type key_data: str - :return: An instance of the key. - :rtype: OpenPGPKey - """ - expiry_date = None - if key['expires']: - expiry_date = datetime.fromtimestamp(int(key['expires'])) - address = [] - for uid in key['uids']: - address.append(_parse_address(uid)) - - return OpenPGPKey( - address, - key_id=key['keyid'], - fingerprint=key['fingerprint'], - key_data=key_data, - private=True if key['type'] == 'sec' else False, - length=int(key['length']), - expiry_date=expiry_date, - refreshed_at=datetime.now(), - ) - - def _parse_address(address): """ Remove name, '<', '>' and the identity suffix after the '+' until the '@' @@ -221,6 +188,26 @@ class OpenPGPKey(EncryptionKey): Base class for OpenPGP keys. """ + def __init__(self, address, gpgbinary=None, **kwargs): + self._gpgbinary = gpgbinary + super(OpenPGPKey, self).__init__(address, **kwargs) + + @property + def signatures(self): + """ + Get the key signatures + + :return: the key IDs that have signed the key + :rtype: list(str) + """ + with TempGPGWrapper(keys=[self], gpgbinary=self._gpgbinary) as gpg: + res = gpg.list_sigs(self.key_id) + for uid, sigs in res.sigs.iteritems(): + if _parse_address(uid) in self.address: + return sigs + + return [] + class OpenPGPScheme(EncryptionScheme): """ @@ -298,7 +285,7 @@ class OpenPGPScheme(EncryptionScheme): deferreds = [] for secret in [True, False]: key = gpg.list_keys(secret=secret).pop() - openpgp_key = _build_key_from_gpg( + openpgp_key = self._build_key_from_gpg( key, gpg.export_keys(key['fingerprint'], secret=secret)) d = self.put_key(openpgp_key, address) @@ -335,7 +322,9 @@ class OpenPGPScheme(EncryptionScheme): leap_assert( address in doc.content[KEY_ADDRESS_KEY], 'Wrong address in key data.') - return build_key_from_dict(OpenPGPKey, doc.content) + key = build_key_from_dict(OpenPGPKey, doc.content) + key._gpgbinary = self._gpgbinary + return key d = self._get_key_doc(address, private) d.addCallback(build_key) @@ -375,7 +364,7 @@ class OpenPGPScheme(EncryptionScheme): openpgp_privkey = None if privkey is not None: # build private key - openpgp_privkey = _build_key_from_gpg( + openpgp_privkey = self._build_key_from_gpg( privkey, gpg.export_keys(privkey['fingerprint'], secret=True)) leap_check(pubkey['fingerprint'] == privkey['fingerprint'], @@ -383,7 +372,7 @@ class OpenPGPScheme(EncryptionScheme): errors.KeyFingerprintMismatch) # build public key - openpgp_pubkey = _build_key_from_gpg( + openpgp_pubkey = self._build_key_from_gpg( pubkey, gpg.export_keys(pubkey['fingerprint'], secret=False)) @@ -452,7 +441,7 @@ class OpenPGPScheme(EncryptionScheme): gpg.import_keys(oldkey.key_data) gpg.import_keys(key.key_data) gpgkey = gpg.list_keys(secret=key.private).pop() - mergedkey = _build_key_from_gpg( + mergedkey = self._build_key_from_gpg( gpgkey, gpg.export_keys(gpgkey['fingerprint'], secret=key.private)) @@ -571,6 +560,40 @@ class OpenPGPScheme(EncryptionScheme): d.addCallback(get_key_from_active_doc) return d + def _build_key_from_gpg(self, key, key_data): + """ + Build an OpenPGPKey for C{address} based on C{key} from + local gpg storage. + + ASCII armored GPG key data has to be queried independently in this + wrapper, so we receive it in C{key_data}. + + :param key: Key obtained from GPG storage. + :type key: dict + :param key_data: Key data obtained from GPG storage. + :type key_data: str + :return: An instance of the key. + :rtype: OpenPGPKey + """ + expiry_date = None + if key['expires']: + expiry_date = datetime.fromtimestamp(int(key['expires'])) + address = [] + for uid in key['uids']: + address.append(_parse_address(uid)) + + return OpenPGPKey( + address, + gpgbinary=self._gpgbinary, + key_id=key['keyid'], + fingerprint=key['fingerprint'], + key_data=key_data, + private=True if key['type'] == 'sec' else False, + length=int(key['length']), + expiry_date=expiry_date, + refreshed_at=datetime.now(), + ) + def delete_key(self, key): """ Remove C{key} from storage. diff --git a/keymanager/src/leap/keymanager/tests/test_validation.py b/keymanager/src/leap/keymanager/tests/test_validation.py index a8b35ca3..15e7d27d 100644 --- a/keymanager/src/leap/keymanager/tests/test_validation.py +++ b/keymanager/src/leap/keymanager/tests/test_validation.py @@ -117,6 +117,14 @@ class ValidationLevelTestCase(KeyManagerWithSoledadTestCase): validation=ValidationLevel.Provider_Endorsement) yield self.assertFailure(d, KeyNotValidUpgrade) + @inlineCallbacks + def test_signed_key(self): + km = self._key_manager() + yield km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) + yield km.put_raw_key(SIGNED_KEY, OpenPGPKey, ADDRESS) + key = yield km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + self.assertEqual(key.fingerprint, SIGNED_FINGERPRINT) + # Key material for testing @@ -284,6 +292,53 @@ HEsoyd7WOsuse7+NkyUHgMXMVW7cz+nU7iO+ht2rkBtv+Z5LGlzgHTeFjKci -----END PGP PRIVATE KEY BLOCK----- """ +# key CA1AD31E: public key "Leap Test Key " +# signed by E36E738D69173C13D709E44F2F455E2824D18DDF +SIGNED_FINGERPRINT = "6704CF3362087DA23E3D2DF8ED81DFD1CA1AD31E" +SIGNED_KEY = """ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.12 (GNU/Linux) + +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 +=xprj +-----END PGP PUBLIC KEY BLOCK----- +""" + import unittest if __name__ == "__main__": diff --git a/keymanager/src/leap/keymanager/validation.py b/keymanager/src/leap/keymanager/validation.py index b3aff3e4..c6fe478b 100644 --- a/keymanager/src/leap/keymanager/validation.py +++ b/keymanager/src/leap/keymanager/validation.py @@ -60,8 +60,6 @@ def can_upgrade(new_key, old_key): :type old_key: EncryptionKey :rtype: bool """ - # XXX implement key signature checking (#6120) - # First contact if old_key is None: return True @@ -90,4 +88,8 @@ def can_upgrade(new_key, old_key): new_key.validation > old_key.validation): return True + # New key signed by the old key + if old_key.key_id in new_key.signatures: + return True + return False -- cgit v1.2.3 From abf2bde0d74e9f6dbafa7fc088a094d7684f8d5a Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 5 Jan 2015 08:54:24 -0600 Subject: Port validation levels to enum34 --- keymanager/src/leap/keymanager/keys.py | 2 +- .../src/leap/keymanager/tests/test_keymanager.py | 2 +- keymanager/src/leap/keymanager/validation.py | 20 ++++++++++---------- 3 files changed, 12 insertions(+), 12 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py index 4e98de6c..562c0a9b 100644 --- a/keymanager/src/leap/keymanager/keys.py +++ b/keymanager/src/leap/keymanager/keys.py @@ -213,7 +213,7 @@ class EncryptionKey(object): KEY_EXPIRY_DATE_KEY: expiry_date, KEY_LAST_AUDITED_AT_KEY: last_audited_at, KEY_REFRESHED_AT_KEY: refreshed_at, - KEY_VALIDATION_KEY: str(self.validation), + KEY_VALIDATION_KEY: self.validation.name, KEY_ENCR_USED_KEY: self.encr_used, KEY_SIGN_USED_KEY: self.sign_used, KEY_TAGS_KEY: [KEYMANAGER_KEY_TAG], diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 86832bab..93bc42c7 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -79,7 +79,7 @@ class KeyManagerUtilTestCase(unittest.TestCase): 'expiry_date': 0, 'last_audited_at': 0, 'refreshed_at': 1311239602, - 'validation': str(ValidationLevel.Weak_Chain), + 'validation': ValidationLevel.Weak_Chain.name, 'encr_used': False, 'sign_used': True, } diff --git a/keymanager/src/leap/keymanager/validation.py b/keymanager/src/leap/keymanager/validation.py index c6fe478b..c81d533a 100644 --- a/keymanager/src/leap/keymanager/validation.py +++ b/keymanager/src/leap/keymanager/validation.py @@ -24,17 +24,17 @@ See: from datetime import datetime -from enum import Enum +from enum import IntEnum -ValidationLevel = Enum( - "Weak_Chain", - "Provider_Trust", - "Provider_Endorsement", - "Third_Party_Endorsement", - "Third_Party_Consensus", - "Historically_Auditing", - "Known_Key", +ValidationLevel = IntEnum("ValidationLevel", + "Weak_Chain " + "Provider_Trust " + "Provider_Endorsement " + "Third_Party_Endorsement " + "Third_Party_Consensus " + "Historically_Auditing " + "Known_Key " "Fingerprint") @@ -49,7 +49,7 @@ def toValidationLevel(value): :raises ValueError: if C{value} is not a validation level """ for level in ValidationLevel: - if value == str(level): + if value == level.name: return level raise ValueError("Not valid validation level: %s" % (value,)) -- cgit v1.2.3 From 74a2c68557fd86752bc52e1bda9f38e0460f0349 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Thu, 19 Feb 2015 10:21:56 -0600 Subject: Fetch keys should return KeyNotFound for unknown errors --- keymanager/src/leap/keymanager/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index a1a59f54..3ef63505 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -230,11 +230,11 @@ class KeyManager(object): if e.response.status_code == 404: d = defer.fail(KeyNotFound(address)) else: - d = defer.fail(e) + d = defer.fail(KeyNotFound(e.message)) logger.warning("HTTP error retrieving key: %r" % (e,)) logger.warning("%s" % (res.content,)) except Exception as e: - d = defer.fail(e) + d = defer.fail(KeyNotFound(e.message)) logger.warning("Error retrieving key: %r" % (e,)) return d -- cgit v1.2.3 From f025de95fbb23ee222721c0f4de14f511c6d9aee Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 30 Mar 2015 11:25:21 +0200 Subject: [feat] set fetched keys as Weak Chain if they are not from the same domain Nicknym server is authoritative for its own domain, but for others it might retrieve keys from key servers. On keys from the same domain we set the validation level to 'Provider Trust'. For other domains in the email address we set it to 'Weak Chain' as we don't have info about its source. Resolves: #6815 Related: #6718 Releases: 0.4.0 --- keymanager/src/leap/keymanager/__init__.py | 40 ++++++++- .../src/leap/keymanager/tests/test_keymanager.py | 96 ++++++++++++++++------ 2 files changed, 109 insertions(+), 27 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 3ef63505..f7b19747 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -44,6 +44,7 @@ import logging import requests from twisted.internet import defer +from urlparse import urlparse from leap.common.check import leap_assert from leap.common.events import signal @@ -219,13 +220,21 @@ class KeyManager(object): res = self._get(self._nickserver_uri, {'address': address}) res.raise_for_status() server_keys = res.json() + # insert keys in local database if self.OPENPGP_KEY in server_keys: + # nicknym server is authoritative for its own domain, + # for other domains the key might come from key servers. + validation_level = ValidationLevel.Weak_Chain + _, domain = _split_email(address) + if (domain == _get_domain(self._nickserver_uri)): + validation_level = ValidationLevel.Provider_Trust + d = self.put_raw_key( server_keys['openpgp'], OpenPGPKey, address=address, - validation=ValidationLevel.Provider_Trust) + validation=validation_level) except requests.exceptions.HTTPError as e: if e.response.status_code == 404: d = defer.fail(KeyNotFound(address)) @@ -786,6 +795,35 @@ class KeyManager(object): if ktype not in self._wrapper_map: raise UnsupportedKeyTypeError(str(ktype)) + +def _split_email(address): + """ + Split username and domain from an email address + + :param address: an email address + :type address: str + + :return: username and domain from the email address + :rtype: (str, str) + """ + if address.count("@") != 1: + return None + return address.split("@") + + +def _get_domain(url): + """ + Get the domain from an url + + :param url: an url + :type url: str + + :return: the domain part of the url + :rtype: str + """ + return urlparse(url).hostname + + from ._version import get_versions __version__ = get_versions()['version'] del get_versions diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 93bc42c7..55f892e9 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -52,6 +52,9 @@ from leap.keymanager.tests import ( ) +NICKSERVER_URI = "http://leap.se/" + + class KeyManagerUtilTestCase(unittest.TestCase): def test_is_address(self): @@ -201,31 +204,16 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): """ Test that the request is well formed when fetching keys from server. """ - km = self._key_manager(url='http://nickserver.domain') - - class Response(object): - status_code = 200 - headers = {'content-type': 'application/json'} - - def json(self): - return {'address': ADDRESS_2, 'openpgp': PUBLIC_KEY_2} - - def raise_for_status(self): - pass - - # mock the fetcher so it returns the key for ADDRESS_2 - km._fetcher.get = Mock( - return_value=Response()) - km.ca_cert_path = 'cacertpath' + km = self._key_manager(url=NICKSERVER_URI) def verify_the_call(_): km._fetcher.get.assert_called_once_with( - 'http://nickserver.domain', + NICKSERVER_URI, data={'address': ADDRESS_2}, verify='cacertpath', ) - d = km._fetch_keys_from_server(ADDRESS_2) + d = self._fetch_key(km, ADDRESS_2, PUBLIC_KEY_2) d.addCallback(verify_the_call) return d @@ -234,14 +222,35 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): """ Test that getting a key successfuly fetches from server. """ - km = self._key_manager(url='http://nickserver.domain') + km = self._key_manager(url=NICKSERVER_URI) + + key = yield self._fetch_key(km, ADDRESS, PUBLIC_KEY) + self.assertIsInstance(key, OpenPGPKey) + self.assertTrue(ADDRESS in key.address) + self.assertEqual(key.validation, ValidationLevel.Provider_Trust) + + @inlineCallbacks + def test_get_key_fetches_other_domain(self): + """ + Test that getting a key successfuly fetches from server. + """ + km = self._key_manager(url=NICKSERVER_URI) + + key = yield self._fetch_key(km, ADDRESS_OTHER, PUBLIC_KEY_OTHER) + self.assertIsInstance(key, OpenPGPKey) + self.assertTrue(ADDRESS_OTHER in key.address) + self.assertEqual(key.validation, ValidationLevel.Weak_Chain) + def _fetch_key(self, km, address, key): + """ + :returns: a Deferred that will fire with the OpenPGPKey + """ class Response(object): status_code = 200 headers = {'content-type': 'application/json'} def json(self): - return {'address': ADDRESS, 'openpgp': PUBLIC_KEY} + return {'address': address, 'openpgp': key} def raise_for_status(self): pass @@ -250,19 +259,18 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km._fetcher.get = Mock(return_value=Response()) km.ca_cert_path = 'cacertpath' # try to key get without fetching from server - d = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) - yield self.assertFailure(d, KeyNotFound) + d_fail = km.get_key(address, OpenPGPKey, fetch_remote=False) + d = self.assertFailure(d_fail, KeyNotFound) # try to get key fetching from server. - key = yield km.get_key(ADDRESS, OpenPGPKey) - self.assertIsInstance(key, OpenPGPKey) - self.assertTrue(ADDRESS in key.address) + d.addCallback(lambda _: km.get_key(address, OpenPGPKey)) + return d @inlineCallbacks def test_put_key_ascii(self): """ Test that putting ascii key works """ - km = self._key_manager(url='http://nickserver.domain') + km = self._key_manager(url=NICKSERVER_URI) yield km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) key = yield km.get_key(ADDRESS, OpenPGPKey) @@ -387,3 +395,39 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): import unittest if __name__ == "__main__": unittest.main() + +# key 0F91B402: someone@somedomain.org +# 9420 EC7B 6DCB 867F 5592 E6D1 7504 C974 0F91 B402 +ADDRESS_OTHER = "someone@somedomain.org" +PUBLIC_KEY_OTHER = """ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +mQENBFUZFLwBCADRzTstykRAV3aWysLAV4O3DXdpXhV3Cww8Pfc6m1bVxAT2ifcL +kLWEaIkOB48SYIHbYzqOi1/h5abJf+5n4uhaIks+FsjsXYo1XOiYpVCNf7+xLnUM +jkmglKT5sASr61QDcFMqWfGTJ8iUTNVCJZ2k14QJ4Vss/ntnV9uB7Ef7wU7RZvxr +wINH/0LfKPsGE9l2qNpKUAAmg2bHn9YdsHj1sqlW7eZpwvefYrQej4KBaL2oq3vt +QQOdXGFqWYMe3cX+bQ1DAMG3ttTF6EGkY97BK7A18I/RJiLujWCEAkMzFr5SK9KU +AOMj6MpjfTOE+GfUKsu7/gGt42eMBFsIOvsZABEBAAG0IFNvbWVvbmUgPHNvbWVv +bmVAc29tZWRvbWFpbi5vcmc+iQE4BBMBAgAiBQJVGRS8AhsDBgsJCAcDAgYVCAIJ +CgsEFgIDAQIeAQIXgAAKCRB1BMl0D5G0AlFsCAC33LhxBRwO64T6DgTb4/39aLpi +9T3yAmXBAHC7Q+4f37IBX5fJBRKu4Lvfp6KherOl/I/Jj34yv8pm0j+kXeWktfxZ +cW+mv2vjBHQVopiUSyMVh7caFSq9sKm+oQdo6oIl9DHSARegbkCn2+0b4VxgJpyj +TZBMyUMD2AayivQU4QHOM3KCozhLNNDbpKy7LH0MSAUDmRaJsPk1zK15lQocK/7R +Z5yF4rdrdzDWrVucZJc09yntSqTGECue3W2GBCaBlb/O1c9xei4MTb4nSHS5Gp/7 +hcjrvIrgPpehndk8ZRREN/Y8uk1W5fbWzx+5z8g31RCGWBQw4NAnG10NZ3oEuQEN +BFUZFLwBCADocYZmLu1iXIE6gKqniR6Z8UDC5XnqgK+BEJwi1abe9zWhjgKeW9Vv +u1i194wuCUiNkP/bMvwMBZLTslDzqxl32ETk9FvB3kWy80S8MDjQJ15IN4I622fq +MEWwtQ0WrRay9VV6M8H2mIf71/1d5T9ysWK4XRyv+N7eRhfg7T2uhrpNyKdCZzjq +2wlgpVkMY7gtxTqJseM+qS5UNiReGxtoOXFLzzmagFgbqK88eMeZJZt8yKf81xhP +SWLTxaVaeBEAlajvEkxZJrrDQuc+maTwtMxmNUe815wJnpcRF8VD91GUpSLAN6EC +1QuJUl6Lc2o2tcHeo6CGsDZ96o0J8pFhABEBAAGJAR8EGAECAAkFAlUZFLwCGwwA +CgkQdQTJdA+RtAKcdwgApzHPhwwaZ9TBjgOytke/hPE0ht/EJ5nRiIda2PucoPh6 +DwnaI8nvmGXUfC4qFy6LM8/fJHof1BqLnMbx8MCLurnm5z30q8RhLE3YWM11zuMy +6wkHGmi/6S1G4okC+Uu8AA4K//HBo8bLcqGVWRnFAmCqy6VMAofsQvmM7vHbRj56 +U919Bki/7I6kcxPEzO73Umh3o82VP/Hz3JMigRNBRfG3jPrX04RLJj3Ib5lhQIDw +XrO8VHz9foOpY+rJnWj+6QAozxorzZYShu6H0GR1nIuqWMwli1nrx6BeIJAVz5cg +QzEd9yAN+81fkIBaa6Y8LCBxV03JCc2J4eCUKXd1gg== +=gDzy +-----END PGP PUBLIC KEY BLOCK----- +""" -- cgit v1.2.3 From 13babd9fe81dd7e3af7266fb99306c7726cf84f5 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Tue, 7 Apr 2015 12:56:20 +0200 Subject: [doc] added the right link the validation levels documentation The mailing list was linked, but now there is a proper documentation page. - Releases: 0.4.0 --- keymanager/src/leap/keymanager/validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/validation.py b/keymanager/src/leap/keymanager/validation.py index c81d533a..dfe64329 100644 --- a/keymanager/src/leap/keymanager/validation.py +++ b/keymanager/src/leap/keymanager/validation.py @@ -19,7 +19,7 @@ Validation levels implementation for key managment. See: - https://lists.riseup.net/www/arc/leap-discuss/2014-09/msg00000.html + https://leap.se/en/docs/design/transitional-key-validation """ -- cgit v1.2.3 From 298d73867104e4e81102392ffb0171fd07b8ab33 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 6 May 2015 18:04:03 -0300 Subject: [feat] adapt to new events api on common - Related: #6359 --- keymanager/src/leap/keymanager/__init__.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index f7b19747..47f479b3 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -47,8 +47,7 @@ from twisted.internet import defer from urlparse import urlparse from leap.common.check import leap_assert -from leap.common.events import signal -from leap.common.events import events_pb2 as proto +from leap.common.events import emit, catalog from leap.common.decorators import memoized_method from leap.keymanager.errors import ( @@ -278,7 +277,7 @@ class KeyManager(object): self._api_version, self._uid) self._put(uri, data) - signal(proto.KEYMANAGER_DONE_UPLOADING_KEYS, self._address) + emit(catalog.KEYMANAGER_DONE_UPLOADING_KEYS, self._address) d = self.get_key( self._address, ktype, private=False, fetch_remote=False) @@ -314,24 +313,24 @@ class KeyManager(object): leap_assert( ktype in self._wrapper_map, 'Unkown key type: %s.' % str(ktype)) - signal(proto.KEYMANAGER_LOOKING_FOR_KEY, address) + emit(catalog.KEYMANAGER_LOOKING_FOR_KEY, address) def key_found(key): - signal(proto.KEYMANAGER_KEY_FOUND, address) + emit(catalog.KEYMANAGER_KEY_FOUND, address) return key def key_not_found(failure): if not failure.check(KeyNotFound): return failure - signal(proto.KEYMANAGER_KEY_NOT_FOUND, address) + emit(catalog.KEYMANAGER_KEY_NOT_FOUND, address) # we will only try to fetch a key from nickserver if fetch_remote # is True and the key is not private. if fetch_remote is False or private is True: return failure - signal(proto.KEYMANAGER_LOOKING_FOR_KEY, address) + emit(catalog.KEYMANAGER_LOOKING_FOR_KEY, address) d = self._fetch_keys_from_server(address) d.addCallback( lambda _: @@ -388,10 +387,10 @@ class KeyManager(object): self._assert_supported_key_type(ktype) def signal_finished(key): - signal(proto.KEYMANAGER_FINISHED_KEY_GENERATION, self._address) + emit(catalog.KEYMANAGER_FINISHED_KEY_GENERATION, self._address) return key - signal(proto.KEYMANAGER_STARTED_KEY_GENERATION, self._address) + emit(catalog.KEYMANAGER_STARTED_KEY_GENERATION, self._address) d = self._wrapper_map[ktype].gen_key(self._address) d.addCallback(signal_finished) return d -- cgit v1.2.3 From 7698fba53ff8d5d311d53d784a83f94a78c68035 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Fri, 26 Jun 2015 18:12:22 +0200 Subject: [bug] remove the dependency on enum34 * Resolves: #7188 --- keymanager/src/leap/keymanager/__init__.py | 14 ++--- keymanager/src/leap/keymanager/keys.py | 10 +-- .../src/leap/keymanager/tests/test_keymanager.py | 13 ++-- .../src/leap/keymanager/tests/test_validation.py | 18 +++--- keymanager/src/leap/keymanager/validation.py | 73 +++++++++++++++------- 5 files changed, 77 insertions(+), 51 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 47f479b3..c2d74096 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -57,7 +57,7 @@ from leap.keymanager.errors import ( UnsupportedKeyTypeError, InvalidSignature ) -from leap.keymanager.validation import ValidationLevel, can_upgrade +from leap.keymanager.validation import ValidationLevels, can_upgrade from leap.keymanager.keys import ( build_key_from_dict, @@ -224,10 +224,10 @@ class KeyManager(object): if self.OPENPGP_KEY in server_keys: # nicknym server is authoritative for its own domain, # for other domains the key might come from key servers. - validation_level = ValidationLevel.Weak_Chain + validation_level = ValidationLevels.Weak_Chain _, domain = _split_email(address) if (domain == _get_domain(self._nickserver_uri)): - validation_level = ValidationLevel.Provider_Trust + validation_level = ValidationLevels.Provider_Trust d = self.put_raw_key( server_keys['openpgp'], @@ -712,7 +712,7 @@ class KeyManager(object): return d def put_raw_key(self, key, ktype, address, - validation=ValidationLevel.Weak_Chain): + validation=ValidationLevels.Weak_Chain): """ Put raw key bound to address in local storage. @@ -724,7 +724,7 @@ class KeyManager(object): :type address: str :param validation: validation level for this key (default: 'Weak_Chain') - :type validation: ValidationLevel + :type validation: ValidationLevels :return: A Deferred which fires when the key is in the storage, or which fails with KeyAddressMismatch if address doesn't match @@ -744,7 +744,7 @@ class KeyManager(object): return d def fetch_key(self, address, uri, ktype, - validation=ValidationLevel.Weak_Chain): + validation=ValidationLevels.Weak_Chain): """ Fetch a public key bound to address from the network and put it in local storage. @@ -757,7 +757,7 @@ class KeyManager(object): :type ktype: subclass of EncryptionKey :param validation: validation level for this key (default: 'Weak_Chain') - :type validation: ValidationLevel + :type validation: ValidationLevels :return: A Deferred which fires when the key is in the storage, or which fails with KeyNotFound: if not valid key on uri or fails diff --git a/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py index 562c0a9b..91559c2f 100644 --- a/keymanager/src/leap/keymanager/keys.py +++ b/keymanager/src/leap/keymanager/keys.py @@ -35,7 +35,7 @@ from datetime import datetime from leap.common.check import leap_assert from twisted.internet import defer -from leap.keymanager.validation import ValidationLevel, toValidationLevel +from leap.keymanager.validation import ValidationLevels logger = logging.getLogger(__name__) @@ -120,11 +120,11 @@ def build_key_from_dict(kClass, kdict): :rtype: C{kClass} """ try: - validation = toValidationLevel(kdict[KEY_VALIDATION_KEY]) + validation = ValidationLevels.get(kdict[KEY_VALIDATION_KEY]) except ValueError: logger.error("Not valid validation level (%s) for key %s", (kdict[KEY_VALIDATION_KEY], kdict[KEY_ID_KEY])) - validation = ValidationLevel.Weak_Chain + validation = ValidationLevels.Weak_Chain expiry_date = _to_datetime(kdict[KEY_EXPIRY_DATE_KEY]) last_audited_at = _to_datetime(kdict[KEY_LAST_AUDITED_AT_KEY]) @@ -176,7 +176,7 @@ class EncryptionKey(object): def __init__(self, address, key_id="", fingerprint="", key_data="", private=False, length=0, expiry_date=None, - validation=ValidationLevel.Weak_Chain, last_audited_at=None, + validation=ValidationLevels.Weak_Chain, last_audited_at=None, refreshed_at=None, encr_used=False, sign_used=False): self.address = address self.key_id = key_id @@ -213,7 +213,7 @@ class EncryptionKey(object): KEY_EXPIRY_DATE_KEY: expiry_date, KEY_LAST_AUDITED_AT_KEY: last_audited_at, KEY_REFRESHED_AT_KEY: refreshed_at, - KEY_VALIDATION_KEY: self.validation.name, + KEY_VALIDATION_KEY: str(self.validation), KEY_ENCR_USED_KEY: self.encr_used, KEY_SIGN_USED_KEY: self.sign_used, KEY_TAGS_KEY: [KEYMANAGER_KEY_TAG], diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 55f892e9..08d37500 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -36,10 +36,7 @@ from leap.keymanager.keys import ( is_address, build_key_from_dict, ) -from leap.keymanager.validation import ( - ValidationLevel, - toValidationLevel -) +from leap.keymanager.validation import ValidationLevels from leap.keymanager.tests import ( KeyManagerWithSoledadTestCase, ADDRESS, @@ -82,7 +79,7 @@ class KeyManagerUtilTestCase(unittest.TestCase): 'expiry_date': 0, 'last_audited_at': 0, 'refreshed_at': 1311239602, - 'validation': ValidationLevel.Weak_Chain.name, + 'validation': str(ValidationLevels.Weak_Chain), 'encr_used': False, 'sign_used': True, } @@ -115,7 +112,7 @@ class KeyManagerUtilTestCase(unittest.TestCase): datetime.fromtimestamp(kdict['refreshed_at']), key.refreshed_at, 'Wrong data in key.') self.assertEqual( - toValidationLevel(kdict['validation']), key.validation, + ValidationLevels.get(kdict['validation']), key.validation, 'Wrong data in key.') self.assertEqual( kdict['encr_used'], key.encr_used, @@ -227,7 +224,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): key = yield self._fetch_key(km, ADDRESS, PUBLIC_KEY) self.assertIsInstance(key, OpenPGPKey) self.assertTrue(ADDRESS in key.address) - self.assertEqual(key.validation, ValidationLevel.Provider_Trust) + self.assertEqual(key.validation, ValidationLevels.Provider_Trust) @inlineCallbacks def test_get_key_fetches_other_domain(self): @@ -239,7 +236,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): key = yield self._fetch_key(km, ADDRESS_OTHER, PUBLIC_KEY_OTHER) self.assertIsInstance(key, OpenPGPKey) self.assertTrue(ADDRESS_OTHER in key.address) - self.assertEqual(key.validation, ValidationLevel.Weak_Chain) + self.assertEqual(key.validation, ValidationLevels.Weak_Chain) def _fetch_key(self, km, address, key): """ diff --git a/keymanager/src/leap/keymanager/tests/test_validation.py b/keymanager/src/leap/keymanager/tests/test_validation.py index 15e7d27d..0c1d155f 100644 --- a/keymanager/src/leap/keymanager/tests/test_validation.py +++ b/keymanager/src/leap/keymanager/tests/test_validation.py @@ -31,10 +31,10 @@ from leap.keymanager.tests import ( PUBLIC_KEY, KEY_FINGERPRINT ) -from leap.keymanager.validation import ValidationLevel +from leap.keymanager.validation import ValidationLevels -class ValidationLevelTestCase(KeyManagerWithSoledadTestCase): +class ValidationLevelsTestCase(KeyManagerWithSoledadTestCase): @inlineCallbacks def test_none_old_key(self): @@ -47,7 +47,7 @@ class ValidationLevelTestCase(KeyManagerWithSoledadTestCase): def test_cant_upgrade(self): km = self._key_manager() yield km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS, - validation=ValidationLevel.Provider_Trust) + validation=ValidationLevels.Provider_Trust) d = km.put_raw_key(UNRELATED_KEY, OpenPGPKey, ADDRESS) yield self.assertFailure(d, KeyNotValidUpgrade) @@ -56,7 +56,7 @@ class ValidationLevelTestCase(KeyManagerWithSoledadTestCase): km = self._key_manager() yield km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) yield km.put_raw_key(UNRELATED_KEY, OpenPGPKey, ADDRESS, - validation=ValidationLevel.Fingerprint) + validation=ValidationLevels.Fingerprint) key = yield km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) self.assertEqual(key.fingerprint, UNRELATED_FINGERPRINT) @@ -73,12 +73,12 @@ class ValidationLevelTestCase(KeyManagerWithSoledadTestCase): km = self._key_manager() yield km.put_raw_key( EXPIRED_KEY, OpenPGPKey, ADDRESS, - validation=ValidationLevel.Third_Party_Endorsement) + validation=ValidationLevels.Third_Party_Endorsement) d = km.put_raw_key( UNRELATED_KEY, OpenPGPKey, ADDRESS, - validation=ValidationLevel.Provider_Trust) + validation=ValidationLevels.Provider_Trust) yield self.assertFailure(d, KeyNotValidUpgrade) @inlineCallbacks @@ -93,9 +93,9 @@ class ValidationLevelTestCase(KeyManagerWithSoledadTestCase): def test_not_used(self): km = self._key_manager() yield km.put_raw_key(UNEXPIRED_KEY, OpenPGPKey, ADDRESS, - validation=ValidationLevel.Provider_Trust) + validation=ValidationLevels.Provider_Trust) yield km.put_raw_key(UNRELATED_KEY, OpenPGPKey, ADDRESS, - validation=ValidationLevel.Provider_Endorsement) + validation=ValidationLevels.Provider_Endorsement) key = yield km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) self.assertEqual(key.fingerprint, UNRELATED_FINGERPRINT) @@ -114,7 +114,7 @@ class ValidationLevelTestCase(KeyManagerWithSoledadTestCase): yield km.verify(TEXT, ADDRESS, OpenPGPKey, detached_sig=signature) d = km.put_raw_key( UNRELATED_KEY, OpenPGPKey, ADDRESS, - validation=ValidationLevel.Provider_Endorsement) + validation=ValidationLevels.Provider_Endorsement) yield self.assertFailure(d, KeyNotValidUpgrade) @inlineCallbacks diff --git a/keymanager/src/leap/keymanager/validation.py b/keymanager/src/leap/keymanager/validation.py index dfe64329..3bb40325 100644 --- a/keymanager/src/leap/keymanager/validation.py +++ b/keymanager/src/leap/keymanager/validation.py @@ -24,34 +24,63 @@ See: from datetime import datetime -from enum import IntEnum -ValidationLevel = IntEnum("ValidationLevel", - "Weak_Chain " - "Provider_Trust " - "Provider_Endorsement " - "Third_Party_Endorsement " - "Third_Party_Consensus " - "Historically_Auditing " - "Known_Key " - "Fingerprint") +class ValidationLevel(object): + """ + A validation level + + Meant to be used to compare levels or get it's string representation. + """ + def __init__(self, name, value): + self.name = name + self.value = value + + def __cmp__(self, other): + return cmp(self.value, other.value) + + def __str__(self): + return self.name + + def __repr__(self): + return "" % (self.name, self.value) -def toValidationLevel(value): +class _ValidationLevels(object): """ - Convert a string representation of a validation level into - C{ValidationLevel} + Handler class to manage validation levels. It should have only one global + instance 'ValidationLevels'. - :param value: validation level - :type value: str - :rtype: ValidationLevel - :raises ValueError: if C{value} is not a validation level + The levels are attributes of the instance and can be used like: + ValidationLevels.Weak_Chain + ValidationLevels.get("Weak_Chain") """ - for level in ValidationLevel: - if value == level.name: - return level - raise ValueError("Not valid validation level: %s" % (value,)) + _level_names = ("Weak_Chain", + "Provider_Trust", + "Provider_Endorsement", + "Third_Party_Endorsement", + "Third_Party_Consensus", + "Historically_Auditing", + "Known_Key", + "Fingerprint") + + def __init__(self): + for name in self._level_names: + setattr(self, name, + ValidationLevel(name, self._level_names.index(name))) + + def get(self, name): + """ + Get the ValidationLevel of a name + + :param name: name of the level + :type name: str + :rtype: ValidationLevel + """ + return getattr(self, name) + + +ValidationLevels = _ValidationLevels() def can_upgrade(new_key, old_key): @@ -69,7 +98,7 @@ def can_upgrade(new_key, old_key): return True # Manually verified fingerprint - if new_key.validation == ValidationLevel.Fingerprint: + if new_key.validation == ValidationLevels.Fingerprint: return True # Expired key and higher validation level -- cgit v1.2.3 From 45669db6a8bd40c2ef115355c5d503e7211930ad Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 29 Jun 2015 12:05:51 -0400 Subject: [style] spelling typo --- keymanager/src/leap/keymanager/validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/validation.py b/keymanager/src/leap/keymanager/validation.py index 3bb40325..734cfce9 100644 --- a/keymanager/src/leap/keymanager/validation.py +++ b/keymanager/src/leap/keymanager/validation.py @@ -30,7 +30,7 @@ class ValidationLevel(object): """ A validation level - Meant to be used to compare levels or get it's string representation. + Meant to be used to compare levels or get its string representation. """ def __init__(self, name, value): self.name = name -- cgit v1.2.3 From 8589e947fbae5baf21caa8b47d75d7d33c2f493b Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 23 Jul 2015 14:34:53 -0400 Subject: [pkg] avoid choking on latest gnupg version latest gnupg version (from pypi) was '2.0.2-py2.7.egg', which is parsed as a LegacyVersion and therefore breaks the numeric comparison. this is a workaround to allow the sanity check to continue, by comparing just the numeric part of the version string. --- keymanager/src/leap/keymanager/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index c2d74096..56633c5d 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -24,13 +24,20 @@ try: from gnupg.gnupg import GPGUtilities assert(GPGUtilities) # pyflakes happy from gnupg import __version__ as _gnupg_version + if '-' in _gnupg_version: + # avoid Parsing it as LegacyVersion, get just + # the release numbers: + _gnupg_version = _gnupg_version.split('-')[0] from pkg_resources import parse_version + # We need to make sure that we're not colliding with the infamous + # python-gnupg assert(parse_version(_gnupg_version) >= parse_version('1.4.0')) except (ImportError, AssertionError): print "*******" print "Ooops! It looks like there is a conflict in the installed version " print "of gnupg." + print "GNUPG_VERSION:", _gnupg_version print print "Disclaimer: Ideally, we would need to work a patch and propose the " print "merge to upstream. But until then do: " -- cgit v1.2.3 From f7043a08502f5d920d62c326f0bcc9530fddbbb6 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 29 Jul 2015 16:27:54 -0400 Subject: [style] pep8 --- keymanager/src/leap/keymanager/__init__.py | 15 ++++++--------- keymanager/src/leap/keymanager/tests/test_keymanager.py | 3 +-- keymanager/src/leap/keymanager/tests/test_validation.py | 3 +-- 3 files changed, 8 insertions(+), 13 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 56633c5d..282ff48c 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -71,10 +71,12 @@ from leap.keymanager.keys import ( KEYMANAGER_KEY_TAG, TAGS_PRIVATE_INDEX, ) -from leap.keymanager.openpgp import ( - OpenPGPKey, - OpenPGPScheme, -) +from leap.keymanager.openpgp import OpenPGPKey, OpenPGPScheme + +from ._version import get_versions + +__version__ = get_versions()['version'] +del get_versions logger = logging.getLogger(__name__) @@ -828,8 +830,3 @@ def _get_domain(url): :rtype: str """ return urlparse(url).hostname - - -from ._version import get_versions -__version__ = get_versions()['version'] -del get_versions diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 08d37500..a12cac0e 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -388,9 +388,8 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): sign=ADDRESS, fetch_remote=False)) return self.assertFailure(d, KeyNotFound) - -import unittest if __name__ == "__main__": + import unittest unittest.main() # key 0F91B402: someone@somedomain.org diff --git a/keymanager/src/leap/keymanager/tests/test_validation.py b/keymanager/src/leap/keymanager/tests/test_validation.py index 0c1d155f..561ca505 100644 --- a/keymanager/src/leap/keymanager/tests/test_validation.py +++ b/keymanager/src/leap/keymanager/tests/test_validation.py @@ -339,7 +339,6 @@ X2+l7IOSt+31KQCBFN/VmhTySJOVQC1d2A56lSH2c/DWVClji+x3suzn -----END PGP PUBLIC KEY BLOCK----- """ - -import unittest if __name__ == "__main__": + import unittest unittest.main() -- cgit v1.2.3 From c05c63f17ebdf40cc47ba6259f14dd1eafb7c59e Mon Sep 17 00:00:00 2001 From: Bruno Wagner Date: Fri, 24 Jul 2015 16:24:13 -0300 Subject: [style] Fixed pep8 warnings Fixed pep8 warnings to prepare the keymanager for CI --- keymanager/src/leap/keymanager/__init__.py | 1 + keymanager/src/leap/keymanager/_version.py | 19 ++++++++----------- keymanager/src/leap/keymanager/openpgp.py | 7 +++++-- .../src/leap/keymanager/tests/test_validation.py | 1 + 4 files changed, 15 insertions(+), 13 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 282ff48c..999b53c3 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -19,6 +19,7 @@ Key Manager is a Nicknym agent for LEAP client. """ # let's do a little sanity check to see if we're using the wrong gnupg import sys +from ._version import get_versions try: from gnupg.gnupg import GPGUtilities diff --git a/keymanager/src/leap/keymanager/_version.py b/keymanager/src/leap/keymanager/_version.py index 28fca968..5153a9bc 100644 --- a/keymanager/src/leap/keymanager/_version.py +++ b/keymanager/src/leap/keymanager/_version.py @@ -1,5 +1,3 @@ - -IN_LONG_VERSION_PY = True # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag # feature). Distribution tarballs (build by setup.py sdist) and build @@ -10,12 +8,15 @@ IN_LONG_VERSION_PY = True # versioneer-0.7+ (https://github.com/warner/python-versioneer) # these strings will be replaced by git during git-archive -git_refnames = "$Format:%d$" -git_full = "$Format:%H$" - - import subprocess import sys +import re +import os.path + +IN_LONG_VERSION_PY = True + +git_refnames = "$Format:%d$" +git_full = "$Format:%H$" def run_command(args, cwd=None, verbose=False): @@ -38,10 +39,6 @@ def run_command(args, cwd=None, verbose=False): return stdout -import re -import os.path - - def get_expanded_variables(versionfile_source): # the code embedded in _version.py can just fetch the value of these # variables. When used from setup.py, we don't want to import @@ -86,7 +83,7 @@ def versions_from_expanded_variables(variables, tag_prefix, verbose=False): # "stabilization", as well as "HEAD" and "master". tags = set([r for r in refs if re.search(r'\d', r)]) if verbose: - print("discarding '%s', no digits" % ",".join(refs-tags)) + print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: print("likely tags: %s" % ",".join(sorted(tags))) for ref in sorted(tags): diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 794a0ec2..5d91dc95 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -114,8 +114,11 @@ class TempGPGWrapper(object): publkeys = filter( lambda pubkey: pubkey.key_id not in privids, publkeys) - listkeys = lambda: self._gpg.list_keys() - listsecretkeys = lambda: self._gpg.list_keys(secret=True) + def listkeys(): + return self._gpg.list_keys() + + def listsecretkeys(): + return self._gpg.list_keys(secret=True) self._gpg = GPG(binary=self._gpgbinary, homedir=tempfile.mkdtemp()) diff --git a/keymanager/src/leap/keymanager/tests/test_validation.py b/keymanager/src/leap/keymanager/tests/test_validation.py index 561ca505..ddf1170d 100644 --- a/keymanager/src/leap/keymanager/tests/test_validation.py +++ b/keymanager/src/leap/keymanager/tests/test_validation.py @@ -18,6 +18,7 @@ Tests for the Validation Levels """ +import unittest from datetime import datetime from twisted.internet.defer import inlineCallbacks -- cgit v1.2.3 From 0df6696936776583cbf662625297f6b099c2cb66 Mon Sep 17 00:00:00 2001 From: Bruno Wagner Date: Mon, 3 Aug 2015 17:37:09 -0300 Subject: [style] Re-added lambdas to openpgp on keymanager --- keymanager/src/leap/keymanager/openpgp.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 5d91dc95..bbdedb23 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -111,14 +111,10 @@ class TempGPGWrapper(object): # and we want to count the keys afterwards. privids = map(lambda privkey: privkey.key_id, privkeys) - publkeys = filter( - lambda pubkey: pubkey.key_id not in privids, publkeys) + publkeys = filter(lambda pubkey: pubkey.key_id not in privids, publkeys) - def listkeys(): - return self._gpg.list_keys() - - def listsecretkeys(): - return self._gpg.list_keys(secret=True) + listkeys = lambda: self._gpg.list_keys() + listsecretkeys = lambda: self._gpg.list_keys(secret=True) self._gpg = GPG(binary=self._gpgbinary, homedir=tempfile.mkdtemp()) -- cgit v1.2.3 From 399b5518deb6bea155042a46cbcbb62d743bc4db Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 17 Aug 2015 19:22:14 -0400 Subject: [style] pep8 fix --- keymanager/src/leap/keymanager/openpgp.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index bbdedb23..794a0ec2 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -111,7 +111,8 @@ class TempGPGWrapper(object): # and we want to count the keys afterwards. privids = map(lambda privkey: privkey.key_id, privkeys) - publkeys = filter(lambda pubkey: pubkey.key_id not in privids, publkeys) + publkeys = filter( + lambda pubkey: pubkey.key_id not in privids, publkeys) listkeys = lambda: self._gpg.list_keys() listsecretkeys = lambda: self._gpg.list_keys(secret=True) -- cgit v1.2.3 From c13286ae8d8555484124cd32c3218e83ebb70462 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 14 Sep 2015 23:08:41 -0400 Subject: [feat] use async events api this avoids using a separate thread with tornado ioloop for events client, since we can use twisted reactor. - Resolves: #7274 --- keymanager/src/leap/keymanager/__init__.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 999b53c3..34bc964e 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -55,7 +55,7 @@ from twisted.internet import defer from urlparse import urlparse from leap.common.check import leap_assert -from leap.common.events import emit, catalog +from leap.common.events import emit_async, catalog from leap.common.decorators import memoized_method from leap.keymanager.errors import ( @@ -287,7 +287,7 @@ class KeyManager(object): self._api_version, self._uid) self._put(uri, data) - emit(catalog.KEYMANAGER_DONE_UPLOADING_KEYS, self._address) + emit_async(catalog.KEYMANAGER_DONE_UPLOADING_KEYS, self._address) d = self.get_key( self._address, ktype, private=False, fetch_remote=False) @@ -323,24 +323,24 @@ class KeyManager(object): leap_assert( ktype in self._wrapper_map, 'Unkown key type: %s.' % str(ktype)) - emit(catalog.KEYMANAGER_LOOKING_FOR_KEY, address) + emit_async(catalog.KEYMANAGER_LOOKING_FOR_KEY, address) def key_found(key): - emit(catalog.KEYMANAGER_KEY_FOUND, address) + emit_async(catalog.KEYMANAGER_KEY_FOUND, address) return key def key_not_found(failure): if not failure.check(KeyNotFound): return failure - emit(catalog.KEYMANAGER_KEY_NOT_FOUND, address) + emit_async(catalog.KEYMANAGER_KEY_NOT_FOUND, address) # we will only try to fetch a key from nickserver if fetch_remote # is True and the key is not private. if fetch_remote is False or private is True: return failure - emit(catalog.KEYMANAGER_LOOKING_FOR_KEY, address) + emit_async(catalog.KEYMANAGER_LOOKING_FOR_KEY, address) d = self._fetch_keys_from_server(address) d.addCallback( lambda _: @@ -397,10 +397,10 @@ class KeyManager(object): self._assert_supported_key_type(ktype) def signal_finished(key): - emit(catalog.KEYMANAGER_FINISHED_KEY_GENERATION, self._address) + emit_async(catalog.KEYMANAGER_FINISHED_KEY_GENERATION, self._address) return key - emit(catalog.KEYMANAGER_STARTED_KEY_GENERATION, self._address) + emit_async(catalog.KEYMANAGER_STARTED_KEY_GENERATION, self._address) d = self._wrapper_map[ktype].gen_key(self._address) d.addCallback(signal_finished) return d -- cgit v1.2.3 From 1e00141ad6c032ca07d3c0e0e75ee8baeef350d6 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 16 Sep 2015 12:55:08 -0400 Subject: [style] pep8 fix --- keymanager/src/leap/keymanager/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 34bc964e..c4534e54 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -74,8 +74,6 @@ from leap.keymanager.keys import ( ) from leap.keymanager.openpgp import OpenPGPKey, OpenPGPScheme -from ._version import get_versions - __version__ = get_versions()['version'] del get_versions @@ -397,7 +395,8 @@ class KeyManager(object): self._assert_supported_key_type(ktype) def signal_finished(key): - emit_async(catalog.KEYMANAGER_FINISHED_KEY_GENERATION, self._address) + emit_async( + catalog.KEYMANAGER_FINISHED_KEY_GENERATION, self._address) return key emit_async(catalog.KEYMANAGER_STARTED_KEY_GENERATION, self._address) -- cgit v1.2.3 From 2deddc1dcb408e14106c425ddc2cbd656d9bf7af Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Wed, 16 Sep 2015 11:04:33 +0200 Subject: [feat] add logging to fetch_key In case of failure of fetch_key will be useful to have some logging telling us wich key is fetching. - Related: #7410 --- keymanager/src/leap/keymanager/__init__.py | 1 + 1 file changed, 1 insertion(+) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index c4534e54..cf430043 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -779,6 +779,7 @@ class KeyManager(object): """ self._assert_supported_key_type(ktype) + logger.info("Fetch key for %s from %s" % (address, uri)) res = self._get(uri) if not res.ok: return defer.fail(KeyNotFound(uri)) -- cgit v1.2.3 From e55c64ca3aa649e93012f4611a4cebbf372b9d66 Mon Sep 17 00:00:00 2001 From: Folker Bernitt Date: Fri, 18 Sep 2015 17:03:14 +0200 Subject: [feature] Use ca_bundle when fetching keys by url This is necessary as a fetch by url will talk to remote sites or, for providers with a commercial cert, with a cert that had not been signed with the provider CA. - support lookup of local keys by url for providers with a commercial cert - combine ca_bundle with ca_cert_path if specified - close soledad after each test --- keymanager/src/leap/keymanager/__init__.py | 42 ++++++++++++- keymanager/src/leap/keymanager/tests/__init__.py | 5 +- .../src/leap/keymanager/tests/test_keymanager.py | 68 ++++++++++++++++++++-- 3 files changed, 107 insertions(+), 8 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index cf430043..1220402a 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -18,7 +18,10 @@ Key Manager is a Nicknym agent for LEAP client. """ # let's do a little sanity check to see if we're using the wrong gnupg +import fileinput import sys +import tempfile +from leap.common import ca_bundle from ._version import get_versions try: @@ -134,12 +137,30 @@ class KeyManager(object): } # the following are used to perform https requests self._fetcher = requests - self._session = self._fetcher.session() + self._combined_ca_bundle = self._create_combined_bundle_file() # # utilities # + def _create_combined_bundle_file(self): + leap_ca_bundle = ca_bundle.where() + + if self._ca_cert_path == leap_ca_bundle: + return self._ca_cert_path # don't merge file with itself + elif self._ca_cert_path is None: + return leap_ca_bundle + + tmp_file = tempfile.NamedTemporaryFile(delete=True) # file is auto deleted when python process ends + + with open(tmp_file.name, 'w') as fout: + fin = fileinput.input(files=(leap_ca_bundle, self._ca_cert_path)) + for line in fin: + fout.write(line) + fin.close() + + return tmp_file.name + def _key_class_from_type(self, ktype): """ Return key class from string representation of key type. @@ -176,6 +197,23 @@ class KeyManager(object): # 'Content-type is not JSON.') return res + def _get_with_combined_ca_bundle(self, uri, data=None): + """ + Send a GET request to C{uri} containing C{data}. + + Instead of using the ca_cert provided on construction time, this version also uses + the default certificates shipped with leap.common + + :param uri: The URI of the request. + :type uri: str + :param data: The body of the request. + :type data: dict, str or file + + :return: The response to the request. + :rtype: requests.Response + """ + return self._fetcher.get(uri, data=data, verify=self._combined_ca_bundle) + def _put(self, uri, data=None): """ Send a PUT request to C{uri} containing C{data}. @@ -780,7 +818,7 @@ class KeyManager(object): self._assert_supported_key_type(ktype) logger.info("Fetch key for %s from %s" % (address, uri)) - res = self._get(uri) + res = self._get_with_combined_ca_bundle(uri) if not res.ok: return defer.fail(KeyNotFound(uri)) diff --git a/keymanager/src/leap/keymanager/tests/__init__.py b/keymanager/src/leap/keymanager/tests/__init__.py index 7128d20d..6b647a4c 100644 --- a/keymanager/src/leap/keymanager/tests/__init__.py +++ b/keymanager/src/leap/keymanager/tests/__init__.py @@ -73,11 +73,12 @@ class KeyManagerWithSoledadTestCase(unittest.TestCase, BaseLeapTest): d = km._wrapper_map[OpenPGPKey].deferred_indexes d.addCallback(get_and_delete_keys) d.addCallback(lambda _: self.tearDownEnv()) + d.addCallback(lambda _: self._soledad.close()) return d - def _key_manager(self, user=ADDRESS, url='', token=None): + def _key_manager(self, user=ADDRESS, url='', token=None, ca_cert_path=None): return KeyManager(user, url, self._soledad, token=token, - gpgbinary=self.gpg_binary_path) + gpgbinary=self.gpg_binary_path, ca_cert_path=ca_cert_path) def _find_gpg(self): gpg_path = distutils.spawn.find_executable('gpg') diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index a12cac0e..984b037a 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -22,7 +22,9 @@ Tests for the Key Manager. from datetime import datetime -from mock import Mock +import tempfile +from leap.common import ca_bundle +from mock import Mock, MagicMock, patch from twisted.internet.defer import inlineCallbacks from twisted.trial import unittest @@ -50,6 +52,7 @@ from leap.keymanager.tests import ( NICKSERVER_URI = "http://leap.se/" +REMOTE_KEY_URL = "http://site.domain/key" class KeyManagerUtilTestCase(unittest.TestCase): @@ -287,7 +290,6 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): content = PUBLIC_KEY km._fetcher.get = Mock(return_value=Response()) - km.ca_cert_path = 'cacertpath' yield km.fetch_key(ADDRESS, "http://site.domain/key", OpenPGPKey) key = yield km.get_key(ADDRESS, OpenPGPKey) @@ -304,7 +306,6 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): content = "" km._fetcher.get = Mock(return_value=Response()) - km.ca_cert_path = 'cacertpath' d = km.fetch_key(ADDRESS, "http://site.domain/key", OpenPGPKey) return self.assertFailure(d, KeyNotFound) @@ -320,10 +321,69 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): content = PUBLIC_KEY km._fetcher.get = Mock(return_value=Response()) - km.ca_cert_path = 'cacertpath' d = km.fetch_key(ADDRESS_2, "http://site.domain/key", OpenPGPKey) return self.assertFailure(d, KeyAddressMismatch) + def _mock_get_response(self, km, body): + class Response(object): + ok = True + content = body + + mock = MagicMock(return_value=Response()) + km._fetcher.get = mock + + return mock + + @inlineCallbacks + def test_fetch_key_uses_ca_bundle_if_none_specified(self): + ca_cert_path = None + km = self._key_manager(ca_cert_path=ca_cert_path) + get_mock = self._mock_get_response(km, PUBLIC_KEY_OTHER) + + yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL, OpenPGPKey) + + get_mock.assert_called_once_with(REMOTE_KEY_URL, data=None, verify=ca_bundle.where()) + + @inlineCallbacks + def test_fetch_key_uses_default_ca_bundle_if_also_set_as_ca_cert_path(self): + ca_cert_path = ca_bundle.where() + km = self._key_manager(ca_cert_path=ca_cert_path) + get_mock = self._mock_get_response(km, PUBLIC_KEY_OTHER) + + yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL, OpenPGPKey) + + get_mock.assert_called_once_with(REMOTE_KEY_URL, data=None, verify=ca_bundle.where()) + + @inlineCallbacks + def test_fetch_uses_combined_ca_bundle_otherwise(self): + with tempfile.NamedTemporaryFile() as tmp_input, tempfile.NamedTemporaryFile() as tmp_output: + ca_content = 'some\ncontent\n' + ca_cert_path = tmp_input.name + self._dump_to_file(ca_cert_path, ca_content) + + with patch('leap.keymanager.tempfile.NamedTemporaryFile') as mock: + mock.return_value = tmp_output + km = self._key_manager(ca_cert_path=ca_cert_path) + get_mock = self._mock_get_response(km, PUBLIC_KEY_OTHER) + + yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL, OpenPGPKey) + + # assert that combined bundle file is passed to get call + get_mock.assert_called_once_with(REMOTE_KEY_URL, data=None, verify=tmp_output.name) + + # assert that files got appended + expected = self._slurp_file(ca_bundle.where()) + ca_content + self.assertEqual(expected, self._slurp_file(tmp_output.name)) + + def _dump_to_file(self, filename, content): + with open(filename, 'w') as out: + out.write(content) + + def _slurp_file(self, filename): + with open(filename) as f: + content = f.read() + return content + class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): -- cgit v1.2.3 From aa02a4603adf7cfe4a414ac83de9a0e82d6088a6 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 21 Sep 2015 19:59:11 +0200 Subject: [style] fix pep8 problems --- keymanager/src/leap/keymanager/__init__.py | 10 ++++++---- keymanager/src/leap/keymanager/tests/__init__.py | 6 ++++-- keymanager/src/leap/keymanager/tests/test_keymanager.py | 14 +++++++++----- 3 files changed, 19 insertions(+), 11 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 1220402a..5248cb0f 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -151,7 +151,8 @@ class KeyManager(object): elif self._ca_cert_path is None: return leap_ca_bundle - tmp_file = tempfile.NamedTemporaryFile(delete=True) # file is auto deleted when python process ends + # file is auto deleted when python process ends + tmp_file = tempfile.NamedTemporaryFile(delete=True) with open(tmp_file.name, 'w') as fout: fin = fileinput.input(files=(leap_ca_bundle, self._ca_cert_path)) @@ -201,8 +202,8 @@ class KeyManager(object): """ Send a GET request to C{uri} containing C{data}. - Instead of using the ca_cert provided on construction time, this version also uses - the default certificates shipped with leap.common + Instead of using the ca_cert provided on construction time, this + version also uses the default certificates shipped with leap.common :param uri: The URI of the request. :type uri: str @@ -212,7 +213,8 @@ class KeyManager(object): :return: The response to the request. :rtype: requests.Response """ - return self._fetcher.get(uri, data=data, verify=self._combined_ca_bundle) + return self._fetcher.get( + uri, data=data, verify=self._combined_ca_bundle) def _put(self, uri, data=None): """ diff --git a/keymanager/src/leap/keymanager/tests/__init__.py b/keymanager/src/leap/keymanager/tests/__init__.py index 6b647a4c..9b95e1ac 100644 --- a/keymanager/src/leap/keymanager/tests/__init__.py +++ b/keymanager/src/leap/keymanager/tests/__init__.py @@ -76,9 +76,11 @@ class KeyManagerWithSoledadTestCase(unittest.TestCase, BaseLeapTest): d.addCallback(lambda _: self._soledad.close()) return d - def _key_manager(self, user=ADDRESS, url='', token=None, ca_cert_path=None): + def _key_manager(self, user=ADDRESS, url='', token=None, + ca_cert_path=None): return KeyManager(user, url, self._soledad, token=token, - gpgbinary=self.gpg_binary_path, ca_cert_path=ca_cert_path) + gpgbinary=self.gpg_binary_path, + ca_cert_path=ca_cert_path) def _find_gpg(self): gpg_path = distutils.spawn.find_executable('gpg') diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 984b037a..7c002926 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -342,21 +342,24 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL, OpenPGPKey) - get_mock.assert_called_once_with(REMOTE_KEY_URL, data=None, verify=ca_bundle.where()) + get_mock.assert_called_once_with(REMOTE_KEY_URL, data=None, + verify=ca_bundle.where()) @inlineCallbacks - def test_fetch_key_uses_default_ca_bundle_if_also_set_as_ca_cert_path(self): + def test_fetch_key_uses_default_ca_bundle_if_also_set_as_ca_cert(self): ca_cert_path = ca_bundle.where() km = self._key_manager(ca_cert_path=ca_cert_path) get_mock = self._mock_get_response(km, PUBLIC_KEY_OTHER) yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL, OpenPGPKey) - get_mock.assert_called_once_with(REMOTE_KEY_URL, data=None, verify=ca_bundle.where()) + get_mock.assert_called_once_with(REMOTE_KEY_URL, data=None, + verify=ca_bundle.where()) @inlineCallbacks def test_fetch_uses_combined_ca_bundle_otherwise(self): - with tempfile.NamedTemporaryFile() as tmp_input, tempfile.NamedTemporaryFile() as tmp_output: + with tempfile.NamedTemporaryFile() as tmp_input, \ + tempfile.NamedTemporaryFile() as tmp_output: ca_content = 'some\ncontent\n' ca_cert_path = tmp_input.name self._dump_to_file(ca_cert_path, ca_content) @@ -369,7 +372,8 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL, OpenPGPKey) # assert that combined bundle file is passed to get call - get_mock.assert_called_once_with(REMOTE_KEY_URL, data=None, verify=tmp_output.name) + get_mock.assert_called_once_with(REMOTE_KEY_URL, data=None, + verify=tmp_output.name) # assert that files got appended expected = self._slurp_file(ca_bundle.where()) + ca_content -- cgit v1.2.3 From d5564316c37c4402b3eabb370b4bf27da3c75463 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 21 Sep 2015 23:15:33 +0200 Subject: [feat] more verbosity in get_key wrong address log --- keymanager/src/leap/keymanager/openpgp.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 794a0ec2..b8b47d02 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -321,7 +321,9 @@ class OpenPGPScheme(EncryptionScheme): raise errors.KeyNotFound(address) leap_assert( address in doc.content[KEY_ADDRESS_KEY], - 'Wrong address in key data.') + 'Wrong address in key %s. Expected %s, found %s.' + % (doc.content[KEY_ID_KEY], address, + doc.content[KEY_ADDRESS_KEY])) key = build_key_from_dict(OpenPGPKey, doc.content) key._gpgbinary = self._gpgbinary return key -- cgit v1.2.3 From f94e3aff14295821d28be0dbf3f4b40df980271c Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Tue, 22 Sep 2015 16:18:36 +0200 Subject: [bug] catch request exceptions On fetch_key we were not catching the request exceptions, now they are returned as failure in the deferred as it should. - Related: #7410 --- keymanager/src/leap/keymanager/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 5248cb0f..e378c91f 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -820,7 +820,11 @@ class KeyManager(object): self._assert_supported_key_type(ktype) logger.info("Fetch key for %s from %s" % (address, uri)) - res = self._get_with_combined_ca_bundle(uri) + try: + res = self._get_with_combined_ca_bundle(uri) + except Exception as e: + logger.warning("There was a problem fetching key: %s" % (e,)) + return defer.fail(KeyNotFound(uri)) if not res.ok: return defer.fail(KeyNotFound(uri)) -- cgit v1.2.3 From b1ce91a02a08103eff65b16afb098f499c00f285 Mon Sep 17 00:00:00 2001 From: Folker Bernitt Date: Tue, 22 Sep 2015 17:43:29 +0200 Subject: [bug] treat empty string ca_cert_path as None Fixup for 9546348c36. This problem only occurs in test setups where '' is passed to ca_cert_path. --- keymanager/src/leap/keymanager/__init__.py | 2 +- keymanager/src/leap/keymanager/tests/test_keymanager.py | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index e378c91f..cf099bb1 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -148,7 +148,7 @@ class KeyManager(object): if self._ca_cert_path == leap_ca_bundle: return self._ca_cert_path # don't merge file with itself - elif self._ca_cert_path is None: + elif not self._ca_cert_path: return leap_ca_bundle # file is auto deleted when python process ends diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 7c002926..3b4aa0f6 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -346,7 +346,18 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): verify=ca_bundle.where()) @inlineCallbacks - def test_fetch_key_uses_default_ca_bundle_if_also_set_as_ca_cert(self): + def test_fetch_key_uses_ca_bundle_if_empty_string_specified(self): + ca_cert_path = '' + km = self._key_manager(ca_cert_path=ca_cert_path) + get_mock = self._mock_get_response(km, PUBLIC_KEY_OTHER) + + yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL, OpenPGPKey) + + get_mock.assert_called_once_with(REMOTE_KEY_URL, data=None, + verify=ca_bundle.where()) + + @inlineCallbacks + def test_fetch_key_uses_default_ca_bundle_if_also_set_as_ca_cert_path(self): ca_cert_path = ca_bundle.where() km = self._key_manager(ca_cert_path=ca_cert_path) get_mock = self._mock_get_response(km, PUBLIC_KEY_OTHER) -- cgit v1.2.3 From 0847c9d6eecd8193b25e0fd17f0c1f16187c4115 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Thu, 24 Sep 2015 01:15:11 +0200 Subject: [bug] don't repush a public key with different address During decryption the signing public key was getting repush with a different address as part of the verify usage flagging. - Resolves: https://github.com/pixelated/pixelated-user-agent/issues/466 - Related: #7420 --- keymanager/src/leap/keymanager/__init__.py | 20 ++++++++++------- .../src/leap/keymanager/tests/test_validation.py | 26 +++++++++++++++++++++- 2 files changed, 37 insertions(+), 9 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index cf099bb1..22fb725b 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -590,10 +590,12 @@ class KeyManager(object): if pubkey is None: signature = KeyNotFound(verify) elif signed: - pubkey.sign_used = True - d = self._wrapper_map[ktype].put_key(pubkey, address) - d.addCallback(lambda _: (decrypted, pubkey)) - return d + signature = pubkey + if not pubkey.sign_used: + pubkey.sign_used = True + d = self._wrapper_map[ktype].put_key(pubkey, verify) + d.addCallback(lambda _: (decrypted, signature)) + return d else: signature = InvalidSignature( 'Failed to verify signature with key %s' % @@ -685,10 +687,12 @@ class KeyManager(object): signed = self._wrapper_map[ktype].verify( data, pubkey, detached_sig=detached_sig) if signed: - pubkey.sign_used = True - d = self._wrapper_map[ktype].put_key(pubkey, address) - d.addCallback(lambda _: pubkey) - return d + if not pubkey.sign_used: + pubkey.sign_used = True + d = self._wrapper_map[ktype].put_key(pubkey, address) + d.addCallback(lambda _: pubkey) + return d + return pubkey else: raise InvalidSignature( 'Failed to verify signature with key %s' % diff --git a/keymanager/src/leap/keymanager/tests/test_validation.py b/keymanager/src/leap/keymanager/tests/test_validation.py index ddf1170d..bcf41c43 100644 --- a/keymanager/src/leap/keymanager/tests/test_validation.py +++ b/keymanager/src/leap/keymanager/tests/test_validation.py @@ -30,6 +30,9 @@ from leap.keymanager.tests import ( KeyManagerWithSoledadTestCase, ADDRESS, PUBLIC_KEY, + ADDRESS_2, + PUBLIC_KEY_2, + PRIVATE_KEY_2, KEY_FINGERPRINT ) from leap.keymanager.validation import ValidationLevels @@ -101,7 +104,7 @@ class ValidationLevelsTestCase(KeyManagerWithSoledadTestCase): self.assertEqual(key.fingerprint, UNRELATED_FINGERPRINT) @inlineCallbacks - def test_used(self): + def test_used_with_verify(self): TEXT = "some text" km = self._key_manager() @@ -118,6 +121,27 @@ class ValidationLevelsTestCase(KeyManagerWithSoledadTestCase): validation=ValidationLevels.Provider_Endorsement) yield self.assertFailure(d, KeyNotValidUpgrade) + @inlineCallbacks + def test_used_with_decrypt(self): + TEXT = "some text" + + km = self._key_manager() + yield km.put_raw_key(UNEXPIRED_KEY, OpenPGPKey, ADDRESS) + yield km.put_raw_key(PRIVATE_KEY_2, OpenPGPKey, ADDRESS_2) + yield km.encrypt(TEXT, ADDRESS, OpenPGPKey) + + km2 = self._key_manager() + yield km2.put_raw_key(UNEXPIRED_PRIVATE, OpenPGPKey, ADDRESS) + yield km2.put_raw_key(PUBLIC_KEY_2, OpenPGPKey, ADDRESS_2) + encrypted = yield km2.encrypt(TEXT, ADDRESS_2, OpenPGPKey, + sign=ADDRESS) + + yield km.decrypt(encrypted, ADDRESS_2, OpenPGPKey, verify=ADDRESS) + d = km.put_raw_key( + UNRELATED_KEY, OpenPGPKey, ADDRESS, + validation=ValidationLevels.Provider_Endorsement) + yield self.assertFailure(d, KeyNotValidUpgrade) + @inlineCallbacks def test_signed_key(self): km = self._key_manager() -- cgit v1.2.3 From fe78108a4a5cf67af9aec374d11596bf5f5e8d3f Mon Sep 17 00:00:00 2001 From: Folker Bernitt Date: Thu, 24 Sep 2015 11:46:14 +0200 Subject: [tests] Add regression tests for sign_used Fails if wrong address is passed to the put_key method, or wrong key is marked as sign_used. - Related: #7420 --- .../src/leap/keymanager/tests/test_keymanager.py | 33 ++++++++++++++++++++++ 1 file changed, 33 insertions(+) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 3b4aa0f6..7b90ae15 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -399,6 +399,39 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): content = f.read() return content + @inlineCallbacks + def test_decrypt_updates_sign_used_for_signer(self): + # given + km = self._key_manager() + yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) + yield km._wrapper_map[OpenPGPKey].put_ascii_key( + PRIVATE_KEY_2, ADDRESS_2) + encdata = yield km.encrypt('data', ADDRESS, OpenPGPKey, + sign=ADDRESS_2, fetch_remote=False) + yield km.decrypt(encdata, ADDRESS, OpenPGPKey, verify=ADDRESS_2, fetch_remote=False) + + # when + key = yield km.get_key(ADDRESS_2, OpenPGPKey, fetch_remote=False) + + # then + self.assertEqual(True, key.sign_used) + + @inlineCallbacks + def test_decrypt_does_not_update_sign_used_for_recipient(self): + # given + km = self._key_manager() + yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) + yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY_2, ADDRESS_2) + encdata = yield km.encrypt('data', ADDRESS, OpenPGPKey, + sign=ADDRESS_2, fetch_remote=False) + yield km.decrypt(encdata, ADDRESS, OpenPGPKey, verify=ADDRESS_2, fetch_remote=False) + + # when + key = yield km.get_key(ADDRESS, OpenPGPKey, private=False, fetch_remote=False) + + # then + self.assertEqual(False, key.sign_used) + class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): -- cgit v1.2.3 From ff85233fdf560c344a473b72b41d9cf078cddba3 Mon Sep 17 00:00:00 2001 From: Folker Bernitt Date: Mon, 21 Sep 2015 15:59:53 +0200 Subject: [bug] keep combined file longer in scope In previous commit 9546348c, the combined bundle ca was not long enough in scope and was therefore deleted when it actually was used. Adopted test to check whether file is deleted. --- keymanager/src/leap/keymanager/__init__.py | 16 ++++++++++++++-- keymanager/src/leap/keymanager/tests/test_keymanager.py | 8 +++++--- 2 files changed, 19 insertions(+), 5 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 22fb725b..f00e049a 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -19,6 +19,7 @@ Key Manager is a Nicknym agent for LEAP client. """ # let's do a little sanity check to see if we're using the wrong gnupg import fileinput +import os import sys import tempfile from leap.common import ca_bundle @@ -139,6 +140,18 @@ class KeyManager(object): self._fetcher = requests self._combined_ca_bundle = self._create_combined_bundle_file() + # + # destructor + # + + def __del__(self): + try: + created_tmp_combined_ca_bundle = self._combined_ca_bundle not in [ca_bundle.where(), self._ca_cert_path] + if created_tmp_combined_ca_bundle: + os.remove(self._combined_ca_bundle) + except OSError: + pass + # # utilities # @@ -151,8 +164,7 @@ class KeyManager(object): elif not self._ca_cert_path: return leap_ca_bundle - # file is auto deleted when python process ends - tmp_file = tempfile.NamedTemporaryFile(delete=True) + tmp_file = tempfile.NamedTemporaryFile(delete=False) # delete when keymanager expires with open(tmp_file.name, 'w') as fout: fin = fileinput.input(files=(leap_ca_bundle, self._ca_cert_path)) diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 7b90ae15..b2722b2e 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -20,7 +20,7 @@ Tests for the Key Manager. """ - +from os import path from datetime import datetime import tempfile from leap.common import ca_bundle @@ -369,8 +369,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): @inlineCallbacks def test_fetch_uses_combined_ca_bundle_otherwise(self): - with tempfile.NamedTemporaryFile() as tmp_input, \ - tempfile.NamedTemporaryFile() as tmp_output: + with tempfile.NamedTemporaryFile() as tmp_input, tempfile.NamedTemporaryFile(delete=False) as tmp_output: ca_content = 'some\ncontent\n' ca_cert_path = tmp_input.name self._dump_to_file(ca_cert_path, ca_content) @@ -390,6 +389,9 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): expected = self._slurp_file(ca_bundle.where()) + ca_content self.assertEqual(expected, self._slurp_file(tmp_output.name)) + del km # force km out of scope + self.assertFalse(path.exists(tmp_output.name)) + def _dump_to_file(self, filename, content): with open(filename, 'w') as out: out.write(content) -- cgit v1.2.3 From 44c30bb893ad36673915243a96c1e3ae709dab82 Mon Sep 17 00:00:00 2001 From: Folker Bernitt Date: Thu, 24 Sep 2015 14:04:43 +0200 Subject: [style] fix pep8 warnings --- keymanager/src/leap/keymanager/__init__.py | 7 +++++-- keymanager/src/leap/keymanager/tests/test_keymanager.py | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index f00e049a..01f3b6eb 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -22,7 +22,9 @@ import fileinput import os import sys import tempfile + from leap.common import ca_bundle + from ._version import get_versions try: @@ -146,7 +148,8 @@ class KeyManager(object): def __del__(self): try: - created_tmp_combined_ca_bundle = self._combined_ca_bundle not in [ca_bundle.where(), self._ca_cert_path] + created_tmp_combined_ca_bundle = self._combined_ca_bundle not in \ + [ca_bundle.where(), self._ca_cert_path] if created_tmp_combined_ca_bundle: os.remove(self._combined_ca_bundle) except OSError: @@ -164,7 +167,7 @@ class KeyManager(object): elif not self._ca_cert_path: return leap_ca_bundle - tmp_file = tempfile.NamedTemporaryFile(delete=False) # delete when keymanager expires + tmp_file = tempfile.NamedTemporaryFile(delete=False) with open(tmp_file.name, 'w') as fout: fin = fileinput.input(files=(leap_ca_bundle, self._ca_cert_path)) diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index b2722b2e..8d4c5dad 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -357,7 +357,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): verify=ca_bundle.where()) @inlineCallbacks - def test_fetch_key_uses_default_ca_bundle_if_also_set_as_ca_cert_path(self): + def test_fetch_key_use_default_ca_bundle_if_set_as_ca_cert_path(self): ca_cert_path = ca_bundle.where() km = self._key_manager(ca_cert_path=ca_cert_path) get_mock = self._mock_get_response(km, PUBLIC_KEY_OTHER) @@ -369,7 +369,8 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): @inlineCallbacks def test_fetch_uses_combined_ca_bundle_otherwise(self): - with tempfile.NamedTemporaryFile() as tmp_input, tempfile.NamedTemporaryFile(delete=False) as tmp_output: + with tempfile.NamedTemporaryFile() as tmp_input, \ + tempfile.NamedTemporaryFile(delete=False) as tmp_output: ca_content = 'some\ncontent\n' ca_cert_path = tmp_input.name self._dump_to_file(ca_cert_path, ca_content) -- cgit v1.2.3 From 1b8847b244586dec237949d477c474bc77c8264c Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 24 Sep 2015 12:11:37 -0400 Subject: [style] more pep8 fixes --- keymanager/src/leap/keymanager/tests/test_keymanager.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 8d4c5dad..856d6da9 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -411,7 +411,8 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): PRIVATE_KEY_2, ADDRESS_2) encdata = yield km.encrypt('data', ADDRESS, OpenPGPKey, sign=ADDRESS_2, fetch_remote=False) - yield km.decrypt(encdata, ADDRESS, OpenPGPKey, verify=ADDRESS_2, fetch_remote=False) + yield km.decrypt( + encdata, ADDRESS, OpenPGPKey, verify=ADDRESS_2, fetch_remote=False) # when key = yield km.get_key(ADDRESS_2, OpenPGPKey, fetch_remote=False) @@ -423,14 +424,18 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): def test_decrypt_does_not_update_sign_used_for_recipient(self): # given km = self._key_manager() - yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) - yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY_2, ADDRESS_2) + yield km._wrapper_map[OpenPGPKey].put_ascii_key( + PRIVATE_KEY, ADDRESS) + yield km._wrapper_map[OpenPGPKey].put_ascii_key( + PRIVATE_KEY_2, ADDRESS_2) encdata = yield km.encrypt('data', ADDRESS, OpenPGPKey, sign=ADDRESS_2, fetch_remote=False) - yield km.decrypt(encdata, ADDRESS, OpenPGPKey, verify=ADDRESS_2, fetch_remote=False) + yield km.decrypt( + encdata, ADDRESS, OpenPGPKey, verify=ADDRESS_2, fetch_remote=False) # when - key = yield km.get_key(ADDRESS, OpenPGPKey, private=False, fetch_remote=False) + key = yield km.get_key( + ADDRESS, OpenPGPKey, private=False, fetch_remote=False) # then self.assertEqual(False, key.sign_used) -- cgit v1.2.3 From f3158ed7a792b7c02b2f89fd90aefa708882557f Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 10 Sep 2015 10:36:53 -0400 Subject: [refactor] refactor key parsing so that it can be tested without needing to instantiate the whole OpenPGPScheme object, that receives a soledad instance. --- keymanager/src/leap/keymanager/openpgp.py | 130 ++++++++++++++---------------- 1 file changed, 59 insertions(+), 71 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index b8b47d02..069a78e3 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # openpgp.py -# Copyright (C) 2013 LEAP +# Copyright (C) 2013-2015 LEAP # # 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 @@ -251,7 +251,7 @@ class OpenPGPScheme(EncryptionScheme): leap_assert(is_address(address), 'Not an user address: %s' % address) def _gen_key(_): - with self._temporary_gpgwrapper() as gpg: + with TempGPGWrapper(gpgbinary=self._gpgbinary) as gpg: # TODO: inspect result, or use decorator params = gpg.gen_key_input( key_type='RSA', @@ -348,37 +348,25 @@ class OpenPGPScheme(EncryptionScheme): # TODO: add more checks for correct key data. leap_assert(key_data is not None, 'Data does not represent a key.') - with self._temporary_gpgwrapper() as gpg: - # TODO: inspect result, or use decorator - gpg.import_keys(key_data) - privkey = None - pubkey = None + priv_info, privkey = process_ascii_key( + key_data, self._gpgbinary, secret=True) + pub_info, pubkey = process_ascii_key( + key_data, self._gpgbinary, secret=False) - try: - privkey = gpg.list_keys(secret=True).pop() - except IndexError: - pass - try: - pubkey = gpg.list_keys(secret=False).pop() # unitary keyring - except IndexError: - return (None, None) - - openpgp_privkey = None - if privkey is not None: - # build private key - openpgp_privkey = self._build_key_from_gpg( - privkey, - gpg.export_keys(privkey['fingerprint'], secret=True)) - leap_check(pubkey['fingerprint'] == privkey['fingerprint'], - 'Fingerprints for public and private key differ.', - errors.KeyFingerprintMismatch) - - # build public key - openpgp_pubkey = self._build_key_from_gpg( - pubkey, - gpg.export_keys(pubkey['fingerprint'], secret=False)) - - return (openpgp_pubkey, openpgp_privkey) + if not pubkey: + return (None, None) + + openpgp_privkey = None + if privkey: + # build private key + openpgp_privkey = self._build_key_from_gpg(priv_info, privkey) + leap_check(pub_info['fingerprint'] == priv_info['fingerprint'], + 'Fingerprints for public and private key differ.', + errors.KeyFingerprintMismatch) + # build public key + openpgp_pubkey = self._build_key_from_gpg(pub_info, pubkey) + + return (openpgp_pubkey, openpgp_privkey) def put_ascii_key(self, key_data, address): """ @@ -439,7 +427,7 @@ class OpenPGPScheme(EncryptionScheme): oldkey = build_key_from_dict(OpenPGPKey, doc.content) if key.fingerprint == oldkey.fingerprint: # in case of an update of the key merge them with gnupg - with self._temporary_gpgwrapper() as gpg: + with TempGPGWrapper(gpgbinary=self._gpgbinary) as gpg: gpg.import_keys(oldkey.key_data) gpg.import_keys(key.key_data) gpgkey = gpg.list_keys(secret=key.private).pop() @@ -577,24 +565,7 @@ class OpenPGPScheme(EncryptionScheme): :return: An instance of the key. :rtype: OpenPGPKey """ - expiry_date = None - if key['expires']: - expiry_date = datetime.fromtimestamp(int(key['expires'])) - address = [] - for uid in key['uids']: - address.append(_parse_address(uid)) - - return OpenPGPKey( - address, - gpgbinary=self._gpgbinary, - key_id=key['keyid'], - fingerprint=key['fingerprint'], - key_data=key_data, - private=True if key['type'] == 'sec' else False, - length=int(key['length']), - expiry_date=expiry_date, - refreshed_at=datetime.now(), - ) + return build_gpg_key(key, key_data, self._gpgbinary) def delete_key(self, key): """ @@ -654,21 +625,6 @@ class OpenPGPScheme(EncryptionScheme): # Data encryption, decryption, signing and verifying # - def _temporary_gpgwrapper(self, keys=None): - """ - Return a gpg wrapper that implements the context manager protocol and - contains C{keys}. - - :param keys: keys to conform the keyring. - :type key: list(OpenPGPKey) - - :return: a TempGPGWrapper instance - :rtype: TempGPGWrapper - """ - # TODO do here checks on key_data - return TempGPGWrapper( - keys=keys, gpgbinary=self._gpgbinary) - @staticmethod def _assert_gpg_result_ok(result): """ @@ -713,7 +669,7 @@ class OpenPGPScheme(EncryptionScheme): leap_assert_type(sign, OpenPGPKey) leap_assert(sign.private is True) keys.append(sign) - with self._temporary_gpgwrapper(keys) as gpg: + with TempGPGWrapper(keys, self._gpgbinary) as gpg: result = gpg.encrypt( data, pubkey.fingerprint, default_key=sign.key_id if sign else None, @@ -755,7 +711,7 @@ class OpenPGPScheme(EncryptionScheme): leap_assert_type(verify, OpenPGPKey) leap_assert(verify.private is False) keys.append(verify) - with self._temporary_gpgwrapper(keys) as gpg: + with TempGPGWrapper(keys, self._gpgbinary) as gpg: try: result = gpg.decrypt( data, passphrase=passphrase, always_trust=True) @@ -783,7 +739,7 @@ class OpenPGPScheme(EncryptionScheme): :return: Whether C{data} was encrypted using this wrapper. :rtype: bool """ - with self._temporary_gpgwrapper() as gpg: + with TempGPGWrapper(gpgbinary=self._gpgbinary) as gpg: gpgutil = GPGUtilities(gpg) return gpgutil.is_encrypted_asym(data) @@ -814,7 +770,7 @@ class OpenPGPScheme(EncryptionScheme): # result.fingerprint - contains the fingerprint of the key used to # sign. - with self._temporary_gpgwrapper(privkey) as gpg: + with TempGPGWrapper(privkey, self._gpgbinary) as gpg: result = gpg.sign(data, default_key=privkey.key_id, digest_algo=digest_algo, clearsign=clearsign, detach=detach, binary=binary) @@ -849,7 +805,7 @@ class OpenPGPScheme(EncryptionScheme): """ leap_assert_type(pubkey, OpenPGPKey) leap_assert(pubkey.private is False) - with self._temporary_gpgwrapper(pubkey) as gpg: + with TempGPGWrapper(pubkey, self._gpgbinary) as gpg: result = None if detached_sig is None: result = gpg.verify(data) @@ -867,3 +823,35 @@ class OpenPGPScheme(EncryptionScheme): rfprint = result.fingerprint kfprint = gpgpubkey['fingerprint'] return valid and rfprint == kfprint + + +def process_ascii_key(key_data, gpgbinary, secret=False): + with TempGPGWrapper(gpgbinary=gpgbinary) as gpg: + try: + gpg.import_keys(key_data) + info = gpg.list_keys(secret=secret).pop() + key = gpg.export_keys(info['fingerprint'], secret=secret) + except IndexError: + info = {} + key = None + return info, key + + +def build_gpg_key(key_info, key_data, gpgbinary=None): + expiry_date = None + if key_info['expires']: + expiry_date = datetime.fromtimestamp(int(key_info['expires'])) + address = [] + for uid in key_info['uids']: + address.append(_parse_address(uid)) + + return OpenPGPKey( + address, + gpgbinary=gpgbinary, + key_id=key_info['keyid'], + fingerprint=key_info['fingerprint'], + key_data=key_data, + private=True if key_info['type'] == 'sec' else False, + length=int(key_info['length']), + expiry_date=expiry_date, + refreshed_at=datetime.now()) -- cgit v1.2.3 From 80f22a70fb12d7fd051aea38b41d5dde104d85b2 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 24 Sep 2015 12:44:28 -0400 Subject: [refactor] improve readability Improve readability of operations on generic keys, by assigning the class matching the type of key (_wrapper_map[ktype]) at the beginning of each block. in the future, we could pass the type of key (only PGP keys being used at the moment) on initialization of the Keymanager, so we don't have to pass the ktype on each method call. --- keymanager/src/leap/keymanager/__init__.py | 48 +++++++++++++++++++----------- 1 file changed, 30 insertions(+), 18 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 01f3b6eb..4826ba70 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -376,6 +376,8 @@ class KeyManager(object): leap_assert( ktype in self._wrapper_map, 'Unkown key type: %s.' % str(ktype)) + _keys = self._wrapper_map[ktype] + emit_async(catalog.KEYMANAGER_LOOKING_FOR_KEY, address) def key_found(key): @@ -396,13 +398,12 @@ class KeyManager(object): emit_async(catalog.KEYMANAGER_LOOKING_FOR_KEY, address) d = self._fetch_keys_from_server(address) d.addCallback( - lambda _: - self._wrapper_map[ktype].get_key(address, private=False)) + lambda _: _keys.get_key(address, private=False)) d.addCallback(key_found) return d # return key if it exists in local database - d = self._wrapper_map[ktype].get_key(address, private=private) + d = _keys.get_key(address, private=private) d.addCallbacks(key_found, key_not_found) return d @@ -448,6 +449,7 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ self._assert_supported_key_type(ktype) + _keys = self._wrapper_map[ktype] def signal_finished(key): emit_async( @@ -455,7 +457,8 @@ class KeyManager(object): return key emit_async(catalog.KEYMANAGER_STARTED_KEY_GENERATION, self._address) - d = self._wrapper_map[ktype].gen_key(self._address) + + d = _keys.gen_key(self._address) d.addCallback(signal_finished) return d @@ -545,14 +548,15 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ self._assert_supported_key_type(ktype) + _keys = self._wrapper_map[ktype] def encrypt(keys): pubkey, signkey = keys - encrypted = self._wrapper_map[ktype].encrypt( + encrypted = _keys.encrypt( data, pubkey, passphrase, sign=signkey, cipher_algo=cipher_algo) pubkey.encr_used = True - d = self._wrapper_map[ktype].put_key(pubkey, address) + d = _keys.put_key(pubkey, address) d.addCallback(lambda _: encrypted) return d @@ -597,10 +601,11 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ self._assert_supported_key_type(ktype) + _keys = self._wrapper_map[ktype] def decrypt(keys): pubkey, privkey = keys - decrypted, signed = self._wrapper_map[ktype].decrypt( + decrypted, signed = _keys.decrypt( data, privkey, passphrase=passphrase, verify=pubkey) if pubkey is None: signature = KeyNotFound(verify) @@ -608,7 +613,7 @@ class KeyManager(object): signature = pubkey if not pubkey.sign_used: pubkey.sign_used = True - d = self._wrapper_map[ktype].put_key(pubkey, verify) + d = _keys.put_key(pubkey, verify) d.addCallback(lambda _: (decrypted, signature)) return d else: @@ -659,9 +664,10 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ self._assert_supported_key_type(ktype) + _keys = self._wrapper_map[ktype] def sign(privkey): - return self._wrapper_map[ktype].sign( + return _keys.sign( data, privkey, digest_algo=digest_algo, clearsign=clearsign, detach=detach, binary=binary) @@ -697,14 +703,15 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ self._assert_supported_key_type(ktype) + _keys = self._wrapper_map[ktype] def verify(pubkey): - signed = self._wrapper_map[ktype].verify( + signed = _keys.verify( data, pubkey, detached_sig=detached_sig) if signed: if not pubkey.sign_used: pubkey.sign_used = True - d = self._wrapper_map[ktype].put_key(pubkey, address) + d = keys.put_key(pubkey, address) d.addCallback(lambda _: pubkey) return d return pubkey @@ -732,7 +739,8 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ self._assert_supported_key_type(type(key)) - return self._wrapper_map[type(key)].delete_key(key) + _keys = self._wrapper_map[type(key)] + return _keys.delete_key(key) def put_key(self, key, address): """ @@ -752,7 +760,9 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ - self._assert_supported_key_type(type(key)) + ktype = type(key) + self._assert_supported_key_type(ktype) + _keys = self._wrapper_map[ktype] if address not in key.address: return defer.fail( @@ -767,14 +777,13 @@ class KeyManager(object): def check_upgrade(old_key): if key.private or can_upgrade(key, old_key): - return self._wrapper_map[type(key)].put_key(key, address) + return _keys.put_key(key, address) else: raise KeyNotValidUpgrade( "Key %s can not be upgraded by new key %s" % (old_key.key_id, key.key_id)) - d = self._wrapper_map[type(key)].get_key(address, - private=key.private) + d = _keys.get_key(address, private=key.private) d.addErrback(old_key_not_found) d.addCallback(check_upgrade) return d @@ -804,7 +813,9 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ self._assert_supported_key_type(ktype) - pubkey, privkey = self._wrapper_map[ktype].parse_ascii_key(key) + _keys = self._wrapper_map[ktype] + + pubkey, privkey = _keys.parse_ascii_key(key) pubkey.validation = validation d = self.put_key(pubkey, address) if privkey is not None: @@ -837,6 +848,7 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ self._assert_supported_key_type(ktype) + _keys = self._wrapper_map[ktype] logger.info("Fetch key for %s from %s" % (address, uri)) try: @@ -848,7 +860,7 @@ class KeyManager(object): return defer.fail(KeyNotFound(uri)) # XXX parse binary keys - pubkey, _ = self._wrapper_map[ktype].parse_ascii_key(res.content) + pubkey, _ = _keys.parse_ascii_key(res.content) if pubkey is None: return defer.fail(KeyNotFound(uri)) -- cgit v1.2.3 From ea753e1ce7b0928abd6c1c71f5ce51cc455badd6 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 28 Sep 2015 19:06:20 +0200 Subject: [bug] fix verify keys usage The latests refactor missed one line. --- keymanager/src/leap/keymanager/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 4826ba70..c7886e0c 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -711,7 +711,7 @@ class KeyManager(object): if signed: if not pubkey.sign_used: pubkey.sign_used = True - d = keys.put_key(pubkey, address) + d = _keys.put_key(pubkey, address) d.addCallback(lambda _: pubkey) return d return pubkey -- cgit v1.2.3 From 87910335a068603ac9444b6a048d9a39e1897f0b Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Tue, 29 Sep 2015 16:36:20 +0200 Subject: [feat] self-repair the keyring if keys get duplicated In some cases in the past keys got stored twice in different documents. Hopefully this issue is solved now, this tries to self-repair the keyring if encounters that. This is not really solving the problem, if it keeps happening we need to investigate the source. - Resolves: #7498 --- keymanager/src/leap/keymanager/openpgp.py | 143 ++++++++++++++------- keymanager/src/leap/keymanager/tests/__init__.py | 7 + .../src/leap/keymanager/tests/test_openpgp.py | 104 ++++++++++++++- 3 files changed, 203 insertions(+), 51 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 069a78e3..d6481377 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -22,6 +22,7 @@ import os import re import shutil import tempfile +import traceback import io @@ -41,6 +42,8 @@ from leap.keymanager.keys import ( TYPE_ADDRESS_PRIVATE_INDEX, KEY_ADDRESS_KEY, KEY_ID_KEY, + KEY_FINGERPRINT_KEY, + KEY_REFRESHED_AT_KEY, KEYMANAGER_ACTIVE_TYPE, ) @@ -422,41 +425,42 @@ class OpenPGPScheme(EncryptionScheme): :rtype: Deferred """ def check_and_put(docs, key): - if len(docs) == 1: - doc = docs.pop() - oldkey = build_key_from_dict(OpenPGPKey, doc.content) - if key.fingerprint == oldkey.fingerprint: - # in case of an update of the key merge them with gnupg - with TempGPGWrapper(gpgbinary=self._gpgbinary) as gpg: - gpg.import_keys(oldkey.key_data) - gpg.import_keys(key.key_data) - gpgkey = gpg.list_keys(secret=key.private).pop() - mergedkey = self._build_key_from_gpg( - gpgkey, - gpg.export_keys(gpgkey['fingerprint'], - secret=key.private)) - mergedkey.validation = max( - [key.validation, oldkey.validation]) - mergedkey.last_audited_at = oldkey.last_audited_at - mergedkey.refreshed_at = key.refreshed_at - mergedkey.encr_used = key.encr_used or oldkey.encr_used - mergedkey.sign_used = key.sign_used or oldkey.sign_used - doc.set_json(mergedkey.get_json()) - d = self._soledad.put_doc(doc) - else: - logger.critical( - "Can't put a key whith the same key_id and different " - "fingerprint: %s, %s" - % (key.fingerprint, oldkey.fingerprint)) - d = defer.fail( - errors.KeyFingerprintMismatch(key.fingerprint)) + deferred_repair = defer.succeed(None) + if len(docs) == 0: + return self._soledad.create_doc_from_json(key.get_json()) elif len(docs) > 1: + deferred_repair = self._repair_key_docs(docs, key.key_id) + + doc = docs[0] + oldkey = build_key_from_dict(OpenPGPKey, doc.content) + if key.fingerprint != oldkey.fingerprint: logger.critical( - "There is more than one key with the same key_id %s" - % (key.key_id,)) - d = defer.fail(errors.KeyAttributesDiffer(key.key_id)) - else: - d = self._soledad.create_doc_from_json(key.get_json()) + "Can't put a key whith the same key_id and different " + "fingerprint: %s, %s" + % (key.fingerprint, oldkey.fingerprint)) + return defer.fail( + errors.KeyFingerprintMismatch(key.fingerprint)) + + # in case of an update of the key merge them with gnupg + with TempGPGWrapper(gpgbinary=self._gpgbinary) as gpg: + gpg.import_keys(oldkey.key_data) + gpg.import_keys(key.key_data) + gpgkey = gpg.list_keys(secret=key.private).pop() + mergedkey = self._build_key_from_gpg( + gpgkey, + gpg.export_keys(gpgkey['fingerprint'], + secret=key.private)) + mergedkey.validation = max( + [key.validation, oldkey.validation]) + mergedkey.last_audited_at = oldkey.last_audited_at + mergedkey.refreshed_at = key.refreshed_at + mergedkey.encr_used = key.encr_used or oldkey.encr_used + mergedkey.sign_used = key.sign_used or oldkey.sign_used + doc.set_json(mergedkey.get_json()) + deferred_put = self._soledad.put_doc(doc) + + d = defer.gatherResults([deferred_put, deferred_repair]) + d.addCallback(lambda res: res[0]) return d d = self._soledad.get_from_index( @@ -533,14 +537,21 @@ class OpenPGPScheme(EncryptionScheme): self.KEY_TYPE, key_id, '1' if private else '0') - d.addCallback(get_doc, key_id) + d.addCallback(get_doc, key_id, activedoc) return d - def get_doc(doclist, key_id): - leap_assert( - len(doclist) is 1, - 'There is %d keys for id %s!' % (len(doclist), key_id)) - return doclist.pop() + def get_doc(doclist, key_id, activedoc): + if len(doclist) == 0: + logger.warning('There is no key for id %s! Self-repairing it.' + % (key_id)) + d = self._soledad.delete_doc(activedoc) + d.addCallback(lambda _: None) + return d + elif len(doclist) > 1: + d = self._repair_key_docs(doclist, key_id) + d.addCallback(lambda _: doclist[0]) + return d + return doclist[0] d = self._soledad.get_from_index( TYPE_ADDRESS_PRIVATE_INDEX, @@ -598,18 +609,20 @@ class OpenPGPScheme(EncryptionScheme): def delete_key(docs): if len(docs) == 0: raise errors.KeyNotFound(key) - if len(docs) > 1: - logger.critical("There is more than one key for key_id %s" - % key.key_id) - - doc = None - for d in docs: - if d.content['fingerprint'] == key.fingerprint: - doc = d - break - if doc is None: + elif len(docs) > 1: + logger.warning("There is more than one key for key_id %s" + % key.key_id) + + has_deleted = False + deferreds = [] + for doc in docs: + if doc.content['fingerprint'] == key.fingerprint: + d = self._soledad.delete_doc(doc) + deferreds.append(d) + has_deleted = True + if not has_deleted: raise errors.KeyNotFound(key) - return self._soledad.delete_doc(doc) + return defer.gatherResults(deferreds) d = self._soledad.get_from_index( TYPE_ID_PRIVATE_INDEX, @@ -621,6 +634,36 @@ class OpenPGPScheme(EncryptionScheme): d.addCallback(delete_key) return d + def _repair_key_docs(self, doclist, key_id): + """ + If there is more than one key for a key id try to self-repair it + + :return: a Deferred that will be fired once all the deletions are + completed + :rtype: Deferred + """ + logger.error("BUG ---------------------------------------------------") + logger.error("There is more than one key with the same key_id %s:" + % (key_id,)) + + def log_key_doc(doc): + logger.error("\t%s: %s" % (doc.content[KEY_ADDRESS_KEY], + doc.content[KEY_FINGERPRINT_KEY])) + + doclist.sort(key=lambda doc: doc.content[KEY_REFRESHED_AT_KEY], + reverse=True) + log_key_doc(doclist[0]) + deferreds = [] + for doc in doclist[1:]: + log_key_doc(doc) + d = self._soledad.delete_doc(doc) + deferreds.append(d) + + logger.error("") + logger.error(traceback.extract_stack()) + logger.error("BUG (please report above info) ------------------------") + return defer.gatherResults(deferreds, consumeErrors=True) + # # Data encryption, decryption, signing and verifying # diff --git a/keymanager/src/leap/keymanager/tests/__init__.py b/keymanager/src/leap/keymanager/tests/__init__.py index 9b95e1ac..cd612c43 100644 --- a/keymanager/src/leap/keymanager/tests/__init__.py +++ b/keymanager/src/leap/keymanager/tests/__init__.py @@ -66,9 +66,15 @@ class KeyManagerWithSoledadTestCase(unittest.TestCase, BaseLeapTest): for private in [True, False]: d = km.get_all_keys(private=private) d.addCallback(delete_keys) + d.addCallback(check_deleted, private) deferreds.append(d) return gatherResults(deferreds) + def check_deleted(_, private): + d = km.get_all_keys(private=private) + d.addCallback(lambda keys: self.assertEqual(keys, [])) + return d + # wait for the indexes to be ready for the tear down d = km._wrapper_map[OpenPGPKey].deferred_indexes d.addCallback(get_and_delete_keys) @@ -91,6 +97,7 @@ class KeyManagerWithSoledadTestCase(unittest.TestCase, BaseLeapTest): # key 24D18DDF: public key "Leap Test Key " +KEY_ID = "2F455E2824D18DDF" KEY_FINGERPRINT = "E36E738D69173C13D709E44F2F455E2824D18DDF" PUBLIC_KEY = """ -----BEGIN PGP PUBLIC KEY BLOCK----- diff --git a/keymanager/src/leap/keymanager/tests/test_openpgp.py b/keymanager/src/leap/keymanager/tests/test_openpgp.py index 5f85c74b..bae83db7 100644 --- a/keymanager/src/leap/keymanager/tests/test_openpgp.py +++ b/keymanager/src/leap/keymanager/tests/test_openpgp.py @@ -21,12 +21,15 @@ Tests for the OpenPGP support on Key Manager. """ -from twisted.internet.defer import inlineCallbacks +from datetime import datetime +from mock import Mock +from twisted.internet.defer import inlineCallbacks, gatherResults, succeed from leap.keymanager import ( KeyNotFound, openpgp, ) +from leap.keymanager.keys import TYPE_ID_PRIVATE_INDEX from leap.keymanager.openpgp import OpenPGPKey from leap.keymanager.tests import ( KeyManagerWithSoledadTestCase, @@ -34,6 +37,7 @@ from leap.keymanager.tests import ( ADDRESS_2, KEY_FINGERPRINT, PUBLIC_KEY, + KEY_ID, PUBLIC_KEY_2, PRIVATE_KEY, PRIVATE_KEY_2, @@ -247,6 +251,104 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): validsign = pgp.verify(data, pubkey, detached_sig=signature) self.assertTrue(validsign) + @inlineCallbacks + def test_self_repair_three_keys(self): + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=self.gpg_binary_path) + yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) + + get_from_index = self._soledad.get_from_index + delete_doc = self._soledad.delete_doc + + def my_get_from_index(*args): + if (args[0] == TYPE_ID_PRIVATE_INDEX and + args[2] == KEY_ID): + k1 = OpenPGPKey(ADDRESS, key_id="1", + refreshed_at=datetime(2005, 1, 1)) + k2 = OpenPGPKey(ADDRESS, key_id="2", + refreshed_at=datetime(2007, 1, 1)) + k3 = OpenPGPKey(ADDRESS, key_id="3", + refreshed_at=datetime(2001, 1, 1)) + d1 = self._soledad.create_doc_from_json(k1.get_json()) + d2 = self._soledad.create_doc_from_json(k2.get_json()) + d3 = self._soledad.create_doc_from_json(k3.get_json()) + return gatherResults([d1, d2, d3]) + return get_from_index(*args) + + self._soledad.get_from_index = my_get_from_index + self._soledad.delete_doc = Mock(return_value=succeed(None)) + + key = yield pgp.get_key(ADDRESS, private=False) + + try: + self.assertEqual(key.key_id, "2") + self.assertEqual(self._soledad.delete_doc.call_count, 2) + finally: + self._soledad.get_from_index = get_from_index + self._soledad.delete_doc = delete_doc + + @inlineCallbacks + def test_self_repair_no_keys(self): + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=self.gpg_binary_path) + yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) + + get_from_index = self._soledad.get_from_index + delete_doc = self._soledad.delete_doc + + def my_get_from_index(*args): + if (args[0] == TYPE_ID_PRIVATE_INDEX and + args[2] == KEY_ID): + return succeed([]) + return get_from_index(*args) + + self._soledad.get_from_index = my_get_from_index + self._soledad.delete_doc = Mock(return_value=succeed(None)) + + try: + yield self.assertFailure(pgp.get_key(ADDRESS, private=False), + KeyNotFound) + self.assertEqual(self._soledad.delete_doc.call_count, 1) + finally: + self._soledad.get_from_index = get_from_index + self._soledad.delete_doc = delete_doc + + @inlineCallbacks + def test_self_repair_put_keys(self): + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=self.gpg_binary_path) + + get_from_index = self._soledad.get_from_index + delete_doc = self._soledad.delete_doc + + def my_get_from_index(*args): + if (args[0] == TYPE_ID_PRIVATE_INDEX and + args[2] == KEY_ID): + k1 = OpenPGPKey(ADDRESS, key_id="1", + fingerprint=KEY_FINGERPRINT, + refreshed_at=datetime(2005, 1, 1)) + k2 = OpenPGPKey(ADDRESS, key_id="2", + fingerprint=KEY_FINGERPRINT, + refreshed_at=datetime(2007, 1, 1)) + k3 = OpenPGPKey(ADDRESS, key_id="3", + fingerprint=KEY_FINGERPRINT, + refreshed_at=datetime(2001, 1, 1)) + d1 = self._soledad.create_doc_from_json(k1.get_json()) + d2 = self._soledad.create_doc_from_json(k2.get_json()) + d3 = self._soledad.create_doc_from_json(k3.get_json()) + return gatherResults([d1, d2, d3]) + return get_from_index(*args) + + self._soledad.get_from_index = my_get_from_index + self._soledad.delete_doc = Mock(return_value=succeed(None)) + + try: + yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) + self.assertEqual(self._soledad.delete_doc.call_count, 2) + finally: + self._soledad.get_from_index = get_from_index + self._soledad.delete_doc = delete_doc + def _assert_key_not_found(self, pgp, address, private=False): d = pgp.get_key(address, private=private) return self.assertFailure(d, KeyNotFound) -- cgit v1.2.3 From 5d5c2eb8619b92c3a782d43418cbe4442b59f269 Mon Sep 17 00:00:00 2001 From: Victor Shyba Date: Wed, 27 Jan 2016 20:45:00 -0300 Subject: [feat] defer blocking requests calls to thread That's a temporary fix for #6506 This commit adapts code to deal with deferreds coming from calling requests from Twisted. Next step is just to change requests for twisted http client present in leap.common. Unfortunately, this last step will be a bit longer and would be better to have integrations tests to ensure current HTTP behaviour. --- keymanager/src/leap/keymanager/__init__.py | 42 ++++++++++++++++++------------ 1 file changed, 26 insertions(+), 16 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index c7886e0c..06128c0d 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -58,6 +58,7 @@ import logging import requests from twisted.internet import defer +from twisted.internet import threads from urlparse import urlparse from leap.common.check import leap_assert @@ -185,6 +186,7 @@ class KeyManager(object): lambda klass: klass.__name__ == ktype, self._wrapper_map).pop() + @defer.inlineCallbacks def _get(self, uri, data=None): """ Send a GET request to C{uri} containing C{data}. @@ -200,7 +202,8 @@ class KeyManager(object): leap_assert( self._ca_cert_path is not None, 'We need the CA certificate path!') - res = self._fetcher.get(uri, data=data, verify=self._ca_cert_path) + res = yield threads.deferToThread(self._fetcher.get, uri, data=data, + verify=self._ca_cert_path) # Nickserver now returns 404 for key not found and 500 for # other cases (like key too small), so we are skipping this # check for the time being @@ -211,7 +214,7 @@ class KeyManager(object): # leap_assert( # res.headers['content-type'].startswith('application/json'), # 'Content-type is not JSON.') - return res + defer.returnValue(res) def _get_with_combined_ca_bundle(self, uri, data=None): """ @@ -228,9 +231,11 @@ class KeyManager(object): :return: The response to the request. :rtype: requests.Response """ - return self._fetcher.get( - uri, data=data, verify=self._combined_ca_bundle) + return threads.deferToThread(self._fetcher.get, + uri, data=data, + verify=self._combined_ca_bundle) + @defer.inlineCallbacks def _put(self, uri, data=None): """ Send a PUT request to C{uri} containing C{data}. @@ -253,14 +258,17 @@ class KeyManager(object): leap_assert( self._token is not None, 'We need a token to interact with webapp!') - res = self._fetcher.put( - uri, data=data, verify=self._ca_cert_path, - headers={'Authorization': 'Token token=%s' % self._token}) + headers = {'Authorization': 'Token token=%s' % self._token} + res = yield threads.deferToThread(self._fetcher.put, + uri, data=data, + verify=self._ca_cert_path, + headers=headers) # assert that the response is valid res.raise_for_status() - return res + defer.returnValue(res) @memoized_method(invalidation=300) + @defer.inlineCallbacks def _fetch_keys_from_server(self, address): """ Fetch keys bound to address from nickserver and insert them in @@ -279,7 +287,7 @@ class KeyManager(object): d = defer.succeed(None) res = None try: - res = self._get(self._nickserver_uri, {'address': address}) + res = yield self._get(self._nickserver_uri, {'address': address}) res.raise_for_status() server_keys = res.json() @@ -307,7 +315,7 @@ class KeyManager(object): except Exception as e: d = defer.fail(KeyNotFound(e.message)) logger.warning("Error retrieving key: %r" % (e,)) - return d + yield d # # key management @@ -339,8 +347,9 @@ class KeyManager(object): self._api_uri, self._api_version, self._uid) - self._put(uri, data) + d = self._put(uri, data) emit_async(catalog.KEYMANAGER_DONE_UPLOADING_KEYS, self._address) + return d d = self.get_key( self._address, ktype, private=False, fetch_remote=False) @@ -822,6 +831,7 @@ class KeyManager(object): d.addCallback(lambda _: self.put_key(privkey, address)) return d + @defer.inlineCallbacks def fetch_key(self, address, uri, ktype, validation=ValidationLevels.Weak_Chain): """ @@ -852,20 +862,20 @@ class KeyManager(object): logger.info("Fetch key for %s from %s" % (address, uri)) try: - res = self._get_with_combined_ca_bundle(uri) + res = yield self._get_with_combined_ca_bundle(uri) except Exception as e: logger.warning("There was a problem fetching key: %s" % (e,)) - return defer.fail(KeyNotFound(uri)) + raise KeyNotFound(uri) if not res.ok: - return defer.fail(KeyNotFound(uri)) + raise KeyNotFound(uri) # XXX parse binary keys pubkey, _ = _keys.parse_ascii_key(res.content) if pubkey is None: - return defer.fail(KeyNotFound(uri)) + raise KeyNotFound(uri) pubkey.validation = validation - return self.put_key(pubkey, address) + yield self.put_key(pubkey, address) def _assert_supported_key_type(self, ktype): """ -- cgit v1.2.3 From cf24b53a3c2fa2f697804ca3a169e58d63b3b743 Mon Sep 17 00:00:00 2001 From: Victor Shyba Date: Wed, 27 Jan 2016 21:35:43 -0300 Subject: [refactor] isolate requests Isolate requests lib related code and update docstrings. --- keymanager/src/leap/keymanager/__init__.py | 112 ++++++++++----------- .../src/leap/keymanager/tests/test_keymanager.py | 8 +- 2 files changed, 56 insertions(+), 64 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 06128c0d..6413e9e5 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -187,35 +187,43 @@ class KeyManager(object): self._wrapper_map).pop() @defer.inlineCallbacks - def _get(self, uri, data=None): + def _get_json(self, address): """ Send a GET request to C{uri} containing C{data}. :param uri: The URI of the request. :type uri: str - :param data: The body of the request. - :type data: dict, str or file - :return: The response to the request. - :rtype: requests.Response + :return: A deferred that will be fired with the GET response + :rtype: Deferred """ leap_assert( self._ca_cert_path is not None, 'We need the CA certificate path!') - res = yield threads.deferToThread(self._fetcher.get, uri, data=data, - verify=self._ca_cert_path) - # Nickserver now returns 404 for key not found and 500 for - # other cases (like key too small), so we are skipping this - # check for the time being - # res.raise_for_status() - + try: + uri, data = self._nickserver_uri, {'address': address} + res = yield threads.deferToThread(self._fetcher.get, uri, + data=data, + verify=self._ca_cert_path) + res.raise_for_status() + except requests.exceptions.HTTPError as e: + if e.response.status_code == 404: + raise KeyNotFound(address) + else: + raise KeyNotFound(e.message) + logger.warning("HTTP error retrieving key: %r" % (e,)) + logger.warning("%s" % (res.content,)) + except Exception as e: + raise KeyNotFound(e.message) + logger.warning("Error retrieving key: %r" % (e,)) # Responses are now text/plain, although it's json anyway, but # this will fail when it shouldn't # leap_assert( # res.headers['content-type'].startswith('application/json'), # 'Content-type is not JSON.') - defer.returnValue(res) + defer.returnValue(res.json()) + @defer.inlineCallbacks def _get_with_combined_ca_bundle(self, uri, data=None): """ Send a GET request to C{uri} containing C{data}. @@ -228,12 +236,18 @@ class KeyManager(object): :param data: The body of the request. :type data: dict, str or file - :return: The response to the request. - :rtype: requests.Response + :return: A deferred that will be fired with the GET response + :rtype: Deferred """ - return threads.deferToThread(self._fetcher.get, - uri, data=data, - verify=self._combined_ca_bundle) + try: + res = yield threads.deferToThread(self._fetcher.get, uri, + verify=self._combined_ca_bundle) + except Exception as e: + logger.warning("There was a problem fetching key: %s" % (e,)) + raise KeyNotFound(uri) + if not res.ok: + raise KeyNotFound(uri) + defer.returnValue(res.content) @defer.inlineCallbacks def _put(self, uri, data=None): @@ -249,8 +263,8 @@ class KeyManager(object): :param data: The body of the request. :type data: dict, str or file - :return: The response to the request. - :rtype: requests.Response + :return: A deferred that will be fired when PUT request finishes + :rtype: Deferred """ leap_assert( self._ca_cert_path is not None, @@ -284,38 +298,22 @@ class KeyManager(object): """ # request keys from the nickserver - d = defer.succeed(None) - res = None - try: - res = yield self._get(self._nickserver_uri, {'address': address}) - res.raise_for_status() - server_keys = res.json() - - # insert keys in local database - if self.OPENPGP_KEY in server_keys: - # nicknym server is authoritative for its own domain, - # for other domains the key might come from key servers. - validation_level = ValidationLevels.Weak_Chain - _, domain = _split_email(address) - if (domain == _get_domain(self._nickserver_uri)): - validation_level = ValidationLevels.Provider_Trust - - d = self.put_raw_key( - server_keys['openpgp'], - OpenPGPKey, - address=address, - validation=validation_level) - except requests.exceptions.HTTPError as e: - if e.response.status_code == 404: - d = defer.fail(KeyNotFound(address)) - else: - d = defer.fail(KeyNotFound(e.message)) - logger.warning("HTTP error retrieving key: %r" % (e,)) - logger.warning("%s" % (res.content,)) - except Exception as e: - d = defer.fail(KeyNotFound(e.message)) - logger.warning("Error retrieving key: %r" % (e,)) - yield d + server_keys = yield self._get_json(address) + + # insert keys in local database + if self.OPENPGP_KEY in server_keys: + # nicknym server is authoritative for its own domain, + # for other domains the key might come from key servers. + validation_level = ValidationLevels.Weak_Chain + _, domain = _split_email(address) + if (domain == _get_domain(self._nickserver_uri)): + validation_level = ValidationLevels.Provider_Trust + + yield self.put_raw_key( + server_keys['openpgp'], + OpenPGPKey, + address=address, + validation=validation_level) # # key management @@ -861,16 +859,10 @@ class KeyManager(object): _keys = self._wrapper_map[ktype] logger.info("Fetch key for %s from %s" % (address, uri)) - try: - res = yield self._get_with_combined_ca_bundle(uri) - except Exception as e: - logger.warning("There was a problem fetching key: %s" % (e,)) - raise KeyNotFound(uri) - if not res.ok: - raise KeyNotFound(uri) + ascii_content = yield self._get_with_combined_ca_bundle(uri) # XXX parse binary keys - pubkey, _ = _keys.parse_ascii_key(res.content) + pubkey, _ = _keys.parse_ascii_key(ascii_content) if pubkey is None: raise KeyNotFound(uri) diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 856d6da9..afcbd5f6 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -342,7 +342,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL, OpenPGPKey) - get_mock.assert_called_once_with(REMOTE_KEY_URL, data=None, + get_mock.assert_called_once_with(REMOTE_KEY_URL, verify=ca_bundle.where()) @inlineCallbacks @@ -353,7 +353,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL, OpenPGPKey) - get_mock.assert_called_once_with(REMOTE_KEY_URL, data=None, + get_mock.assert_called_once_with(REMOTE_KEY_URL, verify=ca_bundle.where()) @inlineCallbacks @@ -364,7 +364,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL, OpenPGPKey) - get_mock.assert_called_once_with(REMOTE_KEY_URL, data=None, + get_mock.assert_called_once_with(REMOTE_KEY_URL, verify=ca_bundle.where()) @inlineCallbacks @@ -383,7 +383,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL, OpenPGPKey) # assert that combined bundle file is passed to get call - get_mock.assert_called_once_with(REMOTE_KEY_URL, data=None, + get_mock.assert_called_once_with(REMOTE_KEY_URL, verify=tmp_output.name) # assert that files got appended -- cgit v1.2.3 From d40016e5b43d4bd6bd6912d0ca18a68674d74702 Mon Sep 17 00:00:00 2001 From: Victor Shyba Date: Wed, 27 Jan 2016 23:18:04 -0300 Subject: [feat] use HTTPClient instead of requests This commit adapts code to use HTTPClient instead of requests. requests library receives a certificate as parameter during requests while HTTPClient recelives a cert only on constructor. In order to have both types (leap cert and commercial certs) working together we introduced two clients on constructor. --- keymanager/src/leap/keymanager/__init__.py | 63 +++++++----- .../src/leap/keymanager/tests/test_keymanager.py | 109 +++++++++------------ 2 files changed, 82 insertions(+), 90 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 6413e9e5..1dcf642d 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -22,6 +22,8 @@ import fileinput import os import sys import tempfile +import json +import urllib from leap.common import ca_bundle @@ -58,10 +60,10 @@ import logging import requests from twisted.internet import defer -from twisted.internet import threads from urlparse import urlparse from leap.common.check import leap_assert +from leap.common.http import HTTPClient from leap.common.events import emit_async, catalog from leap.common.decorators import memoized_method @@ -142,6 +144,8 @@ class KeyManager(object): # the following are used to perform https requests self._fetcher = requests self._combined_ca_bundle = self._create_combined_bundle_file() + self._async_client = HTTPClient(self._combined_ca_bundle) + self._async_client_pinned = HTTPClient(self._ca_cert_path) # # destructor @@ -201,27 +205,29 @@ class KeyManager(object): self._ca_cert_path is not None, 'We need the CA certificate path!') try: - uri, data = self._nickserver_uri, {'address': address} - res = yield threads.deferToThread(self._fetcher.get, uri, - data=data, - verify=self._ca_cert_path) - res.raise_for_status() - except requests.exceptions.HTTPError as e: - if e.response.status_code == 404: - raise KeyNotFound(address) - else: - raise KeyNotFound(e.message) + uri = self._nickserver_uri + '?address=' + address + content = yield self._async_client_pinned.request(str(uri), 'GET') + json_content = json.loads(content) + except IOError as e: + # FIXME: 404 doesnt raise today, but it wont produce json anyway + # if e.response.status_code == 404: + # raise KeyNotFound(address) logger.warning("HTTP error retrieving key: %r" % (e,)) - logger.warning("%s" % (res.content,)) + logger.warning("%s" % (content,)) + raise KeyNotFound(e), None, sys.exc_info()[2] + except ValueError as v: + logger.warning("Invalid JSON data from key: %s" % (uri,)) + raise KeyNotFound(v.message + ' - ' + uri), None, sys.exc_info()[2] + except Exception as e: - raise KeyNotFound(e.message) logger.warning("Error retrieving key: %r" % (e,)) + raise KeyNotFound(e.message), None, sys.exc_info()[2] # Responses are now text/plain, although it's json anyway, but # this will fail when it shouldn't # leap_assert( # res.headers['content-type'].startswith('application/json'), # 'Content-type is not JSON.') - defer.returnValue(res.json()) + defer.returnValue(json_content) @defer.inlineCallbacks def _get_with_combined_ca_bundle(self, uri, data=None): @@ -240,14 +246,13 @@ class KeyManager(object): :rtype: Deferred """ try: - res = yield threads.deferToThread(self._fetcher.get, uri, - verify=self._combined_ca_bundle) + content = yield self._async_client.request(str(uri), 'GET') except Exception as e: logger.warning("There was a problem fetching key: %s" % (e,)) raise KeyNotFound(uri) - if not res.ok: + if not content: raise KeyNotFound(uri) - defer.returnValue(res.content) + defer.returnValue(content) @defer.inlineCallbacks def _put(self, uri, data=None): @@ -272,14 +277,20 @@ class KeyManager(object): leap_assert( self._token is not None, 'We need a token to interact with webapp!') - headers = {'Authorization': 'Token token=%s' % self._token} - res = yield threads.deferToThread(self._fetcher.put, - uri, data=data, - verify=self._ca_cert_path, - headers=headers) - # assert that the response is valid - res.raise_for_status() - defer.returnValue(res) + if type(data) == dict: + data = urllib.urlencode(data) + headers = {'Authorization': [str('Token token=%s' % self._token)]} + headers['Content-Type'] = ['application/x-www-form-urlencoded'] + try: + res = yield self._async_client_pinned.request(str(uri), 'PUT', body=str(data), headers=headers) + except Exception as e: + logger.warning("Error uploading key: %r" % (e,)) + raise e + if 'error' in res: + # FIXME: That's a workaround for 500, + # we need to implement a readBody to assert response code + logger.warning("Error uploading key: %r" % (res,)) + raise Exception(res) @memoized_method(invalidation=300) @defer.inlineCallbacks diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index afcbd5f6..c3426b69 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -21,11 +21,14 @@ Tests for the Key Manager. """ from os import path +import json +import urllib from datetime import datetime import tempfile +import pkg_resources from leap.common import ca_bundle from mock import Mock, MagicMock, patch -from twisted.internet.defer import inlineCallbacks +from twisted.internet import defer from twisted.trial import unittest from leap.keymanager import ( @@ -127,7 +130,7 @@ class KeyManagerUtilTestCase(unittest.TestCase): class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): - @inlineCallbacks + @defer.inlineCallbacks def test_get_all_keys_in_db(self): km = self._key_manager() yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) @@ -142,7 +145,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): self.assertTrue(ADDRESS in keys[0].address) self.assertTrue(keys[0].private) - @inlineCallbacks + @defer.inlineCallbacks def test_get_public_key(self): km = self._key_manager() yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) @@ -155,7 +158,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): key.fingerprint.lower(), KEY_FINGERPRINT.lower()) self.assertFalse(key.private) - @inlineCallbacks + @defer.inlineCallbacks def test_get_private_key(self): km = self._key_manager() yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) @@ -173,7 +176,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): d = km.send_key(OpenPGPKey) return self.assertFailure(d, KeyNotFound) - @inlineCallbacks + @defer.inlineCallbacks def test_send_key(self): """ Test that request is well formed when sending keys to server. @@ -181,7 +184,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): token = "mytoken" km = self._key_manager(token=token) yield km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY, ADDRESS) - km._fetcher.put = Mock() + km._async_client_pinned.request = Mock(return_value=defer.succeed('')) # the following data will be used on the send km.ca_cert_path = 'capath' km.session_id = 'sessionid' @@ -191,13 +194,15 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): yield km.send_key(OpenPGPKey) # setup expected args pubkey = yield km.get_key(km._address, OpenPGPKey) - data = { + data = urllib.urlencode({ km.PUBKEY_KEY: pubkey.key_data, - } + }) + headers = {'Authorization': [str('Token token=%s' % token)]} + headers['Content-Type'] = ['application/x-www-form-urlencoded'] url = '%s/%s/users/%s.json' % ('apiuri', 'apiver', 'myuid') - km._fetcher.put.assert_called_once_with( - url, data=data, verify='capath', - headers={'Authorization': 'Token token=%s' % token}, + km._async_client_pinned.request.assert_called_once_with( + str(url), 'PUT', body=str(data), + headers=headers ) def test_fetch_keys_from_server(self): @@ -205,19 +210,19 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): Test that the request is well formed when fetching keys from server. """ km = self._key_manager(url=NICKSERVER_URI) + expected_url = NICKSERVER_URI + '?address=' + ADDRESS_2 def verify_the_call(_): - km._fetcher.get.assert_called_once_with( - NICKSERVER_URI, - data={'address': ADDRESS_2}, - verify='cacertpath', + km._async_client_pinned.request.assert_called_once_with( + expected_url, + 'GET', ) d = self._fetch_key(km, ADDRESS_2, PUBLIC_KEY_2) d.addCallback(verify_the_call) return d - @inlineCallbacks + @defer.inlineCallbacks def test_get_key_fetches_from_server(self): """ Test that getting a key successfuly fetches from server. @@ -229,7 +234,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): self.assertTrue(ADDRESS in key.address) self.assertEqual(key.validation, ValidationLevels.Provider_Trust) - @inlineCallbacks + @defer.inlineCallbacks def test_get_key_fetches_other_domain(self): """ Test that getting a key successfuly fetches from server. @@ -245,18 +250,10 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): """ :returns: a Deferred that will fire with the OpenPGPKey """ - class Response(object): - status_code = 200 - headers = {'content-type': 'application/json'} - - def json(self): - return {'address': address, 'openpgp': key} - - def raise_for_status(self): - pass + data = json.dumps({'address': address, 'openpgp': key}) # mock the fetcher so it returns the key for ADDRESS_2 - km._fetcher.get = Mock(return_value=Response()) + km._async_client_pinned.request = Mock(return_value=defer.succeed(data)) km.ca_cert_path = 'cacertpath' # try to key get without fetching from server d_fail = km.get_key(address, OpenPGPKey, fetch_remote=False) @@ -265,7 +262,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): d.addCallback(lambda _: km.get_key(address, OpenPGPKey)) return d - @inlineCallbacks + @defer.inlineCallbacks def test_put_key_ascii(self): """ Test that putting ascii key works @@ -277,7 +274,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): self.assertIsInstance(key, OpenPGPKey) self.assertTrue(ADDRESS in key.address) - @inlineCallbacks + @defer.inlineCallbacks def test_fetch_uri_ascii_key(self): """ Test that fetch key downloads the ascii key and gets included in @@ -285,11 +282,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): """ km = self._key_manager() - class Response(object): - ok = True - content = PUBLIC_KEY - - km._fetcher.get = Mock(return_value=Response()) + km._async_client.request = Mock(return_value=defer.succeed(PUBLIC_KEY)) yield km.fetch_key(ADDRESS, "http://site.domain/key", OpenPGPKey) key = yield km.get_key(ADDRESS, OpenPGPKey) @@ -316,25 +309,16 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): """ km = self._key_manager() - class Response(object): - ok = True - content = PUBLIC_KEY - - km._fetcher.get = Mock(return_value=Response()) + km._async_client.request = Mock(return_value=defer.succeed(PUBLIC_KEY)) d = km.fetch_key(ADDRESS_2, "http://site.domain/key", OpenPGPKey) return self.assertFailure(d, KeyAddressMismatch) def _mock_get_response(self, km, body): - class Response(object): - ok = True - content = body - - mock = MagicMock(return_value=Response()) - km._fetcher.get = mock + km._async_client.request = MagicMock(return_value=defer.succeed(body)) - return mock + return km._async_client.request - @inlineCallbacks + @defer.inlineCallbacks def test_fetch_key_uses_ca_bundle_if_none_specified(self): ca_cert_path = None km = self._key_manager(ca_cert_path=ca_cert_path) @@ -342,10 +326,9 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL, OpenPGPKey) - get_mock.assert_called_once_with(REMOTE_KEY_URL, - verify=ca_bundle.where()) + get_mock.assert_called_once_with(REMOTE_KEY_URL, 'GET') - @inlineCallbacks + @defer.inlineCallbacks def test_fetch_key_uses_ca_bundle_if_empty_string_specified(self): ca_cert_path = '' km = self._key_manager(ca_cert_path=ca_cert_path) @@ -353,10 +336,9 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL, OpenPGPKey) - get_mock.assert_called_once_with(REMOTE_KEY_URL, - verify=ca_bundle.where()) + get_mock.assert_called_once_with(REMOTE_KEY_URL, 'GET') - @inlineCallbacks + @defer.inlineCallbacks def test_fetch_key_use_default_ca_bundle_if_set_as_ca_cert_path(self): ca_cert_path = ca_bundle.where() km = self._key_manager(ca_cert_path=ca_cert_path) @@ -364,14 +346,14 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL, OpenPGPKey) - get_mock.assert_called_once_with(REMOTE_KEY_URL, - verify=ca_bundle.where()) + get_mock.assert_called_once_with(REMOTE_KEY_URL, 'GET') - @inlineCallbacks + @defer.inlineCallbacks def test_fetch_uses_combined_ca_bundle_otherwise(self): with tempfile.NamedTemporaryFile() as tmp_input, \ tempfile.NamedTemporaryFile(delete=False) as tmp_output: - ca_content = 'some\ncontent\n' + ca_content = pkg_resources.resource_string('leap.common.testing', + 'cacert.pem') ca_cert_path = tmp_input.name self._dump_to_file(ca_cert_path, ca_content) @@ -383,8 +365,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL, OpenPGPKey) # assert that combined bundle file is passed to get call - get_mock.assert_called_once_with(REMOTE_KEY_URL, - verify=tmp_output.name) + get_mock.assert_called_once_with(REMOTE_KEY_URL, 'GET') # assert that files got appended expected = self._slurp_file(ca_bundle.where()) + ca_content @@ -402,7 +383,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): content = f.read() return content - @inlineCallbacks + @defer.inlineCallbacks def test_decrypt_updates_sign_used_for_signer(self): # given km = self._key_manager() @@ -420,7 +401,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): # then self.assertEqual(True, key.sign_used) - @inlineCallbacks + @defer.inlineCallbacks def test_decrypt_does_not_update_sign_used_for_recipient(self): # given km = self._key_manager() @@ -445,7 +426,7 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): RAW_DATA = 'data' - @inlineCallbacks + @defer.inlineCallbacks def test_keymanager_openpgp_encrypt_decrypt(self): km = self._key_manager() # put raw private key @@ -464,7 +445,7 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): fetch_remote=False) self.assertEqual(signingkey.fingerprint, key.fingerprint) - @inlineCallbacks + @defer.inlineCallbacks def test_keymanager_openpgp_encrypt_decrypt_wrong_sign(self): km = self._key_manager() # put raw keys @@ -481,7 +462,7 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): self.assertEqual(self.RAW_DATA, rawdata) self.assertTrue(isinstance(signingkey, errors.InvalidSignature)) - @inlineCallbacks + @defer.inlineCallbacks def test_keymanager_openpgp_sign_verify(self): km = self._key_manager() # put raw private keys -- cgit v1.2.3 From 2086cf964fb8230802e6ecf47119bbe0acb4b85a Mon Sep 17 00:00:00 2001 From: Victor Shyba Date: Tue, 2 Feb 2016 19:51:36 -0300 Subject: [docs] add docstrings and fixes pep8 Some methods were missing docstrings and some code was exceeding the 80 column limit. Also some asserts arent needed anymore. --- keymanager/src/leap/keymanager/__init__.py | 34 +++++++++++++++++------------- 1 file changed, 19 insertions(+), 15 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 1dcf642d..7e4d30ea 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -184,26 +184,29 @@ class KeyManager(object): def _key_class_from_type(self, ktype): """ - Return key class from string representation of key type. + Given a class type, return a class + + :param ktype: string representation of a class name + :type ktype: str + + :return: A class with the matching name + :rtype: classobj or type """ return filter( lambda klass: klass.__name__ == ktype, self._wrapper_map).pop() @defer.inlineCallbacks - def _get_json(self, address): + def _get_key_from_nicknym(self, address): """ Send a GET request to C{uri} containing C{data}. - :param uri: The URI of the request. - :type uri: str + :param address: The URI of the request. + :type address: str - :return: A deferred that will be fired with the GET response + :return: A deferred that will be fired with GET content as json (dict) :rtype: Deferred """ - leap_assert( - self._ca_cert_path is not None, - 'We need the CA certificate path!') try: uri = self._nickserver_uri + '?address=' + address content = yield self._async_client_pinned.request(str(uri), 'GET') @@ -214,7 +217,7 @@ class KeyManager(object): # raise KeyNotFound(address) logger.warning("HTTP error retrieving key: %r" % (e,)) logger.warning("%s" % (content,)) - raise KeyNotFound(e), None, sys.exc_info()[2] + raise KeyNotFound(e.message), None, sys.exc_info()[2] except ValueError as v: logger.warning("Invalid JSON data from key: %s" % (uri,)) raise KeyNotFound(v.message + ' - ' + uri), None, sys.exc_info()[2] @@ -271,9 +274,6 @@ class KeyManager(object): :return: A deferred that will be fired when PUT request finishes :rtype: Deferred """ - leap_assert( - self._ca_cert_path is not None, - 'We need the CA certificate path!') leap_assert( self._token is not None, 'We need a token to interact with webapp!') @@ -282,7 +282,9 @@ class KeyManager(object): headers = {'Authorization': [str('Token token=%s' % self._token)]} headers['Content-Type'] = ['application/x-www-form-urlencoded'] try: - res = yield self._async_client_pinned.request(str(uri), 'PUT', body=str(data), headers=headers) + res = yield self._async_client_pinned.request(str(uri), 'PUT', + body=str(data), + headers=headers) except Exception as e: logger.warning("Error uploading key: %r" % (e,)) raise e @@ -309,7 +311,7 @@ class KeyManager(object): """ # request keys from the nickserver - server_keys = yield self._get_json(address) + server_keys = yield self._get_key_from_nicknym(address) # insert keys in local database if self.OPENPGP_KEY in server_keys: @@ -357,7 +359,9 @@ class KeyManager(object): self._api_version, self._uid) d = self._put(uri, data) - emit_async(catalog.KEYMANAGER_DONE_UPLOADING_KEYS, self._address) + d.addCallback(lambda _: + emit_async(catalog.KEYMANAGER_DONE_UPLOADING_KEYS, + self._address)) return d d = self.get_key( -- cgit v1.2.3 From 9b0689a7eafe0c2e073059cfd2f1ffedd203437a Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Tue, 9 Feb 2016 16:32:53 +0100 Subject: [style] fix pep8 --- keymanager/src/leap/keymanager/tests/test_keymanager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index c3426b69..0f08326e 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -253,7 +253,8 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): data = json.dumps({'address': address, 'openpgp': key}) # mock the fetcher so it returns the key for ADDRESS_2 - km._async_client_pinned.request = Mock(return_value=defer.succeed(data)) + km._async_client_pinned.request = Mock( + return_value=defer.succeed(data)) km.ca_cert_path = 'cacertpath' # try to key get without fetching from server d_fail = km.get_key(address, OpenPGPKey, fetch_remote=False) -- cgit v1.2.3 From 3e465c7a38c0bd18209b35ee29872d09cee4a7e0 Mon Sep 17 00:00:00 2001 From: Victor Shyba Date: Thu, 11 Feb 2016 19:22:34 -0300 Subject: [feat] defer decrypt, gen_key and encrypt This commit put those gnupg operations to be run on external threads limited by the amount of cores present on user machine. Some gnupg calls spawn processes and communicating to them is a synchronous operation, so running outside of a reactor should improve response time by avoiding reactor locking. --- keymanager/src/leap/keymanager/__init__.py | 18 +++++----- keymanager/src/leap/keymanager/openpgp.py | 40 ++++++++++++++++------ .../src/leap/keymanager/tests/test_openpgp.py | 30 ++++++++-------- 3 files changed, 53 insertions(+), 35 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 7e4d30ea..9aa71395 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -572,15 +572,15 @@ class KeyManager(object): self._assert_supported_key_type(ktype) _keys = self._wrapper_map[ktype] + @defer.inlineCallbacks def encrypt(keys): pubkey, signkey = keys - encrypted = _keys.encrypt( + encrypted = yield _keys.encrypt( data, pubkey, passphrase, sign=signkey, cipher_algo=cipher_algo) pubkey.encr_used = True - d = _keys.put_key(pubkey, address) - d.addCallback(lambda _: encrypted) - return d + yield _keys.put_key(pubkey, address) + defer.returnValue(encrypted) dpub = self.get_key(address, ktype, private=False, fetch_remote=fetch_remote) @@ -625,9 +625,10 @@ class KeyManager(object): self._assert_supported_key_type(ktype) _keys = self._wrapper_map[ktype] + @defer.inlineCallbacks def decrypt(keys): pubkey, privkey = keys - decrypted, signed = _keys.decrypt( + decrypted, signed = yield _keys.decrypt( data, privkey, passphrase=passphrase, verify=pubkey) if pubkey is None: signature = KeyNotFound(verify) @@ -635,14 +636,13 @@ class KeyManager(object): signature = pubkey if not pubkey.sign_used: pubkey.sign_used = True - d = _keys.put_key(pubkey, verify) - d.addCallback(lambda _: (decrypted, signature)) - return d + yield _keys.put_key(pubkey, verify) + defer.returnValue((decrypted, signature)) else: signature = InvalidSignature( 'Failed to verify signature with key %s' % (pubkey.key_id,)) - return (decrypted, signature) + defer.returnValue((decrypted, signature)) dpriv = self.get_key(address, ktype, private=True) dpub = defer.succeed(None) diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index d6481377..90640439 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -27,9 +27,11 @@ import io from datetime import datetime +from multiprocessing import cpu_count from gnupg import GPG from gnupg.gnupg import GPGUtilities from twisted.internet import defer +from twisted.internet.threads import deferToThread from leap.common.check import leap_assert, leap_assert_type, leap_check from leap.keymanager import errors @@ -55,6 +57,16 @@ logger = logging.getLogger(__name__) # A temporary GPG keyring wrapped to provide OpenPGP functionality. # +# This function will be used to call blocking GPG functions outside +# of Twisted reactor and match the concurrent calls to the amount of CPU cores +cpu_core_semaphore = defer.DeferredSemaphore(cpu_count()) + + +def from_thread(func, *args, **kwargs): + call = lambda: deferToThread(func, *args, **kwargs) + return cpu_core_semaphore.run(call) + + class TempGPGWrapper(object): """ A context manager that wraps a temporary GPG keyring which only contains @@ -253,6 +265,7 @@ class OpenPGPScheme(EncryptionScheme): # make sure the key does not already exist leap_assert(is_address(address), 'Not an user address: %s' % address) + @defer.inlineCallbacks def _gen_key(_): with TempGPGWrapper(gpgbinary=self._gpgbinary) as gpg: # TODO: inspect result, or use decorator @@ -264,7 +277,7 @@ class OpenPGPScheme(EncryptionScheme): name_comment='') logger.info("About to generate keys... " "This might take SOME time.") - gpg.gen_key(params) + yield from_thread(gpg.gen_key, params) logger.info("Keys for %s have been successfully " "generated." % (address,)) pubkeys = gpg.list_keys() @@ -293,7 +306,7 @@ class OpenPGPScheme(EncryptionScheme): gpg.export_keys(key['fingerprint'], secret=secret)) d = self.put_key(openpgp_key, address) deferreds.append(d) - return defer.gatherResults(deferreds) + yield defer.gatherResults(deferreds) def key_already_exists(_): raise errors.KeyAlreadyExists(address) @@ -686,6 +699,7 @@ class OpenPGPScheme(EncryptionScheme): raise errors.GPGError( 'Failed to encrypt/decrypt: %s' % stderr) + @defer.inlineCallbacks def encrypt(self, data, pubkey, passphrase=None, sign=None, cipher_algo='AES256'): """ @@ -700,8 +714,8 @@ class OpenPGPScheme(EncryptionScheme): :param cipher_algo: The cipher algorithm to use. :type cipher_algo: str - :return: The encrypted data. - :rtype: str + :return: A Deferred that will be fired with the encrypted data. + :rtype: defer.Deferred :raise EncryptError: Raised if failed encrypting for some reason. """ @@ -713,7 +727,8 @@ class OpenPGPScheme(EncryptionScheme): leap_assert(sign.private is True) keys.append(sign) with TempGPGWrapper(keys, self._gpgbinary) as gpg: - result = gpg.encrypt( + result = yield from_thread( + gpg.encrypt, data, pubkey.fingerprint, default_key=sign.key_id if sign else None, passphrase=passphrase, symmetric=False, @@ -724,11 +739,12 @@ class OpenPGPScheme(EncryptionScheme): # result.data - (bool) contains the result of the operation try: self._assert_gpg_result_ok(result) - return result.data + defer.returnValue(result.data) except errors.GPGError as e: logger.error('Failed to decrypt: %s.' % str(e)) raise errors.EncryptError() + @defer.inlineCallbacks def decrypt(self, data, privkey, passphrase=None, verify=None): """ Decrypt C{data} using private @{privkey} and verify with C{verify} key. @@ -743,8 +759,9 @@ class OpenPGPScheme(EncryptionScheme): :param verify: The key used to verify a signature. :type verify: OpenPGPKey - :return: The decrypted data and if signature verifies - :rtype: (unicode, bool) + :return: Deferred that will fire with the decrypted data and + if signature verifies (unicode, bool) + :rtype: Deferred :raise DecryptError: Raised if failed decrypting for some reason. """ @@ -756,8 +773,9 @@ class OpenPGPScheme(EncryptionScheme): keys.append(verify) with TempGPGWrapper(keys, self._gpgbinary) as gpg: try: - result = gpg.decrypt( - data, passphrase=passphrase, always_trust=True) + result = yield from_thread(gpg.decrypt, + data, passphrase=passphrase, + always_trust=True) self._assert_gpg_result_ok(result) # verify signature @@ -767,7 +785,7 @@ class OpenPGPScheme(EncryptionScheme): verify.fingerprint == result.pubkey_fingerprint): sign_valid = True - return (result.data, sign_valid) + defer.returnValue((result.data, sign_valid)) except errors.GPGError as e: logger.error('Failed to decrypt: %s.' % str(e)) raise errors.DecryptError(str(e)) diff --git a/keymanager/src/leap/keymanager/tests/test_openpgp.py b/keymanager/src/leap/keymanager/tests/test_openpgp.py index bae83db7..96b40a0e 100644 --- a/keymanager/src/leap/keymanager/tests/test_openpgp.py +++ b/keymanager/src/leap/keymanager/tests/test_openpgp.py @@ -109,7 +109,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): # encrypt yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) pubkey = yield pgp.get_key(ADDRESS, private=False) - cyphertext = pgp.encrypt(data, pubkey) + cyphertext = yield pgp.encrypt(data, pubkey) self.assertTrue(cyphertext is not None) self.assertTrue(cyphertext != '') @@ -121,7 +121,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): yield self._assert_key_not_found(pgp, ADDRESS, private=True) yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) privkey = yield pgp.get_key(ADDRESS, private=True) - decrypted, _ = pgp.decrypt(cyphertext, privkey) + decrypted, _ = yield pgp.decrypt(cyphertext, privkey) self.assertEqual(decrypted, data) yield pgp.delete_key(pubkey) @@ -171,9 +171,9 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) privkey = yield pgp.get_key(ADDRESS, private=True) pubkey = yield pgp.get_key(ADDRESS, private=False) - self.assertRaises( - AssertionError, - pgp.encrypt, data, privkey, sign=pubkey) + self.failureResultOf( + pgp.encrypt(data, privkey, sign=pubkey), + AssertionError) @inlineCallbacks def test_decrypt_verify_with_private_raises(self): @@ -183,12 +183,11 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) privkey = yield pgp.get_key(ADDRESS, private=True) pubkey = yield pgp.get_key(ADDRESS, private=False) - encrypted_and_signed = pgp.encrypt( + encrypted_and_signed = yield pgp.encrypt( data, pubkey, sign=privkey) - self.assertRaises( - AssertionError, - pgp.decrypt, - encrypted_and_signed, privkey, verify=privkey) + self.failureResultOf( + pgp.decrypt(encrypted_and_signed, privkey, verify=privkey), + AssertionError) @inlineCallbacks def test_decrypt_verify_with_wrong_key(self): @@ -198,11 +197,12 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) privkey = yield pgp.get_key(ADDRESS, private=True) pubkey = yield pgp.get_key(ADDRESS, private=False) - encrypted_and_signed = pgp.encrypt(data, pubkey, sign=privkey) + encrypted_and_signed = yield pgp.encrypt(data, pubkey, sign=privkey) yield pgp.put_ascii_key(PUBLIC_KEY_2, ADDRESS_2) wrongkey = yield pgp.get_key(ADDRESS_2) - decrypted, validsign = pgp.decrypt(encrypted_and_signed, privkey, - verify=wrongkey) + decrypted, validsign = yield pgp.decrypt(encrypted_and_signed, + privkey, + verify=wrongkey) self.assertEqual(decrypted, data) self.assertFalse(validsign) @@ -232,9 +232,9 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): privkey2 = yield pgp.get_key(ADDRESS_2, private=True) data = 'data' - encrypted_and_signed = pgp.encrypt( + encrypted_and_signed = yield pgp.encrypt( data, pubkey2, sign=privkey) - res, validsign = pgp.decrypt( + res, validsign = yield pgp.decrypt( encrypted_and_signed, privkey2, verify=pubkey) self.assertEqual(data, res) self.assertTrue(validsign) -- cgit v1.2.3 From dfe833a81ba2d80f06421e18619582808ff2657a Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Tue, 15 Dec 2015 13:29:44 +0100 Subject: [feat] move validation, usage and audited date to the active document - Resolves: #7485 --- keymanager/src/leap/keymanager/__init__.py | 1 + keymanager/src/leap/keymanager/keys.py | 132 +++++++--- keymanager/src/leap/keymanager/openpgp.py | 268 +++++++++------------ .../src/leap/keymanager/tests/test_keymanager.py | 15 +- .../src/leap/keymanager/tests/test_openpgp.py | 54 ++++- .../src/leap/keymanager/tests/test_validation.py | 164 ++++++++++++- 6 files changed, 430 insertions(+), 204 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 9aa71395..8b3487f9 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -439,6 +439,7 @@ class KeyManager(object): :return: A Deferred which fires with a list of all keys in local db. :rtype: Deferred """ + # TODO: should it be based on activedocs? def build_keys(docs): return map( lambda doc: build_key_from_dict( diff --git a/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py index 91559c2f..0de6a823 100644 --- a/keymanager/src/leap/keymanager/keys.py +++ b/keymanager/src/leap/keymanager/keys.py @@ -28,6 +28,7 @@ except ImportError: import logging import re import time +import traceback from abc import ABCMeta, abstractmethod @@ -110,39 +111,48 @@ def is_address(address): return bool(re.match('[\w.-]+@[\w.-]+', address)) -def build_key_from_dict(kClass, kdict): +def build_key_from_dict(kClass, key, active=None): """ Build an C{kClass} key based on info in C{kdict}. - :param kdict: Dictionary with key data. - :type kdict: dict + :param key: Dictionary with key data. + :type key: dict + :param active: Dictionary with active data. + :type active: dict :return: An instance of the key. :rtype: C{kClass} """ - try: - validation = ValidationLevels.get(kdict[KEY_VALIDATION_KEY]) - except ValueError: - logger.error("Not valid validation level (%s) for key %s", - (kdict[KEY_VALIDATION_KEY], kdict[KEY_ID_KEY])) - validation = ValidationLevels.Weak_Chain - - expiry_date = _to_datetime(kdict[KEY_EXPIRY_DATE_KEY]) - last_audited_at = _to_datetime(kdict[KEY_LAST_AUDITED_AT_KEY]) - refreshed_at = _to_datetime(kdict[KEY_REFRESHED_AT_KEY]) + validation = ValidationLevels.Weak_Chain + last_audited_at = None + encr_used = False + sign_used = False + + if active: + try: + validation = ValidationLevels.get(active[KEY_VALIDATION_KEY]) + except ValueError: + logger.error("Not valid validation level (%s) for key %s", + (active[KEY_VALIDATION_KEY], active[KEY_ID_KEY])) + last_audited_at = _to_datetime(active[KEY_LAST_AUDITED_AT_KEY]) + encr_used = active[KEY_ENCR_USED_KEY] + sign_used = active[KEY_SIGN_USED_KEY] + + expiry_date = _to_datetime(key[KEY_EXPIRY_DATE_KEY]) + refreshed_at = _to_datetime(key[KEY_REFRESHED_AT_KEY]) return kClass( - kdict[KEY_ADDRESS_KEY], - key_id=kdict[KEY_ID_KEY], - fingerprint=kdict[KEY_FINGERPRINT_KEY], - key_data=kdict[KEY_DATA_KEY], - private=kdict[KEY_PRIVATE_KEY], - length=kdict[KEY_LENGTH_KEY], + key[KEY_ADDRESS_KEY], + key_id=key[KEY_ID_KEY], + fingerprint=key[KEY_FINGERPRINT_KEY], + key_data=key[KEY_DATA_KEY], + private=key[KEY_PRIVATE_KEY], + length=key[KEY_LENGTH_KEY], expiry_date=expiry_date, last_audited_at=last_audited_at, refreshed_at=refreshed_at, validation=validation, - encr_used=kdict[KEY_ENCR_USED_KEY], - sign_used=kdict[KEY_SIGN_USED_KEY], + encr_used=encr_used, + sign_used=sign_used, ) @@ -178,6 +188,7 @@ class EncryptionKey(object): key_data="", private=False, length=0, expiry_date=None, validation=ValidationLevels.Weak_Chain, last_audited_at=None, refreshed_at=None, encr_used=False, sign_used=False): + # TODO: it should know its own active address self.address = address self.key_id = key_id self.fingerprint = fingerprint @@ -185,6 +196,7 @@ class EncryptionKey(object): self.private = private self.length = length self.expiry_date = expiry_date + self.validation = validation self.last_audited_at = last_audited_at self.refreshed_at = refreshed_at @@ -199,7 +211,6 @@ class EncryptionKey(object): :rtype: str """ expiry_date = _to_unix_time(self.expiry_date) - last_audited_at = _to_unix_time(self.last_audited_at) refreshed_at = _to_unix_time(self.refreshed_at) return json.dumps({ @@ -211,11 +222,7 @@ class EncryptionKey(object): KEY_PRIVATE_KEY: self.private, KEY_LENGTH_KEY: self.length, KEY_EXPIRY_DATE_KEY: expiry_date, - KEY_LAST_AUDITED_AT_KEY: last_audited_at, KEY_REFRESHED_AT_KEY: refreshed_at, - KEY_VALIDATION_KEY: str(self.validation), - KEY_ENCR_USED_KEY: self.encr_used, - KEY_SIGN_USED_KEY: self.sign_used, KEY_TAGS_KEY: [KEYMANAGER_KEY_TAG], }) @@ -223,16 +230,20 @@ class EncryptionKey(object): """ Return a JSON string describing this key. - :param address: Address for wich the key is active - :type address: str :return: The JSON string describing this key. :rtype: str """ + last_audited_at = _to_unix_time(self.last_audited_at) + return json.dumps({ KEY_ADDRESS_KEY: address, KEY_TYPE_KEY: self.__class__.__name__ + KEYMANAGER_ACTIVE_TYPE, KEY_ID_KEY: self.key_id, KEY_PRIVATE_KEY: self.private, + KEY_VALIDATION_KEY: str(self.validation), + KEY_LAST_AUDITED_AT_KEY: last_audited_at, + KEY_ENCR_USED_KEY: self.encr_used, + KEY_SIGN_USED_KEY: self.sign_used, KEY_TAGS_KEY: [KEYMANAGER_ACTIVE_TAG], }) @@ -464,3 +475,68 @@ class EncryptionScheme(object): :rtype: bool """ pass + + def _repair_key_docs(self, doclist): + """ + If there is more than one key for a key id try to self-repair it + + :return: a Deferred that will be fired with the valid key doc once all + the deletions are completed + :rtype: Deferred + """ + def log_key_doc(doc): + logger.error("\t%s: %s" % (doc.content[KEY_ADDRESS_KEY], + doc.content[KEY_FINGERPRINT_KEY])) + + def cmp_key(d1, d2): + return cmp(d1.content[KEY_REFRESHED_AT_KEY], + d2.content[KEY_REFRESHED_AT_KEY]) + + return self._repair_docs(doclist, cmp_key, log_key_doc) + + def _repair_active_docs(self, doclist): + """ + If there is more than one active doc for an address try to self-repair + it + + :return: a Deferred that will be fired with the valid active doc once + all the deletions are completed + :rtype: Deferred + """ + def log_active_doc(doc): + logger.error("\t%s: %s" % (doc.content[KEY_ADDRESS_KEY], + doc.content[KEY_ID_KEY])) + + def cmp_active(d1, d2): + res = cmp(d1.content[KEY_LAST_AUDITED_AT_KEY], + d2.content[KEY_LAST_AUDITED_AT_KEY]) + if res != 0: + return res + + used1 = (d1.content[KEY_SIGN_USED_KEY] + + d1.content[KEY_ENCR_USED_KEY]) + used2 = (d2.content[KEY_SIGN_USED_KEY] + + d2.content[KEY_ENCR_USED_KEY]) + return cmp(used1, used2) + + return self._repair_docs(doclist, cmp_active, log_active_doc) + + def _repair_docs(self, doclist, cmp_func, log_func): + logger.error("BUG ---------------------------------------------------") + logger.error("There is more than one doc of type %s:" + % (doclist[0].content[KEY_TYPE_KEY],)) + + doclist.sort(cmp=cmp_func, reverse=True) + log_func(doclist[0]) + deferreds = [] + for doc in doclist[1:]: + log_func(doc) + d = self._soledad.delete_doc(doc) + deferreds.append(d) + + logger.error("") + logger.error(traceback.extract_stack()) + logger.error("BUG (please report above info) ------------------------") + d = defer.gatherResults(deferreds, consumeErrors=True) + d.addCallback(lambda _: doclist[0]) + return d diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 90640439..3c8ac1e1 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -22,7 +22,6 @@ import os import re import shutil import tempfile -import traceback import io @@ -44,8 +43,6 @@ from leap.keymanager.keys import ( TYPE_ADDRESS_PRIVATE_INDEX, KEY_ADDRESS_KEY, KEY_ID_KEY, - KEY_FINGERPRINT_KEY, - KEY_REFRESHED_AT_KEY, KEYMANAGER_ACTIVE_TYPE, ) @@ -223,6 +220,41 @@ class OpenPGPKey(EncryptionKey): return [] + def merge(self, newkey): + if newkey.fingerprint != self.fingerprint: + logger.critical( + "Can't put a key whith the same key_id and different " + "fingerprint: %s, %s" + % (newkey.fingerprint, self.fingerprint)) + raise errors.KeyFingerprintMismatch(newkey.fingerprint) + + with TempGPGWrapper(gpgbinary=self._gpgbinary) as gpg: + gpg.import_keys(self.key_data) + gpg.import_keys(newkey.key_data) + gpgkey = gpg.list_keys(secret=newkey.private).pop() + + if gpgkey['expires']: + self.expiry_date = datetime.fromtimestamp( + int(gpgkey['expires'])) + else: + self.expiry_date = None + + self.uids = [] + for uid in gpgkey['uids']: + self.uids.append(_parse_address(uid)) + + self.length = int(gpgkey['length']) + self.key_data = gpg.export_keys(gpgkey['fingerprint'], + secret=self.private) + + if newkey.validation > self.validation: + self.validation = newkey.validation + if newkey.last_audited_at > self.last_audited_at: + self.validation = newkey.last_audited_at + self.encr_used = newkey.encr_used or self.encr_used + self.sign_used = newkey.sign_used or self.sign_used + self.refreshed_at = datetime.now() + class OpenPGPScheme(EncryptionScheme): """ @@ -332,15 +364,16 @@ class OpenPGPScheme(EncryptionScheme): """ address = _parse_address(address) - def build_key(doc): - if doc is None: + def build_key((keydoc, activedoc)): + if keydoc is None: raise errors.KeyNotFound(address) leap_assert( - address in doc.content[KEY_ADDRESS_KEY], + address in keydoc.content[KEY_ADDRESS_KEY], 'Wrong address in key %s. Expected %s, found %s.' - % (doc.content[KEY_ID_KEY], address, - doc.content[KEY_ADDRESS_KEY])) - key = build_key_from_dict(OpenPGPKey, doc.content) + % (keydoc.content[KEY_ID_KEY], address, + keydoc.content[KEY_ADDRESS_KEY])) + key = build_key_from_dict(OpenPGPKey, keydoc.content, + activedoc.content) key._gpgbinary = self._gpgbinary return key @@ -426,100 +459,44 @@ class OpenPGPScheme(EncryptionScheme): :return: A Deferred which fires when the key is in the storage. :rtype: Deferred """ - d = self._put_key_doc(key) - d.addCallback(lambda _: self._put_active_doc(key, address)) - return d + def merge_and_put((keydoc, activedoc)): + if not keydoc: + return put_new_key(activedoc) - def _put_key_doc(self, key): - """ - Put key document in soledad + active_content = None + if activedoc: + active_content = activedoc.content + oldkey = build_key_from_dict(OpenPGPKey, keydoc.content, + active_content) - :type key: OpenPGPKey - :rtype: Deferred - """ - def check_and_put(docs, key): - deferred_repair = defer.succeed(None) - if len(docs) == 0: - return self._soledad.create_doc_from_json(key.get_json()) - elif len(docs) > 1: - deferred_repair = self._repair_key_docs(docs, key.key_id) - - doc = docs[0] - oldkey = build_key_from_dict(OpenPGPKey, doc.content) - if key.fingerprint != oldkey.fingerprint: - logger.critical( - "Can't put a key whith the same key_id and different " - "fingerprint: %s, %s" - % (key.fingerprint, oldkey.fingerprint)) - return defer.fail( - errors.KeyFingerprintMismatch(key.fingerprint)) - - # in case of an update of the key merge them with gnupg - with TempGPGWrapper(gpgbinary=self._gpgbinary) as gpg: - gpg.import_keys(oldkey.key_data) - gpg.import_keys(key.key_data) - gpgkey = gpg.list_keys(secret=key.private).pop() - mergedkey = self._build_key_from_gpg( - gpgkey, - gpg.export_keys(gpgkey['fingerprint'], - secret=key.private)) - mergedkey.validation = max( - [key.validation, oldkey.validation]) - mergedkey.last_audited_at = oldkey.last_audited_at - mergedkey.refreshed_at = key.refreshed_at - mergedkey.encr_used = key.encr_used or oldkey.encr_used - mergedkey.sign_used = key.sign_used or oldkey.sign_used - doc.set_json(mergedkey.get_json()) - deferred_put = self._soledad.put_doc(doc) - - d = defer.gatherResults([deferred_put, deferred_repair]) - d.addCallback(lambda res: res[0]) - return d + key.merge(oldkey) + keydoc.set_json(key.get_json()) + deferred_key = self._soledad.put_doc(keydoc) - d = self._soledad.get_from_index( - TYPE_ID_PRIVATE_INDEX, - self.KEY_TYPE, - key.key_id, - '1' if key.private else '0') - d.addCallback(check_and_put, key) - return d + active_json = key.get_active_json(address) + if activedoc: + activedoc.set_json(active_json) + deferred_active = self._soledad.put_doc(activedoc) + else: + deferred_active = self._soledad.create_doc_from_json( + active_json) - def _put_active_doc(self, key, address): - """ - Put active key document in soledad + return defer.gatherResults([deferred_key, deferred_active]) - :type key: OpenPGPKey - :type addresses: str - :rtype: Deferred - """ - def check_and_put(docs): - if len(docs) == 1: - doc = docs.pop() - doc.set_json(key.get_active_json(address)) - d = self._soledad.put_doc(doc) - else: - if len(docs) > 1: - logger.error("There is more than one active key document " - "for the address %s" % (address,)) - deferreds = [] - for doc in docs: - delete = self._soledad.delete_doc(doc) - deferreds.append(delete) - d = defer.gatherResults(deferreds, consumeErrors=True) - else: - d = defer.succeed(None) - - d.addCallback( - lambda _: self._soledad.create_doc_from_json( - key.get_active_json(address))) - return d + def put_new_key(activedoc): + deferreds = [] + if activedoc: + d = self._soledad.delete_doc(activedoc) + deferreds.append(d) + for json in [key.get_json(), key.get_active_json(address)]: + d = self._soledad.create_doc_from_json(json) + deferreds.append(d) + return defer.gatherResults(deferreds) - d = self._soledad.get_from_index( - TYPE_ADDRESS_PRIVATE_INDEX, - self.ACTIVE_TYPE, - address, - '1' if key.private else '0') - d.addCallback(check_and_put) + dk = self._get_key_doc_from_keyid(key.key_id, key.private) + da = self._get_active_doc_from_address(address, key.private) + d = defer.gatherResults([dk, da]) + d.addCallback(merge_and_put) return d def _get_key_doc(self, address, private=False): @@ -533,44 +510,26 @@ class OpenPGPScheme(EncryptionScheme): :param private: Whether to look for a private key. :type private: bool - :return: A Deferred which fires with the SoledadDocument with the key - or None if it does not exist. + :return: A Deferred which fires with a touple of two SoledadDocument + (keydoc, activedoc) or None if it does not exist. :rtype: Deferred """ def get_key_from_active_doc(activedoc): - if len(activedoc) is 0: - return None - leap_assert( - len(activedoc) is 1, - 'Found more than one key for address %s!' % (address,)) - - key_id = activedoc[0].content[KEY_ID_KEY] - d = self._soledad.get_from_index( - TYPE_ID_PRIVATE_INDEX, - self.KEY_TYPE, - key_id, - '1' if private else '0') - d.addCallback(get_doc, key_id, activedoc) + if not activedoc: + return (None, None) + key_id = activedoc.content[KEY_ID_KEY] + d = self._get_key_doc_from_keyid(key_id, private) + d.addCallback(delete_active_if_no_key, activedoc) return d - def get_doc(doclist, key_id, activedoc): - if len(doclist) == 0: - logger.warning('There is no key for id %s! Self-repairing it.' - % (key_id)) + def delete_active_if_no_key(keydoc, activedoc): + if not keydoc: d = self._soledad.delete_doc(activedoc) - d.addCallback(lambda _: None) - return d - elif len(doclist) > 1: - d = self._repair_key_docs(doclist, key_id) - d.addCallback(lambda _: doclist[0]) + d.addCallback(lambda _: (None, None)) return d - return doclist[0] + return (keydoc, activedoc) - d = self._soledad.get_from_index( - TYPE_ADDRESS_PRIVATE_INDEX, - self.ACTIVE_TYPE, - address, - '1' if private else '0') + d = self._get_active_doc_from_address(address, private) d.addCallback(get_key_from_active_doc) return d @@ -647,36 +606,6 @@ class OpenPGPScheme(EncryptionScheme): d.addCallback(delete_key) return d - def _repair_key_docs(self, doclist, key_id): - """ - If there is more than one key for a key id try to self-repair it - - :return: a Deferred that will be fired once all the deletions are - completed - :rtype: Deferred - """ - logger.error("BUG ---------------------------------------------------") - logger.error("There is more than one key with the same key_id %s:" - % (key_id,)) - - def log_key_doc(doc): - logger.error("\t%s: %s" % (doc.content[KEY_ADDRESS_KEY], - doc.content[KEY_FINGERPRINT_KEY])) - - doclist.sort(key=lambda doc: doc.content[KEY_REFRESHED_AT_KEY], - reverse=True) - log_key_doc(doclist[0]) - deferreds = [] - for doc in doclist[1:]: - log_key_doc(doc) - d = self._soledad.delete_doc(doc) - deferreds.append(d) - - logger.error("") - logger.error(traceback.extract_stack()) - logger.error("BUG (please report above info) ------------------------") - return defer.gatherResults(deferreds, consumeErrors=True) - # # Data encryption, decryption, signing and verifying # @@ -885,6 +814,31 @@ class OpenPGPScheme(EncryptionScheme): kfprint = gpgpubkey['fingerprint'] return valid and rfprint == kfprint + def _get_active_doc_from_address(self, address, private): + d = self._soledad.get_from_index( + TYPE_ADDRESS_PRIVATE_INDEX, + self.ACTIVE_TYPE, + address, + '1' if private else '0') + d.addCallback(self._repair_and_get_doc, self._repair_active_docs) + return d + + def _get_key_doc_from_keyid(self, key_id, private): + d = self._soledad.get_from_index( + TYPE_ID_PRIVATE_INDEX, + self.KEY_TYPE, + key_id, + '1' if private else '0') + d.addCallback(self._repair_and_get_doc, self._repair_key_docs) + return d + + def _repair_and_get_doc(self, doclist, repair_func): + if len(doclist) is 0: + return None + elif len(doclist) > 1: + return repair_func(doclist) + return doclist[0] + def process_ascii_key(key_data, gpgbinary, secret=False): with TempGPGWrapper(gpgbinary=gpgbinary) as gpg: diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 0f08326e..e4e0d8b4 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -83,13 +83,18 @@ class KeyManagerUtilTestCase(unittest.TestCase): 'private': False, 'length': 4096, 'expiry_date': 0, - 'last_audited_at': 0, 'refreshed_at': 1311239602, + } + adict = { + 'address': ADDRESS, + 'key_id': KEY_FINGERPRINT[-16:], + 'private': False, + 'last_audited_at': 0, 'validation': str(ValidationLevels.Weak_Chain), 'encr_used': False, 'sign_used': True, } - key = build_key_from_dict(OpenPGPKey, kdict) + key = build_key_from_dict(OpenPGPKey, kdict, adict) self.assertEqual( kdict['address'], key.address, 'Wrong data in key.') @@ -118,13 +123,13 @@ class KeyManagerUtilTestCase(unittest.TestCase): datetime.fromtimestamp(kdict['refreshed_at']), key.refreshed_at, 'Wrong data in key.') self.assertEqual( - ValidationLevels.get(kdict['validation']), key.validation, + ValidationLevels.get(adict['validation']), key.validation, 'Wrong data in key.') self.assertEqual( - kdict['encr_used'], key.encr_used, + adict['encr_used'], key.encr_used, 'Wrong data in key.') self.assertEqual( - kdict['sign_used'], key.sign_used, + adict['sign_used'], key.sign_used, 'Wrong data in key.') diff --git a/keymanager/src/leap/keymanager/tests/test_openpgp.py b/keymanager/src/leap/keymanager/tests/test_openpgp.py index 96b40a0e..66415916 100644 --- a/keymanager/src/leap/keymanager/tests/test_openpgp.py +++ b/keymanager/src/leap/keymanager/tests/test_openpgp.py @@ -29,7 +29,10 @@ from leap.keymanager import ( KeyNotFound, openpgp, ) -from leap.keymanager.keys import TYPE_ID_PRIVATE_INDEX +from leap.keymanager.keys import ( + TYPE_ID_PRIVATE_INDEX, + TYPE_ADDRESS_PRIVATE_INDEX, +) from leap.keymanager.openpgp import OpenPGPKey from leap.keymanager.tests import ( KeyManagerWithSoledadTestCase, @@ -308,6 +311,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): try: yield self.assertFailure(pgp.get_key(ADDRESS, private=False), KeyNotFound) + # it should have deleted the index self.assertEqual(self._soledad.delete_doc.call_count, 1) finally: self._soledad.get_from_index = get_from_index @@ -349,6 +353,54 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): self._soledad.get_from_index = get_from_index self._soledad.delete_doc = delete_doc + @inlineCallbacks + def test_self_repair_five_active_docs(self): + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=self.gpg_binary_path) + + get_from_index = self._soledad.get_from_index + delete_doc = self._soledad.delete_doc + + def my_get_from_index(*args): + if (args[0] == TYPE_ADDRESS_PRIVATE_INDEX and + args[2] == ADDRESS): + k1 = OpenPGPKey(ADDRESS, key_id="1", + last_audited_at=datetime(2005, 1, 1)) + k2 = OpenPGPKey(ADDRESS, key_id="2", + last_audited_at=datetime(2007, 1, 1)) + k3 = OpenPGPKey(ADDRESS, key_id="3", + last_audited_at=datetime(2007, 1, 1), + encr_used=True, sign_used=True) + k4 = OpenPGPKey(ADDRESS, key_id="4", + last_audited_at=datetime(2007, 1, 1), + sign_used=True) + k5 = OpenPGPKey(ADDRESS, key_id="5", + last_audited_at=datetime(2007, 1, 1), + encr_used=True) + deferreds = [] + for k in [k1, k2, k3, k4, k5]: + d = self._soledad.create_doc_from_json( + k.get_active_json(ADDRESS)) + deferreds.append(d) + return gatherResults(deferreds) + elif args[0] == TYPE_ID_PRIVATE_INDEX: + key_id = args[2] + self.assertEqual(key_id, "3") + k = OpenPGPKey(ADDRESS, key_id="3") + return succeed( + [self._soledad.create_doc_from_json(k.get_json())]) + return get_from_index(*args) + + self._soledad.get_from_index = my_get_from_index + self._soledad.delete_doc = Mock(return_value=succeed(None)) + + try: + yield pgp.get_key(ADDRESS, private=False) + self.assertEqual(self._soledad.delete_doc.call_count, 4) + finally: + self._soledad.get_from_index = get_from_index + self._soledad.delete_doc = delete_doc + def _assert_key_not_found(self, pgp, address, private=False): d = pgp.get_key(address, private=private) return self.assertFailure(d, KeyNotFound) diff --git a/keymanager/src/leap/keymanager/tests/test_validation.py b/keymanager/src/leap/keymanager/tests/test_validation.py index bcf41c43..44d6e394 100644 --- a/keymanager/src/leap/keymanager/tests/test_validation.py +++ b/keymanager/src/leap/keymanager/tests/test_validation.py @@ -108,14 +108,14 @@ class ValidationLevelsTestCase(KeyManagerWithSoledadTestCase): TEXT = "some text" km = self._key_manager() + yield km.put_raw_key(UNEXPIRED_PRIVATE, OpenPGPKey, ADDRESS) + signature = yield km.sign(TEXT, ADDRESS, OpenPGPKey) + yield self.delete_all_keys(km) + yield km.put_raw_key(UNEXPIRED_KEY, OpenPGPKey, ADDRESS) yield km.encrypt(TEXT, ADDRESS, OpenPGPKey) - - km2 = self._key_manager() - yield km2.put_raw_key(UNEXPIRED_PRIVATE, OpenPGPKey, ADDRESS) - signature = yield km2.sign(TEXT, ADDRESS, OpenPGPKey) - yield km.verify(TEXT, ADDRESS, OpenPGPKey, detached_sig=signature) + d = km.put_raw_key( UNRELATED_KEY, OpenPGPKey, ADDRESS, validation=ValidationLevels.Provider_Endorsement) @@ -126,17 +126,17 @@ class ValidationLevelsTestCase(KeyManagerWithSoledadTestCase): TEXT = "some text" km = self._key_manager() + yield km.put_raw_key(UNEXPIRED_PRIVATE, OpenPGPKey, ADDRESS) + yield km.put_raw_key(PUBLIC_KEY_2, OpenPGPKey, ADDRESS_2) + encrypted = yield km.encrypt(TEXT, ADDRESS_2, OpenPGPKey, + sign=ADDRESS) + yield self.delete_all_keys(km) + yield km.put_raw_key(UNEXPIRED_KEY, OpenPGPKey, ADDRESS) yield km.put_raw_key(PRIVATE_KEY_2, OpenPGPKey, ADDRESS_2) yield km.encrypt(TEXT, ADDRESS, OpenPGPKey) - - km2 = self._key_manager() - yield km2.put_raw_key(UNEXPIRED_PRIVATE, OpenPGPKey, ADDRESS) - yield km2.put_raw_key(PUBLIC_KEY_2, OpenPGPKey, ADDRESS_2) - encrypted = yield km2.encrypt(TEXT, ADDRESS_2, OpenPGPKey, - sign=ADDRESS) - yield km.decrypt(encrypted, ADDRESS_2, OpenPGPKey, verify=ADDRESS) + d = km.put_raw_key( UNRELATED_KEY, OpenPGPKey, ADDRESS, validation=ValidationLevels.Provider_Endorsement) @@ -150,6 +150,33 @@ class ValidationLevelsTestCase(KeyManagerWithSoledadTestCase): key = yield km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) self.assertEqual(key.fingerprint, SIGNED_FINGERPRINT) + @inlineCallbacks + def test_two_uuids(self): + TEXT = "some text" + + km = self._key_manager() + yield km.put_raw_key(UUIDS_PRIVATE, OpenPGPKey, ADDRESS_2) + signature = yield km.sign(TEXT, ADDRESS_2, OpenPGPKey) + yield self.delete_all_keys(km) + + yield km.put_raw_key(UUIDS_KEY, OpenPGPKey, ADDRESS_2) + yield km.put_raw_key(UUIDS_KEY, OpenPGPKey, ADDRESS) + yield km.encrypt(TEXT, ADDRESS_2, OpenPGPKey) + yield km.verify(TEXT, ADDRESS_2, OpenPGPKey, detached_sig=signature) + + d = km.put_raw_key( + PUBLIC_KEY_2, OpenPGPKey, ADDRESS_2, + validation=ValidationLevels.Provider_Endorsement) + yield self.assertFailure(d, KeyNotValidUpgrade) + key = yield km.get_key(ADDRESS_2, OpenPGPKey, fetch_remote=False) + self.assertEqual(key.fingerprint, UUIDS_FINGERPRINT) + + yield km.put_raw_key( + PUBLIC_KEY, OpenPGPKey, ADDRESS, + validation=ValidationLevels.Provider_Endorsement) + key = yield km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + self.assertEqual(key.fingerprint, KEY_FINGERPRINT) + # Key material for testing @@ -364,6 +391,117 @@ X2+l7IOSt+31KQCBFN/VmhTySJOVQC1d2A56lSH2c/DWVClji+x3suzn -----END PGP PUBLIC KEY BLOCK----- """ +# key 0x1DDBAEB928D982F7: public key two uuids +# uid anotheruser +# uid Leap Test Key +UUIDS_FINGERPRINT = "21690ED054C1B2F3ACE963D38FCC7DEFB4EE5A9B" +UUIDS_KEY = """ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +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 +vtQLNMD9Gn7D +=MCXv +-----END PGP PUBLIC KEY BLOCK----- +""" +UUIDS_PRIVATE = """ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1 + +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 +eNWbiM9EvtQLNMD9Gn7D +=/3u/ +-----END PGP PRIVATE KEY BLOCK----- +""" + if __name__ == "__main__": - import unittest unittest.main() -- cgit v1.2.3 From a9ce3ba97c14fcee0cf41307ac3552f256fc1787 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Fri, 18 Dec 2015 19:37:44 +0100 Subject: [feat] Migrate soledad documents by adding versioning field - Resolves: #7713 --- keymanager/src/leap/keymanager/keys.py | 22 ++- keymanager/src/leap/keymanager/migrator.py | 171 +++++++++++++++++++++++ keymanager/src/leap/keymanager/tests/__init__.py | 2 +- 3 files changed, 190 insertions(+), 5 deletions(-) create mode 100644 keymanager/src/leap/keymanager/migrator.py (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py index 0de6a823..a60c19d3 100644 --- a/keymanager/src/leap/keymanager/keys.py +++ b/keymanager/src/leap/keymanager/keys.py @@ -45,6 +45,7 @@ logger = logging.getLogger(__name__) # Dictionary keys used for storing cryptographic keys. # +KEY_VERSION_KEY = 'version' KEY_ADDRESS_KEY = 'address' KEY_TYPE_KEY = 'type' KEY_ID_KEY = 'key_id' @@ -69,6 +70,10 @@ KEYMANAGER_KEY_TAG = 'keymanager-key' KEYMANAGER_ACTIVE_TAG = 'keymanager-active' KEYMANAGER_ACTIVE_TYPE = '-active' +# Version of the Soledad Document schema, +# it should be bumped each time the document format changes +KEYMANAGER_DOC_VERSION = 1 + # # key indexing constants. @@ -223,6 +228,7 @@ class EncryptionKey(object): KEY_LENGTH_KEY: self.length, KEY_EXPIRY_DATE_KEY: expiry_date, KEY_REFRESHED_AT_KEY: refreshed_at, + KEY_VERSION_KEY: KEYMANAGER_DOC_VERSION, KEY_TAGS_KEY: [KEYMANAGER_KEY_TAG], }) @@ -244,6 +250,7 @@ class EncryptionKey(object): KEY_LAST_AUDITED_AT_KEY: last_audited_at, KEY_ENCR_USED_KEY: self.encr_used, KEY_SIGN_USED_KEY: self.sign_used, + KEY_VERSION_KEY: KEYMANAGER_DOC_VERSION, KEY_TAGS_KEY: [KEYMANAGER_ACTIVE_TAG], }) @@ -281,7 +288,8 @@ class EncryptionScheme(object): :type soledad: leap.soledad.Soledad """ self._soledad = soledad - self._init_indexes() + self.deferred_init = self._init_indexes() + self.deferred_init.addCallback(self._migrate_documents_schema) def _init_indexes(self): """ @@ -309,8 +317,14 @@ class EncryptionScheme(object): deferreds.append(d) return defer.gatherResults(deferreds, consumeErrors=True) - self.deferred_indexes = self._soledad.list_indexes() - self.deferred_indexes.addCallback(init_idexes) + d = self._soledad.list_indexes() + d.addCallback(init_idexes) + return d + + def _migrate_documents_schema(self, _): + from leap.keymanager.migrator import KeyDocumentsMigrator + migrator = KeyDocumentsMigrator(self._soledad) + return migrator.migrate() def _wait_indexes(self, *methods): """ @@ -343,7 +357,7 @@ class EncryptionScheme(object): self.stored[method] = getattr(self, method) setattr(self, method, makeWrapper(method)) - self.deferred_indexes.addCallback(restore) + self.deferred_init.addCallback(restore) @abstractmethod def get_key(self, address, private=False): diff --git a/keymanager/src/leap/keymanager/migrator.py b/keymanager/src/leap/keymanager/migrator.py new file mode 100644 index 00000000..b59647a2 --- /dev/null +++ b/keymanager/src/leap/keymanager/migrator.py @@ -0,0 +1,171 @@ +# -*- coding: utf-8 -*- +# migrator.py +# Copyright (C) 2015 LEAP +# +# 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 . + +""" +Document migrator +""" +# XXX: versioning has being added 12/2015 when keymanager was not +# much in use in the wild. We can probably drop support for +# keys without version at some point. + + +from collections import namedtuple +from twisted.internet.defer import gatherResults, succeed + +from leap.keymanager.keys import ( + TAGS_PRIVATE_INDEX, + KEYMANAGER_KEY_TAG, + KEYMANAGER_ACTIVE_TAG, + + KEYMANAGER_DOC_VERSION, + KEY_VERSION_KEY, + KEY_ID_KEY, + KEY_VALIDATION_KEY, + KEY_LAST_AUDITED_AT_KEY, + KEY_ENCR_USED_KEY, + KEY_SIGN_USED_KEY, +) +from leap.keymanager.validation import ValidationLevels + + +KeyDocs = namedtuple("KeyDocs", ['key', 'active']) + + +class KeyDocumentsMigrator(object): + """ + Migrate old KeyManager Soledad Documents to the newest schema + """ + + def __init__(self, soledad): + self._soledad = soledad + + def migrate(self): + deferred_public = self._get_docs(private=False) + deferred_public.addCallback(self._migrate_docs) + + deferred_private = self._get_docs(private=True) + deferred_private.addCallback(self._migrate_docs) + + return gatherResults([deferred_public, deferred_private]) + + def _get_docs(self, private=False): + private_value = '1' if private else '0' + + deferred_keys = self._soledad.get_from_index( + TAGS_PRIVATE_INDEX, + KEYMANAGER_KEY_TAG, + private_value) + deferred_active = self._soledad.get_from_index( + TAGS_PRIVATE_INDEX, + KEYMANAGER_ACTIVE_TAG, + private_value) + return gatherResults([deferred_keys, deferred_active]) + + def _migrate_docs(self, (key_docs, active_docs)): + def update_keys(keys): + deferreds = [] + for key_id in keys: + key = keys[key_id].key + actives = keys[key_id].active + + d = self._migrate_actives(key, actives) + deferreds.append(d) + + d = self._migrate_key(key) + deferreds.append(d) + return gatherResults(deferreds) + + d = self._buildKeyDict(key_docs, active_docs) + d.addCallback(lambda keydict: self._filter_outdated(keydict)) + d.addCallback(update_keys) + + def _buildKeyDict(self, keys, actives): + keydict = { + fp2id(key.content[KEY_FINGERPRINT_KEY]): KeyDocs(key, []) + for key in keys} + + deferreds = [] + for active in actives: + if KEY_ID_KEY in active.content: + key_id = active.content[KEY_ID_KEY] + if key_id not in keydict: + d = self._soledad.delete_doc(active) + deferreds.append(d) + continue + keydict[key_id].active.append(active) + + d = gatherResults(deferreds) + d.addCallback(lambda _: keydict) + return d + + def _filter_outdated(self, keydict): + outdated = {} + for key_id, docs in keydict.items(): + if ((docs.key and KEY_VERSION_KEY not in docs.key.content) or + docs.active): + outdated[key_id] = docs + return outdated + + def _migrate_actives(self, key, actives): + if not key: + deferreds = [] + for active in actives: + d = self._soledad.delete_doc(active) + deferreds.append(d) + return gatherResults(deferreds) + + validation = str(ValidationLevels.Weak_Chain) + last_audited = 0 + encr_used = False + sign_used = False + if len(actives) == 1 and KEY_VERSION_KEY not in key.content: + # we can preserve the validation of the key if there is only one + # active address for the key + validation = key.content[KEY_VALIDATION_KEY] + last_audited = key.content[KEY_LAST_AUDITED_AT_KEY] + encr_used = key.content[KEY_ENCR_USED_KEY] + sign_used = key.content[KEY_SIGN_USED_KEY] + + deferreds = [] + for active in actives: + if KEY_VERSION_KEY in active.content: + continue + + active.content[KEY_VERSION_KEY] = KEYMANAGER_DOC_VERSION + active.content[KEY_VALIDATION_KEY] = validation + active.content[KEY_LAST_AUDITED_AT_KEY] = last_audited + active.content[KEY_ENCR_USED_KEY] = encr_used + active.content[KEY_SIGN_USED_KEY] = sign_used + d = self._soledad.put_doc(active) + deferreds.append(d) + return gatherResults(deferreds) + + def _migrate_key(self, key): + if not key or KEY_VERSION_KEY in key.content: + return succeed(None) + + key.content[KEY_VERSION_KEY] = KEYMANAGER_DOC_VERSION + del key.content[KEY_VALIDATION_KEY] + del key.content[KEY_LAST_AUDITED_AT_KEY] + del key.content[KEY_ENCR_USED_KEY] + del key.content[KEY_SIGN_USED_KEY] + return self._soledad.put_doc(key) + + +def fp2id(fingerprint): + KEY_ID_LENGTH = 16 + return fingerprint[-KEY_ID_LENGTH:] diff --git a/keymanager/src/leap/keymanager/tests/__init__.py b/keymanager/src/leap/keymanager/tests/__init__.py index cd612c43..d02f187f 100644 --- a/keymanager/src/leap/keymanager/tests/__init__.py +++ b/keymanager/src/leap/keymanager/tests/__init__.py @@ -76,7 +76,7 @@ class KeyManagerWithSoledadTestCase(unittest.TestCase, BaseLeapTest): return d # wait for the indexes to be ready for the tear down - d = km._wrapper_map[OpenPGPKey].deferred_indexes + d = km._wrapper_map[OpenPGPKey].deferred_init d.addCallback(get_and_delete_keys) d.addCallback(lambda _: self.tearDownEnv()) d.addCallback(lambda _: self._soledad.close()) -- cgit v1.2.3 From 56b12a84635bd99ed8cca4db9baf5e4160e8d13b Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Fri, 18 Dec 2015 20:31:18 +0100 Subject: [feat] Use fingerprints instead of key ids - Resolves: #7500 --- keymanager/src/leap/keymanager/__init__.py | 6 +- keymanager/src/leap/keymanager/keys.py | 21 ++-- keymanager/src/leap/keymanager/migrator.py | 8 +- keymanager/src/leap/keymanager/openpgp.py | 43 ++++--- keymanager/src/leap/keymanager/tests/__init__.py | 1 - .../src/leap/keymanager/tests/test_keymanager.py | 5 - .../src/leap/keymanager/tests/test_openpgp.py | 123 ++++++++------------- keymanager/src/leap/keymanager/validation.py | 4 +- 8 files changed, 91 insertions(+), 120 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 8b3487f9..8a4efbe9 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -642,7 +642,7 @@ class KeyManager(object): else: signature = InvalidSignature( 'Failed to verify signature with key %s' % - (pubkey.key_id,)) + (pubkey.fingerprint,)) defer.returnValue((decrypted, signature)) dpriv = self.get_key(address, ktype, private=True) @@ -741,7 +741,7 @@ class KeyManager(object): else: raise InvalidSignature( 'Failed to verify signature with key %s' % - (pubkey.key_id,)) + (pubkey.fingerprint,)) d = self.get_key(address, ktype, private=False, fetch_remote=fetch_remote) @@ -804,7 +804,7 @@ class KeyManager(object): else: raise KeyNotValidUpgrade( "Key %s can not be upgraded by new key %s" - % (old_key.key_id, key.key_id)) + % (old_key.fingerprint, key.fingerprint)) d = _keys.get_key(address, private=key.private) d.addErrback(old_key_not_found) diff --git a/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py index a60c19d3..68e3fada 100644 --- a/keymanager/src/leap/keymanager/keys.py +++ b/keymanager/src/leap/keymanager/keys.py @@ -48,7 +48,6 @@ logger = logging.getLogger(__name__) KEY_VERSION_KEY = 'version' KEY_ADDRESS_KEY = 'address' KEY_TYPE_KEY = 'type' -KEY_ID_KEY = 'key_id' KEY_FINGERPRINT_KEY = 'fingerprint' KEY_DATA_KEY = 'key_data' KEY_PRIVATE_KEY = 'private' @@ -80,16 +79,16 @@ KEYMANAGER_DOC_VERSION = 1 # TAGS_PRIVATE_INDEX = 'by-tags-private' -TYPE_ID_PRIVATE_INDEX = 'by-type-id-private' +TYPE_FINGERPRINT_PRIVATE_INDEX = 'by-type-fingerprint-private' TYPE_ADDRESS_PRIVATE_INDEX = 'by-type-address-private' INDEXES = { TAGS_PRIVATE_INDEX: [ KEY_TAGS_KEY, 'bool(%s)' % KEY_PRIVATE_KEY, ], - TYPE_ID_PRIVATE_INDEX: [ + TYPE_FINGERPRINT_PRIVATE_INDEX: [ KEY_TYPE_KEY, - KEY_ID_KEY, + KEY_FINGERPRINT_KEY, 'bool(%s)' % KEY_PRIVATE_KEY, ], TYPE_ADDRESS_PRIVATE_INDEX: [ @@ -137,7 +136,8 @@ def build_key_from_dict(kClass, key, active=None): validation = ValidationLevels.get(active[KEY_VALIDATION_KEY]) except ValueError: logger.error("Not valid validation level (%s) for key %s", - (active[KEY_VALIDATION_KEY], active[KEY_ID_KEY])) + (active[KEY_VALIDATION_KEY], + active[KEY_FINGERPRINT_KEY])) last_audited_at = _to_datetime(active[KEY_LAST_AUDITED_AT_KEY]) encr_used = active[KEY_ENCR_USED_KEY] sign_used = active[KEY_SIGN_USED_KEY] @@ -147,7 +147,6 @@ def build_key_from_dict(kClass, key, active=None): return kClass( key[KEY_ADDRESS_KEY], - key_id=key[KEY_ID_KEY], fingerprint=key[KEY_FINGERPRINT_KEY], key_data=key[KEY_DATA_KEY], private=key[KEY_PRIVATE_KEY], @@ -189,13 +188,12 @@ class EncryptionKey(object): __metaclass__ = ABCMeta - def __init__(self, address, key_id="", fingerprint="", + def __init__(self, address, fingerprint="", key_data="", private=False, length=0, expiry_date=None, validation=ValidationLevels.Weak_Chain, last_audited_at=None, refreshed_at=None, encr_used=False, sign_used=False): # TODO: it should know its own active address self.address = address - self.key_id = key_id self.fingerprint = fingerprint self.key_data = key_data self.private = private @@ -221,7 +219,6 @@ class EncryptionKey(object): return json.dumps({ KEY_ADDRESS_KEY: self.address, KEY_TYPE_KEY: self.__class__.__name__, - KEY_ID_KEY: self.key_id, KEY_FINGERPRINT_KEY: self.fingerprint, KEY_DATA_KEY: self.key_data, KEY_PRIVATE_KEY: self.private, @@ -244,7 +241,7 @@ class EncryptionKey(object): return json.dumps({ KEY_ADDRESS_KEY: address, KEY_TYPE_KEY: self.__class__.__name__ + KEYMANAGER_ACTIVE_TYPE, - KEY_ID_KEY: self.key_id, + KEY_FINGERPRINT_KEY: self.fingerprint, KEY_PRIVATE_KEY: self.private, KEY_VALIDATION_KEY: str(self.validation), KEY_LAST_AUDITED_AT_KEY: last_audited_at, @@ -260,7 +257,7 @@ class EncryptionKey(object): """ return u"<%s 0x%s (%s - %s)>" % ( self.__class__.__name__, - self.key_id, + self.fingerprint, self.address, "priv" if self.private else "publ") @@ -519,7 +516,7 @@ class EncryptionScheme(object): """ def log_active_doc(doc): logger.error("\t%s: %s" % (doc.content[KEY_ADDRESS_KEY], - doc.content[KEY_ID_KEY])) + doc.content[KEY_FINGERPRINT_KEY])) def cmp_active(d1, d2): res = cmp(d1.content[KEY_LAST_AUDITED_AT_KEY], diff --git a/keymanager/src/leap/keymanager/migrator.py b/keymanager/src/leap/keymanager/migrator.py index b59647a2..11cf2439 100644 --- a/keymanager/src/leap/keymanager/migrator.py +++ b/keymanager/src/leap/keymanager/migrator.py @@ -33,7 +33,7 @@ from leap.keymanager.keys import ( KEYMANAGER_DOC_VERSION, KEY_VERSION_KEY, - KEY_ID_KEY, + KEY_FINGERPRINT_KEY, KEY_VALIDATION_KEY, KEY_LAST_AUDITED_AT_KEY, KEY_ENCR_USED_KEY, @@ -42,6 +42,8 @@ from leap.keymanager.keys import ( from leap.keymanager.validation import ValidationLevels +KEY_ID_KEY = 'key_id' + KeyDocs = namedtuple("KeyDocs", ['key', 'active']) @@ -132,6 +134,7 @@ class KeyDocumentsMigrator(object): last_audited = 0 encr_used = False sign_used = False + fingerprint = key.content[KEY_FINGERPRINT_KEY] if len(actives) == 1 and KEY_VERSION_KEY not in key.content: # we can preserve the validation of the key if there is only one # active address for the key @@ -146,10 +149,12 @@ class KeyDocumentsMigrator(object): continue active.content[KEY_VERSION_KEY] = KEYMANAGER_DOC_VERSION + active.content[KEY_FINGERPRINT_KEY] = fingerprint active.content[KEY_VALIDATION_KEY] = validation active.content[KEY_LAST_AUDITED_AT_KEY] = last_audited active.content[KEY_ENCR_USED_KEY] = encr_used active.content[KEY_SIGN_USED_KEY] = sign_used + del active.content[KEY_ID_KEY] d = self._soledad.put_doc(active) deferreds.append(d) return gatherResults(deferreds) @@ -159,6 +164,7 @@ class KeyDocumentsMigrator(object): return succeed(None) key.content[KEY_VERSION_KEY] = KEYMANAGER_DOC_VERSION + del key.content[KEY_ID_KEY] del key.content[KEY_VALIDATION_KEY] del key.content[KEY_LAST_AUDITED_AT_KEY] del key.content[KEY_ENCR_USED_KEY] diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 3c8ac1e1..0f162969 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -39,10 +39,10 @@ from leap.keymanager.keys import ( EncryptionScheme, is_address, build_key_from_dict, - TYPE_ID_PRIVATE_INDEX, + TYPE_FINGERPRINT_PRIVATE_INDEX, TYPE_ADDRESS_PRIVATE_INDEX, KEY_ADDRESS_KEY, - KEY_ID_KEY, + KEY_FINGERPRINT_KEY, KEYMANAGER_ACTIVE_TYPE, ) @@ -122,9 +122,9 @@ class TempGPGWrapper(object): # itself is enough to also have the public key in the keyring, # and we want to count the keys afterwards. - privids = map(lambda privkey: privkey.key_id, privkeys) + privfps = map(lambda privkey: privkey.fingerprint, privkeys) publkeys = filter( - lambda pubkey: pubkey.key_id not in privids, publkeys) + lambda pubkey: pubkey.fingerprint not in privfps, publkeys) listkeys = lambda: self._gpg.list_keys() listsecretkeys = lambda: self._gpg.list_keys(secret=True) @@ -213,7 +213,7 @@ class OpenPGPKey(EncryptionKey): :rtype: list(str) """ with TempGPGWrapper(keys=[self], gpgbinary=self._gpgbinary) as gpg: - res = gpg.list_sigs(self.key_id) + res = gpg.list_sigs(self.fingerprint) for uid, sigs in res.sigs.iteritems(): if _parse_address(uid) in self.address: return sigs @@ -370,7 +370,7 @@ class OpenPGPScheme(EncryptionScheme): leap_assert( address in keydoc.content[KEY_ADDRESS_KEY], 'Wrong address in key %s. Expected %s, found %s.' - % (keydoc.content[KEY_ID_KEY], address, + % (keydoc.content[KEY_FINGERPRINT_KEY], address, keydoc.content[KEY_ADDRESS_KEY])) key = build_key_from_dict(OpenPGPKey, keydoc.content, activedoc.content) @@ -493,7 +493,7 @@ class OpenPGPScheme(EncryptionScheme): deferreds.append(d) return defer.gatherResults(deferreds) - dk = self._get_key_doc_from_keyid(key.key_id, key.private) + dk = self._get_key_doc_from_fingerprint(key.fingerprint, key.private) da = self._get_active_doc_from_address(address, key.private) d = defer.gatherResults([dk, da]) d.addCallback(merge_and_put) @@ -517,8 +517,8 @@ class OpenPGPScheme(EncryptionScheme): def get_key_from_active_doc(activedoc): if not activedoc: return (None, None) - key_id = activedoc.content[KEY_ID_KEY] - d = self._get_key_doc_from_keyid(key_id, private) + fingerprint = activedoc.content[KEY_FINGERPRINT_KEY] + d = self._get_key_doc_from_fingerprint(fingerprint, private) d.addCallback(delete_active_if_no_key, activedoc) return d @@ -573,17 +573,17 @@ class OpenPGPScheme(EncryptionScheme): def get_key_docs(_): return self._soledad.get_from_index( - TYPE_ID_PRIVATE_INDEX, + TYPE_FINGERPRINT_PRIVATE_INDEX, self.KEY_TYPE, - key.key_id, + key.fingerprint, '1' if key.private else '0') def delete_key(docs): if len(docs) == 0: raise errors.KeyNotFound(key) elif len(docs) > 1: - logger.warning("There is more than one key for key_id %s" - % key.key_id) + logger.warning("There is more than one key for fingerprint %s" + % key.fingerprint) has_deleted = False deferreds = [] @@ -597,9 +597,9 @@ class OpenPGPScheme(EncryptionScheme): return defer.gatherResults(deferreds) d = self._soledad.get_from_index( - TYPE_ID_PRIVATE_INDEX, + TYPE_FINGERPRINT_PRIVATE_INDEX, self.ACTIVE_TYPE, - key.key_id, + key.fingerprint, '1' if key.private else '0') d.addCallback(delete_docs) d.addCallback(get_key_docs) @@ -659,7 +659,7 @@ class OpenPGPScheme(EncryptionScheme): result = yield from_thread( gpg.encrypt, data, pubkey.fingerprint, - default_key=sign.key_id if sign else None, + default_key=sign.fingerprint if sign else None, passphrase=passphrase, symmetric=False, cipher_algo=cipher_algo) # Here we cannot assert for correctness of sig because the sig is @@ -761,7 +761,7 @@ class OpenPGPScheme(EncryptionScheme): # result.fingerprint - contains the fingerprint of the key used to # sign. with TempGPGWrapper(privkey, self._gpgbinary) as gpg: - result = gpg.sign(data, default_key=privkey.key_id, + result = gpg.sign(data, default_key=privkey.fingerprint, digest_algo=digest_algo, clearsign=clearsign, detach=detach, binary=binary) rfprint = privkey.fingerprint @@ -770,7 +770,7 @@ class OpenPGPScheme(EncryptionScheme): if result.fingerprint is None: raise errors.SignFailed( 'Failed to sign with key %s: %s' % - (privkey['keyid'], result.stderr)) + (privkey['fingerprint'], result.stderr)) leap_assert( result.fingerprint == kfprint, 'Signature and private key fingerprints mismatch: ' @@ -823,11 +823,11 @@ class OpenPGPScheme(EncryptionScheme): d.addCallback(self._repair_and_get_doc, self._repair_active_docs) return d - def _get_key_doc_from_keyid(self, key_id, private): + def _get_key_doc_from_fingerprint(self, fingerprint, private): d = self._soledad.get_from_index( - TYPE_ID_PRIVATE_INDEX, + TYPE_FINGERPRINT_PRIVATE_INDEX, self.KEY_TYPE, - key_id, + fingerprint, '1' if private else '0') d.addCallback(self._repair_and_get_doc, self._repair_key_docs) return d @@ -863,7 +863,6 @@ def build_gpg_key(key_info, key_data, gpgbinary=None): return OpenPGPKey( address, gpgbinary=gpgbinary, - key_id=key_info['keyid'], fingerprint=key_info['fingerprint'], key_data=key_data, private=True if key_info['type'] == 'sec' else False, diff --git a/keymanager/src/leap/keymanager/tests/__init__.py b/keymanager/src/leap/keymanager/tests/__init__.py index d02f187f..4fbf63ee 100644 --- a/keymanager/src/leap/keymanager/tests/__init__.py +++ b/keymanager/src/leap/keymanager/tests/__init__.py @@ -97,7 +97,6 @@ class KeyManagerWithSoledadTestCase(unittest.TestCase, BaseLeapTest): # key 24D18DDF: public key "Leap Test Key " -KEY_ID = "2F455E2824D18DDF" KEY_FINGERPRINT = "E36E738D69173C13D709E44F2F455E2824D18DDF" PUBLIC_KEY = """ -----BEGIN PGP PUBLIC KEY BLOCK----- diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index e4e0d8b4..2fe9e4cd 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -77,7 +77,6 @@ class KeyManagerUtilTestCase(unittest.TestCase): def test_build_key_from_dict(self): kdict = { 'address': [ADDRESS], - 'key_id': KEY_FINGERPRINT[-16:], 'fingerprint': KEY_FINGERPRINT, 'key_data': PUBLIC_KEY, 'private': False, @@ -87,7 +86,6 @@ class KeyManagerUtilTestCase(unittest.TestCase): } adict = { 'address': ADDRESS, - 'key_id': KEY_FINGERPRINT[-16:], 'private': False, 'last_audited_at': 0, 'validation': str(ValidationLevels.Weak_Chain), @@ -98,9 +96,6 @@ class KeyManagerUtilTestCase(unittest.TestCase): self.assertEqual( kdict['address'], key.address, 'Wrong data in key.') - self.assertEqual( - kdict['key_id'], key.key_id, - 'Wrong data in key.') self.assertEqual( kdict['fingerprint'], key.fingerprint, 'Wrong data in key.') diff --git a/keymanager/src/leap/keymanager/tests/test_openpgp.py b/keymanager/src/leap/keymanager/tests/test_openpgp.py index 66415916..8ed049f6 100644 --- a/keymanager/src/leap/keymanager/tests/test_openpgp.py +++ b/keymanager/src/leap/keymanager/tests/test_openpgp.py @@ -30,7 +30,7 @@ from leap.keymanager import ( openpgp, ) from leap.keymanager.keys import ( - TYPE_ID_PRIVATE_INDEX, + TYPE_FINGERPRINT_PRIVATE_INDEX, TYPE_ADDRESS_PRIVATE_INDEX, ) from leap.keymanager.openpgp import OpenPGPKey @@ -40,7 +40,6 @@ from leap.keymanager.tests import ( ADDRESS_2, KEY_FINGERPRINT, PUBLIC_KEY, - KEY_ID, PUBLIC_KEY_2, PRIVATE_KEY, PRIVATE_KEY_2, @@ -256,39 +255,18 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): @inlineCallbacks def test_self_repair_three_keys(self): + refreshed_keep = datetime(2007, 1, 1) + self._insert_key_docs([datetime(2005, 1, 1), + refreshed_keep, + datetime(2001, 1, 1)]) + delete_doc = self._mock_delete_doc() + pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=self.gpg_binary_path) - yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) - - get_from_index = self._soledad.get_from_index - delete_doc = self._soledad.delete_doc - - def my_get_from_index(*args): - if (args[0] == TYPE_ID_PRIVATE_INDEX and - args[2] == KEY_ID): - k1 = OpenPGPKey(ADDRESS, key_id="1", - refreshed_at=datetime(2005, 1, 1)) - k2 = OpenPGPKey(ADDRESS, key_id="2", - refreshed_at=datetime(2007, 1, 1)) - k3 = OpenPGPKey(ADDRESS, key_id="3", - refreshed_at=datetime(2001, 1, 1)) - d1 = self._soledad.create_doc_from_json(k1.get_json()) - d2 = self._soledad.create_doc_from_json(k2.get_json()) - d3 = self._soledad.create_doc_from_json(k3.get_json()) - return gatherResults([d1, d2, d3]) - return get_from_index(*args) - - self._soledad.get_from_index = my_get_from_index - self._soledad.delete_doc = Mock(return_value=succeed(None)) - key = yield pgp.get_key(ADDRESS, private=False) - - try: - self.assertEqual(key.key_id, "2") - self.assertEqual(self._soledad.delete_doc.call_count, 2) - finally: - self._soledad.get_from_index = get_from_index - self._soledad.delete_doc = delete_doc + self.assertEqual(key.refreshed_at, refreshed_keep) + self.assertEqual(self.count, 2) + self._soledad.delete_doc = delete_doc @inlineCallbacks def test_self_repair_no_keys(self): @@ -300,8 +278,8 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): delete_doc = self._soledad.delete_doc def my_get_from_index(*args): - if (args[0] == TYPE_ID_PRIVATE_INDEX and - args[2] == KEY_ID): + if (args[0] == TYPE_FINGERPRINT_PRIVATE_INDEX and + args[2] == KEY_FINGERPRINT): return succeed([]) return get_from_index(*args) @@ -319,39 +297,16 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): @inlineCallbacks def test_self_repair_put_keys(self): + self._insert_key_docs([datetime(2005, 1, 1), + datetime(2007, 1, 1), + datetime(2001, 1, 1)]) + delete_doc = self._mock_delete_doc() + pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=self.gpg_binary_path) - - get_from_index = self._soledad.get_from_index - delete_doc = self._soledad.delete_doc - - def my_get_from_index(*args): - if (args[0] == TYPE_ID_PRIVATE_INDEX and - args[2] == KEY_ID): - k1 = OpenPGPKey(ADDRESS, key_id="1", - fingerprint=KEY_FINGERPRINT, - refreshed_at=datetime(2005, 1, 1)) - k2 = OpenPGPKey(ADDRESS, key_id="2", - fingerprint=KEY_FINGERPRINT, - refreshed_at=datetime(2007, 1, 1)) - k3 = OpenPGPKey(ADDRESS, key_id="3", - fingerprint=KEY_FINGERPRINT, - refreshed_at=datetime(2001, 1, 1)) - d1 = self._soledad.create_doc_from_json(k1.get_json()) - d2 = self._soledad.create_doc_from_json(k2.get_json()) - d3 = self._soledad.create_doc_from_json(k3.get_json()) - return gatherResults([d1, d2, d3]) - return get_from_index(*args) - - self._soledad.get_from_index = my_get_from_index - self._soledad.delete_doc = Mock(return_value=succeed(None)) - - try: - yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) - self.assertEqual(self._soledad.delete_doc.call_count, 2) - finally: - self._soledad.get_from_index = get_from_index - self._soledad.delete_doc = delete_doc + yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) + self.assertEqual(self.count, 2) + self._soledad.delete_doc = delete_doc @inlineCallbacks def test_self_repair_five_active_docs(self): @@ -364,29 +319,29 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def my_get_from_index(*args): if (args[0] == TYPE_ADDRESS_PRIVATE_INDEX and args[2] == ADDRESS): - k1 = OpenPGPKey(ADDRESS, key_id="1", + k1 = OpenPGPKey(ADDRESS, fingerprint="1", last_audited_at=datetime(2005, 1, 1)) - k2 = OpenPGPKey(ADDRESS, key_id="2", + k2 = OpenPGPKey(ADDRESS, fingerprint="2", last_audited_at=datetime(2007, 1, 1)) - k3 = OpenPGPKey(ADDRESS, key_id="3", + k3 = OpenPGPKey(ADDRESS, fingerprint="3", last_audited_at=datetime(2007, 1, 1), encr_used=True, sign_used=True) - k4 = OpenPGPKey(ADDRESS, key_id="4", + k4 = OpenPGPKey(ADDRESS, fingerprint="4", last_audited_at=datetime(2007, 1, 1), sign_used=True) - k5 = OpenPGPKey(ADDRESS, key_id="5", + k5 = OpenPGPKey(ADDRESS, fingerprint="5", last_audited_at=datetime(2007, 1, 1), encr_used=True) deferreds = [] - for k in [k1, k2, k3, k4, k5]: + for k in (k1, k2, k3, k4, k5): d = self._soledad.create_doc_from_json( k.get_active_json(ADDRESS)) deferreds.append(d) return gatherResults(deferreds) - elif args[0] == TYPE_ID_PRIVATE_INDEX: - key_id = args[2] - self.assertEqual(key_id, "3") - k = OpenPGPKey(ADDRESS, key_id="3") + elif args[0] == TYPE_FINGERPRINT_PRIVATE_INDEX: + fingerprint = args[2] + self.assertEqual(fingerprint, "3") + k = OpenPGPKey(ADDRESS, fingerprint="3") return succeed( [self._soledad.create_doc_from_json(k.get_json())]) return get_from_index(*args) @@ -404,3 +359,21 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def _assert_key_not_found(self, pgp, address, private=False): d = pgp.get_key(address, private=private) return self.assertFailure(d, KeyNotFound) + + @inlineCallbacks + def _insert_key_docs(self, refreshed_at): + for date in refreshed_at: + key = OpenPGPKey(ADDRESS, fingerprint=KEY_FINGERPRINT, + refreshed_at=date) + yield self._soledad.create_doc_from_json(key.get_json()) + yield self._soledad.create_doc_from_json(key.get_active_json()) + + def _mock_delete_doc(self): + delete_doc = self._soledad.delete_doc + self.count = 0 + + def my_delete_doc(*args): + self.count += 1 + return delete_doc(*args) + self._soledad.delete_doc = my_delete_doc + return delete_doc diff --git a/keymanager/src/leap/keymanager/validation.py b/keymanager/src/leap/keymanager/validation.py index 734cfce9..8cf96da1 100644 --- a/keymanager/src/leap/keymanager/validation.py +++ b/keymanager/src/leap/keymanager/validation.py @@ -118,7 +118,9 @@ def can_upgrade(new_key, old_key): return True # New key signed by the old key - if old_key.key_id in new_key.signatures: + # XXX: signatures are using key-ids instead of fingerprints + key_id = old_key.fingerprint[-16:] + if key_id in new_key.signatures: return True return False -- cgit v1.2.3 From ca505182f9664088ef360b8965902f1fbbcd08d3 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 21 Dec 2015 20:24:52 +0100 Subject: [test] add updater tests --- .../src/leap/keymanager/tests/test_migrator.py | 175 +++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 keymanager/src/leap/keymanager/tests/test_migrator.py (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/tests/test_migrator.py b/keymanager/src/leap/keymanager/tests/test_migrator.py new file mode 100644 index 00000000..2d9782b5 --- /dev/null +++ b/keymanager/src/leap/keymanager/tests/test_migrator.py @@ -0,0 +1,175 @@ +# -*- coding: utf-8 -*- +# test_migrator.py +# Copyright (C) 2015 LEAP +# +# 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 . + + +""" +Tests for the migrator. +""" + + +from collections import namedtuple +from mock import Mock +from twisted.internet.defer import succeed, inlineCallbacks + +from leap.keymanager.migrator import KeyDocumentsMigrator, KEY_ID_KEY +from leap.keymanager.keys import ( + TAGS_PRIVATE_INDEX, + KEYMANAGER_ACTIVE_TAG, + KEYMANAGER_KEY_TAG, + KEYMANAGER_DOC_VERSION, + + KEY_ADDRESS_KEY, + KEY_UIDS_KEY, + KEY_VERSION_KEY, + KEY_FINGERPRINT_KEY, + KEY_VALIDATION_KEY, + KEY_LAST_AUDITED_AT_KEY, + KEY_ENCR_USED_KEY, + KEY_SIGN_USED_KEY, +) +from leap.keymanager.validation import ValidationLevels +from leap.keymanager.tests import ( + KeyManagerWithSoledadTestCase, + ADDRESS, + ADDRESS_2, + KEY_FINGERPRINT, +) + + +class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): + @inlineCallbacks + def test_simple_migration(self): + get_from_index = self._soledad.get_from_index + delete_doc = self._soledad.delete_doc + put_doc = self._soledad.put_doc + + def my_get_from_index(*args): + docs = [] + if (args[0] == TAGS_PRIVATE_INDEX and + args[2] == '0'): + SoledadDocument = namedtuple("SoledadDocument", ["content"]) + if args[1] == KEYMANAGER_KEY_TAG: + docs = [SoledadDocument({ + KEY_ADDRESS_KEY: [ADDRESS], + KEY_ID_KEY: KEY_FINGERPRINT[-16:], + KEY_FINGERPRINT_KEY: KEY_FINGERPRINT, + KEY_VALIDATION_KEY: str(ValidationLevels.Weak_Chain), + KEY_LAST_AUDITED_AT_KEY: 0, + KEY_ENCR_USED_KEY: True, + KEY_SIGN_USED_KEY: False, + })] + if args[1] == KEYMANAGER_ACTIVE_TAG: + docs = [SoledadDocument({ + KEY_ID_KEY: KEY_FINGERPRINT[-16:], + })] + return succeed(docs) + + self._soledad.get_from_index = my_get_from_index + self._soledad.delete_doc = Mock(return_value=succeed(None)) + self._soledad.put_doc = Mock(return_value=succeed(None)) + + try: + migrator = KeyDocumentsMigrator(self._soledad) + yield migrator.migrate() + call_list = self._soledad.put_doc.call_args_list + finally: + self._soledad.get_from_index = get_from_index + self._soledad.delete_doc = delete_doc + self._soledad.put_doc = put_doc + + self.assertEqual(len(call_list), 2) + active = call_list[0][0][0] + key = call_list[1][0][0] + + self.assertTrue(KEY_ID_KEY not in active.content) + self.assertEqual(active.content[KEY_VERSION_KEY], + KEYMANAGER_DOC_VERSION) + self.assertEqual(active.content[KEY_FINGERPRINT_KEY], KEY_FINGERPRINT) + self.assertEqual(active.content[KEY_VALIDATION_KEY], + str(ValidationLevels.Weak_Chain)) + self.assertEqual(active.content[KEY_LAST_AUDITED_AT_KEY], 0) + self.assertEqual(active.content[KEY_ENCR_USED_KEY], True) + self.assertEqual(active.content[KEY_SIGN_USED_KEY], False) + + self.assertTrue(KEY_ID_KEY not in key.content) + self.assertTrue(KEY_ADDRESS_KEY not in key.content) + self.assertTrue(KEY_VALIDATION_KEY not in key.content) + self.assertTrue(KEY_LAST_AUDITED_AT_KEY not in key.content) + self.assertTrue(KEY_ENCR_USED_KEY not in key.content) + self.assertTrue(KEY_SIGN_USED_KEY not in key.content) + self.assertEqual(key.content[KEY_UIDS_KEY], [ADDRESS]) + + @inlineCallbacks + def test_two_active_docs(self): + get_from_index = self._soledad.get_from_index + delete_doc = self._soledad.delete_doc + put_doc = self._soledad.put_doc + + def my_get_from_index(*args): + docs = [] + if (args[0] == TAGS_PRIVATE_INDEX and + args[2] == '0'): + SoledadDocument = namedtuple("SoledadDocument", ["content"]) + if args[1] == KEYMANAGER_KEY_TAG: + validation = str(ValidationLevels.Provider_Trust) + docs = [SoledadDocument({ + KEY_ADDRESS_KEY: [ADDRESS, ADDRESS_2], + KEY_ID_KEY: KEY_FINGERPRINT[-16:], + KEY_FINGERPRINT_KEY: KEY_FINGERPRINT, + KEY_VALIDATION_KEY: validation, + KEY_LAST_AUDITED_AT_KEY: 1984, + KEY_ENCR_USED_KEY: True, + KEY_SIGN_USED_KEY: False, + })] + if args[1] == KEYMANAGER_ACTIVE_TAG: + docs = [ + SoledadDocument({ + KEY_ADDRESS_KEY: ADDRESS, + KEY_ID_KEY: KEY_FINGERPRINT[-16:], + }), + SoledadDocument({ + KEY_ADDRESS_KEY: ADDRESS_2, + KEY_ID_KEY: KEY_FINGERPRINT[-16:], + }), + ] + return succeed(docs) + + self._soledad.get_from_index = my_get_from_index + self._soledad.delete_doc = Mock(return_value=succeed(None)) + self._soledad.put_doc = Mock(return_value=succeed(None)) + + try: + migrator = KeyDocumentsMigrator(self._soledad) + yield migrator.migrate() + call_list = self._soledad.put_doc.call_args_list + finally: + self._soledad.get_from_index = get_from_index + self._soledad.delete_doc = delete_doc + self._soledad.put_doc = put_doc + + self.assertEqual(len(call_list), 3) + for active in [call[0][0] for call in call_list][:2]: + self.assertTrue(KEY_ID_KEY not in active.content) + self.assertEqual(active.content[KEY_VERSION_KEY], + KEYMANAGER_DOC_VERSION) + self.assertEqual(active.content[KEY_FINGERPRINT_KEY], + KEY_FINGERPRINT) + self.assertEqual(active.content[KEY_VALIDATION_KEY], + str(ValidationLevels.Weak_Chain)) + self.assertEqual(active.content[KEY_LAST_AUDITED_AT_KEY], 0) + self.assertEqual(active.content[KEY_ENCR_USED_KEY], False) + self.assertEqual(active.content[KEY_SIGN_USED_KEY], False) -- cgit v1.2.3 From f886a1adb141023a823ad4a43bfb7b28d02a7857 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 21 Dec 2015 19:26:55 +0100 Subject: [feat] Make EncryptionKey aware of the active address --- keymanager/src/leap/keymanager/__init__.py | 35 ++++----- keymanager/src/leap/keymanager/keys.py | 25 ++++--- keymanager/src/leap/keymanager/migrator.py | 4 + keymanager/src/leap/keymanager/openpgp.py | 86 +++++++++++++--------- .../src/leap/keymanager/tests/test_keymanager.py | 21 +++--- .../src/leap/keymanager/tests/test_openpgp.py | 2 +- 6 files changed, 95 insertions(+), 78 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 8a4efbe9..99ee1632 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -580,7 +580,7 @@ class KeyManager(object): data, pubkey, passphrase, sign=signkey, cipher_algo=cipher_algo) pubkey.encr_used = True - yield _keys.put_key(pubkey, address) + yield _keys.put_key(pubkey) defer.returnValue(encrypted) dpub = self.get_key(address, ktype, private=False, @@ -637,7 +637,7 @@ class KeyManager(object): signature = pubkey if not pubkey.sign_used: pubkey.sign_used = True - yield _keys.put_key(pubkey, verify) + yield _keys.put_key(pubkey) defer.returnValue((decrypted, signature)) else: signature = InvalidSignature( @@ -734,7 +734,7 @@ class KeyManager(object): if signed: if not pubkey.sign_used: pubkey.sign_used = True - d = _keys.put_key(pubkey, address) + d = _keys.put_key(pubkey) d.addCallback(lambda _: pubkey) return d return pubkey @@ -765,20 +765,16 @@ class KeyManager(object): _keys = self._wrapper_map[type(key)] return _keys.delete_key(key) - def put_key(self, key, address): + def put_key(self, key): """ Put key bound to address in local storage. :param key: The key to be stored :type key: EncryptionKey - :param address: address for which this key will be active - :type address: str :return: A Deferred which fires when the key is in the storage, or - which fails with KeyAddressMismatch if address doesn't match - any uid on the key or fails with KeyNotValidUpdate if a key - with the same uid exists and the new one is not a valid update - for it. + which fails with KeyNotValidUpdate if a key with the same + uid exists and the new one is not a valid update for it. :rtype: Deferred :raise UnsupportedKeyTypeError: if invalid key type @@ -787,11 +783,6 @@ class KeyManager(object): self._assert_supported_key_type(ktype) _keys = self._wrapper_map[ktype] - if address not in key.address: - return defer.fail( - KeyAddressMismatch("UID %s found, but expected %s" - % (str(key.address), address))) - def old_key_not_found(failure): if failure.check(KeyNotFound): return None @@ -800,13 +791,13 @@ class KeyManager(object): def check_upgrade(old_key): if key.private or can_upgrade(key, old_key): - return _keys.put_key(key, address) + return _keys.put_key(key) else: raise KeyNotValidUpgrade( "Key %s can not be upgraded by new key %s" % (old_key.fingerprint, key.fingerprint)) - d = _keys.get_key(address, private=key.private) + d = _keys.get_key(key.address, private=key.private) d.addErrback(old_key_not_found) d.addCallback(check_upgrade) return d @@ -838,11 +829,11 @@ class KeyManager(object): self._assert_supported_key_type(ktype) _keys = self._wrapper_map[ktype] - pubkey, privkey = _keys.parse_ascii_key(key) + pubkey, privkey = _keys.parse_ascii_key(key, address) pubkey.validation = validation - d = self.put_key(pubkey, address) + d = self.put_key(pubkey) if privkey is not None: - d.addCallback(lambda _: self.put_key(privkey, address)) + d.addCallback(lambda _: self.put_key(privkey)) return d @defer.inlineCallbacks @@ -878,12 +869,12 @@ class KeyManager(object): ascii_content = yield self._get_with_combined_ca_bundle(uri) # XXX parse binary keys - pubkey, _ = _keys.parse_ascii_key(ascii_content) + pubkey, _ = _keys.parse_ascii_key(ascii_content, address) if pubkey is None: raise KeyNotFound(uri) pubkey.validation = validation - yield self.put_key(pubkey, address) + yield self.put_key(pubkey) def _assert_supported_key_type(self, ktype): """ diff --git a/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py index 68e3fada..38d66b5f 100644 --- a/keymanager/src/leap/keymanager/keys.py +++ b/keymanager/src/leap/keymanager/keys.py @@ -46,6 +46,7 @@ logger = logging.getLogger(__name__) # KEY_VERSION_KEY = 'version' +KEY_UIDS_KEY = 'uids' KEY_ADDRESS_KEY = 'address' KEY_TYPE_KEY = 'type' KEY_FINGERPRINT_KEY = 'fingerprint' @@ -126,12 +127,14 @@ def build_key_from_dict(kClass, key, active=None): :return: An instance of the key. :rtype: C{kClass} """ + address = None validation = ValidationLevels.Weak_Chain last_audited_at = None encr_used = False sign_used = False if active: + address = active[KEY_ADDRESS_KEY] try: validation = ValidationLevels.get(active[KEY_VALIDATION_KEY]) except ValueError: @@ -146,7 +149,8 @@ def build_key_from_dict(kClass, key, active=None): refreshed_at = _to_datetime(key[KEY_REFRESHED_AT_KEY]) return kClass( - key[KEY_ADDRESS_KEY], + address=address, + uids=key[KEY_UIDS_KEY], fingerprint=key[KEY_FINGERPRINT_KEY], key_data=key[KEY_DATA_KEY], private=key[KEY_PRIVATE_KEY], @@ -188,12 +192,15 @@ class EncryptionKey(object): __metaclass__ = ABCMeta - def __init__(self, address, fingerprint="", + def __init__(self, address=None, uids=[], fingerprint="", key_data="", private=False, length=0, expiry_date=None, validation=ValidationLevels.Weak_Chain, last_audited_at=None, refreshed_at=None, encr_used=False, sign_used=False): - # TODO: it should know its own active address self.address = address + if not uids and address: + self.uids = [address] + else: + self.uids = uids self.fingerprint = fingerprint self.key_data = key_data self.private = private @@ -217,7 +224,7 @@ class EncryptionKey(object): refreshed_at = _to_unix_time(self.refreshed_at) return json.dumps({ - KEY_ADDRESS_KEY: self.address, + KEY_UIDS_KEY: self.uids, KEY_TYPE_KEY: self.__class__.__name__, KEY_FINGERPRINT_KEY: self.fingerprint, KEY_DATA_KEY: self.key_data, @@ -229,7 +236,7 @@ class EncryptionKey(object): KEY_TAGS_KEY: [KEYMANAGER_KEY_TAG], }) - def get_active_json(self, address): + def get_active_json(self): """ Return a JSON string describing this key. @@ -239,7 +246,7 @@ class EncryptionKey(object): last_audited_at = _to_unix_time(self.last_audited_at) return json.dumps({ - KEY_ADDRESS_KEY: address, + KEY_ADDRESS_KEY: self.address, KEY_TYPE_KEY: self.__class__.__name__ + KEYMANAGER_ACTIVE_TYPE, KEY_FINGERPRINT_KEY: self.fingerprint, KEY_PRIVATE_KEY: self.private, @@ -374,14 +381,12 @@ class EncryptionScheme(object): pass @abstractmethod - def put_key(self, key, address): + def put_key(self, key): """ Put a key in local storage. :param key: The key to be stored. :type key: EncryptionKey - :param address: address for which this key will be active. - :type address: str :return: A Deferred which fires when the key is in the storage. :rtype: Deferred @@ -496,7 +501,7 @@ class EncryptionScheme(object): :rtype: Deferred """ def log_key_doc(doc): - logger.error("\t%s: %s" % (doc.content[KEY_ADDRESS_KEY], + logger.error("\t%s: %s" % (doc.content[KEY_UIDS_KEY], doc.content[KEY_FINGERPRINT_KEY])) def cmp_key(d1, d2): diff --git a/keymanager/src/leap/keymanager/migrator.py b/keymanager/src/leap/keymanager/migrator.py index 11cf2439..9e4ae77c 100644 --- a/keymanager/src/leap/keymanager/migrator.py +++ b/keymanager/src/leap/keymanager/migrator.py @@ -32,6 +32,8 @@ from leap.keymanager.keys import ( KEYMANAGER_ACTIVE_TAG, KEYMANAGER_DOC_VERSION, + KEY_ADDRESS_KEY, + KEY_UIDS_KEY, KEY_VERSION_KEY, KEY_FINGERPRINT_KEY, KEY_VALIDATION_KEY, @@ -164,6 +166,8 @@ class KeyDocumentsMigrator(object): return succeed(None) key.content[KEY_VERSION_KEY] = KEYMANAGER_DOC_VERSION + key.content[KEY_UIDS_KEY] = key.content[KEY_ADDRESS_KEY] + del key.content[KEY_ADDRESS_KEY] del key.content[KEY_ID_KEY] del key.content[KEY_VALIDATION_KEY] del key.content[KEY_LAST_AUDITED_AT_KEY] diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 0f162969..0d5a8667 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -41,7 +41,7 @@ from leap.keymanager.keys import ( build_key_from_dict, TYPE_FINGERPRINT_PRIVATE_INDEX, TYPE_ADDRESS_PRIVATE_INDEX, - KEY_ADDRESS_KEY, + KEY_UIDS_KEY, KEY_FINGERPRINT_KEY, KEYMANAGER_ACTIVE_TYPE, ) @@ -200,7 +200,7 @@ class OpenPGPKey(EncryptionKey): Base class for OpenPGP keys. """ - def __init__(self, address, gpgbinary=None, **kwargs): + def __init__(self, address=None, gpgbinary=None, **kwargs): self._gpgbinary = gpgbinary super(OpenPGPKey, self).__init__(address, **kwargs) @@ -215,7 +215,7 @@ class OpenPGPKey(EncryptionKey): with TempGPGWrapper(keys=[self], gpgbinary=self._gpgbinary) as gpg: res = gpg.list_sigs(self.fingerprint) for uid, sigs in res.sigs.iteritems(): - if _parse_address(uid) in self.address: + if _parse_address(uid) in self.uids: return sigs return [] @@ -335,8 +335,9 @@ class OpenPGPScheme(EncryptionScheme): key = gpg.list_keys(secret=secret).pop() openpgp_key = self._build_key_from_gpg( key, - gpg.export_keys(key['fingerprint'], secret=secret)) - d = self.put_key(openpgp_key, address) + gpg.export_keys(key['fingerprint'], secret=secret), + address) + d = self.put_key(openpgp_key) deferreds.append(d) yield defer.gatherResults(deferreds) @@ -368,10 +369,10 @@ class OpenPGPScheme(EncryptionScheme): if keydoc is None: raise errors.KeyNotFound(address) leap_assert( - address in keydoc.content[KEY_ADDRESS_KEY], + address in keydoc.content[KEY_UIDS_KEY], 'Wrong address in key %s. Expected %s, found %s.' % (keydoc.content[KEY_FINGERPRINT_KEY], address, - keydoc.content[KEY_ADDRESS_KEY])) + keydoc.content[KEY_UIDS_KEY])) key = build_key_from_dict(OpenPGPKey, keydoc.content, activedoc.content) key._gpgbinary = self._gpgbinary @@ -381,13 +382,15 @@ class OpenPGPScheme(EncryptionScheme): d.addCallback(build_key) return d - def parse_ascii_key(self, key_data): + def parse_ascii_key(self, key_data, address=None): """ 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 + :param address: Active address for the key. + :type address: str :returns: the public key and private key (if applies) for that data. :rtype: (public, private) -> tuple(OpenPGPKey, OpenPGPKey) @@ -408,12 +411,13 @@ class OpenPGPScheme(EncryptionScheme): openpgp_privkey = None if privkey: # build private key - openpgp_privkey = self._build_key_from_gpg(priv_info, privkey) + openpgp_privkey = self._build_key_from_gpg(priv_info, privkey, + address) leap_check(pub_info['fingerprint'] == priv_info['fingerprint'], 'Fingerprints for public and private key differ.', errors.KeyFingerprintMismatch) # build public key - openpgp_pubkey = self._build_key_from_gpg(pub_info, pubkey) + openpgp_pubkey = self._build_key_from_gpg(pub_info, pubkey, address) return (openpgp_pubkey, openpgp_privkey) @@ -433,12 +437,13 @@ class OpenPGPScheme(EncryptionScheme): openpgp_privkey = None try: - openpgp_pubkey, openpgp_privkey = self.parse_ascii_key(key_data) + openpgp_pubkey, openpgp_privkey = self.parse_ascii_key( + key_data, address) except (errors.KeyAddressMismatch, errors.KeyFingerprintMismatch) as e: return defer.fail(e) def put_key(_, key): - return self.put_key(key, address) + return self.put_key(key) d = defer.succeed(None) if openpgp_pubkey is not None: @@ -447,14 +452,12 @@ class OpenPGPScheme(EncryptionScheme): d.addCallback(put_key, openpgp_privkey) return d - def put_key(self, key, address): + def put_key(self, key): """ Put C{key} in local storage. :param key: The key to be stored. :type key: OpenPGPKey - :param address: address for which this key will be active. - :type address: str :return: A Deferred which fires when the key is in the storage. :rtype: Deferred @@ -471,31 +474,36 @@ class OpenPGPScheme(EncryptionScheme): key.merge(oldkey) keydoc.set_json(key.get_json()) - deferred_key = self._soledad.put_doc(keydoc) - - active_json = key.get_active_json(address) - if activedoc: - activedoc.set_json(active_json) - deferred_active = self._soledad.put_doc(activedoc) - else: - deferred_active = self._soledad.create_doc_from_json( - active_json) - - return defer.gatherResults([deferred_key, deferred_active]) + d = self._soledad.put_doc(keydoc) + d.addCallback(put_active, activedoc) + return d def put_new_key(activedoc): deferreds = [] if activedoc: d = self._soledad.delete_doc(activedoc) deferreds.append(d) - for json in [key.get_json(), key.get_active_json(address)]: + for json in [key.get_json(), key.get_active_json()]: d = self._soledad.create_doc_from_json(json) deferreds.append(d) return defer.gatherResults(deferreds) - dk = self._get_key_doc_from_fingerprint(key.fingerprint, key.private) - da = self._get_active_doc_from_address(address, key.private) - d = defer.gatherResults([dk, da]) + def put_active(_, activedoc): + active_json = key.get_active_json() + if activedoc: + activedoc.set_json(active_json) + d = self._soledad.put_doc(activedoc) + else: + d = self._soledad.create_doc_from_json(active_json) + return d + + def get_active_doc(keydoc): + d = self._get_active_doc_from_address(key.address, key.private) + d.addCallback(lambda activedoc: (keydoc, activedoc)) + return d + + d = self._get_key_doc_from_fingerprint(key.fingerprint, key.private) + d.addCallback(get_active_doc) d.addCallback(merge_and_put) return d @@ -533,7 +541,7 @@ class OpenPGPScheme(EncryptionScheme): d.addCallback(get_key_from_active_doc) return d - def _build_key_from_gpg(self, key, key_data): + def _build_key_from_gpg(self, key, key_data, address=None): """ Build an OpenPGPKey for C{address} based on C{key} from local gpg storage. @@ -541,6 +549,8 @@ class OpenPGPScheme(EncryptionScheme): ASCII armored GPG key data has to be queried independently in this wrapper, so we receive it in C{key_data}. + :param address: Active address for the key. + :type address: str :param key: Key obtained from GPG storage. :type key: dict :param key_data: Key data obtained from GPG storage. @@ -548,7 +558,7 @@ class OpenPGPScheme(EncryptionScheme): :return: An instance of the key. :rtype: OpenPGPKey """ - return build_gpg_key(key, key_data, self._gpgbinary) + return build_gpg_key(key, key_data, address, self._gpgbinary) def delete_key(self, key): """ @@ -852,16 +862,20 @@ def process_ascii_key(key_data, gpgbinary, secret=False): return info, key -def build_gpg_key(key_info, key_data, gpgbinary=None): +def build_gpg_key(key_info, key_data, address=None, gpgbinary=None): expiry_date = None if key_info['expires']: expiry_date = datetime.fromtimestamp(int(key_info['expires'])) - address = [] + uids = [] for uid in key_info['uids']: - address.append(_parse_address(uid)) + uids.append(_parse_address(uid)) + if address and address not in uids: + raise errors.KeyAddressMismatch("UIDs %s found, but expected %s" + % (str(uids), address)) return OpenPGPKey( - address, + address=address, + uids=uids, gpgbinary=gpgbinary, fingerprint=key_info['fingerprint'], key_data=key_data, diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 2fe9e4cd..6347d56e 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -76,7 +76,7 @@ class KeyManagerUtilTestCase(unittest.TestCase): def test_build_key_from_dict(self): kdict = { - 'address': [ADDRESS], + 'uids': [ADDRESS], 'fingerprint': KEY_FINGERPRINT, 'key_data': PUBLIC_KEY, 'private': False, @@ -94,7 +94,7 @@ class KeyManagerUtilTestCase(unittest.TestCase): } key = build_key_from_dict(OpenPGPKey, kdict, adict) self.assertEqual( - kdict['address'], key.address, + kdict['uids'], key.uids, 'Wrong data in key.') self.assertEqual( kdict['fingerprint'], key.fingerprint, @@ -117,6 +117,9 @@ class KeyManagerUtilTestCase(unittest.TestCase): self.assertEqual( datetime.fromtimestamp(kdict['refreshed_at']), key.refreshed_at, 'Wrong data in key.') + self.assertEqual( + adict['address'], key.address, + 'Wrong data in key.') self.assertEqual( ValidationLevels.get(adict['validation']), key.validation, 'Wrong data in key.') @@ -137,12 +140,12 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): # get public keys keys = yield km.get_all_keys(False) self.assertEqual(len(keys), 1, 'Wrong number of keys') - self.assertTrue(ADDRESS in keys[0].address) + self.assertTrue(ADDRESS in keys[0].uids) self.assertFalse(keys[0].private) # get private keys keys = yield km.get_all_keys(True) self.assertEqual(len(keys), 1, 'Wrong number of keys') - self.assertTrue(ADDRESS in keys[0].address) + self.assertTrue(ADDRESS in keys[0].uids) self.assertTrue(keys[0].private) @defer.inlineCallbacks @@ -153,7 +156,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): key = yield km.get_key(ADDRESS, OpenPGPKey, private=False, fetch_remote=False) self.assertTrue(key is not None) - self.assertTrue(ADDRESS in key.address) + self.assertTrue(ADDRESS in key.uids) self.assertEqual( key.fingerprint.lower(), KEY_FINGERPRINT.lower()) self.assertFalse(key.private) @@ -166,7 +169,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): key = yield km.get_key(ADDRESS, OpenPGPKey, private=True, fetch_remote=False) self.assertTrue(key is not None) - self.assertTrue(ADDRESS in key.address) + self.assertTrue(ADDRESS in key.uids) self.assertEqual( key.fingerprint.lower(), KEY_FINGERPRINT.lower()) self.assertTrue(key.private) @@ -231,7 +234,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): key = yield self._fetch_key(km, ADDRESS, PUBLIC_KEY) self.assertIsInstance(key, OpenPGPKey) - self.assertTrue(ADDRESS in key.address) + self.assertTrue(ADDRESS in key.uids) self.assertEqual(key.validation, ValidationLevels.Provider_Trust) @defer.inlineCallbacks @@ -243,7 +246,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): key = yield self._fetch_key(km, ADDRESS_OTHER, PUBLIC_KEY_OTHER) self.assertIsInstance(key, OpenPGPKey) - self.assertTrue(ADDRESS_OTHER in key.address) + self.assertTrue(ADDRESS_OTHER in key.uids) self.assertEqual(key.validation, ValidationLevels.Weak_Chain) def _fetch_key(self, km, address, key): @@ -273,7 +276,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): yield km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) key = yield km.get_key(ADDRESS, OpenPGPKey) self.assertIsInstance(key, OpenPGPKey) - self.assertTrue(ADDRESS in key.address) + self.assertTrue(ADDRESS in key.uids) @defer.inlineCallbacks def test_fetch_uri_ascii_key(self): diff --git a/keymanager/src/leap/keymanager/tests/test_openpgp.py b/keymanager/src/leap/keymanager/tests/test_openpgp.py index 8ed049f6..0e5f6bee 100644 --- a/keymanager/src/leap/keymanager/tests/test_openpgp.py +++ b/keymanager/src/leap/keymanager/tests/test_openpgp.py @@ -335,7 +335,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): deferreds = [] for k in (k1, k2, k3, k4, k5): d = self._soledad.create_doc_from_json( - k.get_active_json(ADDRESS)) + k.get_active_json()) deferreds.append(d) return gatherResults(deferreds) elif args[0] == TYPE_FINGERPRINT_PRIVATE_INDEX: -- cgit v1.2.3 From 940903fbc6e96e2fc9d745b49a482a698682fd4b Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Thu, 21 Jan 2016 18:48:23 +0100 Subject: [feat] update usage only if needed During encryption we where updating 'enc_used' in the key without checking if it was already set. --- keymanager/src/leap/keymanager/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 99ee1632..9e3b6ee2 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -579,8 +579,9 @@ class KeyManager(object): encrypted = yield _keys.encrypt( data, pubkey, passphrase, sign=signkey, cipher_algo=cipher_algo) - pubkey.encr_used = True - yield _keys.put_key(pubkey) + if not pubkey.encr_used: + pubkey.encr_used = True + yield _keys.put_key(pubkey) defer.returnValue(encrypted) dpub = self.get_key(address, ktype, private=False, -- cgit v1.2.3 From 628da289584c8c7b218016ea8aa0740aaa36acb6 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Thu, 25 Feb 2016 11:34:45 -0600 Subject: [test] refactor key deletion tests --- keymanager/src/leap/keymanager/tests/__init__.py | 30 ++++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/tests/__init__.py b/keymanager/src/leap/keymanager/tests/__init__.py index 4fbf63ee..20d05e8e 100644 --- a/keymanager/src/leap/keymanager/tests/__init__.py +++ b/keymanager/src/leap/keymanager/tests/__init__.py @@ -54,6 +54,14 @@ class KeyManagerWithSoledadTestCase(unittest.TestCase, BaseLeapTest): def tearDown(self): km = self._key_manager() + # wait for the indexes to be ready for the tear down + d = km._wrapper_map[OpenPGPKey].deferred_init + d.addCallback(lambda _: self.delete_all_keys(km)) + d.addCallback(lambda _: self.tearDownEnv()) + d.addCallback(lambda _: self._soledad.close()) + return d + + def delete_all_keys(self, km): def delete_keys(keys): deferreds = [] for key in keys: @@ -61,26 +69,18 @@ class KeyManagerWithSoledadTestCase(unittest.TestCase, BaseLeapTest): deferreds.append(d) return gatherResults(deferreds) - def get_and_delete_keys(_): - deferreds = [] - for private in [True, False]: - d = km.get_all_keys(private=private) - d.addCallback(delete_keys) - d.addCallback(check_deleted, private) - deferreds.append(d) - return gatherResults(deferreds) - def check_deleted(_, private): d = km.get_all_keys(private=private) d.addCallback(lambda keys: self.assertEqual(keys, [])) return d - # wait for the indexes to be ready for the tear down - d = km._wrapper_map[OpenPGPKey].deferred_init - d.addCallback(get_and_delete_keys) - d.addCallback(lambda _: self.tearDownEnv()) - d.addCallback(lambda _: self._soledad.close()) - return d + deferreds = [] + for private in [True, False]: + d = km.get_all_keys(private=private) + d.addCallback(delete_keys) + d.addCallback(check_deleted, private) + deferreds.append(d) + return gatherResults(deferreds) def _key_manager(self, user=ADDRESS, url='', token=None, ca_cert_path=None): -- cgit v1.2.3 From 06e6825e37a6f5fa2df2620e7dfa1687b7029ff6 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Sun, 20 Mar 2016 18:44:23 +0100 Subject: [bug] Return KeyNotFound Failure if not valid key is given to put_raw_key - Resolves: #7974 --- keymanager/src/leap/keymanager/__init__.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 9e3b6ee2..1106c235 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -820,9 +820,10 @@ class KeyManager(object): :return: A Deferred which fires when the key is in the storage, or which fails with KeyAddressMismatch if address doesn't match - any uid on the key or fails with KeyNotValidUpdate if a key - with the same uid exists and the new one is not a valid update - for it. + any uid on the key or fails with KeyNotFound if no OpenPGP + material was found in key or fails with KeyNotValidUpdate if a + key with the same uid exists and the new one is not a valid + update for it. :rtype: Deferred :raise UnsupportedKeyTypeError: if invalid key type @@ -831,6 +832,10 @@ class KeyManager(object): _keys = self._wrapper_map[ktype] pubkey, privkey = _keys.parse_ascii_key(key, address) + + if pubkey is None: + return defer.fail(KeyNotFound(key)) + pubkey.validation = validation d = self.put_key(pubkey) if privkey is not None: -- cgit v1.2.3 From 59419055fe0037808ec8b2ae215f8d3ce7595494 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 1 Apr 2016 17:25:58 -0400 Subject: [pkg] update to versioneer 0.16 --- keymanager/src/leap/keymanager/__init__.py | 4 + keymanager/src/leap/keymanager/_version.py | 544 ++++++++++++++++++++++------- 2 files changed, 415 insertions(+), 133 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 1106c235..91cd2f8c 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -921,3 +921,7 @@ def _get_domain(url): :rtype: str """ return urlparse(url).hostname + +from ._version import get_versions +__version__ = get_versions()['version'] +del get_versions diff --git a/keymanager/src/leap/keymanager/_version.py b/keymanager/src/leap/keymanager/_version.py index 5153a9bc..b28c6977 100644 --- a/keymanager/src/leap/keymanager/_version.py +++ b/keymanager/src/leap/keymanager/_version.py @@ -1,73 +1,157 @@ + # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag -# feature). Distribution tarballs (build by setup.py sdist) and build +# feature). Distribution tarballs (built by setup.py sdist) and build # directories (produced by setup.py build) will contain a much shorter file # that just contains the computed version number. # This file is released into the public domain. Generated by -# versioneer-0.7+ (https://github.com/warner/python-versioneer) +# versioneer-0.16 (https://github.com/warner/python-versioneer) + +"""Git implementation of _version.py.""" -# these strings will be replaced by git during git-archive +import errno +import os +import re import subprocess import sys -import re -import os.path -IN_LONG_VERSION_PY = True -git_refnames = "$Format:%d$" -git_full = "$Format:%H$" +def get_keywords(): + """Get the keywords needed to look up the version information.""" + # these strings will be replaced by git during git-archive. + # setup.py/versioneer.py will grep for the variable names, so they must + # each be defined on a line of their own. _version.py will just call + # get_keywords(). + git_refnames = "$Format:%d$" + git_full = "$Format:%H$" + keywords = {"refnames": git_refnames, "full": git_full} + return keywords -def run_command(args, cwd=None, verbose=False): - try: - # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd) - except EnvironmentError: - e = sys.exc_info()[1] +class VersioneerConfig: + """Container for Versioneer configuration parameters.""" + + +def get_config(): + """Create, populate and return the VersioneerConfig() object.""" + # these strings are filled in when 'setup.py versioneer' creates + # _version.py + cfg = VersioneerConfig() + cfg.VCS = "git" + cfg.style = "pep440" + cfg.tag_prefix = "" + cfg.parentdir_prefix = "None" + cfg.versionfile_source = "src/leap/keymanager/_version.py" + cfg.verbose = False + return cfg + + +class NotThisMethod(Exception): + """Exception raised if a method is not valid for the current scenario.""" + + +LONG_VERSION_PY = {} +HANDLERS = {} + + +def register_vcs_handler(vcs, method): # decorator + """Decorator to mark a method as the handler for a particular VCS.""" + def decorate(f): + """Store f in HANDLERS[vcs][method].""" + if vcs not in HANDLERS: + HANDLERS[vcs] = {} + HANDLERS[vcs][method] = f + return f + return decorate + + +def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): + """Call the given command(s).""" + assert isinstance(commands, list) + p = None + for c in commands: + try: + dispcmd = str([c] + args) + # remember shell=False, so use git.cmd on windows, not just git + p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr + else None)) + break + except EnvironmentError: + e = sys.exc_info()[1] + if e.errno == errno.ENOENT: + continue + if verbose: + print("unable to run %s" % dispcmd) + print(e) + return None + else: if verbose: - print("unable to run %s" % args[0]) - print(e) + print("unable to find command, tried %s" % (commands,)) return None stdout = p.communicate()[0].strip() - if sys.version >= '3': + if sys.version_info[0] >= 3: stdout = stdout.decode() if p.returncode != 0: if verbose: - print("unable to run %s (error)" % args[0]) + print("unable to run %s (error)" % dispcmd) return None return stdout -def get_expanded_variables(versionfile_source): +def versions_from_parentdir(parentdir_prefix, root, verbose): + """Try to determine the version from the parent directory name. + + Source tarballs conventionally unpack into a directory that includes + both the project name and a version string. + """ + dirname = os.path.basename(root) + if not dirname.startswith(parentdir_prefix): + if verbose: + print("guessing rootdir is '%s', but '%s' doesn't start with " + "prefix '%s'" % (root, dirname, parentdir_prefix)) + raise NotThisMethod("rootdir doesn't start with parentdir_prefix") + return {"version": dirname[len(parentdir_prefix):], + "full-revisionid": None, + "dirty": False, "error": None} + + +@register_vcs_handler("git", "get_keywords") +def git_get_keywords(versionfile_abs): + """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these - # variables. When used from setup.py, we don't want to import - # _version.py, so we do it with a regexp instead. This function is not - # used from _version.py. - variables = {} + # keywords. When used from setup.py, we don't want to import _version.py, + # so we do it with a regexp instead. This function is not used from + # _version.py. + keywords = {} try: - f = open(versionfile_source, "r") + f = open(versionfile_abs, "r") for line in f.readlines(): if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: - variables["refnames"] = mo.group(1) + keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: - variables["full"] = mo.group(1) + keywords["full"] = mo.group(1) f.close() except EnvironmentError: pass - return variables + return keywords -def versions_from_expanded_variables(variables, tag_prefix, verbose=False): - refnames = variables["refnames"].strip() +@register_vcs_handler("git", "keywords") +def git_versions_from_keywords(keywords, tag_prefix, verbose): + """Get version information from git keywords.""" + if not keywords: + raise NotThisMethod("no keywords at all, weird") + refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: - print("variables are unexpanded, not using") - return {} # unexpanded, so not in an unpacked git-archive tarball + print("keywords are unexpanded, not using") + raise NotThisMethod("unexpanded keywords, not a git-archive tarball") refs = set([r.strip() for r in refnames.strip("()").split(",")]) # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. @@ -83,7 +167,7 @@ def versions_from_expanded_variables(variables, tag_prefix, verbose=False): # "stabilization", as well as "HEAD" and "master". tags = set([r for r in refs if re.search(r'\d', r)]) if verbose: - print("discarding '%s', no digits" % ",".join(refs - tags)) + print("discarding '%s', no digits" % ",".join(refs-tags)) if verbose: print("likely tags: %s" % ",".join(sorted(tags))) for ref in sorted(tags): @@ -93,114 +177,308 @@ def versions_from_expanded_variables(variables, tag_prefix, verbose=False): if verbose: print("picking %s" % r) return {"version": r, - "full": variables["full"].strip()} - # no suitable tags, so we use the full revision id + "full-revisionid": keywords["full"].strip(), + "dirty": False, "error": None + } + # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: - print("no suitable tags, using full revision id") - return {"version": variables["full"].strip(), - "full": variables["full"].strip()} - - -def versions_from_vcs(tag_prefix, versionfile_source, verbose=False): - # this runs 'git' from the root of the source tree. That either means - # someone ran a setup.py command (and this code is in versioneer.py, so - # IN_LONG_VERSION_PY=False, thus the containing directory is the root of - # the source tree), or someone ran a project-specific entry point (and - # this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the - # containing directory is somewhere deeper in the source tree). This only - # gets called if the git-archive 'subst' variables were *not* expanded, - # and _version.py hasn't already been rewritten with a short version - # string, meaning we're inside a checked out source tree. + print("no suitable tags, using unknown + full revision id") + return {"version": "0+unknown", + "full-revisionid": keywords["full"].strip(), + "dirty": False, "error": "no suitable tags"} - try: - here = os.path.abspath(__file__) - except NameError: - # some py2exe/bbfreeze/non-CPython implementations don't do __file__ - return {} # not always correct - - # versionfile_source is the relative path from the top of the source tree - # (where the .git directory might live) to this file. Invert this to find - # the root from __file__. - root = here - if IN_LONG_VERSION_PY: - for i in range(len(versionfile_source.split("/"))): - root = os.path.dirname(root) - else: - root = os.path.dirname(here) + +@register_vcs_handler("git", "pieces_from_vcs") +def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): + """Get version from 'git describe' in the root of the source tree. + + This only gets called if the git-archive 'subst' keywords were *not* + expanded, and _version.py hasn't already been rewritten with a short + version string, meaning we're inside a checked out source tree. + """ if not os.path.exists(os.path.join(root, ".git")): if verbose: print("no .git in %s" % root) - return {} + raise NotThisMethod("no .git directory") - GIT = "git" + GITS = ["git"] if sys.platform == "win32": - GIT = "git.cmd" - stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"], - cwd=root) - if stdout is None: - return {} - if not stdout.startswith(tag_prefix): - if verbose: - print("tag '%s' doesn't start with prefix '%s'" % - (stdout, tag_prefix)) - return {} - tag = stdout[len(tag_prefix):] - stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root) - if stdout is None: - return {} - full = stdout.strip() - if tag.endswith("-dirty"): - full += "-dirty" - return {"version": tag, "full": full} - - -def versions_from_parentdir(parentdir_prefix, versionfile_source, - verbose=False): - if IN_LONG_VERSION_PY: - # We're running from _version.py. If it's from a source tree - # (execute-in-place), we can work upwards to find the root of the - # tree, and then check the parent directory for a version string. If - # it's in an installed application, there's no hope. - try: - here = os.path.abspath(__file__) - except NameError: - # py2exe/bbfreeze/non-CPython don't have __file__ - return {} # without __file__, we have no hope + GITS = ["git.cmd", "git.exe"] + # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] + # if there isn't one, this yields HEX[-dirty] (no NUM) + describe_out = run_command(GITS, ["describe", "--tags", "--dirty", + "--always", "--long", + "--match", "%s*" % tag_prefix], + cwd=root) + # --long was added in git-1.5.5 + if describe_out is None: + raise NotThisMethod("'git describe' failed") + describe_out = describe_out.strip() + full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) + if full_out is None: + raise NotThisMethod("'git rev-parse' failed") + full_out = full_out.strip() + + pieces = {} + pieces["long"] = full_out + pieces["short"] = full_out[:7] # maybe improved later + pieces["error"] = None + + # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] + # TAG might have hyphens. + git_describe = describe_out + + # look for -dirty suffix + dirty = git_describe.endswith("-dirty") + pieces["dirty"] = dirty + if dirty: + git_describe = git_describe[:git_describe.rindex("-dirty")] + + # now we have TAG-NUM-gHEX or HEX + + if "-" in git_describe: + # TAG-NUM-gHEX + mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) + if not mo: + # unparseable. Maybe git-describe is misbehaving? + pieces["error"] = ("unable to parse git-describe output: '%s'" + % describe_out) + return pieces + + # tag + full_tag = mo.group(1) + if not full_tag.startswith(tag_prefix): + if verbose: + fmt = "tag '%s' doesn't start with prefix '%s'" + print(fmt % (full_tag, tag_prefix)) + pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" + % (full_tag, tag_prefix)) + return pieces + pieces["closest-tag"] = full_tag[len(tag_prefix):] + + # distance: number of commits since tag + pieces["distance"] = int(mo.group(2)) + + # commit: short hex revision ID + pieces["short"] = mo.group(3) + + else: + # HEX: no tags + pieces["closest-tag"] = None + count_out = run_command(GITS, ["rev-list", "HEAD", "--count"], + cwd=root) + pieces["distance"] = int(count_out) # total number of commits + + return pieces + + +def plus_or_dot(pieces): + """Return a + if we don't already have one, else return a .""" + if "+" in pieces.get("closest-tag", ""): + return "." + return "+" + + +def render_pep440(pieces): + """Build up version string, with post-release "local version identifier". + + Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you + get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty + + Exceptions: + 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += plus_or_dot(pieces) + rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + else: + # exception #1 + rendered = "0+untagged.%d.g%s" % (pieces["distance"], + pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + return rendered + + +def render_pep440_pre(pieces): + """TAG[.post.devDISTANCE] -- No -dirty. + + Exceptions: + 1: no tags. 0.post.devDISTANCE + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"]: + rendered += ".post.dev%d" % pieces["distance"] + else: + # exception #1 + rendered = "0.post.dev%d" % pieces["distance"] + return rendered + + +def render_pep440_post(pieces): + """TAG[.postDISTANCE[.dev0]+gHEX] . + + The ".dev0" means dirty. Note that .dev0 sorts backwards + (a dirty tree will appear "older" than the corresponding clean one), + but you shouldn't be releasing software with -dirty anyways. + + Exceptions: + 1: no tags. 0.postDISTANCE[.dev0] + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "g%s" % pieces["short"] + else: + # exception #1 + rendered = "0.post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + rendered += "+g%s" % pieces["short"] + return rendered + + +def render_pep440_old(pieces): + """TAG[.postDISTANCE[.dev0]] . + + The ".dev0" means dirty. + + Eexceptions: + 1: no tags. 0.postDISTANCE[.dev0] + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + else: + # exception #1 + rendered = "0.post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + return rendered + + +def render_git_describe(pieces): + """TAG[-DISTANCE-gHEX][-dirty]. + + Like 'git describe --tags --dirty --always'. + + Exceptions: + 1: no tags. HEX[-dirty] (note: no 'g' prefix) + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"]: + rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) + else: + # exception #1 + rendered = pieces["short"] + if pieces["dirty"]: + rendered += "-dirty" + return rendered + + +def render_git_describe_long(pieces): + """TAG-DISTANCE-gHEX[-dirty]. + + Like 'git describe --tags --dirty --always -long'. + The distance/hash is unconditional. + + Exceptions: + 1: no tags. HEX[-dirty] (note: no 'g' prefix) + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) + else: + # exception #1 + rendered = pieces["short"] + if pieces["dirty"]: + rendered += "-dirty" + return rendered + + +def render(pieces, style): + """Render the given version pieces into the requested style.""" + if pieces["error"]: + return {"version": "unknown", + "full-revisionid": pieces.get("long"), + "dirty": None, + "error": pieces["error"]} + + if not style or style == "default": + style = "pep440" # the default + + if style == "pep440": + rendered = render_pep440(pieces) + elif style == "pep440-pre": + rendered = render_pep440_pre(pieces) + elif style == "pep440-post": + rendered = render_pep440_post(pieces) + elif style == "pep440-old": + rendered = render_pep440_old(pieces) + elif style == "git-describe": + rendered = render_git_describe(pieces) + elif style == "git-describe-long": + rendered = render_git_describe_long(pieces) + else: + raise ValueError("unknown style '%s'" % style) + + return {"version": rendered, "full-revisionid": pieces["long"], + "dirty": pieces["dirty"], "error": None} + + +def get_versions(): + """Get version information or return default if unable to do so.""" + # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have + # __file__, we can work backwards from there to the root. Some + # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which + # case we can only use expanded keywords. + + cfg = get_config() + verbose = cfg.verbose + + try: + return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, + verbose) + except NotThisMethod: + pass + + try: + root = os.path.realpath(__file__) # versionfile_source is the relative path from the top of the source - # tree to _version.py. Invert this to find the root from __file__. - root = here - for i in range(len(versionfile_source.split("/"))): + # tree (where the .git directory might live) to this file. Invert + # this to find the root from __file__. + for i in cfg.versionfile_source.split('/'): root = os.path.dirname(root) - else: - # we're running from versioneer.py, which means we're running from - # the setup.py in a source tree. sys.argv[0] is setup.py in the root. - here = os.path.abspath(sys.argv[0]) - root = os.path.dirname(here) + except NameError: + return {"version": "0+unknown", "full-revisionid": None, + "dirty": None, + "error": "unable to find root of source tree"} - # Source tarballs conventionally unpack into a directory that includes - # both the project name and a version string. - dirname = os.path.basename(root) - if not dirname.startswith(parentdir_prefix): - if verbose: - print("guessing rootdir is '%s', but '%s' doesn't start " - "with prefix '%s'" % - (root, dirname, parentdir_prefix)) - return None - return {"version": dirname[len(parentdir_prefix):], "full": ""} - -tag_prefix = "" -parentdir_prefix = "leap.keymanager-" -versionfile_source = "src/leap/keymanager/_version.py" - - -def get_versions(default={"version": "unknown", "full": ""}, verbose=False): - variables = {"refnames": git_refnames, "full": git_full} - ver = versions_from_expanded_variables(variables, tag_prefix, verbose) - if not ver: - ver = versions_from_vcs(tag_prefix, versionfile_source, verbose) - if not ver: - ver = versions_from_parentdir(parentdir_prefix, versionfile_source, - verbose) - if not ver: - ver = default - return ver + try: + pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) + return render(pieces, cfg.style) + except NotThisMethod: + pass + + try: + if cfg.parentdir_prefix: + return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) + except NotThisMethod: + pass + + return {"version": "0+unknown", "full-revisionid": None, + "dirty": None, + "error": "unable to compute version"} -- cgit v1.2.3 From 29d130de4c60988067cfd1bfd55aa658c83cdf8e Mon Sep 17 00:00:00 2001 From: Bruno Wagner Date: Mon, 11 Apr 2016 11:57:58 -0300 Subject: [style] Removed duplicated import There was a duplicate import for get_versions, that was not at the top of the file, that caused a pep warning and was fixed in this commit --- keymanager/src/leap/keymanager/__init__.py | 1 - 1 file changed, 1 deletion(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 91cd2f8c..ce496672 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -922,6 +922,5 @@ def _get_domain(url): """ return urlparse(url).hostname -from ._version import get_versions __version__ = get_versions()['version'] del get_versions -- cgit v1.2.3 From ce59f4869a6477c36322479c2c35168087a57068 Mon Sep 17 00:00:00 2001 From: "Kali Kaneko (leap communications)" Date: Mon, 11 Apr 2016 12:14:01 -0400 Subject: [bug] delete versioneer duplicated block --- keymanager/src/leap/keymanager/__init__.py | 3 --- 1 file changed, 3 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index ce496672..1106c235 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -921,6 +921,3 @@ def _get_domain(url): :rtype: str """ return urlparse(url).hostname - -__version__ = get_versions()['version'] -del get_versions -- cgit v1.2.3 From 1e3d245f081d0948f0f6c6ae5368e3ab81eddd23 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Fri, 8 Apr 2016 18:22:14 +0200 Subject: [feat] reduce log level for encrypt/decrypt errors * Related: #8022 --- keymanager/src/leap/keymanager/openpgp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 0d5a8667..a8432611 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -680,7 +680,7 @@ class OpenPGPScheme(EncryptionScheme): self._assert_gpg_result_ok(result) defer.returnValue(result.data) except errors.GPGError as e: - logger.error('Failed to decrypt: %s.' % str(e)) + logger.warning('Failed to encrypt: %s.' % str(e)) raise errors.EncryptError() @defer.inlineCallbacks @@ -726,7 +726,7 @@ class OpenPGPScheme(EncryptionScheme): defer.returnValue((result.data, sign_valid)) except errors.GPGError as e: - logger.error('Failed to decrypt: %s.' % str(e)) + logger.warning('Failed to decrypt: %s.' % str(e)) raise errors.DecryptError(str(e)) def is_encrypted(self, data): -- cgit v1.2.3 From aeb1e6057804a815b216a44979150e3f28ef2a5e Mon Sep 17 00:00:00 2001 From: elijah Date: Mon, 18 Apr 2016 23:48:17 -0700 Subject: [bug] encryption keys can now be serialized to json using key.get_dict() --- keymanager/src/leap/keymanager/keys.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py index 38d66b5f..34d921c5 100644 --- a/keymanager/src/leap/keymanager/keys.py +++ b/keymanager/src/leap/keymanager/keys.py @@ -258,6 +258,20 @@ class EncryptionKey(object): KEY_TAGS_KEY: [KEYMANAGER_ACTIVE_TAG], }) + def get_dict(self): + """ + :return: a serializable dict representation of this key. + :rtype: dict + """ + return { + KEY_ADDRESS_KEY: self.address, + KEY_UIDS_KEY: self.uids, + KEY_FINGERPRINT_KEY: self.fingerprint, + KEY_LENGTH_KEY: self.length, + KEY_EXPIRY_DATE_KEY: str(self.expiry_date), + KEY_DATA_KEY: self.key_data + } + def __repr__(self): """ Representation of this class -- cgit v1.2.3 From fc8bc28ffa85f7393fe1741a08618817c6e0f315 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 25 Apr 2016 20:24:48 -0300 Subject: [feat] creat an iterator to build dicts Need pizza!!!!!! --- keymanager/src/leap/keymanager/keys.py | 39 ++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 13 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py index 34d921c5..1955d545 100644 --- a/keymanager/src/leap/keymanager/keys.py +++ b/keymanager/src/leap/keymanager/keys.py @@ -192,6 +192,11 @@ class EncryptionKey(object): __metaclass__ = ABCMeta + __slots__ = ('address', 'uids', 'fingerprint', 'key_data', + 'private', 'length', 'expiry_date', 'validation', + 'last_audited_at', 'refreshed_at', + 'encr_used', 'sign_used', '_index') + def __init__(self, address=None, uids=[], fingerprint="", key_data="", private=False, length=0, expiry_date=None, validation=ValidationLevels.Weak_Chain, last_audited_at=None, @@ -212,6 +217,7 @@ class EncryptionKey(object): self.refreshed_at = refreshed_at self.encr_used = encr_used self.sign_used = sign_used + self._index = len(self.__slots__) def get_json(self): """ @@ -258,19 +264,26 @@ class EncryptionKey(object): KEY_TAGS_KEY: [KEYMANAGER_ACTIVE_TAG], }) - def get_dict(self): - """ - :return: a serializable dict representation of this key. - :rtype: dict - """ - return { - KEY_ADDRESS_KEY: self.address, - KEY_UIDS_KEY: self.uids, - KEY_FINGERPRINT_KEY: self.fingerprint, - KEY_LENGTH_KEY: self.length, - KEY_EXPIRY_DATE_KEY: str(self.expiry_date), - KEY_DATA_KEY: self.key_data - } + def next(self): + if self._index == 0: + self._index = len(self.__slots__) + raise StopIteration + + self._index -= 1 + key = self.__slots__[self._index] + + if key.startswith('_'): + return self.next() + + value = getattr(self, key) + if key == "validation": + value = str(value) + elif key in ["expiry_date", "last_audited_at", "refreshed_at"]: + value = str(value) + return key, value + + def __iter__(self): + return self def __repr__(self): """ -- cgit v1.2.3 From 0b29e3a97b30179f5dc269efeee91f78cb3cea03 Mon Sep 17 00:00:00 2001 From: Zara Gebru Date: Tue, 24 May 2016 16:10:40 +0200 Subject: Issue #287: add tests for binary key and refactor --- keymanager/src/leap/keymanager/__init__.py | 4 +- keymanager/src/leap/keymanager/openpgp.py | 18 +++--- keymanager/src/leap/keymanager/tests/__init__.py | 9 +++ .../leap/keymanager/tests/fixtures/private_key.bin | Bin 0 -> 2202 bytes .../leap/keymanager/tests/fixtures/public_key.bin | Bin 0 -> 2202 bytes .../src/leap/keymanager/tests/test_keymanager.py | 68 ++++++++++++++++----- .../src/leap/keymanager/tests/test_openpgp.py | 38 ++++++------ 7 files changed, 93 insertions(+), 44 deletions(-) create mode 100644 keymanager/src/leap/keymanager/tests/fixtures/private_key.bin create mode 100644 keymanager/src/leap/keymanager/tests/fixtures/public_key.bin (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 1106c235..194a4eef 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -831,7 +831,7 @@ class KeyManager(object): self._assert_supported_key_type(ktype) _keys = self._wrapper_map[ktype] - pubkey, privkey = _keys.parse_ascii_key(key, address) + pubkey, privkey = _keys.parse_key(key, address) if pubkey is None: return defer.fail(KeyNotFound(key)) @@ -875,7 +875,7 @@ class KeyManager(object): ascii_content = yield self._get_with_combined_ca_bundle(uri) # XXX parse binary keys - pubkey, _ = _keys.parse_ascii_key(ascii_content, address) + pubkey, _ = _keys.parse_key(ascii_content, address) if pubkey is None: raise KeyNotFound(uri) diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index a8432611..82050cc8 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -382,9 +382,9 @@ class OpenPGPScheme(EncryptionScheme): d.addCallback(build_key) return d - def parse_ascii_key(self, key_data, address=None): + def parse_key(self, key_data, address=None): """ - Parses an ascii armored key (or key pair) data and returns + Parses a key (or key pair) data and returns the OpenPGPKey keys. :param key_data: the key data to be parsed. @@ -400,9 +400,9 @@ class OpenPGPScheme(EncryptionScheme): # TODO: add more checks for correct key data. leap_assert(key_data is not None, 'Data does not represent a key.') - priv_info, privkey = process_ascii_key( + priv_info, privkey = process_key( key_data, self._gpgbinary, secret=True) - pub_info, pubkey = process_ascii_key( + pub_info, pubkey = process_key( key_data, self._gpgbinary, secret=False) if not pubkey: @@ -421,9 +421,9 @@ class OpenPGPScheme(EncryptionScheme): return (openpgp_pubkey, openpgp_privkey) - def put_ascii_key(self, key_data, address): + def put_raw_key(self, key_data, address): """ - Put key contained in ascii-armored C{key_data} in local storage. + Put key contained in C{key_data} in local storage. :param key_data: The key data to be stored. :type key_data: str or unicode @@ -437,7 +437,7 @@ class OpenPGPScheme(EncryptionScheme): openpgp_privkey = None try: - openpgp_pubkey, openpgp_privkey = self.parse_ascii_key( + openpgp_pubkey, openpgp_privkey = self.parse_key( key_data, address) except (errors.KeyAddressMismatch, errors.KeyFingerprintMismatch) as e: return defer.fail(e) @@ -546,7 +546,7 @@ class OpenPGPScheme(EncryptionScheme): Build an OpenPGPKey for C{address} based on C{key} from local gpg storage. - ASCII armored GPG key data has to be queried independently in this + GPG key data has to be queried independently in this wrapper, so we receive it in C{key_data}. :param address: Active address for the key. @@ -850,7 +850,7 @@ class OpenPGPScheme(EncryptionScheme): return doclist[0] -def process_ascii_key(key_data, gpgbinary, secret=False): +def process_key(key_data, gpgbinary, secret=False): with TempGPGWrapper(gpgbinary=gpgbinary) as gpg: try: gpg.import_keys(key_data) diff --git a/keymanager/src/leap/keymanager/tests/__init__.py b/keymanager/src/leap/keymanager/tests/__init__.py index 20d05e8e..2a6a3f1e 100644 --- a/keymanager/src/leap/keymanager/tests/__init__.py +++ b/keymanager/src/leap/keymanager/tests/__init__.py @@ -29,6 +29,7 @@ from leap.soledad.client import Soledad from leap.keymanager import KeyManager from leap.keymanager.openpgp import OpenPGPKey +PATH = os.path.dirname(os.path.realpath(__file__)) ADDRESS = 'leap@leap.se' ADDRESS_2 = 'anotheruser@leap.se' @@ -95,6 +96,14 @@ class KeyManagerWithSoledadTestCase(unittest.TestCase, BaseLeapTest): else: return "/usr/bin/gpg" + def get_public_binary_key(self): + with open(PATH + '/fixtures/public_key.bin', 'r') as binary_public_key: + return binary_public_key.read() + + def get_private_binary_key(self): + with open(PATH + '/fixtures/private_key.bin', 'r') as binary_private_key: + return binary_private_key.read() + # key 24D18DDF: public key "Leap Test Key " KEY_FINGERPRINT = "E36E738D69173C13D709E44F2F455E2824D18DDF" diff --git a/keymanager/src/leap/keymanager/tests/fixtures/private_key.bin b/keymanager/src/leap/keymanager/tests/fixtures/private_key.bin new file mode 100644 index 00000000..ab174317 Binary files /dev/null and b/keymanager/src/leap/keymanager/tests/fixtures/private_key.bin differ diff --git a/keymanager/src/leap/keymanager/tests/fixtures/public_key.bin b/keymanager/src/leap/keymanager/tests/fixtures/public_key.bin new file mode 100644 index 00000000..ab174317 Binary files /dev/null and b/keymanager/src/leap/keymanager/tests/fixtures/public_key.bin differ diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 6347d56e..05c1cdd5 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -136,7 +136,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): @defer.inlineCallbacks def test_get_all_keys_in_db(self): km = self._key_manager() - yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) + yield km._wrapper_map[OpenPGPKey].put_raw_key(PRIVATE_KEY, ADDRESS) # get public keys keys = yield km.get_all_keys(False) self.assertEqual(len(keys), 1, 'Wrong number of keys') @@ -151,7 +151,20 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): @defer.inlineCallbacks def test_get_public_key(self): km = self._key_manager() - yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) + yield km._wrapper_map[OpenPGPKey].put_raw_key(PRIVATE_KEY, ADDRESS) + # get the key + key = yield km.get_key(ADDRESS, OpenPGPKey, private=False, + fetch_remote=False) + self.assertTrue(key is not None) + self.assertTrue(ADDRESS in key.uids) + self.assertEqual( + key.fingerprint.lower(), KEY_FINGERPRINT.lower()) + self.assertFalse(key.private) + + @defer.inlineCallbacks + def test_get_public_key_with_binary_private_key(self): + km = self._key_manager() + yield km._wrapper_map[OpenPGPKey].put_raw_key(self.get_private_binary_key(), ADDRESS) # get the key key = yield km.get_key(ADDRESS, OpenPGPKey, private=False, fetch_remote=False) @@ -164,7 +177,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): @defer.inlineCallbacks def test_get_private_key(self): km = self._key_manager() - yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) + yield km._wrapper_map[OpenPGPKey].put_raw_key(PRIVATE_KEY, ADDRESS) # get the key key = yield km.get_key(ADDRESS, OpenPGPKey, private=True, fetch_remote=False) @@ -186,7 +199,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): """ token = "mytoken" km = self._key_manager(token=token) - yield km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY, ADDRESS) + yield km._wrapper_map[OpenPGPKey].put_raw_key(PUBLIC_KEY, ADDRESS) km._async_client_pinned.request = Mock(return_value=defer.succeed('')) # the following data will be used on the send km.ca_cert_path = 'capath' @@ -278,6 +291,19 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): self.assertIsInstance(key, OpenPGPKey) self.assertTrue(ADDRESS in key.uids) + @defer.inlineCallbacks + def test_put_key_binary(self): + """ + Test that putting binary key works + """ + km = self._key_manager(url=NICKSERVER_URI) + + yield km.put_raw_key(self.get_public_binary_key(), OpenPGPKey, ADDRESS) + key = yield km.get_key(ADDRESS, OpenPGPKey) + + self.assertIsInstance(key, OpenPGPKey) + self.assertTrue(ADDRESS in key.uids) + @defer.inlineCallbacks def test_fetch_uri_ascii_key(self): """ @@ -292,6 +318,20 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): key = yield km.get_key(ADDRESS, OpenPGPKey) self.assertEqual(KEY_FINGERPRINT, key.fingerprint) + @defer.inlineCallbacks + def test_fetch_uri_binary_key(self): + """ + Test that fetch key downloads the binary key and gets included in + the local storage + """ + km = self._key_manager() + + km._async_client.request = Mock(return_value=defer.succeed(self.get_public_binary_key())) + + yield km.fetch_key(ADDRESS, "http://site.domain/key", OpenPGPKey) + key = yield km.get_key(ADDRESS, OpenPGPKey) + self.assertEqual(KEY_FINGERPRINT, key.fingerprint) + def test_fetch_uri_empty_key(self): """ Test that fetch key raises KeyNotFound if no key in the url @@ -391,8 +431,8 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): def test_decrypt_updates_sign_used_for_signer(self): # given km = self._key_manager() - yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) - yield km._wrapper_map[OpenPGPKey].put_ascii_key( + yield km._wrapper_map[OpenPGPKey].put_raw_key(PRIVATE_KEY, ADDRESS) + yield km._wrapper_map[OpenPGPKey].put_raw_key( PRIVATE_KEY_2, ADDRESS_2) encdata = yield km.encrypt('data', ADDRESS, OpenPGPKey, sign=ADDRESS_2, fetch_remote=False) @@ -409,9 +449,9 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): def test_decrypt_does_not_update_sign_used_for_recipient(self): # given km = self._key_manager() - yield km._wrapper_map[OpenPGPKey].put_ascii_key( + yield km._wrapper_map[OpenPGPKey].put_raw_key( PRIVATE_KEY, ADDRESS) - yield km._wrapper_map[OpenPGPKey].put_ascii_key( + yield km._wrapper_map[OpenPGPKey].put_raw_key( PRIVATE_KEY_2, ADDRESS_2) encdata = yield km.encrypt('data', ADDRESS, OpenPGPKey, sign=ADDRESS_2, fetch_remote=False) @@ -434,8 +474,8 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): def test_keymanager_openpgp_encrypt_decrypt(self): km = self._key_manager() # put raw private key - yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) - yield km._wrapper_map[OpenPGPKey].put_ascii_key( + yield km._wrapper_map[OpenPGPKey].put_raw_key(PRIVATE_KEY, ADDRESS) + yield km._wrapper_map[OpenPGPKey].put_raw_key( PRIVATE_KEY_2, ADDRESS_2) # encrypt encdata = yield km.encrypt(self.RAW_DATA, ADDRESS, OpenPGPKey, @@ -453,8 +493,8 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): def test_keymanager_openpgp_encrypt_decrypt_wrong_sign(self): km = self._key_manager() # put raw keys - yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) - yield km._wrapper_map[OpenPGPKey].put_ascii_key( + yield km._wrapper_map[OpenPGPKey].put_raw_key(PRIVATE_KEY, ADDRESS) + yield km._wrapper_map[OpenPGPKey].put_raw_key( PRIVATE_KEY_2, ADDRESS_2) # encrypt encdata = yield km.encrypt(self.RAW_DATA, ADDRESS, OpenPGPKey, @@ -470,7 +510,7 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): def test_keymanager_openpgp_sign_verify(self): km = self._key_manager() # put raw private keys - yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) + yield km._wrapper_map[OpenPGPKey].put_raw_key(PRIVATE_KEY, ADDRESS) signdata = yield km.sign(self.RAW_DATA, ADDRESS, OpenPGPKey, detach=False) self.assertNotEqual(self.RAW_DATA, signdata) @@ -483,7 +523,7 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): def test_keymanager_encrypt_key_not_found(self): km = self._key_manager() - d = km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) + d = km._wrapper_map[OpenPGPKey].put_raw_key(PRIVATE_KEY, ADDRESS) d.addCallback( lambda _: km.encrypt(self.RAW_DATA, ADDRESS_2, OpenPGPKey, sign=ADDRESS, fetch_remote=False)) diff --git a/keymanager/src/leap/keymanager/tests/test_openpgp.py b/keymanager/src/leap/keymanager/tests/test_openpgp.py index 0e5f6bee..68fb4e00 100644 --- a/keymanager/src/leap/keymanager/tests/test_openpgp.py +++ b/keymanager/src/leap/keymanager/tests/test_openpgp.py @@ -68,7 +68,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=self.gpg_binary_path) yield self._assert_key_not_found(pgp, ADDRESS) - yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) + yield pgp.put_raw_key(PUBLIC_KEY, ADDRESS) key = yield pgp.get_key(ADDRESS, private=False) yield pgp.delete_key(key) yield self._assert_key_not_found(pgp, ADDRESS) @@ -78,7 +78,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=self.gpg_binary_path) yield self._assert_key_not_found(pgp, ADDRESS) - yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) + yield pgp.put_raw_key(PUBLIC_KEY, ADDRESS) key = yield pgp.get_key(ADDRESS, private=False) self.assertIsInstance(key, openpgp.OpenPGPKey) self.assertTrue( @@ -93,7 +93,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=self.gpg_binary_path) yield self._assert_key_not_found(pgp, ADDRESS) - yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) + yield pgp.put_raw_key(PUBLIC_KEY, ADDRESS) yield self._assert_key_not_found(pgp, ADDRESS, private=True) key = yield pgp.get_key(ADDRESS, private=False) self.assertTrue(ADDRESS in key.address) @@ -109,7 +109,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): self._soledad, gpgbinary=self.gpg_binary_path) # encrypt - yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) + yield pgp.put_raw_key(PUBLIC_KEY, ADDRESS) pubkey = yield pgp.get_key(ADDRESS, private=False) cyphertext = yield pgp.encrypt(data, pubkey) @@ -121,7 +121,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): # decrypt yield self._assert_key_not_found(pgp, ADDRESS, private=True) - yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) + yield pgp.put_raw_key(PRIVATE_KEY, ADDRESS) privkey = yield pgp.get_key(ADDRESS, private=True) decrypted, _ = yield pgp.decrypt(cyphertext, privkey) self.assertEqual(decrypted, data) @@ -136,7 +136,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): data = 'data' pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=self.gpg_binary_path) - yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) + yield pgp.put_raw_key(PRIVATE_KEY, ADDRESS) privkey = yield pgp.get_key(ADDRESS, private=True) signed = pgp.sign(data, privkey) self.assertRaises( @@ -148,7 +148,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): data = 'data' pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=self.gpg_binary_path) - yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) + yield pgp.put_raw_key(PUBLIC_KEY, ADDRESS) self.assertRaises( AssertionError, pgp.sign, data, ADDRESS, OpenPGPKey) @@ -158,10 +158,10 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): data = 'data' pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=self.gpg_binary_path) - yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) + yield pgp.put_raw_key(PRIVATE_KEY, ADDRESS) privkey = yield pgp.get_key(ADDRESS, private=True) signed = pgp.sign(data, privkey) - yield pgp.put_ascii_key(PUBLIC_KEY_2, ADDRESS_2) + yield pgp.put_raw_key(PUBLIC_KEY_2, ADDRESS_2) wrongkey = yield pgp.get_key(ADDRESS_2) self.assertFalse(pgp.verify(signed, wrongkey)) @@ -170,7 +170,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): data = 'data' pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=self.gpg_binary_path) - yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) + yield pgp.put_raw_key(PRIVATE_KEY, ADDRESS) privkey = yield pgp.get_key(ADDRESS, private=True) pubkey = yield pgp.get_key(ADDRESS, private=False) self.failureResultOf( @@ -182,7 +182,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): data = 'data' pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=self.gpg_binary_path) - yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) + yield pgp.put_raw_key(PRIVATE_KEY, ADDRESS) privkey = yield pgp.get_key(ADDRESS, private=True) pubkey = yield pgp.get_key(ADDRESS, private=False) encrypted_and_signed = yield pgp.encrypt( @@ -196,11 +196,11 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): data = 'data' pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=self.gpg_binary_path) - yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) + yield pgp.put_raw_key(PRIVATE_KEY, ADDRESS) privkey = yield pgp.get_key(ADDRESS, private=True) pubkey = yield pgp.get_key(ADDRESS, private=False) encrypted_and_signed = yield pgp.encrypt(data, pubkey, sign=privkey) - yield pgp.put_ascii_key(PUBLIC_KEY_2, ADDRESS_2) + yield pgp.put_raw_key(PUBLIC_KEY_2, ADDRESS_2) wrongkey = yield pgp.get_key(ADDRESS_2) decrypted, validsign = yield pgp.decrypt(encrypted_and_signed, privkey, @@ -213,7 +213,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): data = 'data' pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=self.gpg_binary_path) - yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) + yield pgp.put_raw_key(PRIVATE_KEY, ADDRESS) privkey = yield pgp.get_key(ADDRESS, private=True) signed = pgp.sign(data, privkey, detach=False) pubkey = yield pgp.get_key(ADDRESS, private=False) @@ -225,11 +225,11 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=self.gpg_binary_path) - yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) + yield pgp.put_raw_key(PRIVATE_KEY, ADDRESS) pubkey = yield pgp.get_key(ADDRESS, private=False) privkey = yield pgp.get_key(ADDRESS, private=True) - yield pgp.put_ascii_key(PRIVATE_KEY_2, ADDRESS_2) + yield pgp.put_raw_key(PRIVATE_KEY_2, ADDRESS_2) pubkey2 = yield pgp.get_key(ADDRESS_2, private=False) privkey2 = yield pgp.get_key(ADDRESS_2, private=True) @@ -246,7 +246,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): data = 'data' pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=self.gpg_binary_path) - yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) + yield pgp.put_raw_key(PRIVATE_KEY, ADDRESS) privkey = yield pgp.get_key(ADDRESS, private=True) signature = yield pgp.sign(data, privkey, detach=True) pubkey = yield pgp.get_key(ADDRESS, private=False) @@ -272,7 +272,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_self_repair_no_keys(self): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=self.gpg_binary_path) - yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) + yield pgp.put_raw_key(PUBLIC_KEY, ADDRESS) get_from_index = self._soledad.get_from_index delete_doc = self._soledad.delete_doc @@ -304,7 +304,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=self.gpg_binary_path) - yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) + yield pgp.put_raw_key(PUBLIC_KEY, ADDRESS) self.assertEqual(self.count, 2) self._soledad.delete_doc = delete_doc -- cgit v1.2.3 From 75bd5f0d5e13a30abe35466ff004210551eb2069 Mon Sep 17 00:00:00 2001 From: Zara Gebru Date: Fri, 27 May 2016 12:03:39 +0200 Subject: [bug] fix invalid json warning when key fetch fails Raise KeyNotFound error, when nicknym throws response 404, as it fails to find a key and add tests. - Resolves: #7987 --- keymanager/src/leap/keymanager/__init__.py | 33 ++++++++++++-- .../src/leap/keymanager/tests/test_keymanager.py | 51 +++++++++++++++++++--- 2 files changed, 74 insertions(+), 10 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 194a4eef..aa0a9ac5 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -26,6 +26,8 @@ import json import urllib from leap.common import ca_bundle +from twisted.web import client +from twisted.web._responses import NOT_FOUND from ._version import get_versions @@ -209,12 +211,12 @@ class KeyManager(object): """ try: uri = self._nickserver_uri + '?address=' + address - content = yield self._async_client_pinned.request(str(uri), 'GET') + content = yield self._fetch_and_handle_404_from_nicknym(uri, address) json_content = json.loads(content) + + except KeyNotFound: + raise except IOError as e: - # FIXME: 404 doesnt raise today, but it wont produce json anyway - # if e.response.status_code == 404: - # raise KeyNotFound(address) logger.warning("HTTP error retrieving key: %r" % (e,)) logger.warning("%s" % (content,)) raise KeyNotFound(e.message), None, sys.exc_info()[2] @@ -232,6 +234,29 @@ class KeyManager(object): # 'Content-type is not JSON.') defer.returnValue(json_content) + def _fetch_and_handle_404_from_nicknym(self, uri, address): + """ + Send a GET request to C{uri} containing C{data}. + + :param uri: The URI of the request. + :type uri: str + :param address: The email corresponding to the key. + :type address: str + + :return: A deferred that will be fired with GET content as json (dict) + :rtype: Deferred + """ + def check_404(response): + if response.code == NOT_FOUND: + message = '%s: %s key not found.' % (response.code, address) + logger.warning(message) + raise KeyNotFound(message), None, sys.exc_info()[2] + return response + + d = self._async_client_pinned.request(str(uri), 'GET', callback=check_404) + d.addCallback(client.readBody) + return d + @defer.inlineCallbacks def _get_with_combined_ca_bundle(self, uri, data=None): """ diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 05c1cdd5..14f47f6a 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -30,6 +30,9 @@ from leap.common import ca_bundle from mock import Mock, MagicMock, patch from twisted.internet import defer from twisted.trial import unittest +from twisted.web._responses import NOT_FOUND + +from leap.keymanager import client from leap.keymanager import ( KeyNotFound, @@ -56,6 +59,7 @@ from leap.keymanager.tests import ( NICKSERVER_URI = "http://leap.se/" REMOTE_KEY_URL = "http://site.domain/key" +INVALID_MAIL_ADDRESS = "notexistingemail@example.org" class KeyManagerUtilTestCase(unittest.TestCase): @@ -229,15 +233,49 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): expected_url = NICKSERVER_URI + '?address=' + ADDRESS_2 def verify_the_call(_): - km._async_client_pinned.request.assert_called_once_with( - expected_url, - 'GET', - ) + used_kwargs = km._async_client_pinned.request.call_args[1] + km._async_client_pinned.request.assert_called_once_with(expected_url, 'GET', **used_kwargs) d = self._fetch_key(km, ADDRESS_2, PUBLIC_KEY_2) d.addCallback(verify_the_call) return d + def test_key_not_found_is_raised_if_key_search_responds_404(self): + """ + Test if key search request comes back with a 404 response then KeyNotFound is raised, with corresponding error message. + """ + km = self._key_manager(url=NICKSERVER_URI) + client.readBody = Mock(return_value=defer.succeed(None)) + km._async_client_pinned.request = Mock(return_value=defer.succeed(None)) + url = NICKSERVER_URI + '?address=' + INVALID_MAIL_ADDRESS + + d = km._fetch_and_handle_404_from_nicknym(url, INVALID_MAIL_ADDRESS) + + def check_key_not_found_is_raised_if_404(_): + used_kwargs = km._async_client_pinned.request.call_args[1] + check_404_callback = used_kwargs['callback'] + fake_response = Mock() + fake_response.code = NOT_FOUND + with self.assertRaisesRegexp(KeyNotFound, '404: %s key not found.' % INVALID_MAIL_ADDRESS): + check_404_callback(fake_response) + + d.addCallback(check_key_not_found_is_raised_if_404) + return d + + def test_non_existing_key_from_nicknym_is_relayed(self): + """ + Test if key search requests throws KeyNotFound, the same error is raised. + """ + km = self._key_manager(url=NICKSERVER_URI) + key_not_found_exception = KeyNotFound('some message') + km._async_client_pinned.request = Mock(side_effect=key_not_found_exception) + + def assert_key_not_found_raised(error): + self.assertEqual(error.value, key_not_found_exception) + + d = km._get_key_from_nicknym(INVALID_MAIL_ADDRESS) + d.addErrback(assert_key_not_found_raised) + @defer.inlineCallbacks def test_get_key_fetches_from_server(self): """ @@ -268,9 +306,10 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): """ data = json.dumps({'address': address, 'openpgp': key}) + client.readBody = Mock(return_value=defer.succeed(data)) + # mock the fetcher so it returns the key for ADDRESS_2 - km._async_client_pinned.request = Mock( - return_value=defer.succeed(data)) + km._async_client_pinned.request = Mock(return_value=defer.succeed(None)) km.ca_cert_path = 'cacertpath' # try to key get without fetching from server d_fail = km.get_key(address, OpenPGPKey, fetch_remote=False) -- cgit v1.2.3 From 485504e1b67f86e6db2cea1e1e3fa365dcb1dd4e Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Sun, 1 May 2016 13:50:02 -0300 Subject: [refactor] remove EncryptionScheme --- keymanager/src/leap/keymanager/keys.py | 311 ++------------------- keymanager/src/leap/keymanager/openpgp.py | 120 +++++++- .../src/leap/keymanager/tests/test_openpgp.py | 2 +- 3 files changed, 143 insertions(+), 290 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py index 1955d545..f2a12a9b 100644 --- a/keymanager/src/leap/keymanager/keys.py +++ b/keymanager/src/leap/keymanager/keys.py @@ -28,10 +28,9 @@ except ImportError: import logging import re import time -import traceback -from abc import ABCMeta, abstractmethod +from abc import ABCMeta from datetime import datetime from leap.common.check import leap_assert from twisted.internet import defer @@ -296,290 +295,32 @@ class EncryptionKey(object): "priv" if self.private else "publ") -# -# Encryption schemes -# - -class EncryptionScheme(object): +def init_indexes(soledad): """ - Abstract class for Encryption Schemes. - - A wrapper for a certain encryption schemes should know how to get and put - keys in local storage using Soledad, how to generate new keys and how to - find out about possibly encrypted content. + Initialize the database indexes. """ + leap_assert(soledad is not None, + "Cannot init indexes with null soledad") - __metaclass__ = ABCMeta - - def __init__(self, soledad): - """ - Initialize this Encryption Scheme. - - :param soledad: A Soledad instance for local storage of keys. - :type soledad: leap.soledad.Soledad - """ - self._soledad = soledad - self.deferred_init = self._init_indexes() - self.deferred_init.addCallback(self._migrate_documents_schema) - - def _init_indexes(self): - """ - Initialize the database indexes. - """ - leap_assert(self._soledad is not None, - "Cannot init indexes with null soledad") - - def init_idexes(indexes): - deferreds = [] - db_indexes = dict(indexes) - # Loop through the indexes we expect to find. - for name, expression in INDEXES.items(): - if name not in db_indexes: - # The index does not yet exist. - d = self._soledad.create_index(name, *expression) - deferreds.append(d) - elif expression != db_indexes[name]: - # The index exists but the definition is not what expected, - # so we delete it and add the proper index expression. - d = self._soledad.delete_index(name) - d.addCallback( - lambda _: - self._soledad.create_index(name, *expression)) - deferreds.append(d) - return defer.gatherResults(deferreds, consumeErrors=True) - - d = self._soledad.list_indexes() - d.addCallback(init_idexes) - return d - - def _migrate_documents_schema(self, _): - from leap.keymanager.migrator import KeyDocumentsMigrator - migrator = KeyDocumentsMigrator(self._soledad) - return migrator.migrate() - - def _wait_indexes(self, *methods): - """ - Methods that need to wait for the indexes to be ready. - - Heavily based on - http://blogs.fluidinfo.com/terry/2009/05/11/a-mixin-class-allowing-python-__init__-methods-to-work-with-twisted-deferreds/ - - :param methods: methods that need to wait for the indexes to be ready - :type methods: tuple(str) - """ - self.waiting = [] - self.stored = {} - - def restore(_): - for method in self.stored: - setattr(self, method, self.stored[method]) - for d in self.waiting: - d.callback(None) - - def makeWrapper(method): - def wrapper(*args, **kw): - d = defer.Deferred() - d.addCallback(lambda _: self.stored[method](*args, **kw)) - self.waiting.append(d) - return d - return wrapper - - for method in methods: - self.stored[method] = getattr(self, method) - setattr(self, method, makeWrapper(method)) - - self.deferred_init.addCallback(restore) - - @abstractmethod - def get_key(self, address, private=False): - """ - Get key from local storage. - - :param address: The address bound to the key. - :type address: str - :param private: Look for a private key instead of a public one? - :type private: bool - - :return: A Deferred which fires with the EncryptionKey bound to - address, or which fails with KeyNotFound if the key was not - found on local storage. - :rtype: Deferred - """ - pass - - @abstractmethod - def put_key(self, key): - """ - Put a key in local storage. - - :param key: The key to be stored. - :type key: EncryptionKey - - :return: A Deferred which fires when the key is in the storage. - :rtype: Deferred - """ - pass - - @abstractmethod - def gen_key(self, address): - """ - Generate a new key. - - :param address: The address bound to the key. - :type address: str - - :return: The key bound to C{address}. - :rtype: EncryptionKey - """ - pass - - @abstractmethod - def delete_key(self, key): - """ - Remove C{key} from storage. - - :param key: The key to be removed. - :type key: EncryptionKey - - :return: A Deferred which fires when the key is deleted, or which - fails with KeyNotFound if the key was not found on local - storage. - :rtype: Deferred - """ - pass - - @abstractmethod - def encrypt(self, data, pubkey, passphrase=None, sign=None): - """ - Encrypt C{data} using public @{pubkey} and sign with C{sign} key. - - :param data: The data to be encrypted. - :type data: str - :param pubkey: The key used to encrypt. - :type pubkey: EncryptionKey - :param sign: The key used for signing. - :type sign: EncryptionKey - - :return: The encrypted data. - :rtype: str - """ - pass - - @abstractmethod - def decrypt(self, data, privkey, passphrase=None, verify=None): - """ - Decrypt C{data} using private @{privkey} and verify with C{verify} key. - - :param data: The data to be decrypted. - :type data: str - :param privkey: The key used to decrypt. - :type privkey: OpenPGPKey - :param verify: The key used to verify a signature. - :type verify: OpenPGPKey - - :return: The decrypted data and if signature verifies - :rtype: (unicode, bool) - - :raise DecryptError: Raised if failed decrypting for some reason. - """ - pass - - @abstractmethod - def sign(self, data, privkey): - """ - Sign C{data} with C{privkey}. - - :param data: The data to be signed. - :type data: str - - :param privkey: The private key to be used to sign. - :type privkey: EncryptionKey - - :return: The signed data. - :rtype: str - """ - pass - - @abstractmethod - def verify(self, data, pubkey, detached_sig=None): - """ - 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: signature matches - :rtype: bool - """ - pass - - def _repair_key_docs(self, doclist): - """ - If there is more than one key for a key id try to self-repair it - - :return: a Deferred that will be fired with the valid key doc once all - the deletions are completed - :rtype: Deferred - """ - def log_key_doc(doc): - logger.error("\t%s: %s" % (doc.content[KEY_UIDS_KEY], - doc.content[KEY_FINGERPRINT_KEY])) - - def cmp_key(d1, d2): - return cmp(d1.content[KEY_REFRESHED_AT_KEY], - d2.content[KEY_REFRESHED_AT_KEY]) - - return self._repair_docs(doclist, cmp_key, log_key_doc) - - def _repair_active_docs(self, doclist): - """ - If there is more than one active doc for an address try to self-repair - it - - :return: a Deferred that will be fired with the valid active doc once - all the deletions are completed - :rtype: Deferred - """ - def log_active_doc(doc): - logger.error("\t%s: %s" % (doc.content[KEY_ADDRESS_KEY], - doc.content[KEY_FINGERPRINT_KEY])) - - def cmp_active(d1, d2): - res = cmp(d1.content[KEY_LAST_AUDITED_AT_KEY], - d2.content[KEY_LAST_AUDITED_AT_KEY]) - if res != 0: - return res - - used1 = (d1.content[KEY_SIGN_USED_KEY] + - d1.content[KEY_ENCR_USED_KEY]) - used2 = (d2.content[KEY_SIGN_USED_KEY] + - d2.content[KEY_ENCR_USED_KEY]) - return cmp(used1, used2) - - return self._repair_docs(doclist, cmp_active, log_active_doc) - - def _repair_docs(self, doclist, cmp_func, log_func): - logger.error("BUG ---------------------------------------------------") - logger.error("There is more than one doc of type %s:" - % (doclist[0].content[KEY_TYPE_KEY],)) - - doclist.sort(cmp=cmp_func, reverse=True) - log_func(doclist[0]) + def create_idexes(indexes): deferreds = [] - for doc in doclist[1:]: - log_func(doc) - d = self._soledad.delete_doc(doc) - deferreds.append(d) - - logger.error("") - logger.error(traceback.extract_stack()) - logger.error("BUG (please report above info) ------------------------") - d = defer.gatherResults(deferreds, consumeErrors=True) - d.addCallback(lambda _: doclist[0]) - return d + db_indexes = dict(indexes) + # Loop through the indexes we expect to find. + for name, expression in INDEXES.items(): + if name not in db_indexes: + # The index does not yet exist. + d = soledad.create_index(name, *expression) + deferreds.append(d) + elif expression != db_indexes[name]: + # The index exists but the definition is not what expected, + # so we delete it and add the proper index expression. + d = soledad.delete_index(name) + d.addCallback( + lambda _: + soledad.create_index(name, *expression)) + deferreds.append(d) + return defer.gatherResults(deferreds, consumeErrors=True) + + d = soledad.list_indexes() + d.addCallback(create_idexes) + return d diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 82050cc8..697ee8a6 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -22,6 +22,7 @@ import os import re import shutil import tempfile +import traceback import io @@ -36,13 +37,19 @@ from leap.common.check import leap_assert, leap_assert_type, leap_check from leap.keymanager import errors from leap.keymanager.keys import ( EncryptionKey, - EncryptionScheme, + init_indexes, is_address, build_key_from_dict, TYPE_FINGERPRINT_PRIVATE_INDEX, TYPE_ADDRESS_PRIVATE_INDEX, KEY_UIDS_KEY, KEY_FINGERPRINT_KEY, + KEY_REFRESHED_AT_KEY, + KEY_LAST_AUDITED_AT_KEY, + KEY_SIGN_USED_KEY, + KEY_ENCR_USED_KEY, + KEY_ADDRESS_KEY, + KEY_TYPE_KEY, KEYMANAGER_ACTIVE_TYPE, ) @@ -256,7 +263,7 @@ class OpenPGPKey(EncryptionKey): self.refreshed_at = datetime.now() -class OpenPGPScheme(EncryptionScheme): +class OpenPGPScheme(object): """ A wrapper for OpenPGP keys management and use (encryption, decyption, signing and verification). @@ -275,9 +282,49 @@ class OpenPGPScheme(EncryptionScheme): :param gpgbinary: Name for GnuPG binary executable. :type gpgbinary: C{str} """ - EncryptionScheme.__init__(self, soledad) - self._wait_indexes("get_key", "put_key") + self._soledad = soledad self._gpgbinary = gpgbinary + self.deferred_init = init_indexes(soledad) + self.deferred_init.addCallback(self._migrate_documents_schema) + self._wait_indexes("get_key", "put_key") + + def _migrate_documents_schema(self, _): + from leap.keymanager.migrator import KeyDocumentsMigrator + migrator = KeyDocumentsMigrator(self._soledad) + return migrator.migrate() + + def _wait_indexes(self, *methods): + """ + Methods that need to wait for the indexes to be ready. + + Heavily based on + http://blogs.fluidinfo.com/terry/2009/05/11/a-mixin-class-allowing-python-__init__-methods-to-work-with-twisted-deferreds/ + + :param methods: methods that need to wait for the indexes to be ready + :type methods: tuple(str) + """ + self.waiting = [] + self.stored = {} + + def restore(_): + for method in self.stored: + setattr(self, method, self.stored[method]) + for d in self.waiting: + d.callback(None) + + def makeWrapper(method): + def wrapper(*args, **kw): + d = defer.Deferred() + d.addCallback(lambda _: self.stored[method](*args, **kw)) + self.waiting.append(d) + return d + return wrapper + + for method in methods: + self.stored[method] = getattr(self, method) + setattr(self, method, makeWrapper(method)) + + self.deferred_init.addCallback(restore) # # Keys management @@ -849,6 +896,71 @@ class OpenPGPScheme(EncryptionScheme): return repair_func(doclist) return doclist[0] + def _repair_key_docs(self, doclist): + """ + If there is more than one key for a key id try to self-repair it + + :return: a Deferred that will be fired with the valid key doc once all + the deletions are completed + :rtype: Deferred + """ + def log_key_doc(doc): + logger.error("\t%s: %s" % (doc.content[KEY_UIDS_KEY], + doc.content[KEY_FINGERPRINT_KEY])) + + def cmp_key(d1, d2): + return cmp(d1.content[KEY_REFRESHED_AT_KEY], + d2.content[KEY_REFRESHED_AT_KEY]) + + return self._repair_docs(doclist, cmp_key, log_key_doc) + + def _repair_active_docs(self, doclist): + """ + If there is more than one active doc for an address try to self-repair + it + + :return: a Deferred that will be fired with the valid active doc once + all the deletions are completed + :rtype: Deferred + """ + def log_active_doc(doc): + logger.error("\t%s: %s" % (doc.content[KEY_ADDRESS_KEY], + doc.content[KEY_FINGERPRINT_KEY])) + + def cmp_active(d1, d2): + res = cmp(d1.content[KEY_LAST_AUDITED_AT_KEY], + d2.content[KEY_LAST_AUDITED_AT_KEY]) + if res != 0: + return res + + used1 = (d1.content[KEY_SIGN_USED_KEY] + + d1.content[KEY_ENCR_USED_KEY]) + used2 = (d2.content[KEY_SIGN_USED_KEY] + + d2.content[KEY_ENCR_USED_KEY]) + return cmp(used1, used2) + + return self._repair_docs(doclist, cmp_active, log_active_doc) + + def _repair_docs(self, doclist, cmp_func, log_func): + logger.error("BUG ---------------------------------------------------") + logger.error("There is more than one doc of type %s:" + % (doclist[0].content[KEY_TYPE_KEY],)) + + doclist.sort(cmp=cmp_func, reverse=True) + log_func(doclist[0]) + deferreds = [] + for doc in doclist[1:]: + log_func(doc) + d = self._soledad.delete_doc(doc) + deferreds.append(d) + + logger.error("") + logger.error(traceback.extract_stack()) + logger.error("BUG (please report above info) ------------------------") + d = defer.gatherResults(deferreds, consumeErrors=True) + d.addCallback(lambda _: doclist[0]) + return d + def process_key(key_data, gpgbinary, secret=False): with TempGPGWrapper(gpgbinary=gpgbinary) as gpg: diff --git a/keymanager/src/leap/keymanager/tests/test_openpgp.py b/keymanager/src/leap/keymanager/tests/test_openpgp.py index 68fb4e00..acb2c1c4 100644 --- a/keymanager/src/leap/keymanager/tests/test_openpgp.py +++ b/keymanager/src/leap/keymanager/tests/test_openpgp.py @@ -305,8 +305,8 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=self.gpg_binary_path) yield pgp.put_raw_key(PUBLIC_KEY, ADDRESS) - self.assertEqual(self.count, 2) self._soledad.delete_doc = delete_doc + self.assertEqual(self.count, 2) @inlineCallbacks def test_self_repair_five_active_docs(self): -- cgit v1.2.3 From e6db963ec7a867ec370edfee1076ba96d658d90a Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Sun, 1 May 2016 14:27:15 -0300 Subject: [refactor] move TempGPGWrapper to it's own file --- keymanager/src/leap/keymanager/openpgp.py | 114 +------------------------ keymanager/src/leap/keymanager/wrapper.py | 134 ++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 113 deletions(-) create mode 100644 keymanager/src/leap/keymanager/wrapper.py (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 697ee8a6..301c46c1 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -20,7 +20,6 @@ Infrastructure for using OpenPGP keys in Key Manager. import logging import os import re -import shutil import tempfile import traceback import io @@ -28,13 +27,13 @@ import io from datetime import datetime from multiprocessing import cpu_count -from gnupg import GPG from gnupg.gnupg import GPGUtilities from twisted.internet import defer from twisted.internet.threads import deferToThread from leap.common.check import leap_assert, leap_assert_type, leap_check from leap.keymanager import errors +from leap.keymanager.wrapper import TempGPGWrapper from leap.keymanager.keys import ( EncryptionKey, init_indexes, @@ -71,117 +70,6 @@ def from_thread(func, *args, **kwargs): return cpu_core_semaphore.run(call) -class TempGPGWrapper(object): - """ - A context manager that wraps a temporary GPG keyring which only contains - the keys given at object creation. - """ - - def __init__(self, keys=None, gpgbinary=None): - """ - Create an empty temporary keyring and import any given C{keys} into - it. - - :param keys: OpenPGP key, or list of. - :type keys: OpenPGPKey or list of OpenPGPKeys - :param gpgbinary: Name for GnuPG binary executable. - :type gpgbinary: C{str} - """ - self._gpg = None - self._gpgbinary = gpgbinary - if not keys: - keys = list() - if not isinstance(keys, list): - keys = [keys] - self._keys = keys - for key in keys: - leap_assert_type(key, OpenPGPKey) - - def __enter__(self): - """ - Build and return a GPG keyring containing the keys given on - object creation. - - :return: A GPG instance containing the keys given on object creation. - :rtype: gnupg.GPG - """ - self._build_keyring() - return self._gpg - - def __exit__(self, exc_type, exc_value, traceback): - """ - Ensure the gpg is properly destroyed. - """ - # TODO handle exceptions and log here - self._destroy_keyring() - - def _build_keyring(self): - """ - Create a GPG keyring containing the keys given on object creation. - - :return: A GPG instance containing the keys given on object creation. - :rtype: gnupg.GPG - """ - privkeys = [key for key in self._keys if key and key.private is True] - publkeys = [key for key in self._keys if key and key.private is False] - # here we filter out public keys that have a correspondent - # private key in the list because the private key_data by - # itself is enough to also have the public key in the keyring, - # and we want to count the keys afterwards. - - privfps = map(lambda privkey: privkey.fingerprint, privkeys) - publkeys = filter( - lambda pubkey: pubkey.fingerprint not in privfps, publkeys) - - listkeys = lambda: self._gpg.list_keys() - listsecretkeys = lambda: self._gpg.list_keys(secret=True) - - self._gpg = GPG(binary=self._gpgbinary, - homedir=tempfile.mkdtemp()) - leap_assert(len(listkeys()) is 0, 'Keyring not empty.') - - # import keys into the keyring: - # concatenating ascii-armored keys, which is correctly - # understood by GPG. - - self._gpg.import_keys("".join( - [x.key_data for x in publkeys + privkeys])) - - # assert the number of keys in the keyring - leap_assert( - len(listkeys()) == len(publkeys) + len(privkeys), - 'Wrong number of public keys in keyring: %d, should be %d)' % - (len(listkeys()), len(publkeys) + len(privkeys))) - leap_assert( - len(listsecretkeys()) == len(privkeys), - 'Wrong number of private keys in keyring: %d, should be %d)' % - (len(listsecretkeys()), len(privkeys))) - - def _destroy_keyring(self): - """ - Securely erase the keyring. - """ - # TODO: implement some kind of wiping of data or a more - # secure way that - # does not write to disk. - - try: - for secret in [True, False]: - for key in self._gpg.list_keys(secret=secret): - self._gpg.delete_keys( - key['fingerprint'], - secret=secret) - leap_assert(len(self._gpg.list_keys()) is 0, 'Keyring not empty!') - - except: - raise - - finally: - leap_assert(self._gpg.homedir != os.path.expanduser('~/.gnupg'), - "watch out! Tried to remove default gnupg home!") - shutil.rmtree(self._gpg.homedir) - - def _parse_address(address): """ Remove name, '<', '>' and the identity suffix after the '+' until the '@' diff --git a/keymanager/src/leap/keymanager/wrapper.py b/keymanager/src/leap/keymanager/wrapper.py new file mode 100644 index 00000000..4f36cec9 --- /dev/null +++ b/keymanager/src/leap/keymanager/wrapper.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- +# wrapper.py +# Copyright (C) 2016 LEAP +# +# 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 . +""" +GPG wrapper for temporary keyrings +""" +import os +import shutil +import tempfile +from gnupg import GPG + +from leap.common.check import leap_assert + + +class TempGPGWrapper(object): + """ + A context manager that wraps a temporary GPG keyring which only contains + the keys given at object creation. + """ + + def __init__(self, keys=None, gpgbinary=None): + """ + Create an empty temporary keyring and import any given C{keys} into + it. + + :param keys: OpenPGP key, or list of. + :type keys: OpenPGPKey or list of OpenPGPKeys + :param gpgbinary: Name for GnuPG binary executable. + :type gpgbinary: C{str} + """ + self._gpg = None + self._gpgbinary = gpgbinary + if not keys: + keys = list() + if not isinstance(keys, list): + keys = [keys] + self._keys = keys + + def __enter__(self): + """ + Build and return a GPG keyring containing the keys given on + object creation. + + :return: A GPG instance containing the keys given on object creation. + :rtype: gnupg.GPG + """ + self._build_keyring() + return self._gpg + + def __exit__(self, exc_type, exc_value, traceback): + """ + Ensure the gpg is properly destroyed. + """ + # TODO handle exceptions and log here + self._destroy_keyring() + + def _build_keyring(self): + """ + Create a GPG keyring containing the keys given on object creation. + + :return: A GPG instance containing the keys given on object creation. + :rtype: gnupg.GPG + """ + privkeys = [key for key in self._keys if key and key.private is True] + publkeys = [key for key in self._keys if key and key.private is False] + # here we filter out public keys that have a correspondent + # private key in the list because the private key_data by + # itself is enough to also have the public key in the keyring, + # and we want to count the keys afterwards. + + privfps = map(lambda privkey: privkey.fingerprint, privkeys) + publkeys = filter( + lambda pubkey: pubkey.fingerprint not in privfps, publkeys) + + listkeys = lambda: self._gpg.list_keys() + listsecretkeys = lambda: self._gpg.list_keys(secret=True) + + self._gpg = GPG(binary=self._gpgbinary, + homedir=tempfile.mkdtemp()) + leap_assert(len(listkeys()) is 0, 'Keyring not empty.') + + # import keys into the keyring: + # concatenating ascii-armored keys, which is correctly + # understood by GPG. + + self._gpg.import_keys("".join( + [x.key_data for x in publkeys + privkeys])) + + # assert the number of keys in the keyring + leap_assert( + len(listkeys()) == len(publkeys) + len(privkeys), + 'Wrong number of public keys in keyring: %d, should be %d)' % + (len(listkeys()), len(publkeys) + len(privkeys))) + leap_assert( + len(listsecretkeys()) == len(privkeys), + 'Wrong number of private keys in keyring: %d, should be %d)' % + (len(listsecretkeys()), len(privkeys))) + + def _destroy_keyring(self): + """ + Securely erase the keyring. + """ + # TODO: implement some kind of wiping of data or a more + # secure way that + # does not write to disk. + + try: + for secret in [True, False]: + for key in self._gpg.list_keys(secret=secret): + self._gpg.delete_keys( + key['fingerprint'], + secret=secret) + leap_assert(len(self._gpg.list_keys()) is 0, 'Keyring not empty!') + + except: + raise + + finally: + leap_assert(self._gpg.homedir != os.path.expanduser('~/.gnupg'), + "watch out! Tried to remove default gnupg home!") + shutil.rmtree(self._gpg.homedir) -- cgit v1.2.3 From e9d00c34a8b2dc7004d0e90098683fb599108b2e Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Sun, 1 May 2016 14:27:51 -0300 Subject: [refactor] remove EncryptionKey --- keymanager/src/leap/keymanager/keys.py | 88 ++++++++++++++++++---- keymanager/src/leap/keymanager/openpgp.py | 84 +-------------------- .../src/leap/keymanager/tests/test_openpgp.py | 2 +- 3 files changed, 79 insertions(+), 95 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py index f2a12a9b..1d039451 100644 --- a/keymanager/src/leap/keymanager/keys.py +++ b/keymanager/src/leap/keymanager/keys.py @@ -30,11 +30,12 @@ import re import time -from abc import ABCMeta from datetime import datetime from leap.common.check import leap_assert from twisted.internet import defer +from leap.keymanager import errors +from leap.keymanager.wrapper import TempGPGWrapper from leap.keymanager.validation import ValidationLevels logger = logging.getLogger(__name__) @@ -177,29 +178,21 @@ def _to_unix_time(date): return 0 -# -# Abstraction for encryption keys -# - -class EncryptionKey(object): +class OpenPGPKey(object): """ - Abstract class for encryption keys. - - A key is "validated" if the nicknym agent has bound the user address to a - public key. + Base class for OpenPGP keys. """ - __metaclass__ = ABCMeta - __slots__ = ('address', 'uids', 'fingerprint', 'key_data', 'private', 'length', 'expiry_date', 'validation', 'last_audited_at', 'refreshed_at', - 'encr_used', 'sign_used', '_index') + 'encr_used', 'sign_used', '_index', '_gpgbinary') - def __init__(self, address=None, uids=[], fingerprint="", + def __init__(self, address=None, gpgbinary=None, uids=[], fingerprint="", key_data="", private=False, length=0, expiry_date=None, validation=ValidationLevels.Weak_Chain, last_audited_at=None, refreshed_at=None, encr_used=False, sign_used=False): + self._gpgbinary = gpgbinary self.address = address if not uids and address: self.uids = [address] @@ -218,6 +211,57 @@ class EncryptionKey(object): self.sign_used = sign_used self._index = len(self.__slots__) + @property + def signatures(self): + """ + Get the key signatures + + :return: the key IDs that have signed the key + :rtype: list(str) + """ + with TempGPGWrapper(keys=[self], gpgbinary=self._gpgbinary) as gpg: + res = gpg.list_sigs(self.fingerprint) + for uid, sigs in res.sigs.iteritems(): + if parse_address(uid) in self.uids: + return sigs + + return [] + + def merge(self, newkey): + if newkey.fingerprint != self.fingerprint: + logger.critical( + "Can't put a key whith the same key_id and different " + "fingerprint: %s, %s" + % (newkey.fingerprint, self.fingerprint)) + raise errors.KeyFingerprintMismatch(newkey.fingerprint) + + with TempGPGWrapper(gpgbinary=self._gpgbinary) as gpg: + gpg.import_keys(self.key_data) + gpg.import_keys(newkey.key_data) + gpgkey = gpg.list_keys(secret=newkey.private).pop() + + if gpgkey['expires']: + self.expiry_date = datetime.fromtimestamp( + int(gpgkey['expires'])) + else: + self.expiry_date = None + + self.uids = [] + for uid in gpgkey['uids']: + self.uids.append(parse_address(uid)) + + self.length = int(gpgkey['length']) + self.key_data = gpg.export_keys(gpgkey['fingerprint'], + secret=self.private) + + if newkey.validation > self.validation: + self.validation = newkey.validation + if newkey.last_audited_at > self.last_audited_at: + self.validation = newkey.last_audited_at + self.encr_used = newkey.encr_used or self.encr_used + self.sign_used = newkey.sign_used or self.sign_used + self.refreshed_at = datetime.now() + def get_json(self): """ Return a JSON string describing this key. @@ -295,6 +339,22 @@ class EncryptionKey(object): "priv" if self.private else "publ") +def parse_address(address): + """ + Remove name, '<', '>' and the identity suffix after the '+' until the '@' + e.g.: test_user+something@provider.com becomes test_user@provider.com + since the key belongs to the identity without the '+' suffix. + + :type address: str + :rtype: str + """ + mail_regex = '(.*<)?([\w.-]+)(\+.*)?(@[\w.-]+)(>.*)?' + match = re.match(mail_regex, address) + if match is None: + return None + return ''.join(match.group(2, 4)) + + def init_indexes(soledad): """ Initialize the database indexes. diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 301c46c1..f8cc9765 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -35,9 +35,10 @@ from leap.common.check import leap_assert, leap_assert_type, leap_check from leap.keymanager import errors from leap.keymanager.wrapper import TempGPGWrapper from leap.keymanager.keys import ( - EncryptionKey, + OpenPGPKey, init_indexes, is_address, + parse_address, build_key_from_dict, TYPE_FINGERPRINT_PRIVATE_INDEX, TYPE_ADDRESS_PRIVATE_INDEX, @@ -70,87 +71,10 @@ def from_thread(func, *args, **kwargs): return cpu_core_semaphore.run(call) -def _parse_address(address): - """ - Remove name, '<', '>' and the identity suffix after the '+' until the '@' - e.g.: test_user+something@provider.com becomes test_user@provider.com - since the key belongs to the identity without the '+' suffix. - - :type address: str - :rtype: str - """ - mail_regex = '(.*<)?([\w.-]+)(\+.*)?(@[\w.-]+)(>.*)?' - match = re.match(mail_regex, address) - if match is None: - return None - return ''.join(match.group(2, 4)) - - # # The OpenPGP wrapper # -class OpenPGPKey(EncryptionKey): - """ - Base class for OpenPGP keys. - """ - - def __init__(self, address=None, gpgbinary=None, **kwargs): - self._gpgbinary = gpgbinary - super(OpenPGPKey, self).__init__(address, **kwargs) - - @property - def signatures(self): - """ - Get the key signatures - - :return: the key IDs that have signed the key - :rtype: list(str) - """ - with TempGPGWrapper(keys=[self], gpgbinary=self._gpgbinary) as gpg: - res = gpg.list_sigs(self.fingerprint) - for uid, sigs in res.sigs.iteritems(): - if _parse_address(uid) in self.uids: - return sigs - - return [] - - def merge(self, newkey): - if newkey.fingerprint != self.fingerprint: - logger.critical( - "Can't put a key whith the same key_id and different " - "fingerprint: %s, %s" - % (newkey.fingerprint, self.fingerprint)) - raise errors.KeyFingerprintMismatch(newkey.fingerprint) - - with TempGPGWrapper(gpgbinary=self._gpgbinary) as gpg: - gpg.import_keys(self.key_data) - gpg.import_keys(newkey.key_data) - gpgkey = gpg.list_keys(secret=newkey.private).pop() - - if gpgkey['expires']: - self.expiry_date = datetime.fromtimestamp( - int(gpgkey['expires'])) - else: - self.expiry_date = None - - self.uids = [] - for uid in gpgkey['uids']: - self.uids.append(_parse_address(uid)) - - self.length = int(gpgkey['length']) - self.key_data = gpg.export_keys(gpgkey['fingerprint'], - secret=self.private) - - if newkey.validation > self.validation: - self.validation = newkey.validation - if newkey.last_audited_at > self.last_audited_at: - self.validation = newkey.last_audited_at - self.encr_used = newkey.encr_used or self.encr_used - self.sign_used = newkey.sign_used or self.sign_used - self.refreshed_at = datetime.now() - - class OpenPGPScheme(object): """ A wrapper for OpenPGP keys management and use (encryption, decyption, @@ -298,7 +222,7 @@ class OpenPGPScheme(object): local storage. :rtype: Deferred """ - address = _parse_address(address) + address = parse_address(address) def build_key((keydoc, activedoc)): if keydoc is None: @@ -868,7 +792,7 @@ def build_gpg_key(key_info, key_data, address=None, gpgbinary=None): expiry_date = datetime.fromtimestamp(int(key_info['expires'])) uids = [] for uid in key_info['uids']: - uids.append(_parse_address(uid)) + uids.append(parse_address(uid)) if address and address not in uids: raise errors.KeyAddressMismatch("UIDs %s found, but expected %s" % (str(uids), address)) diff --git a/keymanager/src/leap/keymanager/tests/test_openpgp.py b/keymanager/src/leap/keymanager/tests/test_openpgp.py index acb2c1c4..0e39dab7 100644 --- a/keymanager/src/leap/keymanager/tests/test_openpgp.py +++ b/keymanager/src/leap/keymanager/tests/test_openpgp.py @@ -33,7 +33,7 @@ from leap.keymanager.keys import ( TYPE_FINGERPRINT_PRIVATE_INDEX, TYPE_ADDRESS_PRIVATE_INDEX, ) -from leap.keymanager.openpgp import OpenPGPKey +from leap.keymanager.keys import OpenPGPKey from leap.keymanager.tests import ( KeyManagerWithSoledadTestCase, ADDRESS, -- cgit v1.2.3 From ea0dfc4f873c2a23800b92bc83a952160ce9decc Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Sun, 1 May 2016 15:23:15 -0300 Subject: [feat] remove the keytype support - Resolves: #8031 --- keymanager/src/leap/keymanager/__init__.py | 161 +++++---------------- keymanager/src/leap/keymanager/tests/__init__.py | 5 +- .../src/leap/keymanager/tests/test_keymanager.py | 147 +++++++++---------- .../src/leap/keymanager/tests/test_validation.py | 94 ++++++------ 4 files changed, 152 insertions(+), 255 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index aa0a9ac5..f72f4036 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -71,9 +71,7 @@ from leap.common.decorators import memoized_method from leap.keymanager.errors import ( KeyNotFound, - KeyAddressMismatch, KeyNotValidUpgrade, - UnsupportedKeyTypeError, InvalidSignature ) from leap.keymanager.validation import ValidationLevels, can_upgrade @@ -138,11 +136,7 @@ class KeyManager(object): self.api_uri = api_uri self.api_version = api_version self.uid = uid - # a dict to map key types to their handlers - self._wrapper_map = { - OpenPGPKey: OpenPGPScheme(soledad, gpgbinary=gpgbinary), - # other types of key will be added to this mapper. - } + self._openpgp = OpenPGPScheme(soledad, gpgbinary=gpgbinary) # the following are used to perform https requests self._fetcher = requests self._combined_ca_bundle = self._create_combined_bundle_file() @@ -184,20 +178,6 @@ class KeyManager(object): return tmp_file.name - def _key_class_from_type(self, ktype): - """ - Given a class type, return a class - - :param ktype: string representation of a class name - :type ktype: str - - :return: A class with the matching name - :rtype: classobj or type - """ - return filter( - lambda klass: klass.__name__ == ktype, - self._wrapper_map).pop() - @defer.inlineCallbacks def _get_key_from_nicknym(self, address): """ @@ -349,7 +329,6 @@ class KeyManager(object): yield self.put_raw_key( server_keys['openpgp'], - OpenPGPKey, address=address, validation=validation_level) @@ -357,24 +336,19 @@ class KeyManager(object): # key management # - def send_key(self, ktype): + def send_key(self): """ - Send user's key of type ktype to provider. + Send user's key to provider. Public key bound to user's is sent to provider, which will sign it and replace any prior keys for the same address in its database. - :param ktype: The type of the key. - :type ktype: subclass of EncryptionKey - :return: A Deferred which fires when the key is sent, or which fails with KeyNotFound if the key was not found in local database. :rtype: Deferred :raise UnsupportedKeyTypeError: if invalid key type """ - self._assert_supported_key_type(ktype) - def send(pubkey): data = { self.PUBKEY_KEY: pubkey.key_data @@ -390,40 +364,33 @@ class KeyManager(object): return d d = self.get_key( - self._address, ktype, private=False, fetch_remote=False) + self._address, private=False, fetch_remote=False) d.addCallback(send) return d - def get_key(self, address, ktype, private=False, fetch_remote=True): + def get_key(self, address, private=False, fetch_remote=True): """ - Return a key of type ktype bound to address. + Return a key bound to address. First, search for the key in local storage. If it is not available, then try to fetch from nickserver. :param address: The address bound to the key. :type address: str - :param ktype: The type of the key. - :type ktype: subclass of EncryptionKey :param private: Look for a private key instead of a public one? :type private: bool :param fetch_remote: If key not found in local storage try to fetch from nickserver :type fetch_remote: bool - :return: A Deferred which fires with an EncryptionKey of type ktype - bound to address, or which fails with KeyNotFound if no key - was found neither locally or in keyserver. + :return: A Deferred which fires with an EncryptionKey bound to address, + or which fails with KeyNotFound if no key was found neither + locally or in keyserver. :rtype: Deferred :raise UnsupportedKeyTypeError: if invalid key type """ - self._assert_supported_key_type(ktype) logger.debug("getting key for %s" % (address,)) - leap_assert( - ktype in self._wrapper_map, - 'Unkown key type: %s.' % str(ktype)) - _keys = self._wrapper_map[ktype] emit_async(catalog.KEYMANAGER_LOOKING_FOR_KEY, address) @@ -445,12 +412,12 @@ class KeyManager(object): emit_async(catalog.KEYMANAGER_LOOKING_FOR_KEY, address) d = self._fetch_keys_from_server(address) d.addCallback( - lambda _: _keys.get_key(address, private=False)) + lambda _: self._openpgp.get_key(address, private=False)) d.addCallback(key_found) return d # return key if it exists in local database - d = _keys.get_key(address, private=private) + d = self._openpgp.get_key(address, private=private) d.addCallbacks(key_found, key_not_found) return d @@ -467,9 +434,7 @@ class KeyManager(object): # TODO: should it be based on activedocs? def build_keys(docs): return map( - lambda doc: build_key_from_dict( - self._key_class_from_type(doc.content['type']), - doc.content), + lambda doc: build_key_from_dict(OpenPGPKey, doc.content), docs) # XXX: there is no check that the soledad indexes are ready, as it @@ -484,20 +449,15 @@ class KeyManager(object): d.addCallback(build_keys) return d - def gen_key(self, ktype): + def gen_key(self): """ - Generate a key of type ktype bound to the user's address. - - :param ktype: The type of the key. - :type ktype: subclass of EncryptionKey + Generate a key bound to the user's address. :return: A Deferred which fires with the generated EncryptionKey. :rtype: Deferred :raise UnsupportedKeyTypeError: if invalid key type """ - self._assert_supported_key_type(ktype) - _keys = self._wrapper_map[ktype] def signal_finished(key): emit_async( @@ -506,7 +466,7 @@ class KeyManager(object): emit_async(catalog.KEYMANAGER_STARTED_KEY_GENERATION, self._address) - d = _keys.gen_key(self._address) + d = self.openpg.gen_key(self._address) d.addCallback(signal_finished) return d @@ -564,7 +524,7 @@ class KeyManager(object): # encrypt/decrypt and sign/verify API # - def encrypt(self, data, address, ktype, passphrase=None, sign=None, + def encrypt(self, data, address, passphrase=None, sign=None, cipher_algo='AES256', fetch_remote=True): """ Encrypt data with the public key bound to address and sign with with @@ -574,8 +534,6 @@ class KeyManager(object): :type data: str :param address: The address to encrypt it for. :type address: str - :param ktype: The type of the key. - :type ktype: subclass of EncryptionKey :param passphrase: The passphrase for the secret key used for the signature. :type passphrase: str @@ -595,30 +553,28 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ - self._assert_supported_key_type(ktype) - _keys = self._wrapper_map[ktype] @defer.inlineCallbacks def encrypt(keys): pubkey, signkey = keys - encrypted = yield _keys.encrypt( + encrypted = yield self._openpgp.encrypt( data, pubkey, passphrase, sign=signkey, cipher_algo=cipher_algo) if not pubkey.encr_used: pubkey.encr_used = True - yield _keys.put_key(pubkey) + yield self._openpgp.put_key(pubkey) defer.returnValue(encrypted) - dpub = self.get_key(address, ktype, private=False, + dpub = self.get_key(address, private=False, fetch_remote=fetch_remote) dpriv = defer.succeed(None) if sign is not None: - dpriv = self.get_key(sign, ktype, private=True) + dpriv = self.get_key(sign, private=True) d = defer.gatherResults([dpub, dpriv], consumeErrors=True) d.addCallbacks(encrypt, self._extract_first_error) return d - def decrypt(self, data, address, ktype, passphrase=None, verify=None, + def decrypt(self, data, address, passphrase=None, verify=None, fetch_remote=True): """ Decrypt data using private key from address and verify with public key @@ -628,8 +584,6 @@ class KeyManager(object): :type data: str :param address: The address to whom data was encrypted. :type address: str - :param ktype: The type of the key. - :type ktype: subclass of EncryptionKey :param passphrase: The passphrase for the secret key used for decryption. :type passphrase: str @@ -649,13 +603,11 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ - self._assert_supported_key_type(ktype) - _keys = self._wrapper_map[ktype] @defer.inlineCallbacks def decrypt(keys): pubkey, privkey = keys - decrypted, signed = yield _keys.decrypt( + decrypted, signed = yield self._openpgp.decrypt( data, privkey, passphrase=passphrase, verify=pubkey) if pubkey is None: signature = KeyNotFound(verify) @@ -663,7 +615,7 @@ class KeyManager(object): signature = pubkey if not pubkey.sign_used: pubkey.sign_used = True - yield _keys.put_key(pubkey) + yield self._openpgp.put_key(pubkey) defer.returnValue((decrypted, signature)) else: signature = InvalidSignature( @@ -671,10 +623,10 @@ class KeyManager(object): (pubkey.fingerprint,)) defer.returnValue((decrypted, signature)) - dpriv = self.get_key(address, ktype, private=True) + dpriv = self.get_key(address, private=True) dpub = defer.succeed(None) if verify is not None: - dpub = self.get_key(verify, ktype, private=False, + dpub = self.get_key(verify, private=False, fetch_remote=fetch_remote) dpub.addErrback(lambda f: None if f.check(KeyNotFound) else f) d = defer.gatherResults([dpub, dpriv], consumeErrors=True) @@ -684,7 +636,7 @@ class KeyManager(object): def _extract_first_error(self, failure): return failure.value.subFailure - def sign(self, data, address, ktype, digest_algo='SHA512', clearsign=False, + def sign(self, data, address, digest_algo='SHA512', clearsign=False, detach=True, binary=False): """ Sign data with private key bound to address. @@ -693,8 +645,6 @@ class KeyManager(object): :type data: str :param address: The address to be used to sign. :type address: EncryptionKey - :param ktype: The type of the key. - :type ktype: subclass of EncryptionKey :param digest_algo: The hash digest to use. :type digest_algo: str :param clearsign: If True, create a cleartext signature. @@ -712,19 +662,17 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ - self._assert_supported_key_type(ktype) - _keys = self._wrapper_map[ktype] def sign(privkey): - return _keys.sign( + return self._openpgp.sign( data, privkey, digest_algo=digest_algo, clearsign=clearsign, detach=detach, binary=binary) - d = self.get_key(address, ktype, private=True) + d = self.get_key(address, private=True) d.addCallback(sign) return d - def verify(self, data, address, ktype, detached_sig=None, + def verify(self, data, address, detached_sig=None, fetch_remote=True): """ Verify signed data with private key bound to address, eventually using @@ -734,8 +682,6 @@ class KeyManager(object): :type data: str :param address: The address to be used to verify. :type address: EncryptionKey - :param ktype: The type of the key. - :type ktype: subclass of EncryptionKey :param detached_sig: A detached signature. If given, C{data} is verified using this detached signature. :type detached_sig: str @@ -751,16 +697,14 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ - self._assert_supported_key_type(ktype) - _keys = self._wrapper_map[ktype] def verify(pubkey): - signed = _keys.verify( + signed = self._openpgp.verify( data, pubkey, detached_sig=detached_sig) if signed: if not pubkey.sign_used: pubkey.sign_used = True - d = _keys.put_key(pubkey) + d = self._openpgp.put_key(pubkey) d.addCallback(lambda _: pubkey) return d return pubkey @@ -769,7 +713,7 @@ class KeyManager(object): 'Failed to verify signature with key %s' % (pubkey.fingerprint,)) - d = self.get_key(address, ktype, private=False, + d = self.get_key(address, private=False, fetch_remote=fetch_remote) d.addCallback(verify) return d @@ -787,9 +731,7 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ - self._assert_supported_key_type(type(key)) - _keys = self._wrapper_map[type(key)] - return _keys.delete_key(key) + return self._openpgp.delete_key(key) def put_key(self, key): """ @@ -805,9 +747,6 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ - ktype = type(key) - self._assert_supported_key_type(ktype) - _keys = self._wrapper_map[ktype] def old_key_not_found(failure): if failure.check(KeyNotFound): @@ -817,26 +756,24 @@ class KeyManager(object): def check_upgrade(old_key): if key.private or can_upgrade(key, old_key): - return _keys.put_key(key) + return self._openpgp.put_key(key) else: raise KeyNotValidUpgrade( "Key %s can not be upgraded by new key %s" % (old_key.fingerprint, key.fingerprint)) - d = _keys.get_key(key.address, private=key.private) + d = self._openpgp.get_key(key.address, private=key.private) d.addErrback(old_key_not_found) d.addCallback(check_upgrade) return d - def put_raw_key(self, key, ktype, address, + def put_raw_key(self, key, address, validation=ValidationLevels.Weak_Chain): """ Put raw key bound to address in local storage. :param key: The ascii key to be stored :type key: str - :param ktype: the type of the key. - :type ktype: subclass of EncryptionKey :param address: address for which this key will be active :type address: str :param validation: validation level for this key @@ -853,10 +790,7 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ - self._assert_supported_key_type(ktype) - _keys = self._wrapper_map[ktype] - - pubkey, privkey = _keys.parse_key(key, address) + pubkey, privkey = self._openpgp.parse_key(key, address) if pubkey is None: return defer.fail(KeyNotFound(key)) @@ -868,8 +802,7 @@ class KeyManager(object): return d @defer.inlineCallbacks - def fetch_key(self, address, uri, ktype, - validation=ValidationLevels.Weak_Chain): + def fetch_key(self, address, uri, validation=ValidationLevels.Weak_Chain): """ Fetch a public key bound to address from the network and put it in local storage. @@ -878,8 +811,6 @@ class KeyManager(object): :type address: str :param uri: The URI of the key. :type uri: str - :param ktype: the type of the key. - :type ktype: subclass of EncryptionKey :param validation: validation level for this key (default: 'Weak_Chain') :type validation: ValidationLevels @@ -893,32 +824,18 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ - self._assert_supported_key_type(ktype) - _keys = self._wrapper_map[ktype] logger.info("Fetch key for %s from %s" % (address, uri)) ascii_content = yield self._get_with_combined_ca_bundle(uri) # XXX parse binary keys - pubkey, _ = _keys.parse_key(ascii_content, address) + pubkey, _ = self._openpgp.parse_key(ascii_content, address) if pubkey is None: raise KeyNotFound(uri) pubkey.validation = validation yield self.put_key(pubkey) - def _assert_supported_key_type(self, ktype): - """ - Check if ktype is one of the supported key types - - :param ktype: the type of the key. - :type ktype: subclass of EncryptionKey - - :raise UnsupportedKeyTypeError: if invalid key type - """ - if ktype not in self._wrapper_map: - raise UnsupportedKeyTypeError(str(ktype)) - def _split_email(address): """ diff --git a/keymanager/src/leap/keymanager/tests/__init__.py b/keymanager/src/leap/keymanager/tests/__init__.py index 2a6a3f1e..a20e1fdf 100644 --- a/keymanager/src/leap/keymanager/tests/__init__.py +++ b/keymanager/src/leap/keymanager/tests/__init__.py @@ -27,7 +27,6 @@ from twisted.trial import unittest from leap.common.testing.basetest import BaseLeapTest from leap.soledad.client import Soledad from leap.keymanager import KeyManager -from leap.keymanager.openpgp import OpenPGPKey PATH = os.path.dirname(os.path.realpath(__file__)) @@ -56,7 +55,7 @@ class KeyManagerWithSoledadTestCase(unittest.TestCase, BaseLeapTest): km = self._key_manager() # wait for the indexes to be ready for the tear down - d = km._wrapper_map[OpenPGPKey].deferred_init + d = km._openpgp.deferred_init d.addCallback(lambda _: self.delete_all_keys(km)) d.addCallback(lambda _: self.tearDownEnv()) d.addCallback(lambda _: self._soledad.close()) @@ -66,7 +65,7 @@ class KeyManagerWithSoledadTestCase(unittest.TestCase, BaseLeapTest): def delete_keys(keys): deferreds = [] for key in keys: - d = km._wrapper_map[key.__class__].delete_key(key) + d = km._openpgp.delete_key(key) deferreds.append(d) return gatherResults(deferreds) diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 14f47f6a..c55a3c38 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -34,13 +34,9 @@ from twisted.web._responses import NOT_FOUND from leap.keymanager import client -from leap.keymanager import ( - KeyNotFound, - KeyAddressMismatch, - errors -) -from leap.keymanager.openpgp import OpenPGPKey +from leap.keymanager import errors from leap.keymanager.keys import ( + OpenPGPKey, is_address, build_key_from_dict, ) @@ -140,7 +136,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): @defer.inlineCallbacks def test_get_all_keys_in_db(self): km = self._key_manager() - yield km._wrapper_map[OpenPGPKey].put_raw_key(PRIVATE_KEY, ADDRESS) + yield km._openpgp.put_raw_key(PRIVATE_KEY, ADDRESS) # get public keys keys = yield km.get_all_keys(False) self.assertEqual(len(keys), 1, 'Wrong number of keys') @@ -155,10 +151,9 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): @defer.inlineCallbacks def test_get_public_key(self): km = self._key_manager() - yield km._wrapper_map[OpenPGPKey].put_raw_key(PRIVATE_KEY, ADDRESS) + yield km._openpgp.put_raw_key(PRIVATE_KEY, ADDRESS) # get the key - key = yield km.get_key(ADDRESS, OpenPGPKey, private=False, - fetch_remote=False) + key = yield km.get_key(ADDRESS, private=False, fetch_remote=False) self.assertTrue(key is not None) self.assertTrue(ADDRESS in key.uids) self.assertEqual( @@ -168,10 +163,9 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): @defer.inlineCallbacks def test_get_public_key_with_binary_private_key(self): km = self._key_manager() - yield km._wrapper_map[OpenPGPKey].put_raw_key(self.get_private_binary_key(), ADDRESS) + yield km._openpgp.put_raw_key(self.get_private_binary_key(), ADDRESS) # get the key - key = yield km.get_key(ADDRESS, OpenPGPKey, private=False, - fetch_remote=False) + key = yield km.get_key(ADDRESS, private=False, fetch_remote=False) self.assertTrue(key is not None) self.assertTrue(ADDRESS in key.uids) self.assertEqual( @@ -181,10 +175,9 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): @defer.inlineCallbacks def test_get_private_key(self): km = self._key_manager() - yield km._wrapper_map[OpenPGPKey].put_raw_key(PRIVATE_KEY, ADDRESS) + yield km._openpgp.put_raw_key(PRIVATE_KEY, ADDRESS) # get the key - key = yield km.get_key(ADDRESS, OpenPGPKey, private=True, - fetch_remote=False) + key = yield km.get_key(ADDRESS, private=True, fetch_remote=False) self.assertTrue(key is not None) self.assertTrue(ADDRESS in key.uids) self.assertEqual( @@ -193,8 +186,8 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): def test_send_key_raises_key_not_found(self): km = self._key_manager() - d = km.send_key(OpenPGPKey) - return self.assertFailure(d, KeyNotFound) + d = km.send_key() + return self.assertFailure(d, errors.KeyNotFound) @defer.inlineCallbacks def test_send_key(self): @@ -203,7 +196,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): """ token = "mytoken" km = self._key_manager(token=token) - yield km._wrapper_map[OpenPGPKey].put_raw_key(PUBLIC_KEY, ADDRESS) + yield km._openpgp.put_raw_key(PUBLIC_KEY, ADDRESS) km._async_client_pinned.request = Mock(return_value=defer.succeed('')) # the following data will be used on the send km.ca_cert_path = 'capath' @@ -211,9 +204,9 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km.uid = 'myuid' km.api_uri = 'apiuri' km.api_version = 'apiver' - yield km.send_key(OpenPGPKey) + yield km.send_key() # setup expected args - pubkey = yield km.get_key(km._address, OpenPGPKey) + pubkey = yield km.get_key(km._address) data = urllib.urlencode({ km.PUBKEY_KEY: pubkey.key_data, }) @@ -312,10 +305,10 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km._async_client_pinned.request = Mock(return_value=defer.succeed(None)) km.ca_cert_path = 'cacertpath' # try to key get without fetching from server - d_fail = km.get_key(address, OpenPGPKey, fetch_remote=False) - d = self.assertFailure(d_fail, KeyNotFound) + d_fail = km.get_key(address, fetch_remote=False) + d = self.assertFailure(d_fail, errors.KeyNotFound) # try to get key fetching from server. - d.addCallback(lambda _: km.get_key(address, OpenPGPKey)) + d.addCallback(lambda _: km.get_key(address)) return d @defer.inlineCallbacks @@ -325,8 +318,8 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): """ km = self._key_manager(url=NICKSERVER_URI) - yield km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) - key = yield km.get_key(ADDRESS, OpenPGPKey) + yield km.put_raw_key(PUBLIC_KEY, ADDRESS) + key = yield km.get_key(ADDRESS) self.assertIsInstance(key, OpenPGPKey) self.assertTrue(ADDRESS in key.uids) @@ -337,8 +330,8 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): """ km = self._key_manager(url=NICKSERVER_URI) - yield km.put_raw_key(self.get_public_binary_key(), OpenPGPKey, ADDRESS) - key = yield km.get_key(ADDRESS, OpenPGPKey) + yield km.put_raw_key(self.get_public_binary_key(), ADDRESS) + key = yield km.get_key(ADDRESS) self.assertIsInstance(key, OpenPGPKey) self.assertTrue(ADDRESS in key.uids) @@ -353,8 +346,8 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km._async_client.request = Mock(return_value=defer.succeed(PUBLIC_KEY)) - yield km.fetch_key(ADDRESS, "http://site.domain/key", OpenPGPKey) - key = yield km.get_key(ADDRESS, OpenPGPKey) + yield km.fetch_key(ADDRESS, "http://site.domain/key") + key = yield km.get_key(ADDRESS) self.assertEqual(KEY_FINGERPRINT, key.fingerprint) @defer.inlineCallbacks @@ -365,10 +358,11 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): """ km = self._key_manager() - km._async_client.request = Mock(return_value=defer.succeed(self.get_public_binary_key())) + km._async_client.request = Mock( + return_value=defer.succeed(self.get_public_binary_key())) - yield km.fetch_key(ADDRESS, "http://site.domain/key", OpenPGPKey) - key = yield km.get_key(ADDRESS, OpenPGPKey) + yield km.fetch_key(ADDRESS, "http://site.domain/key") + key = yield km.get_key(ADDRESS) self.assertEqual(KEY_FINGERPRINT, key.fingerprint) def test_fetch_uri_empty_key(self): @@ -382,8 +376,8 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): content = "" km._fetcher.get = Mock(return_value=Response()) - d = km.fetch_key(ADDRESS, "http://site.domain/key", OpenPGPKey) - return self.assertFailure(d, KeyNotFound) + d = km.fetch_key(ADDRESS, "http://site.domain/key") + return self.assertFailure(d, errors.KeyNotFound) def test_fetch_uri_address_differ(self): """ @@ -393,8 +387,8 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km = self._key_manager() km._async_client.request = Mock(return_value=defer.succeed(PUBLIC_KEY)) - d = km.fetch_key(ADDRESS_2, "http://site.domain/key", OpenPGPKey) - return self.assertFailure(d, KeyAddressMismatch) + d = km.fetch_key(ADDRESS_2, "http://site.domain/key") + return self.assertFailure(d, errors.KeyAddressMismatch) def _mock_get_response(self, km, body): km._async_client.request = MagicMock(return_value=defer.succeed(body)) @@ -407,7 +401,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km = self._key_manager(ca_cert_path=ca_cert_path) get_mock = self._mock_get_response(km, PUBLIC_KEY_OTHER) - yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL, OpenPGPKey) + yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL) get_mock.assert_called_once_with(REMOTE_KEY_URL, 'GET') @@ -417,7 +411,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km = self._key_manager(ca_cert_path=ca_cert_path) get_mock = self._mock_get_response(km, PUBLIC_KEY_OTHER) - yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL, OpenPGPKey) + yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL) get_mock.assert_called_once_with(REMOTE_KEY_URL, 'GET') @@ -427,7 +421,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km = self._key_manager(ca_cert_path=ca_cert_path) get_mock = self._mock_get_response(km, PUBLIC_KEY_OTHER) - yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL, OpenPGPKey) + yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL) get_mock.assert_called_once_with(REMOTE_KEY_URL, 'GET') @@ -445,7 +439,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km = self._key_manager(ca_cert_path=ca_cert_path) get_mock = self._mock_get_response(km, PUBLIC_KEY_OTHER) - yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL, OpenPGPKey) + yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL) # assert that combined bundle file is passed to get call get_mock.assert_called_once_with(REMOTE_KEY_URL, 'GET') @@ -470,16 +464,15 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): def test_decrypt_updates_sign_used_for_signer(self): # given km = self._key_manager() - yield km._wrapper_map[OpenPGPKey].put_raw_key(PRIVATE_KEY, ADDRESS) - yield km._wrapper_map[OpenPGPKey].put_raw_key( - PRIVATE_KEY_2, ADDRESS_2) - encdata = yield km.encrypt('data', ADDRESS, OpenPGPKey, - sign=ADDRESS_2, fetch_remote=False) + yield km._openpgp.put_raw_key(PRIVATE_KEY, ADDRESS) + yield km._openpgp.put_raw_key(PRIVATE_KEY_2, ADDRESS_2) + encdata = yield km.encrypt('data', ADDRESS, sign=ADDRESS_2, + fetch_remote=False) yield km.decrypt( - encdata, ADDRESS, OpenPGPKey, verify=ADDRESS_2, fetch_remote=False) + encdata, ADDRESS, verify=ADDRESS_2, fetch_remote=False) # when - key = yield km.get_key(ADDRESS_2, OpenPGPKey, fetch_remote=False) + key = yield km.get_key(ADDRESS_2, fetch_remote=False) # then self.assertEqual(True, key.sign_used) @@ -488,18 +481,16 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): def test_decrypt_does_not_update_sign_used_for_recipient(self): # given km = self._key_manager() - yield km._wrapper_map[OpenPGPKey].put_raw_key( - PRIVATE_KEY, ADDRESS) - yield km._wrapper_map[OpenPGPKey].put_raw_key( - PRIVATE_KEY_2, ADDRESS_2) - encdata = yield km.encrypt('data', ADDRESS, OpenPGPKey, - sign=ADDRESS_2, fetch_remote=False) + yield km._openpgp.put_raw_key(PRIVATE_KEY, ADDRESS) + yield km._openpgp.put_raw_key(PRIVATE_KEY_2, ADDRESS_2) + encdata = yield km.encrypt('data', ADDRESS, sign=ADDRESS_2, + fetch_remote=False) yield km.decrypt( - encdata, ADDRESS, OpenPGPKey, verify=ADDRESS_2, fetch_remote=False) + encdata, ADDRESS, verify=ADDRESS_2, fetch_remote=False) # when key = yield km.get_key( - ADDRESS, OpenPGPKey, private=False, fetch_remote=False) + ADDRESS, private=False, fetch_remote=False) # then self.assertEqual(False, key.sign_used) @@ -513,35 +504,32 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): def test_keymanager_openpgp_encrypt_decrypt(self): km = self._key_manager() # put raw private key - yield km._wrapper_map[OpenPGPKey].put_raw_key(PRIVATE_KEY, ADDRESS) - yield km._wrapper_map[OpenPGPKey].put_raw_key( - PRIVATE_KEY_2, ADDRESS_2) + yield km._openpgp.put_raw_key(PRIVATE_KEY, ADDRESS) + yield km._openpgp.put_raw_key(PRIVATE_KEY_2, ADDRESS_2) # encrypt - encdata = yield km.encrypt(self.RAW_DATA, ADDRESS, OpenPGPKey, - sign=ADDRESS_2, fetch_remote=False) + encdata = yield km.encrypt(self.RAW_DATA, ADDRESS, sign=ADDRESS_2, + fetch_remote=False) self.assertNotEqual(self.RAW_DATA, encdata) # decrypt rawdata, signingkey = yield km.decrypt( - encdata, ADDRESS, OpenPGPKey, verify=ADDRESS_2, fetch_remote=False) + encdata, ADDRESS, verify=ADDRESS_2, fetch_remote=False) self.assertEqual(self.RAW_DATA, rawdata) - key = yield km.get_key(ADDRESS_2, OpenPGPKey, private=False, - fetch_remote=False) + key = yield km.get_key(ADDRESS_2, private=False, fetch_remote=False) self.assertEqual(signingkey.fingerprint, key.fingerprint) @defer.inlineCallbacks def test_keymanager_openpgp_encrypt_decrypt_wrong_sign(self): km = self._key_manager() # put raw keys - yield km._wrapper_map[OpenPGPKey].put_raw_key(PRIVATE_KEY, ADDRESS) - yield km._wrapper_map[OpenPGPKey].put_raw_key( - PRIVATE_KEY_2, ADDRESS_2) + yield km._openpgp.put_raw_key(PRIVATE_KEY, ADDRESS) + yield km._openpgp.put_raw_key(PRIVATE_KEY_2, ADDRESS_2) # encrypt - encdata = yield km.encrypt(self.RAW_DATA, ADDRESS, OpenPGPKey, - sign=ADDRESS_2, fetch_remote=False) + encdata = yield km.encrypt(self.RAW_DATA, ADDRESS, sign=ADDRESS_2, + fetch_remote=False) self.assertNotEqual(self.RAW_DATA, encdata) # verify rawdata, signingkey = yield km.decrypt( - encdata, ADDRESS, OpenPGPKey, verify=ADDRESS, fetch_remote=False) + encdata, ADDRESS, verify=ADDRESS, fetch_remote=False) self.assertEqual(self.RAW_DATA, rawdata) self.assertTrue(isinstance(signingkey, errors.InvalidSignature)) @@ -549,24 +537,21 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): def test_keymanager_openpgp_sign_verify(self): km = self._key_manager() # put raw private keys - yield km._wrapper_map[OpenPGPKey].put_raw_key(PRIVATE_KEY, ADDRESS) - signdata = yield km.sign(self.RAW_DATA, ADDRESS, OpenPGPKey, - detach=False) + yield km._openpgp.put_raw_key(PRIVATE_KEY, ADDRESS) + signdata = yield km.sign(self.RAW_DATA, ADDRESS, detach=False) self.assertNotEqual(self.RAW_DATA, signdata) # verify - signingkey = yield km.verify(signdata, ADDRESS, OpenPGPKey, - fetch_remote=False) - key = yield km.get_key(ADDRESS, OpenPGPKey, private=False, - fetch_remote=False) + signingkey = yield km.verify(signdata, ADDRESS, fetch_remote=False) + key = yield km.get_key(ADDRESS, private=False, fetch_remote=False) self.assertEqual(signingkey.fingerprint, key.fingerprint) def test_keymanager_encrypt_key_not_found(self): km = self._key_manager() - d = km._wrapper_map[OpenPGPKey].put_raw_key(PRIVATE_KEY, ADDRESS) + d = km._openpgp.put_raw_key(PRIVATE_KEY, ADDRESS) d.addCallback( - lambda _: km.encrypt(self.RAW_DATA, ADDRESS_2, OpenPGPKey, - sign=ADDRESS, fetch_remote=False)) - return self.assertFailure(d, KeyNotFound) + lambda _: km.encrypt(self.RAW_DATA, ADDRESS_2, sign=ADDRESS, + fetch_remote=False)) + return self.assertFailure(d, errors.KeyNotFound) if __name__ == "__main__": import unittest diff --git a/keymanager/src/leap/keymanager/tests/test_validation.py b/keymanager/src/leap/keymanager/tests/test_validation.py index 44d6e394..954b1df8 100644 --- a/keymanager/src/leap/keymanager/tests/test_validation.py +++ b/keymanager/src/leap/keymanager/tests/test_validation.py @@ -22,7 +22,6 @@ import unittest from datetime import datetime from twisted.internet.defer import inlineCallbacks -from leap.keymanager.openpgp import OpenPGPKey from leap.keymanager.errors import ( KeyNotValidUpgrade ) @@ -43,64 +42,62 @@ class ValidationLevelsTestCase(KeyManagerWithSoledadTestCase): @inlineCallbacks def test_none_old_key(self): km = self._key_manager() - yield km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) - key = yield km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + yield km.put_raw_key(PUBLIC_KEY, ADDRESS) + key = yield km.get_key(ADDRESS, fetch_remote=False) self.assertEqual(key.fingerprint, KEY_FINGERPRINT) @inlineCallbacks def test_cant_upgrade(self): km = self._key_manager() - yield km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS, + yield km.put_raw_key(PUBLIC_KEY, ADDRESS, validation=ValidationLevels.Provider_Trust) - d = km.put_raw_key(UNRELATED_KEY, OpenPGPKey, ADDRESS) + d = km.put_raw_key(UNRELATED_KEY, ADDRESS) yield self.assertFailure(d, KeyNotValidUpgrade) @inlineCallbacks def test_fingerprint_level(self): km = self._key_manager() - yield km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) - yield km.put_raw_key(UNRELATED_KEY, OpenPGPKey, ADDRESS, + yield km.put_raw_key(PUBLIC_KEY, ADDRESS) + yield km.put_raw_key(UNRELATED_KEY, ADDRESS, validation=ValidationLevels.Fingerprint) - key = yield km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + key = yield km.get_key(ADDRESS, fetch_remote=False) self.assertEqual(key.fingerprint, UNRELATED_FINGERPRINT) @inlineCallbacks def test_expired_key(self): km = self._key_manager() - yield km.put_raw_key(EXPIRED_KEY, OpenPGPKey, ADDRESS) - yield km.put_raw_key(UNRELATED_KEY, OpenPGPKey, ADDRESS) - key = yield km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + yield km.put_raw_key(EXPIRED_KEY, ADDRESS) + yield km.put_raw_key(UNRELATED_KEY, ADDRESS) + key = yield km.get_key(ADDRESS, fetch_remote=False) self.assertEqual(key.fingerprint, UNRELATED_FINGERPRINT) @inlineCallbacks def test_expired_fail_lower_level(self): km = self._key_manager() yield km.put_raw_key( - EXPIRED_KEY, OpenPGPKey, ADDRESS, + EXPIRED_KEY, ADDRESS, validation=ValidationLevels.Third_Party_Endorsement) d = km.put_raw_key( - UNRELATED_KEY, - OpenPGPKey, - ADDRESS, + UNRELATED_KEY, ADDRESS, validation=ValidationLevels.Provider_Trust) yield self.assertFailure(d, KeyNotValidUpgrade) @inlineCallbacks def test_roll_back(self): km = self._key_manager() - yield km.put_raw_key(EXPIRED_KEY_UPDATED, OpenPGPKey, ADDRESS) - yield km.put_raw_key(EXPIRED_KEY, OpenPGPKey, ADDRESS) - key = yield km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + yield km.put_raw_key(EXPIRED_KEY_UPDATED, ADDRESS) + yield km.put_raw_key(EXPIRED_KEY, ADDRESS) + key = yield km.get_key(ADDRESS, fetch_remote=False) self.assertEqual(key.expiry_date, EXPIRED_KEY_NEW_EXPIRY_DATE) @inlineCallbacks def test_not_used(self): km = self._key_manager() - yield km.put_raw_key(UNEXPIRED_KEY, OpenPGPKey, ADDRESS, + yield km.put_raw_key(UNEXPIRED_KEY, ADDRESS, validation=ValidationLevels.Provider_Trust) - yield km.put_raw_key(UNRELATED_KEY, OpenPGPKey, ADDRESS, + yield km.put_raw_key(UNRELATED_KEY, ADDRESS, validation=ValidationLevels.Provider_Endorsement) - key = yield km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + key = yield km.get_key(ADDRESS, fetch_remote=False) self.assertEqual(key.fingerprint, UNRELATED_FINGERPRINT) @inlineCallbacks @@ -108,16 +105,16 @@ class ValidationLevelsTestCase(KeyManagerWithSoledadTestCase): TEXT = "some text" km = self._key_manager() - yield km.put_raw_key(UNEXPIRED_PRIVATE, OpenPGPKey, ADDRESS) - signature = yield km.sign(TEXT, ADDRESS, OpenPGPKey) + yield km.put_raw_key(UNEXPIRED_PRIVATE, ADDRESS) + signature = yield km.sign(TEXT, ADDRESS) yield self.delete_all_keys(km) - yield km.put_raw_key(UNEXPIRED_KEY, OpenPGPKey, ADDRESS) - yield km.encrypt(TEXT, ADDRESS, OpenPGPKey) - yield km.verify(TEXT, ADDRESS, OpenPGPKey, detached_sig=signature) + yield km.put_raw_key(UNEXPIRED_KEY, ADDRESS) + yield km.encrypt(TEXT, ADDRESS) + yield km.verify(TEXT, ADDRESS, detached_sig=signature) d = km.put_raw_key( - UNRELATED_KEY, OpenPGPKey, ADDRESS, + UNRELATED_KEY, ADDRESS, validation=ValidationLevels.Provider_Endorsement) yield self.assertFailure(d, KeyNotValidUpgrade) @@ -126,28 +123,27 @@ class ValidationLevelsTestCase(KeyManagerWithSoledadTestCase): TEXT = "some text" km = self._key_manager() - yield km.put_raw_key(UNEXPIRED_PRIVATE, OpenPGPKey, ADDRESS) - yield km.put_raw_key(PUBLIC_KEY_2, OpenPGPKey, ADDRESS_2) - encrypted = yield km.encrypt(TEXT, ADDRESS_2, OpenPGPKey, - sign=ADDRESS) + yield km.put_raw_key(UNEXPIRED_PRIVATE, ADDRESS) + yield km.put_raw_key(PUBLIC_KEY_2, ADDRESS_2) + encrypted = yield km.encrypt(TEXT, ADDRESS_2, sign=ADDRESS) yield self.delete_all_keys(km) - yield km.put_raw_key(UNEXPIRED_KEY, OpenPGPKey, ADDRESS) - yield km.put_raw_key(PRIVATE_KEY_2, OpenPGPKey, ADDRESS_2) - yield km.encrypt(TEXT, ADDRESS, OpenPGPKey) - yield km.decrypt(encrypted, ADDRESS_2, OpenPGPKey, verify=ADDRESS) + yield km.put_raw_key(UNEXPIRED_KEY, ADDRESS) + yield km.put_raw_key(PRIVATE_KEY_2, ADDRESS_2) + yield km.encrypt(TEXT, ADDRESS) + yield km.decrypt(encrypted, ADDRESS_2, verify=ADDRESS) d = km.put_raw_key( - UNRELATED_KEY, OpenPGPKey, ADDRESS, + UNRELATED_KEY, ADDRESS, validation=ValidationLevels.Provider_Endorsement) yield self.assertFailure(d, KeyNotValidUpgrade) @inlineCallbacks def test_signed_key(self): km = self._key_manager() - yield km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) - yield km.put_raw_key(SIGNED_KEY, OpenPGPKey, ADDRESS) - key = yield km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + yield km.put_raw_key(PUBLIC_KEY, ADDRESS) + yield km.put_raw_key(SIGNED_KEY, ADDRESS) + key = yield km.get_key(ADDRESS, fetch_remote=False) self.assertEqual(key.fingerprint, SIGNED_FINGERPRINT) @inlineCallbacks @@ -155,26 +151,26 @@ class ValidationLevelsTestCase(KeyManagerWithSoledadTestCase): TEXT = "some text" km = self._key_manager() - yield km.put_raw_key(UUIDS_PRIVATE, OpenPGPKey, ADDRESS_2) - signature = yield km.sign(TEXT, ADDRESS_2, OpenPGPKey) + yield km.put_raw_key(UUIDS_PRIVATE, ADDRESS_2) + signature = yield km.sign(TEXT, ADDRESS_2) yield self.delete_all_keys(km) - yield km.put_raw_key(UUIDS_KEY, OpenPGPKey, ADDRESS_2) - yield km.put_raw_key(UUIDS_KEY, OpenPGPKey, ADDRESS) - yield km.encrypt(TEXT, ADDRESS_2, OpenPGPKey) - yield km.verify(TEXT, ADDRESS_2, OpenPGPKey, detached_sig=signature) + yield km.put_raw_key(UUIDS_KEY, ADDRESS_2) + yield km.put_raw_key(UUIDS_KEY, ADDRESS) + yield km.encrypt(TEXT, ADDRESS_2) + yield km.verify(TEXT, ADDRESS_2, detached_sig=signature) d = km.put_raw_key( - PUBLIC_KEY_2, OpenPGPKey, ADDRESS_2, + PUBLIC_KEY_2, ADDRESS_2, validation=ValidationLevels.Provider_Endorsement) yield self.assertFailure(d, KeyNotValidUpgrade) - key = yield km.get_key(ADDRESS_2, OpenPGPKey, fetch_remote=False) + key = yield km.get_key(ADDRESS_2, fetch_remote=False) self.assertEqual(key.fingerprint, UUIDS_FINGERPRINT) yield km.put_raw_key( - PUBLIC_KEY, OpenPGPKey, ADDRESS, + PUBLIC_KEY, ADDRESS, validation=ValidationLevels.Provider_Endorsement) - key = yield km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + key = yield km.get_key(ADDRESS, fetch_remote=False) self.assertEqual(key.fingerprint, KEY_FINGERPRINT) -- cgit v1.2.3 From bc027b0b0f051b81ebb3339653e8bbaeee875765 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Sun, 1 May 2016 15:38:45 -0300 Subject: [feat] remove keytype from build_key_from_dict --- keymanager/src/leap/keymanager/__init__.py | 4 ++-- keymanager/src/leap/keymanager/keys.py | 10 ++++------ keymanager/src/leap/keymanager/openpgp.py | 8 +++----- keymanager/src/leap/keymanager/tests/test_keymanager.py | 2 +- 4 files changed, 10 insertions(+), 14 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index f72f4036..32e55818 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -81,7 +81,7 @@ from leap.keymanager.keys import ( KEYMANAGER_KEY_TAG, TAGS_PRIVATE_INDEX, ) -from leap.keymanager.openpgp import OpenPGPKey, OpenPGPScheme +from leap.keymanager.openpgp import OpenPGPScheme __version__ = get_versions()['version'] del get_versions @@ -434,7 +434,7 @@ class KeyManager(object): # TODO: should it be based on activedocs? def build_keys(docs): return map( - lambda doc: build_key_from_dict(OpenPGPKey, doc.content), + lambda doc: build_key_from_dict(doc.content), docs) # XXX: there is no check that the soledad indexes are ready, as it diff --git a/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py index 1d039451..296ed865 100644 --- a/keymanager/src/leap/keymanager/keys.py +++ b/keymanager/src/leap/keymanager/keys.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # keys.py -# Copyright (C) 2013 LEAP +# Copyright (C) 2013-2016 LEAP # # 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 @@ -14,8 +14,6 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . - - """ Abstact key type and encryption scheme representations. """ @@ -116,9 +114,9 @@ def is_address(address): return bool(re.match('[\w.-]+@[\w.-]+', address)) -def build_key_from_dict(kClass, key, active=None): +def build_key_from_dict(key, active=None): """ - Build an C{kClass} key based on info in C{kdict}. + Build an OpenPGPKey key based on info in C{kdict}. :param key: Dictionary with key data. :type key: dict @@ -148,7 +146,7 @@ def build_key_from_dict(kClass, key, active=None): expiry_date = _to_datetime(key[KEY_EXPIRY_DATE_KEY]) refreshed_at = _to_datetime(key[KEY_REFRESHED_AT_KEY]) - return kClass( + return OpenPGPKey( address=address, uids=key[KEY_UIDS_KEY], fingerprint=key[KEY_FINGERPRINT_KEY], diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index f8cc9765..fae56005 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # openpgp.py -# Copyright (C) 2013-2015 LEAP +# Copyright (C) 2013-2016 LEAP # # 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 @@ -232,8 +232,7 @@ class OpenPGPScheme(object): 'Wrong address in key %s. Expected %s, found %s.' % (keydoc.content[KEY_FINGERPRINT_KEY], address, keydoc.content[KEY_UIDS_KEY])) - key = build_key_from_dict(OpenPGPKey, keydoc.content, - activedoc.content) + key = build_key_from_dict(keydoc.content, activedoc.content) key._gpgbinary = self._gpgbinary return key @@ -328,8 +327,7 @@ class OpenPGPScheme(object): active_content = None if activedoc: active_content = activedoc.content - oldkey = build_key_from_dict(OpenPGPKey, keydoc.content, - active_content) + oldkey = build_key_from_dict(keydoc.content, active_content) key.merge(oldkey) keydoc.set_json(key.get_json()) diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index c55a3c38..3572fb28 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -92,7 +92,7 @@ class KeyManagerUtilTestCase(unittest.TestCase): 'encr_used': False, 'sign_used': True, } - key = build_key_from_dict(OpenPGPKey, kdict, adict) + key = build_key_from_dict(kdict, adict) self.assertEqual( kdict['uids'], key.uids, 'Wrong data in key.') -- cgit v1.2.3 From 10d33f626fbe1ad54413d6b68aaafc832bbb0750 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Sun, 1 May 2016 16:52:48 -0300 Subject: [refactor] move soledad document definitions to it's own file --- keymanager/src/leap/keymanager/__init__.py | 7 +- keymanager/src/leap/keymanager/documents.py | 101 +++++++++++++ keymanager/src/leap/keymanager/keys.py | 163 +++++---------------- keymanager/src/leap/keymanager/migrator.py | 70 ++++----- keymanager/src/leap/keymanager/openpgp.py | 4 +- .../src/leap/keymanager/tests/test_migrator.py | 2 +- .../src/leap/keymanager/tests/test_openpgp.py | 6 +- 7 files changed, 174 insertions(+), 179 deletions(-) create mode 100644 keymanager/src/leap/keymanager/documents.py (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 32e55818..6420f9a6 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -76,11 +76,8 @@ from leap.keymanager.errors import ( ) from leap.keymanager.validation import ValidationLevels, can_upgrade -from leap.keymanager.keys import ( - build_key_from_dict, - KEYMANAGER_KEY_TAG, - TAGS_PRIVATE_INDEX, -) +from leap.keymanager.keys import build_key_from_dict +from leap.keymanager.documents import KEYMANAGER_KEY_TAG, TAGS_PRIVATE_INDEX from leap.keymanager.openpgp import OpenPGPScheme __version__ = get_versions()['version'] diff --git a/keymanager/src/leap/keymanager/documents.py b/keymanager/src/leap/keymanager/documents.py new file mode 100644 index 00000000..2ed53769 --- /dev/null +++ b/keymanager/src/leap/keymanager/documents.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- +# documents.py +# Copyright (C) 2013-2016 LEAP +# +# 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 . +""" +Soledad documents +""" +from twisted.internet import defer +from leap.common.check import leap_assert + +# +# Dictionary keys used for storing cryptographic keys. +# + +KEY_VERSION_KEY = 'version' +KEY_UIDS_KEY = 'uids' +KEY_ADDRESS_KEY = 'address' +KEY_TYPE_KEY = 'type' +KEY_FINGERPRINT_KEY = 'fingerprint' +KEY_DATA_KEY = 'key_data' +KEY_PRIVATE_KEY = 'private' +KEY_LENGTH_KEY = 'length' +KEY_EXPIRY_DATE_KEY = 'expiry_date' +KEY_LAST_AUDITED_AT_KEY = 'last_audited_at' +KEY_REFRESHED_AT_KEY = 'refreshed_at' +KEY_VALIDATION_KEY = 'validation' +KEY_ENCR_USED_KEY = 'encr_used' +KEY_SIGN_USED_KEY = 'sign_used' +KEY_TAGS_KEY = 'tags' + + +# +# Key storage constants +# + +KEYMANAGER_KEY_TAG = 'keymanager-key' +KEYMANAGER_ACTIVE_TAG = 'keymanager-active' +KEYMANAGER_ACTIVE_TYPE = '-active' + +# Version of the Soledad Document schema, +# it should be bumped each time the document format changes +KEYMANAGER_DOC_VERSION = 1 + + +# +# key indexing constants. +# + +TAGS_PRIVATE_INDEX = 'by-tags-private' +TYPE_FINGERPRINT_PRIVATE_INDEX = 'by-type-fingerprint-private' +TYPE_ADDRESS_PRIVATE_INDEX = 'by-type-address-private' +INDEXES = { + TAGS_PRIVATE_INDEX: [ + KEY_TAGS_KEY, + 'bool(%s)' % KEY_PRIVATE_KEY, + ], + TYPE_FINGERPRINT_PRIVATE_INDEX: [ + KEY_TYPE_KEY, + KEY_FINGERPRINT_KEY, + 'bool(%s)' % KEY_PRIVATE_KEY, + ], + TYPE_ADDRESS_PRIVATE_INDEX: [ + KEY_TYPE_KEY, + KEY_ADDRESS_KEY, + 'bool(%s)' % KEY_PRIVATE_KEY, + ] +} + + +@defer.inlineCallbacks +def init_indexes(soledad): + """ + Initialize the database indexes. + """ + leap_assert(soledad is not None, + "Cannot init indexes with null soledad") + + indexes = yield soledad.list_indexes() + db_indexes = dict(indexes) + # Loop through the indexes we expect to find. + for name, expression in INDEXES.items(): + if name not in db_indexes: + # The index does not yet exist. + yield soledad.create_index(name, *expression) + elif expression != db_indexes[name]: + # The index exists but the definition is not what expected, + # so we delete it and add the proper index expression. + yield soledad.delete_index(name) + yield soledad.create_index(name, *expression) diff --git a/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py index 296ed865..c0ee21b1 100644 --- a/keymanager/src/leap/keymanager/keys.py +++ b/keymanager/src/leap/keymanager/keys.py @@ -27,77 +27,16 @@ import logging import re import time - from datetime import datetime -from leap.common.check import leap_assert -from twisted.internet import defer from leap.keymanager import errors from leap.keymanager.wrapper import TempGPGWrapper from leap.keymanager.validation import ValidationLevels +from leap.keymanager import documents as doc logger = logging.getLogger(__name__) -# -# Dictionary keys used for storing cryptographic keys. -# - -KEY_VERSION_KEY = 'version' -KEY_UIDS_KEY = 'uids' -KEY_ADDRESS_KEY = 'address' -KEY_TYPE_KEY = 'type' -KEY_FINGERPRINT_KEY = 'fingerprint' -KEY_DATA_KEY = 'key_data' -KEY_PRIVATE_KEY = 'private' -KEY_LENGTH_KEY = 'length' -KEY_EXPIRY_DATE_KEY = 'expiry_date' -KEY_LAST_AUDITED_AT_KEY = 'last_audited_at' -KEY_REFRESHED_AT_KEY = 'refreshed_at' -KEY_VALIDATION_KEY = 'validation' -KEY_ENCR_USED_KEY = 'encr_used' -KEY_SIGN_USED_KEY = 'sign_used' -KEY_TAGS_KEY = 'tags' - - -# -# Key storage constants -# - -KEYMANAGER_KEY_TAG = 'keymanager-key' -KEYMANAGER_ACTIVE_TAG = 'keymanager-active' -KEYMANAGER_ACTIVE_TYPE = '-active' - -# Version of the Soledad Document schema, -# it should be bumped each time the document format changes -KEYMANAGER_DOC_VERSION = 1 - - -# -# key indexing constants. -# - -TAGS_PRIVATE_INDEX = 'by-tags-private' -TYPE_FINGERPRINT_PRIVATE_INDEX = 'by-type-fingerprint-private' -TYPE_ADDRESS_PRIVATE_INDEX = 'by-type-address-private' -INDEXES = { - TAGS_PRIVATE_INDEX: [ - KEY_TAGS_KEY, - 'bool(%s)' % KEY_PRIVATE_KEY, - ], - TYPE_FINGERPRINT_PRIVATE_INDEX: [ - KEY_TYPE_KEY, - KEY_FINGERPRINT_KEY, - 'bool(%s)' % KEY_PRIVATE_KEY, - ], - TYPE_ADDRESS_PRIVATE_INDEX: [ - KEY_TYPE_KEY, - KEY_ADDRESS_KEY, - 'bool(%s)' % KEY_PRIVATE_KEY, - ] -} - - # # Key handling utilities # @@ -132,27 +71,27 @@ def build_key_from_dict(key, active=None): sign_used = False if active: - address = active[KEY_ADDRESS_KEY] + address = active[doc.KEY_ADDRESS_KEY] try: - validation = ValidationLevels.get(active[KEY_VALIDATION_KEY]) + validation = ValidationLevels.get(active[doc.KEY_VALIDATION_KEY]) except ValueError: logger.error("Not valid validation level (%s) for key %s", - (active[KEY_VALIDATION_KEY], - active[KEY_FINGERPRINT_KEY])) - last_audited_at = _to_datetime(active[KEY_LAST_AUDITED_AT_KEY]) - encr_used = active[KEY_ENCR_USED_KEY] - sign_used = active[KEY_SIGN_USED_KEY] + (active[doc.KEY_VALIDATION_KEY], + active[doc.KEY_FINGERPRINT_KEY])) + last_audited_at = _to_datetime(active[doc.KEY_LAST_AUDITED_AT_KEY]) + encr_used = active[doc.KEY_ENCR_USED_KEY] + sign_used = active[doc.KEY_SIGN_USED_KEY] - expiry_date = _to_datetime(key[KEY_EXPIRY_DATE_KEY]) - refreshed_at = _to_datetime(key[KEY_REFRESHED_AT_KEY]) + expiry_date = _to_datetime(key[doc.KEY_EXPIRY_DATE_KEY]) + refreshed_at = _to_datetime(key[doc.KEY_REFRESHED_AT_KEY]) return OpenPGPKey( address=address, - uids=key[KEY_UIDS_KEY], - fingerprint=key[KEY_FINGERPRINT_KEY], - key_data=key[KEY_DATA_KEY], - private=key[KEY_PRIVATE_KEY], - length=key[KEY_LENGTH_KEY], + uids=key[doc.KEY_UIDS_KEY], + fingerprint=key[doc.KEY_FINGERPRINT_KEY], + key_data=key[doc.KEY_DATA_KEY], + private=key[doc.KEY_PRIVATE_KEY], + length=key[doc.KEY_LENGTH_KEY], expiry_date=expiry_date, last_audited_at=last_audited_at, refreshed_at=refreshed_at, @@ -271,16 +210,16 @@ class OpenPGPKey(object): refreshed_at = _to_unix_time(self.refreshed_at) return json.dumps({ - KEY_UIDS_KEY: self.uids, - KEY_TYPE_KEY: self.__class__.__name__, - KEY_FINGERPRINT_KEY: self.fingerprint, - KEY_DATA_KEY: self.key_data, - KEY_PRIVATE_KEY: self.private, - KEY_LENGTH_KEY: self.length, - KEY_EXPIRY_DATE_KEY: expiry_date, - KEY_REFRESHED_AT_KEY: refreshed_at, - KEY_VERSION_KEY: KEYMANAGER_DOC_VERSION, - KEY_TAGS_KEY: [KEYMANAGER_KEY_TAG], + doc.KEY_UIDS_KEY: self.uids, + doc.KEY_TYPE_KEY: self.__class__.__name__, + doc.KEY_FINGERPRINT_KEY: self.fingerprint, + doc.KEY_DATA_KEY: self.key_data, + doc.KEY_PRIVATE_KEY: self.private, + doc.KEY_LENGTH_KEY: self.length, + doc.KEY_EXPIRY_DATE_KEY: expiry_date, + doc.KEY_REFRESHED_AT_KEY: refreshed_at, + doc.KEY_VERSION_KEY: doc.KEYMANAGER_DOC_VERSION, + doc.KEY_TAGS_KEY: [doc.KEYMANAGER_KEY_TAG], }) def get_active_json(self): @@ -293,16 +232,17 @@ class OpenPGPKey(object): last_audited_at = _to_unix_time(self.last_audited_at) return json.dumps({ - KEY_ADDRESS_KEY: self.address, - KEY_TYPE_KEY: self.__class__.__name__ + KEYMANAGER_ACTIVE_TYPE, - KEY_FINGERPRINT_KEY: self.fingerprint, - KEY_PRIVATE_KEY: self.private, - KEY_VALIDATION_KEY: str(self.validation), - KEY_LAST_AUDITED_AT_KEY: last_audited_at, - KEY_ENCR_USED_KEY: self.encr_used, - KEY_SIGN_USED_KEY: self.sign_used, - KEY_VERSION_KEY: KEYMANAGER_DOC_VERSION, - KEY_TAGS_KEY: [KEYMANAGER_ACTIVE_TAG], + doc.KEY_ADDRESS_KEY: self.address, + doc.KEY_TYPE_KEY: (self.__class__.__name__ + + doc.KEYMANAGER_ACTIVE_TYPE), + doc.KEY_FINGERPRINT_KEY: self.fingerprint, + doc.KEY_PRIVATE_KEY: self.private, + doc.KEY_VALIDATION_KEY: str(self.validation), + doc.KEY_LAST_AUDITED_AT_KEY: last_audited_at, + doc.KEY_ENCR_USED_KEY: self.encr_used, + doc.KEY_SIGN_USED_KEY: self.sign_used, + doc.KEY_VERSION_KEY: doc.KEYMANAGER_DOC_VERSION, + doc.KEY_TAGS_KEY: [doc.KEYMANAGER_ACTIVE_TAG], }) def next(self): @@ -351,34 +291,3 @@ def parse_address(address): if match is None: return None return ''.join(match.group(2, 4)) - - -def init_indexes(soledad): - """ - Initialize the database indexes. - """ - leap_assert(soledad is not None, - "Cannot init indexes with null soledad") - - def create_idexes(indexes): - deferreds = [] - db_indexes = dict(indexes) - # Loop through the indexes we expect to find. - for name, expression in INDEXES.items(): - if name not in db_indexes: - # The index does not yet exist. - d = soledad.create_index(name, *expression) - deferreds.append(d) - elif expression != db_indexes[name]: - # The index exists but the definition is not what expected, - # so we delete it and add the proper index expression. - d = soledad.delete_index(name) - d.addCallback( - lambda _: - soledad.create_index(name, *expression)) - deferreds.append(d) - return defer.gatherResults(deferreds, consumeErrors=True) - - d = soledad.list_indexes() - d.addCallback(create_idexes) - return d diff --git a/keymanager/src/leap/keymanager/migrator.py b/keymanager/src/leap/keymanager/migrator.py index 9e4ae77c..c73da2ee 100644 --- a/keymanager/src/leap/keymanager/migrator.py +++ b/keymanager/src/leap/keymanager/migrator.py @@ -26,21 +26,7 @@ Document migrator from collections import namedtuple from twisted.internet.defer import gatherResults, succeed -from leap.keymanager.keys import ( - TAGS_PRIVATE_INDEX, - KEYMANAGER_KEY_TAG, - KEYMANAGER_ACTIVE_TAG, - - KEYMANAGER_DOC_VERSION, - KEY_ADDRESS_KEY, - KEY_UIDS_KEY, - KEY_VERSION_KEY, - KEY_FINGERPRINT_KEY, - KEY_VALIDATION_KEY, - KEY_LAST_AUDITED_AT_KEY, - KEY_ENCR_USED_KEY, - KEY_SIGN_USED_KEY, -) +from leap.keymanager import documents as doc from leap.keymanager.validation import ValidationLevels @@ -70,12 +56,12 @@ class KeyDocumentsMigrator(object): private_value = '1' if private else '0' deferred_keys = self._soledad.get_from_index( - TAGS_PRIVATE_INDEX, - KEYMANAGER_KEY_TAG, + doc.TAGS_PRIVATE_INDEX, + doc.KEYMANAGER_KEY_TAG, private_value) deferred_active = self._soledad.get_from_index( - TAGS_PRIVATE_INDEX, - KEYMANAGER_ACTIVE_TAG, + doc.TAGS_PRIVATE_INDEX, + doc.KEYMANAGER_ACTIVE_TAG, private_value) return gatherResults([deferred_keys, deferred_active]) @@ -99,7 +85,7 @@ class KeyDocumentsMigrator(object): def _buildKeyDict(self, keys, actives): keydict = { - fp2id(key.content[KEY_FINGERPRINT_KEY]): KeyDocs(key, []) + fp2id(key.content[doc.KEY_FINGERPRINT_KEY]): KeyDocs(key, []) for key in keys} deferreds = [] @@ -119,7 +105,7 @@ class KeyDocumentsMigrator(object): def _filter_outdated(self, keydict): outdated = {} for key_id, docs in keydict.items(): - if ((docs.key and KEY_VERSION_KEY not in docs.key.content) or + if ((docs.key and doc.KEY_VERSION_KEY not in docs.key.content) or docs.active): outdated[key_id] = docs return outdated @@ -136,43 +122,43 @@ class KeyDocumentsMigrator(object): last_audited = 0 encr_used = False sign_used = False - fingerprint = key.content[KEY_FINGERPRINT_KEY] - if len(actives) == 1 and KEY_VERSION_KEY not in key.content: + fingerprint = key.content[doc.KEY_FINGERPRINT_KEY] + if len(actives) == 1 and doc.KEY_VERSION_KEY not in key.content: # we can preserve the validation of the key if there is only one # active address for the key - validation = key.content[KEY_VALIDATION_KEY] - last_audited = key.content[KEY_LAST_AUDITED_AT_KEY] - encr_used = key.content[KEY_ENCR_USED_KEY] - sign_used = key.content[KEY_SIGN_USED_KEY] + validation = key.content[doc.KEY_VALIDATION_KEY] + last_audited = key.content[doc.KEY_LAST_AUDITED_AT_KEY] + encr_used = key.content[doc.KEY_ENCR_USED_KEY] + sign_used = key.content[doc.KEY_SIGN_USED_KEY] deferreds = [] for active in actives: - if KEY_VERSION_KEY in active.content: + if doc.KEY_VERSION_KEY in active.content: continue - active.content[KEY_VERSION_KEY] = KEYMANAGER_DOC_VERSION - active.content[KEY_FINGERPRINT_KEY] = fingerprint - active.content[KEY_VALIDATION_KEY] = validation - active.content[KEY_LAST_AUDITED_AT_KEY] = last_audited - active.content[KEY_ENCR_USED_KEY] = encr_used - active.content[KEY_SIGN_USED_KEY] = sign_used + active.content[doc.KEY_VERSION_KEY] = doc.KEYMANAGER_DOC_VERSION + active.content[doc.KEY_FINGERPRINT_KEY] = fingerprint + active.content[doc.KEY_VALIDATION_KEY] = validation + active.content[doc.KEY_LAST_AUDITED_AT_KEY] = last_audited + active.content[doc.KEY_ENCR_USED_KEY] = encr_used + active.content[doc.KEY_SIGN_USED_KEY] = sign_used del active.content[KEY_ID_KEY] d = self._soledad.put_doc(active) deferreds.append(d) return gatherResults(deferreds) def _migrate_key(self, key): - if not key or KEY_VERSION_KEY in key.content: + if not key or doc.KEY_VERSION_KEY in key.content: return succeed(None) - key.content[KEY_VERSION_KEY] = KEYMANAGER_DOC_VERSION - key.content[KEY_UIDS_KEY] = key.content[KEY_ADDRESS_KEY] - del key.content[KEY_ADDRESS_KEY] + key.content[doc.KEY_VERSION_KEY] = doc.KEYMANAGER_DOC_VERSION + key.content[doc.KEY_UIDS_KEY] = key.content[doc.KEY_ADDRESS_KEY] + del key.content[doc.KEY_ADDRESS_KEY] del key.content[KEY_ID_KEY] - del key.content[KEY_VALIDATION_KEY] - del key.content[KEY_LAST_AUDITED_AT_KEY] - del key.content[KEY_ENCR_USED_KEY] - del key.content[KEY_SIGN_USED_KEY] + del key.content[doc.KEY_VALIDATION_KEY] + del key.content[doc.KEY_LAST_AUDITED_AT_KEY] + del key.content[doc.KEY_ENCR_USED_KEY] + del key.content[doc.KEY_SIGN_USED_KEY] return self._soledad.put_doc(key) diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index fae56005..08b69f7b 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -36,10 +36,12 @@ from leap.keymanager import errors from leap.keymanager.wrapper import TempGPGWrapper from leap.keymanager.keys import ( OpenPGPKey, - init_indexes, is_address, parse_address, build_key_from_dict, +) +from leap.keymanager.documents import ( + init_indexes, TYPE_FINGERPRINT_PRIVATE_INDEX, TYPE_ADDRESS_PRIVATE_INDEX, KEY_UIDS_KEY, diff --git a/keymanager/src/leap/keymanager/tests/test_migrator.py b/keymanager/src/leap/keymanager/tests/test_migrator.py index 2d9782b5..76323e52 100644 --- a/keymanager/src/leap/keymanager/tests/test_migrator.py +++ b/keymanager/src/leap/keymanager/tests/test_migrator.py @@ -26,7 +26,7 @@ from mock import Mock from twisted.internet.defer import succeed, inlineCallbacks from leap.keymanager.migrator import KeyDocumentsMigrator, KEY_ID_KEY -from leap.keymanager.keys import ( +from leap.keymanager.documents import ( TAGS_PRIVATE_INDEX, KEYMANAGER_ACTIVE_TAG, KEYMANAGER_KEY_TAG, diff --git a/keymanager/src/leap/keymanager/tests/test_openpgp.py b/keymanager/src/leap/keymanager/tests/test_openpgp.py index 0e39dab7..47e9005a 100644 --- a/keymanager/src/leap/keymanager/tests/test_openpgp.py +++ b/keymanager/src/leap/keymanager/tests/test_openpgp.py @@ -29,7 +29,7 @@ from leap.keymanager import ( KeyNotFound, openpgp, ) -from leap.keymanager.keys import ( +from leap.keymanager.documents import ( TYPE_FINGERPRINT_PRIVATE_INDEX, TYPE_ADDRESS_PRIVATE_INDEX, ) @@ -57,7 +57,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): self._soledad, gpgbinary=self.gpg_binary_path) yield self._assert_key_not_found(pgp, 'user@leap.se') key = yield pgp.gen_key('user@leap.se') - self.assertIsInstance(key, openpgp.OpenPGPKey) + self.assertIsInstance(key, OpenPGPKey) self.assertEqual( ['user@leap.se'], key.address, 'Wrong address bound to key.') self.assertEqual( @@ -80,7 +80,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): yield self._assert_key_not_found(pgp, ADDRESS) yield pgp.put_raw_key(PUBLIC_KEY, ADDRESS) key = yield pgp.get_key(ADDRESS, private=False) - self.assertIsInstance(key, openpgp.OpenPGPKey) + self.assertIsInstance(key, OpenPGPKey) self.assertTrue( ADDRESS in key.address, 'Wrong address bound to key.') self.assertEqual( -- cgit v1.2.3 From 6761833c24363e64181b70f94b9bc890ceca1a9f Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Sun, 1 May 2016 21:50:52 -0300 Subject: [feat] make get_all_keys aware of active addresses - Resolves: #8068 --- keymanager/src/leap/keymanager/__init__.py | 21 +------------- keymanager/src/leap/keymanager/openpgp.py | 46 +++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 21 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 6420f9a6..fdd47c36 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -75,9 +75,6 @@ from leap.keymanager.errors import ( InvalidSignature ) from leap.keymanager.validation import ValidationLevels, can_upgrade - -from leap.keymanager.keys import build_key_from_dict -from leap.keymanager.documents import KEYMANAGER_KEY_TAG, TAGS_PRIVATE_INDEX from leap.keymanager.openpgp import OpenPGPScheme __version__ = get_versions()['version'] @@ -428,23 +425,7 @@ class KeyManager(object): :return: A Deferred which fires with a list of all keys in local db. :rtype: Deferred """ - # TODO: should it be based on activedocs? - def build_keys(docs): - return map( - lambda doc: build_key_from_dict(doc.content), - docs) - - # XXX: there is no check that the soledad indexes are ready, as it - # happens with EncryptionScheme. - # The usecases right now are not problematic. This could be solve - # adding a keytype to this funciont and moving the soledad request - # to the EncryptionScheme. - d = self._soledad.get_from_index( - TAGS_PRIVATE_INDEX, - KEYMANAGER_KEY_TAG, - '1' if private else '0') - d.addCallback(build_keys) - return d + return self._openpgp.get_all_keys(private) def gen_key(self): """ diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 08b69f7b..8658e9c2 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -42,6 +42,7 @@ from leap.keymanager.keys import ( ) from leap.keymanager.documents import ( init_indexes, + TAGS_PRIVATE_INDEX, TYPE_FINGERPRINT_PRIVATE_INDEX, TYPE_ADDRESS_PRIVATE_INDEX, KEY_UIDS_KEY, @@ -53,6 +54,8 @@ from leap.keymanager.documents import ( KEY_ADDRESS_KEY, KEY_TYPE_KEY, KEYMANAGER_ACTIVE_TYPE, + KEYMANAGER_KEY_TAG, + KEYMANAGER_ACTIVE_TAG, ) @@ -100,7 +103,7 @@ class OpenPGPScheme(object): self._gpgbinary = gpgbinary self.deferred_init = init_indexes(soledad) self.deferred_init.addCallback(self._migrate_documents_schema) - self._wait_indexes("get_key", "put_key") + self._wait_indexes("get_key", "put_key", "get_all_keys") def _migrate_documents_schema(self, _): from leap.keymanager.migrator import KeyDocumentsMigrator @@ -242,6 +245,47 @@ class OpenPGPScheme(object): d.addCallback(build_key) return d + @defer.inlineCallbacks + def get_all_keys(self, private=False): + """ + Return all keys stored in local database. + + :param private: Include private keys + :type private: bool + + :return: A Deferred which fires with a list of all keys in local db. + :rtype: Deferred + """ + HAS_ACTIVE = "has_active" + + active_docs = yield self._soledad.get_from_index( + TAGS_PRIVATE_INDEX, + KEYMANAGER_ACTIVE_TAG, + '1' if private else '0') + key_docs = yield self._soledad.get_from_index( + TAGS_PRIVATE_INDEX, + KEYMANAGER_KEY_TAG, + '1' if private else '0') + + keys = [] + for active in active_docs: + fp = lambda doc: doc.content[KEY_FINGERPRINT_KEY] + fp_keys = filter(lambda k: fp(k) == fp(active), key_docs) + + if len(fp_keys) == 0: + yield self._soledad.delete_doc(active) + continue + elif len(fp_keys) == 1: + key = fp_keys[0] + else: + key = yield self._repair_key_docs(fp_keys) + key.content[HAS_ACTIVE] = True + keys.append(build_key_from_dict(key.content, active.content)) + + unactive_keys = filter(lambda k: HAS_ACTIVE not in k.content, key_docs) + keys += map(lambda k: build_key_from_dict(k.content), unactive_keys) + defer.returnValue(keys) + def parse_key(self, key_data, address=None): """ Parses a key (or key pair) data and returns -- cgit v1.2.3 From a60855ac2af5a6a562f08de412ebcfd2148ce992 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Thu, 5 May 2016 15:31:23 -0300 Subject: [feat] remove request dependency --- keymanager/src/leap/keymanager/__init__.py | 3 --- keymanager/src/leap/keymanager/tests/test_keymanager.py | 6 +----- 2 files changed, 1 insertion(+), 8 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index fdd47c36..57e815df 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -59,7 +59,6 @@ except (ImportError, AssertionError): sys.exit(1) import logging -import requests from twisted.internet import defer from urlparse import urlparse @@ -131,8 +130,6 @@ class KeyManager(object): self.api_version = api_version self.uid = uid self._openpgp = OpenPGPScheme(soledad, gpgbinary=gpgbinary) - # the following are used to perform https requests - self._fetcher = requests self._combined_ca_bundle = self._create_combined_bundle_file() self._async_client = HTTPClient(self._combined_ca_bundle) self._async_client_pinned = HTTPClient(self._ca_cert_path) diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 3572fb28..387332d8 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -371,11 +371,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): """ km = self._key_manager() - class Response(object): - ok = True - content = "" - - km._fetcher.get = Mock(return_value=Response()) + km._async_client.request = Mock(return_value=defer.succeed("")) d = km.fetch_key(ADDRESS, "http://site.domain/key") return self.assertFailure(d, errors.KeyNotFound) -- cgit v1.2.3 From 4cf66cfa9c3aa05239eb5042d63ffc2d661f8327 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Wed, 1 Jun 2016 21:14:33 +0200 Subject: fixup! [feat] remove the keytype support --- keymanager/src/leap/keymanager/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 57e815df..97d29857 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -441,7 +441,7 @@ class KeyManager(object): emit_async(catalog.KEYMANAGER_STARTED_KEY_GENERATION, self._address) - d = self.openpg.gen_key(self._address) + d = self._openpgp.gen_key(self._address) d.addCallback(signal_finished) return d -- cgit v1.2.3 From 5ebc52f361153fe2f9b19d2908f09ba5df5433d8 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Wed, 1 Jun 2016 21:15:14 +0200 Subject: [test] test key generation (skipped) --- keymanager/src/leap/keymanager/tests/test_keymanager.py | 10 ++++++++++ keymanager/src/leap/keymanager/tests/test_openpgp.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 387332d8..867a13d5 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -133,6 +133,16 @@ class KeyManagerUtilTestCase(unittest.TestCase): class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): + @defer.inlineCallbacks + def _test_gen_key(self): + km = self._key_manager() + key = yield km.gen_key() + self.assertIsInstance(key, OpenPGPKey) + self.assertEqual( + 'leap@leap.se', key.address, 'Wrong address bound to key.') + self.assertEqual( + 4096, key.length, 'Wrong key length.') + @defer.inlineCallbacks def test_get_all_keys_in_db(self): km = self._key_manager() diff --git a/keymanager/src/leap/keymanager/tests/test_openpgp.py b/keymanager/src/leap/keymanager/tests/test_openpgp.py index 47e9005a..4c016764 100644 --- a/keymanager/src/leap/keymanager/tests/test_openpgp.py +++ b/keymanager/src/leap/keymanager/tests/test_openpgp.py @@ -59,7 +59,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): key = yield pgp.gen_key('user@leap.se') self.assertIsInstance(key, OpenPGPKey) self.assertEqual( - ['user@leap.se'], key.address, 'Wrong address bound to key.') + 'user@leap.se', key.address, 'Wrong address bound to key.') self.assertEqual( 4096, key.length, 'Wrong key length.') -- cgit v1.2.3 From 5409d64e20e353f9f0acb77ad2d89b80a8685ed0 Mon Sep 17 00:00:00 2001 From: Zara Gebru Date: Fri, 10 Jun 2016 14:28:17 +0200 Subject: [bug] Add KeyNotFound import Two tests where failing, because - Related: #7987 --- keymanager/src/leap/keymanager/tests/test_keymanager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 867a13d5..9deaff1e 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -259,7 +259,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): check_404_callback = used_kwargs['callback'] fake_response = Mock() fake_response.code = NOT_FOUND - with self.assertRaisesRegexp(KeyNotFound, '404: %s key not found.' % INVALID_MAIL_ADDRESS): + with self.assertRaisesRegexp(errors.KeyNotFound, '404: %s key not found.' % INVALID_MAIL_ADDRESS): check_404_callback(fake_response) d.addCallback(check_key_not_found_is_raised_if_404) @@ -270,7 +270,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): Test if key search requests throws KeyNotFound, the same error is raised. """ km = self._key_manager(url=NICKSERVER_URI) - key_not_found_exception = KeyNotFound('some message') + key_not_found_exception = errors.KeyNotFound('some message') km._async_client_pinned.request = Mock(side_effect=key_not_found_exception) def assert_key_not_found_raised(error): -- cgit v1.2.3 From 161fd0542425dc9afa336dcb0675f56b3e1b0b55 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Tue, 14 Jun 2016 19:06:45 +0200 Subject: [feat] improve duplicated active documents fixup Mostly for private keys we want to keep the latest one in the keyring. It would be desirable to check with one is set in nicknym, but this will need a big refactor that I'm not sure if it's worth it just for that. - Related: #6658, #8059 --- keymanager/src/leap/keymanager/openpgp.py | 35 ++++++++--- .../src/leap/keymanager/tests/test_keymanager.py | 22 ++++--- .../src/leap/keymanager/tests/test_openpgp.py | 67 +++++++++------------- 3 files changed, 67 insertions(+), 57 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 8658e9c2..98ce4649 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -47,8 +47,8 @@ from leap.keymanager.documents import ( TYPE_ADDRESS_PRIVATE_INDEX, KEY_UIDS_KEY, KEY_FINGERPRINT_KEY, + KEY_PRIVATE_KEY, KEY_REFRESHED_AT_KEY, - KEY_LAST_AUDITED_AT_KEY, KEY_SIGN_USED_KEY, KEY_ENCR_USED_KEY, KEY_ADDRESS_KEY, @@ -268,8 +268,8 @@ class OpenPGPScheme(object): '1' if private else '0') keys = [] + fp = lambda doc: doc.content[KEY_FINGERPRINT_KEY] for active in active_docs: - fp = lambda doc: doc.content[KEY_FINGERPRINT_KEY] fp_keys = filter(lambda k: fp(k) == fp(active), key_docs) if len(fp_keys) == 0: @@ -770,6 +770,7 @@ class OpenPGPScheme(object): return self._repair_docs(doclist, cmp_key, log_key_doc) + @defer.inlineCallbacks def _repair_active_docs(self, doclist): """ If there is more than one active doc for an address try to self-repair @@ -779,23 +780,39 @@ class OpenPGPScheme(object): all the deletions are completed :rtype: Deferred """ + keys = {} + for doc in doclist: + fp = doc.content[KEY_FINGERPRINT_KEY] + private = doc.content[KEY_PRIVATE_KEY] + try: + key = yield self._get_key_doc_from_fingerprint(fp, private) + keys[fp] = key + except Exception: + pass + def log_active_doc(doc): logger.error("\t%s: %s" % (doc.content[KEY_ADDRESS_KEY], doc.content[KEY_FINGERPRINT_KEY])) def cmp_active(d1, d2): - res = cmp(d1.content[KEY_LAST_AUDITED_AT_KEY], - d2.content[KEY_LAST_AUDITED_AT_KEY]) - if res != 0: - return res - + # XXX: for private keys it will be nice to check which key is known + # by the nicknym server and keep this one. But this needs a + # refactor that might not be worth it. used1 = (d1.content[KEY_SIGN_USED_KEY] + d1.content[KEY_ENCR_USED_KEY]) used2 = (d2.content[KEY_SIGN_USED_KEY] + d2.content[KEY_ENCR_USED_KEY]) - return cmp(used1, used2) + res = cmp(used1, used2) + if res != 0: + return res + + key1 = keys[d1.content[KEY_FINGERPRINT_KEY]] + key2 = keys[d2.content[KEY_FINGERPRINT_KEY]] + return cmp(key1.content[KEY_REFRESHED_AT_KEY], + key2.content[KEY_REFRESHED_AT_KEY]) - return self._repair_docs(doclist, cmp_active, log_active_doc) + doc = yield self._repair_docs(doclist, cmp_active, log_active_doc) + defer.returnValue(doc) def _repair_docs(self, doclist, cmp_func, log_func): logger.error("BUG ---------------------------------------------------") diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py index 9deaff1e..c4d3fda3 100644 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ b/keymanager/src/leap/keymanager/tests/test_keymanager.py @@ -237,7 +237,8 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): def verify_the_call(_): used_kwargs = km._async_client_pinned.request.call_args[1] - km._async_client_pinned.request.assert_called_once_with(expected_url, 'GET', **used_kwargs) + km._async_client_pinned.request.assert_called_once_with( + expected_url, 'GET', **used_kwargs) d = self._fetch_key(km, ADDRESS_2, PUBLIC_KEY_2) d.addCallback(verify_the_call) @@ -245,11 +246,13 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): def test_key_not_found_is_raised_if_key_search_responds_404(self): """ - Test if key search request comes back with a 404 response then KeyNotFound is raised, with corresponding error message. + Test if key search request comes back with a 404 response then + KeyNotFound is raised, with corresponding error message. """ km = self._key_manager(url=NICKSERVER_URI) client.readBody = Mock(return_value=defer.succeed(None)) - km._async_client_pinned.request = Mock(return_value=defer.succeed(None)) + km._async_client_pinned.request = Mock( + return_value=defer.succeed(None)) url = NICKSERVER_URI + '?address=' + INVALID_MAIL_ADDRESS d = km._fetch_and_handle_404_from_nicknym(url, INVALID_MAIL_ADDRESS) @@ -259,7 +262,9 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): check_404_callback = used_kwargs['callback'] fake_response = Mock() fake_response.code = NOT_FOUND - with self.assertRaisesRegexp(errors.KeyNotFound, '404: %s key not found.' % INVALID_MAIL_ADDRESS): + with self.assertRaisesRegexp( + errors.KeyNotFound, + '404: %s key not found.' % INVALID_MAIL_ADDRESS): check_404_callback(fake_response) d.addCallback(check_key_not_found_is_raised_if_404) @@ -267,11 +272,13 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): def test_non_existing_key_from_nicknym_is_relayed(self): """ - Test if key search requests throws KeyNotFound, the same error is raised. + Test if key search requests throws KeyNotFound, the same error is + raised. """ km = self._key_manager(url=NICKSERVER_URI) key_not_found_exception = errors.KeyNotFound('some message') - km._async_client_pinned.request = Mock(side_effect=key_not_found_exception) + km._async_client_pinned.request = Mock( + side_effect=key_not_found_exception) def assert_key_not_found_raised(error): self.assertEqual(error.value, key_not_found_exception) @@ -312,7 +319,8 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): client.readBody = Mock(return_value=defer.succeed(data)) # mock the fetcher so it returns the key for ADDRESS_2 - km._async_client_pinned.request = Mock(return_value=defer.succeed(None)) + km._async_client_pinned.request = Mock( + return_value=defer.succeed(None)) km.ca_cert_path = 'cacertpath' # try to key get without fetching from server d_fail = km.get_key(address, fetch_remote=False) diff --git a/keymanager/src/leap/keymanager/tests/test_openpgp.py b/keymanager/src/leap/keymanager/tests/test_openpgp.py index 4c016764..17a6b790 100644 --- a/keymanager/src/leap/keymanager/tests/test_openpgp.py +++ b/keymanager/src/leap/keymanager/tests/test_openpgp.py @@ -309,52 +309,37 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): self.assertEqual(self.count, 2) @inlineCallbacks - def test_self_repair_five_active_docs(self): + def test_self_repair_six_active_docs(self): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=self.gpg_binary_path) - get_from_index = self._soledad.get_from_index - delete_doc = self._soledad.delete_doc - - def my_get_from_index(*args): - if (args[0] == TYPE_ADDRESS_PRIVATE_INDEX and - args[2] == ADDRESS): - k1 = OpenPGPKey(ADDRESS, fingerprint="1", - last_audited_at=datetime(2005, 1, 1)) - k2 = OpenPGPKey(ADDRESS, fingerprint="2", - last_audited_at=datetime(2007, 1, 1)) - k3 = OpenPGPKey(ADDRESS, fingerprint="3", - last_audited_at=datetime(2007, 1, 1), - encr_used=True, sign_used=True) - k4 = OpenPGPKey(ADDRESS, fingerprint="4", - last_audited_at=datetime(2007, 1, 1), - sign_used=True) - k5 = OpenPGPKey(ADDRESS, fingerprint="5", - last_audited_at=datetime(2007, 1, 1), - encr_used=True) - deferreds = [] - for k in (k1, k2, k3, k4, k5): - d = self._soledad.create_doc_from_json( - k.get_active_json()) - deferreds.append(d) - return gatherResults(deferreds) - elif args[0] == TYPE_FINGERPRINT_PRIVATE_INDEX: - fingerprint = args[2] - self.assertEqual(fingerprint, "3") - k = OpenPGPKey(ADDRESS, fingerprint="3") - return succeed( - [self._soledad.create_doc_from_json(k.get_json())]) - return get_from_index(*args) + k1 = OpenPGPKey(ADDRESS, fingerprint="1", + refreshed_at=datetime(2005, 1, 1)) + k2 = OpenPGPKey(ADDRESS, fingerprint="2", + refreshed_at=datetime(2007, 1, 1)) + k3 = OpenPGPKey(ADDRESS, fingerprint="3", + refreshed_at=datetime(2007, 1, 1), + encr_used=True, sign_used=True) + k4 = OpenPGPKey(ADDRESS, fingerprint="4", + refreshed_at=datetime(2007, 1, 1), + sign_used=True) + k5 = OpenPGPKey(ADDRESS, fingerprint="5", + refreshed_at=datetime(2007, 1, 1), + encr_used=True) + k6 = OpenPGPKey(ADDRESS, fingerprint="6", + refreshed_at=datetime(2006, 1, 1), + encr_used=True, sign_used=True) + keys = (k1, k2, k3, k4, k5, k6) + for key in keys: + yield self._soledad.create_doc_from_json(key.get_json()) + yield self._soledad.create_doc_from_json(key.get_active_json()) - self._soledad.get_from_index = my_get_from_index - self._soledad.delete_doc = Mock(return_value=succeed(None)) + delete_doc = self._mock_delete_doc() - try: - yield pgp.get_key(ADDRESS, private=False) - self.assertEqual(self._soledad.delete_doc.call_count, 4) - finally: - self._soledad.get_from_index = get_from_index - self._soledad.delete_doc = delete_doc + key = yield pgp.get_key(ADDRESS, private=False) + self._soledad.delete_doc = delete_doc + self.assertEqual(self.count, 5) + self.assertEqual(key.fingerprint, "3") def _assert_key_not_found(self, pgp, address, private=False): d = pgp.get_key(address, private=private) -- cgit v1.2.3 From 5409c0dec9d1d4a562cc69798e36d534b6690d30 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Thu, 16 Jun 2016 20:16:54 +0200 Subject: [feat] check key document versions and fail if it's unknown - Closes: #8165 --- keymanager/src/leap/keymanager/__init__.py | 8 +++++--- keymanager/src/leap/keymanager/errors.py | 9 +++++++++ keymanager/src/leap/keymanager/openpgp.py | 11 +++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/__init__.py b/keymanager/src/leap/keymanager/__init__.py index 97d29857..0b8a5b30 100644 --- a/keymanager/src/leap/keymanager/__init__.py +++ b/keymanager/src/leap/keymanager/__init__.py @@ -376,7 +376,8 @@ class KeyManager(object): :return: A Deferred which fires with an EncryptionKey bound to address, or which fails with KeyNotFound if no key was found neither - locally or in keyserver. + locally or in keyserver or fail with KeyVersionError if the + key has a format not supported by this version of KeyManager :rtype: Deferred :raise UnsupportedKeyTypeError: if invalid key type @@ -522,8 +523,9 @@ class KeyManager(object): :return: A Deferred which fires with the encrypted data as str, or which fails with KeyNotFound if no keys were found neither - locally or in keyserver or fails with EncryptError if failed - encrypting for some reason. + locally or in keyserver or fails with KeyVersionError if the + key format is not supported or fails with EncryptError if + failed encrypting for some reason. :rtype: Deferred :raise UnsupportedKeyTypeError: if invalid key type diff --git a/keymanager/src/leap/keymanager/errors.py b/keymanager/src/leap/keymanager/errors.py index 8a9fb3c7..dfff3936 100644 --- a/keymanager/src/leap/keymanager/errors.py +++ b/keymanager/src/leap/keymanager/errors.py @@ -28,6 +28,15 @@ class KeyNotFound(Exception): pass +class KeyVersionError(KeyNotFound): + """ + Raised when key was found in the keyring but the version is not supported. + + It will usually mean that it was created by a newer version of KeyManager. + """ + pass + + class KeyAlreadyExists(Exception): """ Raised when attempted to create a key that already exists. diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 98ce4649..31c13df1 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -53,6 +53,8 @@ from leap.keymanager.documents import ( KEY_ENCR_USED_KEY, KEY_ADDRESS_KEY, KEY_TYPE_KEY, + KEY_VERSION_KEY, + KEYMANAGER_DOC_VERSION, KEYMANAGER_ACTIVE_TYPE, KEYMANAGER_KEY_TAG, KEYMANAGER_ACTIVE_TAG, @@ -734,6 +736,7 @@ class OpenPGPScheme(object): address, '1' if private else '0') d.addCallback(self._repair_and_get_doc, self._repair_active_docs) + d.addCallback(self._check_version) return d def _get_key_doc_from_fingerprint(self, fingerprint, private): @@ -743,6 +746,7 @@ class OpenPGPScheme(object): fingerprint, '1' if private else '0') d.addCallback(self._repair_and_get_doc, self._repair_key_docs) + d.addCallback(self._check_version) return d def _repair_and_get_doc(self, doclist, repair_func): @@ -752,6 +756,13 @@ class OpenPGPScheme(object): return repair_func(doclist) return doclist[0] + def _check_version(self, doc): + if doc is not None: + version = doc.content[KEY_VERSION_KEY] + if version > KEYMANAGER_DOC_VERSION: + raise errors.KeyVersionError(str(version)) + return doc + def _repair_key_docs(self, doclist): """ If there is more than one key for a key id try to self-repair it -- cgit v1.2.3 From 83c246f9877f4e381e0eb97e2b920f4c0bd37ab9 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Tue, 5 Jul 2016 17:18:33 +0200 Subject: [feat] make ValidationLevels iterable Needed by bytmask cli. - Related: #7965 --- keymanager/src/leap/keymanager/validation.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/validation.py b/keymanager/src/leap/keymanager/validation.py index 8cf96da1..16a897e9 100644 --- a/keymanager/src/leap/keymanager/validation.py +++ b/keymanager/src/leap/keymanager/validation.py @@ -79,6 +79,9 @@ class _ValidationLevels(object): """ return getattr(self, name) + def __iter__(self): + return iter(self._level_names) + ValidationLevels = _ValidationLevels() -- cgit v1.2.3 From f331a5ebb7bb99390877c07856a44d08b9d418c0 Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 11 Jul 2016 20:50:01 +0200 Subject: [test] move tests to root of repo --- keymanager/src/leap/keymanager/tests/__init__.py | 327 ----------- .../leap/keymanager/tests/fixtures/private_key.bin | Bin 2202 -> 0 bytes .../leap/keymanager/tests/fixtures/public_key.bin | Bin 2202 -> 0 bytes .../src/leap/keymanager/tests/test_keymanager.py | 608 --------------------- .../src/leap/keymanager/tests/test_migrator.py | 175 ------ .../src/leap/keymanager/tests/test_openpgp.py | 364 ------------ .../src/leap/keymanager/tests/test_validation.py | 503 ----------------- 7 files changed, 1977 deletions(-) delete mode 100644 keymanager/src/leap/keymanager/tests/__init__.py delete mode 100644 keymanager/src/leap/keymanager/tests/fixtures/private_key.bin delete mode 100644 keymanager/src/leap/keymanager/tests/fixtures/public_key.bin delete mode 100644 keymanager/src/leap/keymanager/tests/test_keymanager.py delete mode 100644 keymanager/src/leap/keymanager/tests/test_migrator.py delete mode 100644 keymanager/src/leap/keymanager/tests/test_openpgp.py delete mode 100644 keymanager/src/leap/keymanager/tests/test_validation.py (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/tests/__init__.py b/keymanager/src/leap/keymanager/tests/__init__.py deleted file mode 100644 index a20e1fdf..00000000 --- a/keymanager/src/leap/keymanager/tests/__init__.py +++ /dev/null @@ -1,327 +0,0 @@ -# -*- coding: utf-8 -*- -# test_keymanager.py -# Copyright (C) 2013 LEAP -# -# 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 . -""" -Base classes for the Key Manager tests. -""" - -import distutils.spawn -import os.path - -from twisted.internet.defer import gatherResults -from twisted.trial import unittest - -from leap.common.testing.basetest import BaseLeapTest -from leap.soledad.client import Soledad -from leap.keymanager import KeyManager - -PATH = os.path.dirname(os.path.realpath(__file__)) - -ADDRESS = 'leap@leap.se' -ADDRESS_2 = 'anotheruser@leap.se' - - -class KeyManagerWithSoledadTestCase(unittest.TestCase, BaseLeapTest): - - def setUp(self): - self.setUpEnv() - self.gpg_binary_path = self._find_gpg() - - self._soledad = Soledad( - u"leap@leap.se", - u"123456", - secrets_path=self.tempdir + "/secret.gpg", - local_db_path=self.tempdir + "/soledad.u1db", - server_url='', - cert_file=None, - auth_token=None, - syncable=False - ) - - def tearDown(self): - km = self._key_manager() - - # wait for the indexes to be ready for the tear down - d = km._openpgp.deferred_init - d.addCallback(lambda _: self.delete_all_keys(km)) - d.addCallback(lambda _: self.tearDownEnv()) - d.addCallback(lambda _: self._soledad.close()) - return d - - def delete_all_keys(self, km): - def delete_keys(keys): - deferreds = [] - for key in keys: - d = km._openpgp.delete_key(key) - deferreds.append(d) - return gatherResults(deferreds) - - def check_deleted(_, private): - d = km.get_all_keys(private=private) - d.addCallback(lambda keys: self.assertEqual(keys, [])) - return d - - deferreds = [] - for private in [True, False]: - d = km.get_all_keys(private=private) - d.addCallback(delete_keys) - d.addCallback(check_deleted, private) - deferreds.append(d) - return gatherResults(deferreds) - - def _key_manager(self, user=ADDRESS, url='', token=None, - ca_cert_path=None): - return KeyManager(user, url, self._soledad, token=token, - gpgbinary=self.gpg_binary_path, - ca_cert_path=ca_cert_path) - - def _find_gpg(self): - gpg_path = distutils.spawn.find_executable('gpg') - if gpg_path is not None: - return os.path.realpath(gpg_path) - else: - return "/usr/bin/gpg" - - def get_public_binary_key(self): - with open(PATH + '/fixtures/public_key.bin', 'r') as binary_public_key: - return binary_public_key.read() - - def get_private_binary_key(self): - with open(PATH + '/fixtures/private_key.bin', 'r') as binary_private_key: - return binary_private_key.read() - - -# key 24D18DDF: public key "Leap Test Key " -KEY_FINGERPRINT = "E36E738D69173C13D709E44F2F455E2824D18DDF" -PUBLIC_KEY = """ ------BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1.4.10 (GNU/Linux) - -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 -=MuOY ------END PGP PUBLIC KEY BLOCK----- -""" -PRIVATE_KEY = """ ------BEGIN PGP PRIVATE KEY BLOCK----- -Version: GnuPG v1.4.10 (GNU/Linux) - -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 -=JTFu ------END PGP PRIVATE KEY BLOCK----- -""" - -# key 7FEE575A: public key "anotheruser " -PUBLIC_KEY_2 = """ ------BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1.4.10 (GNU/Linux) - -mI0EUYwJXgEEAMbTKHuPJ5/Gk34l9Z06f+0WCXTDXdte1UBoDtZ1erAbudgC4MOR -gquKqoj3Hhw0/ILqJ88GcOJmKK/bEoIAuKaqlzDF7UAYpOsPZZYmtRfPC2pTCnXq -Z1vdeqLwTbUspqXflkCkFtfhGKMq5rH8GV5a3tXZkRWZhdNwhVXZagC3ABEBAAG0 -IWFub3RoZXJ1c2VyIDxhbm90aGVydXNlckBsZWFwLnNlPoi4BBMBAgAiBQJRjAle -AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRB/nfpof+5XWotuA/4tLN4E -gUr7IfLy2HkHAxzw7A4rqfMN92DIM9mZrDGaWRrOn3aVF7VU1UG7MDkHfPvp/cFw -ezoCw4s4IoHVc/pVlOkcHSyt4/Rfh248tYEJmFCJXGHpkK83VIKYJAithNccJ6Q4 -JE/o06Mtf4uh/cA1HUL4a4ceqUhtpLJULLeKo7iNBFGMCV4BBADsyQI7GR0wSAxz -VayLjuPzgT+bjbFeymIhjuxKIEwnIKwYkovztW+4bbOcQs785k3Lp6RzvigTpQQt -Z/hwcLOqZbZw8t/24+D+Pq9mMP2uUvCFFqLlVvA6D3vKSQ/XNN+YB919WQ04jh63 -yuRe94WenT1RJd6xU1aaUff4rKizuQARAQABiJ8EGAECAAkFAlGMCV4CGwwACgkQ -f536aH/uV1rPZQQAqCzRysOlu8ez7PuiBD4SebgRqWlxa1TF1ujzfLmuPivROZ2X -Kw5aQstxgGSjoB7tac49s0huh4X8XK+BtJBfU84JS8Jc2satlfwoyZ35LH6sDZck -I+RS/3we6zpMfHs3vvp9xgca6ZupQxivGtxlJs294TpJorx+mFFqbV17AzQ= -=Thdu ------END PGP PUBLIC KEY BLOCK----- -""" - -PRIVATE_KEY_2 = """ ------BEGIN PGP PRIVATE KEY BLOCK----- -Version: GnuPG v1.4.10 (GNU/Linux) - -lQHYBFGMCV4BBADG0yh7jyefxpN+JfWdOn/tFgl0w13bXtVAaA7WdXqwG7nYAuDD -kYKriqqI9x4cNPyC6ifPBnDiZiiv2xKCALimqpcwxe1AGKTrD2WWJrUXzwtqUwp1 -6mdb3Xqi8E21LKal35ZApBbX4RijKuax/BleWt7V2ZEVmYXTcIVV2WoAtwARAQAB -AAP7BLuSAx7tOohnimEs74ks8l/L6dOcsFQZj2bqs4AoY3jFe7bV0tHr4llypb/8 -H3/DYvpf6DWnCjyUS1tTnXSW8JXtx01BUKaAufSmMNg9blKV6GGHlT/Whe9uVyks -7XHk/+9mebVMNJ/kNlqq2k+uWqJohzC8WWLRK+d1tBeqDsECANZmzltPaqUsGV5X -C3zszE3tUBgptV/mKnBtopKi+VH+t7K6fudGcG+bAcZDUoH/QVde52mIIjjIdLje -uajJuHUCAO1mqh+vPoGv4eBLV7iBo3XrunyGXiys4a39eomhxTy3YktQanjjx+ty -GltAGCs5PbWGO6/IRjjvd46wh53kzvsCAO0J97gsWhzLuFnkxFAJSPk7RRlyl7lI -1XS/x0Og6j9XHCyY1OYkfBm0to3UlCfkgirzCYlTYObCofzdKFIPDmSqHbQhYW5v -dGhlcnVzZXIgPGFub3RoZXJ1c2VyQGxlYXAuc2U+iLgEEwECACIFAlGMCV4CGwMG -CwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEH+d+mh/7ldai24D/i0s3gSBSvsh -8vLYeQcDHPDsDiup8w33YMgz2ZmsMZpZGs6fdpUXtVTVQbswOQd8++n9wXB7OgLD -izgigdVz+lWU6RwdLK3j9F+Hbjy1gQmYUIlcYemQrzdUgpgkCK2E1xwnpDgkT+jT -oy1/i6H9wDUdQvhrhx6pSG2kslQst4qjnQHYBFGMCV4BBADsyQI7GR0wSAxzVayL -juPzgT+bjbFeymIhjuxKIEwnIKwYkovztW+4bbOcQs785k3Lp6RzvigTpQQtZ/hw -cLOqZbZw8t/24+D+Pq9mMP2uUvCFFqLlVvA6D3vKSQ/XNN+YB919WQ04jh63yuRe -94WenT1RJd6xU1aaUff4rKizuQARAQABAAP9EyElqJ3dq3EErXwwT4mMnbd1SrVC -rUJrNWQZL59mm5oigS00uIyR0SvusOr+UzTtd8ysRuwHy5d/LAZsbjQStaOMBILx -77TJveOel0a1QK0YSMF2ywZMCKvquvjli4hAtWYz/EwfuzQN3t23jc5ny+GqmqD2 -3FUxLJosFUfLNmECAO9KhVmJi+L9dswIs+2Dkjd1eiRQzNOEVffvYkGYZyKxNiXF -UA5kvyZcB4iAN9sWCybE4WHZ9jd4myGB0MPDGxkCAP1RsXJbbuD6zS7BXe5gwunO -2q4q7ptdSl/sJYQuTe1KNP5d/uGsvlcFfsYjpsopasPjFBIncc/2QThMKlhoEaEB -/0mVAxpT6SrEvUbJ18z7kna24SgMPr3OnPMxPGfvNLJY/Xv/A17YfoqjmByCvsKE -JCDjopXtmbcrZyoEZbEht9mko4ifBBgBAgAJBQJRjAleAhsMAAoJEH+d+mh/7lda -z2UEAKgs0crDpbvHs+z7ogQ+Enm4EalpcWtUxdbo83y5rj4r0TmdlysOWkLLcYBk -o6Ae7WnOPbNIboeF/FyvgbSQX1POCUvCXNrGrZX8KMmd+Sx+rA2XJCPkUv98Hus6 -THx7N776fcYHGumbqUMYrxrcZSbNveE6SaK8fphRam1dewM0 -=a5gs ------END PGP PRIVATE KEY BLOCK----- -""" diff --git a/keymanager/src/leap/keymanager/tests/fixtures/private_key.bin b/keymanager/src/leap/keymanager/tests/fixtures/private_key.bin deleted file mode 100644 index ab174317..00000000 Binary files a/keymanager/src/leap/keymanager/tests/fixtures/private_key.bin and /dev/null differ diff --git a/keymanager/src/leap/keymanager/tests/fixtures/public_key.bin b/keymanager/src/leap/keymanager/tests/fixtures/public_key.bin deleted file mode 100644 index ab174317..00000000 Binary files a/keymanager/src/leap/keymanager/tests/fixtures/public_key.bin and /dev/null differ diff --git a/keymanager/src/leap/keymanager/tests/test_keymanager.py b/keymanager/src/leap/keymanager/tests/test_keymanager.py deleted file mode 100644 index c4d3fda3..00000000 --- a/keymanager/src/leap/keymanager/tests/test_keymanager.py +++ /dev/null @@ -1,608 +0,0 @@ -# -*- coding: utf-8 -*- -# test_keymanager.py -# Copyright (C) 2013 LEAP -# -# 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 . - - -""" -Tests for the Key Manager. -""" - -from os import path -import json -import urllib -from datetime import datetime -import tempfile -import pkg_resources -from leap.common import ca_bundle -from mock import Mock, MagicMock, patch -from twisted.internet import defer -from twisted.trial import unittest -from twisted.web._responses import NOT_FOUND - -from leap.keymanager import client - -from leap.keymanager import errors -from leap.keymanager.keys import ( - OpenPGPKey, - is_address, - build_key_from_dict, -) -from leap.keymanager.validation import ValidationLevels -from leap.keymanager.tests import ( - KeyManagerWithSoledadTestCase, - ADDRESS, - ADDRESS_2, - KEY_FINGERPRINT, - PUBLIC_KEY, - PUBLIC_KEY_2, - PRIVATE_KEY, - PRIVATE_KEY_2, -) - - -NICKSERVER_URI = "http://leap.se/" -REMOTE_KEY_URL = "http://site.domain/key" -INVALID_MAIL_ADDRESS = "notexistingemail@example.org" - - -class KeyManagerUtilTestCase(unittest.TestCase): - - def test_is_address(self): - self.assertTrue( - is_address('user@leap.se'), - 'Incorrect address detection.') - self.assertFalse( - is_address('userleap.se'), - 'Incorrect address detection.') - self.assertFalse( - is_address('user@'), - 'Incorrect address detection.') - self.assertFalse( - is_address('@leap.se'), - 'Incorrect address detection.') - - def test_build_key_from_dict(self): - kdict = { - 'uids': [ADDRESS], - 'fingerprint': KEY_FINGERPRINT, - 'key_data': PUBLIC_KEY, - 'private': False, - 'length': 4096, - 'expiry_date': 0, - 'refreshed_at': 1311239602, - } - adict = { - 'address': ADDRESS, - 'private': False, - 'last_audited_at': 0, - 'validation': str(ValidationLevels.Weak_Chain), - 'encr_used': False, - 'sign_used': True, - } - key = build_key_from_dict(kdict, adict) - self.assertEqual( - kdict['uids'], key.uids, - 'Wrong data in key.') - self.assertEqual( - kdict['fingerprint'], key.fingerprint, - 'Wrong data in key.') - self.assertEqual( - kdict['key_data'], key.key_data, - 'Wrong data in key.') - self.assertEqual( - kdict['private'], key.private, - 'Wrong data in key.') - self.assertEqual( - kdict['length'], key.length, - 'Wrong data in key.') - self.assertEqual( - None, key.expiry_date, - 'Wrong data in key.') - self.assertEqual( - None, key.last_audited_at, - 'Wrong data in key.') - self.assertEqual( - datetime.fromtimestamp(kdict['refreshed_at']), key.refreshed_at, - 'Wrong data in key.') - self.assertEqual( - adict['address'], key.address, - 'Wrong data in key.') - self.assertEqual( - ValidationLevels.get(adict['validation']), key.validation, - 'Wrong data in key.') - self.assertEqual( - adict['encr_used'], key.encr_used, - 'Wrong data in key.') - self.assertEqual( - adict['sign_used'], key.sign_used, - 'Wrong data in key.') - - -class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): - - @defer.inlineCallbacks - def _test_gen_key(self): - km = self._key_manager() - key = yield km.gen_key() - self.assertIsInstance(key, OpenPGPKey) - self.assertEqual( - 'leap@leap.se', key.address, 'Wrong address bound to key.') - self.assertEqual( - 4096, key.length, 'Wrong key length.') - - @defer.inlineCallbacks - def test_get_all_keys_in_db(self): - km = self._key_manager() - yield km._openpgp.put_raw_key(PRIVATE_KEY, ADDRESS) - # get public keys - keys = yield km.get_all_keys(False) - self.assertEqual(len(keys), 1, 'Wrong number of keys') - self.assertTrue(ADDRESS in keys[0].uids) - self.assertFalse(keys[0].private) - # get private keys - keys = yield km.get_all_keys(True) - self.assertEqual(len(keys), 1, 'Wrong number of keys') - self.assertTrue(ADDRESS in keys[0].uids) - self.assertTrue(keys[0].private) - - @defer.inlineCallbacks - def test_get_public_key(self): - km = self._key_manager() - yield km._openpgp.put_raw_key(PRIVATE_KEY, ADDRESS) - # get the key - key = yield km.get_key(ADDRESS, private=False, fetch_remote=False) - self.assertTrue(key is not None) - self.assertTrue(ADDRESS in key.uids) - self.assertEqual( - key.fingerprint.lower(), KEY_FINGERPRINT.lower()) - self.assertFalse(key.private) - - @defer.inlineCallbacks - def test_get_public_key_with_binary_private_key(self): - km = self._key_manager() - yield km._openpgp.put_raw_key(self.get_private_binary_key(), ADDRESS) - # get the key - key = yield km.get_key(ADDRESS, private=False, fetch_remote=False) - self.assertTrue(key is not None) - self.assertTrue(ADDRESS in key.uids) - self.assertEqual( - key.fingerprint.lower(), KEY_FINGERPRINT.lower()) - self.assertFalse(key.private) - - @defer.inlineCallbacks - def test_get_private_key(self): - km = self._key_manager() - yield km._openpgp.put_raw_key(PRIVATE_KEY, ADDRESS) - # get the key - key = yield km.get_key(ADDRESS, private=True, fetch_remote=False) - self.assertTrue(key is not None) - self.assertTrue(ADDRESS in key.uids) - self.assertEqual( - key.fingerprint.lower(), KEY_FINGERPRINT.lower()) - self.assertTrue(key.private) - - def test_send_key_raises_key_not_found(self): - km = self._key_manager() - d = km.send_key() - return self.assertFailure(d, errors.KeyNotFound) - - @defer.inlineCallbacks - def test_send_key(self): - """ - Test that request is well formed when sending keys to server. - """ - token = "mytoken" - km = self._key_manager(token=token) - yield km._openpgp.put_raw_key(PUBLIC_KEY, ADDRESS) - km._async_client_pinned.request = Mock(return_value=defer.succeed('')) - # the following data will be used on the send - km.ca_cert_path = 'capath' - km.session_id = 'sessionid' - km.uid = 'myuid' - km.api_uri = 'apiuri' - km.api_version = 'apiver' - yield km.send_key() - # setup expected args - pubkey = yield km.get_key(km._address) - data = urllib.urlencode({ - km.PUBKEY_KEY: pubkey.key_data, - }) - headers = {'Authorization': [str('Token token=%s' % token)]} - headers['Content-Type'] = ['application/x-www-form-urlencoded'] - url = '%s/%s/users/%s.json' % ('apiuri', 'apiver', 'myuid') - km._async_client_pinned.request.assert_called_once_with( - str(url), 'PUT', body=str(data), - headers=headers - ) - - def test_fetch_keys_from_server(self): - """ - Test that the request is well formed when fetching keys from server. - """ - km = self._key_manager(url=NICKSERVER_URI) - expected_url = NICKSERVER_URI + '?address=' + ADDRESS_2 - - def verify_the_call(_): - used_kwargs = km._async_client_pinned.request.call_args[1] - km._async_client_pinned.request.assert_called_once_with( - expected_url, 'GET', **used_kwargs) - - d = self._fetch_key(km, ADDRESS_2, PUBLIC_KEY_2) - d.addCallback(verify_the_call) - return d - - def test_key_not_found_is_raised_if_key_search_responds_404(self): - """ - Test if key search request comes back with a 404 response then - KeyNotFound is raised, with corresponding error message. - """ - km = self._key_manager(url=NICKSERVER_URI) - client.readBody = Mock(return_value=defer.succeed(None)) - km._async_client_pinned.request = Mock( - return_value=defer.succeed(None)) - url = NICKSERVER_URI + '?address=' + INVALID_MAIL_ADDRESS - - d = km._fetch_and_handle_404_from_nicknym(url, INVALID_MAIL_ADDRESS) - - def check_key_not_found_is_raised_if_404(_): - used_kwargs = km._async_client_pinned.request.call_args[1] - check_404_callback = used_kwargs['callback'] - fake_response = Mock() - fake_response.code = NOT_FOUND - with self.assertRaisesRegexp( - errors.KeyNotFound, - '404: %s key not found.' % INVALID_MAIL_ADDRESS): - check_404_callback(fake_response) - - d.addCallback(check_key_not_found_is_raised_if_404) - return d - - def test_non_existing_key_from_nicknym_is_relayed(self): - """ - Test if key search requests throws KeyNotFound, the same error is - raised. - """ - km = self._key_manager(url=NICKSERVER_URI) - key_not_found_exception = errors.KeyNotFound('some message') - km._async_client_pinned.request = Mock( - side_effect=key_not_found_exception) - - def assert_key_not_found_raised(error): - self.assertEqual(error.value, key_not_found_exception) - - d = km._get_key_from_nicknym(INVALID_MAIL_ADDRESS) - d.addErrback(assert_key_not_found_raised) - - @defer.inlineCallbacks - def test_get_key_fetches_from_server(self): - """ - Test that getting a key successfuly fetches from server. - """ - km = self._key_manager(url=NICKSERVER_URI) - - key = yield self._fetch_key(km, ADDRESS, PUBLIC_KEY) - self.assertIsInstance(key, OpenPGPKey) - self.assertTrue(ADDRESS in key.uids) - self.assertEqual(key.validation, ValidationLevels.Provider_Trust) - - @defer.inlineCallbacks - def test_get_key_fetches_other_domain(self): - """ - Test that getting a key successfuly fetches from server. - """ - km = self._key_manager(url=NICKSERVER_URI) - - key = yield self._fetch_key(km, ADDRESS_OTHER, PUBLIC_KEY_OTHER) - self.assertIsInstance(key, OpenPGPKey) - self.assertTrue(ADDRESS_OTHER in key.uids) - self.assertEqual(key.validation, ValidationLevels.Weak_Chain) - - def _fetch_key(self, km, address, key): - """ - :returns: a Deferred that will fire with the OpenPGPKey - """ - data = json.dumps({'address': address, 'openpgp': key}) - - client.readBody = Mock(return_value=defer.succeed(data)) - - # mock the fetcher so it returns the key for ADDRESS_2 - km._async_client_pinned.request = Mock( - return_value=defer.succeed(None)) - km.ca_cert_path = 'cacertpath' - # try to key get without fetching from server - d_fail = km.get_key(address, fetch_remote=False) - d = self.assertFailure(d_fail, errors.KeyNotFound) - # try to get key fetching from server. - d.addCallback(lambda _: km.get_key(address)) - return d - - @defer.inlineCallbacks - def test_put_key_ascii(self): - """ - Test that putting ascii key works - """ - km = self._key_manager(url=NICKSERVER_URI) - - yield km.put_raw_key(PUBLIC_KEY, ADDRESS) - key = yield km.get_key(ADDRESS) - self.assertIsInstance(key, OpenPGPKey) - self.assertTrue(ADDRESS in key.uids) - - @defer.inlineCallbacks - def test_put_key_binary(self): - """ - Test that putting binary key works - """ - km = self._key_manager(url=NICKSERVER_URI) - - yield km.put_raw_key(self.get_public_binary_key(), ADDRESS) - key = yield km.get_key(ADDRESS) - - self.assertIsInstance(key, OpenPGPKey) - self.assertTrue(ADDRESS in key.uids) - - @defer.inlineCallbacks - def test_fetch_uri_ascii_key(self): - """ - Test that fetch key downloads the ascii key and gets included in - the local storage - """ - km = self._key_manager() - - km._async_client.request = Mock(return_value=defer.succeed(PUBLIC_KEY)) - - yield km.fetch_key(ADDRESS, "http://site.domain/key") - key = yield km.get_key(ADDRESS) - self.assertEqual(KEY_FINGERPRINT, key.fingerprint) - - @defer.inlineCallbacks - def test_fetch_uri_binary_key(self): - """ - Test that fetch key downloads the binary key and gets included in - the local storage - """ - km = self._key_manager() - - km._async_client.request = Mock( - return_value=defer.succeed(self.get_public_binary_key())) - - yield km.fetch_key(ADDRESS, "http://site.domain/key") - key = yield km.get_key(ADDRESS) - self.assertEqual(KEY_FINGERPRINT, key.fingerprint) - - def test_fetch_uri_empty_key(self): - """ - Test that fetch key raises KeyNotFound if no key in the url - """ - km = self._key_manager() - - km._async_client.request = Mock(return_value=defer.succeed("")) - d = km.fetch_key(ADDRESS, "http://site.domain/key") - return self.assertFailure(d, errors.KeyNotFound) - - def test_fetch_uri_address_differ(self): - """ - Test that fetch key raises KeyAttributesDiffer if the address - don't match - """ - km = self._key_manager() - - km._async_client.request = Mock(return_value=defer.succeed(PUBLIC_KEY)) - d = km.fetch_key(ADDRESS_2, "http://site.domain/key") - return self.assertFailure(d, errors.KeyAddressMismatch) - - def _mock_get_response(self, km, body): - km._async_client.request = MagicMock(return_value=defer.succeed(body)) - - return km._async_client.request - - @defer.inlineCallbacks - def test_fetch_key_uses_ca_bundle_if_none_specified(self): - ca_cert_path = None - km = self._key_manager(ca_cert_path=ca_cert_path) - get_mock = self._mock_get_response(km, PUBLIC_KEY_OTHER) - - yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL) - - get_mock.assert_called_once_with(REMOTE_KEY_URL, 'GET') - - @defer.inlineCallbacks - def test_fetch_key_uses_ca_bundle_if_empty_string_specified(self): - ca_cert_path = '' - km = self._key_manager(ca_cert_path=ca_cert_path) - get_mock = self._mock_get_response(km, PUBLIC_KEY_OTHER) - - yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL) - - get_mock.assert_called_once_with(REMOTE_KEY_URL, 'GET') - - @defer.inlineCallbacks - def test_fetch_key_use_default_ca_bundle_if_set_as_ca_cert_path(self): - ca_cert_path = ca_bundle.where() - km = self._key_manager(ca_cert_path=ca_cert_path) - get_mock = self._mock_get_response(km, PUBLIC_KEY_OTHER) - - yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL) - - get_mock.assert_called_once_with(REMOTE_KEY_URL, 'GET') - - @defer.inlineCallbacks - def test_fetch_uses_combined_ca_bundle_otherwise(self): - with tempfile.NamedTemporaryFile() as tmp_input, \ - tempfile.NamedTemporaryFile(delete=False) as tmp_output: - ca_content = pkg_resources.resource_string('leap.common.testing', - 'cacert.pem') - ca_cert_path = tmp_input.name - self._dump_to_file(ca_cert_path, ca_content) - - with patch('leap.keymanager.tempfile.NamedTemporaryFile') as mock: - mock.return_value = tmp_output - km = self._key_manager(ca_cert_path=ca_cert_path) - get_mock = self._mock_get_response(km, PUBLIC_KEY_OTHER) - - yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL) - - # assert that combined bundle file is passed to get call - get_mock.assert_called_once_with(REMOTE_KEY_URL, 'GET') - - # assert that files got appended - expected = self._slurp_file(ca_bundle.where()) + ca_content - self.assertEqual(expected, self._slurp_file(tmp_output.name)) - - del km # force km out of scope - self.assertFalse(path.exists(tmp_output.name)) - - def _dump_to_file(self, filename, content): - with open(filename, 'w') as out: - out.write(content) - - def _slurp_file(self, filename): - with open(filename) as f: - content = f.read() - return content - - @defer.inlineCallbacks - def test_decrypt_updates_sign_used_for_signer(self): - # given - km = self._key_manager() - yield km._openpgp.put_raw_key(PRIVATE_KEY, ADDRESS) - yield km._openpgp.put_raw_key(PRIVATE_KEY_2, ADDRESS_2) - encdata = yield km.encrypt('data', ADDRESS, sign=ADDRESS_2, - fetch_remote=False) - yield km.decrypt( - encdata, ADDRESS, verify=ADDRESS_2, fetch_remote=False) - - # when - key = yield km.get_key(ADDRESS_2, fetch_remote=False) - - # then - self.assertEqual(True, key.sign_used) - - @defer.inlineCallbacks - def test_decrypt_does_not_update_sign_used_for_recipient(self): - # given - km = self._key_manager() - yield km._openpgp.put_raw_key(PRIVATE_KEY, ADDRESS) - yield km._openpgp.put_raw_key(PRIVATE_KEY_2, ADDRESS_2) - encdata = yield km.encrypt('data', ADDRESS, sign=ADDRESS_2, - fetch_remote=False) - yield km.decrypt( - encdata, ADDRESS, verify=ADDRESS_2, fetch_remote=False) - - # when - key = yield km.get_key( - ADDRESS, private=False, fetch_remote=False) - - # then - self.assertEqual(False, key.sign_used) - - -class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): - - RAW_DATA = 'data' - - @defer.inlineCallbacks - def test_keymanager_openpgp_encrypt_decrypt(self): - km = self._key_manager() - # put raw private key - yield km._openpgp.put_raw_key(PRIVATE_KEY, ADDRESS) - yield km._openpgp.put_raw_key(PRIVATE_KEY_2, ADDRESS_2) - # encrypt - encdata = yield km.encrypt(self.RAW_DATA, ADDRESS, sign=ADDRESS_2, - fetch_remote=False) - self.assertNotEqual(self.RAW_DATA, encdata) - # decrypt - rawdata, signingkey = yield km.decrypt( - encdata, ADDRESS, verify=ADDRESS_2, fetch_remote=False) - self.assertEqual(self.RAW_DATA, rawdata) - key = yield km.get_key(ADDRESS_2, private=False, fetch_remote=False) - self.assertEqual(signingkey.fingerprint, key.fingerprint) - - @defer.inlineCallbacks - def test_keymanager_openpgp_encrypt_decrypt_wrong_sign(self): - km = self._key_manager() - # put raw keys - yield km._openpgp.put_raw_key(PRIVATE_KEY, ADDRESS) - yield km._openpgp.put_raw_key(PRIVATE_KEY_2, ADDRESS_2) - # encrypt - encdata = yield km.encrypt(self.RAW_DATA, ADDRESS, sign=ADDRESS_2, - fetch_remote=False) - self.assertNotEqual(self.RAW_DATA, encdata) - # verify - rawdata, signingkey = yield km.decrypt( - encdata, ADDRESS, verify=ADDRESS, fetch_remote=False) - self.assertEqual(self.RAW_DATA, rawdata) - self.assertTrue(isinstance(signingkey, errors.InvalidSignature)) - - @defer.inlineCallbacks - def test_keymanager_openpgp_sign_verify(self): - km = self._key_manager() - # put raw private keys - yield km._openpgp.put_raw_key(PRIVATE_KEY, ADDRESS) - signdata = yield km.sign(self.RAW_DATA, ADDRESS, detach=False) - self.assertNotEqual(self.RAW_DATA, signdata) - # verify - signingkey = yield km.verify(signdata, ADDRESS, fetch_remote=False) - key = yield km.get_key(ADDRESS, private=False, fetch_remote=False) - self.assertEqual(signingkey.fingerprint, key.fingerprint) - - def test_keymanager_encrypt_key_not_found(self): - km = self._key_manager() - d = km._openpgp.put_raw_key(PRIVATE_KEY, ADDRESS) - d.addCallback( - lambda _: km.encrypt(self.RAW_DATA, ADDRESS_2, sign=ADDRESS, - fetch_remote=False)) - return self.assertFailure(d, errors.KeyNotFound) - -if __name__ == "__main__": - import unittest - unittest.main() - -# key 0F91B402: someone@somedomain.org -# 9420 EC7B 6DCB 867F 5592 E6D1 7504 C974 0F91 B402 -ADDRESS_OTHER = "someone@somedomain.org" -PUBLIC_KEY_OTHER = """ ------BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1 - -mQENBFUZFLwBCADRzTstykRAV3aWysLAV4O3DXdpXhV3Cww8Pfc6m1bVxAT2ifcL -kLWEaIkOB48SYIHbYzqOi1/h5abJf+5n4uhaIks+FsjsXYo1XOiYpVCNf7+xLnUM -jkmglKT5sASr61QDcFMqWfGTJ8iUTNVCJZ2k14QJ4Vss/ntnV9uB7Ef7wU7RZvxr -wINH/0LfKPsGE9l2qNpKUAAmg2bHn9YdsHj1sqlW7eZpwvefYrQej4KBaL2oq3vt -QQOdXGFqWYMe3cX+bQ1DAMG3ttTF6EGkY97BK7A18I/RJiLujWCEAkMzFr5SK9KU -AOMj6MpjfTOE+GfUKsu7/gGt42eMBFsIOvsZABEBAAG0IFNvbWVvbmUgPHNvbWVv -bmVAc29tZWRvbWFpbi5vcmc+iQE4BBMBAgAiBQJVGRS8AhsDBgsJCAcDAgYVCAIJ -CgsEFgIDAQIeAQIXgAAKCRB1BMl0D5G0AlFsCAC33LhxBRwO64T6DgTb4/39aLpi -9T3yAmXBAHC7Q+4f37IBX5fJBRKu4Lvfp6KherOl/I/Jj34yv8pm0j+kXeWktfxZ -cW+mv2vjBHQVopiUSyMVh7caFSq9sKm+oQdo6oIl9DHSARegbkCn2+0b4VxgJpyj -TZBMyUMD2AayivQU4QHOM3KCozhLNNDbpKy7LH0MSAUDmRaJsPk1zK15lQocK/7R -Z5yF4rdrdzDWrVucZJc09yntSqTGECue3W2GBCaBlb/O1c9xei4MTb4nSHS5Gp/7 -hcjrvIrgPpehndk8ZRREN/Y8uk1W5fbWzx+5z8g31RCGWBQw4NAnG10NZ3oEuQEN -BFUZFLwBCADocYZmLu1iXIE6gKqniR6Z8UDC5XnqgK+BEJwi1abe9zWhjgKeW9Vv -u1i194wuCUiNkP/bMvwMBZLTslDzqxl32ETk9FvB3kWy80S8MDjQJ15IN4I622fq -MEWwtQ0WrRay9VV6M8H2mIf71/1d5T9ysWK4XRyv+N7eRhfg7T2uhrpNyKdCZzjq -2wlgpVkMY7gtxTqJseM+qS5UNiReGxtoOXFLzzmagFgbqK88eMeZJZt8yKf81xhP -SWLTxaVaeBEAlajvEkxZJrrDQuc+maTwtMxmNUe815wJnpcRF8VD91GUpSLAN6EC -1QuJUl6Lc2o2tcHeo6CGsDZ96o0J8pFhABEBAAGJAR8EGAECAAkFAlUZFLwCGwwA -CgkQdQTJdA+RtAKcdwgApzHPhwwaZ9TBjgOytke/hPE0ht/EJ5nRiIda2PucoPh6 -DwnaI8nvmGXUfC4qFy6LM8/fJHof1BqLnMbx8MCLurnm5z30q8RhLE3YWM11zuMy -6wkHGmi/6S1G4okC+Uu8AA4K//HBo8bLcqGVWRnFAmCqy6VMAofsQvmM7vHbRj56 -U919Bki/7I6kcxPEzO73Umh3o82VP/Hz3JMigRNBRfG3jPrX04RLJj3Ib5lhQIDw -XrO8VHz9foOpY+rJnWj+6QAozxorzZYShu6H0GR1nIuqWMwli1nrx6BeIJAVz5cg -QzEd9yAN+81fkIBaa6Y8LCBxV03JCc2J4eCUKXd1gg== -=gDzy ------END PGP PUBLIC KEY BLOCK----- -""" diff --git a/keymanager/src/leap/keymanager/tests/test_migrator.py b/keymanager/src/leap/keymanager/tests/test_migrator.py deleted file mode 100644 index 76323e52..00000000 --- a/keymanager/src/leap/keymanager/tests/test_migrator.py +++ /dev/null @@ -1,175 +0,0 @@ -# -*- coding: utf-8 -*- -# test_migrator.py -# Copyright (C) 2015 LEAP -# -# 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 . - - -""" -Tests for the migrator. -""" - - -from collections import namedtuple -from mock import Mock -from twisted.internet.defer import succeed, inlineCallbacks - -from leap.keymanager.migrator import KeyDocumentsMigrator, KEY_ID_KEY -from leap.keymanager.documents import ( - TAGS_PRIVATE_INDEX, - KEYMANAGER_ACTIVE_TAG, - KEYMANAGER_KEY_TAG, - KEYMANAGER_DOC_VERSION, - - KEY_ADDRESS_KEY, - KEY_UIDS_KEY, - KEY_VERSION_KEY, - KEY_FINGERPRINT_KEY, - KEY_VALIDATION_KEY, - KEY_LAST_AUDITED_AT_KEY, - KEY_ENCR_USED_KEY, - KEY_SIGN_USED_KEY, -) -from leap.keymanager.validation import ValidationLevels -from leap.keymanager.tests import ( - KeyManagerWithSoledadTestCase, - ADDRESS, - ADDRESS_2, - KEY_FINGERPRINT, -) - - -class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): - @inlineCallbacks - def test_simple_migration(self): - get_from_index = self._soledad.get_from_index - delete_doc = self._soledad.delete_doc - put_doc = self._soledad.put_doc - - def my_get_from_index(*args): - docs = [] - if (args[0] == TAGS_PRIVATE_INDEX and - args[2] == '0'): - SoledadDocument = namedtuple("SoledadDocument", ["content"]) - if args[1] == KEYMANAGER_KEY_TAG: - docs = [SoledadDocument({ - KEY_ADDRESS_KEY: [ADDRESS], - KEY_ID_KEY: KEY_FINGERPRINT[-16:], - KEY_FINGERPRINT_KEY: KEY_FINGERPRINT, - KEY_VALIDATION_KEY: str(ValidationLevels.Weak_Chain), - KEY_LAST_AUDITED_AT_KEY: 0, - KEY_ENCR_USED_KEY: True, - KEY_SIGN_USED_KEY: False, - })] - if args[1] == KEYMANAGER_ACTIVE_TAG: - docs = [SoledadDocument({ - KEY_ID_KEY: KEY_FINGERPRINT[-16:], - })] - return succeed(docs) - - self._soledad.get_from_index = my_get_from_index - self._soledad.delete_doc = Mock(return_value=succeed(None)) - self._soledad.put_doc = Mock(return_value=succeed(None)) - - try: - migrator = KeyDocumentsMigrator(self._soledad) - yield migrator.migrate() - call_list = self._soledad.put_doc.call_args_list - finally: - self._soledad.get_from_index = get_from_index - self._soledad.delete_doc = delete_doc - self._soledad.put_doc = put_doc - - self.assertEqual(len(call_list), 2) - active = call_list[0][0][0] - key = call_list[1][0][0] - - self.assertTrue(KEY_ID_KEY not in active.content) - self.assertEqual(active.content[KEY_VERSION_KEY], - KEYMANAGER_DOC_VERSION) - self.assertEqual(active.content[KEY_FINGERPRINT_KEY], KEY_FINGERPRINT) - self.assertEqual(active.content[KEY_VALIDATION_KEY], - str(ValidationLevels.Weak_Chain)) - self.assertEqual(active.content[KEY_LAST_AUDITED_AT_KEY], 0) - self.assertEqual(active.content[KEY_ENCR_USED_KEY], True) - self.assertEqual(active.content[KEY_SIGN_USED_KEY], False) - - self.assertTrue(KEY_ID_KEY not in key.content) - self.assertTrue(KEY_ADDRESS_KEY not in key.content) - self.assertTrue(KEY_VALIDATION_KEY not in key.content) - self.assertTrue(KEY_LAST_AUDITED_AT_KEY not in key.content) - self.assertTrue(KEY_ENCR_USED_KEY not in key.content) - self.assertTrue(KEY_SIGN_USED_KEY not in key.content) - self.assertEqual(key.content[KEY_UIDS_KEY], [ADDRESS]) - - @inlineCallbacks - def test_two_active_docs(self): - get_from_index = self._soledad.get_from_index - delete_doc = self._soledad.delete_doc - put_doc = self._soledad.put_doc - - def my_get_from_index(*args): - docs = [] - if (args[0] == TAGS_PRIVATE_INDEX and - args[2] == '0'): - SoledadDocument = namedtuple("SoledadDocument", ["content"]) - if args[1] == KEYMANAGER_KEY_TAG: - validation = str(ValidationLevels.Provider_Trust) - docs = [SoledadDocument({ - KEY_ADDRESS_KEY: [ADDRESS, ADDRESS_2], - KEY_ID_KEY: KEY_FINGERPRINT[-16:], - KEY_FINGERPRINT_KEY: KEY_FINGERPRINT, - KEY_VALIDATION_KEY: validation, - KEY_LAST_AUDITED_AT_KEY: 1984, - KEY_ENCR_USED_KEY: True, - KEY_SIGN_USED_KEY: False, - })] - if args[1] == KEYMANAGER_ACTIVE_TAG: - docs = [ - SoledadDocument({ - KEY_ADDRESS_KEY: ADDRESS, - KEY_ID_KEY: KEY_FINGERPRINT[-16:], - }), - SoledadDocument({ - KEY_ADDRESS_KEY: ADDRESS_2, - KEY_ID_KEY: KEY_FINGERPRINT[-16:], - }), - ] - return succeed(docs) - - self._soledad.get_from_index = my_get_from_index - self._soledad.delete_doc = Mock(return_value=succeed(None)) - self._soledad.put_doc = Mock(return_value=succeed(None)) - - try: - migrator = KeyDocumentsMigrator(self._soledad) - yield migrator.migrate() - call_list = self._soledad.put_doc.call_args_list - finally: - self._soledad.get_from_index = get_from_index - self._soledad.delete_doc = delete_doc - self._soledad.put_doc = put_doc - - self.assertEqual(len(call_list), 3) - for active in [call[0][0] for call in call_list][:2]: - self.assertTrue(KEY_ID_KEY not in active.content) - self.assertEqual(active.content[KEY_VERSION_KEY], - KEYMANAGER_DOC_VERSION) - self.assertEqual(active.content[KEY_FINGERPRINT_KEY], - KEY_FINGERPRINT) - self.assertEqual(active.content[KEY_VALIDATION_KEY], - str(ValidationLevels.Weak_Chain)) - self.assertEqual(active.content[KEY_LAST_AUDITED_AT_KEY], 0) - self.assertEqual(active.content[KEY_ENCR_USED_KEY], False) - self.assertEqual(active.content[KEY_SIGN_USED_KEY], False) diff --git a/keymanager/src/leap/keymanager/tests/test_openpgp.py b/keymanager/src/leap/keymanager/tests/test_openpgp.py deleted file mode 100644 index 17a6b790..00000000 --- a/keymanager/src/leap/keymanager/tests/test_openpgp.py +++ /dev/null @@ -1,364 +0,0 @@ -# -*- coding: utf-8 -*- -# test_keymanager.py -# Copyright (C) 2014 LEAP -# -# 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 . - - -""" -Tests for the OpenPGP support on Key Manager. -""" - - -from datetime import datetime -from mock import Mock -from twisted.internet.defer import inlineCallbacks, gatherResults, succeed - -from leap.keymanager import ( - KeyNotFound, - openpgp, -) -from leap.keymanager.documents import ( - TYPE_FINGERPRINT_PRIVATE_INDEX, - TYPE_ADDRESS_PRIVATE_INDEX, -) -from leap.keymanager.keys import OpenPGPKey -from leap.keymanager.tests import ( - KeyManagerWithSoledadTestCase, - ADDRESS, - ADDRESS_2, - KEY_FINGERPRINT, - PUBLIC_KEY, - PUBLIC_KEY_2, - PRIVATE_KEY, - PRIVATE_KEY_2, -) - - -class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): - - # set the trial timeout to 20min, needed by the key generation test - timeout = 1200 - - @inlineCallbacks - def _test_openpgp_gen_key(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=self.gpg_binary_path) - yield self._assert_key_not_found(pgp, 'user@leap.se') - key = yield pgp.gen_key('user@leap.se') - self.assertIsInstance(key, OpenPGPKey) - self.assertEqual( - 'user@leap.se', key.address, 'Wrong address bound to key.') - self.assertEqual( - 4096, key.length, 'Wrong key length.') - - @inlineCallbacks - def test_openpgp_put_delete_key(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=self.gpg_binary_path) - yield self._assert_key_not_found(pgp, ADDRESS) - yield pgp.put_raw_key(PUBLIC_KEY, ADDRESS) - key = yield pgp.get_key(ADDRESS, private=False) - yield pgp.delete_key(key) - yield self._assert_key_not_found(pgp, ADDRESS) - - @inlineCallbacks - def test_openpgp_put_ascii_key(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=self.gpg_binary_path) - yield self._assert_key_not_found(pgp, ADDRESS) - yield pgp.put_raw_key(PUBLIC_KEY, ADDRESS) - key = yield pgp.get_key(ADDRESS, private=False) - self.assertIsInstance(key, OpenPGPKey) - self.assertTrue( - ADDRESS in key.address, 'Wrong address bound to key.') - self.assertEqual( - 4096, key.length, 'Wrong key length.') - yield pgp.delete_key(key) - yield self._assert_key_not_found(pgp, ADDRESS) - - @inlineCallbacks - def test_get_public_key(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=self.gpg_binary_path) - yield self._assert_key_not_found(pgp, ADDRESS) - yield pgp.put_raw_key(PUBLIC_KEY, ADDRESS) - yield self._assert_key_not_found(pgp, ADDRESS, private=True) - key = yield pgp.get_key(ADDRESS, private=False) - self.assertTrue(ADDRESS in key.address) - self.assertFalse(key.private) - self.assertEqual(KEY_FINGERPRINT, key.fingerprint) - yield pgp.delete_key(key) - yield self._assert_key_not_found(pgp, ADDRESS) - - @inlineCallbacks - def test_openpgp_encrypt_decrypt(self): - data = 'data' - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=self.gpg_binary_path) - - # encrypt - yield pgp.put_raw_key(PUBLIC_KEY, ADDRESS) - pubkey = yield pgp.get_key(ADDRESS, private=False) - cyphertext = yield pgp.encrypt(data, pubkey) - - self.assertTrue(cyphertext is not None) - self.assertTrue(cyphertext != '') - self.assertTrue(cyphertext != data) - self.assertTrue(pgp.is_encrypted(cyphertext)) - self.assertTrue(pgp.is_encrypted(cyphertext)) - - # decrypt - yield self._assert_key_not_found(pgp, ADDRESS, private=True) - yield pgp.put_raw_key(PRIVATE_KEY, ADDRESS) - privkey = yield pgp.get_key(ADDRESS, private=True) - decrypted, _ = yield pgp.decrypt(cyphertext, privkey) - self.assertEqual(decrypted, data) - - yield pgp.delete_key(pubkey) - yield pgp.delete_key(privkey) - yield self._assert_key_not_found(pgp, ADDRESS, private=False) - yield self._assert_key_not_found(pgp, ADDRESS, private=True) - - @inlineCallbacks - def test_verify_with_private_raises(self): - data = 'data' - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=self.gpg_binary_path) - yield pgp.put_raw_key(PRIVATE_KEY, ADDRESS) - privkey = yield pgp.get_key(ADDRESS, private=True) - signed = pgp.sign(data, privkey) - self.assertRaises( - AssertionError, - pgp.verify, signed, privkey) - - @inlineCallbacks - def test_sign_with_public_raises(self): - data = 'data' - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=self.gpg_binary_path) - yield pgp.put_raw_key(PUBLIC_KEY, ADDRESS) - self.assertRaises( - AssertionError, - pgp.sign, data, ADDRESS, OpenPGPKey) - - @inlineCallbacks - def test_verify_with_wrong_key_raises(self): - data = 'data' - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=self.gpg_binary_path) - yield pgp.put_raw_key(PRIVATE_KEY, ADDRESS) - privkey = yield pgp.get_key(ADDRESS, private=True) - signed = pgp.sign(data, privkey) - yield pgp.put_raw_key(PUBLIC_KEY_2, ADDRESS_2) - wrongkey = yield pgp.get_key(ADDRESS_2) - self.assertFalse(pgp.verify(signed, wrongkey)) - - @inlineCallbacks - def test_encrypt_sign_with_public_raises(self): - data = 'data' - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=self.gpg_binary_path) - yield pgp.put_raw_key(PRIVATE_KEY, ADDRESS) - privkey = yield pgp.get_key(ADDRESS, private=True) - pubkey = yield pgp.get_key(ADDRESS, private=False) - self.failureResultOf( - pgp.encrypt(data, privkey, sign=pubkey), - AssertionError) - - @inlineCallbacks - def test_decrypt_verify_with_private_raises(self): - data = 'data' - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=self.gpg_binary_path) - yield pgp.put_raw_key(PRIVATE_KEY, ADDRESS) - privkey = yield pgp.get_key(ADDRESS, private=True) - pubkey = yield pgp.get_key(ADDRESS, private=False) - encrypted_and_signed = yield pgp.encrypt( - data, pubkey, sign=privkey) - self.failureResultOf( - pgp.decrypt(encrypted_and_signed, privkey, verify=privkey), - AssertionError) - - @inlineCallbacks - def test_decrypt_verify_with_wrong_key(self): - data = 'data' - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=self.gpg_binary_path) - yield pgp.put_raw_key(PRIVATE_KEY, ADDRESS) - privkey = yield pgp.get_key(ADDRESS, private=True) - pubkey = yield pgp.get_key(ADDRESS, private=False) - encrypted_and_signed = yield pgp.encrypt(data, pubkey, sign=privkey) - yield pgp.put_raw_key(PUBLIC_KEY_2, ADDRESS_2) - wrongkey = yield pgp.get_key(ADDRESS_2) - decrypted, validsign = yield pgp.decrypt(encrypted_and_signed, - privkey, - verify=wrongkey) - self.assertEqual(decrypted, data) - self.assertFalse(validsign) - - @inlineCallbacks - def test_sign_verify(self): - data = 'data' - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=self.gpg_binary_path) - yield pgp.put_raw_key(PRIVATE_KEY, ADDRESS) - privkey = yield pgp.get_key(ADDRESS, private=True) - signed = pgp.sign(data, privkey, detach=False) - pubkey = yield pgp.get_key(ADDRESS, private=False) - validsign = pgp.verify(signed, pubkey) - self.assertTrue(validsign) - - @inlineCallbacks - def test_encrypt_sign_decrypt_verify(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=self.gpg_binary_path) - - yield pgp.put_raw_key(PRIVATE_KEY, ADDRESS) - pubkey = yield pgp.get_key(ADDRESS, private=False) - privkey = yield pgp.get_key(ADDRESS, private=True) - - yield pgp.put_raw_key(PRIVATE_KEY_2, ADDRESS_2) - pubkey2 = yield pgp.get_key(ADDRESS_2, private=False) - privkey2 = yield pgp.get_key(ADDRESS_2, private=True) - - data = 'data' - encrypted_and_signed = yield pgp.encrypt( - data, pubkey2, sign=privkey) - res, validsign = yield pgp.decrypt( - encrypted_and_signed, privkey2, verify=pubkey) - self.assertEqual(data, res) - self.assertTrue(validsign) - - @inlineCallbacks - def test_sign_verify_detached_sig(self): - data = 'data' - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=self.gpg_binary_path) - yield pgp.put_raw_key(PRIVATE_KEY, ADDRESS) - privkey = yield pgp.get_key(ADDRESS, private=True) - signature = yield pgp.sign(data, privkey, detach=True) - pubkey = yield pgp.get_key(ADDRESS, private=False) - validsign = pgp.verify(data, pubkey, detached_sig=signature) - self.assertTrue(validsign) - - @inlineCallbacks - def test_self_repair_three_keys(self): - refreshed_keep = datetime(2007, 1, 1) - self._insert_key_docs([datetime(2005, 1, 1), - refreshed_keep, - datetime(2001, 1, 1)]) - delete_doc = self._mock_delete_doc() - - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=self.gpg_binary_path) - key = yield pgp.get_key(ADDRESS, private=False) - self.assertEqual(key.refreshed_at, refreshed_keep) - self.assertEqual(self.count, 2) - self._soledad.delete_doc = delete_doc - - @inlineCallbacks - def test_self_repair_no_keys(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=self.gpg_binary_path) - yield pgp.put_raw_key(PUBLIC_KEY, ADDRESS) - - get_from_index = self._soledad.get_from_index - delete_doc = self._soledad.delete_doc - - def my_get_from_index(*args): - if (args[0] == TYPE_FINGERPRINT_PRIVATE_INDEX and - args[2] == KEY_FINGERPRINT): - return succeed([]) - return get_from_index(*args) - - self._soledad.get_from_index = my_get_from_index - self._soledad.delete_doc = Mock(return_value=succeed(None)) - - try: - yield self.assertFailure(pgp.get_key(ADDRESS, private=False), - KeyNotFound) - # it should have deleted the index - self.assertEqual(self._soledad.delete_doc.call_count, 1) - finally: - self._soledad.get_from_index = get_from_index - self._soledad.delete_doc = delete_doc - - @inlineCallbacks - def test_self_repair_put_keys(self): - self._insert_key_docs([datetime(2005, 1, 1), - datetime(2007, 1, 1), - datetime(2001, 1, 1)]) - delete_doc = self._mock_delete_doc() - - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=self.gpg_binary_path) - yield pgp.put_raw_key(PUBLIC_KEY, ADDRESS) - self._soledad.delete_doc = delete_doc - self.assertEqual(self.count, 2) - - @inlineCallbacks - def test_self_repair_six_active_docs(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=self.gpg_binary_path) - - k1 = OpenPGPKey(ADDRESS, fingerprint="1", - refreshed_at=datetime(2005, 1, 1)) - k2 = OpenPGPKey(ADDRESS, fingerprint="2", - refreshed_at=datetime(2007, 1, 1)) - k3 = OpenPGPKey(ADDRESS, fingerprint="3", - refreshed_at=datetime(2007, 1, 1), - encr_used=True, sign_used=True) - k4 = OpenPGPKey(ADDRESS, fingerprint="4", - refreshed_at=datetime(2007, 1, 1), - sign_used=True) - k5 = OpenPGPKey(ADDRESS, fingerprint="5", - refreshed_at=datetime(2007, 1, 1), - encr_used=True) - k6 = OpenPGPKey(ADDRESS, fingerprint="6", - refreshed_at=datetime(2006, 1, 1), - encr_used=True, sign_used=True) - keys = (k1, k2, k3, k4, k5, k6) - for key in keys: - yield self._soledad.create_doc_from_json(key.get_json()) - yield self._soledad.create_doc_from_json(key.get_active_json()) - - delete_doc = self._mock_delete_doc() - - key = yield pgp.get_key(ADDRESS, private=False) - self._soledad.delete_doc = delete_doc - self.assertEqual(self.count, 5) - self.assertEqual(key.fingerprint, "3") - - def _assert_key_not_found(self, pgp, address, private=False): - d = pgp.get_key(address, private=private) - return self.assertFailure(d, KeyNotFound) - - @inlineCallbacks - def _insert_key_docs(self, refreshed_at): - for date in refreshed_at: - key = OpenPGPKey(ADDRESS, fingerprint=KEY_FINGERPRINT, - refreshed_at=date) - yield self._soledad.create_doc_from_json(key.get_json()) - yield self._soledad.create_doc_from_json(key.get_active_json()) - - def _mock_delete_doc(self): - delete_doc = self._soledad.delete_doc - self.count = 0 - - def my_delete_doc(*args): - self.count += 1 - return delete_doc(*args) - self._soledad.delete_doc = my_delete_doc - return delete_doc diff --git a/keymanager/src/leap/keymanager/tests/test_validation.py b/keymanager/src/leap/keymanager/tests/test_validation.py deleted file mode 100644 index 954b1df8..00000000 --- a/keymanager/src/leap/keymanager/tests/test_validation.py +++ /dev/null @@ -1,503 +0,0 @@ -# -*- coding: utf-8 -*- -# __init__.py -# Copyright (C) 2014 LEAP -# -# 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 . -""" -Tests for the Validation Levels -""" - -import unittest -from datetime import datetime -from twisted.internet.defer import inlineCallbacks - -from leap.keymanager.errors import ( - KeyNotValidUpgrade -) -from leap.keymanager.tests import ( - KeyManagerWithSoledadTestCase, - ADDRESS, - PUBLIC_KEY, - ADDRESS_2, - PUBLIC_KEY_2, - PRIVATE_KEY_2, - KEY_FINGERPRINT -) -from leap.keymanager.validation import ValidationLevels - - -class ValidationLevelsTestCase(KeyManagerWithSoledadTestCase): - - @inlineCallbacks - def test_none_old_key(self): - km = self._key_manager() - yield km.put_raw_key(PUBLIC_KEY, ADDRESS) - key = yield km.get_key(ADDRESS, fetch_remote=False) - self.assertEqual(key.fingerprint, KEY_FINGERPRINT) - - @inlineCallbacks - def test_cant_upgrade(self): - km = self._key_manager() - yield km.put_raw_key(PUBLIC_KEY, ADDRESS, - validation=ValidationLevels.Provider_Trust) - d = km.put_raw_key(UNRELATED_KEY, ADDRESS) - yield self.assertFailure(d, KeyNotValidUpgrade) - - @inlineCallbacks - def test_fingerprint_level(self): - km = self._key_manager() - yield km.put_raw_key(PUBLIC_KEY, ADDRESS) - yield km.put_raw_key(UNRELATED_KEY, ADDRESS, - validation=ValidationLevels.Fingerprint) - key = yield km.get_key(ADDRESS, fetch_remote=False) - self.assertEqual(key.fingerprint, UNRELATED_FINGERPRINT) - - @inlineCallbacks - def test_expired_key(self): - km = self._key_manager() - yield km.put_raw_key(EXPIRED_KEY, ADDRESS) - yield km.put_raw_key(UNRELATED_KEY, ADDRESS) - key = yield km.get_key(ADDRESS, fetch_remote=False) - self.assertEqual(key.fingerprint, UNRELATED_FINGERPRINT) - - @inlineCallbacks - def test_expired_fail_lower_level(self): - km = self._key_manager() - yield km.put_raw_key( - EXPIRED_KEY, ADDRESS, - validation=ValidationLevels.Third_Party_Endorsement) - d = km.put_raw_key( - UNRELATED_KEY, ADDRESS, - validation=ValidationLevels.Provider_Trust) - yield self.assertFailure(d, KeyNotValidUpgrade) - - @inlineCallbacks - def test_roll_back(self): - km = self._key_manager() - yield km.put_raw_key(EXPIRED_KEY_UPDATED, ADDRESS) - yield km.put_raw_key(EXPIRED_KEY, ADDRESS) - key = yield km.get_key(ADDRESS, fetch_remote=False) - self.assertEqual(key.expiry_date, EXPIRED_KEY_NEW_EXPIRY_DATE) - - @inlineCallbacks - def test_not_used(self): - km = self._key_manager() - yield km.put_raw_key(UNEXPIRED_KEY, ADDRESS, - validation=ValidationLevels.Provider_Trust) - yield km.put_raw_key(UNRELATED_KEY, ADDRESS, - validation=ValidationLevels.Provider_Endorsement) - key = yield km.get_key(ADDRESS, fetch_remote=False) - self.assertEqual(key.fingerprint, UNRELATED_FINGERPRINT) - - @inlineCallbacks - def test_used_with_verify(self): - TEXT = "some text" - - km = self._key_manager() - yield km.put_raw_key(UNEXPIRED_PRIVATE, ADDRESS) - signature = yield km.sign(TEXT, ADDRESS) - yield self.delete_all_keys(km) - - yield km.put_raw_key(UNEXPIRED_KEY, ADDRESS) - yield km.encrypt(TEXT, ADDRESS) - yield km.verify(TEXT, ADDRESS, detached_sig=signature) - - d = km.put_raw_key( - UNRELATED_KEY, ADDRESS, - validation=ValidationLevels.Provider_Endorsement) - yield self.assertFailure(d, KeyNotValidUpgrade) - - @inlineCallbacks - def test_used_with_decrypt(self): - TEXT = "some text" - - km = self._key_manager() - yield km.put_raw_key(UNEXPIRED_PRIVATE, ADDRESS) - yield km.put_raw_key(PUBLIC_KEY_2, ADDRESS_2) - encrypted = yield km.encrypt(TEXT, ADDRESS_2, sign=ADDRESS) - yield self.delete_all_keys(km) - - yield km.put_raw_key(UNEXPIRED_KEY, ADDRESS) - yield km.put_raw_key(PRIVATE_KEY_2, ADDRESS_2) - yield km.encrypt(TEXT, ADDRESS) - yield km.decrypt(encrypted, ADDRESS_2, verify=ADDRESS) - - d = km.put_raw_key( - UNRELATED_KEY, ADDRESS, - validation=ValidationLevels.Provider_Endorsement) - yield self.assertFailure(d, KeyNotValidUpgrade) - - @inlineCallbacks - def test_signed_key(self): - km = self._key_manager() - yield km.put_raw_key(PUBLIC_KEY, ADDRESS) - yield km.put_raw_key(SIGNED_KEY, ADDRESS) - key = yield km.get_key(ADDRESS, fetch_remote=False) - self.assertEqual(key.fingerprint, SIGNED_FINGERPRINT) - - @inlineCallbacks - def test_two_uuids(self): - TEXT = "some text" - - km = self._key_manager() - yield km.put_raw_key(UUIDS_PRIVATE, ADDRESS_2) - signature = yield km.sign(TEXT, ADDRESS_2) - yield self.delete_all_keys(km) - - yield km.put_raw_key(UUIDS_KEY, ADDRESS_2) - yield km.put_raw_key(UUIDS_KEY, ADDRESS) - yield km.encrypt(TEXT, ADDRESS_2) - yield km.verify(TEXT, ADDRESS_2, detached_sig=signature) - - d = km.put_raw_key( - PUBLIC_KEY_2, ADDRESS_2, - validation=ValidationLevels.Provider_Endorsement) - yield self.assertFailure(d, KeyNotValidUpgrade) - key = yield km.get_key(ADDRESS_2, fetch_remote=False) - self.assertEqual(key.fingerprint, UUIDS_FINGERPRINT) - - yield km.put_raw_key( - PUBLIC_KEY, ADDRESS, - validation=ValidationLevels.Provider_Endorsement) - key = yield km.get_key(ADDRESS, fetch_remote=False) - self.assertEqual(key.fingerprint, KEY_FINGERPRINT) - - -# Key material for testing - -# key 901FBCA5: public key "Leap Test Key " -UNRELATED_FINGERPRINT = "ABCCD9C8270B6A8D5633FAC9D04DB2E4901FBCA5" -UNRELATED_KEY = """ ------BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1 - -mQENBFQ9VDoBCACbKflcEhUXZULOT4Fwc2ifRUllJpusd2uX5oeDlZdZ15uLY2eF -LcxnAdIWkI/PsXimh0ev/Pf4oCynfmt02I3c2d9F0N6JXWnRiP+p098oPOcqeEqL -N3CrkH1RVnEXNeJ/Fu7tkD61SBXl1MytMfcHyhN5arg8OcVAjcmghX53+92jFhC9 -8ss87H/qEe5vEX/ahP3tiL5ULvaS4GIX+XB0O3yCVdRoRG9lqMIBP/ZqCkKrNll8 -dT12a6ByG/rWharZUeUETiM4Y+JjDUUaEC2YhNF9k52JNGanLH9LTTtlKy5WTT+E -C6T6VMAtkwcBDpkXr5sBB/N+Y1z0Fp359lIXABEBAAG0HExlYXAgVGVzdCBLZXkg -PGxlYXBAbGVhcC5zZT6JATgEEwECACIFAlQ9VDoCGwMGCwkIBwMCBhUIAgkKCwQW -AgMBAh4BAheAAAoJENBNsuSQH7ylsSUIAIxUFbkeTdHbCF/LVA2U+ktnR1iVikAY -vFK+U+Bto11/AO4Kew2eWniDch/sqLQOoSydtP42z2z3/Al3u7LhQ8bElQHPDY78 -t49qweyJi00V3vCKCdWwPJnPM5eJOIrZHCbwIgeXCsXxVNJVyziVqMuum+px1h2d -1YJZXYejT8rzwa3yBPAsGWRAWETeTvUuyjPMFa59scbnaDuY+bwQ2r/qG9m7UyHU -h2kAHC5sf1rixVOY6rLhw75gQHE/L2BZJRfVsDQqIpEMh2OgMfNbL928jncjwQvc -/IXXwSUx7y50ll+uNh+TVLf0MlUjKdHmHqnGBMlIIWojWJuKxYmOOoO5AQ0EVD1U -OgEIAM/TlhWVSI+tl5XBUAcf60RxjpHQkmdfq1i1jgwUgu/638EKzBfLcnRYX8Rn -DO9CWnHcql/4hp226fIWZN/SyReE81n7UkLDMAglhHgiezHMSH1GYVu4IlfpLVXn -brLVo83KioH5MPFWmZv5tigpU/G8dTx9yVGv1//YW2qqRYYqeIKJfapBaY/bNqyD -vYRfZo1K2brtHx4bToY6mALRF4ruV5SVZGS69e4Sh692C2pXSVbCpRhQ/2WnvkZH -leFIdmNmQN61MC1k26A620Rm+pAsXX71dln0u96xbrCgEVbi6ccfXzbFKtVmThVB -w11CLvVTviOm99TmcgpmDS4cf08AEQEAAYkBHwQYAQIACQUCVD1UOgIbDAAKCRDQ -TbLkkB+8pR+fB/0SeTcRr1duN7VYWdtng1+jO0ornIBtUraglN01dEEmiwN83DTi -J37i+nll+4is7BtiXqhumRptKh1v8UUMyFX/rjjoojCJBg5NExsiOYl3O4le68oF -3+XC+n7yrlyNmI15+3dcQmC9F6HN8EBZgrn5YPKGIOMHTGatB5PryMKg2IKiN5GZ -E0hmrOQgmcGrkeqysKACQYUHTasSk2IY1l1G5YQglqCaBh4+UC82Dmg5fTBbHjxP -YhhojkP4aD/0YW7dgql3nzYqvPCAjBH1Cf6rA9HvAJwUP9Ig/okcrrPEKm638+mG -+vNIuLqIkA4oFLBAAIrgMiQZ+NZz9uD6DJE7 -=FO7G ------END PGP PUBLIC KEY BLOCK----- -""" - -# key A1885A7C: public key "Leap Test Key " -EXPIRED_FINGERPRINT = "7C1F68B0E14157B09B5F4ADE6F15F004A1885A7C" -EXPIRED_KEY = """ ------BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1.4.12 (GNU/Linux) - -mQENBBvrfd0BCADGNpspaNhsbhSjKioCWrE2MTTYC+Sdpes22RabdhQyOCWvlSbj -b8p0y3kmnMOtVBT+c22/w7eu2YBfIpS4RswgE5ypr/1kZLFQueVe/cp29GjPvLwJ -82A3EOHcmXs8rSJ76h2bnkySvbJawz9rwCcaXhpdAwC+sjWvbqiwZYEL+90I4Xp3 -acDh9vNtPxDCg5RdI0bfdIEBGgHTfsda3kWGvo1wH5SgrTRq0+EcTI7aJgkMmM/A -IhnpACE52NvGdG9eB3x7xyQFsQqK8F0XvEev2UJH4SR7vb+Z7FNTJKCy6likYbSV -wGGFuowFSESnzXuUI6PcjyuO6FUbMgeM5euFABEBAAG0HExlYXAgVGVzdCBLZXkg -PGxlYXBAbGVhcC5zZT6JAT4EEwECACgFAhvrfd0CGwMFCQABUYAGCwkIBwMCBhUI -AgkKCwQWAgMBAh4BAheAAAoJEG8V8AShiFp8VNkH/iCQcXkTfMOVlL2rQRyZtJEO -Lr5uTyyY8O6ubeNCHqZzlIopiPAsv4hIYjjMDvOfZ9R53YgmbacUm0rvh1B4MSUf -k+sa9/tequ3y44LUKp7AB6NyyLgVOU5ngl2w+bi7CgXAep3oP4joYKcU0mmSAc2S -2Gj85DVqP0kdzNs47esvyj7g1TOfdBwmLsTx/219H+w3dNBeyCQWkYCYNh7MX/Ba -SZ+P0xr4FetcOVPM3wAzUtDG7hKsgccoIXt0FWhG/nn8cETfGH+o3W/ky7Jktatx -DGDHoZJvAaG2B2ey1pAQlezr8p/O+ZVABiigHk1S+myBHyhlXzUcjhQnEG7aHZ65 -AQ0EG+t93QEIAKqRq/2sBDW4g3FU+11LhixT+GosrfVvnitz3S9k2tBXok/wYpI1 -XeA+kTHiF0LaqoaciDRvkA9DvhDbSrNM1yeuYRyZiHlTmoPZ/Fkl60oA2cyLd1L5 -sXbuipY3TEiakugdSU4rzgi0hFycm6Go6yq2G6eC6UALvD9CTMdZHw40TadG9xpm -4thYPuJ1kPH8/bkbTi9sLHoApYgL+7ssje8w4epr0qD4IGxeKwJPf/tbTRpnd8w3 -leldixHHKAutNt49p0pkXlORAHRpUmp+KMZhFvCvIPwe9o5mYtMR7sDRxjY61ZEQ -KLyKoh5wsJsaPXBjdG7cf6G/cBcwvnQVUHcAEQEAAYkBJQQYAQIADwUCG+t93QIb -DAUJAAFRgAAKCRBvFfAEoYhafOPgB/9z4YCyT/N0262HtegHykhsyykuqEeNb1LV -D9INcP+RbCX/0IjFgP4DTMPP7qqF1OBwR276maALT321Gqxc5HN5YrwxGdmoyBLm -unaQJJlD+7B1C+jnO6r4m44obvJ/NMERxVyzkXap3J2VgRIO1wNLI9I0sH6Kj5/j -Mgy06OwXDcqIc+jB4sIJ3Tnm8LZ3phJzNEm9mI8Ak0oJ7IEcMndR6DzmRt1rJQcq -K/D7hOG02zvyRhxF27U1qR1MxeU/gNnOx8q4dnVyWB+EiV1sFl4iTOyYHEsoyd7W -Osuse7+NkyUHgMXMVW7cz+nU7iO+ht2rkBtv+Z5LGlzgHTeFjKci -=WhX+ ------END PGP PUBLIC KEY BLOCK----- -""" -# updated expiration date -EXPIRED_KEY_NEW_EXPIRY_DATE = datetime.fromtimestamp(2049717872) -EXPIRED_KEY_UPDATED = """ ------BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1.4.12 (GNU/Linux) - -mQENBBvrfd0BCADGNpspaNhsbhSjKioCWrE2MTTYC+Sdpes22RabdhQyOCWvlSbj -b8p0y3kmnMOtVBT+c22/w7eu2YBfIpS4RswgE5ypr/1kZLFQueVe/cp29GjPvLwJ -82A3EOHcmXs8rSJ76h2bnkySvbJawz9rwCcaXhpdAwC+sjWvbqiwZYEL+90I4Xp3 -acDh9vNtPxDCg5RdI0bfdIEBGgHTfsda3kWGvo1wH5SgrTRq0+EcTI7aJgkMmM/A -IhnpACE52NvGdG9eB3x7xyQFsQqK8F0XvEev2UJH4SR7vb+Z7FNTJKCy6likYbSV -wGGFuowFSESnzXuUI6PcjyuO6FUbMgeM5euFABEBAAG0HExlYXAgVGVzdCBLZXkg -PGxlYXBAbGVhcC5zZT6JAT4EEwECACgCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4B -AheABQJUlDCSBQleQLiTAAoJEG8V8AShiFp8t3QH/1eqkVIScXmqaCVeno3VSKiH -HqnxiHcEgtpNRfUlP6tLD4H6QPEpvoUI9S/8HSYi3nbDGXEX8ycKlnwxjdIqWSOW -xj91/7uQAo+dP9QaVJ6xgaAiqzN1x3JzX3Js1wTodmNV0TfmGjxwnC5up/xK7/pd -KuDP3woDsRlwy8Lgj67mkn49xfAFHo6hI6SD36UBDAC/ELq6kZaba4Kk0fEVHCEz -HX0B09ZIY9fmf305cEB3dNh6SMQgKtH0wKozaqI2UM2B+cs3z08bC+YuUUh7UJTH -yr+hI7vF4/WEeJB3fuhP3xsumLhV8P47DaJ7oivmtsDEbAJFKqvigEqNES73Xpy5 -AQ0EG+t93QEIAKqRq/2sBDW4g3FU+11LhixT+GosrfVvnitz3S9k2tBXok/wYpI1 -XeA+kTHiF0LaqoaciDRvkA9DvhDbSrNM1yeuYRyZiHlTmoPZ/Fkl60oA2cyLd1L5 -sXbuipY3TEiakugdSU4rzgi0hFycm6Go6yq2G6eC6UALvD9CTMdZHw40TadG9xpm -4thYPuJ1kPH8/bkbTi9sLHoApYgL+7ssje8w4epr0qD4IGxeKwJPf/tbTRpnd8w3 -leldixHHKAutNt49p0pkXlORAHRpUmp+KMZhFvCvIPwe9o5mYtMR7sDRxjY61ZEQ -KLyKoh5wsJsaPXBjdG7cf6G/cBcwvnQVUHcAEQEAAYkBJQQYAQIADwIbDAUCVJQw -3QUJXkC4/QAKCRBvFfAEoYhafEtiB/9hMfSFNMxtlIJDJArG4JwR7sBOatYUT858 -qZnTgGETZN8wXpeEpXWKdDdmCX9aeE9jsDNgSQ5WWpqU21bGMXh1IGjAzmqTqq3/ -ik1vALuaVfr6OqjTzrJVQujT61CGed26xpP3Zh8hLKyKa+dXnX/VpgZS42wZLPx2 -wcODfANmTfE2AhMap/RyDy21q4nau+z2hMEOKdtF8dpP+pEvzoN5ZexYP1hfT+Av -oFPyVB5YtEMfxTEshDKRPjbdgNmw4faKXd5Cbelo4YxxpO16FHb6gzIdjOX15vQ+ -KwcVXzg9xk4D3cr1mnTCops/iv6TXvcw4Wbo70rrKXwkjl8LKjOP -=sHoe ------END PGP PUBLIC KEY BLOCK----- -""" -UNEXPIRED_KEY = EXPIRED_KEY_UPDATED -UNEXPIRED_PRIVATE = """ ------BEGIN PGP PRIVATE KEY BLOCK----- -Version: GnuPG v1.4.12 (GNU/Linux) - -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 -=dZE8 ------END PGP PRIVATE KEY BLOCK----- -""" - -# key CA1AD31E: public key "Leap Test Key " -# signed by E36E738D69173C13D709E44F2F455E2824D18DDF -SIGNED_FINGERPRINT = "6704CF3362087DA23E3D2DF8ED81DFD1CA1AD31E" -SIGNED_KEY = """ ------BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1.4.12 (GNU/Linux) - -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 -=xprj ------END PGP PUBLIC KEY BLOCK----- -""" - -# key 0x1DDBAEB928D982F7: public key two uuids -# uid anotheruser -# uid Leap Test Key -UUIDS_FINGERPRINT = "21690ED054C1B2F3ACE963D38FCC7DEFB4EE5A9B" -UUIDS_KEY = """ ------BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1 - -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 -vtQLNMD9Gn7D -=MCXv ------END PGP PUBLIC KEY BLOCK----- -""" -UUIDS_PRIVATE = """ ------BEGIN PGP PRIVATE KEY BLOCK----- -Version: GnuPG v1 - -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 -eNWbiM9EvtQLNMD9Gn7D -=/3u/ ------END PGP PRIVATE KEY BLOCK----- -""" - -if __name__ == "__main__": - unittest.main() -- cgit v1.2.3 From 47b57872c2ccd94f6f4900a3bd1e7a3dab9a933a Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 28 Jul 2016 11:17:29 -0300 Subject: [pkg] remove simplejson dep --- keymanager/src/leap/keymanager/keys.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'keymanager/src/leap') diff --git a/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py index c0ee21b1..91ecf3ac 100644 --- a/keymanager/src/leap/keymanager/keys.py +++ b/keymanager/src/leap/keymanager/keys.py @@ -19,10 +19,7 @@ Abstact key type and encryption scheme representations. """ -try: - import simplejson as json -except ImportError: - import json # noqa +import json import logging import re import time -- cgit v1.2.3