summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Kehrer <paul.l.kehrer@gmail.com>2023-05-11 09:09:56 +0800
committerGitHub <noreply@github.com>2023-05-10 21:09:56 -0400
commit1ef3cdb616c7a304e75c89ad458e49c1fbd5943f (patch)
treea3b74f2436f731e98f760ea7169b4ba29b76867e
parentcfee3c85a7d7e9c60f8041678c3070380ac3ca3d (diff)
downloadcryptography-1ef3cdb616c7a304e75c89ad458e49c1fbd5943f.tar.gz
support X.509 certificate PSS signing (#8888)
* support X.509 certificate PSS signing no CSR, CRL, etc * handle PSS.(MAX_LENGTH, DIGEST_LENGTH), review feedback * name the kwarg * test improvements * skip if sha3 isn't supported
-rw-r--r--CHANGELOG.rst3
-rw-r--r--docs/x509/reference.rst18
-rw-r--r--src/cryptography/hazmat/bindings/_rust/x509.pyi2
-rw-r--r--src/cryptography/x509/base.py14
-rw-r--r--src/rust/cryptography-x509/src/common.rs18
-rw-r--r--src/rust/src/pkcs7.rs17
-rw-r--r--src/rust/src/x509/certificate.rs7
-rw-r--r--src/rust/src/x509/crl.rs16
-rw-r--r--src/rust/src/x509/csr.rs15
-rw-r--r--src/rust/src/x509/ocsp_resp.rs15
-rw-r--r--src/rust/src/x509/sign.rs145
-rw-r--r--tests/x509/test_x509.py163
12 files changed, 405 insertions, 28 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index d4fd57624..5073ce32b 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -23,6 +23,9 @@ Changelog
* Added support for obtaining X.509 certificate signature algorithm parameters
(including PSS) via
:meth:`~cryptography.x509.Certificate.signature_algorithm_parameters`.
+* Support signing :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS`
+ X.509 certificates via the new keyword-only argument ``rsa_padding`` on
+ :meth:`~cryptography.x509.CertificateBuilder.sign`.
.. _v40-0-2:
diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst
index 647666c5c..e14c8ffc1 100644
--- a/docs/x509/reference.rst
+++ b/docs/x509/reference.rst
@@ -872,7 +872,7 @@ X.509 Certificate Builder
:param critical: Set to ``True`` if the extension must be understood and
handled by whoever reads the certificate.
- .. method:: sign(private_key, algorithm)
+ .. method:: sign(private_key, algorithm, *, rsa_padding=None)
Sign the certificate using the CA's private key.
@@ -891,6 +891,22 @@ X.509 Certificate Builder
:class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`
otherwise.
+ :param rsa_padding:
+
+ .. versionadded:: 41.0.0
+
+ This is a keyword-only argument. If ``private_key`` is an
+ ``RSAPrivateKey`` then this can be set to either
+ :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15` or
+ :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` to sign
+ with those respective paddings. If this is ``None`` then RSA
+ keys will default to ``PKCS1v15`` padding. All other key types **must**
+ not pass a value other than ``None``.
+
+ :type rsa_padding: ``None``,
+ :class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15`,
+ or :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS`
+
:returns: :class:`~cryptography.x509.Certificate`
diff --git a/src/cryptography/hazmat/bindings/_rust/x509.pyi b/src/cryptography/hazmat/bindings/_rust/x509.pyi
index 71c8d5c22..24b2f5e3a 100644
--- a/src/cryptography/hazmat/bindings/_rust/x509.pyi
+++ b/src/cryptography/hazmat/bindings/_rust/x509.pyi
@@ -6,6 +6,7 @@ import typing
from cryptography import x509
from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives.asymmetric.padding import PSS, PKCS1v15
from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes
def load_pem_x509_certificate(data: bytes) -> x509.Certificate: ...
@@ -23,6 +24,7 @@ def create_x509_certificate(
builder: x509.CertificateBuilder,
private_key: PrivateKeyTypes,
hash_algorithm: typing.Optional[hashes.HashAlgorithm],
+ padding: typing.Optional[typing.Union[PKCS1v15, PSS]],
) -> x509.Certificate: ...
def create_x509_csr(
builder: x509.CertificateSigningRequestBuilder,
diff --git a/src/cryptography/x509/base.py b/src/cryptography/x509/base.py
index 64453eb70..576385e08 100644
--- a/src/cryptography/x509/base.py
+++ b/src/cryptography/x509/base.py
@@ -924,6 +924,10 @@ class CertificateBuilder:
private_key: CertificateIssuerPrivateKeyTypes,
algorithm: typing.Optional[_AllowedHashTypes],
backend: typing.Any = None,
+ *,
+ rsa_padding: typing.Optional[
+ typing.Union[padding.PSS, padding.PKCS1v15]
+ ] = None,
) -> Certificate:
"""
Signs the certificate using the CA's private key.
@@ -946,7 +950,15 @@ class CertificateBuilder:
if self._public_key is None:
raise ValueError("A certificate must have a public key")
- return rust_x509.create_x509_certificate(self, private_key, algorithm)
+ if rsa_padding is not None:
+ if not isinstance(rsa_padding, (padding.PSS, padding.PKCS1v15)):
+ raise TypeError("Padding must be PSS or PKCS1v15")
+ if not isinstance(private_key, rsa.RSAPrivateKey):
+ raise TypeError("Padding is only supported for RSA keys")
+
+ return rust_x509.create_x509_certificate(
+ self, private_key, algorithm, rsa_padding
+ )
class CertificateRevocationListBuilder:
diff --git a/src/rust/cryptography-x509/src/common.rs b/src/rust/cryptography-x509/src/common.rs
index d09971659..60856b7ef 100644
--- a/src/rust/cryptography-x509/src/common.rs
+++ b/src/rust/cryptography-x509/src/common.rs
@@ -6,7 +6,7 @@ use crate::oid;
use asn1::Asn1DefinedByWritable;
use std::marker::PhantomData;
-#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash, Clone, Eq)]
+#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash, Clone, Eq, Debug)]
pub struct AlgorithmIdentifier<'a> {
pub oid: asn1::DefinedByMarker<asn1::ObjectIdentifier>,
#[defined_by(oid)]
@@ -19,7 +19,7 @@ impl AlgorithmIdentifier<'_> {
}
}
-#[derive(asn1::Asn1DefinedByRead, asn1::Asn1DefinedByWrite, PartialEq, Eq, Hash, Clone)]
+#[derive(asn1::Asn1DefinedByRead, asn1::Asn1DefinedByWrite, PartialEq, Eq, Hash, Clone, Debug)]
pub enum AlgorithmParameters<'a> {
#[defined_by(oid::SHA1_OID)]
Sha1(asn1::Null),
@@ -31,6 +31,14 @@ pub enum AlgorithmParameters<'a> {
Sha384(asn1::Null),
#[defined_by(oid::SHA512_OID)]
Sha512(asn1::Null),
+ #[defined_by(oid::SHA3_224_OID)]
+ Sha3_224(asn1::Null),
+ #[defined_by(oid::SHA3_256_OID)]
+ Sha3_256(asn1::Null),
+ #[defined_by(oid::SHA3_384_OID)]
+ Sha3_384(asn1::Null),
+ #[defined_by(oid::SHA3_512_OID)]
+ Sha3_512(asn1::Null),
#[defined_by(oid::ED25519_OID)]
Ed25519,
@@ -225,7 +233,7 @@ pub const PSS_SHA1_HASH_ALG: AlgorithmIdentifier<'_> = AlgorithmIdentifier {
// This is defined as an AlgorithmIdentifier in RFC 4055,
// but the mask generation algorithm **must** contain an AlgorithmIdentifier
// in its params, so we define it this way.
-#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, Clone, PartialEq, Eq)]
+#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, Clone, PartialEq, Eq, Debug)]
pub struct MaskGenAlgorithm<'a> {
pub oid: asn1::ObjectIdentifier,
pub params: AlgorithmIdentifier<'a>,
@@ -245,7 +253,7 @@ pub const PSS_SHA1_MASK_GEN_ALG: MaskGenAlgorithm<'_> = MaskGenAlgorithm {
// mgf1SHA1Identifier,
// saltLength [2] INTEGER DEFAULT 20,
// trailerField [3] INTEGER DEFAULT 1 }
-#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, Clone, PartialEq, Eq)]
+#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, Clone, PartialEq, Eq, Debug)]
pub struct RsaPssParameters<'a> {
#[explicit(0)]
#[default(PSS_SHA1_HASH_ALG)]
@@ -258,7 +266,7 @@ pub struct RsaPssParameters<'a> {
pub salt_length: u16,
#[explicit(3)]
#[default(1u8)]
- _trailer_field: u8,
+ pub _trailer_field: u8,
}
/// A VisibleString ASN.1 element whose contents is not validated as meeting the
diff --git a/src/rust/src/pkcs7.rs b/src/rust/src/pkcs7.rs
index 6bc90173f..17a83fd16 100644
--- a/src/rust/src/pkcs7.rs
+++ b/src/rust/src/pkcs7.rs
@@ -130,7 +130,13 @@ fn sign_and_serialize<'p>(
{
(
None,
- x509::sign::sign_data(py, py_private_key, py_hash_alg, &data_with_header)?,
+ x509::sign::sign_data(
+ py,
+ py_private_key,
+ py_hash_alg,
+ py.None().into_ref(py),
+ &data_with_header,
+ )?,
)
} else {
let mut authenticated_attrs = vec![];
@@ -175,7 +181,13 @@ fn sign_and_serialize<'p>(
Some(common::Asn1ReadableOrWritable::new_write(
asn1::SetOfWriter::new(authenticated_attrs),
)),
- x509::sign::sign_data(py, py_private_key, py_hash_alg, &signed_data)?,
+ x509::sign::sign_data(
+ py,
+ py_private_key,
+ py_hash_alg,
+ py.None().into_ref(py),
+ &signed_data,
+ )?,
)
};
@@ -201,6 +213,7 @@ fn sign_and_serialize<'p>(
py,
py_private_key,
py_hash_alg,
+ py.None().into_ref(py),
)?,
encrypted_digest: signature,
unauthenticated_attributes: None,
diff --git a/src/rust/src/x509/certificate.rs b/src/rust/src/x509/certificate.rs
index dbe761fb9..f77f141fa 100644
--- a/src/rust/src/x509/certificate.rs
+++ b/src/rust/src/x509/certificate.rs
@@ -1002,8 +1002,10 @@ fn create_x509_certificate(
builder: &pyo3::PyAny,
private_key: &pyo3::PyAny,
hash_algorithm: &pyo3::PyAny,
+ rsa_padding: &pyo3::PyAny,
) -> CryptographyResult<Certificate> {
- let sigalg = x509::sign::compute_signature_algorithm(py, private_key, hash_algorithm)?;
+ let sigalg =
+ x509::sign::compute_signature_algorithm(py, private_key, hash_algorithm, rsa_padding)?;
let serialization_mod = py.import(pyo3::intern!(
py,
"cryptography.hazmat.primitives.serialization"
@@ -1056,7 +1058,8 @@ fn create_x509_certificate(
};
let tbs_bytes = asn1::write_single(&tbs_cert)?;
- let signature = x509::sign::sign_data(py, private_key, hash_algorithm, &tbs_bytes)?;
+ let signature =
+ x509::sign::sign_data(py, private_key, hash_algorithm, rsa_padding, &tbs_bytes)?;
let data = asn1::write_single(&cryptography_x509::certificate::Certificate {
tbs_cert,
signature_alg: sigalg,
diff --git a/src/rust/src/x509/crl.rs b/src/rust/src/x509/crl.rs
index 6bb08779a..1331d3377 100644
--- a/src/rust/src/x509/crl.rs
+++ b/src/rust/src/x509/crl.rs
@@ -580,8 +580,12 @@ fn create_x509_crl(
private_key: &pyo3::PyAny,
hash_algorithm: &pyo3::PyAny,
) -> CryptographyResult<CertificateRevocationList> {
- let sigalg = x509::sign::compute_signature_algorithm(py, private_key, hash_algorithm)?;
-
+ let sigalg = x509::sign::compute_signature_algorithm(
+ py,
+ private_key,
+ hash_algorithm,
+ py.None().into_ref(py),
+ )?;
let mut revoked_certs = vec![];
for py_revoked_cert in builder
.getattr(pyo3::intern!(py, "_revoked_certificates"))?
@@ -628,7 +632,13 @@ fn create_x509_crl(
};
let tbs_bytes = asn1::write_single(&tbs_cert_list)?;
- let signature = x509::sign::sign_data(py, private_key, hash_algorithm, &tbs_bytes)?;
+ let signature = x509::sign::sign_data(
+ py,
+ private_key,
+ hash_algorithm,
+ py.None().into_ref(py),
+ &tbs_bytes,
+ )?;
let data = asn1::write_single(&crl::CertificateRevocationList {
tbs_cert_list,
signature_algorithm: sigalg,
diff --git a/src/rust/src/x509/csr.rs b/src/rust/src/x509/csr.rs
index 7ceed3511..110acf3a1 100644
--- a/src/rust/src/x509/csr.rs
+++ b/src/rust/src/x509/csr.rs
@@ -294,7 +294,12 @@ fn create_x509_csr(
private_key: &pyo3::PyAny,
hash_algorithm: &pyo3::PyAny,
) -> CryptographyResult<CertificateSigningRequest> {
- let sigalg = x509::sign::compute_signature_algorithm(py, private_key, hash_algorithm)?;
+ let sigalg = x509::sign::compute_signature_algorithm(
+ py,
+ private_key,
+ hash_algorithm,
+ py.None().into_ref(py),
+ )?;
let serialization_mod = py.import(pyo3::intern!(
py,
"cryptography.hazmat.primitives.serialization"
@@ -364,7 +369,13 @@ fn create_x509_csr(
};
let tbs_bytes = asn1::write_single(&csr_info)?;
- let signature = x509::sign::sign_data(py, private_key, hash_algorithm, &tbs_bytes)?;
+ let signature = x509::sign::sign_data(
+ py,
+ private_key,
+ hash_algorithm,
+ py.None().into_ref(py),
+ &tbs_bytes,
+ )?;
let data = asn1::write_single(&Csr {
csr_info,
signature_alg: sigalg,
diff --git a/src/rust/src/x509/ocsp_resp.rs b/src/rust/src/x509/ocsp_resp.rs
index 728eb92ce..f2a86241e 100644
--- a/src/rust/src/x509/ocsp_resp.rs
+++ b/src/rust/src/x509/ocsp_resp.rs
@@ -679,9 +679,20 @@ fn create_ocsp_response(
)?,
};
- let sigalg = x509::sign::compute_signature_algorithm(py, private_key, hash_algorithm)?;
+ let sigalg = x509::sign::compute_signature_algorithm(
+ py,
+ private_key,
+ hash_algorithm,
+ py.None().into_ref(py),
+ )?;
let tbs_bytes = asn1::write_single(&tbs_response_data)?;
- let signature = x509::sign::sign_data(py, private_key, hash_algorithm, &tbs_bytes)?;
+ let signature = x509::sign::sign_data(
+ py,
+ private_key,
+ hash_algorithm,
+ py.None().into_ref(py),
+ &tbs_bytes,
+ )?;
if !responder_cert
.call_method0(pyo3::intern!(py, "public_key"))?
diff --git a/src/rust/src/x509/sign.rs b/src/rust/src/x509/sign.rs
index 5c69ecedf..c0b0ec5de 100644
--- a/src/rust/src/x509/sign.rs
+++ b/src/rust/src/x509/sign.rs
@@ -4,7 +4,7 @@
use crate::error::{CryptographyError, CryptographyResult};
use crate::exceptions;
-use cryptography_x509::common;
+use cryptography_x509::{common, oid};
#[derive(Debug, PartialEq)]
pub(crate) enum KeyType {
@@ -119,14 +119,88 @@ fn identify_hash_type(
}
}
+fn compute_pss_salt_length<'p>(
+ py: pyo3::Python<'p>,
+ private_key: &'p pyo3::PyAny,
+ hash_algorithm: &'p pyo3::PyAny,
+ rsa_padding: &'p pyo3::PyAny,
+) -> pyo3::PyResult<u16> {
+ let padding_mod = py.import(pyo3::intern!(
+ py,
+ "cryptography.hazmat.primitives.asymmetric.padding"
+ ))?;
+ let maxlen = padding_mod.getattr(pyo3::intern!(py, "_MaxLength"))?;
+ let digestlen = padding_mod.getattr(pyo3::intern!(py, "_DigestLength"))?;
+ let py_saltlen = rsa_padding.getattr(pyo3::intern!(py, "_salt_length"))?;
+ if py_saltlen.is_instance(maxlen)? {
+ padding_mod
+ .getattr(pyo3::intern!(py, "calculate_max_pss_salt_length"))?
+ .call1((private_key, hash_algorithm))?
+ .extract::<u16>()
+ } else if py_saltlen.is_instance(digestlen)? {
+ hash_algorithm
+ .getattr(pyo3::intern!(py, "digest_size"))?
+ .extract::<u16>()
+ } else if py_saltlen.is_instance(py.get_type::<pyo3::types::PyLong>())? {
+ py_saltlen.extract::<u16>()
+ } else {
+ Err(pyo3::exceptions::PyTypeError::new_err(
+ "salt_length must be an int, MaxLength, or DigestLength.",
+ ))
+ }
+}
+
pub(crate) fn compute_signature_algorithm<'p>(
py: pyo3::Python<'p>,
private_key: &'p pyo3::PyAny,
hash_algorithm: &'p pyo3::PyAny,
+ rsa_padding: &'p pyo3::PyAny,
) -> pyo3::PyResult<common::AlgorithmIdentifier<'static>> {
let key_type = identify_key_type(py, private_key)?;
let hash_type = identify_hash_type(py, hash_algorithm)?;
+ let pss_type: &pyo3::types::PyType = py
+ .import(pyo3::intern!(
+ py,
+ "cryptography.hazmat.primitives.asymmetric.padding"
+ ))?
+ .getattr(pyo3::intern!(py, "PSS"))?
+ .extract()?;
+ // If this is RSA-PSS we need to compute the signature algorithm from the
+ // parameters provided in rsa_padding.
+ if !rsa_padding.is_none() && rsa_padding.is_instance(pss_type)? {
+ let hash_alg_params = identify_alg_params_for_hash_type(hash_type)?;
+ let hash_algorithm_id = common::AlgorithmIdentifier {
+ oid: asn1::DefinedByMarker::marker(),
+ params: hash_alg_params,
+ };
+ let salt_length = compute_pss_salt_length(py, private_key, hash_algorithm, rsa_padding)?;
+ let py_mgf_alg = rsa_padding
+ .getattr(pyo3::intern!(py, "_mgf"))?
+ .getattr(pyo3::intern!(py, "_algorithm"))?;
+ let mgf_hash_type = identify_hash_type(py, py_mgf_alg)?;
+ let mgf_alg = common::AlgorithmIdentifier {
+ oid: asn1::DefinedByMarker::marker(),
+ params: identify_alg_params_for_hash_type(mgf_hash_type)?,
+ };
+ let params =
+ common::AlgorithmParameters::RsaPss(Some(Box::new(common::RsaPssParameters {
+ hash_algorithm: hash_algorithm_id,
+ mask_gen_algorithm: common::MaskGenAlgorithm {
+ oid: oid::MGF1_OID,
+ params: mgf_alg,
+ },
+ salt_length,
+ _trailer_field: 1,
+ })));
+
+ return Ok(common::AlgorithmIdentifier {
+ oid: asn1::DefinedByMarker::marker(),
+ params,
+ });
+ }
+ // It's not an RSA PSS signature, so we compute the signature algorithm from
+ // the union of key type and hash type.
match (key_type, hash_type) {
(KeyType::Ed25519, HashType::None) => Ok(common::AlgorithmIdentifier {
oid: asn1::DefinedByMarker::marker(),
@@ -238,6 +312,7 @@ pub(crate) fn sign_data<'p>(
py: pyo3::Python<'p>,
private_key: &'p pyo3::PyAny,
hash_algorithm: &'p pyo3::PyAny,
+ rsa_padding: &'p pyo3::PyAny,
data: &[u8],
) -> pyo3::PyResult<&'p [u8]> {
let key_type = identify_key_type(py, private_key)?;
@@ -257,14 +332,17 @@ pub(crate) fn sign_data<'p>(
private_key.call_method1(pyo3::intern!(py, "sign"), (data, ecdsa))?
}
KeyType::Rsa => {
- let padding_mod = py.import(pyo3::intern!(
- py,
- "cryptography.hazmat.primitives.asymmetric.padding"
- ))?;
- let pkcs1v15 = padding_mod
- .getattr(pyo3::intern!(py, "PKCS1v15"))?
- .call0()?;
- private_key.call_method1(pyo3::intern!(py, "sign"), (data, pkcs1v15, hash_algorithm))?
+ let mut padding = rsa_padding;
+ if padding.is_none() {
+ let padding_mod = py.import(pyo3::intern!(
+ py,
+ "cryptography.hazmat.primitives.asymmetric.padding"
+ ))?;
+ padding = padding_mod
+ .getattr(pyo3::intern!(py, "PKCS1v15"))?
+ .call0()?;
+ }
+ private_key.call_method1(pyo3::intern!(py, "sign"), (data, padding, hash_algorithm))?
}
KeyType::Dsa => {
private_key.call_method1(pyo3::intern!(py, "sign"), (data, hash_algorithm))?
@@ -435,10 +513,29 @@ fn identify_key_hash_type_for_algorithm_params(
}
}
+fn identify_alg_params_for_hash_type(
+ hash_type: HashType,
+) -> pyo3::PyResult<common::AlgorithmParameters<'static>> {
+ match hash_type {
+ HashType::Sha224 => Ok(common::AlgorithmParameters::Sha224(())),
+ HashType::Sha256 => Ok(common::AlgorithmParameters::Sha256(())),
+ HashType::Sha384 => Ok(common::AlgorithmParameters::Sha384(())),
+ HashType::Sha512 => Ok(common::AlgorithmParameters::Sha512(())),
+ HashType::Sha3_224 => Ok(common::AlgorithmParameters::Sha3_224(())),
+ HashType::Sha3_256 => Ok(common::AlgorithmParameters::Sha3_256(())),
+ HashType::Sha3_384 => Ok(common::AlgorithmParameters::Sha3_384(())),
+ HashType::Sha3_512 => Ok(common::AlgorithmParameters::Sha3_512(())),
+ HashType::None => Err(pyo3::exceptions::PyTypeError::new_err(
+ "Algorithm must be a registered hash algorithm, not None.",
+ )),
+ }
+}
+
#[cfg(test)]
mod tests {
use super::{
- identify_key_hash_type_for_algorithm_params, py_hash_name_from_hash_type, HashType, KeyType,
+ identify_alg_params_for_hash_type, identify_key_hash_type_for_algorithm_params,
+ py_hash_name_from_hash_type, HashType, KeyType,
};
use cryptography_x509::{common, oid};
@@ -604,6 +701,34 @@ mod tests {
}
#[test]
+ fn test_identify_alg_params_for_hash_type() {
+ for (hash, params) in [
+ (HashType::Sha224, common::AlgorithmParameters::Sha224(())),
+ (HashType::Sha256, common::AlgorithmParameters::Sha256(())),
+ (HashType::Sha384, common::AlgorithmParameters::Sha384(())),
+ (HashType::Sha512, common::AlgorithmParameters::Sha512(())),
+ (
+ HashType::Sha3_224,
+ common::AlgorithmParameters::Sha3_224(()),
+ ),
+ (
+ HashType::Sha3_256,
+ common::AlgorithmParameters::Sha3_256(()),
+ ),
+ (
+ HashType::Sha3_384,
+ common::AlgorithmParameters::Sha3_384(()),
+ ),
+ (
+ HashType::Sha3_512,
+ common::AlgorithmParameters::Sha3_512(()),
+ ),
+ ] {
+ assert_eq!(identify_alg_params_for_hash_type(hash).unwrap(), params);
+ }
+ }
+
+ #[test]
fn test_py_hash_name_from_hash_type() {
for (hash, name) in [
(HashType::Sha224, "SHA224"),
diff --git a/tests/x509/test_x509.py b/tests/x509/test_x509.py
index b33e09ce5..19a854e24 100644
--- a/tests/x509/test_x509.py
+++ b/tests/x509/test_x509.py
@@ -765,6 +765,21 @@ class TestRSAPSSCertificate:
cert.signature_hash_algorithm,
)
+ def test_load_pss_sha1_mgf1_sha1(self, backend):
+ cert = _load_cert(
+ os.path.join("x509", "ee-pss-sha1-cert.pem"),
+ x509.load_pem_x509_certificate,
+ )
+ assert isinstance(cert, x509.Certificate)
+ pub_key = cert.public_key()
+ assert isinstance(pub_key, rsa.RSAPublicKey)
+ pss = cert.signature_algorithm_parameters
+ assert isinstance(pss, padding.PSS)
+ assert isinstance(pss._mgf, padding.MGF1)
+ assert isinstance(pss._mgf._algorithm, hashes.SHA1)
+ assert pss._salt_length == 20
+ assert isinstance(cert.signature_hash_algorithm, hashes.SHA1)
+
def test_invalid_mgf(self, backend):
cert = _load_cert(
os.path.join("x509", "custom", "rsa_pss_cert_invalid_mgf.der"),
@@ -2404,6 +2419,154 @@ class TestCertificateBuilder:
# GENERALIZED TIME
assert parsed.not_after_tag == 0x18
+ @pytest.mark.parametrize(
+ ("alg", "mgf_alg"),
+ [
+ (hashes.SHA512(), hashes.SHA256()),
+ (hashes.SHA3_512(), hashes.SHA3_256()),
+ ],
+ )
+ def test_sign_pss(
+ self, rsa_key_2048: rsa.RSAPrivateKey, alg, mgf_alg, backend
+ ):
+ if not backend.signature_hash_supported(alg):
+ pytest.skip(f"{alg} signature not supported")
+ builder = (
+ x509.CertificateBuilder()
+ .subject_name(
+ x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")])
+ )
+ .issuer_name(
+ x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")])
+ )
+ .public_key(rsa_key_2048.public_key())
+ .serial_number(777)
+ .not_valid_before(datetime.datetime(2020, 1, 1))
+ .not_valid_after(datetime.datetime(2038, 1, 1))
+ )
+ pss = padding.PSS(
+ mgf=padding.MGF1(mgf_alg), salt_length=alg.digest_size
+ )
+ cert = builder.sign(rsa_key_2048, alg, rsa_padding=pss)
+ pk = cert.public_key()
+ assert isinstance(pk, rsa.RSAPublicKey)
+ assert isinstance(cert.signature_hash_algorithm, type(alg))
+ cert_params = cert.signature_algorithm_parameters
+ assert isinstance(cert_params, padding.PSS)
+ assert cert_params._salt_length == pss._salt_length
+ assert isinstance(cert_params._mgf, padding.MGF1)
+ assert isinstance(cert_params._mgf._algorithm, type(mgf_alg))
+ pk.verify(
+ cert.signature,
+ cert.tbs_certificate_bytes,
+ cert_params,
+ alg,
+ )
+
+ @pytest.mark.parametrize(
+ ("padding_len", "computed_len"),
+ [
+ (padding.PSS.MAX_LENGTH, 222),
+ (padding.PSS.DIGEST_LENGTH, 32),
+ ],
+ )
+ def test_sign_pss_length_options(
+ self,
+ rsa_key_2048: rsa.RSAPrivateKey,
+ padding_len,
+ computed_len,
+ backend,
+ ):
+ builder = (
+ x509.CertificateBuilder()
+ .subject_name(
+ x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")])
+ )
+ .issuer_name(
+ x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")])
+ )
+ .public_key(rsa_key_2048.public_key())
+ .serial_number(777)
+ .not_valid_before(datetime.datetime(2020, 1, 1))
+ .not_valid_after(datetime.datetime(2038, 1, 1))
+ )
+ pss = padding.PSS(
+ mgf=padding.MGF1(hashes.SHA256()), salt_length=padding_len
+ )
+ cert = builder.sign(rsa_key_2048, hashes.SHA256(), rsa_padding=pss)
+ assert isinstance(cert.signature_algorithm_parameters, padding.PSS)
+ assert cert.signature_algorithm_parameters._salt_length == computed_len
+
+ def test_sign_pss_auto_unsupported(
+ self, rsa_key_2048: rsa.RSAPrivateKey, backend
+ ):
+ builder = (
+ x509.CertificateBuilder()
+ .subject_name(
+ x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")])
+ )
+ .issuer_name(
+ x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")])
+ )
+ .public_key(rsa_key_2048.public_key())
+ .serial_number(777)
+ .not_valid_before(datetime.datetime(2020, 1, 1))
+ .not_valid_after(datetime.datetime(2038, 1, 1))
+ )
+ pss = padding.PSS(
+ mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.AUTO
+ )
+ with pytest.raises(TypeError):
+ builder.sign(rsa_key_2048, hashes.SHA256(), rsa_padding=pss)
+
+ def test_sign_invalid_padding(
+ self, rsa_key_2048: rsa.RSAPrivateKey, backend
+ ):
+ builder = (
+ x509.CertificateBuilder()
+ .subject_name(
+ x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")])
+ )
+ .issuer_name(
+ x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")])
+ )
+ .public_key(rsa_key_2048.public_key())
+ .serial_number(777)
+ .not_valid_before(datetime.datetime(2020, 1, 1))
+ .not_valid_after(datetime.datetime(2038, 1, 1))
+ )
+ with pytest.raises(TypeError):
+ builder.sign(
+ rsa_key_2048,
+ hashes.SHA256(),
+ rsa_padding=b"notapadding", # type: ignore[arg-type]
+ )
+ eckey = ec.generate_private_key(ec.SECP256R1())
+ with pytest.raises(TypeError):
+ builder.sign(
+ eckey, hashes.SHA256(), rsa_padding=padding.PKCS1v15()
+ )
+
+ def test_sign_pss_hash_none(
+ self, rsa_key_2048: rsa.RSAPrivateKey, backend
+ ):
+ builder = (
+ x509.CertificateBuilder()
+ .subject_name(
+ x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")])
+ )
+ .issuer_name(
+ x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")])
+ )
+ .public_key(rsa_key_2048.public_key())
+ .serial_number(777)
+ .not_valid_before(datetime.datetime(2020, 1, 1))
+ .not_valid_after(datetime.datetime(2038, 1, 1))
+ )
+ pss = padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=32)
+ with pytest.raises(TypeError):
+ builder.sign(rsa_key_2048, None, rsa_padding=pss)
+
def test_no_subject_name(self, rsa_key_2048: rsa.RSAPrivateKey, backend):
subject_private_key = rsa_key_2048
builder = (