Skip to content

Commit fbafd47

Browse files
tmshortclaude
andcommitted
Preserve Mozilla v5.8 old TLS profile and harden update script
- Move oldTLSProfile to a static old_profile.go (removed from v6+ spec) - Add version-based early exit to update-tls-profiles.sh - Add profile existence and tls_versions field validation in script - Add unit tests for old profile content and X25519MLKEM768 curve Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Todd Short <tshort@redhat.com>
1 parent 4510b1b commit fbafd47

4 files changed

Lines changed: 152 additions & 49 deletions

File tree

hack/tools/update-tls-profiles.sh

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,37 +8,91 @@ if [ -z "${JQ}" ]; then
88
fi
99

1010
OUTPUT=internal/shared/util/tlsprofiles/mozilla_data.go
11-
INPUT=https://ssl-config.mozilla.org/guidelines/5.8.json
11+
INPUT=https://ssl-config.mozilla.org/guidelines/latest.json
1212

1313
TMPFILE="$(mktemp)"
1414
trap 'rm -rf "$TMPFILE"' EXIT
1515

1616
curl -L -s ${INPUT} > ${TMPFILE}
1717

18-
version=$(${JQ} -r '.version' ${TMPFILE})
18+
# Extract stored version from current output file (may be empty on first run)
19+
STORED_VERSION=$(grep '^// DATA VERSION:' "${OUTPUT}" 2>/dev/null | awk '{print $4}' || true)
20+
21+
# Extract version from downloaded JSON and fail early if missing
22+
NEW_VERSION=$(${JQ} -r '.version' ${TMPFILE})
23+
if [ -z "${NEW_VERSION}" ] || [ "${NEW_VERSION}" = "null" ]; then
24+
echo "ERROR: Could not read .version from ${INPUT}" >&2
25+
exit 1
26+
fi
27+
28+
if [ "${NEW_VERSION}" = "${STORED_VERSION}" ]; then
29+
echo "Mozilla TLS data is already at version ${NEW_VERSION}, skipping regeneration."
30+
exit 0
31+
fi
32+
echo "Updating Mozilla TLS data from version ${STORED_VERSION:-unknown} to ${NEW_VERSION}"
1933

2034
cat > ${OUTPUT} <<EOF
2135
package tlsprofiles
2236
2337
// DO NOT EDIT, GENERATED BY ${0}
2438
// DATA SOURCE: ${INPUT}
25-
// DATA VERSION: ${version}
39+
// DATA VERSION: ${NEW_VERSION}
2640
2741
import (
2842
"crypto/tls"
2943
)
3044
EOF
3145

3246
function generate_profile {
47+
local profile="$1"
48+
49+
# Validate the profile key exists before writing any output
50+
local exists
51+
exists=$(${JQ} -r ".configurations | has(\"${profile}\")" "${TMPFILE}")
52+
if [ "${exists}" != "true" ]; then
53+
echo "ERROR: Profile '${profile}' not found in ${INPUT} (version ${NEW_VERSION})" >&2
54+
echo "Available profiles: $(${JQ} -r '.configurations | keys | join(", ")' "${TMPFILE}")" >&2
55+
exit 1
56+
fi
57+
58+
# Validate tls_versions exists (required for Go code generation)
59+
local has_versions
60+
has_versions=$(${JQ} -r ".configurations.${profile} | has(\"tls_versions\")" "${TMPFILE}")
61+
if [ "${has_versions}" != "true" ]; then
62+
echo "ERROR: Profile '${profile}' is missing required field 'tls_versions'" >&2
63+
exit 1
64+
fi
65+
66+
# Validate that at least one cipher is present across ciphersuites and ciphers.iana
67+
# (modern has only ciphersuites; intermediate has both; either alone is valid)
68+
local cipher_count
69+
cipher_count=$(${JQ} -r "
70+
[
71+
(.configurations.${profile}.ciphersuites // []),
72+
(.configurations.${profile}.ciphers.iana // [])
73+
] | add | length" "${TMPFILE}")
74+
if [ "${cipher_count}" -eq 0 ] 2>/dev/null; then
75+
echo "ERROR: Profile '${profile}' has no ciphers in ciphersuites or ciphers.iana" >&2
76+
exit 1
77+
fi
78+
79+
# Validate tls_curves is non-empty
80+
local curve_count
81+
curve_count=$(${JQ} -r ".configurations.${profile}.tls_curves | length" "${TMPFILE}")
82+
if [ "${curve_count}" -eq 0 ] 2>/dev/null; then
83+
echo "ERROR: Profile '${profile}' has no entries in tls_curves" >&2
84+
exit 1
85+
fi
86+
3387
cat >> ${OUTPUT} <<EOF
3488
35-
var ${1}TLSProfile = tlsProfile{
89+
var ${profile}TLSProfile = tlsProfile{
3690
ciphers: cipherSlice{
3791
cipherNums: []uint16{
3892
EOF
3993

40-
${JQ} -r ".configurations.$1.ciphersuites.[] | . |= \"tls.\" + . + \",\"" ${TMPFILE} >> ${OUTPUT}
41-
${JQ} -r ".configurations.$1.ciphers.iana[] | . |= \"tls.\" + . + \",\"" ${TMPFILE} >> ${OUTPUT}
94+
${JQ} -r ".configurations.$profile.ciphersuites.[] | . |= \"tls.\" + . + \",\"" ${TMPFILE} >> ${OUTPUT}
95+
${JQ} -r ".configurations.$profile.ciphers.iana[] | . |= \"tls.\" + . + \",\"" ${TMPFILE} >> ${OUTPUT}
4296

4397
cat >> ${OUTPUT} <<EOF
4498
},
@@ -47,9 +101,9 @@ curves: curveSlice{
47101
curveNums: []tls.CurveID{
48102
EOF
49103

50-
${JQ} -r ".configurations.$1.tls_curves[] | . |= . + \",\"" ${TMPFILE} >> ${OUTPUT}
104+
${JQ} -r ".configurations.$profile.tls_curves[] | . |= . + \",\"" ${TMPFILE} >> ${OUTPUT}
51105

52-
version=$(${JQ} -r ".configurations.$1.tls_versions[0]" ${TMPFILE})
106+
version=$(${JQ} -r ".configurations.$profile.tls_versions[0]" ${TMPFILE})
53107
version=${version/TLSv1./tls.VersionTLS1}
54108
version=${version/TLSv1/tls.VersionTLS10}
55109

@@ -63,9 +117,8 @@ EOF
63117

64118
generate_profile "modern"
65119
generate_profile "intermediate"
66-
generate_profile "old"
67120

68-
# Remove unsupported ciphers from Go's crypto/tls package (Mozilla v5.8 includes these but Go doesn't support them)
121+
# Remove unsupported ciphers from Go's crypto/tls package
69122
sed -i.bak '/TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384/d; /TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384/d; /TLS_RSA_WITH_AES_256_CBC_SHA256/d' ${OUTPUT}
70123
rm -f ${OUTPUT}.bak
71124

internal/shared/util/tlsprofiles/mozilla_data.go

Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package tlsprofiles
22

33
// DO NOT EDIT, GENERATED BY hack/tools/update-tls-profiles.sh
4-
// DATA SOURCE: https://ssl-config.mozilla.org/guidelines/5.8.json
5-
// DATA VERSION: 5.8
4+
// DATA SOURCE: https://ssl-config.mozilla.org/guidelines/latest.json
5+
// DATA VERSION: 6
66

77
import (
88
"crypto/tls"
@@ -51,40 +51,3 @@ var intermediateTLSProfile = tlsProfile{
5151
},
5252
minTLSVersion: tls.VersionTLS12,
5353
}
54-
55-
var oldTLSProfile = tlsProfile{
56-
ciphers: cipherSlice{
57-
cipherNums: []uint16{
58-
tls.TLS_AES_128_GCM_SHA256,
59-
tls.TLS_AES_256_GCM_SHA384,
60-
tls.TLS_CHACHA20_POLY1305_SHA256,
61-
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
62-
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
63-
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
64-
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
65-
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
66-
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
67-
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
68-
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
69-
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
70-
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
71-
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
72-
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
73-
tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
74-
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
75-
tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
76-
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
77-
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
78-
tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
79-
},
80-
},
81-
curves: curveSlice{
82-
curveNums: []tls.CurveID{
83-
X25519MLKEM768,
84-
X25519,
85-
prime256v1,
86-
secp384r1,
87-
},
88-
},
89-
minTLSVersion: tls.VersionTLS10,
90-
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package tlsprofiles
2+
3+
// This file contains a static copy of the Mozilla "old" TLS compatibility profile
4+
// from version 5.8 of the SSL Configuration Generator. The "old" profile was
5+
// removed from the Mozilla specification in later versions but is preserved here
6+
// for backward compatibility with older clients.
7+
//
8+
// Source: https://ssl-config.mozilla.org/guidelines/5.8.json
9+
10+
import "crypto/tls"
11+
12+
var oldTLSProfile = tlsProfile{
13+
ciphers: cipherSlice{
14+
cipherNums: []uint16{
15+
tls.TLS_AES_128_GCM_SHA256,
16+
tls.TLS_AES_256_GCM_SHA384,
17+
tls.TLS_CHACHA20_POLY1305_SHA256,
18+
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
19+
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
20+
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
21+
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
22+
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
23+
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
24+
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
25+
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
26+
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
27+
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
28+
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
29+
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
30+
tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
31+
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
32+
tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
33+
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
34+
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
35+
tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
36+
},
37+
},
38+
curves: curveSlice{
39+
curveNums: []tls.CurveID{
40+
X25519MLKEM768,
41+
X25519,
42+
prime256v1,
43+
secp384r1,
44+
},
45+
},
46+
minTLSVersion: tls.VersionTLS10,
47+
}

internal/shared/util/tlsprofiles/tlsprofiles_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package tlsprofiles
22

33
import (
4+
"crypto/tls"
45
"testing"
56

67
"github.com/stretchr/testify/require"
@@ -114,6 +115,7 @@ func TestSetCustomCurves(t *testing.T) {
114115
name string
115116
result bool
116117
}{
118+
{"X25519MLKEM768", true}, // Post-quantum hybrid curve (Go 1.24+)
117119
{"X25519", true},
118120
{"prime256v1", true},
119121
{"secp384r1", true},
@@ -158,3 +160,41 @@ func TestSetCustomVersion(t *testing.T) {
158160
}
159161
}
160162
}
163+
164+
func TestOldProfileMinVersion(t *testing.T) {
165+
require.Equal(t, tls.VersionTLS10, int(oldTLSProfile.minTLSVersion))
166+
}
167+
168+
func TestOldProfileCiphers(t *testing.T) {
169+
ciphers := oldTLSProfile.ciphers.cipherNums
170+
// v5.8 old profile has exactly 21 ciphers
171+
require.Len(t, ciphers, 21, "old profile cipher count changed unexpectedly")
172+
// Legacy CBC cipher present in old but not modern/intermediate
173+
require.Contains(t, ciphers, uint16(tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA))
174+
// Most legacy cipher (3DES insecure)
175+
require.Contains(t, ciphers, uint16(tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA))
176+
// TLS 1.3 cipher shared with all profiles
177+
require.Contains(t, ciphers, uint16(tls.TLS_AES_128_GCM_SHA256))
178+
}
179+
180+
func TestProfilesMapCompleteness(t *testing.T) {
181+
for _, name := range []string{"modern", "intermediate", "old", "custom"} {
182+
p, err := findTLSProfile(tlsProfileName(name))
183+
require.NoErrorf(t, err, "profile %q must be in profiles map", name)
184+
require.NotNilf(t, p, "profile %q must not be nil", name)
185+
}
186+
require.Len(t, profiles, 4, "unexpected profile count; update test if adding a new profile")
187+
}
188+
189+
// TestX25519MLKEM768InProfiles verifies that the post-quantum hybrid curve
190+
// X25519MLKEM768 (added in Go 1.24) is present in all three named profiles.
191+
// This curve is part of the Mozilla recommendation and its presence should be
192+
// guarded against accidental removal.
193+
func TestX25519MLKEM768InProfiles(t *testing.T) {
194+
for _, name := range []string{"modern", "intermediate", "old"} {
195+
p, err := findTLSProfile(tlsProfileName(name))
196+
require.NoError(t, err)
197+
require.Containsf(t, p.curves.curveNums, tls.CurveID(X25519MLKEM768),
198+
"profile %q must include X25519MLKEM768", name)
199+
}
200+
}

0 commit comments

Comments
 (0)