package main import ( "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/base64" "encoding/json" "encoding/pem" "fmt" "log" "math/big" "net/http" "os" "sync" "time" "github.com/gorilla/mux" ) // CertificateStore speichert signierte Zertifikate type CertificateStore struct { mu sync.RWMutex certificates map[string]*StoredCertificate } // StoredCertificate enthält das signierte Zertifikat und Metadaten type StoredCertificate struct { ID string Certificate *x509.Certificate PEM string CreatedAt time.Time } // CA repräsentiert die Certificate Authority type CA struct { rootCert *x509.Certificate rootKey *rsa.PrivateKey certStore *CertificateStore serialNumber *big.Int mu sync.Mutex } // CSRRequest repräsentiert eine CSR-Anfrage type CSRRequest struct { CSR string `json:"csr"` // Base64-kodierter PEM-formatierter CSR Action string `json:"action"` // z.B. "sign" ValidityDays int `json:"validity_days"` // Gültigkeitsdauer in Tagen (optional, default: 365) } // CSRResponse repräsentiert die Antwort auf eine CSR-Anfrage type CSRResponse struct { ID string `json:"id"` Status string `json:"status"` Message string `json:"message"` Certificate string `json:"certificate,omitempty"` // PEM-formatierter Zertifikat } // CertificateResponse repräsentiert die Antwort beim Abruf eines Zertifikats type CertificateResponse struct { ID string `json:"id"` Certificate string `json:"certificate"` CreatedAt time.Time `json:"created_at"` } // NewCA erstellt eine neue Certificate Authority func NewCA() (*CA, error) { // Root-Zertifikat und Key generieren rootKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return nil, fmt.Errorf("fehler beim Generieren des Root-Keys: %v", err) } // Root-Zertifikat erstellen rootTemplate := &x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{ Organization: []string{"Dummy CA"}, Country: []string{"DE"}, Province: []string{""}, Locality: []string{""}, StreetAddress: []string{""}, PostalCode: []string{""}, CommonName: "Dummy CA Root Certificate", }, NotBefore: time.Now(), NotAfter: time.Now().AddDate(10, 0, 0), // 10 Jahre gültig KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, BasicConstraintsValid: true, IsCA: true, MaxPathLen: 2, MaxPathLenZero: false, } rootCertDER, err := x509.CreateCertificate(rand.Reader, rootTemplate, rootTemplate, &rootKey.PublicKey, rootKey) if err != nil { return nil, fmt.Errorf("fehler beim Erstellen des Root-Zertifikats: %v", err) } rootCert, err := x509.ParseCertificate(rootCertDER) if err != nil { return nil, fmt.Errorf("fehler beim Parsen des Root-Zertifikats: %v", err) } return &CA{ rootCert: rootCert, rootKey: rootKey, certStore: &CertificateStore{certificates: make(map[string]*StoredCertificate)}, serialNumber: big.NewInt(2), }, nil } // SignCSR signiert einen Certificate Signing Request func (ca *CA) SignCSR(csrPEM string, validityDays int) (string, error) { // PEM-Block dekodieren block, _ := pem.Decode([]byte(csrPEM)) if block == nil { return "", fmt.Errorf("ungültiger PEM-Block") } // CSR parsen csr, err := x509.ParseCertificateRequest(block.Bytes) if err != nil { return "", fmt.Errorf("fehler beim Parsen des CSR: %v", err) } // CSR validieren if err := csr.CheckSignature(); err != nil { return "", fmt.Errorf("ungültige CSR-Signatur: %v", err) } // Serialnummer inkrementieren ca.mu.Lock() serial := new(big.Int).Set(ca.serialNumber) ca.serialNumber.Add(ca.serialNumber, big.NewInt(1)) ca.mu.Unlock() // Zertifikat-Template erstellen template := &x509.Certificate{ SerialNumber: serial, Subject: csr.Subject, NotBefore: time.Now(), NotAfter: time.Now().AddDate(0, 0, validityDays), KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, } // SANs (Subject Alternative Names) vom CSR kopieren if len(csr.DNSNames) > 0 { template.DNSNames = csr.DNSNames } if len(csr.IPAddresses) > 0 { template.IPAddresses = csr.IPAddresses } if len(csr.EmailAddresses) > 0 { template.EmailAddresses = csr.EmailAddresses } // Zertifikat signieren certDER, err := x509.CreateCertificate(rand.Reader, template, ca.rootCert, csr.PublicKey, ca.rootKey) if err != nil { return "", fmt.Errorf("fehler beim Signieren des Zertifikats: %v", err) } // Zertifikat parsen cert, err := x509.ParseCertificate(certDER) if err != nil { return "", fmt.Errorf("fehler beim Parsen des signierten Zertifikats: %v", err) } // PEM-formatieren certPEM := pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: certDER, }) // In Store speichern certID := fmt.Sprintf("%x", cert.SerialNumber.Bytes()) ca.certStore.mu.Lock() ca.certStore.certificates[certID] = &StoredCertificate{ ID: certID, Certificate: cert, PEM: string(certPEM), CreatedAt: time.Now(), } ca.certStore.mu.Unlock() return certID, nil } // GetCertificate ruft ein Zertifikat anhand der ID ab func (ca *CA) GetCertificate(id string) (*StoredCertificate, error) { ca.certStore.mu.RLock() defer ca.certStore.mu.RUnlock() cert, exists := ca.certStore.certificates[id] if !exists { return nil, fmt.Errorf("zertifikat mit ID %s nicht gefunden", id) } return cert, nil } // GetRootCertificate gibt das Root-Zertifikat zurück func (ca *CA) GetRootCertificate() string { rootCertPEM := pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: ca.rootCert.Raw, }) return string(rootCertPEM) } // handleSubmitCSR behandelt POST-Anfragen zum Einreichen eines CSR func (ca *CA) handleSubmitCSR(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "nur POST erlaubt", http.StatusMethodNotAllowed) return } var req CSRRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, fmt.Sprintf("fehler beim Dekodieren der Anfrage: %v", err), http.StatusBadRequest) return } // CSR dekodieren (Base64) csrBytes, err := base64.StdEncoding.DecodeString(req.CSR) if err != nil { http.Error(w, fmt.Sprintf("fehler beim Dekodieren des CSR: %v", err), http.StatusBadRequest) return } csrPEM := string(csrBytes) // Validity Days standardmäßig auf 365 setzen validityDays := req.ValidityDays if validityDays <= 0 { validityDays = 365 } // Action prüfen if req.Action != "sign" { http.Error(w, "ungültige Action. Erlaubt: 'sign'", http.StatusBadRequest) return } // CSR signieren certID, err := ca.SignCSR(csrPEM, validityDays) if err != nil { http.Error(w, fmt.Sprintf("fehler beim Signieren: %v", err), http.StatusInternalServerError) return } // Zertifikat abrufen storedCert, err := ca.GetCertificate(certID) if err != nil { http.Error(w, fmt.Sprintf("fehler beim Abrufen des Zertifikats: %v", err), http.StatusInternalServerError) return } // Antwort senden response := CSRResponse{ ID: certID, Status: "success", Message: "CSR erfolgreich signiert", Certificate: storedCert.PEM, } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) } // handleGetCertificate behandelt GET-Anfragen zum Abruf eines Zertifikats func (ca *CA) handleGetCertificate(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) certID := vars["id"] if certID == "" { http.Error(w, "zertifikat-ID erforderlich", http.StatusBadRequest) return } storedCert, err := ca.GetCertificate(certID) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } response := CertificateResponse{ ID: storedCert.ID, Certificate: storedCert.PEM, CreatedAt: storedCert.CreatedAt, } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) } // handleGetRootCertificate gibt das Root-Zertifikat zurück func (ca *CA) handleGetRootCertificate(w http.ResponseWriter, r *http.Request) { rootCert := ca.GetRootCertificate() w.Header().Set("Content-Type", "application/x-pem-file") w.Write([]byte(rootCert)) } // responseWriter ist ein Wrapper für http.ResponseWriter, um den Status Code zu erfassen type responseWriter struct { http.ResponseWriter statusCode int bytesWritten int64 } func newResponseWriter(w http.ResponseWriter) *responseWriter { return &responseWriter{w, http.StatusOK, 0} } func (rw *responseWriter) WriteHeader(code int) { rw.statusCode = code rw.ResponseWriter.WriteHeader(code) } func (rw *responseWriter) Write(b []byte) (int, error) { n, err := rw.ResponseWriter.Write(b) rw.bytesWritten += int64(n) return n, err } // loggingMiddleware loggt alle HTTP-Requests mit Details func loggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() // ResponseWriter wrappen, um Status Code zu erfassen wrapped := newResponseWriter(w) // Request verarbeiten next.ServeHTTP(wrapped, r) // Logging duration := time.Since(start) // Farbige Ausgabe für bessere Lesbarkeit statusColor := "" statusReset := "" if wrapped.statusCode >= 200 && wrapped.statusCode < 300 { statusColor = "\033[32m" // Grün für Erfolg statusReset = "\033[0m" } else if wrapped.statusCode >= 400 && wrapped.statusCode < 500 { statusColor = "\033[33m" // Gelb für Client-Fehler statusReset = "\033[0m" } else if wrapped.statusCode >= 500 { statusColor = "\033[31m" // Rot für Server-Fehler statusReset = "\033[0m" } // Log-Format: [Zeit] METHOD Pfad Status Dauer Größe IP User-Agent log.Printf("[%s] %s %s %s%d%s %v %d bytes %s %s", start.Format("2006-01-02 15:04:05"), r.Method, r.URL.Path, statusColor, wrapped.statusCode, statusReset, duration, wrapped.bytesWritten, r.RemoteAddr, r.UserAgent(), ) // Bei POST-Requests auch Query-Parameter und Content-Length loggen if r.Method == "POST" { if r.URL.RawQuery != "" { log.Printf(" Query: %s", r.URL.RawQuery) } if r.ContentLength > 0 { log.Printf(" Content-Length: %d bytes", r.ContentLength) } } }) } func main() { // CA initialisieren ca, err := NewCA() if err != nil { log.Fatalf("fehler beim Initialisieren der CA: %v", err) } // Router erstellen r := mux.NewRouter() // API-Endpunkte r.HandleFunc("/csr", ca.handleSubmitCSR).Methods("POST") r.HandleFunc("/certificate/{id}", ca.handleGetCertificate).Methods("GET") r.HandleFunc("/root", ca.handleGetRootCertificate).Methods("GET") // Health-Check r.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]string{"status": "ok"}) }).Methods("GET") // Logging-Middleware auf alle Routen anwenden loggedRouter := loggingMiddleware(r) // Logger konfigurieren für bessere Ausgabe log.SetOutput(os.Stdout) log.SetFlags(0) // Keine Standard-Präfixe, wir formatieren selbst log.Println("========================================") log.Println("Dummy CA Server startet auf Port 8088") log.Println("========================================") log.Println("Endpunkte:") log.Println(" POST /csr - CSR einreichen und signieren") log.Println(" GET /certificate/{id} - Zertifikat abrufen") log.Println(" GET /root - Root-Zertifikat abrufen") log.Println(" GET /health - Health-Check") log.Println("") log.Println("Alle API-Anfragen werden geloggt...") log.Println("========================================") log.Println("") if err := http.ListenAndServe(":8088", loggedRouter); err != nil { log.Fatalf("fehler beim Starten des Servers: %v", err) } }