1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
//! Kani proof harnesses for `secure_data`.
//!
//! Compiled only under `cargo kani` via the Kani-injected `#[cfg(kani)]`.
//! Regular `cargo build` and `cargo test` runs exclude this module entirely,
//! so adding harnesses has zero impact on the production build.
//!
//! The `nonce_non_zero` harness is the **bootstrap proof** — its job is to
//! validate that the Kani toolchain is installed, the workspace is wired up,
//! and the advisory CI lane runs on every PR. It proves a small, sound
//! property: a nonce array seeded from a CSPRNG that is *assumed* to never
//! return all-zeros (a cryptographically negligible event modelled here as
//! an axiom) is non-zero through any of the structural paths the AEAD
//! pipeline exercises before reaching the AEAD primitive itself.
//!
//! More substantive proofs in this module land in formal-verification M3
//! (`secure_data` nonce-uniqueness within a single encrypt-call path,
//! `secure_errors` no-internal-detail-leak). The AEAD primitive itself is
//! axiomatised — Kani's bit-precise model checking covers the pipeline
//! around AEAD, not the AEAD construction itself (per the research dossier
//! yellow-flag for AEAD via FFI/asm).
//!
//! See:
//! - Runbook: `docs/slo/future/RUNBOOK-formal-verification-kani-tla.md` M1.
//! - Dev guide: `docs/dev-guide/formal-verification.md`.
//! - CI: `.github/workflows/kani.yml` (advisory, 15-min cap).
// AES-256-GCM nonce length per NIST SP 800-38D (96 bits = 12 bytes).
// Hard-coded here so the fv M1 PR does not depend on `pq::sizes` (which
// lands in pq-readiness M1, a separate PR). Once both PRs merge, future
// fv proofs can import `pq::sizes::AES_256_GCM_NONCE_LEN` instead.
const AES_256_GCM_NONCE_LEN: usize = 12;
/// Proof: a 12-byte nonce array, seeded from a CSPRNG that is assumed not
/// to return all-zeros, remains non-zero after the structural copies the
/// `EnvelopeEncrypted` builder performs before reaching the AEAD.
///
/// Modelling notes:
///
/// 1. `kani::any::<[u8; AES_256_GCM_NONCE_LEN]>()` provides a fully
/// symbolic 12-byte array — Kani explores every possible byte pattern
/// within bounds.
/// 2. `kani::assume(nonce != [0u8; AES_256_GCM_NONCE_LEN])` is the CSPRNG
/// axiom: `OsRng::fill_bytes` does not produce the all-zero output
/// except with cryptographically negligible probability. We model this
/// exclusion as an axiom rather than attempting to verify the CSPRNG
/// itself (which is FFI-backed and not within Kani's verification
/// surface).
/// 3. The post-condition checks that after a copy (the simplest pipeline
/// transformation), the array remains non-zero. This validates that no
/// structural transformation in the path zeros the nonce — a property
/// that is trivially true but exercises the entire Kani pipeline end
/// to end.
///
/// This harness is intentionally minimal — it is the bootstrap proof for
/// the whole pipeline. Replacing it with a vacuous proof (or one that
/// passes regardless of the implementation) would be a regression of the
/// proof's purpose; future contributors should extend, not weaken.
/// Proof: the AES-256-GCM nonce length constant matches the FIPS 203 / NIST
/// SP 800-38D requirement of 12 bytes. This is a static, build-time
/// invariant — proving it under Kani is essentially free and serves as a
/// regression guard against an accidental constant change in
/// `crate::pq::sizes` that would silently break wire-format compatibility.
// ── fv M3 — nonce-property proofs on the AEAD path ─────────────────────────
const XCHACHA20_POLY1305_NONCE_LEN: usize = 24;
use crateCryptoAlgorithm;
/// Proof: `CryptoAlgorithm::nonce_len()` returns a value within the set
/// of canonical AEAD nonce lengths (12 for AES-256-GCM, 24 for
/// XChaCha20-Poly1305).
///
/// This is a structural invariant — catches a future copy-paste accident
/// in `algorithm.rs` that would silently produce a wrong-length nonce.
/// The proof is robust to future algorithm additions (e.g., the M2
/// hybrid PQ variant uses the AES-GCM nonce length per the migration
/// plan, which is already in the canonical set).
/// Proof: a freshly-generated nonce array of the correct length for the
/// algorithm cannot collapse to all-zero (modelled with the CSPRNG
/// axiom) and remains the same length after the structural
/// pass-through the encrypt path performs.
///
/// Strengthens fv M1's `nonce_non_zero` from a single fixed length (12)
/// to a per-algorithm property: for *any* selected algorithm, a
/// CSPRNG-axiomatised nonce of the corresponding length remains
/// length-correct and non-zero through the pipeline.