Revision control
Copy as Markdown
Other Tools
#!/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=<path-to-openssl-3.5> \
# -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 ]