diff options
author | Simo Sorce <simo@redhat.com> | 2015-10-16 13:58:50 -0400 |
---|---|---|
committer | Simo Sorce <simo@redhat.com> | 2015-10-23 14:11:07 -0400 |
commit | cfa5aff186fe0e321d8fe1c191834d157a5e8162 (patch) | |
tree | 54d2328b1cbb949b8e6f98b2d93336051d391b64 | |
parent | edd5cd333e2465f5b79a063243afd61e65d6d82b (diff) | |
download | custodia-cfa5aff186fe0e321d8fe1c191834d157a5e8162.tar.gz custodia-cfa5aff186fe0e321d8fe1c191834d157a5e8162.tar.xz custodia-cfa5aff186fe0e321d8fe1c191834d157a5e8162.zip |
Add a generic encrypting layer for storage
This plugin takes a nother store to use and ecnrypts all content.
note: it does not encrypt key names nor the containers
Signed-off-by: Simo Sorce <simo@redhat.com>
Reviewed-by: Christian Heimes <cheimes@redhat.com>
-rw-r--r-- | custodia.conf | 18 | ||||
-rwxr-xr-x | custodia/custodia | 1 | ||||
-rw-r--r-- | custodia/store/encgen.py | 55 | ||||
-rw-r--r-- | tests/custodia.py | 45 |
4 files changed, 119 insertions, 0 deletions
diff --git a/custodia.conf b/custodia.conf index c77cf8b..35065d1 100644 --- a/custodia.conf +++ b/custodia.conf @@ -95,3 +95,21 @@ forward_headers = {"CUSTODIA_AUTH_ID": "test", "CUSTODIA_AUTH_KEY": "foo-host-ke handler = custodia.forwarder.Forwarder forward_uri = http+unix://%2e%2fserver_socket/forwarder_loop forward_headers = {"REMOTE_USER": "test"} + +# Encgen example +[store:backing] +handler = custodia.store.sqlite.SqliteStore +dburi = examples/enclite.db +table = enclite + +[store:overlay] +handler = custodia.store.encgen.EncryptedOverlay +backing_store = backing +master_key = examples/enclite.sample.key +master_enctype = A128CBC-HS256 + +[authz:kemgen] +handler = custodia.message.kem.KEMKeysStore +path = /encgen/secrets/ +store = overlay + diff --git a/custodia/custodia b/custodia/custodia index 8d69dea..36074cc 100755 --- a/custodia/custodia +++ b/custodia/custodia @@ -102,6 +102,7 @@ def parse_config(cfgfile): attach_store('auth:', config['authenticators'], config['stores']) attach_store('authz:', config['authorizers'], config['stores']) attach_store('', config['consumers'], config['stores']) + attach_store('store:', config['stores'], config['stores']) return config diff --git a/custodia/store/encgen.py b/custodia/store/encgen.py new file mode 100644 index 0000000..671f152 --- /dev/null +++ b/custodia/store/encgen.py @@ -0,0 +1,55 @@ +# Copyright (C) 2015 Custodia Project Contributors - see LICENSE file + +from jwcrypto.common import json_decode, json_encode +from jwcrypto.jwe import JWE +from jwcrypto.jwk import JWK + +from custodia.store.interface import CSStore, CSStoreError + + +class EncryptedOverlay(CSStore): + + def __init__(self, config): + super(EncryptedOverlay, self).__init__(config) + + if 'backing_store' not in config: + raise ValueError('Missing "backing_store" for Encrypted Store') + self.store_name = config['backing_store'] + self.store = None + + if 'master_key' not in config: + raise ValueError('Missing "master_key" for Encrypted Store') + + with open(config['master_key']) as f: + data = f.read() + key = json_decode(data) + self.mkey = JWK(**key) + + self.enc = config.get('master_enctype', 'A256CBC_HS512') + + def get(self, key): + value = self.store.get(key) + if value is None: + return None + try: + jwe = JWE() + jwe.deserialize(value, self.mkey) + return jwe.payload.decode('utf-8') + except Exception as err: + self.logger.error("Error parsing key %s: [%r]" % (key, repr(err))) + raise CSStoreError('Error occurred while trying to parse key') + + def set(self, key, value, replace=False): + jwe = JWE(value, json_encode({'alg': 'dir', 'enc': self.enc})) + jwe.add_recipient(self.mkey) + cvalue = jwe.serialize(compact=True) + return self.store.set(key, cvalue, replace) + + def span(self, key): + return self.store.span(key) + + def list(self, keyfilter=''): + return self.store.list(keyfilter) + + def cut(self, key): + return self.store.cut(key) diff --git a/tests/custodia.py b/tests/custodia.py index 0817d81..cce1c40 100644 --- a/tests/custodia.py +++ b/tests/custodia.py @@ -10,6 +10,8 @@ import unittest from string import Template +from jwcrypto import jwk + from requests.exceptions import HTTPError from custodia.client import CustodiaClient @@ -69,6 +71,21 @@ forward_headers = {"CUSTODIA_AUTH_ID": "${TEST_AUTH_ID}", \ handler = custodia.forwarder.Forwarder forward_uri = ${SOCKET_URL}/forwarder_loop forward_headers = {"REMOTE_USER": "test"} + +# Encgen +[store:encgen] +handler = custodia.store.encgen.EncryptedOverlay +backing_store = simple +master_key = test_mkey.conf +master_enctype = A128CBC-HS256 + +[authz:enc] +handler = custodia.httpd.authorizers.SimplePathAuthz +paths = /enc + +[/enc] +handler = custodia.secrets.Secrets +store = encgen """ @@ -83,6 +100,12 @@ def unlink_if_exists(filename): raise +def generate_key(filename): + key = jwk.JWK(generate='oct', size=256) + with (open(filename, 'w+')) as keyfile: + keyfile.write(key.export()) + + class CustodiaTests(unittest.TestCase): @classmethod @@ -92,12 +115,14 @@ class CustodiaTests(unittest.TestCase): pexec = env.get('CUSTODIAPYTHON', 'python') unlink_if_exists('test_socket') unlink_if_exists('test_secrets.db') + unlink_if_exists('test_mkey.conf') unlink_if_exists('test_custodia.conf') unlink_if_exists('test_log.txt') unlink_if_exists('test_audit.log') cls.socket_url = TEST_SOCKET_URL cls.test_auth_id = "test_user" cls.test_auth_key = "cd54b735-e756-4f12-aa18-d85509baef36" + generate_key('test_mkey.conf') with (open('test_custodia.conf', 'w+')) as conffile: t = Template(TEST_CUSTODIA_CONF) conf = t.substitute({'SOCKET_URL': cls.socket_url, @@ -121,6 +146,8 @@ class CustodiaTests(unittest.TestCase): cls.fwd.headers['REMOTE_USER'] = 'test' cls.loop = CustodiaClient(cls.socket_url + '/forwarder_loop') cls.loop.headers['REMOTE_USER'] = 'test' + cls.enc = CustodiaClient(cls.socket_url + '/enc') + cls.enc.headers['REMOTE_USER'] = 'enc' @classmethod def tearDownClass(cls): @@ -179,3 +206,21 @@ class CustodiaTests(unittest.TestCase): self.loop.list_container('test') except HTTPError as e: self.assertEqual(e.response.status_code, 502) + + def test_A_enc_1_create_container(self): + self.enc.create_container('container') + r = self.enc.list_container('container') + self.assertEqual(r.json(), []) + self.enc.delete_container('container') + try: + self.enc.list_container('container') + except HTTPError as e: + self.assertEqual(e.response.status_code, 404) + + def test_A_enc_2_set_simple_key(self): + self.enc.create_container('enc') + self.enc.set_simple_key('enc/key', 'simple') + key = self.admin.get_simple_key('enc/key') + self.assertNotEqual(key, 'simple') + key = self.enc.get_simple_key('enc/key') + self.assertEqual(key, 'simple') |