-
-
Save borun/0b8df609790aa9253473e99ed092d6a0 to your computer and use it in GitHub Desktop.
This is a python script to generate client OpenVPN configuration files. This is based mostly on the easyrsa script and is much simpler to understand.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Many features of pyOpenSSL package are depricated. So we are using the cryptography library instead. | |
| # pip install cryptography | |
| from cryptography import x509 | |
| from cryptography.x509.oid import NameOID | |
| from cryptography.hazmat.primitives import hashes | |
| from cryptography.hazmat.primitives.asymmetric import rsa | |
| from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption | |
| from cryptography.hazmat.primitives import serialization # Fix: Import serialization | |
| from datetime import datetime, timedelta, UTC # Fix: Import datetime and timedelta | |
| # Create a new keypair of specified algorithm and number of bits. | |
| def make_keypair(numbits=2048): | |
| return rsa.generate_private_key(public_exponent=65537, key_size=numbits) | |
| # Creates a certificate signing request (CSR) given the specified subject attributes. | |
| def make_csr(private_key, CN, C=None, ST=None, L=None, O=None, OU=None, emailAddress=None): | |
| subject = [] | |
| if C: | |
| subject.append(x509.NameAttribute(NameOID.COUNTRY_NAME, C)) | |
| if ST: | |
| subject.append(x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, ST)) | |
| if L: | |
| subject.append(x509.NameAttribute(NameOID.LOCALITY_NAME, L)) | |
| if O: | |
| subject.append(x509.NameAttribute(NameOID.ORGANIZATION_NAME, O)) | |
| if OU: | |
| subject.append(x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, OU)) | |
| if CN: | |
| subject.append(x509.NameAttribute(NameOID.COMMON_NAME, CN)) | |
| if emailAddress: | |
| subject.append(x509.NameAttribute(NameOID.EMAIL_ADDRESS, emailAddress)) | |
| csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name(subject)).sign( | |
| private_key, hashes.SHA256() | |
| ) | |
| return csr | |
| # Create a certificate authority (if we need one) | |
| def create_ca(CN, C="", ST="", L="", O="", OU="", emailAddress=""): | |
| cakey = make_keypair() | |
| subject = x509.Name([ | |
| x509.NameAttribute(NameOID.COUNTRY_NAME, C), | |
| x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, ST), | |
| x509.NameAttribute(NameOID.LOCALITY_NAME, L), | |
| x509.NameAttribute(NameOID.ORGANIZATION_NAME, O), | |
| x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, OU), | |
| x509.NameAttribute(NameOID.COMMON_NAME, CN), | |
| ]) | |
| cacert = ( | |
| x509.CertificateBuilder() | |
| .subject_name(subject) | |
| .issuer_name(subject) | |
| .public_key(cakey.public_key()) | |
| .serial_number(x509.random_serial_number()) | |
| .not_valid_before(datetime.utcnow()) # Fix: Use datetime.utcnow() | |
| .not_valid_after(datetime.utcnow() + timedelta(days=3650)) # Fix: Use timedelta | |
| .add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True) | |
| .add_extension(x509.SubjectKeyIdentifier.from_public_key(cakey.public_key()), critical=False) | |
| .sign(cakey, hashes.SHA256()) | |
| ) | |
| return cacert, cakey | |
| # Create a new slave cert. | |
| def create_slave_certificate(csr, cakey, cacert, serial): | |
| cert = ( | |
| x509.CertificateBuilder() | |
| .subject_name(csr.subject) | |
| .issuer_name(cacert.subject) | |
| .public_key(csr.public_key()) | |
| .serial_number(serial) | |
| .not_valid_before( datetime.now(UTC)) # Fix: Use datetime.utcnow() | |
| .not_valid_after( datetime.now(UTC) + timedelta(days=3650)) # Fix: Use timedelta | |
| .add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=True) | |
| .add_extension(x509.SubjectKeyIdentifier.from_public_key(csr.public_key()), critical=False) | |
| .add_extension( | |
| x509.AuthorityKeyIdentifier.from_issuer_public_key(cakey.public_key()), critical=False | |
| ) | |
| .sign(cakey, hashes.SHA256()) | |
| ) | |
| return cert | |
| # Dumps content to a string | |
| def dump_file_in_mem(material): | |
| if isinstance(material, x509.Certificate): | |
| return material.public_bytes(Encoding.PEM) | |
| elif isinstance(material, rsa.RSAPrivateKey): | |
| return material.private_bytes( | |
| Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption() | |
| ) | |
| elif isinstance(material, x509.CertificateSigningRequest): | |
| return material.public_bytes(Encoding.PEM) | |
| else: | |
| raise Exception(f"Don't know how to dump content type: {type(material)}") | |
| # Loads the file into the appropriate cryptography object type. | |
| def load_from_file(materialfile, objtype): | |
| with open(materialfile, "rb") as f: | |
| data = f.read() | |
| if objtype == x509.Certificate: | |
| return x509.load_pem_x509_certificate(data) | |
| elif objtype == x509.CertificateSigningRequest: | |
| return x509.load_pem_x509_csr(data) | |
| elif objtype == rsa.RSAPrivateKey: | |
| return serialization.load_pem_private_key(data, password=None) | |
| else: | |
| raise Exception(f"Unsupported material type: {objtype}") | |
| def make_new_ovpn_file(ca_cert, ca_key, clientname, serial, commonoptspath, filepath): | |
| # Read our common options file first | |
| with open(commonoptspath, "r") as f: | |
| common = f.read() | |
| cacert = load_from_file(ca_cert, x509.Certificate) | |
| cakey = load_from_file(ca_key, rsa.RSAPrivateKey) | |
| # Generate a new private key pair for a new certificate. | |
| key = make_keypair() | |
| # Generate a certificate request | |
| csr = make_csr(key, clientname) | |
| # Sign the certificate with the new csr | |
| crt = create_slave_certificate(csr, cakey, cacert, serial) | |
| # Now we have a successfully signed certificate. We must now | |
| # create a .ovpn file and then dump it somewhere. | |
| clientkey = dump_file_in_mem(key) | |
| clientcert = dump_file_in_mem(crt) | |
| cacertdump = dump_file_in_mem(cacert) | |
| ovpn = f"{common}<ca>\n{cacertdump.decode('utf-8')}</ca>\n<cert>\n{clientcert.decode('utf-8')}</cert>\n<key>\n{clientkey.decode('utf-8')}</key>\n" | |
| # Write our file. | |
| with open(filepath, "w") as f: | |
| f.write(ovpn) | |
| if __name__ == "__main__": | |
| # "common.txt" is the common client configuration file | |
| # justasictest is the new client name | |
| # justasictest.ovpn file will be generated with common config and necessary certificates | |
| make_new_ovpn_file("ca.crt", "ca.key", "justasictest", 0x0C, "common.txt", "justastictest.ovpn") | |
| print("Done") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment