Cover unsafe TLS material reuse

This commit is contained in:
Mario Fetka
2026-07-22 16:23:05 +02:00
parent d63d6acd80
commit 045f93c49e
3 changed files with 208 additions and 1 deletions
+177
View File
@@ -0,0 +1,177 @@
#!/bin/sh
# This program is free software, licensed under the terms of the GNU GPL.
# See the Bongo COPYING file for full details.
# Copyright (c) 2026 Bongo Project contributors
set -eu
export LC_ALL=C
if [ "$#" -ne 2 ]; then
echo "Usage: $0 BONGO_CONFIG TEMPORARY_SSL_DIRECTORY" >&2
exit 64
fi
binary=$1
directory=$2
case "$directory" in
/tmp/*) ;;
*)
echo "Refusing a TLS test directory outside /tmp: $directory" >&2
exit 64
;;
esac
if [ ! -x "$binary" ]; then
echo "Not an executable bongo-config test binary: $binary" >&2
exit 64
fi
certificate="$directory/server.crt"
key="$directory/server.key"
good_certificate="$directory/server.crt.test-good"
good_key="$directory/server.key.test-good"
wrong_key="$directory/server.key.test-wrong"
for path in "$certificate" "$key" "$good_certificate" "$good_key" "$wrong_key"; do
if [ -e "$path" ] || [ -L "$path" ]; then
echo "Refusing to replace pre-existing test input: $path" >&2
exit 64
fi
done
mkdir -p "$directory"
cleanup()
{
rm -f "$certificate" "$key" "$good_certificate" "$good_key" "$wrong_key"
}
trap cleanup EXIT HUP INT TERM
run_crypto()
{
unshare --user --map-root-user "$binary" \
--hostname mail.bongo.test \
--tls-name mail.bongo.test \
--tls-name smtp.bongo.test crypto
}
restore_pair()
{
rm -f "$certificate" "$key"
cp "$good_certificate" "$certificate"
cp "$good_key" "$key"
chmod 0644 "$certificate"
chmod 0640 "$key"
}
expect_failure()
{
label=$1
expected=$2
before=$(sha256sum "$certificate" 2>/dev/null || true)
before="$before|$(sha256sum "$key" 2>/dev/null || true)"
set +e
output=$(run_crypto 2>&1)
status=$?
set -e
after=$(sha256sum "$certificate" 2>/dev/null || true)
after="$after|$(sha256sum "$key" 2>/dev/null || true)"
if [ "$status" -ne 6 ] ||
! printf '%s' "$output" | grep -F "$expected" >/dev/null ||
[ "$before" != "$after" ]; then
printf 'FAIL %s: status=%s output=%s\n' "$label" "$status" "$output" >&2
exit 1
fi
printf 'PASS %s: rejected without changing input files\n' "$label"
}
run_crypto | grep -F 'Crypto data generated.' >/dev/null
permissions=$(unshare --user --map-root-user stat -c '%a %u:%g' \
"$directory" "$certificate" "$key")
expected_permissions='750 0:0
644 0:0
640 0:0'
if [ "$permissions" != "$expected_permissions" ]; then
printf 'FAIL TLS permissions:\n%s\n' "$permissions" >&2
exit 1
fi
information=$(certtool --certificate-info --infile "$certificate" 2>&1)
for expected in \
'Version: 3' \
'Algorithm Security Level: High (3072 bits)' \
'DNSname: mail.bongo.test' \
'DNSname: smtp.bongo.test' \
'Certificate Authority (CA): FALSE' \
'Digital signature.' \
'Key encipherment.' \
'TLS WWW Server.' \
'Signature Algorithm: RSA-SHA256'; do
if ! printf '%s' "$information" | grep -F "$expected" >/dev/null; then
printf 'FAIL generated certificate lacks: %s\n' "$expected" >&2
exit 1
fi
done
certificate_id=$(certtool --pubkey-info --load-certificate "$certificate" 2>&1 |
sed -n 's/^[[:space:]]*sha256://p' | head -n 1)
key_id=$(certtool --pubkey-info --load-privkey "$key" 2>&1 |
sed -n 's/^[[:space:]]*sha256://p' | head -n 1)
if [ -z "$certificate_id" ] || [ "$certificate_id" != "$key_id" ]; then
echo 'FAIL generated certificate and private key do not match' >&2
exit 1
fi
first_hashes=$(sha256sum "$certificate" "$key")
run_crypto | grep -F 'Reusing existing TLS certificate and key' >/dev/null
second_hashes=$(sha256sum "$certificate" "$key")
if [ "$first_hashes" != "$second_hashes" ]; then
echo 'FAIL a valid existing TLS pair was replaced' >&2
exit 1
fi
printf 'PASS generated TLS material and byte-identical reuse\n'
cp "$certificate" "$good_certificate"
cp "$key" "$good_key"
certtool --generate-privkey --key-type rsa --bits 3072 \
--outfile "$wrong_key" >/dev/null 2>&1
chmod 0640 "$wrong_key"
cp "$wrong_key" "$key"
expect_failure 'mismatched private key' 'do not form a valid pair'
restore_pair
rm -f "$key"
expect_failure 'incomplete pair' 'Unsafe or incomplete TLS key pair'
restore_pair
chmod 0666 "$key"
expect_failure 'unsafe private-key mode' 'Unsafe or incomplete TLS key pair'
restore_pair
rm -f "$key"
ln -s "$good_key" "$key"
expect_failure 'private-key symlink' 'Unsafe or incomplete TLS key pair'
restore_pair
rm -f "$certificate"
ln -s "$good_certificate" "$certificate"
expect_failure 'certificate symlink' 'Unsafe or incomplete TLS key pair'
restore_pair
printf '%s\n' 'not a certificate' >"$certificate"
expect_failure 'malformed certificate' 'do not form a valid pair'
restore_pair
before=$(sha256sum "$certificate" "$key")
set +e
output=$(unshare --user --map-root-user "$binary" \
--hostname mail.bongo.test --tls-name pop.bongo.test crypto 2>&1)
status=$?
set -e
after=$(sha256sum "$certificate" "$key")
if [ "$status" -ne 6 ] ||
! printf '%s' "$output" |
grep -F 'does not cover DNS name pop.bongo.test' >/dev/null ||
[ "$before" != "$after" ]; then
printf 'FAIL missing SAN: status=%s output=%s\n' "$status" "$output" >&2
exit 1
fi
printf 'PASS missing SAN: rejected without changing input files\n'
+1 -1
View File
@@ -94,7 +94,7 @@ inputs and outputs is absent from the ZIP.
| CFG-07 | Scanner editor preserves unrelated settings and restores on failure | [PASS](test-evidence/0.7-r1.md#cfg-07) | | |
| CFG-08 | Reverse-proxy, HAProxy, direct HTTP, and direct HTTPS validation paths | [PASS](test-evidence/0.7-r1.md#cfg-08) | | |
| CFG-09 | Self-signed GnuTLS certificate/key generation and permission checks | [PASS](test-evidence/0.7-r1.md#cfg-09) | | |
| CFG-10 | Existing certificate/key validation and safe rejection of broken pairs | | | |
| CFG-10 | Existing certificate/key validation and safe rejection of broken pairs | [PASS](test-evidence/0.7-r1.md#cfg-10) | | |
| CFG-11 | DKIM/SPF/DMARC discovery, key import/generation, DNS hints, and task | | | |
| CFG-12 | Manager start, reload, restart, clean stop, stale PID, and crash recovery | | | |
| CFG-13 | Every agent starts as intended and drops privileges | | | |
+30
View File
@@ -1064,3 +1064,33 @@ CTest run passed all 82 tests in 7.01 seconds.
Runtime configuration: isolated build and disposable TLS/state roots below
`/tmp`; no live certificate, Store document, listener, or service state was
changed. The private key was not committed.
## CFG-10
Result: **PASS**
The repeatable `contrib/testing/test-config-tls-material.sh` integration test
ran the real `bongo-config crypto` executable with compile-time TLS paths below
`/tmp`. It first confirmed that a matching certificate/key pair covering all
requested names is reused. It then independently injected a mismatched private
key, an incomplete pair, a world-writable private key, private-key and
certificate symlinks, malformed certificate data, and a requested DNS name
which the otherwise valid certificate did not cover.
All seven unsafe or invalid states returned the documented crypto failure
status and a specific diagnostic. SHA-256 snapshots before and after every
validation failure were identical, including the missing-file state of the
incomplete-pair case: validation never replaced, truncated, or removed an
operator-supplied input. The script also rejects test directories outside
`/tmp` and refuses any pre-existing TLS input before it starts.
- Source under test: commit
`6e03d3a65d5d5d91601503dea7ade35c0bb19dd3`.
- Result: valid-pair reuse plus seven rejection cases passed.
- Regression coverage: multiple SANs, key/certificate identity, X.509
properties, file modes, byte-identical reuse, pair integrity, symlink and
permission safety, malformed PEM, and hostname coverage.
Runtime configuration: private user namespace and disposable paths below
`/tmp`. The harness removed all generated private-key material on exit and did
not read or change the live Bongo TLS pair.