Added state file location defintion to the CA configuration. Added more certificate properties to certificate database.

This commit is contained in:
2025-07-28 17:41:41 +02:00
parent e4469fde96
commit b387a016be
4 changed files with 45 additions and 26 deletions

34
ca.go
View File

@@ -24,6 +24,7 @@ import (
type Paths struct { type Paths struct {
Certificates string `hcl:"certificates"` Certificates string `hcl:"certificates"`
PrivateKeys string `hcl:"private_keys"` PrivateKeys string `hcl:"private_keys"`
StatePath string `hcl:"state_file"`
} }
type CAConfig struct { type CAConfig struct {
@@ -143,15 +144,23 @@ func (c *Certificates) LoadFromFile(path string) error {
// Global CA configuration and state variables // Global CA configuration and state variables
var caConfigPath string var caConfigPath string
var caState *CAState
var caConfig *CAConfig var caConfig *CAConfig
var caStatePath string
var caState *CAState
var caKey *rsa.PrivateKey var caKey *rsa.PrivateKey
var caCert *x509.Certificate var caCert *x509.Certificate
// LoadCAConfig parses and validates the CA config from the given path and stores it in the CAConfig global variable // LoadCAConfig parses and validates the CA config from the given path and stores it in the CAConfig global variable
func LoadCAConfig() error { func LoadCAConfig() error {
if verbose { if verbose {
fmt.Printf("Loading CA config from \"%s\"", caConfigPath) cwd, err := os.Getwd()
if err != nil {
return err
}
fmt.Printf("The current working dirctory: \"%s\"\n", cwd)
fmt.Printf("Loading CA config from \"%s\"... ", caConfigPath)
} }
parser := hclparse.NewParser() parser := hclparse.NewParser()
file, diags := parser.ParseHCLFile(caConfigPath) file, diags := parser.ParseHCLFile(caConfigPath)
@@ -172,6 +181,14 @@ func LoadCAConfig() error {
if err := config.CA.Validate(); err != nil { if err := config.CA.Validate(); err != nil {
return err return err
} }
// If the state file is specified as an absolute path, use it directly.
if filepath.IsAbs(config.CA.Paths.StatePath) {
caStatePath = config.CA.Paths.StatePath
} else {
caStatePath = filepath.Join(filepath.Dir(caConfigPath), config.CA.Paths.StatePath)
}
caConfig = &config.CA caConfig = &config.CA
return nil return nil
} }
@@ -329,6 +346,7 @@ func InitCA() error {
err = LoadCAConfig() err = LoadCAConfig()
if err != nil { if err != nil {
fmt.Printf("ERROR: %v\n", err)
return err return err
} }
@@ -532,12 +550,8 @@ func issueSingleCertificate(def CertificateDefinition) error {
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
basename := def.Name certFile := filepath.Join(caConfig.Paths.Certificates, def.Name+".crt.pem")
if basename == "" { keyFile := filepath.Join(caConfig.Paths.PrivateKeys, def.Name+".key.pem")
basename = def.Subject
}
certFile := filepath.Join(caConfig.Paths.Certificates, basename+".crt.pem")
keyFile := filepath.Join(caConfig.Paths.PrivateKeys, basename+".key.pem")
if err := SavePEM(certFile, certPEM, false); err != nil { if err := SavePEM(certFile, certPEM, false); err != nil {
return fmt.Errorf("error saving certificate: %v", err) return fmt.Errorf("error saving certificate: %v", err)
} }
@@ -562,7 +576,9 @@ Certificate:
} }
caState.UpdateCAStateAfterIssue( caState.UpdateCAStateAfterIssue(
caConfig.SerialType, caConfig.SerialType,
basename, def.Name,
def.Subject,
def.Type,
serialNumber, serialNumber,
validityDur, validityDur,
) )

View File

@@ -10,7 +10,6 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"os" "os"
"path/filepath"
"time" "time"
) )
@@ -26,6 +25,8 @@ type CAState struct {
// CertificateRecord represents a single certificate record in the CA state // CertificateRecord represents a single certificate record in the CA state
type CertificateRecord struct { type CertificateRecord struct {
Name string `json:"name"` Name string `json:"name"`
Subject string `json:"subject"`
Type string `json:"type"`
Issued string `json:"issued"` Issued string `json:"issued"`
Expires string `json:"expires"` Expires string `json:"expires"`
Serial string `json:"serial"` Serial string `json:"serial"`
@@ -33,15 +34,14 @@ type CertificateRecord struct {
RevokeReason int `json:"revokeReason,omitempty"` RevokeReason int `json:"revokeReason,omitempty"`
} }
func caStatePath() string { // func caStatePath() string {
return filepath.Join(filepath.Dir(caConfigPath), caConfig.GetStateFileName()) // return filepath.Join(filepath.Dir(caConfigPath), caConfig.GetStateFileName())
} // }
// LoadCAState loads the CA state from a JSON file // LoadCAState loads the CA state from a JSON file
func LoadCAState() error { func LoadCAState() error {
path := caStatePath() fmt.Printf("Loading CA state from %s\n", caStatePath)
fmt.Printf("Loading CA state from %s\n", path) f, err := os.Open(caStatePath)
f, err := os.Open(path)
if err != nil { if err != nil {
return err return err
} }
@@ -56,7 +56,7 @@ func LoadCAState() error {
// SaveCAState saves the CA state to a JSON file // SaveCAState saves the CA state to a JSON file
func SaveCAState() error { func SaveCAState() error {
caState.UpdatedAt = time.Now().UTC().Format(time.RFC3339) caState.UpdatedAt = time.Now().UTC().Format(time.RFC3339)
f, err := os.Create(caStatePath()) f, err := os.Create(caStatePath)
if err != nil { if err != nil {
return err return err
} }
@@ -67,7 +67,7 @@ func SaveCAState() error {
} }
// UpdateCAStateAfterIssue updates the CA state JSON after issuing a certificate // UpdateCAStateAfterIssue updates the CA state JSON after issuing a certificate
func (s *CAState) UpdateCAStateAfterIssue(serialType, basename string, serialNumber any, validity time.Duration) error { func (s *CAState) UpdateCAStateAfterIssue(serialType, name string, subject string, certType string, serialNumber any, validity time.Duration) error {
if s == nil { if s == nil {
fmt.Fprintf(os.Stderr, "FATAL: CAState is nil in UpdateCAStateAfterIssue. This indicates a programming error.\n") fmt.Fprintf(os.Stderr, "FATAL: CAState is nil in UpdateCAStateAfterIssue. This indicates a programming error.\n")
os.Exit(1) os.Exit(1)
@@ -84,17 +84,19 @@ func (s *CAState) UpdateCAStateAfterIssue(serialType, basename string, serialNum
default: default:
serialStr = fmt.Sprintf("%v", serialNumber) serialStr = fmt.Sprintf("%v", serialNumber)
} }
s.AddCertificate(basename, issued, expires, serialStr) s.AddCertificate(name, subject, certType, issued, expires, serialStr)
return nil return nil
} }
func (s *CAState) AddCertificate(name, issued, expires, serial string) { func (s *CAState) AddCertificate(name, subject, certType, issued, expires, serial string) {
if s == nil { if s == nil {
fmt.Fprintf(os.Stderr, "FATAL: CAState is nil in AddCertificate. This indicates a programming error.\n") fmt.Fprintf(os.Stderr, "FATAL: CAState is nil in AddCertificate. This indicates a programming error.\n")
os.Exit(1) os.Exit(1)
} }
rec := CertificateRecord{ rec := CertificateRecord{
Name: name, Name: name,
Subject: subject,
Type: certType,
Issued: issued, Issued: issued,
Expires: expires, Expires: expires,
Serial: serial, Serial: serial,

View File

@@ -9,5 +9,6 @@ ca "example_ca" {
paths { paths {
certificates = "certs" certificates = "certs"
private_keys = "private" private_keys = "private"
state_file = "ca_state.json"
} }
} }

View File

@@ -48,7 +48,7 @@ func main() {
rootCmd.PersistentFlags().BoolVar(&overwrite, "overwrite", false, "Allow overwriting existing files") rootCmd.PersistentFlags().BoolVar(&overwrite, "overwrite", false, "Allow overwriting existing files")
rootCmd.PersistentFlags().BoolVar(&verbose, "verbose", false, "Print detailed information about each processed certificate") rootCmd.PersistentFlags().BoolVar(&verbose, "verbose", false, "Print detailed information about each processed certificate")
rootCmd.PersistentFlags().BoolVar(&dryRun, "dry-run", false, "Validate and show what would be created, but do not write files (batch mode)") rootCmd.PersistentFlags().BoolVar(&dryRun, "dry-run", false, "Validate and show what would be created, but do not write files (batch mode)")
rootCmd.PersistentFlags().StringVar(&caConfigPath, "config-path", "ca_config.hcl", "Path to CA configuration file") rootCmd.PersistentFlags().StringVar(&caConfigPath, "config", "ca_config.hcl", "Path to CA configuration file")
// lab-ca initca command // lab-ca initca command
var initCmd = &cobra.Command{ var initCmd = &cobra.Command{