diff --git a/ca.go b/ca.go index f8a9c79..8192bb6 100644 --- a/ca.go +++ b/ca.go @@ -26,7 +26,7 @@ type Paths struct { PrivateKeys string `hcl:"private_keys"` } -type _CAConfig struct { +type CAConfig struct { Label string `hcl:",label"` Name string `hcl:"name"` Country string `hcl:"country"` @@ -41,12 +41,12 @@ type _CAConfig struct { Paths Paths `hcl:"paths,block"` } -func (c *_CAConfig) GetStateFileName() string { +func (c *CAConfig) GetStateFileName() string { return c.Label + "_state.json" } type Configuration struct { - Current _CAConfig `hcl:"ca,block"` + CA CAConfig `hcl:"ca,block"` } type CertificateDefinition struct { @@ -142,19 +142,19 @@ func (c *Certificates) LoadFromFile(path string) error { } // Global CA configuration and state variables -var CAConfigPath string -var CAState *_CAState -var CAConfig *_CAConfig -var CAKey *rsa.PrivateKey -var CACert *x509.Certificate +var caConfigPath string +var caState *CAState +var caConfig *CAConfig +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) + fmt.Printf("Loading CA config from \"%s\"", caConfigPath) } parser := hclparse.NewParser() - file, diags := parser.ParseHCLFile(CAConfigPath) + file, diags := parser.ParseHCLFile(caConfigPath) if diags.HasErrors() { return fmt.Errorf("failed to parse HCL: %s", diags.Error()) } @@ -163,16 +163,16 @@ func LoadCAConfig() error { if diags.HasErrors() { return fmt.Errorf("failed to decode HCL: %s", diags.Error()) } - if (_CAConfig{}) == config.Current { + if (CAConfig{}) == config.CA { return fmt.Errorf("no 'ca' block found in config file") } - if config.Current.Label == "" { + if config.CA.Label == "" { return fmt.Errorf("the 'ca' block must have a label (e.g., ca \"mylabel\" {...})") } - if err := config.Current.Validate(); err != nil { + if err := config.CA.Validate(); err != nil { return err } - CAConfig = &config.Current + caConfig = &config.CA return nil } @@ -186,8 +186,8 @@ func LoadCA() error { } // Load CA key and certificate - caCertPath := filepath.Join(CAConfig.Paths.Certificates, "ca_cert.pem") - caKeyPath := filepath.Join(CAConfig.Paths.PrivateKeys, "ca_key.pem") + caCertPath := filepath.Join(caConfig.Paths.Certificates, "ca_cert.pem") + caKeyPath := filepath.Join(caConfig.Paths.PrivateKeys, "ca_key.pem") caCertPEM, err := os.ReadFile(caCertPath) if err != nil { @@ -202,7 +202,7 @@ func LoadCA() error { if caCertBlock == nil { return fmt.Errorf("failed to parse CA certificate PEM") } - CACert, err = x509.ParseCertificate(caCertBlock.Bytes) + caCert, err = x509.ParseCertificate(caCertBlock.Bytes) if err != nil { return fmt.Errorf("failed to parse CA certificate: %v", err) } @@ -210,7 +210,7 @@ func LoadCA() error { if caKeyBlock == nil { return fmt.Errorf("failed to parse CA key PEM") } - CAKey, err = x509.ParsePKCS1PrivateKey(caKeyBlock.Bytes) + caKey, err = x509.ParsePKCS1PrivateKey(caKeyBlock.Bytes) if err != nil { return fmt.Errorf("failed to parse CA private key: %v", err) } @@ -299,7 +299,7 @@ func (p *Paths) Validate() error { return nil } -func (c *_CAConfig) Validate() error { +func (c *CAConfig) Validate() error { if c.Name == "" { return fmt.Errorf("CA 'name' is required") } @@ -333,28 +333,28 @@ func InitCA() error { } // Create certificates directory with 0755, private keys with 0700 - if CAConfig.Paths.Certificates != "" { - if err := os.MkdirAll(CAConfig.Paths.Certificates, 0755); err != nil { - fmt.Printf("Error creating certificates directory '%s': %v\n", CAConfig.Paths.Certificates, err) + if caConfig.Paths.Certificates != "" { + if err := os.MkdirAll(caConfig.Paths.Certificates, 0755); err != nil { + fmt.Printf("Error creating certificates directory '%s': %v\n", caConfig.Paths.Certificates, err) return err } } - if CAConfig.Paths.PrivateKeys != "" { - if err := os.MkdirAll(CAConfig.Paths.PrivateKeys, 0700); err != nil { - fmt.Printf("Error creating private keys directory '%s': %v\n", CAConfig.Paths.PrivateKeys, err) + if caConfig.Paths.PrivateKeys != "" { + if err := os.MkdirAll(caConfig.Paths.PrivateKeys, 0700); err != nil { + fmt.Printf("Error creating private keys directory '%s': %v\n", caConfig.Paths.PrivateKeys, err) return err } } // Initialize CAState empty state with serial starting from 1 - CAState = &_CAState{ + caState = &CAState{ Serial: 1, // Start serial from 1 CreatedAt: time.Now().UTC().Format(time.RFC3339), UpdatedAt: time.Now().UTC().Format(time.RFC3339), Certificates: []CertificateRecord{}, } - keySize := CAConfig.KeySize + keySize := caConfig.KeySize if keySize == 0 { keySize = 4096 } @@ -368,28 +368,28 @@ func InitCA() error { return fmt.Errorf("failed to generate serial number: %v", err) } - if CAConfig.Validity == "" { - CAConfig.Validity = "5y" // Use default validity of 5 years + if caConfig.Validity == "" { + caConfig.Validity = "5y" // Use default validity of 5 years } - validity, err := parseValidity(CAConfig.Validity) + validity, err := parseValidity(caConfig.Validity) if err != nil { return err } now := time.Now() // Store CA certificate creation time - CAState.CreatedAt = now.UTC().Format(time.RFC3339) + caState.CreatedAt = now.UTC().Format(time.RFC3339) tmpl := x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ - Country: []string{CAConfig.Country}, - Organization: []string{CAConfig.Organization}, - OrganizationalUnit: optionalSlice(CAConfig.OrganizationalUnit), - Locality: optionalSlice(CAConfig.Locality), - Province: optionalSlice(CAConfig.Province), - CommonName: CAConfig.Name, + Country: []string{caConfig.Country}, + Organization: []string{caConfig.Organization}, + OrganizationalUnit: optionalSlice(caConfig.OrganizationalUnit), + Locality: optionalSlice(caConfig.Locality), + Province: optionalSlice(caConfig.Province), + CommonName: caConfig.Name, }, NotBefore: now, NotAfter: now.Add(validity), @@ -398,10 +398,10 @@ func InitCA() error { IsCA: true, } // Add email if present - if CAConfig.Email != "" { + if caConfig.Email != "" { tmpl.Subject.ExtraNames = append(tmpl.Subject.ExtraNames, pkix.AttributeTypeAndValue{ Type: []int{1, 2, 840, 113549, 1, 9, 1}, // emailAddress OID - Value: CAConfig.Email, + Value: caConfig.Email, }) } certDER, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, &priv.PublicKey, priv) @@ -411,17 +411,17 @@ func InitCA() error { certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) - if err := SavePEM(filepath.Join(CAConfig.Paths.Certificates, "ca_cert.pem"), certPEM, false); err != nil { + if err := SavePEM(filepath.Join(caConfig.Paths.Certificates, "ca_cert.pem"), certPEM, false); err != nil { fmt.Println("Error saving CA certificate:", err) return err } - if err := SavePEM(filepath.Join(CAConfig.Paths.PrivateKeys, "ca_key.pem"), keyPEM, true); err != nil { + if err := SavePEM(filepath.Join(caConfig.Paths.PrivateKeys, "ca_key.pem"), keyPEM, true); err != nil { fmt.Println("Error saving CA key:", err) return err } // set last updated time in the CAState - CAState.UpdatedAt = time.Now().UTC().Format(time.RFC3339) + caState.UpdatedAt = time.Now().UTC().Format(time.RFC3339) // Save the state err = SaveCAState() @@ -525,7 +525,7 @@ func issueSingleCertificate(def CertificateDefinition) error { return fmt.Errorf("unknown certificate type. Use one of: client, server, server-only, code-signing, email") } - certDER, err := x509.CreateCertificate(rand.Reader, &certTmpl, CACert, &priv.PublicKey, CAKey) + certDER, err := x509.CreateCertificate(rand.Reader, &certTmpl, caCert, &priv.PublicKey, caKey) if err != nil { return fmt.Errorf("failed to create certificate: %v", err) } @@ -536,8 +536,8 @@ func issueSingleCertificate(def CertificateDefinition) error { 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, basename+".crt.pem") + keyFile := filepath.Join(caConfig.Paths.PrivateKeys, basename+".key.pem") if err := SavePEM(certFile, certPEM, false); err != nil { return fmt.Errorf("error saving certificate: %v", err) } @@ -560,8 +560,8 @@ Certificate: def.SAN, ) } - CAState.UpdateCAStateAfterIssue( - CAConfig.SerialType, + caState.UpdateCAStateAfterIssue( + caConfig.SerialType, basename, serialNumber, validityDur, diff --git a/certdb.go b/certdb.go index ef0ee24..0d191db 100644 --- a/certdb.go +++ b/certdb.go @@ -14,8 +14,8 @@ import ( "time" ) -// _CAState represents the persisted CA state in JSON -type _CAState struct { +// CAState represents the persisted CA state in JSON +type CAState struct { CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` Serial int `json:"serial,omitempty"` @@ -34,7 +34,7 @@ type CertificateRecord struct { } 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 @@ -46,8 +46,8 @@ func LoadCAState() error { return err } defer f.Close() - CAState = &_CAState{} - if err := json.NewDecoder(f).Decode(CAState); err != nil { + caState = &CAState{} + if err := json.NewDecoder(f).Decode(caState); err != nil { return err } return nil @@ -55,7 +55,7 @@ func LoadCAState() error { // SaveCAState saves the CA state to a JSON file func SaveCAState() error { - CAState.UpdatedAt = time.Now().UTC().Format(time.RFC3339) + caState.UpdatedAt = time.Now().UTC().Format(time.RFC3339) f, err := os.Create(caStatePath()) if err != nil { return err @@ -63,11 +63,11 @@ func SaveCAState() error { defer f.Close() enc := json.NewEncoder(f) enc.SetIndent("", " ") - return enc.Encode(CAState) + return enc.Encode(caState) } // 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, basename 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) @@ -77,8 +77,8 @@ func (s *_CAState) UpdateCAStateAfterIssue(serialType, basename string, serialNu serialStr := "" switch serialType { case "sequential": - serialStr = fmt.Sprintf("%d", CAState.Serial) - CAState.Serial++ + serialStr = fmt.Sprintf("%d", caState.Serial) + caState.Serial++ case "random": serialStr = fmt.Sprintf("%x", serialNumber) default: @@ -88,7 +88,7 @@ func (s *_CAState) UpdateCAStateAfterIssue(serialType, basename string, serialNu return nil } -func (s *_CAState) AddCertificate(name, issued, expires, serial string) { +func (s *CAState) AddCertificate(name, 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) @@ -103,7 +103,7 @@ func (s *_CAState) AddCertificate(name, issued, expires, serial string) { } // RevokeCertificate revokes a certificate by serial number and reason code, updates state, and saves to disk -func (s *_CAState) RevokeCertificate(serial string, reason int) error { +func (s *CAState) RevokeCertificate(serial string, reason int) error { if s == nil { fmt.Fprintf(os.Stderr, "FATAL: CAState is nil in RevokeCertificate. This indicates a programming error.\n") os.Exit(1) @@ -129,11 +129,11 @@ func (s *_CAState) RevokeCertificate(serial string, reason int) error { // GenerateCRL generates a CRL file from revoked certificates and writes it to the given path // validityDays defines the number of days for which the CRL is valid (NextUpdate - ThisUpdate) -func (s *_CAState) GenerateCRL(crlPath string, validityDays int) error { +func (s *CAState) GenerateCRL(crlPath string, validityDays int) error { if s == nil { return fmt.Errorf("CAState is nil in GenerateCRL") } - if CACert == nil || CAKey == nil { + if caCert == nil || caKey == nil { return fmt.Errorf("CA certificate or key not loaded") } var revokedCerts []pkix.RevokedCertificate @@ -162,14 +162,14 @@ func (s *_CAState) GenerateCRL(crlPath string, validityDays int) error { now := time.Now().UTC() nextUpdate := now.Add(time.Duration(validityDays) * 24 * time.Hour) // validityDays * 24 * 60 * 60 * 1000 milliseconds template := &x509.RevocationList{ - SignatureAlgorithm: CACert.SignatureAlgorithm, + SignatureAlgorithm: caCert.SignatureAlgorithm, RevokedCertificates: revokedCerts, Number: big.NewInt(int64(s.CRLNumber + 1)), ThisUpdate: now, NextUpdate: nextUpdate, - Issuer: CACert.Subject, + Issuer: caCert.Subject, } - crlBytes, err := x509.CreateRevocationList(nil, template, CACert, CAKey) + crlBytes, err := x509.CreateRevocationList(nil, template, caCert, caKey) if err != nil { return fmt.Errorf("failed to create CRL: %v", err) } diff --git a/main.go b/main.go index 090fc37..5d94b2c 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-path", "ca_config.hcl", "Path to CA configuration file") // lab-ca initca command var initCmd = &cobra.Command{ @@ -124,7 +124,7 @@ func main() { serial := "" if revokeName != "" { found := false - for _, rec := range CAState.Certificates { + for _, rec := range caState.Certificates { if rec.Name == revokeName { serial = rec.Serial found = true @@ -157,7 +157,7 @@ func main() { fmt.Println() os.Exit(1) } - if err := CAState.RevokeCertificate(serial, reasonCode); err != nil { + if err := caState.RevokeCertificate(serial, reasonCode); err != nil { fmt.Printf("ERROR: %v\n", err) os.Exit(1) } @@ -181,7 +181,7 @@ func main() { if crlValidityDays <= 0 { crlValidityDays = 30 // default to 30 days } - err := CAState.GenerateCRL(crlFile, crlValidityDays) + err := caState.GenerateCRL(crlFile, crlValidityDays) if err != nil { fmt.Printf("ERROR generating CRL: %v\n", err) os.Exit(1)