diff --git a/modules/caddytls/values_test.go b/modules/caddytls/values_test.go new file mode 100644 index 000000000..14ff603e4 --- /dev/null +++ b/modules/caddytls/values_test.go @@ -0,0 +1,237 @@ +package caddytls + +import ( + "crypto/tls" + "testing" +) + +func TestCipherSuiteNameSupported(t *testing.T) { + tests := []struct { + name string + input string + supported bool + }{ + { + name: "valid TLS 1.2 cipher", + input: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + supported: true, + }, + { + name: "valid TLS 1.2 cipher RSA", + input: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + supported: true, + }, + { + name: "valid ChaCha20 cipher", + input: "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + supported: true, + }, + { + name: "unknown cipher name", + input: "TLS_UNKNOWN_CIPHER", + supported: false, + }, + { + name: "empty string", + input: "", + supported: false, + }, + { + name: "partial name", + input: "TLS_ECDHE", + supported: false, + }, + { + name: "case sensitive mismatch", + input: "tls_ecdhe_ecdsa_with_aes_256_gcm_sha384", + supported: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := CipherSuiteNameSupported(tt.input) + if got != tt.supported { + t.Errorf("CipherSuiteNameSupported(%q) = %v, want %v", tt.input, got, tt.supported) + } + }) + } +} + +func TestCipherSuiteID(t *testing.T) { + tests := []struct { + name string + input string + wantID uint16 + }{ + { + name: "known cipher returns correct ID", + input: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + wantID: tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + }, + { + name: "another known cipher", + input: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + wantID: tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + }, + { + name: "unknown cipher returns 0", + input: "DOES_NOT_EXIST", + wantID: 0, + }, + { + name: "empty string returns 0", + input: "", + wantID: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := CipherSuiteID(tt.input) + if got != tt.wantID { + t.Errorf("CipherSuiteID(%q) = 0x%04x, want 0x%04x", tt.input, got, tt.wantID) + } + }) + } +} + +func TestSupportedCipherSuites(t *testing.T) { + suites := SupportedCipherSuites() + if len(suites) == 0 { + t.Fatal("SupportedCipherSuites() returned empty list") + } + + // Every suite should have a non-empty name and non-zero ID + for _, cs := range suites { + if cs.Name == "" { + t.Errorf("cipher suite with ID 0x%04x has empty name", cs.ID) + } + if cs.ID == 0 { + t.Errorf("cipher suite %q has zero ID", cs.Name) + } + } + + // Verify that CipherSuiteID can find each suite by name (round-trip) + for _, cs := range suites { + id := CipherSuiteID(cs.Name) + if id != cs.ID { + t.Errorf("CipherSuiteID(%q) = 0x%04x, want 0x%04x", cs.Name, id, cs.ID) + } + } +} + +func TestProtocolName(t *testing.T) { + tests := []struct { + name string + id uint16 + want string + }{ + { + name: "TLS 1.2", + id: tls.VersionTLS12, + want: "tls1.2", + }, + { + name: "TLS 1.3", + id: tls.VersionTLS13, + want: "tls1.3", + }, + { + name: "unsupported SSL 3.0", + id: tls.VersionSSL30, //nolint:staticcheck + want: "ssl3.0", + }, + { + name: "unsupported TLS 1.0", + id: tls.VersionTLS10, + want: "tls1.0", + }, + { + name: "unsupported TLS 1.1", + id: tls.VersionTLS11, + want: "tls1.1", + }, + { + name: "unknown protocol falls back to hex", + id: 0x9999, + want: "0x9999", + }, + { + name: "zero protocol falls back to hex", + id: 0, + want: "0x0000", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ProtocolName(tt.id) + if got != tt.want { + t.Errorf("ProtocolName(0x%04x) = %q, want %q", tt.id, got, tt.want) + } + }) + } +} + +func TestSupportedProtocolsMap(t *testing.T) { + // SupportedProtocols should contain at least TLS 1.2 and 1.3 + expectedProtocols := map[string]uint16{ + "tls1.2": tls.VersionTLS12, + "tls1.3": tls.VersionTLS13, + } + + for name, wantID := range expectedProtocols { + gotID, ok := SupportedProtocols[name] + if !ok { + t.Errorf("SupportedProtocols missing %q", name) + continue + } + if gotID != wantID { + t.Errorf("SupportedProtocols[%q] = 0x%04x, want 0x%04x", name, gotID, wantID) + } + } +} + +func TestSupportedCurvesMap(t *testing.T) { + // SupportedCurves should contain key exchanges + expectedCurves := []string{"x25519", "secp256r1", "secp384r1", "secp521r1"} + for _, name := range expectedCurves { + if _, ok := SupportedCurves[name]; !ok { + t.Errorf("SupportedCurves missing %q", name) + } + } + + // All values should be non-zero + for name, id := range SupportedCurves { + if id == 0 { + t.Errorf("SupportedCurves[%q] has zero value", name) + } + } +} + +func TestGetOptimalDefaultCipherSuites(t *testing.T) { + suites := getOptimalDefaultCipherSuites() + if len(suites) == 0 { + t.Fatal("getOptimalDefaultCipherSuites() returned empty list") + } + + // All returned IDs should be non-zero + for i, id := range suites { + if id == 0 { + t.Errorf("getOptimalDefaultCipherSuites()[%d] is zero", i) + } + } + + // All returned IDs should be recognized cipher suites + knownSuites := SupportedCipherSuites() + knownIDs := make(map[uint16]bool) + for _, cs := range knownSuites { + knownIDs[cs.ID] = true + } + for _, id := range suites { + if !knownIDs[id] { + t.Errorf("getOptimalDefaultCipherSuites() contains unrecognized ID 0x%04x", id) + } + } +}