diff --git a/barbican/cmd/pkcs11_kek_rewrap.py b/barbican/cmd/pkcs11_kek_rewrap.py new file mode 100755 index 000000000..aa7e161ee --- /dev/null +++ b/barbican/cmd/pkcs11_kek_rewrap.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import argparse +import json +import traceback + +import sqlalchemy +from sqlalchemy import orm +from sqlalchemy.orm import scoping + +from barbican.common import utils +from barbican.model import models +from barbican.plugin.crypto import p11_crypto + +# Use config values from p11_crypto +CONF = p11_crypto.CONF + + +class KekRewrap(object): + + def __init__(self, db_connection, library_path, login, slot_id, + new_mkek_label): + self.dry_run = False + self.new_label = new_mkek_label + self.db_engine = sqlalchemy.create_engine(db_connection) + self._session_creator = scoping.scoped_session( + orm.sessionmaker( + bind=self.db_engine, + autocommit=True + ) + ) + self.crypto_plugin = p11_crypto.P11CryptoPlugin(CONF) + self.pkcs11 = self.crypto_plugin.pkcs11 + self.plugin_name = utils.generate_fullname_for(self.crypto_plugin) + + def rewrap_kek(self, project, kek): + with self.db_session.begin(): + meta_dict = json.loads(kek.plugin_meta) + + if self.dry_run: + msg = 'Would have unwrapped key with {} and rewrapped with {}' + print(msg.format(meta_dict['mkek_label'], self.new_label)) + print('Would have updated KEKDatum in db {}'.format(kek.id)) + + else: + hsm_session = self.pkcs11.create_working_session() + + print('Rewrapping KEK {}'.format(kek.id)) + print('Pre-change IV: {}, Wrapped Key: {}'.format( + meta_dict['iv'], meta_dict['wrapped_key'])) + + updated_meta = self.pkcs11.rewrap_kek( + iv=meta_dict['iv'], + wrapped_key=meta_dict['wrapped_key'], + hmac=meta_dict['hmac'], + mkek_label=meta_dict['mkek_label'], + hmac_label=meta_dict['hmac_label'], + key_length=32, + session=hsm_session + ) + + self.pkcs11.close_session(hsm_session) + print('Post-change IV: {}, Wrapped Key: {}'.format( + updated_meta['iv'], updated_meta['wrapped_key'])) + + # Update KEK metadata in DB + kek.plugin_meta = json.dumps(updated_meta) + + def get_keks_for_project(self, project): + keks = [] + with self.db_session.begin() as transaction: + print('Retrieving KEKs for Project {}'.format(project.id)) + query = transaction.session.query(models.KEKDatum) + query = query.filter_by(project_id=project.id) + query = query.filter_by(plugin_name=self.plugin_name) + + keks = query.all() + + return keks + + def get_projects(self): + print('Retrieving all available projects') + + projects = [] + with self.db_session.begin() as transaction: + projects = transaction.session.query(models.Project).all() + + return projects + + @property + def db_session(self): + return self._session_creator() + + def execute(self, dry_run=True): + self.dry_run = dry_run + if self.dry_run: + print('-- Running in dry-run mode --') + + projects = self.get_projects() + for project in projects: + keks = self.get_keks_for_project(project) + for kek in keks: + try: + self.rewrap_kek(project, kek) + except Exception: + print('Error occurred! SQLAlchemy automatically rolled-' + 'back the transaction') + traceback.print_exc() + + +def main(): + script_desc = ('Utility to re-wrap project KEKs after rotating an MKEK.') + + parser = argparse.ArgumentParser(description=script_desc) + parser.add_argument( + '--dry-run', + action='store_true', + help='Displays changes that will be made (Non-destructive)' + ) + args = parser.parse_args() + + rewrapper = KekRewrap( + db_connection=CONF.sql_connection, + library_path=CONF.p11_crypto_plugin.library_path, + login=CONF.p11_crypto_plugin.login, + slot_id=CONF.p11_crypto_plugin.slot_id, + new_mkek_label=CONF.p11_crypto_plugin.mkek_label + ) + rewrapper.execute(args.dry_run) + +if __name__ == '__main__': + main() diff --git a/barbican/plugin/crypto/pkcs11.py b/barbican/plugin/crypto/pkcs11.py index 90d7e40df..1d1a5b28e 100644 --- a/barbican/plugin/crypto/pkcs11.py +++ b/barbican/plugin/crypto/pkcs11.py @@ -485,7 +485,7 @@ class PKCS11(object): ck_attributes = self.build_attributes([ Attribute(CKA_CLASS, CKO_SECRET_KEY), Attribute(CKA_KEY_TYPE, CKK_AES), - Attribute(CKA_LABEL, mkek_label) + Attribute(CKA_LABEL, str(mkek_label)) ]) rv = self.lib.C_FindObjectsInit( session, ck_attributes.template, len(ck_attributes.template) @@ -542,6 +542,44 @@ class PKCS11(object): self.check_error(rv) return object_handle_ptr[0] + def rewrap_kek(self, iv, wrapped_key, hmac, mkek_label, hmac_label, + key_length, session): + unwrapped_kek = self.unwrap_key(iv, hmac, wrapped_key, mkek_label, + hmac_label, session) + mkek = self.key_handles[self.current_mkek_label] + + iv = self.generate_random(16, session) + mech = self.ffi.new("CK_MECHANISM *") + mech.mechanism = CKM_AES_CBC_PAD + mech.parameter = iv + mech.parameter_len = 16 + + padded_length = key_length + self.block_size + + buf = self.ffi.new("CK_BYTE[{0}]".format(padded_length)) + buf_len = self.ffi.new("CK_ULONG *", padded_length) + + rv = self.lib.C_WrapKey( + session, + mech, + mkek, + unwrapped_kek, + buf, + buf_len + ) + self.check_error(rv) + + wrapped_kek = self.ffi.buffer(buf, buf_len[0])[:] + hmac = self.compute_hmac(wrapped_kek, session) + + return { + 'iv': base64.b64encode(self.ffi.buffer(iv)[:]), + 'wrapped_key': base64.b64encode(wrapped_kek), + 'hmac': base64.b64encode(hmac), + 'mkek_label': self.current_mkek_label, + 'hmac_label': self.current_hmac_label + } + def generate_wrapped_kek(self, kek_label, key_length, session): # generate a non-persistent key that is extractable ck_attributes = self.build_attributes([ diff --git a/barbican/tests/plugin/crypto/test_p11_crypto.py b/barbican/tests/plugin/crypto/test_p11_crypto.py index 9435d3c9d..b8c8bcebf 100644 --- a/barbican/tests/plugin/crypto/test_p11_crypto.py +++ b/barbican/tests/plugin/crypto/test_p11_crypto.py @@ -232,6 +232,34 @@ class WhenTestingP11CryptoPlugin(utils.BaseTestCase): self.assertEqual(self.lib.C_UnwrapKey.call_count, 1) self.assertEqual(self.lib.C_Verify.call_count, 1) + def test_rewrap_kek(self): + plugin_meta = { + 'iv': base64.b64encode(b"\x00" * 16), + 'hmac': base64.b64encode(b"\x00" * 32), + 'wrapped_key': base64.b64encode(b"\x00" * 48), + 'mkek_label': 'mkek', + 'hmac_label': 'hmac', + } + self.lib.C_WrapKey.return_value = pkcs11.CKR_OK + self.lib.C_UnwrapKey.return_value = pkcs11.CKR_OK + self.lib.C_VerifyInit.return_value = pkcs11.CKR_OK + self.lib.C_Verify.return_value = pkcs11.CKR_OK + self.lib.C_SignInit.return_value = pkcs11.CKR_OK + self.lib.C_Sign.return_value = pkcs11.CKR_OK + + self.plugin.pkcs11.rewrap_kek( + plugin_meta['iv'], + plugin_meta['wrapped_key'], + plugin_meta['hmac'], + plugin_meta['mkek_label'], + plugin_meta['hmac'], + 32, + self.test_session + ) + self.assertEqual(self.lib.C_UnwrapKey.call_count, 1) + self.assertEqual(self.lib.C_WrapKey.call_count, 1) + self.assertEqual(self.lib.C_Verify.call_count, 1) + def test_generate_asymmetric_raises_error(self): self.assertRaises(NotImplementedError, self.plugin.generate_asymmetric, diff --git a/setup.cfg b/setup.cfg index f91955c3a..9d4ed6b4e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,6 +27,7 @@ console_scripts = barbican-keystone-listener = barbican.cmd.keystone_listener:main barbican-worker = barbican.cmd.worker:main barbican-worker-retry-scheduler = barbican.cmd.worker_retry_scheduler:main + pkcs11-kek-rewrap = barbican.cmd.pkcs11_kek_rewrap:main barbican.secretstore.plugin = store_crypto = barbican.plugin.store_crypto:StoreCryptoAdapterPlugin