exchange_v2_test.go raw
1 package crypto
2
3 import (
4 "testing"
5
6 "git.mleku.dev/mleku/dendrite/pkg/ratio"
7 )
8
9 func TestPrepareExchange(t *testing.T) {
10 params := DefaultParams(Security128)
11 tags := []string{"word", "punct"}
12
13 kp, err := Generate(params, tags, testFactory)
14 if err != nil {
15 t.Fatalf("Generate: %v", err)
16 }
17
18 // Create a peer spore.
19 kpPeer, err := Generate(params, tags, testFactory)
20 if err != nil {
21 t.Fatalf("Generate(peer): %v", err)
22 }
23
24 msg, err := PrepareExchange(&kp.Private, kp.Public.Spore, kpPeer.Public.Spore, params)
25 if err != nil {
26 t.Fatalf("PrepareExchange: %v", err)
27 }
28
29 if msg == nil {
30 t.Fatal("ephemeral message should not be nil")
31 }
32 if msg.Signature == nil {
33 t.Error("signature should not be nil")
34 }
35 if len(msg.Pattern) == 0 {
36 t.Error("pattern should not be empty")
37 }
38 if msg.SenderFingerprint.Hash == "" {
39 t.Error("sender fingerprint should not be empty")
40 }
41 }
42
43 func TestExchangeV2FullProtocol(t *testing.T) {
44 params := DefaultParams(Security128)
45 tags := []string{"word", "punct"}
46
47 // Generate two keypairs.
48 kpAlice, err := Generate(params, tags, testFactory)
49 if err != nil {
50 t.Fatalf("Generate(Alice): %v", err)
51 }
52 kpBob, err := Generate(params, tags, testFactory)
53 if err != nil {
54 t.Fatalf("Generate(Bob): %v", err)
55 }
56
57 // Alice prepares her message for Bob.
58 aliceMsg, err := PrepareExchange(
59 &kpAlice.Private, kpAlice.Public.Spore, kpBob.Public.Spore, params,
60 )
61 if err != nil {
62 t.Fatalf("PrepareExchange(Alice): %v", err)
63 }
64
65 // Bob prepares his message for Alice.
66 bobMsg, err := PrepareExchange(
67 &kpBob.Private, kpBob.Public.Spore, kpAlice.Public.Spore, params,
68 )
69 if err != nil {
70 t.Fatalf("PrepareExchange(Bob): %v", err)
71 }
72
73 // Alice completes the exchange using Bob's message.
74 aliceSecret, err := CompleteExchange(
75 kpAlice.Public.Spore, kpBob.Public.Spore,
76 aliceMsg, bobMsg, params,
77 )
78 if err != nil {
79 t.Fatalf("CompleteExchange(Alice): %v", err)
80 }
81
82 // Bob completes the exchange using Alice's message.
83 bobSecret, err := CompleteExchange(
84 kpBob.Public.Spore, kpAlice.Public.Spore,
85 bobMsg, aliceMsg, params,
86 )
87 if err != nil {
88 t.Fatalf("CompleteExchange(Bob): %v", err)
89 }
90
91 // Both should be authenticated.
92 if !aliceSecret.Authenticated {
93 t.Error("Alice's secret should be authenticated")
94 }
95 if !bobSecret.Authenticated {
96 t.Error("Bob's secret should be authenticated")
97 }
98
99 // Both should have common tags.
100 if len(aliceSecret.CommonTags) == 0 {
101 t.Error("should have common tags")
102 }
103
104 // Confidence should be positive.
105 if !aliceSecret.Confidence.IsPositive() {
106 t.Error("Alice's confidence should be positive")
107 }
108 if !bobSecret.Confidence.IsPositive() {
109 t.Error("Bob's confidence should be positive")
110 }
111
112 // Secrets should not be zero.
113 if aliceSecret.Secret == (Hamadryad{}) {
114 t.Error("Alice's secret should not be zero")
115 }
116 if bobSecret.Secret == (Hamadryad{}) {
117 t.Error("Bob's secret should not be zero")
118 }
119 }
120
121 func TestExchangeV2NilInputs(t *testing.T) {
122 params := DefaultParams(Security128)
123 tags := []string{"word"}
124 kp, _ := Generate(params, tags, testFactory)
125 kpPeer, _ := Generate(params, tags, testFactory)
126
127 // Nil private key.
128 _, err := PrepareExchange(nil, kp.Public.Spore, kpPeer.Public.Spore, params)
129 if err == nil {
130 t.Error("expected error with nil private key")
131 }
132
133 // Nil own spore.
134 _, err = PrepareExchange(&kp.Private, nil, kpPeer.Public.Spore, params)
135 if err == nil {
136 t.Error("expected error with nil own spore")
137 }
138
139 // Nil peer spore.
140 _, err = PrepareExchange(&kp.Private, kp.Public.Spore, nil, params)
141 if err == nil {
142 t.Error("expected error with nil peer spore")
143 }
144 }
145
146 func TestExchangeV2RejectsTamperedSignature(t *testing.T) {
147 params := DefaultParams(Security128)
148 tags := []string{"word", "punct"}
149
150 kpAlice, _ := Generate(params, tags, testFactory)
151 kpBob, _ := Generate(params, tags, testFactory)
152
153 aliceMsg, _ := PrepareExchange(
154 &kpAlice.Private, kpAlice.Public.Spore, kpBob.Public.Spore, params,
155 )
156 bobMsg, _ := PrepareExchange(
157 &kpBob.Private, kpBob.Public.Spore, kpAlice.Public.Spore, params,
158 )
159
160 // Tamper with Bob's signature.
161 bobMsg.Signature.Challenge[0] ^= 0xFF
162
163 _, err := CompleteExchange(
164 kpAlice.Public.Spore, kpBob.Public.Spore,
165 aliceMsg, bobMsg, params,
166 )
167 if err == nil {
168 t.Error("should reject tampered signature")
169 }
170 }
171
172 func TestExchangeV2RejectsMITM(t *testing.T) {
173 // MITM attack: Eve intercepts and replaces Bob's message with her own.
174 params := DefaultParams(Security128)
175 tags := []string{"word", "punct"}
176
177 kpAlice, _ := Generate(params, tags, testFactory)
178 kpBob, _ := Generate(params, tags, testFactory)
179 kpEve, _ := Generate(params, tags, testFactory)
180
181 aliceMsg, _ := PrepareExchange(
182 &kpAlice.Private, kpAlice.Public.Spore, kpBob.Public.Spore, params,
183 )
184
185 // Eve creates a message pretending to be for Alice, but using Eve's key.
186 eveMsg, _ := PrepareExchange(
187 &kpEve.Private, kpEve.Public.Spore, kpAlice.Public.Spore, params,
188 )
189
190 // Alice tries to complete with Eve's message (thinking it's Bob's).
191 _, err := CompleteExchange(
192 kpAlice.Public.Spore, kpBob.Public.Spore,
193 aliceMsg, eveMsg, params,
194 )
195 // This should fail because Eve's signature was made with Eve's key,
196 // not Bob's key. Alice verifies against Bob's fingerprint.
197 if err == nil {
198 t.Error("MITM attack should be rejected: Eve's signature doesn't verify as Bob's")
199 }
200 }
201
202 func TestExchangeV2EavesdropperCannotDerive(t *testing.T) {
203 // An eavesdropper who sees both spores (public) cannot derive the
204 // shared secret because they cannot produce the bonding patterns
205 // (which require the private constraint factories).
206 params := DefaultParams(Security128)
207 tags := []string{"word", "punct"}
208
209 kpAlice, _ := Generate(params, tags, testFactory)
210 kpBob, _ := Generate(params, tags, testFactory)
211
212 // The eavesdropper has both public spores.
213 // They can compute the old (broken) Exchange.
214 eaveSecret, err := Exchange(kpAlice.Public.Spore, kpBob.Public.Spore)
215 if err != nil {
216 t.Fatalf("eavesdropper Exchange: %v", err)
217 }
218
219 // The eavesdropper's secret is derived purely from public data.
220 // The authenticated ExchangeV2 requires the private key to produce
221 // the ephemeral bonding patterns.
222 // We just verify the eavesdropper can compute SOMETHING (the broken v1)
223 // but it won't match the authenticated v2 secret.
224 if eaveSecret.Secret == (Hamadryad{}) && len(eaveSecret.CommonTags) > 0 {
225 t.Error("eavesdropper should be able to compute v1 secret from public data")
226 }
227
228 // The v2 secret requires private key operations that the eavesdropper
229 // cannot perform. This is proven by the fact that PrepareExchange
230 // requires a private key, and CompleteExchange verifies signatures.
231 }
232
233 func TestExchangeV2DeriveKey(t *testing.T) {
234 params := DefaultParams(Security128)
235 tags := []string{"word", "punct"}
236
237 kpAlice, _ := Generate(params, tags, testFactory)
238 kpBob, _ := Generate(params, tags, testFactory)
239
240 aliceMsg, _ := PrepareExchange(
241 &kpAlice.Private, kpAlice.Public.Spore, kpBob.Public.Spore, params,
242 )
243 bobMsg, _ := PrepareExchange(
244 &kpBob.Private, kpBob.Public.Spore, kpAlice.Public.Spore, params,
245 )
246
247 aliceResult, err := CompleteExchange(
248 kpAlice.Public.Spore, kpBob.Public.Spore,
249 aliceMsg, bobMsg, params,
250 )
251 if err != nil {
252 t.Fatalf("CompleteExchange(Alice): %v", err)
253 }
254
255 // Derive a key from the authenticated secret.
256 ss := &SharedSecret{
257 CommonTags: aliceResult.CommonTags,
258 Secret: aliceResult.Secret,
259 Confidence: aliceResult.Confidence,
260 }
261
262 key := DeriveKey(ss, "aes-256", 32)
263 if len(key) != 32 {
264 t.Errorf("key length = %d, want 32", len(key))
265 }
266
267 // Key should be non-zero.
268 allZero := true
269 for _, b := range key {
270 if b != 0 {
271 allZero = false
272 break
273 }
274 }
275 if allZero {
276 t.Error("derived key should not be all zeros")
277 }
278 }
279
280 func TestExchangeV2InvalidParams(t *testing.T) {
281 tags := []string{"word"}
282 params := DefaultParams(Security128)
283 kp, _ := Generate(params, tags, testFactory)
284 kpPeer, _ := Generate(params, tags, testFactory)
285
286 badParams := Params{} // invalid
287 _, err := PrepareExchange(&kp.Private, kp.Public.Spore, kpPeer.Public.Spore, badParams)
288 if err == nil {
289 t.Error("expected error with invalid params")
290 }
291 }
292
293 func TestExchangeV2Confidence(t *testing.T) {
294 params := DefaultParams(Security128)
295
296 // Same tags → confidence should be 1/1.
297 kp1, _ := Generate(params, []string{"word", "punct"}, testFactory)
298 kp2, _ := Generate(params, []string{"word", "punct"}, testFactory)
299
300 msg1, _ := PrepareExchange(&kp1.Private, kp1.Public.Spore, kp2.Public.Spore, params)
301 msg2, _ := PrepareExchange(&kp2.Private, kp2.Public.Spore, kp1.Public.Spore, params)
302
303 result, err := CompleteExchange(kp1.Public.Spore, kp2.Public.Spore, msg1, msg2, params)
304 if err != nil {
305 t.Fatalf("CompleteExchange: %v", err)
306 }
307
308 if !result.Confidence.Equal(ratio.One) {
309 t.Errorf("same tags: confidence = %s, want 1/1", result.Confidence)
310 }
311 }
312