build(deps): bump actions/cache from 4 to 5 #99
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Formal Verification Workflow | |
| # Runs the full formal verification suite: ProVerif, TLA+ (all 3 models), | |
| # Lean 4 (build + sorry gate), Tamarin (Docker), Verus (Docker), | |
| # and Tamarin PQ negative tests. | |
| # | |
| # Triggered on: | |
| # - Push to main/develop that modifies formal/ directory | |
| # - Pull requests that modify formal/ directory | |
| # - Manual workflow dispatch | |
| # - Weekly schedule (comprehensive verification) | |
| name: Formal Verification | |
| on: | |
| push: | |
| branches: [main, develop] | |
| paths: | |
| - "formal/**" | |
| - "crypto_core/src/verus*" | |
| - "crypto_core/src/lib.rs" | |
| - "crypto_core/src/aead.rs" | |
| - "meow_decoder/crypto.py" | |
| - "meow_decoder/crypto_enhanced.py" | |
| - "meow_decoder/ratchet.py" | |
| - "meow_decoder/pq_hybrid.py" | |
| - "meow_decoder/pq_ratchet_beacon.py" | |
| - "meow_decoder/forward_secrecy.py" | |
| - "meow_decoder/x25519_forward_secrecy.py" | |
| - "meow_decoder/schrodinger_encode.py" | |
| - "meow_decoder/encode.py" | |
| - "meow_decoder/decode_gif.py" | |
| - ".github/workflows/formal-verification.yml" | |
| # F-04: expanded trigger surface for all formally-modelled modules | |
| - "crypto_core/src/secure_alloc.rs" | |
| - "crypto_core/src/aead_wrapper.rs" | |
| - "crypto_core/src/pure_crypto.rs" | |
| - "meow_decoder/constant_time.py" | |
| - "meow_decoder/quantum_mixer.py" | |
| - "meow_decoder/fountain.py" | |
| pull_request: | |
| paths: | |
| - "formal/**" | |
| - "crypto_core/src/verus*" | |
| - "crypto_core/src/lib.rs" | |
| - "crypto_core/src/aead.rs" | |
| - "meow_decoder/crypto.py" | |
| - "meow_decoder/crypto_enhanced.py" | |
| - "meow_decoder/ratchet.py" | |
| - "meow_decoder/pq_hybrid.py" | |
| - "meow_decoder/pq_ratchet_beacon.py" | |
| - "meow_decoder/forward_secrecy.py" | |
| - "meow_decoder/x25519_forward_secrecy.py" | |
| - "meow_decoder/schrodinger_encode.py" | |
| - "meow_decoder/encode.py" | |
| - "meow_decoder/decode_gif.py" | |
| - ".github/workflows/formal-verification.yml" | |
| # F-04: expanded trigger surface for all formally-modelled modules | |
| - "crypto_core/src/secure_alloc.rs" | |
| - "crypto_core/src/aead_wrapper.rs" | |
| - "crypto_core/src/pure_crypto.rs" | |
| - "meow_decoder/constant_time.py" | |
| - "meow_decoder/quantum_mixer.py" | |
| - "meow_decoder/fountain.py" | |
| workflow_dispatch: | |
| inputs: | |
| verbose: | |
| description: "Show attack traces for failed queries" | |
| required: false | |
| default: "false" | |
| type: boolean | |
| schedule: | |
| # Weekly comprehensive verification on Sunday at 4 AM UTC (staggered from rust-security-suite) | |
| - cron: "0 4 * * 0" | |
| permissions: | |
| contents: read | |
| jobs: | |
| proverif: | |
| name: ProVerif Security Analysis | |
| permissions: | |
| contents: read | |
| pull-requests: write # Required for PR comment on failure | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - name: Install ProVerif | |
| run: | | |
| set -e | |
| sudo apt-get update | |
| # Ubuntu 24.04 (ubuntu-latest) does NOT ship ProVerif in its apt repos. | |
| # ProVerif is NOT on the Ubuntu 24.04 apt mirror, so we always fall | |
| # through to the source build. The source build uses OCaml without the | |
| # optional lablgtk2 GUI library, which produces the command-line binary. | |
| if sudo apt-get install -y proverif 2>/dev/null; then | |
| echo "ProVerif installed from apt" | |
| else | |
| echo "Building ProVerif 2.05 from source (expected on Ubuntu 24.04)..." | |
| # ocaml-native-compilers provides ocamlopt for fast native builds. | |
| # ocaml-findlib provides ocamlfind used by ./build. | |
| sudo apt-get install -y ocaml ocaml-native-compilers ocaml-findlib | |
| PROVERIF_URL="https://bblanche.gitlabpages.inria.fr/proverif/proverif2.05.tar.gz" | |
| wget -q "$PROVERIF_URL" -O /tmp/proverif.tar.gz | |
| tar xzf /tmp/proverif.tar.gz -C /tmp/ | |
| cd /tmp/proverif2.05 | |
| # ./build detects the absence of lablgtk2 and builds only the | |
| # command-line proverif binary (not proverifinter). | |
| # Note: ./build may exit non-zero when the optional lablgtk2 GUI | |
| # package is missing. This is harmless β the CLI binary is still | |
| # produced. We capture the exit code and check for the binary. | |
| set +e | |
| ./build 2>&1 | |
| BUILD_EXIT=$? | |
| set -e | |
| # Hard-fail if the binary was not produced. | |
| if [ ! -f proverif ]; then | |
| echo "ERROR: proverif binary not found after ./build (exit $BUILD_EXIT)" | |
| exit 1 | |
| fi | |
| echo "ProVerif CLI binary built successfully (build exit=$BUILD_EXIT)." | |
| sudo cp proverif /usr/local/bin/proverif | |
| cd - | |
| fi | |
| # Verify installation succeeded. This must not have a || fallback; | |
| # if it fails, the ProVerif job should fail rather than run silently | |
| # without a working tool and report false success. | |
| # Note: ProVerif 2.05 does not support -version; use -help instead. | |
| proverif -help 2>&1 | head -5 | |
| - name: Run ProVerif Analysis | |
| id: proverif | |
| run: | | |
| cd formal/proverif | |
| echo "============================================" | |
| echo "π Running ProVerif Security Analysis" | |
| echo "============================================" | |
| echo "" | |
| # Run ProVerif with error capture | |
| set +e | |
| proverif meow_encode.pv 2>&1 | tee proverif_output.txt | |
| PROVERIF_EXIT=$? | |
| set -e | |
| # Extract verification results | |
| echo "" | |
| echo "============================================" | |
| echo "π Verification Summary" | |
| echo "============================================" | |
| # Count passed/failed queries | |
| PASSED=$(grep -c "is true" proverif_output.txt || echo "0") | |
| FAILED=$(grep -c "is false" proverif_output.txt || echo "0") | |
| CANNOT=$(grep -c "cannot be proved" proverif_output.txt || echo "0") | |
| echo "β Verified (true): $PASSED" | |
| echo "β Failed (false): $FAILED" | |
| echo "β οΈ Could not prove: $CANNOT" | |
| echo "" | |
| # Set output for badge | |
| echo "passed=$PASSED" >> $GITHUB_OUTPUT | |
| echo "failed=$FAILED" >> $GITHUB_OUTPUT | |
| # Check for critical security failures | |
| # Note: Some session correspondence queries return FALSE due to model | |
| # architecture (replication creates separate session IDs). These are | |
| # documented in meow_encode.pv and are NOT security vulnerabilities. | |
| # Critical properties that MUST be true: | |
| CRITICAL_FAILURES=0 | |
| # Check secrecy properties | |
| if grep -q "Query not attacker(real_secret" proverif_output.txt; then | |
| if ! grep -q "Query not attacker(real_secret.*is true" proverif_output.txt; then | |
| echo "β CRITICAL: Real secret secrecy violated!" | |
| CRITICAL_FAILURES=$((CRITICAL_FAILURES + 1)) | |
| fi | |
| fi | |
| if grep -q "Query not attacker(real_password" proverif_output.txt; then | |
| if ! grep -q "Query not attacker(real_password.*is true" proverif_output.txt; then | |
| echo "β CRITICAL: Password secrecy violated!" | |
| CRITICAL_FAILURES=$((CRITICAL_FAILURES + 1)) | |
| fi | |
| fi | |
| # Check duress safety | |
| if grep -q "DuressPasswordUsed.*DecoderOutputReal.*is false" proverif_output.txt; then | |
| echo "β CRITICAL: Duress mode outputs real secret!" | |
| CRITICAL_FAILURES=$((CRITICAL_FAILURES + 1)) | |
| fi | |
| # Check authentication requirement | |
| if grep -q "DecoderOutputReal.*DecoderAuthenticated.*is false" proverif_output.txt; then | |
| echo "β CRITICAL: Output without authentication!" | |
| CRITICAL_FAILURES=$((CRITICAL_FAILURES + 1)) | |
| fi | |
| if [ "$CRITICAL_FAILURES" -gt 0 ]; then | |
| echo "" | |
| echo "β οΈ $CRITICAL_FAILURES critical security properties failed!" | |
| exit 1 | |
| fi | |
| # Note: Session correspondence queries (EncoderEncrypted, EncoderStarted, | |
| # EncoderGeneratedNonce) may return FALSE due to model structure. This is | |
| # expected and documented - see meow_encode.pv for explanation. | |
| echo "" | |
| echo "βΉοΈ Session correspondence queries may show FALSE - this is expected" | |
| echo " for multi-session models. See meow_encode.pv for documentation." | |
| echo "" | |
| echo "β All critical security properties verified!" | |
| - name: Run ProVerif NEGATIVE test (replay without counter) | |
| run: | | |
| echo "============================================" | |
| echo "π΄ ProVerif NEGATIVE test: Replay Without Counter Check" | |
| echo "============================================" | |
| set +e | |
| cd formal/proverif | |
| proverif meow_encode_NEGATIVE_ReplayNoCounterCheck.pv 2>&1 | tee proverif_neg_output.txt | |
| set -e | |
| # In the negative model, RejectFrame is never emitted (replay | |
| # detection disabled). The fail-closed query is trivially TRUE, | |
| # confirming that removing table-based nonce tracking removes | |
| # replay protection. If the positive model's replay queries | |
| # also returned TRUE but they return FALSE, something is wrong. | |
| echo "" | |
| echo "β ProVerif negative test completed (documents replay vulnerability)" | |
| # F-02: Run additional ProVerif models not previously CI-gated | |
| - name: Run Additional ProVerif Models | |
| run: | | |
| cd formal/proverif | |
| echo "============================================" | |
| echo "π ProVerif β Additional Security Models (F-02)" | |
| echo "============================================" | |
| EXTRA_FAILED=0 | |
| for MODEL in pq_beacon_pcs.pv manifest_signing.pv deadmans_switch_duress.pv meow_aad_8field_binding.pv; do | |
| if [ ! -f "$MODEL" ]; then | |
| echo "β οΈ $MODEL not found, skipping" | |
| continue | |
| fi | |
| echo "" | |
| echo "--- Checking $MODEL ---" | |
| set +e | |
| proverif "$MODEL" 2>&1 | tee "extra_${MODEL%.pv}.txt" | |
| PV_EXIT=$? | |
| set -e | |
| # Fail if ProVerif found a secrecy/auth violation (is false) | |
| if grep -q "RESULT.*is false" "extra_${MODEL%.pv}.txt"; then | |
| echo "β $MODEL: query falsified β potential security issue" | |
| EXTRA_FAILED=$((EXTRA_FAILED + 1)) | |
| elif [ "$PV_EXIT" -ne 0 ]; then | |
| echo "β οΈ $MODEL: ProVerif returned exit $PV_EXIT (may be non-fatal)" | |
| else | |
| PASSED_QUERIES=$(grep -c "is true" "extra_${MODEL%.pv}.txt" || echo 0) | |
| echo "β $MODEL: $PASSED_QUERIES queries verified" | |
| fi | |
| done | |
| if [ "$EXTRA_FAILED" -gt 0 ]; then | |
| echo "β $EXTRA_FAILED additional ProVerif model(s) failed" | |
| exit 1 | |
| fi | |
| echo "" | |
| echo "β All additional ProVerif models passed" | |
| - name: Generate HTML Report | |
| if: always() | |
| run: | | |
| cd formal/proverif | |
| mkdir -p html_output | |
| proverif -html html_output meow_encode.pv || true | |
| - name: Upload ProVerif Results | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.0 | |
| with: | |
| name: proverif-results | |
| path: | | |
| formal/proverif/proverif_output.txt | |
| formal/proverif/proverif_neg_output.txt | |
| formal/proverif/html_output/ | |
| retention-days: 30 | |
| - name: Comment on PR (if failed) | |
| if: failure() && github.event_name == 'pull_request' | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const output = fs.readFileSync('formal/proverif/proverif_output.txt', 'utf8'); | |
| // Extract failed queries | |
| const failedLines = output.split('\n') | |
| .filter(line => line.includes('is false') || line.includes('cannot be proved')) | |
| .join('\n'); | |
| const body = '## β οΈ ProVerif Security Analysis Failed\n' + | |
| '\nThe following security properties could not be verified:\n' + | |
| '\n```\n' + failedLines + '\n```\n' + | |
| '\nThis indicates a potential security issue in the protocol design.\n' + | |
| 'Please review the changes and ensure cryptographic properties are maintained.'; | |
| github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: body | |
| }); | |
| tlaplus: | |
| name: TLA+ Model Checking | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - name: Set up Java | |
| uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4 | |
| with: | |
| distribution: "temurin" | |
| java-version: "17" | |
| - name: Download TLA+ Tools | |
| run: | | |
| mkdir -p formal/tla | |
| curl -sL -o formal/tla/tla2tools.jar \ | |
| https://github.com/tlaplus/tlaplus/releases/download/v1.8.0/tla2tools.jar | |
| echo "Downloaded tla2tools.jar ($(wc -c < formal/tla/tla2tools.jar) bytes)" | |
| - name: Run TLA+ Model Checker | |
| run: | | |
| cd formal/tla | |
| echo "============================================" | |
| echo "π Running TLA+ Model Checking" | |
| echo "============================================" | |
| echo "" | |
| FAILED=0 | |
| # Run TLC model checker on MeowEncode.tla | |
| echo "π Checking MeowEncode.tla..." | |
| java -jar tla2tools.jar \ | |
| -config MeowEncode.cfg \ | |
| -workers auto \ | |
| -deadlock \ | |
| MeowEncode.tla 2>&1 | tee tlc_encode_output.txt | |
| if grep -qE "(No error has been found|Model checking completed\. No error)" tlc_encode_output.txt; then | |
| echo "β MeowEncode: No invariant violations found" | |
| else | |
| echo "β MeowEncode: Model checking found issues" | |
| FAILED=1 | |
| fi | |
| echo "" | |
| # Run TLC model checker on MeowFountain.tla | |
| echo "π Checking MeowFountain.tla..." | |
| java -jar tla2tools.jar \ | |
| -config MeowFountain.cfg \ | |
| -workers auto \ | |
| -deadlock \ | |
| MeowFountain.tla 2>&1 | tee tlc_fountain_output.txt | |
| if grep -qE "(No error has been found|Model checking completed\. No error)" tlc_fountain_output.txt; then | |
| echo "β MeowFountain: No invariant violations found" | |
| else | |
| echo "β MeowFountain: Model checking found issues" | |
| FAILED=1 | |
| fi | |
| echo "" | |
| # Run TLC model checker on MeowStreaming.tla | |
| echo "π Checking MeowStreaming.tla..." | |
| java -jar tla2tools.jar \ | |
| -config MeowStreaming.cfg \ | |
| -workers auto \ | |
| -deadlock \ | |
| MeowStreaming.tla 2>&1 | tee tlc_streaming_output.txt | |
| if grep -qE "(No error has been found|Model checking completed\. No error)" tlc_streaming_output.txt; then | |
| echo "β MeowStreaming: No invariant violations found" | |
| else | |
| echo "β MeowStreaming: Model checking found issues" | |
| FAILED=1 | |
| fi | |
| # F-01: Run 4 additional TLA+ models not previously CI-gated | |
| echo "" | |
| echo "--- F-01: Additional TLA+ models ---" | |
| run_tlc() { | |
| local SPEC="$1" | |
| local CFG="$2" | |
| local LABEL="$3" | |
| local OUTFILE="$4" | |
| echo "" | |
| echo "π Checking $SPEC..." | |
| java -jar tla2tools.jar \ | |
| -config "$CFG" \ | |
| -workers auto \ | |
| -deadlock \ | |
| "$SPEC" 2>&1 | tee "$OUTFILE" | |
| if grep -qE "(No error has been found|Model checking completed\. No error)" "$OUTFILE"; then | |
| echo "β $LABEL: No invariant violations found" | |
| else | |
| echo "β $LABEL: Model checking found issues" | |
| FAILED=1 | |
| fi | |
| } | |
| run_tlc TimingEqualizer.tla TimingEqualizer.cfg "TimingEqualizer" tlc_timing_output.txt | |
| run_tlc MeowRatchet.tla MeowRatchet.cfg "MeowRatchet" tlc_ratchet_output.txt | |
| run_tlc ExpiryProtocol.tla ExpiryProtocol.cfg "ExpiryProtocol" tlc_expiry_output.txt | |
| run_tlc MasterRatchet.tla MasterRatchet.cfg "MasterRatchet" tlc_master_output.txt | |
| # Combine outputs for artifact | |
| cat tlc_encode_output.txt tlc_fountain_output.txt tlc_streaming_output.txt \ | |
| tlc_timing_output.txt tlc_ratchet_output.txt tlc_expiry_output.txt tlc_master_output.txt \ | |
| > tlc_output.txt | |
| echo "" | |
| echo "============================================" | |
| echo "π TLC Summary" | |
| echo "============================================" | |
| if [ "$FAILED" -eq 0 ]; then | |
| echo "β All TLA+ models verified successfully" | |
| else | |
| echo "β Some TLA+ models failed verification" | |
| exit 1 | |
| fi | |
| - name: Upload TLA+ Results | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.0 | |
| with: | |
| name: tla-results | |
| path: | | |
| formal/tla/tlc_output.txt | |
| formal/tla/tlc_encode_output.txt | |
| formal/tla/tlc_fountain_output.txt | |
| formal/tla/tlc_streaming_output.txt | |
| formal/tla/tlc_timing_output.txt | |
| formal/tla/tlc_ratchet_output.txt | |
| formal/tla/tlc_expiry_output.txt | |
| formal/tla/tlc_master_output.txt | |
| retention-days: 30 | |
| lean: | |
| name: Lean 4 Proofs + Sorry Gate | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - name: Install elan (Lean toolchain manager) | |
| run: | | |
| curl -sSf https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh | sh -s -- -y --default-toolchain none | |
| echo "$HOME/.elan/bin" >> $GITHUB_PATH | |
| - name: Cache elan toolchain and lake packages | |
| uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 | |
| with: | |
| path: | | |
| ~/.elan | |
| formal/lean/.lake | |
| key: lean-${{ hashFiles('formal/lean/lean-toolchain', 'formal/lean/lakefile.lean', 'formal/lean/lake-manifest.json') }} | |
| restore-keys: | | |
| lean- | |
| - name: Build Lean proofs | |
| run: | | |
| cd formal/lean | |
| echo "============================================" | |
| echo "π· Building Lean 4 Fountain Code Proofs" | |
| echo "============================================" | |
| lake build 2>&1 | tee lean_build_output.txt | |
| if [ "${PIPESTATUS[0]}" -ne 0 ]; then | |
| echo "β Lean 4 build failed" | |
| exit 1 | |
| fi | |
| echo "β Lean 4 build succeeded" | |
| - name: Sorry gate (no unapproved sorry) | |
| run: | | |
| echo "============================================" | |
| echo "π· Lean Sorry Gate β checking for unapproved sorry" | |
| echo "============================================" | |
| # Count actual sorry keywords in Lean code (not in comments or documentation). | |
| # Exclude lines containing AXIOM:/APPROVED: tags, Lean line comments (--), | |
| # and documentation references to 'sorry' in block comments. | |
| # NOTE: grep -n output has format "file:N:content"; match comment lines with | |
| # ':[[:blank:]]*--' instead of '^[[:blank:]]*--' to account for the prefix. | |
| SORRY_COUNT=$(grep -rn '\bsorry\b' formal/lean/ --include='*.lean' \ | |
| --exclude-dir='.lake' --exclude-dir='lake-packages' \ | |
| | grep -v -e 'AXIOM:' -e 'APPROVED:' -e ':[[:blank:]]*--' -e ':[[:blank:]]*/--' \ | |
| | grep -v -i -e '`sorry`' -e "sorry instance" -e "sorry statement" \ | |
| -e "sorry tag" -e "approved sorry" -e "sorry for" \ | |
| | wc -l) | |
| if [ "$SORRY_COUNT" -gt 0 ]; then | |
| echo "β Found $SORRY_COUNT unapproved sorry statement(s):" | |
| grep -rn '\bsorry\b' formal/lean/ --include='*.lean' \ | |
| --exclude-dir='.lake' --exclude-dir='lake-packages' \ | |
| | grep -v -e 'AXIOM:' -e 'APPROVED:' -e ':[[:blank:]]*--' -e ':[[:blank:]]*/--' \ | |
| | grep -v -i -e '`sorry`' -e "sorry instance" -e "sorry statement" \ | |
| -e "sorry tag" -e "approved sorry" -e "sorry for" | |
| exit 1 | |
| else | |
| echo "β No unapproved sorry statements found." | |
| fi | |
| - name: Upload Lean Results | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.0 | |
| with: | |
| name: lean-results | |
| path: formal/lean/lean_build_output.txt | |
| retention-days: 30 | |
| tamarin: | |
| name: Tamarin OE (Docker β MEOW3 + MEOW4 PQ) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - name: Build Tamarin Docker image | |
| run: docker build -f formal/Dockerfile.tamarin -t meow-tamarin . | |
| - name: Run Tamarin MEOW3 + MEOW4 positive proofs | |
| run: | | |
| echo "============================================" | |
| echo "π£ Tamarin OE β MEOW3 + MEOW4 PQ (Docker)" | |
| echo "============================================" | |
| docker run --rm meow-tamarin 2>&1 | tee tamarin_output.txt | |
| - name: Run Tamarin AEAD AAD-binding model (MeowAEADBinding.spthy) | |
| run: | | |
| echo "============================================" | |
| echo "π΅ Tamarin AEAD binding β 4-ary encrypt with AAD" | |
| echo " Closes INV-004: previous models used 2-ary senc, missing AAD" | |
| echo "============================================" | |
| docker run --rm meow-tamarin sh -c \ | |
| "tamarin-prover --prove /formal/tamarin/MeowAEADBinding.spthy" \ | |
| 2>&1 | tee tamarin_aead.txt | |
| if grep -q "verified" tamarin_aead.txt; then | |
| echo "β AEAD binding lemmas verified" | |
| elif grep -q "falsified" tamarin_aead.txt; then | |
| echo "β AEAD binding lemma FALSIFIED β security property violated" | |
| exit 1 | |
| else | |
| echo "β οΈ Tamarin analysis incomplete (timeout/no-lemma) β treating as pass" | |
| fi | |
| - name: Run Tamarin PQ negative tests | |
| run: | | |
| echo "============================================" | |
| echo "π΄ Tamarin PQ NEGATIVE tests (should FAIL)" | |
| echo "============================================" | |
| echo "" | |
| echo "Test 1: KEM ct not bound to HMAC β KEM integrity lemma should be FALSIFIED" | |
| set +e | |
| docker run --rm meow-tamarin sh -c \ | |
| "tamarin-prover --prove /formal/tamarin/MeowDuressEquivPQ_NEGATIVE_NoKEMBinding.spthy" \ | |
| 2>&1 | tee tamarin_neg1.txt | |
| NEG1_EXIT=$? | |
| set -e | |
| if [ "$NEG1_EXIT" -ne 0 ]; then | |
| echo "β Negative test 1 failed as expected (exit $NEG1_EXIT)" | |
| else | |
| if grep -q "falsified" tamarin_neg1.txt || grep -q "analysis incomplete" tamarin_neg1.txt; then | |
| echo "β Negative test 1: property falsified as expected" | |
| else | |
| echo "β Negative test 1 PASSED unexpectedly β harness may be broken" | |
| exit 1 | |
| fi | |
| fi | |
| echo "" | |
| echo "Test 2: Non-uniform failure observables β PQ_Failure_Uniform_Observable should FAIL" | |
| set +e | |
| docker run --rm meow-tamarin sh -c \ | |
| "tamarin-prover --diff /formal/tamarin/MeowDuressEquivPQ_NEGATIVE_LeaksFailureReason.spthy --prove" \ | |
| 2>&1 | tee tamarin_neg2.txt | |
| NEG2_EXIT=$? | |
| set -e | |
| if [ "$NEG2_EXIT" -ne 0 ]; then | |
| echo "β Negative test 2 failed as expected (exit $NEG2_EXIT)" | |
| else | |
| if grep -q "falsified" tamarin_neg2.txt || grep -q "analysis incomplete" tamarin_neg2.txt; then | |
| echo "β Negative test 2: property falsified as expected" | |
| else | |
| echo "β Negative test 2 PASSED unexpectedly β harness may be broken" | |
| exit 1 | |
| fi | |
| fi | |
| # F-03: Run additional Tamarin models not previously CI-gated. | |
| # Critical models (SchrΓΆdinger deniability, key commitment, ratchet FS) are | |
| # now "blocking" β timeout counts as a failure, not inconclusive. | |
| # Auxiliary models (OE simplifications, guard pages) remain non-blocking on | |
| # timeout to avoid flaky CI on large models with many lemmas. | |
| - name: Run Tamarin Extended Models (F-03) | |
| run: | | |
| echo "============================================" | |
| echo "π£ Tamarin Extended Models (F-03)" | |
| echo "============================================" | |
| EXTENDED_FAILED=0 | |
| # $1 = model filename | |
| # $2 = description | |
| # $3 = "blocking" (timeout is failure) | "nonblocking" (timeout is inconclusive) | |
| run_tamarin_model() { | |
| local MODEL="$1" | |
| local DESC="$2" | |
| local BLOCKING="${3:-nonblocking}" | |
| local EXTRA_FLAGS="${4:-}" | |
| echo "" | |
| echo "--- $DESC ---" | |
| set +e | |
| timeout 1800 docker run --rm meow-tamarin sh -c \ | |
| "tamarin-prover $EXTRA_FLAGS --prove /formal/tamarin/$MODEL" \ | |
| 2>&1 | tee "tamarin_ext_${MODEL%.spthy}.txt" | |
| EXIT=$? | |
| set -e | |
| if [ "$EXIT" -eq 124 ]; then | |
| if [ "$BLOCKING" = "blocking" ]; then | |
| echo "β $MODEL timed out (30 min) β BLOCKING model must complete; increase runner resources or split model further" | |
| EXTENDED_FAILED=$((EXTENDED_FAILED + 1)) | |
| else | |
| echo "β οΈ $MODEL timed out (30 min) β treated as inconclusive (non-blocking auxiliary model)" | |
| fi | |
| elif grep -q "falsified" "tamarin_ext_${MODEL%.spthy}.txt"; then | |
| echo "β $MODEL: lemma FALSIFIED β security property violated" | |
| EXTENDED_FAILED=$((EXTENDED_FAILED + 1)) | |
| elif grep -q "verified" "tamarin_ext_${MODEL%.spthy}.txt"; then | |
| NVERIFIED=$(grep -c "verified" "tamarin_ext_${MODEL%.spthy}.txt" || echo "?") | |
| echo "β $MODEL: $NVERIFIED lemma(s) verified" | |
| else | |
| if [ "$BLOCKING" = "blocking" ]; then | |
| echo "β $MODEL: analysis incomplete β BLOCKING model must verify; check Tamarin output artifact" | |
| EXTENDED_FAILED=$((EXTENDED_FAILED + 1)) | |
| else | |
| echo "β οΈ $MODEL: analysis incomplete β inconclusive (non-blocking auxiliary model)" | |
| fi | |
| fi | |
| } | |
| # ββ BLOCKING: critical security properties ββββββββββββββββββββββββ | |
| # RL-3 mitigation: split 15-lemma SchrΓΆdinger model into two files. | |
| run_tamarin_model "MeowSchrodingerDeniability_Core.spthy" "SchrΓΆdinger deniability core (lemmas 1-10)" blocking | |
| run_tamarin_model "MeowSchrodingerDeniability_Ratchet.spthy" "SchrΓΆdinger deniability ratchet (lemmas 11-15)" blocking | |
| run_tamarin_model "MeowKeyCommitment.spthy" "Key commitment / invisible salamanders" blocking | |
| run_tamarin_model "MeowRatchetFS.spthy" "Per-frame forward secrecy + PCS (ratchet)" blocking | |
| run_tamarin_model "meow_deadmans_switch.spthy" "Dead man's switch duress protocol" blocking | |
| # ββ NON-BLOCKING: large / auxiliary models ββββββββββββββββββββββββ | |
| run_tamarin_model "MeowSchrodingerDeniabilityTiming.spthy" "SchrΓΆdinger deniability with timing channel (Gap-7 fix, lemmas T1-T5)" nonblocking | |
| run_tamarin_model "secure_alloc_guard_pages.spthy" "Guard page memory isolation proofs" nonblocking | |
| # F-10: Gate the 3 remaining Tamarin models not covered by F-03 | |
| run_tamarin_model "MeowRatchetHeaderOE.spthy" "Ratchet header encryption OE (frame-index hiding)" nonblocking | |
| run_tamarin_model "MeowSchrodingerOE.spthy" "SchrΓΆdinger dual-password OE (simplified)" nonblocking | |
| run_tamarin_model "meow_encode_equiv.spthy" "Encoding observational equivalence (minimal)" nonblocking | |
| if [ "$EXTENDED_FAILED" -gt 0 ]; then | |
| echo "" | |
| echo "β $EXTENDED_FAILED Tamarin model(s) failed (falsified lemma or blocking timeout)" | |
| exit 1 | |
| fi | |
| echo "" | |
| echo "β Tamarin extended models: all blocking models verified, no falsified lemmas" | |
| - name: Upload Tamarin Results | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.0 | |
| with: | |
| name: tamarin-results | |
| path: | | |
| tamarin_output.txt | |
| tamarin_aead.txt | |
| tamarin_neg1.txt | |
| tamarin_neg2.txt | |
| tamarin_ext_*.txt | |
| retention-days: 30 | |
| verus: | |
| name: Verus Structural Regression Check (NOT Z3) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - name: Build Verus Docker image | |
| run: docker build -f formal/Dockerfile.verus -t meow-verus . | |
| - name: Run Verus structural regression check | |
| run: | | |
| echo "============================================" | |
| echo "π Verus Structural Regression Check" | |
| echo " (counts proof fn / spec fn β does NOT invoke Z3)" | |
| echo "============================================" | |
| docker run --rm meow-verus 2>&1 | tee verus_output.txt | |
| if grep -q "structural inventory:: proof_spec_fn_count:" verus_output.txt; then | |
| COUNT=$(grep "structural inventory" verus_output.txt | grep -oP 'proof_spec_fn_count: \K\d+') | |
| echo "Proof/spec fn declarations found: $COUNT" | |
| if [ "$COUNT" -lt 1 ]; then | |
| echo "β Regression: Verus proof blocks appear to have been deleted" | |
| exit 1 | |
| fi | |
| echo "β Structural regression check passed ($COUNT declarations)" | |
| else | |
| echo "β Could not parse structural check output" | |
| exit 1 | |
| fi | |
| - name: Upload Verus Results | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.0 | |
| with: | |
| name: verus-results | |
| path: verus_output.txt | |
| retention-days: 30 | |
| constant-time-stats: | |
| name: Dudect Statistical Constant-Time (RL-2 empirical) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - name: Install Rust stable | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache cargo registry | |
| uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| crypto_core/target | |
| key: ${{ runner.os }}-cargo-ct-${{ hashFiles('crypto_core/Cargo.lock') }} | |
| - name: Run constant-time statistical benchmarks | |
| run: | | |
| cd crypto_core | |
| echo "============================================" | |
| echo "π Running dudect-style constant-time benchmarks" | |
| echo " (RL-2 empirical mitigation β not a formal proof)" | |
| echo "============================================" | |
| echo "" | |
| # Run with --test flag to execute the measurement loop without | |
| # the full criterion HTML report overhead. | |
| cargo bench --bench constant_time 2>&1 | tee ct_bench_output.txt | |
| # Check for TIMING_LEAK_WARNING lines emitted by the bench. | |
| if grep -q "TIMING_LEAK_WARNING" ct_bench_output.txt; then | |
| echo "" | |
| echo "β οΈ Potential timing difference detected β see artifact for details." | |
| echo " This is a WARNING, not a hard failure. Timing jitter on shared" | |
| echo " CI infrastructure can produce false positives. Investigate if" | |
| echo " |t| is consistently > 4.5 across multiple runs." | |
| else | |
| echo "" | |
| echo "β No statistically significant timing difference detected" | |
| fi | |
| # Always exit 0 (warning-only) unless MEOW_CT_STRICT=1 | |
| exit 0 | |
| - name: Upload timing results | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.0 | |
| with: | |
| name: constant-time-stats | |
| path: crypto_core/ct_bench_output.txt | |
| retention-days: 30 | |
| # Summary job to provide unified status | |
| verification-summary: | |
| name: Verification Summary | |
| needs: [proverif, tlaplus, lean, tamarin, verus, constant-time-stats] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| if: always() | |
| steps: | |
| - name: Check results | |
| run: | | |
| echo "============================================" | |
| echo "π Formal Verification Summary" | |
| echo "============================================" | |
| echo "" | |
| PROVERIF_STATUS="${{ needs.proverif.result }}" | |
| TLAPLUS_STATUS="${{ needs.tlaplus.result }}" | |
| LEAN_STATUS="${{ needs.lean.result }}" | |
| TAMARIN_STATUS="${{ needs.tamarin.result }}" | |
| VERUS_STATUS="${{ needs.verus.result }}" | |
| CT_STATUS="${{ needs.constant-time-stats.result }}" | |
| echo "ProVerif: $PROVERIF_STATUS" | |
| echo "TLA+: $TLAPLUS_STATUS" | |
| echo "Lean 4: $LEAN_STATUS" | |
| echo "Tamarin: $TAMARIN_STATUS" | |
| echo "Verus: $VERUS_STATUS" | |
| echo "CT-Stats: $CT_STATUS (warning-only, RL-2 empirical)" | |
| echo "" | |
| ALL_PASS=true | |
| for STATUS in "$PROVERIF_STATUS" "$TLAPLUS_STATUS" "$LEAN_STATUS" "$TAMARIN_STATUS" "$VERUS_STATUS"; do | |
| if [ "$STATUS" != "success" ]; then | |
| ALL_PASS=false | |
| fi | |
| done | |
| # CT_STATUS is warning-only (see constant-time-stats job); do not gate on it. | |
| if [ "$ALL_PASS" = "true" ]; then | |
| echo "β All formal verification passed!" | |
| exit 0 | |
| else | |
| echo "β οΈ Some verification tasks failed" | |
| exit 1 | |
| fi |