mirror of
https://github.com/project-zot/zot.git
synced 2026-06-17 12:58:02 +08:00
fix: separate cipher suites and curve preferences into FIPS and non FIPS, and use them accordingly (#3523)
See: https://github.com/project-zot/zot/actions/runs/19209741002/job/54910194536 `failed to ping registry localhost:11448: Get "https://localhost:11448/v2/": crypto/ecdh: use of X25519 is not allowed in FIPS 140-only mode` Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
This commit is contained in:
@@ -12,7 +12,15 @@ permissions: read-all
|
||||
jobs:
|
||||
tls-check:
|
||||
runs-on: ubuntu-latest
|
||||
name: TLS check
|
||||
strategy:
|
||||
matrix:
|
||||
mode: [non-fips, fips]
|
||||
include:
|
||||
- mode: non-fips
|
||||
godebug: ""
|
||||
- mode: fips
|
||||
godebug: "fips140=only"
|
||||
name: TLS check (${{ matrix.mode }})
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v6
|
||||
@@ -25,15 +33,31 @@ jobs:
|
||||
mkdir -p test/data
|
||||
cd test/data
|
||||
../scripts/gen_certs.sh
|
||||
- name: Check for TLS settings
|
||||
- name: Build binary
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE
|
||||
make binary
|
||||
- name: Start zot server (${{ matrix.mode }})
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE
|
||||
if [[ -n "${{ matrix.godebug }}" ]]; then
|
||||
export GODEBUG="${{ matrix.godebug }}"
|
||||
fi
|
||||
bin/zot-linux-amd64 serve examples/config-tls.json & echo $! > zot.PID
|
||||
if [[ -n "${{ matrix.godebug }}" ]]; then
|
||||
unset GODEBUG
|
||||
fi
|
||||
sleep 5
|
||||
# Check if zot server is running
|
||||
cat /proc/$(cat zot.PID)/status | grep State || exit 1
|
||||
curl -k --connect-timeout 3 --max-time 5 --retry 60 --retry-delay 1 --retry-max-time 180 --retry-connrefused https://localhost:8080/v2/
|
||||
|
||||
# zot server is running: proceed to testing
|
||||
- name: Run TLS tests (${{ matrix.mode }})
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE
|
||||
./test/scripts/tls_scan.sh
|
||||
./test/scripts/tls_cipher_check.sh ${{ matrix.mode }} localhost:8080
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE
|
||||
[[ -f zot.PID ]] && kill $(cat zot.PID) 2>/dev/null || true
|
||||
|
||||
+31
-13
@@ -204,21 +204,39 @@ func (c *Controller) Run() error {
|
||||
|
||||
tlsConfig := c.Config.CopyTLSConfig()
|
||||
if tlsConfig != nil && tlsConfig.Key != "" && tlsConfig.Cert != "" {
|
||||
server.TLSConfig = &tls.Config{
|
||||
CipherSuites: []uint16{
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
// These are the same as the cipher suites in defaultCipherSuitesFIPS for TLS 1.2
|
||||
// see https://cs.opensource.google/go/go/+/refs/tags/go1.24.9:src/crypto/tls/defaults.go;l=123
|
||||
// Note: Order doesn't matter - Go 1.17+ automatically orders cipher suites based on
|
||||
// hardware capabilities and security properties. See https://go.dev/blog/tls-cipher-suites
|
||||
cipherSuites := []uint16{
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
}
|
||||
if !fips140.Enabled() {
|
||||
// CHACHA20_POLY1305 is not FIPS-compliant
|
||||
cipherSuites = append(cipherSuites,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
},
|
||||
CurvePreferences: []tls.CurveID{
|
||||
tls.CurveP256,
|
||||
tls.X25519,
|
||||
},
|
||||
PreferServerCipherSuites: true,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
)
|
||||
}
|
||||
|
||||
// This is a subset of the default curve preferences in defaultCurvePreferencesFIPS for TLS 1.2
|
||||
// see https://cs.opensource.google/go/go/+/refs/tags/go1.24.9:src/crypto/tls/defaults.go;l=106
|
||||
curvePreferences := []tls.CurveID{
|
||||
tls.CurveP256,
|
||||
}
|
||||
if !fips140.Enabled() {
|
||||
// X25519 is not FIPS-compliant
|
||||
curvePreferences = append(curvePreferences, tls.X25519)
|
||||
}
|
||||
|
||||
server.TLSConfig = &tls.Config{
|
||||
CipherSuites: cipherSuites,
|
||||
CurvePreferences: curvePreferences,
|
||||
// PreferServerCipherSuites is ignored in Go 1.17+ - Go automatically orders cipher suites
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
|
||||
if tlsConfig.CACert != "" {
|
||||
|
||||
Executable
+185
@@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Script to check TLS cipher suites used in FIPS vs non-FIPS mode
|
||||
# Usage: ./tls_cipher_check.sh [fips|non-fips] [host:port]
|
||||
|
||||
set -e
|
||||
|
||||
MODE="${1:-non-fips}"
|
||||
HOST="${2:-localhost:8080}"
|
||||
|
||||
# FIPS-compliant cipher suites (TLS 1.2)
|
||||
# See https://cs.opensource.google/go/go/+/refs/tags/go1.24.9:src/crypto/tls/defaults.go;l=123
|
||||
FIPS_TLS12_CIPHERS=(
|
||||
"ECDHE-ECDSA-AES128-GCM-SHA256"
|
||||
"ECDHE-ECDSA-AES256-GCM-SHA384"
|
||||
"ECDHE-RSA-AES128-GCM-SHA256"
|
||||
"ECDHE-RSA-AES256-GCM-SHA384"
|
||||
)
|
||||
|
||||
# FIPS-compliant cipher suites (TLS 1.3)
|
||||
# See https://cs.opensource.google/go/go/+/refs/tags/go1.24.9:src/crypto/tls/defaults.go;l=131
|
||||
FIPS_TLS13_CIPHERS=(
|
||||
"TLS_AES_128_GCM_SHA256"
|
||||
"TLS_AES_256_GCM_SHA384"
|
||||
)
|
||||
|
||||
# Non-FIPS cipher suites (TLS 1.2)
|
||||
NON_FIPS_TLS12_CIPHERS=(
|
||||
"ECDHE-ECDSA-CHACHA20-POLY1305"
|
||||
"ECDHE-RSA-CHACHA20-POLY1305"
|
||||
)
|
||||
|
||||
# Non-FIPS cipher suites (TLS 1.3)
|
||||
NON_FIPS_TLS13_CIPHERS=(
|
||||
"TLS_CHACHA20_POLY1305_SHA256"
|
||||
)
|
||||
|
||||
echo "=== TLS Cipher Suite Check (Mode: $MODE) ==="
|
||||
echo "Testing connection to: $HOST"
|
||||
echo ""
|
||||
|
||||
# Test a specific cipher suite
|
||||
# Returns 0 if connection succeeds, 1 if it fails
|
||||
test_cipher() {
|
||||
local tls_version=$1
|
||||
local cipher=$2
|
||||
local output
|
||||
|
||||
# -no_ticket disables TLS session tickets to ensure each connection performs
|
||||
# a full handshake, providing consistent and accurate cipher suite detection
|
||||
if [[ "$tls_version" == "1.2" ]]; then
|
||||
output=$(echo | openssl s_client -connect "$HOST" -tls1_2 -cipher "$cipher" -no_ticket 2>&1 || true)
|
||||
elif [[ "$tls_version" == "1.3" ]]; then
|
||||
output=$(echo | openssl s_client -connect "$HOST" -tls1_3 -ciphersuites "$cipher" -no_ticket 2>&1 || true)
|
||||
else
|
||||
echo "Unknown TLS version: $tls_version"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Output openssl output for debugging
|
||||
echo "Debug: Testing TLS $tls_version cipher '$cipher':"
|
||||
echo "----------------------------------------"
|
||||
echo "$output"
|
||||
echo "----------------------------------------"
|
||||
|
||||
# Check if handshake completed successfully
|
||||
# Successful handshakes show "New, TLSv1.2" or "New, TLSv1.3" with a cipher
|
||||
# Failed handshakes show "New, (NONE), Cipher is (NONE)" or "Cipher : 0000"
|
||||
if echo "$output" | grep -qE "New, TLSv[0-9]"; then
|
||||
# Verify the cipher was actually used and is not (NONE)
|
||||
if echo "$output" | grep -qiE "Cipher is.*$cipher|Cipher\s*:.*$cipher" && \
|
||||
! echo "$output" | grep -qiE "Cipher is.*\(NONE\)|Cipher\s*:\s*0000"; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Test TLS 1.2 FIPS ciphers
|
||||
echo "--- Testing TLS 1.2 FIPS-compliant cipher suites ---"
|
||||
TLS12_FIPS_PASSED=0
|
||||
for cipher in "${FIPS_TLS12_CIPHERS[@]}"; do
|
||||
if test_cipher "1.2" "$cipher"; then
|
||||
echo "✓ TLS 1.2 FIPS cipher '$cipher': SUCCESS"
|
||||
TLS12_FIPS_PASSED=$((TLS12_FIPS_PASSED + 1))
|
||||
else
|
||||
echo "✗ TLS 1.2 FIPS cipher '$cipher': FAILED"
|
||||
fi
|
||||
done
|
||||
|
||||
# In FIPS mode, require at least one TLS 1.2 FIPS cipher to work.
|
||||
# We can't require ALL TLS 1.2 FIPS ciphers because certificate type determines which ones work:
|
||||
# - RSA certificates: only RSA-based ciphers (ECDHE-RSA-*) work
|
||||
# - ECDSA certificates: only ECDSA-based ciphers (ECDHE-ECDSA-*) work
|
||||
# If none work, it indicates a configuration issue or a certificate mismatch.
|
||||
if [[ "$MODE" == "fips" ]] && [[ $TLS12_FIPS_PASSED -eq 0 ]]; then
|
||||
echo ""
|
||||
echo "✗ ERROR: No TLS 1.2 FIPS-compliant cipher suites work (expected at least 1)"
|
||||
echo " This may indicate a certificate mismatch or configuration issue"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test TLS 1.3 FIPS ciphers - all must work in both FIPS and non-FIPS modes
|
||||
echo ""
|
||||
echo "--- Testing TLS 1.3 FIPS-compliant cipher suites ---"
|
||||
TLS13_FIPS_PASSED=0
|
||||
for cipher in "${FIPS_TLS13_CIPHERS[@]}"; do
|
||||
if test_cipher "1.3" "$cipher"; then
|
||||
echo "✓ TLS 1.3 FIPS cipher '$cipher': SUCCESS"
|
||||
TLS13_FIPS_PASSED=$((TLS13_FIPS_PASSED + 1))
|
||||
else
|
||||
echo "✗ TLS 1.3 FIPS cipher '$cipher': FAILED"
|
||||
echo ""
|
||||
echo "✗ ERROR: Required TLS 1.3 FIPS cipher '$cipher' failed"
|
||||
echo " TLS 1.3 FIPS ciphers must work in both FIPS and non-FIPS modes"
|
||||
echo " This may indicate a configuration issue (e.g., TLS 1.3 disabled)"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Test TLS 1.2 non-FIPS ciphers - must fail in FIPS mode
|
||||
echo ""
|
||||
echo "--- Testing TLS 1.2 non-FIPS cipher suites ---"
|
||||
for cipher in "${NON_FIPS_TLS12_CIPHERS[@]}"; do
|
||||
if test_cipher "1.2" "$cipher"; then
|
||||
echo "✗ TLS 1.2 non-FIPS cipher '$cipher': SUCCESS (should fail in FIPS mode)"
|
||||
if [[ "$MODE" == "fips" ]]; then
|
||||
echo ""
|
||||
echo "✗ ERROR: Non-FIPS cipher '$cipher' was accepted (should be rejected in FIPS mode)"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "✓ TLS 1.2 non-FIPS cipher '$cipher': FAILED (expected in FIPS mode)"
|
||||
fi
|
||||
done
|
||||
|
||||
# Test TLS 1.3 non-FIPS ciphers - must fail in FIPS mode
|
||||
echo ""
|
||||
echo "--- Testing TLS 1.3 non-FIPS cipher suites ---"
|
||||
for cipher in "${NON_FIPS_TLS13_CIPHERS[@]}"; do
|
||||
if test_cipher "1.3" "$cipher"; then
|
||||
echo "✗ TLS 1.3 non-FIPS cipher '$cipher': SUCCESS (should fail in FIPS mode)"
|
||||
if [[ "$MODE" == "fips" ]]; then
|
||||
echo ""
|
||||
echo "✗ ERROR: Non-FIPS cipher '$cipher' was accepted (should be rejected in FIPS mode)"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "✓ TLS 1.3 non-FIPS cipher '$cipher': FAILED (expected in FIPS mode)"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== Verification Results ==="
|
||||
|
||||
# Summary for FIPS mode
|
||||
if [[ "$MODE" == "fips" ]]; then
|
||||
echo "FIPS Mode: Summary..."
|
||||
echo " TLS 1.2 FIPS: $TLS12_FIPS_PASSED/${#FIPS_TLS12_CIPHERS[@]} passed (at least 1 required)"
|
||||
echo " TLS 1.3 FIPS: $TLS13_FIPS_PASSED/${#FIPS_TLS13_CIPHERS[@]} passed (all required)"
|
||||
echo ""
|
||||
echo "Note: TLS 1.2 cipher suites depend on certificate type:"
|
||||
echo " - RSA certificates: only RSA-based ciphers (ECDHE-RSA-*) work"
|
||||
echo " - ECDSA certificates: only ECDSA-based ciphers (ECDHE-ECDSA-*) work"
|
||||
echo " - TLS 1.3 cipher suites work with any certificate type"
|
||||
echo "Note: All non-FIPS cipher suites were correctly rejected"
|
||||
fi
|
||||
|
||||
# Summary for non-FIPS mode
|
||||
if [[ "$MODE" == "non-fips" ]]; then
|
||||
echo "Non-FIPS Mode: Summary..."
|
||||
echo " TLS 1.2 FIPS: $TLS12_FIPS_PASSED/${#FIPS_TLS12_CIPHERS[@]} passed"
|
||||
echo " TLS 1.3 FIPS: $TLS13_FIPS_PASSED/${#FIPS_TLS13_CIPHERS[@]} passed"
|
||||
echo ""
|
||||
echo "Note: Non-FIPS mode should accept both FIPS and non-FIPS cipher suites"
|
||||
echo "Note: TLS 1.2 cipher suites depend on certificate type:"
|
||||
echo " - RSA certificates: only RSA-based ciphers (ECDHE-RSA-*) work"
|
||||
echo " - ECDSA certificates: only ECDSA-based ciphers (ECDHE-ECDSA-*) work"
|
||||
echo " - TLS 1.3 cipher suites work with any certificate type"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== All checks passed ==="
|
||||
exit 0
|
||||
Reference in New Issue
Block a user