Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/cryptography/hazmat/primitives/asymmetric/rsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ def sign(
self,
data: bytes,
padding: AsymmetricPadding,
algorithm: asym_utils.Prehashed | hashes.HashAlgorithm,
algorithm: asym_utils.Prehashed
| hashes.HashAlgorithm
| asym_utils.NoDigestInfo,
) -> bytes:
"""
Signs the data.
Expand Down Expand Up @@ -121,7 +123,7 @@ def recover_data_from_signature(
self,
signature: bytes,
padding: AsymmetricPadding,
algorithm: hashes.HashAlgorithm | None,
algorithm: hashes.HashAlgorithm | asym_utils.NoDigestInfo | None,
) -> bytes:
"""
Recovers the original data from the signature.
Expand Down
4 changes: 4 additions & 0 deletions src/cryptography/hazmat/primitives/asymmetric/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
encode_dss_signature = asn1.encode_dss_signature


class NoDigestInfo:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to document this in the same section as Prehashed

pass


class Prehashed:
def __init__(self, algorithm: hashes.HashAlgorithm):
if not isinstance(algorithm, hashes.HashAlgorithm):
Expand Down
29 changes: 25 additions & 4 deletions src/rust/src/backend/rsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,8 +290,16 @@ impl RsaPrivateKey {
padding: &pyo3::Bound<'p, pyo3::PyAny>,
algorithm: &pyo3::Bound<'p, pyo3::PyAny>,
) -> CryptographyResult<pyo3::Bound<'p, pyo3::types::PyAny>> {
let (data, algorithm) =
utils::calculate_digest_and_algorithm(py, data.as_bytes(), algorithm)?;
let (data, algorithm) = {
if algorithm.is_instance(&types::NO_DIGEST_INFO.get(py)?)? {
(
utils::BytesOrPyBytes::Bytes(data.as_bytes()),
pyo3::types::PyNone::get(py).to_owned().into_any(),
)
} else {
utils::calculate_digest_and_algorithm(py, data.as_bytes(), algorithm)?
}
};

let mut ctx = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?;
ctx.sign_init().map_err(|_| {
Expand Down Expand Up @@ -434,8 +442,16 @@ impl RsaPublicKey {
padding: &pyo3::Bound<'_, pyo3::PyAny>,
algorithm: &pyo3::Bound<'_, pyo3::PyAny>,
) -> CryptographyResult<()> {
let (data, algorithm) =
utils::calculate_digest_and_algorithm(py, data.as_bytes(), algorithm)?;
let (data, algorithm) = {
if algorithm.is_instance(&types::NO_DIGEST_INFO.get(py)?)? {
(
utils::BytesOrPyBytes::Bytes(data.as_bytes()),
pyo3::types::PyNone::get(py).to_owned().into_any(),
)
} else {
utils::calculate_digest_and_algorithm(py, data.as_bytes(), algorithm)?
}
};

let mut ctx = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?;
ctx.verify_init()?;
Expand Down Expand Up @@ -481,6 +497,11 @@ impl RsaPublicKey {
padding: &pyo3::Bound<'_, pyo3::PyAny>,
algorithm: &pyo3::Bound<'_, pyo3::PyAny>,
) -> CryptographyResult<pyo3::Bound<'p, pyo3::types::PyBytes>> {
let algorithm = if algorithm.is_instance(&types::NO_DIGEST_INFO.get(py)?)? {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error[E0716]: temporary value dropped while borrowed
   --> src\backend\rsa.rs:501:14
    |
500 |         let algorithm = if algorithm.is_instance(&types::NO_DIGEST_INFO.get(py)?)? {
    |             --------- borrow later stored here
501 |             &pyo3::types::PyNone::get(py).to_owned().into_any()
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ creates a temporary value which is freed while still in use
502 |         } else {
    |         - temporary value is freed at the end of this statement
    |
    = note: consider using a `let` binding to create a longer lived value

For more information about this error, try `rustc --explain E0716`.
error: could not compile `cryptography-rust` (lib) due to previous error

&pyo3::types::PyNone::get(py).to_owned().into_any()
} else {
algorithm
};
if algorithm.is_instance(&types::PREHASHED.get(py)?)? {
return Err(CryptographyError::from(
pyo3::exceptions::PyTypeError::new_err(
Expand Down
4 changes: 4 additions & 0 deletions src/rust/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,10 @@ pub static SHA1: LazyPyImport =
pub static SHA256: LazyPyImport =
LazyPyImport::new("cryptography.hazmat.primitives.hashes", &["SHA256"]);

pub static NO_DIGEST_INFO: LazyPyImport = LazyPyImport::new(
"cryptography.hazmat.primitives.asymmetric.utils",
&["NoDigestInfo"],
);
pub static PREHASHED: LazyPyImport = LazyPyImport::new(
"cryptography.hazmat.primitives.asymmetric.utils",
&["Prehashed"],
Expand Down
67 changes: 66 additions & 1 deletion tests/hazmat/primitives/test_rsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@
)
from .utils import (
_check_rsa_private_numbers,
compute_rsa_hash_digest,
generate_rsa_verification_test,
generate_rsa_verification_without_digest_test,
skip_fips_traditional_openssl,
)

Expand Down Expand Up @@ -442,6 +444,49 @@ def test_pkcs1v15_signing(self, backend, subtests):
)
assert binascii.hexlify(signature) == example["signature"]

@pytest.mark.supported(
only_if=lambda backend: backend.rsa_padding_supported(
padding.PKCS1v15()
),
skip_message="Does not support PKCS1v1.5.",
)
@pytest.mark.supported(
only_if=lambda backend: backend.signature_hash_supported(
hashes.SHA1()
),
skip_message="Does not support SHA1 signature.",
)
def test_pkcs1v15_signing_without_digest(self, backend, subtests):
vectors = _flatten_pkcs1_examples(
load_vectors_from_file(
os.path.join("asymmetric", "RSA", "pkcs1v15sign-vectors.txt"),
load_pkcs1_vectors,
)
)
for private, public, example in vectors:
with subtests.test():
private_key = rsa.RSAPrivateNumbers(
p=private["p"],
q=private["q"],
d=private["private_exponent"],
dmp1=private["dmp1"],
dmq1=private["dmq1"],
iqmp=private["iqmp"],
public_numbers=rsa.RSAPublicNumbers(
e=private["public_exponent"], n=private["modulus"]
),
).private_key(backend, unsafe_skip_rsa_key_validation=True)
signature = private_key.sign(
binascii.unhexlify(
compute_rsa_hash_digest(
backend, hashes.SHA1(), example["message"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer we used SHA256 for the tests here because otherwise we'll increasingly have coverage challenges as more and more things disable SHA1.

)
),
padding.PKCS1v15(),
asym_utils.NoDigestInfo(),
)
assert binascii.hexlify(signature) == example["signature"]

@pytest.mark.supported(
only_if=lambda backend: backend.rsa_padding_supported(
padding.PSS(
Expand Down Expand Up @@ -910,7 +955,7 @@ def test_pkcs1v15_verification(self, backend, subtests):
# Test recovery of all data (full DigestInfo) with hash alg. as
# None
rec_sig_data = public_key.recover_data_from_signature(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to document that recover_data_from_signature now takes this new argument.

signature, padding.PKCS1v15(), None
signature, padding.PKCS1v15(), asym_utils.NoDigestInfo()
)
assert len(rec_sig_data) > len(msg_digest)
assert msg_digest == rec_sig_data[-len(msg_digest) :]
Expand Down Expand Up @@ -1522,6 +1567,26 @@ class TestRSAPKCS1Verification:
)
)

test_rsa_pkcs1v15_verify_sha1_without_digest = pytest.mark.supported(
only_if=lambda backend: (
backend.signature_hash_supported(hashes.SHA1())
and backend.rsa_padding_supported(padding.PKCS1v15())
),
skip_message="Does not support SHA1 and PKCS1v1.5.",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same SHA1 concern

)(
generate_rsa_verification_without_digest_test(
load_rsa_nist_vectors,
os.path.join("asymmetric", "RSA", "FIPS_186-2"),
[
"SigGen15_186-2.rsp",
"SigGen15_186-3.rsp",
"SigVer15_186-3.rsp",
],
hashes.SHA1(),
lambda params, hash_alg: padding.PKCS1v15(),
)
)

test_rsa_pkcs1v15_verify_sha224 = pytest.mark.supported(
only_if=lambda backend: (
backend.signature_hash_supported(hashes.SHA224())
Expand Down
36 changes: 36 additions & 0 deletions tests/hazmat/primitives/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
)
from cryptography.hazmat.primitives import hashes, hmac, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.asymmetric import utils as asym_utils
from cryptography.hazmat.primitives.ciphers import (
BlockCipherAlgorithm,
Cipher,
Expand All @@ -37,6 +38,14 @@

from ...utils import load_vectors_from_file

_hash_alg_oids = {
"sha1": binascii.unhexlify(b"3021300906052b0e03021a05000414"),
"sha224": binascii.unhexlify(b"302d300d06096086480165030402040500041c"),
"sha256": binascii.unhexlify(b"3031300d060960864801650304020105000420"),
"sha384": binascii.unhexlify(b"3041300d060960864801650304020205000430"),
"sha512": binascii.unhexlify(b"3051300d060960864801650304020305000440"),
}


def _load_all_params(path, file_names, param_loader):
all_params = []
Expand All @@ -47,6 +56,13 @@ def _load_all_params(path, file_names, param_loader):
return all_params


def compute_rsa_hash_digest(backend, hash_alg, msg):
oid = _hash_alg_oids[hash_alg.name]
h = hashes.Hash(hash_alg, backend=backend)
h.update(binascii.unhexlify(msg))
return binascii.hexlify(oid) + binascii.hexlify(h.finalize())


def generate_encrypt_test(
param_loader, path, file_names, cipher_factory, mode_factory
):
Expand Down Expand Up @@ -497,6 +513,26 @@ def test_rsa_verification(self, backend, subtests):
return test_rsa_verification


def generate_rsa_verification_without_digest_test(
param_loader, path, file_names, hash_alg, pad_factory
):
def test_rsa_verification(self, backend, subtests):
all_params = _load_all_params(path, file_names, param_loader)
all_params = [
i for i in all_params if i["algorithm"] == hash_alg.name.upper()
]
for params in all_params:
with subtests.test():
params["msg"] = compute_rsa_hash_digest(
backend, hash_alg, params["msg"]
)
rsa_verification_test(
backend, params, asym_utils.NoDigestInfo(), pad_factory
)

return test_rsa_verification


def rsa_verification_test(backend, params, hash_alg, pad_factory):
public_numbers = rsa.RSAPublicNumbers(
e=params["public_exponent"], n=params["modulus"]
Expand Down
Loading