kat/internal/pki/certs.go

220 lines
5.8 KiB
Go

package pki
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"net"
"os"
"time"
)
// GenerateCertificateRequest generates a new RSA key pair and a Certificate Signing Request (CSR).
// It saves the private key and CSR to the specified paths.
func GenerateCertificateRequest(commonName string, keyOutPath, csrOutPath string) error {
// Generate RSA key
key, err := rsa.GenerateKey(rand.Reader, DefaultRSAKeySize)
if err != nil {
return fmt.Errorf("failed to generate key: %w", err)
}
// Create CSR template
template := x509.CertificateRequest{
Subject: pkix.Name{
CommonName: commonName,
Organization: []string{"KAT System"},
},
DNSNames: []string{commonName},
}
// Add IP addresses if commonName is an IP
if ip := net.ParseIP(commonName); ip != nil {
template.IPAddresses = []net.IP{ip}
}
// Create CSR
csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &template, key)
if err != nil {
return fmt.Errorf("failed to create CSR: %w", err)
}
// Save private key
keyOut, err := os.OpenFile(keyOutPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return fmt.Errorf("failed to open 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 key to file: %w", err)
}
// Save CSR
csrOut, err := os.OpenFile(csrOutPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return fmt.Errorf("failed to open CSR file for writing: %w", err)
}
defer csrOut.Close()
err = pem.Encode(csrOut, &pem.Block{
Type: "CERTIFICATE REQUEST",
Bytes: csrBytes,
})
if err != nil {
return fmt.Errorf("failed to write CSR to file: %w", err)
}
return nil
}
// SignCertificateRequest signs a CSR using the CA key and certificate.
// It loads the CA key and certificate from the specified paths,
// parses the CSR data, and issues a signed certificate.
func SignCertificateRequest(caKeyPath, caCertPath string, csrData []byte, certOutPath string, durationDays int) error {
// Load CA private key
caKey, err := LoadCAPrivateKey(caKeyPath)
if err != nil {
return err
}
// Load CA certificate
caCert, err := LoadCACertificate(caCertPath)
if err != nil {
return err
}
// Parse CSR
block, _ := pem.Decode(csrData)
if block == nil || block.Type != "CERTIFICATE REQUEST" {
return fmt.Errorf("failed to decode PEM block containing CSR")
}
csr, err := x509.ParseCertificateRequest(block.Bytes)
if err != nil {
return fmt.Errorf("failed to parse CSR: %w", err)
}
// Verify CSR signature
if err := csr.CheckSignature(); err != nil {
return fmt.Errorf("CSR signature verification failed: %w", err)
}
// Generate serial number
serialNumber, err := generateSerialNumber()
if err != nil {
return fmt.Errorf("failed to generate serial number: %w", err)
}
// Set certificate validity period
if durationDays <= 0 {
durationDays = DefaultCertValidityDays
}
notBefore := time.Now()
notAfter := notBefore.Add(time.Duration(durationDays) * 24 * time.Hour)
// Create certificate template
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: csr.Subject,
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
BasicConstraintsValid: true,
IsCA: false,
DNSNames: csr.DNSNames,
IPAddresses: csr.IPAddresses,
}
// Create certificate
derBytes, err := x509.CreateCertificate(
rand.Reader,
&template,
caCert,
csr.PublicKey,
caKey,
)
if err != nil {
return fmt.Errorf("failed to create certificate: %w", err)
}
// Save certificate
certOut, err := os.OpenFile(certOutPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return fmt.Errorf("failed to open 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 certificate to file: %w", err)
}
return nil
}
// ParseCSRFromBytes parses a PEM-encoded CSR from bytes
func ParseCSRFromBytes(csrData []byte) (*x509.CertificateRequest, error) {
block, _ := pem.Decode(csrData)
if block == nil || block.Type != "CERTIFICATE REQUEST" {
return nil, fmt.Errorf("failed to decode PEM block containing CSR")
}
csr, err := x509.ParseCertificateRequest(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse CSR: %w", err)
}
return csr, nil
}
// LoadCertificate loads an X.509 certificate from a file
func LoadCertificate(certPath string) (*x509.Certificate, error) {
certPEM, err := os.ReadFile(certPath)
if err != nil {
return nil, fmt.Errorf("failed to read 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 certificate: %w", err)
}
return cert, nil
}
// LoadPrivateKey loads an RSA private key from a file
func LoadPrivateKey(keyPath string) (*rsa.PrivateKey, error) {
keyPEM, err := os.ReadFile(keyPath)
if err != nil {
return nil, fmt.Errorf("failed to read 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 private key: %w", err)
}
return key, nil
}