diff options
29 files changed, 1340 insertions, 12 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 93423988..13c62de5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,12 @@ Changelog :doc:`/hazmat/backends/commoncrypto` and :doc:`/hazmat/backends/openssl`. * Added ``AES`` :class:`~cryptography.hazmat.primitives.ciphers.modes.CTR` support to the OpenSSL backend when linked against 0.9.8. +* Added + :class:`~cryptography.hazmat.backends.interfaces.PKCS8SerializationBackend` + and + :class:`~cryptography.hazmat.backends.interfaces.TraditionalOpenSSLSerializationBackend` + support to the :doc:`/hazmat/backends/openssl`. + 0.4 - 2014-05-03 ~~~~~~~~~~~~~~~~ diff --git a/cryptography/hazmat/backends/interfaces.py b/cryptography/hazmat/backends/interfaces.py index 97a7a4fd..ba02bbd2 100644 --- a/cryptography/hazmat/backends/interfaces.py +++ b/cryptography/hazmat/backends/interfaces.py @@ -196,6 +196,16 @@ class TraditionalOpenSSLSerializationBackend(object): @six.add_metaclass(abc.ABCMeta) +class PKCS8SerializationBackend(object): + @abc.abstractmethod + def load_pkcs8_pem_private_key(self, data, password): + """ + Load a private key from PEM encoded data, using password if the data + is encrypted. + """ + + +@six.add_metaclass(abc.ABCMeta) class CMACBackend(object): @abc.abstractmethod def cmac_algorithm_supported(self, algorithm): @@ -208,3 +218,39 @@ class CMACBackend(object): """ Create a CMACContext for calculating a message authentication code. """ + + +@six.add_metaclass(abc.ABCMeta) +class EllipticCurveBackend(object): + @abc.abstractmethod + def elliptic_curve_signature_algorithm_supported( + self, signature_algorithm, curve + ): + """ + Returns True if the backend supports the named elliptic curve with the + specified signature algorithm. + """ + + @abc.abstractmethod + def elliptic_curve_supported(self, curve): + """ + Returns True if the backend supports the named elliptic curve. + """ + + @abc.abstractmethod + def generate_elliptic_curve_private_key(self, curve): + """ + Return an object conforming to the EllipticCurvePrivateKey interface. + """ + + @abc.abstractmethod + def elliptic_curve_public_key_from_numbers(self, numbers): + """ + Return an EllipticCurvePublicKey provider using the given numbers. + """ + + @abc.abstractmethod + def elliptic_curve_private_key_from_numbers(self, numbers): + """ + Return an EllipticCurvePublicKey provider using the given numbers. + """ diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index 9387c933..c6bcbaaa 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -26,7 +26,8 @@ from cryptography.exceptions import ( ) from cryptography.hazmat.backends.interfaces import ( CMACBackend, CipherBackend, DSABackend, HMACBackend, HashBackend, - PBKDF2HMACBackend, RSABackend, TraditionalOpenSSLSerializationBackend + PBKDF2HMACBackend, PKCS8SerializationBackend, RSABackend, + TraditionalOpenSSLSerializationBackend ) from cryptography.hazmat.bindings.openssl.binding import Binding from cryptography.hazmat.primitives import hashes, interfaces @@ -55,6 +56,7 @@ _OpenSSLError = collections.namedtuple("_OpenSSLError", @utils.register_interface(PBKDF2HMACBackend) @utils.register_interface(RSABackend) @utils.register_interface(TraditionalOpenSSLSerializationBackend) +@utils.register_interface(PKCS8SerializationBackend) class Backend(object): """ OpenSSL API binding interfaces. @@ -770,6 +772,12 @@ class Backend(object): return _CMACContext(self, algorithm) def load_traditional_openssl_pem_private_key(self, data, password): + # OpenSSLs API for loading PKCS#8 certs can also load the traditional + # format so we just use that for both of them. + + return self.load_pkcs8_pem_private_key(data, password) + + def load_pkcs8_pem_private_key(self, data, password): mem_bio = self._bytes_to_bio(data) password_callback, password_func = self._pem_password_cb(password) @@ -786,10 +794,18 @@ class Backend(object): if not errors: raise ValueError("Could not unserialize key data.") - if errors[0][1:] == ( - self._lib.ERR_LIB_PEM, - self._lib.PEM_F_PEM_DO_HEADER, - self._lib.PEM_R_BAD_PASSWORD_READ + if ( + errors[0][1:] == ( + self._lib.ERR_LIB_PEM, + self._lib.PEM_F_PEM_DO_HEADER, + self._lib.PEM_R_BAD_PASSWORD_READ + ) + ) or ( + errors[0][1:] == ( + self._lib.ERR_LIB_PEM, + self._lib.PEM_F_PEM_READ_BIO_PRIVATEKEY, + self._lib.PEM_R_BAD_PASSWORD_READ + ) ): assert not password raise TypeError( @@ -804,13 +820,36 @@ class Backend(object): "Bad decrypt. Incorrect password?" ) - elif errors[0][1:] == ( - self._lib.ERR_LIB_PEM, - self._lib.PEM_F_PEM_GET_EVP_CIPHER_INFO, - self._lib.PEM_R_UNSUPPORTED_ENCRYPTION + elif errors[0][1:] in ( + ( + self._lib.ERR_LIB_PEM, + self._lib.PEM_F_PEM_GET_EVP_CIPHER_INFO, + self._lib.PEM_R_UNSUPPORTED_ENCRYPTION + ), + + ( + self._lib.ERR_LIB_EVP, + self._lib.EVP_F_EVP_PBE_CIPHERINIT, + self._lib.EVP_R_UNKNOWN_PBE_ALGORITHM + ) + ): + raise UnsupportedAlgorithm( + "PEM data is encrypted with an unsupported cipher", + _Reasons.UNSUPPORTED_CIPHER + ) + + elif any( + error[1:] == ( + self._lib.ERR_LIB_EVP, + self._lib.EVP_F_EVP_PKCS82PKEY, + self._lib.EVP_R_UNSUPPORTED_PRIVATE_KEY_ALGORITHM + ) + for error in errors ): raise UnsupportedAlgorithm( - "PEM data is encrypted with an unsupported cipher") + "Unsupported public key algorithm.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ) else: assert errors[0][1] in ( diff --git a/cryptography/hazmat/bindings/commoncrypto/binding.py b/cryptography/hazmat/bindings/commoncrypto/binding.py index 144bb099..ee7378ad 100644 --- a/cryptography/hazmat/bindings/commoncrypto/binding.py +++ b/cryptography/hazmat/bindings/commoncrypto/binding.py @@ -25,10 +25,16 @@ class Binding(object): """ _module_prefix = "cryptography.hazmat.bindings.commoncrypto." _modules = [ + "cf", "common_digest", "common_hmac", "common_key_derivation", "common_cryptor", + "secimport", + "secitem", + "seckey", + "seckeychain", + "sectransform", ] ffi = None @@ -45,6 +51,7 @@ class Binding(object): cls.ffi, cls.lib = build_ffi( module_prefix=cls._module_prefix, modules=cls._modules, + extra_link_args=["-framework", "Security"] ) @classmethod diff --git a/cryptography/hazmat/bindings/commoncrypto/cf.py b/cryptography/hazmat/bindings/commoncrypto/cf.py new file mode 100644 index 00000000..671963a3 --- /dev/null +++ b/cryptography/hazmat/bindings/commoncrypto/cf.py @@ -0,0 +1,114 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include <CoreFoundation/CoreFoundation.h> +""" + +TYPES = """ +typedef bool Boolean; +typedef signed long OSStatus; +typedef unsigned char UInt8; +typedef uint32_t UInt32; + +typedef const void * CFAllocatorRef; +const CFAllocatorRef kCFAllocatorDefault; +typedef const void * CFDataRef; +typedef signed long long CFIndex; +typedef ... *CFStringRef; +typedef ... *CFArrayRef; +typedef ... *CFBooleanRef; +typedef ... *CFErrorRef; +typedef ... *CFNumberRef; +typedef ... *CFTypeRef; +typedef ... *CFDictionaryRef; +typedef ... *CFMutableDictionaryRef; +typedef struct { + ...; +} CFDictionaryKeyCallBacks; +typedef struct { + ...; +} CFDictionaryValueCallBacks; +typedef struct { + ...; +} CFRange; + +typedef UInt32 CFStringEncoding; +enum { + kCFStringEncodingASCII = 0x0600 +}; + +enum { + kCFNumberSInt8Type = 1, + kCFNumberSInt16Type = 2, + kCFNumberSInt32Type = 3, + kCFNumberSInt64Type = 4, + kCFNumberFloat32Type = 5, + kCFNumberFloat64Type = 6, + kCFNumberCharType = 7, + kCFNumberShortType = 8, + kCFNumberIntType = 9, + kCFNumberLongType = 10, + kCFNumberLongLongType = 11, + kCFNumberFloatType = 12, + kCFNumberDoubleType = 13, + kCFNumberCFIndexType = 14, + kCFNumberNSIntegerType = 15, + kCFNumberCGFloatType = 16, + kCFNumberMaxType = 16 +}; +typedef int CFNumberType; + +const CFDictionaryKeyCallBacks kCFTypeDictionaryKeyCallBacks; +const CFDictionaryValueCallBacks kCFTypeDictionaryValueCallBacks; + +const CFBooleanRef kCFBooleanTrue; +const CFBooleanRef kCFBooleanFalse; +""" + +FUNCTIONS = """ +CFDataRef CFDataCreate(CFAllocatorRef, const UInt8 *, CFIndex); +CFStringRef CFStringCreateWithCString(CFAllocatorRef, const char *, + CFStringEncoding); +CFDictionaryRef CFDictionaryCreate(CFAllocatorRef, const void **, + const void **, CFIndex, + const CFDictionaryKeyCallBacks *, + const CFDictionaryValueCallBacks *); +CFMutableDictionaryRef CFDictionaryCreateMutable( + CFAllocatorRef, + CFIndex, + const CFDictionaryKeyCallBacks *, + const CFDictionaryValueCallBacks * +); +void CFDictionarySetValue(CFMutableDictionaryRef, const void *, const void *); +CFIndex CFArrayGetCount(CFArrayRef); +const void *CFArrayGetValueAtIndex(CFArrayRef, CFIndex); +CFIndex CFDataGetLength(CFDataRef); +void CFDataGetBytes(CFDataRef, CFRange, UInt8 *); +CFRange CFRangeMake(CFIndex, CFIndex); +void CFShow(CFTypeRef); +Boolean CFBooleanGetValue(CFBooleanRef); +CFNumberRef CFNumberCreate(CFAllocatorRef, CFNumberType, const void *); +void CFRelease(CFTypeRef); +CFTypeRef CFRetain(CFTypeRef); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" + +CONDITIONAL_NAMES = {} diff --git a/cryptography/hazmat/bindings/commoncrypto/secimport.py b/cryptography/hazmat/bindings/commoncrypto/secimport.py new file mode 100644 index 00000000..add62c79 --- /dev/null +++ b/cryptography/hazmat/bindings/commoncrypto/secimport.py @@ -0,0 +1,95 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include <Security/SecImportExport.h> +""" + +TYPES = """ +typedef ... *SecAccessRef; + +CFStringRef kSecImportExportPassphrase; +CFStringRef kSecImportExportKeychain; +CFStringRef kSecImportExportAccess; + +typedef uint32_t SecExternalItemType; +enum { + kSecItemTypeUnknown, + kSecItemTypePrivateKey, + kSecItemTypePublicKey, + kSecItemTypeSessionKey, + kSecItemTypeCertificate, + kSecItemTypeAggregate +}; + + +typedef uint32_t SecExternalFormat; +enum { + kSecFormatUnknown = 0, + kSecFormatOpenSSL, + kSecFormatSSH, + kSecFormatBSAFE, + kSecFormatRawKey, + kSecFormatWrappedPKCS8, + kSecFormatWrappedOpenSSL, + kSecFormatWrappedSSH, + kSecFormatWrappedLSH, + kSecFormatX509Cert, + kSecFormatPEMSequence, + kSecFormatPKCS7, + kSecFormatPKCS12, + kSecFormatNetscapeCertSequence, + kSecFormatSSHv2 +}; + +typedef uint32_t SecItemImportExportFlags; +enum { + kSecKeyImportOnlyOne = 0x00000001, + kSecKeySecurePassphrase = 0x00000002, + kSecKeyNoAccessControl = 0x00000004 +}; +typedef uint32_t SecKeyImportExportFlags; + +typedef struct { + /* for import and export */ + uint32_t version; + SecKeyImportExportFlags flags; + CFTypeRef passphrase; + CFStringRef alertTitle; + CFStringRef alertPrompt; + + /* for import only */ + SecAccessRef accessRef; + CFArrayRef keyUsage; + + CFArrayRef keyAttributes; +} SecItemImportExportKeyParameters; +""" + +FUNCTIONS = """ +OSStatus SecItemImport(CFDataRef, CFStringRef, SecExternalFormat *, + SecExternalItemType *, SecItemImportExportFlags, + const SecItemImportExportKeyParameters *, + SecKeychainRef, CFArrayRef *); +OSStatus SecPKCS12Import(CFDataRef, CFDictionaryRef, CFArrayRef *); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" + +CONDITIONAL_NAMES = {} diff --git a/cryptography/hazmat/bindings/commoncrypto/secitem.py b/cryptography/hazmat/bindings/commoncrypto/secitem.py new file mode 100644 index 00000000..4d7710bd --- /dev/null +++ b/cryptography/hazmat/bindings/commoncrypto/secitem.py @@ -0,0 +1,40 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include <Security/SecItem.h> +""" + +TYPES = """ +const CFTypeRef kSecAttrKeyType; +const CFTypeRef kSecAttrKeySizeInBits; +const CFTypeRef kSecAttrIsPermanent; +const CFTypeRef kSecAttrKeyTypeRSA; +const CFTypeRef kSecAttrKeyTypeDSA; +const CFTypeRef kSecAttrKeyTypeEC; +const CFTypeRef kSecAttrKeyTypeEC; +const CFTypeRef kSecUseKeychain; +""" + +FUNCTIONS = """ +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" + +CONDITIONAL_NAMES = {} diff --git a/cryptography/hazmat/bindings/commoncrypto/seckey.py b/cryptography/hazmat/bindings/commoncrypto/seckey.py new file mode 100644 index 00000000..38aaece8 --- /dev/null +++ b/cryptography/hazmat/bindings/commoncrypto/seckey.py @@ -0,0 +1,34 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include <Security/SecKey.h> +""" + +TYPES = """ +typedef ... *SecKeyRef; +""" + +FUNCTIONS = """ +OSStatus SecKeyGeneratePair(CFDictionaryRef, SecKeyRef *, SecKeyRef *); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" + +CONDITIONAL_NAMES = {} diff --git a/cryptography/hazmat/bindings/commoncrypto/seckeychain.py b/cryptography/hazmat/bindings/commoncrypto/seckeychain.py new file mode 100644 index 00000000..c045c347 --- /dev/null +++ b/cryptography/hazmat/bindings/commoncrypto/seckeychain.py @@ -0,0 +1,36 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include <Security/SecKeychain.h> +""" + +TYPES = """ +typedef ... *SecKeychainRef; +""" + +FUNCTIONS = """ +OSStatus SecKeychainCreate(const char *, UInt32, const void *, Boolean, + SecAccessRef, SecKeychainRef *); +OSStatus SecKeychainDelete(SecKeychainRef); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" + +CONDITIONAL_NAMES = {} diff --git a/cryptography/hazmat/bindings/commoncrypto/sectransform.py b/cryptography/hazmat/bindings/commoncrypto/sectransform.py new file mode 100644 index 00000000..d6dbc5f6 --- /dev/null +++ b/cryptography/hazmat/bindings/commoncrypto/sectransform.py @@ -0,0 +1,79 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include <Security/SecDigestTransform.h> +#include <Security/SecSignVerifyTransform.h> +#include <Security/SecEncryptTransform.h> +""" + +TYPES = """ +typedef ... *SecTransformRef; + +CFStringRef kSecImportExportPassphrase; +CFStringRef kSecImportExportKeychain; +CFStringRef kSecImportExportAccess; + +CFStringRef kSecEncryptionMode; +CFStringRef kSecEncryptKey; +CFStringRef kSecIVKey; +CFStringRef kSecModeCBCKey; +CFStringRef kSecModeCFBKey; +CFStringRef kSecModeECBKey; +CFStringRef kSecModeNoneKey; +CFStringRef kSecModeOFBKey; +CFStringRef kSecOAEPEncodingParametersAttributeName; +CFStringRef kSecPaddingKey; +CFStringRef kSecPaddingNoneKey; +CFStringRef kSecPaddingOAEPKey; +CFStringRef kSecPaddingPKCS1Key; +CFStringRef kSecPaddingPKCS5Key; +CFStringRef kSecPaddingPKCS7Key; + +const CFStringRef kSecTransformInputAttributeName; +const CFStringRef kSecTransformOutputAttributeName; +const CFStringRef kSecTransformDebugAttributeName; +const CFStringRef kSecTransformTransformName; +const CFStringRef kSecTransformAbortAttributeName; + +CFStringRef kSecInputIsAttributeName; +CFStringRef kSecInputIsPlainText; +CFStringRef kSecInputIsDigest; +CFStringRef kSecInputIsRaw; + +const CFStringRef kSecDigestTypeAttribute; +const CFStringRef kSecDigestLengthAttribute; +const CFStringRef kSecDigestMD5; +const CFStringRef kSecDigestSHA1; +const CFStringRef kSecDigestSHA2; +""" + +FUNCTIONS = """ +Boolean SecTransformSetAttribute(SecTransformRef, CFStringRef, CFTypeRef, + CFErrorRef *); +SecTransformRef SecDecryptTransformCreate(SecKeyRef, CFErrorRef *); +SecTransformRef SecEncryptTransformCreate(SecKeyRef, CFErrorRef *); +SecTransformRef SecVerifyTransformCreate(SecKeyRef, CFDataRef, CFErrorRef *); +SecTransformRef SecSignTransformCreate(SecKeyRef, CFErrorRef *) ; +CFTypeRef SecTransformExecute(SecTransformRef, CFErrorRef *); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" + +CONDITIONAL_NAMES = {} diff --git a/cryptography/hazmat/bindings/openssl/asn1.py b/cryptography/hazmat/bindings/openssl/asn1.py index dfdf1bf5..2edfd2d8 100644 --- a/cryptography/hazmat/bindings/openssl/asn1.py +++ b/cryptography/hazmat/bindings/openssl/asn1.py @@ -141,6 +141,9 @@ ASN1_INTEGER *BN_to_ASN1_INTEGER(BIGNUM *, ASN1_INTEGER *); /* These isn't a macro the arg is const on openssl 1.0.2+ */ int ASN1_GENERALIZEDTIME_check(ASN1_GENERALIZEDTIME *); + +/* Not a macro, const on openssl 1.0 */ +int ASN1_STRING_set_default_mask_asc(char *); """ CUSTOMIZATIONS = """ diff --git a/cryptography/hazmat/bindings/openssl/err.py b/cryptography/hazmat/bindings/openssl/err.py index f6456d66..f685e494 100644 --- a/cryptography/hazmat/bindings/openssl/err.py +++ b/cryptography/hazmat/bindings/openssl/err.py @@ -135,6 +135,7 @@ static const int EVP_F_PKCS5_V2_PBE_KEYIVGEN; static const int EVP_F_PKCS8_SET_BROKEN; static const int EVP_F_RC2_MAGIC_TO_METH; static const int EVP_F_RC5_CTRL; + static const int EVP_R_AES_KEY_SETUP_FAILED; static const int EVP_R_ASN1_LIB; static const int EVP_R_BAD_BLOCK_LENGTH; @@ -168,6 +169,7 @@ static const int EVP_R_UNSUPPORTED_CIPHER; static const int EVP_R_UNSUPPORTED_KEY_DERIVATION_FUNCTION; static const int EVP_R_UNSUPPORTED_KEYLENGTH; static const int EVP_R_UNSUPPORTED_SALT_TYPE; +static const int EVP_R_UNSUPPORTED_PRIVATE_KEY_ALGORITHM; static const int EVP_R_WRONG_FINAL_BLOCK_LENGTH; static const int EVP_R_WRONG_PUBLIC_KEY_TYPE; diff --git a/cryptography/hazmat/primitives/asymmetric/ec.py b/cryptography/hazmat/primitives/asymmetric/ec.py new file mode 100644 index 00000000..1e49ad7b --- /dev/null +++ b/cryptography/hazmat/primitives/asymmetric/ec.py @@ -0,0 +1,69 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +import six + +from cryptography.hazmat.primitives import interfaces + + +class EllipticCurvePublicNumbers(object): + def __init__(self, x, y, curve): + if ( + not isinstance(x, six.integer_types) or + not isinstance(y, six.integer_types) + ): + raise TypeError("x and y must be integers.") + + if not isinstance(curve, interfaces.EllipticCurve): + raise TypeError("curve must provide the EllipticCurve interface.") + + self._y = y + self._x = x + self._curve = curve + + @property + def curve(self): + return self._curve + + @property + def x(self): + return self._x + + @property + def y(self): + return self._y + + +class EllipticCurvePrivateNumbers(object): + def __init__(self, private_value, public_numbers): + if not isinstance(private_value, six.integer_types): + raise TypeError("private_value must be an integer.") + + if not isinstance(public_numbers, EllipticCurvePublicNumbers): + raise TypeError( + "public_numbers must be an EllipticCurvePublicNumbers " + "instance." + ) + + self._private_value = private_value + self._public_numbers = public_numbers + + @property + def private_value(self): + return self._private_value + + @property + def public_numbers(self): + return self._public_numbers diff --git a/cryptography/hazmat/primitives/interfaces.py b/cryptography/hazmat/primitives/interfaces.py index 810a67a4..0dd1d01a 100644 --- a/cryptography/hazmat/primitives/interfaces.py +++ b/cryptography/hazmat/primitives/interfaces.py @@ -489,3 +489,63 @@ class CMACContext(object): """ Return a CMACContext that is a copy of the current context. """ + + +@six.add_metaclass(abc.ABCMeta) +class EllipticCurve(object): + @abc.abstractproperty + def name(self): + """ + The name of the curve. e.g. secp256r1. + """ + + @abc.abstractproperty + def key_size(self): + """ + The bit length of the base point of the curve. + """ + + +@six.add_metaclass(abc.ABCMeta) +class EllipticCurveSignatureAlgorithm(object): + @abc.abstractproperty + def algorithm(self): + """ + The digest algorithm used with this signature. + """ + + +@six.add_metaclass(abc.ABCMeta) +class EllipticCurvePrivateKey(object): + @abc.abstractmethod + def signer(self, signature_algorithm): + """ + Returns an AsymmetricSignatureContext used for signing data. + """ + + @abc.abstractmethod + def public_key(self): + """ + The EllipticCurvePublicKey for this private key. + """ + + @abc.abstractproperty + def curve(self): + """ + The EllipticCurve that this key is on. + """ + + +@six.add_metaclass(abc.ABCMeta) +class EllipticCurvePublicKey(object): + @abc.abstractmethod + def verifier(self, signature, signature_algorithm): + """ + Returns an AsymmetricVerificationContext used for signing data. + """ + + @abc.abstractproperty + def curve(self): + """ + The EllipticCurve that this key is on. + """ diff --git a/cryptography/hazmat/primitives/serialization.py b/cryptography/hazmat/primitives/serialization.py index 38937508..ed73c4c4 100644 --- a/cryptography/hazmat/primitives/serialization.py +++ b/cryptography/hazmat/primitives/serialization.py @@ -18,3 +18,9 @@ def load_pem_traditional_openssl_private_key(data, password, backend): return backend.load_traditional_openssl_pem_private_key( data, password ) + + +def load_pem_pkcs8_private_key(data, password, backend): + return backend.load_pkcs8_pem_private_key( + data, password + ) diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst index 78a35cd9..c1ce621a 100644 --- a/docs/hazmat/backends/interfaces.rst +++ b/docs/hazmat/backends/interfaces.rst @@ -454,3 +454,76 @@ A specific ``backend`` may provide one or more of these interfaces. :returns: :class:`~cryptography.hazmat.primitives.interfaces.CMACContext` + + +.. class:: PKCS8SerializationBackend + + .. versionadded:: 0.5 + + A backend with methods for working with PKCS #8 key serialization. + + .. method:: load_pkcs8_pem_private_key(data, password) + + :param bytes data: PEM data to deserialize. + + :param bytes password: The password to use if this data is encrypted. + Should be None if the data is not encrypted. + + :return: A new instance of the appropriate private key or public key + that the serialized data contains. + + :raises ValueError: If the data could not be deserialized correctly. + + :raises cryptography.exceptions.UnsupportedAlgorithm: If the data is + encrypted with an unsupported algorithm. + + +.. class:: EllipticCurveBackend + + .. versionadded:: 0.5 + + .. method:: elliptic_curve_supported(curve) + + :param curve: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurve` + provider. + + :returns: True if the elliptic curve is supported by this backend. + + .. method:: elliptic_curve_signature_algorithm_supported(signature_algorithm, curve) + + :param signature_algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurveSignatureAlgorithm` + provider. + + :param curve: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurve` + provider. + + :returns: True if the signature algorithm and curve are supported by this backend. + + .. method:: generate_elliptic_curve_private_key(curve) + + :param curve: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurve` + provider. + + .. method:: elliptic_curve_private_key_from_numbers(numbers) + + :param numbers: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurvePrivateNumbers` + provider. + + :returns: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurvePrivateKey` + provider. + + .. method:: elliptic_curve_public_key_from_numbers(numbers) + + :param numbers: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurvePublicNumbers` + provider. + + :returns: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurvePublicKey` + provider. diff --git a/docs/hazmat/backends/openssl.rst b/docs/hazmat/backends/openssl.rst index 6ad0d045..e829798a 100644 --- a/docs/hazmat/backends/openssl.rst +++ b/docs/hazmat/backends/openssl.rst @@ -19,7 +19,9 @@ Red Hat Enterprise Linux 5) and greater. Earlier versions may work but are * :class:`~cryptography.hazmat.backends.interfaces.HashBackend` * :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` * :class:`~cryptography.hazmat.backends.interfaces.PBKDF2HMACBackend` + * :class:`~cryptography.hazmat.backends.interfaces.PKCS8SerializationBackend` * :class:`~cryptography.hazmat.backends.interfaces.RSABackend` + * :class:`~cryptography.hazmat.backends.interfaces.TraditionalOpenSSLSerializationBackend` It also exposes the following: diff --git a/docs/hazmat/primitives/asymmetric/ec.rst b/docs/hazmat/primitives/asymmetric/ec.rst new file mode 100644 index 00000000..f88b965a --- /dev/null +++ b/docs/hazmat/primitives/asymmetric/ec.rst @@ -0,0 +1,51 @@ +.. hazmat:: + +Elliptic Curve +============== + +.. currentmodule:: cryptography.hazmat.primitives.asymmetric.ec + + +.. class:: EllipticCurvePrivateNumbers(private_value, public_numbers) + + .. versionadded:: 0.5 + + The collection of integers that make up an EC private key. + + .. attribute:: public_numbers + + :type: :class:`~cryptography.hazmat.primitives.ec.EllipticCurvePublicNumbers` + + The :class:`EllipticCurvePublicNumbers` which makes up the EC public + key associated with this EC private key. + + .. attribute:: private_value + + :type: int + + The private value. + + +.. class:: EllipticCurvePublicNumbers(x, y, curve) + + .. versionadded:: 0.5 + + The collection of integers that make up an EC public key. + + .. attribute:: curve + + :type: :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurve` + + The elliptic curve for this key. + + .. attribute:: x + + :type: int + + The affine x component of the public point used for verifying. + + .. attribute:: y + + :type: int + + The affine y component of the public point used for verifying. diff --git a/docs/hazmat/primitives/asymmetric/index.rst b/docs/hazmat/primitives/asymmetric/index.rst index 047f9cb9..6a5228ba 100644 --- a/docs/hazmat/primitives/asymmetric/index.rst +++ b/docs/hazmat/primitives/asymmetric/index.rst @@ -7,6 +7,7 @@ Asymmetric algorithms :maxdepth: 1 dsa + ec rsa padding serialization diff --git a/docs/hazmat/primitives/asymmetric/serialization.rst b/docs/hazmat/primitives/asymmetric/serialization.rst index 8d32ae58..2b3eb511 100644 --- a/docs/hazmat/primitives/asymmetric/serialization.rst +++ b/docs/hazmat/primitives/asymmetric/serialization.rst @@ -9,6 +9,58 @@ There are several common schemes for serializing asymmetric private and public keys to bytes. They generally support encryption of private keys and additional key metadata. +Many serialization formats support multiple different types of asymmetric keys +and will return an an instance of the appropriate type. You should check that +the returned key matches the type your application expects when using these +methods. + + .. code-block:: pycon + + >>> key = load_pkcs8_private_key(pem_data, None, backend) + >>> if isinstance(key, rsa.RSAPrivateKey): + >>> signature = sign_with_rsa_key(key, message) + >>> elif isinstance(key, dsa.DSAPrivateKey): + >>> signature = sign_with_dsa_key(key, message) + >>> else: + >>> raise TypeError + + +PKCS #8 Format +~~~~~~~~~~~~~~ + +PKCS #8 is a serialization format originally standardized by RSA and +currently maintained by the IETF in :rfc:`5208`. It supports password based +encryption and additional key metadata attributes. + + +.. function:: load_pkcs8_private_key(data, password, backend) + + .. versionadded:: 0.5 + + Deserialize a private key from PEM encoded data to one of the supported + asymmetric private key types. + + :param bytes data: The PEM encoded key data. + + :param bytes password: The password to use to decrypt the data. Should + be ``None`` if the private key is not encrypted. + :param backend: A + :class:`~cryptography.hazmat.backends.interfaces.PKCS8SerializationBackend` + provider. + + :returns: A new instance of a private key. + + :raises ValueError: If the PEM data could not be decrypted or if its + structure could not be decoded successfully. + + :raises TypeError: If a ``password`` was given and the private key was + not encrypted. Or if the key was encrypted but no + password was supplied. + + :raises UnsupportedAlgorithm: If the serialized key is of a type that + is not supported by the backend or if the key is encrypted with a + symmetric cipher that is not supported by the backend. + Traditional OpenSSL Format ~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst index dc09a26f..b2857f58 100644 --- a/docs/hazmat/primitives/interfaces.rst +++ b/docs/hazmat/primitives/interfaces.rst @@ -463,6 +463,97 @@ Asymmetric interfaces :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricVerificationContext` +.. class:: EllipticCurve + + .. versionadded:: 0.5 + + A named elliptic curve. + + .. attribute:: name + + :type: string + + The name of the curve. Usually the name used for the ASN.1 OID such as + ``secp256k1``. + + .. attribute:: key_size + + :type: int + + The bit length of the curve's base point. + + +.. class:: EllipticCurveSignatureAlgorithm + + .. versionadded:: 0.5 + + A signature algorithm for use with elliptic curve keys. + + .. attribute:: algorithm + + :type: :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + + The digest algorithm to be used with the signature scheme. + + +.. class:: EllipticCurvePrivateKey + + .. versionadded:: 0.5 + + An elliptic curve private key for use with an algorithm such as `ECDSA`_ or + `EdDSA`_. + + .. classmethod:: signer(signature_algorithm) + + Sign data which can be verified later by others using the public key. + + :param signature_algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurveSignatureAlgorithm` + provider. + + :returns: + :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricSignatureContext` + + .. attribute:: curve + + :type: :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurve` + + The elliptic curve for this key. + + .. method:: public_key() + + :return: :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurvePublicKey` + + The EllipticCurvePublicKey object for this private key. + + +.. class:: EllipticCurvePublicKey + + .. versionadded:: 0.5 + + An elliptic curve public key. + + .. classmethod:: verifier(signer, signature_algorithm) + + Verify data was signed by the private key associated with this public + key. + + :param bytes signature: The signature to verify. + + :param signature_algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurveSignatureAlgorithm` + provider. + + :returns: + :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricSignatureContext` + + .. attribute:: curve + + :type: :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurve` + + The elliptic curve for this key. + + .. class:: AsymmetricSignatureContext .. versionadded:: 0.2 @@ -612,3 +703,5 @@ Key derivation functions .. _`Chinese remainder theorem`: https://en.wikipedia.org/wiki/Chinese_remainder_theorem .. _`DSA`: https://en.wikipedia.org/wiki/Digital_Signature_Algorithm .. _`CMAC`: https://en.wikipedia.org/wiki/CMAC +.. _`ECDSA`: http://en.wikipedia.org/wiki/ECDSA +.. _`EdDSA`: http://en.wikipedia.org/wiki/EdDSA diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index b5896158..9baf0822 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -1,3 +1,4 @@ +affine backend backends Backends @@ -9,4 +9,5 @@ markers = pbkdf2hmac: this test requires a backend providing PBKDF2HMACBackend rsa: this test requires a backend providing RSABackend traditional_openssl_serialization: this test requires a backend providing TraditionalOpenSSLSerializationBackend + pkcs8_serialization: this test requires a backend providing PKCS8SerializationBackend supported: parametrized test requiring only_if and skip_message diff --git a/tests/conftest.py b/tests/conftest.py index 86d5a03b..b1326dc8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,7 +18,8 @@ import pytest from cryptography.hazmat.backends import _available_backends from cryptography.hazmat.backends.interfaces import ( CMACBackend, CipherBackend, DSABackend, HMACBackend, HashBackend, - PBKDF2HMACBackend, RSABackend, TraditionalOpenSSLSerializationBackend + PBKDF2HMACBackend, PKCS8SerializationBackend, RSABackend, + TraditionalOpenSSLSerializationBackend ) from .utils import check_backend_support, check_for_iface, select_backends @@ -45,6 +46,11 @@ def pytest_runtest_setup(item): TraditionalOpenSSLSerializationBackend, item ) + check_for_iface( + "pkcs8_serialization", + PKCS8SerializationBackend, + item + ) check_backend_support(item) diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py new file mode 100644 index 00000000..53985fe2 --- /dev/null +++ b/tests/hazmat/primitives/test_ec.py @@ -0,0 +1,79 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from __future__ import absolute_import, division, print_function + +import pytest + +from cryptography import utils +from cryptography.hazmat.primitives import interfaces +from cryptography.hazmat.primitives.asymmetric import ec + + +@utils.register_interface(interfaces.EllipticCurve) +class DummyCurve(object): + name = "dummy-curve" + + +class TestECC(object): + def test_ec_numbers(self): + numbers = ec.EllipticCurvePrivateNumbers( + 1, + ec.EllipticCurvePublicNumbers( + 2, 3, DummyCurve() + ) + ) + + assert numbers.private_value == 1 + assert numbers.public_numbers.x == 2 + assert numbers.public_numbers.y == 3 + assert isinstance(numbers.public_numbers.curve, DummyCurve) + + with pytest.raises(TypeError): + ec.EllipticCurvePrivateNumbers( + None, + ec.EllipticCurvePublicNumbers( + 2, 3, DummyCurve() + ) + ) + + with pytest.raises(TypeError): + ec.EllipticCurvePrivateNumbers( + 1, + ec.EllipticCurvePublicNumbers( + None, 3, DummyCurve() + ) + ) + + with pytest.raises(TypeError): + ec.EllipticCurvePrivateNumbers( + 1, + ec.EllipticCurvePublicNumbers( + 2, None, DummyCurve() + ) + ) + + with pytest.raises(TypeError): + ec.EllipticCurvePrivateNumbers( + 1, + ec.EllipticCurvePublicNumbers( + 2, 3, None + ) + ) + + with pytest.raises(TypeError): + ec.EllipticCurvePrivateNumbers( + 1, + None + ) diff --git a/tests/hazmat/primitives/test_serialization.py b/tests/hazmat/primitives/test_serialization.py index 8d3b8fd4..b19990e0 100644 --- a/tests/hazmat/primitives/test_serialization.py +++ b/tests/hazmat/primitives/test_serialization.py @@ -19,8 +19,10 @@ import textwrap import pytest +from cryptography.exceptions import _Reasons from cryptography.hazmat.primitives.asymmetric import dsa, rsa from cryptography.hazmat.primitives.serialization import ( + load_pem_pkcs8_private_key, load_pem_traditional_openssl_private_key ) @@ -242,7 +244,303 @@ class TestTraditionalOpenSSLSerialisation(object): password = b"password" - with raises_unsupported_algorithm(None): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): load_pem_traditional_openssl_private_key( key_data, password, backend ) + + +@pytest.mark.pkcs8_serialization +class TestPKCS8Serialisation(object): + @pytest.mark.parametrize( + ("key_file", "password"), + [ + ("unencpkcs8.pem", None), + ("encpkcs8.pem", b"foobar"), + ("enc2pkcs8.pem", b"baz"), + ("pkcs12_s2k_pem-X_9607.pem", b"123456"), + ("pkcs12_s2k_pem-X_9671.pem", b"123456"), + ("pkcs12_s2k_pem-X_9925.pem", b"123456"), + ("pkcs12_s2k_pem-X_9926.pem", b"123456"), + ("pkcs12_s2k_pem-X_9927.pem", b"123456"), + ("pkcs12_s2k_pem-X_9928.pem", b"123456"), + ("pkcs12_s2k_pem-X_9929.pem", b"123456"), + ("pkcs12_s2k_pem-X_9930.pem", b"123456"), + ("pkcs12_s2k_pem-X_9931.pem", b"123456"), + ("pkcs12_s2k_pem-X_9932.pem", b"123456"), + ] + ) + def test_load_pem_rsa_private_key(self, key_file, password, backend): + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", key_file), + lambda pemfile: load_pem_pkcs8_private_key( + pemfile.read().encode(), password, backend + ) + ) + + assert key + assert isinstance(key, rsa.RSAPrivateKey) + _check_rsa_private_key(key) + + def test_unused_password(self, backend): + key_file = os.path.join( + "asymmetric", "PKCS8", "unencpkcs8.pem") + password = b"this password will not be used" + + with pytest.raises(TypeError): + load_vectors_from_file( + key_file, + lambda pemfile: load_pem_pkcs8_private_key( + pemfile.read().encode(), password, backend + ) + ) + + def test_wrong_password(self, backend): + key_file = os.path.join( + "asymmetric", "PKCS8", "encpkcs8.pem") + password = b"this password is wrong" + + with pytest.raises(ValueError): + load_vectors_from_file( + key_file, + lambda pemfile: load_pem_pkcs8_private_key( + pemfile.read().encode(), password, backend + ) + ) + + @pytest.mark.parametrize("password", [None, b""]) + def test_missing_password(self, backend, password): + key_file = os.path.join( + "asymmetric", + "PKCS8", + "encpkcs8.pem" + ) + + with pytest.raises(TypeError): + load_vectors_from_file( + key_file, + lambda pemfile: load_pem_pkcs8_private_key( + pemfile.read().encode(), password, backend + ) + ) + + def test_wrong_format(self, backend): + key_data = b"---- NOT A KEY ----\n" + + with pytest.raises(ValueError): + load_pem_pkcs8_private_key( + key_data, None, backend + ) + + with pytest.raises(ValueError): + load_pem_pkcs8_private_key( + key_data, b"this password will not be used", backend + ) + + def test_corrupt_format(self, backend): + # unencpkcs8.pem with a bunch of data missing. + key_data = textwrap.dedent("""\ + -----BEGIN PRIVATE KEY----- + MIICdQIBADALBgkqhkiG9w0BAQEEggJhMIICXQIBAAKBgQC7JHoJfg6yNzLMOWet + 8Z49a4KD0dCspMAYvo2YAMB7/wdEycocujbhJ2n/seONi+5XqTqqFkM5VBl8rmkk + FPZk/7x0xmdsTPECSWnHK+HhoaNDFPR3j8jQhVo1laxiqcEhAHegi5cwtFosuJAv + FiRC0Cgz+frQPFQEBsAV9RuasyQxqzxrR0Ow0qncBeGBWbYE6WZhqtcLAI895b+i + +F4lbB4iD7T9QeIDMU/aIMXA81UO4cns1z4qDAHKeyLLrPQrJ/B4X7XC+egUWm5+ + hr1qmyAMusyXIBECQQDJWZ8piluf4yrYfsJAn6hF5T4RjTztbqvO0GVG2McHY7Uj + NPSffhzHx/ll0fQEQji+OgydCCX8o3HZrgw5YfSJAkEA7e+rqdU5nO5ZG//PSEQb + tjLnRiTzBH/elQhtdZ5nF7pcpNTi4k13zutmKcWW4GK75azcRGJUhu1kDM7QYAOd + SQJAVNkYcifkvna7GmooL5VYEsQsqLbM4v0NF2TIGNfG3z1MGp75KrC5LhL97MNR + we2p/bd2k0HYyCKUGnf2nMPDiQJBAI75pwittSoE240EobUGIDTSz8CJsXIxuDmL + z+KOpdpPRR5TQmbEMEspjsFpFymMiuYPgmihQbO2cJl1qScY5OkCQQCJ6m5tcN8l + Xxg/SNpjEIv+qAyUD96XVlOJlOIeLHQ8kYE0C6ZA+MsqYIzgAreJk88Yn0lU/X0/ + mu/UpE/BRZmR + -----END PRIVATE KEY----- + """).encode() + + with pytest.raises(ValueError): + load_pem_pkcs8_private_key( + key_data, None, backend + ) + + with pytest.raises(ValueError): + load_pem_pkcs8_private_key( + key_data, b"this password will not be used", backend + ) + + def test_encrypted_corrupt_format(self, backend): + # encpkcs8.pem with some bits flipped. + key_data = textwrap.dedent("""\ + -----BEGIN ENCRYPTED PRIVATE KEY----- + MIICojAcBgoqhkiG9w0BDAEDMA4ECHK0M0+QuEL9AgIBIcSCAoDRq+KRY+0XP0tO + lwBTzViiXSXoyNnKAZKt5r5K/fGNntv22g/1s/ZNCetrqsJDC5eMUPPacz06jFq/ + Ipsep4/OgjQ9UAOzXNrWEoNyrHnWDo7usgD3CW0mKyqER4+wG0adVMbt3N+CJHGB + 85jzRmQTfkdx1rSWeSx+XyswHn8ER4+hQ+omKWMVm7AFkjjmP/KnhUnLT98J8rhU + ArQoFPHz/6HVkypFccNaPPNg6IA4aS2A+TU9vJYOaXSVfFB2yf99hfYYzC+ukmuU + 5Lun0cysK5s/5uSwDueUmDQKspnaNyiaMGDxvw8hilJc7vg0fGObfnbIpizhxJwq + gKBfR7Zt0Hv8OYi1He4MehfMGdbHskztF+yQ40LplBGXQrvAqpU4zShga1BoQ98T + 0ekbBmqj7hg47VFsppXR7DKhx7G7rpMmdKbFhAZVCjae7rRGpUtD52cpFdPhMyAX + huhMkoczwUW8B/rM4272lkHo6Br0yk/TQfTEGkvryflNVu6lniPTV151WV5U1M3o + 3G3a44eDyt7Ln+WSOpWtbPQMTrpKhur6WXgJvrpa/m02oOGdvOlDsoOCgavgQMWg + 7xKKL7620pHl7p7f/8tlE8q6vLXVvyNtAOgt/JAr2rgvrHaZSzDE0DwgCjBXEm+7 + cVMVNkHod7bLQefVanVtWqPzbmr8f7gKeuGwWSG9oew/lN2hxcLEPJHAQlnLgx3P + 0GdGjK9NvwA0EP2gYIeE4+UtSder7xQ7bVh25VB20R4TTIIs4aXXCVOoQPagnzaT + 6JLgl8FrvdfjHwIvmSOO1YMNmILBq000Q8WDqyErBDs4hsvtO6VQ4LeqJj6gClX3 + qeJNaJFu + -----END ENCRYPTED PRIVATE KEY----- + """).encode() + + password = b"this password is wrong" + + with pytest.raises(ValueError): + load_pem_pkcs8_private_key( + key_data, None, backend + ) + + with pytest.raises(ValueError): + load_pem_pkcs8_private_key( + key_data, password, backend + ) + + def test_key1_pem_encrypted_values(self, backend): + pkey = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", "encpkcs8.pem"), + lambda pemfile: load_pem_pkcs8_private_key( + pemfile.read().encode(), b"foobar", backend + ) + ) + assert pkey + + assert pkey.modulus == int( + "00beec64d6db5760ac2fd4c971145641b9bd7f5c56558ece608795c79807" + "376a7fe5b19f95b35ca358ea5c8abd7ae051d49cd2f1e45969a1ae945460" + "3c14b278664a0e414ebc8913acb6203626985525e17a600611b028542dd0" + "562aad787fb4f1650aa318cdcff751e1b187cbf6785fbe164e9809491b95" + "dd68480567c99b1a57", 16 + ) + + assert pkey.public_exponent == 65537 + + assert pkey.private_exponent == int( + "0cfe316e9dc6b8817f4fcfd5ae38a0886f68f773b8a6db4c9e6d8703c599" + "f3d9785c3a2c09e4c8090909fb3721e19a3009ec21221523a729265707a5" + "8f13063671c42a4096cad378ef2510cb59e23071489d8893ac4934dd149f" + "34f2d094bea57f1c8027c3a77248ac9b91218737d0c3c3dfa7d7829e6977" + "cf7d995688c86c81", 16 + ) + + assert pkey.p == int( + "00db122ac857b2c0437d7616daa98e597bb75ca9ad3a47a70bec10c10036" + "03328794b225c8e3eee6ffd3fd6d2253d28e071fe27d629ab072faa14377" + "ce6118cb67", 16 + ) + + assert pkey.q == int( + "00df1b8aa8506fcbbbb9d00257f2975e38b33d2698fd0f37e82d7ef38c56" + "f21b6ced63c825383782a7115cfcc093300987dbd2853b518d1c8f26382a" + "2d2586d391", 16 + ) + + assert pkey.dmp1 == int( + "00be18aca13e60712fdf5daa85421eb10d86d654b269e1255656194fb0c4" + "2dd01a1070ea12c19f5c39e09587af02f7b1a1030d016a9ffabf3b36d699" + "ceaf38d9bf", 16 + ) + + assert pkey.dmq1 == int( + "71aa8978f90a0c050744b77cf1263725b203ac9f730606d8ae1d289dce4a" + "28b8d534e9ea347aeb808c73107e583eb80c546d2bddadcdb3c82693a4c1" + "3d863451", 16 + ) + + assert pkey.iqmp == int( + "136b7b1afac6e6279f71b24217b7083485a5e827d156024609dae39d48a6" + "bdb55af2f062cc4a3b077434e6fffad5faa29a2b5dba2bed3e4621e478c0" + "97ccfe7f", 16 + ) + + @pytest.mark.parametrize( + ("key_file", "password"), + [ + ("unenc-dsa-pkcs8.pem", None), + ] + ) + def test_load_pem_dsa_private_key(self, key_file, password, backend): + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", key_file), + lambda pemfile: load_pem_traditional_openssl_private_key( + pemfile.read().encode(), password, backend + ) + ) + assert key + assert isinstance(key, dsa.DSAPrivateKey) + + params = key.parameters() + assert isinstance(params, dsa.DSAParameters) + + assert key.x == int("00a535a8e1d0d91beafc8bee1d9b2a3a8de3311203", 16) + assert key.y == int( + "2b260ea97dc6a12ae932c640e7df3d8ff04a8a05a0324f8d5f1b23f15fa1" + "70ff3f42061124eff2586cb11b49a82dcdc1b90fc6a84fb10109cb67db5d" + "2da971aeaf17be5e37284563e4c64d9e5fc8480258b319f0de29d54d8350" + "70d9e287914d77df81491f4423b62da984eb3f45eb2a29fcea5dae525ac6" + "ab6bcce04bfdf5b6", + 16 + ) + + assert params.p == int( + "00aa0930cc145825221caffa28ac2894196a27833de5ec21270791689420" + "7774a2e7b238b0d36f1b2499a2c2585083eb01432924418d867faa212dd1" + "071d4dceb2782794ad393cc08a4d4ada7f68d6e839a5fcd34b4e402d82cb" + "8a8cb40fec31911bf9bd360b034caacb4c5e947992573c9e90099c1b0f05" + "940cabe5d2de49a167", + 16 + ) + + assert params.q == int("00adc0e869b36f0ac013a681fdf4d4899d69820451", + 16) + + assert params.g == int( + "008c6b4589afa53a4d1048bfc346d1f386ca75521ccf72ddaa251286880e" + "e13201ff48890bbfc33d79bacaec71e7a778507bd5f1a66422e39415be03" + "e71141ba324f5b93131929182c88a9fa4062836066cebe74b5c6690c7d10" + "1106c240ab7ebd54e4e3301fd086ce6adac922fb2713a2b0887cba13b9bc" + "68ce5cfff241cd3246", + 16 + ) + + @pytest.mark.parametrize( + ("key_file", "password"), + [ + ("bad-oid-dsa-key.pem", None), + ] + ) + def test_load_bad_oid_key(self, key_file, password, backend): + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", key_file), + lambda pemfile: load_pem_traditional_openssl_private_key( + pemfile.read().encode(), password, backend + ) + ) + + @pytest.mark.parametrize( + ("key_file", "password"), + [ + ("bad-encryption-oid.pem", b"password"), + ] + ) + def test_load_bad_encryption_oid_key(self, key_file, password, backend): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", key_file), + lambda pemfile: load_pem_traditional_openssl_private_key( + pemfile.read().encode(), password, backend + ) + ) diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/bad-encryption-oid.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/bad-encryption-oid.pem new file mode 100644 index 00000000..4ebcc12c --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/bad-encryption-oid.pem @@ -0,0 +1,17 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIICojAcBgoYYYYYYYYYYYYYMA4ECHK0M0+QuEL9AgIBIgSCAoDRq+KRY+0XP0tO +lwBTzViiXSXoyNnKAZKt5r5K/fGNntv22g/1s/ZNCetrqsJDC5eMUPPacz06jFq/ +Ipsep4/OgjQ9UAOzXNrWEoNyrHnWDo7usgD3CW0mKyqER4+wG0ZdVMbt3N+CJHGB +85jzRmQTfkdx1rSWeSx+XyswHn8ER4+hQ+omKWMVm7AFkjjmP/KmhUnLT98J8rhU +ArQoFPHz/6HVkypFccNaPPNg6IA4aS2A+TU9vJYOaXSVfFB2yf99hfYYzC+ukmuU +5Lun0cysK5s/5uSwDueUmDQKspnaNyiaMGDxvw8hilJc7vg0fGObfnbIpizhxJwq +gKBfR7Zt0Hv8OYi1He4MehfMGdbHskztF+yQ40LplBGXQrvAqpU4zShga1BoQ98T +0ekbBmqj7hg47VFsppXR7DKhx7G7rpMmdKbFhAZVCjae7rRGpUtD52cpFdPhMyAX +huhMkoczwUW8B/rM4272lkHo6Br0yk/TQfTEGkvryflNVu6lniPTV151WV5U1M3o +3G3a44eDyt7Ln+WSOpWtbPQMTrpKhur6WXgJvrpa/m02oOGdvOlDsoOCgavgQMWg +7xKKL7620pHl7p7f/8tlE8q6vLXVvyNtAOgt/JAr2rgvrHaZSzDE0DwgCjBXEm+7 +cVMVNkHod7bLQefVanVtWqPzbmr8f7gKeuGwWSG9oew/lN2hxcLEPJHAQlnLgx3P +0GdGjK9NvwA0EP2gYIeE4+UtSder7xQ7bVh25VB20R4TTIIs4aXXCVOoQPagnzaT +6JLgl8FrvdfjHwIvmSOO1YMNmILBq000Q8WDqyErBDs4hsvtO6VQ4LeqJj6gClX3 +qeJNaJFu +-----END ENCRYPTED PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/bad-oid-dsa-key.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/bad-oid-dsa-key.pem new file mode 100644 index 00000000..50d045be --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/bad-oid-dsa-key.pem @@ -0,0 +1,9 @@ +-----BEGIN PRIVATE KEY----- +MIIBTAIBADCCASwGByXXXXXXXXEwggEfAoGBAKoJMMwUWCUiHK/6KKwolBlqJ4M9 +5ewhJweRaJQgd3Si57I4sNNvGySZosJYUIPrAUMpJEGNhn+qIS3RBx1NzrJ4J5St +OTzAik1K2n9o1ug5pfzTS05ALYLLioy0D+wxkRv5vTYLA0yqy0xelHmSVzyekAmc +Gw8FlAyr5dLeSaFnAhUArcDoabNvCsATpoH99NSJnWmCBFECgYEAjGtFia+lOk0Q +SL/DRtHzhsp1UhzPct2qJRKGiA7hMgH/SIkLv8M9ebrK7HHnp3hQe9XxpmQi45QV +vgPnEUG6Mk9bkxMZKRgsiKn6QGKDYGbOvnS1xmkMfRARBsJAq369VOTjMB/Qhs5q +2ski+ycTorCIfLoTubxozlz/8kHNMkYEFwIVAKU1qOHQ2Rvq/IvuHZsqOo3jMRID +-----END PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/PKCS8/unenc-dsa-pkcs8.pem b/vectors/cryptography_vectors/asymmetric/PKCS8/unenc-dsa-pkcs8.pem new file mode 100644 index 00000000..7b2099d3 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/PKCS8/unenc-dsa-pkcs8.pem @@ -0,0 +1,9 @@ +-----BEGIN PRIVATE KEY----- +MIIBTAIBADCCASwGByqGSM44BAEwggEfAoGBAKoJMMwUWCUiHK/6KKwolBlqJ4M9 +5ewhJweRaJQgd3Si57I4sNNvGySZosJYUIPrAUMpJEGNhn+qIS3RBx1NzrJ4J5St +OTzAik1K2n9o1ug5pfzTS05ALYLLioy0D+wxkRv5vTYLA0yqy0xelHmSVzyekAmc +Gw8FlAyr5dLeSaFnAhUArcDoabNvCsATpoH99NSJnWmCBFECgYEAjGtFia+lOk0Q +SL/DRtHzhsp1UhzPct2qJRKGiA7hMgH/SIkLv8M9ebrK7HHnp3hQe9XxpmQi45QV +vgPnEUG6Mk9bkxMZKRgsiKn6QGKDYGbOvnS1xmkMfRARBsJAq369VOTjMB/Qhs5q +2ski+ycTorCIfLoTubxozlz/8kHNMkYEFwIVAKU1qOHQ2Rvq/IvuHZsqOo3jMRID +-----END PRIVATE KEY----- |