init
This commit is contained in:
1
.direnv/flake-profile
Symbolic link
1
.direnv/flake-profile
Symbolic link
@@ -0,0 +1 @@
|
||||
flake-profile-1-link
|
||||
1
.direnv/flake-profile-1-link
Symbolic link
1
.direnv/flake-profile-1-link
Symbolic link
@@ -0,0 +1 @@
|
||||
/nix/store/5rjiqs4b1fqs5nzwwyfwi0dvk4z6pzbd-nix-shell-env
|
||||
35
README.md
Normal file
35
README.md
Normal 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
61
flake.lock
generated
Normal 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
31
flake.nix
Normal 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
153
proverif/3dh.pv
Normal 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)))
|
||||
150
proverif/deniability/3dh-initiator-deny.pv
Normal file
150
proverif/deniability/3dh-initiator-deny.pv
Normal 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 *)
|
||||
167
proverif/deniability/3dh-rep-nodeny.pv
Normal file
167
proverif/deniability/3dh-rep-nodeny.pv
Normal 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))
|
||||
135
proverif/deniability/fan-out-deny.pv
Normal file
135
proverif/deniability/fan-out-deny.pv
Normal 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()))
|
||||
|
||||
232
proverif/deniability/megolm-initiator-deny.pv
Normal file
232
proverif/deniability/megolm-initiator-deny.pv
Normal 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)))
|
||||
232
proverif/deniability/megolm-resp-nodeny.pv
Normal file
232
proverif/deniability/megolm-resp-nodeny.pv
Normal 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))))
|
||||
203
proverif/deniability/olm-initiator-deny.pv
Normal file
203
proverif/deniability/olm-initiator-deny.pv
Normal 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 *)
|
||||
204
proverif/deniability/olm-resp-nodeny.pv
Normal file
204
proverif/deniability/olm-resp-nodeny.pv
Normal 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 *)
|
||||
281
proverif/deniability/sender-keys-initiator-deny.pv
Normal file
281
proverif/deniability/sender-keys-initiator-deny.pv
Normal 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)))
|
||||
|
||||
281
proverif/deniability/sender-keys-resp-nodeny.pv
Normal file
281
proverif/deniability/sender-keys-resp-nodeny.pv
Normal 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))))
|
||||
|
||||
194
proverif/deniability/signal-initiator-deny.pv
Normal file
194
proverif/deniability/signal-initiator-deny.pv
Normal 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)))
|
||||
193
proverif/deniability/signal-resp-nodeny.pv
Normal file
193
proverif/deniability/signal-resp-nodeny.pv
Normal 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)))
|
||||
168
proverif/deniability/x3dh-initiator-deny.pv
Normal file
168
proverif/deniability/x3dh-initiator-deny.pv
Normal 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 *)
|
||||
168
proverif/deniability/x3dh-resp-nodeny.pv
Normal file
168
proverif/deniability/x3dh-resp-nodeny.pv
Normal 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))
|
||||
249
proverif/megolm-olm-unsigned.pv
Normal file
249
proverif/megolm-olm-unsigned.pv
Normal 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
319
proverif/megolm.pv
Normal 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
211
proverif/olm-unsigned.pv
Normal 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
220
proverif/olm.pv
Normal 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
310
proverif/sender-keys.pv
Normal 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
217
proverif/signal.pv
Normal 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
174
proverif/x3dh.pv
Normal 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
4
verifpal/README.md
Normal 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.
|
||||
121
verifpal/olm-fs-violation.vp
Normal file
121
verifpal/olm-fs-violation.vp
Normal 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
116
verifpal/olm-fs.vp
Normal 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
102
verifpal/signal.vp
Normal 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
|
||||
]
|
||||
Reference in New Issue
Block a user