barbican/barbican/tests/plugin/crypto/test_p11_crypto.py

378 lines
16 KiB
Python

# Copyright (c) 2013-2014 Rackspace, Inc.
#
# 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.
from unittest import mock
import six
from barbican.common import exception as ex
from barbican.model import models
from barbican.plugin.crypto import base as plugin_import
from barbican.plugin.crypto import p11_crypto
from barbican.plugin.crypto import pkcs11
from barbican.tests import utils
def generate_random_effect(length, session):
return b'0' * length
class WhenTestingP11CryptoPlugin(utils.BaseTestCase):
def setUp(self):
super(WhenTestingP11CryptoPlugin, self).setUp()
self.pkcs11 = mock.Mock()
self.pkcs11.get_session.return_value = int(1)
self.pkcs11.return_session.return_value = None
self.pkcs11.generate_random.side_effect = generate_random_effect
self.pkcs11.get_key_handle.return_value = int(2)
self.pkcs11.encrypt.return_value = {'iv': b'0', 'ct': b'0'}
self.pkcs11.decrypt.return_value = b'0'
self.pkcs11.generate_key.return_value = int(3)
self.pkcs11.wrap_key.return_value = {'iv': b'1', 'wrapped_key': b'1'}
self.pkcs11.unwrap_key.return_value = int(4)
self.pkcs11.compute_hmac.return_value = b'1'
self.pkcs11.verify_hmac.return_value = None
self.pkcs11.destroy_object.return_value = None
self.pkcs11.finalize.return_value = None
self.cfg_mock = mock.MagicMock(name='config mock')
self.cfg_mock.p11_crypto_plugin.mkek_label = 'mkek_label'
self.cfg_mock.p11_crypto_plugin.hmac_label = 'hmac_label'
self.cfg_mock.p11_crypto_plugin.mkek_length = 32
self.cfg_mock.p11_crypto_plugin.slot_id = 1
self.cfg_mock.p11_crypto_plugin.token_serial_number = None
self.cfg_mock.p11_crypto_plugin.token_label = None
self.cfg_mock.p11_crypto_plugin.token_labels = None
self.cfg_mock.p11_crypto_plugin.rw_session = True
self.cfg_mock.p11_crypto_plugin.pkek_length = 32
self.cfg_mock.p11_crypto_plugin.pkek_cache_ttl = 900
self.cfg_mock.p11_crypto_plugin.pkek_cache_limit = 10
self.cfg_mock.p11_crypto_plugin.encryption_mechanism = 'CKM_AES_CBC'
self.cfg_mock.p11_crypto_plugin.seed_file = ''
self.cfg_mock.p11_crypto_plugin.seed_length = 32
self.cfg_mock.p11_crypto_plugin.hmac_keywrap_mechanism = \
'CKM_SHA256_HMAC'
self.plugin_name = 'Test PKCS11 plugin'
self.cfg_mock.p11_crypto_plugin.plugin_name = self.plugin_name
self.plugin = p11_crypto.P11CryptoPlugin(
conf=self.cfg_mock,
pkcs11=self.pkcs11
)
def test_backwards_compatibility_for_token_label(self):
self.cfg_mock.p11_crypto_plugin.token_label = 'deprecatedLabel'
self.cfg_mock.p11_crypto_plugin.token_labels = ['label1', 'label2']
plugin = p11_crypto.P11CryptoPlugin(
conf=self.cfg_mock,
pkcs11=self.pkcs11
)
self.assertIn('deprecatedLabel', plugin.token_labels)
def test_invalid_library_path(self):
cfg = self.cfg_mock.p11_crypto_plugin
cfg.library_path = None
self.assertRaises(ValueError, p11_crypto.P11CryptoPlugin,
conf=self.cfg_mock, pkcs11=self.pkcs11)
def test_bind_kek_metadata_without_existing_key(self):
kek_datum = models.KEKDatum()
dto = plugin_import.KEKMetaDTO(kek_datum)
dto = self.plugin.bind_kek_metadata(dto)
self.assertEqual('AES', dto.algorithm)
self.assertEqual(256, dto.bit_length)
self.assertEqual('CBC', dto.mode)
self.assertEqual(2, self.pkcs11.get_key_handle.call_count)
self.assertEqual(1, self.pkcs11.generate_key.call_count)
self.assertEqual(1, self.pkcs11.wrap_key.call_count)
self.assertEqual(1, self.pkcs11.compute_hmac.call_count)
def test_bind_kek_metadata_with_existing_key(self):
kek_datum = models.KEKDatum()
dto = plugin_import.KEKMetaDTO(kek_datum)
dto.plugin_meta = '{}'
dto = self.plugin.bind_kek_metadata(dto)
self.assertEqual(0, self.pkcs11.generate_key.call_count)
self.assertEqual(0, self.pkcs11.wrap_key.call_count)
self.assertEqual(0, self.pkcs11.compute_hmac.call_count)
def test_encrypt(self):
payload = b'test payload'
encrypt_dto = plugin_import.EncryptDTO(payload)
kek_meta = mock.MagicMock()
kek_meta.kek_label = 'pkek'
kek_meta.plugin_meta = ('{"iv": "iv==",'
'"hmac": "hmac",'
'"wrapped_key": "wrappedkey==",'
'"mkek_label": "mkek_label",'
'"hmac_label": "hmac_label"}')
response_dto = self.plugin.encrypt(encrypt_dto,
kek_meta,
mock.MagicMock())
self.assertEqual(b'0', response_dto.cypher_text)
self.assertIn('iv', response_dto.kek_meta_extended)
self.assertEqual(2, self.pkcs11.get_key_handle.call_count)
self.assertEqual(2, self.pkcs11.get_session.call_count)
self.assertEqual(1, self.pkcs11.verify_hmac.call_count)
self.assertEqual(1, self.pkcs11.unwrap_key.call_count)
self.assertEqual(1, self.pkcs11.encrypt.call_count)
self.assertEqual(1, self.pkcs11.return_session.call_count)
def test_encrypt_bad_session(self):
self.pkcs11.get_session.return_value = mock.DEFAULT
self.pkcs11.get_session.side_effect = ex.P11CryptoPluginException(
'Testing error handling'
)
payload = b'test payload'
encrypt_dto = plugin_import.EncryptDTO(payload)
kek_meta = mock.MagicMock()
kek_meta.kek_label = 'pkek'
kek_meta.plugin_meta = ('{"iv": "iv==",'
'"hmac": "hmac",'
'"wrapped_key": "wrappedkey==",'
'"mkek_label": "mkek_label",'
'"hmac_label": "hmac_label"}')
self.assertRaises(ex.P11CryptoPluginException,
self.plugin._encrypt,
encrypt_dto,
kek_meta,
mock.MagicMock())
self.assertEqual(2, self.pkcs11.get_key_handle.call_count)
self.assertEqual(2, self.pkcs11.get_session.call_count)
self.assertEqual(1, self.pkcs11.verify_hmac.call_count)
self.assertEqual(1, self.pkcs11.unwrap_key.call_count)
self.assertEqual(0, self.pkcs11.encrypt.call_count)
self.assertEqual(0, self.pkcs11.return_session.call_count)
def test_decrypt(self):
ct = b'ctct'
kek_meta_extended = '{"iv":"AAAA","mechanism":"CKM_AES_CBC"}'
decrypt_dto = plugin_import.DecryptDTO(ct)
kek_meta = mock.MagicMock()
kek_meta.kek_label = 'pkek'
kek_meta.plugin_meta = ('{"iv": "iv==",'
'"hmac": "hmac",'
'"wrapped_key": "c2VjcmV0a2V5BwcHBwcHBw==",'
'"mkek_label": "mkek_label",'
'"hmac_label": "hmac_label"}')
pt = self.plugin.decrypt(decrypt_dto,
kek_meta,
kek_meta_extended,
mock.MagicMock())
self.assertEqual(b'0', pt)
self.assertEqual(2, self.pkcs11.get_key_handle.call_count)
self.assertEqual(2, self.pkcs11.get_session.call_count)
self.assertEqual(1, self.pkcs11.verify_hmac.call_count)
self.assertEqual(1, self.pkcs11.unwrap_key.call_count)
self.assertEqual(1, self.pkcs11.decrypt.call_count)
self.assertEqual(1, self.pkcs11.return_session.call_count)
def test_decrypt_bad_session(self):
self.pkcs11.get_session.return_value = mock.DEFAULT
self.pkcs11.get_session.side_effect = ex.P11CryptoPluginException(
'Testing error handling'
)
ct = b'ctct'
kek_meta_extended = '{"iv":"AAAA","mechanism":"CKM_AES_CBC"}'
decrypt_dto = plugin_import.DecryptDTO(ct)
kek_meta = mock.MagicMock()
kek_meta.kek_label = 'pkek'
kek_meta.plugin_meta = ('{"iv": "iv==",'
'"hmac": "hmac",'
'"wrapped_key": "wrappedkey==",'
'"mkek_label": "mkek_label",'
'"hmac_label": "hmac_label"}')
self.assertRaises(ex.P11CryptoPluginException,
self.plugin._decrypt,
decrypt_dto,
kek_meta,
kek_meta_extended,
mock.MagicMock())
self.assertEqual(2, self.pkcs11.get_key_handle.call_count)
self.assertEqual(2, self.pkcs11.get_session.call_count)
self.assertEqual(1, self.pkcs11.verify_hmac.call_count)
self.assertEqual(1, self.pkcs11.unwrap_key.call_count)
self.assertEqual(0, self.pkcs11.decrypt.call_count)
self.assertEqual(0, self.pkcs11.return_session.call_count)
def test_generate_symmetric(self):
secret = models.Secret()
secret.bit_length = 128
secret.algorithm = 'AES'
generate_dto = plugin_import.GenerateDTO(
secret.algorithm,
secret.bit_length,
None, None)
kek_meta = mock.MagicMock()
kek_meta.kek_label = 'pkek'
kek_meta.plugin_meta = ('{"iv": "iv==",'
'"hmac": "hmac",'
'"wrapped_key": "wrappedkey==",'
'"mkek_label": "mkek_label",'
'"hmac_label": "hmac_label"}')
response_dto = self.plugin.generate_symmetric(generate_dto,
kek_meta,
mock.MagicMock())
self.assertEqual(b'0', response_dto.cypher_text)
self.assertIn('iv', response_dto.kek_meta_extended)
self.assertEqual(2, self.pkcs11.get_key_handle.call_count)
self.assertEqual(2, self.pkcs11.get_session.call_count)
self.assertEqual(1, self.pkcs11.generate_random.call_count)
self.assertEqual(1, self.pkcs11.verify_hmac.call_count)
self.assertEqual(1, self.pkcs11.unwrap_key.call_count)
self.assertEqual(1, self.pkcs11.encrypt.call_count)
self.assertEqual(1, self.pkcs11.return_session.call_count)
def test_generate_asymmetric_raises_error(self):
self.assertRaises(NotImplementedError,
self.plugin.generate_asymmetric,
mock.MagicMock(),
mock.MagicMock(),
mock.MagicMock())
def test_supports_encrypt_decrypt(self):
self.assertTrue(
self.plugin.supports(
plugin_import.PluginSupportTypes.ENCRYPT_DECRYPT
)
)
def test_supports_symmetric_key_generation(self):
self.assertTrue(
self.plugin.supports(
plugin_import.PluginSupportTypes.SYMMETRIC_KEY_GENERATION
)
)
def test_does_not_supports_asymmetric_key_generation(self):
self.assertFalse(
self.plugin.supports(
plugin_import.PluginSupportTypes.ASYMMETRIC_KEY_GENERATION
)
)
def test_does_not_support_unknown_type(self):
self.assertFalse(
self.plugin.supports('SOMETHING_RANDOM')
)
def test_missing_mkek(self):
self.pkcs11.get_key_handle.return_value = None
self.assertRaises(ex.P11CryptoKeyHandleException,
self.plugin._get_master_key,
self.plugin.mkek_key_type,
'bad_key_label')
def test_cached_kek_expired(self):
self.plugin.pkek_cache['expired_kek'] = p11_crypto.CachedKEK(4, 0)
self.assertIsNone(self.plugin._pkek_cache_get('expired_kek'))
def test_create_pkcs11(self):
def _generate_random(session, buf, length):
ffi.buffer(buf)[:] = b'0' * length
return pkcs11.CKR_OK
lib = mock.Mock()
lib.C_Initialize.return_value = pkcs11.CKR_OK
lib.C_GetSlotList.return_value = pkcs11.CKR_OK
lib.C_GetTokenInfo.return_value = pkcs11.CKR_OK
lib.C_OpenSession.return_value = pkcs11.CKR_OK
lib.C_CloseSession.return_value = pkcs11.CKR_OK
lib.C_GetSessionInfo.return_value = pkcs11.CKR_OK
lib.C_Login.return_value = pkcs11.CKR_OK
lib.C_GenerateRandom.side_effect = _generate_random
lib.C_SeedRandom.return_value = pkcs11.CKR_OK
ffi = pkcs11.build_ffi()
setattr(ffi, 'dlopen', lambda x: lib)
p11 = self.plugin._create_pkcs11(ffi)
self.assertIsInstance(p11, pkcs11.PKCS11)
# test for when plugin_conf.seed_file is not None
self.plugin.seed_file = 'seed_file'
d = '01234567' * 4
mo = mock.mock_open(read_data=d)
with mock.patch(six.moves.builtins.__name__ + '.open',
mo,
create=True):
p11 = self.plugin._create_pkcs11(ffi)
self.assertIsInstance(p11, pkcs11.PKCS11)
mo.assert_called_once_with('seed_file', 'rb')
calls = [mock.call('seed_file', 'rb'),
mock.call().__enter__(),
mock.call().read(32),
mock.call().__exit__(None, None, None)]
self.assertEqual(mo.mock_calls, calls)
lib.C_SeedRandom.assert_called_once_with(mock.ANY, mock.ANY, 32)
self.cfg_mock.p11_crypto_plugin.seed_file = ''
def test_call_pkcs11_with_token_error(self):
self.plugin._encrypt = mock.Mock()
self.plugin._encrypt.side_effect = [ex.P11CryptoTokenException(
'Testing error handling'
),
'test payload']
self.plugin._reinitialize_pkcs11 = mock.Mock()
self.plugin._reinitialize_pkcs11.return_value = mock.DEFAULT
self.plugin.encrypt(mock.MagicMock(), mock.MagicMock(),
mock.MagicMock())
self.assertEqual(2, self.pkcs11.get_key_handle.call_count)
self.assertEqual(1, self.pkcs11.get_session.call_count)
self.assertEqual(0, self.pkcs11.return_session.call_count)
self.assertEqual(2, self.plugin._encrypt.call_count)
def test_call_pkcs11_reinitializes_pkcs11_object(self):
self.plugin._reinitialize_pkcs11 = mock.Mock()
self.plugin.pkcs11 = None
def test_func(*args, **kwargs):
pass
self.plugin._call_pkcs11(test_func)
self.plugin._reinitialize_pkcs11.assert_called_once()
def test_reinitialize_pkcs11(self):
pkcs11 = self.pkcs11
self.plugin._create_pkcs11 = mock.Mock()
self.plugin._create_pkcs11.return_value = pkcs11
self.plugin._configure_object_cache = mock.Mock()
self.plugin._configure_object_cache.return_value = mock.DEFAULT
self.plugin._reinitialize_pkcs11()
self.assertEqual(1, self.pkcs11.finalize.call_count)
self.assertEqual(1, self.plugin._create_pkcs11.call_count)
self.assertEqual(1, self.plugin._configure_object_cache.call_count)
def test_get_plugin_name(self):
self.assertEqual(self.plugin_name, self.plugin.get_plugin_name())