CryptoVerif and Tamarin models, minor doc updates
Signed-off-by: Kamal Tufekcic <kamal@lo.sh>
This commit is contained in:
parent
9ba7ea2def
commit
3acaa0fa3f
18 changed files with 2925 additions and 8 deletions
121
cryptoverif/LO_Auth.cv
Normal file
121
cryptoverif/LO_Auth.cv
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
(* LO-Auth: Key Possession Proof (Theorem 6)
|
||||
*
|
||||
* Protocol (§6):
|
||||
* Server → Client: c, where (c, ss) ← XWing.Encaps(pk_IK_client[XWing])
|
||||
* Client → Server: proof = MAC(key=ss, data="lo-auth-v1")
|
||||
* Server: accept iff proof = token, where token = MAC(key=ss, data="lo-auth-v1")
|
||||
*
|
||||
* Security claims:
|
||||
* 1. Correspondence: ServerAccepts ==> ClientResponds (Theorem 6)
|
||||
* 2. Injective correspondence: each acceptance maps to a distinct client
|
||||
* response (single-use, matching Tamarin's Auth_Single_Use)
|
||||
*
|
||||
* Reduces to: X-Wing IND-CCA2 + HMAC-SHA3-256 SUF-CMA.
|
||||
*
|
||||
* Decaps oracle (§8.3 compositional note): In the Tamarin model, an explicit
|
||||
* DecapsOracle rule models the adversary's ability to submit arbitrary
|
||||
* ciphertexts and observe MAC(Decaps(sk_IK, c), "lo-auth-v1"). In CryptoVerif,
|
||||
* this is subsumed by the IND-CCA2 KEM assumption, which already provides the
|
||||
* adversary with a raw decapsulation oracle returning ss directly — strictly
|
||||
* more powerful than the MAC-wrapped output. The Nc parameter counts all
|
||||
* client-side decapsulation queries (both legitimate responses and adversarial
|
||||
* oracle use); the IND-CCA2 advantage bound P_kem(time, 1, Ns, Nc) accounts
|
||||
* for all such queries.
|
||||
*)
|
||||
|
||||
(* Session counts *)
|
||||
param Ns. (* Server challenge sessions *)
|
||||
param Nc. (* Client response sessions (includes §8.3 Decaps oracle queries) *)
|
||||
|
||||
(* ---------- Type declarations ---------- *)
|
||||
|
||||
type kem_keyseed [large, fixed].
|
||||
type kem_pkey [bounded].
|
||||
type kem_skey [bounded].
|
||||
type kem_secret [large, fixed].
|
||||
type kem_ciphertext [bounded].
|
||||
type kem_encapoutput [bounded].
|
||||
|
||||
type macres [fixed].
|
||||
type label [fixed].
|
||||
|
||||
(* ---------- X-Wing KEM (IND-CCA2) ---------- *)
|
||||
|
||||
proba P_kem.
|
||||
proba P_kem_keycoll.
|
||||
proba P_kem_ctxtcoll.
|
||||
|
||||
expand IND_CCA2_KEM(
|
||||
kem_keyseed, kem_pkey, kem_skey,
|
||||
kem_secret, kem_ciphertext, kem_encapoutput,
|
||||
kem_pkgen, kem_skgen, kem_encap, kem_pair, kem_decap,
|
||||
injbot_kem, P_kem, P_kem_keycoll, P_kem_ctxtcoll
|
||||
).
|
||||
|
||||
(* ---------- HMAC-SHA3-256 as SUF-CMA deterministic MAC ---------- *)
|
||||
|
||||
proba P_mac.
|
||||
|
||||
expand SUF_CMA_det_mac(kem_secret, label, macres, mac_auth, mac_check, P_mac).
|
||||
|
||||
const lo_auth_label: label.
|
||||
|
||||
(* ---------- Events ---------- *)
|
||||
|
||||
event ServerAccepts(kem_pkey, kem_ciphertext).
|
||||
event ClientResponds(kem_pkey, kem_ciphertext).
|
||||
|
||||
(* ---------- Security queries ---------- *)
|
||||
|
||||
(* Theorem 6 (Key Possession): If the server accepts for a challenge
|
||||
* ciphertext ct, then the client responded to that same ct. *)
|
||||
query pk: kem_pkey, ct: kem_ciphertext;
|
||||
event(ServerAccepts(pk, ct)) ==> event(ClientResponds(pk, ct)).
|
||||
|
||||
(* Single-use (Tamarin: Auth_Single_Use): Each server acceptance maps to
|
||||
* a distinct client response — no replay of a single client response can
|
||||
* satisfy two server sessions. *)
|
||||
query pk: kem_pkey, ct: kem_ciphertext;
|
||||
inj-event(ServerAccepts(pk, ct)) ==> inj-event(ClientResponds(pk, ct)).
|
||||
|
||||
(* ---------- Channels ---------- *)
|
||||
|
||||
channel c_start, c_srv_start, c_srv_out, c_srv_recv,
|
||||
c_cli_in, c_cli_out, c_pub.
|
||||
|
||||
(* ---------- Server process ---------- *)
|
||||
|
||||
let Server(pk_client: kem_pkey) =
|
||||
foreach i_s <= Ns do
|
||||
in(c_srv_start, ());
|
||||
let kem_pair(ss: kem_secret, ct: kem_ciphertext) = kem_encap(pk_client) in
|
||||
let token: macres = mac_auth(lo_auth_label, ss) in
|
||||
out(c_srv_out, ct);
|
||||
in(c_srv_recv, p: macres);
|
||||
if p = token then (
|
||||
event ServerAccepts(pk_client, ct)
|
||||
).
|
||||
|
||||
(* ---------- Client process ---------- *)
|
||||
|
||||
let Client(sk_client: kem_skey, pk_client: kem_pkey) =
|
||||
foreach i_c <= Nc do
|
||||
in(c_cli_in, ct: kem_ciphertext);
|
||||
let injbot_kem(ss: kem_secret) = kem_decap(ct, sk_client) in
|
||||
let tag: macres = mac_auth(lo_auth_label, ss) in
|
||||
event ClientResponds(pk_client, ct);
|
||||
out(c_cli_out, tag).
|
||||
|
||||
(* ---------- Main process ---------- *)
|
||||
|
||||
process
|
||||
in(c_start, ());
|
||||
new kem_seed: kem_keyseed;
|
||||
let pk_client = kem_pkgen(kem_seed) in
|
||||
let sk_client = kem_skgen(kem_seed) in
|
||||
out(c_pub, pk_client);
|
||||
(Server(pk_client) | Client(sk_client, pk_client))
|
||||
|
||||
(* EXPECTED
|
||||
All queries proved.
|
||||
END *)
|
||||
231
cryptoverif/LO_KEX.cv
Normal file
231
cryptoverif/LO_KEX.cv
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
(* LO-KEX: Session Establishment (Theorems 1, 2b)
|
||||
*
|
||||
* Protocol (§4):
|
||||
* Bob publishes bundle with σ_SPK = Sign(sk_IK_B, label ‖ pk_SPK_B)
|
||||
* Alice verifies bundle, encapsulates to IK_B/SPK_B/OPK_B
|
||||
* Alice signs SI: σ_SI = Sign(sk_IK_A, label ‖ SI)
|
||||
* Bob verifies σ_SI, decapsulates, derives same (rk, ek)
|
||||
*
|
||||
* Simplifications:
|
||||
* - HybridSig → single EUF-CMA scheme (§2.2: "may be treated as single")
|
||||
* - Three X-Wing KEMs → three independent IND-CCA2 KEMs
|
||||
* - HKDF → PRF keyed by ss_IK with (ss_SPK, ss_OPK, pks) as input
|
||||
* - First-message AEAD omitted (Theorem 1 is about session key, not message)
|
||||
* - σ_SPK signed by Bob's signing key (same as IK in the hybrid scheme)
|
||||
* - Alice uses a SEPARATE signing key (models the distinct sk_IK_A[Ed25519+ML-DSA])
|
||||
*)
|
||||
|
||||
proof {
|
||||
crypto uf_cma(sigA_sign);
|
||||
simplify;
|
||||
success
|
||||
}
|
||||
|
||||
param N_A.
|
||||
param N_B.
|
||||
|
||||
(* ---------- Types ---------- *)
|
||||
|
||||
type kem_keyseed [large, fixed].
|
||||
type kem_pkey [bounded].
|
||||
type kem_skey [bounded].
|
||||
type kem_secret [large, fixed].
|
||||
type kem_ciphertext [bounded].
|
||||
type kem_encapoutput [bounded].
|
||||
|
||||
type spk_keyseed [large, fixed].
|
||||
type spk_pkey [bounded].
|
||||
type spk_skey [bounded].
|
||||
type spk_secret [large, fixed].
|
||||
type spk_ciphertext [bounded].
|
||||
type spk_encapoutput [bounded].
|
||||
|
||||
type opk_keyseed [large, fixed].
|
||||
type opk_pkey [bounded].
|
||||
type opk_skey [bounded].
|
||||
type opk_secret [large, fixed].
|
||||
type opk_ciphertext [bounded].
|
||||
type opk_encapoutput [bounded].
|
||||
|
||||
type sig_keyseed [large, fixed].
|
||||
type sig_pkey [bounded].
|
||||
type sig_skey [bounded].
|
||||
type sig_signature [bounded].
|
||||
|
||||
type sessionkey [large, fixed].
|
||||
|
||||
(* ---------- IK KEM (IND-CCA2) ---------- *)
|
||||
|
||||
proba P_kem_ik.
|
||||
proba P_kem_ik_keycoll.
|
||||
proba P_kem_ik_ctxtcoll.
|
||||
|
||||
expand IND_CCA2_KEM(
|
||||
kem_keyseed, kem_pkey, kem_skey,
|
||||
kem_secret, kem_ciphertext, kem_encapoutput,
|
||||
ik_pkgen, ik_skgen, ik_encap, ik_pair, ik_decap,
|
||||
injbot_ik, P_kem_ik, P_kem_ik_keycoll, P_kem_ik_ctxtcoll
|
||||
).
|
||||
|
||||
(* ---------- SPK KEM (IND-CCA2) ---------- *)
|
||||
|
||||
proba P_kem_spk.
|
||||
proba P_kem_spk_keycoll.
|
||||
proba P_kem_spk_ctxtcoll.
|
||||
|
||||
expand IND_CCA2_KEM(
|
||||
spk_keyseed, spk_pkey, spk_skey,
|
||||
spk_secret, spk_ciphertext, spk_encapoutput,
|
||||
spk_pkgen, spk_skgen, spk_encap, spk_pair, spk_decap,
|
||||
injbot_spk, P_kem_spk, P_kem_spk_keycoll, P_kem_spk_ctxtcoll
|
||||
).
|
||||
|
||||
(* ---------- OPK KEM (IND-CCA2) ---------- *)
|
||||
|
||||
proba P_kem_opk.
|
||||
proba P_kem_opk_keycoll.
|
||||
proba P_kem_opk_ctxtcoll.
|
||||
|
||||
expand IND_CCA2_KEM(
|
||||
opk_keyseed, opk_pkey, opk_skey,
|
||||
opk_secret, opk_ciphertext, opk_encapoutput,
|
||||
opk_pkgen, opk_skgen, opk_encap, opk_pair, opk_decap,
|
||||
injbot_opk, P_kem_opk, P_kem_opk_keycoll, P_kem_opk_ctxtcoll
|
||||
).
|
||||
|
||||
(* ---------- Bob's signature (EUF-CMA) — signs SPK bundle ---------- *)
|
||||
|
||||
proba P_sig_B.
|
||||
proba P_sig_B_keycoll.
|
||||
|
||||
expand UF_CMA_proba_signature(
|
||||
sig_keyseed, sig_pkey, sig_skey, bitstring, sig_signature,
|
||||
sigB_skgen, sigB_pkgen, sigB_sign, sigB_verify,
|
||||
P_sig_B, P_sig_B_keycoll
|
||||
).
|
||||
|
||||
(* ---------- Alice's signature (EUF-CMA) — signs SessionInit ---------- *)
|
||||
|
||||
type sigA_keyseed [large, fixed].
|
||||
type sigA_pkey [bounded].
|
||||
type sigA_skey [bounded].
|
||||
type sigA_signature [bounded].
|
||||
|
||||
proba P_sig_A.
|
||||
proba P_sig_A_keycoll.
|
||||
|
||||
expand UF_CMA_proba_signature(
|
||||
sigA_keyseed, sigA_pkey, sigA_skey, bitstring, sigA_signature,
|
||||
sigA_skgen, sigA_pkgen, sigA_sign, sigA_verify,
|
||||
P_sig_A, P_sig_A_keycoll
|
||||
).
|
||||
|
||||
(* ---------- HKDF as PRF ---------- *)
|
||||
|
||||
proba P_prf.
|
||||
|
||||
expand PRF_large(kem_secret, bitstring, sessionkey, kdf_kex, P_prf).
|
||||
|
||||
(* ---------- Domain separation constants ---------- *)
|
||||
|
||||
const lo_spk_sig_label: bitstring.
|
||||
const lo_kex_init_sig_label: bitstring.
|
||||
|
||||
(* ---------- Events ---------- *)
|
||||
|
||||
(* Events bound on the signed session init content — what the signature
|
||||
* directly authenticates. rk is derived from this, but the correspondence
|
||||
* is over the authenticated payload. *)
|
||||
event Alice_Init(sigA_pkey, sig_pkey, kem_ciphertext, spk_ciphertext, opk_ciphertext).
|
||||
event Bob_Accept(sigA_pkey, sig_pkey, kem_ciphertext, spk_ciphertext, opk_ciphertext).
|
||||
|
||||
(* ---------- Security queries ---------- *)
|
||||
|
||||
(* Theorem 2b: Initiator authentication — if Bob accepts SI, Alice signed it *)
|
||||
query pkA: sigA_pkey, pkB: sig_pkey,
|
||||
cik: kem_ciphertext, cspk: spk_ciphertext, copk: opk_ciphertext;
|
||||
event(Bob_Accept(pkA, pkB, cik, cspk, copk))
|
||||
==> event(Alice_Init(pkA, pkB, cik, cspk, copk)).
|
||||
|
||||
(* Note: Injective variant not claimed — session-init replay is application-layer
|
||||
* (§7.5 A4). Alice may reuse the same bundle, producing identical SI contents.
|
||||
* Replay prevention is a caller obligation, not a cryptographic guarantee. *)
|
||||
|
||||
(* ---------- Channels ---------- *)
|
||||
|
||||
channel c_start, c_pub, c_alice_start, c_alice_out, c_bob_in.
|
||||
|
||||
(* ---------- Alice (Initiator) ---------- *)
|
||||
|
||||
let Alice(sk_sig_A: sigA_skey, pk_sig_A: sigA_pkey,
|
||||
pk_sig_B: sig_pkey, pk_ik_B: kem_pkey,
|
||||
pk_spk_B: spk_pkey, pk_opk_B: opk_pkey,
|
||||
sig_spk: sig_signature) =
|
||||
foreach i_a <= N_A do
|
||||
in(c_alice_start, ());
|
||||
(* §4.2: Verify Bob's SPK signature *)
|
||||
if sigB_verify((lo_spk_sig_label, pk_spk_B), pk_sig_B, sig_spk) = true then (
|
||||
(* §4.3 Step 2: Encapsulate to IK, SPK, OPK *)
|
||||
let ik_pair(ss_ik: kem_secret, c_ik: kem_ciphertext) = ik_encap(pk_ik_B) in
|
||||
let spk_pair(ss_spk: spk_secret, c_spk: spk_ciphertext) = spk_encap(pk_spk_B) in
|
||||
let opk_pair(ss_opk: opk_secret, c_opk: opk_ciphertext) = opk_encap(pk_opk_B) in
|
||||
(* §4.3 Step 3: Derive session key *)
|
||||
let rk: sessionkey = kdf_kex(ss_ik, (ss_spk, ss_opk, pk_sig_A, pk_sig_B)) in
|
||||
(* §4.3 Step 5: Sign session init *)
|
||||
let si: bitstring = (pk_sig_A, pk_sig_B, c_ik, c_spk, c_opk) in
|
||||
let sig_si: sigA_signature = sigA_sign((lo_kex_init_sig_label, si), sk_sig_A) in
|
||||
event Alice_Init(pk_sig_A, pk_sig_B, c_ik, c_spk, c_opk);
|
||||
out(c_alice_out, (si, sig_si, c_ik, c_spk, c_opk))
|
||||
).
|
||||
|
||||
(* ---------- Bob (Responder) ---------- *)
|
||||
|
||||
let Bob(sk_ik_B: kem_skey, sk_spk_B: spk_skey, sk_opk_B: opk_skey,
|
||||
pk_sig_A: sigA_pkey, pk_sig_B: sig_pkey) =
|
||||
foreach i_b <= N_B do
|
||||
in(c_bob_in, (sig_si: sigA_signature,
|
||||
c_ik: kem_ciphertext, c_spk: spk_ciphertext,
|
||||
c_opk: opk_ciphertext));
|
||||
(* Bob reconstructs SI from the received components *)
|
||||
let si: bitstring = (pk_sig_A, pk_sig_B, c_ik, c_spk, c_opk) in
|
||||
(* §4.4 Step 2: Verify Alice's signature *)
|
||||
if sigA_verify((lo_kex_init_sig_label, si), pk_sig_A, sig_si) = true then (
|
||||
(* §4.4 Step 5: Decapsulate *)
|
||||
let injbot_ik(ss_ik: kem_secret) = ik_decap(c_ik, sk_ik_B) in
|
||||
let injbot_spk(ss_spk: spk_secret) = spk_decap(c_spk, sk_spk_B) in
|
||||
let injbot_opk(ss_opk: opk_secret) = opk_decap(c_opk, sk_opk_B) in
|
||||
(* §4.4 Step 7: Derive session key *)
|
||||
let rk: sessionkey = kdf_kex(ss_ik, (ss_spk, ss_opk, pk_sig_A, pk_sig_B)) in
|
||||
event Bob_Accept(pk_sig_A, pk_sig_B, c_ik, c_spk, c_opk)
|
||||
).
|
||||
|
||||
(* ---------- Main process ---------- *)
|
||||
|
||||
process
|
||||
in(c_start, ());
|
||||
(* Bob: IK KEM key *)
|
||||
new ik_seed: kem_keyseed;
|
||||
let pk_ik_B = ik_pkgen(ik_seed) in
|
||||
let sk_ik_B = ik_skgen(ik_seed) in
|
||||
(* Bob: SPK *)
|
||||
new spk_seed: spk_keyseed;
|
||||
let pk_spk_B = spk_pkgen(spk_seed) in
|
||||
let sk_spk_B = spk_skgen(spk_seed) in
|
||||
(* Bob: OPK *)
|
||||
new opk_seed: opk_keyseed;
|
||||
let pk_opk_B = opk_pkgen(opk_seed) in
|
||||
let sk_opk_B = opk_skgen(opk_seed) in
|
||||
(* Bob: signing key (signs SPK bundle) *)
|
||||
new sigB_seed: sig_keyseed;
|
||||
let pk_sig_B = sigB_pkgen(sigB_seed) in
|
||||
let sk_sig_B = sigB_skgen(sigB_seed) in
|
||||
(* Alice: signing key (signs SessionInit) *)
|
||||
new sigA_seed: sigA_keyseed;
|
||||
let pk_sig_A = sigA_pkgen(sigA_seed) in
|
||||
let sk_sig_A = sigA_skgen(sigA_seed) in
|
||||
(* Bob signs SPK *)
|
||||
let sig_spk: sig_signature = sigB_sign((lo_spk_sig_label, pk_spk_B), sk_sig_B) in
|
||||
(* Publish everything *)
|
||||
out(c_pub, (pk_ik_B, pk_spk_B, pk_opk_B, pk_sig_B, pk_sig_A, sig_spk));
|
||||
(Alice(sk_sig_A, pk_sig_A, pk_sig_B, pk_ik_B, pk_spk_B, pk_opk_B, sig_spk)
|
||||
| Bob(sk_ik_B, sk_spk_B, sk_opk_B, pk_sig_A, pk_sig_B))
|
||||
153
cryptoverif/LO_KEX_Secrecy.cv
Normal file
153
cryptoverif/LO_KEX_Secrecy.cv
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
(* LO-KEX: Session Key Secrecy (Theorem 1)
|
||||
*
|
||||
* The session key (rk) is computationally indistinguishable from random
|
||||
* to any adversary that has not corrupted all of Bob's keys AND Alice's RNG.
|
||||
*
|
||||
* Model: Alice encapsulates to Bob's IK/SPK/OPK, derives rk via HKDF-as-PRF.
|
||||
* Bob decapsulates and derives the same rk. The adversary sees all public
|
||||
* values (pk_IK_B, pk_SPK_B, pk_OPK_B, ciphertexts, signatures) but not
|
||||
* the shared secrets or the derived key.
|
||||
*
|
||||
* The `secret` query tests whether rk_A (Alice's derived key) is
|
||||
* indistinguishable from a uniformly random sessionkey.
|
||||
*
|
||||
* Reduces to: 3× IND-CCA2 KEM + HKDF PRF.
|
||||
*
|
||||
* Simplifications:
|
||||
* - Signatures omitted: Theorem 1 (key secrecy) does not depend on
|
||||
* authentication. Bundle integrity (SPK not substituted) is implicit
|
||||
* in the process structure (Alice receives authentic pk_spk_B).
|
||||
* - KDF info field simplified: uses (ss_spk, ss_opk, pk_ik_B) rather
|
||||
* than the full spec info (pk_IK_A, pk_IK_B, pk_EK, crypto_version).
|
||||
* The PRF proof holds regardless of info content.
|
||||
* - X-Wing as black-box IND-CCA2: the spec (§2.1) recommends opening
|
||||
* the combiner for CryptoVerif. The black-box assumption is stronger;
|
||||
* the bound is in terms of P_kem rather than component advantages.
|
||||
* - No corruption oracles: the proof covers the no-corruption case.
|
||||
* Corruption-parameterized secrecy is verified by Tamarin (LO_KEX.spthy).
|
||||
*)
|
||||
|
||||
param N_A.
|
||||
param N_B.
|
||||
|
||||
(* ---------- Types ---------- *)
|
||||
|
||||
type kem_keyseed [large, fixed].
|
||||
type kem_pkey [bounded].
|
||||
type kem_skey [bounded].
|
||||
type kem_secret [large, fixed].
|
||||
type kem_ciphertext [bounded].
|
||||
type kem_encapoutput [bounded].
|
||||
|
||||
type spk_keyseed [large, fixed].
|
||||
type spk_pkey [bounded].
|
||||
type spk_skey [bounded].
|
||||
type spk_secret [large, fixed].
|
||||
type spk_ciphertext [bounded].
|
||||
type spk_encapoutput [bounded].
|
||||
|
||||
type opk_keyseed [large, fixed].
|
||||
type opk_pkey [bounded].
|
||||
type opk_skey [bounded].
|
||||
type opk_secret [large, fixed].
|
||||
type opk_ciphertext [bounded].
|
||||
type opk_encapoutput [bounded].
|
||||
|
||||
type sessionkey [large, fixed].
|
||||
|
||||
(* ---------- Three IND-CCA2 KEMs ---------- *)
|
||||
|
||||
proba P_kem_ik.
|
||||
proba P_kem_ik_keycoll.
|
||||
proba P_kem_ik_ctxtcoll.
|
||||
|
||||
expand IND_CCA2_KEM(
|
||||
kem_keyseed, kem_pkey, kem_skey,
|
||||
kem_secret, kem_ciphertext, kem_encapoutput,
|
||||
ik_pkgen, ik_skgen, ik_encap, ik_pair, ik_decap,
|
||||
injbot_ik, P_kem_ik, P_kem_ik_keycoll, P_kem_ik_ctxtcoll
|
||||
).
|
||||
|
||||
proba P_kem_spk.
|
||||
proba P_kem_spk_keycoll.
|
||||
proba P_kem_spk_ctxtcoll.
|
||||
|
||||
expand IND_CCA2_KEM(
|
||||
spk_keyseed, spk_pkey, spk_skey,
|
||||
spk_secret, spk_ciphertext, spk_encapoutput,
|
||||
spk_pkgen, spk_skgen, spk_encap, spk_pair, spk_decap,
|
||||
injbot_spk, P_kem_spk, P_kem_spk_keycoll, P_kem_spk_ctxtcoll
|
||||
).
|
||||
|
||||
proba P_kem_opk.
|
||||
proba P_kem_opk_keycoll.
|
||||
proba P_kem_opk_ctxtcoll.
|
||||
|
||||
expand IND_CCA2_KEM(
|
||||
opk_keyseed, opk_pkey, opk_skey,
|
||||
opk_secret, opk_ciphertext, opk_encapoutput,
|
||||
opk_pkgen, opk_skgen, opk_encap, opk_pair, opk_decap,
|
||||
injbot_opk, P_kem_opk, P_kem_opk_keycoll, P_kem_opk_ctxtcoll
|
||||
).
|
||||
|
||||
(* ---------- HKDF as PRF keyed by ss_ik ---------- *)
|
||||
|
||||
proba P_prf.
|
||||
|
||||
expand PRF_large(kem_secret, bitstring, sessionkey, kdf_kex, P_prf).
|
||||
|
||||
(* ---------- Security query ---------- *)
|
||||
|
||||
(* Theorem 1: rk_A is indistinguishable from random.
|
||||
* cv_onesession: secrecy for a single tested session (standard model). *)
|
||||
query secret rk_A [cv_onesession].
|
||||
|
||||
(* ---------- Channels ---------- *)
|
||||
|
||||
channel c_start, c_pub, c_alice_start, c_alice_out, c_bob_in, c_bob_done.
|
||||
|
||||
(* ---------- Alice (Initiator) ---------- *)
|
||||
|
||||
let Alice(pk_ik_B: kem_pkey, pk_spk_B: spk_pkey, pk_opk_B: opk_pkey) =
|
||||
foreach i_a <= N_A do
|
||||
in(c_alice_start, ());
|
||||
(* §4.3 Step 2: Encapsulate to IK, SPK, OPK *)
|
||||
let ik_pair(ss_ik: kem_secret, c_ik: kem_ciphertext) = ik_encap(pk_ik_B) in
|
||||
let spk_pair(ss_spk: spk_secret, c_spk: spk_ciphertext) = spk_encap(pk_spk_B) in
|
||||
let opk_pair(ss_opk: opk_secret, c_opk: opk_ciphertext) = opk_encap(pk_opk_B) in
|
||||
(* §4.3 Step 3: Derive session key *)
|
||||
let rk_A: sessionkey = kdf_kex(ss_ik, (ss_spk, ss_opk, pk_ik_B)) in
|
||||
out(c_alice_out, (c_ik, c_spk, c_opk)).
|
||||
|
||||
(* ---------- Bob (Responder) ---------- *)
|
||||
(* Bob decapsulates — models the CCA2 decapsulation oracle *)
|
||||
|
||||
let Bob(sk_ik_B: kem_skey, sk_spk_B: spk_skey, sk_opk_B: opk_skey,
|
||||
pk_ik_B: kem_pkey) =
|
||||
foreach i_b <= N_B do
|
||||
in(c_bob_in, (c_ik: kem_ciphertext, c_spk: spk_ciphertext,
|
||||
c_opk: opk_ciphertext));
|
||||
let injbot_ik(ss_ik: kem_secret) = ik_decap(c_ik, sk_ik_B) in
|
||||
let injbot_spk(ss_spk: spk_secret) = spk_decap(c_spk, sk_spk_B) in
|
||||
let injbot_opk(ss_opk: opk_secret) = opk_decap(c_opk, sk_opk_B) in
|
||||
let rk_B: sessionkey = kdf_kex(ss_ik, (ss_spk, ss_opk, pk_ik_B)) in
|
||||
out(c_bob_done, ()).
|
||||
|
||||
(* ---------- Main process ---------- *)
|
||||
|
||||
process
|
||||
in(c_start, ());
|
||||
(* Bob's KEM keys *)
|
||||
new ik_seed: kem_keyseed;
|
||||
let pk_ik_B = ik_pkgen(ik_seed) in
|
||||
let sk_ik_B = ik_skgen(ik_seed) in
|
||||
new spk_seed: spk_keyseed;
|
||||
let pk_spk_B = spk_pkgen(spk_seed) in
|
||||
let sk_spk_B = spk_skgen(spk_seed) in
|
||||
new opk_seed: opk_keyseed;
|
||||
let pk_opk_B = opk_pkgen(opk_seed) in
|
||||
let sk_opk_B = opk_skgen(opk_seed) in
|
||||
(* Publish public keys *)
|
||||
out(c_pub, (pk_ik_B, pk_spk_B, pk_opk_B));
|
||||
(Alice(pk_ik_B, pk_spk_B, pk_opk_B)
|
||||
| Bob(sk_ik_B, sk_spk_B, sk_opk_B, pk_ik_B))
|
||||
44
cryptoverif/LO_Ratchet_MsgSecrecy.cv
Normal file
44
cryptoverif/LO_Ratchet_MsgSecrecy.cv
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
(* LO-Ratchet: Message Key Secrecy (Theorem 3)
|
||||
*
|
||||
* Given a fresh epoch key ek (from Theorem 1 + KDF_Root), proves that
|
||||
* message keys mk = KDF_MsgKey(ek, counter) are indistinguishable from
|
||||
* random. Combined with AEAD security under random keys (standard
|
||||
* composition via [BN00]), this gives full message secrecy.
|
||||
*
|
||||
* Reduces to: HMAC-SHA3-256 PRF.
|
||||
*)
|
||||
|
||||
param N_msg.
|
||||
|
||||
(* ---------- Types ---------- *)
|
||||
|
||||
type epoch_key [large, fixed].
|
||||
type msg_key [large, fixed].
|
||||
type counter [fixed].
|
||||
|
||||
(* ---------- KDF_MsgKey as PRF ---------- *)
|
||||
|
||||
proba P_prf.
|
||||
|
||||
expand PRF_large(epoch_key, counter, msg_key, kdf_msgkey, P_prf).
|
||||
|
||||
(* ---------- Security query ---------- *)
|
||||
|
||||
query secret test_mk [cv_onesession].
|
||||
|
||||
(* ---------- Channels ---------- *)
|
||||
|
||||
channel c_start, c_ready, c_test_in, c_test_out.
|
||||
|
||||
(* ---------- Process ---------- *)
|
||||
(* Single derivation: ek is fresh, derive mk at one counter.
|
||||
* The PRF transformation replaces kdf_msgkey(ek, ctr) with a random value.
|
||||
* No oracle needed — the PRF_large game handles multi-query internally. *)
|
||||
|
||||
process
|
||||
in(c_start, ());
|
||||
new ek: epoch_key;
|
||||
out(c_ready, ());
|
||||
in(c_test_in, ctr: counter);
|
||||
let test_mk: msg_key = kdf_msgkey(ek, ctr) in
|
||||
out(c_test_out, ())
|
||||
116
cryptoverif/LO_Stream_Secrecy.cv
Normal file
116
cryptoverif/LO_Stream_Secrecy.cv
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
(* LO-Stream: Chunk Confidentiality + Integrity (Theorem 13, P1+P2)
|
||||
*
|
||||
* Adapted from CryptoVerif's TLS 1.3 Record Protocol example.
|
||||
*
|
||||
* IND-CPA: Bit-guessing game — adversary submits two equal-length plaintexts,
|
||||
* receives encryption of one based on secret bit b0. Advantage = Pr[guess b0].
|
||||
*
|
||||
* INT-CTXT: Injective correspondence — if decryption succeeds at (count, msg),
|
||||
* encryption must have produced (count, msg).
|
||||
*
|
||||
* Nonce uniqueness enforced via table lookup (nonce-respecting adversary, §9.11(f)).
|
||||
* Nonce derivation: chunk_nonce = xor(base_nonce, count) with injectivity equation.
|
||||
*
|
||||
* Reduces to: XChaCha20-Poly1305 IND-CPA + INT-CTXT.
|
||||
*)
|
||||
|
||||
type key [fixed, large].
|
||||
type seqn [fixed].
|
||||
type nonce_t [fixed, large].
|
||||
type add_data [bounded].
|
||||
|
||||
param N_enc, N_dec.
|
||||
|
||||
(* ---------- Nonce derivation with injectivity ---------- *)
|
||||
(* §15.2: chunk_nonce = base_nonce XOR mask(index, tag_byte)
|
||||
* Modeled as xor(iv, count) per TLS 1.3 record protocol pattern.
|
||||
* CryptoVerif needs the explicit injectivity equation. *)
|
||||
|
||||
fun xor(key, seqn): nonce_t.
|
||||
|
||||
equation forall k: key, n: seqn, n': seqn;
|
||||
(xor(k, n) = xor(k, n')) = (n = n').
|
||||
|
||||
(* ---------- AEAD (IND-CPA + INT-CTXT under unique nonces) ---------- *)
|
||||
|
||||
proba P_cpa.
|
||||
proba P_ctxt.
|
||||
|
||||
expand AEAD_nonce(key, bitstring, bitstring, add_data, nonce_t,
|
||||
enc, dec, injbot, Z, P_cpa, P_ctxt).
|
||||
|
||||
(* Per-chunk AAD includes base_nonce + count (§15.4) *)
|
||||
fun derive_aad(key, seqn): add_data [data].
|
||||
|
||||
letfun stream_encrypt(k: key, n: nonce_t, m: bitstring, aad: add_data) =
|
||||
enc(m, aad, k, n).
|
||||
letfun stream_decrypt(k: key, n: nonce_t, c: bitstring, aad: add_data) =
|
||||
dec(c, aad, k, n).
|
||||
|
||||
(* ---------- Tables for nonce uniqueness (§9.11(f) game hypothesis) ---------- *)
|
||||
|
||||
table table_enc_nonce(seqn).
|
||||
table table_dec_nonce(seqn).
|
||||
|
||||
(* ---------- Security queries ---------- *)
|
||||
|
||||
(* P1 (IND-CPA): secret bit indistinguishable *)
|
||||
query secret b0 [cv_bit].
|
||||
|
||||
(* P2 (INT-CTXT): injective correspondence *)
|
||||
event Sent(seqn, bitstring).
|
||||
event Received(seqn, bitstring).
|
||||
|
||||
query count: seqn, msg: bitstring;
|
||||
inj-event(Received(count, msg)) ==> inj-event(Sent(count, msg))
|
||||
public_vars b0.
|
||||
|
||||
(* ---------- Channels ---------- *)
|
||||
|
||||
channel c_start, c_ready, c_enc_in, c_enc_out, c_dec_in, c_dec_out.
|
||||
|
||||
(* ---------- Encrypt oracle ---------- *)
|
||||
(* Adversary submits (m0, m1, count). Oracle encrypts m_b where b = b0.
|
||||
* Count must not have been used before (nonce-respecting). *)
|
||||
|
||||
let Encrypt(k: key, iv: key, b: bool) =
|
||||
!N_enc
|
||||
in(c_enc_in, (clear1: bitstring, clear2: bitstring, count: seqn));
|
||||
(* Nonce-respecting: reject reused counter *)
|
||||
get table_enc_nonce(=count) in yield else
|
||||
insert table_enc_nonce(count);
|
||||
(* Equal-length requirement for IND-CPA *)
|
||||
if Z(clear1) = Z(clear2) then
|
||||
let clear = if_fun(b, clear1, clear2) in
|
||||
let aad: add_data = derive_aad(iv, count) in
|
||||
let nonce = xor(iv, count) in
|
||||
event Sent(count, clear);
|
||||
let cipher = stream_encrypt(k, nonce, clear, aad) in
|
||||
out(c_enc_out, cipher).
|
||||
|
||||
(* ---------- Decrypt oracle ---------- *)
|
||||
(* Adversary submits (ciphertext, count). Must not reuse count. *)
|
||||
|
||||
let Decrypt(k: key, iv: key) =
|
||||
!N_dec
|
||||
in(c_dec_in, (cipher: bitstring, count: seqn));
|
||||
get table_dec_nonce(=count) in yield else
|
||||
insert table_dec_nonce(count);
|
||||
let aad: add_data = derive_aad(iv, count) in
|
||||
let nonce = xor(iv, count) in
|
||||
let injbot(clear) = stream_decrypt(k, nonce, cipher, aad) in
|
||||
event Received(count, clear).
|
||||
|
||||
(* ---------- Main process ---------- *)
|
||||
|
||||
process
|
||||
in(c_start, ());
|
||||
new b0: bool;
|
||||
new k: key;
|
||||
new iv: key; (* base_nonce — public *)
|
||||
out(c_ready, iv);
|
||||
(Encrypt(k, iv, b0) | Decrypt(k, iv))
|
||||
|
||||
(* EXPECTED
|
||||
All queries proved.
|
||||
END *)
|
||||
112
cryptoverif/README.md
Normal file
112
cryptoverif/README.md
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
# CryptoVerif Models
|
||||
|
||||
Computational formal verification of the Soliton cryptographic protocol using
|
||||
[CryptoVerif](https://bblanche.gitlabpages.inria.fr/CryptoVerif/).
|
||||
|
||||
These models were authored by the protocol designers and have not undergone
|
||||
independent peer review. They are published for transparency and to facilitate
|
||||
third-party verification. All results are machine-checkable and reproducible.
|
||||
|
||||
## Requirements
|
||||
|
||||
- CryptoVerif 2.12+
|
||||
- The `pq.cvl` library (ships with CryptoVerif)
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# All models
|
||||
CV_LIB=/path/to/pq ../verify.sh cryptoverif
|
||||
|
||||
# Single model
|
||||
cryptoverif -lib /path/to/pq LO_Auth.cv
|
||||
```
|
||||
|
||||
## Resource Usage
|
||||
|
||||
All 5 models complete in under 5 seconds total with negligible RAM usage.
|
||||
No special hardware required.
|
||||
|
||||
## Results
|
||||
|
||||
Verified with CryptoVerif 2.12.
|
||||
|
||||
### LO_Auth.cv — Theorem 6 (Key Possession)
|
||||
|
||||
| Query | Result | Bound |
|
||||
|-------|--------|-------|
|
||||
| event(ServerAccepts) ==> event(ClientResponds) | proved | Ns × P_mac + P_kem |
|
||||
| inj-event(ServerAccepts) ==> inj-event(ClientResponds) | proved | Ns × P_mac + P_kem |
|
||||
|
||||
Primitives: IND-CCA2 KEM (X-Wing), SUF-CMA deterministic MAC (HMAC-SHA3-256).
|
||||
|
||||
### LO_KEX.cv — Theorem 2b (Initiator Authentication)
|
||||
|
||||
| Query | Result | Bound |
|
||||
|-------|--------|-------|
|
||||
| event(Bob_Accept) ==> event(Alice_Init) | proved | P_sig_A |
|
||||
|
||||
Primitives: EUF-CMA signature (HybridSig). Proof uses only Alice's signature
|
||||
unforgeability. Non-injective (replay is application-layer per §7.5 A4).
|
||||
|
||||
### LO_KEX_Secrecy.cv — Theorem 1 (Session Key Secrecy)
|
||||
|
||||
| Query | Result | Bound |
|
||||
|-------|--------|-------|
|
||||
| secret rk_A [cv_onesession] | proved | 2·P_prf + 2·P_kem_ik + 2·P_kem_spk + 2·P_kem_opk + collision terms |
|
||||
|
||||
Primitives: 3× IND-CCA2 KEM, PRF (HKDF). Signatures omitted (Theorem 1 is
|
||||
secrecy, not authentication). No corruption oracles (Tamarin covers corruption
|
||||
cases). See header comment for full simplifications list.
|
||||
|
||||
### LO_Ratchet_MsgSecrecy.cv — Theorem 3 (Message Key Secrecy)
|
||||
|
||||
| Query | Result | Bound |
|
||||
|-------|--------|-------|
|
||||
| secret test_mk [cv_onesession] | proved | 2 × P_prf |
|
||||
|
||||
Precondition: epoch key ek is fresh (from Theorem 1 + KDF_Root output
|
||||
independence). Combined with AEAD IND-CPA+INT-CTXT under random keys
|
||||
(standard [BN00] composition), gives full message secrecy.
|
||||
|
||||
### LO_Stream_Secrecy.cv — Theorem 13, Properties 1+2 (Streaming AEAD)
|
||||
|
||||
| Query | Result | Bound |
|
||||
|-------|--------|-------|
|
||||
| secret b0 [cv_bit] (IND-CPA) | proved | 2·P_ctxt + 2·P_cpa(time, N_enc) |
|
||||
| inj-event(Received) ==> inj-event(Sent) (INT-CTXT) | proved | P_ctxt |
|
||||
|
||||
Adapted from CryptoVerif's TLS 1.3 Record Protocol example. Nonce uniqueness
|
||||
enforced via table-based game hypothesis (§9.11(f)). base_nonce is public.
|
||||
|
||||
Key properties of the bounds:
|
||||
- **INT-CTXT has no Q-factor** — direct forgery reduction
|
||||
- **IND-CPA scales as N_enc × P_cpa** — Q-step hybrid argument
|
||||
|
||||
## Scope and Limitations
|
||||
|
||||
- **X-Wing as black box**: All models treat X-Wing as a monolithic IND-CCA2
|
||||
KEM. The spec (§2.1) recommends opening the combiner for CryptoVerif. The
|
||||
black-box assumption is stronger; bounds are in terms of P_kem rather than
|
||||
component advantages (P_mlkem + P_x25519 + P_sha3_ro).
|
||||
- **No corruption oracles**: The CryptoVerif KEX models prove security for
|
||||
the no-corruption case. Corruption-parameterized secrecy (partial key
|
||||
compromise, RNG corruption) is verified by the Tamarin models.
|
||||
- **Simplified KDF info**: LO_KEX_Secrecy.cv binds fewer values in the PRF
|
||||
input than the full HKDF info field. The PRF proof holds regardless of
|
||||
info content; session-binding properties are verified by Tamarin.
|
||||
- **Single-epoch message secrecy**: LO_Ratchet_MsgSecrecy.cv assumes a fresh
|
||||
epoch key. The composition chain (Theorem 1 → KDF_Root → fresh ek → PRF →
|
||||
fresh mk → AEAD) is sound but not mechanically verified end-to-end.
|
||||
- **No Theorem 2c/d**: Key confirmation requires a combined KEX+Ratchet model.
|
||||
|
||||
## Theorem Coverage
|
||||
|
||||
| Theorem | Model | What's proved |
|
||||
|---------|-------|---------------|
|
||||
| 1 (KEX Key Secrecy) | LO_KEX_Secrecy | rk indistinguishable from random |
|
||||
| 2b (Initiator Auth) | LO_KEX | σ_SI authentication via EUF-CMA |
|
||||
| 3 (Message Secrecy) | LO_Ratchet_MsgSecrecy | mk indistinguishable from random |
|
||||
| 6 (Auth Key Possession) | LO_Auth | Correspondence + injective |
|
||||
| 13 P1 (IND-CPA) | LO_Stream_Secrecy | Bit secrecy of challenge bit |
|
||||
| 13 P2 (INT-CTXT) | LO_Stream_Secrecy | Injective correspondence |
|
||||
Loading…
Add table
Add a link
Reference in a new issue