kat/internal/pki/ca.go

167 lines
4.5 KiB
Go

package pki
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"os"
"path/filepath"
"time"
)
const (
// Default key size for RSA keys
DefaultRSAKeySize = 2048
// Default CA certificate validity period
DefaultCAValidityDays = 3650 // ~10 years
// Default certificate validity period
DefaultCertValidityDays = 365 // 1 year
// Default PKI directory
DefaultPKIDir = "/var/lib/kat/pki"
)
// GenerateCA creates a new Certificate Authority key pair and certificate.
// It saves the private key and certificate to the specified paths.
func GenerateCA(pkiDir string, keyPath, certPath string) error {
// Create PKI directory if it doesn't exist
if err := os.MkdirAll(pkiDir, 0700); err != nil {
return fmt.Errorf("failed to create PKI directory: %w", err)
}
// Generate RSA key
key, err := rsa.GenerateKey(rand.Reader, DefaultRSAKeySize)
if err != nil {
return fmt.Errorf("failed to generate CA key: %w", err)
}
// Create self-signed certificate
serialNumber, err := generateSerialNumber()
if err != nil {
return fmt.Errorf("failed to generate serial number: %w", err)
}
// Certificate template
notBefore := time.Now()
notAfter := notBefore.Add(time.Duration(DefaultCAValidityDays) * 24 * time.Hour)
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: "KAT Root CA",
Organization: []string{"KAT System"},
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
BasicConstraintsValid: true,
IsCA: true,
MaxPathLen: 1, // Only allow one level of intermediate certs
}
// Create certificate
derBytes, err := x509.CreateCertificate(
rand.Reader,
&template,
&template, // Self-signed
&key.PublicKey,
key,
)
if err != nil {
return fmt.Errorf("failed to create CA certificate: %w", err)
}
// Save private key
keyOut, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return fmt.Errorf("failed to open CA key file for writing: %w", err)
}
defer keyOut.Close()
err = pem.Encode(keyOut, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(key),
})
if err != nil {
return fmt.Errorf("failed to write CA key to file: %w", err)
}
// Save certificate
certOut, err := os.OpenFile(certPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return fmt.Errorf("failed to open CA certificate file for writing: %w", err)
}
defer certOut.Close()
err = pem.Encode(certOut, &pem.Block{
Type: "CERTIFICATE",
Bytes: derBytes,
})
if err != nil {
return fmt.Errorf("failed to write CA certificate to file: %w", err)
}
return nil
}
// GetPKIPathFromClusterConfig determines the PKI directory from the cluster configuration.
// If backupPath is provided, it uses the parent directory of backupPath.
// Otherwise, it uses the default PKI directory.
func GetPKIPathFromClusterConfig(backupPath string) string {
if backupPath == "" {
return DefaultPKIDir
}
// Use the parent directory of backupPath
return filepath.Dir(backupPath) + "/pki"
}
// generateSerialNumber creates a random serial number for certificates
func generateSerialNumber() (*big.Int, error) {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) // 128 bits
return rand.Int(rand.Reader, serialNumberLimit)
}
// LoadCACertificate loads a CA certificate from a file
func LoadCACertificate(certPath string) (*x509.Certificate, error) {
certPEM, err := os.ReadFile(certPath)
if err != nil {
return nil, fmt.Errorf("failed to read CA certificate file: %w", err)
}
block, _ := pem.Decode(certPEM)
if block == nil || block.Type != "CERTIFICATE" {
return nil, fmt.Errorf("failed to decode PEM block containing certificate")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse CA certificate: %w", err)
}
return cert, nil
}
// LoadCAPrivateKey loads a CA private key from a file
func LoadCAPrivateKey(keyPath string) (*rsa.PrivateKey, error) {
keyPEM, err := os.ReadFile(keyPath)
if err != nil {
return nil, fmt.Errorf("failed to read CA key file: %w", err)
}
block, _ := pem.Decode(keyPEM)
if block == nil || block.Type != "RSA PRIVATE KEY" {
return nil, fmt.Errorf("failed to decode PEM block containing private key")
}
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse CA private key: %w", err)
}
return key, nil
}