import subprocess from datetime import datetime from pathlib import Path from OpenSSL import crypto from passlib import pwd class EasyRSA: __directory: Path | None __ca_password: str | None def __init__(self, directory: Path) -> None: self.__directory = directory def set_ca_password(self, password: str | None = None) -> None: if password is None: password = pwd.genword(length=32, charset="ascii_62") self.__ca_password = password print(self.__ca_password) def __easyrsa( self, *easyrsa_args: str, ) -> subprocess.CompletedProcess: return subprocess.run( [ "easyrsa", "--batch", f"--pki-dir={self.__directory}", *easyrsa_args, ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True, ) def __build_cert( self, cert_filename: Path, *easyrsa_args: str, ) -> crypto.X509: self.__easyrsa(*easyrsa_args) with open( self.__directory.joinpath(cert_filename), "r" ) as cert_file: return crypto.load_certificate( crypto.FILETYPE_PEM, cert_file.read() ) def init_pki(self) -> bool: self.__easyrsa("init-pki") def build_ca( self, days: int = 365 * 50, cn: str = "kiwi-ca" ) -> crypto.X509: return self.__build_cert( Path("ca.crt"), f"--passout=pass:{self.__ca_password}", f"--passin=pass:{self.__ca_password}", # "--dn-mode=org", # "--req-c=EX", # "--req-st=EXAMPLE", # "--req-city=EXAMPLE", # "--req-org=EXAMPLE", # "--req-ou=EXAMPLE", # "--req-email=EXAMPLE", f"--req-cn={cn}", f"--days={days}", "build-ca", ) def issue( self, days: int = 365 * 50, cn: str = "kiwi-vpn", cert_type: str = "client" ) -> crypto.X509: return self.__build_cert( Path(f"issued/{cn}.crt"), f"--passin=pass:{self.__ca_password}", f"--days={days}", f"build-{cert_type}-full", cn, "nopass", ) if __name__ == "__main__": rsa = EasyRSA(Path("tmp/pki")) rsa.init_pki() rsa.set_ca_password() ca = rsa.build_ca() server = rsa.issue(cert_type="server", cn="kiwi-server") client = rsa.issue(cert_type="client", cn="kiwi-client") print(ca.get_subject()) print(server.get_subject()) print(client.get_subject()) date_format, encoding = "%Y%m%d%H%M%SZ", "ascii" print(datetime.strptime( client.get_notAfter().decode(encoding), date_format))