Tutorial: A Simple Unilateral Authentication Protocol
This is a introductory tutorial for the StrandsRocq framework which illustrates a simple unilateral authentication protocol based on nonces. The initiator
A sends a fresh nonce
Na to the responder
B who encrypts it together with
A's identifier under a symmetric key
SK A B shared with
A. The protocol in the Alice/Bob notation follows:
A → B : A ⋅ B ⋅ Na
B → A : ⟨ Na ⋅ A ⟩_(SK A B)
We use
⟨ M ⟩_k to note the encryption of
M under key
k and
M1 ⋅ M2 to denote the concatenation of
M1 and
M2.
From now onwards we assume basic familiarity with the Strand spaces framework, for more information please refer to
our paper or the
original Strand spaces paper.
We begin by importing some standard packages.
Require Import Lia.
Require Import Coq.Lists.List.
Import Coq.Lists.List.ListNotations.
StrandsRocq provides some default instances for the universes, the terms, and the penetrator.
These can be easily imported as follows.
Require Import StrandsRocq.Instances.DefaultInstances.
Require Import StrandsRocq.Instances.Penetrator.
Set Implicit Arguments.
Protocol Specification and Roles
Section SimpleAuthSpec.
The first step consists in specifying the protocol within our framework by inductively defining all the possible strands the participants can execute.
A strand is just a pair
(i, tr), where
i is a natural number identifying the strand and
tr is a trace. So, specifying the strands amounts to specifying the corresponding traces, since the identifier is immaterial and is usually universally quantified.
In this simple protocol, the initiator and responder each execute a single strand, parameterized by the principal names
A and
B and the nonce
Na. In general, each principal may execute multiple strands, as is the case with the penetrator.
The initiator sends the principal names
A and
B, and a fresh nonce
Na as output and reads the expected answer
⟨ Na ⋅ A ⟩_(SK A B) from the responder. Since the type of
A, B, Na is
T, representing atomic terms, we write
$Na to represent those values as generic term of type
𝔸. Symbols
⊕ and
⊖ respectively represent output and input nodes. Thus, the corresponding trace is
[ ⊕ $A ⋅ $B ⋅ $Na; ⊖ ⟨ $Na ⋅ $A ⟩_(SK A B) ]. Given
A, B, Na, initiator strands execute this trace, while responder strands execute the dual trace (with
⊖ and
⊕ exchanged), as formalized below:
Inductive SA_initiator_strand (A B Na : T) : Σ → Prop :=
| SAS_Init : ∀ i,
SA_initiator_strand A B Na (i, [ ⊕ $A ⋅ $B ⋅ $Na; ⊖ ⟨ $Na ⋅ $A ⟩_(SK A B) ]).
Inductive SA_responder_strand (A B Na : T) : Σ → Prop :=
| SAS_Resp : ∀ i,
SA_responder_strand A B Na (i, [ ⊖ $A ⋅ $B ⋅ $Na; ⊕ ⟨ $Na ⋅ $A ⟩_(SK A B) ]).
Finally, we define the protocol strand space as the combination of penetrator strands along with initiator and responder strands, parameterized by the penetrator's keys K__P:
Inductive SA_StrandSpace (K__P : K → Prop) : Σ → Prop :=
| SASS_Pen : ∀ s, penetrator_strand K__P s → SA_StrandSpace K__P s
| SASS_Init : ∀ (A B Na : T) s, SA_initiator_strand A B Na s → SA_StrandSpace K__P s
| SASS_Resp : ∀ (A B Na : T) s, SA_responder_strand A B Na s → SA_StrandSpace K__P s.
We are now ready to prove our first fact: no symmetric key SK U U' ever originates on a regular strand, i.e., neither the initiator nor the responder send symmetric keys as protocol messages.
Lemma SK_AB_never_originates_regular :
∀ C K__P, strandspace_bundle C (SA_StrandSpace K__P) →
∀ U U', never_originates_regular K__P (SK U U') C.
Proof.
intros C K__P [C_is_bundle His_SA] U U' n Hnodeof Horig.
specialize (His_SA n Hnodeof).
inversion His_SA as [s Hpen|A B Na s Hinires|A B Na s Hinires]; try easy;
rewrite <-H in Hinires;
destruct Hinires; apply strand_trace in H;
inversion H as [Htrace];
apply (originates_then_mpt Htrace) in Horig;
unfold mpt in Horig; simpl in ×.
all: now simplify_prop in Horig.
Qed.
End SimpleAuthSpec.
Section SimpleAuthSecurity.
We consider a strand s, a bundle C, principals A, B, and a nonce Na. All of these variables are generic and thus universally quantified in this section, allowing statements to be more readable.
Variable s : Σ.
Variable C : edge_set__t.
Variable A B Na: T.
We assume that:
- C is a bundle of the protocol strand space, where fun k ⇒ k ≠ SK A B specifies that the attacker knows any key except SK A B for the given A and B. Notice that the attacker knows the key of any other participant. This assumption is the minimal requirement for ensuring protocol security with respect to the given principals A and B;
- s is an initiator strand with parameters A, B, Na;
- s is a strand of bundle C.
Hypothesis C_is_SA_bundle : strandspace_bundle C (SA_StrandSpace (fun k ⇒ k ≠ SK A B)).
Hypothesis s_is_SA_init : SA_initiator_strand A B Na s.
Hypothesis s_strand_of_C : is_strand_of s C.
C is a indeed bundle. This is useful for next definitions and proof.
Proposition C_is_bundle : is_bundle C.
Proof. now unfold strandspace_bundle in C_is_SA_bundle. Qed.
We now prove unilateral authentication properties of the protocol from the initiator's perspective.
Non-injective agreement
The first security property states that the initiator is guaranteed that the responder run the protocol and agreed on parameters
A,
B and
Na. Formally:
∃ s' : Σ,
SA_responder_strand A B Na s' ∧
is_strand_of s' C.
The proof revolves around showing that only the responder, with parameters
A,
B and
Na, can generate the expected ciphertext
⟨ $Na ⋅ $A ⟩_(SK A B).
We define the set of nodes belonging to bundle C, that have
⟨ $Na ⋅ $A ⟩_(SK A B) as subterm.
First, we define a function
Ncp from terms to
Prop that checks if
c ⊏ t and we prove its decidability.
Then, we partially instantiate
N,
N_iff_inC_p and
minimal_N_then_mpt from
MinimalMPT.v in order to respectively define
- Nc : the set of nodes whose term satisfy Ncp;
- Nc_iff_inC_Ncp : the logical characterization of nodes belonging to Nc, i.e., In n (nodes_of C) ∧ Ncp (uns_term n)
- minimal_Nc_then_mpt : a lemma that allows to characterize the minimal of Nc as a Prop.
Definition Ncp (t : 𝔸) := (⟨ $Na ⋅ $A ⟩_(SK A B)) ⊏ t.
#[local] Hint Unfold Ncp uns term uns_term : core.
Lemma Ncp_dec : ∀ t, { Ncp t } + { ¬ Ncp t }.
Proof.
intros t;
destruct (A_subterm_dec (⟨ $Na ⋅ $A ⟩_(SK A B)) t);
(try now right); (try now left).
Qed.
Definition Nc := N Ncp C Ncp_dec.
Definition Nc_iff_inC_Ncp := N_iff_inC_p Ncp C Ncp_dec.
Definition minimal_Nc_then_mpt := minimal_N_then_mpt Ncp C_is_bundle Ncp_dec.
We now prove that Nc is not empty. This trivially comes from the fact that the term of second node of the initiator strand is indeed equal to ⟨ $Na ⋅ $A ⟩_(SK A B)
Lemma Nc_non_empty :
Nc ≠ nil.
Proof.
unfold Nc.
specialize (s_is_SA_init) as Ht.
inversion Ht as [i Htrs].
specialize (s_strand_of_C (s, 1)) as HinC;
specialize (Nc_iff_inC_Ncp (s, 1)) as [_ Hin].
st_implication HinC.
intros Heq; rewrite Heq in Hin.
destruct Hin; split; try easy.
autounfold. simpl; auto.
Qed.
We now prove noninjective agreement. The proof is based on lemma exists_minimal_bundle stating that each nonempty subset of nodes has a minimal with respect to the bundle_le (⪯) relation. We apply the lemma to Nc. This minimal node is, intuitively, the one that performs the encryption in ⟨ $Na ⋅ $A ⟩_(SK A B). We reason by cases and we prove that this minimal node cannot lie neither on a Penetrator nor on an Initiator strand. It lies instead on a Responder strand whose parameters can be proved to be exactly A, B and Na, as required by the agreement property. Notice, in fact, that ⟨ $Na ⋅ $A ⟩_(SK A B) contains A, Na in the payload and B, implicitly, in the key SK A B.
Proposition noninjective_agreement :
∃ s' : Σ,
SA_responder_strand A B Na s' ∧
is_strand_of s' C.
Proof.
specialize (exists_minimal_bundle C_is_bundle Nc_non_empty) as [m [Hin Hmin]].
assert (Hin':=Hin).
apply (Nc_iff_inC_Ncp) in Hin' as [HinC HNcp].
inversion s_is_SA_init as [i Hstrace0].
destruct C_is_SA_bundle as [His_bundle HSS].
specialize (HSS m HinC) as His_SA.
inversion His_SA as [s' Hpen|A' B' Na' s' Hini|A' B' Na' s' Hres].
Penetrator case
- inversion Hpen as
[t j Htrace|g j Htrace|g j Htrace|g h j Htrace|g h j Htrace|
k Hpenkey j Htrace| k h j Htrace|k h j Htrace].
we now use the minimal_Nc_then_mpt lemma which provides a characterization of the minimal element of Nc in terms of a Prop covering all the possible cases.
all: apply (f_equal tr) in Htrace;
specialize (minimal_Nc_then_mpt Htrace Hin Hmin) as Hmpti;
autounfold in Hmpti; simpl in Hmpti.
For example, for the first case, which is the output of an atomic term
t written
[⊕ $ t] we obtain the following Prop:
False ∨ ⟨ $ Na ⋅ $ A ⟩_ SK A B = $ t ∧ True ∧ index m = 0, stating that
⟨ $ Na ⋅ $ A ⟩_ SK A B = $ t, which is clearly false since
$ t is an atomic term.
In other cases the
Prop is more complicate. For example, for pair generation
[⊖ g; ⊖ h; ⊕ g ⋅ h] we obtain
((False ∨
(⟨ $ Na ⋅ $ A ⟩_ SK A B) ⊏ g ∧
False ∧ index m = 0) ∨
¬ (⟨ $ Na ⋅ $ A ⟩_ SK A B) ⊏ g ∧
(⟨ $ Na ⋅ $ A ⟩_ SK A B) ⊏ h ∧
False ∧ index m = 1) ∨
¬ (⟨ $ Na ⋅ $ A ⟩_ SK A B) ⊏ g ∧
¬ (⟨ $ Na ⋅ $ A ⟩_ SK A B) ⊏ h ∧
(⟨ $ Na ⋅ $ A ⟩_ SK A B = g ⋅ h ∨
(⟨ $ Na ⋅ $ A ⟩_ SK A B) ⊏ g ∨
(⟨ $ Na ⋅ $ A ⟩_ SK A B) ⊏ h) ∧
True ∧ index m = 2
that is less trivial to analyze by hand.
We developed a tactic named
simplify_prop, which simplifies the
Prop by eliminating all cases except the nontrivial ones. For this protocol, the only nontrivial case is encryption:
[⊖ # (SK A B); ⊖ $ Na ⋅ $ A; ⊕ c], the only penetrator strand that generates a ciphertext.
all: simplify_prop in Hmpti; try tauto.
We easily eliminate this case by exploiting the fact that the penetrator can never learn a secure symmetric key, in this case SK A B.
specialize
(index_lt_strand_implies_is_node_of C_is_bundle (strand m, 0) m)
as Hnodeof; st_implication Hnodeof.
specialize
(penetrator_never_learn_secure_encryption_key C_is_bundle
(SK_AB_never_originates_regular C_is_SA_bundle A B)
Hpen Htrace Hnodeof) as Hkey; st_implication Hkey.
Initiator case: this is trivially solved by the simplify_prop tactic
- inversion Hini as [j Htrace]; apply (f_equal tr) in Htrace.
specialize (minimal_Nc_then_mpt Htrace Hin Hmin) as Hmpti;
autounfold in Hmpti; simpl in Hmpti.
simplify_prop in Hmpti.
Responder case:
- inversion Hres as [j Htrace]. apply (f_equal tr) in Htrace.
specialize (minimal_Nc_then_mpt Htrace Hin Hmin) as Hmpti;
autounfold in Hmpti; simpl in Hmpti.
simplify_prop in Hmpti; try rewrite Hand2.
Here we have two cases depending whether A and B are equal or not. Both cases are solved trivially as they provide a valid binding for the protocol parameters.
all: ∃ (strand m); split; auto;
specialize (last_node_implies_is_strand_of C_is_bundle m) as Hsof;
st_implication Hsof.
Qed.
Injective agreement
The second security property additionally states that each responder session correspond to different initiator session, i.e., that authentication is injective and cannot be reused in a replay attack. This property only holds if
Na is freshly generated which, in the strand spaces model, is captured by the
uniquely_originates definition. Formally:
uniquely_originates $Na →
(
∃ s' : Σ,
SA_responder_strand A B Na s' ∧
is_strand_of s' C
)
∧
(
∀ s'' : Σ,
SA_initiator_strand A B Na s'' →
s'' = s
).
We first prove injectivity alone, i.e., if
Na uniquely originates then there's a unique initiator trace agreeing on
Na. The proof just applies the definition on
uniquely_originates:
Proposition injectivity :
uniquely_originates $Na →
∀ U U' s',
SA_initiator_strand U U' Na s' →
s' = s.
Proof.
intros Huorig U U' s' Hini'.
inversion Hini' as [i' Htrace'].
specialize s_is_SA_init as Hini.
inversion Hini as [i Htrace].
inversion Huorig as [n [_ Horigx]].
assert (Horigx' := Horigx).
specialize (Horigx (s, 0)); specialize (Horigx' (s', 0)).
specialize (mpti_then_originates $Na (s, 0)) as Horig.
specialize (mpti_then_originates $Na (s', 0)) as Horig'.
simplify_term_in Horig; st_implication Horig.
simplify_term_in Horig'; st_implication Horig'.
specialize (Horigx Horig); specialize (Horigx' Horig'); subst.
now inversion Horigx'.
Qed.
From noninjective_agreement and injectivity we obtain injective agreement as a corollary:
Corollary injective_agreement :
uniquely_originates $Na →
(
∃ s' : Σ,
SA_responder_strand A B Na s' ∧
is_strand_of s' C
)
∧
(
∀ s'' : Σ,
SA_initiator_strand A B Na s'' →
s'' = s
).
Proof.
intros Huniq. split.
- now apply noninjective_agreement.
- now apply injectivity.
Qed.
End SimpleAuthSecurity.