(* pEp Key Synchronization Protocol
 *
 * The protocol aims for secure peer-to-peer synchronization
 * of private keys across devices belonging to the same user.
 *
 * Assumptions:
 * - the attacker has no access to the user's device
 * - the trustwords comparison is correct
*)

(*set traceDisplay = long.*)

set preciseActions = true.

type UUID.
type status.
type mssgType.
type pkey.
type skey.

(*--- Var, const, pred ---*)
const SOLE: status.
const GROUPED: status.
const NegReq: mssgType.
const NegOpen: mssgType.
const CommitAcceptOff: mssgType.
const CommitAcceptReq: mssgType.

free network:channel.
free devRusr, usrRdev, devOusr, usrOdev: channel[private].

free skR, skO:skey[private].


(*--- Functions ---*)
(* pubKey is not private because knowing the secret key, generating the corresponding public key is an easy calculation *)
fun pubKey(skey): pkey.
fun xor(UUID, UUID): bitstring.
(* a deterministic function that generates a list of words out of 2 public keys *)
fun trustwords(pkey, pkey): bitstring.

fun encrypt(bitstring,pkey): bitstring.
reduc	forall m:bitstring, k:skey; decrypt(encrypt(m,pubKey(k)),k) = m.

(* Trustwords comparison. The 2 lists match only if they are generated from the same 2 pks *)
fun handshake(bitstring, bitstring): bool
reduc
	forall a:pkey, b:pkey; handshake(trustwords(a,b), trustwords(a,b)) = true
	otherwise forall a:pkey, b:pkey; handshake(trustwords(a,b), trustwords(b,a)) = true
	otherwise forall w1:bitstring, w2:bitstring; handshake(w1,w2) = false.


(*--- Events ---*)
event groupKeys(UUID, UUID, skey, skey).
event groupedO(UUID, UUID).
event groupedR(UUID, UUID).
event req(pkey,UUID).
event offrr(pkey,UUID).
event mismatch.


(*--- Properties ---*)
(*
query x:UUID, y:UUID, skx:skey, sky:skey;
	event(groupedR(x,y)) && event(groupedO(x,y)) ==> event(groupKeys(x,y,skx,sky)) && event(groupKeys(y,x,sky,skx)).
	*)
query attacker(skO).
(* query (attacker(skO) ==> false) && attacker(skR). *)
(* query event(mismatch) && attacker(skR) ==> false. *)


(*--- Processes ---*)
let Requester(tidR:UUID, pairity:status, secKreq:skey, pubKreq:pkey) =
	event req(pubKreq,tidR);
  in(network, (tidOffr:UUID,stts:status,pkOffr:pkey));
  (* Requester asks to start a handshake *)
  if (tidOffr<>tidR  && stts=SOLE) then
    let tidNegotiation = xor(tidR,tidOffr) in
      out(network, encrypt((NegReq, tidNegotiation, tidR, SOLE, pubKreq),pkOffr));
			(* and waits for the confirmation *)
      in (network, negotiationOpen:bitstring);
			(* if sameNegotiationAndPartner *)
			let (=NegOpen, =tidNegotiation) = decrypt(negotiationOpen,secKreq) in
        (* display the trustwords to the user *)
        out(devRusr, trustwords(pubKreq,pkOffr));
        in(network, acceptOfferer:bitstring);
				in(usrRdev, usrCheck:bool);
				(* if the offerer and the user confirm the trustwords *)
				let (=CommitAcceptOff,=tidNegotiation) = decrypt(acceptOfferer,secKreq) in
          if usrCheck then
            (* the requester commits to the secure communication *)
            out(network, encrypt((CommitAcceptReq, tidNegotiation),pkOffr));
            (* and sends its list of identities *)
            out(network, encrypt((pubKreq,secKreq),pkOffr));
            in(network, offererIds:bitstring);
            (* then, it forms a group with its own and the identities from the offerer *)
						let (=pkOffr, sk:skey) = decrypt(offererIds,secKreq) in
            	event groupKeys(tidR,tidOffr,secKreq,sk);
            	event groupedR(tidR,tidOffr).


let Offerer(tidO:UUID, pairity:status, secKoffr:skey, pubKoffr:pkey) =
	event offrr(pubKoffr,tidO);
  (* The Offerer waits for a request to be paired *)
	in(network, negotiationReq:bitstring);
	(* When received from a Sole device*)
	let (=NegReq, tidNeg:bitstring, tidReq:UUID, =SOLE, pkReq:pkey) = decrypt(negotiationReq,secKoffr) in
		(* then open the negotiation with the requester *)
    out(network, encrypt((NegOpen, tidNeg),pkReq));
    (* and show the trustwods to the user *)
    out(devOusr, trustwords(pubKoffr,pkReq));
    in(usrOdev, usrAccept:bool);
    (* if the user confirms them *)
    if not(usrAccept) then
			event mismatch
		else
      (* commit to the communication *)
      out(network, encrypt((CommitAcceptOff,tidNeg),pkReq));
			in(network, acceptRequester:bitstring);
			let (=CommitAcceptReq,=tidNeg) = decrypt(acceptRequester, secKoffr) in
          out(network, encrypt((pubKoffr,secKoffr),pkReq));
          in(network, requesterIds:bitstring);
          (* then, it forms a group with its own and the identities from the offerer *)
					let (=pkReq,sk:skey) = decrypt(requesterIds,secKoffr) in
          	event groupKeys(tidO,tidReq,secKoffr,sk);
          	event groupedO(tidReq,tidO).


let User =
  in(devOusr, twsO:bitstring);
  in(devRusr, twsR:bitstring);
  out(usrOdev, handshake(twsO,twsR));
  out(usrRdev, handshake(twsO,twsR)).


process
  new TIDr: UUID;
  new TIDo: UUID;
	(* Beacon messages to establish communication.
			The becons are sent as plaintext since no other public key is known *)
	let pkR=pubKey(skR) in out(network, (TIDr,SOLE,pkR));
	let pkO=pubKey(skO) in out(network, (TIDo,SOLE,pkO));
	(* the next roles are assigned to the devices assuming TIDr < TIDo *)
  (Requester(TIDr, SOLE, skR, pkR) | Offerer(TIDo, SOLE, skO, pkO) | User)

(* EXPECTPV
RESULT not attacker(skO[]) is true.
0.032s (user 0.024s + system 0.008s), max rss 9348K
END *)
