chiark / gitweb /
Some progress made on laptop.
[tripe] / keyexch.c
1 /* -*-c-*-
2  *
3  * $Id: keyexch.c,v 1.4 2001/06/22 19:40:36 mdw Exp $
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 $
32  * Revision 1.4  2001/06/22 19:40:36  mdw
33  * Support expiry of other peers' public keys.
34  *
35  * Revision 1.3  2001/06/19 22:07:09  mdw
36  * Cosmetic fixes.
37  *
38  * Revision 1.2  2001/02/16 21:24:27  mdw
39  * Rewrite for new key exchange protocol.
40  *
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)
53 #define T_RETRY SEC(10)
54
55 #define ISVALID(kx, now) ((now) < (kx)->t_valid)
56
57 /*----- Various utilities -------------------------------------------------*/
58
59 /* --- @hashmp@ --- *
60  *
61  * Arguments:   @HASH_CTX *r@ = pointer to hash context
62  *              @mp *m@ = pointer to multiprecision integer
63  *
64  * Returns:     ---
65  *
66  * Use:         Adds the hash of a multiprecision integer to the context.
67  *              Corrupts @buf_t@.
68  */
69
70 static void hashmp(HASH_CTX *r, mp *m)
71 {
72   buf b;
73   buf_init(&b, buf_t, sizeof(buf_t));
74   buf_putmp(&b, m);
75   assert(BOK(&b));
76   HASH(r, BBASE(&b), BLEN(&b));
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
118 /*----- Challenge management ----------------------------------------------*/
119
120 /* --- Notes on challenge management --- *
121  *
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
136  *
137  * Returns:     ---
138  *
139  * Use:         Disposes of a challenge block.
140  */
141
142 static void kxc_destroy(kxchal *kxc)
143 {
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 }
151
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  */
162
163 static void kxc_stoptimer(kxchal *kxc)
164 {
165   if (kxc->f & KXF_TIMER)
166     sel_rmtimer(&kxc->t);
167 }
168
169 /* --- @kxc_new@ --- *
170  *
171  * Arguments:   @keyexch *kx@ = pointer to key exchange block
172  *
173  * Returns:     A pointer to the challenge block.
174  *
175  * Use:         Returns a pointer to a new challenge block to fill in.
176  */
177
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]);
190   }
191
192   /* --- Fill in the new structure --- */
193
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 }
203
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  */
234
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]);
242   }
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 --- */
283
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 --- *
416    *
417    * If there isn't one already, create a new one.
418    */
419
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;
438     }
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);
501   }
502
503   /* --- Answer the challenge if we need to --- */
504
505   if (hrx && !kxc->r) {
506     mp *r;
507     if ((r = getreply(kx, c, hrx)) == 0)
508       goto bad;
509     kxc->r = r;
510   }
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);
523 }
524
525 /* --- @resend@ --- *
526  *
527  * Arguments:   @keyexch *kx@ = pointer to key exchange context
528  *
529  * Returns:     ---
530  *
531  * Use:         Sends the next message for a key exchange.
532  */
533
534 static void resend(keyexch *kx)
535 {
536   kxchal *kxc;
537   buf bb;
538   stats *st = p_stats(kx->p);
539   buf *b;
540
541   switch (kx->s) {
542     case KXS_CHAL:
543       T( trace(T_KEYEXCH, "keyexch: sending prechallenge to `%s'",
544                p_name(kx->p)); )
545       b = p_txstart(kx->p, MSG_KEYEXCH | KX_PRECHAL);
546       buf_putmp(b, kx->c);
547       break;
548     case KXS_COMMIT:
549       T( trace(T_KEYEXCH, "keyexch: sending switch request to `%s'",
550                p_name(kx->p)); )
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:
562       T( trace(T_KEYEXCH, "keyexch: sending switch confirmation to `%s'",
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();
573   }
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);
583 }
584
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)
603 {
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   }
623
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;
643   }
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);
665 }
666
667 /* --- @commit@ --- *
668  *
669  * Arguments:   @keyexch *kx@ = pointer to key exchange context
670  *              @kxchal *kxc@ = pointer to challenge to commit to
671  *
672  * Returns:     ---
673  *
674  * Use:         Commits to a particular challenge as being the `right' one,
675  *              since a reply has arrived for it.
676  */
677
678 static void commit(keyexch *kx, kxchal *kxc)
679 {
680   unsigned i;
681
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);  
690 }
691
692 /* --- @doreply@ --- *
693  *
694  * Arguments:   @keyexch *kx@ = pointer to key exchange context
695  *              @buf *b@ = buffer containing packet
696  *
697  * Returns:     Zero if OK, nonzero if the packet was rejected.
698  *
699  * Use:         Handles a reply packet.  This doesn't handle the various
700  *              switch packets: they're rather too different.
701  */
702
703 static int doreply(keyexch *kx, buf *b)
704 {
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);
733 }
734
735 /* --- @doswitch@ --- *
736  *
737  * Arguments:   @keyexch *kx@ = pointer to key exchange block
738  *              @buf *b@ = pointer to buffer containing packet
739  *
740  * Returns:     Zero if OK, nonzero if the packet was rejected.
741  *
742  * Use:         Handles a reply with a switch request bolted onto it.
743  */
744
745 static int doswitch(keyexch *kx, buf *b)
746 {
747   const octet *hc_in, *hc_out, *hswrq;
748   kxchal *kxc;
749
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;
754   }
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);
782 }
783
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)
795 {
796   const octet *hswok;
797   kxchal *kxc;
798   buf bb;
799
800   if (kx->s < KXS_COMMIT) {
801     a_warn("unexpected switch confirmation from `%s'", p_name(kx->p));
802     goto bad;
803   }
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
853   if (kx->f & KXF_DEAD)
854     return;
855
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);
863   kx->t_valid = 0;
864   kx->f |= KXF_DEAD;
865   kx->f &= ~KXF_TIMER;
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
883   assert(kx->f & KXF_DEAD);
884
885   kx->f &= ~KXF_DEAD;
886   kx->nr = 0;
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   })
907 }
908
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
935 /* --- @kx_start@ --- *
936  *
937  * Arguments:   @keyexch *kx@ = pointer to key exchange context
938  *
939  * Returns:     ---
940  *
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.
944  */
945
946 void kx_start(keyexch *kx)
947 {
948   time_t now = time(0);
949
950   if (checkpub(kx))
951     return;
952   if (!ISVALID(kx, now)) {
953     stop(kx);
954     start(kx, now);
955   }
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
985   if (checkpub(kx))
986     return;
987
988   if (!ISVALID(kx, now)) {
989     stop(kx);
990     start(kx, now);
991   }
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;
1016   }
1017
1018   if (rc)
1019     st->n_reject++;
1020   else {
1021     st->n_kxin++;
1022     st->sz_kxin += sz;
1023   }
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 {
1037   stop(kx);
1038   if (kx->f & KXF_PUBKEY)
1039     dh_pubfree(&kx->kpub);
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
1058   if (km_getpubkey(p_name(kx->p), &dp, &kx->texp_kpub))
1059     return;
1060   if (kx->f & KXF_PUBKEY)
1061     dh_pubfree(&kx->kpub);
1062   kx->kpub = dp;
1063   kx->f |= KXF_PUBKEY;
1064   if ((kx->f & KXF_DEAD) || kx->s != KXS_SWITCH) {
1065     T( trace(T_KEYEXCH, "keyexch: restarting key negotiation with `%s'",
1066              p_name(kx->p)); )
1067     stop(kx);
1068     start(kx, time(0));
1069     resend(kx);
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;
1090   if (km_getpubkey(p_name(p), &kx->kpub, &kx->texp_kpub))
1091     return (-1);
1092   kx->f = KXF_DEAD | KXF_PUBKEY;
1093   start(kx, time(0));
1094   resend(kx);
1095   return (0);
1096 }
1097
1098 /*----- That's all, folks -------------------------------------------------*/