diff options
| author | Paul Kehrer <paul.l.kehrer@gmail.com> | 2022-10-03 09:29:45 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-10-03 14:29:45 +0000 |
| commit | 01687d63a5cb4475d84feae959bec6dea5fce3f1 (patch) | |
| tree | 9702e32358f4aa17d5a989de83b0ee3d0c8f3456 /src/cryptography | |
| parent | fd6bae730503934917050bea112f4d703fd6b237 (diff) | |
| download | cryptography-01687d63a5cb4475d84feae959bec6dea5fce3f1.tar.gz | |
add unsafe_skip_rsa_key_validation (#7667)
* add unsafe_skip_rsa_key_validation
This allows users to skip RSA key validation when calling
load_pem_private_key, load_der_private_key, and
RSAPrivateNumbers.private_key. This is a significant performance
improvement but is **only safe if you know the key is valid**. If you
use this when the key is invalid OpenSSL makes no guarantees about what
might happen. Infinite loops, crashes, and all manner of terrible things
become possible if that occurs. Beware, beware, beware.
* Apply suggestions from code review
Co-authored-by: Alex Gaynor <alex.gaynor@gmail.com>
* remove unneeded variable
Co-authored-by: Alex Gaynor <alex.gaynor@gmail.com>
Diffstat (limited to 'src/cryptography')
4 files changed, 69 insertions, 21 deletions
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 890e2f952..b84753758 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -186,7 +186,6 @@ class Backend: self._binding = binding.Binding() self._ffi = self._binding.ffi self._lib = self._binding.lib - self._rsa_skip_check_key = False self._fips_enabled = self._is_fips_enabled() self._cipher_registry = {} @@ -542,8 +541,9 @@ class Backend: self.openssl_assert(res == 1) evp_pkey = self._rsa_cdata_to_evp_pkey(rsa_cdata) + # We can skip RSA key validation here since we just generated the key return _RSAPrivateKey( - self, rsa_cdata, evp_pkey, self._rsa_skip_check_key + self, rsa_cdata, evp_pkey, unsafe_skip_rsa_key_validation=True ) def generate_rsa_parameters_supported( @@ -556,7 +556,9 @@ class Backend: ) def load_rsa_private_numbers( - self, numbers: rsa.RSAPrivateNumbers + self, + numbers: rsa.RSAPrivateNumbers, + unsafe_skip_rsa_key_validation: bool, ) -> rsa.RSAPrivateKey: rsa._check_private_key_components( numbers.p, @@ -588,7 +590,10 @@ class Backend: evp_pkey = self._rsa_cdata_to_evp_pkey(rsa_cdata) return _RSAPrivateKey( - self, rsa_cdata, evp_pkey, self._rsa_skip_check_key + self, + rsa_cdata, + evp_pkey, + unsafe_skip_rsa_key_validation=unsafe_skip_rsa_key_validation, ) def load_rsa_public_numbers( @@ -653,7 +658,9 @@ class Backend: bio_data = self._ffi.buffer(buf[0], buf_len)[:] return bio_data - def _evp_pkey_to_private_key(self, evp_pkey) -> PRIVATE_KEY_TYPES: + def _evp_pkey_to_private_key( + self, evp_pkey, unsafe_skip_rsa_key_validation: bool + ) -> PRIVATE_KEY_TYPES: """ Return the appropriate type of PrivateKey given an evp_pkey cdata pointer. @@ -666,7 +673,10 @@ class Backend: self.openssl_assert(rsa_cdata != self._ffi.NULL) rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) return _RSAPrivateKey( - self, rsa_cdata, evp_pkey, self._rsa_skip_check_key + self, + rsa_cdata, + evp_pkey, + unsafe_skip_rsa_key_validation=unsafe_skip_rsa_key_validation, ) elif ( key_type == self._lib.EVP_PKEY_RSA_PSS @@ -685,7 +695,9 @@ class Backend: res = self._lib.i2d_RSAPrivateKey_bio(bio, rsa_cdata) self.openssl_assert(res == 1) return self.load_der_private_key( - self._read_mem_bio(bio), password=None + self._read_mem_bio(bio), + password=None, + unsafe_skip_rsa_key_validation=unsafe_skip_rsa_key_validation, ) elif key_type == self._lib.EVP_PKEY_DSA: dsa_cdata = self._lib.EVP_PKEY_get1_DSA(evp_pkey) @@ -932,13 +944,16 @@ class Backend: return _CMACContext(self, algorithm) def load_pem_private_key( - self, data: bytes, password: typing.Optional[bytes] + self, + data: bytes, + password: typing.Optional[bytes], + unsafe_skip_rsa_key_validation: bool, ) -> PRIVATE_KEY_TYPES: return self._load_key( self._lib.PEM_read_bio_PrivateKey, - self._evp_pkey_to_private_key, data, password, + unsafe_skip_rsa_key_validation, ) def load_pem_public_key(self, data: bytes) -> PUBLIC_KEY_TYPES: @@ -996,7 +1011,10 @@ class Backend: self._handle_key_loading_error() def load_der_private_key( - self, data: bytes, password: typing.Optional[bytes] + self, + data: bytes, + password: typing.Optional[bytes], + unsafe_skip_rsa_key_validation: bool, ) -> PRIVATE_KEY_TYPES: # OpenSSL has a function called d2i_AutoPrivateKey that in theory # handles this automatically, however it doesn't handle encrypted @@ -1005,15 +1023,17 @@ class Backend: bio_data = self._bytes_to_bio(data) key = self._evp_pkey_from_der_traditional_key(bio_data, password) if key: - return self._evp_pkey_to_private_key(key) + return self._evp_pkey_to_private_key( + key, unsafe_skip_rsa_key_validation + ) else: # Finally we try to load it with the method that handles encrypted # PKCS8 properly. return self._load_key( self._lib.d2i_PKCS8PrivateKey_bio, - self._evp_pkey_to_private_key, data, password, + unsafe_skip_rsa_key_validation, ) def _evp_pkey_from_der_traditional_key(self, bio_data, password): @@ -1146,7 +1166,9 @@ class Backend: if self._lib.EVP_PKEY_cmp(key1._evp_pkey, key2._evp_pkey) != 1: raise ValueError("Keys do not correspond") - def _load_key(self, openssl_read_func, convert_func, data, password): + def _load_key( + self, openssl_read_func, data, password, unsafe_skip_rsa_key_validation + ): mem_bio = self._bytes_to_bio(data) userdata = self._ffi.new("CRYPTOGRAPHY_PASSWORD_DATA *") @@ -1192,7 +1214,9 @@ class Backend: password is not None and userdata.called == 1 ) or password is None - return convert_func(evp_pkey) + return self._evp_pkey_to_private_key( + evp_pkey, unsafe_skip_rsa_key_validation + ) def _handle_key_loading_error(self) -> typing.NoReturn: errors = self._consume_errors() @@ -2191,7 +2215,11 @@ class Backend: if evp_pkey_ptr[0] != self._ffi.NULL: evp_pkey = self._ffi.gc(evp_pkey_ptr[0], self._lib.EVP_PKEY_free) - key = self._evp_pkey_to_private_key(evp_pkey) + # We don't support turning off RSA key validation when loading + # PKCS12 keys + key = self._evp_pkey_to_private_key( + evp_pkey, unsafe_skip_rsa_key_validation=False + ) if x509_ptr[0] != self._ffi.NULL: x509 = self._ffi.gc(x509_ptr[0], self._lib.X509_free) diff --git a/src/cryptography/hazmat/backends/openssl/rsa.py b/src/cryptography/hazmat/backends/openssl/rsa.py index 31cff1620..694829d2c 100644 --- a/src/cryptography/hazmat/backends/openssl/rsa.py +++ b/src/cryptography/hazmat/backends/openssl/rsa.py @@ -367,7 +367,12 @@ class _RSAPrivateKey(RSAPrivateKey): _key_size: int def __init__( - self, backend: "Backend", rsa_cdata, evp_pkey, _skip_check_key: bool + self, + backend: "Backend", + rsa_cdata, + evp_pkey, + *, + unsafe_skip_rsa_key_validation: bool, ): res: int # RSA_check_key is slower in OpenSSL 3.0.0 due to improved @@ -375,7 +380,7 @@ class _RSAPrivateKey(RSAPrivateKey): # since users don't load new keys constantly, but for TESTING we've # added an init arg that allows skipping the checks. You should not # use this in production code unless you understand the consequences. - if not _skip_check_key: + if not unsafe_skip_rsa_key_validation: res = backend._lib.RSA_check_key(rsa_cdata) if res != 1: errors = backend._consume_errors_with_text() diff --git a/src/cryptography/hazmat/primitives/asymmetric/rsa.py b/src/cryptography/hazmat/primitives/asymmetric/rsa.py index 5ffe767cd..36d360f22 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/rsa.py +++ b/src/cryptography/hazmat/primitives/asymmetric/rsa.py @@ -354,12 +354,19 @@ class RSAPrivateNumbers: def public_numbers(self) -> "RSAPublicNumbers": return self._public_numbers - def private_key(self, backend: typing.Any = None) -> RSAPrivateKey: + def private_key( + self, + backend: typing.Any = None, + *, + unsafe_skip_rsa_key_validation: bool = False, + ) -> RSAPrivateKey: from cryptography.hazmat.backends.openssl.backend import ( backend as ossl, ) - return ossl.load_rsa_private_numbers(self) + return ossl.load_rsa_private_numbers( + self, unsafe_skip_rsa_key_validation + ) def __eq__(self, other: object) -> bool: if not isinstance(other, RSAPrivateNumbers): diff --git a/src/cryptography/hazmat/primitives/serialization/base.py b/src/cryptography/hazmat/primitives/serialization/base.py index 059b6e40f..8a8417664 100644 --- a/src/cryptography/hazmat/primitives/serialization/base.py +++ b/src/cryptography/hazmat/primitives/serialization/base.py @@ -16,10 +16,14 @@ def load_pem_private_key( data: bytes, password: typing.Optional[bytes], backend: typing.Any = None, + *, + unsafe_skip_rsa_key_validation: bool = False, ) -> PRIVATE_KEY_TYPES: from cryptography.hazmat.backends.openssl.backend import backend as ossl - return ossl.load_pem_private_key(data, password) + return ossl.load_pem_private_key( + data, password, unsafe_skip_rsa_key_validation + ) def load_pem_public_key( @@ -42,10 +46,14 @@ def load_der_private_key( data: bytes, password: typing.Optional[bytes], backend: typing.Any = None, + *, + unsafe_skip_rsa_key_validation: bool = False, ) -> PRIVATE_KEY_TYPES: from cryptography.hazmat.backends.openssl.backend import backend as ossl - return ossl.load_der_private_key(data, password) + return ossl.load_der_private_key( + data, password, unsafe_skip_rsa_key_validation + ) def load_der_public_key( |
