Repository: pyca/cryptography

Issue:
Request adding rsa_padding support to CertificateRevocationListBuilder.sign()
As background, support was added for signing X.509 certificates using PSS padding in #8888, by adding the `rsa_padding` kwarg to the `CertificateBuilder.sign()` method. 

When developing applications to be used by certificate authorities using PSS padding, a CA should be able to sign both a newly issued certificate (which #8888 allows us to do) and also a CRL in case the newly issued certificate needs to be revoked later. Can the `rsa_padding` kwarg also be added to the `CertificateRevocationListBuilder.sign()` method to allow a CA to generate a CRL using the same key as was used to build the certificate?



Discussion:
Yes, this should be doable for our next release.

Resolving patch:
diff --git a/src/cryptography/x509/base.py b/src/cryptography/x509/base.py
--- a/src/cryptography/x509/base.py
+++ b/src/cryptography/x509/base.py
@@ -423,6 +423,15 @@ def signature_algorithm_oid(self) -> ObjectIdentifier:
         Returns the ObjectIdentifier of the signature algorithm.
         """
 
+    @property
+    @abc.abstractmethod
+    def signature_algorithm_parameters(
+        self,
+    ) -> None | padding.PSS | padding.PKCS1v15 | ec.ECDSA:
+        """
+        Returns the signature algorithm parameters.
+        """
+
     @property
     @abc.abstractmethod
     def issuer(self) -> Name:
@@ -1146,6 +1155,8 @@ def sign(
         private_key: CertificateIssuerPrivateKeyTypes,
         algorithm: _AllowedHashTypes | None,
         backend: typing.Any = None,
+        *,
+        rsa_padding: padding.PSS | padding.PKCS1v15 | None = None,
     ) -> CertificateRevocationList:
         if self._issuer_name is None:
             raise ValueError("A CRL must have an issuer name")
@@ -1156,7 +1167,15 @@ def sign(
         if self._next_update is None:
             raise ValueError("A CRL must have a next update time")
 
-        return rust_x509.create_x509_crl(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_crl(
+            self, private_key, algorithm, rsa_padding
+        )
 
 
 class RevokedCertificateBuilder:


Test patch:
diff --git a/tests/x509/test_x509_crlbuilder.py b/tests/x509/test_x509_crlbuilder.py
--- a/tests/x509/test_x509_crlbuilder.py
+++ b/tests/x509/test_x509_crlbuilder.py
@@ -10,7 +10,13 @@
 from cryptography import utils, x509
 from cryptography.exceptions import UnsupportedAlgorithm
 from cryptography.hazmat.primitives import hashes
-from cryptography.hazmat.primitives.asymmetric import ec, ed448, ed25519, rsa
+from cryptography.hazmat.primitives.asymmetric import (
+    ec,
+    ed448,
+    ed25519,
+    padding,
+    rsa,
+)
 from cryptography.x509.oid import (
     AuthorityInformationAccessOID,
     NameOID,
@@ -204,6 +210,38 @@ def test_no_next_update(self, rsa_key_2048: rsa.RSAPrivateKey, backend):
         with pytest.raises(ValueError):
             builder.sign(private_key, hashes.SHA256(), backend)
 
+    def test_sign_invalid_padding(
+        self, rsa_key_2048: rsa.RSAPrivateKey, backend
+    ):
+        last_update = datetime.datetime(2002, 1, 1, 12, 1)
+        next_update = datetime.datetime(2030, 1, 1, 12, 1)
+        builder = (
+            x509.CertificateRevocationListBuilder()
+            .issuer_name(
+                x509.Name(
+                    [
+                        x509.NameAttribute(
+                            NameOID.COMMON_NAME, "cryptography.io CA"
+                        )
+                    ]
+                )
+            )
+            .last_update(last_update)
+            .next_update(next_update)
+        )
+
+        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_empty_list(self, rsa_key_2048: rsa.RSAPrivateKey, backend):
         private_key = rsa_key_2048
         last_update = datetime.datetime(2002, 1, 1, 12, 1)
@@ -235,6 +273,40 @@ def test_sign_empty_list(self, rsa_key_2048: rsa.RSAPrivateKey, backend):
             tzinfo=datetime.timezone.utc
         )
 
+    def test_sign_pss(self, rsa_key_2048: rsa.RSAPrivateKey, backend):
+        private_key = rsa_key_2048
+        last_update = datetime.datetime(2002, 1, 1, 12, 1)
+        next_update = datetime.datetime(2030, 1, 1, 12, 1)
+        builder = (
+            x509.CertificateRevocationListBuilder()
+            .issuer_name(
+                x509.Name(
+                    [
+                        x509.NameAttribute(
+                            NameOID.COMMON_NAME, "cryptography.io CA"
+                        )
+                    ]
+                )
+            )
+            .last_update(last_update)
+            .next_update(next_update)
+        )
+
+        pss = padding.PSS(
+            mgf=padding.MGF1(hashes.SHA256()),
+            salt_length=padding.PSS.DIGEST_LENGTH,
+        )
+        crl = builder.sign(private_key, hashes.SHA256(), rsa_padding=pss)
+        assert len(crl) == 0
+        assert isinstance(crl.signature_algorithm_parameters, padding.PSS)
+        assert crl.signature_algorithm_parameters._salt_length == 32
+        private_key.public_key().verify(
+            crl.signature,
+            crl.tbs_certlist_bytes,
+            crl.signature_algorithm_parameters,
+            hashes.SHA256(),
+        )
+
     @pytest.mark.parametrize(
         "extension",
         [