First commit
This commit is contained in:
327
internal/services/crypto_service.go
Normal file
327
internal/services/crypto_service.go
Normal file
@@ -0,0 +1,327 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user