Skip to content

Commit

Permalink
Add simple derivation mechanisms support
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
fuzzykat authored and LudovicRousseau committed Aug 7, 2024
1 parent 46589ad commit 4b5cc4a
Show file tree
Hide file tree
Showing 4 changed files with 289 additions and 2 deletions.
69 changes: 69 additions & 0 deletions PyKCS11/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions src/opensc/pkcs11.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down
49 changes: 49 additions & 0 deletions src/pykcs11.i
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand All @@ -275,6 +283,27 @@ typedef struct CK_DATE{
}
}

// typemap for CK_BYTE_PTR (unsigned char*) mechanism parameters
%typemap(in) unsigned char* {
vector<unsigned char> *vect;
// If the value being set is of ckbytelist type:
int res = SWIG_ConvertPtr($input, (void **)&vect, $descriptor(vector<unsigned char> *), 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<unsigned char> *vect;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
165 changes: 163 additions & 2 deletions test/test_derive.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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"

Expand Down Expand Up @@ -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,)
Expand Down Expand Up @@ -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)

0 comments on commit 4b5cc4a

Please sign in to comment.