From f671e1efa998b75700e5383292a43dcf1775b2b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Tue, 22 Mar 2022 00:34:06 +0000 Subject: [PATCH] crude EasyRSA class --- api/.devcontainer/Dockerfile | 8 +++ api/kiwi_vpn_api/easyrsa.py | 112 +++++++++++++++++++++++++++++++++++ api/poetry.lock | 21 ++++++- api/pyproject.toml | 1 + 4 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 api/kiwi_vpn_api/easyrsa.py diff --git a/api/.devcontainer/Dockerfile b/api/.devcontainer/Dockerfile index 5400f9f..ff2f93e 100644 --- a/api/.devcontainer/Dockerfile +++ b/api/.devcontainer/Dockerfile @@ -17,6 +17,14 @@ RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/ # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ # && apt-get -y install --no-install-recommends +RUN set -ex; \ + \ + export DEBIAN_FRONTEND=noninteractive; \ + apt-get update; apt-get -y install --no-install-recommends \ + easy-rsa \ + ; rm -rf /var/lib/apt/lists/*; \ + ln -s /usr/share/easy-rsa/easyrsa /usr/local/bin; + # [Optional] Uncomment this line to install global node packages. # RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 diff --git a/api/kiwi_vpn_api/easyrsa.py b/api/kiwi_vpn_api/easyrsa.py new file mode 100644 index 0000000..e769450 --- /dev/null +++ b/api/kiwi_vpn_api/easyrsa.py @@ -0,0 +1,112 @@ +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)) diff --git a/api/poetry.lock b/api/poetry.lock index 7f2a7a1..3f5e84d 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -292,6 +292,21 @@ typing-extensions = ">=3.7.4.3" dotenv = ["python-dotenv (>=0.10.4)"] email = ["email-validator (>=1.0.3)"] +[[package]] +name = "pyopenssl" +version = "22.0.0" +description = "Python wrapper module around the OpenSSL library" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +cryptography = ">=35.0" + +[package.extras] +docs = ["sphinx", "sphinx-rtd-theme"] +test = ["flaky", "pretend", "pytest (>=3.0.1)"] + [[package]] name = "pyparsing" version = "3.0.7" @@ -462,7 +477,7 @@ standard = ["websockets (>=10.0)", "httptools (>=0.4.0)", "watchgod (>=0.6)", "p [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "13a440999017394ffb9361fe45548d43f8c3e2b039dabceccae6f585d439c464" +content-hash = "432d2933102f8a0091cec1b5484944a0211ca74c5dc9b65877d99d7bd160e4bb" [metadata.files] anyio = [ @@ -751,6 +766,10 @@ pydantic = [ {file = "pydantic-1.9.0-py3-none-any.whl", hash = "sha256:085ca1de245782e9b46cefcf99deecc67d418737a1fd3f6a4f511344b613a5b3"}, {file = "pydantic-1.9.0.tar.gz", hash = "sha256:742645059757a56ecd886faf4ed2441b9c0cd406079c2b4bee51bcc3fbcd510a"}, ] +pyopenssl = [ + {file = "pyOpenSSL-22.0.0-py2.py3-none-any.whl", hash = "sha256:ea252b38c87425b64116f808355e8da644ef9b07e429398bfece610f893ee2e0"}, + {file = "pyOpenSSL-22.0.0.tar.gz", hash = "sha256:660b1b1425aac4a1bea1d94168a85d99f0b3144c869dd4390d27629d0087f1bf"}, +] pyparsing = [ {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, diff --git a/api/pyproject.toml b/api/pyproject.toml index b32597e..a105d62 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -12,6 +12,7 @@ uvicorn = "^0.17.6" python-jose = {extras = ["cryptography"], version = "^3.3.0"} passlib = {extras = ["argon2", "bcrypt"], version = "^1.7.4"} SQLAlchemy = "^1.4.32" +pyOpenSSL = "^22.0.0" [tool.poetry.dev-dependencies] pytest = "^7.1.0"