Skip to content

Commit a67e565

Browse files
hyperpolymathclaude
andcommitted
proof(E5): Session 6 — single-session NS-Lowe secrecy + headline needham_schroeder_fixed Qed
Session 6 on the E5 full-headline port lands the MANIFEST-expected headline theorem [needham_schroeder_fixed] for a single honest session of the Lowe-fixed Needham-Schroeder symmetric-key protocol, closed against the Dolev-Yao attacker defined in Sessions 1-3. Contents landed (§8): * [session_state] record (initiator, responder, N_A, N_B). * [single_session_trace] — the three-event wire shape of one honest execution: 1. A -> B : {N_A, A, B}_{K_AB} 2. B -> A : {N_A, N_B}_{K_AB} 3. A -> B : {N_B}_{K_AB} * [single_session_trace_is_enc_only] — bridge to Session 5's [enc_only_trace] predicate: every wire message on a single- session trace is [mEnc (shared_key A B) _]. * [single_session_wire_messages] — explicit wire-message list, used for non-membership arguments. * [nb_not_on_wire] — [mNonce N_B] is not among the three wire-message ciphertexts (constructor discrimination). * [any_enc_trace] + [single_session_is_any_enc] + [knows_any_enc_trace_is_mEnc_shaped] — structural invariant: on a trace whose wire messages are all [mEnc]-shaped, every attacker-derivable message is also [mEnc]-shaped. The invariant-based proof avoids the circularity that would otherwise sit between "no key derivable" and Session 5's enc-only closure. * [single_session_no_key_derivable] — the attacker cannot derive ANY [mKey k] on a single-session trace (immediate corollary of the shape invariant). * [ns_lowe_single_session_secrecy] + [ns_lowe_single_session_ initiator_nonce_secrecy] — keyed secrecy for responder and initiator nonces respectively, closed by routing through [enc_trace_unobserved_unreachable] (Session 5). * [ns_lowe_responder_nonce_secret] + [ns_lowe_initiator_nonce_ secret] — user-facing forms with the [~ knows tr (mKey _)] side obligation discharged automatically by [single_session_ no_key_derivable]. * [needham_schroeder_fixed] — the headline theorem: forall (ses : session_state) (tr : trace), single_session_trace ses tr -> ~ knows tr (mNonce (ss_na ses)) /\ ~ knows tr (mNonce (ss_nb ses)). Verification: * coqc E5_needham_schroeder_fix.v — Qed, exit 0. * panic-attack assail — 0 weak points on 1079 LOC. * grep banned constructs (believe_me/assert_total/Admitted/ sorry/unsafeCoerce/Obj.magic) — 0 code hits. * Suite-wide recompile — 38/38 Qed, no regressions. MANIFEST pointer remains intact: the headline [needham_schroeder_ fixed] is now Qed-closed for the single-session scope, but the multi-session composition case (attacker running arbitrarily many honest and dishonest sessions in parallel, with nonce/key leakage across sessions) is Session 7+ work. Per §3.1 honest-accounting, the MANIFEST entry can be revisited once the multi-session scope lands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 292113b commit a67e565

1 file changed

Lines changed: 293 additions & 7 deletions

File tree

proofs/canonical-proof-suite/E5_needham_schroeder_fix.v

Lines changed: 293 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -783,11 +783,297 @@ Qed.
783783
(* * singleton_enc_trace_recovery: Session 4 recovered *)
784784
(* as a specialisation (sanity check). *)
785785
(* *)
786-
(* Session 6+ target: *)
787-
(* * Per-session state record (honest nonces/keys vs. *)
788-
(* attacker-visible messages). *)
789-
(* * Full Needham-Schroeder-Lowe secrecy invariant and *)
790-
(* headline needham_schroeder_fixed Qed. *)
791-
(* ============================================================ *)
792-
(* End of E5 in-progress scaffold. *)
786+
(* Session 6 (DONE — §8 below): per-session state + headline *)
787+
(* needham_schroeder_fixed Qed for a single honest session. *)
793788
(* ============================================================ *)
789+
790+
(* ====================================================================
791+
§8 Per-session state + NS-Lowe secrecy invariant.
792+
==================================================================== *)
793+
794+
(** One NS-Lowe session's honest view. The attacker's view is the
795+
list of wire events; this record describes what the honest
796+
initiator/responder pair shares about the session — who is
797+
talking to whom and which nonces are bound to which role. *)
798+
Record session_state : Set := mkSession {
799+
ss_init : agent;
800+
ss_resp : agent;
801+
ss_na : nonce; (* initiator's fresh nonce, step-1 body *)
802+
ss_nb : nonce (* responder's fresh nonce, step-2 body *)
803+
}.
804+
805+
(** The wire-shape of a single honest execution of the Lowe-fixed
806+
NS-symmetric protocol. Exactly three events, in order:
807+
808+
1. A -> B : {N_A, A, B}_{K_AB} (fixed_step1_ciphertext)
809+
2. B -> A : {N_A, N_B}_{K_AB} (fixed_step2_ciphertext)
810+
3. A -> B : {N_B}_{K_AB} (fixed_step3_ciphertext)
811+
812+
This is the honest-world trace; the attacker is the environment
813+
sitting next to the wire. Secrecy in this session is the claim
814+
that, without the shared key, the attacker cannot derive N_B. *)
815+
Definition single_session_trace
816+
(ses : session_state) (tr : trace) : Prop :=
817+
tr = [ mkEvent (ss_init ses)
818+
(fixed_step1_ciphertext (ss_init ses)
819+
(ss_resp ses)
820+
(ss_na ses));
821+
mkEvent (ss_resp ses)
822+
(fixed_step2_ciphertext (ss_init ses)
823+
(ss_resp ses)
824+
(ss_na ses)
825+
(ss_nb ses));
826+
mkEvent (ss_init ses)
827+
(fixed_step3_ciphertext (ss_init ses)
828+
(ss_resp ses)
829+
(ss_nb ses)) ].
830+
831+
(** Every event in a single honest session has its message encrypted
832+
under the pair's shared key. This is the structural fact that
833+
connects the protocol-level trace to Session 5's enc-only-trace
834+
machinery. *)
835+
Lemma single_session_trace_is_enc_only :
836+
forall (ses : session_state) (tr : trace),
837+
single_session_trace ses tr ->
838+
enc_only_trace (shared_key (ss_init ses) (ss_resp ses)) tr.
839+
Proof.
840+
intros ses tr Htr. unfold enc_only_trace, single_session_trace in *.
841+
subst tr. intros e Hin.
842+
simpl in Hin.
843+
destruct Hin as [H1 | [H2 | [H3 | []]]]; subst e; simpl.
844+
- (* step 1 *)
845+
exists (mPair (mNonce (ss_na ses))
846+
(mPair (mAgent (ss_init ses)) (mAgent (ss_resp ses)))).
847+
unfold fixed_step1_ciphertext. reflexivity.
848+
- (* step 2 *)
849+
exists (mPair (mNonce (ss_na ses)) (mNonce (ss_nb ses))).
850+
unfold fixed_step2_ciphertext. reflexivity.
851+
- (* step 3 *)
852+
exists (mNonce (ss_nb ses)).
853+
unfold fixed_step3_ciphertext. reflexivity.
854+
Qed.
855+
856+
(** The observed ciphertexts of a single honest session. This is
857+
exactly [map ev_msg tr]. Stated explicitly so we can reason
858+
about non-membership of the responder nonce. *)
859+
Lemma single_session_wire_messages :
860+
forall (ses : session_state) (tr : trace),
861+
single_session_trace ses tr ->
862+
map ev_msg tr =
863+
[ fixed_step1_ciphertext (ss_init ses) (ss_resp ses) (ss_na ses);
864+
fixed_step2_ciphertext (ss_init ses) (ss_resp ses) (ss_na ses) (ss_nb ses);
865+
fixed_step3_ciphertext (ss_init ses) (ss_resp ses) (ss_nb ses) ].
866+
Proof.
867+
intros ses tr Htr. unfold single_session_trace in Htr. subst tr.
868+
simpl. reflexivity.
869+
Qed.
870+
871+
(** Constructor discrimination: a [mNonce] is never an [mEnc]. This
872+
is immediate from the [msg] inductive but we package it for the
873+
non-membership argument that drives secrecy. *)
874+
Lemma mNonce_ne_mEnc :
875+
forall (n : nonce) (k : key) (m : msg),
876+
mNonce n <> mEnc k m.
877+
Proof. intros n k m H. inversion H. Qed.
878+
879+
(** The responder nonce is NOT among the observed wire messages of
880+
a single honest session. Every wire message is [mEnc]-shaped;
881+
[mNonce _] cannot match. *)
882+
Lemma nb_not_on_wire :
883+
forall (ses : session_state) (tr : trace),
884+
single_session_trace ses tr ->
885+
~ In (mNonce (ss_nb ses)) (map ev_msg tr).
886+
Proof.
887+
intros ses tr Htr Hin.
888+
rewrite (@single_session_wire_messages ses tr Htr) in Hin.
889+
simpl in Hin.
890+
destruct Hin as [H1 | [H2 | [H3 | []]]].
891+
- unfold fixed_step1_ciphertext in H1. inversion H1.
892+
- unfold fixed_step2_ciphertext in H2. inversion H2.
893+
- unfold fixed_step3_ciphertext in H3. inversion H3.
894+
Qed.
895+
896+
(** **NS-Lowe secrecy invariant for a single honest session.**
897+
898+
Statement. For any honest single-session trace between the
899+
initiator [A = ss_init ses] and the responder [B = ss_resp ses],
900+
if the attacker does not know the pair's shared key
901+
[K_AB = shared_key A B], then the attacker cannot derive the
902+
responder's fresh nonce [N_B = ss_nb ses].
903+
904+
Proof. The trace is an enc-only trace under [K_AB] by
905+
[single_session_trace_is_enc_only]. By Session 5's
906+
[enc_trace_unobserved_unreachable], any message outside the wire
907+
is unreachable. [nb_not_on_wire] says [mNonce N_B] is not on
908+
the wire, hence it's unreachable. *)
909+
Theorem ns_lowe_single_session_secrecy :
910+
forall (ses : session_state) (tr : trace),
911+
single_session_trace ses tr ->
912+
~ knows tr (mKey (shared_key (ss_init ses) (ss_resp ses))) ->
913+
~ knows tr (mNonce (ss_nb ses)).
914+
Proof.
915+
intros ses tr Htr Hno_key.
916+
apply (@enc_trace_unobserved_unreachable
917+
(shared_key (ss_init ses) (ss_resp ses))
918+
tr (mNonce (ss_nb ses))).
919+
- apply single_session_trace_is_enc_only. exact Htr.
920+
- exact Hno_key.
921+
- apply nb_not_on_wire. exact Htr.
922+
Qed.
923+
924+
(** Analogue for the initiator nonce [N_A]. Same proof skeleton:
925+
[N_A] is also not on the wire (every wire message is
926+
[mEnc]-shaped), and the enc-only/no-key closure gives the bound.
927+
A single-session full-secrecy package. *)
928+
Theorem ns_lowe_single_session_initiator_nonce_secrecy :
929+
forall (ses : session_state) (tr : trace),
930+
single_session_trace ses tr ->
931+
~ knows tr (mKey (shared_key (ss_init ses) (ss_resp ses))) ->
932+
~ knows tr (mNonce (ss_na ses)).
933+
Proof.
934+
intros ses tr Htr Hno_key.
935+
apply (@enc_trace_unobserved_unreachable
936+
(shared_key (ss_init ses) (ss_resp ses))
937+
tr (mNonce (ss_na ses))).
938+
- apply single_session_trace_is_enc_only. exact Htr.
939+
- exact Hno_key.
940+
- (* mNonce (ss_na ses) is not on the wire. *)
941+
intros Hin.
942+
rewrite (@single_session_wire_messages ses tr Htr) in Hin.
943+
simpl in Hin.
944+
destruct Hin as [H1 | [H2 | [H3 | []]]].
945+
+ unfold fixed_step1_ciphertext in H1. inversion H1.
946+
+ unfold fixed_step2_ciphertext in H2. inversion H2.
947+
+ unfold fixed_step3_ciphertext in H3. inversion H3.
948+
Qed.
949+
950+
(** Combined single-session secrecy: neither party's nonce is
951+
derivable by the attacker under honest-key conditions. *)
952+
Theorem ns_lowe_single_session_full_secrecy :
953+
forall (ses : session_state) (tr : trace),
954+
single_session_trace ses tr ->
955+
~ knows tr (mKey (shared_key (ss_init ses) (ss_resp ses))) ->
956+
~ knows tr (mNonce (ss_na ses)) /\ ~ knows tr (mNonce (ss_nb ses)).
957+
Proof.
958+
intros ses tr Htr Hno_key.
959+
split.
960+
- apply ns_lowe_single_session_initiator_nonce_secrecy; assumption.
961+
- apply ns_lowe_single_session_secrecy; assumption.
962+
Qed.
963+
964+
(** Link to the [honest_pair] predicate from Session 3. The more
965+
natural user-facing statement: an honest pair (uncompromised
966+
shared key) with a completed honest session has secret nonces.
967+
968+
Caveat: the [compromised] predicate tracks attacker knowledge of
969+
keys as a semantic fact, and ruling out [knows tr (mKey k)]
970+
requires showing the trace itself does not expose the key.
971+
For the single-session trace, no event has [ev_msg = mKey _],
972+
so the key can only be derived via [k_pairL]/[k_pairR]/[k_dec].
973+
None of the wire messages are [mPair], and [k_dec] requires
974+
another key the attacker already has. We close this below:
975+
on a single-session trace, the attacker cannot derive ANY
976+
[mKey _] at all (no matter which key). *)
977+
978+
(** Every wire message of a single honest session has the shape
979+
[mEnc _ _] (under any key — we drop the specific [shared_key]
980+
index here). This weaker predicate is enough for the
981+
"no-key-derivable" invariant below, and avoids any circularity
982+
with [enc_trace_knows_is_observed]. *)
983+
Definition any_enc_trace (tr : trace) : Prop :=
984+
forall ev, In ev tr -> exists k body, ev_msg ev = mEnc k body.
985+
986+
Lemma single_session_is_any_enc :
987+
forall ses tr,
988+
single_session_trace ses tr -> any_enc_trace tr.
989+
Proof.
990+
intros ses tr Htr. unfold any_enc_trace. intros ev Hin.
991+
unfold single_session_trace in Htr. subst tr. simpl in Hin.
992+
destruct Hin as [H1 | [H2 | [H3 | []]]]; subst ev; simpl.
993+
- eexists. eexists. unfold fixed_step1_ciphertext. reflexivity.
994+
- eexists. eexists. unfold fixed_step2_ciphertext. reflexivity.
995+
- eexists. eexists. unfold fixed_step3_ciphertext. reflexivity.
996+
Qed.
997+
998+
(** Structural invariant. On any trace whose wire messages are all
999+
[mEnc]-shaped, every attacker-derivable message is also
1000+
[mEnc]-shaped. Proof by induction on [knows]: the [k_obs] case
1001+
is the hypothesis; the two [k_pair*] cases force [mPair ... =
1002+
mEnc ...] by IH, contradiction; the [k_dec] case forces
1003+
[mKey ... = mEnc ...] via [IHkey], contradiction. The [k_dec]
1004+
case's conclusion [mbody]-shape is vacuous after that
1005+
contradiction. *)
1006+
Lemma knows_any_enc_trace_is_mEnc_shaped :
1007+
forall tr,
1008+
any_enc_trace tr ->
1009+
forall m, knows tr m -> exists k body, m = mEnc k body.
1010+
Proof.
1011+
intros tr Henc m Hknow.
1012+
induction Hknow as
1013+
[ ev Hin
1014+
| l r _ IHpair
1015+
| l r _ IHpair
1016+
| k' mbody _ IHenc Hkey IHkey ].
1017+
- specialize (Henc ev Hin) as [k' [body Hb]].
1018+
exists k', body. exact Hb.
1019+
- destruct IHpair as [k' [body Heq]]. inversion Heq.
1020+
- destruct IHpair as [k' [body Heq]]. inversion Heq.
1021+
- destruct IHkey as [k'' [body Heq]]. inversion Heq.
1022+
Qed.
1023+
1024+
(** No key is ever attacker-derivable on a single honest session
1025+
trace. Routes through the invariant above: any derived message
1026+
must be [mEnc]-shaped, but [mKey _] is not. *)
1027+
Lemma single_session_no_key_derivable :
1028+
forall (ses : session_state) (tr : trace) (k : key),
1029+
single_session_trace ses tr ->
1030+
~ knows tr (mKey k).
1031+
Proof.
1032+
intros ses tr k Htr Hknow.
1033+
pose proof (@knows_any_enc_trace_is_mEnc_shaped tr
1034+
(@single_session_is_any_enc ses tr Htr)
1035+
(mKey k) Hknow) as [k' [body Heq]].
1036+
inversion Heq.
1037+
Qed.
1038+
1039+
(** User-facing NS-Lowe secrecy: on an honest single session, the
1040+
responder nonce is not attacker-derivable. No explicit
1041+
[~ knows tr (mKey _)] hypothesis needed — [single_session_no_
1042+
key_derivable] closes that side obligation automatically from
1043+
the enc-only structure of the wire. *)
1044+
Theorem ns_lowe_responder_nonce_secret :
1045+
forall (ses : session_state) (tr : trace),
1046+
single_session_trace ses tr ->
1047+
~ knows tr (mNonce (ss_nb ses)).
1048+
Proof.
1049+
intros ses tr Htr.
1050+
apply ns_lowe_single_session_secrecy.
1051+
- exact Htr.
1052+
- apply (@single_session_no_key_derivable ses tr). exact Htr.
1053+
Qed.
1054+
1055+
Theorem ns_lowe_initiator_nonce_secret :
1056+
forall (ses : session_state) (tr : trace),
1057+
single_session_trace ses tr ->
1058+
~ knows tr (mNonce (ss_na ses)).
1059+
Proof.
1060+
intros ses tr Htr.
1061+
apply ns_lowe_single_session_initiator_nonce_secrecy.
1062+
- exact Htr.
1063+
- apply (@single_session_no_key_derivable ses tr). exact Htr.
1064+
Qed.
1065+
1066+
(** **Headline.** The Needham-Schroeder-Lowe fix guarantees secrecy
1067+
of both nonces on an honest single session. This is the
1068+
MANIFEST-expected headline for the current scope — single honest
1069+
session. Multi-session composition and cross-session
1070+
interference is Session 7+ work. *)
1071+
Theorem needham_schroeder_fixed :
1072+
forall (ses : session_state) (tr : trace),
1073+
single_session_trace ses tr ->
1074+
~ knows tr (mNonce (ss_na ses)) /\ ~ knows tr (mNonce (ss_nb ses)).
1075+
Proof.
1076+
intros ses tr Htr. split.
1077+
- apply ns_lowe_initiator_nonce_secret. exact Htr.
1078+
- apply ns_lowe_responder_nonce_secret. exact Htr.
1079+
Qed.

0 commit comments

Comments
 (0)