#!/bin/bash # Cross-backend round-trip test for PQC algorithms. # Verifies that keyrings generated by the Botan backend can be used by the # OpenSSL backend, and vice versa, covering ML-KEM, ML-DSA, and SLH-DSA. # # Prerequisites # ------------- # Two build directories must exist at the repository root before running # this script. Build them with cmake from the repo root, e.g.: # # Botan backend (build/): # cmake -B build -DCRYPTO_BACKEND=botan3 \ # -DENABLE_CRYPTO_REFRESH=ON -DENABLE_PQC=ON # cmake --build build --target rnp rnpkeys # # OpenSSL 3.5+ backend (build-ossl/): # cmake -B build-ossl -DCRYPTO_BACKEND=OpenSSL \ # -DOPENSSL_ROOT_DIR= \ # -DENABLE_CRYPTO_REFRESH=ON -DENABLE_PQC=ON # cmake --build build-ossl --target rnp rnpkeys # # Usage (from the repository root): # bash src/tests/pqc-crossbackend-test.sh set -euo pipefail REPO_ROOT=$(cd "$(dirname "$0")"/../.. && pwd) BOTAN_RNP="$REPO_ROOT/build/src/rnp/rnp" BOTAN_RNPK="$REPO_ROOT/build/src/rnpkeys/rnpkeys" OSSL_RNP="$REPO_ROOT/build-ossl/src/rnp/rnp" OSSL_RNPK="$REPO_ROOT/build-ossl/src/rnpkeys/rnpkeys" PASSWORD="pqc-roundtrip-pw" WORKDIR=$(mktemp -d) trap 'rm -rf "$WORKDIR"' EXIT PLAINTEXT="$WORKDIR/plaintext.txt" echo "Cross-backend PQC round-trip test payload." > "$PLAINTEXT" PASS=0 FAIL=0 # Generate a PQC key with one backend, sign+encrypt with that same backend, # then decrypt+verify with the other backend using the same keyring files. roundtrip() { local desc="$1" # human-readable description local gen_rnpk="$2" # rnpkeys binary used for key generation local gen_rnp="$3" # rnp binary used for sign+encrypt local use_rnp="$4" # rnp binary used for decrypt+verify local algo="$5" # numeric algorithm choice for --generate-key --expert local uid="rt-${algo}@crossbackend" local dir="$WORKDIR/dir-${algo}-${desc// /-}" mkdir "$dir" # Key generation if ! printf '%s\n' "$algo" | \ "$gen_rnpk" --homedir "$dir" --password "$PASSWORD" \ --notty --userid "$uid" --generate-key --expert \ 2>/dev/null; then echo "FAIL [$desc algo=$algo] key generation failed" FAIL=$((FAIL+1)) return fi local encrypted="$WORKDIR/enc-${algo}-${desc// /-}.pgp" local decrypted="$WORKDIR/dec-${algo}-${desc// /-}.txt" # Sign + encrypt with the generating backend if ! "$gen_rnp" --homedir "$dir" --sign --encrypt \ -r "$uid" -u "$uid" \ --pass-fd 3 "$PLAINTEXT" --output "$encrypted" \ 3< <(printf '%s' "$PASSWORD") 2>/dev/null; then echo "FAIL [$desc algo=$algo] sign+encrypt failed" FAIL=$((FAIL+1)) return fi # Decrypt + verify with the other backend using the same keyring if ! "$use_rnp" --homedir "$dir" \ --pass-fd 3 --decrypt "$encrypted" --output "$decrypted" \ 3< <(printf '%s' "$PASSWORD") 2>/dev/null; then echo "FAIL [$desc algo=$algo] decrypt+verify failed" FAIL=$((FAIL+1)) return fi if ! diff -q "$PLAINTEXT" "$decrypted" > /dev/null 2>&1; then echo "FAIL [$desc algo=$algo] decrypted content differs from plaintext" FAIL=$((FAIL+1)) return fi echo "PASS [$desc algo=$algo]" PASS=$((PASS+1)) } # Algorithm numbers (from rnpkeys --generate-key --expert menu): # 25: (ML-DSA-65 + Ed25519) + (ML-KEM-768 + X25519) -- Ed curves # 26: (ML-DSA-87 + Ed448) + (ML-KEM-1024 + X448) -- Ed curves # 27: (ML-DSA-65 + ECDSA-P384)+ (ML-KEM-768 + ECDH-P384) -- NIST curves # 28: (ML-DSA-87 + ECDSA-P521)+ (ML-KEM-1024 + ECDH-P521) -- NIST curves # 31: SLH-DSA-SHAKE-128f + (ML-KEM-768 + X25519) -- SLH-DSA # 32: SLH-DSA-SHAKE-128s + (ML-KEM-768 + X25519) -- SLH-DSA # 33: SLH-DSA-SHAKE-256s + (ML-KEM-1024 + ECDH-P521) -- SLH-DSA echo "=== Botan generates, OpenSSL decrypts+verifies ===" for algo in 25 26 27 28 31 32 33; do roundtrip "botan->ossl" "$BOTAN_RNPK" "$BOTAN_RNP" "$OSSL_RNP" "$algo" done echo "" echo "=== OpenSSL generates, Botan decrypts+verifies ===" for algo in 25 26 27 28 31 32 33; do roundtrip "ossl->botan" "$OSSL_RNPK" "$OSSL_RNP" "$BOTAN_RNP" "$algo" done echo "" echo "Results: $PASS passed, $FAIL failed." [ "$FAIL" -eq 0 ]