summaryrefslogtreecommitdiffstats
path: root/keystone/contrib/kds/core.py
diff options
context:
space:
mode:
Diffstat (limited to 'keystone/contrib/kds/core.py')
-rw-r--r--keystone/contrib/kds/core.py129
1 files changed, 109 insertions, 20 deletions
diff --git a/keystone/contrib/kds/core.py b/keystone/contrib/kds/core.py
index d707482d..53941f80 100644
--- a/keystone/contrib/kds/core.py
+++ b/keystone/contrib/kds/core.py
@@ -47,6 +47,7 @@ CONF.register_opts(kds_opts, group='kds')
LOG = logging.getLogger(__name__)
KEY_SIZE = 16
+GROUP_KEY_TTL = 900 # 15 minutes
@dependency.provider('kds_api')
@@ -134,18 +135,52 @@ class Manager(manager.Manager):
return plain
- def _get_key(self, key_id):
+ def _gen_group_key(self, key_id):
+ """Generates a new group key."""
+ exp = time.time() + GROUP_KEY_TTL
+
+ keyblock = self.crypto.new_key(KEY_SIZE)
+ sig, enc = self._encrypt_keyblock(keyblock)
+
+ gen = self.driver.set_group_key(key_id, exp, sig + enc)
+
+ return keyblock, gen
+
+ def _get_key(self, key_id, gen=0):
"""Decrypts the provided encoded key and returns
the clear text key.
:param key_id: Key Identifier
"""
# Get Requestor's encypted key
- key = self.driver.get_shared_key(key_id)
- if not key:
- raise exception.Unauthorized('Invalid Requestor')
+ k = self.driver.get_shared_key(key_id)
+ if not k:
+ raise exception.Forbidden('Invalid Key ID')
+ if 'key_v1' in k:
+ if gen != 0:
+ raise exception.Forbidden('Invalid Key Type')
+ key = base64.b64decode(k['key_v1'])
+ elif 'group_keys' in k:
+ g = k['group_keys']
+ # find a specific generation or throw back an error
+ if gen != 0:
+ key = base64.b64decode(g[gen]['key_v1'])
+
+ # find one not expired and with at least 10 more
+ # minutes lifetime, otherwise build a new generation
+ else:
+ t = time.time()
+ last_gen = k['generation']
+ if last_gen in g and g[last_gen]['expiration'] > t:
+ key = base64.b64decode(g[last_gen]['key_v1'])
+ else:
+ return self._gen_group_key(key_id)
+ else:
+ raise exception.UnexpectedError('Unknown key format')
- return self._decrypt_keyblock(key_id, key)
+ plain = self._decrypt_keyblock(key_id, key)
+
+ return plain, gen
def _set_key(self, key_id, keyblock):
"""Encrypts the provided key and stores it.
@@ -155,18 +190,24 @@ class Manager(manager.Manager):
sig, enc = self._encrypt_keyblock(key_id, keyblock)
self.driver.set_shared_key(key_id, sig + enc)
- def get_ticket(self, sig, req):
- if not ('metadata' in req):
+ def _common_request(self, sig, req):
+ if 'metadata' not in req:
raise exception.Forbidden('Invalid Request format')
try:
meta = jsonutils.loads(base64.b64decode(req['metadata']))
except Exception:
raise exception.Forbidden('Invalid Request format')
- if not ('requestor' in meta):
+ if 'requestor' not in meta:
raise exception.Forbidden('Invalid Request format')
- rkey = self._get_key(meta['requestor'])
+ # Requests can be made only by non-group identities
+ try:
+ rkey, gen = self._get_key(meta['requestor'], 0)
+ except exception.UnexpectedError:
+ raise exception.Unauthorized('Invalid Request')
+ if gen != 0:
+ raise exception.Unauthorized('Invalid Request')
try:
signature = self.crypto.sign(rkey, req['metadata'])
@@ -176,21 +217,33 @@ class Manager(manager.Manager):
if signature != sig:
raise exception.Unauthorized('Invalid Request')
+ #TODO(simo): check and store signature for replay attack detection
+
timestamp = time.time()
if meta['timestamp'] < (timestamp - self.ttl):
raise exception.Unauthorized('Invalid Request (expired)')
- #TODO(simo): check and store signature for replay attack detection
+ return meta, rkey, timestamp
+
+ def get_ticket(self, sig, req):
- tkey = self._get_key(meta['target'])
+ meta, rkey, timestamp = self._common_request(sig, req)
+
+ source = meta['requestor']
+ target = meta['target']
+
+ tkey, gen = self._get_key(target)
if not tkey:
- raise exception.Unauthorized('Invalid Target')
+ raise exception.Unauthorized('Invalid Destination')
+
+ if gen != 0:
+ target += ':' + str(gen)
# use new_key to get a random salt
rndkey = self.hkdf.extract(rkey, self.crypto.new_key(KEY_SIZE))
- info = '%s,%s,%s' % (meta['requestor'], meta['target'], str(timestamp))
+ info = '%s,%s,%s' % (source, target, str(timestamp))
sek = self.hkdf.expand(rndkey, info, KEY_SIZE * 2)
skey, ekey = self._split_key(sek, KEY_SIZE)
@@ -202,21 +255,53 @@ class Manager(manager.Manager):
'esek': esek})
rep = dict()
- metadata = jsonutils.dumps({'source': meta['requestor'],
- 'destination': meta['target'],
- 'expiration': (keydata['timestamp']
- + keydata['ttl']),
- 'encryption': True})
+ metadata = jsonutils.dumps({'source': source, 'destination': target,
+ 'expiration': (timestamp + self.ttl)})
rep['metadata'] = base64.b64encode(metadata)
rep['ticket'] = self.crypto.encrypt(rkey, ticket)
- rep['signature'] = self.crypto.sign(rkey,
- (rep['metadata'] + rep['ticket']))
+
+ payload = rep['metadata'] + rep['ticket']
+ rep['signature'] = self.crypto.sign(rkey, payload)
+
+ return {'reply': rep}
+
+ def get_group_key(self, sig, req):
+
+ meta, rkey, timestamp = self._common_request(sig, req)
+
+ source = meta['requestor']
+ target = meta['target']
+
+ group = source.split('.')[0]
+
+ if ':' not in target:
+ raise exception.Unauthorized('Invalid Target')
+ grp, generation = target.split(':', 1)
+
+ if group != grp:
+ raise exception.Unauthorized('Invalid Target')
+
+ tkey, gen = self._get_key(group, generation)
+ if not tkey:
+ raise exception.Unauthorized('Invalid Target')
+
+ rep = dict()
+ metadata = jsonutils.dumps({'source': source, 'destination': target,
+ 'expiration': (timestamp + self.ttl)})
+ rep['metadata'] = base64.b64encode(metadata)
+ rep['group_key'] = self.crypto.encrypt(rkey, tkey)
+
+ payload = rep['metadata'] + rep['group_key']
+ rep['signature'] = self.crypto.sign(rkey, payload)
return {'reply': rep}
def set_key(self, name, req):
self._set_key(name, base64.b64decode(req['key']))
+ def create_group(self, name):
+ self.driver.set_group_key(name)
+
class Driver(object):
"""Interface description for a KDS driver."""
@@ -225,6 +310,10 @@ class Driver(object):
"""Set key related to kds_id."""
raise exception.NotImplemented()
+ def set_group_key(self, kds_id, expiration=None, key=None):
+ """Set or Create group key object."""
+ raise exception.NotImplemented()
+
def get_shared_key(self, kds_id):
"""Get key related to kds_id.