07092026
test / build-docs (push) Successful in 1s

This commit is contained in:
jonnybravo
2026-09-07 17:02:48 +02:00
parent 6678aed520
commit 23b7df0037
118 changed files with 2029 additions and 1467 deletions
+91
View File
@@ -0,0 +1,91 @@
# selfsign_ca
CA-gestützte Erstellung selbst-signierter X.509-Zertifikate mit Python `cryptography`.
## Funktionsweise
Beim ersten Aufruf wird automatisch eine Root-CA erstellt (`crt/ca.key` + `crt/ca.crt`). Alle weiteren Zertifikate werden von dieser CA signiert. Existiert die CA bereits, wird sie wiederverwendet.
Jedes Zertifikat wird als Trio abgelegt:
| Datei | Inhalt |
|---|---|
| `crt/<name>.key` | RSA-2048 Private Key (PEM) |
| `crt/<name>.crt` | X.509-Zertifikat, signiert von der CA |
| `crt/<name>-chain.crt` | Server-Zertifikat + CA-Zertifikat (Chain) |
## Abhängigkeiten
```bash
pip install cryptography
```
## Verwendung
### Direktaufruf
```bash
python selfsign_ca.py
```
Erzeugt eine CA (falls nicht vorhanden) und das Zertifikat `test.*` mit den im `__main__`-Block hinterlegten Defaults.
### Als Modul
```python
from selfsign_ca import generate_and_save_cert
generate_and_save_cert(
cert_country="DE",
cert_state="Berlin",
cert_organization="Meine GmbH",
cert_locality="Berlin",
cert_organizational_unit="IT",
cert_common_name="myserver.local",
valid_days=365,
serial_number=100,
cert_name="myserver",
)
```
### CA-Parameter (optional)
Die CA-Daten werden nur beim ersten Erstellen der CA verwendet. Überschreiben via:
```python
generate_and_save_cert(
...,
ca_country="DE",
ca_state="Sachsen",
ca_organization="IchAG",
ca_locality="Dresden",
ca_common_name="My Root CA",
)
```
## Zertifikat prüfen
```bash
openssl verify -CAfile crt/ca.crt crt/<name>.crt
openssl x509 -in crt/<name>.crt -text -noout
```
## CA auf Clients importieren (Vertrauen)
Damit Clients dem Zertifikat vertrauen, muss `crt/ca.crt` im Trust-Store hinterlegt werden:
```bash
# Arch/Manjaro
sudo trust anchor crt/ca.crt
# Debian/Ubuntu
sudo cp crt/ca.crt /usr/local/share/ca-certificates/my-ca.crt
sudo update-ca-certificates
```
## Technische Details
- **CA**: RSA 4096 Bit, SHA256, 10 Jahre gültig, `basicConstraints: CA:TRUE`
- **Endzertifikat**: RSA 2048 Bit, SHA256, Gültigkeit nach `valid_days`, `basicConstraints: CA:FALSE`
- X.509 v3 Extensions: `BasicConstraints`, `KeyUsage`, `SubjectKeyIdentifier`, `AuthorityKeyIdentifier`
- Alle Dateien landen im Unterverzeichnis `crt/` (wird automatisch angelegt)
+218
View File
@@ -0,0 +1,218 @@
import os
from datetime import datetime, timedelta, timezone
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
CRT_DIR = "crt"
CA_KEY_FILE = os.path.join(CRT_DIR, "ca.key")
CA_CRT_FILE = os.path.join(CRT_DIR, "ca.crt")
def _ensure_crt_dir():
"""Create crt/ directory if it doesn't exist."""
os.makedirs(CRT_DIR, exist_ok=True)
def _save_pem(filename, data):
"""Write PEM data to file, converting \\n to real newlines."""
if isinstance(data, bytes):
data = data.decode()
with open(filename, "w") as f:
for line in data.replace("\r", "").split("\\n"):
f.write(line + "\n")
def _build_subject(country, state, organization, locality, common_name,
organizational_unit=None):
"""Build a cryptography X.509 Name from individual fields."""
attrs = [
x509.NameAttribute(NameOID.COUNTRY_NAME, country),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, state),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, organization),
x509.NameAttribute(NameOID.LOCALITY_NAME, locality),
x509.NameAttribute(NameOID.COMMON_NAME, common_name),
]
if organizational_unit:
attrs.append(x509.NameAttribute(
NameOID.ORGANIZATIONAL_UNIT_NAME, organizational_unit))
return x509.Name(attrs)
def generate_ca_cert(cert_country, cert_state, cert_organization,
cert_locality, cert_common_name, valid_days=3650):
"""
Generate a self-signed CA certificate and save ca.key / ca.crt.
Returns (cert_pem, key_pem).
"""
subject = _build_subject(cert_country, cert_state, cert_organization,
cert_locality, cert_common_name)
private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096)
now = datetime.now(timezone.utc)
cert = (
x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(subject) # self-signed
.public_key(private_key.public_key())
.serial_number(1)
.not_valid_before(now)
.not_valid_after(now + timedelta(days=valid_days))
.add_extension(x509.BasicConstraints(ca=True, path_length=None),
critical=True)
.add_extension(x509.KeyUsage(
key_cert_sign=True, crl_sign=True,
digital_signature=False, content_commitment=False,
key_encipherment=False, data_encipherment=False,
key_agreement=False, encipher_only=False, decipher_only=False,
), critical=True)
.add_extension(x509.SubjectKeyIdentifier.from_public_key(
private_key.public_key()), critical=False)
.sign(private_key, hashes.SHA256())
)
cert_pem = cert.public_bytes(serialization.Encoding.PEM)
key_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
)
_ensure_crt_dir()
_save_pem(CA_KEY_FILE, key_pem)
_save_pem(CA_CRT_FILE, cert_pem)
print(f"CA erstellt: {CA_KEY_FILE}, {CA_CRT_FILE}")
return cert_pem, key_pem
def load_ca():
"""
Load CA certificate and private key from disk.
Returns (ca_cert, ca_pkey) as cryptography objects, or None if missing.
"""
if not os.path.exists(CA_CRT_FILE) or not os.path.exists(CA_KEY_FILE):
return None
with open(CA_CRT_FILE, "rb") as f:
ca_cert = x509.load_pem_x509_certificate(f.read())
with open(CA_KEY_FILE, "rb") as f:
ca_pkey = serialization.load_pem_private_key(f.read(), password=None)
return ca_cert, ca_pkey
def generate_cert_signed_by_ca(ca_cert, ca_pkey,
cert_country, cert_state, cert_organization,
cert_locality, cert_organizational_unit,
cert_common_name, valid_days, serial_number):
"""
Generate a certificate signed by the given CA.
Returns (cert_pem, key_pem) as strings.
"""
subject = _build_subject(cert_country, cert_state, cert_organization,
cert_locality, cert_common_name,
cert_organizational_unit)
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
now = datetime.now(timezone.utc)
cert = (
x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(ca_cert.subject)
.public_key(private_key.public_key())
.serial_number(serial_number)
.not_valid_before(now)
.not_valid_after(now + timedelta(days=valid_days))
.add_extension(x509.BasicConstraints(ca=False, path_length=None),
critical=True)
.add_extension(x509.KeyUsage(
digital_signature=True, key_encipherment=True,
key_cert_sign=False, crl_sign=False,
content_commitment=False, data_encipherment=False,
key_agreement=False, encipher_only=False, decipher_only=False,
), critical=True)
.add_extension(x509.SubjectKeyIdentifier.from_public_key(
private_key.public_key()), critical=False)
.add_extension(x509.AuthorityKeyIdentifier.from_issuer_public_key(
ca_pkey.public_key()), critical=False)
.sign(ca_pkey, hashes.SHA256())
)
cert_pem = cert.public_bytes(serialization.Encoding.PEM)
key_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
)
return str(cert_pem.decode()), str(key_pem.decode())
def generate_and_save_cert(cert_country, cert_state, cert_organization,
cert_locality, cert_organizational_unit,
cert_common_name, valid_days=365, serial_number=100,
ca_country="DE", ca_state="Sachsen",
ca_organization="IchAG",
ca_locality="Dresden",
ca_common_name="My Root CA",
cert_name="server"):
"""
High-level entry point:
1. If CA not present, create it.
2. Generate certificate signed by the CA.
3. Save key and cert (with optional chain) to <cert_name>.key / <cert_name>.crt.
"""
ca = load_ca()
if ca is None:
print("CA nicht gefunden – erstelle CA ...")
generate_ca_cert(ca_country, ca_state, ca_organization,
ca_locality, ca_common_name)
ca = load_ca()
if ca is None:
raise RuntimeError("CA konnte nicht erstellt/geladen werden.")
ca_cert, ca_pkey = ca
cert_pem, key_pem = generate_cert_signed_by_ca(
ca_cert, ca_pkey,
cert_country, cert_state, cert_organization,
cert_locality, cert_organizational_unit,
cert_common_name, valid_days, serial_number,
)
key_file = os.path.join(CRT_DIR, f"{cert_name}.key")
crt_file = os.path.join(CRT_DIR, f"{cert_name}.crt")
chain_file = os.path.join(CRT_DIR, f"{cert_name}-chain.crt")
_ensure_crt_dir()
_save_pem(key_file, key_pem)
_save_pem(crt_file, cert_pem)
# Chain file: server cert + CA cert
chain = cert_pem.replace("\r", "")
ca_pem = ca_cert.public_bytes(serialization.Encoding.PEM).decode()
chain += "\n" + ca_pem
_save_pem(chain_file, chain)
print(f"Zertifikat erstellt: {key_file}, {crt_file}, {chain_file}")
return cert_pem, key_pem
if __name__ == "__main__":
generate_and_save_cert(
cert_country="DE",
cert_state="Dresden",
cert_organization="IchAG",
cert_locality="Sachsen",
cert_organizational_unit="mandan01",
cert_common_name="mandan01",
valid_days=365,
serial_number=12,
cert_name="test",
)
-50
View File
@@ -1,50 +0,0 @@
from OpenSSL import crypto
from Crypto.PublicKey import RSA
def generate_self_signed_cert(cert_country, cert_state, cert_organization,
cert_locality, cert_organizational_unit,
cert_common_name, valid_days, serial_number):
rsa_key = RSA.generate(2048)
pk = crypto.load_privatekey(crypto.FILETYPE_PEM,
rsa_key.exportKey('PEM', pkcs=1))
cert = crypto.X509()
sub = cert.get_subject()
sub.CN = cert_common_name
sub.C = cert_country
sub.ST = cert_state
sub.L = cert_locality
sub.O = cert_organization
# optional
if cert_organizational_unit:
sub.OU = cert_organizational_unit
cert.set_serial_number(serial_number)
cert.gmtime_adj_notBefore(0)
cert.gmtime_adj_notAfter(valid_days * 24 * 60 * 60) # Valid for a year
cert.set_issuer(sub)
cert.set_pubkey(pk)
cert.sign(pk, 'sha1')
cert_text = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
priv_key_text = rsa_key.exportKey('PEM', pkcs=1)
return str(cert_text), str(priv_key_text)
if __name__ == "__main__":
test = generate_self_signed_cert(cert_country="DE", cert_state="Dresden", cert_organization="IchAG", cert_locality="Sachsen", cert_organizational_unit="mandan01", cert_common_name="mandan01", valid_days=365, serial_number=12)
privatekey = test[1].rstrip("'").strip("b'")
cert = test[0].rstrip("'").strip("b'")
#print(privatekey)
for key in privatekey.split("\\n"):
with open("testkey", "a") as testcert:
print(key, file=testcert)
for cert_out in cert.split("\\n"):
with open("testcert", "a") as testcert_out:
print(cert_out, file=testcert_out)
#test2 = "xccj\njdkjd\n"
#print(test2)