@@ -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)
|
||||
Executable
+218
@@ -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",
|
||||
)
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user