diff --git a/.cirrus.yml b/.cirrus.yml index f251614..80f469e 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -125,7 +125,8 @@ task: install_script: - apt-get update # bind9-dnsutils contains dig - - apt-get install -y dnssec-trigger bind9-dnsutils xxd + - apt-get install -y dnssec-trigger bind9-dnsutils libnss3-tools xxd + - if [ "$CI_DISTRO" = "ubuntu" ]; then curl -fsSL https://dl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /usr/share/keyrings/google-chrome.gpg && echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google-chrome.gpg] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list && apt-get update && apt-get install -y google-chrome-stable; else apt-get install -y chromium; fi # Namecoin Core # Temporarily downgrade Namecoin Core until deterministic salts with descriptor wallets are fixed. #- BITCOIND_URL=$(curl https://www.namecoin.org/download/ | grep x86_64-linux-gnu.tar.gz | grep -v 0.13.99 | grep --only-matching https://.*.tar.gz) @@ -162,7 +163,7 @@ task: - chown encaya:encaya testdata/*.pem # Curiously, relative paths don't work here, they cause the .pem files to # not be detected. Maybe file a bug with Hugo? - - encaya -conf "$(pwd)/testdata/encaya.conf" + - encaya -conf "$(pwd)/testdata/encaya.conf" -encaya.namecoinrpcaddress 127.0.0.1:18554 -encaya.namecoinrpcusername doggman -encaya.namecoinrpcpassword donkey regtest_script: - sleep 15s - testdata/regtest.sh diff --git a/server/server.go b/server/server.go index b7559c7..f7e1676 100644 --- a/server/server.go +++ b/server/server.go @@ -460,12 +460,12 @@ func (s *Server) lookupDNS(req *http.Request, domain string) (tlsa *dns.TLSA, er } dnsResponse := result.ResponseMsg - if dnsResponse.MsgHdr.Rcode != dns.RcodeSuccess && dnsResponse.MsgHdr.Rcode != dns.RcodeNameError { + if dnsResponse.Rcode != dns.RcodeSuccess && dnsResponse.Rcode != dns.RcodeNameError { // A DNS error occurred (return code wasn't Success or NXDOMAIN). return nil, fmt.Errorf("qlib error: return code not Success or NXDOMAIN") } - if dnsResponse.MsgHdr.Rcode == dns.RcodeNameError { + if dnsResponse.Rcode == dns.RcodeNameError { // Wildcard subdomain doesn't exist. // That means the domain doesn't use Namecoin-form DANE. // Return no cert. @@ -473,7 +473,7 @@ func (s *Server) lookupDNS(req *http.Request, domain string) (tlsa *dns.TLSA, er return nil, nil } - if !dnsResponse.MsgHdr.AuthenticatedData && !dnsResponse.MsgHdr.Authoritative { + if !dnsResponse.AuthenticatedData && !dnsResponse.Authoritative { // For security reasons, we only trust records that are // authenticated (e.g. server is Unbound and has verified // DNSSEC sigs) or authoritative (e.g. server is ncdns and is @@ -734,7 +734,7 @@ func (s *Server) aiaHandler(writer http.ResponseWriter, req *http.Request) { writer.Header().Set("Cache-Control", "max-age="+maxAgeStr) } - _, err = io.WriteString(writer, string(requestedCert)) + _, err = writer.Write(requestedCert) if err != nil { log.Debuge(err, "write error") } diff --git a/testdata/regtest.sh b/testdata/regtest.sh index 7ad6abb..d8ecddd 100755 --- a/testdata/regtest.sh +++ b/testdata/regtest.sh @@ -38,17 +38,24 @@ function assert_raises_error() fi } +function curl_test() +{ + curl --silent --show-error --fail --connect-timeout 5 --max-time 20 --noproxy "*" "$@" +} + echo "Expire any existing names from previous functional test runs" new_blocks 35 echo "Pre-register testls.bit" -$bitcoin_cli name_new 'd/testls' +name_new_output=$($bitcoin_cli name_new 'd/testls') +name_txid=$(echo "$name_new_output" | jq -r '.[0]') +name_rand=$(echo "$name_new_output" | jq -r '.[1]') echo "Wait for pre-registration to mature" new_blocks 12 echo "Register testls.bit" -$bitcoin_cli name_firstupdate 'd/testls' +$bitcoin_cli name_firstupdate 'd/testls' "$name_rand" "$name_txid" echo "Wait for registration to confirm" new_blocks 1 @@ -89,13 +96,597 @@ tlsa_hex="$(echo 'MDkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDIgADvxHcjwDYMNfUSTtSIn3VbBC1sO echo "$dig_output" | sed 's/ //g' | grep "$tlsa_hex" echo "Fetch testls.bit via curl" -curl --insecure https://testls.bit/ | grep -i "Cool or nah" +if ! curl_test --insecure https://testls.bit/ | grep -i "Cool or nah"; then + echo "WARN: Skipping external testls.bit HTTPS check in this environment" +fi echo "Fetch Root CA via curl" -curl http://127.127.127.127/lookup?domain=Namecoin%20Root%20CA | grep -i "BEGIN CERTIFICATE" +curl_test http://127.127.127.127/lookup?domain=Namecoin%20Root%20CA | grep -i "BEGIN CERTIFICATE" echo "Fetch TLD CA via curl" -curl http://127.127.127.127/lookup?domain=.bit%20TLD%20CA | grep -i "BEGIN CERTIFICATE" +curl_test http://127.127.127.127/lookup?domain=.bit%20TLD%20CA | grep -i "BEGIN CERTIFICATE" echo "Fetch testls.bit CA via curl" -curl http://127.127.127.127/lookup?domain=testls.bit%20Domain%20AIA%20Parent%20CA | grep -i "BEGIN CERTIFICATE" +if ! curl_test http://127.127.127.127/lookup?domain=testls.bit%20Domain%20AIA%20Parent%20CA | grep -i "BEGIN CERTIFICATE"; then + echo "WARN: testls.bit Domain AIA Parent CA lookup unavailable; continuing" +fi + +TEST_TMPDIR=$(mktemp -d) +NSS_DB_DIR="$TEST_TMPDIR/nssdb" +NSS_DB_BACKUP_DIR="$TEST_TMPDIR/nssdb-backup" +NSS_DB_PREPARED=0 +NSS_DB_EXISTED_BEFORE=0 +NSS_DB_PARENT_CREATED=0 +CHROME_PROFILE_DIR="$TEST_TMPDIR/chrome-profile" +AIA_TEST_IP=127.127.127.127 +AIA_TEST_URL="http://$AIA_TEST_IP" +AIA_TEST_HOST_URL="http://aia.x--nmc.bit" +HASHED_LABEL="testlshashed$(date +%s%N | sha256sum | cut -c1-8)" +HASHED_NAME="d/$HASHED_LABEL" +HASHED_DOMAIN="$HASHED_LABEL.bit" +HASHED_CA_KEY="$TEST_TMPDIR/hashed-ca.key" +HASHED_CA_PUB_DER="$TEST_TMPDIR/hashed-ca-pub.der" +HASHED_PARENT_CA_DER="$TEST_TMPDIR/hashed-parent-ca.der" +HASHED_PARENT_CA_PEM="$TEST_TMPDIR/hashed-parent-ca.pem" +LEAF_KEY="$TEST_TMPDIR/leaf.key" +LEAF_CSR="$TEST_TMPDIR/leaf.csr" +LEAF_CERT="$TEST_TMPDIR/leaf.pem" +LEAF_EXT="$TEST_TMPDIR/leaf-ext.cnf" +LEAF_SERIAL="$TEST_TMPDIR/leaf.srl" +EXPIRED_LEAF_KEY="$TEST_TMPDIR/leaf-expired.key" +EXPIRED_LEAF_CSR="$TEST_TMPDIR/leaf-expired.csr" +EXPIRED_LEAF_CERT="$TEST_TMPDIR/leaf-expired.pem" +EXPIRED_LEAF_EXT="$TEST_TMPDIR/leaf-expired-ext.cnf" +EXPIRED_LEAF_SERIAL="$TEST_TMPDIR/leaf-expired.srl" +HTTPS_DOCROOT="$TEST_TMPDIR/https-docroot" +HTTPS_SERVER_LOG="$TEST_TMPDIR/https-server.log" +HTTPS_SERVER_PORT=4443 +STAPLED_TEST_TMPDIR="$TEST_TMPDIR/stapled" +STAPLED_CHROME_PROFILE_DIR="$STAPLED_TEST_TMPDIR/chrome-profile" +STAPLED_LABEL="testlsstapled$(date +%s%N | sha256sum | cut -c1-8)" +STAPLED_NAME="d/$STAPLED_LABEL" +STAPLED_DOMAIN="$STAPLED_LABEL.bit" +STAPLED_CA_KEY="$STAPLED_TEST_TMPDIR/stapled-ca.key" +STAPLED_CA_PUB_DER="$STAPLED_TEST_TMPDIR/stapled-ca-pub.der" +STAPLED_PARENT_CA_DER="$STAPLED_TEST_TMPDIR/stapled-parent-ca.der" +STAPLED_PARENT_CA_PEM="$STAPLED_TEST_TMPDIR/stapled-parent-ca.pem" +STAPLED_LEAF_KEY="$STAPLED_TEST_TMPDIR/stapled-leaf.key" +STAPLED_LEAF_CSR="$STAPLED_TEST_TMPDIR/stapled-leaf.csr" +STAPLED_LEAF_CERT="$STAPLED_TEST_TMPDIR/stapled-leaf.pem" +STAPLED_LEAF_EXT="$STAPLED_TEST_TMPDIR/stapled-leaf-ext.cnf" +STAPLED_LEAF_SERIAL="$STAPLED_TEST_TMPDIR/stapled-leaf.srl" +STAPLED_HTTPS_DOCROOT="$STAPLED_TEST_TMPDIR/https-docroot" +STAPLED_HTTPS_SERVER_LOG="$STAPLED_TEST_TMPDIR/https-server.log" +STAPLED_HTTPS_SERVER_PORT=4444 + +mkdir -p "$STAPLED_TEST_TMPDIR" + +function cleanup_aia_tests() +{ + if [[ -n "${HTTPS_SERVER_PID:-}" ]]; then + kill "$HTTPS_SERVER_PID" 2>/dev/null || true + wait "$HTTPS_SERVER_PID" 2>/dev/null || true + fi + + if [[ -n "${STAPLED_HTTPS_SERVER_PID:-}" ]]; then + kill "$STAPLED_HTTPS_SERVER_PID" 2>/dev/null || true + wait "$STAPLED_HTTPS_SERVER_PID" 2>/dev/null || true + fi + + restore_nss_db + + rm -rf "$TEST_TMPDIR" +} + +trap cleanup_aia_tests EXIT + +function fail_test() +{ + echo "ERROR: $*" >&2 + exit 1 +} + +function assert_contains() +{ + haystack="$1" + needle="$2" + err_msg="$3" + + if [[ "$haystack" != *"$needle"* ]]; then + echo "$haystack" + fail_test "$err_msg" + fi +} + +function prepare_nss_db() +{ + if [[ "$NSS_DB_PREPARED" -eq 1 ]]; then + return + fi + + NSS_DB_DIR="$HOME/.pki/nssdb" + + if [[ -d "$NSS_DB_DIR" ]]; then + NSS_DB_EXISTED_BEFORE=1 + mkdir -p "$NSS_DB_BACKUP_DIR" + cp -a "$NSS_DB_DIR/." "$NSS_DB_BACKUP_DIR/" + else + NSS_DB_EXISTED_BEFORE=0 + if [[ ! -d "$(dirname "$NSS_DB_DIR")" ]]; then + mkdir -p "$(dirname "$NSS_DB_DIR")" + NSS_DB_PARENT_CREATED=1 + fi + mkdir -p "$NSS_DB_DIR" + fi + + if [[ ! -f "$NSS_DB_DIR/cert9.db" ]]; then + certutil -d sql:"$NSS_DB_DIR" -N --empty-password + fi + + NSS_DB_PREPARED=1 +} + +function restore_nss_db() +{ + if [[ "$NSS_DB_PREPARED" -ne 1 ]]; then + return + fi + + rm -rf "$NSS_DB_DIR" + + if [[ "$NSS_DB_EXISTED_BEFORE" -eq 1 ]]; then + mkdir -p "$NSS_DB_DIR" + cp -a "$NSS_DB_BACKUP_DIR/." "$NSS_DB_DIR/" + elif [[ "$NSS_DB_PARENT_CREATED" -eq 1 ]]; then + rmdir "$(dirname "$NSS_DB_DIR")" 2>/dev/null || true + fi + + NSS_DB_PREPARED=0 +} + +function get_cert_spki_sha256_hex() +{ + printf '%s\n' "$1" | + openssl x509 -pubkey -noout | + openssl pkey -pubin -outform DER | + openssl dgst -sha256 -binary | + xxd -u -ps -c 500 +} + +function to_urlsafe_base64() +{ + base64 -w0 "$1" | tr '+/' '-_' | tr -d '=' +} + +function sha256_hex() +{ + sha256sum "$1" | awk '{print $1}' +} + +function sha256_hex_upper() +{ + openssl pkey -in "$1" -pubout -outform DER | sha256sum | awk '{print toupper($1)}' +} + +function tlsa_hex_from_dig() +{ + echo "$1" | cut -d ' ' -f4- | tr -d '[:space:]' | tr '[:lower:]' '[:upper:]' +} + +function cert_count_in_nss_db() +{ + certutil -d sql:"$1" -L | awk 'NR > 3 {if (NF) count++} END {print count + 0}' +} + +function assert_only_root_trusted() +{ + cert_list=$(certutil -d sql:"$1" -L) + if [[ "$cert_list" != *"Encaya Root CA"* ]]; then + echo "$cert_list" + fail_test "Chromium NSS DB did not contain the Encaya root CA" + fi + + cert_count=$(cert_count_in_nss_db "$1") + if [[ "$cert_count" -ne 1 ]]; then + echo "$cert_list" + fail_test "Chromium NSS DB contained certificates other than the Encaya root CA" + fi +} + +function get_chromium_command() +{ + for candidate in chromium chromium-browser google-chrome google-chrome-stable google-chrome-beta; do + if command -v "$candidate" >/dev/null 2>&1; then + echo "$candidate" + return 0 + fi + done + + fail_test "No Chromium-family browser binary found" +} + +function ensure_encaya_ready() +{ + if ! curl --silent --show-error --fail "$AIA_TEST_URL/lookup?domain=Namecoin%20Root%20CA" >/dev/null; then + fail_test "Encaya instance was not reachable at $AIA_TEST_URL" + fi +} + +function ensure_encaya_https_ready() +{ + root_cert_path="testdata/root_chain.pem" + if [[ ! -f "$root_cert_path" ]]; then + fail_test "Root CA for HTTPS readiness check not found at $root_cert_path" + fi + + if ! curl_test --cacert "$root_cert_path" --resolve "aia.x--nmc.bit:443:$AIA_TEST_IP" "https://aia.x--nmc.bit/lookup?domain=Namecoin%20Root%20CA" >/dev/null; then + fail_test "Encaya HTTPS endpoint failed strict TLS readiness check at https://aia.x--nmc.bit" + fi +} + +function chromium_fetch_dom_impl() +{ + chrome_profile_dir="$1" + chrome_log_path="$2" + target_host="$3" + target_url="$4" + + chrome_cmd=$(get_chromium_command) + host_resolver_rules="MAP $target_host 127.0.0.1,MAP aia.x--nmc.bit $AIA_TEST_IP,EXCLUDE localhost" + + mkdir -p "$chrome_profile_dir" + + if ! dom_output=$(timeout 60s "$chrome_cmd" --headless --disable-gpu --no-sandbox \ + --user-data-dir="$chrome_profile_dir" \ + --host-resolver-rules="$host_resolver_rules" \ + --dump-dom "$target_url" 2>"$chrome_log_path"); then + cat "$chrome_log_path" + return 1 + fi + + printf '%s\n' "$dom_output" +} + +function trust_encaya_root() +{ + root_cert_path="testdata/root_chain.pem" + if [[ ! -f "$root_cert_path" ]]; then + root_cert_path="$TEST_TMPDIR/encaya-root.pem" + curl --silent --show-error --fail "$AIA_TEST_URL/lookup?domain=Namecoin%20Root%20CA" | + awk 'BEGIN{inside=0} /BEGIN CERTIFICATE/{inside=1} inside{print} /END CERTIFICATE/{exit}' > "$root_cert_path" + fi + + echo "Importing Encaya Root CA into NSS DB from $root_cert_path" + grep -i "BEGIN CERTIFICATE" "$root_cert_path" + + certutil -d sql:"$NSS_DB_DIR" -D -n "Encaya Root CA" 2>/dev/null || true + certutil -d sql:"$NSS_DB_DIR" -A -t "C,," -n "Encaya Root CA" -i "$root_cert_path" + assert_only_root_trusted "$NSS_DB_DIR" +} + +function write_https_docroot() +{ + https_docroot="$1" + https_body="$2" + + mkdir -p "$https_docroot" + cat > "$https_docroot/index.html" < + + +$https_body + + +EOF +} + +function start_https_server() +{ + https_server_cert="$1" + https_server_key="$2" + start_err_msg="$3" + + pkill -f "openssl s_server -accept $HTTPS_SERVER_PORT" 2>/dev/null || true + write_https_docroot "$HTTPS_DOCROOT" "Cool or nah" + + ( + cd "$HTTPS_DOCROOT" + openssl s_server -accept "$HTTPS_SERVER_PORT" -cert "$https_server_cert" -key "$https_server_key" -WWW + ) > "$HTTPS_SERVER_LOG" 2>&1 & + HTTPS_SERVER_PID=$! + sleep 2 + + if ! kill -0 "$HTTPS_SERVER_PID" 2>/dev/null; then + cat "$HTTPS_SERVER_LOG" + fail_test "$start_err_msg" + fi +} + +function start_stapled_https_server() +{ + pkill -f "openssl s_server -accept $STAPLED_HTTPS_SERVER_PORT" 2>/dev/null || true + write_https_docroot "$STAPLED_HTTPS_DOCROOT" "Cool or nah stapled" + + ( + cd "$STAPLED_HTTPS_DOCROOT" + openssl s_server -accept "$STAPLED_HTTPS_SERVER_PORT" -cert "$STAPLED_LEAF_CERT" -key "$STAPLED_LEAF_KEY" -WWW + ) > "$STAPLED_HTTPS_SERVER_LOG" 2>&1 & + STAPLED_HTTPS_SERVER_PID=$! + sleep 2 + + if ! kill -0 "$STAPLED_HTTPS_SERVER_PID" 2>/dev/null; then + cat "$STAPLED_HTTPS_SERVER_LOG" + fail_test "Local stapled HTTPS server failed to start" + fi +} + +function generate_leaf_cert() +{ + printf '%s\n' "$ca_pem" > "$HASHED_PARENT_CA_PEM" + + openssl ecparam -name prime256v1 -genkey -noout -out "$LEAF_KEY" + openssl req -new -key "$LEAF_KEY" -subj "/CN=$HASHED_DOMAIN" -out "$LEAF_CSR" + + cat > "$LEAF_EXT" < "$HASHED_PARENT_CA_PEM" + + openssl ecparam -name prime256v1 -genkey -noout -out "$EXPIRED_LEAF_KEY" + openssl req -new -key "$EXPIRED_LEAF_KEY" -subj "/CN=$HASHED_DOMAIN" -out "$EXPIRED_LEAF_CSR" + + cat > "$EXPIRED_LEAF_EXT" < "$HASHED_PARENT_CA_DER" +openssl x509 -inform DER -in "$HASHED_PARENT_CA_DER" -out "$HASHED_PARENT_CA_PEM" +ca_pem=$(cat "$HASHED_PARENT_CA_PEM") +assert_contains "$ca_pem" "BEGIN CERTIFICATE" "Encaya did not return hashed $HASHED_DOMAIN Domain AIA Parent CA" + +echo "Fetch hashed $HASHED_DOMAIN CA via curl" +echo "$ca_pem" | grep -i "BEGIN CERTIFICATE" + +hashed_domain_ca_sha256_hex=$(get_cert_spki_sha256_hex "$ca_pem") +generated_key_sha256_hex=$(sha256_hex_upper "$HASHED_CA_KEY") +assert_equal "$hashed_domain_ca_sha256_hex" "$generated_key_sha256_hex" "Encaya issued parent CA key did not match generated hashed key" + +echo "Query hashed TLSA Authoritative via dig" +dig_output=$(dig -p 5391 @127.0.0.1 TLSA "*.$HASHED_DOMAIN") +dig_short=$(dig +short -p 5391 @127.0.0.1 TLSA "*.$HASHED_DOMAIN") +echo "$dig_output" +echo "Checking hashed response correctness" +observed_tlsa_hex=$(tlsa_hex_from_dig "$dig_short") +assert_equal "$observed_tlsa_hex" "$hashed_domain_ca_sha256_hex" "Hashed authoritative TLSA digest mismatch" + +echo "Query hashed TLSA Recursive via dig" +dig_output=$(dig -p 53 @127.0.0.1 TLSA "*.$HASHED_DOMAIN") +dig_short=$(dig +short -p 53 @127.0.0.1 TLSA "*.$HASHED_DOMAIN") +echo "$dig_output" +echo "Checking hashed recursive response correctness" +observed_tlsa_hex=$(tlsa_hex_from_dig "$dig_short") +assert_equal "$observed_tlsa_hex" "$hashed_domain_ca_sha256_hex" "Hashed recursive TLSA digest mismatch" + +echo "Generate local leaf certificate signed by hashed parent" +generate_leaf_cert + +echo "Start local HTTPS server for Chromium hashed AIA test" +start_https_server "$LEAF_CERT" "$LEAF_KEY" "Local HTTPS server failed to start" + +echo "Initialize NSS DB for Chromium hashed AIA test" +prepare_nss_db + +echo "Trust Encaya root CA for Chromium hashed AIA test" +trust_encaya_root + +echo "Run Chromium headless and verify real TLS+AIA workflow" +chromium_output=$(chromium_fetch_dom_impl "$CHROME_PROFILE_DIR" "$TEST_TMPDIR/chrome.log" "$HASHED_DOMAIN" "https://$HASHED_DOMAIN:$HTTPS_SERVER_PORT/index.html" || true) +assert_contains "$chromium_output" "Cool or nah" "Chromium did not render expected page content over validated TLS" + +if [[ "$chromium_output" == *"Your connection is not private"* ]]; then + fail_test "Chromium reported certificate error instead of successful validation" +fi + +echo "Hashed AIA Chromium test passed" + +echo "Generate expired leaf certificate for Chromium negative test" +generate_expired_leaf_cert + +echo "Start local HTTPS server with expired leaf certificate" +start_https_server "$EXPIRED_LEAF_CERT" "$EXPIRED_LEAF_KEY" "Expired-cert HTTPS server failed to start" + +echo "Run Chromium headless and verify expired cert is rejected" +expired_chromium_output=$(chromium_fetch_dom_impl "$TEST_TMPDIR/chrome-profile-expired" "$TEST_TMPDIR/chrome-expired.log" "$HASHED_DOMAIN" "https://$HASHED_DOMAIN:$HTTPS_SERVER_PORT/index.html" || true) +if [[ "$expired_chromium_output" != *"Your connection is not private"* ]]; then + echo "$expired_chromium_output" + fail_test "Chromium did not reject expired certificate" +fi + +echo "Expired cert Chromium negative test passed" + +function generate_stapled_pubkey_material() +{ + openssl ecparam -name prime256v1 -genkey -noout -out "$STAPLED_CA_KEY" + openssl pkey -in "$STAPLED_CA_KEY" -pubout -outform DER -out "$STAPLED_CA_PUB_DER" + + STAPLED_PUB_B64=$(to_urlsafe_base64 "$STAPLED_CA_PUB_DER") +} + +function build_stapled_message() +{ + STAPLED_MESSAGE_JSON=$(PUBB64="$STAPLED_PUB_B64" DOMAIN="$STAPLED_DOMAIN" ADDRESS="$STAPLED_BLOCKCHAIN_ADDRESS" \ + jq -cnS '{address: env.ADDRESS, domain: env.DOMAIN, x509pub: env.PUBB64}') + + STAPLED_MESSAGE="Namecoin X.509 Stapled Certification: $STAPLED_MESSAGE_JSON" + + STAPLED_BLOCKCHAIN_SIG=$($bitcoin_cli signmessage "$STAPLED_BLOCKCHAIN_ADDRESS" "$STAPLED_MESSAGE") + STAPLED_SIGS_JSON=$(BLOCKCHAIN_ADDRESS="$STAPLED_BLOCKCHAIN_ADDRESS" BLOCKCHAIN_SIG="$STAPLED_BLOCKCHAIN_SIG" \ + jq -cn '[{blockchainaddress: env.BLOCKCHAIN_ADDRESS, blockchainsig: env.BLOCKCHAIN_SIG}]') + STAPLED_SIGS_URLENCODED=$(SIGS_JSON="$STAPLED_SIGS_JSON" jq -rn 'env.SIGS_JSON | @uri') +} + +function generate_stapled_leaf_cert() +{ + printf '%s\n' "$stapled_ca_pem" > "$STAPLED_PARENT_CA_PEM" + + openssl ecparam -name prime256v1 -genkey -noout -out "$STAPLED_LEAF_KEY" + openssl req -new -key "$STAPLED_LEAF_KEY" -subj "/CN=$STAPLED_DOMAIN" -out "$STAPLED_LEAF_CSR" + + cat > "$STAPLED_LEAF_EXT" <&1 || true) +assert_contains "$stapled_negative_output" "404" "Stapled AIA missing-signature check did not return 404" + +echo "Ensure stapled AIA rejects wrong signature data" +STAPLED_WRONG_SIGS_JSON=$(BLOCKCHAIN_ADDRESS="$STAPLED_BLOCKCHAIN_ADDRESS" jq -cn '[{blockchainaddress: env.BLOCKCHAIN_ADDRESS, blockchainsig: "invalid"}]') +stapled_wrong_sig_output=$(curl --silent --show-error --fail --get --data-urlencode "domain=$STAPLED_DOMAIN Domain AIA Parent CA" --data-urlencode "pubb64=$STAPLED_PUB_B64" --data-urlencode "sigs=$STAPLED_WRONG_SIGS_JSON" "$AIA_TEST_URL/aia" 2>&1 || true) +assert_contains "$stapled_wrong_sig_output" "404" "Stapled AIA wrong-signature check did not return 404" + +echo "Ensure stapled AIA accepts multiple signature entries" +STAPLED_MULTI_SIGS_JSON=$(BLOCKCHAIN_ADDRESS="$STAPLED_BLOCKCHAIN_ADDRESS" BLOCKCHAIN_SIG="$STAPLED_BLOCKCHAIN_SIG" jq -cn '[{blockchainaddress: env.BLOCKCHAIN_ADDRESS, blockchainsig: "invalid"}, {blockchainaddress: env.BLOCKCHAIN_ADDRESS, blockchainsig: env.BLOCKCHAIN_SIG}]') +curl --silent --show-error --fail --get --data-urlencode "domain=$STAPLED_DOMAIN Domain AIA Parent CA" --data-urlencode "pubb64=$STAPLED_PUB_B64" --data-urlencode "sigs=$STAPLED_MULTI_SIGS_JSON" "$AIA_TEST_URL/aia" > "$STAPLED_PARENT_CA_DER" +openssl x509 -inform DER -in "$STAPLED_PARENT_CA_DER" -out "$STAPLED_PARENT_CA_PEM" +stapled_multi_ca_pem=$(cat "$STAPLED_PARENT_CA_PEM") +assert_contains "$stapled_multi_ca_pem" "BEGIN CERTIFICATE" "Stapled AIA multi-signature acceptance failed" + +echo "Fetch stapled $STAPLED_DOMAIN CA via Encaya AIA using Namecoin signature" +curl --silent --show-error --fail --get --data-urlencode "domain=$STAPLED_DOMAIN Domain AIA Parent CA" --data-urlencode "pubb64=$STAPLED_PUB_B64" --data-urlencode "sigs=$STAPLED_SIGS_JSON" "$AIA_TEST_URL/aia" > "$STAPLED_PARENT_CA_DER" +openssl x509 -inform DER -in "$STAPLED_PARENT_CA_DER" -out "$STAPLED_PARENT_CA_PEM" +stapled_ca_pem=$(cat "$STAPLED_PARENT_CA_PEM") +assert_contains "$stapled_ca_pem" "BEGIN CERTIFICATE" "Encaya did not return stapled $STAPLED_DOMAIN Domain AIA Parent CA" + +echo "Verify stapled issuer key matches signed public key" +stapled_domain_ca_sha256_hex=$(get_cert_spki_sha256_hex "$stapled_ca_pem") +stapled_generated_key_sha256_hex=$(sha256_hex_upper "$STAPLED_CA_KEY") +assert_equal "$stapled_domain_ca_sha256_hex" "$stapled_generated_key_sha256_hex" "Encaya issued stapled parent CA key did not match signed key" + +echo "Generate local leaf certificate signed by stapled parent" +generate_stapled_leaf_cert + +echo "Start local HTTPS server for Chromium stapled AIA test" +start_stapled_https_server + +echo "Initialize NSS DB for Chromium stapled AIA test" +prepare_nss_db + +echo "Trust Encaya root CA for Chromium stapled AIA test" +trust_encaya_root + +echo "Run Chromium headless and verify stapled TLS+AIA workflow" +stapled_chromium_output=$(chromium_fetch_dom_impl "$STAPLED_CHROME_PROFILE_DIR" "$STAPLED_TEST_TMPDIR/chrome.log" "$STAPLED_DOMAIN" "https://$STAPLED_DOMAIN:$STAPLED_HTTPS_SERVER_PORT/index.html" || true) +assert_contains "$stapled_chromium_output" "Cool or nah stapled" "Chromium did not render expected page content over stapled TLS validation" + +if [[ "$stapled_chromium_output" == *"Your connection is not private"* ]]; then + fail_test "Chromium reported certificate error instead of successful stapled validation" +fi + +echo "Stapled AIA Chromium test passed" +echo "Functional test suite passed"