Add get-roots

This commit is contained in:
Pim van Pelt
2025-08-24 11:39:03 +02:00
parent a508beefba
commit 8003270329
2 changed files with 95 additions and 2 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
tesseract-genconf tesseract-genconf
roots.pem

View File

@@ -13,6 +13,7 @@ import (
"fmt" "fmt"
"io" "io"
"log" "log"
"net/http"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -145,6 +146,8 @@ func main() {
generateEnv(*configFile) generateEnv(*configFile)
case "gen-key": case "gen-key":
generateKeys(*configFile) generateKeys(*configFile)
case "gen-roots":
generateRoots(args[1:])
default: default:
fmt.Fprintf(os.Stderr, "Unknown command: %s\n", args[0]) fmt.Fprintf(os.Stderr, "Unknown command: %s\n", args[0])
showHelp() showHelp()
@@ -190,6 +193,9 @@ func showHelp() {
fmt.Printf(" Combines global roots and log-specific extraroots into roots.pem.\n\n") fmt.Printf(" Combines global roots and log-specific extraroots into roots.pem.\n\n")
fmt.Printf(" gen-key Generate prime256v1 private keys for each log (only if they don't exist).\n") fmt.Printf(" gen-key Generate prime256v1 private keys for each log (only if they don't exist).\n")
fmt.Printf(" Creates EC private key files at the path specified in log.secret.\n\n") fmt.Printf(" Creates EC private key files at the path specified in log.secret.\n\n")
fmt.Printf(" gen-roots Download root certificates from a Certificate Transparency log.\n")
fmt.Printf(" Options: --source <url> (default: https://rennet2027h2.log.ct.ipng.ch/)\n")
fmt.Printf(" --output <file> (default: roots.pem)\n\n")
} }
func showConfig(yamlFile string) { func showConfig(yamlFile string) {
@@ -430,6 +436,92 @@ func createCombinedRootsPem(rootsFile, extraRootsFile, outputPath string) error
return nil return nil
} }
type CTLogRootsResponse struct {
Certificates []string `json:"certificates"`
}
func generateRoots(args []string) {
sourceURL := "https://rennet2027h2.log.ct.ipng.ch/"
outputFile := "roots.pem"
// Parse command line arguments
for i := 0; i < len(args); i++ {
switch args[i] {
case "--source":
if i+1 >= len(args) {
log.Fatal("--source flag requires a URL argument")
}
sourceURL = args[i+1]
i++ // Skip the next argument since we used it
case "--output":
if i+1 >= len(args) {
log.Fatal("--output flag requires a filename argument")
}
outputFile = args[i+1]
i++ // Skip the next argument since we used it
default:
log.Fatalf("Unknown argument: %s", args[i])
}
}
// Ensure source URL ends with /
if !strings.HasSuffix(sourceURL, "/") {
sourceURL += "/"
}
// Construct the get-roots URL
getRootsURL := sourceURL + "ct/v1/get-roots"
// Fetch roots from CT log
fmt.Printf("Fetching roots from: %s\n", getRootsURL)
resp, err := http.Get(getRootsURL)
if err != nil {
log.Fatalf("Failed to fetch roots: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.Fatalf("HTTP request failed with status: %d", resp.StatusCode)
}
// Parse JSON response
var rootsResp CTLogRootsResponse
err = json.NewDecoder(resp.Body).Decode(&rootsResp)
if err != nil {
log.Fatalf("Failed to parse JSON response: %v", err)
}
// Create output file
outFile, err := os.Create(outputFile)
if err != nil {
log.Fatalf("Failed to create output file %s: %v", outputFile, err)
}
defer outFile.Close()
// Write each certificate as PEM
for _, certBase64 := range rootsResp.Certificates {
// Decode base64 certificate
certBytes, err := base64.StdEncoding.DecodeString(certBase64)
if err != nil {
log.Fatalf("Failed to decode certificate: %v", err)
}
// Create PEM block
pemBlock := &pem.Block{
Type: "CERTIFICATE",
Bytes: certBytes,
}
// Write PEM to file
err = pem.Encode(outFile, pemBlock)
if err != nil {
log.Fatalf("Failed to write PEM certificate: %v", err)
}
}
fmt.Printf("Successfully wrote %d certificates to %s\n", len(rootsResp.Certificates), outputFile)
}
func generateKeys(yamlFile string) { func generateKeys(yamlFile string) {
config := loadConfig(yamlFile) config := loadConfig(yamlFile)