332 lines
9.6 KiB
Go
332 lines
9.6 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},
|
|
OrganizationalUnit []string{req.OrganizationalUnit},
|
|
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,
|
|
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,
|
|
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)
|
|
}
|
|
|
|
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
|
|
}
|