pki-manager/internal/services/crypto_service.go

328 lines
9.4 KiB
Go

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},
Country: []string{req.Country},
Province: []string{req.Province},
Locality: []string{req.Locality},
}
// 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,
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,
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},
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,
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)
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: req.CommonName,
},
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,
Subject: template.Subject.String(),
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
}