aboutsummaryrefslogtreecommitdiff
path: root/sof-dhcr.spthy
blob: 8d4d1413d71813a6e97722dda29eddb69a345f68 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/* Copyright 2026 Osmium Sorcerer
 * SPDX-License-Identifier: MIT
 */

theory DHCR
begin

builtins: hashing, diffie-hellman

/* Configuration of a username-certificate pair on the server. This sets up
 * the identity of a user prior to authentication. Client knows the secret key
 * for its username, and the server uses the corresponding certificate (public
 * key) to authenticate the client.
 *
 * ~sk is a randomly generated secret key bound to the username.
 * 'g'^~sk is the public key derived from the secret key ~sk.
 *
 * The public key is deliberately exposed at this step, as the security model
 * assumes both usernames and public keys to be nonsecret.
 */
rule Setup:
  [ Fr(~sk) ]
--[ UserSecretKey($U, ~sk) ]->
  [ !ConfiguredUser($U, 'g'^~sk), !UserSecret($U, ~sk), Out('g'^~sk) ]

/* Client begins the authentication flow by sending the username.
 * Honest participants know the secret key they must use for this.
 */
rule ClientAuthStart:
  [ !UserSecret($U, sk) ]
-->
  [ Out($U), ClientPending($U, sk) ]

/* Server generates a fresh ephemeral secret key ~ck, computes the public key
 * 'g'^~ck (challenge), and sends it to the client.
 *
 * Unlike the client's preconfigured, long-term identity key pair, server's
 * key is freshly randomly generated for every new authentication attempt and is
 * never reused.
 */
rule ServerChallenge:
  [ In(U), Fr(~ck) ]
-->
  [ Out('g'^~ck), ServerPending(U, ~ck) ]

/* Client mixes his secret key into the challenge to derive the shared secret.
 * To construct a response, it hashes it together with the entire transcript:
 * challenge (ensuring freshness and binding to this session), public key and
 * username (binding to the identity).
 */
rule ClientResponse:
  let response = h(<challenge^sk, challenge, 'g'^sk, username>)
  in
  [ In(challenge), ClientPending(username, sk) ]
--[ Responded(username, 'g'^sk, challenge) ]->
  [ Out(response) ]

/* Server accepts the response if:
 *
 * - The username and public key are configured on the server.
 *
 * - Server ends up deriving the same shared secret as the client, namely
 *   pk^ck = challenge^sk. This holds if the client possesses the secret key
 *   sk corresponding to its public key pk.
 *
 * - Client and server agree on the rest of the parameters: challenge, pk,
 *   username, exactly as they've been transmitted and seen.
 */
rule ServerAuthFinish:
  let challenge = 'g'^ck
      response = h(<pk^ck, challenge, pk, username>)
  in
  [ In(response), ServerPending(username, ck), !ConfiguredUser(username, pk) ]
--[ Accepted(username, pk, challenge) ]->
  []

/* The event of a key compromise. The environment may leak a key at any time
 * (stolen or mismanaged), which is outside of the protocol's control, and the
 * proofs must be aware of this.
 */
rule LeakUserKey:
  [ !UserSecret(u, sk) ]
--[ Leaked(u) ]->
  [ Out(sk) ]

/* Properties */

/* The user's secret key is never revealed in honest protocol runs. */
lemma KeySecrecy:
  "All u sk #i. UserSecretKey(u, sk) @ i & not (Ex #j. Leaked(u) @ j)
      ==> not (Ex #k. K(sk) @ k)"

/* Correct authentication is possible: client's response precedes successful
 * authenticaton with the same parameters, and the key doesn't get leaked.
 */
lemma HonestSessionExists:
  exists-trace "Ex u pk ch #i #j. Accepted(u, pk, ch) @ i
    & Responded(u, pk, ch) @ j
    & #j < #i
    & not (Ex #r. Leaked(u) @ r)"

/* Main lemma of the protocol.
 *
 * Server authenticates the user's identity with configured username u and
 * public key pk, with challenge ch. The user hasn't been compromised.
 *
 * Then, the client ran the protocol with the same parameters and previously
 * produced the matching response.
 *
 * "Injective" here means that each authentication session is unique. The server
 * won't accept multiple sessions using the same challenge.
 */
lemma InjectiveAgreement:
  "All u pk ch #i. Accepted(u, pk, ch) @ i & not (Ex #r. Leaked(u) @ r)
    ==>
      (Ex #j. Responded(u, pk, ch) @ j & #j < #i)
        & not (Ex u2 pk2 #i2. Accepted(u2, pk2, ch) @ i2 & not (#i = #i2))"

end