Add engine support for sign operation

This change adds the sign operation functionality
to the cryptography engine.
This commit is contained in:
Dane 2017-08-03 14:56:28 -04:00 committed by Dane Fichter
parent df74c854b7
commit df06aa8ad8
3 changed files with 436 additions and 2 deletions

View File

@ -154,3 +154,33 @@ class CryptographicEngine(object):
Returns: Returns:
bytes: the bytes of the decrypted data bytes: the bytes of the decrypted data
""" """
@abstractmethod
def sign(self,
digital_signature_algorithm,
cryptographic_algorithm,
hash_algorithm,
signing_key,
data):
"""
Generate a signature for the provided data.
Args:
digital_signature_algorithm (DigitalSignatureAlgorithm): An
enumeration specifying the asymmetric cryptographic algorithm
and hashing algorithm to use for the signature operation. Can
be None if cryptographic_algorithm and hash_algorithm are set.
cryptographic_algorithm (CryptographicAlgorithm): An enumeration
specifying the asymmetric cryptographic algorithm to use for
the signature operation. Can be None if
digital_signature_algorithm is set.
hash_algorithm (HashingAlgorithm): An enumeration specifying the
hash algorithm to use for the signature operation. Can be None
if digital_signature_algorithm is set.
signing_key (bytes): The bytes of the private key to use for the
signature operation.
data (bytes): The data to be signed.
Returns:
bytes: the bytes of the signature data
"""

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import binascii
import logging import logging
import os import os
@ -89,7 +90,8 @@ class CryptographyEngine(api.CryptographicEngine):
} }
self._asymmetric_padding_methods = { self._asymmetric_padding_methods = {
enums.PaddingMethod.OAEP: asymmetric_padding.OAEP, enums.PaddingMethod.OAEP: asymmetric_padding.OAEP,
enums.PaddingMethod.PKCS1v15: asymmetric_padding.PKCS1v15 enums.PaddingMethod.PKCS1v15: asymmetric_padding.PKCS1v15,
enums.PaddingMethod.PSS: asymmetric_padding.PSS
} }
self._symmetric_padding_methods = { self._symmetric_padding_methods = {
enums.PaddingMethod.ANSI_X923: symmetric_padding.ANSIX923, enums.PaddingMethod.ANSI_X923: symmetric_padding.ANSIX923,
@ -105,6 +107,21 @@ class CryptographyEngine(api.CryptographicEngine):
enums.BlockCipherMode.GCM enums.BlockCipherMode.GCM
] ]
self._digital_signature_algorithms = {
enums.DigitalSignatureAlgorithm.MD5_WITH_RSA_ENCRYPTION:
(hashes.MD5, enums.CryptographicAlgorithm.RSA),
enums.DigitalSignatureAlgorithm.SHA1_WITH_RSA_ENCRYPTION:
(hashes.SHA1, enums.CryptographicAlgorithm.RSA),
enums.DigitalSignatureAlgorithm.SHA224_WITH_RSA_ENCRYPTION:
(hashes.SHA224, enums.CryptographicAlgorithm.RSA),
enums.DigitalSignatureAlgorithm.SHA256_WITH_RSA_ENCRYPTION:
(hashes.SHA256, enums.CryptographicAlgorithm.RSA),
enums.DigitalSignatureAlgorithm.SHA384_WITH_RSA_ENCRYPTION:
(hashes.SHA384, enums.CryptographicAlgorithm.RSA),
enums.DigitalSignatureAlgorithm.SHA512_WITH_RSA_ENCRYPTION:
(hashes.SHA512, enums.CryptographicAlgorithm.RSA)
}
def create_symmetric_key(self, algorithm, length): def create_symmetric_key(self, algorithm, length):
""" """
Create a symmetric key. Create a symmetric key.
@ -1170,3 +1187,125 @@ class CryptographyEngine(api.CryptographicEngine):
"Wrapping method '{0}' is not a supported key wrapping " "Wrapping method '{0}' is not a supported key wrapping "
"method.".format(wrapping_method) "method.".format(wrapping_method)
) )
def _create_RSA_private_key(self,
bytes):
"""
Instantiates an RSA key from bytes.
Args:
bytes (byte string): Bytes of RSA private key.
Returns:
private_key
(cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey):
RSA private key created from key bytes.
"""
try:
private_key = serialization.load_pem_private_key(
binascii.unhexlify(bytes),
password=None,
backend=default_backend()
)
return private_key
except:
private_key = serialization.load_der_private_key(
binascii.unhexlify(bytes),
password=None,
backend=default_backend()
)
return private_key
def sign(self,
digital_signature_algorithm,
crypto_alg,
hash_algorithm,
padding,
signing_key,
data):
"""
Args:
digital_signature_algorithm (DigitalSignatureAlgorithm): An
enumeration specifying the asymmetric cryptographic algorithm
and hashing algorithm to use for the signature operation. Can
be None if cryptographic_algorithm and hash_algorithm are set.
crypto_alg (CryptographicAlgorithm): An enumeration
specifying the asymmetric cryptographic algorithm to use for
the signature operation. Can be None if
digital_signature_algorithm is set.
hash_algorithm (HashingAlgorithm): An enumeration specifying the
hash algorithm to use for the signature operation. Can be None
if digital_signature_algorithm is set.
padding (PaddingMethod): An enumeration specifying the asymmetric
padding method to use for the signature operation.
signing_key (bytes): The bytes of the private key to use for the
signature operation.
data (bytes): The data to be signed.
Returns:
signature (bytes): the bytes of the signature data
Raises:
CryptographicFailure: Raised when an error occurs during signature
creation.
InvalidField: Raised when an unsupported hashing or cryptographic
algorithm is specified.
"""
if digital_signature_algorithm:
(hash_alg, crypto_alg) = self._digital_signature_algorithms.get(
digital_signature_algorithm,
(None, None)
)
elif crypto_alg and hash_algorithm:
hash_alg = self._encryption_hash_algorithms.get(
hash_algorithm, None
)
else:
raise exceptions.InvalidField(
'For signing, either a digital signature algorithm or a hash'
' algorithm and a cryptographic algorithm must be specified.'
)
if crypto_alg == enums.CryptographicAlgorithm.RSA:
try:
key = self._create_RSA_private_key(signing_key)
except:
raise exceptions.InvalidField('Unable to deserialize key '
'bytes, unknown format.')
else:
raise exceptions.InvalidField(
'For signing, an RSA key must be used.'
)
if padding:
padding_method = self._asymmetric_padding_methods.get(
padding, None
)
else:
raise exceptions.InvalidField(
'For signing, a padding method must be specified.'
)
if padding == enums.PaddingMethod.PSS:
signature = key.sign(
data,
asymmetric_padding.PSS(
mgf=asymmetric_padding.MGF1(hash_alg()),
salt_length=asymmetric_padding.PSS.MAX_LENGTH
),
hash_alg()
)
elif padding == enums.PaddingMethod.PKCS1v15:
signature = key.sign(
data,
padding_method(),
hash_alg()
)
else:
raise exceptions.InvalidField(
"Padding method '{0}' is not a supported signature "
"padding method.".format(padding)
)
return signature

View File

@ -13,14 +13,18 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import binascii
import mock import mock
import pytest import pytest
import testtools import testtools
from cryptography.hazmat import backends from cryptography.hazmat import backends
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.ciphers import algorithms from cryptography.hazmat.primitives.ciphers import algorithms
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from kmip.core import enums from kmip.core import enums
from kmip.core import exceptions from kmip.core import exceptions
@ -904,6 +908,113 @@ class TestCryptographyEngine(testtools.TestCase):
*args *args
) )
def test_sign_no_alg(self):
"""
Test that an InvalidField exception is raised when sign is
called without sufficient crypto parameters.
"""
engine = crypto.CryptographyEngine()
args = (None, None, None, None, None, None)
self.assertRaisesRegexp(
exceptions.InvalidField,
'For signing, either a digital signature algorithm or a hash'
' algorithm and a cryptographic algorithm must be specified.',
engine.sign,
*args
)
def test_sign_non_RSA(self):
"""
Test that an InvalidField exception is raised when sign is
called with a crypto algorithm other than RSA.
"""
engine = crypto.CryptographyEngine()
args = (
None,
enums.CryptographicAlgorithm.TRIPLE_DES,
enums.HashingAlgorithm.MD5,
None,
None,
None
)
self.assertRaisesRegexp(
exceptions.InvalidField,
'For signing, an RSA key must be used.',
engine.sign,
*args
)
def test_sign_invalid_padding(self):
"""
Test that an InvalidField exception is raised when sign is
called with an unsupported padding algorithm.
"""
engine = crypto.CryptographyEngine()
args = (
None,
enums.CryptographicAlgorithm.RSA,
enums.HashingAlgorithm.MD5,
enums.PaddingMethod.OAEP,
DER_RSA_KEY,
None
)
self.assertRaisesRegexp(
exceptions.InvalidField,
"Padding method 'PaddingMethod.OAEP' is not a supported"
" signature padding method.",
engine.sign,
*args
)
def test_sign_no_padding(self):
"""
Test that an InvalidField exception is raised when sign is
called without a padding algorithm.
"""
engine = crypto.CryptographyEngine()
args = (
None,
enums.CryptographicAlgorithm.RSA,
enums.HashingAlgorithm.MD5,
None,
DER_RSA_KEY,
None
)
self.assertRaisesRegexp(
exceptions.InvalidField,
'For signing, a padding method must be specified.',
engine.sign,
*args
)
def test_sign_invalid_key_bytes(self):
"""
Test that an InvalidField exception is raised when
sign is called with invalid key bytes.
"""
engine = crypto.CryptographyEngine()
args = (
None,
enums.CryptographicAlgorithm.RSA,
enums.HashingAlgorithm.MD5,
enums.PaddingMethod.PKCS1v15,
'thisisnotavalidkey',
None
)
self.assertRaisesRegexp(
exceptions.InvalidField,
'Unable to deserialize key '
'bytes, unknown format.',
engine.sign,
*args
)
# TODO(peter-hamilton): Replace this with actual fixture files from NIST CAPV. # TODO(peter-hamilton): Replace this with actual fixture files from NIST CAPV.
# Most of these test vectors were obtained from the pyca/cryptography test # Most of these test vectors were obtained from the pyca/cryptography test
@ -2140,3 +2251,157 @@ def test_wrap_key(wrapping_parameters):
) )
assert wrapping_parameters.get('wrapped_data') == result assert wrapping_parameters.get('wrapped_data') == result
# Test vectors obtained from pyca/cryptography
# https://cryptography.io/en/latest/
DER_RSA_KEY = ('3082025e02010002818100aebac1b9a174315d27cc3c201e215789'
'4372d6450d4cf80ce0ebcf5169519b9e8550036f4abe0fe4f94fbf'
'9cca606f39743365499611ba3f25a9a47158ba05214b655f4258a4'
'c29516becaa583f2d26650696ad6fc03d5b47d3aba9c5479fdb047'
'7d29513399cb19283ccdc28dbb23b7c7eee4b35dc940daca0055dc'
'd28f503b02030100010281810092890942d6c68d47a4c2c181e602'
'ec58af7a357c7fa5173a25bf5d84d7209bb41bf5788bf350e61f8f'
'7e7421d80f7bf7e11de14a0f531ab12eb2d0b84642eb5d181170c2'
'c58aabbd6754842fafee57fef2f545d09fdc664902e55baced5a3c'
'6d26f3465859d33a33a555537daf2263aaef28354c8b53513145a7'
'e228824dabb1024100d3aa237e8942b93d56a681254c27be1f4a49'
'6ca4a87fc0604b0cff8f980e742d2bbb91b88a247b6ebbed01458c'
'4afdb68c0f8c6d4a37e028c5fcb3a6a39ca64f024100d354168c61'
'9c836e8597fef50193a6f42607952a1c87ebae91db5043b8855072'
'b4e92af5dcedb2148773dfbd217bafc8dc9da8ae8e757e7248c1e5'
'13a144685502410090fda214c2b7b726825dca679f3436333ef2ee'
'fe180272e84360e30b1d11019a13b4080d0e6c1135787bd07c30af'
'09feeb10979421dc06ac477b6420c940bc570240164de8b7565213'
'9925a67e3553be46bfbc07ced98bfb5887ab434f7c664c43ca6787'
'b88e0c8c55e04ecf8f0cc22cf0c7ad69427571f9baa7cb4013b277'
'b1e5a5024100cae150f5fa559b2e2c39444e0f5c651034092ac97b'
'ac10d528dd15dfda254cb06bef41e39881f7e7496910b4655659dc'
'842d30b9ae2759f3c2cd41c79a3684ec')
PEM_RSA_KEY = ('2d2d2d2d2d424547494e205253412050524956415445204b45592d'
'2d2d2d2d0a4d4949435867494241414b4267514375757347356f58'
'51785853664d504341654956654a51334c575251314d2b417a6736'
'383952615647626e6f56514132394b0a76672f6b2b552b2f6e4d70'
'67627a6c304d32564a6c6847365079577070484659756755685332'
'5666516c696b777055577673716c672f4c535a6c4270617462380a'
'41395730665471366e4652352f62424866536c524d356e4c475367'
'387a634b4e75794f33782b376b7331334a514e724b41465863306f'
'39514f774944415141420a416f4742414a4b4a43554c57786f3148'
'704d4c426765594337466976656a5638663655584f69572f585954'
'58494a7530472f5634692f4e5135682b50666e51680a3241393739'
'2b456434556f50557871784c724c5175455a433631305945584443'
'785971727657645568432b76376c662b38765646304a2f635a6b6b'
'43355675730a37566f386253627a526c685a307a6f7a7056565466'
'613869593672764b44564d69314e524d55576e3469694354617578'
'416b454130366f6a666f6c43755431570a706f456c5443652b4830'
'704a624b536f6638426753777a2f6a35674f644330727535473469'
'695237627276744155574d537632326a412b4d62556f3334436a46'
'0a2f4c4f6d6f35796d54774a42414e4e55466f78686e494e75685a'
'662b395147547076516d42355571484966727270486255454f3468'
'564279744f6b7139647a740a7368534863392b3949587576794e79'
'64714b364f64583579534d486c4536464561465543515143512f61'
'4955777265334a6f4a64796d65664e44597a50764c750a2f686743'
'63756844594f4d4c485245426d684f304341304f62424531654876'
'516644437643663772454a655549647747724564375a43444a514c'
'7858416b41570a54656933566c49546d53576d666a5654766b612f'
'7641664f3259763757496572513039385a6b7844796d6548754934'
'4d6a46586754732b50444d4973384d65740a61554a3163666d3670'
'38744145374a337365576c416b454179754651396670566d793473'
'4f55524f4431786c4544514a4b736c37724244564b4e305633396f'
'6c0a544c42723730486a6d49483335306c70454c526c566c6e6368'
'4330777561346e576650437a5548486d6a614537413d3d0a2d2d2d'
'2d2d454e44205253412050524956415445204b45592d2d2d2d2d0a')
RSA_private_key = serialization.load_der_private_key(
binascii.unhexlify(DER_RSA_KEY),
password=None,
backend=default_backend()
)
RSA_public_key = RSA_private_key.public_key()
SIGN_TEST_DATA = (b'\x01\x02\x03\x04\x05\x06\x07\x08'
b'\x09\x10\x11\x12\x13\x14\x15\x16')
@pytest.fixture(
scope='function',
params=[
{'digital_signature_algorithm':
enums.DigitalSignatureAlgorithm.MD5_WITH_RSA_ENCRYPTION,
'crypto_alg': None,
'hash_algorithm': None,
'padding': enums.PaddingMethod.PSS,
'key': DER_RSA_KEY,
'verify_args': (padding.PSS(
mgf=padding.MGF1(hashes.MD5()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.MD5())},
{'digital_signature_algorithm':
enums.DigitalSignatureAlgorithm.SHA1_WITH_RSA_ENCRYPTION,
'crypto_alg': None,
'hash_algorithm': None,
'padding': enums.PaddingMethod.PKCS1v15,
'key': PEM_RSA_KEY,
'verify_args': (padding.PKCS1v15(), hashes.SHA1())},
{'digital_signature_algorithm':
enums.DigitalSignatureAlgorithm.SHA224_WITH_RSA_ENCRYPTION,
'crypto_alg': None,
'hash_algorithm': None,
'padding': enums.PaddingMethod.PSS,
'key': DER_RSA_KEY,
'verify_args': (padding.PSS(
mgf=padding.MGF1(hashes.SHA224()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA224())},
{'digital_signature_algorithm':
enums.DigitalSignatureAlgorithm.SHA256_WITH_RSA_ENCRYPTION,
'crypto_alg': None,
'hash_algorithm': None,
'padding': enums.PaddingMethod.PKCS1v15,
'key': PEM_RSA_KEY,
'verify_args': (padding.PKCS1v15(), hashes.SHA256())},
{'digital_signature_algorithm':
enums.DigitalSignatureAlgorithm.SHA384_WITH_RSA_ENCRYPTION,
'crypto_alg': None,
'hash_algorithm': None,
'padding': enums.PaddingMethod.PSS,
'key': DER_RSA_KEY,
'verify_args': (padding.PSS(
mgf=padding.MGF1(hashes.SHA384()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA384())},
{'digital_signature_algorithm': None,
'crypto_alg': enums.CryptographicAlgorithm.RSA,
'hash_algorithm': enums.HashingAlgorithm.SHA_512,
'padding': enums.PaddingMethod.PKCS1v15,
'key': PEM_RSA_KEY,
'verify_args': (padding.PKCS1v15(), hashes.SHA512())}
]
)
def signing_parameters(request):
return request.param
def test_sign(signing_parameters):
engine = crypto.CryptographyEngine()
result = engine.sign(
signing_parameters.get('digital_signature_algorithm'),
signing_parameters.get('crypto_alg'),
signing_parameters.get('hash_algorithm'),
signing_parameters.get('padding'),
signing_parameters.get('key'),
SIGN_TEST_DATA
)
RSA_public_key.verify(
result,
SIGN_TEST_DATA,
signing_parameters.get('verify_args')[0],
signing_parameters.get('verify_args')[1]
)