diff --git a/.github/workflows/make-test.yml b/.github/workflows/make-test.yml index 81deb64..b4a8b51 100644 --- a/.github/workflows/make-test.yml +++ b/.github/workflows/make-test.yml @@ -14,6 +14,12 @@ on: required: false default: master type: string + # Short label for this configuration. Used to keep the failure-log + # artifact names unique across the caller's matrix. + config-name: + required: false + default: default + type: string jobs: @@ -28,10 +34,10 @@ jobs: sudo apt install -y libevent-dev libpcap-dev # pull wolfKeyMgr - - uses: actions/checkout@master + - uses: actions/checkout@v5 # wolfSSL build, check and install - - uses: actions/checkout@master + - uses: actions/checkout@v5 with: repository: wolfssl/wolfssl ref: ${{ inputs.wolfssl-ref }} @@ -93,9 +99,11 @@ jobs: # capture logs on failure - name: Upload failure logs if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: - name: wolfKeyMgr-logs + name: wolfKeyMgr-${{ inputs.config-name }}-wolfssl-${{ inputs.wolfssl-ref }}-logs path: | test-suite.log + tests/*.log + tests/credential-options-failure/** retention-days: 5 diff --git a/.github/workflows/sanitizer.yml b/.github/workflows/sanitizer.yml index 1b82cbd..29c33b3 100644 --- a/.github/workflows/sanitizer.yml +++ b/.github/workflows/sanitizer.yml @@ -38,7 +38,7 @@ jobs: sudo apt-get install -y libevent-dev libpcap-dev autoconf automake libtool - name: Checkout wolfKeyMgr - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Build wolfSSL (${{ matrix.sanitizer.name }}) run: | @@ -70,10 +70,11 @@ jobs: - name: Upload failure logs if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: wolfKeyMgr-${{ matrix.sanitizer.name }}-wolfssl-${{ matrix.wolfssl-ref }}-logs path: | test-suite.log tests/*.log + tests/credential-options-failure/** retention-days: 5 diff --git a/.github/workflows/test-nightly.yml b/.github/workflows/test-nightly.yml index dc77ac1..87f0198 100644 --- a/.github/workflows/test-nightly.yml +++ b/.github/workflows/test-nightly.yml @@ -23,6 +23,7 @@ jobs: build_test: needs: resolve strategy: + fail-fast: false matrix: wolfssl-ref: ${{ fromJson(needs.resolve.outputs.refs) }} uses: ./.github/workflows/make-test.yml @@ -30,10 +31,12 @@ jobs: config-args: make-args: wolfssl-ref: ${{ matrix.wolfssl-ref }} + config-name: default build_debug_test: needs: resolve strategy: + fail-fast: false matrix: wolfssl-ref: ${{ fromJson(needs.resolve.outputs.refs) }} uses: ./.github/workflows/make-test.yml @@ -41,10 +44,12 @@ jobs: config-args: --enable-debug make-args: wolfssl-ref: ${{ matrix.wolfssl-ref }} + config-name: debug build_no_vault_test: needs: resolve strategy: + fail-fast: false matrix: wolfssl-ref: ${{ fromJson(needs.resolve.outputs.refs) }} uses: ./.github/workflows/make-test.yml @@ -52,10 +57,12 @@ jobs: config-args: --disable-vault make-args: wolfssl-ref: ${{ matrix.wolfssl-ref }} + config-name: no-vault build_no_sniffer_test: needs: resolve strategy: + fail-fast: false matrix: wolfssl-ref: ${{ fromJson(needs.resolve.outputs.refs) }} uses: ./.github/workflows/make-test.yml @@ -63,10 +70,12 @@ jobs: config-args: --disable-sniffer make-args: wolfssl-ref: ${{ matrix.wolfssl-ref }} + config-name: no-sniffer build_vault_clear_test: needs: resolve strategy: + fail-fast: false matrix: wolfssl-ref: ${{ fromJson(needs.resolve.outputs.refs) }} uses: ./.github/workflows/make-test.yml @@ -74,10 +83,12 @@ jobs: config-args: --enable-vault=clear make-args: wolfssl-ref: ${{ matrix.wolfssl-ref }} + config-name: vault-clear build_minimal_test: needs: resolve strategy: + fail-fast: false matrix: wolfssl-ref: ${{ fromJson(needs.resolve.outputs.refs) }} uses: ./.github/workflows/make-test.yml @@ -85,3 +96,4 @@ jobs: config-args: --disable-sniffer --disable-vault make-args: wolfssl-ref: ${{ matrix.wolfssl-ref }} + config-name: minimal diff --git a/.github/workflows/test-pull.yml b/.github/workflows/test-pull.yml index eaabe71..6aadefd 100644 --- a/.github/workflows/test-pull.yml +++ b/.github/workflows/test-pull.yml @@ -16,6 +16,7 @@ jobs: build_test: needs: resolve strategy: + fail-fast: false matrix: wolfssl-ref: ${{ fromJson(needs.resolve.outputs.refs) }} uses: ./.github/workflows/make-test.yml @@ -23,10 +24,12 @@ jobs: config-args: make-args: wolfssl-ref: ${{ matrix.wolfssl-ref }} + config-name: default build_debug_test: needs: resolve strategy: + fail-fast: false matrix: wolfssl-ref: ${{ fromJson(needs.resolve.outputs.refs) }} uses: ./.github/workflows/make-test.yml @@ -34,10 +37,12 @@ jobs: config-args: --enable-debug make-args: wolfssl-ref: ${{ matrix.wolfssl-ref }} + config-name: debug build_no_vault_test: needs: resolve strategy: + fail-fast: false matrix: wolfssl-ref: ${{ fromJson(needs.resolve.outputs.refs) }} uses: ./.github/workflows/make-test.yml @@ -45,10 +50,12 @@ jobs: config-args: --disable-vault make-args: wolfssl-ref: ${{ matrix.wolfssl-ref }} + config-name: no-vault build_no_sniffer_test: needs: resolve strategy: + fail-fast: false matrix: wolfssl-ref: ${{ fromJson(needs.resolve.outputs.refs) }} uses: ./.github/workflows/make-test.yml @@ -56,10 +63,12 @@ jobs: config-args: --disable-sniffer make-args: wolfssl-ref: ${{ matrix.wolfssl-ref }} + config-name: no-sniffer build_vault_clear_test: needs: resolve strategy: + fail-fast: false matrix: wolfssl-ref: ${{ fromJson(needs.resolve.outputs.refs) }} uses: ./.github/workflows/make-test.yml @@ -67,10 +76,12 @@ jobs: config-args: --enable-vault=clear make-args: wolfssl-ref: ${{ matrix.wolfssl-ref }} + config-name: vault-clear build_minimal_test: needs: resolve strategy: + fail-fast: false matrix: wolfssl-ref: ${{ fromJson(needs.resolve.outputs.refs) }} uses: ./.github/workflows/make-test.yml @@ -78,3 +89,4 @@ jobs: config-args: --disable-sniffer --disable-vault make-args: wolfssl-ref: ${{ matrix.wolfssl-ref }} + config-name: minimal diff --git a/.gitignore b/.gitignore index 4f17b0b..a237ba0 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,6 @@ tracefile.txt # test files vault.bin wolfkeymgr.vault + +# credential-options test failure capture +/tests/credential-options-failure/ diff --git a/certs/gen-certs.sh b/certs/gen-certs.sh index c0811fb..71a54e6 100755 --- a/certs/gen-certs.sh +++ b/certs/gen-certs.sh @@ -8,26 +8,43 @@ echo "Run from wolfkeymgr root" reject_shipped_key() { key_file="$1" shipped_fingerprint="$2" + pub_file="$1.pub.tmp" - if [ -f "$key_file" ]; then - if ! key_fingerprint=$(openssl pkey -in "$key_file" \ - -passin pass:'wolfssl' -pubout -outform DER 2>/dev/null | \ - openssl dgst -sha256 -r | awk '{print $1}'); then - echo "Refusing an existing key that could not be fingerprinted:" \ - "$key_file" >&2 - exit 1 - fi - if [ -z "$key_fingerprint" ]; then - echo "Refusing an existing key with an empty fingerprint:" \ - "$key_file" >&2 - exit 1 - fi - if [ "$key_fingerprint" = "$shipped_fingerprint" ]; then - echo "Refusing to reuse a credential shipped in an older release:" \ - "$key_file" >&2 - echo "Rotate the demo credentials with: ./certs/gen-certs.sh clean" >&2 - exit 1 - fi + if [ ! -f "$key_file" ]; then + return 0 + fi + + # Write the public key to a file rather than piping it into "openssl + # dgst". A pipeline whose reader can exit first lets the writer die of + # SIGPIPE, which "set -o pipefail" then reports as a fingerprint + # failure. That made this check intermittently take the wrong branch. + rm -f "$pub_file" + if ! openssl pkey -in "$key_file" -passin pass:'wolfssl' -pubout \ + -outform DER -out "$pub_file" 2>/dev/null; then + rm -f "$pub_file" + echo "Refusing an existing key that could not be fingerprinted:" \ + "$key_file" >&2 + exit 1 + fi + if ! key_fingerprint=$(openssl dgst -sha256 -r "$pub_file" \ + | awk '{print $1}'); then + rm -f "$pub_file" + echo "Refusing an existing key that could not be fingerprinted:" \ + "$key_file" >&2 + exit 1 + fi + rm -f "$pub_file" + + if [ -z "$key_fingerprint" ]; then + echo "Refusing an existing key with an empty fingerprint:" \ + "$key_file" >&2 + exit 1 + fi + if [ "$key_fingerprint" = "$shipped_fingerprint" ]; then + echo "Refusing to reuse a credential shipped in an older release:" \ + "$key_file" >&2 + echo "Rotate the demo credentials with: ./certs/gen-certs.sh clean" >&2 + exit 1 fi } diff --git a/tests/credential-options.test b/tests/credential-options.test index 4092612..4b51a7e 100755 --- a/tests/credential-options.test +++ b/tests/credential-options.test @@ -2,16 +2,56 @@ set -eu +# Trace every command to stderr; automake captures it into +# tests/credential-options.log, which CI uploads on failure. Do not use +# $LINENO here: dash does not implement it and "set -u" then aborts. +PS4='+ credential-options: ' +set -x + test_dir=$(mktemp -d "${TMPDIR:-/tmp}/wolfkeymgr-credentials.XXXXXX") -trap 'rm -rf "$test_dir"' EXIT HUP INT TERM +fail_dir=tests/credential-options-failure + +# Keep every log this test produces and hand it to the caller when something +# goes wrong. Without this the only record of a failure is the automake +# "exit status: 1" line, which says nothing about which check failed. +on_exit() +{ + status=$? + set +x + if [ "$status" -ne 0 ]; then + echo "credential-options: FAILED with status $status" >&2 + for log in "$test_dir"/*.log; do + if [ -f "$log" ]; then + echo "--- $log" >&2 + sed 's/^/ /' "$log" >&2 + fi + done + # Drop the FIFO first: a named pipe left in the uploaded artifact + # tree makes the CI upload step block forever trying to read it. + rm -f "$test_dir/password-pipe" + rm -rf "$fail_dir" + if mkdir -p "$fail_dir" 2>/dev/null; then + cp -R "$test_dir"/. "$fail_dir"/ 2>/dev/null || true + echo "credential-options: contents preserved in $fail_dir" >&2 + fi + fi + rm -rf "$test_dir" + exit "$status" +} +trap on_exit EXIT +trap 'exit 143' HUP INT TERM + +rejected_count=0 expect_usage() { password_file="$1" + rejected_count=$((rejected_count + 1)) + output_file="$test_dir/rejected-password-$rejected_count.log" set +e ./src/wolfkeymgr -k unused-key.pem -W "$password_file" \ - -c unused-cert.pem -A unused-ca.pem >/dev/null 2>&1 + -c unused-cert.pem -A unused-ca.pem >"$output_file" 2>&1 status=$? set -e test "$status" -eq 64 @@ -59,15 +99,15 @@ mkfifo "$test_dir/password-pipe" expect_usage "$test_dir/password-pipe" set +e -./src/wolfkeymgr -w password >/dev/null 2>&1 +./src/wolfkeymgr -w password >"$test_dir/unknown-option.log" 2>&1 status=$? set -e test "$status" -eq 64 -./src/wolfkeymgr '-?' >/dev/null 2>&1 +./src/wolfkeymgr '-?' >"$test_dir/help.log" 2>&1 set +e -./src/wolfkeymgr -W >/dev/null 2>&1 +./src/wolfkeymgr -W >"$test_dir/missing-argument.log" 2>&1 status=$? set -e test "$status" -eq 64 @@ -77,8 +117,8 @@ cp certs/gen-certs.sh certs/ca-ecc.cnf certs/test-key.pem \ certs/test-cert.pem "$test_dir/generator/certs/" printf '%s\n' '#!/bin/sh' 'exit 1' > "$test_dir/bin/openssl" chmod 700 "$test_dir/bin/openssl" -if (cd "$test_dir/generator" && \ - PATH="$test_dir/bin:$PATH" ./certs/gen-certs.sh >/dev/null 2>&1); then +if (cd "$test_dir/generator" && PATH="$test_dir/bin:$PATH" \ + ./certs/gen-certs.sh >"$test_dir/generator.log" 2>&1); then exit 1 fi @@ -87,20 +127,34 @@ cp certs/gen-certs.sh certs/ca-ecc.cnf certs/test-key.pem \ certs/test-cert.pem "$test_dir/malformed/certs/" printf '%s\n' 'not a private key' > "$test_dir/malformed/certs/ca-key.pem" if (cd "$test_dir/malformed" && \ - ./certs/gen-certs.sh >/dev/null 2>&1); then + ./certs/gen-certs.sh >"$test_dir/malformed.log" 2>&1); then exit 1 fi +grep -q 'could not be fingerprinted' "$test_dir/malformed.log" +# gen-certs.sh writes the public key to a file and digests that file, so the +# stub has to honor "-out" instead of writing to stdout. mkdir -p "$test_dir/matched/certs" "$test_dir/matched-bin" cp certs/gen-certs.sh certs/ca-ecc.cnf certs/test-key.pem \ certs/test-cert.pem "$test_dir/matched/certs/" printf '%s\n' 'controlled test key' > "$test_dir/matched/certs/ca-key.pem" printf '%s\n' '#!/bin/sh' \ 'if [ "$1" = pkey ]; then' \ - ' printf "%s\\n" controlled-public-key' \ - 'else' \ - ' printf "%s -\\n" e69c343c84239413663520f8ccf82635b1b8954b9e7a793f2ed3b2b5fd774e5c' \ - 'fi' > "$test_dir/matched-bin/openssl" + ' out=' \ + ' prev=' \ + ' for arg in "$@"; do' \ + ' if [ "$prev" = -out ]; then' \ + ' out=$arg' \ + ' fi' \ + ' prev=$arg' \ + ' done' \ + ' if [ -n "$out" ]; then' \ + ' printf "%s" controlled-public-key > "$out"' \ + ' fi' \ + ' exit 0' \ + 'fi' \ + 'printf "%s -\\n" e69c343c84239413663520f8ccf82635b1b8954b9e7a793f2ed3b2b5fd774e5c' \ + > "$test_dir/matched-bin/openssl" chmod 700 "$test_dir/matched-bin/openssl" if (cd "$test_dir/matched" && PATH="$test_dir/matched-bin:$PATH" \ ./certs/gen-certs.sh >"$test_dir/matched.log" 2>&1); then @@ -113,7 +167,7 @@ cp certs/gen-certs.sh certs/ca-ecc.cnf certs/test-key.pem \ certs/test-cert.pem "$test_dir/valid/certs/" printf '%s\n' stale > "$test_dir/valid/certs/demo-password.txt" chmod 644 "$test_dir/valid/certs/demo-password.txt" -(cd "$test_dir/valid" && ./certs/gen-certs.sh >/dev/null 2>&1) +(cd "$test_dir/valid" && ./certs/gen-certs.sh >"$test_dir/valid.log" 2>&1) test -s "$test_dir/valid/certs/ca-cert.pem" test -s "$test_dir/valid/certs/client-cert.pem" test -s "$test_dir/valid/certs/server-rsa-cert.pem" @@ -121,3 +175,8 @@ if find "$test_dir/valid/certs/demo-password.txt" ! -perm 600 -print | \ grep -q .; then exit 1 fi + +# No stray public-key temporaries left behind by reject_shipped_key(). +if find "$test_dir/valid/certs" -name '*.pub.tmp' -print | grep -q .; then + exit 1 +fi diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 0072f8c..28f2556 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -84,6 +84,10 @@ static int vault_test(void) {2, "testname2", "testdata2"} }; + /* start from a clean vault: otherwise this test inherits the items and + * the encryption mode of whatever build ran last in this tree */ + unlink(testFile); + ret = wolfVaultOpen(&ctx, testFile); if (ret == 0) { wolfVaultPrintInfo(ctx);