Repository: pyca/cryptography

Issue:
Add additional x509 certificate to SMIMESignatureBuilder
As mentioned in #5433: It would be great to be able to add an additional certificate when using the new SMIMESignatureBuilder API. 

The background is that my x509 certificate is from a commercial CA. But - as it's commonly the case - it is not signed directly by the Root CA (instead it's signed from an intermediate/sub CA). So when I send a message to a new contact I would definitely want to include my certificate AND the certificate of the intermediate CA. 


Discussion:

Resolving patch:
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -2695,6 +2695,15 @@ def pkcs7_sign(self, builder, encoding, options):
         init_flags = self._lib.PKCS7_PARTIAL
         final_flags = 0
 
+        if len(builder._additional_certs) == 0:
+            certs = self._ffi.NULL
+        else:
+            certs = self._lib.sk_X509_new_null()
+            certs = self._ffi.gc(certs, self._lib.sk_X509_free)
+            for cert in builder._additional_certs:
+                res = self._lib.sk_X509_push(certs, cert._x509)
+                self.openssl_assert(res >= 1)
+
         if pkcs7.PKCS7Options.DetachedSignature in options:
             # Don't embed the data in the PKCS7 structure
             init_flags |= self._lib.PKCS7_DETACHED
@@ -2705,7 +2714,7 @@ def pkcs7_sign(self, builder, encoding, options):
         p7 = self._lib.PKCS7_sign(
             self._ffi.NULL,
             self._ffi.NULL,
-            self._ffi.NULL,
+            certs,
             self._ffi.NULL,
             init_flags,
         )
diff --git a/src/cryptography/hazmat/primitives/serialization/pkcs7.py b/src/cryptography/hazmat/primitives/serialization/pkcs7.py
--- a/src/cryptography/hazmat/primitives/serialization/pkcs7.py
+++ b/src/cryptography/hazmat/primitives/serialization/pkcs7.py
@@ -24,9 +24,10 @@ def load_der_pkcs7_certificates(data):
 
 
 class PKCS7SignatureBuilder(object):
-    def __init__(self, data=None, signers=[]):
+    def __init__(self, data=None, signers=[], additional_certs=[]):
         self._data = data
         self._signers = signers
+        self._additional_certs = additional_certs
 
     def set_data(self, data):
         _check_byteslike("data", data)
@@ -63,6 +64,14 @@ def add_signer(self, certificate, private_key, hash_algorithm):
             self._signers + [(certificate, private_key, hash_algorithm)],
         )
 
+    def add_certificate(self, certificate):
+        if not isinstance(certificate, x509.Certificate):
+            raise TypeError("certificate must be a x509.Certificate")
+
+        return PKCS7SignatureBuilder(
+            self._data, self._signers, self._additional_certs + [certificate]
+        )
+
     def sign(self, encoding, options, backend=None):
         if len(self._signers) == 0:
             raise ValueError("Must have at least one signer")


Test patch:
diff --git a/tests/hazmat/primitives/test_pkcs7.py b/tests/hazmat/primitives/test_pkcs7.py
--- a/tests/hazmat/primitives/test_pkcs7.py
+++ b/tests/hazmat/primitives/test_pkcs7.py
@@ -607,3 +607,52 @@ def test_multiple_signers_different_hash_algs(self, backend):
             options,
             backend,
         )
+
+    def test_add_additional_cert_not_a_cert(self, backend):
+        with pytest.raises(TypeError):
+            pkcs7.PKCS7SignatureBuilder().add_certificate(b"notacert")
+
+    def test_add_additional_cert(self, backend):
+        data = b"hello world"
+        cert, key = _load_cert_key()
+        rsa_cert = load_vectors_from_file(
+            os.path.join("x509", "custom", "ca", "rsa_ca.pem"),
+            loader=lambda pemfile: x509.load_pem_x509_certificate(
+                pemfile.read()
+            ),
+            mode="rb",
+        )
+        builder = (
+            pkcs7.PKCS7SignatureBuilder()
+            .set_data(data)
+            .add_signer(cert, key, hashes.SHA384())
+            .add_certificate(rsa_cert)
+        )
+        options = []
+        sig = builder.sign(serialization.Encoding.DER, options)
+        assert (
+            sig.count(rsa_cert.public_bytes(serialization.Encoding.DER)) == 1
+        )
+
+    def test_add_multiple_additional_certs(self, backend):
+        data = b"hello world"
+        cert, key = _load_cert_key()
+        rsa_cert = load_vectors_from_file(
+            os.path.join("x509", "custom", "ca", "rsa_ca.pem"),
+            loader=lambda pemfile: x509.load_pem_x509_certificate(
+                pemfile.read()
+            ),
+            mode="rb",
+        )
+        builder = (
+            pkcs7.PKCS7SignatureBuilder()
+            .set_data(data)
+            .add_signer(cert, key, hashes.SHA384())
+            .add_certificate(rsa_cert)
+            .add_certificate(rsa_cert)
+        )
+        options = []
+        sig = builder.sign(serialization.Encoding.DER, options)
+        assert (
+            sig.count(rsa_cert.public_bytes(serialization.Encoding.DER)) == 2
+        )