#!/usr/bin/python3 """Make test certificates""" import os import datetime import cryptography # Explicit imports of cryptography submodules import cryptography.x509 import cryptography.x509.oid import cryptography.hazmat.primitives.hashes import cryptography.hazmat.primitives.asymmetric.rsa import cryptography.hazmat.primitives.serialization import cryptography.hazmat.primitives.serialization.pkcs12 # Import classes and functions from the cryptography module from cryptography.x509 import ( AuthorityKeyIdentifier, BasicConstraints, Certificate, CertificateBuilder, CertificateRevocationListBuilder, CRLDistributionPoints, CRLNumber, CRLReason, DistributionPoint, DNSName, ExtendedKeyUsage, KeyUsage, Name, NameAttribute, NameConstraints, random_serial_number, RevokedCertificateBuilder, ReasonFlags, SubjectKeyIdentifier, UniformResourceIdentifier ) from cryptography.x509.oid import ( ExtendedKeyUsageOID, NameOID ) from cryptography.hazmat.primitives.hashes import SHA256 from cryptography.hazmat.primitives.asymmetric.rsa import ( generate_private_key, RSAPrivateKey ) from cryptography.hazmat.primitives.serialization import ( BestAvailableEncryption, Encoding, NoEncryption, PrivateFormat ) from cryptography.hazmat.primitives.serialization.pkcs12 import serialize_key_and_certificates try: if cryptography.__version__ >= '38.0.0': from cryptography.hazmat.primitives.serialization.pkcs12 import PBES except ImportError: pass RESULT_PATH = os.getcwd() CERTS_PATH = os.path.join(RESULT_PATH, "./Testing/certs/") date_20170101 = datetime.datetime(2017, 1, 1) date_20180101 = datetime.datetime(2018, 1, 1) date_20190101 = datetime.datetime(2019, 1, 1) PASSWORD='passme' class X509Extensions(): """Base class for X509 Extensions""" def __init__(self, unit_name, cdp_port, cdp_name): self.unit_name = unit_name self.port = cdp_port self.name = cdp_name def create_x509_name(self, common_name) -> Name: """Return x509.Name""" return Name( [ NameAttribute(NameOID.COUNTRY_NAME, "PL"), NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Mazovia Province"), NameAttribute(NameOID.LOCALITY_NAME, "Warsaw"), NameAttribute(NameOID.ORGANIZATION_NAME, "osslsigncode"), NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, self.unit_name), NameAttribute(NameOID.COMMON_NAME, common_name) ] ) def create_x509_crldp(self) -> CRLDistributionPoints: """Return x509.CRLDistributionPoints""" return CRLDistributionPoints( [ DistributionPoint( full_name=[UniformResourceIdentifier( "http://127.0.0.1:" + str(self.port) + "/" + str(self.name)) ], relative_name=None, reasons=None, crl_issuer=None ) ] ) def create_x509_name_constraints(self) -> NameConstraints: """Return x509.NameConstraints""" return NameConstraints( permitted_subtrees = [DNSName('test.com'), DNSName('test.org')], excluded_subtrees = None ) class IntermediateCACertificate(X509Extensions): """Base class for Intermediate CA certificate""" def __init__(self, issuer_cert, issuer_key): self.issuer_cert = issuer_cert self.issuer_key = issuer_key super().__init__("Certification Authority", 0, None) def make_cert(self) -> (Certificate, RSAPrivateKey): """Generate intermediate CA certificate""" key = generate_private_key(public_exponent=65537, key_size=2048) key_public = key.public_key() authority_key = AuthorityKeyIdentifier.from_issuer_subject_key_identifier( self.issuer_cert.extensions.get_extension_for_class(SubjectKeyIdentifier).value ) key_usage = KeyUsage( digital_signature=True, content_commitment=False, key_encipherment=False, data_encipherment=False, key_agreement=False, key_cert_sign=True, crl_sign=True, encipher_only=False, decipher_only=False ) cert = ( CertificateBuilder() .subject_name(self.create_x509_name("Intermediate CA")) .issuer_name(self.issuer_cert.subject) .public_key(key_public) .serial_number(random_serial_number()) .not_valid_before(date_20180101) .not_valid_after(date_20180101 + datetime.timedelta(days=7300)) .add_extension(BasicConstraints(ca=True, path_length=0), critical=True) .add_extension(SubjectKeyIdentifier.from_public_key(key_public), critical=False) .add_extension(authority_key, critical=False) .add_extension(key_usage, critical=True) .sign(self.issuer_key, SHA256()) ) file_path=os.path.join(CERTS_PATH, "intermediateCA.pem") with open(file_path, mode="wb") as file: file.write(cert.public_bytes(encoding=Encoding.PEM)) return cert, key class RootCACertificate(X509Extensions): """Base class for Root CA certificate""" def __init__(self): self.key_usage = KeyUsage( digital_signature=True, content_commitment=False, key_encipherment=False, data_encipherment=False, key_agreement=False, key_cert_sign=True, crl_sign=True, encipher_only=False, decipher_only=False ) super().__init__("Certification Authority", 0, None) def make_cert(self) -> (Certificate, RSAPrivateKey): """Generate CA certificates""" ca_root, root_key = self.make_ca_cert("Trusted Root CA", "CAroot.pem") ca_cert, ca_key = self.make_ca_cert("Root CA", "CACert.pem") self.make_cross_cert(ca_root, root_key, ca_cert, ca_key) return ca_cert, ca_key def make_ca_cert(self, common_name, file_name) -> None: """Generate self-signed root CA certificate""" ca_key = generate_private_key(public_exponent=65537, key_size=2048) ca_public = ca_key.public_key() authority_key = AuthorityKeyIdentifier.from_issuer_public_key(ca_public) name = self.create_x509_name(common_name) ca_cert = ( CertificateBuilder() .subject_name(name) .issuer_name(name) .public_key(ca_public) .serial_number(random_serial_number()) .not_valid_before(date_20170101) .not_valid_after(date_20170101 + datetime.timedelta(days=7300)) .add_extension(BasicConstraints(ca=True, path_length=None), critical=True) .add_extension(SubjectKeyIdentifier.from_public_key(ca_public), critical=False) .add_extension(authority_key, critical=False) .add_extension(self.key_usage, critical=True) .sign(ca_key, SHA256()) ) file_path=os.path.join(CERTS_PATH, file_name) with open(file_path, mode="wb") as file: file.write(ca_cert.public_bytes(encoding=Encoding.PEM)) return ca_cert, ca_key def make_cross_cert(self, ca_root, root_key, ca_cert, ca_key) -> None: """Generate cross-signed root CA certificate""" ca_public = ca_key.public_key() authority_key = AuthorityKeyIdentifier.from_issuer_subject_key_identifier( ca_root.extensions.get_extension_for_class(SubjectKeyIdentifier).value ) ca_cross = ( CertificateBuilder() .subject_name(ca_cert.subject) .issuer_name(ca_root.subject) .public_key(ca_public) .serial_number(ca_cert.serial_number) .not_valid_before(date_20180101) .not_valid_after(date_20180101 + datetime.timedelta(days=7300)) .add_extension(BasicConstraints(ca=True, path_length=None), critical=True) .add_extension(SubjectKeyIdentifier.from_public_key(ca_public), critical=False) .add_extension(authority_key, critical=False) .add_extension(self.key_usage, critical=True) .sign(root_key, SHA256()) ) file_path=os.path.join(CERTS_PATH, "CAcross.pem") with open(file_path, mode="wb") as file: file.write(ca_cross.public_bytes(encoding=Encoding.PEM)) def write_key(self, key, file_name) -> None: """Write a private RSA key""" # Write password file_path = os.path.join(CERTS_PATH, "password.txt") with open(file_path, mode="w", encoding="utf-8") as file: file.write("{}".format(PASSWORD)) # Write encrypted key in PEM format file_path = os.path.join(CERTS_PATH, file_name + "p.pem") with open(file_path, mode="wb") as file: file.write(key.private_bytes( encoding=Encoding.PEM, format=PrivateFormat.PKCS8, encryption_algorithm=BestAvailableEncryption(PASSWORD.encode()) ) ) # Write decrypted key in PEM format file_path = os.path.join(CERTS_PATH, file_name + ".pem") with open(file_path, mode="wb") as file: file.write(key.private_bytes( encoding=Encoding.PEM, format=PrivateFormat.PKCS8, encryption_algorithm=NoEncryption() ) ) # Write the key in DER format file_path = os.path.join(CERTS_PATH, file_name + ".der") with open(file_path, mode="wb") as file: file.write(key.private_bytes( encoding=Encoding.DER, format=PrivateFormat.PKCS8, encryption_algorithm=NoEncryption() ) ) class TSARootCACertificate(X509Extensions): """Base class for TSA certificates""" def __init__(self): super().__init__("Timestamp Authority Root CA", 0, None) def make_cert(self) -> (Certificate, RSAPrivateKey): """Generate a Time Stamp Authority certificate""" ca_key = generate_private_key(public_exponent=65537, key_size=2048) ca_public = ca_key.public_key() authority_key = AuthorityKeyIdentifier.from_issuer_public_key(ca_public) name = self.create_x509_name("TSA Root CA") key_usage = KeyUsage( digital_signature=False, content_commitment=False, key_encipherment=False, data_encipherment=False, key_agreement=False, key_cert_sign=True, crl_sign=True, encipher_only=False, decipher_only=False ) ca_cert = ( CertificateBuilder() .subject_name(name) .issuer_name(name) .public_key(ca_public) .serial_number(random_serial_number()) .not_valid_before(date_20170101) .not_valid_after(date_20170101 + datetime.timedelta(days=7300)) .add_extension(BasicConstraints(ca=True, path_length=None), critical=True) .add_extension(SubjectKeyIdentifier.from_public_key(ca_public), critical=False) .add_extension(authority_key, critical=False) .add_extension(key_usage, critical=True) .sign(ca_key, SHA256()) ) file_path=os.path.join(CERTS_PATH, "TSACA.pem") with open(file_path, mode="wb") as file: file.write(ca_cert.public_bytes(encoding=Encoding.PEM)) return ca_cert, ca_key def write_key(self, key, file_name) -> None: """Write decrypted private RSA key into PEM format""" file_path = os.path.join(CERTS_PATH, file_name + ".key") with open(file_path, mode="wb") as file: file.write(key.private_bytes( encoding=Encoding.PEM, format=PrivateFormat.PKCS8, encryption_algorithm=NoEncryption() ) ) class LeafCertificate(X509Extensions): """Base class for a leaf certificate""" def __init__(self, issuer_cert, issuer_key, unit_name, common_name, cdp_port, cdp_name): #pylint: disable=too-many-arguments self.issuer_cert = issuer_cert self.issuer_key = issuer_key self.common_name = common_name super().__init__(unit_name, cdp_port, cdp_name) def make_cert(self, public_key, not_before, days) -> Certificate: """Generate a leaf certificate""" authority_key = AuthorityKeyIdentifier.from_issuer_subject_key_identifier( self.issuer_cert.extensions.get_extension_for_class(SubjectKeyIdentifier).value ) extended_key_usage = ExtendedKeyUsage( [ExtendedKeyUsageOID.CODE_SIGNING] ) cert = ( CertificateBuilder() .subject_name(self.create_x509_name(self.common_name)) .issuer_name(self.issuer_cert.subject) .public_key(public_key) .serial_number(random_serial_number()) .not_valid_before(not_before) .not_valid_after(not_before + datetime.timedelta(days=days)) .add_extension(BasicConstraints(ca=False, path_length=None), critical=False) .add_extension(SubjectKeyIdentifier.from_public_key(public_key), critical=False) .add_extension(authority_key, critical=False) .add_extension(extended_key_usage, critical=False) .add_extension(self.create_x509_crldp(), critical=False) .sign(self.issuer_key, SHA256()) ) # Write PEM file and attach intermediate certificate file_path = os.path.join(CERTS_PATH, self.common_name + ".pem") with open(file_path, mode="wb") as file: file.write(cert.public_bytes(encoding=Encoding.PEM)) file.write(self.issuer_cert.public_bytes(encoding=Encoding.PEM)) return cert def revoke_cert(self, serial_number, file_name) -> None: """Revoke a certificate""" revoked = ( RevokedCertificateBuilder() .serial_number(serial_number) .revocation_date(date_20190101) .add_extension(CRLReason(ReasonFlags.superseded), critical=False) .build() ) # Generate CRL authority_key = AuthorityKeyIdentifier.from_issuer_subject_key_identifier( self.issuer_cert.extensions.get_extension_for_class(SubjectKeyIdentifier).value ) crl = ( CertificateRevocationListBuilder() .issuer_name(self.issuer_cert.subject) .last_update(date_20190101) .next_update(date_20190101 + datetime.timedelta(days=7300)) .add_extension(authority_key, critical=False) .add_extension(CRLNumber(4097), critical=False) .add_revoked_certificate(revoked) .sign(self.issuer_key, SHA256()) ) # Write CRL file file_path = os.path.join(CERTS_PATH, file_name + ".pem") with open(file_path, mode="wb") as file: file.write(crl.public_bytes(encoding=Encoding.PEM)) file_path = os.path.join(CERTS_PATH, file_name + ".der") with open(file_path, mode="wb") as file: file.write(crl.public_bytes(encoding=Encoding.DER)) class LeafCACertificate(LeafCertificate): """Base class for a leaf certificate""" def __init__(self, issuer_cert, issuer_key, common, cdp_port): super().__init__(issuer_cert, issuer_key, "CSP", common, cdp_port, "intermediateCA") class LeafTSACertificate(LeafCertificate): """Base class for a TSA leaf certificate""" def __init__(self, issuer_cert, issuer_key, common, cdp_port): self.issuer_cert = issuer_cert self.issuer_key = issuer_key self.common_name = common super().__init__(issuer_cert, issuer_key, "Timestamp Root CA", common, cdp_port, "TSACA") def make_cert(self, public_key, not_before, days) -> Certificate: """Generate a TSA leaf certificate""" authority_key = AuthorityKeyIdentifier.from_issuer_subject_key_identifier( self.issuer_cert.extensions.get_extension_for_class(SubjectKeyIdentifier).value ) # The TSA signing certificate must have exactly one extended key usage # assigned to it: timeStamping. The extended key usage must also be critical, # otherwise the certificate is going to be refused. extended_key_usage = ExtendedKeyUsage( [ExtendedKeyUsageOID.TIME_STAMPING] ) cert = ( CertificateBuilder() .subject_name(self.create_x509_name(self.common_name)) .issuer_name(self.issuer_cert.subject) .public_key(public_key) .serial_number(random_serial_number()) .not_valid_before(not_before) .not_valid_after(not_before + datetime.timedelta(days=days)) .add_extension(BasicConstraints(ca=False, path_length=None), critical=True) .add_extension(SubjectKeyIdentifier.from_public_key(public_key), critical=False) .add_extension(authority_key, critical=False) .add_extension(extended_key_usage, critical=True) .add_extension(self.create_x509_crldp(), critical=False) .add_extension(self.create_x509_name_constraints(), critical=False) .sign(self.issuer_key, SHA256()) ) # Write PEM file and attach intermediate certificate file_path = os.path.join(CERTS_PATH, self.common_name + ".pem") with open(file_path, mode="wb") as file: file.write(cert.public_bytes(encoding=Encoding.PEM)) file.write(self.issuer_cert.public_bytes(encoding=Encoding.PEM)) return cert class CertificateMaker(): """Base class for test certificates""" def __init__(self, cdp_port, logs): self.cdp_port = cdp_port self.logs = logs def make_certs(self) -> None: """Make test certificates""" try: self.make_ca_certs() self.make_tsa_certs() logs = os.path.join(CERTS_PATH, "./cert.log") with open(logs, mode="w", encoding="utf-8") as file: file.write("Test certificates generation succeeded") except Exception as err: # pylint: disable=broad-except with open(self.logs, mode="a", encoding="utf-8") as file: file.write("Error: {}".format(err)) def make_ca_certs(self): """Make test certificates""" # Generate root CA certificate root = RootCACertificate() ca_cert, ca_key = root.make_cert() # Generate intermediate root CA certificate intermediate = IntermediateCACertificate(ca_cert, ca_key) issuer_cert, issuer_key = intermediate.make_cert() # Generate private RSA key private_key = generate_private_key(public_exponent=65537, key_size=2048) public_key = private_key.public_key() root.write_key(key=private_key, file_name="key") # Generate expired certificate expired = LeafCACertificate(issuer_cert, issuer_key, "expired", self.cdp_port) expired.make_cert(public_key, date_20180101, 365) # Generate revoked certificate revoked = LeafCACertificate(issuer_cert, issuer_key, "revoked", self.cdp_port) cert = revoked.make_cert(public_key, date_20180101, 5840) revoked.revoke_cert(cert.serial_number, "CACertCRL") # Generate code signing certificate signer = LeafCACertificate(issuer_cert, issuer_key, "cert", self.cdp_port) cert = signer.make_cert(public_key, date_20180101, 5840) # Write a certificate and a key into PKCS#12 container self.write_pkcs12_container( cert=cert, key=private_key, issuer=issuer_cert ) # Write DER file and attach intermediate certificate file_path = os.path.join(CERTS_PATH, "cert.der") with open(file_path, mode="wb") as file: file.write(cert.public_bytes(encoding=Encoding.DER)) def make_tsa_certs(self): """Make test TSA certificates""" # Time Stamp Authority certificate root = TSARootCACertificate() issuer_cert, issuer_key = root.make_cert() # Generate private RSA key private_key = generate_private_key(public_exponent=65537, key_size=2048) public_key = private_key.public_key() root.write_key(key=private_key, file_name="TSA") # Generate revoked TSA certificate revoked = LeafTSACertificate(issuer_cert, issuer_key, "TSA_revoked", self.cdp_port) cert = revoked.make_cert(public_key, date_20180101, 7300) revoked.revoke_cert(cert.serial_number, "TSACertCRL") # Generate TSA certificate signer = LeafTSACertificate(issuer_cert, issuer_key, "TSA", self.cdp_port) cert = signer.make_cert(public_key, date_20180101, 7300) # Save the chain to be included in the TSA response file_path = os.path.join(CERTS_PATH, "tsa-chain.pem") with open(file_path, mode="wb") as file: file.write(cert.public_bytes(encoding=Encoding.PEM)) file.write(issuer_cert.public_bytes(encoding=Encoding.PEM)) def write_pkcs12_container(self, cert, key, issuer) -> None: """Write a certificate and a key into a PKCS#12 container""" # Set an encryption algorithm if cryptography.__version__ >= "38.0.0": # For OpenSSL legacy mode use the default algorithm for certificate # and private key encryption: DES-EDE3-CBC (vel 3DES_CBC) # pylint: disable=no-member encryption = ( PrivateFormat.PKCS12.encryption_builder() .key_cert_algorithm(PBES.PBESv1SHA1And3KeyTripleDESCBC) .kdf_rounds(5000) .build(PASSWORD.encode()) ) else: encryption = BestAvailableEncryption(PASSWORD.encode()) # Generate PKCS#12 struct pkcs12 = serialize_key_and_certificates( name=b'certificate', key=key, cert=cert, cas=(issuer,), encryption_algorithm=encryption ) # Write into a PKCS#12 container file_path = os.path.join(CERTS_PATH, "cert.p12") with open(file_path, mode="wb") as file: file.write(pkcs12) # pylint: disable=pointless-string-statement """Local Variables: c-basic-offset: 4 tab-width: 4 indent-tabs-mode: nil End: vim: set ts=4 expandtab: """