410c8acf |
1 | /* -*-c-*- |
2 | * |
00e64b67 |
3 | * $Id: keyexch.c,v 1.4 2001/06/22 19:40:36 mdw Exp $ |
410c8acf |
4 | * |
5 | * Key exchange protocol |
6 | * |
7 | * (c) 2001 Straylight/Edgeware |
8 | */ |
9 | |
10 | /*----- Licensing notice --------------------------------------------------* |
11 | * |
12 | * This file is part of Trivial IP Encryption (TrIPE). |
13 | * |
14 | * TrIPE is free software; you can redistribute it and/or modify |
15 | * it under the terms of the GNU General Public License as published by |
16 | * the Free Software Foundation; either version 2 of the License, or |
17 | * (at your option) any later version. |
18 | * |
19 | * TrIPE is distributed in the hope that it will be useful, |
20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
22 | * GNU General Public License for more details. |
23 | * |
24 | * You should have received a copy of the GNU General Public License |
25 | * along with TrIPE; if not, write to the Free Software Foundation, |
26 | * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
27 | */ |
28 | |
29 | /*----- Revision history --------------------------------------------------* |
30 | * |
31 | * $Log: keyexch.c,v $ |
00e64b67 |
32 | * Revision 1.4 2001/06/22 19:40:36 mdw |
33 | * Support expiry of other peers' public keys. |
34 | * |
56814747 |
35 | * Revision 1.3 2001/06/19 22:07:09 mdw |
36 | * Cosmetic fixes. |
37 | * |
0617b6e7 |
38 | * Revision 1.2 2001/02/16 21:24:27 mdw |
39 | * Rewrite for new key exchange protocol. |
40 | * |
410c8acf |
41 | * Revision 1.1 2001/02/03 20:26:37 mdw |
42 | * Initial checkin. |
43 | * |
44 | */ |
45 | |
46 | /*----- Header files ------------------------------------------------------*/ |
47 | |
48 | #include "tripe.h" |
49 | |
50 | /*----- Tunable parameters ------------------------------------------------*/ |
51 | |
52 | #define T_VALID MIN(2) |
410c8acf |
53 | #define T_RETRY SEC(10) |
410c8acf |
54 | |
0617b6e7 |
55 | #define ISVALID(kx, now) ((now) < (kx)->t_valid) |
56 | |
57 | /*----- Various utilities -------------------------------------------------*/ |
410c8acf |
58 | |
59 | /* --- @hashmp@ --- * |
60 | * |
0617b6e7 |
61 | * Arguments: @HASH_CTX *r@ = pointer to hash context |
410c8acf |
62 | * @mp *m@ = pointer to multiprecision integer |
63 | * |
64 | * Returns: --- |
65 | * |
66 | * Use: Adds the hash of a multiprecision integer to the context. |
0617b6e7 |
67 | * Corrupts @buf_t@. |
410c8acf |
68 | */ |
69 | |
0617b6e7 |
70 | static void hashmp(HASH_CTX *r, mp *m) |
410c8acf |
71 | { |
72 | buf b; |
0617b6e7 |
73 | buf_init(&b, buf_t, sizeof(buf_t)); |
410c8acf |
74 | buf_putmp(&b, m); |
75 | assert(BOK(&b)); |
0617b6e7 |
76 | HASH(r, BBASE(&b), BLEN(&b)); |
410c8acf |
77 | } |
78 | |
79 | /* --- @timer@ --- * |
80 | * |
81 | * Arguments: @struct timeval *tv@ = the current time |
82 | * @void *v@ = pointer to key exchange context |
83 | * |
84 | * Returns: --- |
85 | * |
86 | * Use: Acts when the key exchange timer goes off. |
87 | */ |
88 | |
89 | static void timer(struct timeval *tv, void *v) |
90 | { |
91 | keyexch *kx = v; |
92 | kx->f &= ~KXF_TIMER; |
93 | T( trace(T_KEYEXCH, "keyexch: timer has popped"); ) |
94 | kx_start(kx); |
95 | } |
96 | |
97 | /* --- @settimer@ --- * |
98 | * |
99 | * Arguments: @keyexch *kx@ = pointer to key exchange context |
100 | * @time_t t@ = when to set the timer for |
101 | * |
102 | * Returns: --- |
103 | * |
104 | * Use: Sets the timer for the next key exchange attempt. |
105 | */ |
106 | |
107 | static void settimer(keyexch *kx, time_t t) |
108 | { |
109 | struct timeval tv; |
110 | if (kx->f & KXF_TIMER) |
111 | sel_rmtimer(&kx->t); |
112 | tv.tv_sec = t; |
113 | tv.tv_usec = 0; |
114 | sel_addtimer(&sel, &kx->t, &tv, timer, kx); |
115 | kx->f |= KXF_TIMER; |
116 | } |
117 | |
0617b6e7 |
118 | /*----- Challenge management ----------------------------------------------*/ |
119 | |
120 | /* --- Notes on challenge management --- * |
410c8acf |
121 | * |
0617b6e7 |
122 | * We may get multiple different replies to our key exchange; some will be |
123 | * correct, some inserted by attackers. Up until @KX_THRESH@, all challenges |
124 | * received will be added to the table and given a full response. After |
125 | * @KX_THRESH@ distinct challenges are received, we return only a `cookie': |
126 | * our existing challenge, followed by a hash of the sender's challenge. We |
127 | * do %%\emph{not}%% give a bare challenge a reply slot at this stage. All |
128 | * properly-formed cookies are assigned a table slot: if none is spare, a |
129 | * used slot is randomly selected and destroyed. A cookie always receives a |
130 | * full reply. |
131 | */ |
132 | |
133 | /* --- @kxc_destroy@ --- * |
134 | * |
135 | * Arguments: @kxchal *kxc@ = pointer to the challenge block |
410c8acf |
136 | * |
137 | * Returns: --- |
138 | * |
0617b6e7 |
139 | * Use: Disposes of a challenge block. |
410c8acf |
140 | */ |
141 | |
0617b6e7 |
142 | static void kxc_destroy(kxchal *kxc) |
410c8acf |
143 | { |
0617b6e7 |
144 | if (kxc->f & KXF_TIMER) |
145 | sel_rmtimer(&kxc->t); |
146 | mp_drop(kxc->c); |
147 | mp_drop(kxc->r); |
148 | ks_drop(kxc->ks); |
149 | DESTROY(kxc); |
150 | } |
410c8acf |
151 | |
0617b6e7 |
152 | /* --- @kxc_stoptimer@ --- * |
153 | * |
154 | * Arguments: @kxchal *kxc@ = pointer to the challenge block |
155 | * |
156 | * Returns: --- |
157 | * |
158 | * Use: Stops the challenge's retry timer from sending messages. |
159 | * Useful when the state machine is in the endgame of the |
160 | * exchange. |
161 | */ |
410c8acf |
162 | |
0617b6e7 |
163 | static void kxc_stoptimer(kxchal *kxc) |
164 | { |
165 | if (kxc->f & KXF_TIMER) |
166 | sel_rmtimer(&kxc->t); |
167 | } |
410c8acf |
168 | |
0617b6e7 |
169 | /* --- @kxc_new@ --- * |
170 | * |
171 | * Arguments: @keyexch *kx@ = pointer to key exchange block |
0617b6e7 |
172 | * |
173 | * Returns: A pointer to the challenge block. |
174 | * |
175 | * Use: Returns a pointer to a new challenge block to fill in. |
176 | */ |
410c8acf |
177 | |
0617b6e7 |
178 | static kxchal *kxc_new(keyexch *kx) |
179 | { |
180 | kxchal *kxc; |
181 | unsigned i; |
182 | |
183 | /* --- If we're over reply threshold, discard one at random --- */ |
184 | |
185 | if (kx->nr < KX_NCHAL) |
186 | i = kx->nr++; |
187 | else { |
188 | i = rand_global.ops->range(&rand_global, KX_NCHAL); |
189 | kxc_destroy(kx->r[i]); |
410c8acf |
190 | } |
191 | |
0617b6e7 |
192 | /* --- Fill in the new structure --- */ |
410c8acf |
193 | |
0617b6e7 |
194 | kxc = CREATE(kxchal); |
195 | kxc->c = 0; |
196 | kxc->r = 0; |
197 | kxc->ks = 0; |
198 | kxc->kx = kx; |
199 | kxc->f = 0; |
200 | kx->r[i] = kxc; |
201 | return (kxc); |
202 | } |
410c8acf |
203 | |
0617b6e7 |
204 | /* --- @kxc_bychal@ --- * |
205 | * |
206 | * Arguments: @keyexch *kx@ = pointer to key exchange block |
207 | * @mp *c@ = challenge from remote host |
208 | * |
209 | * Returns: Pointer to the challenge block, or null. |
210 | * |
211 | * Use: Finds a challenge block, given its challenge. |
212 | */ |
213 | |
214 | static kxchal *kxc_bychal(keyexch *kx, mp *c) |
215 | { |
216 | unsigned i; |
217 | |
218 | for (i = 0; i < kx->nr; i++) { |
219 | if (MP_EQ(c, kx->r[i]->c)) |
220 | return (kx->r[i]); |
221 | } |
222 | return (0); |
223 | } |
224 | |
225 | /* --- @kxc_byhc@ --- * |
226 | * |
227 | * Arguments: @keyexch *kx@ = pointer to key exchange block |
228 | * @const octet *hc@ = challenge hash from remote host |
229 | * |
230 | * Returns: Pointer to the challenge block, or null. |
231 | * |
232 | * Use: Finds a challenge block, given a hash of its challenge. |
233 | */ |
410c8acf |
234 | |
0617b6e7 |
235 | static kxchal *kxc_byhc(keyexch *kx, const octet *hc) |
236 | { |
237 | unsigned i; |
238 | |
239 | for (i = 0; i < kx->nr; i++) { |
240 | if (memcmp(hc, kx->r[i]->hc, HASHSZ) == 0) |
241 | return (kx->r[i]); |
410c8acf |
242 | } |
0617b6e7 |
243 | return (0); |
244 | } |
245 | |
246 | /* --- @kxc_answer@ --- * |
247 | * |
248 | * Arguments: @keyexch *kx@ = pointer to key exchange block |
249 | * @kxchal *kxc@ = pointer to challenge block |
250 | * |
251 | * Returns: --- |
252 | * |
253 | * Use: Sends a reply to the remote host, according to the data in |
254 | * this challenge block. |
255 | */ |
256 | |
257 | static void kxc_answer(keyexch *kx, kxchal *kxc); |
258 | |
259 | static void kxc_timer(struct timeval *tv, void *v) |
260 | { |
261 | kxchal *kxc = v; |
262 | kxc->f &= ~KXF_TIMER; |
263 | kxc_answer(kxc->kx, kxc); |
264 | } |
265 | |
266 | static void kxc_answer(keyexch *kx, kxchal *kxc) |
267 | { |
268 | stats *st = p_stats(kx->p); |
269 | buf *b = p_txstart(kx->p, MSG_KEYEXCH | (kxc->r ? KX_REPLY : KX_CHAL)); |
270 | struct timeval tv; |
271 | buf bb; |
272 | |
273 | /* --- Build the reply packet --- */ |
274 | |
275 | if (!kxc->r) |
276 | buf_putmp(b, kx->c); |
277 | else |
278 | buf_put(b, kx->hc, HASHSZ); |
279 | buf_put(b, kxc->hc, HASHSZ); |
280 | buf_put(b, kxc->hrx, HASHSZ); |
281 | |
282 | /* --- Maybe send an actual reply, if we have one --- */ |
410c8acf |
283 | |
0617b6e7 |
284 | if (!kxc->r) { |
285 | T( trace(T_KEYEXCH, "keyexch: resending challenge to `%s'", |
286 | p_name(kx->p)); ) |
287 | } else { |
288 | T( trace(T_KEYEXCH, "keyexch: sending reply to `%s'", p_name(kx->p)); ) |
289 | buf_init(&bb, buf_i, sizeof(buf_i)); |
290 | buf_putmp(&bb, kxc->r); |
291 | buf_flip(&bb); |
292 | ks_encrypt(kxc->ks, &bb, b); |
293 | } |
294 | |
295 | /* --- Update the statistics --- */ |
296 | |
297 | if (BOK(b)) { |
298 | st->n_kxout++; |
299 | st->sz_kxout += BLEN(b); |
300 | p_txend(kx->p); |
301 | } |
302 | |
303 | /* --- Schedule another resend --- */ |
304 | |
305 | if (kxc->f & KXF_TIMER) |
306 | sel_rmtimer(&kxc->t); |
307 | gettimeofday(&tv, 0); |
308 | tv.tv_sec += T_RETRY; |
309 | sel_addtimer(&sel, &kxc->t, &tv, kxc_timer, kxc); |
310 | kxc->f |= KXF_TIMER; |
311 | } |
312 | |
313 | /*----- Individual message handlers ---------------------------------------*/ |
314 | |
315 | /* --- @getreply@ --- * |
316 | * |
317 | * Arguments: @keyexch *kx@ = pointer to key exchange context |
318 | * @mp *c@ = a challenge |
319 | * @const octet *hrx@ = the supplied expected-reply hash |
320 | * |
321 | * Returns: A pointer to the reply, or null if the reply-hash was wrong. |
322 | * |
323 | * Use: Computes replies to challenges. |
324 | */ |
325 | |
326 | static mp *getreply(keyexch *kx, mp *c, const octet *hrx) |
327 | { |
328 | mp *r = mpmont_exp(&mg, MP_NEW, c, kpriv.x); |
329 | HASH_CTX h; |
330 | octet buf[HASHSZ]; |
331 | |
332 | HASH_INIT(&h); |
333 | HASH_STRING(&h, "tripe-expected-reply"); |
334 | hashmp(&h, c); |
335 | hashmp(&h, kx->c); |
336 | hashmp(&h, r); |
337 | HASH_DONE(&h, buf); |
338 | IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, { |
339 | trace(T_CRYPTO, "crypto: computed reply = %s", mpstr(r)); |
340 | trace_block(T_CRYPTO, "crypto: computed reply hash", buf, HASHSZ); |
341 | })) |
342 | if (memcmp(buf, hrx, HASHSZ) != 0) { |
343 | a_warn("invalid expected-reply hash from `%s'", p_name(kx->p)); |
344 | mp_drop(r); |
345 | return (0); |
346 | } |
347 | return (r); |
348 | } |
349 | |
350 | /* --- @dochallenge@ --- * |
351 | * |
352 | * Arguments: @keyexch *kx@ = pointer to key exchange block |
353 | * @unsigned msg@ = message code for the packet |
354 | * @buf *b@ = buffer containing the packet |
355 | * |
356 | * Returns: Zero if OK, nonzero if the packet was rejected. |
357 | * |
358 | * Use: Processes a packet containing a challenge. |
359 | */ |
360 | |
361 | static int dochallenge(keyexch *kx, unsigned msg, buf *b) |
362 | { |
363 | mp *c = 0; |
364 | const octet *hc = 0, *hrx = 0; |
365 | kxchal *kxc; |
366 | HASH_CTX h; |
367 | |
368 | /* --- Ensure that we're in a sensible state --- */ |
369 | |
370 | if (kx->s != KXS_CHAL) { |
371 | a_warn("unexpected challenge from `%s'", p_name(kx->p)); |
372 | goto bad; |
373 | } |
374 | |
375 | /* --- Unpack the packet --- */ |
376 | |
377 | if ((c = buf_getmp(b)) == 0 || |
378 | (msg >= KX_COOKIE && (hc = buf_get(b, HASHSZ)) == 0) || |
379 | (msg >= KX_CHAL && (hrx = buf_get(b, HASHSZ)) == 0) || |
380 | BLEFT(b)) { |
381 | a_warn("malformed packet from `%s'", p_name(kx->p)); |
382 | goto bad; |
383 | } |
384 | |
385 | IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, { |
386 | trace(T_CRYPTO, "crypto: challenge = %s", mpstr(c)); |
387 | if (hc) trace_block(T_CRYPTO, "crypto: cookie", hc, HASHSZ); |
388 | if (hrx) trace_block(T_CRYPTO, "crypto: response hash", hrx, HASHSZ); |
389 | })) |
390 | |
391 | /* --- First, handle a bare challenge --- * |
392 | * |
393 | * If the table is heavily loaded, just emit a cookie and return. |
394 | */ |
395 | |
396 | if (!hc && kx->nr >= KX_THRESH) { |
397 | T( trace(T_KEYEXCH, "keyexch: too many challenges -- sending cookie"); ) |
398 | b = p_txstart(kx->p, MSG_KEYEXCH | KX_COOKIE); |
399 | buf_putmp(b, kx->c); |
400 | HASH_INIT(&h); |
401 | HASH_STRING(&h, "tripe-cookie"); |
402 | hashmp(&h, c); |
403 | HASH_DONE(&h, buf_get(b, HASHSZ)); |
404 | p_txend(kx->p); |
405 | goto tidy; |
406 | } |
407 | |
408 | /* --- Discard a packet with an invalid cookie --- */ |
409 | |
410 | if (hc && memcmp(hc, kx->hc, HASHSZ) != 0) { |
411 | a_warn("incorrect cookie from `%s'", p_name(kx->p)); |
412 | goto bad; |
413 | } |
414 | |
415 | /* --- Find a challenge block for this packet --- * |
410c8acf |
416 | * |
0617b6e7 |
417 | * If there isn't one already, create a new one. |
410c8acf |
418 | */ |
419 | |
0617b6e7 |
420 | if ((kxc = kxc_bychal(kx, c)) == 0) { |
421 | size_t x, y, z; |
422 | mp *r; |
423 | |
424 | /* --- Be careful here --- * |
425 | * |
426 | * If this is a full challenge, and it's the first time I've seen it, I |
427 | * want to be able to throw it away before committing a table entry to |
428 | * it. |
429 | */ |
430 | |
431 | if (!hrx) |
432 | kxc = kxc_new(kx); |
433 | else { |
434 | if ((r = getreply(kx, c, hrx)) == 0) |
435 | goto bad; |
436 | kxc = kxc_new(kx); |
437 | kxc->r = r; |
410c8acf |
438 | } |
0617b6e7 |
439 | kxc->c = mp_copy(c); |
440 | |
441 | /* --- Work out the cookie for this challenge --- */ |
442 | |
443 | HASH_INIT(&h); |
444 | HASH_STRING(&h, "tripe-cookie"); |
445 | hashmp(&h, kxc->c); |
446 | HASH_DONE(&h, kxc->hc); |
447 | |
448 | /* --- Compute the expected-reply hash --- */ |
449 | |
450 | HASH_INIT(&h); |
451 | HASH_STRING(&h, "tripe-expected-reply"); |
452 | hashmp(&h, kx->c); |
453 | hashmp(&h, kxc->c); |
454 | hashmp(&h, kx->rx); |
455 | HASH_DONE(&h, kxc->hrx); |
456 | |
457 | /* --- Work out the shared key --- */ |
458 | |
459 | r = mpmont_exp(&mg, MP_NEW, c, kx->alpha); |
460 | |
461 | /* --- Compute the switch messages --- */ |
462 | |
463 | HASH_INIT(&h); HASH_STRING(&h, "tripe-switch-request"); |
464 | hashmp(&h, kx->c); hashmp(&h, kxc->c); |
465 | HASH_DONE(&h, kxc->hswrq_out); |
466 | HASH_INIT(&h); HASH_STRING(&h, "tripe-switch-confirm"); |
467 | hashmp(&h, kx->c); hashmp(&h, kxc->c); |
468 | HASH_DONE(&h, kxc->hswok_out); |
469 | |
470 | HASH_INIT(&h); HASH_STRING(&h, "tripe-switch-request"); |
471 | hashmp(&h, kxc->c); hashmp(&h, kx->c); |
472 | HASH_DONE(&h, kxc->hswrq_in); |
473 | HASH_INIT(&h); HASH_STRING(&h, "tripe-switch-confirm"); |
474 | hashmp(&h, kxc->c); hashmp(&h, kx->c); |
475 | HASH_DONE(&h, kxc->hswok_in); |
476 | |
477 | IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, { |
478 | trace_block(T_CRYPTO, "crypto: computed cookie", kxc->hc, HASHSZ); |
479 | trace_block(T_CRYPTO, "crypto: my reply hash", kxc->hrx, HASHSZ); |
480 | trace(T_CRYPTO, "crypto: shared secret = %s", mpstr(r)); |
481 | trace_block(T_CRYPTO, "crypto: outbound switch request", |
482 | kxc->hswrq_out, HASHSZ); |
483 | trace_block(T_CRYPTO, "crypto: outbound switch confirm", |
484 | kxc->hswok_out, HASHSZ); |
485 | trace_block(T_CRYPTO, "crypto: inbound switch request", |
486 | kxc->hswrq_in, HASHSZ); |
487 | trace_block(T_CRYPTO, "crypto: inbound switch confirm", |
488 | kxc->hswok_in, HASHSZ); |
489 | })) |
490 | |
491 | /* --- Create a new symmetric keyset --- */ |
492 | |
493 | buf_init(b, buf_o, sizeof(buf_o)); |
494 | buf_putmp(b, kx->c); x = BLEN(b); |
495 | buf_putmp(b, kxc->c); y = BLEN(b); |
496 | buf_putmp(b, r); z = BLEN(b); |
497 | assert(BOK(b)); |
498 | |
499 | kxc->ks = ks_gen(BBASE(b), x, y, z); |
500 | mp_drop(r); |
410c8acf |
501 | } |
502 | |
0617b6e7 |
503 | /* --- Answer the challenge if we need to --- */ |
410c8acf |
504 | |
0617b6e7 |
505 | if (hrx && !kxc->r) { |
506 | mp *r; |
507 | if ((r = getreply(kx, c, hrx)) == 0) |
508 | goto bad; |
509 | kxc->r = r; |
410c8acf |
510 | } |
0617b6e7 |
511 | |
512 | kxc_answer(kx, kxc); |
513 | |
514 | /* --- Tidy up and go home --- */ |
515 | |
516 | tidy: |
517 | mp_drop(c); |
518 | return (0); |
519 | |
520 | bad: |
521 | mp_drop(c); |
522 | return (-1); |
410c8acf |
523 | } |
524 | |
0617b6e7 |
525 | /* --- @resend@ --- * |
410c8acf |
526 | * |
527 | * Arguments: @keyexch *kx@ = pointer to key exchange context |
410c8acf |
528 | * |
529 | * Returns: --- |
530 | * |
0617b6e7 |
531 | * Use: Sends the next message for a key exchange. |
410c8acf |
532 | */ |
533 | |
0617b6e7 |
534 | static void resend(keyexch *kx) |
410c8acf |
535 | { |
0617b6e7 |
536 | kxchal *kxc; |
537 | buf bb; |
538 | stats *st = p_stats(kx->p); |
410c8acf |
539 | buf *b; |
540 | |
0617b6e7 |
541 | switch (kx->s) { |
542 | case KXS_CHAL: |
00e64b67 |
543 | T( trace(T_KEYEXCH, "keyexch: sending prechallenge to `%s'", |
544 | p_name(kx->p)); ) |
0617b6e7 |
545 | b = p_txstart(kx->p, MSG_KEYEXCH | KX_PRECHAL); |
546 | buf_putmp(b, kx->c); |
547 | break; |
548 | case KXS_COMMIT: |
00e64b67 |
549 | T( trace(T_KEYEXCH, "keyexch: sending switch request to `%s'", |
550 | p_name(kx->p)); ) |
0617b6e7 |
551 | kxc = kx->r[0]; |
552 | b = p_txstart(kx->p, MSG_KEYEXCH | KX_SWITCH); |
553 | buf_put(b, kx->hc, HASHSZ); |
554 | buf_put(b, kxc->hc, HASHSZ); |
555 | buf_init(&bb, buf_i, sizeof(buf_i)); |
556 | buf_putmp(&bb, kxc->r); |
557 | buf_put(&bb, kxc->hswrq_out, HASHSZ); |
558 | buf_flip(&bb); |
559 | ks_encrypt(kxc->ks, &bb, b); |
560 | break; |
561 | case KXS_SWITCH: |
00e64b67 |
562 | T( trace(T_KEYEXCH, "keyexch: sending switch confirmation to `%s'", |
0617b6e7 |
563 | p_name(kx->p)); ) |
564 | kxc = kx->r[0]; |
565 | b = p_txstart(kx->p, MSG_KEYEXCH | KX_SWITCHOK); |
566 | buf_init(&bb, buf_i, sizeof(buf_i)); |
567 | buf_put(&bb, kxc->hswok_out, HASHSZ); |
568 | buf_flip(&bb); |
569 | ks_encrypt(kxc->ks, &bb, b); |
570 | break; |
571 | default: |
572 | abort(); |
410c8acf |
573 | } |
0617b6e7 |
574 | |
575 | if (BOK(b)) { |
576 | st->n_kxout++; |
577 | st->sz_kxout += BLEN(b); |
578 | p_txend(kx->p); |
579 | } |
580 | |
581 | if (kx->s < KXS_SWITCH) |
582 | settimer(kx, time(0) + T_RETRY); |
410c8acf |
583 | } |
584 | |
0617b6e7 |
585 | /* --- @matchreply@ --- * |
586 | * |
587 | * Arguments: @keyexch *kx@ = pointer to key exchange context |
588 | * @const octet *hc_in@ = a hash of his challenge |
589 | * @const octet *hc_out@ = a hash of my challenge (cookie) |
590 | * @const octet *krx@ = his expected-reply hash (optional) |
591 | * @buf *b@ = encrypted remainder of the packet |
592 | * |
593 | * Returns: A pointer to the challenge block if OK, or null on failure. |
594 | * |
595 | * Use: Checks a reply or switch packet, ensuring that its contents |
596 | * are sensible and correct. If they are, @*b@ is set to point |
597 | * to the remainder of the encrypted data, and the correct |
598 | * challenge is returned. |
599 | */ |
600 | |
601 | static kxchal *matchreply(keyexch *kx, const octet *hc_in, |
602 | const octet *hc_out, const octet *hrx, buf *b) |
410c8acf |
603 | { |
0617b6e7 |
604 | kxchal *kxc; |
605 | buf bb; |
606 | mp *r = 0; |
607 | |
608 | /* --- Check the plaintext portions of the data --- */ |
609 | |
610 | IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, { |
611 | trace_block(T_CRYPTO, "crypto: challenge", hc_in, HASHSZ); |
612 | trace_block(T_CRYPTO, "crypto: cookie", hc_out, HASHSZ); |
613 | if (hrx) trace_block(T_CRYPTO, "crypto: response hash", hrx, HASHSZ); |
614 | })) |
615 | if (memcmp(hc_out, kx->hc, HASHSZ) != 0) { |
616 | a_warn("incorrect cookie from `%s'", p_name(kx->p)); |
617 | goto bad; |
618 | } |
619 | if ((kxc = kxc_byhc(kx, hc_in)) == 0) { |
620 | a_warn("received reply for unknown challenge from `%s'", p_name(kx->p)); |
621 | goto bad; |
622 | } |
410c8acf |
623 | |
0617b6e7 |
624 | /* --- Maybe compute a reply for the challenge --- */ |
625 | |
626 | if (!kxc->r) { |
627 | if (!hrx) { |
628 | a_warn("unexpected switch request from `%s'", p_name(kx->p)); |
629 | goto bad; |
630 | } |
631 | if ((r = getreply(kx, kxc->c, hrx)) == 0) |
632 | goto bad; |
633 | kxc->r = r; |
634 | r = 0; |
635 | } |
636 | |
637 | /* --- Decrypt the rest of the packet --- */ |
638 | |
639 | buf_init(&bb, buf_o, sizeof(buf_o)); |
640 | if (ks_decrypt(kxc->ks, b, &bb)) { |
641 | a_warn("failed to decrypt reply from `%s'", p_name(kx->p)); |
642 | goto bad; |
410c8acf |
643 | } |
0617b6e7 |
644 | buf_init(b, BBASE(&bb), BLEN(&bb)); |
645 | if ((r = buf_getmp(b)) == 0) { |
646 | a_warn("invalid reply packet from `%s'", p_name(kx->p)); |
647 | goto bad; |
648 | } |
649 | IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, { |
650 | trace(T_CRYPTO, "crypto: reply = %s", mpstr(r)); |
651 | })) |
652 | if (!mp_eq(r, kx->rx)) { |
653 | a_warn("incorrect reply from `%s'", p_name(kx->p)); |
654 | goto bad; |
655 | } |
656 | |
657 | /* --- Done --- */ |
658 | |
659 | mp_drop(r); |
660 | return (kxc); |
661 | |
662 | bad: |
663 | mp_drop(r); |
664 | return (0); |
410c8acf |
665 | } |
666 | |
0617b6e7 |
667 | /* --- @commit@ --- * |
410c8acf |
668 | * |
669 | * Arguments: @keyexch *kx@ = pointer to key exchange context |
0617b6e7 |
670 | * @kxchal *kxc@ = pointer to challenge to commit to |
410c8acf |
671 | * |
672 | * Returns: --- |
673 | * |
0617b6e7 |
674 | * Use: Commits to a particular challenge as being the `right' one, |
675 | * since a reply has arrived for it. |
410c8acf |
676 | */ |
677 | |
0617b6e7 |
678 | static void commit(keyexch *kx, kxchal *kxc) |
410c8acf |
679 | { |
0617b6e7 |
680 | unsigned i; |
410c8acf |
681 | |
0617b6e7 |
682 | for (i = 0; i < kx->nr; i++) { |
683 | if (kx->r[i] != kxc) |
684 | kxc_destroy(kx->r[i]); |
685 | } |
686 | kx->r[0] = kxc; |
687 | kx->nr = 1; |
688 | kxc_stoptimer(kxc); |
689 | ksl_link(kx->ks, kxc->ks); |
410c8acf |
690 | } |
691 | |
0617b6e7 |
692 | /* --- @doreply@ --- * |
410c8acf |
693 | * |
694 | * Arguments: @keyexch *kx@ = pointer to key exchange context |
0617b6e7 |
695 | * @buf *b@ = buffer containing packet |
410c8acf |
696 | * |
0617b6e7 |
697 | * Returns: Zero if OK, nonzero if the packet was rejected. |
410c8acf |
698 | * |
0617b6e7 |
699 | * Use: Handles a reply packet. This doesn't handle the various |
700 | * switch packets: they're rather too different. |
410c8acf |
701 | */ |
702 | |
0617b6e7 |
703 | static int doreply(keyexch *kx, buf *b) |
410c8acf |
704 | { |
0617b6e7 |
705 | const octet *hc_in, *hc_out, *hrx; |
706 | kxchal *kxc; |
707 | |
708 | if (kx->s != KXS_CHAL && kx->s != KXS_COMMIT) { |
709 | a_warn("unexpected reply from `%s'", p_name(kx->p)); |
710 | goto bad; |
711 | } |
712 | if ((hc_in = buf_get(b, HASHSZ)) == 0 || |
713 | (hc_out = buf_get(b, HASHSZ)) == 0 || |
714 | (hrx = buf_get(b, HASHSZ)) == 0) { |
715 | a_warn("invalid reply packet from `%s'", p_name(kx->p)); |
716 | goto bad; |
717 | } |
718 | if ((kxc = matchreply(kx, hc_in, hc_out, hrx, b)) == 0) |
719 | goto bad; |
720 | if (BLEFT(b)) { |
721 | a_warn("invalid reply packet from `%s'", p_name(kx->p)); |
722 | goto bad; |
723 | } |
724 | if (kx->s == KXS_CHAL) { |
725 | commit(kx, kxc); |
726 | kx->s = KXS_COMMIT; |
727 | } |
728 | resend(kx); |
729 | return (0); |
730 | |
731 | bad: |
732 | return (-1); |
410c8acf |
733 | } |
734 | |
0617b6e7 |
735 | /* --- @doswitch@ --- * |
410c8acf |
736 | * |
0617b6e7 |
737 | * Arguments: @keyexch *kx@ = pointer to key exchange block |
738 | * @buf *b@ = pointer to buffer containing packet |
410c8acf |
739 | * |
0617b6e7 |
740 | * Returns: Zero if OK, nonzero if the packet was rejected. |
410c8acf |
741 | * |
0617b6e7 |
742 | * Use: Handles a reply with a switch request bolted onto it. |
410c8acf |
743 | */ |
744 | |
0617b6e7 |
745 | static int doswitch(keyexch *kx, buf *b) |
410c8acf |
746 | { |
0617b6e7 |
747 | const octet *hc_in, *hc_out, *hswrq; |
748 | kxchal *kxc; |
410c8acf |
749 | |
0617b6e7 |
750 | if ((hc_in = buf_get(b, HASHSZ)) == 0 || |
751 | (hc_out = buf_get(b, HASHSZ)) == 0) { |
752 | a_warn("invalid switch request from `%s'", p_name(kx->p)); |
753 | goto bad; |
410c8acf |
754 | } |
0617b6e7 |
755 | if ((kxc = matchreply(kx, hc_in, hc_out, 0, b)) == 0) |
756 | goto bad; |
757 | if ((hswrq = buf_get(b, HASHSZ)) == 0 || BLEFT(b)) { |
758 | a_warn("invalid switch request from `%s'", p_name(kx->p)); |
759 | goto bad; |
760 | } |
761 | IF_TRACING(T_KEYEXCH, { |
762 | trace_block(T_CRYPTO, "crypto: switch request hash", hswrq, HASHSZ); |
763 | }) |
764 | if (memcmp(hswrq, kxc->hswrq_in, HASHSZ) != 0) { |
765 | a_warn("incorrect switch request hash from `%s'", p_name(kx->p)); |
766 | goto bad; |
767 | } |
768 | switch (kx->s) { |
769 | case KXS_CHAL: |
770 | commit(kx, kxc); |
771 | case KXS_COMMIT: |
772 | ks_activate(kxc->ks); |
773 | settimer(kx, ks_tregen(kxc->ks)); |
774 | kx->s = KXS_SWITCH; |
775 | break; |
776 | } |
777 | resend(kx); |
778 | return (0); |
779 | |
780 | bad: |
781 | return (-1); |
410c8acf |
782 | } |
783 | |
0617b6e7 |
784 | /* --- @doswitchok@ --- * |
785 | * |
786 | * Arguments: @keyexch *kx@ = pointer to key exchange block |
787 | * @buf *b@ = pointer to buffer containing packet |
788 | * |
789 | * Returns: Zero if OK, nonzero if the packet was rejected. |
790 | * |
791 | * Use: Handles a reply with a switch request bolted onto it. |
792 | */ |
793 | |
794 | static int doswitchok(keyexch *kx, buf *b) |
410c8acf |
795 | { |
0617b6e7 |
796 | const octet *hswok; |
797 | kxchal *kxc; |
798 | buf bb; |
410c8acf |
799 | |
0617b6e7 |
800 | if (kx->s < KXS_COMMIT) { |
801 | a_warn("unexpected switch confirmation from `%s'", p_name(kx->p)); |
802 | goto bad; |
410c8acf |
803 | } |
0617b6e7 |
804 | kxc = kx->r[0]; |
805 | buf_init(&bb, buf_o, sizeof(buf_o)); |
806 | if (ks_decrypt(kxc->ks, b, &bb)) { |
807 | a_warn("failed to decrypt switch confirmation from `%s'", p_name(kx->p)); |
808 | goto bad; |
809 | } |
810 | buf_init(b, BBASE(&bb), BLEN(&bb)); |
811 | if ((hswok = buf_get(b, HASHSZ)) == 0 || BLEFT(b)) { |
812 | a_warn("invalid switch confirmation from `%s'", p_name(kx->p)); |
813 | goto bad; |
814 | } |
815 | IF_TRACING(T_KEYEXCH, { |
816 | trace_block(T_CRYPTO, "crypto: switch confirmation hash", hswok, HASHSZ); |
817 | }) |
818 | if (memcmp(hswok, kxc->hswok_in, HASHSZ) != 0) { |
819 | a_warn("incorrect switch confirmation hash from `%s'", p_name(kx->p)); |
820 | goto bad; |
821 | } |
822 | if (kx->s < KXS_SWITCH) { |
823 | ks_activate(kxc->ks); |
824 | settimer(kx, ks_tregen(kxc->ks)); |
825 | kx->s = KXS_SWITCH; |
826 | } |
827 | return (0); |
828 | |
829 | bad: |
830 | return (-1); |
831 | } |
832 | |
833 | /*----- Main code ---------------------------------------------------------*/ |
834 | |
835 | /* --- @stop@ --- * |
836 | * |
837 | * Arguments: @keyexch *kx@ = pointer to key exchange context |
838 | * |
839 | * Returns: --- |
840 | * |
841 | * Use: Stops a key exchange dead in its tracks. Throws away all of |
842 | * the context information. The context is left in an |
843 | * inconsistent state. The only functions which understand this |
844 | * state are @kx_free@ and @kx_init@ (which cause it internally |
845 | * it), and @start@ (which expects it to be the prevailing |
846 | * state). |
847 | */ |
848 | |
849 | static void stop(keyexch *kx) |
850 | { |
851 | unsigned i; |
852 | |
00e64b67 |
853 | if (kx->f & KXF_DEAD) |
854 | return; |
855 | |
0617b6e7 |
856 | if (kx->f & KXF_TIMER) |
857 | sel_rmtimer(&kx->t); |
858 | for (i = 0; i < kx->nr; i++) |
859 | kxc_destroy(kx->r[i]); |
860 | mp_drop(kx->alpha); |
861 | mp_drop(kx->c); |
862 | mp_drop(kx->rx); |
00e64b67 |
863 | kx->t_valid = 0; |
864 | kx->f |= KXF_DEAD; |
865 | kx->f &= ~KXF_TIMER; |
0617b6e7 |
866 | } |
867 | |
868 | /* --- @start@ --- * |
869 | * |
870 | * Arguments: @keyexch *kx@ = pointer to key exchange context |
871 | * @time_t now@ = the current time |
872 | * |
873 | * Returns: --- |
874 | * |
875 | * Use: Starts a new key exchange with the peer. The context must be |
876 | * in the bizarre state left by @stop@ or @kx_init@. |
877 | */ |
878 | |
879 | static void start(keyexch *kx, time_t now) |
880 | { |
881 | HASH_CTX h; |
882 | |
00e64b67 |
883 | assert(kx->f & KXF_DEAD); |
884 | |
885 | kx->f &= ~KXF_DEAD; |
0617b6e7 |
886 | kx->nr = 0; |
0617b6e7 |
887 | kx->alpha = mprand_range(MP_NEW, kpriv.dp.q, &rand_global, 0); |
888 | kx->c = mpmont_exp(&mg, MP_NEW, kpriv.dp.g, kx->alpha); |
889 | kx->rx = mpmont_exp(&mg, MP_NEW, kx->kpub.y, kx->alpha); |
890 | kx->s = KXS_CHAL; |
891 | kx->t_valid = now + T_VALID; |
892 | |
893 | HASH_INIT(&h); |
894 | HASH_STRING(&h, "tripe-cookie"); |
895 | hashmp(&h, kx->c); |
896 | HASH_DONE(&h, kx->hc); |
897 | |
898 | IF_TRACING(T_KEYEXCH, { |
899 | trace(T_KEYEXCH, "keyexch: creating new challenge"); |
900 | IF_TRACING(T_CRYPTO, { |
901 | trace(T_CRYPTO, "crypto: secret = %s", mpstr(kx->alpha)); |
902 | trace(T_CRYPTO, "crypto: challenge = %s", mpstr(kx->c)); |
903 | trace(T_CRYPTO, "crypto: expected response = %s", mpstr(kx->rx)); |
904 | trace_block(T_CRYPTO, "crypto: challenge cookie", kx->hc, HASHSZ); |
905 | }) |
906 | }) |
410c8acf |
907 | } |
908 | |
00e64b67 |
909 | /* --- @checkpub@ --- * |
910 | * |
911 | * Arguments: @keyexch *kx@ = pointer to key exchange context |
912 | * |
913 | * Returns: Zero if OK, nonzero if the peer's public key has expired. |
914 | * |
915 | * Use: Deactivates the key-exchange until the peer acquires a new |
916 | * public key. |
917 | */ |
918 | |
919 | static int checkpub(keyexch *kx) |
920 | { |
921 | time_t now; |
922 | if (kx->f & KXF_DEAD) |
923 | return (-1); |
924 | now = time(0); |
925 | if (KEY_EXPIRED(now, kx->texp_kpub)) { |
926 | stop(kx); |
927 | a_warn("public key for `%s' has expired", p_name(kx->p)); |
928 | dh_pubfree(&kx->kpub); |
929 | kx->f &= ~KXF_PUBKEY; |
930 | return (-1); |
931 | } |
932 | return (0); |
933 | } |
934 | |
0617b6e7 |
935 | /* --- @kx_start@ --- * |
410c8acf |
936 | * |
937 | * Arguments: @keyexch *kx@ = pointer to key exchange context |
410c8acf |
938 | * |
939 | * Returns: --- |
940 | * |
0617b6e7 |
941 | * Use: Stimulates a key exchange. If a key exchage is in progress, |
942 | * a new challenge is sent (unless the quiet timer forbids |
943 | * this); if no exchange is in progress, one is commenced. |
410c8acf |
944 | */ |
945 | |
0617b6e7 |
946 | void kx_start(keyexch *kx) |
410c8acf |
947 | { |
948 | time_t now = time(0); |
410c8acf |
949 | |
00e64b67 |
950 | if (checkpub(kx)) |
951 | return; |
0617b6e7 |
952 | if (!ISVALID(kx, now)) { |
953 | stop(kx); |
954 | start(kx, now); |
410c8acf |
955 | } |
0617b6e7 |
956 | resend(kx); |
957 | } |
958 | |
959 | /* --- @kx_message@ --- * |
960 | * |
961 | * Arguments: @keyexch *kx@ = pointer to key exchange context |
962 | * @unsigned msg@ = the message code |
963 | * @buf *b@ = pointer to buffer containing the packet |
964 | * |
965 | * Returns: --- |
966 | * |
967 | * Use: Reads a packet containing key exchange messages and handles |
968 | * it. |
969 | */ |
970 | |
971 | void kx_message(keyexch *kx, unsigned msg, buf *b) |
972 | { |
973 | time_t now = time(0); |
974 | stats *st = p_stats(kx->p); |
975 | size_t sz = BSZ(b); |
976 | int rc; |
977 | |
978 | #ifndef NTRACE |
979 | static const char *const pkname[] = { |
980 | "prechallenge", "cookie", "challenge", |
981 | "reply", "switch request", "switch confirmation" |
982 | }; |
983 | #endif |
984 | |
00e64b67 |
985 | if (checkpub(kx)) |
986 | return; |
987 | |
0617b6e7 |
988 | if (!ISVALID(kx, now)) { |
989 | stop(kx); |
990 | start(kx, now); |
410c8acf |
991 | } |
0617b6e7 |
992 | |
993 | T( trace(T_KEYEXCH, "keyexch: processing %s packet from `%s'", |
994 | msg < KX_NMSG ? pkname[msg] : "unknown", p_name(kx->p)); ) |
995 | |
996 | switch (msg) { |
997 | case KX_PRECHAL: |
998 | case KX_COOKIE: |
999 | case KX_CHAL: |
1000 | rc = dochallenge(kx, msg, b); |
1001 | break; |
1002 | case KX_REPLY: |
1003 | rc = doreply(kx, b); |
1004 | break; |
1005 | case KX_SWITCH: |
1006 | rc = doswitch(kx, b); |
1007 | break; |
1008 | case KX_SWITCHOK: |
1009 | rc = doswitchok(kx, b); |
1010 | break; |
1011 | default: |
1012 | a_warn("unexpected key exchange message type %u from `%p'", |
1013 | p_name(kx->p)); |
1014 | rc = -1; |
1015 | break; |
410c8acf |
1016 | } |
410c8acf |
1017 | |
0617b6e7 |
1018 | if (rc) |
1019 | st->n_reject++; |
1020 | else { |
1021 | st->n_kxin++; |
1022 | st->sz_kxin += sz; |
1023 | } |
410c8acf |
1024 | } |
1025 | |
1026 | /* --- @kx_free@ --- * |
1027 | * |
1028 | * Arguments: @keyexch *kx@ = pointer to key exchange context |
1029 | * |
1030 | * Returns: --- |
1031 | * |
1032 | * Use: Frees everything in a key exchange context. |
1033 | */ |
1034 | |
1035 | void kx_free(keyexch *kx) |
1036 | { |
0617b6e7 |
1037 | stop(kx); |
00e64b67 |
1038 | if (kx->f & KXF_PUBKEY) |
1039 | dh_pubfree(&kx->kpub); |
410c8acf |
1040 | } |
1041 | |
1042 | /* --- @kx_newkeys@ --- * |
1043 | * |
1044 | * Arguments: @keyexch *kx@ = pointer to key exchange context |
1045 | * |
1046 | * Returns: --- |
1047 | * |
1048 | * Use: Informs the key exchange module that its keys may have |
1049 | * changed. If fetching the new keys fails, the peer will be |
1050 | * destroyed, we log messages and struggle along with the old |
1051 | * keys. |
1052 | */ |
1053 | |
1054 | void kx_newkeys(keyexch *kx) |
1055 | { |
1056 | dh_pub dp; |
1057 | |
00e64b67 |
1058 | if (km_getpubkey(p_name(kx->p), &dp, &kx->texp_kpub)) |
410c8acf |
1059 | return; |
00e64b67 |
1060 | if (kx->f & KXF_PUBKEY) |
1061 | dh_pubfree(&kx->kpub); |
410c8acf |
1062 | kx->kpub = dp; |
00e64b67 |
1063 | kx->f |= KXF_PUBKEY; |
1064 | if ((kx->f & KXF_DEAD) || kx->s != KXS_SWITCH) { |
410c8acf |
1065 | T( trace(T_KEYEXCH, "keyexch: restarting key negotiation with `%s'", |
1066 | p_name(kx->p)); ) |
00e64b67 |
1067 | stop(kx); |
1068 | start(kx, time(0)); |
1069 | resend(kx); |
410c8acf |
1070 | } |
1071 | } |
1072 | |
1073 | /* --- @kx_init@ --- * |
1074 | * |
1075 | * Arguments: @keyexch *kx@ = pointer to key exchange context |
1076 | * @peer *p@ = pointer to peer context |
1077 | * @keyset **ks@ = pointer to keyset list |
1078 | * |
1079 | * Returns: Zero if OK, nonzero if it failed. |
1080 | * |
1081 | * Use: Initializes a key exchange module. The module currently |
1082 | * contains no keys, and will attempt to initiate a key |
1083 | * exchange. |
1084 | */ |
1085 | |
1086 | int kx_init(keyexch *kx, peer *p, keyset **ks) |
1087 | { |
1088 | kx->ks = ks; |
1089 | kx->p = p; |
00e64b67 |
1090 | if (km_getpubkey(p_name(p), &kx->kpub, &kx->texp_kpub)) |
410c8acf |
1091 | return (-1); |
00e64b67 |
1092 | kx->f = KXF_DEAD | KXF_PUBKEY; |
0617b6e7 |
1093 | start(kx, time(0)); |
1094 | resend(kx); |
410c8acf |
1095 | return (0); |
1096 | } |
1097 | |
1098 | /*----- That's all, folks -------------------------------------------------*/ |