From f83e25c81bb186ed8a96d4a569d5068546a24349 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 21 Feb 2015 18:34:00 -0600 Subject: Support for traditional OpenSSL and PKCS8 RSA private key serialization --- src/cryptography/hazmat/backends/openssl/rsa.py | 61 +++++++++++++++++++++- .../hazmat/primitives/asymmetric/rsa.py | 12 ++++- .../hazmat/primitives/serialization.py | 47 +++++++++++++++++ src/cryptography/utils.py | 1 + 4 files changed, 118 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/cryptography/hazmat/backends/openssl/rsa.py b/src/cryptography/hazmat/backends/openssl/rsa.py index 00ddcda3..1357889f 100644 --- a/src/cryptography/hazmat/backends/openssl/rsa.py +++ b/src/cryptography/hazmat/backends/openssl/rsa.py @@ -17,8 +17,13 @@ from cryptography.hazmat.primitives.asymmetric import ( from cryptography.hazmat.primitives.asymmetric.padding import ( AsymmetricPadding, MGF1, OAEP, PKCS1v15, PSS ) -from cryptography.hazmat.primitives.interfaces import ( - RSAPrivateKeyWithNumbers, RSAPublicKeyWithNumbers +from cryptography.hazmat.primitives.asymmetric.rsa import ( + RSAPrivateKeyWithNumbers, RSAPrivateKeyWithSerialization, + RSAPublicKeyWithNumbers +) +from cryptography.hazmat.primitives.serialization import ( + BestAvailable, Encoding, KeySerializationEncryption, NoEncryption, PKCS8, + TraditionalOpenSSL ) @@ -507,6 +512,7 @@ class _RSAVerificationContext(object): @utils.register_interface(RSAPrivateKeyWithNumbers) +@utils.register_interface(RSAPrivateKeyWithSerialization) class _RSAPrivateKey(object): def __init__(self, backend, rsa_cdata): self._backend = backend @@ -559,6 +565,57 @@ class _RSAPrivateKey(object): ) ) + def dump(self, serializer, encryption_algorithm): + if isinstance(serializer, PKCS8): + write_bio = self._backend._lib.PEM_write_bio_PKCS8PrivateKey + key = self._evp_pkey + elif isinstance(serializer, TraditionalOpenSSL): + write_bio = self._backend._lib.PEM_write_bio_RSAPrivateKey + key = self._rsa_cdata + else: + raise TypeError("serializer must be PKCS8 or TraditionalOpenSSL") + + if serializer.encoding != Encoding.PEM: + raise ValueError("Only PEM encoding is supported by this backend") + + if not isinstance(encryption_algorithm, KeySerializationEncryption): + raise TypeError( + "Encryption algorithm must be a KeySerializationEncryption " + "instance" + ) + + if isinstance(encryption_algorithm, NoEncryption): + password = b"" + passlen = 0 + evp_cipher = self._backend._ffi.NULL + elif isinstance(encryption_algorithm, BestAvailable): + # This is a curated value that we will update over time. + evp_cipher = self._backend._lib.EVP_get_cipherbyname( + b"aes-256-cbc" + ) + password = encryption_algorithm.password + passlen = len(password) + if passlen > 1023: + raise ValueError( + "Passwords longer than 1023 bytes are not supported by " + "this backend" + ) + else: + raise ValueError("Unsupported encryption type") + + bio = self._backend._create_mem_bio() + res = write_bio( + bio, + key, + evp_cipher, + password, + passlen, + self._backend._ffi.NULL, + self._backend._ffi.NULL + ) + assert res == 1 + return self._backend._read_mem_bio(bio) + @utils.register_interface(RSAPublicKeyWithNumbers) class _RSAPublicKey(object): diff --git a/src/cryptography/hazmat/primitives/asymmetric/rsa.py b/src/cryptography/hazmat/primitives/asymmetric/rsa.py index 332ad2c3..e994a9cc 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/rsa.py +++ b/src/cryptography/hazmat/primitives/asymmetric/rsa.py @@ -42,13 +42,23 @@ class RSAPrivateKey(object): @six.add_metaclass(abc.ABCMeta) -class RSAPrivateKeyWithNumbers(RSAPrivateKey): +class RSAPrivateKeyWithSerialization(RSAPrivateKey): @abc.abstractmethod def private_numbers(self): """ Returns an RSAPrivateNumbers. """ + @abc.abstractmethod + def dump(self, serializer, encryption_algorithm): + """ + Returns the PEM encoded key. + """ + + +# DeprecatedIn08 +RSAPrivateKeyWithNumbers = RSAPrivateKeyWithSerialization + @six.add_metaclass(abc.ABCMeta) class RSAPublicKey(object): diff --git a/src/cryptography/hazmat/primitives/serialization.py b/src/cryptography/hazmat/primitives/serialization.py index 0f9506e1..9bfbc6b7 100644 --- a/src/cryptography/hazmat/primitives/serialization.py +++ b/src/cryptography/hazmat/primitives/serialization.py @@ -4,11 +4,14 @@ from __future__ import absolute_import, division, print_function +import abc import base64 import struct +from enum import Enum import six +from cryptography import utils from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa @@ -164,3 +167,47 @@ else: data = data[4:] return result + + +class Encoding(Enum): + PEM = "PEM" + DER = "DER" + + +class PKCS8(object): + def __init__(self, encoding): + if not isinstance(encoding, Encoding): + raise TypeError( + "Encoding must be an element from the Encoding enum" + ) + + self.encoding = encoding + + +class TraditionalOpenSSL(object): + def __init__(self, encoding): + if not isinstance(encoding, Encoding): + raise TypeError( + "Encoding must be an element from the Encoding enum" + ) + + self.encoding = encoding + + +@six.add_metaclass(abc.ABCMeta) +class KeySerializationEncryption(object): + pass + + +@utils.register_interface(KeySerializationEncryption) +class BestAvailable(object): + def __init__(self, password): + if not isinstance(password, bytes) or len(password) == 0: + raise ValueError("Password must be 1 or more bytes.") + + self.password = password + + +@utils.register_interface(KeySerializationEncryption) +class NoEncryption(object): + pass diff --git a/src/cryptography/utils.py b/src/cryptography/utils.py index 78dcc1ca..77b6d253 100644 --- a/src/cryptography/utils.py +++ b/src/cryptography/utils.py @@ -12,6 +12,7 @@ import warnings # DeprecatedIn07 objects exist. This comment exists to remind developers to # look for them when it's time for the ninth release cycle deprecation dance. +# DeprecatedIn08 objects also exist. DeprecatedIn08 = PendingDeprecationWarning -- cgit v1.2.3