Skip to content

Commit 2ba049b

Browse files
committed
crypto: add test to detect drift between DefaultCiphers() and guidelines
1 parent 4a03f7c commit 2ba049b

File tree

3 files changed

+344
-0
lines changed

3 files changed

+344
-0
lines changed

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ verify: verify-podnetworkconnectivitychecks
2323
verify-podnetworkconnectivitychecks:
2424
$(MAKE) -C pkg/operator/connectivitycheckcontroller verify
2525

26+
.PHONY: update-mozilla-guidelines
27+
update-mozilla-guidelines:
28+
curl -sS -o pkg/crypto/testfiles/mozilla-guidelines.json https://ssl-config.mozilla.org/guidelines/latest.json
29+
@echo "Mozilla TLS configuration guidelines updated to latest version"
30+
2631
test-e2e-encryption: GO_TEST_PACKAGES :=./test/e2e-encryption/...
2732
.PHONY: test-e2e-encryption
2833

pkg/crypto/crypto_test.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ package crypto
22

33
import (
44
"crypto"
5+
"crypto/tls"
56
"crypto/x509"
67
"crypto/x509/pkix"
8+
_ "embed"
9+
"encoding/json"
710
"fmt"
811
"go/importer"
912
"os"
@@ -549,3 +552,130 @@ func TestServerCertRegeneration(t *testing.T) {
549552
require.NotNil(t, serverCert)
550553
require.True(t, created)
551554
}
555+
556+
//go:embed testfiles/mozilla-guidelines.json
557+
var guidelinesJSON []byte
558+
559+
// TestDefaultCiphersAgainstMozillaGuidelines compares our DefaultCiphers() with the Mozilla TLS
560+
// configuration guidelines stored in testfiles/mozilla-guidelines.json.
561+
func TestDefaultCiphersAgainstMozillaGuidelines(t *testing.T) {
562+
563+
// Parse the JSON
564+
var guidelines struct {
565+
Version float64 `json:"version"`
566+
Configurations map[string]struct {
567+
TLSVersions []string `json:"tls_versions"`
568+
Ciphers map[string][]string `json:"ciphers"`
569+
Ciphersuites []string `json:"ciphersuites"` // TLS 1.3 ciphers
570+
} `json:"configurations"`
571+
}
572+
if err := json.Unmarshal(guidelinesJSON, &guidelines); err != nil {
573+
t.Fatalf("Failed to parse Mozilla guidelines JSON: %v", err)
574+
}
575+
576+
// Get Intermediate profile
577+
intermediate, ok := guidelines.Configurations["intermediate"]
578+
if !ok {
579+
t.Fatalf("Mozilla guidelines missing 'intermediate' configuration")
580+
}
581+
582+
// Get Go cipher names from Mozilla (they use IANA names)
583+
mozillaCipherNames := sets.New[string]()
584+
mozillaCipherIDs := sets.New[uint16]()
585+
unknownCiphers := []string{}
586+
587+
// Process TLS 1.2 ciphers (from "go" field)
588+
goCiphers := intermediate.Ciphers["go"]
589+
for _, cipherName := range goCiphers {
590+
mozillaCipherNames.Insert(cipherName)
591+
592+
// Convert IANA name to cipher suite ID
593+
cipherID, ok := ciphers[cipherName]
594+
if ok {
595+
mozillaCipherIDs.Insert(cipherID)
596+
} else {
597+
unknownCiphers = append(unknownCiphers, cipherName)
598+
}
599+
}
600+
601+
// Process TLS 1.3 ciphersuites
602+
for _, cipherName := range intermediate.Ciphersuites {
603+
mozillaCipherNames.Insert(cipherName)
604+
605+
// Convert IANA name to cipher suite ID
606+
cipherID, ok := ciphers[cipherName]
607+
if ok {
608+
mozillaCipherIDs.Insert(cipherID)
609+
} else {
610+
unknownCiphers = append(unknownCiphers, cipherName)
611+
}
612+
}
613+
614+
// Get our default ciphers
615+
ourCiphers := DefaultCiphers()
616+
ourCipherIDs := sets.New[uint16](ourCiphers...)
617+
ourCipherNames := sets.New[string]()
618+
for _, id := range ourCiphers {
619+
ourCipherNames.Insert(CipherSuiteToNameOrDie(id))
620+
}
621+
622+
// Find discrepancies
623+
deprecated := ourCipherIDs.Difference(mozillaCipherIDs)
624+
missing := mozillaCipherIDs.Difference(ourCipherIDs)
625+
626+
// Report findings
627+
t.Logf("Mozilla SSL Configuration Guidelines Version: %.1f", guidelines.Version)
628+
t.Logf("Mozilla Intermediate Profile: TLS Versions %v", intermediate.TLSVersions)
629+
t.Logf("Mozilla Intermediate Profile: %d cipher suites", len(intermediate.Ciphers["go"])+len(intermediate.Ciphersuites))
630+
t.Logf("DefaultCiphers(): %d cipher suites", len(ourCiphers))
631+
632+
if len(unknownCiphers) > 0 {
633+
t.Errorf("Mozilla recommends %d cipher(s) that are not in our cipher mapping", len(unknownCiphers))
634+
for _, cipher := range unknownCiphers {
635+
t.Errorf(" - %s (OpenSSL name)", cipher)
636+
}
637+
t.Errorf("To fix: Add these ciphers to DefaultCiphers() in crypto.go, or update the cached guidelines file with 'make update-mozilla-guidelines'")
638+
}
639+
640+
if len(deprecated) > 0 {
641+
t.Errorf("DefaultCiphers() includes %d deprecated cipher(s) not in Mozilla Intermediate recommendations", len(deprecated))
642+
var deprecatedNames []string
643+
for id := range deprecated {
644+
name := CipherSuiteToNameOrDie(id)
645+
deprecatedNames = append(deprecatedNames, name)
646+
}
647+
sort.Strings(deprecatedNames)
648+
for _, name := range deprecatedNames {
649+
t.Errorf(" - %s", name)
650+
}
651+
t.Errorf("To fix: Remove these ciphers from DefaultCiphers() in crypto.go, or update the cached guidelines file with 'make update-mozilla-guidelines'")
652+
}
653+
654+
if len(missing) > 0 {
655+
t.Errorf("DefaultCiphers() is missing %d cipher(s) recommended by Mozilla Intermediate profile", len(missing))
656+
var missingNames []string
657+
for id := range missing {
658+
// Use tls.CipherSuiteName if available, otherwise lookup in our map
659+
name := tls.CipherSuiteName(id)
660+
if name == "" || !strings.HasPrefix(name, "TLS_") {
661+
// Fallback to our mapping
662+
for k, v := range ciphers {
663+
if v == id {
664+
name = k
665+
break
666+
}
667+
}
668+
}
669+
missingNames = append(missingNames, name)
670+
}
671+
sort.Strings(missingNames)
672+
for _, name := range missingNames {
673+
t.Errorf(" - %s", name)
674+
}
675+
t.Errorf("To fix: Add these ciphers to the ciphers map in crypto.go, or update the cached guidelines file with 'make update-mozilla-guidelines'")
676+
}
677+
678+
if len(deprecated) == 0 && len(missing) == 0 {
679+
t.Logf("DefaultCiphers() is perfectly aligned with Mozilla Intermediate profile")
680+
}
681+
}
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
{
2+
"version": 5.7,
3+
"href": "https://ssl-config.mozilla.org/guidelines/5.7.json",
4+
"configurations": {
5+
"modern": {
6+
"certificate_curves": ["prime256v1", "secp384r1"],
7+
"certificate_signatures": ["ecdsa-with-SHA256", "ecdsa-with-SHA384", "ecdsa-with-SHA512"],
8+
"certificate_types": ["ecdsa"],
9+
"ciphers": {
10+
"caddy": [],
11+
"go": [],
12+
"iana": [],
13+
"openssl": []
14+
},
15+
"ciphersuites": [
16+
"TLS_AES_128_GCM_SHA256",
17+
"TLS_AES_256_GCM_SHA384",
18+
"TLS_CHACHA20_POLY1305_SHA256"
19+
],
20+
"dh_param_size": null,
21+
"ecdh_param_size": 256,
22+
"hsts_min_age": 63072000,
23+
"maximum_certificate_lifespan": 90,
24+
"ocsp_staple": true,
25+
"oldest_clients": ["Firefox 63", "Android 10.0", "Chrome 70", "Edge 75", "Java 11", "OpenSSL 1.1.1", "Opera 57", "Safari 12.1"],
26+
"recommended_certificate_lifespan": 90,
27+
"rsa_key_size": null,
28+
"server_preferred_order": false,
29+
"tls_curves": ["X25519", "prime256v1", "secp384r1"],
30+
"tls_versions": ["TLSv1.3"]
31+
},
32+
"intermediate": {
33+
"certificate_curves": ["prime256v1", "secp384r1"],
34+
"certificate_signatures": ["sha256WithRSAEncryption", "ecdsa-with-SHA256", "ecdsa-with-SHA384", "ecdsa-with-SHA512"],
35+
"certificate_types": ["ecdsa", "rsa"],
36+
"ciphers": {
37+
"caddy": [
38+
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
39+
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
40+
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
41+
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
42+
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
43+
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"
44+
],
45+
"go": [
46+
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
47+
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
48+
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
49+
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
50+
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
51+
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305"
52+
],
53+
"iana": [
54+
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
55+
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
56+
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
57+
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
58+
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
59+
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
60+
"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
61+
"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
62+
"TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256"
63+
],
64+
"openssl": [
65+
"ECDHE-ECDSA-AES128-GCM-SHA256",
66+
"ECDHE-RSA-AES128-GCM-SHA256",
67+
"ECDHE-ECDSA-AES256-GCM-SHA384",
68+
"ECDHE-RSA-AES256-GCM-SHA384",
69+
"ECDHE-ECDSA-CHACHA20-POLY1305",
70+
"ECDHE-RSA-CHACHA20-POLY1305",
71+
"DHE-RSA-AES128-GCM-SHA256",
72+
"DHE-RSA-AES256-GCM-SHA384",
73+
"DHE-RSA-CHACHA20-POLY1305"
74+
]
75+
},
76+
"ciphersuites": [
77+
"TLS_AES_128_GCM_SHA256",
78+
"TLS_AES_256_GCM_SHA384",
79+
"TLS_CHACHA20_POLY1305_SHA256"
80+
],
81+
"dh_param_size": 2048,
82+
"ecdh_param_size": 256,
83+
"hsts_min_age": 63072000,
84+
"maximum_certificate_lifespan": 366,
85+
"ocsp_staple": true,
86+
"oldest_clients": ["Firefox 27", "Android 4.4.2", "Chrome 31", "Edge", "IE 11 on Windows 7", "Java 8u31", "OpenSSL 1.0.1", "Opera 20", "Safari 9"],
87+
"recommended_certificate_lifespan": 90,
88+
"rsa_key_size": 2048,
89+
"server_preferred_order": false,
90+
"tls_curves": ["X25519", "prime256v1", "secp384r1"],
91+
"tls_versions": ["TLSv1.2", "TLSv1.3"]
92+
},
93+
"old": {
94+
"certificate_curves": ["prime256v1", "secp384r1"],
95+
"certificate_signatures": ["sha256WithRSAEncryption"],
96+
"certificate_types": ["rsa"],
97+
"ciphers": {
98+
"caddy": [
99+
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
100+
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
101+
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
102+
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
103+
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
104+
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
105+
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
106+
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
107+
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
108+
"TLS_RSA_WITH_AES_128_GCM_SHA256",
109+
"TLS_RSA_WITH_AES_256_GCM_SHA384",
110+
"TLS_RSA_WITH_AES_128_CBC_SHA",
111+
"TLS_RSA_WITH_AES_256_CBC_SHA",
112+
"TLS_RSA_WITH_3DES_EDE_CBC_SHA"
113+
],
114+
"go": [
115+
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
116+
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
117+
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
118+
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
119+
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
120+
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
121+
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
122+
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
123+
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
124+
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
125+
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
126+
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
127+
"TLS_RSA_WITH_AES_128_GCM_SHA256",
128+
"TLS_RSA_WITH_AES_256_GCM_SHA384",
129+
"TLS_RSA_WITH_AES_128_CBC_SHA256",
130+
"TLS_RSA_WITH_AES_128_CBC_SHA",
131+
"TLS_RSA_WITH_AES_256_CBC_SHA",
132+
"TLS_RSA_WITH_3DES_EDE_CBC_SHA"
133+
],
134+
"iana": [
135+
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
136+
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
137+
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
138+
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
139+
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
140+
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
141+
"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
142+
"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
143+
"TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
144+
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
145+
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
146+
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
147+
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
148+
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
149+
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
150+
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
151+
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
152+
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256",
153+
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
154+
"TLS_RSA_WITH_AES_128_GCM_SHA256",
155+
"TLS_RSA_WITH_AES_256_GCM_SHA384",
156+
"TLS_RSA_WITH_AES_128_CBC_SHA256",
157+
"TLS_RSA_WITH_AES_256_CBC_SHA256",
158+
"TLS_RSA_WITH_AES_128_CBC_SHA",
159+
"TLS_RSA_WITH_AES_256_CBC_SHA",
160+
"TLS_RSA_WITH_3DES_EDE_CBC_SHA"
161+
],
162+
"openssl": [
163+
"ECDHE-ECDSA-AES128-GCM-SHA256",
164+
"ECDHE-RSA-AES128-GCM-SHA256",
165+
"ECDHE-ECDSA-AES256-GCM-SHA384",
166+
"ECDHE-RSA-AES256-GCM-SHA384",
167+
"ECDHE-ECDSA-CHACHA20-POLY1305",
168+
"ECDHE-RSA-CHACHA20-POLY1305",
169+
"DHE-RSA-AES128-GCM-SHA256",
170+
"DHE-RSA-AES256-GCM-SHA384",
171+
"DHE-RSA-CHACHA20-POLY1305",
172+
"ECDHE-ECDSA-AES128-SHA256",
173+
"ECDHE-RSA-AES128-SHA256",
174+
"ECDHE-ECDSA-AES128-SHA",
175+
"ECDHE-RSA-AES128-SHA",
176+
"ECDHE-ECDSA-AES256-SHA384",
177+
"ECDHE-RSA-AES256-SHA384",
178+
"ECDHE-ECDSA-AES256-SHA",
179+
"ECDHE-RSA-AES256-SHA",
180+
"DHE-RSA-AES128-SHA256",
181+
"DHE-RSA-AES256-SHA256",
182+
"AES128-GCM-SHA256",
183+
"AES256-GCM-SHA384",
184+
"AES128-SHA256",
185+
"AES256-SHA256",
186+
"AES128-SHA",
187+
"AES256-SHA",
188+
"DES-CBC3-SHA"
189+
]
190+
},
191+
"ciphersuites": [
192+
"TLS_AES_128_GCM_SHA256",
193+
"TLS_AES_256_GCM_SHA384",
194+
"TLS_CHACHA20_POLY1305_SHA256"
195+
],
196+
"dh_param_size": 1024,
197+
"ecdh_param_size": 256,
198+
"hsts_min_age": 63072000,
199+
"maximum_certificate_lifespan": 366,
200+
"ocsp_staple": true,
201+
"oldest_clients": ["Firefox 1", "Android 2.3", "Chrome 1", "Edge 12", "IE8 on Windows XP", "Java 6", "OpenSSL 0.9.8", "Opera 5", "Safari 1"],
202+
"recommended_certificate_lifespan": 90,
203+
"rsa_key_size": 2048,
204+
"server_preferred_order": true,
205+
"tls_curves": ["X25519", "prime256v1", "secp384r1"],
206+
"tls_versions": ["TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"]
207+
}
208+
}
209+
}

0 commit comments

Comments
 (0)