From b387a016bedb2f2e1f81b6f4825e514f1d15a42f Mon Sep 17 00:00:00 2001 From: Slawek Koszewski Date: Mon, 28 Jul 2025 17:41:41 +0200 Subject: [PATCH] Added state file location defintion to the CA configuration. Added more certificate properties to certificate database. --- ca.go | 34 +++++++++++++++++++++++++--------- certdb.go | 24 +++++++++++++----------- examples/ca_config.hcl | 11 ++++++----- main.go | 2 +- 4 files changed, 45 insertions(+), 26 deletions(-) diff --git a/ca.go b/ca.go index 8192bb6..d0d0cb8 100644 --- a/ca.go +++ b/ca.go @@ -24,6 +24,7 @@ import ( type Paths struct { Certificates string `hcl:"certificates"` PrivateKeys string `hcl:"private_keys"` + StatePath string `hcl:"state_file"` } type CAConfig struct { @@ -143,15 +144,23 @@ func (c *Certificates) LoadFromFile(path string) error { // Global CA configuration and state variables var caConfigPath string -var caState *CAState var caConfig *CAConfig + +var caStatePath string +var caState *CAState + var caKey *rsa.PrivateKey var caCert *x509.Certificate // LoadCAConfig parses and validates the CA config from the given path and stores it in the CAConfig global variable func LoadCAConfig() error { 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() file, diags := parser.ParseHCLFile(caConfigPath) @@ -172,6 +181,14 @@ func LoadCAConfig() error { if err := config.CA.Validate(); err != nil { 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 return nil } @@ -329,6 +346,7 @@ func InitCA() error { err = LoadCAConfig() if err != nil { + fmt.Printf("ERROR: %v\n", err) return err } @@ -532,12 +550,8 @@ func issueSingleCertificate(def CertificateDefinition) error { certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) - basename := def.Name - if basename == "" { - basename = def.Subject - } - certFile := filepath.Join(caConfig.Paths.Certificates, basename+".crt.pem") - keyFile := filepath.Join(caConfig.Paths.PrivateKeys, basename+".key.pem") + certFile := filepath.Join(caConfig.Paths.Certificates, def.Name+".crt.pem") + keyFile := filepath.Join(caConfig.Paths.PrivateKeys, def.Name+".key.pem") if err := SavePEM(certFile, certPEM, false); err != nil { return fmt.Errorf("error saving certificate: %v", err) } @@ -562,7 +576,9 @@ Certificate: } caState.UpdateCAStateAfterIssue( caConfig.SerialType, - basename, + def.Name, + def.Subject, + def.Type, serialNumber, validityDur, ) diff --git a/certdb.go b/certdb.go index 0d191db..26b2a36 100644 --- a/certdb.go +++ b/certdb.go @@ -10,7 +10,6 @@ import ( "fmt" "math/big" "os" - "path/filepath" "time" ) @@ -26,6 +25,8 @@ type CAState struct { // CertificateRecord represents a single certificate record in the CA state type CertificateRecord struct { Name string `json:"name"` + Subject string `json:"subject"` + Type string `json:"type"` Issued string `json:"issued"` Expires string `json:"expires"` Serial string `json:"serial"` @@ -33,15 +34,14 @@ type CertificateRecord struct { RevokeReason int `json:"revokeReason,omitempty"` } -func caStatePath() string { - return filepath.Join(filepath.Dir(caConfigPath), caConfig.GetStateFileName()) -} +// func caStatePath() string { +// return filepath.Join(filepath.Dir(caConfigPath), caConfig.GetStateFileName()) +// } // LoadCAState loads the CA state from a JSON file func LoadCAState() error { - path := caStatePath() - fmt.Printf("Loading CA state from %s\n", path) - f, err := os.Open(path) + fmt.Printf("Loading CA state from %s\n", caStatePath) + f, err := os.Open(caStatePath) if err != nil { return err } @@ -56,7 +56,7 @@ func LoadCAState() error { // SaveCAState saves the CA state to a JSON file func SaveCAState() error { caState.UpdatedAt = time.Now().UTC().Format(time.RFC3339) - f, err := os.Create(caStatePath()) + f, err := os.Create(caStatePath) if err != nil { return err } @@ -67,7 +67,7 @@ func SaveCAState() error { } // 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 { fmt.Fprintf(os.Stderr, "FATAL: CAState is nil in UpdateCAStateAfterIssue. This indicates a programming error.\n") os.Exit(1) @@ -84,17 +84,19 @@ func (s *CAState) UpdateCAStateAfterIssue(serialType, basename string, serialNum default: serialStr = fmt.Sprintf("%v", serialNumber) } - s.AddCertificate(basename, issued, expires, serialStr) + s.AddCertificate(name, subject, certType, issued, expires, serialStr) 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 { fmt.Fprintf(os.Stderr, "FATAL: CAState is nil in AddCertificate. This indicates a programming error.\n") os.Exit(1) } rec := CertificateRecord{ Name: name, + Subject: subject, + Type: certType, Issued: issued, Expires: expires, Serial: serial, diff --git a/examples/ca_config.hcl b/examples/ca_config.hcl index 1c4a955..2c74428 100644 --- a/examples/ca_config.hcl +++ b/examples/ca_config.hcl @@ -1,13 +1,14 @@ ca "example_ca" { - name = "Example CA" - country = "PL" + name = "Example CA" + country = "PL" organization = "ACME Corp" - serial_type = "random" - key_size = 4096 - validity = "10y" + serial_type = "random" + key_size = 4096 + validity = "10y" paths { certificates = "certs" private_keys = "private" + state_file = "ca_state.json" } } diff --git a/main.go b/main.go index 5d94b2c..66e8ded 100644 --- a/main.go +++ b/main.go @@ -48,7 +48,7 @@ func main() { 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(&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 var initCmd = &cobra.Command{