aere-quantum/pq-gateway/proba-pq-gateway-control-negativ.sh

145 lines
9.5 KiB
Bash

#!/bin/bash
# Controlul negativ al probelor gateway-ului PQ (proba-pq-gateway.mjs). Paznicii din pq-gateway.mjs se ORBESC pe rand,
# intr-o COPIE a dosarului (originalul nu se atinge), si proba tinta TREBUIE sa iasa ROSIE pe numele ei. Suita trebuie
# sa fi RULAT intreaga, altfel verdictul e STRICAT, nu rosu. La urma suita neatinsa trebuie sa fie verde.
# bash aerenew/cloud-gateway/pq-gateway/proba-pq-gateway-control-negativ.sh
# Iesire: un rand pe paznic, apoi "CONTROL NEGATIV: N treceri, M esecuri"; cod nenul la orice esec sau STRICAT.
set -u
AICI="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
W=$(mktemp -d); trap 'rm -rf "$W"' EXIT
# probele (s) folosesc autoritatea de certificare PQ de langa gateway; copia nu o are, deci calea ei vine din mediu
export AERE_PQ_PKI="$(cd "$AICI/../pq-pki" && pwd)/pki.mjs"
if command -v cygpath >/dev/null 2>&1; then AERE_PQ_PKI="$(cygpath -w "$AERE_PQ_PKI")"; fi
for f in pq-gateway.mjs proba-pq-gateway.mjs pq-gateway.example.json; do
cp "$AICI/$f" "$W/$f"
cmp -s "$AICI/$f" "$W/$f" || { echo "STRICAT: copia lui $f difera de original"; exit 3; }
done
PROBE=$(grep -c "^await test(" "$AICI/proba-pq-gateway.mjs")
[ "$PROBE" -gt 0 ] || { echo "STRICAT: nu am gasit nicio proba in proba-pq-gateway.mjs"; exit 3; }
cale_nod() { if command -v cygpath >/dev/null 2>&1; then cygpath -w "$1"; else echo "$1"; fi; }
treceri=0; esecuri=0
# $1 vechi, $2 nou. Ancora trebuie sa apara EXACT o data, altfel plantarea e un esec al controlului, nu un verde.
planteaza() {
AERE_VECHI="$1" AERE_NOU="$2" node -e "
const fs = require('fs'); const p = process.argv[1]; const t = fs.readFileSync(p, 'utf8'); const a = process.env.AERE_VECHI;
const n = t.split(a).length - 1;
if (n !== 1) { console.log(' ancora apare de ' + n + ' ori'); process.exitCode = 3; }
else fs.writeFileSync(p, t.split(a).join(process.env.AERE_NOU));" "$(cale_nod "$W/pq-gateway.mjs")"
}
# $1 descrierea paznicului orbit, $2 eticheta probei tinta, de ex. (g)
masoara() {
if cmp -s "$AICI/pq-gateway.mjs" "$W/pq-gateway.mjs"; then
echo " $1: STRICAT (plantarea nu a schimbat nimic)"; esecuri=$((esecuri+1)); return
fi
if ! cmp -s "$AICI/proba-pq-gateway.mjs" "$W/proba-pq-gateway.mjs"; then
echo " $1: STRICAT (proba din copie difera de original)"; esecuri=$((esecuri+1)); return
fi
( cd "$W" && node proba-pq-gateway.mjs > "$W/out.txt" 2>&1 )
local cod=$?
local rulate; rulate=$(grep -c "^ OK \|^ ESEC \|^ -- " "$W/out.txt")
local tinta; tinta=$(grep "^ ESEC " "$W/out.txt" | grep -cF "ESEC $2 ")
local rosii; rosii=$(grep -c "^ ESEC " "$W/out.txt")
if [ "$rulate" -ne "$PROBE" ]; then
echo " $1: STRICAT (au rulat $rulate probe din $PROBE, cod $cod)"; tail -3 "$W/out.txt" | cut -c1-220; esecuri=$((esecuri+1))
elif [ "$cod" -eq 0 ]; then
echo " $1: CONTROL NEGATIV CAZUT (suita a iesit 0)"; esecuri=$((esecuri+1))
elif [ "$tinta" -ge 1 ]; then
echo " $1: ROSU cum trebuia ($rosii esecuri in suita, intre ele $2)"
grep "^ ESEC " "$W/out.txt" | grep -F "ESEC $2 " | cut -c1-330 | sed 's/^/ /'
treceri=$((treceri+1))
else
echo " $1: CONTROL NEGATIV CAZUT (proba $2 a ramas verde; au picat: $(grep '^ ESEC ' "$W/out.txt" | cut -c8-12 | tr '\n' ' '))"; esecuri=$((esecuri+1))
fi
cp "$AICI/pq-gateway.mjs" "$W/pq-gateway.mjs"
cmp -s "$AICI/pq-gateway.mjs" "$W/pq-gateway.mjs" || { echo "STRICAT: copia nu s-a putut reface"; exit 3; }
}
control() { # $1 descriere, $2 tinta, $3 ancora, $4 inlocuire
if planteaza "$3" "$4"; then masoara "$1" "$2"; else echo " $1: STRICAT (plantarea a esuat)"; esecuri=$((esecuri+1)); fi
}
control "autentificarea raportata mereu post-quantum" "(s2)" \
"const autentificare = lant.length && lant.every(pq) ? 'post-quantum'" \
"const autentificare = lant.length && (lant.every(pq) || Boolean(Number('1'))) ? 'post-quantum'"
control "REQUIRE_PQ_AUTH ignorat" "(s3)" \
" if (cfg.requirePqAuth && lantCert.autentificare !== 'post-quantum') {" \
" if (cfg.requirePqAuth && lantCert.autentificare !== 'post-quantum' && Boolean(Number('0'))) {"
control "numai cheile lantului judecate, nu si semnaturile" "(s3)" \
"const pq = (c) => /^ML-DSA-/.test(c.publicKeyAlgorithm) && /^ML-DSA-/.test(c.signatureAlgorithm);" \
"const pq = (c) => /^ML-DSA-/.test(c.publicKeyAlgorithm);"
control "X-Real-IP al clientului crezut (revizuirea 2026-09-25, A8)" "(e3)" \
"'x-forwarded-host', 'x-real-ip', 'forwarded'" \
"'x-forwarded-host', 'forwarded'"
control "X-Client-* si X-SSL-* ale clientului trec la upstream (A8)" "(e3)" \
"PREFIXE_IDENTITATE.some((p) => n.startsWith(p))" \
"PREFIXE_IDENTITATE.some((p) => n.startsWith(p) && Boolean(Number('0')))"
control "refuzul pe algoritmul de semnatura numarat ca other (A9)" "(g3)" \
"if (c === 'ERR_SSL_NO_SUITABLE_SIGNATURE_ALGORITHM') return 'no shared signature algorithm';" \
"if (c === 'ERR_SSL_NO_SUITABLE_SIGNATURE_ALGORITHM' && Boolean(Number('0'))) return 'no shared signature algorithm';"
control "hybrid-only ofera si X25519" "(g)" \
"'hybrid-only': 'X25519MLKEM768'," "'hybrid-only': 'X25519MLKEM768:X25519',"
control "motivul refuzului nu mai e numarat (totul 'other')" "(g)" \
"export function motivRefuz(e) {" "export function motivRefuz(e) { if (!process.env.AERE_NU) return 'other';"
control "minVersion TLSv1.3 scos (ramane implicitul Node, TLSv1.2)" "(h)" \
"minVersion: 'TLSv1.3'," ""
control "antetele hop-by-hop nescoase" "(e)" \
"if (HOP.has(n) || n.startsWith('proxy-') || numite.has(n)) continue;" "if (Boolean(process.env.AERE_NU)) continue;"
control "numele enumerate in Connection nescoase" "(e)" \
"if (HOP.has(n) || n.startsWith('proxy-') || numite.has(n)) continue;" "if (HOP.has(n) || n.startsWith('proxy-')) continue;"
control "X-Forwarded-For al clientului crezut" "(e)" \
"if (cfg.trustForwarded) xffVechi =" "if (!process.env.AERE_NU) xffVechi ="
control "x-aere-pq-gateway nepus (cel falsificat de client trece)" "(e)" \
"out.push('x-aere-pq-gateway', cfg.mode);" "if (Boolean(process.env.AERE_NU)) out.push('x-aere-pq-gateway', cfg.mode);"
control "cheia privata inclusa in stare" "(f)" \
"certificate: { sha256: x509.fingerprint256," "privateKey: cheiePem.toString('utf8'), certificate: { sha256: x509.fingerprint256,"
control "starea trimisa la upstream in loc sa fie servita" "(f)" \
"if (esteCaleaStarii(req.url)) {" "if (Boolean(process.env.AERE_NU) && esteCaleaStarii(req.url)) {"
control "upstream cazut: niciun raspuns (agatare)" "(i)" \
"raspundeJson(req, res, cod, corpEroare(cod, e), true);" "if (Boolean(process.env.AERE_NU)) raspundeJson(req, res, cod, corpEroare(cod, e), true);"
control "timeout raportat ca 502" "(i2)" \
"const cod = e && e.aereTimeout ? 504 : 502;" "const cod = 502;"
control "hybrid-preferred pretinde hibrid garantat" "(j)" \
"hybridKeyExchangeOnEveryConnection: false," "hybridKeyExchangeOnEveryConnection: true,"
control "hybrid-preferred ca lista plata (fara tuplu, fara HelloRetryRequest)" "(j2)" \
"'hybrid-preferred': 'X25519MLKEM768/X25519:P-256'," "'hybrid-preferred': 'X25519MLKEM768:X25519:P-256',"
control "corpul bufferat intreg inainte de trimitere" "(k)" \
"req.pipe(upReq);" "(async () => { const b = []; for await (const x of req) b.push(x); upReq.end(Buffer.concat(b)); })().catch(() => upReq.destroy());"
control "tunelul upgrade numai intr-un sens" "(d)" \
"upSock.pipe(socket);" ""
control "upgrade refuzat de upstream trimis fara Connection: close" "(d2)" \
"linii.push('Connection: close');" ""
control "upgrade catre upstream cazut: niciun raspuns pe socket" "(i3)" \
"if (!socket.destroyed) scrieBrut(socket, cod, corpEroare(cod, e));" "if (Boolean(process.env.AERE_NU)) scrieBrut(socket, cod, corpEroare(cod, e));"
control "drumul catre upstream https fara preferinta hibrida" "(q)" \
"ecdhCurve: GRUPURI['hybrid-preferred'], ...(caSus ?" "ecdhCurve: 'X25519', ...(caSus ?"
control "upgrade catre upstream https fara CA-ul configurat" "(d3)" \
"ecdhCurve: GRUPURI['hybrid-preferred'], ...caUpgrade }" "ecdhCurve: GRUPURI['hybrid-preferred'] }"
control "plecarea clientului numarata ca 502 (cerere)" "(s)" \
"if (res.headersSent || res.destroyed) { res.destroy(); return; }" ""
control "plecarea clientului numarata ca 502 (upgrade)" "(s)" \
"if (socket.destroyed) return; //" "if (Boolean(process.env.AERE_NU)) return; //"
control "raspunsul care curge taiat de cronometrul pornit la 'finish'" "(k2)" \
"if (raspunsPrimit) return;" "if (Boolean(process.env.AERE_NU)) return;"
control "adresa ::ffff: trimisa neschimbata in X-Forwarded-For" "(e2)" \
"return s.toLowerCase().startsWith('::ffff:') && net.isIPv4(s.slice(7)) ? s.slice(7) : s;" "return s;"
control "certificatul upstream-ului https nu se mai verifica" "(q2)" \
"new https.Agent({ keepAlive: true," "new https.Agent({ rejectUnauthorized: false, keepAlive: true,"
control "fara cronometru de conectare la upstream" "(r)" \
"tConectare = setTimeout(() => upReq.destroy(expirat('connect')), cfg.connectTimeoutMs);" "tConectare = null;"
control "oprirea taie cererile in curs" "(l)" \
"server.closeIdleConnections();" "server.closeAllConnections();"
( cd "$AICI" && node proba-pq-gateway.mjs > "$W/final.txt" 2>&1 )
codf=$?
rulatef=$(grep -c "^ OK \|^ ESEC \|^ -- " "$W/final.txt")
okf=$(grep -c "^ OK " "$W/final.txt")
if [ "$codf" -eq 0 ] && [ "$rulatef" -eq "$PROBE" ] && [ "$okf" -eq "$PROBE" ]; then
echo " suita neatinsa: VERDE ($(tail -1 "$W/final.txt"))"; treceri=$((treceri+1))
else
echo " suita neatinsa: NU e verde (cod $codf, $okf din $PROBE OK, rulate $rulatef)"; grep "^ ESEC \|^ -- \|STRICAT" "$W/final.txt" | cut -c1-220; esecuri=$((esecuri+1))
fi
echo "CONTROL NEGATIV: $treceri treceri, $esecuri esecuri"
[ "$esecuri" -eq 0 ] || exit 1