URL: https://github.com/freeipa/freeipa/pull/510 Author: tiran Title: #510: Vault: port key wrapping to python-cryptography Action: synchronized
To pull the PR as Git branch: git remote add ghfreeipa https://github.com/freeipa/freeipa git fetch ghfreeipa pull/510/head:pr510 git checkout pr510
From 3c13f75c12e91edb5bd99b5e0dbe2ab007d144b2 Mon Sep 17 00:00:00 2001 From: Christian Heimes <chei...@redhat.com> Date: Sat, 25 Feb 2017 13:09:11 +0100 Subject: [PATCH] Vault: port key wrapping to python-cryptography https://fedorahosted.org/freeipa/ticket/6650 Signed-off-by: Christian Heimes <chei...@redhat.com> --- ipaclient/plugins/vault.py | 181 +++++++++++++++++++++++---------------------- ipaclient/setup.py | 1 - 2 files changed, 92 insertions(+), 90 deletions(-) diff --git a/ipaclient/plugins/vault.py b/ipaclient/plugins/vault.py index 9efb1f1..70756df 100644 --- a/ipaclient/plugins/vault.py +++ b/ipaclient/plugins/vault.py @@ -31,10 +31,11 @@ from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.primitives.asymmetric import padding -from cryptography.hazmat.primitives.serialization import load_pem_public_key,\ - load_pem_private_key - -import nss.nss as nss +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.primitives.padding import PKCS7 +from cryptography.hazmat.primitives.serialization import ( + load_pem_public_key, load_pem_private_key) +from cryptography.x509 import load_der_x509_certificate from ipaclient.frontend import MethodOverride from ipalib.frontend import Local, Method, Object @@ -639,6 +640,39 @@ def get_output_params(self): def _iter_output(self): return self.api.Command.vault_archive_internal.output() + def _wrap_data(self, transport_cert_der, json_vault_data): + """Encrypt data with wrapped session key and transport cert + + :param bytes transport_cert_der: transport cert in DER encoding + :param bytes json_vault_data: dumped vault data + :return: + """ + transport_cert = load_der_x509_certificate( + transport_cert_der, default_backend()) + public_key = transport_cert.public_key() + + # generate session key + key_length = max(algorithms.TripleDES.key_sizes) + algo = algorithms.TripleDES(os.urandom(key_length // 8)) + nonce = os.urandom(algo.block_size // 8) + + # wrap session key with transport certificate + wrapped_session_key = public_key.encrypt( + algo.key, + padding.PKCS1v15() + ) + + # wrap vault_data with session key + padder = PKCS7(algo.block_size).padder() + padded_data = padder.update(json_vault_data) + padded_data += padder.finalize() + + cipher = Cipher(algo, modes.CBC(nonce), backend=default_backend()) + encryptor = cipher.encryptor() + wrapped_vault_data = encryptor.update(padded_data) + encryptor.finalize() + + return wrapped_session_key, nonce, wrapped_vault_data + def forward(self, *args, **options): data = options.get('data') input_file = options.get('in') @@ -762,57 +796,29 @@ def forward(self, *args, **options): name='vault_type', error=_('Invalid vault type')) - # initialize NSS database - nss.nss_init(api.env.nss_dir) - - # retrieve transport certificate - config = self.api.Command.vaultconfig_show()['result'] - transport_cert_der = config['transport_cert'] - nss_transport_cert = nss.Certificate(transport_cert_der) - - # generate session key - mechanism = nss.CKM_DES3_CBC_PAD - slot = nss.get_best_slot(mechanism) - key_length = slot.get_best_key_length(mechanism) - session_key = slot.key_gen(mechanism, None, key_length) - - # wrap session key with transport certificate - # pylint: disable=no-member - public_key = nss_transport_cert.subject_public_key_info.public_key - # pylint: enable=no-member - wrapped_session_key = nss.pub_wrap_sym_key(mechanism, - public_key, - session_key) - - options['session_key'] = wrapped_session_key.data - - nonce_length = nss.get_iv_length(mechanism) - nonce = nss.generate_random(nonce_length) - options['nonce'] = nonce - - vault_data = {} - vault_data[u'data'] = base64.b64encode(data).decode('utf-8') + vault_data = { + 'data': base64.b64encode(data).decode('utf-8') + } if encrypted_key: vault_data[u'encrypted_key'] = base64.b64encode(encrypted_key)\ .decode('utf-8') - json_vault_data = json.dumps(vault_data) - - # wrap vault_data with session key - iv_si = nss.SecItem(nonce) - iv_param = nss.param_from_iv(mechanism, iv_si) - - encoding_ctx = nss.create_context_by_sym_key(mechanism, - nss.CKA_ENCRYPT, - session_key, - iv_param) - - wrapped_vault_data = encoding_ctx.cipher_op(json_vault_data)\ - + encoding_ctx.digest_final() + json_vault_data = json.dumps(vault_data).encode('utf-8') - options['vault_data'] = wrapped_vault_data + # retrieve transport certificate + config = self.api.Command.vaultconfig_show()['result'] + transport_cert_der = config['transport_cert'] + # created wrapped session key and wrap vault data + wrapped_session_key, nonce, wrapped_vault_data = self._wrap_data( + transport_cert_der, json_vault_data + ) + options.update( + session_key=wrapped_session_key, + nonce=nonce, + vault_data=wrapped_vault_data + ) return self.api.Command.vault_archive_internal(*args, **options) @@ -893,6 +899,33 @@ def get_output_params(self): def _iter_output(self): return self.api.Command.vault_retrieve_internal.output() + def _wrap_session_key(self, transport_cert_der): + transport_cert = load_der_x509_certificate( + transport_cert_der, default_backend()) + public_key = transport_cert.public_key() + # generate session key + key_length = max(algorithms.TripleDES.key_sizes) + algo = algorithms.TripleDES(os.urandom(key_length // 8)) + # wrap session key with transport certificate + wrapped_session_key = public_key.encrypt( + algo.key, + padding.PKCS1v15() + ) + return algo, wrapped_session_key + + def _unwrap_response(self, algo, nonce, vault_data): + cipher = Cipher(algo, modes.CBC(nonce), backend=default_backend()) + # decrypt + decryptor = cipher.decryptor() + padded_data = decryptor.update(vault_data) + padded_data += decryptor.finalize() + # remove padding + unpadder = PKCS7(algo.block_size).unpadder() + json_vault_data = unpadder.update(padded_data) + json_vault_data += unpadder.finalize() + # load JSON + return json.loads(json_vault_data.decode('utf-8')) + def forward(self, *args, **options): output_file = options.get('out') @@ -922,56 +955,26 @@ def forward(self, *args, **options): # retrieve vault info vault = self.api.Command.vault_show(*args, **options)['result'] - vault_type = vault['ipavaulttype'][0] - # initialize NSS database - nss.nss_init(api.env.nss_dir) - # retrieve transport certificate config = self.api.Command.vaultconfig_show()['result'] - transport_cert_der = config['transport_cert'] - nss_transport_cert = nss.Certificate(transport_cert_der) - - # generate session key - mechanism = nss.CKM_DES3_CBC_PAD - slot = nss.get_best_slot(mechanism) - key_length = slot.get_best_key_length(mechanism) - session_key = slot.key_gen(mechanism, None, key_length) - - # wrap session key with transport certificate - # pylint: disable=no-member - public_key = nss_transport_cert.subject_public_key_info.public_key - # pylint: enable=no-member - wrapped_session_key = nss.pub_wrap_sym_key(mechanism, - public_key, - session_key) - + # create algo and wrap session key with transport cert + algo, wrapped_session_key = self._wrap_session_key( + config['transport_cert'] + ) # send retrieval request to server - options['session_key'] = wrapped_session_key.data - + options['session_key'] = wrapped_session_key response = self.api.Command.vault_retrieve_internal(*args, **options) - - result = response['result'] - nonce = result['nonce'] - # unwrap data with session key - wrapped_vault_data = result['vault_data'] - - iv_si = nss.SecItem(nonce) - iv_param = nss.param_from_iv(mechanism, iv_si) - - decoding_ctx = nss.create_context_by_sym_key(mechanism, - nss.CKA_DECRYPT, - session_key, - iv_param) - - json_vault_data = decoding_ctx.cipher_op(wrapped_vault_data)\ - + decoding_ctx.digest_final() + vault_data = self._unwrap_response( + algo, + response['result']['nonce'], + response['result']['vault_data'] + ) + del algo - vault_data = json.loads(json_vault_data.decode('utf-8')) data = base64.b64decode(vault_data[u'data'].encode('utf-8')) - encrypted_key = None if 'encrypted_key' in vault_data: diff --git a/ipaclient/setup.py b/ipaclient/setup.py index e7c8072..93cb1e8 100644 --- a/ipaclient/setup.py +++ b/ipaclient/setup.py @@ -48,7 +48,6 @@ "ipalib", "ipapython", "jinja2", - "python-nss", "python-yubico", "pyusb", "qrcode",
-- Manage your subscription for the Freeipa-devel mailing list: https://www.redhat.com/mailman/listinfo/freeipa-devel Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code