// Package mtls implementiert Core API-09: gegenseitige TLS-Authentifizierung // zwischen Core/DMS/Mail/Archive/Workflow/AI/Connect-Instanzen fuer // Stufe-2/3-Installationen (siehe SKALIERUNGSKONZEPT.md) — schuetzt die // VERTRAULICHKEIT des Transportwegs, dort wo API-05 (asymmetrische // JWT-Signatur) und API-02 (Service-Credential) nur Authentizitaet/ // Integritaet, aber keine Transportverschluesselung zwischen getrennten // Hosts absichern. // // Zertifikatsverteilung (Akzeptanzkriterium 1): Authority.IssueCert liefert // PEM-kodiertes Zertifikat + privaten Schluessel, die ueber denselben Weg // wie andere Secrets verteilt werden (Umgebungsvariablen/Secret-Store, // niemals im Code) — automatisierbar, da IssueCert ein reiner // Funktionsaufruf ohne manuelle Schritte ist. package mtls import ( "crypto/ed25519" "crypto/rand" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/pem" "fmt" "math/big" "net" "time" ) // Authority ist eine interne Zertifizierungsstelle fuer Modul-zu-Modul- // mTLS. Haelt — analog zu internal/moduletrust.KeyManager (API-05) — ALLE // noch gueltigen historischen CA-Zertifikate im Vertrauensspeicher, damit // eine Rotation bereits ausgestellte Leaf-Zertifikate nicht ungueltig macht // (Akzeptanzkriterium 3: Rotation ohne Ausfallzeit). type Authority struct { current caGeneration trustPool *x509.CertPool generations []caGeneration } type caGeneration struct { cert *x509.Certificate key ed25519.PrivateKey } // NewAuthority erzeugt eine frische interne CA. func NewAuthority() (*Authority, error) { gen, err := newCAGeneration() if err != nil { return nil, err } pool := x509.NewCertPool() pool.AddCert(gen.cert) return &Authority{current: gen, trustPool: pool, generations: []caGeneration{gen}}, nil } func newCAGeneration() (caGeneration, error) { pub, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { return caGeneration{}, fmt.Errorf("ca-schluesselpaar erzeugen: %w", err) } serial, err := rand.Int(rand.Reader, big.NewInt(1<<62)) if err != nil { return caGeneration{}, fmt.Errorf("seriennummer erzeugen: %w", err) } template := &x509.Certificate{ SerialNumber: serial, Subject: pkix.Name{CommonName: "nexarch-internal-ca"}, NotBefore: time.Now().Add(-time.Minute), NotAfter: time.Now().Add(5 * 365 * 24 * time.Hour), KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, BasicConstraintsValid: true, IsCA: true, } der, err := x509.CreateCertificate(rand.Reader, template, template, pub, priv) if err != nil { return caGeneration{}, fmt.Errorf("ca-zertifikat erstellen: %w", err) } cert, err := x509.ParseCertificate(der) if err != nil { return caGeneration{}, fmt.Errorf("ca-zertifikat parsen: %w", err) } return caGeneration{cert: cert, key: priv}, nil } // Rotate erzeugt eine NEUE CA-Generation fuer zukuenftige IssueCert-Aufrufe, // behaelt aber ALLE bisherigen CA-Zertifikate im Vertrauensspeicher — // bereits ausgestellte Leaf-Zertifikate bleiben dadurch gueltig, eine // laufende mTLS-Verbindung wird durch Rotate NICHT unterbrochen // (Akzeptanzkriterium 3 / Pruefung 2). func (a *Authority) Rotate() error { gen, err := newCAGeneration() if err != nil { return err } a.trustPool.AddCert(gen.cert) a.generations = append(a.generations, gen) a.current = gen return nil } // TrustPool liefert den Vertrauensspeicher mit ALLEN (auch historischen, // noch nicht abgelaufenen) CA-Zertifikaten — Grundlage der // Server-seitigen Client-Zertifikatspruefung (Akzeptanzkriterium 2). func (a *Authority) TrustPool() *x509.CertPool { return a.trustPool } // IssuedCert ist ein ausgestelltes Leaf-Zertifikat inklusive privatem // Schluessel, PEM-kodiert zur Verteilung (Akzeptanzkriterium 1). type IssuedCert struct { CertPEM []byte KeyPEM []byte } // IssueCert stellt ein Leaf-Zertifikat fuer EINE Modul-Instanz aus, signiert // mit der AKTUELLEN CA-Generation (Akzeptanzkriterium 1). func (a *Authority) IssueCert(commonName string, validity time.Duration) (IssuedCert, error) { pub, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { return IssuedCert{}, fmt.Errorf("leaf-schluesselpaar erzeugen: %w", err) } serial, err := rand.Int(rand.Reader, big.NewInt(1<<62)) if err != nil { return IssuedCert{}, fmt.Errorf("seriennummer erzeugen: %w", err) } template := &x509.Certificate{ SerialNumber: serial, Subject: pkix.Name{CommonName: commonName}, NotBefore: time.Now().Add(-time.Minute), NotAfter: time.Now().Add(validity), KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, // DNSNames/IPAddresses decken lokale Testumgebungen ab (127.0.0.1, // localhost) — in echten Stufe-2/3-Installationen entspricht // commonName dem tatsaechlichen internen Hostnamen der Instanz. DNSNames: []string{commonName, "localhost"}, IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback}, } der, err := x509.CreateCertificate(rand.Reader, template, a.current.cert, pub, a.current.key) if err != nil { return IssuedCert{}, fmt.Errorf("leaf-zertifikat erstellen: %w", err) } certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}) keyBytes, err := x509.MarshalPKCS8PrivateKey(priv) if err != nil { return IssuedCert{}, fmt.Errorf("leaf-schluessel serialisieren: %w", err) } keyPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes}) return IssuedCert{CertPEM: certPEM, KeyPEM: keyPEM}, nil } // TLSCertificate wandelt ein IssuedCert in ein tls.Certificate um, wie es // tls.Config.Certificates erwartet. func (c IssuedCert) TLSCertificate() (tls.Certificate, error) { return tls.X509KeyPair(c.CertPEM, c.KeyPEM) }