(* JFKi *)

param redundantHypElim = beginOnly.

(* Exponential and Diffie-Hellman *)

data g/0.
fun exp/2.
equation exp(exp(g,y),z) = exp(exp(g,z),y).

(* Signature *)

fun S/2.
fun Pk/1.
data true/0.
fun V/3.
fun RecoverKey/1.
fun RecoverText/1.
equation V(S(k,v), Pk(k),v) = true.
equation RecoverKey(S(k,v)) = Pk(k). (* For the attacker *)
equation RecoverText(S(k,v)) = v.    (* For the attacker *)

(* Shared-key encryption *)

fun E/2.
fun D/2.
equation D(k,E(k,v)) = v.

(* Keyed hash function *)

fun H/2.

(* Sets *)

data consset/2.
data emptyset/0.
pred member/2.
clauses
  member:x,consset(x,y);
  member:x,y -> member:x,consset(z,y).

(* Tags *)

data tagE/0. data tagA/0. data tagV/0.

(* Constructors for JFK's formatted messages
   Selectors are implicit when using "data" *)

data cons1/3. data cons2/7. data cons3/7. data cons4/2.

(* More constants *)

data constI/0. data constR/0. data saR/0.

(* Free names *)

free c. (* Public channel *)
free pub, getprinc, getexponential, grpinfoR.

(* Queries: properties to prove *)

(* Correspondence assertions *)

ifdef(`EVCACHE',`
  (* Denial of service for I. *)
  query evinj:enddosi(XIDA, XNI) ==> 
    (evinj:mess2rec(XIDA, XNI, XNR, XxR, XgrpinfoR, XIDB, XsR2, XtR, XxI, XIDRp, XsaI) ==>
    (evinj:mess1(XIDA, XNI, XxI, XIDRp, XinitA, Xautorid, XsaI) ==>
     evinj:init(XinitA, Xautorid, XIDRp, XsaI)))
   & ev:princ(XkAminus, XIDA, XinitA, XacceptA, XconnectA, XSIA).

  (* First part of Property 1 of Theorem 3.  *)
  query ev:accept(XacceptB, XIDA, XsaI, XsaR, XKv) ==> 
		  ev:princ(XkBminus, XIDB, XinitB, XacceptB, XconnectB, XSIB)
		& member:XIDA,XSIB.

  (* First part of Property 2 of Theorem 3.  *)
  query evinj:connect(XconnectA, XIDB, XsaI, XsaR, XKv) ==>
		  evinj:init(XinitA, Xautorid, XIDRp, XsaI)
		& ev:princ(XkAminus, XIDA, XinitA, XacceptA, XconnectA, XSIA)
		& member:XIDB, Xautorid.

  (* Second part of Property 2 of Theorem 3. *)
  query evinj:connecthonest(XconnectA, XIDB, XsaI, XsaR, XKv) ==> 
		  evinj:accept(XacceptB, XIDA, XsaI, XsaR, XKv) 
		& ev:princ(XkAminus, XIDA, XinitA, XacceptA, XconnectA, XSIA)
                & ev:princ(XkBminus, XIDB, XinitB, XacceptB, XconnectB, XSIB).

  (* Certificates that the attacker can have *)
  query attacker:S(kAminus, x).

  define(`HONESTEVENTS')
')


ifdef(`EVNOCACHE',`
  (* To prove these injective correspondences, you need to replace the caching 
     of message 3 with a version without caching *)

  (* Denial of service for R. *)
  query evinj:enddosr(XIDA, XNI, XNR) ==> 
     ev:princ(XkAminus, XIDA, XinitA, XacceptA, XconnectA, XSIA)
   & (evinj:mess3rec(XIDA, XNI, XNR, XxI, XxR, XtR, XeI, XhI) ==>
     (evinj:mess2(XIDA, XNI, XNR, XxIp, XxR, XgR, XS, XtR, XIDRpp) ==>
      evinj:mess1rec(XIDA, XNI, XxIp, XIDRpp))).

  (* Second part of Property 1 of Theorem 3. *)
  query evinj:accepthonest(XacceptB, XIDA, XsaI, XsaR, XKv) ==> 
		  evinj:init(XinitA, Xautorid, XIDRp, XsaI) 
		& ev:princ(XkAminus, XIDA, XinitA, XacceptA, XconnectA, XSIA)
                & ev:princ(XkBminus, XIDB, XinitB, XacceptB, XconnectB, XSIB).

  (* Reordering *)
  query evinj:connecthonest(XconnectA, XIDB, XsaI, XsaR, XKv) ==> 
                  ev:princ(XkAminus, XIDA, XinitA, XacceptA, XconnectA, XSIA)
                & ev:princ(XkBminus, XIDB, XinitB, XacceptB, XconnectB, XSIB)
		&(evinj:mess4rec(XIDA, XeR, XhR, XconnectA, XIDB, XsaI, XsaR, XKv) ==>
                 (evinj:mess4(XIDB, XIDA, XeR, XhR, XNI, XNR, XxI, XxR, XtR, XeI, XhI) ==>
                 (evinj:accept(XacceptB, XIDA, XsaI, XsaR, XKv) ==>
		 (evinj:mess3rec(XIDB, XNI, XNR, XxI, XxR, XtR, XeI, XhI) ==>
		 (evinj:mess3(XIDA, XNI, XNR, XxI, XxR, XtRp, XeI, XhI, XgR, XIDRp, XsaI, XKv) ==>
		 (evinj:mess2rec(XIDA, XNI, XNR, XxR, XgR, XIDB, S(XkBminus, (XxR, XgR)), XtRp, XxI, XIDRp, XsaI) ==> 
		  (evinj:mess2(XIDB, XNI, XNR, XxIp, XxR, XgR, S(XkBminus, (XxR, XgR)), XtR, XIDRpp) ==>
	           evinj:mess1rec(XIDB, XNI, XxIp, XIDRpp))
		& (evinj:mess1(XIDA, XNI, XxI, XIDRp, XinitA, Xautorid, XsaI) ==>
		   evinj:init(XinitA, Xautorid, XIDRp, XsaI)))))))).

  (* Note the presence of XtRp in events mess3 and mess2rec and 
     XxIp and XIDRpp in events mess2 and mess1rec, instead of 
     XtR, XxI, and XIDRp respectively *)

  define(`NOCACHE')
  define(`HONESTEVENTS')
')

(* Secrecy *)

ifdef(`SECRECY',`
  (* Secrecy of the exchanged key Kv, from the point of view of I and R *)
  private free secretI, secretR.
  query attacker:secretI;
        attacker:secretR.
')

(* Identity protection.
   The predicate member: must be removed, because it is not supported
   for observational equivalence proofs. So we use a version without cache.
   Only compliant principals are allowed to connect to R. *)

ifdef(`IDISECR',`
  (* Identity protection for I against active adversaries *)
  private fun honest/1.
  reduc ishonest(Pk(honest(x))) = true.

  private free hIminus0, hIminus3, hminus1, hminus2.
  noninterf hIminus0 among (hminus1, hminus2), 
            hIminus3 among (hminus1, hminus2).
  not hminus1.
  not hminus2.
  not hAminus.
  define(`NOCACHE')
')


(* Secrecy assumptions *)

ifdef(`IDISECR',,
not kAminus phase 0.
)
not d.
ifdef(`NOCACHE',,
not f.
)

(* Initiator 
   The process processI corresponds to I^A in the figure. *)

let processI =
  !
  in(exponent, (dI, xI));
  !
  in(init, (autorid, IDRp, saI));    (* Init message *)
  event init(init, autorid, IDRp, saI); 
  new NI; 
  event mess1(IDA, NI, xI, IDRp, init, autorid, saI);
  out(c, cons1(NI, xI, IDRp));
  in(c, cons2(=NI, NR, xR, grpinfoR, IDRl, sR2, tR));
  event mess2rec(IDA, NI, NR, xR, grpinfoR, IDRl, sR2, tR, xI, IDRp, saI);
ifdef(`IDISECR',
  (* Only honest hosts are allowed to connect to I in this case *)
  if ishonest(IDRl) = true then
,`
  if member:IDRl,autorid then
')
  event enddosi(IDA, NI);
  if V(sR2, IDRl, (xR, grpinfoR)) = true then 
  let h = exp(xR, dI) in
  let Ka = H(h, (NI, NR, tagA)) in
  let Ke = H(h, (NI, NR, tagE)) in
  let Kv = H(h, (NI, NR, tagV)) in
  let sI = S(kAminus, (NI, NR, xI, xR, IDRl, saI)) in
  let eI = E(Ke, (IDA, saI, sI)) in
  let hI = H(Ka, (constI, eI)) in
  event mess3(IDA, NI, NR, xI, xR, tR, eI, hI, grpinfoR, IDRp, saI, Kv);
  out(c, cons3(NI, NR, xI, xR, tR, eI, hI));
  in(c, cons4(eR, hR));
  if H(Ka, (constR, eR)) = hR then
  let (sR, saR) = D(Ke, eR) in
  if V(sR, IDRl, (NI, NR, xI, xR, IDA, saI, saR)) = true then 
  event mess4rec(IDA, eR, hR, connect, IDRl, saI, saR, Kv);
ifdef(`HONESTEVENTS',
  (
    in(honestC, =IDRl);
    event connecthonest(connect, IDRl, saI, saR, Kv)
  )
  |
)
  event connect(connect, IDRl, saI, saR, Kv);
  out(connect, (IDRl, saI, saR, Kv));
ifdef(`SECRECY',
  in(honestC, =IDRl);
  out(c, E(Kv, secretI));
)
  0.

(* Responder
   The process processR corresponds to R^A in the figure. *)

ifdef(`NOCACHE',`

let processR =
  new KR;
  !
  in(exponent, (dR, xR));
  !
  in(c, cons1(NI, xI, IDRp));
  event mess1rec(IDA, NI, xI, IDRp);
  new NR;
  new tR;
  event mess2(IDA, NI, NR, xI, xR, grpinfoR, S(kAminus, (xR, grpinfoR)), tR, IDRp);
  out(c, cons2(NI, NR, xR, grpinfoR, IDA, S(kAminus, (xR, grpinfoR)), tR));
  new l;
  (
    (
    !
    in(c, cons3(=NI,=NR,xI,=xR,=tR,eI,hI));
    out(l, (xI,eI,hI))
    )
  |
    (
    in(l, (xI,eI,hI));
    event mess3rec(IDA, NI, NR, xI, xR, tR, eI, hI);
    processR4
    )
  ).

',`

let processR =
  new KR;
  (
    (
    !
    in(exponent, (dR, xR));
    !
    in(c, cons1(NI, xI, IDRp));
    new NR;
    let tR = H(KR, (xR, NR, NI)) in
    event mess2(IDA, NI, NR, xI, xR, grpinfoR, S(kAminus, (xR, grpinfoR)), tR, IDRp);
    out(c, cons2(NI, NR, xR, grpinfoR, IDA, S(kAminus, (xR, grpinfoR)), tR))
    )
  |
    new f;
    (
      out(f, emptyset)
    |
      (
        !
        in(c, cons3(NI,NR,xI,xR,tR,eI,hI));
        event mess3rec(IDA, NI, NR, xI, xR, tR, eI, hI);
        if tR = H(KR, (xR, NR, NI)) then
        in(f, cache);
        (
          out(f, consset(tR, cache))
        |
          if member:tR,cache then 0 else 
          new l;
          (
	    (
            !
            in(exponent, (dR, =xR));
            out(l, dR)
            )
          |
            (
            in(l, dR);
            processR4
            )
          )
        )
      )
    )
  ).

')


let processR4 =
  event enddosr(IDA, NI, NR);
  let h = exp(xI,dR) in
  let Ka = H(h, (NI, NR, tagA)) in
  let Ke = H(h, (NI, NR, tagE)) in
  let Kv = H(h, (NI, NR, tagV)) in
  if H(Ka, (constI, eI)) = hI then
  let (IDIl, saI, sI) = D(Ke,eI) in
ifdef(`IDISECR',
  if ishonest(IDIl) = true then
,`
  if member:IDIl,SIA then
')
  if V(sI, IDIl, (NI, NR, xI, xR, IDA, grpinfoR)) = true then
ifdef(`HONESTEVENTS',
  (
    in(honestC, =IDIl);
    event accepthonest(accept, IDIl, saI, saR, Kv)
  )
  |
)
  event accept(accept, IDIl, saI, saR, Kv);
  out(accept, (IDIl, saI, saR, Kv));
  (
    (
      let sR = S(kAminus, (NI, NR, xI, xR, IDIl, saI, saR)) in
      let eR = E(Ke, (sR, saR)) in
      let hR = H(Ka, (constR, eR)) in
      event mess4(IDA, IDIl, eR, hR, NI, NR, xI, xR, tR, eI, hI);
      out(c, cons4(eR, hR));
ifdef(`SECRECY',
      in(honestC, =IDIl);
      out(c, E(Kv, secretR));
)
      0
    )
  ).


(* Whole JFK system. *)

ifdef(`IDISECR',
(* Version for identity protection with active adversaries *)

process
  new exponent;
  ( ! new d; let x = exp(g,d) in out(getexponential, x); ! out(exponent, (d,x)) )
  |
  new initI;     (* private channel used to launch principals playing role I *)
  let kminus1 = honest(hminus1) in
  let ID1 = Pk(kminus1) in
  let kminus2 = honest(hminus2) in
  let ID2 = Pk(kminus2) in
  out(pub, ID1);
  out(pub, ID2);
  (
    (
      (* Create principal playing role I, with secret key kIminus0 *)
      let kIminus0 = honest(hIminus0) in
      let IDI0 = Pk(kIminus0) in
      new connect0; new init0;
      out(pub, init0);
      (
        (! in(connect0, x))
      | out(initI, (kIminus0, IDI0, init0, connect0))
      )
    )
  |
    (
      (* Create principal playing role I, with secret key kIminus3 *)
      let kIminus3 = honest(hIminus3) in
      let IDI3 = Pk(kIminus3) in
      new connect3; new init3;
      out(pub, init3);
      (
        (! in(connect3, x))
      | out(initI, (kIminus3, IDI3, init3, connect3))
      )
    )
  |
    (
      !
      new hAminus;
      let kAminus = honest(hAminus) in
      let IDA = Pk(kAminus) in
      new connect; new accept; new init; new channelSIA;
      out(getprinc, (IDA, init, channelSIA));
      in(channelSIA, SIA);
      (
        (! in(accept, x))
      | (! in(connect, x))
      | out(initI, (kAminus, IDA, init, connect))
      | processR 
      )
    )
  |
    (! in(initI, (kAminus, IDA, init, connect)); processI) 
  )

,
(* Standard version of the process *)

process

  new exponent;
  ( ! new d; let x = exp(g,d) in out(getexponential, x); ! out(exponent, (d,x)) )
  |
  new honestC;  (* private channel used to simulate the set C of honest principals *)
  !
  new kAminus;
  let IDA = Pk(kAminus) in
  new connect; new accept; new init; new channelSIA;
ifdef(`SECRECY',
  (* Do not give accept and connect to the adversary when proving secrecy of Kv:
     These messages contain Kv! Instead, input on these channels here. *)
  out(getprinc, (IDA, init,  channelSIA));
  in(channelSIA, SIA);
  event princ(kAminus, IDA, init, accept, connect, SIA);
  (
    ( phase 1; out(pub, kAminus) )
  | (! in(accept, x))
  | (! in(connect, x))
  | (! out(honestC, IDA) )   (* IDA is in C *)
  | processI
  | processR
  )
,
  out(getprinc, (IDA, init, accept, connect, channelSIA));
  in(channelSIA, SIA);
  event princ(kAminus, IDA, init, accept, connect, SIA);
  (
    (! out(honestC, IDA) )   (* IDA is in C *)
  | processI
  | processR
  )
)

)
