Files
cheese/tesseract/genconf/html_test.go
2025-08-28 21:17:37 +02:00

317 lines
8.4 KiB
Go

package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/json"
"encoding/pem"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func TestGenerateHTML(t *testing.T) {
tmpDir := t.TempDir()
// Create test directories
testLogDir := filepath.Join(tmpDir, "test-log")
err := os.MkdirAll(testLogDir, 0755)
if err != nil {
t.Fatal(err)
}
// Generate test private key
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
privKeyDER, err := x509.MarshalECPrivateKey(privKey)
if err != nil {
t.Fatal(err)
}
privKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "EC PRIVATE KEY",
Bytes: privKeyDER,
})
keyFile := filepath.Join(tmpDir, "test-key.pem")
err = os.WriteFile(keyFile, privKeyPEM, 0600)
if err != nil {
t.Fatal(err)
}
// Create test config
configContent := `listen:
- ":8080"
logs:
- shortname: "test-log"
submissionprefix: "https://example.com/submit"
monitoringprefix: "https://example.com/monitor"
secret: "` + keyFile + `"
localdirectory: "` + testLogDir + `"
notafterstart: "2024-01-01T00:00:00Z"
notafterlimit: "2025-01-01T00:00:00Z"`
configFile := filepath.Join(tmpDir, "test-config.yaml")
err = os.WriteFile(configFile, []byte(configContent), 0644)
if err != nil {
t.Fatal(err)
}
// Run generateHTML
generateHTML(configFile, false, true, false)
// Verify HTML file was created
htmlFile := filepath.Join(testLogDir, "index.html")
htmlContent, err := os.ReadFile(htmlFile)
if err != nil {
t.Fatalf("Failed to read HTML file: %v", err)
}
htmlStr := string(htmlContent)
// Check HTML contains expected elements
if !strings.Contains(htmlStr, "<!DOCTYPE html>") {
t.Error("Expected HTML doctype")
}
if !strings.Contains(htmlStr, "TesseraCT") {
t.Error("Expected TesseraCT title")
}
if !strings.Contains(htmlStr, "example.com") {
t.Error("Expected origin hostname in HTML")
}
if !strings.Contains(htmlStr, "https://example.com/submit/") {
t.Error("Expected submission prefix in HTML")
}
if !strings.Contains(htmlStr, "https://example.com/monitor/") {
t.Error("Expected monitoring prefix in HTML")
}
if !strings.Contains(htmlStr, "2024-01-01T00:00:00Z") {
t.Error("Expected start time in HTML")
}
if !strings.Contains(htmlStr, "2025-01-01T00:00:00Z") {
t.Error("Expected end time in HTML")
}
if !strings.Contains(htmlStr, "-----BEGIN PUBLIC KEY-----") {
t.Error("Expected public key in HTML")
}
// Verify JSON file was created
jsonFile := filepath.Join(testLogDir, "log.v3.json")
jsonContent, err := os.ReadFile(jsonFile)
if err != nil {
t.Fatalf("Failed to read JSON file: %v", err)
}
var logJSON LogV3JSON
err = json.Unmarshal(jsonContent, &logJSON)
if err != nil {
t.Fatalf("Failed to parse JSON: %v", err)
}
// Verify JSON content
if logJSON.Description != "example.com" {
t.Errorf("Expected description 'example.com', got %s", logJSON.Description)
}
if logJSON.SubmissionURL != "https://example.com/submit/" {
t.Errorf("Expected submission URL 'https://example.com/submit/', got %s", logJSON.SubmissionURL)
}
if logJSON.MonitoringURL != "https://example.com/monitor/" {
t.Errorf("Expected monitoring URL 'https://example.com/monitor/', got %s", logJSON.MonitoringURL)
}
if logJSON.TemporalInterval.StartInclusive != "2024-01-01T00:00:00Z" {
t.Errorf("Expected start time '2024-01-01T00:00:00Z', got %s", logJSON.TemporalInterval.StartInclusive)
}
if logJSON.TemporalInterval.EndExclusive != "2025-01-01T00:00:00Z" {
t.Errorf("Expected end time '2025-01-01T00:00:00Z', got %s", logJSON.TemporalInterval.EndExclusive)
}
if logJSON.MMD != 60 {
t.Errorf("Expected MMD 60, got %d", logJSON.MMD)
}
if logJSON.LogID == "" {
t.Error("Expected non-empty LogID")
}
if logJSON.Key == "" {
t.Error("Expected non-empty Key")
}
}
func TestComputeKeyInfo(t *testing.T) {
tmpDir := t.TempDir()
// Generate test private key
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
privKeyDER, err := x509.MarshalECPrivateKey(privKey)
if err != nil {
t.Fatal(err)
}
privKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "EC PRIVATE KEY",
Bytes: privKeyDER,
})
keyFile := filepath.Join(tmpDir, "test-key.pem")
err = os.WriteFile(keyFile, privKeyPEM, 0600)
if err != nil {
t.Fatal(err)
}
// Create log entry
logEntry := Log{
ShortName: "test-log",
Secret: keyFile,
}
// Compute key info
err = computeKeyInfo(&logEntry)
if err != nil {
t.Fatalf("computeKeyInfo() error = %v", err)
}
// Verify computed fields
if logEntry.LogID == "" {
t.Error("Expected non-empty LogID")
}
if logEntry.PublicKeyPEM == "" {
t.Error("Expected non-empty PublicKeyPEM")
}
if !strings.Contains(logEntry.PublicKeyPEM, "-----BEGIN PUBLIC KEY-----") {
t.Error("Expected PEM format public key")
}
if !strings.Contains(logEntry.PublicKeyPEM, "-----END PUBLIC KEY-----") {
t.Error("Expected PEM format public key")
}
if logEntry.PublicKeyDERB64 == "" {
t.Error("Expected non-empty PublicKeyDERB64")
}
if logEntry.PublicKeyBase64 == "" {
t.Error("Expected non-empty PublicKeyBase64")
}
if logEntry.PublicKeyDERB64 != logEntry.PublicKeyBase64 {
t.Error("Expected PublicKeyDERB64 and PublicKeyBase64 to be the same")
}
}
func TestComputeKeyInfoInvalidKey(t *testing.T) {
tmpDir := t.TempDir()
// Create invalid key file
invalidKeyFile := filepath.Join(tmpDir, "invalid-key.pem")
invalidKeyContent := `-----BEGIN EC PRIVATE KEY-----
INVALID KEY DATA
-----END EC PRIVATE KEY-----`
err := os.WriteFile(invalidKeyFile, []byte(invalidKeyContent), 0600)
if err != nil {
t.Fatal(err)
}
logEntry := Log{
Secret: invalidKeyFile,
}
err = computeKeyInfo(&logEntry)
if err == nil {
t.Error("Expected error for invalid key file")
}
}
func TestComputeKeyInfoMissingFile(t *testing.T) {
logEntry := Log{
Secret: "/nonexistent/key.pem",
}
err := computeKeyInfo(&logEntry)
if err == nil {
t.Error("Expected error for missing key file")
}
}
func TestGenerateLogJSONWithStatus(t *testing.T) {
tmpDir := t.TempDir()
outputFile := filepath.Join(tmpDir, "test-log.v3.json")
testTime1, _ := time.Parse("2006-01-02T15:04:05Z", "2024-01-01T00:00:00Z")
testTime2, _ := time.Parse("2006-01-02T15:04:05Z", "2025-01-01T00:00:00Z")
logEntry := Log{
Origin: "test.example.com",
SubmissionPrefix: "https://test.example.com/submit",
MonitoringPrefix: "https://test.example.com/monitor",
NotAfterStart: testTime1,
NotAfterLimit: testTime2,
LogID: "dGVzdC1sb2ctaWQ=", // base64 encoded "test-log-id"
PublicKeyBase64: "dGVzdC1wdWJsaWMta2V5", // base64 encoded "test-public-key"
}
err := generateLogJSONWithStatus(logEntry, outputFile, false, true, false)
if err != nil {
t.Fatalf("generateLogJSONWithStatus() error = %v", err)
}
// Verify JSON was created
jsonContent, err := os.ReadFile(outputFile)
if err != nil {
t.Fatalf("Failed to read JSON file: %v", err)
}
var logJSON LogV3JSON
err = json.Unmarshal(jsonContent, &logJSON)
if err != nil {
t.Fatalf("Failed to parse JSON: %v", err)
}
// Verify content
if logJSON.Description != "test.example.com" {
t.Errorf("Expected description 'test.example.com', got %s", logJSON.Description)
}
if logJSON.SubmissionURL != "https://test.example.com/submit/" {
t.Errorf("Expected submission URL to have trailing slash")
}
if logJSON.MonitoringURL != "https://test.example.com/monitor/" {
t.Errorf("Expected monitoring URL to have trailing slash")
}
if logJSON.LogID != "dGVzdC1sb2ctaWQ=" {
t.Errorf("Expected LogID 'dGVzdC1sb2ctaWQ=', got %s", logJSON.LogID)
}
if logJSON.Key != "dGVzdC1wdWJsaWMta2V5" {
t.Errorf("Expected Key 'dGVzdC1wdWJsaWMta2V5', got %s", logJSON.Key)
}
if logJSON.MMD != 60 {
t.Errorf("Expected MMD 60, got %d", logJSON.MMD)
}
}
func TestGenerateHTMLMissingDirectory(t *testing.T) {
tmpDir := t.TempDir()
// Create config with non-existent directory
configContent := `logs:
- shortname: "test-log"
submissionprefix: "https://example.com/submit"
monitoringprefix: "https://example.com/monitor"
secret: "test.key"
localdirectory: "/nonexistent/directory"`
configFile := filepath.Join(tmpDir, "test-config.yaml")
err := os.WriteFile(configFile, []byte(configContent), 0644)
if err != nil {
t.Fatal(err)
}
// Should call log.Fatalf which exits the program
// We can't easily test this without subprocess, so we'll skip it
t.Skip("Cannot easily test log.Fatalf without subprocess")
}