Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Load RSA PSS keys as regular RSA keys #7112

Merged
merged 5 commits into from Apr 26, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Expand Up @@ -70,6 +70,12 @@ Changelog
to :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS`. This
constant will set the salt length to the same length as the ``PSS`` hash
algorithm.
* Added support for loading RSA-PSS key types with
:func:`~cryptography.hazmat.primitives.serialization.load_pem_private_key`
and
:func:`~cryptography.hazmat.primitives.serialization.load_der_private_key`.
This functionality is limited to OpenSSL 1.1.1e+ and loads the key as a
normal RSA private key, discarding the PSS constraint information.

.. _v36-0-2:

Expand Down
3 changes: 3 additions & 0 deletions src/_cffi_src/openssl/cryptography.py
Expand Up @@ -70,6 +70,8 @@
(OPENSSL_VERSION_NUMBER < 0x10101020 || CRYPTOGRAPHY_IS_LIBRESSL)
#define CRYPTOGRAPHY_OPENSSL_LESS_THAN_111D \
(OPENSSL_VERSION_NUMBER < 0x10101040 || CRYPTOGRAPHY_IS_LIBRESSL)
#define CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E \
(OPENSSL_VERSION_NUMBER < 0x10101050 || CRYPTOGRAPHY_IS_LIBRESSL)
#if (CRYPTOGRAPHY_OPENSSL_LESS_THAN_111D && !CRYPTOGRAPHY_IS_LIBRESSL && \
!defined(OPENSSL_NO_ENGINE)) || defined(USE_OSRANDOM_RNG_FOR_TESTING)
#define CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE 1
Expand All @@ -84,6 +86,7 @@

static const int CRYPTOGRAPHY_OPENSSL_LESS_THAN_111;
static const int CRYPTOGRAPHY_OPENSSL_LESS_THAN_111B;
static const int CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E;
static const int CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE;

static const int CRYPTOGRAPHY_LIBRESSL_LESS_THAN_340;
Expand Down
7 changes: 7 additions & 0 deletions src/_cffi_src/openssl/evp.py
Expand Up @@ -16,6 +16,7 @@
typedef ... EVP_PKEY;
typedef ... EVP_PKEY_CTX;
static const int EVP_PKEY_RSA;
static const int EVP_PKEY_RSA_PSS;
static const int EVP_PKEY_DSA;
static const int EVP_PKEY_DH;
static const int EVP_PKEY_DHX;
Expand Down Expand Up @@ -293,4 +294,10 @@
#else
static const long Cryptography_HAS_EVP_PKEY_DH = 1;
#endif

// This can be removed when we drop OpenSSL 1.1.0 support
// OPENSSL_LESS_THAN_111
#if !defined(EVP_PKEY_RSA_PSS)
#define EVP_PKEY_RSA_PSS 912
#endif
"""
24 changes: 24 additions & 0 deletions src/cryptography/hazmat/backends/openssl/backend.py
Expand Up @@ -644,6 +644,30 @@ def _evp_pkey_to_private_key(self, evp_pkey) -> PRIVATE_KEY_TYPES:
return _RSAPrivateKey(
self, rsa_cdata, evp_pkey, self._rsa_skip_check_key
)
elif (
key_type == self._lib.EVP_PKEY_RSA_PSS
and not self._lib.CRYPTOGRAPHY_IS_LIBRESSL
and not self._lib.CRYPTOGRAPHY_IS_BORINGSSL
and not self._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E
):
# At the moment the way we handle RSA PSS keys is to strip the
# PSS constraints from them and treat them as normal RSA keys
# Unfortunately the RSA * itself tracks this data so we need to
# extract, serialize, and reload it without the constraints.
rsa_cdata = self._lib.EVP_PKEY_get1_RSA(evp_pkey)
self.openssl_assert(rsa_cdata != self._ffi.NULL)
rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free)
bio = self._create_mem_bio_gc()
res = self._lib.i2d_RSAPrivateKey_bio(bio, rsa_cdata)
self.openssl_assert(res == 1)
new_evp_pkey = self._lib.d2i_PrivateKey_bio(bio, self._ffi.NULL)
self.openssl_assert(new_evp_pkey != self._ffi.NULL)
new_rsa_cdata = self._lib.EVP_PKEY_get1_RSA(new_evp_pkey)
self.openssl_assert(new_rsa_cdata != self._ffi.NULL)
new_rsa_cdata = self._ffi.gc(new_rsa_cdata, self._lib.RSA_free)
return _RSAPrivateKey(
self, new_rsa_cdata, new_evp_pkey, self._rsa_skip_check_key
)
reaperhulk marked this conversation as resolved.
Show resolved Hide resolved
elif key_type == self._lib.EVP_PKEY_DSA:
dsa_cdata = self._lib.EVP_PKEY_get1_DSA(evp_pkey)
self.openssl_assert(dsa_cdata != self._ffi.NULL)
Expand Down
59 changes: 59 additions & 0 deletions tests/hazmat/primitives/test_rsa.py
Expand Up @@ -11,6 +11,7 @@

from cryptography.exceptions import (
InvalidSignature,
UnsupportedAlgorithm,
_Reasons,
)
from cryptography.hazmat.primitives import hashes, serialization
Expand Down Expand Up @@ -256,6 +257,64 @@ def test_load_pss_vect_example_keys(self, pkcs1_example):
assert public_num.n == public_num2.n
assert public_num.e == public_num2.e

@pytest.mark.supported(
only_if=lambda backend: (
not backend._lib.CRYPTOGRAPHY_IS_LIBRESSL
and not backend._lib.CRYPTOGRAPHY_IS_BORINGSSL
and not backend._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E
),
skip_message="Does not support RSA PSS loading",
)
@pytest.mark.parametrize(
"path",
[
os.path.join("asymmetric", "PKCS8", "rsa_pss_2048.pem"),
os.path.join("asymmetric", "PKCS8", "rsa_pss_2048_hash.pem"),
os.path.join("asymmetric", "PKCS8", "rsa_pss_2048_hash_mask.pem"),
os.path.join(
"asymmetric", "PKCS8", "rsa_pss_2048_hash_mask_diff.pem"
),
os.path.join(
"asymmetric", "PKCS8", "rsa_pss_2048_hash_mask_salt.pem"
),
],
)
def test_load_pss_keys_strips_constraints(self, path, backend):
key = load_vectors_from_file(
filename=path,
loader=lambda p: serialization.load_pem_private_key(
p.read(), None
),
mode="rb",
)
# These keys have constraints that prohibit PKCS1v15 signing,
# but for now we load them without the constraint and test that
# it's truly removed by performing a disallowed signature.
assert isinstance(key, rsa.RSAPrivateKey)
key.sign(b"whatever", padding.PKCS1v15(), hashes.SHA224())
reaperhulk marked this conversation as resolved.
Show resolved Hide resolved

@pytest.mark.supported(
only_if=lambda backend: (
backend._lib.CRYPTOGRAPHY_IS_LIBRESSL
or backend._lib.CRYPTOGRAPHY_IS_BORINGSSL
or backend._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111E
),
skip_message="Test requires a backend without RSA-PSS key support",
)
def test_load_pss_unsupported(self, backend):
# Key loading errors unfortunately have multiple paths so
# we need to allow ValueError and UnsupportedAlgorithm
with pytest.raises((UnsupportedAlgorithm, ValueError)):
load_vectors_from_file(
filename=os.path.join(
"asymmetric", "PKCS8", "rsa_pss_2048.pem"
),
loader=lambda p: serialization.load_pem_private_key(
p.read(), None
reaperhulk marked this conversation as resolved.
Show resolved Hide resolved
),
mode="rb",
)

@pytest.mark.parametrize(
"vector",
load_vectors_from_file(
Expand Down