def test_sign_no_data(self): cert, key = _load_cert_key() builder = pkcs7.PKCS7SignatureBuilder().add_signer( cert, key, hashes.SHA256() ) with pytest.raises(ValueError): builder.sign(serialization.Encoding.SMIME, [])
def test_sign_text(self, backend): data = b"hello world" cert, key = _load_cert_key() builder = (pkcs7.PKCS7SignatureBuilder().set_data(data).add_signer( cert, key, hashes.SHA256())) options = [ pkcs7.PKCS7Options.Text, pkcs7.PKCS7Options.DetachedSignature, ] sig_pem = builder.sign(serialization.Encoding.SMIME, options) # The text option adds text/plain headers to the S/MIME message # These headers are only relevant in SMIME mode, not binary, which is # just the PKCS7 structure itself. assert b"text/plain" in sig_pem # When passing the Text option the header is prepended so the actual # signed data is this. signed_data = b"Content-Type: text/plain\r\n\r\nhello world" _pkcs7_verify( serialization.Encoding.SMIME, sig_pem, signed_data, [cert], options, backend, )
def test_sign_invalid_options_text_no_detached(self, backend): cert, key = _load_cert_key() builder = (pkcs7.PKCS7SignatureBuilder().set_data(b"test").add_signer( cert, key, hashes.SHA256())) options = [pkcs7.PKCS7Options.Text] with pytest.raises(ValueError): builder.sign(serialization.Encoding.SMIME, options)
def test_sign_binary(self, backend): data = b"hello\nworld" cert, key = _load_cert_key() builder = (pkcs7.PKCS7SignatureBuilder().set_data(data).add_signer( cert, key, hashes.SHA256())) options: typing.List[pkcs7.PKCS7Options] = [] sig_no_binary = builder.sign(serialization.Encoding.DER, options) sig_binary = builder.sign(serialization.Encoding.DER, [pkcs7.PKCS7Options.Binary]) # Binary prevents translation of LF to CR+LF (SMIME canonical form) # so data should not be present in sig_no_binary, but should be present # in sig_binary assert data not in sig_no_binary _pkcs7_verify( serialization.Encoding.DER, sig_no_binary, None, [cert], options, backend, ) assert data in sig_binary _pkcs7_verify( serialization.Encoding.DER, sig_binary, None, [cert], options, backend, )
def test_multiple_signers_different_hash_algs(self, backend): data = b"hello world" cert, key = _load_cert_key() rsa_key = load_vectors_from_file( os.path.join("x509", "custom", "ca", "rsa_key.pem"), lambda pemfile: serialization.load_pem_private_key( pemfile.read(), None), mode="rb", ) 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", ) assert isinstance(rsa_key, rsa.RSAPrivateKey) builder = (pkcs7.PKCS7SignatureBuilder().set_data(data).add_signer( cert, key, hashes.SHA384()).add_signer(rsa_cert, rsa_key, hashes.SHA512())) options: typing.List[pkcs7.PKCS7Options] = [] sig = builder.sign(serialization.Encoding.DER, options) # There should be two SHA384 and two SHA512 OIDs in this structure assert sig.count(b"\x06\t`\x86H\x01e\x03\x04\x02\x02") == 2 assert sig.count(b"\x06\t`\x86H\x01e\x03\x04\x02\x03") == 2 _pkcs7_verify( serialization.Encoding.DER, sig, None, [cert, rsa_cert], options, backend, )
def test_sign_no_attributes(self, backend): data = b"hello world" cert, key = _load_cert_key() builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(data) .add_signer(cert, key, hashes.SHA256()) ) options = [pkcs7.PKCS7Options.NoAttributes] sig_binary = builder.sign(serialization.Encoding.DER, options) # NoAttributes removes all authenticated attributes, so we shouldn't # find SMIMECapabilities or signingTime. # 1.2.840.113549.1.9.15 SMIMECapabilities as an ASN.1 DER encoded OID assert b"\x06\t*\x86H\x86\xf7\r\x01\t\x0f" not in sig_binary # 1.2.840.113549.1.9.5 signingTime as an ASN.1 DER encoded OID assert b"\x06\t*\x86H\x86\xf7\r\x01\t\x05" not in sig_binary _pkcs7_verify( serialization.Encoding.DER, sig_binary, None, [cert], options, backend, )
def test_unsupported_key_type(self, backend): cert, _ = _load_cert_key() key = ed25519.Ed25519PrivateKey.generate() with pytest.raises(TypeError): pkcs7.PKCS7SignatureBuilder().add_signer( cert, key, hashes.SHA256() )
def test_smime_sign_detached(self, backend): data = b"hello world" cert, key = _load_cert_key() options = [pkcs7.PKCS7Options.DetachedSignature] builder = (pkcs7.PKCS7SignatureBuilder().set_data(data).add_signer( cert, key, hashes.SHA256())) sig = builder.sign(serialization.Encoding.SMIME, options) sig_binary = builder.sign(serialization.Encoding.DER, options) # We don't have a generic ASN.1 parser available to us so we instead # will assert on specific byte sequences being present based on the # parameters chosen above. assert b"sha-256" in sig # Detached signature means that the signed data is *not* embedded into # the PKCS7 structure itself, but is present in the SMIME serialization # as a separate section before the PKCS7 data. So we should expect to # have data in sig but not in sig_binary assert data in sig _pkcs7_verify(serialization.Encoding.SMIME, sig, data, [cert], options, backend) assert data not in sig_binary _pkcs7_verify( serialization.Encoding.DER, sig_binary, data, [cert], options, backend, )
def test_sign_no_capabilities(self, backend): data = b"hello world" cert, key = _load_cert_key() builder = (pkcs7.PKCS7SignatureBuilder().set_data(data).add_signer( cert, key, hashes.SHA256())) options = [pkcs7.PKCS7Options.NoCapabilities] sig_binary = builder.sign(serialization.Encoding.DER, options) # NoCapabilities removes the SMIMECapabilities attribute from the # PKCS7 structure. This is an ASN.1 sequence with the # OID 1.2.840.113549.1.9.15. It does NOT remove all authenticated # attributes, so we verify that by looking for the signingTime OID. # 1.2.840.113549.1.9.15 SMIMECapabilities as an ASN.1 DER encoded OID assert b"\x06\t*\x86H\x86\xf7\r\x01\t\x0f" not in sig_binary # 1.2.840.113549.1.9.5 signingTime as an ASN.1 DER encoded OID assert b"\x06\t*\x86H\x86\xf7\r\x01\t\x05" in sig_binary _pkcs7_verify( serialization.Encoding.DER, sig_binary, None, [cert], options, backend, )
def test_not_a_cert(self, backend): cert, key = _load_cert_key() with pytest.raises(TypeError): pkcs7.PKCS7SignatureBuilder().add_signer( b"notacert", key, hashes.SHA256() # type: ignore[arg-type] )
def test_unsupported_hash_alg(self, backend): cert, key = _load_cert_key() with pytest.raises(TypeError): pkcs7.PKCS7SignatureBuilder().add_signer( cert, key, hashes.SHA512_256() # type: ignore[arg-type] )
def test_sign_invalid_options(self, backend): cert, key = _load_cert_key() builder = (pkcs7.PKCS7SignatureBuilder().set_data(b"test").add_signer( cert, key, hashes.SHA256())) with pytest.raises(ValueError): builder.sign( serialization.Encoding.SMIME, [b"invalid"], # type: ignore[list-item] )
def test_sign_byteslike(self, backend): data = bytearray(b"hello world") cert, key = _load_cert_key() options = [pkcs7.PKCS7Options.DetachedSignature] builder = (pkcs7.PKCS7SignatureBuilder().set_data(data).add_signer( cert, key, hashes.SHA256())) sig = builder.sign(serialization.Encoding.SMIME, options) assert bytes(data) in sig
def test_sign_invalid_encoding(self): cert, key = _load_cert_key() builder = ( pkcs7.PKCS7SignatureBuilder() .set_data(b"test") .add_signer(cert, key, hashes.SHA256()) ) with pytest.raises(ValueError): builder.sign(serialization.Encoding.Raw, [])
def test_sign_alternate_digests_detached(self, hash_alg, expected_value): data = b"hello world" cert, key = _load_cert_key() builder = (pkcs7.PKCS7SignatureBuilder().set_data(data).add_signer( cert, key, hash_alg)) options = [pkcs7.PKCS7Options.DetachedSignature] sig = builder.sign(serialization.Encoding.SMIME, options) # When in detached signature mode the hash algorithm is stored as a # byte string like "sha-384". assert expected_value in sig
def _sign_payload(self, temp_dir: str, cert: str, key: str) -> bytes: """ signs the payload with one signer """ sb = pkcs7.PKCS7SignatureBuilder(Path(temp_dir, self.PAYLOAD_FILENAME).read_bytes())\ .add_signer(certs.decode_certificate(cert), keys.decode_key(key), hashes.SHA512()) return sb.sign(serialization.Encoding.DER, [ pkcs7.PKCS7Options.NoCerts, pkcs7.PKCS7Options.NoCapabilities, pkcs7.PKCS7Options.Binary ])
def test_sign_invalid_options_no_attrs_and_no_caps(self, backend): cert, key = _load_cert_key() builder = (pkcs7.PKCS7SignatureBuilder().set_data(b"test").add_signer( cert, key, hashes.SHA256())) options = [ pkcs7.PKCS7Options.NoAttributes, pkcs7.PKCS7Options.NoCapabilities, ] with pytest.raises(ValueError): builder.sign(serialization.Encoding.SMIME, options)
def test_sign_alternate_digests_der(self, hash_alg, expected_value, backend): data = b"hello world" cert, key = _load_cert_key() builder = (pkcs7.PKCS7SignatureBuilder().set_data(data).add_signer( cert, key, hash_alg)) options: typing.List[pkcs7.PKCS7Options] = [] sig = builder.sign(serialization.Encoding.DER, options) assert expected_value in sig _pkcs7_verify(serialization.Encoding.DER, sig, None, [cert], options, backend)
def test_sign_no_certs(self, backend): data = b"hello world" cert, key = _load_cert_key() builder = (pkcs7.PKCS7SignatureBuilder().set_data(data).add_signer( cert, key, hashes.SHA256())) options: typing.List[pkcs7.PKCS7Options] = [] sig = builder.sign(serialization.Encoding.DER, options) assert sig.count(cert.public_bytes(serialization.Encoding.DER)) == 1 options = [pkcs7.PKCS7Options.NoCerts] sig_no = builder.sign(serialization.Encoding.DER, options) assert sig_no.count(cert.public_bytes(serialization.Encoding.DER)) == 0
def test_sign_alternate_digests_detached(self, hash_alg, expected_value, backend): if isinstance(hash_alg, hashes.SHA1) and backend._fips_enabled: pytest.skip("SHA1 not supported in FIPS mode") data = b"hello world" cert, key = _load_cert_key() builder = (pkcs7.PKCS7SignatureBuilder().set_data(data).add_signer( cert, key, hash_alg)) options = [pkcs7.PKCS7Options.DetachedSignature] sig = builder.sign(serialization.Encoding.SMIME, options) # When in detached signature mode the hash algorithm is stored as a # byte string like "sha-384". assert expected_value in sig
def test_sign_alternate_digests_der(self, hash_alg, expected_value, backend): if isinstance(hash_alg, hashes.SHA1) and backend._fips_enabled: pytest.skip("SHA1 not supported in FIPS mode") data = b"hello world" cert, key = _load_cert_key() builder = (pkcs7.PKCS7SignatureBuilder().set_data(data).add_signer( cert, key, hash_alg)) options: typing.List[pkcs7.PKCS7Options] = [] sig = builder.sign(serialization.Encoding.DER, options) assert expected_value in sig _pkcs7_verify(serialization.Encoding.DER, sig, None, [cert], options, backend)
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: typing.List[pkcs7.PKCS7Options] = [] sig = builder.sign(serialization.Encoding.DER, options) assert (sig.count(rsa_cert.public_bytes( serialization.Encoding.DER)) == 1)
def sign_data(data: str) -> str: with open(config.mobileconfig_key(), "rb") as private_key_file: private_key = serialization.load_pem_private_key( private_key_file.read(), password=None, backend=None) with open(config.mobileconfig_cert(), "rb") as public_cert_file: public_cert = x509.load_pem_x509_certificate(public_cert_file.read()) with open(config.mobileconfig_ca(), "rb") as public_ca_file: ca_cert = x509.load_pem_x509_certificate(public_ca_file.read()) return pkcs7.PKCS7SignatureBuilder().set_data(data).add_signer( public_cert, private_key, hashes.SHA256()).add_certificate(ca_cert).sign( serialization.Encoding.DER, [])
def test_sign_pem(self, backend): data = b"hello world" cert, key = _load_cert_key() options: typing.List[pkcs7.PKCS7Options] = [] builder = (pkcs7.PKCS7SignatureBuilder().set_data(data).add_signer( cert, key, hashes.SHA256())) sig = builder.sign(serialization.Encoding.PEM, options) _pkcs7_verify( serialization.Encoding.PEM, sig, None, [cert], options, backend, )
def test_sign_attached(self, backend): data = b"hello world" cert, key = _load_cert_key() options: typing.List[pkcs7.PKCS7Options] = [] builder = (pkcs7.PKCS7SignatureBuilder().set_data(data).add_signer( cert, key, hashes.SHA256())) sig_binary = builder.sign(serialization.Encoding.DER, options) # When not passing detached signature the signed data is embedded into # the PKCS7 structure itself assert data in sig_binary _pkcs7_verify( serialization.Encoding.DER, sig_binary, None, [cert], options, backend, )
def test_sign_smime_canonicalization(self, backend): data = b"hello\nworld" cert, key = _load_cert_key() builder = (pkcs7.PKCS7SignatureBuilder().set_data(data).add_signer( cert, key, hashes.SHA256())) options: typing.List[pkcs7.PKCS7Options] = [] sig_binary = builder.sign(serialization.Encoding.DER, options) # LF gets converted to CR+LF (SMIME canonical form) # so data should not be present in the sig assert data not in sig_binary assert b"hello\r\nworld" in sig_binary _pkcs7_verify( serialization.Encoding.DER, sig_binary, None, [cert], options, backend, )
def sign_payload(payload): api_settings = settings["api"] tls_privkey = api_settings.get("tls_privkey") if not tls_privkey: logger.error("Could not sign payload: missing tls privkey") return payload tls_fullchain = api_settings.get("tls_fullchain") if not tls_fullchain: logger.error("Could not sign payload: missing tls fullchain") return payload certificates = [] key = serialization.load_pem_private_key( api_settings["tls_privkey"].encode("utf-8"), None) head = "-----BEGIN CERTIFICATE-----" for tail in api_settings["tls_fullchain"].split(head)[1:]: cert_data = (head + tail).encode("utf-8") certificates.append(x509.load_pem_x509_certificate(cert_data)) signature_builder = pkcs7.PKCS7SignatureBuilder().set_data( payload).add_signer(certificates.pop(0), key, hashes.SHA256()) for certificate in certificates: signature_builder = signature_builder.add_certificate(certificate) return signature_builder.sign(serialization.Encoding.DER, [pkcs7.PKCS7Options.Binary])
def test_add_additional_cert_not_a_cert(self, backend): with pytest.raises(TypeError): pkcs7.PKCS7SignatureBuilder().add_certificate( b"notacert" # type: ignore[arg-type] )
def test_set_data_twice(self, backend): builder = pkcs7.PKCS7SignatureBuilder().set_data(b"test") with pytest.raises(ValueError): builder.set_data(b"test")
def test_sign_no_signer(self, backend): builder = pkcs7.PKCS7SignatureBuilder().set_data(b"test") with pytest.raises(ValueError): builder.sign(serialization.Encoding.SMIME, [])