feat: PKI API Go avec persistance MongoDB et abstraction storage
- API REST complète pour gestion Infrastructure à Clé Publique - Authentification JWT sur tous les endpoints (sauf login) - Hiérarchie de certificats (Root CA, Sub-CA, End Certificates) - Abstraction de stockage avec MemoryStore et MongoStore - Configuration centralisée via variables d'environnement - Support déploiement Docker Compose avec MongoDB - Tests unitaires pour sérialisation des clés RSA - Documentation complète avec exemples API
This commit is contained in:
236
internal/pki/certificate.go
Normal file
236
internal/pki/certificate.go
Normal file
@@ -0,0 +1,236 @@
|
||||
package pki
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"math/big"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Certificate représente un certificat X.509
|
||||
type Certificate struct {
|
||||
ID string
|
||||
Subject string
|
||||
Issuer string
|
||||
NotBefore time.Time
|
||||
NotAfter time.Time
|
||||
PublicKey *rsa.PublicKey
|
||||
PrivateKey *rsa.PrivateKey // Stocké pour les CAs
|
||||
Cert *x509.Certificate
|
||||
Revoked bool
|
||||
IsCA bool // True si c'est une autorité de certification
|
||||
}
|
||||
|
||||
// GenerateCertificate crée un nouveau certificat auto-signé
|
||||
func GenerateCertificate(subject string, validityDays int) (*Certificate, error) {
|
||||
// Générer une clé RSA
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Créer le certificat
|
||||
serialNumber, _ := rand.Int(rand.Reader, big.NewInt(1000000))
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.AddDate(0, 0, validityDays)
|
||||
|
||||
cert := &x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
CommonName: subject,
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
}
|
||||
|
||||
// Auto-signer le certificat
|
||||
certBytes, err := x509.CreateCertificate(rand.Reader, cert, cert, &privateKey.PublicKey, privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parsedCert, err := x509.ParseCertificate(certBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Certificate{
|
||||
Subject: subject,
|
||||
Issuer: subject,
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
PublicKey: &privateKey.PublicKey,
|
||||
PrivateKey: privateKey,
|
||||
Cert: parsedCert,
|
||||
Revoked: false,
|
||||
IsCA: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GenerateCA crée une nouvelle autorité de certification
|
||||
func GenerateCA(subject string, validityDays int) (*Certificate, error) {
|
||||
// Générer une clé RSA
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Créer le certificat CA
|
||||
serialNumber, _ := rand.Int(rand.Reader, big.NewInt(1000000000))
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.AddDate(0, 0, validityDays)
|
||||
|
||||
cert := &x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
CommonName: subject,
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
MaxPathLen: -1, // Pas de limite de chaîne
|
||||
}
|
||||
|
||||
// Auto-signer le certificat CA
|
||||
certBytes, err := x509.CreateCertificate(rand.Reader, cert, cert, &privateKey.PublicKey, privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parsedCert, err := x509.ParseCertificate(certBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Certificate{
|
||||
Subject: subject,
|
||||
Issuer: subject,
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
PublicKey: &privateKey.PublicKey,
|
||||
PrivateKey: privateKey,
|
||||
Cert: parsedCert,
|
||||
Revoked: false,
|
||||
IsCA: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SignCertificate signe un certificat avec une CA
|
||||
func SignCertificate(caCert *Certificate, subject string, validityDays int) (*Certificate, error) {
|
||||
if !caCert.IsCA {
|
||||
return nil, ErrNotACA
|
||||
}
|
||||
|
||||
if caCert.PrivateKey == nil {
|
||||
return nil, ErrMissingPrivateKey
|
||||
}
|
||||
|
||||
// Générer une clé RSA pour le nouveau certificat
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Créer le certificat à signer
|
||||
serialNumber, _ := rand.Int(rand.Reader, big.NewInt(1000000000))
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.AddDate(0, 0, validityDays)
|
||||
|
||||
cert := &x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
CommonName: subject,
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
Issuer: caCert.Cert.Subject,
|
||||
}
|
||||
|
||||
// Signer le certificat avec la clé privée de la CA
|
||||
certBytes, err := x509.CreateCertificate(rand.Reader, cert, caCert.Cert, &privateKey.PublicKey, caCert.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parsedCert, err := x509.ParseCertificate(certBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Certificate{
|
||||
Subject: subject,
|
||||
Issuer: caCert.Subject,
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
PublicKey: &privateKey.PublicKey,
|
||||
PrivateKey: privateKey,
|
||||
Cert: parsedCert,
|
||||
Revoked: false,
|
||||
IsCA: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SignSubCA signe une CA intermédiaire avec une CA parent
|
||||
func SignSubCA(parentCA *Certificate, subject string, validityDays int) (*Certificate, error) {
|
||||
if !parentCA.IsCA {
|
||||
return nil, ErrNotACA
|
||||
}
|
||||
|
||||
if parentCA.PrivateKey == nil {
|
||||
return nil, ErrMissingPrivateKey
|
||||
}
|
||||
|
||||
// Générer une clé RSA pour la sub-CA
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Créer le certificat CA intermédiaire
|
||||
serialNumber, _ := rand.Int(rand.Reader, big.NewInt(1000000000))
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.AddDate(0, 0, validityDays)
|
||||
|
||||
subCA := &x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
CommonName: subject,
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
MaxPathLen: 0, // Limite la chaîne : cette CA ne peut pas signer d'autres CAs
|
||||
Issuer: parentCA.Cert.Subject,
|
||||
}
|
||||
|
||||
// Signer le certificat CA avec la clé privée du parent
|
||||
certBytes, err := x509.CreateCertificate(rand.Reader, subCA, parentCA.Cert, &privateKey.PublicKey, parentCA.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parsedCert, err := x509.ParseCertificate(certBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Certificate{
|
||||
Subject: subject,
|
||||
Issuer: parentCA.Subject,
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
PublicKey: &privateKey.PublicKey,
|
||||
PrivateKey: privateKey,
|
||||
Cert: parsedCert,
|
||||
Revoked: false,
|
||||
IsCA: true,
|
||||
}, nil
|
||||
}
|
||||
8
internal/pki/errors.go
Normal file
8
internal/pki/errors.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package pki
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrNotACA = errors.New("n'est pas une autorité de certification")
|
||||
ErrMissingPrivateKey = errors.New("clé privée manquante")
|
||||
)
|
||||
Reference in New Issue
Block a user