// A certificate database management functions package main import ( "encoding/json" "fmt" "os" "time" ) // CAState represents the persisted CA state in JSON // (matches the structure of example_ca.json) type CAState struct { CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` Serial int `json:"serial,omitempty"` Certificates []CertificateRecord `json:"certificates"` } type CertificateRecord struct { Name string `json:"name"` Issued string `json:"issued"` Expires string `json:"expires"` Serial string `json:"serial"` Valid bool `json:"valid"` } // LoadCAState loads the CA state from a JSON file func LoadCAState(filename string) (*CAState, error) { f, err := os.Open(filename) if err != nil { return nil, err } defer f.Close() var state CAState if err := json.NewDecoder(f).Decode(&state); err != nil { return nil, err } return &state, nil } // SaveCAState saves the CA state to a JSON file func SaveCAState(filename string, state *CAState) error { state.UpdatedAt = time.Now().UTC().Format(time.RFC3339) f, err := os.Create(filename) if err != nil { return err } defer f.Close() enc := json.NewEncoder(f) enc.SetIndent("", " ") return enc.Encode(state) } // UpdateCAStateAfterIssue updates the CA state JSON after issuing a certificate func UpdateCAStateAfterIssue(jsonFile, serialType, basename string, serialNumber any, validity time.Duration) error { var err error if GlobalCAState == nil { GlobalCAState, err = LoadCAState(jsonFile) if err != nil { GlobalCAState = nil } } if GlobalCAState == nil { fmt.Fprintf(os.Stderr, "FATAL: GlobalCAState is nil in UpdateCAStateAfterIssue. This indicates a programming error.\n") os.Exit(1) } issued := time.Now().UTC().Format(time.RFC3339) expires := time.Now().Add(validity).UTC().Format(time.RFC3339) serialStr := "" switch serialType { case "sequential": serialStr = fmt.Sprintf("%d", GlobalCAState.Serial) GlobalCAState.Serial++ case "random": serialStr = fmt.Sprintf("%x", serialNumber) default: serialStr = fmt.Sprintf("%v", serialNumber) } AddCertificate(basename, issued, expires, serialStr, true) return nil } // AddCertificate appends a new CertificateRecord to the GlobalCAState func AddCertificate(name, issued, expires, serial string, valid bool) { if GlobalCAState == nil { fmt.Fprintf(os.Stderr, "FATAL: GlobalCAState is nil in AddCertificate. This indicates a programming error.\n") os.Exit(1) } rec := CertificateRecord{ Name: name, Issued: issued, Expires: expires, Serial: serial, Valid: valid, } GlobalCAState.Certificates = append(GlobalCAState.Certificates, rec) }