(*

Woo and Lam public-key auth protocol (1992, computer)

A -> B: A
B -> A: N
A -> B: { N }_skA
B -> S: A
S -> B: A, { A, pkA }_skS

Terminates with attack

Full agreement is true, but there is an attack !
(Indeed, the messages never mention B.)

If we include B as a pseudo first message sent to A, then full agreement
is false.
*)

free c: channel.

type host.
type nonce.
type pkey.
type skey.

fun nonce_to_bitstring(nonce): bitstring [data,typeConverter].

(* Signatures *)

fun pk(skey): pkey.
fun sign(bitstring, skey): bitstring.
reduc forall m: bitstring, k: skey; getmess(sign(m,k)) = m.
reduc forall m: bitstring, k: skey; checksign(sign(m,k), pk(k)) = m.

(* Secrecy assumptions *)

not attacker(new skA).
not attacker(new skB).
not attacker(new skS).

(* 2 honest host names A and B *)

free A, B: host.

(* the table host names/keys 
   The key table consists of pairs (host, public key) *)
table keys(host, pkey).

(* Queries *)

event beginBparam(host, host).
event endBparam(host, host).
event beginBfull(host, host, nonce).
event endBfull(host, host, nonce).

query x: host, y: host; inj-event(endBparam(x,y)) ==> inj-event(beginBparam(x,y)).
query x: host, y: host, z: nonce; inj-event(endBfull(x,y,z)) ==> inj-event(beginBfull(x,y,z)).


(* Role of the initiator with identity xA and secret key skxA *)

let processInitiator(skA: skey, skB: skey) =
        (* The attacker starts the initiator by choosing identity xA,
	   and its interlocutor xB0.
	   We check that xA is honest (i.e. is A or B)
	   and get its corresponding key.
	*)
        in(c, (xA: host, xB0: host));
	if xA = A || xA = B then
	let skxA = if xA = A then skA else skB in
	(* Real start of the role *)
        event beginBparam(xA, xB0);
        out(c, xA);
        in(c,n: nonce);
	event beginBfull(xA, xB0, n);
	out(c,sign(nonce_to_bitstring(n),skxA)).

(* Role of the responder with identity xB *)

let processResponder(pkS: pkey) =
        (* The attacker starts the responder by choosing identity xB.
	   We check that xB is honest (i.e. is A or B). *)
        in(c, xB: host);
	if xB = A || xB = B then
	(* Real start of the role *)
	in(c, xA0: host);
        new n: nonce; 
	out(c,n);
        in(c,m: bitstring); 
        out(c, xA0);
        in(c, (=xA0, m4: bitstring)); 
        let (=xA0, pkA2: pkey) = checksign(m4, pkS) in
        if nonce_to_bitstring(n) = checksign(m,pkA2) then
	(* OK *)
        if xA0 = A || xA0 = B then
	event endBparam(xA0, xB);
	event endBfull(xA0, xB, n).

(* Server *)

let processS(skS: skey) = 
    	in(c, xA: host);
	get keys(=xA, pkA) in
	out(c, (xA, sign((xA, pkA), skS))).

(* Key registration *)

let processK =
        in(c, (h: host, k: pkey));
        if h <> A && h <> B then insert keys(h,k).

(* Start process *)

process new skA: skey; 
	new skB: skey; 
	new skS: skey;
        let pkA = pk(skA) in
        let pkB = pk(skB) in
        let pkS = pk(skS) in
	out(c, pkA); out(c, pkB); out(c, pkS);
	insert keys(A, pkA);
	insert keys(B, pkB);
	((!processInitiator(skA, skB)) | 
         (!processResponder(pkS)) | (!processS(skS)))

(* EXPECTPV
RESULT inj-event(endBparam(x,y)) ==> inj-event(beginBparam(x,y)) is false.
RESULT (even event(endBparam(x,y)) ==> event(beginBparam(x,y)) is false.)
RESULT inj-event(endBfull(x,y,z)) ==> inj-event(beginBfull(x,y,z)) is false.
RESULT (even event(endBfull(x,y,z)) ==> event(beginBfull(x,y,z)) is false.)
0.040s (user 0.012s + system 0.028s), max rss 8928K
END *)
