diff --git a/.github/workflows/formal-verification.yml b/.github/workflows/formal-verification.yml index d9184ae..1372ac2 100644 --- a/.github/workflows/formal-verification.yml +++ b/.github/workflows/formal-verification.yml @@ -61,7 +61,29 @@ jobs: - name: Run Kani proofs (14 fast + 5 DSSE) run: cargo kani -p wsc --default-unwind 4 --output-format terse timeout-minutes: 60 - # 14/19 proofs pass within timeout. 5 DSSE proofs use Vec allocation - # in compute_pae() which is expensive for CBMC. These properties are - # proven at the Verus SMT level instead (unbounded, no allocation). + continue-on-error: true + + rocq-proofs: + name: Rocq/coq-of-rust Translation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Nix + uses: cachix/install-nix-action@v30 + with: + nix_path: nixpkgs=channel:nixos-unstable + + - name: Setup Bazel + uses: bazel-contrib/setup-bazel@0.14.0 + with: + bazelisk-cache: true + disk-cache: ${{ github.workflow }}-rocq + repository-cache: true + + - name: Translate PAE to Rocq and compile proof + run: bazel build //verification/rocq:pae_verified + timeout-minutes: 30 + # coq-of-rust needs Rust nightly with LLVM internals — Nix provides + # this locally but CI may need LIBRARY_PATH configuration. continue-on-error: true diff --git a/MODULE.bazel b/MODULE.bazel index 2a4ee8f..94f9ec9 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -145,3 +145,7 @@ git_override( commit = "6a8da0b", remote = "https://github.com/pulseengine/rules_rocq_rust.git", ) + +# Import coq-of-rust repos from rules_rocq_rust extension +rocq_of_rust = use_extension("@rules_rocq_rust//coq_of_rust:extensions.bzl", "rocq_of_rust") +use_repo(rocq_of_rust, "rocq_of_rust_source") diff --git a/verification/rocq/BUILD.bazel b/verification/rocq/BUILD.bazel index 8d283d3..edb14ea 100644 --- a/verification/rocq/BUILD.bazel +++ b/verification/rocq/BUILD.bazel @@ -1,17 +1,10 @@ load("@rules_rocq_rust//coq_of_rust:defs.bzl", "rocq_rust_verified_library") -# Translate DSSE module from Rust to Rocq via coq-of-rust (CV-22) +# Translate PAE (Pre-Authentication Encoding) to Rocq via coq-of-rust (CV-22) +# Self-contained extraction of compute_pae from dsse.rs — no external deps. rocq_rust_verified_library( - name = "dsse_verified", - rust_sources = ["//src/lib:src/dsse.rs"], - extra_flags = ["-impredicative-set"], - visibility = ["//visibility:public"], -) - -# Translate format detection module from Rust to Rocq (CV-23) -rocq_rust_verified_library( - name = "format_verified", - rust_sources = ["//src/lib:src/format/mod.rs"], + name = "pae_verified", + rust_sources = ["pae.rs"], extra_flags = ["-impredicative-set"], visibility = ["//visibility:public"], ) diff --git a/verification/rocq/pae.rs b/verification/rocq/pae.rs new file mode 100644 index 0000000..335354d --- /dev/null +++ b/verification/rocq/pae.rs @@ -0,0 +1,64 @@ +/// Pre-Authentication Encoding for DSSE (extracted for Rocq verification). +/// +/// This is a self-contained extraction of the PAE function from dsse.rs, +/// suitable for coq-of-rust translation. + +/// Compute Pre-Authentication Encoding (PAE) per DSSE spec. +/// +/// PAE(payloadType, payload) = +/// "DSSEv1 " || LEN(payloadType) || " " || payloadType || " " || +/// LEN(payload) || " " || payload +pub fn compute_pae(payload_type: &str, payload: &[u8]) -> Vec { + let mut pae = Vec::new(); + + // Header + pae.extend_from_slice(b"DSSEv1 "); + + // LEN(payloadType) SP payloadType + pae.extend_from_slice(payload_type.len().to_string().as_bytes()); + pae.push(b' '); + pae.extend_from_slice(payload_type.as_bytes()); + + // SP + pae.push(b' '); + + // LEN(payload) SP payload + pae.extend_from_slice(payload.len().to_string().as_bytes()); + pae.push(b' '); + pae.extend_from_slice(payload); + + pae +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_pae_deterministic() { + let a = compute_pae("test", b"data"); + let b = compute_pae("test", b"data"); + assert_eq!(a, b); + } + + #[test] + fn test_pae_injective_types() { + let a = compute_pae("type_a", b"data"); + let b = compute_pae("type_b", b"data"); + assert_ne!(a, b); + } + + #[test] + fn test_pae_injective_payloads() { + let a = compute_pae("type", b"data_a"); + let b = compute_pae("type", b"data_b"); + assert_ne!(a, b); + } + + #[test] + fn test_pae_length_prefix_prevents_ambiguity() { + let a = compute_pae("ab", b"cd"); + let b = compute_pae("a", b"bcd"); + assert_ne!(a, b); + } +}