add proverif analysis of Rosenpass, the protocol

The analysis was conducted as joint effort between @koraa and @blipp.

Co-authored-by: Benjamin Lipp <blipp@mailbox.org>
This commit is contained in:
Karolin Varner
2023-02-23 20:18:35 +01:00
committed by wucke13
parent 2a917de6d8
commit 137cd5e85a
21 changed files with 1242 additions and 0 deletions

View File

View File

@@ -0,0 +1,192 @@
#pragma once
#include "prelude/basic.mpv"
#include "prelude/bits.mpv"
#include "crypto/key.mpv"
#include "crypto/kem.mpv"
#include "crypto/aead.mpv"
#include "rosenpass/prf.mpv"
type SessionId.
const sid0:SessionId.
letfun sid_new() =
new sid:SessionId;
sid.
fun sid2b(SessionId) : bits [typeConverter].
type Role.
const ini_role:Role.
const res_role:Role.
type Handshake_t.
fun Handshake(
Role, // role
key, // biscuit_key
kem_sk, // sskm
kem_pk, // spkm
key, // psk
kem_pk, // spkt
SessionId, // sidm (session id of mine)
SessionId, // sidt (session id of theirs)
key, // ck
kem_sk, // eski
kem_pk // epki
) : Handshake_t [data].
#define decl_hs() \
biscuit_key <- key0; \
sskm <- kem_sk0; \
spkm <- kem_pk0; \
psk <- key0; \
spkt <- kem_pk0; \
sidm <- sid0; \
sidt <- sid0; \
ck <- key0; \
eski <- ccakem_sk0; \
epki <- ccakem_pk0
#define HS_DECL_ARGS \
biscuit_key:key, \
sskm:kem_sk, \
spkm:kem_pk, \
psk:key, \
spkt:kem_pk, \
sidm:SessionId, \
sidt:SessionId, \
ck:key, \
eski:kem_sk, \
epki:kem_pk
#define HS_PASS_ARGS \
biscuit_key, \
sskm, \
spkm, \
psk, \
spkt, \
sidm, \
sidt, \
ck, \
eski, \
epki
#define hs Handshake(role, biscuit_key, sskm, spkm, psk, spkt, sidm, sidt, ck, eski, epki)
#define is_ini role(hs) = ini_role.
#define is_res role(hs) = res_role.
// peer id
#ifdef SIMPLE_MODEL
fun peerid(kem_pk) : bits.
#else
letfun peerid(pk:kem_pk) =
k2b(lprf1(PEER_ID, kem_pk2b(pk))).
#endif
#define pidm peerid(spkm)
#define pidt peerid(spkt)
#define LOOKUP_SENDER(pid) \
ASSERT(pidt = (pid));
// Handshake processing functions
#ifdef SIMPLE_MODEL
fun ck_mix(key, bits) : key.
fun ck_hs_enc(key) : key.
fun ck_osk(key) : key.
#endif
#ifdef SIMPLE_MODEL
#define MIX(ikm) ck <- ck_mix(ck, ikm);
#else
#define MIX(ikm) ck <- prf(extract_key(MIX), ikm).
#endif
#define EXTRACT_KEY(l) prf(ck, k2b(lprf1(CK_EXTRACT, l)));
#define EXTRACT_KEY2(a, b) prf(ck, k2b(lprf2(CK_EXTRACT, a, b)));
#define EXPORT_KEY(l) EXTRACT_KEY2(USER, l)
#define MIX2(a, b) MIX(a) MIX(b)
#define MIX3(a, b, c) MIX(a) MIX2(b, c)
#ifdef SIMPLE_MODEL
#define hs_enc ck_hs_enc(ck)
#define osk ck_osk(ck)
#else
#define hs_enc EXTRACT_KEY(HS_ENC)
#define osk EXPORT_KEY(OSK)
#endif
(* PERFORMANCE: This leads to exponential expression tree sizes because
it updates the hash to contain itself twice: Once as part of the hash
chain and once through the AEAD cipher text.
As a fix, the hash of the AEAD cipher text is used itself to generate
the hash chain. This improves performance reducing runtime from ~60s to ~35.
Whether this is actually a good idea remains to be debated. *)
#ifdef SIMPLE_MODEL
#define ENCRYPT_AND_MIX(ct, pt) \
ct <- aead_enc(hs_enc, pt); \
MIX(pt)
#define DECRYPT_AND_MIX(pt, ct) \
pt <- aead_dec(hs_enc, ct); \
MIX(pt)
#else
letfun ENCRYPT_AND_MIX(ct, pt) \
AEAD_ENC(ct, hs_enc, 0, pt, empty) \
MIX(ct)
#define DECRYPT_AND_MIX(pt, ct) \
AEAD_DEC(pt, hs_enc, 0, ct, empty)
MIX(ct)
#endif
// TODO: Migrate kems to use binary ciphertexts directly
#define ENCAPS_AND_MIX(ct, pk, shk) \
ct <- kem_enc(pk, shk); \
MIX3(kem_pk2b(pk), ct, k2b(shk))
#define DECAPS_AND_MIX(sk, pk, ct) \
DUMMY(shk) <- kem_dec(sk, ct); \
MIX3(kem_pk2b(pk), ct, k2b(DUMMY(shk)))
// biscuits
/*
Biscuit replay protection is handled differently
in the model than in the specification; the specification
uses a nonce counter; the model uses a biscuit id
the adversary and stores a table of all used nonces.
This technique is used because modeling state updates in proverif
is possible but inefficient.
*/
type Biscuit_t.
fun Biscuit(
bits, // pidi
Atom, // no
key // ck
) : Biscuit_t [data].
fun Biscuit2b(Biscuit_t) : bitstring [typeConverter].
#define BiscuitBits(pidi, no, ck) Biscuit2b(Biscuit(pidi, no, ck))
#ifdef SIMPLE_MODEL
fun biscuit_ad(kem_pk, SessionId, SessionId) : bits.
#else
letfun biscuit_ad(spkr:kem_pk, sidi:SessionId, sidr:SessionId) =
k2b(lprf3(BISCUIT_AD, kem_pk2b(spkr), sid2b(sidi), sid2b(sidr))).
#endif
#define STORE_BISCUIT(ct) \
ct <- xaead_enc(biscuit_key, \
/* pt */ BiscuitBits(pidi, biscuit_no, ck), \
/* ad */ biscuit_ad(spkr, sidi, sidr)); \
MIX(ct)
#define LOAD_BISCUIT(nonce, ct) \
let BiscuitBits(DUMMY(pid), nonce, ck) = \
xaead_dec(biscuit_key, ct, \
/* ad */ biscuit_ad(spkr, sidi, sidr)) in \
MIX(ct) \
LOOKUP_SENDER(DUMMY(pid))

View File

@@ -0,0 +1,12 @@
#include "rosenpass/undef.macro"
#define role ini_role
#define sski sskm
#define spki spkm
#define sskr kem_sk0
#define spkr spkt
#define eskm eski
#define epkm epki
#define sidi sidm
#define sidr sidt
#define pidi pidm
#define pidr pidt

View File

@@ -0,0 +1,227 @@
#pragma once
#include "rosenpass/prf.mpv"
#include "rosenpass/handshake_state.mpv"
#include "rosenpass/protocol.mpv"
DECL_SETUP(key)
DECL_SETUP(kem_sk)
#define kem_pk_tmpl kem_sk_tmpl
letfun setup_kem_pk(sks:kem_pk_tmpl) =
kem_pub(setup_kem_sk(sks)).
#define SETUP_KEM_PAIR(sk, pk, setup) \
sk <- setup_kem_sk(setup); \
pk <- kem_pub(sk);
// TODO: Model use of multiple shared keys
// TODO: Hide shk inside the kem abstraction?
fun biscuit_key(kem_sk) : key [private].
#define SETUP_SERVER(biscuit_key, sk, pk, setup) \
SETUP_KEM_PAIR(sk, pk, setup) \
biscuit_key <- biscuit_key(sk);
#define SETUP_HANDSHAKE_STATE() \
SETUP_SERVER(biscuit_key, sskm, spkm, Ssskm) \
psk <- setup_key(Spsk); \
spkt <- setup_kem_pk(Sspkt);
type seed.
fun rng_key(seed) : key.
fun rng_kem_sk(seed) : kem_sk.
DECL_SETUP(seed)
#define RNG_KEM_PAIR(sk, pk, setup) \
sk <- rng_kem_sk(setup_seed(setup)); \
pk <- kem_pub(sk);
event ConsumeSeed(Atom, seed, Atom).
const Sptr, Epti, Spti, Eski:Atom.
restriction s:seed, p1:Atom, p2:Atom, ad1:Atom, ad2:Atom;
event(ConsumeSeed(p1, s, ad1)) && event(ConsumeSeed(p2, s, ad2))
==> p1 = p2 && ad1 = ad2.
#include "rosenpass/responder.macro"
fun Cinit_conf(kem_sk_tmpl, key_tmpl, kem_pk_tmpl, InitConf_t) : Atom [data].
CK_EV( event OskOinit_conf(key, key). )
MTX_EV( event ICRjct(InitConf_t, key, kem_sk, kem_pk). )
SES_EV( event ResponderSession(InitConf_t, key). )
event ConsumeBiscuit(Atom, kem_sk, kem_pk, Atom).
let Oinit_conf() =
in(C, Cinit_conf(Ssskm, Spsk, Sspkt, ic));
#if RANDOMIZED_CALL_IDS
new call:Atom;
#else
call <- Cinit_conf(Ssskm, Spsk, Sspkt, ic);
#endif
SETUP_HANDSHAKE_STATE()
eski <- kem_sk0;
epki <- kem_pk0;
let try_ = (
INITCONF_CONSUME()
event ConsumeBiscuit(biscuit_no, sskm, spkt, call);
CK_EV( event OskOinit_conf(ck_rh, osk); )
SES_EV( event ResponderSession(ic, osk); )
0
) in (
NOP
) else (
#if MESSAGE_TRANSMISSION_EVENTS
MTX_EV( event ICRjct(ic, psk, sskr, spki) )
#else
0
#endif
).
restriction biscuit_no:Atom, sskm:kem_sk, spkr:kem_pk, ad1:Atom, ad2:Atom;
event(ConsumeBiscuit(biscuit_no, sskm, spkr, ad1)) && event(ConsumeBiscuit(biscuit_no, sskm, spkr, ad2))
==> ad1 = ad2.
// TODO: Restriction biscuit no invalidation
#include "rosenpass/initiator.macro"
fun Cresp_hello(RespHello_t) : Atom [data].
CK_EV( event OskOresp_hello(key, key, key). )
MTX_EV( event RHRjct(RespHello_t, key, kem_sk, kem_pk). )
MTX_EV( event ICSent(RespHello_t, InitConf_t, key, kem_sk, kem_pk). )
SES_EV( event InitiatorSession(RespHello_t, key). )
let Oresp_hello(HS_DECL_ARGS) =
in(C, Cresp_hello(RespHello(sidr, =sidi, ecti, scti, biscuit, auth)));
rh <- RespHello(sidr, sidi, ecti, scti, biscuit, auth);
/* try */ let ic = (
ck_ini <- ck;
RESPHELLO_CONSUME()
ck_ih <- ck;
INITCONF_PRODUCE()
CK_EV (event OskOresp_hello(ck_ini, ck_ih, osk); ) // TODO: Queries testing that there is no duplication
MTX_EV( event ICSent(rh, ic, psk, sski, spkr); )
SES_EV( event InitiatorSession(rh, osk); )
ic
/* success */ ) in (
out(C, ic)
/* fail */ ) else (
#if MESSAGE_TRANSMISSION_EVENTS
event RHRjct(rh, psk, sski, spkr)
#else
0
#endif
).
// TODO: Restriction: Biscuit no invalidation
#include "rosenpass/responder.macro"
fun Cinit_hello(SessionId, Atom, kem_sk_tmpl, key_tmpl, kem_pk_tmpl, seed_tmpl, seed_tmpl, InitHello_t) : Atom [data].
CK_EV( event OskOinit_hello(key, key, key, kem_sk, kem_pk, kem_pk, key, key). )
MTX_EV( event IHRjct(InitHello_t, key, kem_sk, kem_pk). )
MTX_EV( event RHSent(InitHello_t, RespHello_t, key, kem_sk, kem_pk). )
event ConsumeSidr(SessionId, Atom).
event ConsumeBn(Atom, kem_sk, kem_pk, Atom).
let Oinit_hello() =
in(C, Cinit_hello(sidr, biscuit_no, Ssskm, Spsk, Sspkt, Septi, Sspti, ih));
#if RANDOMIZED_CALL_IDS
new call:Atom;
#else
call <- Cinit_hello(sidr, biscuit_no, Ssskm, Spsk, Sspkt, Septi, Sspti, ih);
#endif
// TODO: This is ugly
let InitHello(sidi, epki, sctr, pidiC, auth) = ih in
SETUP_HANDSHAKE_STATE()
eski <- kem_sk0;
epti <- rng_key(setup_seed(Septi)); // RHR4
spti <- rng_key(setup_seed(Sspti)); // RHR5
event ConsumeBn(biscuit_no, sskm, spkt, call);
event ConsumeSidr(sidr, call);
event ConsumeSeed(Epti, setup_seed(Septi), call);
event ConsumeSeed(Spti, setup_seed(Sspti), call);
let rh = (
INITHELLO_CONSUME()
ck_ini <- ck;
RESPHELLO_PRODUCE()
CK_EV( event OskOinit_hello(ck_ini, ck, psk, sskr, spki, epki, epti, spti); ) // TODO: Queries testing that there is no duplication
MTX_EV( event RHSent(ih, rh, psk, sskr, spki); )
rh
/* success */ ) in (
out(C, rh)
/* fail */ ) else (
#if MESSAGE_TRANSMISSION_EVENTS
event IHRjct(ih, psk, sskr, spki)
#else
0
#endif
).
restriction sid:SessionId, ad1:Atom, ad2:Atom;
event(ConsumeSidr(sid, ad1)) && event(ConsumeSidr(sid, ad2))
==> ad1 = ad2.
restriction biscuit_no:Atom, sskm:kem_sk, spkr:kem_pk, ad1:Atom, ad2:Atom;
event(ConsumeBn(biscuit_no, sskm, spkr, ad1)) && event(ConsumeBn(biscuit_no, sskm, spkr, ad2))
==> ad1 = ad2.
// TODO: Restriction: Attacker may not reuse session ids
// TODO: Restriction: Attacker may not reuse biscuit no
#include "rosenpass/initiator.macro"
fun Cinitiator(SessionId, kem_sk_tmpl, key_tmpl, kem_pk_tmpl, seed_tmpl, seed_tmpl) : Atom [data].
CK_EV( event OskOinitiator_ck(key). )
CK_EV( event OskOinitiator(key, key, kem_sk, kem_pk, key). )
MTX_EV( event IHSent(InitHello_t, key, kem_sk, kem_pk). )
event ConsumeSidi(SessionId, Atom).
let Oinitiator() =
in(C, Cinitiator(sidi, Ssskm, Spsk, Sspkt, Seski, Ssptr));
#if RANDOMIZED_CALL_IDS
new call:Atom;
#else
call <- Cinitiator(sidi, Ssskm, Spsk, Sspkt, Seski, Ssptr);
#endif
SETUP_HANDSHAKE_STATE()
RNG_KEM_PAIR(eski, epki, Seski) // IHI3
sidr <- sid0;
sptr <- rng_key(setup_seed(Ssptr)); // IHI5
event ConsumeSidi(sidi, call);
event ConsumeSeed(Sptr, setup_seed(Ssptr), call);
event ConsumeSeed(Eski, setup_seed(Seski), call);
INITHELLO_PRODUCE()
CK_EV( event OskOinitiator_ck(ck); )
CK_EV( event OskOinitiator(ck, psk, sski, spkr, sptr); )
MTX_EV( event IHSent(ih, psk, sski, spkr); )
out(C, ih);
Oresp_hello(HS_PASS_ARGS).
restriction sid:SessionId, ad1:Atom, ad2:Atom;
event(ConsumeSidi(sid, ad1)) && event(ConsumeSidi(sid, ad2))
==> ad1 = ad2.
// TODO: Should this be modeled without an oracle
fun Creveal_kem_pk(kem_sk_tmpl) : Atom [data].
event RevealPk(kem_pk).
let Oreveal_kem_pk =
in(C, Creveal_kem_pk(Spk));
pk <- setup_kem_pk(Spk);
event RevealPk(pk);
out(C, pk).
let rosenpass_main() = 0
| !Oreveal_kem_pk
| REP(INITIATOR_BOUND, Oinitiator)
| REP(RESPONDER_BOUND, Oinit_hello)
| REP(RESPONDER_BOUND, Oinit_conf).
nounif v:seed_prec; attacker(prepare_seed(trusted_seed( v )))/6217[hypothesis].
nounif v:seed; attacker(prepare_seed( v ))/6216[hypothesis].
nounif v:seed; attacker(rng_kem_sk( v ))/6215[hypothesis].
nounif v:seed; attacker(rng_key( v ))/6214[hypothesis].
nounif v:key_prec; attacker(prepare_key(trusted_key( v )))/6213[hypothesis].
nounif v:kem_sk_prec; attacker(prepare_kem_sk(trusted_kem_sk( v )))/6212[hypothesis].
nounif v:key; attacker(prepare_key( v ))/6211[hypothesis].
nounif v:kem_sk; attacker(prepare_kem_sk( v ))/6210[hypothesis].
nounif Spk:kem_sk_tmpl;
attacker(Creveal_kem_pk(Spk))/6110[conclusion].
nounif sid:SessionId, Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, Seski:seed_tmpl, Ssptr:seed_tmpl;
attacker(Cinitiator( *sid, *Ssskm, *Spsk, *Sspkt, *Seski, *Ssptr ))/6109[conclusion].
nounif sid:SessionId, biscuit_no:Atom, Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, Septi:seed_tmpl, Sspti:seed_tmpl, ih:InitHello_t;
attacker(Cinit_hello( *sid, *biscuit_no, *Ssskm, *Spsk, *Sspkt, *Septi, *Sspti, *ih ))/6108[conclusion].
nounif rh:RespHello_t;
attacker(Cresp_hello( *rh ))/6107[conclusion].
nounif Ssskm:kem_sk_tmpl, Spsk:key_tmpl, Sspkt:kem_sk_tmpl, ic:InitConf_t;
attacker(Cinit_conf( *Ssskm, *Spsk, *Sspkt, *ic ))/6106[conclusion].

View File

@@ -0,0 +1,32 @@
#pragma once
#include "prelude/bits.mpv"
#include "crypto/key.mpv"
@module prfs
(* Labels, as specified in the paper *)
fun prf(key, bits) : key.
const PROTOCOL:bits.
const MAC:bits.
const COOKIE:bits.
const PEER_ID:bits.
const BISCUIT_AD:bits.
const CK_INIT:bits.
const CK_EXTRACT:bits.
const MIX:bits.
const USER:bits.
const HS_ENC:bits.
const INI_ENC:bits.
const RES_ENC:bits.
const OSK:bits.
letfun prf2(k:key, a:bits, b:bits) = prf(prf(k, a), b).
letfun lprf0(lbl:bits) = prf2(key0, PROTOCOL, lbl).
letfun lprf1(lbl:bits, a:bits) = prf(lprf0(lbl), a).
letfun lprf2(lbl:bits, a:bits, b:bits) = prf(lprf1(lbl, a), b).
letfun lprf3(lbl:bits, a:bits, b:bits, c:bits) = prf(lprf2(lbl, a, b), c).

View File

@@ -0,0 +1,81 @@
#pragma once
#include "crypto/kem.mpv"
#include "rosenpass/handshake_state.mpv"
type InitHello_t.
fun InitHello(
SessionId, // sidi
kem_pk, // epki
bits, // sctr
bits, // pidiC
bits // auth
) : InitHello_t [data].
#define INITHELLO_PRODUCE() \
ck <- lprf1(CK_INIT, kem_pk2b(spkr)); /* IHI1 */ \
/* not handled here */ /* IHI2 */ \
/* not handled here */ /* IHI3 */ \
MIX2(sid2b(sidi), kem_pk2b(epki)) /* IHI4 */ \
ENCAPS_AND_MIX(sctr, spkr, sptr) /* IHI5 */ \
ENCRYPT_AND_MIX(pidiC, pidi) /* IHI6 */ \
MIX2(kem_pk2b(spki), k2b(psk)) /* IHI7 */ \
ENCRYPT_AND_MIX(auth, empty) /* IHI8 */ \
ih <- InitHello(sidi, epki, sctr, pidiC, auth);
#define INITHELLO_CONSUME() \
ck <- lprf1(CK_INIT, kem_pk2b(spkr)); /* IHR1 */ \
MIX2(sid2b(sidi), kem_pk2b(epki)) /* IHR4 */ \
DECAPS_AND_MIX(sskr, spkr, sctr) /* IHR5 */ \
DECRYPT_AND_MIX(pid, pidiC) /* IHR6 */ \
LOOKUP_SENDER(pid) /* IHR6 */ \
MIX2(kem_pk2b(spki), k2b(psk)) /* IHR7 */ \
DECRYPT_AND_MIX(DUMMY(empty), auth)
type RespHello_t.
fun RespHello(
SessionId, // sidr
SessionId, // sidi
bits, // ecti
bits, // scti
bits, // biscuit
bits // auth
) : RespHello_t [data].
#define RESPHELLO_PRODUCE() \
/* not handled here */ /* RHR1 */ \
MIX2(sid2b(sidr), sid2b(sidi)) /* RHR3 */ \
ENCAPS_AND_MIX(ecti, epki, epti) /* RHR4 */ \
ENCAPS_AND_MIX(scti, spki, spti) /* RHR5 */ \
STORE_BISCUIT(biscuit) /* RHR6 */ \
ENCRYPT_AND_MIX(auth, empty) /* RHR7 */ \
rh <- RespHello(sidr, sidi, ecti, scti, biscuit, auth);
#define RESPHELLO_CONSUME() \
let RespHello(sidr, sidi, ecti, scti, biscuit, auth) = rh in \
/* not handled here */ /* RHI2 */ \
MIX2(sid2b(sidr), sid2b(sidi)) /* RHI3 */ \
DECAPS_AND_MIX(eski, epki, ecti) /* RHI4 */ \
DECAPS_AND_MIX(sski, spki, scti) /* RHI5 */ \
MIX(biscuit) /* RHI6 */ \
DECRYPT_AND_MIX(DUMMY(empty), auth) /* RHI7 */
type InitConf_t.
fun InitConf(
SessionId, // sidi
SessionId, // sidr
bits, // biscuit
bits // auth
) : InitConf_t [data].
#define INITCONF_PRODUCE() \
MIX2(sid2b(sidi), sid2b(sidr)) /* ICI3 */ \
ENCRYPT_AND_MIX(auth, empty) /* ICI4 */ \
ic <- InitConf(sidi, sidr, biscuit, auth);
#define INITCONF_CONSUME() \
let InitConf(sidi, sidr, biscuit, auth) = ic in \
LOAD_BISCUIT(biscuit_no, biscuit) /* ICR1 */ \
ENCRYPT_AND_MIX(rh_auth, empty) /* ICIR */ \
ck_rh <- ck; /* ---- */ /* TODO: Move into oracles.mpv */ \
MIX2(sid2b(sidi), sid2b(sidr)) /* ICR3 */ \
DECRYPT_AND_MIX(DUMMY(empty), auth) /* ICR4 */

View File

@@ -0,0 +1,12 @@
#include "rosenpass/undef.macro"
#define role res_role
#define sski kem_sk0
#define spki spkt
#define sskr sskm
#define spkr spkm
#define eskm kem_sk0
#define epkm kem_pk0
#define sidi sidt
#define sidr sidm
#define pidi pidt
#define pidr pidm

View File

@@ -0,0 +1,11 @@
#pragma once
#include "prelude/basic.mpv"
#include "crypto/key.mpv"
#include "crypto/kem.mpv"
#define SERVER_NEW(biscuit_key, sk, pk) \
biscuit_key <- key_new(); \
new sk:kem_sk; \
pk <- kem_pub(sk);
fun Server_id(kem_pk) : Atom.

View File

@@ -0,0 +1,33 @@
#ifdef role
#undef role
#endif
#ifdef sski
#undef sski
#endif
#ifdef spki
#undef spki
#endif
#ifdef sskr
#undef sskr
#endif
#ifdef spkr
#undef spkr
#endif
#ifdef eskm
#undef eskm
#endif
#ifdef epkm
#undef epkm
#endif
#ifdef sidi
#undef sidi
#endif
#ifdef sidr
#undef sidr
#endif
#ifdef pidi
#undef pidi
#endif
#ifdef pidr
#undef pidr
#endif