Commit | Line | Data |
---|---|---|
247f344a MW |
1 | /* -*-c-*- |
2 | * | |
3 | * Request a key over UDP, or respond to such a request | |
4 | * | |
5 | * (c) 2012 Mark Wooding | |
6 | */ | |
7 | ||
8 | /*----- Licensing notice --------------------------------------------------* | |
9 | * | |
10 | * This file is part of udpkey. | |
11 | * | |
12 | * The udpkey program is free software; you can redistribute it and/or modify | |
13 | * it under the terms of the GNU General Public License as published by | |
14 | * the Free Software Foundation; either version 2 of the License, or | |
15 | * (at your option) any later version. | |
16 | * | |
17 | * The udpkey program is distributed in the hope that it will be useful, | |
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
20 | * GNU General Public License for more details. | |
21 | * | |
22 | * You should have received a copy of the GNU General Public License | |
23 | * along with udpkey; if not, write to the Free Software Foundation, | |
24 | * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
25 | */ | |
26 | ||
27 | /*----- Header files ------------------------------------------------------*/ | |
28 | ||
29 | #include <ctype.h> | |
30 | #include <errno.h> | |
31 | #include <stdio.h> | |
32 | #include <stdlib.h> | |
33 | #include <string.h> | |
34 | #include <time.h> | |
35 | ||
36 | #include <sys/types.h> | |
37 | #include <sys/time.h> | |
38 | #include <unistd.h> | |
39 | #include <fcntl.h> | |
40 | ||
41 | #include <syslog.h> | |
42 | ||
43 | #include <sys/socket.h> | |
44 | #include <arpa/inet.h> | |
45 | #include <netinet/in.h> | |
46 | #include <netdb.h> | |
47 | ||
48 | #include <mLib/alloc.h> | |
49 | #include <mLib/buf.h> | |
50 | #include <mLib/daemonize.h> | |
51 | #include <mLib/dstr.h> | |
52 | #include <mLib/fdflags.h> | |
53 | #include <mLib/fwatch.h> | |
54 | #include <mLib/hex.h> | |
55 | #include <mLib/mdwopt.h> | |
56 | #include <mLib/quis.h> | |
57 | #include <mLib/report.h> | |
58 | #include <mLib/sub.h> | |
59 | #include <mLib/tv.h> | |
60 | ||
61 | #include <catacomb/buf.h> | |
a5f873be | 62 | #include <catacomb/ct.h> |
247f344a MW |
63 | #include <catacomb/dh.h> |
64 | #include <catacomb/ec.h> | |
65 | #include <catacomb/ec-keys.h> | |
66 | #include <catacomb/gcipher.h> | |
67 | #include <catacomb/gmac.h> | |
68 | #include <catacomb/group.h> | |
69 | #include <catacomb/key.h> | |
70 | #include <catacomb/mp.h> | |
71 | #include <catacomb/mprand.h> | |
72 | #include <catacomb/noise.h> | |
73 | #include <catacomb/rand.h> | |
74 | ||
75 | #include <catacomb/rijndael-counter.h> | |
76 | #include <catacomb/sha256.h> | |
77 | ||
78 | #ifdef DEBUG | |
79 | # define D(x) x | |
80 | #else | |
81 | # define D(x) | |
82 | #endif | |
83 | ||
84 | /*---- Static variables ---------------------------------------------------*/ | |
85 | ||
86 | static unsigned flags = 0; | |
87 | #define f_bogus 1u | |
88 | #define f_listen 2u | |
89 | #define f_daemon 4u | |
90 | #define f_syslog 8u | |
91 | ||
92 | #define BUFSZ 65536 | |
93 | static unsigned char ibuf[BUFSZ], obuf[BUFSZ]; | |
94 | ||
95 | static key_file *kf; | |
96 | static const char *kfname = "keyring"; | |
97 | static const char *pidfile; | |
98 | static fwatch kfwatch; | |
99 | static unsigned nq; | |
100 | ||
101 | /*----- Miscellaneous utilities -------------------------------------------*/ | |
102 | ||
103 | /* Resolve NAME, storing the address in *ADDR. Exit on error. */ | |
104 | static void resolve(const char *name, struct in_addr *addr) | |
105 | { | |
106 | struct hostent *h; | |
107 | ||
108 | if ((h = gethostbyname(name)) == 0) | |
109 | die(1, "failed to resolve `%s': %s", name, hstrerror(h_errno)); | |
110 | if (h->h_addrtype != AF_INET) | |
111 | die(1, "unexpected address type %d", h->h_addrtype); | |
112 | memcpy(addr, h->h_addr, sizeof(struct in_addr)); | |
113 | } | |
114 | ||
115 | /* Convert PORT to a port number (in host byte order). Exit on error. */ | |
116 | static unsigned short getport(const char *port) | |
117 | { | |
118 | unsigned long i = 0; | |
119 | char *q; | |
120 | int e = errno; | |
121 | ||
122 | errno = 0; | |
123 | if (!isdigit(*port) || | |
124 | (i = strtoul(port, &q, 0)) == 0 || | |
125 | i >= 65536 || *q || errno) | |
126 | die(1, "invalid port number `%s'", port); | |
127 | errno = e; | |
128 | return ((unsigned short)i); | |
129 | } | |
130 | ||
131 | /* Read the file named by NAME into a buffer -- or at least an initial | |
132 | * portion of it; set *P to the start and *SZ to the length. Return -1 if it | |
133 | * didn't work. The buffer doesn't need to be freed: the data is stashed in | |
134 | * ibuf. | |
135 | */ | |
136 | static int snarf(const char *name, void **p, size_t *sz) | |
137 | { | |
138 | ssize_t n; | |
139 | int fd; | |
140 | ||
141 | if ((fd = open(name, O_RDONLY)) < 0) return (-1); | |
142 | n = read(fd, ibuf, sizeof(ibuf)); | |
143 | close(fd); | |
144 | if (n < 0) return (-1); | |
145 | *p = ibuf; *sz = n; | |
146 | return (0); | |
147 | } | |
148 | ||
149 | /* Complain about something. If f_syslog is set then complain to that; | |
150 | * otherwise write to stderr. Don't use `%m' because that won't work when | |
151 | * writing to stderr. | |
152 | */ | |
92469bdf | 153 | static void PRINTF_LIKE(2, 3) complain(int sev, const char *msg, ...) |
247f344a MW |
154 | { |
155 | va_list ap; | |
156 | ||
157 | va_start(ap, msg); | |
158 | if (flags & f_syslog) | |
159 | vsyslog(sev, msg, ap); | |
160 | else { | |
161 | fprintf(stderr, "%s: ", QUIS); | |
162 | vfprintf(stderr, msg, ap); | |
163 | fputc('\n', stderr); | |
164 | } | |
165 | } | |
166 | ||
167 | /*----- Reading key data --------------------------------------------------*/ | |
168 | ||
169 | struct kinfo { | |
170 | group *g; | |
171 | ge *X; | |
172 | mp *x; | |
173 | const gccipher *cc; | |
174 | const gcmac *mc; size_t tagsz; | |
175 | const gchash *hc; | |
176 | }; | |
177 | ||
178 | /* Clear a kinfo structure so it can be freed without trouble. */ | |
179 | static void k_init(struct kinfo *k) { k->g = 0; k->x = 0; k->X = 0; } | |
180 | ||
181 | /* Free a kinfo structure. This is safe on any initialized kinfo | |
182 | * structure. | |
183 | */ | |
184 | static void k_free(struct kinfo *k) | |
185 | { | |
186 | if (k->X) { G_DESTROY(k->g, k->X); k->X = 0; } | |
187 | if (k->x) { MP_DROP(k->x); k->x = 0; } | |
188 | if (k->g) { G_DESTROYGROUP(k->g); k->g = 0; } | |
189 | } | |
190 | ||
191 | /* Empty macro arguments are forbidden. But arguments are expended during | |
192 | * replacement, not while the call is being processed, so this hack is OK. | |
193 | * Unfortunately, if a potentially empty argument is passed on to another | |
194 | * macro then it needs to be guarded with a use of EMPTY too... | |
195 | */ | |
196 | #define EMPTY | |
197 | ||
198 | /* Table of key types. Entries have the form | |
199 | * | |
200 | * _(name, NAME, SETGROUP, SETPRIV, SETPUB) | |
201 | * | |
202 | * The name and NAME are lower- and uppercase names for the type used for | |
203 | * constructing various type name constant names. The code fragment SETGROUP | |
204 | * initializes k->g given the name_{pub,priv} structure in p; SETPRIV and | |
205 | * SETPUB set up k->x and k->X respectively. (In this last case, k->X will | |
206 | * have been created as a group element already.) | |
207 | */ | |
208 | #define KEYTYPES(_) \ | |
209 | \ | |
210 | _(dh, DH, \ | |
211 | { k->g = group_prime(&p.dp); }, \ | |
212 | { k->x = MP_COPY(p.x); }, \ | |
213 | { if (G_FROMINT(k->g, k->X, p.y)) { \ | |
214 | complain(LOG_ERR, "bad public key in `%s'", t->buf); \ | |
215 | goto fail; \ | |
216 | } \ | |
217 | }) \ | |
218 | \ | |
219 | _(ec, EC, \ | |
220 | { ec_info ei; const char *e; \ | |
221 | if ((e = ec_getinfo(&ei, p.cstr)) != 0) { \ | |
222 | complain(LOG_ERR, "bad elliptic curve in `%s': %s", t->buf, e); \ | |
223 | goto fail; \ | |
224 | } \ | |
225 | k->g = group_ec(&ei); \ | |
226 | }, \ | |
227 | { k->x = MP_COPY(p.x); }, \ | |
228 | { if (G_FROMEC(k->g, k->X, &p.p)) { \ | |
229 | complain(LOG_ERR, "bad public point in `%s'", t->buf); \ | |
230 | goto fail; \ | |
231 | } \ | |
232 | }) | |
233 | ||
234 | /* Define load_tywhich, where which is `pub' or `priv', to load a public or | |
235 | * private key. Other parameters are as for the KEYTYPES list above. | |
236 | */ | |
237 | #define KLOAD(ty, TY, which, WHICH, setgroup, setpriv, setpub) \ | |
238 | static int load_##ty##which(key_data *kd, struct kinfo *k, dstr *t) \ | |
239 | { \ | |
240 | key_packstruct kps[TY##_##WHICH##FETCHSZ]; \ | |
241 | key_packdef *kp; \ | |
242 | ty##_##which p; \ | |
243 | int rc; \ | |
244 | \ | |
245 | /* Extract the key data from the keydata. */ \ | |
246 | kp = key_fetchinit(ty##_##which##fetch, kps, &p); \ | |
247 | if ((rc = key_unpack(kp, kd, t)) != 0) { \ | |
248 | complain(LOG_ERR, "failed to unpack key `%s': %s", \ | |
249 | t->buf, key_strerror(rc)); \ | |
250 | goto fail; \ | |
251 | } \ | |
252 | \ | |
253 | /* Extract the components as abstract group elements. */ \ | |
254 | setgroup; \ | |
255 | setpriv; \ | |
256 | k->X = G_CREATE(k->g); \ | |
257 | setpub; \ | |
258 | \ | |
259 | /* Dispose of stuff we don't need. */ \ | |
260 | key_fetchdone(kp); \ | |
261 | return (0); \ | |
262 | \ | |
263 | /* Tidy up after mishaps. */ \ | |
264 | fail: \ | |
265 | k_free(k); \ | |
266 | key_fetchdone(kp); \ | |
267 | return (-1); \ | |
268 | } | |
269 | ||
270 | /* Map over the KEYTYPES to declare the load_tywhich functions using KLOAD | |
271 | * above. | |
272 | */ | |
273 | #define KEYTYPE_KLOAD(ty, TY, setgroup, setpriv, setpub) \ | |
274 | KLOAD(ty, TY, priv, PRIV, setgroup, setpriv, \ | |
275 | { G_EXP(k->g, k->X, k->g->g, k->x); }) \ | |
276 | KLOAD(ty, TY, pub, PUB, setgroup, { }, setpub) | |
277 | KEYTYPES(KEYTYPE_KLOAD) | |
278 | ||
279 | /* Define a table of group key-loading operations. */ | |
280 | struct kload_ops { | |
281 | const char *name; | |
282 | int (*loadpriv)(key_data *, struct kinfo *, dstr *); | |
283 | int (*loadpub)(key_data *, struct kinfo *, dstr *); | |
284 | }; | |
285 | ||
286 | static const struct kload_ops kload_ops[] = { | |
287 | #define KEYTYPE_OPS(ty, TY, setgroup, setpriv, setpub) \ | |
288 | { #ty, load_##ty##priv, load_##ty##pub }, | |
289 | KEYTYPES(KEYTYPE_OPS) | |
290 | { 0 } | |
291 | }; | |
292 | ||
293 | /* Load a private or public (indicated by PRIVP) key named TAG into a kinfo | |
294 | * structure K. Also fill in the cipher suite selections extracted from the | |
295 | * key attributes. | |
296 | */ | |
297 | static int loadkey(const char *tag, struct kinfo *k, int privp) | |
298 | { | |
299 | const struct kload_ops *ops; | |
300 | dstr d = DSTR_INIT, dd = DSTR_INIT; | |
301 | key *ky; | |
302 | key_data **kd; | |
303 | const char *ty, *p; | |
304 | char *q; | |
305 | int tsz; | |
306 | int rc; | |
307 | ||
308 | /* Find the key data. */ | |
309 | if (key_qtag(kf, tag, &d, &ky, &kd)) { | |
310 | complain(LOG_ERR, "unknown key tag `%s'", tag); | |
311 | goto fail; | |
312 | } | |
313 | ||
314 | /* Find the key's group type and locate the group operations. */ | |
315 | ty = key_getattr(kf, ky, "group"); | |
316 | if (!ty && strncmp(ky->type, "udpkey-", 7) == 0) ty = ky->type + 7; | |
317 | if (!ty) { | |
318 | complain(LOG_ERR, "no group type for key %s", d.buf); | |
319 | goto fail; | |
320 | } | |
321 | for (ops = kload_ops; ops->name; ops++) { | |
322 | if (strcmp(ty, ops->name) == 0) | |
323 | goto found; | |
324 | } | |
325 | complain(LOG_ERR, "unknown group type `%s' in key %s", ty, d.buf); | |
326 | goto fail; | |
327 | ||
328 | found: | |
329 | /* Extract the key data into an appropriately abstract form. */ | |
330 | k->g = 0; k->x = 0; k->X = 0; | |
331 | if ((rc = (privp ? ops->loadpriv : ops->loadpub)(*kd, k, &d)) != 0) | |
332 | goto fail; | |
333 | ||
334 | /* Extract the chosen symmetric cipher. */ | |
335 | if ((p = key_getattr(kf, ky, "cipher")) == 0) | |
336 | k->cc = &rijndael_counter; | |
337 | else if ((k->cc = gcipher_byname(p)) == 0) { | |
338 | complain(LOG_ERR, "unknown cipher `%s' in key %s", p, d.buf); | |
339 | goto fail; | |
340 | } | |
341 | ||
342 | /* And the chosen hash function. */ | |
343 | if ((p = key_getattr(kf, ky, "hash")) == 0) | |
344 | k->hc = &sha256; | |
345 | else if ((k->hc = ghash_byname(p)) == 0) { | |
346 | complain(LOG_ERR, "unknown hash `%s' in key %s", p, d.buf); | |
347 | goto fail; | |
348 | } | |
349 | ||
350 | /* And finally a MAC. This is more fiddly because we must handle (a) | |
351 | * truncation and (b) defaulting based on the hash. | |
352 | */ | |
353 | if ((p = key_getattr(kf, ky, "mac")) == 0) | |
354 | dstr_putf(&dd, "%s-hmac", k->hc->name); | |
355 | else | |
356 | dstr_puts(&dd, p); | |
357 | if ((q = strchr(dd.buf, '/')) != 0) *q++ = 0; | |
358 | else q = 0; | |
359 | if ((k->mc = gmac_byname(dd.buf)) == 0) { | |
360 | complain(LOG_ERR, "unknown mac `%s' in key %s", dd.buf, d.buf); | |
361 | goto fail; | |
362 | } | |
363 | if (!q) | |
364 | k->tagsz = k->mc->hashsz/2; | |
365 | else { | |
366 | tsz = atoi(q); | |
367 | if (tsz <= 0 || tsz%8 || tsz/8 > k->mc->hashsz) { | |
46716e8f | 368 | complain(LOG_ERR, "bad tag size `%s' for mac `%s' in key %s", |
247f344a MW |
369 | q, k->mc->name, d.buf); |
370 | goto fail; | |
371 | } | |
372 | k->tagsz = tsz/8; | |
373 | } | |
374 | ||
375 | /* Done. */ | |
376 | rc = 0; | |
377 | goto done; | |
378 | ||
379 | fail: | |
380 | rc = -1; | |
381 | done: | |
382 | dstr_destroy(&d); | |
383 | dstr_destroy(&dd); | |
384 | return (rc); | |
385 | } | |
386 | ||
387 | static void keymoan(const char *file, int line, const char *err, void *p) | |
388 | { complain(LOG_ERR, "%s:%d: %s", file, line, err); } | |
389 | ||
390 | /* Update the keyring `kf' if the file has been changed since we last looked. | |
391 | */ | |
392 | static void kfupdate(void) | |
393 | { | |
394 | key_file *kfnew; | |
395 | ||
396 | if (!fwatch_update(&kfwatch, kfname)) return; | |
397 | kfnew = CREATE(key_file); | |
398 | if (key_open(kfnew, kfname, KOPEN_READ, keymoan, 0)) { | |
399 | DESTROY(kfnew); | |
400 | return; | |
401 | } | |
402 | key_close(kf); | |
403 | DESTROY(kf); | |
404 | kf = kfnew; | |
405 | } | |
406 | ||
407 | /*----- Low-level crypto operations ---------------------------------------*/ | |
408 | ||
409 | /* Derive a key, writing its address to *KK and size to *N. The size is | |
410 | * compatible with the keysz rules KSZ. It is generated for the purpose of | |
411 | * keying a WHAT (used for key separation and in error messages), and NAME is | |
412 | * the name of the specific instance (e.g., `twofish-counter') from the class | |
413 | * name. The kinfo structure K tells us which algorithms to use for the | |
414 | * derivation. The group elements U and Z are the cryptographic inputs | |
415 | * for the derivation. | |
416 | * | |
de14f3b0 | 417 | * Basically all we do is compute H(what || U || V || Z). |
247f344a | 418 | */ |
de14f3b0 | 419 | static int derive(struct kinfo *k, ge *U, ge *V, ge *Z, |
247f344a MW |
420 | const char *what, const char *name, const octet *ksz, |
421 | octet **kk, size_t *n) | |
422 | { | |
423 | buf b; | |
424 | ghash *h; | |
425 | octet *p; | |
426 | ||
427 | /* Find a suitable key size. */ | |
428 | if ((*n = keysz(k->hc->hashsz, ksz)) == 0) { | |
429 | complain(LOG_ERR, | |
430 | "failed to find suitable key size for %s `%s' and hash `%s'", | |
431 | what, name, k->hc->name); | |
432 | return (-1); | |
433 | } | |
434 | ||
435 | /* Build the hash preimage. */ | |
436 | buf_init(&b, obuf, sizeof(obuf)); | |
437 | buf_put(&b, "udpkey-", 7); | |
438 | buf_putstrz(&b, what); | |
de14f3b0 MW |
439 | if (U) G_TORAW(k->g, &b, U); |
440 | if (V) G_TORAW(k->g, &b, V); | |
247f344a MW |
441 | G_TORAW(k->g, &b, Z); |
442 | if (BBAD(&b)) { | |
443 | complain(LOG_ERR, "overflow while deriving key (prepare preimage)!"); | |
444 | return (-1); | |
445 | } | |
446 | ||
447 | /* Derive the output key. */ | |
448 | h = GH_INIT(k->hc); | |
449 | GH_HASH(h, BBASE(&b), BLEN(&b)); | |
450 | buf_init(&b, obuf, sizeof(obuf)); | |
451 | if ((p = buf_get(&b, h->ops->c->hashsz)) == 0) { | |
452 | complain(LOG_ERR, "overflow while deriving key (output hash)!"); | |
453 | GH_DESTROY(h); | |
454 | return (-1); | |
455 | } | |
456 | GH_DONE(h, p); | |
457 | GH_DESTROY(h); | |
458 | *kk = p; | |
459 | return (0); | |
460 | } | |
461 | ||
462 | #ifdef DEBUG | |
463 | static void debug_mp(const char *what, mp *x) | |
464 | { fprintf(stderr, "%s: *** ", QUIS); MP_EPRINT(what, x); } | |
465 | static void debug_ge(const char *what, group *g, ge *X) | |
466 | { | |
467 | fprintf(stderr, "%s: *** %s = ", QUIS, what); | |
468 | group_writefile(g, X, stderr); | |
469 | fputc('\n', stderr); | |
470 | } | |
471 | #endif | |
472 | ||
de14f3b0 MW |
473 | /* Derive the symmetric keys for Diffie--Hellman-based symmetric crypto, |
474 | * given (optional) sender public key U, (optional) recipient public key V, | |
475 | * and secret Z, constructing cipher and mac objects for the actual work. | |
476 | * | |
477 | * Your protocol must be consistent about whether it sends U and/or V: the | |
478 | * formatting is ambiguous if you sometimes use one and sometimes the other. | |
479 | */ | |
480 | static void dh_derive_keys(struct kinfo *k, ge *U, ge *V, ge *Z, | |
481 | gcipher **cc, gmac **mm) | |
482 | { | |
483 | octet *kk; | |
484 | size_t ksz; | |
485 | ||
486 | derive(k, U, V, Z, "cipher", k->cc->name, k->cc->keysz, &kk, &ksz); | |
487 | *cc = GC_INIT(k->cc, kk, ksz); | |
488 | derive(k, U, V, Z, "mac", k->mc->name, k->mc->keysz, &kk, &ksz); | |
489 | *mm = GM_KEY(k->mc, kk, ksz); | |
490 | } | |
491 | ||
492 | /* Prepare for an IES encryption. Given a public key X, prepare a clue R and | |
493 | * secret Z for later. | |
494 | */ | |
495 | static void ies_enc_prepare(struct kinfo *k, ge *X, ge *R, ge *Z) | |
496 | { | |
497 | mp *r = mprand_range(MP_NEW, k->g->r, &rand_global, 0); | |
498 | G_EXP(k->g, R, k->g->g, r); | |
499 | G_EXP(k->g, Z, X, r); | |
500 | D( debug_mp("r", r); debug_ge("R", k.g, R); ) | |
501 | MP_DROP(r); | |
502 | } | |
503 | ||
504 | /* Actually perform an IES encryption. Given a prepared clue R and secret Z, | |
505 | * and an SZ-byte plaintext message P, write the ciphertext to B. Note that | |
506 | * the clue itself is /not/ written to B: you must do that yourself. | |
507 | */ | |
508 | static int ies_enc_perform(struct kinfo *k, buf *b, ge *R, ge *Z, | |
509 | const void *p, size_t sz) | |
510 | { | |
511 | octet *t, *tt, *ct; | |
512 | gcipher *c; | |
513 | gmac *m; | |
514 | ghash *h; | |
515 | ||
516 | if ((t = buf_get(b, k->tagsz)) == 0 || | |
517 | (ct = buf_get(b, sz)) == 0) { | |
518 | complain(LOG_ERR, "overflow while writing ciphertext"); | |
519 | return (-1); | |
520 | } | |
521 | ||
522 | dh_derive_keys(k, R, 0, Z, &c, &m); | |
523 | GC_ENCRYPT(c, p, ct, sz); | |
524 | h = GM_INIT(m); | |
525 | GH_HASH(h, ct, sz); | |
526 | tt = GH_DONE(h, 0); | |
527 | memcpy(t, tt, k->tagsz); | |
528 | ||
529 | GC_DESTROY(c); | |
530 | GM_DESTROY(m); | |
531 | GH_DESTROY(h); | |
532 | } | |
533 | ||
7f5ea7d3 | 534 | /*----- Protocol summary --------------------------------------------------* |
36fb0983 MW |
535 | * |
536 | * There are two protocol versions. The original version works as follows. | |
7f5ea7d3 MW |
537 | * |
538 | * * Request | |
539 | * memz KEYTAG tag of wanted secret | |
540 | * ge U public vector | |
541 | * | |
542 | * * Response | |
543 | * ge V public vector: V = v P | |
de14f3b0 MW |
544 | * ge W masked IES clue: W = R - Y = r P - v U |
545 | * iesct[*](H(R, r X)) | |
546 | * mem[*] S secret | |
36fb0983 MW |
547 | * |
548 | * The new version provides forward secrecy, which involves additional flows. | |
549 | * | |
de14f3b0 | 550 | * * Request |
36fb0983 MW |
551 | * u8 0 marker byte for new protocol |
552 | * u8 1 packet type | |
553 | * mem8 KEYTAG wanted secret tag | |
de14f3b0 | 554 | * ge U public vector: U = u P |
36fb0983 MW |
555 | * |
556 | * * Challenge | |
557 | * u8 17 packet type | |
de14f3b0 MW |
558 | * ge R IES clue |
559 | * iesct[*](H(R, r X)) | |
560 | * ge U client's public vector (confirms receipt) | |
561 | * ge V public vector: V = v P | |
562 | * mem[*] a nonce | |
36fb0983 MW |
563 | * |
564 | * * Response | |
565 | * u8 0 marker byte for new protocol | |
566 | * u8 2 packet type | |
de14f3b0 | 567 | * mem[*] a server's nonce (confirm receipt of V) |
36fb0983 | 568 | * |
de14f3b0 | 569 | * * Answer |
36fb0983 | 570 | * u8 18 packet type |
de14f3b0 MW |
571 | * iesct[*](H(U, V, Z)) |
572 | * mem[*] S secret | |
7f5ea7d3 MW |
573 | */ |
574 | ||
de14f3b0 MW |
575 | #define FWS_REQ 0x01 |
576 | #define FWS_CHAL 0x11 | |
36fb0983 | 577 | #define FWS_RESP 0x02 |
de14f3b0 | 578 | #define FWS_ANS 0x12 |
36fb0983 | 579 | |
247f344a MW |
580 | /*----- Listening for requests --------------------------------------------*/ |
581 | ||
582 | /* Rate limiting parameters. | |
583 | * | |
584 | * There's a probabilistic rate-limiting mechanism. A counter starts at 0. | |
c33ded25 | 585 | * Every time we process a request, we increment the counter. The counter |
247f344a MW |
586 | * drops by RATE_REFILL every second. If the counter is below RATE_CREDIT |
587 | * then the request is processed; otherwise it is processed with probability | |
588 | * 1/(counter - RATE_CREDIT). | |
589 | */ | |
590 | #define RATE_REFILL 10 /* Credits per second. */ | |
591 | #define RATE_CREDIT 1000 /* Initial credit. */ | |
592 | ||
7f5ea7d3 MW |
593 | static time_t now; |
594 | ||
0057c20e MW |
595 | /* Secrets table. |
596 | * | |
597 | * The server doesn't want to maintain state for each client. Instead, we | |
598 | * generate a global secret, and derive per-client secrets from it. A secret | |
599 | * needs to have an expiry time (at which point we won't use it for new | |
600 | * requests) and a deletion time (at which point we just forget that it ever | |
601 | * existed). This lets us roll over to a new secret without leaving existing | |
602 | * clients completely in the lurch. | |
603 | * | |
604 | * Secrets are kept in a linked list, ordered by expiry time. At any given | |
605 | * time there is at most one unexpired secret (because we only make a new one | |
606 | * when the old one expires). | |
de14f3b0 MW |
607 | * |
608 | * The nonce has the form ... | |
0057c20e MW |
609 | */ |
610 | ||
611 | struct secret { | |
612 | struct secret *next; | |
613 | uint32 seq; | |
614 | time_t t_exp, t_del; | |
615 | octet x[32]; | |
616 | }; | |
617 | static struct secret *secrets = 0, *live_secret = 0; | |
618 | static uint32 next_secret_seq = 0; | |
619 | #define T_SECEXP 30 | |
620 | #define T_SECDEL 45 | |
621 | ||
622 | static void kill_dead_secrets(void) | |
623 | { | |
624 | struct secret *s = secrets, *ss; | |
625 | ||
626 | for (s = secrets; s && s->t_del <= now; s = ss) { | |
627 | ss = s->next; | |
628 | DESTROY(s); | |
629 | } | |
630 | secrets = 0; | |
631 | if (!s) live_secret = 0; | |
632 | } | |
633 | ||
634 | static struct secret *find_secret(uint32 seq) | |
635 | { | |
636 | struct secret *s; | |
637 | ||
638 | kill_dead_secrets(); | |
639 | for (s = secrets; s; s = s->next) | |
640 | if (s->seq == seq) return (s); | |
641 | return (0); | |
642 | } | |
643 | ||
644 | static struct secret *fresh_secret(void) | |
645 | { | |
646 | struct secret *s; | |
647 | ||
648 | if (live_secret && live_secret->t_exp > now) return (live_secret); | |
649 | kill_dead_secrets(); | |
650 | ||
651 | s = CREATE(struct secret); | |
652 | s->seq = next_secret_seq++; | |
653 | s->next = 0; | |
654 | rand_get(RAND_GLOBAL, s->x, sizeof(s->x)); | |
655 | s->t_exp = now + T_SECEXP; s->t_del = now + T_SECDEL; | |
656 | if (live_secret) live_secret->next = s; | |
657 | else secrets = s; | |
658 | live_secret = s; | |
659 | return (s); | |
660 | } | |
661 | ||
7f5ea7d3 MW |
662 | static int fetch_key(const char *tag, struct sockaddr_in *sin, |
663 | key **ky, struct kinfo *k) | |
664 | { | |
665 | dstr d = DSTR_INIT, dd = DSTR_INIT; | |
666 | key_data **kkd; | |
667 | char *p, *q; | |
668 | const char *pp; | |
669 | struct in_addr in; | |
670 | int ch, mlen, rc = -1; | |
671 | ||
672 | /* Find the key. */ | |
673 | kfupdate(); | |
674 | if (key_qtag(kf, tag, &d, ky, &kkd)) { | |
675 | complain(LOG_WARNING, "unknown key tag `%s' from %s:%d", | |
676 | tag, inet_ntoa(sin->sin_addr), ntohs(sin->sin_port)); | |
677 | goto done; | |
678 | } | |
679 | ||
680 | /* And make sure that it has the right shape. */ | |
681 | if (((*ky)->k->e & KF_ENCMASK) != KENC_BINARY) { | |
682 | complain(LOG_ERR, "key %s is not plain binary data", d.buf); | |
683 | goto done; | |
684 | } | |
685 | ||
686 | /* Find the list of clients, and look up the caller's address in the | |
687 | * list. Entries have the form ADDRESS[/LEN][=TAG] and are separated by | |
688 | * `;'. | |
689 | */ | |
690 | if ((pp = key_getattr(kf, *ky, "clients")) == 0) { | |
691 | complain(LOG_WARNING, | |
692 | "key %s requested from %s:%d has no `clients' attribute", | |
693 | d.buf, inet_ntoa(sin->sin_addr), ntohs(sin->sin_port)); | |
694 | goto done; | |
695 | } | |
696 | dstr_puts(&dd, pp); | |
697 | p = dd.buf; | |
698 | while (*p) { | |
699 | q = p; | |
700 | while (isdigit((unsigned char)*q) || *q == '.') q++; | |
701 | ch = *q; *q++ = 0; | |
702 | if (!inet_aton(p, &in)) goto skip; | |
703 | if (ch != '/') | |
704 | mlen = 32; | |
705 | else { | |
706 | p = q; | |
707 | while (isdigit((unsigned char)*q)) q++; | |
708 | ch = *q; *q++ = 0; | |
709 | mlen = atoi(p); | |
710 | } | |
711 | if (((sin->sin_addr.s_addr ^ in.s_addr) & | |
712 | htonl(0xffffffff << (32 - mlen))) == 0) | |
713 | goto match; | |
714 | skip: | |
715 | if (!ch) break; | |
716 | p = q; | |
717 | while (*p && *p != ';') p++; | |
718 | if (*p) p++; | |
719 | } | |
720 | complain(LOG_WARNING, "access to key %s denied to %s:%d", | |
721 | d.buf, inet_ntoa(sin->sin_addr), ntohs(sin->sin_port)); | |
722 | goto done; | |
723 | ||
724 | match: | |
725 | /* Build a tag name for the caller's KEM key, either from the client | |
726 | * match or the source address. | |
727 | */ | |
728 | if (ch != '=') { | |
729 | DRESET(&dd); | |
730 | dstr_puts(&dd, "client-"); | |
731 | dstr_puts(&dd, inet_ntoa(sin->sin_addr)); | |
732 | p = dd.buf; | |
733 | } else { | |
734 | p = q; | |
735 | while (*q && *q != ';') q++; | |
736 | if (*q == ';') *q++ = 0; | |
737 | } | |
738 | ||
739 | /* Report the match. */ | |
740 | complain(LOG_NOTICE, "client %s:%d (`%s') requests key %s", | |
741 | inet_ntoa(sin->sin_addr), ntohs(sin->sin_port), p, d.buf); | |
742 | ||
743 | /* Load the KEM key. */ | |
744 | if (loadkey(p, k, 0)) goto done; | |
745 | D( debug_ge("X", k.g, k.X); ) | |
746 | ||
747 | /* All complete. */ | |
748 | rc = 0; | |
749 | ||
750 | done: | |
751 | /* Clean everything up. */ | |
752 | dstr_destroy(&d); | |
753 | dstr_destroy(&dd); | |
754 | if (rc) k_free(k); | |
755 | return (rc); | |
756 | } | |
757 | ||
758 | static int respond_v0(buf *bin, buf *bout, struct sockaddr_in *sin) | |
759 | { | |
760 | ge *R = 0, *U = 0, *V = 0, *W = 0, *Y = 0, *Z = 0; | |
de14f3b0 MW |
761 | mp *v = MP_NEW; |
762 | struct kinfo k; | |
7f5ea7d3 MW |
763 | char *p; |
764 | size_t sz; | |
7f5ea7d3 | 765 | key *ky; |
7f5ea7d3 MW |
766 | int rc = -1; |
767 | ||
768 | /* Clear out the key state. */ | |
769 | k_init(&k); | |
770 | ||
771 | /* Extract the key tag name. */ | |
772 | if ((p = buf_getmemz(bin, &sz)) == 0) { | |
773 | complain(LOG_WARNING, "invalid key tag from %s:%d", | |
774 | inet_ntoa(sin->sin_addr), ntohs(sin->sin_port)); | |
775 | goto done; | |
776 | } | |
777 | ||
778 | /* Find the client's key and check that it's allowed. */ | |
779 | if (fetch_key(p, sin, &ky, &k)) goto done; | |
780 | ||
781 | /* Read the caller's ephemeral key. */ | |
782 | R = G_CREATE(k.g); W = G_CREATE(k.g); | |
783 | U = G_CREATE(k.g); V = G_CREATE(k.g); | |
784 | Y = G_CREATE(k.g); Z = G_CREATE(k.g); | |
785 | if (G_FROMBUF(k.g, bin, U)) { | |
786 | complain(LOG_WARNING, "failed to read ephemeral vector from %s:%d", | |
787 | inet_ntoa(sin->sin_addr), ntohs(sin->sin_port)); | |
788 | goto done; | |
789 | } | |
790 | D( debug_ge("U", k.g, U); ) | |
791 | if (BLEFT(bin)) { | |
792 | complain(LOG_WARNING, "trailing junk in request from %s:%d", | |
793 | inet_ntoa(sin->sin_addr), ntohs(sin->sin_port)); | |
794 | goto done; | |
795 | } | |
796 | ||
797 | /* Ephemeral Diffie--Hellman. Choose v in GF(q) at random; compute | |
798 | * V = v P and -Y = (-v) U. | |
799 | */ | |
800 | v = mprand_range(v, k.g->r, &rand_global, 0); | |
801 | G_EXP(k.g, V, k.g->g, v); | |
802 | D( debug_mp("v", v); debug_ge("V", k.g, V); ) | |
26b2a043 | 803 | v = mp_sub(v, k.g->r, v); |
7f5ea7d3 MW |
804 | G_EXP(k.g, Y, U, v); |
805 | D( debug_ge("-Y", k.g, Y); ) | |
806 | ||
807 | /* DLIES. Choose r in GF(q) at random; compute R = r P and Z = r X. Mask | |
808 | * the clue R as W = R - Y. (Doing the subtraction here makes life easier | |
809 | * at the other end, since we can determine -Y by negating v whereas the | |
810 | * recipient must subtract vectors which may be less efficient.) | |
811 | */ | |
de14f3b0 | 812 | ies_enc_prepare(&k, k.X, R, Z); |
7f5ea7d3 MW |
813 | G_MUL(k.g, W, R, Y); |
814 | D( debug_ge("Z", k.g, Z); debug_ge("W", k.g, W); ) | |
815 | ||
de14f3b0 | 816 | /* Write the ciphertext. */ |
7f5ea7d3 MW |
817 | rc = 0; |
818 | if (G_TOBUF(k.g, bout, V) || | |
de14f3b0 MW |
819 | G_TOBUF(k.g, bout, W) || |
820 | ies_enc_perform(&k, bout, R, Z, ky->k->u.k.k, ky->k->u.k.sz)) | |
7f5ea7d3 | 821 | goto done; |
7f5ea7d3 MW |
822 | |
823 | done: | |
824 | /* Clear everything up and go home. */ | |
825 | if (R) G_DESTROY(k.g, R); | |
826 | if (U) G_DESTROY(k.g, U); | |
827 | if (V) G_DESTROY(k.g, V); | |
828 | if (W) G_DESTROY(k.g, W); | |
829 | if (Y) G_DESTROY(k.g, Y); | |
830 | if (Z) G_DESTROY(k.g, Z); | |
7f5ea7d3 MW |
831 | if (v) MP_DROP(v); |
832 | k_free(&k); | |
833 | return (rc); | |
834 | } | |
835 | ||
247f344a MW |
836 | static int dolisten(int argc, char *argv[]) |
837 | { | |
838 | int sk; | |
7f5ea7d3 | 839 | char *p; |
247f344a MW |
840 | char *aspec; |
841 | ssize_t n; | |
247f344a MW |
842 | fd_set fdin; |
843 | struct sockaddr_in sin; | |
247f344a MW |
844 | socklen_t len; |
845 | buf bin, bout; | |
247f344a | 846 | FILE *fp = 0; |
247f344a | 847 | unsigned bucket = 0, toks; |
7f5ea7d3 | 848 | time_t last = 0; |
247f344a MW |
849 | |
850 | /* Set up the socket address. */ | |
851 | sin.sin_family = AF_INET; | |
852 | aspec = xstrdup(argv[0]); | |
853 | if ((p = strchr(aspec, ':')) == 0) { | |
854 | p = aspec; | |
855 | sin.sin_addr.s_addr = INADDR_ANY; | |
856 | } else { | |
857 | *p++ = 0; | |
858 | resolve(aspec, &sin.sin_addr); | |
859 | } | |
860 | sin.sin_port = htons(getport(p)); | |
861 | ||
862 | /* Create and set up the socket itself. */ | |
863 | if ((sk = socket(PF_INET, SOCK_DGRAM, 0)) < 0 || | |
864 | fdflags(sk, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC) || | |
865 | bind(sk, (struct sockaddr *)&sin, sizeof(sin))) | |
866 | die(1, "failed to create socket: %s", strerror(errno)); | |
867 | ||
868 | /* That's enough initialization. If we should fork, then do that. */ | |
869 | if (flags & f_daemon) { | |
870 | if (pidfile && (fp = fopen(pidfile, "w")) == 0) | |
871 | die(1, "failed to open pidfile `%s': %s", pidfile, strerror(errno)); | |
872 | openlog(QUIS, LOG_PID, LOG_DAEMON); | |
873 | if (daemonize()) | |
874 | die(1, "failed to become background process: %s", strerror(errno)); | |
875 | if (pidfile) { fprintf(fp, "%ld\n", (long)getpid()); fclose(fp); } | |
876 | flags |= f_syslog; | |
877 | } | |
878 | ||
879 | for (;;) { | |
880 | ||
247f344a MW |
881 | /* Wait for something to happen. */ |
882 | FD_ZERO(&fdin); | |
883 | FD_SET(sk, &fdin); | |
884 | if (select(sk + 1, &fdin, 0, 0, 0) < 0) | |
885 | die(1, "select failed: %s", strerror(errno)); | |
886 | noise_timer(RAND_GLOBAL); | |
887 | ||
888 | /* Fetch a packet. */ | |
889 | len = sizeof(sin); | |
890 | n = recvfrom(sk, ibuf, sizeof(ibuf), 0, (struct sockaddr *)&sin, &len); | |
891 | if (n < 0) { | |
892 | if (errno != EAGAIN && errno != EINTR) | |
893 | complain(LOG_ERR, "unexpected receive error: %s", strerror(errno)); | |
7f5ea7d3 | 894 | continue; |
247f344a MW |
895 | } |
896 | ||
897 | /* Refill the bucket, and see whether we should reject this packet. */ | |
898 | now = time(0); | |
899 | if (bucket && now != last) { | |
900 | toks = (now - last)*RATE_REFILL; | |
901 | bucket = bucket < toks ? 0 : bucket - toks; | |
902 | } | |
903 | last = now; | |
904 | if (bucket > RATE_CREDIT && | |
905 | grand_range(&rand_global, bucket - RATE_CREDIT)) | |
7f5ea7d3 | 906 | continue; |
247f344a MW |
907 | bucket++; |
908 | ||
909 | /* Set up the input buffer for parsing the request. */ | |
910 | buf_init(&bin, ibuf, n); | |
247f344a | 911 | buf_init(&bout, obuf, sizeof(obuf)); |
7f5ea7d3 MW |
912 | |
913 | /* Handle the client's message. */ | |
914 | if (respond_v0(&bin, &bout, &sin)) continue; | |
247f344a MW |
915 | |
916 | /* Send the reply packet back to the caller. */ | |
7f5ea7d3 | 917 | if (!BOK(&bout)) goto bad; |
247f344a MW |
918 | if (sendto(sk, BBASE(&bout), BLEN(&bout), 0, |
919 | (struct sockaddr *)&sin, len) < 0) { | |
920 | complain(LOG_ERR, "failed to send response to %s:%d: %s", | |
921 | inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), | |
922 | strerror(errno)); | |
7f5ea7d3 | 923 | continue; |
247f344a MW |
924 | } |
925 | ||
7f5ea7d3 | 926 | continue; |
247f344a MW |
927 | |
928 | bad: | |
929 | /* Report a problem building the reply. */ | |
930 | complain(LOG_ERR, "failed to construct response to %s:%d", | |
931 | inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); | |
247f344a MW |
932 | } |
933 | ||
934 | return (-1); | |
935 | } | |
936 | ||
937 | /*----- Sending requests and processing responses -------------------------*/ | |
938 | ||
939 | struct query { | |
940 | struct query *next; | |
4efa5091 | 941 | const char *tag; |
247f344a MW |
942 | octet *k; |
943 | size_t sz; | |
944 | struct server *s; | |
945 | }; | |
946 | ||
947 | struct server { | |
948 | struct server *next; | |
949 | struct sockaddr_in sin; | |
950 | struct kinfo k; | |
4efa5091 | 951 | const struct client_protocol *proto; |
247f344a MW |
952 | mp *u; |
953 | ge *U; | |
954 | octet *h; | |
955 | }; | |
956 | ||
4efa5091 MW |
957 | struct client_protocol { |
958 | const char *name; | |
959 | int (*setup)(struct query *, struct server *); | |
960 | int (*receive)(struct query *, struct server *, buf *, buf *); | |
961 | int (*retransmit)(struct query *, struct server *, buf *); | |
962 | }; | |
963 | ||
247f344a MW |
964 | /* Record a successful fetch of key material for a query Q. The data starts |
965 | * at K and is SZ bytes long. The data is copied: it's safe to overwrite it. | |
966 | */ | |
4efa5091 MW |
967 | static int donequery(struct query *q, struct server *s, |
968 | const void *k, size_t sz) | |
969 | { | |
970 | octet *tt; | |
971 | ghash *h = 0; | |
972 | int diffp; | |
973 | ||
974 | /* If we have a hash, check that the fragment matches it. */ | |
975 | if (s && s->h) { | |
976 | h = GH_INIT(s->k.hc); | |
977 | GH_HASH(h, k, sz); | |
978 | tt = GH_DONE(h, 0); | |
979 | diffp = memcmp(tt, s->h, h->ops->c->hashsz); | |
980 | GH_DESTROY(h); | |
981 | if (diffp) { | |
982 | moan("response from %s:%d doesn't match hash", | |
983 | inet_ntoa(s->sin.sin_addr), ntohs(s->sin.sin_port)); | |
984 | return (-1); | |
985 | } | |
986 | } | |
987 | ||
988 | /* Stash a copy of the key fragment for later. */ | |
989 | q->k = xmalloc(sz); | |
990 | memcpy(q->k, k, sz); | |
991 | q->sz = sz; nq--; | |
992 | ||
993 | /* All good. */ | |
994 | return (0); | |
995 | } | |
996 | ||
997 | static int setup_v0(struct query *q, struct server *s) | |
998 | { | |
999 | /* Choose an ephemeral private key u. Let x be our private key. We | |
1000 | * compute U = u P and transmit this. | |
1001 | */ | |
1002 | s->u = mprand_range(MP_NEW, s->k.g->r, &rand_global, 0); | |
1003 | s->U = G_CREATE(s->k.g); | |
1004 | G_EXP(s->k.g, s->U, s->k.g->g, s->u); | |
1005 | D( debug_mp("u", s->u); debug_ge("U", s->k.g, s->U); ) | |
1006 | ||
1007 | return (0); | |
1008 | } | |
1009 | ||
1010 | static int retransmit_v0(struct query *q, struct server *s, buf *bout) | |
1011 | { | |
1012 | buf_putstrz(bout, q->tag); | |
1013 | G_TOBUF(s->k.g, bout, s->U); | |
1014 | return (0); | |
1015 | } | |
1016 | ||
1017 | static int receive_v0(struct query *q, struct server *s, buf *bin, buf *bout) | |
1018 | { | |
1019 | ge *R, *V = 0, *W = 0, *Y = 0, *Z = 0; | |
1020 | octet *kk, *t, *tt; | |
1021 | gcipher *c = 0; | |
1022 | gmac *m = 0; | |
1023 | ghash *h = 0; | |
1024 | size_t n, ksz; | |
1025 | octet *p; | |
1026 | int rc = -1; | |
1027 | ||
1028 | R = G_CREATE(s->k.g); | |
1029 | V = G_CREATE(s->k.g); W = G_CREATE(s->k.g); | |
1030 | Y = G_CREATE(s->k.g); Z = G_CREATE(s->k.g); | |
1031 | if (G_FROMBUF(s->k.g, bin, V)) { | |
1032 | moan("invalid Diffie--Hellman vector from %s:%d", | |
1033 | inet_ntoa(s->sin.sin_addr), ntohs(s->sin.sin_port)); | |
1034 | goto done; | |
1035 | } | |
1036 | if (G_FROMBUF(s->k.g, bin, W)) { | |
1037 | moan("invalid clue vector from %s:%d", | |
1038 | inet_ntoa(s->sin.sin_addr), ntohs(s->sin.sin_port)); | |
1039 | goto done; | |
1040 | } | |
1041 | D( debug_ge("V", s->k.g, V); debug_ge("W", s->k.g, W); ) | |
1042 | ||
1043 | /* We have V and W from the server; determine Y = u V, R = W + Y and | |
1044 | * Z = x R, and then derive the symmetric keys. | |
1045 | */ | |
1046 | G_EXP(s->k.g, Y, V, s->u); | |
1047 | G_MUL(s->k.g, R, W, Y); | |
1048 | G_EXP(s->k.g, Z, R, s->k.x); | |
1049 | D( debug_ge("R", s->k.g, R); | |
1050 | debug_ge("Y", s->k.g, Y); | |
1051 | debug_ge("Z", s->k.g, Z); ) | |
de14f3b0 | 1052 | derive(&s->k, R, 0, Z, "cipher", s->k.cc->name, s->k.cc->keysz, &kk, &ksz); |
4efa5091 | 1053 | c = GC_INIT(s->k.cc, kk, ksz); |
de14f3b0 | 1054 | derive(&s->k, R, 0, Z, "mac", s->k.mc->name, s->k.mc->keysz, &kk, &ksz); |
4efa5091 MW |
1055 | m = GM_KEY(s->k.mc, kk, ksz); |
1056 | ||
1057 | /* Find where the MAC tag is. */ | |
1058 | if ((t = buf_get(bin, s->k.tagsz)) == 0) { | |
1059 | moan("missing tag from %s:%d", | |
1060 | inet_ntoa(s->sin.sin_addr), ntohs(s->sin.sin_port)); | |
1061 | goto done; | |
1062 | } | |
1063 | ||
1064 | /* Check the integrity of the ciphertext against the tag. */ | |
1065 | p = BCUR(bin); n = BLEFT(bin); | |
1066 | h = GM_INIT(m); | |
1067 | GH_HASH(h, p, n); | |
1068 | tt = GH_DONE(h, 0); | |
1069 | if (!ct_memeq(t, tt, s->k.tagsz)) { | |
1070 | moan("incorrect tag from %s:%d", | |
1071 | inet_ntoa(s->sin.sin_addr), ntohs(s->sin.sin_port)); | |
1072 | goto done; | |
1073 | } | |
1074 | ||
1075 | /* Decrypt the result and declare this server done. */ | |
1076 | GC_DECRYPT(c, p, p, n); | |
1077 | rc = donequery(q, s, p, n); | |
1078 | ||
1079 | done: | |
1080 | /* Clear up and go home. */ | |
1081 | if (R) G_DESTROY(s->k.g, R); | |
1082 | if (V) G_DESTROY(s->k.g, V); | |
1083 | if (W) G_DESTROY(s->k.g, W); | |
1084 | if (Y) G_DESTROY(s->k.g, Y); | |
1085 | if (Z) G_DESTROY(s->k.g, Z); | |
1086 | if (c) GC_DESTROY(c); | |
1087 | if (m) GM_DESTROY(m); | |
1088 | if (h) GH_DESTROY(h); | |
1089 | return (rc); | |
1090 | } | |
1091 | ||
1092 | static const struct client_protocol prototab[] = { | |
1093 | { "v0", setup_v0, receive_v0, retransmit_v0 }, | |
1094 | { 0 } | |
1095 | }; | |
247f344a MW |
1096 | |
1097 | /* Initialize a query to a remote server. */ | |
1098 | static struct query *qinit_net(const char *tag, const char *spec) | |
1099 | { | |
1100 | struct query *q; | |
1101 | struct server *s, **stail; | |
1102 | dstr d = DSTR_INIT, dd = DSTR_INIT; | |
4efa5091 | 1103 | const struct client_protocol *proto; |
247f344a MW |
1104 | hex_ctx hc; |
1105 | char *p, *pp, ch; | |
1106 | ||
1107 | /* Allocate the query block. */ | |
1108 | q = CREATE(struct query); | |
4efa5091 | 1109 | q->tag = tag; |
247f344a MW |
1110 | stail = &q->s; |
1111 | ||
1112 | /* Put the spec somewhere we can hack at it. */ | |
1113 | dstr_puts(&d, spec); | |
1114 | p = d.buf; | |
1115 | ||
1116 | /* Parse the query spec. Entries have the form ADDRESS:PORT[=TAG][#HASH] | |
1117 | * and are separated by `;'. | |
1118 | */ | |
1119 | while (*p) { | |
1120 | ||
1121 | /* Allocate a new server node. */ | |
1122 | s = CREATE(struct server); | |
1123 | s->sin.sin_family = AF_INET; | |
1124 | ||
1125 | /* Extract the server address. */ | |
1126 | if ((pp = strchr(p, ':')) == 0) | |
1127 | die(1, "invalid syntax: missing `:PORT'"); | |
1128 | *pp++ = 0; | |
1129 | resolve(p, &s->sin.sin_addr); | |
1130 | ||
1131 | /* Extract the port number. */ | |
1132 | p = pp; | |
1133 | while (isdigit((unsigned char)*pp)) pp++; | |
1134 | ch = *pp; *pp++ = 0; | |
1135 | s->sin.sin_port = htons(getport(p)); | |
1136 | ||
4efa5091 MW |
1137 | /* See if there's a protocol name. */ |
1138 | if (ch != '?') | |
1139 | p = "v0"; | |
1140 | else { | |
1141 | p = pp; | |
1142 | pp += strcspn(pp, ";#="); | |
1143 | ch = *pp; *pp++ = 0; | |
1144 | } | |
1145 | for (proto = prototab; proto->name; proto++) | |
1146 | if (strcmp(proto->name, p) == 0) goto found_proto; | |
1147 | die(1, "unknown protocol name `%s'", p); | |
1148 | found_proto: | |
1149 | s->proto = proto; | |
1150 | ||
247f344a MW |
1151 | /* If there's a key tag then extract that; otherwise use a default. */ |
1152 | if (ch != '=') | |
1153 | p = "udpkey-kem"; | |
1154 | else { | |
1155 | p = pp; | |
1156 | pp += strcspn(pp, ";#"); | |
1157 | ch = *pp; *pp++ = 0; | |
1158 | } | |
1159 | if (loadkey(p, &s->k, 1)) exit(1); | |
1160 | D( debug_mp("x", s->k.x); debug_ge("X", s->k.g, s->k.X); ) | |
1161 | ||
247f344a MW |
1162 | /* Link the server on. */ |
1163 | *stail = s; stail = &s->next; | |
1164 | ||
1165 | /* If there's a trailing hash then extract it. */ | |
1166 | if (ch != '#') | |
1167 | s->h = 0; | |
1168 | else { | |
1169 | p = pp; | |
1170 | while (*pp == '-' || isxdigit((unsigned char)*pp)) pp++; | |
1171 | hex_init(&hc); | |
1172 | DRESET(&dd); | |
1173 | hex_decode(&hc, p, pp - p, &dd); | |
1174 | if (dd.len != s->k.hc->hashsz) die(1, "incorrect hash length"); | |
1175 | s->h = xmalloc(dd.len); | |
1176 | memcpy(s->h, dd.buf, dd.len); | |
1177 | ch = *pp++; | |
1178 | } | |
1179 | ||
4efa5091 MW |
1180 | /* Initialize the protocol. */ |
1181 | if (s->proto->setup(q, s)) die(1, "failed to initialize protocol"); | |
1182 | ||
247f344a MW |
1183 | /* If there are more servers, then continue parsing. */ |
1184 | if (!ch) break; | |
1185 | else if (ch != ';') die(1, "invalid syntax: expected `;'"); | |
1186 | p = pp; | |
1187 | } | |
1188 | ||
1189 | /* Terminate the server list and return. */ | |
1190 | *stail = 0; | |
1191 | q->k = 0; | |
1192 | dstr_destroy(&d); | |
1193 | dstr_destroy(&dd); | |
1194 | return (q); | |
1195 | } | |
1196 | ||
1197 | /* Handle a `query' to a local file. */ | |
1198 | static struct query *qinit_file(const char *tag, const char *file) | |
1199 | { | |
1200 | struct query *q; | |
1201 | void *k; | |
1202 | size_t sz; | |
1203 | ||
1204 | /* Snarf the file. */ | |
1205 | q = CREATE(struct query); | |
1206 | if (snarf(file, &k, &sz)) | |
1207 | die(1, "failed to read `%s': %s", file, strerror(errno)); | |
1208 | q->s = 0; | |
4efa5091 | 1209 | donequery(q, 0, k, sz); |
247f344a MW |
1210 | return (q); |
1211 | } | |
1212 | ||
c33ded25 | 1213 | /* Retransmission and timeout parameters. */ |
247f344a MW |
1214 | #define TO_NEXT(t) (((t) + 2)*4/3) /* Timeout growth function */ |
1215 | #define TO_MAX 30 /* When to give up */ | |
1216 | ||
1217 | static int doquery(int argc, char *argv[]) | |
1218 | { | |
1219 | struct query *q = 0, *qq, **qtail = &qq; | |
1220 | struct server *s = 0; | |
1221 | const char *tag = argv[0]; | |
1222 | octet *p; | |
1223 | int i; | |
1224 | int sk; | |
1225 | fd_set fdin; | |
1226 | struct timeval now, when, tv; | |
1227 | struct sockaddr_in sin; | |
247f344a MW |
1228 | socklen_t len; |
1229 | unsigned next = 0; | |
1230 | buf bin, bout; | |
4efa5091 | 1231 | size_t n, j; |
247f344a MW |
1232 | ssize_t nn; |
1233 | ||
1234 | /* Create a socket. We just use the one socket for everything. We don't | |
1235 | * care which port we get allocated. | |
1236 | */ | |
1237 | if ((sk = socket(PF_INET, SOCK_DGRAM, 0)) < 0 || | |
1238 | fdflags(sk, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC)) | |
1239 | die(1, "failed to create socket: %s", strerror(errno)); | |
1240 | ||
1241 | /* Parse the query target specifications. The adjustments of `nq' aren't | |
1242 | * in the right order but that doesn't matter. | |
1243 | */ | |
1244 | for (i = 1; i < argc; i++) { | |
1245 | if (*argv[i] == '.' || *argv[i] == '/') q = qinit_file(tag, argv[i]); | |
1246 | else if (strchr(argv[i], ':')) q = qinit_net(tag, argv[i]); | |
1247 | else die(1, "unrecognized query target `%s'", argv[i]); | |
1248 | *qtail = q; qtail = &q->next; nq++; | |
1249 | } | |
1250 | *qtail = 0; | |
1251 | ||
1252 | /* Find the current time so we can compute retransmission times properly. | |
1253 | */ | |
1254 | gettimeofday(&now, 0); | |
1255 | when = now; | |
1256 | ||
1257 | /* Continue retransmitting until we have all the answers. */ | |
1258 | while (nq) { | |
1259 | ||
1260 | /* Work out when we next want to wake up. */ | |
1261 | if (TV_CMP(&now, >=, &when)) { | |
1262 | do { | |
1263 | if (next >= TO_MAX) die(1, "no responses: giving up"); | |
1264 | next = TO_NEXT(next); | |
1265 | TV_ADDL(&when, &when, next, 0); | |
1266 | } while (TV_CMP(&when, <=, &now)); | |
1267 | for (q = qq; q; q = q->next) { | |
1268 | if (q->k) continue; | |
1269 | for (s = q->s; s; s = s->next) { | |
1270 | buf_init(&bout, obuf, sizeof(obuf)); | |
4efa5091 | 1271 | if (s->proto->retransmit(q, s, &bout)) continue; |
247f344a MW |
1272 | if (BBAD(&bout)) { |
1273 | moan("overflow while constructing request!"); | |
1274 | continue; | |
1275 | } | |
1276 | sendto(sk, BBASE(&bout), BLEN(&bout), 0, | |
1277 | (struct sockaddr *)&s->sin, sizeof(s->sin)); | |
1278 | } | |
1279 | } | |
1280 | } | |
1281 | ||
1282 | /* Wait until something interesting happens. */ | |
1283 | FD_ZERO(&fdin); | |
1284 | FD_SET(sk, &fdin); | |
1285 | TV_SUB(&tv, &when, &now); | |
1286 | if (select(sk + 1, &fdin, 0, 0, &tv) < 0) | |
1287 | die(1, "select failed: %s", strerror(errno)); | |
1288 | gettimeofday(&now, 0); | |
1289 | ||
1290 | /* If we have an input event, process incoming packets. */ | |
1291 | if (FD_ISSET(sk, &fdin)) { | |
1292 | for (;;) { | |
1293 | ||
1294 | /* Read a packet and capture its address. */ | |
1295 | len = sizeof(sin); | |
1296 | nn = recvfrom(sk, ibuf, sizeof(ibuf), 0, | |
1297 | (struct sockaddr *)&sin, &len); | |
1298 | if (nn < 0) { | |
1299 | if (errno == EAGAIN) break; | |
1300 | else if (errno == EINTR) continue; | |
1301 | else { | |
1302 | moan("error receiving reply: %s", strerror(errno)); | |
4efa5091 | 1303 | continue; |
247f344a MW |
1304 | } |
1305 | } | |
1306 | ||
1f27bade | 1307 | /* See whether this corresponds to any of our servers. Don't just |
c33ded25 | 1308 | * check the active servers, since this may be late a reply caused by |
247f344a MW |
1309 | * retransmissions or similar. |
1310 | */ | |
1311 | for (q = qq; q; q = q->next) { | |
1312 | for (s = q->s; s; s = s->next) { | |
1313 | if (s->sin.sin_addr.s_addr == sin.sin_addr.s_addr && | |
1314 | s->sin.sin_port == sin.sin_port) | |
1315 | goto found; | |
1316 | } | |
1317 | } | |
1318 | moan("received reply from unexpected source %s:%d", | |
1319 | inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); | |
4efa5091 | 1320 | continue; |
247f344a MW |
1321 | |
1322 | found: | |
1323 | /* If the query we found has now been satisfied, ignore this packet. | |
1324 | */ | |
4efa5091 | 1325 | if (q->k) continue; |
247f344a | 1326 | |
4efa5091 MW |
1327 | /* Parse the reply, and either finish the job or get a message to |
1328 | * send back to the server. | |
247f344a | 1329 | */ |
4efa5091 MW |
1330 | buf_init(&bin, ibuf, nn); |
1331 | buf_init(&bout, obuf, sizeof(obuf)); | |
1332 | if (s->proto->receive(q, s, &bin, &bout)) continue; | |
1333 | if (q->k) continue; | |
1334 | if (!BLEN(&bout) && s->proto->retransmit(q, s, &bout)) continue; | |
1335 | if (BBAD(&bout)) { | |
1336 | moan("overflow while constructing request!"); | |
1337 | continue; | |
247f344a | 1338 | } |
4efa5091 MW |
1339 | sendto(sk, BBASE(&bout), BLEN(&bout), 0, |
1340 | (struct sockaddr *)&s->sin, sizeof(s->sin)); | |
247f344a MW |
1341 | } |
1342 | } | |
1343 | } | |
1344 | ||
1345 | /* Check that all of the responses match up and XOR them together. */ | |
1346 | n = qq->sz; | |
1347 | if (n > BUFSZ) die(1, "response too large"); | |
1348 | memset(obuf, 0, n); | |
1349 | for (q = qq; q; q = q->next) { | |
1350 | if (!q->k) die(1, "INTERNAL: query not complete"); | |
1351 | if (q->sz != n) die(1, "inconsistent response sizes"); | |
1352 | for (j = 0; j < n; j++) obuf[j] ^= q->k[j]; | |
1353 | } | |
1354 | ||
1355 | /* Write out the completed answer. */ | |
1356 | p = obuf; | |
1357 | while (n) { | |
1358 | if ((nn = write(STDOUT_FILENO, p, n)) < 0) | |
1359 | die(1, "error writing response: %s", strerror(errno)); | |
1360 | p += nn; n -= nn; | |
1361 | } | |
1362 | return (0); | |
1363 | } | |
1364 | ||
1365 | /*----- Main program ------------------------------------------------------*/ | |
1366 | ||
1367 | static void usage(FILE *fp) | |
1368 | { | |
1369 | pquis(fp, "Usage: \n\ | |
461b1e7f | 1370 | $ [-OPTS] LABEL {ADDR:PORT[=TAG][#HASH];... | FILE} ...\n\ |
247f344a MW |
1371 | $ [-OPTS] -l [ADDR:]PORT\n\ |
1372 | "); | |
1373 | } | |
1374 | ||
1375 | static void version(FILE *fp) | |
a2fd0b74 | 1376 | { pquis(fp, "$, version " VERSION "\n"); } |
247f344a MW |
1377 | |
1378 | static void help(FILE *fp) | |
1379 | { | |
1380 | version(fp); | |
1381 | putc('\n', fp); | |
1382 | usage(fp); | |
1383 | fputs("\n\ | |
1384 | Options:\n\ | |
1385 | \n\ | |
1386 | -d, --daemon Run in the background while listening.\n\ | |
1387 | -k, --keyring=FILE Read keys from FILE. [default = `keyring']\n\ | |
1388 | -l, --listen Listen for incoming requests and serve keys.\n\ | |
1389 | -p, --pidfile=FILE Write process id to FILE if in daemon mode.\n\ | |
1390 | -r, --random=FILE Key random number generator with contents of FILE.\n\ | |
1391 | ", fp); | |
1392 | } | |
1393 | ||
1394 | int main(int argc, char *argv[]) | |
1395 | { | |
1396 | int argmin, argmax; | |
1397 | void *k; | |
1398 | size_t sz; | |
1399 | ||
1400 | ego(argv[0]); | |
1401 | for (;;) { | |
1402 | static const struct option opts[] = { | |
1403 | { "help", 0, 0, 'h' }, | |
1404 | { "version", 0, 0, 'v' }, | |
1405 | { "usage", 0, 0, 'u' }, | |
1406 | { "daemon", 0, 0, 'd' }, | |
1407 | { "keyfile", OPTF_ARGREQ, 0, 'k' }, | |
1408 | { "listen", 0, 0, 'l' }, | |
1409 | { "pidfile", OPTF_ARGREQ, 0, 'p' }, | |
1410 | { "random", OPTF_ARGREQ, 0, 'r' }, | |
1411 | { 0 } | |
1412 | }; | |
1413 | ||
1414 | int i = mdwopt(argc, argv, "hvu" "dk:lp:r:", opts, 0, 0, 0); | |
1415 | if (i < 0) break; | |
1416 | ||
1417 | switch (i) { | |
1418 | case 'h': help(stdout); exit(0); | |
1419 | case 'v': version(stdout); exit(0); | |
1420 | case 'u': usage(stdout); exit(0); | |
1421 | ||
1422 | case 'd': flags |= f_daemon; break; | |
1423 | case 'k': kfname = optarg; break; | |
1424 | case 'l': flags |= f_listen; break; | |
1425 | case 'p': pidfile = optarg; break; | |
1426 | case 'r': | |
1427 | if (snarf(optarg, &k, &sz)) | |
1428 | die(1, "failed to read `%s': %s", optarg, strerror(errno)); | |
1429 | rand_key(RAND_GLOBAL, k, sz); | |
1430 | break; | |
1431 | ||
1432 | default: flags |= f_bogus; break; | |
1433 | } | |
1434 | } | |
1435 | ||
1436 | argv += optind; argc -= optind; | |
1437 | if (flags & f_listen) argmin = argmax = 1; | |
1438 | else argmin = 2, argmax = -1; | |
1439 | if ((flags & f_bogus) || argc < argmin || (argmax >= 0 && argc > argmax)) | |
1440 | { usage(stderr); exit(1); } | |
1441 | ||
1442 | fwatch_init(&kfwatch, kfname); | |
1443 | kf = CREATE(key_file); | |
1444 | if (key_open(kf, kfname, KOPEN_READ, keymoan, 0)) | |
1445 | die(1, "failed to open keyring file `%s'", kfname); | |
1446 | ||
1447 | rand_noisesrc(RAND_GLOBAL, &noise_source); | |
1448 | rand_seed(RAND_GLOBAL, 512); | |
1449 | ||
1450 | if (flags & f_listen) return dolisten(argc, argv); | |
1451 | else return doquery(argc, argv); | |
1452 | } | |
1453 | ||
1454 | /*----- That's all, folks -------------------------------------------------*/ |