/* Tutorial for the Tamarin prover for security protocol analysis ============================================================== Authors: Simon Meier, Benedikt Schmidt Date: September 2012 Introduction ------------ This user guide assumes that you have a copy of the submitted draft of Meier's PhD thesis, which is available from http://www.infsec.ethz.ch/research/software/tamarin. The input files for the Tamarin prover have the extension .spthy, which is short for 'security protocol theory'. A security protocol theory specifies 1. the signature and equational theory to use for the message algebra, 2. the set of multiset rewriting rules modeling the protocol and the adversary capabilities, and 3. the guarded trace properties whose satisfiability or validity we wish to check for this set of multiset rewriting rules. We explain each of these parts where they occur in the following security protocol theory. Before we start, let us add a few notes on the syntax. As you probably noticed, comments are C-style. All identifiers are case-sensitive. The parser is layout-insensitive, i.e., your are free to use whitespace as it suits you. We provide a complete specification of the input syntax in the REFERENCE MANUAL (available at doc/MANUAL.md). Modeling a security protocol ---------------------------- Every security protocol theory starts with a header of the following form. */ theory Tutorial begin /* Obviously, you can replace 'Tutorial' with any name you like to give your theory. After 'begin', you can declare function symbols, equations that they must satisfy, multiset rewriting rules, and lemmas specifying security properties. Moreover, you can also insert formal comments, to structure your theory. We give examples of each of these elements while modeling a simple protocol. In this protocol, a client C generates a fresh symmetric key 'k', encrypts it with the public key of a server 'S' and sends it to 'S'. The server confirms the receipt of the key by sending back its hash to the client. In Alice-and-Bob notation the protocol would read as follows. C -> S: aenc{k}pk(S) C <- S: h(k) This protocol is artificial and it satisfies only very weak security guarantees. We can prove that from the perspective of the client, the freshly generated key is secret provided that the server is uncompromised. We model this protocol in three steps. First, we declare the function symbols and the equations defining them. Then, we introduce multiset rewriting rules modeling a public key infrastructure (PKI) and the protocol. Finally, we state the expected security properties. Function Signature and Equational Theory ---------------------------------------- We model hashing using the unary function 'h'. We model asymmetric encryption by declaring a binary function 'aenc' denoting the encryption algorithm, a binary function 'adec' denoting the decryption algorithm, and a unary function 'pk' denoting the algorithm computing a public key from a private key. */ functions: h/1, aenc/2, adec/2, pk/1 equations: adec(aenc(m, pk(k)), k) = m /* The above equation models the interaction between calls to these three algorithms. All such user-specified equations must be subterm-convergent rewriting rules, when oriented from left to right. This means that the right-hand-side must be a subterm of the left-hand-side or a nullary function symbol. Certain equational theories are used very often when modeling cryptographic messages. We therefore provide builtins definitions for them, using the keyword 'builtins'. The above theory could also be enabled using the declaration builtins: hashing, asymmetric-encryption We support the following builtins theories: diffie-hellman, signing, asymmetric-encryption, symmetric-encryption, hashing Note that the theory for hashing only introduces the function symbol 'h/1' and contains no equations. Apart from 'diffie-hellman', all of these theories are subterm-convergent and can therefore also be declared directly, as above. You can inspect their definitions by uncommenting the following two line-comments and calling tamarin-prover Tutorial.spthy */ // builtins: diffie-hellman, signing, asymmetric-encryption, symmetric-encryption, // hashing /* The call 'tamarin-prover Tutorial.spthy' parses the Tutorial.spthy file, computes the variants of the multiset rewriting rules, checks their wellformedness (explained below), and pretty-prints the theory. The declaration of the signature and the equations can be found at the top of the pretty-printed theory. Proving all lemmas contained in the theory is as simple as adding the flag '--prove' to the call; i.e., tamarin-prover Tutorial.spthy --prove However, let's not go there yet. We first have to model the PKI and our protocol. Modeling the Public Key Infrastructure -------------------------------------- */ // Registering a public key rule Register_pk: [ Fr(~ltk) ] --> [ !Ltk($A, ~ltk), !Pk($A, pk(~ltk)) ] /* The above rule models registering a public key. It makes use of the following syntax. Facts always start with an upper-case letter and do not have to be declared. If their name is prefixed with an exclamation mark '!', then they are persistent. Otherwise, they are linear. Note that you must use every fact name consistently; i.e., you must always use it with the same arity, casing, and multiplicity. Otherwise, the tamarin prover complains that the theory is not wellformed. The 'Fr' fact is a builtins fact. It denotes a freshly generated fresh name. See the paper for details. We denote the sort of variables using prefixes: ~x denotes x:fresh $x denotes x:pub #i denotes i:temporal i denotes i:msg 'c' denotes a public name 'c \in PN'; i.e., a fixed, global constant Thus, the above rule can be read as follows. First, freshly generate a fresh name 'ltk', the new private key and nondeterministically choose a public name 'A', the agent for which we are generating the key-pair. Then, generate the persistent fact !Ltk($A, ~ltk), which denotes the association between agent 'A' and its private key 'ltk, and generate the persistent fact !Pk($A, pk(~ltk)), which denotes the association between the agent 'A' and its public key 'pk(~ltk)'. We allow the adversary to retrieve any public key using the following rule. Intuitively, it just reads a public-key database entry and sends the public key to the network using the builtins fact 'Out' denoting a message sent to the network. See our paper for more information. */ rule Get_pk: [ !Pk(A, pk) ] --> [ Out(pk) ] /* We model the dynamic compromise of long-term private keys using the following rule. Intuitively, it reads a private-key database entry and sends it to the adversary. This rule has an observable 'LtkReveal' action stating that the long-term key of agent 'A' was compromised. We will use this action in the security property below to determine which agents are compromised. */ rule Reveal_ltk: [ !Ltk(A, ltk) ] --[ LtkReveal(A) ]-> [ Out(ltk) ] /* Modeling the protocol ---------------------- Recall that we want to model the following protocol. C -> S: aenc{k}pk(S) C <- S: h(k) We model it using the following three rules. */ // Start a new thread executing the client role, choosing the server // non-deterministically. rule Client_1: [ Fr(~k) // choose fresh key , !Pk($S, pkS) // lookup public-key of server ] --> [ Client_1( $S, ~k ) // Store server and key for next step of thread , Out( aenc{'1', ~k}pkS ) // Send the encrypted session key to the server // We add the tag '1' to the request to allow // the server to check whether the decryption // was successful. ] rule Client_2: [ Client_1(S, k) // Retrieve server and session key from previous step , In( h(k) ) // Receive hashed session key from network ] --[ SessKeyC( S, k ) ]-> // State that the session key 'k' [] // was setup with server 'S' // A server thread answering in one-step to a session-key setup request from // some client. rule Serv_1: [ !Ltk($S, ~ltkS) // lookup the private-key , In( request ) // receive a request ] --[ Eq(fst(adec(request, ~ltkS)), '1') , AnswerRequest($S, snd(adec(request, ~ltkS))) // Explanation below ]-> [ Out( h(snd(adec(request, ~ltkS))) ) ] // Return the hash of the // decrypted request. /* Above, we model all applications of cryptographic algorithms explicitly. Call 'tamarin-prover Tutorial.spthy' to inspect the finite variants of the Serv_1 rule, which list all possible interactions of the destructors used. In our proof search, we will consider all these interactions. We also model that the server explicitly checks that the first component of the request is equal to '1'. We model this by logging the claimed equality and then adapting the security property such that it only considers traces where all 'Eq' actions occur with two equal arguments. Note that 'Eq' is NO builtin fact. Guarded trace properties are strong enough to formalize this requirement without builtin support. Note that inequalities can be modeled analogously. We log the session-key setup requests received by servers to allow formalizing the authentication property for the client. Modeling the security properties -------------------------------- The syntax for specifying security properties uses All for universal quantification, temporal variables are prefixed with # Ex for existential quantification, temporal variables are prefixed with # ==> for implication & for conjunction | for disjunction not for negation f @ i for action constraints, the sort prefix for the temporal variable 'i' is optional i < j for temporal ordering, the sort prefix for the temporal variables 'i' and 'j' is optional #i = #j for an equality between temporal variables 'i' and 'j' x = y for an equality between message variables 'x' and 'y' Note that apart from public names (delimited using single-quotes), no terms may occur in guarded trace properties. Moreover, all variables must be guarded. The error message for an unguarded variable is currently not very good. For universally quantified variables, one has to check that they all occur in an action constraint right after the quantifier and that the outermost logical operator inside the quantifier is an implication. For existentially quantified variables, one has to check that they all occur in an action constraint right after the quantifier and that the outermost logical operator inside the quantifier is a conjunction. Note also that currently the precedence of the logical connectives is not specified. We therefore recommend to use parentheses, when in doubt. */ /* Note that you can specify additional restrictions that restrict the set of considered traces. In this example, we restrict our attention to traces where all equality checks succeed. */ restriction Equality_Checks_Succeed: "All x y #i. Eq(x,y) @ i ==> x = y" /* The following two properties should be self-explanatory. Note that the order between restrictions and lemmas does not matter. All restrictions are always available/assumed in the proofs of all security properties. */ lemma Client_session_key_secrecy: " /* It cannot be that a */ not( Ex S k #i #j. /* client has set up a session key 'k' with a server'S' */ SessKeyC(S, k) @ #i /* and the adversary knows 'k' */ & K(k) @ #j /* without having performed a long-term key reveal on 'S'. */ & not(Ex #r. LtkReveal(S) @ r) ) " lemma Client_auth: " /* For all session keys 'k' setup by clients with a server 'S' */ ( All S k #i. SessKeyC(S, k) @ #i ==> /* there is a server that answered the request */ ( (Ex #a. AnswerRequest(S, k) @ a) /* or the intruder performed a long-term key reveal on 'S' before the key was setup. */ | (Ex #r. LtkReveal(S) @ r & r < i) ) ) " /* Note that we can also strengthen the authentication property to a version of injective authentication. Our formulation is stronger than the standard formulation of injective authentication, as it is based on uniqueness instead of counting. For most protocols, that guarantee injective authentication one can also prove such a uniqueness claim, as they agree on appropriate fresh data. */ lemma Client_auth_injective: " /* For all session keys 'k' setup by clients with a server 'S' */ ( All S k #i. SessKeyC(S, k) @ #i ==> /* there is a server that answered the request */ ( (Ex #a. AnswerRequest(S, k) @ a /* and there is no other client that had the same request */ & (All #j. SessKeyC(S, k) @ #j ==> #i = #j) ) /* or the intruder performed a long-term key reveal on 'S' before the key was setup. */ | (Ex #r. LtkReveal(S) @ r & r < i) ) ) " /* You can verify them by calling tamarin-prover --prove Tutorial.spthy This will first output some logging from the constraint solver and then the Tutorial security protocol theory with the lemmas and their attached (dis)proofs. Note that when adding inconsistent restrictions, you can prove any property. To check that there still exist traces, you can state an 'exists-trace' lemma. When modeling protocols such existence proofs are very useful sanity checks. The following property must be provable, as otherwise there would be no possibility to setup a session key with a honest sever. */ lemma Client_session_key_honest_setup: exists-trace " Ex S k #i. SessKeyC(S, k) @ #i & not(Ex #r. LtkReveal(S) @ r) " /* Interactive proof visualization and construction ------------------------------------------------ Just call tamarin-prover interactive Tutorial.spthy This will start a web-server that loads all security protocol theories in the same directory as Tutorial.spthy. Point your browser to http://localhost:3001 and explore the the Tutorial theory interactively by clicking on the 'Tutorial' entry in the table of loaded theories. You can prove a lemma interactively by clicking on the available proof methods (corresponding to applications of constraint reduction rules) or by calling the 'autoprover' by right-clicking on a node in the theory overview. Note that that the proof methods in the GUI are sorted according to our heuristic. Always selecting the first proof method will result in the same proof as the ones constructed by the 'autoprover' and '--prove'. Conclusion ---------- By now, you should have enough knowledge to understand the case studies included with Tamarin. Recall that you can find them in the directory listed at the bottom of the help message, when calling 'tamarin-prover' without any arguments. Note that Tamarin also outputs the path to the reference MANUAL specifying and explaining the grammar of security protocol theories and giving some additional hints on additional theory exploited by Tamarin. If you have further questions, please do not hesitate to contact either Benedikt Schmidt benedikt.schmidt@inf.ethz.ch Simon Meier iridcode@gmail.com Cas Cremers cas.cremers@cs.ox.ac.uk BTW, every security protocol theory must be delimited with 'end'. (-: HAPPY PROVING :-) */ end