This commit is contained in:
Pim van Pelt
2025-08-24 11:11:25 +02:00
parent f26322e56b
commit 9b1dd06acf

View File

@@ -30,24 +30,24 @@ type Config struct {
} }
type Log struct { type Log struct {
ShortName string `yaml:"shortname"` ShortName string `yaml:"shortname"`
Inception string `yaml:"inception"` Inception string `yaml:"inception"`
Period int `yaml:"period"` Period int `yaml:"period"`
PoolSize int `yaml:"poolsize"` PoolSize int `yaml:"poolsize"`
SubmissionPrefix string `yaml:"submissionprefix"` SubmissionPrefix string `yaml:"submissionprefix"`
MonitoringPrefix string `yaml:"monitoringprefix"` MonitoringPrefix string `yaml:"monitoringprefix"`
CCadbRoots string `yaml:"ccadbroots"` CCadbRoots string `yaml:"ccadbroots"`
ExtraRoots string `yaml:"extraroots"` ExtraRoots string `yaml:"extraroots"`
Secret string `yaml:"secret"` Secret string `yaml:"secret"`
Cache string `yaml:"cache"` Cache string `yaml:"cache"`
LocalDirectory string `yaml:"localdirectory"` LocalDirectory string `yaml:"localdirectory"`
NotAfterStart time.Time `yaml:"notafterstart"` NotAfterStart time.Time `yaml:"notafterstart"`
NotAfterLimit time.Time `yaml:"notafterlimit"` NotAfterLimit time.Time `yaml:"notafterlimit"`
// Computed fields // Computed fields
LogID string LogID string
PublicKeyPEM string PublicKeyPEM string
PublicKeyDERB64 string PublicKeyDERB64 string
PublicKeyBase64 string PublicKeyBase64 string
} }
const htmlTemplate = `<!DOCTYPE html> const htmlTemplate = `<!DOCTYPE html>
@@ -188,23 +188,23 @@ func showConfig(yamlFile string) {
fmt.Printf("Listen addresses: %v\n", config.Listen) fmt.Printf("Listen addresses: %v\n", config.Listen)
fmt.Printf("Checkpoints: %s\n", config.Checkpoints) fmt.Printf("Checkpoints: %s\n", config.Checkpoints)
fmt.Printf("Number of logs: %d\n", len(config.Logs)) fmt.Printf("Number of logs: %d\n", len(config.Logs))
for i, logEntry := range config.Logs { for i, logEntry := range config.Logs {
fmt.Printf("Log %d: %s (Period: %d, Pool size: %d)\n", fmt.Printf("Log %d: %s (Period: %d, Pool size: %d)\n",
i+1, logEntry.ShortName, logEntry.Period, logEntry.PoolSize) i+1, logEntry.ShortName, logEntry.Period, logEntry.PoolSize)
} }
} }
func generateHTML(yamlFile string) { func generateHTML(yamlFile string) {
config := loadConfig(yamlFile) config := loadConfig(yamlFile)
// Check that all local directories exist // Check that all local directories exist
for _, logEntry := range config.Logs { for _, logEntry := range config.Logs {
if _, err := os.Stat(logEntry.LocalDirectory); os.IsNotExist(err) { if _, err := os.Stat(logEntry.LocalDirectory); os.IsNotExist(err) {
log.Fatalf("User is required to create %s", logEntry.LocalDirectory) log.Fatalf("User is required to create %s", logEntry.LocalDirectory)
} }
} }
// Compute key information for each log // Compute key information for each log
for i := range config.Logs { for i := range config.Logs {
err := computeKeyInfo(&config.Logs[i]) err := computeKeyInfo(&config.Logs[i])
@@ -212,30 +212,30 @@ func generateHTML(yamlFile string) {
log.Fatalf("Failed to compute key info for %s: %v", config.Logs[i].ShortName, err) log.Fatalf("Failed to compute key info for %s: %v", config.Logs[i].ShortName, err)
} }
} }
tmpl, err := template.New("html").Parse(htmlTemplate) tmpl, err := template.New("html").Parse(htmlTemplate)
if err != nil { if err != nil {
log.Fatalf("Failed to parse template: %v", err) log.Fatalf("Failed to parse template: %v", err)
} }
// Write HTML file to each log's local directory // Write HTML file to each log's local directory
for _, logEntry := range config.Logs { for _, logEntry := range config.Logs {
indexPath := fmt.Sprintf("%s/index.html", logEntry.LocalDirectory) indexPath := fmt.Sprintf("%s/index.html", logEntry.LocalDirectory)
file, err := os.Create(indexPath) file, err := os.Create(indexPath)
if err != nil { if err != nil {
log.Fatalf("Failed to create %s: %v", indexPath, err) log.Fatalf("Failed to create %s: %v", indexPath, err)
} }
err = tmpl.Execute(file, config) err = tmpl.Execute(file, config)
if err != nil { if err != nil {
file.Close() file.Close()
log.Fatalf("Failed to write HTML to %s: %v", indexPath, err) log.Fatalf("Failed to write HTML to %s: %v", indexPath, err)
} }
file.Close() file.Close()
fmt.Printf("Generated %s\n", indexPath) fmt.Printf("Generated %s\n", indexPath)
// Generate log.v3.json for this log // Generate log.v3.json for this log
jsonPath := filepath.Join(logEntry.LocalDirectory, "log.v3.json") jsonPath := filepath.Join(logEntry.LocalDirectory, "log.v3.json")
err = generateLogJSON(logEntry, jsonPath) err = generateLogJSON(logEntry, jsonPath)
@@ -252,58 +252,58 @@ func computeKeyInfo(logEntry *Log) error {
if err != nil { if err != nil {
return fmt.Errorf("failed to read key file: %v", err) return fmt.Errorf("failed to read key file: %v", err)
} }
// Parse PEM block // Parse PEM block
block, _ := pem.Decode(keyData) block, _ := pem.Decode(keyData)
if block == nil { if block == nil {
return fmt.Errorf("failed to decode PEM block") return fmt.Errorf("failed to decode PEM block")
} }
// Parse EC private key // Parse EC private key
privKey, err := x509.ParseECPrivateKey(block.Bytes) privKey, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil { if err != nil {
return fmt.Errorf("failed to parse EC private key: %v", err) return fmt.Errorf("failed to parse EC private key: %v", err)
} }
// Extract public key // Extract public key
pubKey := &privKey.PublicKey pubKey := &privKey.PublicKey
// Convert public key to DER format // Convert public key to DER format
pubKeyDER, err := x509.MarshalPKIXPublicKey(pubKey) pubKeyDER, err := x509.MarshalPKIXPublicKey(pubKey)
if err != nil { if err != nil {
return fmt.Errorf("failed to marshal public key: %v", err) return fmt.Errorf("failed to marshal public key: %v", err)
} }
// Create PEM format // Create PEM format
pubKeyPEM := pem.EncodeToMemory(&pem.Block{ pubKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY", Type: "PUBLIC KEY",
Bytes: pubKeyDER, Bytes: pubKeyDER,
}) })
// Compute Log ID (SHA-256 of the DER-encoded public key) // Compute Log ID (SHA-256 of the DER-encoded public key)
logIDBytes := sha256.Sum256(pubKeyDER) logIDBytes := sha256.Sum256(pubKeyDER)
logID := base64.StdEncoding.EncodeToString(logIDBytes[:]) logID := base64.StdEncoding.EncodeToString(logIDBytes[:])
// Base64 encode DER for download link // Base64 encode DER for download link
pubKeyDERB64 := base64.StdEncoding.EncodeToString(pubKeyDER) pubKeyDERB64 := base64.StdEncoding.EncodeToString(pubKeyDER)
// Set computed fields // Set computed fields
logEntry.LogID = logID logEntry.LogID = logID
logEntry.PublicKeyPEM = string(pubKeyPEM) logEntry.PublicKeyPEM = string(pubKeyPEM)
logEntry.PublicKeyDERB64 = pubKeyDERB64 logEntry.PublicKeyDERB64 = pubKeyDERB64
logEntry.PublicKeyBase64 = pubKeyDERB64 // Same as DER base64 for JSON logEntry.PublicKeyBase64 = pubKeyDERB64 // Same as DER base64 for JSON
return nil return nil
} }
type LogV3JSON struct { type LogV3JSON struct {
Description string `json:"description"` Description string `json:"description"`
SubmissionURL string `json:"submission_url"` SubmissionURL string `json:"submission_url"`
MonitoringURL string `json:"monitoring_url"` MonitoringURL string `json:"monitoring_url"`
TemporalInterval TemporalInterval `json:"temporal_interval"` TemporalInterval TemporalInterval `json:"temporal_interval"`
LogID string `json:"log_id"` LogID string `json:"log_id"`
Key string `json:"key"` Key string `json:"key"`
MMD int `json:"mmd"` MMD int `json:"mmd"`
} }
type TemporalInterval struct { type TemporalInterval struct {
@@ -324,34 +324,34 @@ func generateLogJSON(logEntry Log, outputPath string) error {
Key: logEntry.PublicKeyBase64, Key: logEntry.PublicKeyBase64,
MMD: 60, // Default MMD of 60 seconds MMD: 60, // Default MMD of 60 seconds
} }
jsonData, err := json.MarshalIndent(logJSON, "", " ") jsonData, err := json.MarshalIndent(logJSON, "", " ")
if err != nil { if err != nil {
return fmt.Errorf("failed to marshal JSON: %v", err) return fmt.Errorf("failed to marshal JSON: %v", err)
} }
err = os.WriteFile(outputPath, jsonData, 0644) err = os.WriteFile(outputPath, jsonData, 0644)
if err != nil { if err != nil {
return fmt.Errorf("failed to write JSON file: %v", err) return fmt.Errorf("failed to write JSON file: %v", err)
} }
return nil return nil
} }
func generateEnv(yamlFile string) { func generateEnv(yamlFile string) {
config := loadConfig(yamlFile) config := loadConfig(yamlFile)
// Check that all local directories exist // Check that all local directories exist
for _, logEntry := range config.Logs { for _, logEntry := range config.Logs {
if _, err := os.Stat(logEntry.LocalDirectory); os.IsNotExist(err) { if _, err := os.Stat(logEntry.LocalDirectory); os.IsNotExist(err) {
log.Fatalf("User is required to create %s", logEntry.LocalDirectory) log.Fatalf("User is required to create %s", logEntry.LocalDirectory)
} }
} }
// Generate .env file for each log // Generate .env file for each log
for _, logEntry := range config.Logs { for _, logEntry := range config.Logs {
envPath := filepath.Join(logEntry.LocalDirectory, ".env") envPath := filepath.Join(logEntry.LocalDirectory, ".env")
// Create combined roots.pem file // Create combined roots.pem file
rootsPemPath := filepath.Join(logEntry.LocalDirectory, "roots.pem") rootsPemPath := filepath.Join(logEntry.LocalDirectory, "roots.pem")
err := createCombinedRootsPem(config.Roots, logEntry.ExtraRoots, rootsPemPath) err := createCombinedRootsPem(config.Roots, logEntry.ExtraRoots, rootsPemPath)
@@ -359,7 +359,7 @@ func generateEnv(yamlFile string) {
log.Fatalf("Failed to create %s: %v", rootsPemPath, err) log.Fatalf("Failed to create %s: %v", rootsPemPath, err)
} }
fmt.Printf("Generated %s\n", rootsPemPath) fmt.Printf("Generated %s\n", rootsPemPath)
// Build TESSERACT_ARGS string // Build TESSERACT_ARGS string
args := []string{ args := []string{
fmt.Sprintf("--private_key=%s", logEntry.Secret), fmt.Sprintf("--private_key=%s", logEntry.Secret),
@@ -367,15 +367,15 @@ func generateEnv(yamlFile string) {
fmt.Sprintf("--storage_dir=%s", logEntry.LocalDirectory), fmt.Sprintf("--storage_dir=%s", logEntry.LocalDirectory),
fmt.Sprintf("--roots_pem_file=%s", rootsPemPath), fmt.Sprintf("--roots_pem_file=%s", rootsPemPath),
} }
tesseractArgs := strings.Join(args, " ") tesseractArgs := strings.Join(args, " ")
envContent := fmt.Sprintf("TESSERACT_ARGS=\"%s\"\n", tesseractArgs) envContent := fmt.Sprintf("TESSERACT_ARGS=\"%s\"\n", tesseractArgs)
err = os.WriteFile(envPath, []byte(envContent), 0644) err = os.WriteFile(envPath, []byte(envContent), 0644)
if err != nil { if err != nil {
log.Fatalf("Failed to write %s: %v", envPath, err) log.Fatalf("Failed to write %s: %v", envPath, err)
} }
fmt.Printf("Generated %s\n", envPath) fmt.Printf("Generated %s\n", envPath)
} }
} }
@@ -387,7 +387,7 @@ func createCombinedRootsPem(rootsFile, extraRootsFile, outputPath string) error
return fmt.Errorf("failed to create output file: %v", err) return fmt.Errorf("failed to create output file: %v", err)
} }
defer outputFile.Close() defer outputFile.Close()
// Copy main roots file // Copy main roots file
if rootsFile != "" { if rootsFile != "" {
rootsData, err := os.Open(rootsFile) rootsData, err := os.Open(rootsFile)
@@ -395,13 +395,13 @@ func createCombinedRootsPem(rootsFile, extraRootsFile, outputPath string) error
return fmt.Errorf("failed to open roots file %s: %v", rootsFile, err) return fmt.Errorf("failed to open roots file %s: %v", rootsFile, err)
} }
defer rootsData.Close() defer rootsData.Close()
_, err = io.Copy(outputFile, rootsData) _, err = io.Copy(outputFile, rootsData)
if err != nil { if err != nil {
return fmt.Errorf("failed to copy roots file: %v", err) return fmt.Errorf("failed to copy roots file: %v", err)
} }
} }
// Append extra roots file if it exists // Append extra roots file if it exists
if extraRootsFile != "" { if extraRootsFile != "" {
extraRootsData, err := os.Open(extraRootsFile) extraRootsData, err := os.Open(extraRootsFile)
@@ -409,19 +409,19 @@ func createCombinedRootsPem(rootsFile, extraRootsFile, outputPath string) error
return fmt.Errorf("failed to open extra roots file %s: %v", extraRootsFile, err) return fmt.Errorf("failed to open extra roots file %s: %v", extraRootsFile, err)
} }
defer extraRootsData.Close() defer extraRootsData.Close()
_, err = io.Copy(outputFile, extraRootsData) _, err = io.Copy(outputFile, extraRootsData)
if err != nil { if err != nil {
return fmt.Errorf("failed to copy extra roots file: %v", err) return fmt.Errorf("failed to copy extra roots file: %v", err)
} }
} }
return nil return nil
} }
func generateKeys(yamlFile string) { func generateKeys(yamlFile string) {
config := loadConfig(yamlFile) config := loadConfig(yamlFile)
// Generate keys for each log // Generate keys for each log
for _, logEntry := range config.Logs { for _, logEntry := range config.Logs {
// Check if key already exists // Check if key already exists
@@ -429,36 +429,36 @@ func generateKeys(yamlFile string) {
fmt.Printf("Key already exists: %s (skipped)\n", logEntry.Secret) fmt.Printf("Key already exists: %s (skipped)\n", logEntry.Secret)
continue continue
} }
// Generate new prime256v1 key // Generate new prime256v1 key
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil { if err != nil {
log.Fatalf("Failed to generate key for %s: %v", logEntry.ShortName, err) log.Fatalf("Failed to generate key for %s: %v", logEntry.ShortName, err)
} }
// Marshal private key to DER format // Marshal private key to DER format
privKeyDER, err := x509.MarshalECPrivateKey(privKey) privKeyDER, err := x509.MarshalECPrivateKey(privKey)
if err != nil { if err != nil {
log.Fatalf("Failed to marshal private key for %s: %v", logEntry.ShortName, err) log.Fatalf("Failed to marshal private key for %s: %v", logEntry.ShortName, err)
} }
// Create PEM block // Create PEM block
privKeyPEM := pem.EncodeToMemory(&pem.Block{ privKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "EC PRIVATE KEY", Type: "EC PRIVATE KEY",
Bytes: privKeyDER, Bytes: privKeyDER,
}) })
// Ensure directory exists // Ensure directory exists
if err := os.MkdirAll(filepath.Dir(logEntry.Secret), 0755); err != nil { if err := os.MkdirAll(filepath.Dir(logEntry.Secret), 0755); err != nil {
log.Fatalf("Failed to create directory for %s: %v", logEntry.Secret, err) log.Fatalf("Failed to create directory for %s: %v", logEntry.Secret, err)
} }
// Write key to file // Write key to file
err = os.WriteFile(logEntry.Secret, privKeyPEM, 0600) err = os.WriteFile(logEntry.Secret, privKeyPEM, 0600)
if err != nil { if err != nil {
log.Fatalf("Failed to write key file %s: %v", logEntry.Secret, err) log.Fatalf("Failed to write key file %s: %v", logEntry.Secret, err)
} }
fmt.Printf("Generated %s\n", logEntry.Secret) fmt.Printf("Generated %s\n", logEntry.Secret)
} }
} }