From 4b5cc4a4795bf53201efff0bbe899717bff1f96d Mon Sep 17 00:00:00 2001 From: fuzzykat <70575698+fuzzykat@users.noreply.github.com> Date: Wed, 7 Aug 2024 16:33:42 +0300 Subject: [PATCH] Add simple derivation mechanisms support Added support of the following key derivation mechanisms (as per section 2.43 of the PKCS#11 3.0 specification): - CKM_CONCATENATE_BASE_AND_KEY - CKM_CONCATENATE_BASE_AND_DATA - CKM_CONCATENATE_DATA_AND_BASE - CKM_XOR_BASE_AND_DATA Unfortunately, these mechanisms are currently not supported by SoftHSM (2.6.1). However CKM_CONCATENATE* mechanisms are available in develop branch of SoftHSM (tested with a SoftHSM build from the develop branch) --- PyKCS11/__init__.py | 69 ++++++++++++++++++ src/opensc/pkcs11.h | 8 +++ src/pykcs11.i | 49 +++++++++++++ test/test_derive.py | 165 +++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 289 insertions(+), 2 deletions(-) diff --git a/PyKCS11/__init__.py b/PyKCS11/__init__.py index fe48993..9941b53 100644 --- a/PyKCS11/__init__.py +++ b/PyKCS11/__init__.py @@ -924,6 +924,75 @@ def to_native(self): return self._mech +class CONCATENATE_BASE_AND_KEY_Mechanism(object): + """CKM_CONCATENATE_BASE_AND_KEY key derivation mechanism""" + + def __init__(self, encKey): + """ + :param encKey: a handle of encryption key + """ + self._mech = PyKCS11.LowLevel.CK_MECHANISM() + self._mech.mechanism = CKM_CONCATENATE_BASE_AND_KEY + self._mech.pParameter = encKey + self._mech.ulParameterLen = PyKCS11.LowLevel.CK_OBJECT_HANDLE_LENGTH + + def to_native(self): + return self._mech + + +class KEY_DERIVATION_STRING_DATA_MechanismBase(object): + """Base class for mechanisms using derivation string data""" + + def __init__(self, data, mechType): + """ + :param data: a byte array to concatenate the key with + :param mechType: mechanism type + """ + self._param = PyKCS11.LowLevel.CK_KEY_DERIVATION_STRING_DATA() + + self._data = ckbytelist(data) + self._param.pData = self._data + self._param.ulLen = len(self._data) + + self._mech = PyKCS11.LowLevel.CK_MECHANISM() + self._mech.mechanism = mechType + self._mech.pParameter = self._param + self._mech.ulParameterLen = PyKCS11.LowLevel.CK_KEY_DERIVATION_STRING_DATA_LENGTH + + def to_native(self): + return self._mech + + +class CONCATENATE_BASE_AND_DATA_Mechanism(KEY_DERIVATION_STRING_DATA_MechanismBase): + """CKM_CONCATENATE_BASE_AND_DATA key derivation mechanism""" + + def __init__(self, data): + """ + :param data: a byte array to concatenate the key with + """ + super().__init__(data, CKM_CONCATENATE_BASE_AND_DATA) + + +class CONCATENATE_DATA_AND_BASE_Mechanism(KEY_DERIVATION_STRING_DATA_MechanismBase): + """CKM_CONCATENATE_DATA_AND_BASE key derivation mechanism""" + + def __init__(self, data): + """ + :param data: a byte array to concatenate the key with + """ + super().__init__(data, CKM_CONCATENATE_DATA_AND_BASE) + + +class XOR_BASE_AND_DATA_Mechanism(KEY_DERIVATION_STRING_DATA_MechanismBase): + """CKM_XOR_BASE_AND_DATA key derivation mechanism""" + + def __init__(self, data): + """ + :param data: a byte array to xor the key with + """ + super().__init__(data, CKM_XOR_BASE_AND_DATA) + + class DigestSession(object): def __init__(self, lib, session, mecha): self._lib = lib diff --git a/src/opensc/pkcs11.h b/src/opensc/pkcs11.h index 80fbad5..45d3b91 100644 --- a/src/opensc/pkcs11.h +++ b/src/opensc/pkcs11.h @@ -769,6 +769,11 @@ struct ck_ecdh1_derive_params { void * pPublicData; } ; +struct ck_key_derivation_string_data { + unsigned char * pData; + unsigned long ulLen; +} ; + #define CKF_HW (1 << 0) #define CKF_ENCRYPT (1 << 8) #define CKF_DECRYPT (1 << 9) @@ -1346,6 +1351,9 @@ typedef struct ck_aes_ctr_params *CK_AES_CTR_PARAMS_PTR; typedef struct ck_ecdh1_derive_params CK_ECDH1_DERIVE_PARAMS; typedef struct ck_ecdh1_derive_params *CK_ECDH1_DERIVE_PARAMS_PTR; +typedef struct ck_key_derivation_string_data CK_KEY_DERIVATION_STRING_DATA; +typedef struct ck_key_derivation_string_data *CK_KEY_DERIVATION_STRING_DATA_PTR; + typedef struct ck_function_list CK_FUNCTION_LIST; typedef struct ck_function_list *CK_FUNCTION_LIST_PTR; typedef struct ck_function_list **CK_FUNCTION_LIST_PTR_PTR; diff --git a/src/pykcs11.i b/src/pykcs11.i index 9b3586d..52c68af 100644 --- a/src/pykcs11.i +++ b/src/pykcs11.i @@ -267,6 +267,14 @@ typedef struct CK_DATE{ res2 = SWIG_ConvertPtr($input, &arg2, $descriptor(CK_AES_CTR_PARAMS*), 0); if( SWIG_IsOK( res2 ) ) break; + + res2 = SWIG_ConvertPtr($input, &arg2, $descriptor(CK_KEY_DERIVATION_STRING_DATA*), 0); + if( SWIG_IsOK( res2 ) ) + break; + + res2 = SWIG_ConvertPtr($input, &arg2, $descriptor(CK_OBJECT_HANDLE*), 0); + if( SWIG_IsOK( res2 ) ) + break; } while(0); if (!SWIG_IsOK(res2)) { @@ -275,6 +283,27 @@ typedef struct CK_DATE{ } } +// typemap for CK_BYTE_PTR (unsigned char*) mechanism parameters +%typemap(in) unsigned char* { + vector *vect; + // If the value being set is of ckbytelist type: + int res = SWIG_ConvertPtr($input, (void **)&vect, $descriptor(vector *), 0); + if (SWIG_IsOK(res)) + { + // Get the data from the vector + // Only set value if not null + if (vect) + arg2 = vect->data(); + else + arg2 = NULL; + } + else + { + // If a mechanism parameter has a 'CK_BYTE_PTR' as a member, it must be represented as a ckbytelist + SWIG_exception_fail(SWIG_ArgError(res), "CK_BYTE_PTR members of CK_* mechanism params must be represented as ckbytelist type"); + } +} + // typemap for CK_BYTE static arrays %typemap(in) unsigned char[ANY](unsigned char out[$1_dim0]) { vector *vect; @@ -335,6 +364,8 @@ typedef struct CK_MECHANISM { } } +%constant int CK_OBJECT_HANDLE_LENGTH = sizeof(CK_OBJECT_HANDLE); + typedef struct CK_GCM_PARAMS { void * pIv; unsigned long ulIvLen; @@ -443,6 +474,24 @@ typedef struct CK_ECDH1_DERIVE_PARAMS { %constant int CK_ECDH1_DERIVE_PARAMS_LENGTH = sizeof(CK_ECDH1_DERIVE_PARAMS); +typedef struct CK_KEY_DERIVATION_STRING_DATA { + unsigned char * pData; + unsigned long ulLen; +} CK_KEY_DERIVATION_STRING_DATA; + +%extend CK_KEY_DERIVATION_STRING_DATA +{ + CK_KEY_DERIVATION_STRING_DATA() + { + CK_KEY_DERIVATION_STRING_DATA *p = new CK_KEY_DERIVATION_STRING_DATA(); + p->ulLen = 0; + p->pData = NULL; + return p; + } +}; + +%constant int CK_KEY_DERIVATION_STRING_DATA_LENGTH = sizeof(CK_KEY_DERIVATION_STRING_DATA); + typedef struct CK_MECHANISM_INFO { %immutable; unsigned long ulMinKeySize; diff --git a/test/test_derive.py b/test/test_derive.py index 71929a4..2c35ace 100644 --- a/test/test_derive.py +++ b/test/test_derive.py @@ -9,7 +9,7 @@ def setUp(self): # get SoftHSM major version info = self.pkcs11.getInfo() - self.SoftHSMversion = info.libraryVersion[0] + self.SoftHSMversion = info.libraryVersion self.manufacturer = info.manufacturerID self.slot = self.pkcs11.getSlotList(tokenPresent=True)[0] @@ -18,6 +18,36 @@ def setUp(self): ) self.session.login("1234") + # common templates used in derive test cases + self.aesKeyTemplate = [ + (PyKCS11.CKA_CLASS, PyKCS11.CKO_SECRET_KEY), + (PyKCS11.CKA_KEY_TYPE, PyKCS11.CKK_AES), + (PyKCS11.CKA_VALUE_LEN, 32), + (PyKCS11.CKA_TOKEN, PyKCS11.CK_TRUE), + (PyKCS11.CKA_SENSITIVE, PyKCS11.CK_FALSE), + (PyKCS11.CKA_EXTRACTABLE, PyKCS11.CK_TRUE), + (PyKCS11.CKA_PRIVATE, PyKCS11.CK_TRUE), + (PyKCS11.CKA_LABEL, "DeriveTestBaseAes256Key"), + (PyKCS11.CKA_DERIVE, PyKCS11.CK_TRUE), + ] + + self.genericKeyTemplate = [ + (PyKCS11.CKA_CLASS, PyKCS11.CKO_SECRET_KEY), + (PyKCS11.CKA_KEY_TYPE, PyKCS11.CKK_GENERIC_SECRET), + (PyKCS11.CKA_TOKEN, PyKCS11.CK_FALSE), + (PyKCS11.CKA_SENSITIVE, PyKCS11.CK_FALSE), + (PyKCS11.CKA_EXTRACTABLE, PyKCS11.CK_TRUE), + (PyKCS11.CKA_PRIVATE, PyKCS11.CK_TRUE), + (PyKCS11.CKA_LABEL, "DeriveTestGenericKey"), + ] + + # generate a common symmetric base key for tests + keyID = (0x01,) + baseKeyTemplate = self.aesKeyTemplate + [(PyKCS11.CKA_ID, keyID)] + mechanism = PyKCS11.Mechanism(PyKCS11.CKM_AES_KEY_GEN, None) + self.baseKey = self.session.generateKey(baseKeyTemplate, mechanism) + self.assertIsNotNone(self.baseKey) + # Select the curve to be used for the keys curve = u"secp256r1" @@ -65,8 +95,14 @@ def tearDown(self): self.pkcs11.closeAllSessions(self.slot) del self.pkcs11 + def getCkaValue(self, key): + return list( + self.session.getAttributeValue( + key, [PyKCS11.CKA_VALUE])[0] + ) + def test_deriveKey_ECDH1_DERIVE(self): - if self.SoftHSMversion < 2: + if self.SoftHSMversion[0] < 2: self.skipTest("generateKeyPair() only supported by SoftHSM >= 2") keyID = (0x11,) @@ -142,3 +178,128 @@ def test_deriveKey_ECDH1_DERIVE(self): self.session.destroyObject(derivedKey2) self.session.destroyObject(pubKey) self.session.destroyObject(pvtKey) + + def test_deriveKey_CKM_CONCATENATE_BASE_AND_KEY(self): + # This mechanism is not supported in the current release of SoftHSM (2.6.1), however available in develop branch, + # see https://github.com/opendnssec/SoftHSMv2/commit/fa595c07a185656382c18ea2a6a12cad825d48b4 + if self.SoftHSMversion <= (2,6): + self.skipTest("CKM_CONCATENATE_BASE_AND_KEY is not supported by SoftHSM <= 2.6") + + # generate a key to concatenate with + keyID = (0x11,) + concatenateKeyTemplate = self.aesKeyTemplate + [(PyKCS11.CKA_ID, keyID)] + mechanism = PyKCS11.Mechanism(PyKCS11.CKM_AES_KEY_GEN, None) + concKey = self.session.generateKey(concatenateKeyTemplate, mechanism) + self.assertIsNotNone(concKey) + + # concatenate two keys + keyID = (0x22,) + derivedKeyTemplate = self.genericKeyTemplate + [ + (PyKCS11.CKA_VALUE_LEN, 64), + (PyKCS11.CKA_ID, keyID) + ] + mechanism = PyKCS11.CONCATENATE_BASE_AND_KEY_Mechanism(concKey) + derivedKey = self.session.deriveKey( + self.baseKey, derivedKeyTemplate, mechanism) + self.assertIsNotNone(derivedKey) + + # check derived key's value + baseKeyValue = self.getCkaValue(self.baseKey) + concKeyValue = self.getCkaValue(concKey) + derivedKeyValue = self.getCkaValue(derivedKey) + + # match: check values + self.assertSequenceEqual(baseKeyValue + concKeyValue, derivedKeyValue) + + # cleanup + self.session.destroyObject(derivedKey) + self.session.destroyObject(concKey) + + def test_deriveKey_CKM_CONCATENATE_BASE_AND_DATA(self): + # This mechanism is not supported in the current release of SoftHSM (2.6.1), however available in develop branch, + # see https://github.com/opendnssec/SoftHSMv2/commit/dba00d73e1b69f65b68397d235e7f73bbf59ab6a + if self.SoftHSMversion <= (2,6): + self.skipTest("CKM_CONCATENATE_BASE_AND_DATA is not supported by SoftHSM <= 2.6") + + # generate data to concatenate with + concData = list(self.session.generateRandom(32)) + + # concatenate key with data + keyID = (0x22,) + derivedKeyTemplate = self.genericKeyTemplate + [ + (PyKCS11.CKA_VALUE_LEN, 64), + (PyKCS11.CKA_ID, keyID) + ] + mechanism = PyKCS11.CONCATENATE_BASE_AND_DATA_Mechanism(concData) + derivedKey = self.session.deriveKey( + self.baseKey, derivedKeyTemplate, mechanism) + self.assertIsNotNone(derivedKey) + + # check derived key's value + baseKeyValue = self.getCkaValue(self.baseKey) + derivedKeyValue = self.getCkaValue(derivedKey) + + # match: check values + self.assertSequenceEqual(baseKeyValue + concData, derivedKeyValue) + + # cleanup + self.session.destroyObject(derivedKey) + + def test_deriveKey_CKM_CONCATENATE_DATA_AND_BASE(self): + # This mechanism is not supported in the current release of SoftHSM (2.6.1), however available in develop branch, + # see https://github.com/opendnssec/SoftHSMv2/commit/fae0d9f769ac30d25f563c5fc6c417e9199e4403 + if self.SoftHSMversion <= (2,6): + self.skipTest("CKM_CONCATENATE_DATA_AND_BASE is not supported by SoftHSM <= 2.6") + + # generate data to concatenate with + concData = list(self.session.generateRandom(32)) + + # concatenate data with key + keyID = (0x22,) + derivedKeyTemplate = self.genericKeyTemplate + [ + (PyKCS11.CKA_VALUE_LEN, 64), + (PyKCS11.CKA_ID, keyID) + ] + mechanism = PyKCS11.CONCATENATE_DATA_AND_BASE_Mechanism(concData) + derivedKey = self.session.deriveKey( + self.baseKey, derivedKeyTemplate, mechanism) + self.assertIsNotNone(derivedKey) + + # check derived key's value + baseKeyValue = self.getCkaValue(self.baseKey) + derivedKeyValue = self.getCkaValue(derivedKey) + + # match: check values + self.assertSequenceEqual(concData + baseKeyValue, derivedKeyValue) + + # cleanup + self.session.destroyObject(derivedKey) + + def test_deriveKey_CKM_XOR_BASE_AND_DATA(self): + if self.manufacturer.startswith("SoftHSM"): + self.skipTest("SoftHSM does not support CKM_XOR_BASE_AND_DATA") + + # generate data to xor with + xorData = list(self.session.generateRandom(32)) + + # xor key with data + keyID = (0x22,) + derivedKeyTemplate = self.genericKeyTemplate + [ + (PyKCS11.CKA_VALUE_LEN, 32), + (PyKCS11.CKA_ID, keyID) + ] + mechanism = PyKCS11.XOR_BASE_AND_DATA_Mechanism(xorData) + derivedKey = self.session.deriveKey( + self.baseKey, derivedKeyTemplate, mechanism) + self.assertIsNotNone(derivedKey) + + # check derived key's value + baseKeyValue = self.getCkaValue(self.baseKey) + derivedKeyValue = self.getCkaValue(derivedKey) + expectedValue = map(lambda x, y: x ^ y, baseKeyValue, xorData) + + # match: check values + self.assertSequenceEqual(list(expectedValue), derivedKeyValue) + + # cleanup + self.session.destroyObject(derivedKey)