summaryrefslogtreecommitdiff
path: root/src/cryptography
diff options
context:
space:
mode:
authorPaul Kehrer <paul.l.kehrer@gmail.com>2022-10-03 09:29:45 -0500
committerGitHub <noreply@github.com>2022-10-03 14:29:45 +0000
commit01687d63a5cb4475d84feae959bec6dea5fce3f1 (patch)
tree9702e32358f4aa17d5a989de83b0ee3d0c8f3456 /src/cryptography
parentfd6bae730503934917050bea112f4d703fd6b237 (diff)
downloadcryptography-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')
-rw-r--r--src/cryptography/hazmat/backends/openssl/backend.py58
-rw-r--r--src/cryptography/hazmat/backends/openssl/rsa.py9
-rw-r--r--src/cryptography/hazmat/primitives/asymmetric/rsa.py11
-rw-r--r--src/cryptography/hazmat/primitives/serialization/base.py12
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(