Partial implementation of CA database.

This commit is contained in:
2025-07-27 19:48:38 +02:00
parent 6e427acb18
commit 0c32da1e84
4 changed files with 249 additions and 100 deletions

98
certdb.go Normal file
View File

@@ -0,0 +1,98 @@
// 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)
}