This commit is contained in:
2025-08-27 02:13:43 -04:00
commit 333cb4a69d
31 changed files with 4934 additions and 0 deletions

1
.direnv/flake-profile Symbolic link
View File

@@ -0,0 +1 @@
flake-profile-1-link

View File

@@ -0,0 +1 @@
/nix/store/5rjiqs4b1fqs5nzwwyfwi0dvk4z6pzbd-nix-shell-env

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use flake

35
README.md Normal file
View File

@@ -0,0 +1,35 @@
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)
# Environment Setupmegolm-initiator-deny.pv
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`.
# Model Descriptions
- `3dh.pv`: a complete model of the Triple Diffie-Hellman handshake. Proving authentication and secrecy.
- `x3dh.pv`: a complete model of the Extended Triple Diffie-Hellman handshake. Proving authentication and secrecy.
- `olm.pv`: a complete model of the Olm protocol, as described [here](https://gitlab.matrix.org/matrix-org/olm/blob/master/docs/olm.md). Informally, this is 3DH composed with double ratchet. Secrecy, authentication, reachability, PCS, and PFS are formalized.
- `olm-unsigned.pv`: the same model as `olm.pv`, but pre-keys are unsigned. Demonstrates compromise of the pre-key messages.
- `signal.pv`: a complete model of the Signal protocol. Informally, this is X3DH composed with double ratchet. Secrecy, authentication, reachability, PCS, and PFS are formalized.
- `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.
The `deniability` folder includes both initiator deniability and responder undeniability results for 3dh, x3dh, olm, signal, and megolm.
# Notes on Proverif reachability
In each model, there exists several queries designed to ensure reachability and no deadlocks. First,
each model has an event "start" that is positioned *before* the PeerA/PeerB processes begin. We simply check `query event(start())`, which asks ProVerif if it is the case `NOT event(start)`. This query will always return *false* because in every possible execution of the protocol, `event start()` is triggered. Thus, broadly, testing reachability involves ensuring `query event(event-name())` always resolves to false.
This flavor of query is present in each model and highly necessary, as it ensures other properties are never trivially violated or trivially satisfied.
# Notes on optimizations
A common complaint among ProVerif users is optimizing ProVerif code is hard and un-intuitive.
The manual isn't very helpful here. Thus, we list out the optimizations and tricks we employ:
- Phases. In ProVerif, phases are *synchronization facilities* that ensure every peer must stop *together* at phase n before proceeding to phase n+1. Placing phases right not only allows us to capture perfect forward secrecy and post-compromise security, but allows us to massively cut down on unnecessary intertwinings of operations between peers.
- Channels. Across models we create three channels: `c`, the channel peers use to communicate; `p`, a private channel to ensure the distribution of public keys with integrity and authenticity; `a`, a channel dedicated to publishing material to the attacker.
- Correct checksign and checkmac reduction rules. One fatal flaw in reduction rule design present in the principal ProVerif examples available on the [main ProVerif website](https://bblanche.gitlabpages.inria.fr/proverif/) is that a term reduction of a function *reduces* to an argument of that function. An example is `checksign(sign(m,k), pk(k)) = m` (ripped directly from the ProVerif examples). The critical problem here is that, chained with other rewrite rules (i.e. concat(m1,m2) -> m), this can lead to non-termination and many headaches. So, the correct reduction strategy for checksign and checkmac is `checksign(pk(sk), m, sign(sk, m)):bool` where `bool` is *strictly* not a term in the input of the function. We follow in the lead of [Deepsec](https://sapic-plus.github.io/) and [A Tale of Two Worlds, a Formal Story of WireGuard Hybridization](https://eprint.iacr.org/2025/1179) and design our reduction rules for checksign and checkmac like `checksign(pk(sk), m, sign(sk, m)) = okay`.
- Event orderings. Events denoting sending a message *always* are activated after the message is sent onto the channel.
- Minimizing rules. We do our absolute best to minimize the number of functions, reduction rules, and equations defined. Our definitions of key derivation and concatonation, and the way we design our models around these functions, are minimized to the best degree possible. Furthermore, the [data] tag is instantiated for each concatonation function, indicating reversibility to ProVerif.
- Strong reduction rules. We generally define Diffie-Hellman as an equational rule, a la `equation forall a: skey, b: skey; dh(pk(a), b) = dh(pk(b), a)`. However, ProVerif hates this, and may cause non-termination. In such cases, we provide more general equational rules that relate the Diffie-Hellman equational operation with other operations in the associated protocol.
- Tagging. Although we do not currently implement tagging in our models, one trick that we employed to speed up analysis for certain test scenarios involves "tagging" each message sent on a channel with a "tag" bitstring marked as private (i.e. `free tag_oe1: bitstring [private]`). This way, two similarly structured messages on the same channel are not explored in parallel, and only the correct message is processed. Indeed, there some [theoretical work](https://doi.org/10.1007/3-540-36576-1_9) proving that tagged protocols always terminate. See section 6.7.2 in the ProVerif manual for more details.
- Options. We tuned the options ProVerif provides in order to ensure each model terminates in a reasonable amount of time. We tuned the selection function primarily. One thing to note is that options never sacrifice completeness. More information on ProVerif settings can be found in the manual, section 6.6.2.

61
flake.lock generated Normal file
View File

@@ -0,0 +1,61 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1756125398,
"narHash": "sha256-XexyKZpf46cMiO5Vbj+dWSAXOnr285GHsMch8FBoHbc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "3b9f00d7a7bf68acd4c4abb9d43695afb04e03a5",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

31
flake.nix Normal file
View File

@@ -0,0 +1,31 @@
{
description = "a flake indeed";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils, ... } @ inputs:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
in {
packages.default = pkgs.stdenv.mkDerivation {
pname = "nested ratchet fm";
src = ./.;
};
devShells.default = pkgs.mkShellNoCC rec {
buildInputs = with pkgs; [
proverif
verifpal
];
shellHook = ''
echo "Switching to the nested ratchet formal methods dev environment ... :D"
'';
};
}
);
}

153
proverif/3dh.pv Normal file
View File

@@ -0,0 +1,153 @@
(*
3DH; proving authenticity and secrecy
Author: [redacted]
model assumption #1: same key is used for signing and encryption (i.e. X25519)
*)
free m1: bitstring [private].
set simpEqAll = false.
set selFun = Nounifset.
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.
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(key, key, key): key [data].
fun khash(key): key.
fun hkdf2_dev1(key): key.
fun hkdf2_dev2(key): key.
letfun hkdf2(k: key) =
(hkdf2_dev1(k), hkdf2_dev2(k)).
(* the concats *)
fun concat1(bitstring, pkey): bitstring [data].
(* events *)
event sendE1(bitstring, key, pkey).
event recvE1(bitstring, key, pkey).
event compromiseSKA(skey).
event compromiseSKB(skey).
event start().
let PeerA(SK_A: skey, PK_A: pkey, PK_B: pkey) =
phase 1;
new ao: skey;
let gao = pk(ao) in
(* generate amaster and enc msg (PHASE 1) *)
(* in(c, gbo: pkey); *)
in(c, (gbo: pkey, gbo_sig: bitstring));
if checksign(PK_B, rb(gbo), gbo_sig) = okay then (
let amaster = hkdf1(dh(PK_B, SK_A), dh(gbo, SK_A), dh(PK_B, ao)) 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, gao)) in
event sendE1(m1, mak1, gao);
phase 2;
out(c, (x1, x1_mac, gao))
).
let PeerB(SK_B: skey, PK_B: pkey, PK_A: pkey) =
new bo: skey;
let gbo = pk(bo) in
let gbo_sig = sign(SK_B, rb(gbo)) in
out(c, (gbo, gbo_sig));
phase 1;
(* first stage: derive bmaster, verfiy a's msgs, decrypt prekey message, reply *)
in(c, (x1: bitstring, x1_mac: bitstring, gao: pkey));
let bmaster = hkdf1(dh(PK_A, SK_B), dh(PK_A, bo), dh(gao, SK_B)) 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, gao), x1_mac) = okay then
(
let m1 = sdec(mbk1_enc, x1) in
event recvE1(m1, mbk1, gao);
phase 2;
event compromiseSKB(SK_B);
out(c, SK_B)
).
query event(start()). (* reachable from all possible executions *)
(* auth *)
query m: bitstring, rk: key, k1: pkey; inj-event(recvE1(m, rk, k1)) ==> inj-event(sendE1(m, rk, k1)).
(* secrecy *)
query attacker(m1).
(* reachability *)
query m: bitstring, rk: key, k1: pkey; event(recvE1(m, rk, k1)). (* reachable from all executions *)
query m: bitstring, rk: key, k1: pkey; event(sendE1(m, rk, k1)). (* 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,150 @@
(*
3DH INITIATOR DENY
Author: [redacted]
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 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.
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(key, key, key): key [data].
fun khash(key): key.
fun hkdf2_dev1(key): key.
fun hkdf2_dev2(key): key.
letfun hkdf2(k: key) =
(hkdf2_dev1(k), hkdf2_dev2(k)).
(* the concats *)
fun concat1(bitstring, pkey): bitstring [data].
(* events *)
event sendE1(bitstring, key, pkey).
event recvE1(bitstring, key, pkey).
event compromiseSKA(skey).
event compromiseSKB(skey).
event start().
let PeerA(SK_A: skey, PK_A: pkey, PK_B: pkey) =
phase 1;
new ao: skey;
let gao = pk(ao) in
(* generate amaster and enc msg (PHASE 1) *)
(* in(c, gbo: pkey); *)
in(c, (gbo: pkey, gbo_sig: bitstring));
if checksign(PK_B, rb(gbo), gbo_sig) = okay then (
let amaster = hkdf1(dh(PK_B, SK_A), dh(gbo, SK_A), dh(PK_B, ao)) 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, gao)) in
event sendE1(m1, mak1, gao);
phase 2;
out(c, (x1, x1_mac, gao))
).
let PeerB(SK_B: skey, PK_B: pkey, PK_A: pkey) =
new bo: skey;
let gbo = pk(bo) in
let gbo_sig = sign(SK_B, rb(gbo)) in
out(c, (gbo, gbo_sig));
phase 1;
(* first stage: derive bmaster, verfiy a's msgs, decrypt prekey message, reply *)
in(c, (x1: bitstring, x1_mac: bitstring, gao: pkey));
let bmaster = hkdf1(dh(PK_A, SK_B), dh(PK_A, bo), dh(gao, SK_B)) 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, gao), x1_mac) = okay then
(
let m1 = sdec(mbk1_enc, x1) in
event recvE1(m1, mbk1, gao);
phase 2;
event compromiseSKB(SK_B);
out(c, SK_B)
).
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 *)

View File

@@ -0,0 +1,167 @@
(*
3DH MUTUAL DENIABILITY
Author: [redacted]
model assumption #1: same key is used for signing and encryption (i.e. X25519)
*)
free m1: bitstring [private].
set selFun = Nounifset.
set simplifyProcess = 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.
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(key, key, key): key [data].
fun khash(key): key.
fun hkdf2_dev1(key): key.
fun hkdf2_dev2(key): key.
letfun hkdf2(k: key) =
(hkdf2_dev1(k), hkdf2_dev2(k)).
(* the concats *)
fun concat1(bitstring, pkey): bitstring [data].
(* events *)
event sendE1(bitstring, key, pkey).
event recvE1(bitstring, key, pkey).
event compromiseSKA(skey).
event compromiseSKB(skey).
event start().
let PeerA(SK_A: skey, PK_A: pkey, PK_B: pkey) =
phase 1;
new ao: skey;
let gao = pk(ao) in
(* generate amaster and enc msg (PHASE 1) *)
(* in(c, gbo: pkey); *)
in(c, (gbo: pkey, gbo_sig: bitstring));
if checksign(PK_B, rb(gbo), gbo_sig) = okay then (
let amaster = hkdf1(dh(PK_B, SK_A), dh(gbo, SK_A), dh(PK_B, ao)) 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, gao)) in
event sendE1(m1, mak1, gao);
phase 2;
out(c, (x1, x1_mac, gao))
).
let PeerB(SK_B: skey, PK_B: pkey, PK_A: pkey) =
new bo: skey;
let gbo = pk(bo) in
let gbo_sig = sign(SK_B, rb(gbo)) in
out(c, (gbo, gbo_sig));
phase 1;
(* first stage: derive bmaster, verfiy a's msgs, decrypt prekey message, reply *)
in(c, (x1: bitstring, x1_mac: bitstring, gao: pkey));
let bmaster = hkdf1(dh(PK_A, SK_B), dh(PK_A, bo), dh(gao, SK_B)) 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, gao), x1_mac) = okay then
(
let m1 = sdec(mbk1_enc, x1) in
event recvE1(m1, mbk1, gao);
phase 2;
event compromiseSKB(SK_B);
out(c, SK_B)
).
(*
query event(start()). (* reachable from all possible executions *)
(* auth *)
query m: bitstring, rk: key, k1: pkey; inj-event(recvE1(m, rk, k1)) ==> inj-event(sendE1(m, rk, k1)).
(* secrecy *)
query attacker(m1).
(* reachability *)
query m: bitstring, rk: key, k1: pkey; event(recvE1(m, rk, k1)). (* reachable from all executions *)
query m: bitstring, rk: key, k1: pkey; event(sendE1(m, rk, k1)). (* 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);
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))

View File

@@ -0,0 +1,135 @@
(*
Showing the compromise of a fan-out session signing key results in a loss of deniability,
in isolation from the peer-to-peer layer
*)
set simplifyProcess = false.
set preciseActions = true.
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.
(* 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(key, key, key): key [data].
fun khash(key): key.
fun hkdf2_dev1(key): key.
fun hkdf2_dev2(key): key.
letfun hkdf2(k: key) =
(hkdf2_dev1(k), hkdf2_dev2(k)).
(* the concats *)
fun concat1(pkey, key): bitstring [data].
fun concat2(bitstring, bitstring): bitstring [data].
(* events *)
event sendE1(bitstring, key, pkey).
event recvE1(bitstring, key, pkey).
event compromiseSKA(skey).
event compromiseSKB(skey).
event start().
free k: key [private].
free m1: bitstring [private].
let PeerA() =
new SK_A: skey; let PK_A = pk(SK_A) in
new rk: key;
new fib1: skey;
let sk_A = choice [fib1, SK_A] in
let x1 = senc(k, (pk(sk_A), rk)) in
let x1_mac = mac(k, x1) in
out(c, (x1, x1_mac));
phase 1;
let mx1 = senc(rk, m1) in
let mx1_mac = mac(rk, mx1) in
let mx1_sig = sign(sk_A, concat2(mx1, mx1_mac)) in
out(c, (mx1, mx1_mac, mx1_sig));
phase 2;
phase 3;
out(a, PK_A);
0.
let PeerB() =
new SK_B: skey; let PK_B = pk(SK_B) in
phase 1;
in(c, (x1: bitstring, x1_mac: bitstring));
if checkmac(k, x1, x1_mac) = okay then
let (PK_A: pkey, rk: key) = sdec(k, x1) in
phase 2;
in(c, (mx1: bitstring, mx1_mac: bitstring, mx1_sig: bitstring));
(* in(c, (mx1: bitstring, mx1_mac: bitstring)); *)
if checksign(PK_A, concat2(mx1, mx1_mac), mx1_sig) = okay then
if checkmac(rk, mx1, mx1_mac) = okay then
let m1 = sdec(rk, mx1) in
event start();
phase 3;
0.
(* query event(start()). (* reachable from all possible executions *) *)
process
(*
out(a, PK_A);
out(a, PK_B);
*)
new fib1: skey;
(* let (kM_A: skey) = choice [fib1, SK_A] in *)
(* let kM_A = (SK_A) in *)
( (!PeerA()) |
(!PeerB()))

View File

@@ -0,0 +1,232 @@
(*
Olm + Megolm initiator deniability
Author: [redacted]
*)
free m1: bitstring [private].
free m2: bitstring [private].
set attacker = passive.
set simpEqAll = false.
set selFun = Nounifset.
set redundancyElim = best.
set redundantHypElim = true.
set simplifyProcess = false.
set stopTerm = false.
(*
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): bitstring.
equation forall a: skey, b: skey; dh(pk(a), b) = dh(pk(b), a). (* symmetry of DH *)
(* the concat functions *)
fun khash(key): key.
fun hkdf(bitstring): key [data].
fun concat0(key): bitstring [data, typeConverter].
fun concat1(bitstring, pkey, pkey): bitstring [data].
reduc forall m1: bitstring, k1: pkey, k2: pkey; split1(concat1(m1, k1, k2)) = (m1, k1, k2).
fun concat2(bitstring, pkey): bitstring [data].
reduc forall m1: bitstring, k1: pkey; split2(concat2(m1, k1)) = (m1, k1).
fun concat3(bitstring, bitstring): bitstring [data].
reduc forall m1: bitstring, m2: bitstring; split3(concat3(m1, m2)) = (m1, m2).
fun concat4(bitstring, bitstring, bitstring): bitstring [data].
reduc forall m1: bitstring, m2: bitstring, m3:bitstring; split4(concat4(m1, m2, m3)) = (m1, m2, m3).
equation forall k: key, a: skey, b: skey; hkdf(concat3(concat0(khash(k)), dh(pk(a), b))) = hkdf(concat3(concat0(khash(k)), dh(pk(b), a))).
(* the concats *)
(* events *)
event sendOE1(pkey, key, pkey, pkey).
event recvOE1(pkey, key, pkey, pkey).
event sendOE2(pkey, key, pkey, pkey).
event recvOE2(pkey, key, pkey, pkey).
event sendME1(bitstring, key, pkey).
event recvME1(bitstring, key, pkey).
event sendME2(bitstring, key, pkey).
event recvME2(bitstring, key, pkey).
event compromiseOSKA(skey).
event compromiseOSKB(skey).
event start().
free tag_oe1: bitstring [private].
free tag_oe2: bitstring [private].
free tag_me1: bitstring [private].
free tag_me2: bitstring [private].
free tag_b_eph: bitstring [private].
let PeerA(OSK_A: skey, OPK_A: pkey, OPK_B: pkey, OLM_KEYS_STR:bitstring, OLM_ROOT_STR: bitstring, MSK_A: skey, MPK_A: pkey) =
phase 1;
new ao: skey;
let gao = pk(ao) in
(* generate amaster and enc msg (PHASE 1) *)
(* in(c, gbo: pkey); *)
in(c, (gbo: pkey, gbo_sig: bitstring));
if checksign(OPK_B, rb(gbo), gbo_sig) = okay then (
(* =================== PHASE 2: DERIVE OLM MASTER + MEGOLM SESSION A -> B =================== *)
let amaster = hkdf(concat4(dh(OPK_B, OSK_A), dh(gbo, OSK_A), dh(OPK_B, ao))) in
let ra1 = hkdf(concat0(amaster)) in
let ca1 = hkdf(concat0(amaster)) in
new ta1: skey;
let gta1 = pk(ta1) in
let ak1 = khash(ca1) in
let ak1_auth = hkdf(concat0(ak1)) in
let ak1_enc = hkdf(concat0(ak1)) in
new mra0: key; (* mra0 := megolm ratchet *)
let x1 = senc(ak1_enc, (MPK_A, mra0)) in
let x1_mac = mac(ak1_auth, concat1(x1, gao, gta1)) in
event sendOE1(MPK_A, mra0, gao, gta1);
out(c, (x1, x1_mac, gao, gta1));
(* =================== PHASE 4: MEGOLM MSG A -> B =================== *)
let mra1 = khash(mra0) in
let mak1_auth = hkdf(concat0(mra1)) in
let mak1_enc = hkdf(concat0(mra1)) in
let mx1 = senc(mak1_enc, m1) in
let mx1_mac = mac(mak1_auth, mx1) in
let mx1_sig = sign(MSK_A, concat3(mx1, mx1_mac)) in
event sendME1(m1, mra1, MPK_A);
out(c, (mx1, mx1_mac, mx1_sig));
phase 3 (* =================== PHASE 6: PCS =================== *)
).
let PeerB(OSK_B: skey, OPK_B: pkey, OPK_A: pkey, OLM_KEYS_STR:bitstring, OLM_ROOT_STR: bitstring, MSK_B: skey, MPK_B: pkey) =
new bo: skey;
let gbo = pk(bo) in
let gbo_sig = sign(OSK_B, rb(gbo)) in
out(c, (gbo, gbo_sig));
phase 1;
(* =================== PHASE 2: DERIVE OLM MASTER + MEGOLM SESSION A -> B =================== *)
(* first stage: derive bmaster, verfiy a's msgs, decrypt prekey message, reply *)
in(c, (x1: bitstring, x1_mac: bitstring, gao: pkey, gta1: pkey));
let bmaster = hkdf(concat4(dh(OPK_A, OSK_B), dh(OPK_A, bo), dh(gao, OSK_B))) in
let rb1 = hkdf(concat0(bmaster)) in
let cb1 = hkdf(concat0(bmaster)) in
let bk1 = khash(cb1) in
let bk1_auth = hkdf(concat0(bk1)) in
let bk1_enc = hkdf(concat0(bk1)) in
if checkmac(bk1_auth, concat1(x1, gao, gta1), x1_mac) = okay then
(
let (MPK_A: pkey, mra0: key) = sdec(bk1_enc, x1) in
event recvOE1(MPK_A, mra0, gao, gta1);
(* =================== PHASE 4: MEGOLM MSG A -> B =================== *)
in(c, (mx1: bitstring, mx1_mac: bitstring, mx1_sig: bitstring));
let mra1 = khash(mra0) in
let mak1_auth = hkdf(concat0(mra1)) in
let mak1_enc = hkdf(concat0(mra1)) in
if checksign(MPK_A, concat3(mx1, mx1_mac), mx1_sig) = okay then (
if checkmac(mak1_auth, mx1, mx1_mac) = okay then (
let m1 = sdec(mak1_enc, mx1) in
event recvME1(m1, mra1, MPK_A);
(* =================== PHASE 6: PCS =================== *)
phase 3;
event compromiseOSKB(OSK_B);
out(c, OSK_B)
))).
process
new OLM_KEYS_STR:bitstring; out(a, OLM_KEYS_STR);
new OLM_ROOT_STR:bitstring; out(a, OLM_ROOT_STR);
new OSK_A: skey; let OPK_A = pk(OSK_A) in
new OSK_B: skey; let OPK_B = pk(OSK_B) in
new MSK_A: skey; let MPK_A = pk(MSK_A) in
new MSK_B: skey; let MPK_B = pk(MSK_B) in
out(a, OPK_A);
out(a, OPK_B);
(*
out(a, MPK_A);
out(a, MPK_B);
*)
new fib1: skey;
new fib2: skey;
new fib3: skey;
new fib4: skey;
let (kO_A: skey, kM_A: skey) = choice [(OSK_A, MSK_A), (fib1, fib2) ] in
let (kO_B: skey, kM_B: skey) = choice [(OSK_B, MSK_B), (fib1, fib2) ] in
( (!PeerA(kO_A, pk(kO_A), OPK_B, OLM_KEYS_STR, OLM_ROOT_STR, kM_A, pk(kM_A))) |
(!PeerB(OSK_B, OPK_B, OPK_A, OLM_KEYS_STR, OLM_ROOT_STR, MSK_B, MPK_B)))

View File

@@ -0,0 +1,232 @@
(*
Olm + Megolm responder deniability
Author: [redacted]
*)
free m1: bitstring [private].
free m2: bitstring [private].
set attacker = active.
set simpEqAll = false.
set selFun = Nounifset.
set redundancyElim = best.
set redundantHypElim = true.
set simplifyProcess = false.
set stopTerm = false.
(*
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): bitstring.
equation forall a: skey, b: skey; dh(pk(a), b) = dh(pk(b), a). (* symmetry of DH *)
(* the concat functions *)
fun khash(key): key.
fun hkdf(bitstring): key [data].
fun concat0(key): bitstring [data, typeConverter].
fun concat1(bitstring, pkey, pkey): bitstring [data].
reduc forall m1: bitstring, k1: pkey, k2: pkey; split1(concat1(m1, k1, k2)) = (m1, k1, k2).
fun concat2(bitstring, pkey): bitstring [data].
reduc forall m1: bitstring, k1: pkey; split2(concat2(m1, k1)) = (m1, k1).
fun concat3(bitstring, bitstring): bitstring [data].
reduc forall m1: bitstring, m2: bitstring; split3(concat3(m1, m2)) = (m1, m2).
fun concat4(bitstring, bitstring, bitstring): bitstring [data].
reduc forall m1: bitstring, m2: bitstring, m3:bitstring; split4(concat4(m1, m2, m3)) = (m1, m2, m3).
equation forall k: key, a: skey, b: skey; hkdf(concat3(concat0(khash(k)), dh(pk(a), b))) = hkdf(concat3(concat0(khash(k)), dh(pk(b), a))).
(* the concats *)
(* events *)
event sendOE1(pkey, key, pkey, pkey).
event recvOE1(pkey, key, pkey, pkey).
event sendOE2(pkey, key, pkey, pkey).
event recvOE2(pkey, key, pkey, pkey).
event sendME1(bitstring, key, pkey).
event recvME1(bitstring, key, pkey).
event sendME2(bitstring, key, pkey).
event recvME2(bitstring, key, pkey).
event compromiseOSKA(skey).
event compromiseOSKB(skey).
event start().
free tag_oe1: bitstring [private].
free tag_oe2: bitstring [private].
free tag_me1: bitstring [private].
free tag_me2: bitstring [private].
free tag_b_eph: bitstring [private].
let PeerA(OSK_A: skey, OPK_A: pkey, OPK_B: pkey, OLM_KEYS_STR:bitstring, OLM_ROOT_STR: bitstring, MSK_A: skey, MPK_A: pkey) =
phase 1;
new ao: skey;
let gao = pk(ao) in
(* generate amaster and enc msg (PHASE 1) *)
(* in(c, gbo: pkey); *)
in(c, (gbo: pkey, gbo_sig: bitstring));
if checksign(OPK_B, rb(gbo), gbo_sig) = okay then (
(* =================== PHASE 2: DERIVE OLM MASTER + MEGOLM SESSION A -> B =================== *)
let amaster = hkdf(concat4(dh(OPK_B, OSK_A), dh(gbo, OSK_A), dh(OPK_B, ao))) in
let ra1 = hkdf(concat0(amaster)) in
let ca1 = hkdf(concat0(amaster)) in
new ta1: skey;
let gta1 = pk(ta1) in
let ak1 = khash(ca1) in
let ak1_auth = hkdf(concat0(ak1)) in
let ak1_enc = hkdf(concat0(ak1)) in
new mra0: key; (* mra0 := megolm ratchet *)
let x1 = senc(ak1_enc, (MPK_A, mra0)) in
let x1_mac = mac(ak1_auth, concat1(x1, gao, gta1)) in
event sendOE1(MPK_A, mra0, gao, gta1);
out(c, (x1, x1_mac, gao, gta1));
(* =================== PHASE 4: MEGOLM MSG A -> B =================== *)
let mra1 = khash(mra0) in
let mak1_auth = hkdf(concat0(mra1)) in
let mak1_enc = hkdf(concat0(mra1)) in
let mx1 = senc(mak1_enc, m1) in
let mx1_mac = mac(mak1_auth, mx1) in
let mx1_sig = sign(MSK_A, concat3(mx1, mx1_mac)) in
event sendME1(m1, mra1, MPK_A);
out(c, (mx1, mx1_mac, mx1_sig));
phase 3 (* =================== PHASE 6: PCS =================== *)
).
let PeerB(OSK_B: skey, OPK_B: pkey, OPK_A: pkey, OLM_KEYS_STR:bitstring, OLM_ROOT_STR: bitstring, MSK_B: skey, MPK_B: pkey) =
new bo: skey;
let gbo = pk(bo) in
let gbo_sig = sign(OSK_B, rb(gbo)) in
out(c, (gbo, gbo_sig));
phase 1;
(* =================== PHASE 2: DERIVE OLM MASTER + MEGOLM SESSION A -> B =================== *)
(* first stage: derive bmaster, verfiy a's msgs, decrypt prekey message, reply *)
in(c, (x1: bitstring, x1_mac: bitstring, gao: pkey, gta1: pkey));
let bmaster = hkdf(concat4(dh(OPK_A, OSK_B), dh(OPK_A, bo), dh(gao, OSK_B))) in
let rb1 = hkdf(concat0(bmaster)) in
let cb1 = hkdf(concat0(bmaster)) in
let bk1 = khash(cb1) in
let bk1_auth = hkdf(concat0(bk1)) in
let bk1_enc = hkdf(concat0(bk1)) in
if checkmac(bk1_auth, concat1(x1, gao, gta1), x1_mac) = okay then
(
let (MPK_A: pkey, mra0: key) = sdec(bk1_enc, x1) in
event recvOE1(MPK_A, mra0, gao, gta1);
(* =================== PHASE 4: MEGOLM MSG A -> B =================== *)
in(c, (mx1: bitstring, mx1_mac: bitstring, mx1_sig: bitstring));
let mra1 = khash(mra0) in
let mak1_auth = hkdf(concat0(mra1)) in
let mak1_enc = hkdf(concat0(mra1)) in
if checksign(MPK_A, concat3(mx1, mx1_mac), mx1_sig) = okay then (
if checkmac(mak1_auth, mx1, mx1_mac) = okay then (
let m1 = sdec(mak1_enc, mx1) in
event recvME1(m1, mra1, MPK_A);
(* =================== PHASE 6: PCS =================== *)
phase 3;
event compromiseOSKB(OSK_B);
out(c, OSK_B)
))).
process
new OLM_KEYS_STR:bitstring; out(a, OLM_KEYS_STR);
new OLM_ROOT_STR:bitstring; out(a, OLM_ROOT_STR);
new OSK_A: skey; let OPK_A = pk(OSK_A) in
new OSK_B: skey; let OPK_B = pk(OSK_B) in
new MSK_A: skey; let MPK_A = pk(MSK_A) in
new MSK_B: skey; let MPK_B = pk(MSK_B) in
out(a, OPK_A);
out(a, OPK_B);
(*
out(a, MPK_A);
out(a, MPK_B);
*)
new fib1: skey;
new fib2: skey;
new fib3: skey;
new fib4: skey;
let (kO_A: skey, kM_A: skey) = choice [(OSK_A, MSK_A), (fib1, fib2) ] in
let (kO_B: skey, kM_B: skey) = choice [(OSK_B, MSK_B), (fib1, fib2) ] in
( (!PeerA(OSK_A, OPK_A, OPK_B, OLM_KEYS_STR, OLM_ROOT_STR, MSK_A, MPK_A)) |
(!PeerB(kO_B, pk(kO_B), OPK_A, OLM_KEYS_STR, OLM_ROOT_STR, kM_A, pk(kM_A))))

View File

@@ -0,0 +1,203 @@
(*
Olm 3DH+Double Ratchet; initiator deniability
Author: [redacted]
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 simpEqAll = false.
set selFun = Nounifset.
set redundancyElim = best.
set redundantHypElim = true.
set simplifyProcess = false.
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 *)
(* the concat functions *)
fun hkdf1(key, key, key): key [data].
fun khash(key): key.
fun hkdf2_dev1(key): key.
fun hkdf2_dev2(key): key.
letfun hkdf2(k: key) =
(hkdf2_dev1(k), hkdf2_dev2(k)).
fun hkdf3_dev1(key, bitstring): key.
fun hkdf3_dev2(key, bitstring): key.
letfun hkdf3(k: key, b: bitstring) =
(hkdf3_dev1(k, b), hkdf3_dev2(k, b)).
fun hkdf4_dev1(key, key): key.
fun hkdf4_dev2(key, key): key.
letfun hkdf4(k1: key, k2: key) =
(hkdf4_dev1(k1, k2), hkdf4_dev2(k1, k2)).
(* the concats *)
fun concat1(bitstring, pkey, pkey): bitstring [data].
fun concat2(bitstring, pkey): bitstring [data].
(* events *)
event sendE1(bitstring, key, pkey, pkey).
event recvE1(bitstring, key, pkey, pkey).
event sendE2(bitstring, key, pkey, pkey).
event recvE2(bitstring, key, pkey, pkey).
event compromiseSKA(skey).
event compromiseSKB(skey).
event start().
free tag_oe1: bitstring [private].
free tag_oe2: bitstring [private].
free tag_me1: bitstring [private].
free tag_me2: bitstring [private].
free tag_b_eph: bitstring [private].
let PeerA(SK_A: skey, PK_A: pkey, PK_B: pkey, OLM_KEYS_STR:bitstring, OLM_ROOT_STR: bitstring) =
phase 1;
new ao: skey;
let gao = pk(ao) in
(* generate amaster and enc msg (PHASE 1) *)
(* in(c, gbo: pkey); *)
in(c, (gbo: pkey, gbo_sig: bitstring));
if checksign(PK_B, rb(gbo), gbo_sig) = okay then (
let amaster = hkdf1(dh(PK_B, SK_A), dh(gbo, SK_A), dh(PK_B, ao)) in
let (ra1: key, ca1: key) = hkdf3(amaster, OLM_ROOT_STR) in (* derive the root and chain key *)
new ta1: skey;
let gta1 = pk(ta1) in
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, gao, gta1)) in
event sendE1(m1, mak1, gao, gta1);
out(c, (x1, x1_mac, gao, gta1));
(* second stage: now, decrypt the received message from bob *)
in(c, (x2: bitstring, x2_mac: bitstring, gtb2: pkey));
let (ra2: key, ca2: key) = hkdf4(ra1, dh(gtb2, ta1)) in
let mak2 = khash(ca2) in
let (mak2_auth: key, mak2_enc: key) = hkdf2(mak2) in
if checkmac(mak2_auth, concat2(x2, gtb2), x2_mac) = okay then
(
let m2 = sdec(mak2_enc, x2) in
event recvE2(m2, mak2, gta1, gtb2);
phase 2;
event compromiseSKA(SK_A);
out(c, SK_A)
)).
let PeerB(SK_B: skey, PK_B: pkey, PK_A: pkey, OLM_KEYS_STR:bitstring, OLM_ROOT_STR: bitstring) =
new bo: skey;
let gbo = pk(bo) in
let gbo_sig = sign(SK_B, rb(gbo)) in
out(c, (gbo, gbo_sig));
phase 1;
(* first stage: derive bmaster, verfiy a's msgs, decrypt prekey message, reply *)
in(c, (x1: bitstring, x1_mac: bitstring, gao: pkey, gta1: pkey));
let bmaster = hkdf1(dh(PK_A, SK_B), dh(PK_A, bo), dh(gao, SK_B)) in
let (rb1: key, cb1: key) = hkdf3(bmaster, OLM_ROOT_STR) 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, gao, gta1), x1_mac) = okay then
(
let m1 = sdec(mbk1_enc, x1) in
event recvE1(m1, mbk1, gao, gta1);
new tb2: skey;
let gtb2 = pk(tb2) in
let (rb2: key, cb2: key) = hkdf4(rb1, dh(gta1, 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, concat2(x2, gtb2)) in
event sendE2(m2, mbk2, gta1, gtb2);
out(c, (x2, x2_mac, gtb2));
phase 2;
event compromiseSKB(SK_B);
out(c, SK_B)
).
process
new OLM_KEYS_STR:bitstring; out(a, OLM_KEYS_STR);
new OLM_ROOT_STR:bitstring; out(a, OLM_ROOT_STR);
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, OLM_KEYS_STR, OLM_ROOT_STR)) |
(PeerB(SK_B, PK_B, PK_A, OLM_KEYS_STR, OLM_ROOT_STR)) |
out(a, (m1, m2))) (* transcript publish *)

View File

@@ -0,0 +1,204 @@
(*
Olm 3DH+Double Ratchet; responder deniability
Author: [redacted]
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 simpEqAll = false.
set selFun = Nounifset.
set redundancyElim = best.
set redundantHypElim = true.
set simplifyProcess = false.
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 *)
(* the concat functions *)
fun hkdf1(key, key, key): key [data].
fun khash(key): key.
fun hkdf2_dev1(key): key.
fun hkdf2_dev2(key): key.
letfun hkdf2(k: key) =
(hkdf2_dev1(k), hkdf2_dev2(k)).
fun hkdf3_dev1(key, bitstring): key.
fun hkdf3_dev2(key, bitstring): key.
letfun hkdf3(k: key, b: bitstring) =
(hkdf3_dev1(k, b), hkdf3_dev2(k, b)).
fun hkdf4_dev1(key, key): key.
fun hkdf4_dev2(key, key): key.
letfun hkdf4(k1: key, k2: key) =
(hkdf4_dev1(k1, k2), hkdf4_dev2(k1, k2)).
(* the concats *)
fun concat1(bitstring, pkey, pkey): bitstring [data].
fun concat2(bitstring, pkey): bitstring [data].
(* events *)
event sendE1(bitstring, key, pkey, pkey).
event recvE1(bitstring, key, pkey, pkey).
event sendE2(bitstring, key, pkey, pkey).
event recvE2(bitstring, key, pkey, pkey).
event compromiseSKA(skey).
event compromiseSKB(skey).
event start().
free tag_oe1: bitstring [private].
free tag_oe2: bitstring [private].
free tag_me1: bitstring [private].
free tag_me2: bitstring [private].
free tag_b_eph: bitstring [private].
let PeerA(SK_A: skey, PK_A: pkey, PK_B: pkey, OLM_KEYS_STR:bitstring, OLM_ROOT_STR: bitstring) =
phase 1;
new ao: skey;
let gao = pk(ao) in
(* generate amaster and enc msg (PHASE 1) *)
(* in(c, gbo: pkey); *)
in(c, (gbo: pkey, gbo_sig: bitstring));
if checksign(PK_B, rb(gbo), gbo_sig) = okay then (
let amaster = hkdf1(dh(PK_B, SK_A), dh(gbo, SK_A), dh(PK_B, ao)) in
let (ra1: key, ca1: key) = hkdf3(amaster, OLM_ROOT_STR) in (* derive the root and chain key *)
new ta1: skey;
let gta1 = pk(ta1) in
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, gao, gta1)) in
event sendE1(m1, mak1, gao, gta1);
out(c, (x1, x1_mac, gao, gta1));
(* second stage: now, decrypt the received message from bob *)
in(c, (x2: bitstring, x2_mac: bitstring, gtb2: pkey));
let (ra2: key, ca2: key) = hkdf4(ra1, dh(gtb2, ta1)) in
let mak2 = khash(ca2) in
let (mak2_auth: key, mak2_enc: key) = hkdf2(mak2) in
if checkmac(mak2_auth, concat2(x2, gtb2), x2_mac) = okay then
(
let m2 = sdec(mak2_enc, x2) in
event recvE2(m2, mak2, gta1, gtb2);
phase 2;
event compromiseSKA(SK_A);
out(c, SK_A)
)).
let PeerB(SK_B: skey, PK_B: pkey, PK_A: pkey, OLM_KEYS_STR:bitstring, OLM_ROOT_STR: bitstring) =
new bo: skey;
let gbo = pk(bo) in
let gbo_sig = sign(SK_B, rb(gbo)) in
out(c, (gbo, gbo_sig));
phase 1;
(* first stage: derive bmaster, verfiy a's msgs, decrypt prekey message, reply *)
in(c, (x1: bitstring, x1_mac: bitstring, gao: pkey, gta1: pkey));
let bmaster = hkdf1(dh(PK_A, SK_B), dh(PK_A, bo), dh(gao, SK_B)) in
let (rb1: key, cb1: key) = hkdf3(bmaster, OLM_ROOT_STR) 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, gao, gta1), x1_mac) = okay then
(
let m1 = sdec(mbk1_enc, x1) in
event recvE1(m1, mbk1, gao, gta1);
new tb2: skey;
let gtb2 = pk(tb2) in
let (rb2: key, cb2: key) = hkdf4(rb1, dh(gta1, 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, concat2(x2, gtb2)) in
event sendE2(m2, mbk2, gta1, gtb2);
out(c, (x2, x2_mac, gtb2));
phase 2;
event compromiseSKB(SK_B);
out(c, SK_B)
).
process
new OLM_KEYS_STR:bitstring; out(a, OLM_KEYS_STR);
new OLM_ROOT_STR:bitstring; out(a, OLM_ROOT_STR);
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();
event start();
( (PeerA(SK_B, PK_A, PK_B, OLM_KEYS_STR, OLM_ROOT_STR)) |
(PeerB(k_B, pk(k_B), PK_A, OLM_KEYS_STR, OLM_ROOT_STR)) |
out(a, (m1, m2))) (* transcript publish *)

View File

@@ -0,0 +1,281 @@
(*
Sender Keys protocol; proving secrecy, authentication, PCS, and 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): bitstring.
equation forall a: skey, b: skey; dh(pk(a), b) = dh(pk(b), a). (* symmetry of DH *)
(* the concat functions *)
fun khash(key): key.
fun hkdf(bitstring): key [data].
fun concat0(key): bitstring [data, typeConverter].
fun concat1(bitstring, pkey, pkey): bitstring [data].
reduc forall m1: bitstring, k1: pkey, k2: pkey; split1(concat1(m1, k1, k2)) = (m1, k1, k2).
fun concat2(bitstring, pkey): bitstring [data].
reduc forall m1: bitstring, k1: pkey; split2(concat2(m1, k1)) = (m1, k1).
fun concat3(bitstring, bitstring): bitstring [data].
reduc forall m1: bitstring, m2: bitstring; split3(concat3(m1, m2)) = (m1, m2).
fun concat4(bitstring, bitstring, bitstring): bitstring [data].
reduc forall m1: bitstring, m2: bitstring, m3:bitstring; split4(concat4(m1, m2, m3)) = (m1, m2, m3).
equation forall k: key, a: skey, b: skey; hkdf(concat3(concat0(khash(k)), dh(pk(a), b))) = hkdf(concat3(concat0(khash(k)), dh(pk(b), a))).
(* the concats *)
(* events *)
event sendOE1(pkey, key, pkey, pkey).
event recvOE1(pkey, key, pkey, pkey).
event sendOE2(pkey, key, pkey, pkey).
event recvOE2(pkey, key, pkey, pkey).
event sendME1(bitstring, key, pkey).
event recvME1(bitstring, key, pkey).
event sendME2(bitstring, key, pkey).
event recvME2(bitstring, key, pkey).
event compromiseOSKA(skey).
event compromiseOSKB(skey).
event start().
free tag_oe1: bitstring [private].
free tag_oe2: bitstring [private].
free tag_me1: bitstring [private].
free tag_me2: bitstring [private].
free tag_b_eph: bitstring [private].
let PeerA(OSK_A: skey, OPK_A: pkey, OPK_B: pkey, MSK_A: skey, MPK_A: pkey) =
phase 1;
new ao: skey;
let gao = pk(ao) in
(* generate amaster and enc msg (PHASE 1) *)
(* in(c, gbo: pkey); *)
in(c, (gbo: pkey, gbo_sig: bitstring));
if checksign(OPK_B, rb(gbo), gbo_sig) = okay then
(* =================== PHASE 2: DERIVE P2P MASTER + SENDER KEYS SESSION A -> B =================== *)
let amaster = hkdf(concat4(dh(OPK_B, OSK_A), dh(gbo, OSK_A), dh(OPK_B, ao))) in
let ra1 = hkdf(concat0(amaster)) in
let ca1 = hkdf(concat0(amaster)) in
new ta1: skey;
let gta1 = pk(ta1) in
let ak1 = khash(ca1) in
let ak1_auth = hkdf(concat0(ak1)) in
let ak1_enc = hkdf(concat0(ak1)) in
new mra0: key; (* mra0 := megolm ratchet *)
let x1 = senc(ak1_enc, (MPK_A, mra0)) in
let x1_mac = mac(ak1_auth, concat1(x1, gao, gta1)) in
event sendOE1(MPK_A, mra0, gao, gta1);
out(c, (x1, x1_mac, gao, gta1));
(* =================== PHASE 3: SENDER KEYS SESSION B -> A =================== *)
(* second stage: now, decrypt the received message from bob *)
in(c, (x2: bitstring, x2_mac: bitstring, gtb2: pkey));
let ca2 = hkdf(concat3(concat0(khash(ra1)), dh(gtb2, ta1))) in
let ak2 = khash(ca2) in
let ak2_auth = hkdf(concat0(ak2)) in
if checkmac(ak2_auth, concat2(x2, gtb2), x2_mac) = okay then (
let mak2_enc = hkdf(concat0(ak2)) in
let (MPK_B: pkey, mrb0: key) = sdec(mak2_enc, x2) in
event recvOE2(MPK_B, mrb0, gao, gta1);
(* =================== PHASE 4: SENDER KEYS MSG A -> B =================== *)
let mra1 = khash(mra0) in
let mak1_auth = hkdf(concat0(mra1)) in
let mak1_enc = hkdf(concat0(mra1)) in
let mx1 = senc(mak1_enc, m1) in
let mx1_sig = sign(MSK_A, mx1) in
event sendME1(m1, mra1, MPK_A);
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 = hkdf(concat0(mrb1)) in
let mbk1_enc = hkdf(concat0(mrb1)) in
if checksign(MPK_B, mx2, mx2_sig) = okay then
let m2 = sdec(mbk1_enc, mx2) in
event recvME2(m2, mrb1, MPK_B);
phase 3; (* =================== PHASE 6: PCS =================== *)
event compromiseOSKA(OSK_A);
out(c, OSK_A)
).
let PeerB(OSK_B: skey, OPK_B: pkey, OPK_A: pkey, MSK_B: skey, MPK_B: pkey) =
new bo: skey;
let gbo = pk(bo) in
let gbo_sig = sign(OSK_B, rb(gbo)) in
out(c, (gbo, gbo_sig));
phase 1;
(* =================== PHASE 2: DERIVE P2P MASTER + SENDER KEYS SESSION A -> B =================== *)
(* first stage: derive bmaster, verfiy a's msgs, decrypt prekey message, reply *)
in(c, (x1: bitstring, x1_mac: bitstring, gao: pkey, gta1: pkey));
let bmaster = hkdf(concat4(dh(OPK_A, OSK_B), dh(OPK_A, bo), dh(gao, OSK_B))) in
let rb1 = hkdf(concat0(bmaster)) in
let cb1 = hkdf(concat0(bmaster)) in
let bk1 = khash(cb1) in
let bk1_auth = hkdf(concat0(bk1)) in
let bk1_enc = hkdf(concat0(bk1)) in
if checkmac(bk1_auth, concat1(x1, gao, gta1), x1_mac) = okay then
(
let (MPK_A: pkey, mra0: key) = sdec(bk1_enc, x1) in
event recvOE1(MPK_A, mra0, gao, gta1);
(* =================== PHASE 3: SENDER KEYS SESH B -> A =================== *)
new tb2: skey;
let gtb2 = pk(tb2) in
new tb2: skey;
let gtb2 = pk(tb2) in
let rb2 = hkdf(concat3(concat0(khash(rb1)), dh(gta1, tb2))) in
let cb2 = hkdf(concat3(concat0(khash(rb1)), dh(gta1, tb2))) in
let bk2 = khash(cb2) in
let bk2_auth = hkdf(concat0(bk2)) in
let bk2_enc = hkdf(concat0(bk2)) in
new mrb0: key; (* mrb0 := sender keys ratchet *)
let x2 = senc(bk2_enc, (MPK_B, mrb0)) in
let x2_mac = mac(bk2_auth, concat2(x2, gtb2)) in
event sendOE2(MPK_B, mrb0, gao, gta1);
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 = hkdf(concat0(mra1)) in
let mak1_enc = hkdf(concat0(mra1)) in
if checksign(MPK_A, mx1, mx1_sig) = okay then (
let m1 = sdec(mak1_enc, mx1) in
event recvME1(m1, mra1, MPK_A);
phase 3;
event compromiseOSKB(OSK_B);
out(c, OSK_B);
(* =================== PHASE 5: SENDER KEYS MSG B -> A =================== *)
let mrb1 = khash(mrb0) in
let mbk1_auth = hkdf(concat0(mrb1)) in
let mbk1_enc = hkdf(concat0(mrb1)) in
let mx2 = senc(mbk1_enc, m2) in
let mx2_sig = sign(MSK_B, mx2) in
event sendME2(m2, mrb1, MPK_B);
out(c, (mx2, mx2_sig))
(* =================== PHASE 6: PCS =================== *)
)).
process
new OSK_A: skey; let OPK_A = pk(OSK_A) in
new OSK_B: skey; let OPK_B = pk(OSK_B) in
new MSK_A: skey; let MPK_A = pk(MSK_A) in
new MSK_B: skey; let MPK_B = pk(MSK_B) in
out(a, OPK_A);
out(a, OPK_B);
(*
out(a, MPK_A);
out(a, MPK_B);
*)
new fib1: skey;
new fib2: skey;
new fib3: skey;
new fib4: skey;
let (kO_A: skey, kM_A: skey) = choice [(OSK_A, MSK_A), (fib1, fib2) ] in
let (kO_B: skey, kM_B: skey) = choice [(OSK_B, MSK_B), (fib1, fib2) ] in
( (!PeerA(kO_A, pk(kO_A), OPK_B, kM_A, pk(kM_A))) |
(!PeerB(OSK_B, OPK_B, OPK_A, MSK_B, MPK_B)))

View File

@@ -0,0 +1,281 @@
(*
Sender Keys protocol; proving secrecy, authentication, PCS, and 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): bitstring.
equation forall a: skey, b: skey; dh(pk(a), b) = dh(pk(b), a). (* symmetry of DH *)
(* the concat functions *)
fun khash(key): key.
fun hkdf(bitstring): key [data].
fun concat0(key): bitstring [data, typeConverter].
fun concat1(bitstring, pkey, pkey): bitstring [data].
reduc forall m1: bitstring, k1: pkey, k2: pkey; split1(concat1(m1, k1, k2)) = (m1, k1, k2).
fun concat2(bitstring, pkey): bitstring [data].
reduc forall m1: bitstring, k1: pkey; split2(concat2(m1, k1)) = (m1, k1).
fun concat3(bitstring, bitstring): bitstring [data].
reduc forall m1: bitstring, m2: bitstring; split3(concat3(m1, m2)) = (m1, m2).
fun concat4(bitstring, bitstring, bitstring): bitstring [data].
reduc forall m1: bitstring, m2: bitstring, m3:bitstring; split4(concat4(m1, m2, m3)) = (m1, m2, m3).
equation forall k: key, a: skey, b: skey; hkdf(concat3(concat0(khash(k)), dh(pk(a), b))) = hkdf(concat3(concat0(khash(k)), dh(pk(b), a))).
(* the concats *)
(* events *)
event sendOE1(pkey, key, pkey, pkey).
event recvOE1(pkey, key, pkey, pkey).
event sendOE2(pkey, key, pkey, pkey).
event recvOE2(pkey, key, pkey, pkey).
event sendME1(bitstring, key, pkey).
event recvME1(bitstring, key, pkey).
event sendME2(bitstring, key, pkey).
event recvME2(bitstring, key, pkey).
event compromiseOSKA(skey).
event compromiseOSKB(skey).
event start().
free tag_oe1: bitstring [private].
free tag_oe2: bitstring [private].
free tag_me1: bitstring [private].
free tag_me2: bitstring [private].
free tag_b_eph: bitstring [private].
let PeerA(OSK_A: skey, OPK_A: pkey, OPK_B: pkey, MSK_A: skey, MPK_A: pkey) =
phase 1;
new ao: skey;
let gao = pk(ao) in
(* generate amaster and enc msg (PHASE 1) *)
(* in(c, gbo: pkey); *)
in(c, (gbo: pkey, gbo_sig: bitstring));
if checksign(OPK_B, rb(gbo), gbo_sig) = okay then
(* =================== PHASE 2: DERIVE P2P MASTER + SENDER KEYS SESSION A -> B =================== *)
let amaster = hkdf(concat4(dh(OPK_B, OSK_A), dh(gbo, OSK_A), dh(OPK_B, ao))) in
let ra1 = hkdf(concat0(amaster)) in
let ca1 = hkdf(concat0(amaster)) in
new ta1: skey;
let gta1 = pk(ta1) in
let ak1 = khash(ca1) in
let ak1_auth = hkdf(concat0(ak1)) in
let ak1_enc = hkdf(concat0(ak1)) in
new mra0: key; (* mra0 := megolm ratchet *)
let x1 = senc(ak1_enc, (MPK_A, mra0)) in
let x1_mac = mac(ak1_auth, concat1(x1, gao, gta1)) in
event sendOE1(MPK_A, mra0, gao, gta1);
out(c, (x1, x1_mac, gao, gta1));
(* =================== PHASE 3: SENDER KEYS SESSION B -> A =================== *)
(* second stage: now, decrypt the received message from bob *)
in(c, (x2: bitstring, x2_mac: bitstring, gtb2: pkey));
let ca2 = hkdf(concat3(concat0(khash(ra1)), dh(gtb2, ta1))) in
let ak2 = khash(ca2) in
let ak2_auth = hkdf(concat0(ak2)) in
if checkmac(ak2_auth, concat2(x2, gtb2), x2_mac) = okay then (
let mak2_enc = hkdf(concat0(ak2)) in
let (MPK_B: pkey, mrb0: key) = sdec(mak2_enc, x2) in
event recvOE2(MPK_B, mrb0, gao, gta1);
(* =================== PHASE 4: SENDER KEYS MSG A -> B =================== *)
let mra1 = khash(mra0) in
let mak1_auth = hkdf(concat0(mra1)) in
let mak1_enc = hkdf(concat0(mra1)) in
let mx1 = senc(mak1_enc, m1) in
let mx1_sig = sign(MSK_A, mx1) in
event sendME1(m1, mra1, MPK_A);
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 = hkdf(concat0(mrb1)) in
let mbk1_enc = hkdf(concat0(mrb1)) in
if checksign(MPK_B, mx2, mx2_sig) = okay then
let m2 = sdec(mbk1_enc, mx2) in
event recvME2(m2, mrb1, MPK_B);
phase 3; (* =================== PHASE 6: PCS =================== *)
event compromiseOSKA(OSK_A);
out(c, OSK_A)
).
let PeerB(OSK_B: skey, OPK_B: pkey, OPK_A: pkey, MSK_B: skey, MPK_B: pkey) =
new bo: skey;
let gbo = pk(bo) in
let gbo_sig = sign(OSK_B, rb(gbo)) in
out(c, (gbo, gbo_sig));
phase 1;
(* =================== PHASE 2: DERIVE P2P MASTER + SENDER KEYS SESSION A -> B =================== *)
(* first stage: derive bmaster, verfiy a's msgs, decrypt prekey message, reply *)
in(c, (x1: bitstring, x1_mac: bitstring, gao: pkey, gta1: pkey));
let bmaster = hkdf(concat4(dh(OPK_A, OSK_B), dh(OPK_A, bo), dh(gao, OSK_B))) in
let rb1 = hkdf(concat0(bmaster)) in
let cb1 = hkdf(concat0(bmaster)) in
let bk1 = khash(cb1) in
let bk1_auth = hkdf(concat0(bk1)) in
let bk1_enc = hkdf(concat0(bk1)) in
if checkmac(bk1_auth, concat1(x1, gao, gta1), x1_mac) = okay then
(
let (MPK_A: pkey, mra0: key) = sdec(bk1_enc, x1) in
event recvOE1(MPK_A, mra0, gao, gta1);
(* =================== PHASE 3: SENDER KEYS SESH B -> A =================== *)
new tb2: skey;
let gtb2 = pk(tb2) in
new tb2: skey;
let gtb2 = pk(tb2) in
let rb2 = hkdf(concat3(concat0(khash(rb1)), dh(gta1, tb2))) in
let cb2 = hkdf(concat3(concat0(khash(rb1)), dh(gta1, tb2))) in
let bk2 = khash(cb2) in
let bk2_auth = hkdf(concat0(bk2)) in
let bk2_enc = hkdf(concat0(bk2)) in
new mrb0: key; (* mrb0 := sender keys ratchet *)
let x2 = senc(bk2_enc, (MPK_B, mrb0)) in
let x2_mac = mac(bk2_auth, concat2(x2, gtb2)) in
event sendOE2(MPK_B, mrb0, gao, gta1);
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 = hkdf(concat0(mra1)) in
let mak1_enc = hkdf(concat0(mra1)) in
if checksign(MPK_A, mx1, mx1_sig) = okay then (
let m1 = sdec(mak1_enc, mx1) in
event recvME1(m1, mra1, MPK_A);
phase 3;
event compromiseOSKB(OSK_B);
out(c, OSK_B);
(* =================== PHASE 5: SENDER KEYS MSG B -> A =================== *)
let mrb1 = khash(mrb0) in
let mbk1_auth = hkdf(concat0(mrb1)) in
let mbk1_enc = hkdf(concat0(mrb1)) in
let mx2 = senc(mbk1_enc, m2) in
let mx2_sig = sign(MSK_B, mx2) in
event sendME2(m2, mrb1, MPK_B);
out(c, (mx2, mx2_sig))
(* =================== PHASE 6: PCS =================== *)
)).
process
new OSK_A: skey; let OPK_A = pk(OSK_A) in
new OSK_B: skey; let OPK_B = pk(OSK_B) in
new MSK_A: skey; let MPK_A = pk(MSK_A) in
new MSK_B: skey; let MPK_B = pk(MSK_B) in
out(a, OPK_A);
out(a, OPK_B);
(*
out(a, MPK_A);
out(a, MPK_B);
*)
new fib1: skey;
new fib2: skey;
new fib3: skey;
new fib4: skey;
let (kO_A: skey, kM_A: skey) = choice [(OSK_A, MSK_A), (fib1, fib2) ] in
let (kO_B: skey, kM_B: skey) = choice [(OSK_B, MSK_B), (fib1, fib2) ] in
( (!PeerA(OSK_A, OPK_A, OPK_B, MSK_A, MPK_A)) |
(!PeerB(kO_B, pk(kO_B), OPK_A, kM_A, pk(kM_A))))

View File

@@ -0,0 +1,194 @@
(*
Signal X3DH+Double Ratchet; initiator deniability
Author: [redacted]
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 simpEqAll = false.
set selFun = Nounifset.
set redundancyElim = best.
set redundantHypElim = true.
set simplifyProcess = false.
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 *)
(* the concat functions *)
fun hkdf1(bitstring): key.
fun khash(key): key.
fun hkdf2_dev1(key): key.
fun hkdf2_dev2(key): key.
letfun hkdf2(k: key) =
(hkdf2_dev1(k), hkdf2_dev2(k)).
fun hkdf4_dev1(key, key): key.
fun hkdf4_dev2(key, key): key.
letfun hkdf4(k1: key, k2: key) =
(hkdf4_dev1(k1, k2), hkdf4_dev2(k1, k2)).
(* the concats *)
fun concat1(bitstring, pkey, pkey): bitstring [data].
fun concat2(bitstring, pkey): bitstring [data].
fun concat3(key, key, key, key): 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 start().
free tag_oe1: bitstring [private].
free tag_oe2: bitstring [private].
free tag_me1: bitstring [private].
free tag_me2: bitstring [private].
free tag_b_eph: bitstring [private].
let PeerA(SK_A: skey, PK_A: pkey, PK_B: pkey) =
phase 1;
new ao: skey;
new ae1: skey;
let gae1 = pk(ae1) in
(* generate amaster and enc msg (PHASE 1) *)
in(c, (gbssig: bitstring, gbs: pkey, gbo: pkey));
if checksign(PK_B, rb(gbs), gbssig) = okay then
let amaster = hkdf1(concat3(dh(gbs, SK_A), dh(PK_B, ae1), dh(gbs, ae1), dh(gbo, ae1))) in
let (ra1: key, ca1: key) = hkdf2(amaster) in (* derive the root and chain key *)
new ta1: skey;
let gta1 = pk(ta1) in
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, gta1, gae1)) in
event sendE1(m1, mak1);
out(c, (x1, x1_mac, gta1, gae1));
(* second stage: now, decrypt the received message from bob *)
in(c, (x2: bitstring, x2_mac: bitstring, gtb2: pkey));
let (ra2: key, ca2: key) = hkdf4(ra1, dh(gtb2, ta1)) in
let mak2 = khash(ca2) in
let (mak2_auth: key, mak2_enc: key) = hkdf2(mak2) in
if checkmac(mak2_auth, concat2(x2, gtb2), x2_mac) = okay then
let m2 = sdec(mak2_enc, x2) in
event recvE2(m2, mak2);
phase 2;
0.
let PeerB(SK_B: skey, PK_B: pkey, PK_A: pkey) =
new bo: skey;
new bs: skey;
let gbo = pk(bo) in
let gbs = pk(bs) in
let gbssig = sign(SK_B, rb(gbs)) in
out(c, (gbssig, gbs, gbo));
phase 1;
(* first stage: derive bmaster, verfiy a's msgs, decrypt prekey message, reply *)
in(c, (x1: bitstring, x1_mac: bitstring, gta1: pkey, gae1: pkey));
let bmaster = hkdf1(concat3(dh(PK_A, bs), dh(gae1, SK_B), dh(gae1, bs), dh(gae1, bo))) 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, gta1, gae1), x1_mac) = okay then
let m1 = sdec(mbk1_enc, x1) in
event recvE1(m1, mbk1);
new tb2: skey;
let gtb2 = pk(tb2) in
let (rb2: key, cb2: key) = hkdf4(rb1, dh(gta1, 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, concat2(x2, gtb2)) in
event sendE2(m2, mbk2);
out(c, (x2, x2_mac, gtb2));
phase 2;
event compromiseSKB(SK_B);
out(c, SK_B);
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
( (!PeerA(k_A, pk(k_A), PK_B)) |
(!PeerB(SK_B, PK_B, PK_A)))

View File

@@ -0,0 +1,193 @@
(*
Signal X3DH+Double Ratchet; initiator deniability
Author: [redacted]
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 simpEqAll = false.
set selFun = Nounifset.
set redundancyElim = best.
set redundantHypElim = true.
set simplifyProcess = false.
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 *)
(* the concat functions *)
fun hkdf1(bitstring): key.
fun khash(key): key.
fun hkdf2_dev1(key): key.
fun hkdf2_dev2(key): key.
letfun hkdf2(k: key) =
(hkdf2_dev1(k), hkdf2_dev2(k)).
fun hkdf4_dev1(key, key): key.
fun hkdf4_dev2(key, key): key.
letfun hkdf4(k1: key, k2: key) =
(hkdf4_dev1(k1, k2), hkdf4_dev2(k1, k2)).
(* the concats *)
fun concat1(bitstring, pkey, pkey): bitstring [data].
fun concat2(bitstring, pkey): bitstring [data].
fun concat3(key, key, key, key): 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 start().
free tag_oe1: bitstring [private].
free tag_oe2: bitstring [private].
free tag_me1: bitstring [private].
free tag_me2: bitstring [private].
free tag_b_eph: bitstring [private].
let PeerA(SK_A: skey, PK_A: pkey, PK_B: pkey) =
phase 1;
new ao: skey;
new ae1: skey;
let gae1 = pk(ae1) in
(* generate amaster and enc msg (PHASE 1) *)
in(c, (gbssig: bitstring, gbs: pkey, gbo: pkey));
if checksign(PK_B, rb(gbs), gbssig) = okay then
let amaster = hkdf1(concat3(dh(gbs, SK_A), dh(PK_B, ae1), dh(gbs, ae1), dh(gbo, ae1))) in
let (ra1: key, ca1: key) = hkdf2(amaster) in (* derive the root and chain key *)
new ta1: skey;
let gta1 = pk(ta1) in
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, gta1, gae1)) in
event sendE1(m1, mak1);
out(c, (x1, x1_mac, gta1, gae1));
(* second stage: now, decrypt the received message from bob *)
in(c, (x2: bitstring, x2_mac: bitstring, gtb2: pkey));
let (ra2: key, ca2: key) = hkdf4(ra1, dh(gtb2, ta1)) in
let mak2 = khash(ca2) in
let (mak2_auth: key, mak2_enc: key) = hkdf2(mak2) in
if checkmac(mak2_auth, concat2(x2, gtb2), x2_mac) = okay then
let m2 = sdec(mak2_enc, x2) in
event recvE2(m2, mak2);
phase 2;
0.
let PeerB(SK_B: skey, PK_B: pkey, PK_A: pkey) =
new bo: skey;
new bs: skey;
let gbo = pk(bo) in
let gbs = pk(bs) in
let gbssig = sign(SK_B, rb(gbs)) in
out(c, (gbssig, gbs, gbo));
phase 1;
(* first stage: derive bmaster, verfiy a's msgs, decrypt prekey message, reply *)
in(c, (x1: bitstring, x1_mac: bitstring, gta1: pkey, gae1: pkey));
let bmaster = hkdf1(concat3(dh(PK_A, bs), dh(gae1, SK_B), dh(gae1, bs), dh(gae1, bo))) 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, gta1, gae1), x1_mac) = okay then
let m1 = sdec(mbk1_enc, x1) in
event recvE1(m1, mbk1);
new tb2: skey;
let gtb2 = pk(tb2) in
let (rb2: key, cb2: key) = hkdf4(rb1, dh(gta1, 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, concat2(x2, gtb2)) in
event sendE2(m2, mbk2);
out(c, (x2, x2_mac, gtb2));
phase 2;
event compromiseSKB(SK_B);
out(c, SK_B);
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
( (PeerA(SK_A, PK_A, PK_B)) |
(PeerB(k_B, pk(k_B), PK_A)))

View File

@@ -0,0 +1,168 @@
(*
X3DH; initiator deniability
Author: [redacted]
model assumption #1: same key is used for signing and encryption (i.e. X25519)
*)
free m1: bitstring [private].
set selFun = Nounifset.
set attacker = passive.
(*
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)).
(* the concats *)
fun concat1(bitstring, pkey): bitstring [data].
fun concat2(key, key, key, key): bitstring [data].
(* events *)
event sendE1(bitstring, key).
event recvE1(bitstring, key).
event compromiseSKA(skey).
event compromiseSKB(skey).
event start().
equation forall a1: key, a2: key, a3: key, a4: key; hkdf1(concat2(a1,a2,a3,a4)) = hkdf1(concat2(a1,a2,a3,a4)).
(*
k1: bs
k2: SK_A
k3: SK_B
k4: ae1
k5: bo
let amaster = hkdf1(concat2(dh(gbs, SK_A), dh(PK_B, ae1), dh(gbs, ae1), dh(gbo, ae1))) in
let bmaster = hkdf1(concat2(dh(PK_A, bs), dh(gae1, SK_B), dh(gae1, bs), dh(gae1, bo))) in
equation forall k1: skey, k2: skey, k3: skey, k4: skey, k5: skey; hkdf1(concat2(dh(pk(k1), k2),dh(pk(k3), k4),dh(pk(k1), k4),dh(pk(k5), k4))) = hkdf1(concat2(dh(pk(k2), k1),dh(pk(k4), k3),dh(pk(k4), k1),dh(pk(k4), k5))) [convergent].
*)
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));
if checksign(PK_B, rb(gbs), gbssig) = okay then
let amaster = hkdf1(concat2(dh(gbs, SK_A), dh(PK_B, ae1), dh(gbs, ae1), dh(gbo, ae1))) 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)) in
event sendE1(m1, mak1);
out(c, (x1, x1_mac, gae1));
phase 2;
0.
let PeerB(SK_B: skey, PK_B: pkey, PK_A: pkey) =
new bo: skey;
new bs: skey;
let gbs = pk(bs) in
let gbo = pk(bo) in
let gbssig = sign(SK_B, rb(gbs)) in
out(c, (gbssig, gbs, gbo));
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));
let bmaster = hkdf1(concat2(dh(PK_A, bs), dh(gae1, SK_B), dh(gae1, bs), dh(gae1, bo))) 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), 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.
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 *)

View File

@@ -0,0 +1,168 @@
(*
X3DH; responder deniability
Author: [redacted]
model assumption #1: same key is used for signing and encryption (i.e. X25519)
*)
free m1: bitstring [private].
set selFun = Nounifset.
set attacker = passive.
set simplifyProcess = false.
(*
set simpEqAll = false.
set simpEqAll = false.
set redundancyElim = best.
set redundantHypElim = 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)).
(* the concats *)
fun concat1(bitstring, pkey): bitstring [data].
fun concat2(key, key, key, key): bitstring [data].
(* events *)
event sendE1(bitstring, key).
event recvE1(bitstring, key).
event compromiseSKA(skey).
event compromiseSKB(skey).
event start().
equation forall a1: key, a2: key, a3: key, a4: key; hkdf1(concat2(a1,a2,a3,a4)) = hkdf1(concat2(a1,a2,a3,a4)).
(*
k1: bs
k2: SK_A
k3: SK_B
k4: ae1
k5: bo
let amaster = hkdf1(concat2(dh(gbs, SK_A), dh(PK_B, ae1), dh(gbs, ae1), dh(gbo, ae1))) in
let bmaster = hkdf1(concat2(dh(PK_A, bs), dh(gae1, SK_B), dh(gae1, bs), dh(gae1, bo))) in
equation forall k1: skey, k2: skey, k3: skey, k4: skey, k5: skey; hkdf1(concat2(dh(pk(k1), k2),dh(pk(k3), k4),dh(pk(k1), k4),dh(pk(k5), k4))) = hkdf1(concat2(dh(pk(k2), k1),dh(pk(k4), k3),dh(pk(k4), k1),dh(pk(k4), k5))) [convergent].
*)
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));
if checksign(PK_B, rb(gbs), gbssig) = okay then
let amaster = hkdf1(concat2(dh(gbs, SK_A), dh(PK_B, ae1), dh(gbs, ae1), dh(gbo, ae1))) 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)) in
event sendE1(m1, mak1);
out(c, (x1, x1_mac, gae1));
phase 2;
0.
let PeerB(SK_B: skey, PK_B: pkey, PK_A: pkey) =
new bo: skey;
new bs: skey;
let gbs = pk(bs) in
let gbo = pk(bo) in
let gbssig = sign(SK_B, rb(gbs)) in
out(c, (gbssig, gbs, gbo));
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));
let bmaster = hkdf1(concat2(dh(PK_A, bs), dh(gae1, SK_B), dh(gae1, bs), dh(gae1, bo))) 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), 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.
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))

View File

@@ -0,0 +1,249 @@
(*
Olm + Megolm protocol
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): bitstring.
equation forall a: skey, b: skey; dh(pk(a), b) = dh(pk(b), a). (* symmetry of DH *)
(* the concat functions *)
fun khash(key): key.
fun hkdf(bitstring): key [data].
fun concat0(key): bitstring [data, typeConverter].
fun concat1(bitstring, pkey, pkey): bitstring [data].
reduc forall m1: bitstring, k1: pkey, k2: pkey; split1(concat1(m1, k1, k2)) = (m1, k1, k2).
fun concat2(bitstring, pkey): bitstring [data].
reduc forall m1: bitstring, k1: pkey; split2(concat2(m1, k1)) = (m1, k1).
fun concat3(bitstring, bitstring): bitstring [data].
reduc forall m1: bitstring, m2: bitstring; split3(concat3(m1, m2)) = (m1, m2).
fun concat4(bitstring, bitstring, bitstring): bitstring [data].
reduc forall m1: bitstring, m2: bitstring, m3:bitstring; split4(concat4(m1, m2, m3)) = (m1, m2, m3).
equation forall k: key, a: skey, b: skey; hkdf(concat3(concat0(khash(k)), dh(pk(a), b))) = hkdf(concat3(concat0(khash(k)), dh(pk(b), a))).
(* the concats *)
(* events *)
event sendOE1(pkey, key, pkey, pkey).
event recvOE1(pkey, key, pkey, pkey).
event sendOE2(pkey, key, pkey, pkey).
event recvOE2(pkey, key, pkey, pkey).
event sendME1(bitstring, key, pkey).
event recvME1(bitstring, key, pkey).
event sendME2(bitstring, key, pkey).
event recvME2(bitstring, key, pkey).
event compromiseOSKA(skey).
event compromiseOSKB(skey).
event start().
free tag_oe1: bitstring [private].
free tag_oe2: bitstring [private].
free tag_me1: bitstring [private].
free tag_me2: bitstring [private].
free tag_b_eph: bitstring [private].
let PeerA(OSK_A: skey, OPK_A: pkey, OPK_B: pkey, OLM_KEYS_STR:bitstring, OLM_ROOT_STR: bitstring, MSK_A: skey, MPK_A: pkey) =
phase 1;
new ao: skey;
let gao = pk(ao) in
(* generate amaster and enc msg (PHASE 1) *)
(* in(c, gbo: pkey); *)
in(c, (gbo: pkey, gbo_sig: bitstring));
(* if checksign(OPK_B, rb(gbo), gbo_sig) = okay then ( *)
(* =================== PHASE 2: DERIVE OLM MASTER + MEGOLM SESSION A -> B =================== *)
let amaster = hkdf(concat4(dh(OPK_B, OSK_A), dh(gbo, OSK_A), dh(OPK_B, ao))) in
let ra1 = hkdf(concat0(amaster)) in
let ca1 = hkdf(concat0(amaster)) in
new ta1: skey;
let gta1 = pk(ta1) in
let ak1 = khash(ca1) in
let ak1_auth = hkdf(concat0(ak1)) in
let ak1_enc = hkdf(concat0(ak1)) in
new mra0: key; (* mra0 := megolm ratchet *)
let x1 = senc(ak1_enc, (MPK_A, mra0)) in
let x1_mac = mac(ak1_auth, concat1(x1, gao, gta1)) in
event sendOE1(MPK_A, mra0, gao, gta1);
out(c, (x1, x1_mac, gao, gta1));
(* =================== PHASE 4: MEGOLM MSG A -> B =================== *)
let mra1 = khash(mra0) in
let mak1_auth = hkdf(concat0(mra1)) in
let mak1_enc = hkdf(concat0(mra1)) in
let mx1 = senc(mak1_enc, m1) in
let mx1_mac = mac(mak1_auth, mx1) in
let mx1_sig = sign(MSK_A, concat3(mx1, mx1_mac)) in
event sendME1(m1, mra1, MPK_A);
out(c, (mx1, mx1_mac, mx1_sig));
phase 3; (* =================== PHASE 6: PCS =================== *)
(* out(c, PK_A); *)
0.
let PeerB(OSK_B: skey, OPK_B: pkey, OPK_A: pkey, OLM_KEYS_STR:bitstring, OLM_ROOT_STR: bitstring, MSK_B: skey, MPK_B: pkey) =
new bo: skey;
let gbo = pk(bo) in
let gbo_sig = sign(OSK_B, rb(gbo)) in
out(c, (gbo, gbo_sig));
phase 1;
(* =================== PHASE 2: DERIVE OLM MASTER + MEGOLM SESSION A -> B =================== *)
(* first stage: derive bmaster, verfiy a's msgs, decrypt prekey message, reply *)
in(c, (x1: bitstring, x1_mac: bitstring, gao: pkey, gta1: pkey));
let bmaster = hkdf(concat4(dh(OPK_A, OSK_B), dh(OPK_A, bo), dh(gao, OSK_B))) in
let rb1 = hkdf(concat0(bmaster)) in
let cb1 = hkdf(concat0(bmaster)) in
let bk1 = khash(cb1) in
let bk1_auth = hkdf(concat0(bk1)) in
let bk1_enc = hkdf(concat0(bk1)) in
if checkmac(bk1_auth, concat1(x1, gao, gta1), x1_mac) = okay then
(
let (MPK_A: pkey, mra0: key) = sdec(bk1_enc, x1) in
event recvOE1(MPK_A, mra0, gao, gta1);
(* =================== PHASE 4: MEGOLM MSG A -> B =================== *)
in(c, (mx1: bitstring, mx1_mac: bitstring, mx1_sig: bitstring));
let mra1 = khash(mra0) in
let mak1_auth = hkdf(concat0(mra1)) in
let mak1_enc = hkdf(concat0(mra1)) in
if checksign(MPK_A, concat3(mx1, mx1_mac), mx1_sig) = okay then (
if checkmac(mak1_auth, mx1, mx1_mac) = okay then (
let m1 = sdec(mak1_enc, mx1) in
event recvME1(m1, mra1, MPK_A);
(* =================== PHASE 6: PCS =================== *)
phase 3;
event compromiseOSKB(OSK_B);
out(c, OSK_B)
))).
query event(start()). (* reachable from all possible executions: trivially false *)
(* pre-compromise security, aka forward secrecy. the only way
m1 can be compromised is if alice's sk is compromised
NOTE: if signed, this is trivially true since m1 is never compromised
*)
query sk: skey; attacker(m1) ==> event(compromiseOSKB(sk)).
(* post-compromise security. even if the secret key is compromised, message two remains secret
*)
query sk1: skey, sk2: skey; (event(compromiseOSKB(sk1)) && attacker(m1)) ==> false.
(* auth *)
query k1: pkey, rk: key, k2: pkey, k3: pkey; inj-event(recvOE1(k1, rk, k2, k3)) ==> inj-event(sendOE1(k1, rk, k2, k3)).
query m: bitstring, rk: key, k1: pkey; inj-event(recvME1(m, rk, k1)) ==> inj-event(sendME1(m, rk, k1)).
(* secrecy *)
query attacker(m1). (* expected to be false *)
(* reachability: all expected to be false *)
query k1: pkey, rk: key, k2: pkey, k3: pkey; event(sendOE1(k1,rk,k2,k3)).
query k1: pkey, rk: key, k2: pkey, k3: pkey; event(recvOE1(k1,rk,k2,k3)).
query m: bitstring, rk: key, k1: pkey; event(sendME1(m, rk, k1)).
query m: bitstring, rk: key, k1: pkey; event(recvME1(m, rk, k1)).
process
new OLM_KEYS_STR:bitstring; out(a, OLM_KEYS_STR);
new OLM_ROOT_STR:bitstring; out(a, OLM_ROOT_STR);
new OSK_A: skey; let OPK_A = pk(OSK_A) in
new OSK_B: skey; let OPK_B = pk(OSK_B) in
new MSK_A: skey; let MPK_A = pk(MSK_A) in
new MSK_B: skey; let MPK_B = pk(MSK_B) in
out(a, OPK_A);
out(a, OPK_B);
(*
do not publish sender keys material
out(a, MPK_A);
out(a, MPK_B);
*)
event start();
( (!PeerA(OSK_A, OPK_A, OPK_B, OLM_KEYS_STR, OLM_ROOT_STR, MSK_A, MPK_A)) |
(!PeerB(OSK_B, OPK_B, OPK_A, OLM_KEYS_STR, OLM_ROOT_STR, MSK_B, MPK_B)))

319
proverif/megolm.pv Normal file
View File

@@ -0,0 +1,319 @@
(*
Olm + Megolm protocol; proving secrecy, authentication, PCS, and 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.
free mra0: key [private].
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): bitstring.
equation forall a: skey, b: skey; dh(pk(a), b) = dh(pk(b), a). (* symmetry of DH *)
(* the concat functions *)
fun khash(key): key.
fun hkdf(bitstring): key [data].
fun concat0(key): bitstring [data, typeConverter].
fun concat1(bitstring, pkey, pkey): bitstring [data].
reduc forall m1: bitstring, k1: pkey, k2: pkey; split1(concat1(m1, k1, k2)) = (m1, k1, k2).
fun concat2(bitstring, pkey): bitstring [data].
reduc forall m1: bitstring, k1: pkey; split2(concat2(m1, k1)) = (m1, k1).
fun concat3(bitstring, bitstring): bitstring [data].
reduc forall m1: bitstring, m2: bitstring; split3(concat3(m1, m2)) = (m1, m2).
fun concat4(bitstring, bitstring, bitstring): bitstring [data].
reduc forall m1: bitstring, m2: bitstring, m3:bitstring; split4(concat4(m1, m2, m3)) = (m1, m2, m3).
equation forall k: key, a: skey, b: skey; hkdf(concat3(concat0(khash(k)), dh(pk(a), b))) = hkdf(concat3(concat0(khash(k)), dh(pk(b), a))).
(* the concats *)
(* events *)
event sendOE1(pkey, key, pkey, pkey).
event recvOE1(pkey, key, pkey, pkey).
event sendOE2(pkey, key, pkey, pkey).
event recvOE2(pkey, key, pkey, pkey).
event sendME1(bitstring, key, pkey).
event recvME1(bitstring, key, pkey).
event sendME2(bitstring, key, pkey).
event recvME2(bitstring, key, pkey).
event compromiseOSKA(skey).
event compromiseOSKB(skey).
event start().
free tag_oe1: bitstring [private].
free tag_oe2: bitstring [private].
free tag_me1: bitstring [private].
free tag_me2: bitstring [private].
free tag_b_eph: bitstring [private].
let PeerA(OSK_A: skey, OPK_A: pkey, OPK_B: pkey, OLM_KEYS_STR:bitstring, OLM_ROOT_STR: bitstring, MSK_A: skey, MPK_A: pkey) =
phase 1;
new ao: skey;
let gao = pk(ao) in
(* generate amaster and enc msg (PHASE 1) *)
(* in(c, gbo: pkey); *)
in(c, (gbo: pkey, gbo_sig: bitstring));
if checksign(OPK_B, rb(gbo), gbo_sig) = okay then
(* =================== PHASE 2: DERIVE OLM MASTER + MEGOLM SESSION A -> B =================== *)
let amaster = hkdf(concat4(dh(OPK_B, OSK_A), dh(gbo, OSK_A), dh(OPK_B, ao))) in
let ra1 = hkdf(concat0(amaster)) in
let ca1 = hkdf(concat0(amaster)) in
new ta1: skey;
let gta1 = pk(ta1) in
let ak1 = khash(ca1) in
let ak1_auth = hkdf(concat0(ak1)) in
let ak1_enc = hkdf(concat0(ak1)) in
new mra0: key; (* mra0 := megolm ratchet *)
let x1 = senc(ak1_enc, (MPK_A, mra0)) in
let x1_mac = mac(ak1_auth, concat1(x1, gao, gta1)) in
event sendOE1(MPK_A, mra0, gao, gta1);
out(c, (x1, x1_mac, gao, gta1));
(* =================== PHASE 3: MEGOLM SESH B -> A =================== *)
(* second stage: now, decrypt the received message from bob *)
in(c, (x2: bitstring, x2_mac: bitstring, gtb2: pkey));
let ca2 = hkdf(concat3(concat0(khash(ra1)), dh(gtb2, ta1))) in
let ak2 = khash(ca2) in
let ak2_auth = hkdf(concat0(ak2)) in
if checkmac(ak2_auth, concat2(x2, gtb2), x2_mac) = okay then (
let mak2_enc = hkdf(concat0(ak2)) in
let (MPK_B: pkey, mrb0: key) = sdec(mak2_enc, x2) in
event recvOE2(MPK_B, mrb0, gao, gta1);
(* =================== PHASE 4: MEGOLM MSG A -> B =================== *)
let mra1 = khash(mra0) in
let mak1_auth = hkdf(concat0(mra1)) in
let mak1_enc = hkdf(concat0(mra1)) in
let mx1 = senc(mak1_enc, m1) in
let mx1_mac = mac(mak1_auth, mx1) in
let mx1_sig = sign(MSK_A, concat3(mx1, mx1_mac)) in
event sendME1(m1, mra1, MPK_A);
out(c, (mx1, mx1_mac, mx1_sig));
(* =================== PHASE 5: MEGOLM MSG B -> A =================== *)
in(c, (mx2: bitstring, mx2_mac: bitstring, mx2_sig: bitstring));
let mrb1 = khash(mrb0) in
let mbk1_auth = hkdf(concat0(mrb1)) in
let mbk1_enc = hkdf(concat0(mrb1)) in
if checksign(MPK_B, concat3(mx2, mx2_mac), mx2_sig) = okay then
if checkmac(mbk1_auth, mx2, mx2_mac) = okay then
let m2 = sdec(mbk1_enc, mx2) in
event recvME2(m2, mrb1, MPK_B);
phase 3; (* =================== PHASE 6: PCS =================== *)
event compromiseOSKA(OSK_A);
out(c, OSK_A)
).
let PeerB(OSK_B: skey, OPK_B: pkey, OPK_A: pkey, OLM_KEYS_STR:bitstring, OLM_ROOT_STR: bitstring, MSK_B: skey, MPK_B: pkey) =
new bo: skey;
let gbo = pk(bo) in
let gbo_sig = sign(OSK_B, rb(gbo)) in
out(c, (gbo, gbo_sig));
phase 1;
(* =================== PHASE 2: DERIVE OLM MASTER + MEGOLM SESSION A -> B =================== *)
(* first stage: derive bmaster, verfiy a's msgs, decrypt prekey message, reply *)
in(c, (x1: bitstring, x1_mac: bitstring, gao: pkey, gta1: pkey));
let bmaster = hkdf(concat4(dh(OPK_A, OSK_B), dh(OPK_A, bo), dh(gao, OSK_B))) in
let rb1 = hkdf(concat0(bmaster)) in
let cb1 = hkdf(concat0(bmaster)) in
let bk1 = khash(cb1) in
let bk1_auth = hkdf(concat0(bk1)) in
let bk1_enc = hkdf(concat0(bk1)) in
if checkmac(bk1_auth, concat1(x1, gao, gta1), x1_mac) = okay then
(
let (MPK_A: pkey, mra0: key) = sdec(bk1_enc, x1) in
event recvOE1(MPK_A, mra0, gao, gta1);
(* =================== PHASE 3: MEGOLM SESH B -> A =================== *)
new tb2: skey;
let gtb2 = pk(tb2) in
new tb2: skey;
let gtb2 = pk(tb2) in
let rb2 = hkdf(concat3(concat0(khash(rb1)), dh(gta1, tb2))) in
let cb2 = hkdf(concat3(concat0(khash(rb1)), dh(gta1, tb2))) in
let bk2 = khash(cb2) in
let bk2_auth = hkdf(concat0(bk2)) in
let bk2_enc = hkdf(concat0(bk2)) in
new mrb0: key; (* mrb0 := megolm ratchet *)
let x2 = senc(bk2_enc, (MPK_B, mrb0)) in
let x2_mac = mac(bk2_auth, concat2(x2, gtb2)) in
event sendOE2(MPK_B, mrb0, gao, gta1);
out(c, (x2, x2_mac, gtb2));
(* =================== PHASE 4: MEGOLM MSG A -> B =================== *)
in(c, (mx1: bitstring, mx1_mac: bitstring, mx1_sig: bitstring));
let mra1 = khash(mra0) in
let mak1_auth = hkdf(concat0(mra1)) in
let mak1_enc = hkdf(concat0(mra1)) in
if checksign(MPK_A, concat3(mx1, mx1_mac), mx1_sig) = okay then (
if checkmac(mak1_auth, mx1, mx1_mac) = okay then (
let m1 = sdec(mak1_enc, mx1) in
event recvME1(m1, mra1, MPK_A);
phase 3;
event compromiseOSKB(OSK_B);
out(c, OSK_B);
(* =================== PHASE 5: MEGOLM MSG B -> A =================== *)
let mrb1 = khash(mrb0) in
let mbk1_auth = hkdf(concat0(mrb1)) in
let mbk1_enc = hkdf(concat0(mrb1)) in
let mx2 = senc(mbk1_enc, m2) in
let mx2_mac = mac(mbk1_auth, mx2) in
let mx2_sig = sign(MSK_B, concat3(mx2, mx2_mac)) in
event sendME2(m2, mrb1, MPK_B);
out(c, (mx2, mx2_mac, mx2_sig))
(* =================== PHASE 6: PCS =================== *)
))).
query event(start()). (* reachable from all possible executions *)
(* pre-compromise security, aka forward secrecy. the only way
m1 can be compromised is if alice's sk is compromised
NOTE: if signed, this is trivially true since m1 is never compromised
*)
query sk: skey; attacker(m1) ==> event(compromiseOSKB(sk)).
(* post-compromise security. even if the secret key is compromised, message two remains secret
*)
query sk1: skey, sk2: skey; (event(compromiseOSKB(sk1)) && event(compromiseOSKA(sk2)) && attacker(m1)) ==> false.
(* auth *)
query k1: pkey, rk: key, k2: pkey, k3: pkey; inj-event(recvOE1(k1, rk, k2, k3)) ==> inj-event(sendOE1(k1, rk, k2, k3)).
query k1: pkey, rk: key, k2: pkey, k3: pkey; inj-event(recvOE2(k1, rk, k2, k3)) ==> inj-event(sendOE2(k1, rk, k2, k3)).
query m: bitstring, rk: key, k1: pkey; inj-event(recvME1(m, rk, k1)) ==> inj-event(sendME1(m, rk, k1)).
query m: bitstring, rk: key, k1: pkey; inj-event(recvME2(m, rk, k1)) ==> inj-event(sendME2(m, rk, k1)).
(* secrecy *)
query attacker(m1).
query attacker(m2).
(* reachability *)
query k1: pkey, rk: key, k2: pkey, k3: pkey; event(sendOE1(k1,rk,k2,k3)).
query k1: pkey, rk: key, k2: pkey, k3: pkey; event(recvOE1(k1,rk,k2,k3)).
query k1: pkey, rk: key, k2: pkey, k3: pkey; event(sendOE2(k1,rk,k2,k3)).
query k1: pkey, rk: key, k2: pkey, k3: pkey; event(recvOE2(k1,rk,k2,k3)).
query m: bitstring, rk: key, k1: pkey; event(sendME1(m, rk, k1)).
query m: bitstring, rk: key, k1: pkey; event(recvME1(m, rk, k1)).
query m: bitstring, rk: key, k1: pkey; event(sendME2(m, rk, k1)).
query m: bitstring, rk: key, k1: pkey; event(recvME2(m, rk, k1)).
process
new OLM_KEYS_STR:bitstring; out(a, OLM_KEYS_STR);
new OLM_ROOT_STR:bitstring; out(a, OLM_ROOT_STR);
new OSK_A: skey; let OPK_A = pk(OSK_A) in
new OSK_B: skey; let OPK_B = pk(OSK_B) in
new MSK_A: skey; let MPK_A = pk(MSK_A) in
new MSK_B: skey; let MPK_B = pk(MSK_B) in
out(a, OPK_A);
out(a, OPK_B);
(*
out(a, MPK_A);
out(a, MPK_B);
*)
event start();
( (!PeerA(OSK_A, OPK_A, OPK_B, OLM_KEYS_STR, OLM_ROOT_STR, MSK_A, MPK_A)) |
(!PeerB(OSK_B, OPK_B, OPK_A, OLM_KEYS_STR, OLM_ROOT_STR, MSK_B, MPK_B)))

211
proverif/olm-unsigned.pv Normal file
View File

@@ -0,0 +1,211 @@
(*
Olm 3DH+Double Ratchet without olm signing;
Author: [redacted]
*)
free m1: bitstring [private].
free m2: bitstring [private].
set simpEqAll = false.
set selFun = Nounifset.
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 keys *)
type skey.
type pkey.
fun rb(pkey): bitstring.
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(key, key, key): key [data].
fun khash(key): key.
fun hkdf2_dev1(key): key.
fun hkdf2_dev2(key): key.
letfun hkdf2(k: key) =
(hkdf2_dev1(k), hkdf2_dev2(k)).
fun hkdf3_dev1(key, bitstring): key.
fun hkdf3_dev2(key, bitstring): key.
letfun hkdf3(k: key, b: bitstring) =
(hkdf3_dev1(k, b), hkdf3_dev2(k, b)).
fun hkdf4_dev1(key, key): key.
fun hkdf4_dev2(key, key): key.
letfun hkdf4(k1: key, k2: key) =
(hkdf4_dev1(k1, k2), hkdf4_dev2(k1, k2)).
(* the concats *)
fun concat1(bitstring, pkey, pkey): bitstring [data].
fun concat2(bitstring, pkey): bitstring [data].
(* events *)
event sendE1(bitstring, key, pkey, pkey).
event recvE1(bitstring, key, pkey, pkey).
event sendE2(bitstring, key, pkey, pkey).
event recvE2(bitstring, key, pkey, pkey).
event compromiseSKA(skey).
event compromiseSKB(skey).
event start().
free tag_oe1: bitstring [private].
free tag_oe2: bitstring [private].
free tag_me1: bitstring [private].
free tag_me2: bitstring [private].
free tag_b_eph: bitstring [private].
let PeerA(SK_A: skey, PK_A: pkey, PK_B: pkey, OLM_KEYS_STR:bitstring, OLM_ROOT_STR: bitstring) =
phase 1;
new ao: skey;
let gao = pk(ao) in
(* generate amaster and enc msg (PHASE 1) *)
in(c, (gbo: pkey));
let amaster = hkdf1(dh(PK_B, SK_A), dh(gbo, SK_A), dh(PK_B, ao)) in
let (ra1: key, ca1: key) = hkdf3(amaster, OLM_ROOT_STR) in (* derive the root and chain key *)
new ta1: skey;
let gta1 = pk(ta1) in
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, gao, gta1)) in
event sendE1(m1, mak1, gao, gta1);
out(c, (x1, x1_mac, gao, gta1));
(* second stage: now, decrypt the received message from bob *)
in(c, (x2: bitstring, x2_mac: bitstring, gtb2: pkey));
let (ra2: key, ca2: key) = hkdf4(ra1, dh(gtb2, ta1)) in
let mak2 = khash(ca2) in
let (mak2_auth: key, mak2_enc: key) = hkdf2(mak2) in
if checkmac(mak2_auth, concat2(x2, gtb2), x2_mac) = okay then
(
let m2 = sdec(mak2_enc, x2) in
event recvE2(m2, mak2, gta1, gtb2);
phase 2
).
let PeerB(SK_B: skey, PK_B: pkey, PK_A: pkey, OLM_KEYS_STR:bitstring, OLM_ROOT_STR: bitstring) =
new bo: skey;
let gbo = pk(bo) in
out(c, (gbo));
phase 1;
(* first stage: derive bmaster, verfiy a's msgs, decrypt prekey message, reply *)
in(c, (x1: bitstring, x1_mac: bitstring, gao: pkey, gta1: pkey));
let bmaster = hkdf1(dh(PK_A, SK_B), dh(PK_A, bo), dh(gao, SK_B)) in
let (rb1: key, cb1: key) = hkdf3(bmaster, OLM_ROOT_STR) 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, gao, gta1), x1_mac) = okay then
(
let m1 = sdec(mbk1_enc, x1) in
event recvE1(m1, mbk1, gao, gta1);
new tb2: skey;
let gtb2 = pk(tb2) in
let (rb2: key, cb2: key) = hkdf4(rb1, dh(gta1, 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, concat2(x2, gtb2)) in
event sendE2(m2, mbk2, gta1, gtb2);
out(c, (x2, x2_mac, gtb2));
phase 2;
event compromiseSKB(SK_B);
out(c, SK_B)
).
query event(start()). (* reachable from all possible executions *)
(* pre-compromise security, aka forward secrecy. the only way
m1 can be compromised is if alice's sk is compromised
NOTE: if signed, this is trivially true since m1 is never compromised
*)
query sk: skey; attacker(m1) ==> event(compromiseSKB(sk)).
(* post-compromise security. even if the secret key is compromised, message two remains secret
*)
query sk: skey; (event(compromiseSKB(sk)) && attacker(m2)) ==> false.
(* auth *)
query m: bitstring, rk: key, k1: pkey, k2: pkey; inj-event(recvE1(m, rk, k1, k2)) ==> inj-event(sendE1(m, rk, k1, k2)).
query m: bitstring, rk: key, k1: pkey, k2: pkey, k3: pkey, k4: pkey; inj-event(recvE2(m, rk, k1, k2)) ==> inj-event(sendE2(m, rk, k1, k2)).
(* secrecy *)
query attacker(m1).
query attacker(m2).
(* reachability *)
query m: bitstring, rk: key, k1: pkey, k2: pkey; event(recvE1(m, rk, k1, k2)). (* reachable from all executions *)
query m: bitstring, rk: key, k1: pkey, k2: pkey; event(recvE2(m, rk, k1, k2)). (* reachable from all executions *)
query m: bitstring, rk: key, k1: pkey, k2: pkey; event(sendE1(m, rk, k1, k2)). (* reachable from all executions *)
query m: bitstring, rk: key, k1: pkey, k2: pkey; event(sendE2(m, rk, k1, k2)). (* rechable from all executions *)
process
new OLM_KEYS_STR:bitstring; out(a, OLM_KEYS_STR);
new OLM_ROOT_STR:bitstring; out(a, OLM_ROOT_STR);
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, OLM_KEYS_STR, OLM_ROOT_STR)) |
(!PeerB(SK_B, PK_B, PK_A, OLM_KEYS_STR, OLM_ROOT_STR)))

220
proverif/olm.pv Normal file
View File

@@ -0,0 +1,220 @@
(*
Olm 3DH+Double Ratchet; proving authenticity, secrecy, forward secrecy, and post-compromise security
Author: [redacted]
model assumption #1: same key is used for signing and encryption (i.e. X25519)
*)
free m1: bitstring [private].
free m2: bitstring [private].
set simpEqAll = false.
set selFun = Nounifset.
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.
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 *)
(* the concat functions *)
fun hkdf1(key, key, key): key [data].
fun khash(key): key.
fun hkdf2_dev1(key): key.
fun hkdf2_dev2(key): key.
letfun hkdf2(k: key) =
(hkdf2_dev1(k), hkdf2_dev2(k)).
fun hkdf3_dev1(key, bitstring): key.
fun hkdf3_dev2(key, bitstring): key.
letfun hkdf3(k: key, b: bitstring) =
(hkdf3_dev1(k, b), hkdf3_dev2(k, b)).
fun hkdf4_dev1(key, key): key.
fun hkdf4_dev2(key, key): key.
letfun hkdf4(k1: key, k2: key) =
(hkdf4_dev1(k1, k2), hkdf4_dev2(k1, k2)).
(* the concats *)
fun concat1(bitstring, pkey, pkey): bitstring [data].
fun concat2(bitstring, pkey): bitstring [data].
(* events *)
event sendE1(bitstring, key, pkey, pkey).
event recvE1(bitstring, key, pkey, pkey).
event sendE2(bitstring, key, pkey, pkey).
event recvE2(bitstring, key, pkey, pkey).
event compromiseSKA(skey).
event compromiseSKB(skey).
event start().
free tag_oe1: bitstring [private].
free tag_oe2: bitstring [private].
free tag_me1: bitstring [private].
free tag_me2: bitstring [private].
free tag_b_eph: bitstring [private].
let PeerA(SK_A: skey, PK_A: pkey, PK_B: pkey, OLM_KEYS_STR:bitstring, OLM_ROOT_STR: bitstring) =
phase 1;
new ao: skey;
let gao = pk(ao) in
(* generate amaster and enc msg (PHASE 1) *)
(* in(c, gbo: pkey); *)
in(c, (gbo: pkey, gbo_sig: bitstring));
if checksign(PK_B, rb(gbo), gbo_sig) = okay then (
let amaster = hkdf1(dh(PK_B, SK_A), dh(gbo, SK_A), dh(PK_B, ao)) in
let (ra1: key, ca1: key) = hkdf3(amaster, OLM_ROOT_STR) in (* derive the root and chain key *)
new ta1: skey;
let gta1 = pk(ta1) in
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, gao, gta1)) in
event sendE1(m1, mak1, gao, gta1);
out(c, (x1, x1_mac, gao, gta1));
(* second stage: now, decrypt the received message from bob *)
in(c, (x2: bitstring, x2_mac: bitstring, gtb2: pkey));
let (ra2: key, ca2: key) = hkdf4(ra1, dh(gtb2, ta1)) in
let mak2 = khash(ca2) in
let (mak2_auth: key, mak2_enc: key) = hkdf2(mak2) in
if checkmac(mak2_auth, concat2(x2, gtb2), x2_mac) = okay then
(
let m2 = sdec(mak2_enc, x2) in
event recvE2(m2, mak2, gta1, gtb2);
phase 2
)).
let PeerB(SK_B: skey, PK_B: pkey, PK_A: pkey, OLM_KEYS_STR:bitstring, OLM_ROOT_STR: bitstring) =
new bo: skey;
let gbo = pk(bo) in
let gbo_sig = sign(SK_B, rb(gbo)) in
out(c, (gbo, gbo_sig));
phase 1;
(* first stage: derive bmaster, verfiy a's msgs, decrypt prekey message, reply *)
in(c, (x1: bitstring, x1_mac: bitstring, gao: pkey, gta1: pkey));
let bmaster = hkdf1(dh(PK_A, SK_B), dh(PK_A, bo), dh(gao, SK_B)) in
let (rb1: key, cb1: key) = hkdf3(bmaster, OLM_ROOT_STR) 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, gao, gta1), x1_mac) = okay then
(
let m1 = sdec(mbk1_enc, x1) in
event recvE1(m1, mbk1, gao, gta1);
new tb2: skey;
let gtb2 = pk(tb2) in
let (rb2: key, cb2: key) = hkdf4(rb1, dh(gta1, 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, concat2(x2, gtb2)) in
event sendE2(m2, mbk2, gta1, gtb2);
out(c, (x2, x2_mac, gtb2));
phase 2;
event compromiseSKB(SK_B);
out(c, SK_B)
).
query event(start()). (* reachable from all possible executions *)
(* pre-compromise security, aka forward secrecy. the only way
m1 can be compromised is if alice's sk is compromised
NOTE: if signed, this is trivially true since m1 is never compromised
*)
query sk: skey; attacker(m1) ==> event(compromiseSKB(sk)).
(* post-compromise security. even if the secret key is compromised, message two remains secret
*)
query sk: skey; (event(compromiseSKB(sk)) && attacker(m2)) ==> false.
(* auth *)
query m: bitstring, rk: key, k1: pkey, k2: pkey; inj-event(recvE1(m, rk, k1, k2)) ==> inj-event(sendE1(m, rk, k1, k2)).
query m: bitstring, rk: key, k1: pkey, k2: pkey, k3: pkey, k4: pkey; inj-event(recvE2(m, rk, k1, k2)) ==> inj-event(sendE2(m, rk, k1, k2)).
(* secrecy *)
query attacker(m1).
query attacker(m2).
(* reachability *)
query m: bitstring, rk: key, k1: pkey, k2: pkey; event(recvE1(m, rk, k1, k2)). (* reachable from all executions *)
query m: bitstring, rk: key, k1: pkey, k2: pkey; event(recvE2(m, rk, k1, k2)). (* reachable from all executions *)
query m: bitstring, rk: key, k1: pkey, k2: pkey; event(sendE1(m, rk, k1, k2)). (* reachable from all executions *)
query m: bitstring, rk: key, k1: pkey, k2: pkey; event(sendE2(m, rk, k1, k2)). (* rechable from all executions *)
process
new OLM_KEYS_STR:bitstring; out(a, OLM_KEYS_STR);
new OLM_ROOT_STR:bitstring; out(a, OLM_ROOT_STR);
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, OLM_KEYS_STR, OLM_ROOT_STR)) |
(!PeerB(SK_B, PK_B, PK_A, OLM_KEYS_STR, OLM_ROOT_STR)))

310
proverif/sender-keys.pv Normal file
View File

@@ -0,0 +1,310 @@
(*
Sender Keys protocol; proving secrecy, authentication, PCS, and 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): bitstring.
equation forall a: skey, b: skey; dh(pk(a), b) = dh(pk(b), a). (* symmetry of DH *)
(* the concat functions *)
fun khash(key): key.
fun hkdf(bitstring): key [data].
fun concat0(key): bitstring [data, typeConverter].
fun concat1(bitstring, pkey, pkey): bitstring [data].
reduc forall m1: bitstring, k1: pkey, k2: pkey; split1(concat1(m1, k1, k2)) = (m1, k1, k2).
fun concat2(bitstring, pkey): bitstring [data].
reduc forall m1: bitstring, k1: pkey; split2(concat2(m1, k1)) = (m1, k1).
fun concat3(bitstring, bitstring): bitstring [data].
reduc forall m1: bitstring, m2: bitstring; split3(concat3(m1, m2)) = (m1, m2).
fun concat4(bitstring, bitstring, bitstring): bitstring [data].
reduc forall m1: bitstring, m2: bitstring, m3:bitstring; split4(concat4(m1, m2, m3)) = (m1, m2, m3).
equation forall k: key, a: skey, b: skey; hkdf(concat3(concat0(khash(k)), dh(pk(a), b))) = hkdf(concat3(concat0(khash(k)), dh(pk(b), a))).
(* the concats *)
(* events *)
event sendOE1(pkey, key, pkey, pkey).
event recvOE1(pkey, key, pkey, pkey).
event sendOE2(pkey, key, pkey, pkey).
event recvOE2(pkey, key, pkey, pkey).
event sendME1(bitstring, key, pkey).
event recvME1(bitstring, key, pkey).
event sendME2(bitstring, key, pkey).
event recvME2(bitstring, key, pkey).
event compromiseOSKA(skey).
event compromiseOSKB(skey).
event start().
free tag_oe1: bitstring [private].
free tag_oe2: bitstring [private].
free tag_me1: bitstring [private].
free tag_me2: bitstring [private].
free tag_b_eph: bitstring [private].
let PeerA(OSK_A: skey, OPK_A: pkey, OPK_B: pkey, MSK_A: skey, MPK_A: pkey) =
phase 1;
new ao: skey;
let gao = pk(ao) in
(* generate amaster and enc msg (PHASE 1) *)
(* in(c, gbo: pkey); *)
in(c, (gbo: pkey, gbo_sig: bitstring));
if checksign(OPK_B, rb(gbo), gbo_sig) = okay then
(* =================== PHASE 2: DERIVE P2P MASTER + SENDER KEYS SESSION A -> B =================== *)
let amaster = hkdf(concat4(dh(OPK_B, OSK_A), dh(gbo, OSK_A), dh(OPK_B, ao))) in
let ra1 = hkdf(concat0(amaster)) in
let ca1 = hkdf(concat0(amaster)) in
new ta1: skey;
let gta1 = pk(ta1) in
let ak1 = khash(ca1) in
let ak1_auth = hkdf(concat0(ak1)) in
let ak1_enc = hkdf(concat0(ak1)) in
new mra0: key; (* mra0 := megolm ratchet *)
let x1 = senc(ak1_enc, (MPK_A, mra0)) in
let x1_mac = mac(ak1_auth, concat1(x1, gao, gta1)) in
event sendOE1(MPK_A, mra0, gao, gta1);
out(c, (x1, x1_mac, gao, gta1));
(* =================== PHASE 3: SENDER KEYS SESSION B -> A =================== *)
(* second stage: now, decrypt the received message from bob *)
in(c, (x2: bitstring, x2_mac: bitstring, gtb2: pkey));
let ca2 = hkdf(concat3(concat0(khash(ra1)), dh(gtb2, ta1))) in
let ak2 = khash(ca2) in
let ak2_auth = hkdf(concat0(ak2)) in
if checkmac(ak2_auth, concat2(x2, gtb2), x2_mac) = okay then (
let mak2_enc = hkdf(concat0(ak2)) in
let (MPK_B: pkey, mrb0: key) = sdec(mak2_enc, x2) in
event recvOE2(MPK_B, mrb0, gao, gta1);
(* =================== PHASE 4: SENDER KEYS MSG A -> B =================== *)
let mra1 = khash(mra0) in
let mak1_auth = hkdf(concat0(mra1)) in
let mak1_enc = hkdf(concat0(mra1)) in
let mx1 = senc(mak1_enc, m1) in
let mx1_sig = sign(MSK_A, mx1) in
event sendME1(m1, mra1, MPK_A);
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 = hkdf(concat0(mrb1)) in
let mbk1_enc = hkdf(concat0(mrb1)) in
if checksign(MPK_B, mx2, mx2_sig) = okay then
let m2 = sdec(mbk1_enc, mx2) in
event recvME2(m2, mrb1, MPK_B);
phase 3; (* =================== PHASE 6: PCS =================== *)
event compromiseOSKA(OSK_A);
out(c, OSK_A)
).
let PeerB(OSK_B: skey, OPK_B: pkey, OPK_A: pkey, MSK_B: skey, MPK_B: pkey) =
new bo: skey;
let gbo = pk(bo) in
let gbo_sig = sign(OSK_B, rb(gbo)) in
out(c, (gbo, gbo_sig));
phase 1;
(* =================== PHASE 2: DERIVE P2P MASTER + SENDER KEYS SESSION A -> B =================== *)
(* first stage: derive bmaster, verfiy a's msgs, decrypt prekey message, reply *)
in(c, (x1: bitstring, x1_mac: bitstring, gao: pkey, gta1: pkey));
let bmaster = hkdf(concat4(dh(OPK_A, OSK_B), dh(OPK_A, bo), dh(gao, OSK_B))) in
let rb1 = hkdf(concat0(bmaster)) in
let cb1 = hkdf(concat0(bmaster)) in
let bk1 = khash(cb1) in
let bk1_auth = hkdf(concat0(bk1)) in
let bk1_enc = hkdf(concat0(bk1)) in
if checkmac(bk1_auth, concat1(x1, gao, gta1), x1_mac) = okay then
(
let (MPK_A: pkey, mra0: key) = sdec(bk1_enc, x1) in
event recvOE1(MPK_A, mra0, gao, gta1);
(* =================== PHASE 3: SENDER KEYS SESH B -> A =================== *)
new tb2: skey;
let gtb2 = pk(tb2) in
new tb2: skey;
let gtb2 = pk(tb2) in
let rb2 = hkdf(concat3(concat0(khash(rb1)), dh(gta1, tb2))) in
let cb2 = hkdf(concat3(concat0(khash(rb1)), dh(gta1, tb2))) in
let bk2 = khash(cb2) in
let bk2_auth = hkdf(concat0(bk2)) in
let bk2_enc = hkdf(concat0(bk2)) in
new mrb0: key; (* mrb0 := sender keys ratchet *)
let x2 = senc(bk2_enc, (MPK_B, mrb0)) in
let x2_mac = mac(bk2_auth, concat2(x2, gtb2)) in
event sendOE2(MPK_B, mrb0, gao, gta1);
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 = hkdf(concat0(mra1)) in
let mak1_enc = hkdf(concat0(mra1)) in
if checksign(MPK_A, mx1, mx1_sig) = okay then (
let m1 = sdec(mak1_enc, mx1) in
event recvME1(m1, mra1, MPK_A);
phase 3;
event compromiseOSKB(OSK_B);
out(c, OSK_B);
(* =================== PHASE 5: SENDER KEYS MSG B -> A =================== *)
let mrb1 = khash(mrb0) in
let mbk1_auth = hkdf(concat0(mrb1)) in
let mbk1_enc = hkdf(concat0(mrb1)) in
let mx2 = senc(mbk1_enc, m2) in
let mx2_sig = sign(MSK_B, mx2) in
event sendME2(m2, mrb1, MPK_B);
out(c, (mx2, mx2_sig))
(* =================== PHASE 6: PCS =================== *)
)).
query event(start()). (* reachable from all possible executions *)
(* pre-compromise security, aka forward secrecy. the only way
m1 can be compromised is if alice's sk is compromised
NOTE: if signed, this is trivially true since m1 is never compromised
*)
query sk: skey; attacker(m1) ==> event(compromiseOSKB(sk)).
(* post-compromise security. even if the secret key is compromised, message two remains secret
*)
query sk1: skey, sk2: skey; (event(compromiseOSKB(sk1)) && event(compromiseOSKA(sk2)) && attacker(m1)) ==> false.
(* auth *)
query k1: pkey, rk: key, k2: pkey, k3: pkey; inj-event(recvOE1(k1, rk, k2, k3)) ==> inj-event(sendOE1(k1, rk, k2, k3)).
query k1: pkey, rk: key, k2: pkey, k3: pkey; inj-event(recvOE2(k1, rk, k2, k3)) ==> inj-event(sendOE2(k1, rk, k2, k3)).
query m: bitstring, rk: key, k1: pkey; inj-event(recvME1(m, rk, k1)) ==> inj-event(sendME1(m, rk, k1)).
query m: bitstring, rk: key, k1: pkey; inj-event(recvME2(m, rk, k1)) ==> inj-event(sendME2(m, rk, k1)).
(* secrecy *)
query attacker(m1).
query attacker(m2).
(* reachability *)
query k1: pkey, rk: key, k2: pkey, k3: pkey; event(sendOE1(k1,rk,k2,k3)).
query k1: pkey, rk: key, k2: pkey, k3: pkey; event(recvOE1(k1,rk,k2,k3)).
query k1: pkey, rk: key, k2: pkey, k3: pkey; event(sendOE2(k1,rk,k2,k3)).
query k1: pkey, rk: key, k2: pkey, k3: pkey; event(recvOE2(k1,rk,k2,k3)).
query m: bitstring, rk: key, k1: pkey; event(sendME1(m, rk, k1)).
query m: bitstring, rk: key, k1: pkey; event(recvME1(m, rk, k1)).
query m: bitstring, rk: key, k1: pkey; event(sendME2(m, rk, k1)).
query m: bitstring, rk: key, k1: pkey; event(recvME2(m, rk, k1)).
process
new OSK_A: skey; let OPK_A = pk(OSK_A) in
new OSK_B: skey; let OPK_B = pk(OSK_B) in
new MSK_A: skey; let MPK_A = pk(MSK_A) in
new MSK_B: skey; let MPK_B = pk(MSK_B) in
out(a, OPK_A);
out(a, OPK_B);
(*
out(a, MPK_A);
out(a, MPK_B);
*)
event start();
( (!PeerA(OSK_A, OPK_A, OPK_B, MSK_A, MPK_A)) |
(!PeerB(OSK_B, OPK_B, OPK_A, MSK_B, MPK_B)))

217
proverif/signal.pv Normal file
View File

@@ -0,0 +1,217 @@
(*
Signal X3DH+Double Ratchet; proving authenticity, secrecy, forward secrecy, and post-compromise security
Author: [redacted]
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 `x3dh.pv`
*)
free m1: bitstring [private].
free m2: bitstring [private].
set simpEqAll = false.
set selFun = Nounifset.
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.
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 *)
(* the concat functions *)
fun hkdf1(bitstring): key.
fun khash(key): key.
fun hkdf2_dev1(key): key.
fun hkdf2_dev2(key): key.
letfun hkdf2(k: key) =
(hkdf2_dev1(k), hkdf2_dev2(k)).
fun hkdf4_dev1(key, key): key.
fun hkdf4_dev2(key, key): key.
letfun hkdf4(k1: key, k2: key) =
(hkdf4_dev1(k1, k2), hkdf4_dev2(k1, k2)).
(* the concats *)
fun concat1(bitstring, pkey, pkey): bitstring [data].
fun concat2(bitstring, pkey): bitstring [data].
fun concat3(key, key, key, key): 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 start().
free tag_oe1: bitstring [private].
free tag_oe2: bitstring [private].
free tag_me1: bitstring [private].
free tag_me2: bitstring [private].
free tag_b_eph: bitstring [private].
let PeerA(SK_A: skey, PK_A: pkey, PK_B: pkey) =
phase 1;
new ao: skey;
new ae1: skey;
let gae1 = pk(ae1) in
(* generate amaster and enc msg (PHASE 1) *)
in(c, (gbssig: bitstring, gbs: pkey, gbo: pkey));
if checksign(PK_B, rb(gbs), gbssig) = okay then
let amaster = hkdf1(concat3(dh(gbs, SK_A), dh(PK_B, ae1), dh(gbs, ae1), dh(gbo, ae1))) in
let (ra1: key, ca1: key) = hkdf2(amaster) in (* derive the root and chain key *)
new ta1: skey;
let gta1 = pk(ta1) in
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, gta1, gae1)) in
event sendE1(m1, mak1);
out(c, (x1, x1_mac, gta1, gae1));
(* second stage: now, decrypt the received message from bob *)
in(c, (x2: bitstring, x2_mac: bitstring, gtb2: pkey));
let (ra2: key, ca2: key) = hkdf4(ra1, dh(gtb2, ta1)) in
let mak2 = khash(ca2) in
let (mak2_auth: key, mak2_enc: key) = hkdf2(mak2) in
if checkmac(mak2_auth, concat2(x2, gtb2), x2_mac) = okay then
let m2 = sdec(mak2_enc, x2) in
event recvE2(m2, mak2);
phase 2;
0.
let PeerB(SK_B: skey, PK_B: pkey, PK_A: pkey) =
new bo: skey;
new bs: skey;
let gbo = pk(bo) in
let gbs = pk(bs) in
let gbssig = sign(SK_B, rb(gbs)) in
out(c, (gbssig, gbs, gbo));
phase 1;
(* first stage: derive bmaster, verfiy a's msgs, decrypt prekey message, reply *)
in(c, (x1: bitstring, x1_mac: bitstring, gta1: pkey, gae1: pkey));
let bmaster = hkdf1(concat3(dh(PK_A, bs), dh(gae1, SK_B), dh(gae1, bs), dh(gae1, bo))) 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, gta1, gae1), x1_mac) = okay then
let m1 = sdec(mbk1_enc, x1) in
event recvE1(m1, mbk1);
new tb2: skey;
let gtb2 = pk(tb2) in
let (rb2: key, cb2: key) = hkdf4(rb1, dh(gta1, 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, concat2(x2, gtb2)) in
event sendE2(m2, mbk2);
out(c, (x2, x2_mac, gtb2));
phase 2;
event compromiseSKB(SK_B);
out(c, SK_B);
0.
query event(start()). (* reachable from all possible executions *)
(* pre-compromise security, aka forward secrecy. the only way
m1 can be compromised is if alice's sk is compromised
NOTE: if signed, this is trivially true since m1 is never compromised
*)
query sk: skey; attacker(m1) ==> event(compromiseSKB(sk)).
(* post-compromise security. even if the secret key is compromised, message two remains secret
*)
query sk: skey; (event(compromiseSKB(sk)) && attacker(m2)) ==> 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)).
(* secrecy *)
query attacker(m1).
query attacker(m2).
(* 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)))

174
proverif/x3dh.pv Normal file
View File

@@ -0,0 +1,174 @@
(*
X3DH; proving authenticity and secrecy
Author: [redacted]
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)).
(* the concats *)
fun concat1(bitstring, pkey): bitstring [data].
fun concat2(key, key, key, key): bitstring [data].
(* events *)
event sendE1(bitstring, key).
event recvE1(bitstring, key).
event compromiseSKA(skey).
event compromiseSKB(skey).
event start().
(*
equation forall a1: key, a2: key, a3: key, a4: key; hkdf1(concat2(a1,a2,a3,a4)) = hkdf1(concat2(a1,a2,a3,a4)).
*)
(*
k1: bs
k2: SK_A
k3: SK_B
k4: ae1
k5: bo
let amaster = hkdf1(concat2(dh(gbs, SK_A), dh(PK_B, ae1), dh(gbs, ae1), dh(gbo, ae1))) in
let bmaster = hkdf1(concat2(dh(PK_A, bs), dh(gae1, SK_B), dh(gae1, bs), dh(gae1, bo))) in
equation forall k1: skey, k2: skey, k3: skey, k4: skey, k5: skey; hkdf1(concat2(dh(pk(k1), k2),dh(pk(k3), k4),dh(pk(k1), k4),dh(pk(k5), k4))) = hkdf1(concat2(dh(pk(k2), k1),dh(pk(k4), k3),dh(pk(k4), k1),dh(pk(k4), k5))) [convergent].
*)
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));
if checksign(PK_B, rb(gbs), gbssig) = okay then
let amaster = hkdf1(concat2(dh(gbs, SK_A), dh(PK_B, ae1), dh(gbs, ae1), dh(gbo, ae1))) 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)) in
event sendE1(m1, mak1);
out(c, (x1, x1_mac, gae1));
phase 2;
0.
let PeerB(SK_B: skey, PK_B: pkey, PK_A: pkey) =
new bo: skey;
new bs: skey;
let gbs = pk(bs) in
let gbo = pk(bo) in
let gbssig = sign(SK_B, rb(gbs)) in
out(c, (gbssig, gbs, gbo));
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));
let bmaster = hkdf1(concat2(dh(PK_A, bs), dh(gae1, SK_B), dh(gae1, bs), dh(gae1, bo))) 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), 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 *)
query m: bitstring, rk: key; event(recvE1(m, rk)) ==> event(sendE1(m, rk)).
(* auth *)
(* secrecy *)
query attacker(m1).
(* 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)))

4
verifpal/README.md Normal file
View File

@@ -0,0 +1,4 @@
Models in this folder:
- `olm-fs-violation.vp`: demonstrating an explicit attack trace for Olm pre-key message secrecy violation in the case pre-keys remain unsigned by the responder
- `olm-fs`: demonstrating Olm with pre-key signing retains secrecy, authentication, and perfect forward secrecy
- `signal.vp`: A reference model from the original [Verifpal paper](https://eprint.iacr.org/2019/971.pdf). Attribution is at the header of the model.

View File

@@ -0,0 +1,121 @@
// Violation of forward secrecy without prekey signing
// closely follows: https://gitlab.matrix.org/matrix-org/olm/blob/master/docs/olm.md
// signage is discussed here: https://gitlab.matrix.org/matrix-org/olm/-/blob/master/docs/signing.md
attacker[active]
principal Bob[
// initialize private key, ot key
generates bob_private, bob_ot_private
bob_ot_public = G^bob_ot_private
bob_public = G^bob_private
]
Bob -> Alice: [bob_public], bob_ot_public
principal Alice[
// initialize private key, ot key
generates alice_private, alice_ot_private
alice_ot_public = G^alice_ot_private
alice_public = G^alice_private
knows public c0, c1, c2, c3, c4
k1a = bob_public^alice_private
k2a = bob_ot_public^alice_private
k3a = bob_public^alice_ot_private
// derive the master secret
sa = HASH(k1a, k2a, k3a, bob_public, alice_public)
// create the root and chain key from the 3DH'ed secret
ra1, ca1 = HKDF(sa, c1, c2)
// create ratchet key
generates ta1_private
ta1_public = G^ta1_private
ta1_sig = SIGN(alice_private, ta1_public)
// computing the first message key
mak1 = MAC(ca1, c3)
// ciphertext
generates ma1
xa1 = AEAD_ENC(mak1, ma1, CONCAT(alice_public, bob_public))
// signage not specified
xa1_sig = SIGN(alice_private, xa1)
]
// in the implementation we'd include a chain index, but since this is a model we do not
Alice -> Bob: [alice_public], alice_ot_public, ta1_public, ta1_sig, xa1, xa1_sig
principal Bob[
_ = SIGNVERIF(alice_public, xa1, xa1_sig)
_ = SIGNVERIF(alice_public, ta1_public, ta1_sig)?
k1b = alice_public^bob_private
k2b = alice_public^bob_ot_private
k3b = alice_ot_public^bob_private
// derive master
knows public c0, c1, c2, c3, c4
sb = HASH(k1b, k2b, k3b, bob_public, alice_public)
// derive root and chain
rb1, cb1 = HKDF(sb, c1, c2)
// create bob's initial message key
mbk1 = MAC(cb1, c3)
// decrypt
mb1 = AEAD_DEC(mbk1, xa1, CONCAT(alice_public, bob_public))
// now, bob wants to send a message back and advances the ratchet
// generate new ratchet key
generates tb2_private
tb2_public = G^tb2_private
tb2_sig = SIGN(bob_private, tb2_public)
// advance the ratchet using prev root key, other ratchet stuff
rb2, cb2 = HKDF(MAC(rb1, c3), ta1_public^tb2_private, c3)
// create message key
mbk2 = MAC(cb2, c3)
// ciphertext
generates mb2
xb2 = AEAD_ENC(mbk2, mb2, CONCAT(alice_public, bob_public))
// signage not specified
xb2_sig = SIGN(bob_private, xb2)
]
Bob -> Alice: xb2, xb2_sig, tb2_public, tb2_sig
principal Alice[
_ = SIGNVERIF(bob_public, xb2, xb2_sig)?
_ = SIGNVERIF(bob_public, tb2_public, tb2_sig)?
// derive new root key
ra2, ca2 = HKDF(MAC(ra1, c3), tb2_public^ta1_private, c3)
// derive new message key from the chain key
mak2 = MAC(ca2, c3)
// decrypt
ma2 = AEAD_DEC(mak2, xb2, CONCAT(alice_public, bob_public))
]
// phase[1]
principal Bob[leaks bob_private]
// principal Alice[leaks alice_private]
// first two queries violate, while the second two do not
// first two queries take a few seconds to violate, while the second two
// take a full exploration to confirm
queries[
// confidentiality? ma1
// authentication? Alice -> Bob: xa1
confidentiality? ma2
// authentication? Bob -> Alice: xb2
]

116
verifpal/olm-fs.vp Normal file
View File

@@ -0,0 +1,116 @@
// proving authentication, confidentiality, and forward secrecy of Olm.
// closely follows: https://gitlab.matrix.org/matrix-org/olm/blob/master/docs/olm.md
// signage is discussed here: https://gitlab.matrix.org/matrix-org/olm/-/blob/master/docs/signing.md
attacker[active]
principal Bob[
// initialize private key, ot key
generates bob_private, bob_ot_private
bob_ot_public = G^bob_ot_private
bob_public = G^bob_private
bob_ot_sig = SIGN(bob_private, bob_ot_public) // OPTIONAL
]
Bob -> Alice: [bob_public], bob_ot_public, bob_ot_sig
principal Alice[
// initialize private key, ot key
generates alice_private, alice_ot_private
alice_ot_public = G^alice_ot_private
alice_public = G^alice_private
alice_ot_sig = SIGN(alice_private, alice_ot_public) // OPTIONAL
knows public c0, c1, c2, c3, c4
_ = SIGNVERIF(bob_public, bob_ot_public, bob_ot_sig)?
k1a = bob_public^alice_private
k2a = bob_ot_public^alice_private
k3a = bob_public^alice_ot_private
// derive the master secret
sa = HASH(k1a, k2a, k3a, bob_public, alice_public)
// create the root and chain key from the 3DH'ed secret
ra1, ca1 = HKDF(sa, c1, c2)
// create ratchet key
generates ta1_private
ta1_public = G^ta1_private
ta1_sig = SIGN(alice_private, ta1_public)
// computing the first message key
mak1 = MAC(ca1, c3)
// ciphertext
generates ma1
xa1 = AEAD_ENC(mak1, ma1, CONCAT(alice_public, bob_public))
// signage not specified
xa1_sig = SIGN(alice_private, xa1)
]
// in the implementation we'd include a chain index, but since this is a model we do not
Alice -> Bob: [alice_public], alice_ot_public, alice_ot_sig, ta1_public, ta1_sig, xa1, xa1_sig
principal Bob[
_ = SIGNVERIF(alice_public, xa1, xa1_sig)
_ = SIGNVERIF(alice_public, alice_ot_public, alice_ot_sig)?
_ = SIGNVERIF(alice_public, ta1_public, ta1_sig)?
k1b = alice_public^bob_private
k2b = alice_public^bob_ot_private
k3b = alice_ot_public^bob_private
// derive master
knows public c0, c1, c2, c3, c4
sb = HASH(k1b, k2b, k3b, bob_public, alice_public)
// derive root and chain
rb1, cb1 = HKDF(sb, c1, c2)
// create bob's initial message key
mbk1 = MAC(cb1, c3)
// decrypt
mb1 = AEAD_DEC(mbk1, xa1, CONCAT(alice_public, bob_public))
// now, bob wants to send a message back and advances the ratchet
// generate new ratchet key
generates tb2_private
tb2_public = G^tb2_private
tb2_sig = SIGN(bob_private, tb2_public)
// advance the ratchet using prev root key, other ratchet stuff
rb2, cb2 = HKDF(rb1, ta1_public^tb2_private, c3)
// create message key
mbk2 = MAC(cb2, c3)
// ciphertext
generates mb2
xb2 = AEAD_ENC(mbk2, mb2, CONCAT(alice_public, bob_public))
]
Bob -> Alice: xb2, tb2_public
principal Alice[
// derive new root key
ra2, ca2 = HKDF(ra1, tb2_public^ta1_private, c3)
// derive new message key from the chain key
mak2 = MAC(ca2, c3)
// decrypt
ma2 = AEAD_DEC(mak2, xb2, CONCAT(alice_public, bob_public))
]
principal Bob[leaks bob_private]
// all four queries verify
queries[
confidentiality? ma1
authentication? Alice -> Bob: xa1
confidentiality? ma2
authentication? Bob -> Alice: xb2
]

102
verifpal/signal.vp Normal file
View File

@@ -0,0 +1,102 @@
// SPDX-FileCopyrightText: © 2019-2022 Nadim Kobeissi <nadim@symbolic.software>
// SPDX-License-Identifier: GPL-3.0-only
// (included in our artifact as a reference model)
attacker[active]
principal Alice[
knows private alongterm
galongterm = G^alongterm
]
principal Bob[
knows private blongterm, bs
generates bo
gblongterm = G^blongterm
gbs = G^bs
gbo = G^bo
gbssig = SIGN(blongterm, gbs)
]
Bob -> Alice: [gblongterm], gbssig, gbs, gbo
principal Alice[
generates ae1
gae1 = G^ae1
amaster = HASH(gbs^alongterm, gblongterm^ae1, gbs^ae1, gbo^ae1)
arkba1, ackba1 = HKDF(amaster, nil, nil)
]
principal Alice[
generates m1, ae2
gae2 = G^ae2
_ = SIGNVERIF(gblongterm, gbs, gbssig)?
akshared1 = gbs^ae2
arkab1, ackab1 = HKDF(akshared1, arkba1, nil)
akenc1, _ = HKDF(MAC(ackab1, nil), nil, nil)
e1 = AEAD_ENC(akenc1, m1, HASH(galongterm, gblongterm, gae2))
]
Alice -> Bob: [galongterm], gae1, gae2, e1
principal Bob[
bmaster = HASH(galongterm^bs, gae1^blongterm, gae1^bs, gae1^bo)
brkba1, bckba1 = HKDF(bmaster, nil, nil)
]
principal Bob[
bkshared1 = gae2^bs
brkab1, bckab1 = HKDF(bkshared1, brkba1, nil)
bkenc1, bkenc2 = HKDF(MAC(bckab1, nil), nil, nil)
m1_d = AEAD_DEC(bkenc1, e1, HASH(galongterm, gblongterm, gae2))
]
principal Bob[
generates m2, be
gbe = G^be
bkshared2 = gae2^be
brkba2, bckba2 = HKDF(bkshared2, brkab1, nil)
bkenc3, bkenc4 = HKDF(MAC(bckba2, nil), nil, nil)
e2 = AEAD_ENC(bkenc3, m2, HASH(gblongterm, galongterm, gbe))
]
Bob -> Alice: gbe, e2
principal Alice[
akshared2 = gbe^ae2
arkba2, ackba2 = HKDF(akshared2, arkab1, nil)
akenc3, akenc4 = HKDF(MAC(ackba2, nil), nil, nil)
m2_d = AEAD_DEC(akenc3, e2, HASH(gblongterm, galongterm, gbe))
]
// principal Alice[
// generates m3, ae3
// gae3 = G^ae3
// akshared3 = gbe^ae3
// arkab3, ackab3 = HKDF(akshared3, arkba2, nil)
// akenc5, akenc6 = HKDF(MAC(ackab3, nil), nil, nil)
// e3 = AEAD_ENC(akenc5, m3, HASH(gblongterm, galongterm, gae3))
// ]
// Alice -> Bob: gae3, e3
// principal Bob[
// bkshared3 = gae3^be
// brkab3, bckab3 = HKDF(bkshared3, brkba2, nil)
// bkenc5, bkenc6 = HKDF(MAC(bckab3, nil), nil, nil)
// m3_d = AEAD_DEC(bkenc5, e3, HASH(gblongterm, galongterm, gae3))
// ]
phase[1]
principal Alice[leaks alongterm]
principal Bob[leaks blongterm]
queries[
confidentiality? m1
authentication? Alice -> Bob: e1
confidentiality? m2
authentication? Bob -> Alice: e2
// confidentiality? m3
// authentication? Alice -> Bob: e3
]