Skip to content

Instantly share code, notes, and snippets.

@borun
Forked from Justasic/openvpn_gen.py
Last active April 14, 2025 13:29
Show Gist options
  • Select an option

  • Save borun/0b8df609790aa9253473e99ed092d6a0 to your computer and use it in GitHub Desktop.

Select an option

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.
# 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