package api import ( "crypto/x509" "encoding/base64" "encoding/pem" "net/http" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/stef/pkiapi/internal/pki" "github.com/stef/pkiapi/internal/storage" ) // CreateCertificateRequest représente la requête de création de certificat type CreateCertificateRequest struct { Subject string `json:"subject" binding:"required"` ValidityDays int `json:"validity_days" binding:"required,min=1,max=3650"` } // CertificateResponse représente la réponse avec les données du certificat type CertificateResponse struct { ID string `json:"id"` Subject string `json:"subject"` Issuer string `json:"issuer"` NotBefore string `json:"not_before"` NotAfter string `json:"not_after"` SerialNumber string `json:"serial_number"` Certificate string `json:"certificate"` // Base64 encoded PrivateKey string `json:"private_key,omitempty"` // Base64 encoded (optional) Revoked bool `json:"revoked"` } // encodePrivateKey encode une clé privée en format base64 PKCS#8 PEM func encodePrivateKey(privateKey interface{}) (string, error) { if privateKey == nil { return "", nil } privKeyDER, err := x509.MarshalPKCS8PrivateKey(privateKey) if err != nil { return "", err } return base64.StdEncoding.EncodeToString(privKeyDER), nil } // certificateStore est un store global pour les certificats var certificateStore storage.CertificateStore // InitCertificateStore initialise le store pour les certificats func InitCertificateStore(store storage.CertificateStore) { certificateStore = store } // CreateCertificate crée un nouveau certificat auto-signé func CreateCertificate(c *gin.Context) { var req CreateCertificateRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Générer le certificat cert, err := pki.GenerateCertificate(req.Subject, req.ValidityDays) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "erreur génération certificat"}) return } // Générer un ID unique certID := uuid.New().String() cert.ID = certID // Sauvegarder le certificat if err := certificateStore.SaveCertificate(certID, cert); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "erreur sauvegarde certificat"}) return } // Récupérer l'utilisateur depuis le contexte JWT userID, _ := c.Get("user_id") // Retourner la réponse response := CertificateResponse{ ID: certID, Subject: cert.Subject, Issuer: cert.Issuer, NotBefore: cert.NotBefore.Format("2006-01-02T15:04:05Z"), NotAfter: cert.NotAfter.Format("2006-01-02T15:04:05Z"), Revoked: false, } if cert.Cert != nil { response.SerialNumber = cert.Cert.SerialNumber.String() response.Certificate = base64.StdEncoding.EncodeToString(cert.Cert.Raw) } // Ajouter la clé privée if cert.PrivateKey != nil { privKeyBase64, err := encodePrivateKey(cert.PrivateKey) if err == nil { response.PrivateKey = privKeyBase64 } } c.JSON(http.StatusCreated, gin.H{ "certificate": response, "created_by": userID, }) } // ListCertificates retourne tous les certificats func ListCertificates(c *gin.Context) { certs := certificateStore.ListCertificates() var responses []CertificateResponse for _, cert := range certs { response := CertificateResponse{ ID: cert.ID, Subject: cert.Subject, Issuer: cert.Issuer, NotBefore: cert.NotBefore.Format("2006-01-02T15:04:05Z"), NotAfter: cert.NotAfter.Format("2006-01-02T15:04:05Z"), Revoked: cert.Revoked, } if cert.Cert != nil { response.SerialNumber = cert.Cert.SerialNumber.String() } responses = append(responses, response) } c.JSON(http.StatusOK, gin.H{"certificates": responses}) } // GetCertificate retourne un certificat par ID func GetCertificate(c *gin.Context) { id := c.Param("id") cert, err := certificateStore.GetCertificate(id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "certificat non trouvé"}) return } response := CertificateResponse{ ID: cert.ID, Subject: cert.Subject, Issuer: cert.Issuer, NotBefore: cert.NotBefore.Format("2006-01-02T15:04:05Z"), NotAfter: cert.NotAfter.Format("2006-01-02T15:04:05Z"), Revoked: cert.Revoked, } if cert.Cert != nil { response.SerialNumber = cert.Cert.SerialNumber.String() response.Certificate = base64.StdEncoding.EncodeToString(cert.Cert.Raw) } // Ajouter la clé privée si disponible if cert.PrivateKey != nil { privKeyBase64, err := encodePrivateKey(cert.PrivateKey) if err == nil { response.PrivateKey = privKeyBase64 } } c.JSON(http.StatusOK, gin.H{"certificate": response}) } // RevokeCertificate révoque un certificat func RevokeCertificate(c *gin.Context) { var req struct { CertificateID string `json:"certificate_id" binding:"required"` Reason string `json:"reason"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } cert, err := certificateStore.GetCertificate(req.CertificateID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "certificat non trouvé"}) return } cert.Revoked = true certificateStore.SaveCertificate(req.CertificateID, cert) c.JSON(http.StatusOK, gin.H{ "message": "certificat révoqué", "id": req.CertificateID, "reason": req.Reason, }) } // GetCRL retourne la liste de révocation func GetCRL(c *gin.Context) { certs := certificateStore.ListCertificates() var revokedCerts []gin.H for _, cert := range certs { if cert.Revoked && cert.Cert != nil { revokedCerts = append(revokedCerts, gin.H{ "serial_number": cert.Cert.SerialNumber.String(), "subject": cert.Subject, }) } } c.JSON(http.StatusOK, gin.H{ "crl": revokedCerts, "version": 1, }) } // ExportCertificatePEM retourne un certificat au format PEM func ExportCertificatePEM(c *gin.Context) { id := c.Param("id") cert, err := certificateStore.GetCertificate(id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "certificat non trouvé"}) return } if cert.Cert == nil { c.JSON(http.StatusBadRequest, gin.H{"error": "certificat non disponible"}) return } // Convertir le certificat en format PEM pemCert := &pem.Block{ Type: "CERTIFICATE", Bytes: cert.Cert.Raw, } pemData := pem.EncodeToMemory(pemCert) // Retourner en tant que fichier téléchargeable filename := "certificate_" + id + ".pem" c.Header("Content-Type", "application/x-pem-file") c.Header("Content-Disposition", "attachment; filename="+filename) c.String(http.StatusOK, string(pemData)) } // ExportCertificateDER retourne un certificat au format DER func ExportCertificateDER(c *gin.Context) { id := c.Param("id") cert, err := certificateStore.GetCertificate(id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "certificat non trouvé"}) return } if cert.Cert == nil { c.JSON(http.StatusBadRequest, gin.H{"error": "certificat non disponible"}) return } // Retourner en tant que fichier binaire DER filename := "certificate_" + id + ".der" c.Header("Content-Type", "application/pkix-cert") c.Header("Content-Disposition", "attachment; filename="+filename) c.Data(http.StatusOK, "application/pkix-cert", cert.Cert.Raw) } // ExportCertificateWithPrivateKeyPEM retourne un certificat avec sa clé privée au format PEM func ExportCertificateWithPrivateKeyPEM(c *gin.Context) { id := c.Param("id") cert, err := certificateStore.GetCertificate(id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "certificat non trouvé"}) return } if cert.Cert == nil || cert.PrivateKey == nil { c.JSON(http.StatusBadRequest, gin.H{"error": "certificat ou clé privée non disponible"}) return } // Convertir le certificat en PEM pemCert := &pem.Block{ Type: "CERTIFICATE", Bytes: cert.Cert.Raw, } certPEM := pem.EncodeToMemory(pemCert) // Convertir la clé privée en PKCS#8 DER privateKeyDER, err := x509.MarshalPKCS8PrivateKey(cert.PrivateKey) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "erreur sérialisation clé privée"}) return } // Convertir en PEM pemKey := &pem.Block{ Type: "PRIVATE KEY", Bytes: privateKeyDER, } keyPEM := pem.EncodeToMemory(pemKey) // Combiner certificat + clé privée combined := string(certPEM) + string(keyPEM) // Retourner en tant que fichier téléchargeable filename := "certificate_" + id + "_with_key.pem" c.Header("Content-Type", "application/x-pem-file") c.Header("Content-Disposition", "attachment; filename="+filename) c.String(http.StatusOK, combined) } // ExportCertificateChain retourne une chaîne de certificats (certificat + CA parent) func ExportCertificateChain(c *gin.Context) { id := c.Param("id") cert, err := certificateStore.GetCertificate(id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "certificat non trouvé"}) return } if cert.Cert == nil { c.JSON(http.StatusBadRequest, gin.H{"error": "certificat non disponible"}) return } // Commencer par le certificat lui-même var chain []*pem.Block pemCert := &pem.Block{ Type: "CERTIFICATE", Bytes: cert.Cert.Raw, } chain = append(chain, pemCert) // Essayer de trouver le certificat parent (issuer) // En cherchant par l'issuer DN if cert.Issuer != cert.Subject { allCerts := certificateStore.ListCertificates() for _, parentCert := range allCerts { if parentCert.Cert != nil && parentCert.Subject == cert.Issuer { parentPEM := &pem.Block{ Type: "CERTIFICATE", Bytes: parentCert.Cert.Raw, } chain = append(chain, parentPEM) break } } } // Encoder tous les certificats en PEM var chainPEM string for _, block := range chain { chainPEM += string(pem.EncodeToMemory(block)) } // Retourner en tant que fichier téléchargeable filename := "certificate_chain_" + id + ".pem" c.Header("Content-Type", "application/x-pem-file") c.Header("Content-Disposition", "attachment; filename="+filename) c.String(http.StatusOK, chainPEM) }