second iteration

This commit is contained in:
2025-09-15 00:04:30 -04:00
parent 53303ba86e
commit 618ce7c797
8 changed files with 1701 additions and 1 deletions

View File

@@ -1,5 +1,7 @@
Formal Analysis of the Nested Ratchet Protocol, and Symbolic Formal Verification of Sender Keys, Signal, Megolm, Olm, 3DH, and X3DH. The main models are written in [ProVerif](https://en.wikipedia.org/wiki/ProVerif); additional supplementary models (extra from the main results described in the paper) are provided in [VerifPal](https://en.wikipedia.org/wiki/ProVerif)
**Note: this version of the artifact includes the post-quantum construction of sender keys present in Signal. For the original artifact at the time of submission, please see the first zenodo artifact version.**
# Environment Setup
1. Install the [nix package manager](https://nixos.org/download/)
2. Navigate to the current directory, and run `nix develop`. You will get dropped into a devshell with the correct versions of [ProVerif](https://en.wikipedia.org/wiki/ProVerif) and [VerifPal](https://verifpal.com/). Feel free to execute another shell (i.e. `zsh`) if you have something against `bash`.
@@ -13,8 +15,10 @@ Formal Analysis of the Nested Ratchet Protocol, and Symbolic Formal Verification
- `megolm.pv`: a composition of Megolm and Olm.
- `megolm-olm-unsigned.pv`: a composition of Megolm and Olm, where Olm pre-keys are unsigned. Demonstrates the first Megolm session.
- `sender-keys.pv`: a composition of fan-out layer session sharing and Signal. Includes secrecy, authentication, reachability, PCS, and PFS properties.
- `pq/pqxdh.pv`: a complete model of the Post-Quantum Extended Triple Diffie-Hellman handshake. Proving authentication, secrecy, forward secrecy, and quantum forward secrecy.
- `pq/signal.pv` and `pq/signal-pcs.pv`: a complete model of the Signal protocol, including PQXDH and Double Ratchet. Proving secrecy, authentication, forward secrecy, quantum forward secrecy, and post-compromise security.
The `deniability` folder includes both initiator deniability and responder undeniability results for 3dh, x3dh, olm, signal, and megolm.
The `deniability` folder includes both initiator deniability and responder undeniability results for 3dh, x3dh, olm, signal (with x3dh), and megolm. The `pq` folder includes initiator deniability and responder undeniability for signal with pqxdh.
# Notes on Proverif reachability
In each model, there exists several queries designed to ensure reachability and no deadlocks. First,

195
proverif/pq/pqxdh.pv Normal file
View File

@@ -0,0 +1,195 @@
(*
PQXDH; proving authenticity, secrecy, forward secrecy, and quantum forward secrecy
Author: jake ginesin
model assumption #1: same key is used for signing and encryption (i.e. X25519)
*)
free m1: bitstring [private].
set selFun = Nounifset.
(*
set simpEqAll = false.
set simpEqAll = false.
set redundancyElim = best.
set redundantHypElim = true.
set simplifyProcess = true.
set stopTerm = false.
*)
free c: channel.
free a: channel. (* channel for the attacker *)
free p: channel [private]. (* For the distribution of public keys with integrity and authenticity - verification happens out of band. This is a standard assumption. *)
(* Symmetric key encryption *)
type key.
fun senc(key, bitstring): bitstring.
reduc forall m: bitstring, k: key; sdec(k, senc(k,m)) = m.
(* Asymmetric key encryption *)
type skey.
type pkey.
fun rb(pkey): bitstring [data].
fun pk(skey): pkey.
(* Digital signatures *)
fun sign(skey, bitstring): bitstring.
fun okay():bitstring.
reduc forall m: bitstring, sk: skey; checksign(pk(sk), m, sign(sk, m)) = okay.
(* MACs *)
fun mac(key, bitstring): bitstring.
reduc forall k: key, m: bitstring; checkmac(k, m, mac(k, m)) = okay.
(* Diffie-Hellman *)
(* DH -> Public^Private *)
fun dh(pkey, skey): key.
equation forall a: skey, b: skey; dh(pk(a), b) = dh(pk(b), a). (* symmetry of DH *)
(* the concat functions *)
fun hkdf1(bitstring): key [data].
fun khash(key): key.
fun hkdf2_dev1(key): key [data].
fun hkdf2_dev2(key): key [data].
letfun hkdf2(k: key) =
(hkdf2_dev1(k), hkdf2_dev2(k)).
(* KEM encapsulation *)
type kempriv.
type kempub.
fun kempk(kempriv):kempub.
fun penc(kempub, bitstring):bitstring.
(* fun pdec(kempriv,bitstring):bitstring. *)
reduc forall sk: kempriv, m:bitstring; pdec(sk, penc(kempk(sk), m)) = m.
letfun kempriv2pub(k:kempriv) = kempk(k).
letfun pqkem_enc(pk:kempub) =
new ss:bitstring;
(penc(pk,ss),ss).
letfun pqkem_dec(sk:kempriv,ct:bitstring) =
pdec(sk,ct).
fun qb(kempub): bitstring [data].
(* the concats *)
fun concat1(bitstring, pkey, bitstring): bitstring [data].
fun concat2(key, key, key, key, bitstring): bitstring [data].
(* events *)
event sendE1(bitstring, key).
event recvE1(bitstring, key).
event compromiseSKA(skey).
event compromiseSKB(skey).
event breakDH(key, key, key, key).
event start().
let PeerA(SK_A: skey, PK_A: pkey, PK_B: pkey) =
new ae1: skey;
new ae2: skey;
let gae1 = pk(ae1) in
let gae2 = pk(ae2) in
(* generate amaster and enc msg (PHASE 1) *)
phase 1;
in(c, (gbssig: bitstring, gbs: pkey, gbo: pkey, gpqbo: kempub, gpqbsig: bitstring));
if checksign(PK_B, rb(gbs), gbssig) = okay then
if checksign(PK_B, qb(gpqbo), gpqbsig) = okay then
let (ct: bitstring, ss: bitstring) = pqkem_enc(gpqbo) in
let amaster = hkdf1(concat2(dh(gbs, SK_A), dh(PK_B, ae1), dh(gbs, ae1), dh(gbo, ae1), ss)) in
let (ra1: key, ca1: key) = hkdf2(amaster) in (* derive the root and chain key *)
let mak1 = khash(ca1) in
let (mak1_auth: key, mak1_enc: key) = hkdf2(mak1) in
let x1 = senc(mak1_enc, m1) in
let x1_mac = mac(mak1_auth, concat1(x1, gae1, ct)) in
event sendE1(m1, mak1);
out(c, (x1, x1_mac, gae1, ct));
phase 2;
event compromiseSKA(SK_A);
out(a, SK_A);
phase 3;
event breakDH(dh(gbs, SK_A), dh(PK_B, ae1), dh(gbs, ae1), dh(gbo, ae1));
out(a, (dh(gbs, SK_A), dh(PK_B, ae1), dh(gbs, ae1), dh(gbo, ae1)));
0.
let PeerB(SK_B: skey, PK_B: pkey, PK_A: pkey) =
new bo: skey;
new bs: skey;
new pqbo: kempriv;
let gbs = pk(bs) in
let gbo = pk(bo) in
let gpqbo = kempk(pqbo) in
let gbssig = sign(SK_B, rb(gbs)) in
let gpqbsig = sign(SK_B, qb(gpqbo)) in
out(c, (gbssig, gbs, gbo, gpqbo, gpqbsig));
phase 1; (* peer B commits first *)
(* first stage: derive bmaster, verfiy a's msgs, decrypt prekey message, reply *)
in(c, (x1: bitstring, x1_mac: bitstring, gae1: pkey, ct: bitstring));
let ss = pqkem_dec(pqbo, ct) in
let bmaster = hkdf1(concat2(dh(PK_A, bs), dh(gae1, SK_B), dh(gae1, bs), dh(gae1, bo), ss)) in
let (rb1: key, cb1: key) = hkdf2(bmaster) in (* derive the root and chain key *)
let mbk1 = khash(cb1) in
let (mbk1_auth: key, mbk1_enc: key) = hkdf2(mbk1) in
if checkmac(mbk1_auth, concat1(x1, gae1, ct), x1_mac) = okay then
let m1 = sdec(mbk1_enc, x1) in
event recvE1(m1, mbk1);
phase 2;
event compromiseSKB(SK_B);
out(a, SK_B);
0.
query event(start()). (* reachable from all possible executions *)
(* forward secrecy *)
query sk1: skey, sk2: skey; (event(compromiseSKA(sk1)) && event(compromiseSKB(sk2)) && attacker(m1)) ==> false.
(* secrecy *)
query attacker(m1).
(* forward pq secrecy *)
query k1: key, k2: key, k3: key, k4: key; (event(breakDH(k1, k2, k3, k4)) && attacker(m1)) ==> false.
(* auth *)
query m: bitstring, rk: key; event(recvE1(m, rk)) ==> event(sendE1(m, rk)).
(* reachability *)
query m: bitstring, rk: key; event(recvE1(m, rk)). (* reachable from all executions *)
query m: bitstring, rk: key; event(sendE1(m, rk)). (* reachable from all executions *)
process
new SK_A: skey; let PK_A = pk(SK_A) in
new SK_B: skey; let PK_B = pk(SK_B) in
out(a, PK_A);
out(a, PK_B);
event start();
( (PeerA(SK_A, PK_A, PK_B)) |
(PeerB(SK_B, PK_B, PK_A)))

View File

@@ -0,0 +1,302 @@
(*
Sender Keys protocol with PQXDH; proving secrecy, authentication, PFS, quantum PFS
Author: [redacted]
*)
free m1: bitstring [private].
free m2: bitstring [private].
set selFun = Nounifset.
set redundancyElim = best.
set simpEqAll = false.
set redundantHypElim = true.
set simplifyProcess = true.
set stopTerm = false.
free c: channel.
free a: channel. (* channel for the attacker *)
free p: channel [private]. (* For the distribution of public keys with integrity and authenticity - verification happens out of band. This is a standard assumption. *)
(* Symmetric key encryption *)
type key.
fun senc(key, bitstring): bitstring.
reduc forall m: bitstring, k: key; sdec(k, senc(k,m)) = m.
(* Asymmetric key encryption *)
type skey.
type pkey.
fun rb(pkey): bitstring.
fun pk(skey): pkey.
fun aenc(bitstring, pkey): bitstring.
reduc forall m: bitstring, sk: skey; adec(aenc(m,pk(sk)),sk) = m.
(* Digital signatures *)
fun sign(skey, bitstring): bitstring.
fun okay():bitstring.
reduc forall m: bitstring, sk: skey; checksign(pk(sk), m, sign(sk, m)) = okay.
(* MACs *)
fun mac(key, bitstring): bitstring.
reduc forall k: key, m: bitstring; checkmac(k, m, mac(k, m)) = okay.
(* Diffie-Hellman *)
(* DH -> Public^Private *)
fun dh(pkey, skey): key.
equation forall a: skey, b: skey; dh(pk(a), b) = dh(pk(b), a). (* symmetry of DH *)
(* KEM encapsulation *)
type kempriv.
type kempub.
fun kempk(kempriv):kempub.
fun penc(kempub, bitstring):bitstring.
(* fun pdec(kempriv,bitstring):bitstring. *)
reduc forall sk: kempriv, m:bitstring; pdec(sk, penc(kempk(sk), m)) = m.
letfun kempriv2pub(k:kempriv) = kempk(k).
letfun pqkem_enc(pk:kempub) =
new ss:bitstring;
(penc(pk,ss),ss).
letfun pqkem_dec(sk:kempriv,ct:bitstring) =
pdec(sk,ct).
fun qb(kempub): bitstring [data].
(* the concats *)
fun concat1(bitstring, pkey, bitstring): bitstring [data].
fun concat2(key, key, key, key, bitstring): bitstring [data].
fun concat3(key, key): key [data].
fun concat4(bitstring, pkey): bitstring [data].
fun khash(key): key.
fun hkdf1(bitstring): key [data].
fun hkdf2_dev1(key): key [data].
fun hkdf2_dev2(key): key [data].
letfun hkdf2(k: key) =
(hkdf2_dev1(k), hkdf2_dev2(k)).
(* event declarations *)
event sendOE1(pkey, key, pkey).
event recvOE1(pkey, key, pkey).
event sendOE2(pkey, key, pkey).
event recvOE2(pkey, key, pkey).
event sendME1(bitstring, key).
event recvME1(bitstring, key).
event sendME2(bitstring, key).
event recvME2(bitstring, key).
event compromiseSKA(skey).
event compromiseSKB(skey).
event breakDH(key, key, key, key).
event start().
let PeerA(SK_A: skey, PK_A: pkey, PK_B: pkey, FSK_A: skey, FPK_A: pkey) =
new ae1: skey;
new ae2: skey;
let gae1 = pk(ae1) in
let gae2 = pk(ae2) in
(* generate amaster and enc msg (PHASE 1) *)
phase 1;
in(c, (gbssig: bitstring, gbs: pkey, gbo: pkey, gpqbo: kempub, gpqbsig: bitstring));
if checksign(PK_B, rb(gbs), gbssig) = okay then
if checksign(PK_B, qb(gpqbo), gpqbsig) = okay then
let (ct: bitstring, ss: bitstring) = pqkem_enc(gpqbo) in
let amaster = hkdf1(concat2(dh(gbs, SK_A), dh(PK_B, ae1), dh(gbs, ae1), dh(gbo, ae1), ss)) in
let (ra1: key, ca1: key) = hkdf2(amaster) in (* derive the root and chain key *)
let ak1 = khash(ca1) in
let (ak1_auth: key, ak1_enc: key) = hkdf2(ak1) in
new mra0: key; (* mra0 := fan out layer ratchet *)
let x1 = senc(ak1_enc, (FPK_A, mra0)) in
let x1_mac = mac(ak1_auth, concat1(x1, gae1, ct)) in
event sendOE1(FPK_A, mra0, gae1);
out(c, (x1, x1_mac, gae1, ct));
(* =================== PHASE 3: SENDER KEYS SESSION B -> A =================== *)
in(c, (x2: bitstring, x2_mac: bitstring, gtb2: pkey));
let (ca2: key, ra2: key) = hkdf2(concat3(khash(ra1), dh(gtb2, ae1))) in
let ak2 = khash(ca2) in
let (ak2_auth: key, ak2_enc: key) = hkdf2(ak2) in
if checkmac(ak2_auth, concat4(x2, gtb2), x2_mac) = okay then
let (FPK_B: pkey, mrb0: key) = sdec(ak2_enc, x2) in
event recvOE2(FPK_B, mrb0, gae2);
(* =================== PHASE 4: SENDER KEYS MSG A -> B =================== *)
let mra1 = khash(mra0) in
let (mak1_auth: key, mak1_enc: key) = hkdf2(mra1) in
let mx1 = senc(mak1_enc, m1) in
let mx1_sig = sign(FSK_A, mx1) in
event sendME1(m1, mra1);
out(c, (mx1, mx1_sig));
(* =================== PHASE 5: SENDER KEYS MSG B -> A =================== *)
in(c, (mx2: bitstring, mx2_sig: bitstring));
let mrb1 = khash(mrb0) in
let (mbk1_auth: key, mbk1_enc: key) = hkdf2(mrb1) in
if checksign(FPK_B, mx2, mx2_sig) = okay then
let m2 = sdec(mbk1_enc, mx2) in
event recvME2(m2, mrb1);
(* =================== PHASE 6: Compromise =================== *)
phase 5;
event compromiseSKA(SK_A);
out(a, SK_A);
phase 6;
event breakDH(dh(gbs, SK_A), dh(PK_B, ae1), dh(gbs, ae1), dh(gbo, ae1));
out(a, (dh(gbs, SK_A), dh(PK_B, ae1), dh(gbs, ae1), dh(gbo, ae1)));
0.
let PeerB(SK_B: skey, PK_B: pkey, PK_A: pkey, FSK_B: skey, FPK_B: pkey) =
new bo: skey;
new bs: skey;
new pqbo: kempriv;
let gbs = pk(bs) in
let gbo = pk(bo) in
let gpqbo = kempk(pqbo) in
let gbssig = sign(SK_B, rb(gbs)) in
let gpqbsig = sign(SK_B, qb(gpqbo)) in
out(c, (gbssig, gbs, gbo, gpqbo, gpqbsig));
phase 1; (* peer B commits first *)
(* =================== PHASE 2: DERIVE P2P MASTER + SENDER KEYS SESSION A -> B =================== *)
in(c, (x1: bitstring, x1_mac: bitstring, gae1: pkey, ct: bitstring));
let ss = pqkem_dec(pqbo, ct) in
let bmaster = hkdf1(concat2(dh(PK_A, bs), dh(gae1, SK_B), dh(gae1, bs), dh(gae1, bo), ss)) in
let (rb1: key, cb1: key) = hkdf2(bmaster) in (* derive the root and chain key *)
let bk1 = khash(cb1) in
let (bk1_auth: key, bk1_enc: key) = hkdf2(bk1) in
if checkmac(bk1_auth, concat1(x1, gae1, ct), x1_mac) = okay then
let (mra0: key, FPK_A: pkey) = sdec(bk1_enc, x1) in
event recvOE1(FPK_A, mra0, gae1);
(* =================== PHASE 3: SENDER KEYS SESH B -> A =================== *)
new tb2: skey;
let gtb2 = pk(tb2) in
let (rb2: key, cb2: key) = hkdf2(concat3(khash(rb1), dh(gae1, tb2))) in
let bk2 = khash(cb2) in
let (bk2_auth: key, bk2_enc: key) = hkdf2(bk2) in
new mrb0: key; (* mrb0 := sender keys ratchet *)
let x2 = senc(bk2_enc, (FPK_B, mrb0)) in
let x2_mac = mac(bk2_auth, concat4(x2, gtb2)) in
event sendOE2(FPK_B, mrb0, gae1);
out(c, (x2, x2_mac, gtb2));
(* =================== PHASE 4: SENDER KEYS MSG A -> B =================== *)
in(c, (mx1: bitstring, mx1_sig: bitstring));
let mra1 = khash(mra0) in
let (mak1_auth: key, mak1_enc: key) = hkdf2(mra1) in
if checksign(FPK_A, mx1, mx1_sig) = okay then
let m1 = sdec(mak1_enc, mx1) in
event recvME1(m1, mra1);
(* =================== PHASE 5: SENDER KEYS MSG B -> A =================== *)
let mrb1 = khash(mrb0) in
let (mbk1_auth: key, mbk1_enc: key) = hkdf2(mrb1) in
let mx2 = senc(mbk1_enc, m2) in
let mx2_sig = sign(FSK_B, mx2) in
event sendME2(m2, mrb1);
out(c, (mx2, mx2_sig));
(* =================== PHASE 6: Compromise =================== *)
phase 5;
event compromiseSKB(SK_B);
out(a, SK_B);
0.
query event(start()).
(* forward secrecy *)
query sk1: skey, sk2: skey; (event(compromiseSKA(sk1)) && event(compromiseSKB(sk2)) && attacker(m1)) ==> false.
(* secrecy *)
query attacker(m1).
query attacker(m2).
(* forward pq secrecy *)
query k1: key, k2: key, k3: key, k4: key; (event(breakDH(k1, k2, k3, k4)) && attacker(m1)) ==> false.
(* auth *)
query k1: pkey, rk: key, k2: pkey; inj-event(recvOE1(k1, rk, k2)) ==> inj-event(sendOE1(k1, rk, k2)).
query k1: pkey, rk: key, k2: pkey; inj-event(recvOE2(k1, rk, k2)) ==> inj-event(sendOE2(k1, rk, k2)).
query m: bitstring, rk: key; inj-event(recvME1(m, rk)) ==> inj-event(sendME1(m, rk)).
query m: bitstring, rk: key; inj-event(recvME2(m, rk)) ==> inj-event(sendME2(m, rk)).
(* reachability *)
query k1: pkey, rk: key, k2: pkey; event(sendOE1(k1,rk,k2)).
query k1: pkey, rk: key, k2: pkey; event(recvOE1(k1,rk,k2)).
query k1: pkey, rk: key, k2: pkey; event(sendOE2(k1,rk,k2)).
query k1: pkey, rk: key, k2: pkey; event(recvOE2(k1,rk,k2)).
query m: bitstring, rk: key; event(sendME1(m, rk)).
query m: bitstring, rk: key; event(recvME1(m, rk)).
query m: bitstring, rk: key; event(sendME2(m, rk)).
query m: bitstring, rk: key; event(recvME2(m, rk)).
process
new SK_A: skey; let PK_A = pk(SK_A) in
new SK_B: skey; let PK_B = pk(SK_B) in
new FSK_A: skey; let FPK_A = pk(FSK_A) in
new FSK_B: skey; let FPK_B = pk(FSK_B) in
out(a, PK_A);
out(a, PK_B);
(*
out(a, FPK_A);
out(a, FPK_B);
*)
event start();
( (PeerA(SK_A, PK_A, PK_B, FSK_A, FPK_A)) |
(PeerB(SK_B, PK_B, PK_A, FSK_B, FPK_B)))

View File

@@ -0,0 +1,212 @@
(*
PQXDH + Double Ratchet; proving offline deniability for the initiator
Author: jake ginesin
model assumption #1: same key is used for signing and encryption (i.e. X25519)
*)
free m1: bitstring [private].
free m2: bitstring [private].
set attacker = passive.
set selFun = Nounifset.
set simplifyProcess = false.
(*
set simpEqAll = false.
set simpEqAll = false.
set redundancyElim = best.
set redundantHypElim = true.
set simplifyProcess = true.
set stopTerm = false.
*)
free c: channel.
free a: channel. (* channel for the attacker *)
free p: channel [private]. (* For the distribution of public keys with integrity and authenticity - verification happens out of band. This is a standard assumption. *)
(* Symmetric key encryption *)
type key.
fun senc(key, bitstring): bitstring.
reduc forall m: bitstring, k: key; sdec(k, senc(k,m)) = m.
(* Asymmetric key encryption *)
type skey.
type pkey.
fun rb(pkey): bitstring [data].
fun pk(skey): pkey.
(* Digital signatures *)
fun sign(skey, bitstring): bitstring.
fun okay():bitstring.
reduc forall m: bitstring, sk: skey; checksign(pk(sk), m, sign(sk, m)) = okay.
(* MACs *)
fun mac(key, bitstring): bitstring.
reduc forall k: key, m: bitstring; checkmac(k, m, mac(k, m)) = okay.
(* Diffie-Hellman *)
(* DH -> Public^Private *)
fun dh(pkey, skey): key.
equation forall a: skey, b: skey; dh(pk(a), b) = dh(pk(b), a). (* symmetry of DH *)
(* the concat functions *)
fun hkdf1(bitstring): key [data].
fun khash(key): key.
fun hkdf2_dev1(key): key [data].
fun hkdf2_dev2(key): key [data].
letfun hkdf2(k: key) =
(hkdf2_dev1(k), hkdf2_dev2(k)).
fun hkdf4_dev1(key, key): key [data].
fun hkdf4_dev2(key, key): key [data].
letfun hkdf4(k1: key, k2: key) =
(hkdf4_dev1(k1, k2), hkdf4_dev2(k1, k2)).
(* KEM encapsulation *)
type kempriv.
type kempub.
fun kempk(kempriv):kempub.
fun penc(kempub, bitstring):bitstring.
(* fun pdec(kempriv,bitstring):bitstring. *)
reduc forall sk: kempriv, m:bitstring; pdec(sk, penc(kempk(sk), m)) = m.
letfun kempriv2pub(k:kempriv) = kempk(k).
letfun pqkem_enc(pk:kempub) =
new ss:bitstring;
(penc(pk,ss),ss).
letfun pqkem_dec(sk:kempriv,ct:bitstring) =
pdec(sk,ct).
fun qb(kempub): bitstring [data].
(* the concats *)
fun concat1(bitstring, pkey, pkey, bitstring): bitstring [data].
fun concat2(key, key, key, key, bitstring): bitstring [data].
fun concat3(bitstring, pkey): bitstring [data].
(* events *)
event sendE1(bitstring, key).
event recvE1(bitstring, key).
event sendE2(bitstring, key).
event recvE2(bitstring, key).
event compromiseSKA(skey).
event compromiseSKB(skey).
event breakDH(key, key, key, key).
event masterLeak(key).
event start().
let PeerA(SK_A: skey, PK_A: pkey, PK_B: pkey) =
new ae1: skey;
new ae2: skey;
let gae1 = pk(ae1) in
let gae2 = pk(ae2) in
(* generate amaster and enc msg (PHASE 1) *)
phase 1;
in(c, (gbssig: bitstring, gbs: pkey, gbo: pkey, gpqbo: kempub, gpqbsig: bitstring));
if checksign(PK_B, rb(gbs), gbssig) = okay then
if checksign(PK_B, qb(gpqbo), gpqbsig) = okay then
let (ct: bitstring, ss: bitstring) = pqkem_enc(gpqbo) in
let amaster = hkdf1(concat2(dh(gbs, SK_A), dh(PK_B, ae1), dh(gbs, ae1), dh(gbo, ae1), ss)) in
let (ra1: key, ca1: key) = hkdf2(amaster) in (* derive the root and chain key *)
let mak1 = khash(ca1) in
let (mak1_auth: key, mak1_enc: key) = hkdf2(mak1) in
let x1 = senc(mak1_enc, m1) in
let x1_mac = mac(mak1_auth, concat1(x1, gae1, gae2, ct)) in
event sendE1(m1, mak1);
out(c, (x1, x1_mac, gae1, gae2, ct));
(* second stage *)
in(c, (x2: bitstring, x2_mac: bitstring, gtb2: pkey));
let (ra2: key, ca2: key) = hkdf4(ra1, dh(gtb2, ae1)) in
let mak2 = khash(ca2) in
let (mak2_auth: key, mak2_enc: key) = hkdf2(mak2) in
if checkmac(mak2_auth, concat3(x2, gtb2), x2_mac) = okay then
let m2 = sdec(mak2_enc, x2) in
event recvE2(m2, mak2);
0.
let PeerB(SK_B: skey, PK_B: pkey, PK_A: pkey) =
new bo: skey;
new bs: skey;
new pqbo: kempriv;
let gbs = pk(bs) in
let gbo = pk(bo) in
let gpqbo = kempk(pqbo) in
let gbssig = sign(SK_B, rb(gbs)) in
let gpqbsig = sign(SK_B, qb(gpqbo)) in
out(c, (gbssig, gbs, gbo, gpqbo, gpqbsig));
phase 1; (* peer B commits first *)
(* first stage: derive bmaster, verfiy a's msgs, decrypt prekey message, reply *)
in(c, (x1: bitstring, x1_mac: bitstring, gae1: pkey, gae2: pkey, ct: bitstring));
let ss = pqkem_dec(pqbo, ct) in
let bmaster = hkdf1(concat2(dh(PK_A, bs), dh(gae1, SK_B), dh(gae1, bs), dh(gae1, bo), ss)) in
let (rb1: key, cb1: key) = hkdf2(bmaster) in (* derive the root and chain key *)
let mbk1 = khash(cb1) in
let (mbk1_auth: key, mbk1_enc: key) = hkdf2(mbk1) in
if checkmac(mbk1_auth, concat1(x1, gae1, gae2, ct), x1_mac) = okay then
let m1 = sdec(mbk1_enc, x1) in
event recvE1(m1, mbk1);
(* second stage *)
new tb2: skey;
let gtb2 = pk(tb2) in
let (rb2: key, cb2: key) = hkdf4(rb1, dh(gae2, tb2)) in
let mbk2 = khash(cb2) in
let (mbk2_auth: key, mbk2_enc: key) = hkdf2(mbk2) in
let x2 = senc(mbk2_enc, m2) in
let x2_mac = mac(mbk2_auth, concat3(x2, gtb2)) in
event sendE2(m2, mbk2);
out(c, (x2, x2_mac, gtb2));
0.
process
new SK_A: skey; let PK_A = pk(SK_A) in
new SK_B: skey; let PK_B = pk(SK_B) in
out(a, PK_A);
out(a, PK_B);
new fib1: skey;
new fib2: skey;
let k_A = choice [ SK_A, fib1 ] in
let k_B = choice [ SK_B, fib2 ] in
event start();
( (PeerA(k_A, pk(k_A), PK_B)) |
(PeerB(SK_B, PK_B, PK_A)) |
out(a, m1)) (* transcript publish *)

229
proverif/pq/signal-pcs.pv Normal file
View File

@@ -0,0 +1,229 @@
(*
PQXDH + Double Ratchet; proving post-compromise security
Author: jake ginesin
model assumption #1: same key is used for signing and encryption (i.e. X25519)
model assumption #2: authentication for the first message holds, and is thus omitted from this model. authentication was proved standalone in `pqxdh.pv`
*)
free m1: bitstring [private].
free m2: bitstring [private].
set selFun = Nounifset.
(*
set simpEqAll = false.
set simpEqAll = false.
set redundancyElim = best.
set redundantHypElim = true.
set simplifyProcess = true.
set stopTerm = false.
*)
free c: channel.
free a: channel. (* channel for the attacker *)
free p: channel [private]. (* For the distribution of public keys with integrity and authenticity - verification happens out of band. This is a standard assumption. *)
(* Symmetric key encryption *)
type key.
fun senc(key, bitstring): bitstring.
reduc forall m: bitstring, k: key; sdec(k, senc(k,m)) = m.
(* Asymmetric key encryption *)
type skey.
type pkey.
fun rb(pkey): bitstring [data].
fun pk(skey): pkey.
(* Digital signatures *)
fun sign(skey, bitstring): bitstring.
fun okay():bitstring.
reduc forall m: bitstring, sk: skey; checksign(pk(sk), m, sign(sk, m)) = okay.
(* MACs *)
fun mac(key, bitstring): bitstring.
reduc forall k: key, m: bitstring; checkmac(k, m, mac(k, m)) = okay.
(* Diffie-Hellman *)
(* DH -> Public^Private *)
fun dh(pkey, skey): key.
equation forall a: skey, b: skey; dh(pk(a), b) = dh(pk(b), a). (* symmetry of DH *)
(* the concat functions *)
fun hkdf1(bitstring): key [data].
fun khash(key): key.
fun hkdf2_dev1(key): key [data].
fun hkdf2_dev2(key): key [data].
letfun hkdf2(k: key) =
(hkdf2_dev1(k), hkdf2_dev2(k)).
fun hkdf4_dev1(key, key): key [data].
fun hkdf4_dev2(key, key): key [data].
letfun hkdf4(k1: key, k2: key) =
(hkdf4_dev1(k1, k2), hkdf4_dev2(k1, k2)).
(* KEM encapsulation *)
type kempriv.
type kempub.
fun kempk(kempriv):kempub.
fun penc(kempub, bitstring):bitstring.
(* fun pdec(kempriv,bitstring):bitstring. *)
reduc forall sk: kempriv, m:bitstring; pdec(sk, penc(kempk(sk), m)) = m.
letfun kempriv2pub(k:kempriv) = kempk(k).
letfun pqkem_enc(pk:kempub) =
new ss:bitstring;
(penc(pk,ss),ss).
letfun pqkem_dec(sk:kempriv,ct:bitstring) =
pdec(sk,ct).
fun qb(kempub): bitstring [data].
(* the concats *)
fun concat1(bitstring, pkey, pkey, bitstring): bitstring [data].
fun concat2(key, key, key, key, bitstring): bitstring [data].
fun concat3(bitstring, pkey): bitstring [data].
(* events *)
event sendE1(bitstring, key).
event recvE1(bitstring, key).
event sendE2(bitstring, key).
event recvE2(bitstring, key).
event compromiseSKA(skey).
event compromiseSKB(skey).
event breakDH(key, key, key, key).
event masterLeak(key).
event start().
let PeerA(SK_A: skey, PK_A: pkey, PK_B: pkey) =
new ae1: skey;
new ae2: skey;
let gae1 = pk(ae1) in
let gae2 = pk(ae2) in
(* generate amaster and enc msg (PHASE 1) *)
phase 1;
in(c, (gbssig: bitstring, gbs: pkey, gbo: pkey, gpqbo: kempub, gpqbsig: bitstring));
if checksign(PK_B, rb(gbs), gbssig) = okay then
if checksign(PK_B, qb(gpqbo), gpqbsig) = okay then
let (ct: bitstring, ss: bitstring) = pqkem_enc(gpqbo) in
let amaster = hkdf1(concat2(dh(gbs, SK_A), dh(PK_B, ae1), dh(gbs, ae1), dh(gbo, ae1), ss)) in
let (ra1: key, ca1: key) = hkdf2(amaster) in (* derive the root and chain key *)
let mak1 = khash(ca1) in
let (mak1_auth: key, mak1_enc: key) = hkdf2(mak1) in
let x1 = senc(mak1_enc, m1) in
let x1_mac = mac(mak1_auth, concat1(x1, gae1, gae2, ct)) in
event sendE1(m1, mak1);
out(c, (x1, x1_mac, gae1, gae2, ct));
(* second stage *)
phase 2;
in(c, (x2: bitstring, x2_mac: bitstring, gtb2: pkey));
let (ra2: key, ca2: key) = hkdf4(ra1, dh(gtb2, ae2)) in
let mak2 = khash(ca2) in
let (mak2_auth: key, mak2_enc: key) = hkdf2(mak2) in
if checkmac(mak2_auth, concat3(x2, gtb2), x2_mac) = okay then
let m2 = sdec(mak2_enc, x2) in
event recvE2(m2, mak2);
phase 3;
event breakDH(dh(gbs, SK_A), dh(PK_B, ae1), dh(gbs, ae1), dh(gbo, ae1));
out(a, (dh(gbs, SK_A), dh(PK_B, ae1), dh(gbs, ae1), dh(gbo, ae1)));
phase 4;
event masterLeak(amaster);
out(a, amaster);
0.
let PeerB(SK_B: skey, PK_B: pkey, PK_A: pkey) =
new bo: skey;
new bs: skey;
new pqbo: kempriv;
let gbs = pk(bs) in
let gbo = pk(bo) in
let gpqbo = kempk(pqbo) in
let gbssig = sign(SK_B, rb(gbs)) in
let gpqbsig = sign(SK_B, qb(gpqbo)) in
out(c, (gbssig, gbs, gbo, gpqbo, gpqbsig));
phase 1; (* peer B commits first *)
(* first stage: derive bmaster, verfiy a's msgs, decrypt prekey message, reply *)
in(c, (x1: bitstring, x1_mac: bitstring, gae1: pkey, gae2: pkey, ct: bitstring));
let ss = pqkem_dec(pqbo, ct) in
let bmaster = hkdf1(concat2(dh(PK_A, bs), dh(gae1, SK_B), dh(gae1, bs), dh(gae1, bo), ss)) in
let (rb1: key, cb1: key) = hkdf2(bmaster) in (* derive the root and chain key *)
let mbk1 = khash(cb1) in
let (mbk1_auth: key, mbk1_enc: key) = hkdf2(mbk1) in
if checkmac(mbk1_auth, concat1(x1, gae1, gae2, ct), x1_mac) = okay then
let m1 = sdec(mbk1_enc, x1) in
event recvE1(m1, mbk1);
(* second stage *)
phase 2;
new tb2: skey;
let gtb2 = pk(tb2) in
let (rb2: key, cb2: key) = hkdf4(rb1, dh(gae2, tb2)) in
let mbk2 = khash(cb2) in
let (mbk2_auth: key, mbk2_enc: key) = hkdf2(mbk2) in
let x2 = senc(mbk2_enc, m2) in
let x2_mac = mac(mbk2_auth, concat3(x2, gtb2)) in
event sendE2(m2, mbk2);
out(c, (x2, x2_mac, gtb2));
0.
(* post-compromise security: compromise of the master key results in the compromise of the
message in the same ratchet session, but the next ratchet session's messages are fine *)
query k1: key; (event(masterLeak(k1)) && attacker(m2)) ==> false.
query k1: key; attacker(m1) ==> event(masterLeak(k1)).
query event(start()). (* reachable from all possible executions *)
(* reachability *)
query m: bitstring, rk: key; event(recvE1(m, rk)). (* reachable from all executions *)
query m: bitstring, rk: key; event(recvE2(m, rk)). (* reachable from all executions *)
query m: bitstring, rk: key; event(sendE1(m, rk)). (* reachable from all executions *)
query m: bitstring, rk: key; event(sendE2(m, rk)). (* rechable from all executions *)
process
new SK_A: skey; let PK_A = pk(SK_A) in
new SK_B: skey; let PK_B = pk(SK_B) in
out(a, PK_A);
out(a, PK_B);
event start();
( (PeerA(SK_A, PK_A, PK_B)) |
(PeerB(SK_B, PK_B, PK_A)))

View File

@@ -0,0 +1,213 @@
(*
PQXDH + Double Ratchet; proving no offline deniability for the responder
Author: jake ginesin
model assumption #1: same key is used for signing and encryption (i.e. X25519)
model assumption #2: authentication for the first message holds, and is thus omitted from this model. authentication was proved standalone in `pqxdh.pv`
*)
free m1: bitstring [private].
free m2: bitstring [private].
set attacker = passive.
set selFun = Nounifset.
set simplifyProcess = false.
(*
set simpEqAll = false.
set simpEqAll = false.
set redundancyElim = best.
set redundantHypElim = true.
set simplifyProcess = true.
set stopTerm = false.
*)
free c: channel.
free a: channel. (* channel for the attacker *)
free p: channel [private]. (* For the distribution of public keys with integrity and authenticity - verification happens out of band. This is a standard assumption. *)
(* Symmetric key encryption *)
type key.
fun senc(key, bitstring): bitstring.
reduc forall m: bitstring, k: key; sdec(k, senc(k,m)) = m.
(* Asymmetric key encryption *)
type skey.
type pkey.
fun rb(pkey): bitstring [data].
fun pk(skey): pkey.
(* Digital signatures *)
fun sign(skey, bitstring): bitstring.
fun okay():bitstring.
reduc forall m: bitstring, sk: skey; checksign(pk(sk), m, sign(sk, m)) = okay.
(* MACs *)
fun mac(key, bitstring): bitstring.
reduc forall k: key, m: bitstring; checkmac(k, m, mac(k, m)) = okay.
(* Diffie-Hellman *)
(* DH -> Public^Private *)
fun dh(pkey, skey): key.
equation forall a: skey, b: skey; dh(pk(a), b) = dh(pk(b), a). (* symmetry of DH *)
(* the concat functions *)
fun hkdf1(bitstring): key [data].
fun khash(key): key.
fun hkdf2_dev1(key): key [data].
fun hkdf2_dev2(key): key [data].
letfun hkdf2(k: key) =
(hkdf2_dev1(k), hkdf2_dev2(k)).
fun hkdf4_dev1(key, key): key [data].
fun hkdf4_dev2(key, key): key [data].
letfun hkdf4(k1: key, k2: key) =
(hkdf4_dev1(k1, k2), hkdf4_dev2(k1, k2)).
(* KEM encapsulation *)
type kempriv.
type kempub.
fun kempk(kempriv):kempub.
fun penc(kempub, bitstring):bitstring.
(* fun pdec(kempriv,bitstring):bitstring. *)
reduc forall sk: kempriv, m:bitstring; pdec(sk, penc(kempk(sk), m)) = m.
letfun kempriv2pub(k:kempriv) = kempk(k).
letfun pqkem_enc(pk:kempub) =
new ss:bitstring;
(penc(pk,ss),ss).
letfun pqkem_dec(sk:kempriv,ct:bitstring) =
pdec(sk,ct).
fun qb(kempub): bitstring [data].
(* the concats *)
fun concat1(bitstring, pkey, pkey, bitstring): bitstring [data].
fun concat2(key, key, key, key, bitstring): bitstring [data].
fun concat3(bitstring, pkey): bitstring [data].
(* events *)
event sendE1(bitstring, key).
event recvE1(bitstring, key).
event sendE2(bitstring, key).
event recvE2(bitstring, key).
event compromiseSKA(skey).
event compromiseSKB(skey).
event breakDH(key, key, key, key).
event masterLeak(key).
event start().
let PeerA(SK_A: skey, PK_A: pkey, PK_B: pkey) =
new ae1: skey;
new ae2: skey;
let gae1 = pk(ae1) in
let gae2 = pk(ae2) in
(* generate amaster and enc msg (PHASE 1) *)
phase 1;
in(c, (gbssig: bitstring, gbs: pkey, gbo: pkey, gpqbo: kempub, gpqbsig: bitstring));
if checksign(PK_B, rb(gbs), gbssig) = okay then
if checksign(PK_B, qb(gpqbo), gpqbsig) = okay then
let (ct: bitstring, ss: bitstring) = pqkem_enc(gpqbo) in
let amaster = hkdf1(concat2(dh(gbs, SK_A), dh(PK_B, ae1), dh(gbs, ae1), dh(gbo, ae1), ss)) in
let (ra1: key, ca1: key) = hkdf2(amaster) in (* derive the root and chain key *)
let mak1 = khash(ca1) in
let (mak1_auth: key, mak1_enc: key) = hkdf2(mak1) in
let x1 = senc(mak1_enc, m1) in
let x1_mac = mac(mak1_auth, concat1(x1, gae1, gae2, ct)) in
event sendE1(m1, mak1);
out(c, (x1, x1_mac, gae1, gae2, ct));
(* second stage *)
in(c, (x2: bitstring, x2_mac: bitstring, gtb2: pkey));
let (ra2: key, ca2: key) = hkdf4(ra1, dh(gtb2, ae1)) in
let mak2 = khash(ca2) in
let (mak2_auth: key, mak2_enc: key) = hkdf2(mak2) in
if checkmac(mak2_auth, concat3(x2, gtb2), x2_mac) = okay then
let m2 = sdec(mak2_enc, x2) in
event recvE2(m2, mak2);
0.
let PeerB(SK_B: skey, PK_B: pkey, PK_A: pkey) =
new bo: skey;
new bs: skey;
new pqbo: kempriv;
let gbs = pk(bs) in
let gbo = pk(bo) in
let gpqbo = kempk(pqbo) in
let gbssig = sign(SK_B, rb(gbs)) in
let gpqbsig = sign(SK_B, qb(gpqbo)) in
out(c, (gbssig, gbs, gbo, gpqbo, gpqbsig));
phase 1; (* peer B commits first *)
(* first stage: derive bmaster, verfiy a's msgs, decrypt prekey message, reply *)
in(c, (x1: bitstring, x1_mac: bitstring, gae1: pkey, gae2: pkey, ct: bitstring));
let ss = pqkem_dec(pqbo, ct) in
let bmaster = hkdf1(concat2(dh(PK_A, bs), dh(gae1, SK_B), dh(gae1, bs), dh(gae1, bo), ss)) in
let (rb1: key, cb1: key) = hkdf2(bmaster) in (* derive the root and chain key *)
let mbk1 = khash(cb1) in
let (mbk1_auth: key, mbk1_enc: key) = hkdf2(mbk1) in
if checkmac(mbk1_auth, concat1(x1, gae1, gae2, ct), x1_mac) = okay then
let m1 = sdec(mbk1_enc, x1) in
event recvE1(m1, mbk1);
(* second stage *)
new tb2: skey;
let gtb2 = pk(tb2) in
let (rb2: key, cb2: key) = hkdf4(rb1, dh(gae2, tb2)) in
let mbk2 = khash(cb2) in
let (mbk2_auth: key, mbk2_enc: key) = hkdf2(mbk2) in
let x2 = senc(mbk2_enc, m2) in
let x2_mac = mac(mbk2_auth, concat3(x2, gtb2)) in
event sendE2(m2, mbk2);
out(c, (x2, x2_mac, gtb2));
0.
process
new SK_A: skey; let PK_A = pk(SK_A) in
new SK_B: skey; let PK_B = pk(SK_B) in
out(a, PK_A);
out(a, PK_B);
new fib1: skey;
new fib2: skey;
let k_A = choice [ SK_A, fib1 ] in
let k_B = choice [ SK_B, fib2 ] in
event start();
( (PeerA(SK_B, PK_A, pk(k_B))) |
(PeerB(k_B, pk(k_B), PK_A)) |
out(a, m1))

243
proverif/pq/signal.pv Normal file
View File

@@ -0,0 +1,243 @@
(*
PQXDH + Double Ratchet; proving authenticity, secrecy, forward secrecy, and post-quantum forward secrecy
Author: jake ginesin
model assumption #1: same key is used for signing and encryption (i.e. X25519)
model assumption #2: authentication for the first message holds, and is thus omitted from this model. authentication was proved standalone in `pqxdh.pv`
*)
free m1: bitstring [private].
free m2: bitstring [private].
set selFun = Nounifset.
(*
set simpEqAll = false.
set simpEqAll = false.
set redundancyElim = best.
set redundantHypElim = true.
set simplifyProcess = true.
set stopTerm = false.
*)
free c: channel.
free a: channel. (* channel for the attacker *)
free p: channel [private]. (* For the distribution of public keys with integrity and authenticity - verification happens out of band. This is a standard assumption. *)
(* Symmetric key encryption *)
type key.
fun senc(key, bitstring): bitstring.
reduc forall m: bitstring, k: key; sdec(k, senc(k,m)) = m.
(* Asymmetric key encryption *)
type skey.
type pkey.
fun rb(pkey): bitstring [data].
fun pk(skey): pkey.
(* Digital signatures *)
fun sign(skey, bitstring): bitstring.
fun okay():bitstring.
reduc forall m: bitstring, sk: skey; checksign(pk(sk), m, sign(sk, m)) = okay.
(* MACs *)
fun mac(key, bitstring): bitstring.
reduc forall k: key, m: bitstring; checkmac(k, m, mac(k, m)) = okay.
(* Diffie-Hellman *)
(* DH -> Public^Private *)
fun dh(pkey, skey): key.
equation forall a: skey, b: skey; dh(pk(a), b) = dh(pk(b), a). (* symmetry of DH *)
(* the concat functions *)
fun hkdf1(bitstring): key [data].
fun khash(key): key.
fun hkdf2_dev1(key): key [data].
fun hkdf2_dev2(key): key [data].
letfun hkdf2(k: key) =
(hkdf2_dev1(k), hkdf2_dev2(k)).
fun hkdf4_dev1(key, key): key [data].
fun hkdf4_dev2(key, key): key [data].
letfun hkdf4(k1: key, k2: key) =
(hkdf4_dev1(k1, k2), hkdf4_dev2(k1, k2)).
(* KEM encapsulation *)
type kempriv.
type kempub.
fun kempk(kempriv):kempub.
fun penc(kempub, bitstring):bitstring.
(* fun pdec(kempriv,bitstring):bitstring. *)
reduc forall sk: kempriv, m:bitstring; pdec(sk, penc(kempk(sk), m)) = m.
letfun kempriv2pub(k:kempriv) = kempk(k).
letfun pqkem_enc(pk:kempub) =
new ss:bitstring;
(penc(pk,ss),ss).
letfun pqkem_dec(sk:kempriv,ct:bitstring) =
pdec(sk,ct).
fun qb(kempub): bitstring [data].
(* the concats *)
fun concat1(bitstring, pkey, pkey, bitstring): bitstring [data].
fun concat2(key, key, key, key, bitstring): bitstring [data].
fun concat3(bitstring, pkey): bitstring [data].
(* events *)
event sendE1(bitstring, key).
event recvE1(bitstring, key).
event sendE2(bitstring, key).
event recvE2(bitstring, key).
event compromiseSKA(skey).
event compromiseSKB(skey).
event breakDH(key, key, key, key).
event masterLeak(key).
event start().
let PeerA(SK_A: skey, PK_A: pkey, PK_B: pkey) =
new ae1: skey;
new ae2: skey;
let gae1 = pk(ae1) in
let gae2 = pk(ae2) in
(* generate amaster and enc msg (PHASE 1) *)
phase 1;
in(c, (gbssig: bitstring, gbs: pkey, gbo: pkey, gpqbo: kempub, gpqbsig: bitstring));
if checksign(PK_B, rb(gbs), gbssig) = okay then
if checksign(PK_B, qb(gpqbo), gpqbsig) = okay then
let (ct: bitstring, ss: bitstring) = pqkem_enc(gpqbo) in
let amaster = hkdf1(concat2(dh(gbs, SK_A), dh(PK_B, ae1), dh(gbs, ae1), dh(gbo, ae1), ss)) in
let (ra1: key, ca1: key) = hkdf2(amaster) in (* derive the root and chain key *)
let mak1 = khash(ca1) in
let (mak1_auth: key, mak1_enc: key) = hkdf2(mak1) in
let x1 = senc(mak1_enc, m1) in
let x1_mac = mac(mak1_auth, concat1(x1, gae1, gae2, ct)) in
event sendE1(m1, mak1);
out(c, (x1, x1_mac, gae1, gae2, ct));
(* second stage *)
phase 2;
in(c, (x2: bitstring, x2_mac: bitstring, gtb2: pkey));
let (ra2: key, ca2: key) = hkdf4(ra1, dh(gtb2, ae1)) in
let mak2 = khash(ca2) in
let (mak2_auth: key, mak2_enc: key) = hkdf2(mak2) in
if checkmac(mak2_auth, concat3(x2, gtb2), x2_mac) = okay then
let m2 = sdec(mak2_enc, x2) in
event recvE2(m2, mak2);
phase 3;
event compromiseSKA(SK_A);
out(a, SK_A);
phase 4;
event breakDH(dh(gbs, SK_A), dh(PK_B, ae1), dh(gbs, ae1), dh(gbo, ae1));
out(a, (dh(gbs, SK_A), dh(PK_B, ae1), dh(gbs, ae1), dh(gbo, ae1)));
0.
let PeerB(SK_B: skey, PK_B: pkey, PK_A: pkey) =
new bo: skey;
new bs: skey;
new pqbo: kempriv;
let gbs = pk(bs) in
let gbo = pk(bo) in
let gpqbo = kempk(pqbo) in
let gbssig = sign(SK_B, rb(gbs)) in
let gpqbsig = sign(SK_B, qb(gpqbo)) in
out(c, (gbssig, gbs, gbo, gpqbo, gpqbsig));
phase 1; (* peer B commits first *)
(* first stage: derive bmaster, verfiy a's msgs, decrypt prekey message, reply *)
in(c, (x1: bitstring, x1_mac: bitstring, gae1: pkey, gae2: pkey, ct: bitstring));
let ss = pqkem_dec(pqbo, ct) in
let bmaster = hkdf1(concat2(dh(PK_A, bs), dh(gae1, SK_B), dh(gae1, bs), dh(gae1, bo), ss)) in
let (rb1: key, cb1: key) = hkdf2(bmaster) in (* derive the root and chain key *)
let mbk1 = khash(cb1) in
let (mbk1_auth: key, mbk1_enc: key) = hkdf2(mbk1) in
if checkmac(mbk1_auth, concat1(x1, gae1, gae2, ct), x1_mac) = okay then
let m1 = sdec(mbk1_enc, x1) in
event recvE1(m1, mbk1);
(* second stage *)
phase 2;
new tb2: skey;
let gtb2 = pk(tb2) in
let (rb2: key, cb2: key) = hkdf4(rb1, dh(gae2, tb2)) in
let mbk2 = khash(cb2) in
let (mbk2_auth: key, mbk2_enc: key) = hkdf2(mbk2) in
let x2 = senc(mbk2_enc, m2) in
let x2_mac = mac(mbk2_auth, concat3(x2, gtb2)) in
event sendE2(m2, mbk2);
out(c, (x2, x2_mac, gtb2));
phase 3;
event compromiseSKB(SK_B);
out(a, SK_B);
0.
query event(start()). (* reachable from all possible executions *)
(* forward secrecy *)
query sk1: skey, sk2: skey; (event(compromiseSKA(sk1)) && event(compromiseSKA(sk2)) && attacker(m1)) ==> false.
(* secrecy *)
query attacker(m1).
query attacker(m2).
(* forward pq secrecy *)
query k1: key, k2: key, k3: key, k4: key; (event(breakDH(k1, k2, k3, k4)) && attacker(m1)) ==> false.
(* auth *)
(* query m: bitstring, rk: key; event(recvE1(m, rk)) ==> event(sendE1(m, rk)). *)
query m: bitstring, rk: key; inj-event(recvE2(m, rk)) ==> inj-event(sendE2(m, rk)).
(* reachability *)
query m: bitstring, rk: key; event(recvE1(m, rk)). (* reachable from all executions *)
query m: bitstring, rk: key; event(recvE2(m, rk)). (* reachable from all executions *)
query m: bitstring, rk: key; event(sendE1(m, rk)). (* reachable from all executions *)
query m: bitstring, rk: key; event(sendE2(m, rk)). (* rechable from all executions *)
process
new SK_A: skey; let PK_A = pk(SK_A) in
new SK_B: skey; let PK_B = pk(SK_B) in
out(a, PK_A);
out(a, PK_B);
event start();
( (PeerA(SK_A, PK_A, PK_B)) |
(PeerB(SK_B, PK_B, PK_A)))

View File

@@ -0,0 +1,302 @@
(*
Sender Keys protocol with PQXDH; proving secrecy, authentication, PFS, quantum PFS
Author: [redacted]
*)
free m1: bitstring [private].
free m2: bitstring [private].
set selFun = Nounifset.
set redundancyElim = best.
set simpEqAll = false.
set redundantHypElim = true.
set simplifyProcess = true.
set stopTerm = false.
free c: channel.
free a: channel. (* channel for the attacker *)
free p: channel [private]. (* For the distribution of public keys with integrity and authenticity - verification happens out of band. This is a standard assumption. *)
(* Symmetric key encryption *)
type key.
fun senc(key, bitstring): bitstring.
reduc forall m: bitstring, k: key; sdec(k, senc(k,m)) = m.
(* Asymmetric key encryption *)
type skey.
type pkey.
fun rb(pkey): bitstring.
fun pk(skey): pkey.
fun aenc(bitstring, pkey): bitstring.
reduc forall m: bitstring, sk: skey; adec(aenc(m,pk(sk)),sk) = m.
(* Digital signatures *)
fun sign(skey, bitstring): bitstring.
fun okay():bitstring.
reduc forall m: bitstring, sk: skey; checksign(pk(sk), m, sign(sk, m)) = okay.
(* MACs *)
fun mac(key, bitstring): bitstring.
reduc forall k: key, m: bitstring; checkmac(k, m, mac(k, m)) = okay.
(* Diffie-Hellman *)
(* DH -> Public^Private *)
fun dh(pkey, skey): key.
equation forall a: skey, b: skey; dh(pk(a), b) = dh(pk(b), a). (* symmetry of DH *)
(* KEM encapsulation *)
type kempriv.
type kempub.
fun kempk(kempriv):kempub.
fun penc(kempub, bitstring):bitstring.
(* fun pdec(kempriv,bitstring):bitstring. *)
reduc forall sk: kempriv, m:bitstring; pdec(sk, penc(kempk(sk), m)) = m.
letfun kempriv2pub(k:kempriv) = kempk(k).
letfun pqkem_enc(pk:kempub) =
new ss:bitstring;
(penc(pk,ss),ss).
letfun pqkem_dec(sk:kempriv,ct:bitstring) =
pdec(sk,ct).
fun qb(kempub): bitstring [data].
(* the concats *)
fun concat1(bitstring, pkey, bitstring): bitstring [data].
fun concat2(key, key, key, key, bitstring): bitstring [data].
fun concat3(key, key): key [data].
fun concat4(bitstring, pkey): bitstring [data].
fun khash(key): key.
fun hkdf1(bitstring): key [data].
fun hkdf2_dev1(key): key [data].
fun hkdf2_dev2(key): key [data].
letfun hkdf2(k: key) =
(hkdf2_dev1(k), hkdf2_dev2(k)).
(* event declarations *)
event sendOE1(pkey, key, pkey).
event recvOE1(pkey, key, pkey).
event sendOE2(pkey, key, pkey).
event recvOE2(pkey, key, pkey).
event sendME1(bitstring, key).
event recvME1(bitstring, key).
event sendME2(bitstring, key).
event recvME2(bitstring, key).
event compromiseSKA(skey).
event compromiseSKB(skey).
event breakDH(key, key, key, key).
event start().
let PeerA(SK_A: skey, PK_A: pkey, PK_B: pkey, FSK_A: skey, FPK_A: pkey) =
new ae1: skey;
new ae2: skey;
let gae1 = pk(ae1) in
let gae2 = pk(ae2) in
(* generate amaster and enc msg (PHASE 1) *)
phase 1;
in(c, (gbssig: bitstring, gbs: pkey, gbo: pkey, gpqbo: kempub, gpqbsig: bitstring));
if checksign(PK_B, rb(gbs), gbssig) = okay then
if checksign(PK_B, qb(gpqbo), gpqbsig) = okay then
let (ct: bitstring, ss: bitstring) = pqkem_enc(gpqbo) in
let amaster = hkdf1(concat2(dh(gbs, SK_A), dh(PK_B, ae1), dh(gbs, ae1), dh(gbo, ae1), ss)) in
let (ra1: key, ca1: key) = hkdf2(amaster) in (* derive the root and chain key *)
let ak1 = khash(ca1) in
let (ak1_auth: key, ak1_enc: key) = hkdf2(ak1) in
new mra0: key; (* mra0 := fan out layer ratchet *)
let x1 = senc(ak1_enc, (FPK_A, mra0)) in
let x1_mac = mac(ak1_auth, concat1(x1, gae1, ct)) in
event sendOE1(FPK_A, mra0, gae1);
out(c, (x1, x1_mac, gae1, ct));
(* =================== PHASE 3: SENDER KEYS SESSION B -> A =================== *)
in(c, (x2: bitstring, x2_mac: bitstring, gtb2: pkey));
let (ca2: key, ra2: key) = hkdf2(concat3(khash(ra1), dh(gtb2, ae1))) in
let ak2 = khash(ca2) in
let (ak2_auth: key, ak2_enc: key) = hkdf2(ak2) in
if checkmac(ak2_auth, concat4(x2, gtb2), x2_mac) = okay then
let (FPK_B: pkey, mrb0: key) = sdec(ak2_enc, x2) in
event recvOE2(FPK_B, mrb0, gae2);
(* =================== PHASE 4: SENDER KEYS MSG A -> B =================== *)
let mra1 = khash(mra0) in
let (mak1_auth: key, mak1_enc: key) = hkdf2(mra1) in
let mx1 = senc(mak1_enc, m1) in
let mx1_sig = sign(FSK_A, mx1) in
event sendME1(m1, mra1);
out(c, (mx1, mx1_sig));
(* =================== PHASE 5: SENDER KEYS MSG B -> A =================== *)
in(c, (mx2: bitstring, mx2_sig: bitstring));
let mrb1 = khash(mrb0) in
let (mbk1_auth: key, mbk1_enc: key) = hkdf2(mrb1) in
if checksign(FPK_B, mx2, mx2_sig) = okay then
let m2 = sdec(mbk1_enc, mx2) in
event recvME2(m2, mrb1);
(* =================== PHASE 6: Compromise =================== *)
phase 5;
event compromiseSKA(SK_A);
out(a, SK_A);
phase 6;
event breakDH(dh(gbs, SK_A), dh(PK_B, ae1), dh(gbs, ae1), dh(gbo, ae1));
out(a, (dh(gbs, SK_A), dh(PK_B, ae1), dh(gbs, ae1), dh(gbo, ae1)));
0.
let PeerB(SK_B: skey, PK_B: pkey, PK_A: pkey, FSK_B: skey, FPK_B: pkey) =
new bo: skey;
new bs: skey;
new pqbo: kempriv;
let gbs = pk(bs) in
let gbo = pk(bo) in
let gpqbo = kempk(pqbo) in
let gbssig = sign(SK_B, rb(gbs)) in
let gpqbsig = sign(SK_B, qb(gpqbo)) in
out(c, (gbssig, gbs, gbo, gpqbo, gpqbsig));
phase 1; (* peer B commits first *)
(* =================== PHASE 2: DERIVE P2P MASTER + SENDER KEYS SESSION A -> B =================== *)
in(c, (x1: bitstring, x1_mac: bitstring, gae1: pkey, ct: bitstring));
let ss = pqkem_dec(pqbo, ct) in
let bmaster = hkdf1(concat2(dh(PK_A, bs), dh(gae1, SK_B), dh(gae1, bs), dh(gae1, bo), ss)) in
let (rb1: key, cb1: key) = hkdf2(bmaster) in (* derive the root and chain key *)
let bk1 = khash(cb1) in
let (bk1_auth: key, bk1_enc: key) = hkdf2(bk1) in
if checkmac(bk1_auth, concat1(x1, gae1, ct), x1_mac) = okay then
let (mra0: key, FPK_A: pkey) = sdec(bk1_enc, x1) in
event recvOE1(FPK_A, mra0, gae1);
(* =================== PHASE 3: SENDER KEYS SESH B -> A =================== *)
new tb2: skey;
let gtb2 = pk(tb2) in
let (rb2: key, cb2: key) = hkdf2(concat3(khash(rb1), dh(gae1, tb2))) in
let bk2 = khash(cb2) in
let (bk2_auth: key, bk2_enc: key) = hkdf2(bk2) in
new mrb0: key; (* mrb0 := sender keys ratchet *)
let x2 = senc(bk2_enc, (FPK_B, mrb0)) in
let x2_mac = mac(bk2_auth, concat4(x2, gtb2)) in
event sendOE2(FPK_B, mrb0, gae1);
out(c, (x2, x2_mac, gtb2));
(* =================== PHASE 4: SENDER KEYS MSG A -> B =================== *)
in(c, (mx1: bitstring, mx1_sig: bitstring));
let mra1 = khash(mra0) in
let (mak1_auth: key, mak1_enc: key) = hkdf2(mra1) in
if checksign(FPK_A, mx1, mx1_sig) = okay then
let m1 = sdec(mak1_enc, mx1) in
event recvME1(m1, mra1);
(* =================== PHASE 5: SENDER KEYS MSG B -> A =================== *)
let mrb1 = khash(mrb0) in
let (mbk1_auth: key, mbk1_enc: key) = hkdf2(mrb1) in
let mx2 = senc(mbk1_enc, m2) in
let mx2_sig = sign(FSK_B, mx2) in
event sendME2(m2, mrb1);
out(c, (mx2, mx2_sig));
(* =================== PHASE 6: Compromise =================== *)
phase 5;
event compromiseSKB(SK_B);
out(a, SK_B);
0.
query event(start()).
(* forward secrecy *)
query sk1: skey, sk2: skey; (event(compromiseSKA(sk1)) && event(compromiseSKB(sk2)) && attacker(m1)) ==> false.
(* secrecy *)
query attacker(m1).
query attacker(m2).
(* forward pq secrecy *)
query k1: key, k2: key, k3: key, k4: key; (event(breakDH(k1, k2, k3, k4)) && attacker(m1)) ==> false.
(* auth *)
query k1: pkey, rk: key, k2: pkey; inj-event(recvOE1(k1, rk, k2)) ==> inj-event(sendOE1(k1, rk, k2)).
query k1: pkey, rk: key, k2: pkey; inj-event(recvOE2(k1, rk, k2)) ==> inj-event(sendOE2(k1, rk, k2)).
query m: bitstring, rk: key; inj-event(recvME1(m, rk)) ==> inj-event(sendME1(m, rk)).
query m: bitstring, rk: key; inj-event(recvME2(m, rk)) ==> inj-event(sendME2(m, rk)).
(* reachability *)
query k1: pkey, rk: key, k2: pkey; event(sendOE1(k1,rk,k2)).
query k1: pkey, rk: key, k2: pkey; event(recvOE1(k1,rk,k2)).
query k1: pkey, rk: key, k2: pkey; event(sendOE2(k1,rk,k2)).
query k1: pkey, rk: key, k2: pkey; event(recvOE2(k1,rk,k2)).
query m: bitstring, rk: key; event(sendME1(m, rk)).
query m: bitstring, rk: key; event(recvME1(m, rk)).
query m: bitstring, rk: key; event(sendME2(m, rk)).
query m: bitstring, rk: key; event(recvME2(m, rk)).
process
new SK_A: skey; let PK_A = pk(SK_A) in
new SK_B: skey; let PK_B = pk(SK_B) in
new FSK_A: skey; let FPK_A = pk(FSK_A) in
new FSK_B: skey; let FPK_B = pk(FSK_B) in
out(a, PK_A);
out(a, PK_B);
(*
out(a, FPK_A);
out(a, FPK_B);
*)
event start();
( (PeerA(SK_A, PK_A, PK_B, FSK_A, FPK_A)) |
(PeerB(SK_B, PK_B, PK_A, FSK_B, FPK_B)))