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