package services import ( "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/pem" "fmt" "math/big" "net" "time" "pki-manager/internal/models" ) type CryptoService struct { certsPath string } func NewCryptoService(certsPath string) *CryptoService { return &CryptoService{certsPath: certsPath} } func (s *CryptoService) GenerateRootCA(req models.CreateCARequest) (*models.CA, error) { // Generate private key priv, err := rsa.GenerateKey(rand.Reader, req.KeySize) if err != nil { return nil, fmt.Errorf("failed to generate private key: %v", err) } // Create certificate template serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) if err != nil { return nil, fmt.Errorf("failed to generate serial number: %v", err) } // Préparer le sujet avec email optionnel subject := pkix.Name{ CommonName: req.CommonName, Organization: []string{req.Organization}, OrganizationalUnit: []string{req.OrganizationalUnit}, Country: []string{req.Country}, Province: []string{req.Province}, Locality: []string{req.Locality}, StreetAddress: []string{req.StreetAddress}, PostalCode: []string{req.PostalCode}, } // Ajouter l'email seulement s'il est fourni if req.Email != "" { // L'email n'est pas un champ standard dans pkix.Name // On le stockera dans les champs personnalisés } template := x509.Certificate{ SerialNumber: serialNumber, Subject: subject, NotBefore: time.Now(), NotAfter: time.Now().AddDate(req.ValidYears, 0, 0), KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, BasicConstraintsValid: true, IsCA: true, MaxPathLen: 0, MaxPathLenZero: true, } // Ajouter l'email seulement s'il est fourni if req.Email != "" { // L'email n'est pas un champ standard dans pkix.Name // On le stockera dans les champs personnalisés } // Self-sign the certificate certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) if err != nil { return nil, fmt.Errorf("failed to create certificate: %v", err) } // Encode private key privBytes := x509.MarshalPKCS1PrivateKey(priv) privPEM := pem.EncodeToMemory(&pem.Block{ Type: "RSA PRIVATE KEY", Bytes: privBytes, }) // Encode certificate certPEM := pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: certBytes, }) ca := &models.CA{ ID: fmt.Sprintf("ca_%d", time.Now().UnixNano()), Name: req.Name, CommonName: req.CommonName, Organization: req.Organization, OrganizationalUnit: req.OrganizationalUnit, Country: req.Country, Province: req.Province, Locality: req.Locality, StreetAddress: req.StreetAddress, PostalCode: req.PostalCode, Email: req.Email, PrivateKey: string(privPEM), Certificate: string(certPEM), SerialNumber: serialNumber.String(), ValidFrom: template.NotBefore, ValidTo: template.NotAfter, IsRoot: req.IsRoot, } return ca, nil } func (s *CryptoService) GenerateSubCA(req models.CreateSubCARequest, parentCA *models.CA) (*models.SubCA, error) { // Parse parent CA certificate and private key parentCertBlock, _ := pem.Decode([]byte(parentCA.Certificate)) if parentCertBlock == nil { return nil, fmt.Errorf("failed to parse parent CA certificate") } parentCert, err := x509.ParseCertificate(parentCertBlock.Bytes) if err != nil { return nil, fmt.Errorf("failed to parse parent CA certificate: %v", err) } parentKeyBlock, _ := pem.Decode([]byte(parentCA.PrivateKey)) if parentKeyBlock == nil { return nil, fmt.Errorf("failed to parse parent CA private key") } parentKey, err := x509.ParsePKCS1PrivateKey(parentKeyBlock.Bytes) if err != nil { return nil, fmt.Errorf("failed to parse parent CA private key: %v", err) } // Generate subCA private key priv, err := rsa.GenerateKey(rand.Reader, req.KeySize) if err != nil { return nil, fmt.Errorf("failed to generate private key: %v", err) } // Create certificate template serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) if err != nil { return nil, fmt.Errorf("failed to generate serial number: %v", err) } subject := pkix.Name{ CommonName: req.CommonName, Organization: []string{req.Organization}, OrganizationalUnit: []string{req.OrganizationalUnit}, Country: []string{req.Country}, Province: []string{req.Province}, Locality: []string{req.Locality}, } template := x509.Certificate{ SerialNumber: serialNumber, Subject: subject, NotBefore: time.Now(), NotAfter: time.Now().AddDate(req.ValidYears, 0, 0), KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, BasicConstraintsValid: true, IsCA: true, MaxPathLen: 0, MaxPathLenZero: false, } // Sign the certificate with parent CA certBytes, err := x509.CreateCertificate(rand.Reader, &template, parentCert, &priv.PublicKey, parentKey) if err != nil { return nil, fmt.Errorf("failed to create certificate: %v", err) } // Encode private key privBytes := x509.MarshalPKCS1PrivateKey(priv) privPEM := pem.EncodeToMemory(&pem.Block{ Type: "RSA PRIVATE KEY", Bytes: privBytes, }) // Encode certificate certPEM := pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: certBytes, }) subca := &models.SubCA{ ID: fmt.Sprintf("subca_%d", time.Now().UnixNano()), Name: req.Name, CommonName: req.CommonName, Organization: req.Organization, OrganizationalUnit: req.OrganizationalUnit, Country: req.Country, Province: req.Province, Locality: req.Locality, Email: req.Email, PrivateKey: string(privPEM), Certificate: string(certPEM), SerialNumber: serialNumber.String(), ValidFrom: template.NotBefore, ValidTo: template.NotAfter, ParentCAID: req.ParentCAID, } return subca, nil } func (s *CryptoService) GenerateCertificate(req models.CreateCertificateRequest, issuer interface{}) (*models.Certificate, error) { var issuerCert *x509.Certificate var issuerKey *rsa.PrivateKey // Parse issuer based on type switch iss := issuer.(type) { case *models.CA: certBlock, _ := pem.Decode([]byte(iss.Certificate)) if certBlock == nil { return nil, fmt.Errorf("failed to parse issuer certificate") } var err error issuerCert, err = x509.ParseCertificate(certBlock.Bytes) if err != nil { return nil, fmt.Errorf("failed to parse issuer certificate: %v", err) } keyBlock, _ := pem.Decode([]byte(iss.PrivateKey)) if keyBlock == nil { return nil, fmt.Errorf("failed to parse issuer private key") } issuerKey, err = x509.ParsePKCS1PrivateKey(keyBlock.Bytes) if err != nil { return nil, fmt.Errorf("failed to parse issuer private key: %v", err) } case *models.SubCA: certBlock, _ := pem.Decode([]byte(iss.Certificate)) if certBlock == nil { return nil, fmt.Errorf("failed to parse issuer certificate") } var err error issuerCert, err = x509.ParseCertificate(certBlock.Bytes) if err != nil { return nil, fmt.Errorf("failed to parse issuer certificate: %v", err) } keyBlock, _ := pem.Decode([]byte(iss.PrivateKey)) if keyBlock == nil { return nil, fmt.Errorf("failed to parse issuer private key") } issuerKey, err = x509.ParsePKCS1PrivateKey(keyBlock.Bytes) if err != nil { return nil, fmt.Errorf("failed to parse issuer private key: %v", err) } default: return nil, fmt.Errorf("unsupported issuer type") } // Generate certificate private key priv, err := rsa.GenerateKey(rand.Reader, req.KeySize) if err != nil { return nil, fmt.Errorf("failed to generate private key: %v", err) } // Create certificate template serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) if err != nil { return nil, fmt.Errorf("failed to generate serial number: %v", err) } subject := pkix.Name{ CommonName: req.CommonName, Organization: []string{issuerCert.Subject.Organization[0]}, OrganizationalUnit: []string{issuerCert.Subject.OrganizationalUnit[0]}, Country: []string{issuerCert.Subject.Country[0]}, Province: []string{issuerCert.Subject.Province[0]}, Locality: []string{issuerCert.Subject.Locality[0]}, StreetAddress: []string{issuerCert.Subject.StreetAddress[0]}, PostalCode: []string{issuerCert.Subject.PostalCode[0]}, } template := x509.Certificate{ SerialNumber: serialNumber, Subject: subject, NotBefore: time.Now(), NotAfter: time.Now().AddDate(0, 0, req.ValidDays), KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, ExtKeyUsage: s.getExtKeyUsage(req.Type), DNSNames: req.DNSNames, IPAddresses: s.parseIPs(req.IPAddresses), } // Sign the certificate certBytes, err := x509.CreateCertificate(rand.Reader, &template, issuerCert, &priv.PublicKey, issuerKey) if err != nil { return nil, fmt.Errorf("failed to create certificate: %v", err) } // Encode private key privBytes := x509.MarshalPKCS1PrivateKey(priv) privPEM := pem.EncodeToMemory(&pem.Block{ Type: "RSA PRIVATE KEY", Bytes: privBytes, }) // Encode certificate certPEM := pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: certBytes, }) cert := &models.Certificate{ ID: fmt.Sprintf("cert_%d", time.Now().UnixNano()), CommonName: req.CommonName, DNSNames: req.DNSNames, IPAddresses: req.IPAddresses, Type: req.Type, PrivateKey: string(privPEM), Certificate: string(certPEM), SerialNumber: serialNumber.String(), ValidFrom: template.NotBefore, ValidTo: template.NotAfter, IssuerCAID: req.IssuerCAID, Revoked: false, } return cert, nil } func (s *CryptoService) getExtKeyUsage(certType string) []x509.ExtKeyUsage { switch certType { case "server": return []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth} case "client": return []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth} default: return []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth} } } func (s *CryptoService) parseIPs(ips []string) []net.IP { var parsedIPs []net.IP for _, ipStr := range ips { if ip := net.ParseIP(ipStr); ip != nil { parsedIPs = append(parsedIPs, ip) } } return parsedIPs } func (s *CryptoService) ParseCertificate(data []byte) (*x509.Certificate, error) { block, _ := pem.Decode(data) if block == nil { return nil, fmt.Errorf("format PEM invalide") } return x509.ParseCertificate(block.Bytes) } func (s *CryptoService) ParsePrivateKey(data []byte) (interface{}, error) { block, _ := pem.Decode(data) if block == nil { return nil, fmt.Errorf("format PEM invalide") } if key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil { return key, nil } if key, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil { return key, nil } if key, err := x509.ParseECPrivateKey(block.Bytes); err == nil { return key, nil } return nil, fmt.Errorf("format de clé non supporté") }