360 likes | 483 Views
Constraint Logic Programming for Verifying Security Protocols a gzipped tutorial. Sandro Etalle Ricardo Corin University of Twente. Why another talk. Addressing the (old) problem of verifying security protocols. Against e.g., man-in-the-middle attacks.
E N D
Constraint Logic Programming for Verifying Security Protocolsa gzipped tutorial Sandro Etalle Ricardo Corin University of Twente
Why another talk • Addressing the (old) problem of verifying security protocols. • Against e.g., man-in-the-middle attacks. • use constraint solvingto model efficiently the intruder. • It works lazily. • We have a very fast tool. • Based on: Millen & Shmatikov.
Index • How to specify a protocol. • The intruder model. • The constraint-solving algorithm. • Conclusions.
Preliminaries: Prolog’s notation • variables: begin with uppercase or with _ • Complex terms can be built using predicate (function) symbols: • pk(b) • Nb*pk(B) is the same as *(Nb,pk(B)): * is an infix-operator. • send(Nb*pk(B)) • Lists: [t1,…,tn]
The same, old example: Needham-Schroeder A->B : [A,Na]*pk(B) B->A : [Na,Nb]*pk(A) A->B : [Nb]*pk(B) • Notation • msg*k: asymmetric encryption • Na, Nb: nonces • A, B: Agents (Alice and Bob) • pk(A): public key of A • First, we want to model it.
Roles • Here we have 2 roles: initiator & responder. • A role is specified as a sequence of events: • send(t) • recv(t) • t is a term (a message) • initiator(A,B,Na,Nb) = [ send([A,Na]*pk(B)), recv([Na,Nb]*pk(A)), send(Nb*pk(B))].
The Responder • Just exchange “send” and “recv” responder(A,B,Na,Nb) = [ recv([A,Na]*pk(B)), send([Na,Nb]*pk(A)), recv(Nb*pk(B))]). • Notice the variables. • names & nonces are not fixed • roles are parametric • We still need to: • fix some parameters • determine how many agents are there, what they know etc. • We do this by specifying a session.
Session (scenario) • session: set of (instantiated) roles • {initiator(a,B,na,Nb), responder(A,b,Na,nb)} • Notice the variables • Freshness nonces = new terms (ground terms that don’t occur elsewhere ). • We can add more agents etc. • next: network model & constraint-solving
The network model • Dolev-Yao • Constraint Solving: to see what the intruder can generate. Scenario Agent Role Role Role Role Role send(t) recv(t) Network/Intruder
Constraints • A constraint is a pair: m:T • m is a message term, T is a list of terms. • is called simple if m is a variable. • intuitive meaning: “m is generable from T” • The Constraint Store (CS) is a set of constraints.
the Verification Algorithm • S: scenario • CS: constraint store (initially empty) • K: intruder’s knowledge (K0 is provided by the user) • A step of the verification algorithm: • choose the first event e from a non-empty role of S • case 1) e = send(t) • K := K U {t}; proceed • case 2) e = recv(t) • CS := CS U {t:K } • if CS can be solved to CS’ with solution , • S := S; K:= K; CS := CS’; • proceed • otherwise, stop
What is solvable? • CS can be solved to CS’ with solution if we can apply reduction rules to CS until we obtain CS’, where • CS’ is empty or • CS’ contains only simple constraints. • Two kind of rules • synthesis. • analysis.
Synthesis reduction rules • :rewriting step yielding substitution • is the empty substitution • Local rules: • Pair: [m1,m2]:T m1:T , m2:T • hash: h(m):T m:T • penc: m*pk(a) :T m:T, a:T • senc: m+k :T m:T, k:T • sig: m*sk(e) m:T • Global rule • unify: {m:T,C1,…,Cn} {C1,…,Cn} • provided that =mgu(m,t), tT
Analysis reduction rules (2) • Affect the other side of the constraint. • Local rules • split: m:{[t1,t2]} T m:{t1,t2} T • pdec: m:{t*pk(e)} :T m:{t} T • sdec: m:T{t+k} k:T{t-k}, m:T{t,k} • forget about this one • used only for constructed symmetric keys • Global rule • ksub: {m:{t*k}T, C1, …, Cn} {m:({t*k}T), C1, …, Cn} • where =mgu(k,pk(e)), kpk(e)
the Result • CS0 1 CS1 2… n CSn • each time a constraint in CS is selected and a rule is applied to it • The rewriting stops when • CSn is empty or made of simple constraints • CS is solved • the composition of the substitutions is the result of the simplification: := 1 2 … n • a constraint is selected that cannot be simplified • CS is unsolvable • there is no result (failure)
Properties • Is it Confluent? • No. • Different reduction sequences are possible: • in total: 4 sources of nondeterminism • choice of the event in the algorithm. • choice of the constraint to be reduced. • choice of the rule to be applied. • in the the analysis rules and in the unify rule there is the additional choice of the term in T to which the rule is applied. • Full backtracking to preserve completeness. • Local analysis reduction rules preserve confluence, and this can be used for optimization.
Finding Secrecy flaws • What is a secrecy flaw? • To check if na remains secret, one just has to add to the scenario the singleton role [recv(na)] • na remains secret <=> the intruder cannot output it! • in practice we define a special role • secrecy(X) = [recv(X)].
Finding Authentication Flaws • More complex than checking secrecy. • What is an authentication flaw? • Various definitions. • Basically: an input event recv(t) without corresponding preceding output event send(t). • Can be checked by e.g., running the responder strand without an initiator role. • Presently: a pain in the neck. • We are working on it.
An Example for Laziness • send(na*pk(b)), recv(X), recv(X*pk(b)) • two constraints are generated: • X : {a,b,e} • X*pk(b) : {a,b,e,na*pk(b)} • by rule (unify): • na : {a,b,e} • not solvable! • we did not know this after the first step.
Bibliography Remark • The system is strongly based on that of Millen and Shmatikov [MS01] • Various differences: • Constraints checked “on the fly” • Consider run also with unfinished roles (very important in practice) • Few other minor things.
Part 3 Example
Example • Consider the scenario for NS with OA: {initiator(a,B,na,Nb),responder(A,b,Na,nb), {recv(nb)}} • A possible interleaving: recv([A,B]), send([a,na]*pk(B)) recv([A,Na]*pk(b)), send([Na,nb]*pk(A)) recv( [na,Nb]*pk(a)), send([Nb]*pk(B)), recv(nb) ... • We omit the events after recv(nb)
Example (cont) recv([A,B]), send([a,na]*pk(B)) recv([A,Na]*pk(b)), send([Na,nb]*pk(A)) recv( [na,Nb]*pk(a)), send([Nb]*pk(B)), recv(nb) ... • find out what happens to the CS • T = {a,b,e} (intruder knowledge) • CS = {}
The run (1) recv([A,B]), send([a,na]*pk(B)), recv([A,Na]*pk(b)), send([Na,nb]*pk(A)), recv( [na,Nb]*pk(a)), send([Nb]*pk(B)), recv(nb) ... • Before the step • T = {a,b,e} • CS = {} • After (T does not change) • CS = {[A,B]:{a,b,e}} • By applying pair • CS’ = {A:{a,b,e}, B:{a,b,e}}
The run (2) send([a,na]*pk(B)), recv([A,Na]*pk(b)), send([Na,nb]*pk(A)), recv( [na,Nb]*pk(a)), send([Nb]*pk(B)), recv(nb) ... • Before the step • T = T0 = {a,b,e} • CS = {A:{a,b,e}, B:{a,b,e}} • After • T = T1 = {a,b,e,[a,na]*pk(B)}
The run (3) recv([A,Na]*pk(b)), send([Na,nb]*pk(A)), recv( [na,Nb]*pk(a)), send([Nb]*pk(B)), recv(nb) ... • Before the step • T = T1 = {a,b,e,[a,na]*pk(B)} (T0 = {a,b,e}) • CS = {A:{a,b,e}, B:{a,b,e}} • After • CS = {A:T0, B:T0, [A,Na]*pk(b):T1} • by penc + pair • CS = {A:T0, B:T0, A:T1, Na:T1, b:T1} • by nif • CS = {A:T0, B:T0, A:T1, Na:T1}
The run (4) send([Na,nb]*pk(A)), recv( [na,Nb]*pk(a)), send([Nb]*pk(B)), recv(nb) ... • Before the step • T = T1 = {a,b,e,[a,na]*pk(B)} • T0 = {a,b,e} • CS = {A:T0, B:T0, A:T1, Na:T1} • After • T = T2 = T1 U {[Na,nb]*pk(A) • T1 = {a,b,e,[a,na]*pk(B)} • T0 = {a,b,e} • CS is unchanged
The run (5) recv( [na,Nb]*pk(a)), send([Nb]*pk(B)), recv(nb) ... • Before • T = T2 = T1 U {[Na,nb]*pk(A) • T1 = {a,b,e,[a,na]*pk(B)} • T0 = {a,b,e} • CS = {A:T0, B:T0, A:T1, Na:T1} • After • CS= {A:T0, B:T0, A:T1, Na:T1,[na,Nb]*pk(a):T2} • unify! (Na-> na , Nb -> nb and A-> a) • The unification has to be applied to the rest…
The run (5.1) recv([na,b]*pk(a)), send([nb]*pk(B)), recv(nb) ... • After the unification: • T = T2 = T1 U {[na,nb]*pk(a) • T1 = {a,b,e,[a,na]*pk(B)} • T0 = {a,b,e} • CS= {a:T0, B:T0, a:T1, na:T1} • Unify • CS= {B:{a,b,e}, na: {a,b,e,[a,na]*pk(B)}}
The run (5.2) recv([na,b]*pk(a)), send([nb]*pk(e)), recv(nb) ... • After the unification: • CS= {B:{a,b,e}, na: {a,b,e,[a,na]*pk(B)}} • ksub (unification B -> e) + split • CS= {e:{a,b,e}, na: {a,b,e,a,na}} • unify twice, with empty answer • CS = {} • T = {[na,nb]*pk(a), a,b,e,[a,na]*pk(e)}
The run (5.3) send([nb]*pk(e)), recv(nb) ... • Before • CS = {} • T = {[na,nb]*pk(a), a,b,e,[a,na]*pk(e)} • After • CS = {} • T = {[na,nb]*pk(a), a,b,e,[a,na]*pk(e), [nb]*pk(e)}
The run (5.4) recv(nb) ... • Before • CS = {} • T = {[na,nb]*pk(a), a,b,e,[a,na]*pk(e), [nb]*pk(e)} • After • CS = {nb:{[na,nb]*pk(a), a,b,e,[a,na]*pk(e), [nb]*pk(e)}} • pdec • CS = {nb:{[na,nb]*pk(a), a,b,e,[a,na]*pk(e),nb}} • unify (empty substitution) • CS = {} !!!
The solution substitution • We ended up with an empty CS • => the system has a solution • in the process, reduction rules gave us the `solution substitution’ = {A->a,Na->na,Nb->b, B->e}
Part 3 Considerations
Laziness • We stop simplifying a constraint when the lhs is a variable. • This enforces a call-by-need mechanism. • As long as the lhs is a variable the constraint is trivially solvable. • If subsequent unification step instantiate the lhs of a constraint, then I check further if it can be solved. • It would be silly to guess.
An Example for Laziness • Consider two roles: roleA(X,A) = { recv(X), recv(X*pk(A)) } roleB(Na,A) = { send(Na*pk(A)) } • and this scenario:{roleA(X, b), roleB(na,b)} • initial intruder knowledge: {a,b,e} • there’s only one possible order: send(na*pk(b)), recv(X), recv(X*pk(b))