mirror of https://github.com/OpenKMIP/PyKMIP.git
Add engine support for sign operation
This change adds the sign operation functionality to the cryptography engine.
This commit is contained in:
parent
df74c854b7
commit
df06aa8ad8
|
@ -154,3 +154,33 @@ class CryptographicEngine(object):
|
|||
Returns:
|
||||
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
|
||||
"""
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import binascii
|
||||
import logging
|
||||
import os
|
||||
|
||||
|
@ -89,7 +90,8 @@ class CryptographyEngine(api.CryptographicEngine):
|
|||
}
|
||||
self._asymmetric_padding_methods = {
|
||||
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 = {
|
||||
enums.PaddingMethod.ANSI_X923: symmetric_padding.ANSIX923,
|
||||
|
@ -105,6 +107,21 @@ class CryptographyEngine(api.CryptographicEngine):
|
|||
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):
|
||||
"""
|
||||
Create a symmetric key.
|
||||
|
@ -1170,3 +1187,125 @@ class CryptographyEngine(api.CryptographicEngine):
|
|||
"Wrapping method '{0}' is not a supported key wrapping "
|
||||
"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
|
||||
|
|
|
@ -13,14 +13,18 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import binascii
|
||||
import mock
|
||||
import pytest
|
||||
import testtools
|
||||
|
||||
from cryptography.hazmat import backends
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
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 import hashes
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
|
||||
from kmip.core import enums
|
||||
from kmip.core import exceptions
|
||||
|
@ -904,6 +908,113 @@ class TestCryptographyEngine(testtools.TestCase):
|
|||
*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.
|
||||
# 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
|
||||
|
||||
# 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]
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue