import argparse import datetime from cryptography import x509 from cryptography.x509.oid import NameOID from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption def create_key(): return ec.generate_private_key(ec.SECP256R1()) def create_certificate(cert_type, subject, issuer, private_key, public_key, dns_san=None): serial_number = x509.random_serial_number() not_valid_before = datetime.datetime.now(datetime.UTC) not_valid_after = not_valid_before + datetime.timedelta(days=365) subject_name = x509.Name([ x509.NameAttribute(NameOID.COUNTRY_NAME, subject.get('C', 'ZZ')), x509.NameAttribute(NameOID.ORGANIZATION_NAME, subject.get('O', 'No Organization')), x509.NameAttribute(NameOID.COMMON_NAME, subject.get('CN', 'No CommonName')), ]) issuer_name = x509.Name([ x509.NameAttribute(NameOID.COUNTRY_NAME, issuer.get('C', 'ZZ')), x509.NameAttribute(NameOID.ORGANIZATION_NAME, issuer.get('O', 'No Organization')), x509.NameAttribute(NameOID.COMMON_NAME, issuer.get('CN', 'No CommonName')), ]) builder = x509.CertificateBuilder() builder = builder.subject_name(subject_name) builder = builder.issuer_name(issuer_name) builder = builder.public_key(public_key) builder = builder.serial_number(serial_number) builder = builder.not_valid_before(not_valid_before) builder = builder.not_valid_after(not_valid_after) if cert_type == 'root': builder = builder.add_extension( x509.BasicConstraints(ca=True, path_length=None), critical=True ) elif cert_type == 'intermediate': builder = builder.add_extension( x509.BasicConstraints(ca=True, path_length=0), critical=True ) elif cert_type == 'leaf': builder = builder.add_extension( x509.BasicConstraints(ca=False, path_length=None), critical=True ) else: raise ValueError(f'Invalid cert_type: {cert_type}') if dns_san: builder = builder.add_extension( x509.SubjectAlternativeName([x509.DNSName(d) for d in dns_san.split(',')]), critical=False ) return builder.sign(private_key=private_key, algorithm=hashes.SHA256()) def main(): parser = argparse.ArgumentParser(description='Generate HTTPS server certificate.') parser.add_argument('--ca', required=True, help='Path to write the X509 CA certificate in PEM format') parser.add_argument('--cert', required=True, help='Path to write the X509 certificate in PEM format') parser.add_argument('--key', required=True, help='Path to write the private key in PEM format') parser.add_argument('--dnssan', required=False, default=None, help='Comma-separated list of DNS SANs') parser.add_argument('--type', required=True, choices=['selfsign', 'fullchain'], help='Type of certificate to generate') args = parser.parse_args() key = create_key() public_key = key.public_key() if args.type == 'selfsign': subject = {"C": "ZZ", "O": "Certificate", "CN": "Certificate"} cert = create_certificate( cert_type='root', subject=subject, issuer=subject, private_key=key, public_key=public_key, dns_san=args.dnssan) with open(args.ca, 'wb') as f: f.write(cert.public_bytes(Encoding.PEM)) with open(args.cert, 'wb') as f: f.write(cert.public_bytes(Encoding.PEM)) with open(args.key, 'wb') as f: f.write( key.private_bytes(Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption())) elif args.type == 'fullchain': ca_key = create_key() ca_public_key = ca_key.public_key() ca_subject = {"C": "ZZ", "O": "Root CA", "CN": "Root CA"} ca_cert = create_certificate( cert_type='root', subject=ca_subject, issuer=ca_subject, private_key=ca_key, public_key=ca_public_key) intermediate_key = create_key() intermediate_public_key = intermediate_key.public_key() intermediate_subject = {"C": "ZZ", "O": "Intermediate CA", "CN": "Intermediate CA"} intermediate_cert = create_certificate( cert_type='intermediate', subject=intermediate_subject, issuer=ca_subject, private_key=ca_key, public_key=intermediate_public_key) leaf_subject = {"C": "ZZ", "O": "Leaf Certificate", "CN": "Leaf Certificate"} cert = create_certificate( cert_type='leaf', subject=leaf_subject, issuer=intermediate_subject, private_key=intermediate_key, public_key=public_key, dns_san=args.dnssan) with open(args.ca, 'wb') as f: f.write(ca_cert.public_bytes(Encoding.PEM)) with open(args.cert, 'wb') as f: f.write(cert.public_bytes(Encoding.PEM)) f.write(intermediate_cert.public_bytes(Encoding.PEM)) with open(args.key, 'wb') as f: f.write( key.private_bytes(Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption())) if __name__ == "__main__": main()