summaryrefslogtreecommitdiff
path: root/src/leap/keymanager/keys.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/leap/keymanager/keys.py')
-rw-r--r--src/leap/keymanager/keys.py227
1 files changed, 167 insertions, 60 deletions
diff --git a/src/leap/keymanager/keys.py b/src/leap/keymanager/keys.py
index ec1bfeb..91559c2 100644
--- a/src/leap/keymanager/keys.py
+++ b/src/leap/keymanager/keys.py
@@ -25,11 +25,19 @@ try:
import simplejson as json
except ImportError:
import json # noqa
+import logging
import re
+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 ValidationLevels
+
+logger = logging.getLogger(__name__)
#
@@ -44,9 +52,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'
@@ -55,6 +65,8 @@ KEY_TAGS_KEY = 'tags'
#
KEYMANAGER_KEY_TAG = 'keymanager-key'
+KEYMANAGER_ACTIVE_TAG = 'keymanager-active'
+KEYMANAGER_ACTIVE_TYPE = '-active'
#
@@ -62,14 +74,20 @@ KEYMANAGER_KEY_TAG = 'keymanager-key'
#
TAGS_PRIVATE_INDEX = 'by-tags-private'
-TAGS_ADDRESS_PRIVATE_INDEX = 'by-tags-address-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,
],
- TAGS_ADDRESS_PRIVATE_INDEX: [
- KEY_TAGS_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,
'bool(%s)' % KEY_PRIVATE_KEY,
]
@@ -92,34 +110,56 @@ 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 == kdict[KEY_ADDRESS_KEY],
- 'Wrong address in key data.')
+ 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])
+
return kClass(
- address,
+ 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],
- 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.
+ 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],
)
+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
#
@@ -129,23 +169,15 @@ 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
- 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=ValidationLevels.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
@@ -154,8 +186,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):
"""
@@ -164,21 +198,44 @@ class EncryptionKey(object):
:return: The JSON string describing this key.
: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({
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,
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_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],
})
+ 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
@@ -221,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):
@@ -247,19 +344,25 @@ 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
@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
+
+ :return: A Deferred which fires when the key is in the storage.
+ :rtype: Deferred
"""
pass
@@ -283,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
@@ -315,11 +423,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
@@ -353,7 +460,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