Files
cheese/tesseract/genconf/roots_test.go
2025-08-28 21:15:13 +02:00

217 lines
11 KiB
Go

package main
import (
"encoding/json"
"encoding/pem"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
)
func TestGenerateRoots(t *testing.T) {
tmpDir := t.TempDir()
outputFile := filepath.Join(tmpDir, "test-roots.pem")
// Create a test HTTP server that returns mock root certificates
mockResponse := CTLogRootsResponse{
Certificates: []string{
// Real certificate (base64 encoded DER from OpenSSL)
"MIIDFzCCAf+gAwIBAgIUbOZ5dIVBuGei8T0VdIKjCpACVgMwDQYJKoZIhvcNAQELBQAwGzEZMBcGA1UEAwwQVGVzdCBDZXJ0aWZpY2F0ZTAeFw0yNTA4MjgxOTA4MjlaFw0yNjA4MjgxOTA4MjlaMBsxGTAXBgNVBAMMEFRlc3QgQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDEmF+JcVMD9u0efdi8AkIH4xiKb/8lJveOYu5UKJ/hE7j+QN0HcK+3d0Fh83Hgny7ZgF3A8JlTDnlZrkI1+n8uxLAbvZ/PVNS286FwlnWppnarSBwFMuHp+SoGFuNIQtB5LaxVt943Oxaw4vVaFBqi0iYPFKXj2JxLIwiwKxLiZ2hS15g/JIhRXSycHjMCvukPj1wtRo/cHF83Miydw+ngFqrfXcEzvG/Yx5qvAgowvG/FEKha5zB36ywy35SHq8gX7DHs77eQ52lxd4yvYfPhIwaAzTvnpyACcvfgP/XiETMxsaPoZ3WFzK4y3Smg6zUMLAB1YE4RxUd+4NA+NBmbAgMBAAGjUzBRMB0GA1UdDgQWBBQxgJBg/4o2f1e58aBMOlyEO0svIDAfBgNVHSMEGDAWgBQxgJBg/4o2f1e58aBMOlyEO0svIDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA1Z1r8pJeYizgo30kA+Q4SICq70jvAO6XPrin2hp8MlvE7nUG2afInzNh6FXjqMI0TWctqV+wF9nnCkPBPu4ybCbm7reiJ1SojPmp/qxv+qoyXXFZxX/ugOhhJv4BbSoHxepdSPBtqQb5uZPL/1q9vYmLfsEdEdh1dI4vEbrvvZR/fV/5+ZjL2uOuWf6zw0BpMqXyC0wcoZIWKHl62yLOhdJwbUVLfKiHuwrhsdIIc0QDI74U6x5pu/eIa1hCPX9e/X8vEK8/0EBV2xGhAeeteu4bY04AjrSs0tESQO5EfACmhQM/1ytlqMx3qsPGb7pIptUttHREvr+RL6qKCRZCK",
// Second certificate (same but different serial for testing)
"MIIDFzCCAf+gAwIBAgIUbOZ5dIVBuGei8T0VdIKjCpACVgQwDQYJKoZIhvcNAQELBQAwGzEZMBcGA1UEAwwQVGVzdCBDZXJ0aWZpY2F0ZTAeFw0yNTA4MjgxOTA4MjlaFw0yNjA4MjgxOTA4MjlaMBsxGTAXBgNVBAMMEFRlc3QgQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDEmF+JcVMD9u0efdi8AkIH4xiKb/8lJveOYu5UKJ/hE7j+QN0HcK+3d0Fh83Hgny7ZgF3A8JlTDnlZrkI1+n8uxLAbvZ/PVNS286FwlnWppnarSBwFMuHp+SoGFuNIQtB5LaxVt943Oxaw4vVaFBqi0iYPFKXj2JxLIwiwKxLiZ2hS15g/JIhRXSycHjMCvukPj1wtRo/cHF83Miydw+ngFqrfXcEzvG/Yx5qvAgowvG/FEKha5zB36ywy35SHq8gX7DHs77eQ52lxd4yvYfPhIwaAzTvnpyACcvfgP/XiETMxsaPoZ3WFzK4y3Smg6zUMLAB1YE4RxUd+4NA+NBmbAgMBAAGjUzBRMB0GA1UdDgQWBBQxgJBg/4o2f1e58aBMOlyEO0svIDAfBgNVHSMEGDAWgBQxgJBg/4o2f1e58aBMOlyEO0svIDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA1Z1r8pJeYizgo30kA+Q4SICq70jvAO6XPrin2hp8MlvE7nUG2afInzNh6FXjqMI0TWctqV+wF9nnCkPBPu4ybCbm7reiJ1SojPmp/qxv+qoyXXFZxX/ugOhhJv4BbSoHxepdSPBtqQb5uZPL/1q9vYmLfsEdEdh1dI4vEbrvvZR/fV/5+ZjL2uOuWf6zw0BpMqXyC0wcoZIWKHl62yLOhdJwbUVLfKiHuwrhsdIIc0QDI74U6x5pu/eIa1hCPX9e/X8vEK8/0EBV2xGhAeeteu4bY04AjrSs0tESQO5EfACmhQM/1ytlqMx3qsPGb7pIptUttHREvr+RL6qKCRZCK",
},
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !strings.HasSuffix(r.URL.Path, "/ct/v1/get-roots") {
t.Errorf("Expected path to end with /ct/v1/get-roots, got %s", r.URL.Path)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(mockResponse)
}))
defer server.Close()
// Test with default arguments
args := []string{"--source", server.URL, "--output", outputFile}
generateRoots(args, false, true, false)
// Verify output file was created
if _, err := os.Stat(outputFile); os.IsNotExist(err) {
t.Fatal("Output file was not created")
}
// Verify file contents
content, err := os.ReadFile(outputFile)
if err != nil {
t.Fatal(err)
}
contentStr := string(content)
// Should contain PEM headers
if !strings.Contains(contentStr, "-----BEGIN CERTIFICATE-----") {
t.Error("Expected PEM certificate headers")
}
if !strings.Contains(contentStr, "-----END CERTIFICATE-----") {
t.Error("Expected PEM certificate footers")
}
// Count certificates (should have 2)
certCount := strings.Count(contentStr, "-----BEGIN CERTIFICATE-----")
if certCount != 2 {
t.Errorf("Expected 2 certificates, found %d", certCount)
}
}
func TestGenerateRootsWithNegativeSerial(t *testing.T) {
// Should call log.Fatalf due to invalid base64
// Can't easily test this without subprocess, so we'll skip it
t.Skip("Cannot easily test log.Fatalf without subprocess")
}
func TestGenerateRootsHTTPError(t *testing.T) {
// Should call log.Fatalf due to HTTP error
// Can't easily test this without subprocess, so we'll skip it
t.Skip("Cannot easily test log.Fatalf without subprocess")
}
func TestGenerateRootsInvalidJSON(t *testing.T) {
// Should call log.Fatalf due to JSON parse error
// Can't easily test this without subprocess, so we'll skip it
t.Skip("Cannot easily test log.Fatalf without subprocess")
}
func TestGenerateRootsArgumentParsing(t *testing.T) {
tmpDir := t.TempDir()
customOutputFile := filepath.Join(tmpDir, "custom-roots.pem")
mockResponse := CTLogRootsResponse{
Certificates: []string{
"MIIDFzCCAf+gAwIBAgIUbOZ5dIVBuGei8T0VdIKjCpACVgMwDQYJKoZIhvcNAQELBQAwGzEZMBcGA1UEAwwQVGVzdCBDZXJ0aWZpY2F0ZTAeFw0yNTA4MjgxOTA4MjlaFw0yNjA4MjgxOTA4MjlaMBsxGTAXBgNVBAMMEFRlc3QgQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDEmF+JcVMD9u0efdi8AkIH4xiKb/8lJveOYu5UKJ/hE7j+QN0HcK+3d0Fh83Hgny7ZgF3A8JlTDnlZrkI1+n8uxLAbvZ/PVNS286FwlnWppnarSBwFMuHp+SoGFuNIQtB5LaxVt943Oxaw4vVaFBqi0iYPFKXj2JxLIwiwKxLiZ2hS15g/JIhRXSycHjMCvukPj1wtRo/cHF83Miydw+ngFqrfXcEzvG/Yx5qvAgowvG/FEKha5zB36ywy35SHq8gX7DHs77eQ52lxd4yvYfPhIwaAzTvnpyACcvfgP/XiETMxsaPoZ3WFzK4y3Smg6zUMLAB1YE4RxUd+4NA+NBmbAgMBAAGjUzBRMB0GA1UdDgQWBBQxgJBg/4o2f1e58aBMOlyEO0svIDAfBgNVHSMEGDAWgBQxgJBg/4o2f1e58aBMOlyEO0svIDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA1Z1r8pJeYizgo30kA+Q4SICq70jvAO6XPrin2hp8MlvE7nUG2afInzNh6FXjqMI0TWctqV+wF9nnCkPBPu4ybCbm7reiJ1SojPmp/qxv+qoyXXFZxX/ugOhhJv4BbSoHxepdSPBtqQb5uZPL/1q9vYmLfsEdEdh1dI4vEbrvvZR/fV/5+ZjL2uOuWf6zw0BpMqXyC0wcoZIWKHl62yLOhdJwbUVLfKiHuwrhsdIIc0QDI74U6x5pu/eIa1hCPX9e/X8vEK8/0EBV2xGhAeeteu4bY04AjrSs0tESQO5EfACmhQM/1ytlqMx3qsPGb7pIptUttHREvr+RL6qKCRZCK",
},
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(mockResponse)
}))
defer server.Close()
// Test custom source and output
args := []string{"--source", server.URL, "--output", customOutputFile}
generateRoots(args, false, true, false)
// Verify custom output file was created
if _, err := os.Stat(customOutputFile); os.IsNotExist(err) {
t.Error("Custom output file was not created")
}
}
func TestGenerateRootsMissingSourceArgument(t *testing.T) {
// Should call log.Fatal due to missing source URL argument
// Can't easily test this without subprocess, so we'll skip it
t.Skip("Cannot easily test log.Fatal without subprocess")
}
func TestGenerateRootsMissingOutputArgument(t *testing.T) {
// Should call log.Fatal due to missing output filename argument
// Can't easily test this without subprocess, so we'll skip it
t.Skip("Cannot easily test log.Fatal without subprocess")
}
func TestGenerateRootsUnknownArgument(t *testing.T) {
// Should call log.Fatalf due to unknown argument
// Can't easily test this without subprocess, so we'll skip it
t.Skip("Cannot easily test log.Fatalf without subprocess")
}
func TestGenerateRootsDefaultArguments(t *testing.T) {
// Test that default arguments are used correctly
// This test would normally make a real HTTP request, so we'll skip it
// unless we're doing integration tests
t.Skip("Skipping test that would make real HTTP request")
}
func TestGenerateRootsSourceURLFormatting(t *testing.T) {
tmpDir := t.TempDir()
outputFile := filepath.Join(tmpDir, "test-roots.pem")
mockResponse := CTLogRootsResponse{
Certificates: []string{
"MIIDFzCCAf+gAwIBAgIUbOZ5dIVBuGei8T0VdIKjCpACVgMwDQYJKoZIhvcNAQELBQAwGzEZMBcGA1UEAwwQVGVzdCBDZXJ0aWZpY2F0ZTAeFw0yNTA4MjgxOTA4MjlaFw0yNjA4MjgxOTA4MjlaMBsxGTAXBgNVBAMMEFRlc3QgQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDEmF+JcVMD9u0efdi8AkIH4xiKb/8lJveOYu5UKJ/hE7j+QN0HcK+3d0Fh83Hgny7ZgF3A8JlTDnlZrkI1+n8uxLAbvZ/PVNS286FwlnWppnarSBwFMuHp+SoGFuNIQtB5LaxVt943Oxaw4vVaFBqi0iYPFKXj2JxLIwiwKxLiZ2hS15g/JIhRXSycHjMCvukPj1wtRo/cHF83Miydw+ngFqrfXcEzvG/Yx5qvAgowvG/FEKha5zB36ywy35SHq8gX7DHs77eQ52lxd4yvYfPhIwaAzTvnpyACcvfgP/XiETMxsaPoZ3WFzK4y3Smg6zUMLAB1YE4RxUd+4NA+NBmbAgMBAAGjUzBRMB0GA1UdDgQWBBQxgJBg/4o2f1e58aBMOlyEO0svIDAfBgNVHSMEGDAWgBQxgJBg/4o2f1e58aBMOlyEO0svIDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA1Z1r8pJeYizgo30kA+Q4SICq70jvAO6XPrin2hp8MlvE7nUG2afInzNh6FXjqMI0TWctqV+wF9nnCkPBPu4ybCbm7reiJ1SojPmp/qxv+qoyXXFZxX/ugOhhJv4BbSoHxepdSPBtqQb5uZPL/1q9vYmLfsEdEdh1dI4vEbrvvZR/fV/5+ZjL2uOuWf6zw0BpMqXyC0wcoZIWKHl62yLOhdJwbUVLfKiHuwrhsdIIc0QDI74U6x5pu/eIa1hCPX9e/X8vEK8/0EBV2xGhAeeteu4bY04AjrSs0tESQO5EfACmhQM/1ytlqMx3qsPGb7pIptUttHREvr+RL6qKCRZCK",
},
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Verify the path formatting
expectedPath := "/ct/v1/get-roots"
if r.URL.Path != expectedPath {
t.Errorf("Expected path %s, got %s", expectedPath, r.URL.Path)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(mockResponse)
}))
defer server.Close()
// Test with URL that doesn't end with /
sourceURL := strings.TrimSuffix(server.URL, "/")
args := []string{"--source", sourceURL, "--output", outputFile}
generateRoots(args, false, true, false)
// Should still work (the function adds trailing slash)
if _, err := os.Stat(outputFile); os.IsNotExist(err) {
t.Error("Output file was not created when source URL lacks trailing slash")
}
}
func TestPEMEncoding(t *testing.T) {
tmpDir := t.TempDir()
outputFile := filepath.Join(tmpDir, "test-roots.pem")
mockResponse := CTLogRootsResponse{
Certificates: []string{
"MIIDFzCCAf+gAwIBAgIUbOZ5dIVBuGei8T0VdIKjCpACVgMwDQYJKoZIhvcNAQELBQAwGzEZMBcGA1UEAwwQVGVzdCBDZXJ0aWZpY2F0ZTAeFw0yNTA4MjgxOTA4MjlaFw0yNjA4MjgxOTA4MjlaMBsxGTAXBgNVBAMMEFRlc3QgQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDEmF+JcVMD9u0efdi8AkIH4xiKb/8lJveOYu5UKJ/hE7j+QN0HcK+3d0Fh83Hgny7ZgF3A8JlTDnlZrkI1+n8uxLAbvZ/PVNS286FwlnWppnarSBwFMuHp+SoGFuNIQtB5LaxVt943Oxaw4vVaFBqi0iYPFKXj2JxLIwiwKxLiZ2hS15g/JIhRXSycHjMCvukPj1wtRo/cHF83Miydw+ngFqrfXcEzvG/Yx5qvAgowvG/FEKha5zB36ywy35SHq8gX7DHs77eQ52lxd4yvYfPhIwaAzTvnpyACcvfgP/XiETMxsaPoZ3WFzK4y3Smg6zUMLAB1YE4RxUd+4NA+NBmbAgMBAAGjUzBRMB0GA1UdDgQWBBQxgJBg/4o2f1e58aBMOlyEO0svIDAfBgNVHSMEGDAWgBQxgJBg/4o2f1e58aBMOlyEO0svIDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA1Z1r8pJeYizgo30kA+Q4SICq70jvAO6XPrin2hp8MlvE7nUG2afInzNh6FXjqMI0TWctqV+wF9nnCkPBPu4ybCbm7reiJ1SojPmp/qxv+qoyXXFZxX/ugOhhJv4BbSoHxepdSPBtqQb5uZPL/1q9vYmLfsEdEdh1dI4vEbrvvZR/fV/5+ZjL2uOuWf6zw0BpMqXyC0wcoZIWKHl62yLOhdJwbUVLfKiHuwrhsdIIc0QDI74U6x5pu/eIa1hCPX9e/X8vEK8/0EBV2xGhAeeteu4bY04AjrSs0tESQO5EfACmhQM/1ytlqMx3qsPGb7pIptUttHREvr+RL6qKCRZCK",
},
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(mockResponse)
}))
defer server.Close()
args := []string{"--source", server.URL, "--output", outputFile}
generateRoots(args, false, true, false)
// Read and verify PEM structure
content, err := os.ReadFile(outputFile)
if err != nil {
t.Fatal(err)
}
// Parse PEM blocks
var certCount int
remaining := content
for len(remaining) > 0 {
block, rest := pem.Decode(remaining)
if block == nil {
break
}
if block.Type != "CERTIFICATE" {
t.Errorf("Expected block type 'CERTIFICATE', got %s", block.Type)
}
certCount++
remaining = rest
}
if certCount != 1 {
t.Errorf("Expected 1 certificate, found %d", certCount)
}
}