316 lines
8.4 KiB
Go
316 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")
|
|
} |