package main import ( "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/json" "encoding/pem" "fmt" "log" "os" "path/filepath" "text/template" ) const htmlTemplate = ` TesseraCT

A TesseraCT logo, a stylized four-dimensional hypercube with on the left side penstrokes of orange with a padlock, and on the right penstrokes of dark blue with a magnifying glass.

This is a TesseraCT Certificate Transparency log instance.


The following logs are active. {{range .Logs}}

{{.ShortName}}.log.ct.ipng.ch

Log ID: {{.LogID}}
Monitoring prefix: {{.MonitoringPrefix}}/
Submission prefix: {{.SubmissionPrefix}}/
Interval: {{.NotAfterStart.Format "2006-01-02T15:04:05Z"}} – {{.NotAfterLimit.Format "2006-01-02T15:04:05Z"}}
Links: checkpoint key get-roots json
Ratelimit: {{.PoolSize}} req/s

{{.PublicKeyPEM}}
{{end}} ` type LogV3JSON struct { Description string `json:"description"` SubmissionURL string `json:"submission_url"` MonitoringURL string `json:"monitoring_url"` TemporalInterval TemporalInterval `json:"temporal_interval"` LogID string `json:"log_id"` Key string `json:"key"` MMD int `json:"mmd"` } type TemporalInterval struct { StartInclusive string `json:"start_inclusive"` EndExclusive string `json:"end_exclusive"` } func generateHTML(yamlFile string) { config := loadConfig(yamlFile) // Check that all local directories exist for _, logEntry := range config.Logs { if _, err := os.Stat(logEntry.LocalDirectory); os.IsNotExist(err) { log.Fatalf("User is required to create %s", logEntry.LocalDirectory) } } // Compute key information for each log for i := range config.Logs { err := computeKeyInfo(&config.Logs[i]) if err != nil { log.Fatalf("Failed to compute key info for %s: %v", config.Logs[i].ShortName, err) } } tmpl, err := template.New("html").Parse(htmlTemplate) if err != nil { log.Fatalf("Failed to parse template: %v", err) } // Write HTML file to each log's local directory for _, logEntry := range config.Logs { indexPath := fmt.Sprintf("%s/index.html", logEntry.LocalDirectory) file, err := os.Create(indexPath) if err != nil { log.Fatalf("Failed to create %s: %v", indexPath, err) } err = tmpl.Execute(file, config) if err != nil { file.Close() log.Fatalf("Failed to write HTML to %s: %v", indexPath, err) } file.Close() fmt.Printf("Generated %s\n", indexPath) // Generate log.v3.json for this log jsonPath := filepath.Join(logEntry.LocalDirectory, "log.v3.json") err = generateLogJSON(logEntry, jsonPath) if err != nil { log.Fatalf("Failed to generate %s: %v", jsonPath, err) } fmt.Printf("Generated %s\n", jsonPath) } } func computeKeyInfo(logEntry *Log) error { // Read the private key file keyData, err := os.ReadFile(logEntry.Secret) if err != nil { return fmt.Errorf("failed to read key file: %v", err) } // Parse PEM block block, _ := pem.Decode(keyData) if block == nil { return fmt.Errorf("failed to decode PEM block") } // Parse EC private key privKey, err := x509.ParseECPrivateKey(block.Bytes) if err != nil { return fmt.Errorf("failed to parse EC private key: %v", err) } // Extract public key pubKey := &privKey.PublicKey // Convert public key to DER format pubKeyDER, err := x509.MarshalPKIXPublicKey(pubKey) if err != nil { return fmt.Errorf("failed to marshal public key: %v", err) } // Create PEM format pubKeyPEM := pem.EncodeToMemory(&pem.Block{ Type: "PUBLIC KEY", Bytes: pubKeyDER, }) // Compute Log ID (SHA-256 of the DER-encoded public key) logIDBytes := sha256.Sum256(pubKeyDER) logID := base64.StdEncoding.EncodeToString(logIDBytes[:]) // Base64 encode DER for download link pubKeyDERB64 := base64.StdEncoding.EncodeToString(pubKeyDER) // Set computed fields logEntry.LogID = logID logEntry.PublicKeyPEM = string(pubKeyPEM) logEntry.PublicKeyDERB64 = pubKeyDERB64 logEntry.PublicKeyBase64 = pubKeyDERB64 // Same as DER base64 for JSON return nil } func generateLogJSON(logEntry Log, outputPath string) error { logJSON := LogV3JSON{ Description: fmt.Sprintf("%s.log.ct.ipng.ch", logEntry.ShortName), SubmissionURL: fmt.Sprintf("%s/", logEntry.SubmissionPrefix), MonitoringURL: fmt.Sprintf("%s/", logEntry.MonitoringPrefix), TemporalInterval: TemporalInterval{ StartInclusive: logEntry.NotAfterStart.Format("2006-01-02T15:04:05Z"), EndExclusive: logEntry.NotAfterLimit.Format("2006-01-02T15:04:05Z"), }, LogID: logEntry.LogID, Key: logEntry.PublicKeyBase64, MMD: 60, // Default MMD of 60 seconds } jsonData, err := json.MarshalIndent(logJSON, "", " ") if err != nil { return fmt.Errorf("failed to marshal JSON: %v", err) } err = os.WriteFile(outputPath, jsonData, 0644) if err != nil { return fmt.Errorf("failed to write JSON file: %v", err) } return nil }