Skip to content

Add AES Key Derivation Support (ECB,CBC Encrypt) #97

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dev-requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ setuptools_scm
# Used for tests
oscrypto
cryptography
parameterized

sphinx
sphinx-rtd-theme
Expand Down
1 change: 1 addition & 0 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ markupsafe==1.1.1 # via jinja2
mccabe==0.6.1 # via flake8
oscrypto==1.2.0 # via -r dev-requirements.in
packaging==20.1 # via sphinx
parameterized==0.7.4 # via -r dev-requirements.in
pycodestyle==2.5.0 # via flake8
pycparser==2.19 # via cffi
pyflakes==2.1.1 # via flake8
Expand Down
18 changes: 18 additions & 0 deletions pkcs11/_pkcs11.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ cdef class MechanismWithParam:
cdef CK_RSA_PKCS_OAEP_PARAMS *oaep_params
cdef CK_RSA_PKCS_PSS_PARAMS *pss_params
cdef CK_ECDH1_DERIVE_PARAMS *ecdh1_params
cdef CK_KEY_DERIVATION_STRING_DATA *aes_ecb_params
cdef CK_AES_CBC_ENCRYPT_DATA_PARAMS *aes_cbc_params
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is getting to be a good indication that the mechanism types above should be implemented as subclasses of MechanismWithParam (which should be renamed), mechanism_param keyword should be removed and this class allowed as a parameter to mechanism.

This would allow mechanism=RSA_PKCS, mechanism=Mechanism(AES_CBC, param=iv) or mechanism=mechanisms.RSA_PKCS_OAEP(params=...) with proper typing and a much better point for implementing extensions beyond this package.

That doesn't have to be done now, just a note.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So shall this be added as a feature request, or do you want to take this up now?

If one has been created, I could add its link here in order to complete the tracking of this suggestion.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you wanted to give refactoring it a go, I'd be keen. I've opened #98 to track it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi Danni, if it's okay with you, I'll attempt that once these changes have been merged.


# Unpack mechanism parameters
if mechanism is Mechanism.RSA_PKCS_OAEP:
Expand Down Expand Up @@ -163,6 +165,22 @@ cdef class MechanismWithParam:
ecdh1_params.pPublicData = public_data
ecdh1_params.ulPublicDataLen = <CK_ULONG> len(public_data)

elif mechanism is Mechanism.AES_ECB_ENCRYPT_DATA:
paramlen = sizeof(CK_KEY_DERIVATION_STRING_DATA)
self.param = aes_ecb_params = \
<CK_KEY_DERIVATION_STRING_DATA *> PyMem_Malloc(paramlen)
aes_ecb_params.pData = <CK_BYTE *> param
aes_ecb_params.ulLen = len(param)

elif mechanism is Mechanism.AES_CBC_ENCRYPT_DATA:
paramlen = sizeof(CK_AES_CBC_ENCRYPT_DATA_PARAMS)
self.param = aes_cbc_params = \
<CK_AES_CBC_ENCRYPT_DATA_PARAMS *> PyMem_Malloc(paramlen)
(iv, data) = param
aes_cbc_params.iv = iv[:16]
aes_cbc_params.pData = <CK_BYTE *> data
aes_cbc_params.length = len(data)

elif isinstance(param, bytes):
self.data.pParameter = <CK_BYTE *> param
paramlen = len(param)
Expand Down
9 changes: 9 additions & 0 deletions pkcs11/_pkcs11_defn.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,15 @@ cdef extern from '../extern/cryptoki.h':
CK_ULONG ulPublicDataLen
CK_BYTE *pPublicData

ctypedef struct CK_KEY_DERIVATION_STRING_DATA:
CK_BYTE *pData
CK_ULONG ulLen

ctypedef struct CK_AES_CBC_ENCRYPT_DATA_PARAMS:
CK_BYTE iv[16]
CK_BYTE *pData
CK_ULONG length

cdef struct CK_FUNCTION_LIST:
CK_VERSION version
## pointers to library functions are stored here
Expand Down
209 changes: 209 additions & 0 deletions tests/test_aes.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from . import TestCase, requires, FIXME

from parameterized import parameterized
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing from dev-requirements.{in,txt}


class AESTests(TestCase):

Expand Down Expand Up @@ -123,3 +124,211 @@ def test_wrap(self):

self.assertEqual(key[pkcs11.Attribute.VALUE],
key2[pkcs11.Attribute.VALUE])

@parameterized.expand([
("POSITIVE_128_BIT", 128, 16, TestCase.assertIsNotNone),
("POSITIVE_128_BIT_LONG_IV", 128, 32, TestCase.assertIsNotNone),
("NEGATIVE_128_BIT_BAD_IV", 128, 15, TestCase.assertIsNone),
("POSITIVE_256_BIT_LONG_IV", 256, 32, TestCase.assertIsNotNone),
("NEGATIVE_256_BIT_SHORT_IV", 256, 16, TestCase.assertIsNone),
("NEGATIVE_256_BIT_BAD_IV", 256, 31, TestCase.assertIsNone),
])
@requires(Mechanism.AES_ECB_ENCRYPT_DATA)
@FIXME.opencryptoki # can't set key attributes
def test_derive_using_ecb_encrypt(self, test_type, test_key_length, iv_length, assert_fn):
"""Function to test AES Key Derivation using the ECB_ENCRYPT Mechanism.

Refer to Section 2.15 of http://docs.oasis-open.org/pkcs11/pkcs11-curr/v2.40/errata01/os/pkcs11-curr-v2.40-errata01-os-complete.html#_Toc441850521
"""

# Create the Master Key
capabilities = pkcs11.defaults.DEFAULT_KEY_CAPABILITIES[pkcs11.KeyType.AES]
capabilities |= pkcs11.MechanismFlag.DERIVE
key = self.session.generate_key(pkcs11.KeyType.AES, key_length=test_key_length,
capabilities=capabilities,
template={
pkcs11.Attribute.EXTRACTABLE: True,
pkcs11.Attribute.DERIVE: True,
pkcs11.Attribute.SENSITIVE: False,
})

self.assertTrue(key is not None, "Failed to create {}-bit Master Key".format(test_key_length))

# Derive a Key from the Master Key
iv = b'0' * iv_length
try:
derived_key = key.derive_key(pkcs11.KeyType.AES, key_length=test_key_length,
capabilities=capabilities,
mechanism=Mechanism.AES_ECB_ENCRYPT_DATA,
mechanism_param=iv,
template={
pkcs11.Attribute.EXTRACTABLE: True,
pkcs11.Attribute.SENSITIVE: False,
})
except (pkcs11.exceptions.MechanismParamInvalid,
pkcs11.exceptions.FunctionFailed) as e:
derived_key = None

assert_fn(self, derived_key, "{}-bit Key Derivation Failure".format(test_key_length))

@parameterized.expand([
("POSITIVE_128_BIT", 128, 16),
("POSITIVE_256_BIT_LONG_IV", 256, 32),
])
@requires(Mechanism.AES_ECB_ENCRYPT_DATA)
@FIXME.opencryptoki # can't set key attributes
def test_encrypt_with_key_derived_using_ecb_encrypt(self, test_type, test_key_length, iv_length):
"""Function to test Data Encryption/Decryption using a Derived AES Key.

Function to test Data Encryption/Decryption using an AES Key
Derived by the ECB_ENCRYPT Mechanism.

Refer to Section 2.15 of http://docs.oasis-open.org/pkcs11/pkcs11-curr/v2.40/errata01/os/pkcs11-curr-v2.40-errata01-os-complete.html#_Toc441850521
"""

# Create the Master Key
capabilities = pkcs11.defaults.DEFAULT_KEY_CAPABILITIES[pkcs11.KeyType.AES]
capabilities |= pkcs11.MechanismFlag.DERIVE
key = self.session.generate_key(pkcs11.KeyType.AES, key_length=test_key_length,
capabilities=capabilities,
template={
pkcs11.Attribute.EXTRACTABLE: True,
pkcs11.Attribute.DERIVE: True,
pkcs11.Attribute.SENSITIVE: False,
})

self.assertTrue(key is not None, "Failed to create {}-bit Master Key".format(test_key_length))

# Derive a Key from the Master Key
iv = b'0' * iv_length
try:
derived_key = key.derive_key(pkcs11.KeyType.AES, key_length=test_key_length,
capabilities=capabilities,
mechanism=Mechanism.AES_ECB_ENCRYPT_DATA,
mechanism_param=iv,
template={
pkcs11.Attribute.EXTRACTABLE: True,
pkcs11.Attribute.SENSITIVE: False,
})
except (pkcs11.exceptions.MechanismParamInvalid,
pkcs11.exceptions.FunctionFailed) as e:
derived_key = None

self.assertTrue(derived_key is not None, "Failed to derive {}-bit Derived Key".format(test_key_length))

# Test capability of Key to Encrypt/Decrypt data
data = b'HELLO WORLD' * 1024

iv = self.session.generate_random(128)
crypttext = self.key.encrypt(data, mechanism_param=iv)
text = self.key.decrypt(crypttext, mechanism_param=iv)

self.assertEqual(text, data)

@parameterized.expand([
("POSITIVE_128_BIT", 128, 16, 16, TestCase.assertIsNotNone),
("POSITIVE_128_BIT_LONG_DATA", 128, 16, 64, TestCase.assertIsNotNone),
("NEGATIVE_128_BIT_BAD_IV", 128, 15, 16, TestCase.assertIsNone),
("NEGATIVE_128_BIT_BAD_DATA", 128, 16, 31, TestCase.assertIsNone),
("POSITIVE_256_BIT", 256, 16, 32, TestCase.assertIsNotNone),
("POSITIVE_256_BIT_LONG_DATA", 256, 16, 64, TestCase.assertIsNotNone),
("NEGATIVE_256_BIT_BAD_IV", 256, 15, 16, TestCase.assertIsNone),
("NEGATIVE_256_BIT_BAD_DATA", 256, 16, 31, TestCase.assertIsNone),
("NEGATIVE_256_BIT_SHORT_DATA", 256, 16, 16, TestCase.assertIsNone),
])
@requires(Mechanism.AES_CBC_ENCRYPT_DATA)
@FIXME.opencryptoki # can't set key attributes
def test_derive_using_cbc_encrypt(self, test_type, test_key_length, iv_length, data_length, assert_fn):
"""Function to test AES Key Derivation using the CBC_ENCRYPT Mechanism.

Refer to Section 2.15 of http://docs.oasis-open.org/pkcs11/pkcs11-curr/v2.40/errata01/os/pkcs11-curr-v2.40-errata01-os-complete.html#_Toc441850521
"""

# Create the Master Key
capabilities = pkcs11.defaults.DEFAULT_KEY_CAPABILITIES[pkcs11.KeyType.AES]
capabilities |= pkcs11.MechanismFlag.DERIVE
key = self.session.generate_key(pkcs11.KeyType.AES, key_length=test_key_length,
capabilities=capabilities,
template={
pkcs11.Attribute.EXTRACTABLE: True,
pkcs11.Attribute.DERIVE: True,
pkcs11.Attribute.SENSITIVE: False,
})

self.assertTrue(key is not None, "Failed to create {}-bit Master Key".format(test_key_length))

# Derive a Key from the Master Key
iv = b'0' * iv_length
data = b'1' * data_length
try:
derived_key = key.derive_key(pkcs11.KeyType.AES, key_length=test_key_length,
capabilities=capabilities,
mechanism=Mechanism.AES_CBC_ENCRYPT_DATA,
mechanism_param=(iv, data),
template={
pkcs11.Attribute.EXTRACTABLE: True,
pkcs11.Attribute.SENSITIVE: False,
})
except (pkcs11.exceptions.MechanismParamInvalid,
pkcs11.exceptions.FunctionFailed,
IndexError) as e:
derived_key = None

assert_fn(self, derived_key, "{}-bit Key Derivation Failure".format(test_key_length))

@parameterized.expand([
("POSITIVE_128_BIT", 128, 16, 16),
("POSITIVE_256_BIT", 256, 16, 32),
("POSITIVE_256_BIT_LONG_DATA", 256, 16, 64),
])
@requires(Mechanism.AES_CBC_ENCRYPT_DATA)
@FIXME.opencryptoki # can't set key attributes
def test_encrypt_with_key_derived_using_cbc_encrypt(self, test_type, test_key_length, iv_length, data_length):
"""Function to test Data Encryption/Decryption using a Derived AES Key.

Function to test Data Encryption/Decryption using an AES Key
Derived by the CBC_ENCRYPT Mechanism.

Refer to Section 2.15 of http://docs.oasis-open.org/pkcs11/pkcs11-curr/v2.40/errata01/os/pkcs11-curr-v2.40-errata01-os-complete.html#_Toc441850521
"""

# Create the Master Key
capabilities = pkcs11.defaults.DEFAULT_KEY_CAPABILITIES[pkcs11.KeyType.AES]
capabilities |= pkcs11.MechanismFlag.DERIVE
key = self.session.generate_key(pkcs11.KeyType.AES, key_length=test_key_length,
capabilities=capabilities,
template={
pkcs11.Attribute.EXTRACTABLE: True,
pkcs11.Attribute.DERIVE: True,
pkcs11.Attribute.SENSITIVE: False,
})

self.assertTrue(key is not None, "Failed to create {}-bit Master Key".format(test_key_length))

# Derive a Key from the Master Key
iv = b'0' * iv_length
data = b'1' * data_length
try:
derived_key = key.derive_key(pkcs11.KeyType.AES, key_length=test_key_length,
capabilities=capabilities,
mechanism=Mechanism.AES_CBC_ENCRYPT_DATA,
mechanism_param=(iv, data),
template={
pkcs11.Attribute.EXTRACTABLE: True,
pkcs11.Attribute.SENSITIVE: False,
})
except (pkcs11.exceptions.MechanismParamInvalid,
pkcs11.exceptions.FunctionFailed,
IndexError) as e:
derived_key = None

self.assertTrue(derived_key is not None, "Failed to derive {}-bit Derived Key".format(test_key_length))

# Test capability of Key to Encrypt/Decrypt data
data = b'HELLO WORLD' * 1024

iv = self.session.generate_random(128)
crypttext = self.key.encrypt(data, mechanism_param=iv)
text = self.key.decrypt(crypttext, mechanism_param=iv)

self.assertEqual(text, data)