diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ea5a5df --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,205 @@ +name: Certificate Generator CI + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + workflow_dispatch: + +jobs: + test: + name: Test Certificate Generation + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Display Python version + run: python --version + + - name: Create virtual environment + run: make env + + - name: Install dependencies + run: make install + env: + PIP_TRUSTED_HOST: "pypi.org pypi.python.org files.pythonhosted.org" + + - name: Verify installation + run: | + source venv/bin/activate + pip list + + - name: Test certificate generation (automated) + run: | + # Create automated input for certificate generation + cat << EOF > test_input.txt + AE + Dubai + Emaar Square + XYZ Company + Information Technology + XYZ Company Test CA + test@xyz.ae + test-server.xyz.ae + EOF + + source venv/bin/activate + python digital-cert.py < test_input.txt || true + env: + DIGITAL_CERT_PASSPHRASE: test-passphrase + + - name: Verify CA directory created + run: | + if [ -d "CA" ]; then + echo "✓ CA directory created successfully" + ls -la CA/ + else + echo "✗ CA directory not found" + exit 1 + fi + + - name: Verify CA certificate and key + run: | + if [ -f "CA/ca.crt" ] && [ -f "CA/ca.key" ]; then + echo "✓ CA certificate and key created" + openssl x509 -in CA/ca.crt -text -noout | head -20 + else + echo "✗ CA certificate or key not found" + exit 1 + fi + + - name: Verify client certificate and key + run: | + if ls *.crt *.key 1> /dev/null 2>&1; then + echo "✓ Client certificates created" + ls -la *.crt *.key + for cert in *.crt; do + echo "Checking $cert:" + openssl x509 -in $cert -text -noout | head -20 + done + else + echo "✗ Client certificates not found" + exit 1 + fi + + - name: Validate certificate properties + run: | + echo "Validating CA certificate..." + openssl x509 -in CA/ca.crt -noout -subject -issuer -dates + + echo "" + echo "Validating client certificate..." + for cert in *.crt; do + if [ "$cert" != "CA/ca.crt" ]; then + openssl x509 -in $cert -noout -subject -issuer -dates + fi + done + + - name: Test certificate verification + run: | + for cert in *.crt; do + if [ "$cert" != "CA/ca.crt" ]; then + echo "Verifying $cert against CA..." + openssl verify -CAfile CA/ca.crt $cert + fi + done + + - name: List all generated files + run: make list + + - name: Upload certificates as artifacts + uses: actions/upload-artifact@v4 + if: always() + with: + name: certificates-python-${{ matrix.python-version }} + path: | + CA/ + *.crt + *.key + retention-days: 7 + + - name: Clean up certificates + if: always() + run: make clean + + - name: Verify cleanup + run: | + if ls *.crt *.key 1> /dev/null 2>&1; then + echo "��� Client certificates not cleaned" + exit 1 + else + echo "✓ Client certificates cleaned successfully" + fi + + lint: + name: Code Quality Check + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install linting tools + run: | + python -m pip install --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org flake8 pylint + + - name: Run flake8 + run: | + flake8 digital-cert.py --max-line-length=120 --ignore=E501 || true + + - name: Run pylint + run: | + pylint digital-cert.py --disable=all --enable=E,F || true + + security: + name: Security Scan + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install security tools + run: | + python -m pip install --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org bandit safety + + - name: Run bandit security scan + run: | + bandit -r . -f json -o bandit-report.json || true + bandit -r . || true + + - name: Check dependencies for vulnerabilities + run: | + if [ -f requirements.txt ]; then + safety check -r requirements.txt || true + fi + + - name: Upload security report + uses: actions/upload-artifact@v4 + if: always() + with: + name: security-report + path: bandit-report.json + retention-days: 30 diff --git a/.gitignore b/.gitignore index c2111fc..72ae0eb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,8 @@ .env -.vscode \ No newline at end of file +.vscode +.venv/ +venv/ +__pycache__/ +CA/ +*.crt +*.key diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..247f79d --- /dev/null +++ b/Makefile @@ -0,0 +1,67 @@ +PYTHON ?= python3 +VENV ?= venv +PYTHON_BIN := $(VENV)/bin/python +PIP := $(PYTHON_BIN) -m pip +SCRIPT := digital-cert.py +CA_DIR := CA + +.PHONY: all help env venv install cert run list check audit clean clean-all clean-artifacts clean-env + +all: help + +help: + @printf "Digital Certificate Generator\n\n" + @printf "Available targets:\n" + @printf " make env|venv Create the Python virtual environment\n" + @printf " make install Install project dependencies\n" + @printf " make cert|run Generate CA and client certificates\n" + @printf " make list List generated certificates\n" + @printf " make check Compile-check the Python script\n" + @printf " make audit Audit Python dependencies\n" + @printf " make clean Remove generated client certificates and keys\n" + @printf " make clean-all Remove all generated certificates including the CA\n" + @printf " make clean-env Remove the virtual environment\n" + +$(PYTHON_BIN): + $(PYTHON) -m venv $(VENV) + +$(VENV)/.installed: requirements.txt | $(PYTHON_BIN) + $(PIP) install --upgrade pip + $(PIP) install -r requirements.txt + touch $(VENV)/.installed + +$(VENV)/.audit-installed: | $(PYTHON_BIN) + $(PIP) install pip-audit + touch $(VENV)/.audit-installed + +env: venv + +venv: $(PYTHON_BIN) + +install: $(VENV)/.installed + +cert: $(VENV)/.installed + $(PYTHON_BIN) $(SCRIPT) + +run: cert + +list: + @if [ -d "$(CA_DIR)" ]; then ls -lh "$(CA_DIR)"; else printf "No CA directory found\n"; fi + @ls -lh *.crt *.key 2>/dev/null || printf "No client certificates found\n" + +check: $(VENV)/.installed + $(PYTHON_BIN) -m py_compile $(SCRIPT) + +audit: $(VENV)/.installed $(VENV)/.audit-installed + $(PYTHON_BIN) -m pip_audit -r requirements.txt + +clean: + rm -f *.crt *.key + +clean-all: clean + rm -rf $(CA_DIR) + +clean-artifacts: clean-all + +clean-env: + rm -rf $(VENV) __pycache__ diff --git a/README.md b/README.md index 2b304b5..2ef117c 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,52 @@ # Digital Certificate Generator +[![Certificate Generator CI](https://github.com/iquzart/python-digital-certificate/actions/workflows/ci.yml/badge.svg)](https://github.com/iquzart/python-digital-certificate/actions/workflows/ci.yml) + ### About -The script created to ease the process of creating self signed certificates. It will create both CA and Server/Client certificates. +This script creates a self-signed CA and signed server/client certificates. Version: 3 -Encription: SHA256 with RSA Encription(4096 bit) +Encryption: SHA256 with RSA encryption (4096 bit) + +### Security improvements +- Uses the actively maintained `cryptography` package instead of legacy `pyOpenSSL` bindings. +- Generates cryptographically secure certificate serial numbers. +- Encrypts generated private keys with a passphrase. +- Restricts private key file permissions to owner-only access. +- Sanitizes certificate output filenames to prevent path traversal. + +Set `DIGITAL_CERT_PASSPHRASE` to avoid interactive passphrase prompts. + +### Install + +```bash +make install +``` ### Create Certificate -CA cenrtificate and key will be stored under CA directory. +CA certificate and key will be stored under the `CA` directory. ```bash -python3 digital-cert.py +make run ``` ### Sample output ``` -Creating CA driectory -Creating CA Certificate, Please provide the values +Creating CA Certificate, please provide the values Country Name (2 letter code) [XX]: AE State or Province Name (full name) []: Dubai Locality Name (eg, city) [Default City]: Emaar Square Organization Name (eg, company) [Default Company Ltd]: XYZ Company Organizational Unit Name (eg, section) []: Information Technology -Common Name (eg, your name or your server's hostname) []: XYZ Company SS CA +Common Name (eg, your name or your server's hostname): XYZ Company SS CA Email Address []: email@xyz.ae +Private key passphrase: +Confirm private key passphrase: Created CA Certificate CA Certificate valid for 3649 days Client Certificate CN: svc1.xyz.ae -``` - - -``` -CA digital-cert.py README.md requirements.txt svc1.xyz.ae.crt svc1.xyz.ae.key +Private key passphrase: +Confirm private key passphrase: +Created client certificate: svc1.xyz.ae.crt +Created private key: svc1.xyz.ae.key ``` diff --git a/digital-cert.py b/digital-cert.py index 34f34ac..3ec7c68 100644 --- a/digital-cert.py +++ b/digital-cert.py @@ -1,170 +1,283 @@ #!/usr/bin/env python3 -# -# Description :- Generate self signed CA and certificates. -# Author :- Muhammed Iqbal -# +"""Generate a self-signed CA and client certificate.""" -import random +from __future__ import annotations + +import getpass +import ipaddress import os -from datetime import datetime -from OpenSSL import crypto +import re +from datetime import datetime, timedelta, timezone +from pathlib import Path + +from cryptography import x509 +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.x509.oid import ExtendedKeyUsageOID, NameOID +CA_DIR = Path("CA") +CA_CERT_PATH = CA_DIR / "ca.crt" +CA_KEY_PATH = CA_DIR / "ca.key" +CA_VALIDITY_DAYS = 3650 +CLIENT_VALIDITY_DAYS = 365 +PASSPHRASE_ENV_VAR = "DIGITAL_CERT_PASSPHRASE" -def create_CA(root_ca_path, key_path): - ''' Create CA and Key''' - - ca_key = crypto.PKey() - ca_key.generate_key(crypto.TYPE_RSA, 4096) +def prompt_value(label: str, default: str = "") -> str: + value = input(f"{label} [{default}]: ").strip() + return value or default - ca_cert = crypto.X509() - ca_cert.set_version(2) - ca_cert.set_serial_number(random.randint(50000000, 100000000)) +def prompt_required(label: str) -> str: + while True: + value = input(f"{label}: ").strip() + if value: + return value + print("Please provide a non-empty value.") + + +def get_passphrase(confirm: bool = False) -> bytes: + env_passphrase = os.environ.get(PASSPHRASE_ENV_VAR) + if env_passphrase: + return env_passphrase.encode("utf-8") + + while True: + passphrase = getpass.getpass("Private key passphrase: ") + if not passphrase: + print("A passphrase is required to protect the private key.") + continue + if not confirm: + return passphrase.encode("utf-8") + + confirmation = getpass.getpass("Confirm private key passphrase: ") + if passphrase == confirmation: + return passphrase.encode("utf-8") + print("Passphrases do not match.") + + +def build_subject() -> x509.Name: + values = [ + (NameOID.COUNTRY_NAME, prompt_value("Country Name (2 letter code)", "XX")), + (NameOID.STATE_OR_PROVINCE_NAME, prompt_value("State or Province Name (full name)")), + (NameOID.LOCALITY_NAME, prompt_value("Locality Name (eg, city)", "Default City")), + (NameOID.ORGANIZATION_NAME, prompt_value("Organization Name (eg, company)", "Default Company Ltd")), + (NameOID.ORGANIZATIONAL_UNIT_NAME, prompt_value("Organizational Unit Name (eg, section)")), + (NameOID.COMMON_NAME, prompt_required("Common Name (eg, your name or your server's hostname)")), + (NameOID.EMAIL_ADDRESS, prompt_value("Email Address")), + ] + + attributes = [] + for oid, value in values: + value = value.strip() + if value: + if oid == NameOID.COUNTRY_NAME and len(value) != 2: + raise ValueError("Country Name must be a 2 letter code.") + attributes.append(x509.NameAttribute(oid, value)) + return x509.Name(attributes) + + +def create_private_key() -> rsa.RSAPrivateKey: + return rsa.generate_private_key(public_exponent=65537, key_size=4096) + + +def write_bytes(path: Path, data: bytes, mode: int = 0o644) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, mode) + with os.fdopen(fd, "wb") as file_obj: + file_obj.write(data) + + +def build_output_stem(common_name: str) -> str: + sanitized = re.sub(r"[^A-Za-z0-9._-]+", "_", common_name.strip()).strip("._-") + if not sanitized: + raise ValueError("Common Name must contain at least one safe filename character.") + return sanitized + + +def build_subject_alternative_name(common_name: str) -> x509.SubjectAlternativeName | None: + try: + ip_value = ipaddress.ip_address(common_name) + return x509.SubjectAlternativeName([x509.IPAddress(ip_value)]) + except ValueError: + pass + + if "@" in common_name: + return x509.SubjectAlternativeName([x509.RFC822Name(common_name)]) + + if re.fullmatch(r"[A-Za-z0-9.-]+", common_name): + return x509.SubjectAlternativeName([x509.DNSName(common_name)]) + + return None + + +def certificate_expiry(certificate: x509.Certificate) -> datetime: + if hasattr(certificate, "not_valid_after_utc"): + return certificate.not_valid_after_utc + return certificate.not_valid_after.replace(tzinfo=timezone.utc) + + +def create_ca(root_ca_path: Path, key_path: Path) -> tuple[x509.Certificate, rsa.RSAPrivateKey]: + ca_key = create_private_key() + ca_subject = build_subject() + now = datetime.now(timezone.utc) + passphrase = get_passphrase(confirm=True) + + ca_cert = ( + x509.CertificateBuilder() + .subject_name(ca_subject) + .issuer_name(ca_subject) + .public_key(ca_key.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(now - timedelta(minutes=1)) + .not_valid_after(now + timedelta(days=CA_VALIDITY_DAYS)) + .add_extension(x509.SubjectKeyIdentifier.from_public_key(ca_key.public_key()), critical=False) + .add_extension(x509.AuthorityKeyIdentifier.from_issuer_public_key(ca_key.public_key()), critical=False) + .add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True) + .add_extension( + x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=True, + crl_sign=True, + encipher_only=False, + decipher_only=False, + ), + critical=True, + ) + .sign(private_key=ca_key, algorithm=hashes.SHA256()) + ) + + write_bytes(root_ca_path, ca_cert.public_bytes(serialization.Encoding.PEM)) + write_bytes( + key_path, + ca_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.BestAvailableEncryption(passphrase), + ), + mode=0o600, + ) + return ca_cert, ca_key - ca_subj = ca_cert.get_subject() - ca_subj.countryName = input("Country Name (2 letter code) [XX]: ") - ca_subj.stateOrProvinceName = input("State or Province Name (full name) []: ") - ca_subj.localityName = input("Locality Name (eg, city) [Default City]: ") - ca_subj.organizationName = input("Organization Name (eg, company) [Default Company Ltd]: ") - ca_subj.organizationalUnitName = input("Organizational Unit Name (eg, section) []: ") - ca_subj.commonName = input("Common Name (eg, your name or your server's hostname) []: ") - ca_subj.emailAddress = input("Email Address []: ") - - ca_cert.set_issuer(ca_subj) - ca_cert.set_pubkey(ca_key) - ca_cert.add_extensions([ - crypto.X509Extension(b"subjectKeyIdentifier", False, b"hash", subject=ca_cert), - ]) +def load_ca(root_ca_path: Path, key_path: Path) -> tuple[x509.Certificate, rsa.RSAPrivateKey]: + ca_cert = x509.load_pem_x509_certificate(root_ca_path.read_bytes()) + key_bytes = key_path.read_bytes() - ca_cert.add_extensions([ - crypto.X509Extension(b"authorityKeyIdentifier", False, b"keyid:always,issuer", issuer=ca_cert), - ]) + try: + ca_key = serialization.load_pem_private_key(key_bytes, password=get_passphrase()) + except TypeError: + ca_key = serialization.load_pem_private_key(key_bytes, password=None) - ca_cert.add_extensions([ - crypto.X509Extension(b"basicConstraints", True, b"CA:TRUE"), - #crypto.X509Extension(b"keyUsage", True, b"digitalSignature, keyCertSign, cRLSign"), - ]) + return ca_cert, ca_key - ca_cert.gmtime_adj_notBefore(0) - ca_cert.gmtime_adj_notAfter(10*365*24*60*60) +def ca_verification(ca_cert: x509.Certificate) -> None: + validity = (certificate_expiry(ca_cert) - datetime.now(timezone.utc)).days + print(f"CA Certificate valid for {validity} days") + + +def create_cert( + ca_cert: x509.Certificate, + ca_key: rsa.RSAPrivateKey, + client_cn: str, +) -> tuple[Path, Path]: + client_key = create_private_key() + subject = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, client_cn)]) + output_stem = build_output_stem(client_cn) + cert_path = Path(f"{output_stem}.crt") + key_path = Path(f"{output_stem}.key") + now = datetime.now(timezone.utc) + + builder = ( + x509.CertificateBuilder() + .subject_name(subject) + .issuer_name(ca_cert.subject) + .public_key(client_key.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(now - timedelta(minutes=1)) + .not_valid_after(now + timedelta(days=CLIENT_VALIDITY_DAYS)) + .add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=True) + .add_extension(x509.SubjectKeyIdentifier.from_public_key(client_key.public_key()), critical=False) + .add_extension( + x509.AuthorityKeyIdentifier.from_issuer_public_key(ca_key.public_key()), + critical=False, + ) + .add_extension( + x509.KeyUsage( + digital_signature=True, + content_commitment=False, + key_encipherment=True, + data_encipherment=False, + key_agreement=False, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=False, + ), + critical=True, + ) + .add_extension( + x509.ExtendedKeyUsage([ExtendedKeyUsageOID.CLIENT_AUTH, ExtendedKeyUsageOID.SERVER_AUTH]), + critical=False, + ) + ) + + san_extension = build_subject_alternative_name(client_cn) + if san_extension is not None: + builder = builder.add_extension(san_extension, critical=False) + + client_cert = builder.sign(private_key=ca_key, algorithm=hashes.SHA256()) + passphrase = get_passphrase(confirm=True) + + write_bytes(cert_path, client_cert.public_bytes(serialization.Encoding.PEM)) + write_bytes( + key_path, + client_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.BestAvailableEncryption(passphrase), + ), + mode=0o600, + ) + return cert_path, key_path + + +def ensure_ca_material() -> tuple[x509.Certificate, rsa.RSAPrivateKey]: + CA_DIR.mkdir(mode=0o700, exist_ok=True) + + if CA_CERT_PATH.exists() and not CA_KEY_PATH.exists(): + raise FileNotFoundError(f"Missing CA private key: {CA_KEY_PATH}") + + if CA_KEY_PATH.exists() and not CA_CERT_PATH.exists(): + raise FileNotFoundError(f"Missing CA certificate: {CA_CERT_PATH}") + + if not CA_CERT_PATH.exists(): + print("Creating CA Certificate, please provide the values") + ca_cert, ca_key = create_ca(CA_CERT_PATH, CA_KEY_PATH) + print("Created CA Certificate") + else: + print(f"CA certificate has been found as {CA_CERT_PATH}") + ca_cert, ca_key = load_ca(CA_CERT_PATH, CA_KEY_PATH) - ca_cert.sign(ca_key, 'sha256') + ca_verification(ca_cert) + return ca_cert, ca_key - # Save certificate - with open(root_ca_path, "wt") as f: - f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, ca_cert).decode("utf-8")) - # Save private key - with open(key_path, "wt") as f: - f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, ca_key).decode("utf-8")) - - - -def load_CA(root_ca_path, key_path): - ''' Load CA and Key''' +def main() -> None: + """Create self-signed certificates.""" - with open(root_ca_path, "r") as f: - ca_cert = crypto.load_certificate(crypto.FILETYPE_PEM, f.read()) - with open(key_path, "r") as f: - ca_key = crypto.load_privatekey(crypto.FILETYPE_PEM, f.read()) - return ca_cert, ca_key + ca_cert, ca_key = ensure_ca_material() + client_cn = prompt_required("Client Certificate CN") + cert_path, key_path = create_cert(ca_cert, ca_key, client_cn) + print(f"Created client certificate: {cert_path}") + print(f"Created private key: {key_path}") -def CA_varification(ca_cert): - ''' Varify the CA certificate ''' - - ca_expiry = datetime.strptime(str(ca_cert.get_notAfter(), 'utf-8'),"%Y%m%d%H%M%SZ") - now = datetime.now() - validity = (ca_expiry - now).days - print ("CA Certificate valid for {} days".format(validity)) - - -def create_cert(ca_cert, ca_subj, ca_key, client_cn): - ''' Create Client certificate ''' - - client_key = crypto.PKey() - client_key.generate_key(crypto.TYPE_RSA, 4096) - - client_cert = crypto.X509() - client_cert.set_version(2) - client_cert.set_serial_number(random.randint(50000000, 100000000)) - - client_subj = client_cert.get_subject() - client_subj.commonName = client_cn - - client_cert.set_issuer(ca_subj) - client_cert.set_pubkey(client_key) - - client_cert.add_extensions([ - crypto.X509Extension(b"basicConstraints", False, b"CA:FALSE"), - ]) - - client_cert.add_extensions([ - crypto.X509Extension(b"authorityKeyIdentifier", False, b"keyid", issuer=ca_cert), - #crypto.X509Extension(b"extendedKeyUsage", False, b"serverAuth"), - crypto.X509Extension(b"keyUsage", True, b"digitalSignature, keyEncipherment"), - ]) - - client_cert.add_extensions([ - crypto.X509Extension(b"subjectKeyIdentifier", False, b"hash", subject=client_cert), - ]) - - client_cert.gmtime_adj_notBefore(0) - client_cert.gmtime_adj_notAfter(365*24*60*60) - - client_cert.sign(ca_key, 'sha256') - - - with open(client_cn + ".crt", "wt") as f: - f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, client_cert).decode("utf-8")) - - - with open(client_cn + ".key", "wt") as f: - f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, client_key).decode("utf-8")) - -def client_varification(): - pass - - - -def main(): - - '''Create self signed certificates''' - - key_path = "CA/ca.key" - root_ca_path = "CA/ca.crt" - - - if not os.path.exists('CA'): - print ("Creating CA driectory") - os.makedirs('CA') - - if not os.path.exists(root_ca_path): - print ("Creating CA Certificate, Please provide the values") - create_CA(root_ca_path, key_path) - print ("Created CA Certificate") - ca_cert, ca_key = load_CA(root_ca_path, key_path) - CA_varification(ca_cert) - else: - print ("CA certificate has been found as {}".format(root_ca_path)) - ca_cert, ca_key = load_CA(root_ca_path, key_path) - CA_varification(ca_cert) - - - while True: - client_cn = input("Client Certificate CN: ") - if client_cn != '': - break - else: - print ("Please provide a valid CN for client certificate") - - subject = ca_cert.get_subject() - create_cert(ca_cert, subject, ca_key, client_cn) - if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/requirements.txt b/requirements.txt index 50267c0..7daa598 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1 @@ -cffi==1.14.0 -cryptography==41.0.3 -pycparser==2.20 -pyOpenSSL==19.1.0 -six==1.14.0 +cryptography>=45.0.0,<46.0.0