## ************** ## Copyright 2014 MIT Lincoln Laboratory ## Project: PACE ## Authors: ATLH ## Description: Class for encryption key management ## Modifications: ## Date Name Modification ## ---- ---- ------------ ## 10 Dec 2014 ATLH Original file ## ************** import os import sys this_dir = os.path.dirname(os.path.dirname(__file__)) base_dir = os.path.join(this_dir, '../..') sys.path.append(base_dir) from Crypto.PublicKey import RSA from base64 import b64encode from beaker.cache import CacheManager from beaker.util import parse_cache_config_options from pace.pki.keystore import KeyInfo from pace.pki.abstractpki import PKILookupError from pace.pki.accumulo_keystore import AccumuloKeyStore from pace.common.fakeconn import FakeConnection import pace.pki.key_wrap_utils as key_utils from pace.encryption.enc_classes import AES_ALGORITHMS, VIS_ALGORITHMS #creating cache where all the contents are stored within local memory cache_opts = { 'cache.type': 'memory', } cache = CacheManager(**parse_cache_config_options(cache_opts)) ### NB: For functions decorated with: ### ### @cache.cache('key',expire=3600) ### ### ### Objects are cached for a paritcular set of arguments ### when a function call is made - in this particular example ### it will be for an hour. Thus when a call is made again, ### with the same set of arguments, the cached value is ### returned in lieu of another execution of the method. ### ### NB: There exists one copy of the cache for each class. This means ### that two instances of the cache, that may have different ### instance variables, will use the same copy of the cache. In ### this particular case, two copies of the PKI object that refer ### to different users on the same system or users who have more ### than one key store to connect to cannot be supported without ### further modification. For flexibility, we include both a plain key ### store and a cached subclass of it. class EncryptionPKIBase(object): def __init__(self, *args): pass def get_key(self, algorithm): """ Arguments: algorithm - (string) Name of algorithm for which the user wishes to retrieve the current key, triggers PKILookupError if the algorithm is not part of AES_ALGORITHMS Returns: Current key for (userid, algorithm) tuple Raises: PKILookupError if the key cannot be found for the (userid, algorithm) pairing. """ pass def get_current_key(self, algorithm): """ Arguments: algorithm - (string) Name of algorithm for which the user wishes to retrieve the key, triggers PKILookupError if the algorithm is not part of AES_ALGORITHMS Returns: (Most current key for (userid, algorithm) tuple, version) Raises: PKILookupError if the key cannot be found for the (userid, algorithm) tuple. """ pass def get_attribute_key(self, algorithm, attribute): """ Arguments: algorithm - (string) Name of algorithm for which the user wishes to retrieve the current key, triggers PKILookupError if the algorithm is, not part of VIS_ALGORITHMS attribute - Attribute for which to rereive the current key for, currently does not support looking for versions. Returns: Current key for (userid, algorithm, attribute) triple Raises: PKILookupError if the key cannot be found for the (userid, algorithm, attribute) tuple. """ pass def get_current_attribute_key(self, algorithm, attribute): """ Arguments: algorithm - (string) Name of algorithm for which the user wishes to retrieve the key, triggers PKILookupError if the algorithm is not part of AES_ALGORITHMS attribute - Attribute for which to retrieve the key for Returns: (Most current key for (userid, algorithm, attribute) tuple, version) Raises: PKILookupError if the key cannot be found for the (userid, algorithm, attribute) tuple. """ pass class EncryptionPKIAccumulo(EncryptionPKIBase): """ PKI object for encryption that uses an AccumuloKeyStore as a backend. Matches the EncryptionPKIBase interface. Instance variables: _acc_keystore - a handle on an instance of an Accumulo Keystore to retrieve keys from _user_id - the string representing the user's identity _rsa_key - RSA key object produced by RSA.generate(). """ def __init__(self, conn, user_id, rsa_key): """ Arguments: conn - (Accumulo connection) Connection to the Accumulo instance that is being used as the Keystore. NB: This represents the authentication for the user to be accessing the keystore - including what keys they should be able to access based on visability labels. user_id - (string) String that is used in the Keystore to identify the particular user rsa_key - (Crypto.PublicKey.RSA) Key object to wrap/unwrap keys with. TODO: create a loader from a file for RSA keys """ self._acc_keystore = AccumuloKeyStore(conn) self._user_id = user_id self._rsa_key = rsa_key def get_current_key(self, algorithm): """ Arguments: algorithm - (string) Name of algorithm for which the user wishes to retrieve the key for, triggers PKILookupError if the algorithm is not part of AES_ALGORITHMS Returns: (Most current key for (userid, algorithm) tuple, version) Raises: PKILookupError if the key cannot be found for the (userid, algorithm) tuple. """ #get the keys try: key_wrap = self._acc_keystore.retrieve_latest_version(self._user_id, algorithm, '') except PKILookupError: raise PKILookupError("User " + self._user_id + " with algorithm " +\ algorithm + " is not present in keystore.") #unwrap the key return (key_utils.unwrap_key(key_wrap.keywrap, self._rsa_key), key_wrap.vers) def get_current_attribute_key(self, algorithm, attribute): """ Arguments: algorithm - (string) Name of algorithm for which the user wishes to retrieve the key for, triggers PKILookupError if the algorithm is not part of AES_ALGORITHMS attribute - (string) Attribute for which to retrieve the key for Returns: (Most current key for (userid, algorithm, attribute) tuple, version) Raises: PKILookupError if the key cannot be found for the (userid, algorithm, attribute) tuple. """ #get the keys try: key_wrap = self._acc_keystore.retrieve_latest_version(self._user_id, algorithm, attribute) except PKILookupError as ple: raise PKILookupError( "User %s with algorithm %s and attribute %s is not present in keystore." %(self._user_id, algorithm, attribute)) #unwrap the key return (key_utils.unwrap_key(key_wrap.keywrap, self._rsa_key), key_wrap.vers) def get_key(self, algorithm, version=1): """ Arguments: algorithm - (string) Name of algorithm for which the user wishes to retrieve the key for, triggers PKILookupError if the algorithm is not part of AES_ALGORITHMS version - (int) version of key to grab, defaults to 1 Returns: Key for (userid, algorithm, version) tuple Raises: PKILookupError if the key cannot be found for the (userid, algorithm, version) tuple. """ #get the keys try: key_wrap = self._acc_keystore.retrieve(self._user_id, '', version, algorithm) except PKILookupError: raise PKILookupError("User " + self._user_id + " with algorithm " +\ algorithm + " is not present in keystore.") #unwrap the key return key_utils.unwrap_key(key_wrap, self._rsa_key) def get_attribute_key(self, algorithm, attribute, version=1): """ Arguments: algorithm - (string) Name of algorithm for which the user wishes to retrieve the current key for, triggers PKILookupError if the algorithm is not part of VIS_ALGORITHMS attribute - (string) Attribute for which to retrieve the key for version - (int)version of key to grab, defaults to 1 Returns: Key for (userid, algorithm, attribute, version) tuple Raises: PKILookupError if the key cannot be found for the (userid, algorithm, attribute, version) tuple. """ #get the keys try: key_wrap = self._acc_keystore.retrieve(self._user_id, attribute, version, algorithm) except PKILookupError as ple: raise PKILookupError( "User %s with algorithm %s and attribute %s is not present in keystore." %(self._user_id, algorithm, attribute)) #unwrap the key return key_utils.unwrap_key(key_wrap, self._rsa_key) class CachingEncryptionPKIMixin(object): """ Mixin class that implements caching the results for an hour """ @cache.cache('key',expire=3600) def get_current_key(self, algorithm): return super(CachingEncryptionPKIMixin, self).get_current_key(algorithm) @cache.cache('key',expire=3600) def get_current_attribute_key(self, algorithm, attribute): return super(CachingEncryptionPKIMixin, self).get_current_attribute_key(algorithm, attribute) @cache.cache('key',expire=3600) def get_key(self, algorithm, version=1): return super(CachingEncryptionPKIMixin, self).get_key(algorithm, version) @cache.cache('key',expire=3600) def get_attribute_key(self, algorithm, attribute, version=1): return super(CachingEncryptionPKIMixin, self).get_attribute_key(algorithm, attribute, version) class CachingEncryptionPKIAccumulo(CachingEncryptionPKIMixin, EncryptionPKIAccumulo): """ Same as EncryptionPKIAccumulo, but caches results for an hour """ pass class DummyEncryptionPKI(EncryptionPKIAccumulo): """ Sample PKI object for encryption that uses an AccumuloKeyStore as a backend. Contains hard-code key values. Used primarily for testing. Matches the EncryptionPKIAcummulo interface. """ def __init__(self, conn=None, terms=None): """ Arguments: conn - connection to AccumuloKeyStore. Can be connection to a live Accumulo instance. Defaults to a FakeConnection() terms - (list) attributes to insert keys for - can be 'a'-'e'. Allows for the creation of a 'limited' PKI for demos and testing. Defaults to ['a','b',c','d','e'] Note: For key names we recommend to use a combination of the algorithm being used and the specific table name, along the lines of AES_CBC__table__, not the names listed below. """ # Initialize the new FakeConnection here so Python doesn't # create a new hidden global variable for the default argument if conn is None: conn = FakeConnection() if terms is None: terms = ['a','b','c','d','e'] SYM_KEYS_TO_INSERT = {"table1": [(1,b'Sixteen by1e key')], "Pycrypto_AES_CFB": [(1,b'Sixteen by1e key'), (2,b'Sixteen by2e key'), (3,b'Sixteen by3e key')], "Pycrypto_AES_CBC": [(1,b'Sixteen bb1e key')], "Pycrypto_AES_OFB": [(1,b'Sixteen bc1e key'), (2,b'Sixteen bc2e key'), (3,b'Sixteen bc3e key')], "Pycrypto_AES_CTR": [(1,b'Sixteen bd1e key')], "Pycrypto_AES_GCM": [(1,b'Sixteen be1e key'), (2,b'Sixteen be2e key')], "Pycrypto_AES_SIV": [(1, b'Sixteen byte keySixteen byte key')]} ATTR_KEYS_TO_INSERT={ "VIS_Identity" : [('a', 1, b'Sixteen bate k1y'), ('a', 2, b'Sixteen bate k2y'), ('a', 3, b'Sixteen bate k3y'), ('b', 1, b'Sixteen bbte k1y'), ('b', 2, b'Sixteen bbte k2y'), ('c', 1, b'Sixteen bcte key'), ('d', 1, b'Sixteen bdte k1y'), ('d', 2, b'Sixteen bdte k2y'), ('d', 3, b'Sixteen bdte k3y'), ('d', 4, b'Sixteen bdte k4y'), ('e', 1, b'Sixteen bete key')], "VIS_AES_CFB" : [('a', 1, b'Sixteen bate key'), ('b', 2, b'Sixteen bbte k2y'), ('b', 3, b'Sixteen bbte k3y'), ('c', 1, b'Sixteen bcte key'), ('d', 2, b'Sixteen bdte key'), ('d', 3, b'Sixteen bdte key'), ('d', 4, b'Sixteen bdte key'), ('d', 5, b'Sixteen bdte key'), ('e', 1, b'Sixteen bete key')], "VIS_AES_CBC" : [('a', 1, b'Sixteen bate k1y'), ('a', 2, b'Sixteen bate k2y'), ('a', 3, b'Sixteen bate k3y'), ('a', 5, b'Sixteen bate k5y'), ('b', 1, b'Sixteen bbte k1y'), ('b', 2, b'Sixteen bbte k2y'), ('b', 3, b'Sixteen bbte k3y'), ('c', 3, b'Sixteen bcte k3y'), ('c', 4, b'Sixteen bcte k4y'), ('c', 5, b'Sixteen bcte k5y'), ('d', 1, b'Sixteen bdte k1y'), ('d', 2, b'Sixteen bdte k2y'), ('e', 1, b'Sixteen bete key')], "VIS_AES_OFB" : [('a', 2, b'Sixteen bate k2y'), ('a', 3, b'Sixteen bate k3y'), ('a', 4, b'Sixteen bate k4y'), ('a', 5, b'Sixteen bate k5y'), ('b', 1, b'Sixteen bbte k1y'), ('b', 2, b'Sixteen bbte k2y'), ('b', 3, b'Sixteen bbte k3y'), ('b', 4, b'Sixteen bbte k4y'), ('c', 2, b'Sixteen bcte k2y'), ('c', 3, b'Sixteen bcte k3y'), ('d', 2, b'Sixteen bdte key'), ('e', 1, b'Sixteen bete key')], "VIS_AES_CTR" : [('a', 1, b'Sixteen bate k1y'), ('a', 3, b'Sixteen bate k3y'), ('a', 4, b'Sixteen bate k4y'), ('b', 1, b'Sixteen bbte k1y'), ('b', 2, b'Sixteen bbte k3y'), ('b', 3, b'Sixteen bbte k3y'), ('c', 2, b'Sixteen bcte key'), ('d', 1, b'Sixteen bdte key'), ('e', 3, b'Sixteen bete k3y'), ('e', 5, b'Sixteen bete k5y')], "VIS_AES_GCM" : [('a', 1, b'Sixteen bate key'), ('b', 2, b'Sixteen bbte key'), ('c', 1, b'Sixteen bcte k1y'), ('c', 2, b'Sixteen bcte k2y'), ('c', 3, b'Sixteen bcte k3y'), ('d', 1, b'Sixteen bdte k1y'), ('d', 2, b'Sixteen bdte k2y'), ('d', 4, b'Sixteen bdte k4y'), ('e', 5, b'Sixteen bete key')]} #remove existing symmetric key tables for metadata in SYM_KEYS_TO_INSERT.keys(): if conn.table_exists(metadata): conn.delete_table(metadata) #remove existing attribute key tables for metadata in ATTR_KEYS_TO_INSERT.keys(): if conn.table_exists(metadata): conn.delete_table(metadata) #generate RSA key RSA_key = RSA.generate(3072) super(DummyEncryptionPKI,self).__init__(conn, 'one', RSA_key) #add symmetric keys for (algorithm, keys) in SYM_KEYS_TO_INSERT.iteritems(): for ver, key in keys: self._acc_keystore.insert(str(self._user_id), KeyInfo(attr='', vers=ver, metadata=algorithm, keywrap=key_utils.wrap_key(key, self._rsa_key), keylen=len(key))) #add attribute keys keys_to_insert = [] for (algorithm, keys) in ATTR_KEYS_TO_INSERT.iteritems(): for attr, vers, key in keys: if attr in terms: keys_to_insert.append( KeyInfo(attr=attr, vers=vers, metadata=algorithm, keywrap=key_utils.wrap_key(key, self._rsa_key), keylen=len(key))) self._acc_keystore.batch_insert(str(self._user_id), keys_to_insert) class DummyCachingEncryptionPKI(CachingEncryptionPKIMixin, DummyEncryptionPKI): """ Same as DummyEncryptionPKI, but caches results for an hour """ pass