Skip to content

Commit

Permalink
test and implement signing of pre-hashed messages
Browse files Browse the repository at this point in the history
  • Loading branch information
mathiasertl committed Jan 31, 2025
1 parent 575f821 commit 23a5826
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 12 deletions.
15 changes: 11 additions & 4 deletions ca/django_ca/key_backends/hsm/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
calculate_max_pss_salt_length,
)
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
from cryptography.hazmat.primitives.asymmetric.utils import Prehashed
from cryptography.hazmat.primitives.serialization import load_der_public_key

EdwardsPublicKeyTypeVar = TypeVar("EdwardsPublicKeyTypeVar", ed448.Ed448PublicKey, ed25519.Ed25519PublicKey)
Expand Down Expand Up @@ -144,7 +145,7 @@ def key_size(self) -> int:
return self.public_key().key_size

def _get_pss_signing_parameters(
self, padding: PSS, algorithm: hashes.HashAlgorithm
self, padding: PSS, algorithm: Union[hashes.HashAlgorithm, Prehashed]
) -> tuple[Mechanism, MGF, int]:
# PYLINT NOTE: No public access available.
mgf_algorithm: hashes.HashAlgorithm = padding.mgf._algorithm # pylint: disable=protected-access
Expand All @@ -159,6 +160,9 @@ def _get_pss_signing_parameters(
else:
raise ValueError(f"{mgf_algorithm.name}: Hash algorithm not supported.")

if isinstance(algorithm, Prehashed):
algorithm = algorithm._algorithm # pylint: disable=protected-access # only way to access

if isinstance(algorithm, hashes.SHA224):
pkcs11_algorithm = Mechanism.SHA224
elif isinstance(algorithm, hashes.SHA256):
Expand Down Expand Up @@ -189,9 +193,6 @@ def sign(
padding: AsymmetricPadding,
algorithm: Union[asym_utils.Prehashed, hashes.HashAlgorithm],
) -> bytes:
if isinstance(algorithm, asym_utils.Prehashed):
raise ValueError("Signing of prehashed data is not supported.")

mechanism_param = None
if isinstance(padding, PSS):
mechanism_param = self._get_pss_signing_parameters(padding, algorithm)
Expand All @@ -213,9 +214,15 @@ def sign(
mechanism = pkcs11.Mechanism.SHA512_RSA_PKCS_PSS
elif isinstance(algorithm, hashes.SHA512) and isinstance(padding, PKCS1v15):
mechanism = pkcs11.Mechanism.SHA512_RSA_PKCS
elif isinstance(algorithm, Prehashed) and isinstance(padding, PKCS1v15):
# NOTE: pkcs11.Mechanism.RSA_PKCS does not work.
raise ValueError("Prehashed data with PKCS1v15 is not supported.")
elif isinstance(algorithm, Prehashed) and isinstance(padding, PSS):
mechanism = pkcs11.Mechanism.RSA_PKCS_PSS
elif isinstance(algorithm, (hashes.SHA3_224, hashes.SHA3_384, hashes.SHA3_256, hashes.SHA3_512)):
raise ValueError("SHA3 is not support by the HSM backend.")
else:
assert isinstance(algorithm, hashes.HashAlgorithm) # Cannot be pre-hashed at this point
raise ValueError(
f"{algorithm.name} with {padding.name} padding: Unknown signing algorithm and/or padding."
)
Expand Down
48 changes: 48 additions & 0 deletions ca/django_ca/tests/key_backends/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import dsa, ec, ed448, ed25519, rsa
from cryptography.hazmat.primitives.asymmetric.padding import MGF1, PSS, AsymmetricPadding, PKCS1v15
from cryptography.hazmat.primitives.asymmetric.utils import Prehashed

import pytest

Expand Down Expand Up @@ -98,6 +99,53 @@ def test_sign_data_with_rsa(
assert isinstance(public_key, rsa.RSAPublicKey)
assert public_key.verify(signature, data, algorithm=algorithm, padding=padding) is None

@pytest.mark.parametrize("data", (b"", b"abc"))
@pytest.mark.parametrize(
"algorithm", (hashes.SHA224(), hashes.SHA256(), hashes.SHA384(), hashes.SHA512())
)
def test_sign_data_with_rsa_with_pss_prehashed(
self,
usable_root: CertificateAuthority,
use_key_backend_options: BaseModel,
data: bytes,
algorithm: hashes.HashAlgorithm,
) -> None:
"""Test signing pre-hashed data with PSS padding."""
h = hashes.Hash(algorithm)
h.update(data)
digest = h.finalize()
prehashed_alg = Prehashed(algorithm)
pss = PSS(mgf=MGF1(algorithm), salt_length=0)
signature = usable_root.key_backend.sign_data(
usable_root, use_key_backend_options, digest, algorithm=prehashed_alg, padding=pss
)
public_key = cast(rsa.RSAPublicKey, usable_root.pub.loaded.public_key())
assert isinstance(public_key, rsa.RSAPublicKey)
public_key.verify(signature, data, pss, algorithm)

@pytest.mark.parametrize("data", (b"", b"abc"))
@pytest.mark.parametrize(
"algorithm", (hashes.SHA224(), hashes.SHA256(), hashes.SHA384(), hashes.SHA512())
)
def test_sign_data_with_rsa_with_pkcs15_prehashed(
self,
usable_root: CertificateAuthority,
use_key_backend_options: BaseModel,
data: bytes,
algorithm: hashes.HashAlgorithm,
) -> None:
"""Test signing pre-hashed data with PKCS1v15 padding."""
h = hashes.Hash(algorithm)
h.update(data)
digest = h.finalize()
prehashed_alg = Prehashed(algorithm)
signature = usable_root.key_backend.sign_data(
usable_root, use_key_backend_options, digest, algorithm=prehashed_alg, padding=PKCS1v15()
)
public_key = cast(rsa.RSAPublicKey, usable_root.pub.loaded.public_key())
assert isinstance(public_key, rsa.RSAPublicKey)
public_key.verify(signature, data, PKCS1v15(), algorithm)

@pytest.mark.parametrize("data", (b"", b"abc"))
@pytest.mark.parametrize(
"algorithm", (hashes.SHA224(), hashes.SHA256(), hashes.SHA384(), hashes.SHA512())
Expand Down
17 changes: 16 additions & 1 deletion ca/django_ca/tests/key_backends/hsm/test_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@

from cryptography import x509
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric.padding import MGF1, PSS, AsymmetricPadding
from cryptography.hazmat.primitives.asymmetric.padding import MGF1, PSS, AsymmetricPadding, PKCS1v15
from cryptography.hazmat.primitives.asymmetric.utils import Prehashed

from django.conf import settings

Expand Down Expand Up @@ -269,6 +270,9 @@ def test_sign_data_with_dsa(self) -> None: # type: ignore[override]
def test_sign_data_with_dsa_without_algorithm(self) -> None: # type: ignore[override]
pytest.xfail("DSA is not supported for HSMs.")

def test_sign_data_with_rsa_with_pkcs15_prehashed(self) -> None: # type: ignore[override]
pytest.xfail("Prehashed data with PKCS1v15 padding is not supported.")

def test_sign_data_with_rsa_with_unsupported_algorithm(
self, usable_root: CertificateAuthority, use_key_backend_options: HSMUsePrivateKeyOptions
) -> None:
Expand Down Expand Up @@ -302,3 +306,14 @@ def test_sign_data_with_rsa_with_unsupported_digest_length(
usable_root.key_backend.sign_data(
usable_root, use_key_backend_options, b"", algorithm=hashes.SHA256(), padding=padding
)

def test_sign_data_with_rsa_with_prehashed_and_pkcs1v15(
self, usable_root: CertificateAuthority, use_key_backend_options: HSMUsePrivateKeyOptions
) -> None:
"""Try signing pre-hashed data with PKCS1v15, which is not supported.."""
padding = PKCS1v15()
algo = Prehashed(hashes.SHA256())
with pytest.raises(ValueError, match=r"^Prehashed data with PKCS1v15 is not supported\.$"):
usable_root.key_backend.sign_data(
usable_root, use_key_backend_options, b"", algorithm=algo, padding=padding
)
7 changes: 0 additions & 7 deletions ca/django_ca/tests/key_backends/hsm/test_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,6 @@ def test_not_implemented_error(key_class: type[PKCS11PrivateKeyTypes]) -> None:
key.decrypt(b"foo", PKCS1v15())


def test_rsa_with_prehashed() -> None:
"""Test signing data with prehashed data, which we do not support."""
key = PKCS11RSAPrivateKey(None, "key_id", "key_label")
with pytest.raises(ValueError, match=r"^Signing of prehashed data is not supported\.$"):
key.sign(b"", PKCS1v15(), Prehashed(hashes.SHA512()))


def test_rsa_with_sha3_error() -> None:
"""Test signing data with SHA3, which is unsupported (by the underlying library)."""
key = PKCS11RSAPrivateKey(None, "key_id", "key_label")
Expand Down

0 comments on commit 23a5826

Please sign in to comment.