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 }