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 | * | |
417 | * Basically all we do is compute H(what || U || Z). | |
418 | */ | |
419 | static int derive(struct kinfo *k, ge *U, ge *Z, | |
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); | |
439 | G_TORAW(k->g, &b, U); | |
440 | G_TORAW(k->g, &b, Z); | |
441 | if (BBAD(&b)) { | |
442 | complain(LOG_ERR, "overflow while deriving key (prepare preimage)!"); | |
443 | return (-1); | |
444 | } | |
445 | ||
446 | /* Derive the output key. */ | |
447 | h = GH_INIT(k->hc); | |
448 | GH_HASH(h, BBASE(&b), BLEN(&b)); | |
449 | buf_init(&b, obuf, sizeof(obuf)); | |
450 | if ((p = buf_get(&b, h->ops->c->hashsz)) == 0) { | |
451 | complain(LOG_ERR, "overflow while deriving key (output hash)!"); | |
452 | GH_DESTROY(h); | |
453 | return (-1); | |
454 | } | |
455 | GH_DONE(h, p); | |
456 | GH_DESTROY(h); | |
457 | *kk = p; | |
458 | return (0); | |
459 | } | |
460 | ||
461 | #ifdef DEBUG | |
462 | static void debug_mp(const char *what, mp *x) | |
463 | { fprintf(stderr, "%s: *** ", QUIS); MP_EPRINT(what, x); } | |
464 | static void debug_ge(const char *what, group *g, ge *X) | |
465 | { | |
466 | fprintf(stderr, "%s: *** %s = ", QUIS, what); | |
467 | group_writefile(g, X, stderr); | |
468 | fputc('\n', stderr); | |
469 | } | |
470 | #endif | |
471 | ||
7f5ea7d3 MW |
472 | /*----- Protocol summary --------------------------------------------------* |
473 | * | |
36fb0983 MW |
474 | * There are two protocol versions. The original version works as follows. |
475 | * | |
7f5ea7d3 MW |
476 | * * Request |
477 | * memz KEYTAG tag of wanted secret | |
478 | * ge U public vector | |
479 | * | |
480 | * * Response | |
481 | * ge V public vector: V = v P | |
482 | * ge W encrypted clue: W = R - Y = r P - v U | |
483 | * mem[TAGSZ] TAG MAC tag on ciphertext | |
484 | * mem[KSZ] CT secret, encrypted with Z = r X | |
36fb0983 MW |
485 | * |
486 | * The new version provides forward secrecy, which involves additional flows. | |
487 | * | |
488 | * * Greeting | |
489 | * u8 0 marker byte for new protocol | |
490 | * u8 1 packet type | |
491 | * mem8 KEYTAG wanted secret tag | |
492 | * | |
493 | * * Challenge | |
494 | * u8 17 packet type | |
495 | * u32 REF server's reference | |
496 | * ge R public DLIES vector: R = r P | |
497 | * ge W masked DH vector: W = V - Y = v P - r X | |
498 | * | |
499 | * * Response | |
500 | * u8 0 marker byte for new protocol | |
501 | * u8 2 packet type | |
502 | * mem8 KEYTAG wanted secret tag | |
503 | * u32 REF reference from challenge | |
504 | * ge U public DH vector | |
505 | * mem[HASHSZ] H0 hash; H0||H1 = H(U, V, Z), where Z = v U | |
506 | * | |
507 | * * Reply | |
508 | * u8 18 packet type | |
509 | * mem[TAGSZ] TAG MAC tag on ciphertext | |
510 | * mem[KSZ] CT secret, encrypted with H1 | |
7f5ea7d3 MW |
511 | */ |
512 | ||
36fb0983 MW |
513 | #define FWS_GREET 0x01 |
514 | #define FWS_CHALL 0x11 | |
515 | #define FWS_RESP 0x02 | |
516 | #define FWS_REPLY 0x12 | |
517 | ||
247f344a MW |
518 | /*----- Listening for requests --------------------------------------------*/ |
519 | ||
520 | /* Rate limiting parameters. | |
521 | * | |
522 | * There's a probabilistic rate-limiting mechanism. A counter starts at 0. | |
c33ded25 | 523 | * Every time we process a request, we increment the counter. The counter |
247f344a MW |
524 | * drops by RATE_REFILL every second. If the counter is below RATE_CREDIT |
525 | * then the request is processed; otherwise it is processed with probability | |
526 | * 1/(counter - RATE_CREDIT). | |
527 | */ | |
528 | #define RATE_REFILL 10 /* Credits per second. */ | |
529 | #define RATE_CREDIT 1000 /* Initial credit. */ | |
530 | ||
7f5ea7d3 MW |
531 | static time_t now; |
532 | ||
0057c20e MW |
533 | /* Secrets table. |
534 | * | |
535 | * The server doesn't want to maintain state for each client. Instead, we | |
536 | * generate a global secret, and derive per-client secrets from it. A secret | |
537 | * needs to have an expiry time (at which point we won't use it for new | |
538 | * requests) and a deletion time (at which point we just forget that it ever | |
539 | * existed). This lets us roll over to a new secret without leaving existing | |
540 | * clients completely in the lurch. | |
541 | * | |
542 | * Secrets are kept in a linked list, ordered by expiry time. At any given | |
543 | * time there is at most one unexpired secret (because we only make a new one | |
544 | * when the old one expires). | |
545 | */ | |
546 | ||
547 | struct secret { | |
548 | struct secret *next; | |
549 | uint32 seq; | |
550 | time_t t_exp, t_del; | |
551 | octet x[32]; | |
552 | }; | |
553 | static struct secret *secrets = 0, *live_secret = 0; | |
554 | static uint32 next_secret_seq = 0; | |
555 | #define T_SECEXP 30 | |
556 | #define T_SECDEL 45 | |
557 | ||
558 | static void kill_dead_secrets(void) | |
559 | { | |
560 | struct secret *s = secrets, *ss; | |
561 | ||
562 | for (s = secrets; s && s->t_del <= now; s = ss) { | |
563 | ss = s->next; | |
564 | DESTROY(s); | |
565 | } | |
566 | secrets = 0; | |
567 | if (!s) live_secret = 0; | |
568 | } | |
569 | ||
570 | static struct secret *find_secret(uint32 seq) | |
571 | { | |
572 | struct secret *s; | |
573 | ||
574 | kill_dead_secrets(); | |
575 | for (s = secrets; s; s = s->next) | |
576 | if (s->seq == seq) return (s); | |
577 | return (0); | |
578 | } | |
579 | ||
580 | static struct secret *fresh_secret(void) | |
581 | { | |
582 | struct secret *s; | |
583 | ||
584 | if (live_secret && live_secret->t_exp > now) return (live_secret); | |
585 | kill_dead_secrets(); | |
586 | ||
587 | s = CREATE(struct secret); | |
588 | s->seq = next_secret_seq++; | |
589 | s->next = 0; | |
590 | rand_get(RAND_GLOBAL, s->x, sizeof(s->x)); | |
591 | s->t_exp = now + T_SECEXP; s->t_del = now + T_SECDEL; | |
592 | if (live_secret) live_secret->next = s; | |
593 | else secrets = s; | |
594 | live_secret = s; | |
595 | return (s); | |
596 | } | |
597 | ||
7f5ea7d3 MW |
598 | static int fetch_key(const char *tag, struct sockaddr_in *sin, |
599 | key **ky, struct kinfo *k) | |
600 | { | |
601 | dstr d = DSTR_INIT, dd = DSTR_INIT; | |
602 | key_data **kkd; | |
603 | char *p, *q; | |
604 | const char *pp; | |
605 | struct in_addr in; | |
606 | int ch, mlen, rc = -1; | |
607 | ||
608 | /* Find the key. */ | |
609 | kfupdate(); | |
610 | if (key_qtag(kf, tag, &d, ky, &kkd)) { | |
611 | complain(LOG_WARNING, "unknown key tag `%s' from %s:%d", | |
612 | tag, inet_ntoa(sin->sin_addr), ntohs(sin->sin_port)); | |
613 | goto done; | |
614 | } | |
615 | ||
616 | /* And make sure that it has the right shape. */ | |
617 | if (((*ky)->k->e & KF_ENCMASK) != KENC_BINARY) { | |
618 | complain(LOG_ERR, "key %s is not plain binary data", d.buf); | |
619 | goto done; | |
620 | } | |
621 | ||
622 | /* Find the list of clients, and look up the caller's address in the | |
623 | * list. Entries have the form ADDRESS[/LEN][=TAG] and are separated by | |
624 | * `;'. | |
625 | */ | |
626 | if ((pp = key_getattr(kf, *ky, "clients")) == 0) { | |
627 | complain(LOG_WARNING, | |
628 | "key %s requested from %s:%d has no `clients' attribute", | |
629 | d.buf, inet_ntoa(sin->sin_addr), ntohs(sin->sin_port)); | |
630 | goto done; | |
631 | } | |
632 | dstr_puts(&dd, pp); | |
633 | p = dd.buf; | |
634 | while (*p) { | |
635 | q = p; | |
636 | while (isdigit((unsigned char)*q) || *q == '.') q++; | |
637 | ch = *q; *q++ = 0; | |
638 | if (!inet_aton(p, &in)) goto skip; | |
639 | if (ch != '/') | |
640 | mlen = 32; | |
641 | else { | |
642 | p = q; | |
643 | while (isdigit((unsigned char)*q)) q++; | |
644 | ch = *q; *q++ = 0; | |
645 | mlen = atoi(p); | |
646 | } | |
647 | if (((sin->sin_addr.s_addr ^ in.s_addr) & | |
648 | htonl(0xffffffff << (32 - mlen))) == 0) | |
649 | goto match; | |
650 | skip: | |
651 | if (!ch) break; | |
652 | p = q; | |
653 | while (*p && *p != ';') p++; | |
654 | if (*p) p++; | |
655 | } | |
656 | complain(LOG_WARNING, "access to key %s denied to %s:%d", | |
657 | d.buf, inet_ntoa(sin->sin_addr), ntohs(sin->sin_port)); | |
658 | goto done; | |
659 | ||
660 | match: | |
661 | /* Build a tag name for the caller's KEM key, either from the client | |
662 | * match or the source address. | |
663 | */ | |
664 | if (ch != '=') { | |
665 | DRESET(&dd); | |
666 | dstr_puts(&dd, "client-"); | |
667 | dstr_puts(&dd, inet_ntoa(sin->sin_addr)); | |
668 | p = dd.buf; | |
669 | } else { | |
670 | p = q; | |
671 | while (*q && *q != ';') q++; | |
672 | if (*q == ';') *q++ = 0; | |
673 | } | |
674 | ||
675 | /* Report the match. */ | |
676 | complain(LOG_NOTICE, "client %s:%d (`%s') requests key %s", | |
677 | inet_ntoa(sin->sin_addr), ntohs(sin->sin_port), p, d.buf); | |
678 | ||
679 | /* Load the KEM key. */ | |
680 | if (loadkey(p, k, 0)) goto done; | |
681 | D( debug_ge("X", k.g, k.X); ) | |
682 | ||
683 | /* All complete. */ | |
684 | rc = 0; | |
685 | ||
686 | done: | |
687 | /* Clean everything up. */ | |
688 | dstr_destroy(&d); | |
689 | dstr_destroy(&dd); | |
690 | if (rc) k_free(k); | |
691 | return (rc); | |
692 | } | |
693 | ||
694 | static int respond_v0(buf *bin, buf *bout, struct sockaddr_in *sin) | |
695 | { | |
696 | ge *R = 0, *U = 0, *V = 0, *W = 0, *Y = 0, *Z = 0; | |
697 | mp *r = MP_NEW, *v = MP_NEW; | |
698 | octet *kk, *t, *tt; | |
699 | char *p; | |
700 | size_t sz; | |
701 | ghash *h = 0; | |
702 | gmac *m = 0; | |
703 | gcipher *c = 0; | |
704 | struct kinfo k; | |
705 | key *ky; | |
706 | size_t ksz; | |
707 | int rc = -1; | |
708 | ||
709 | /* Clear out the key state. */ | |
710 | k_init(&k); | |
711 | ||
712 | /* Extract the key tag name. */ | |
713 | if ((p = buf_getmemz(bin, &sz)) == 0) { | |
714 | complain(LOG_WARNING, "invalid key tag from %s:%d", | |
715 | inet_ntoa(sin->sin_addr), ntohs(sin->sin_port)); | |
716 | goto done; | |
717 | } | |
718 | ||
719 | /* Find the client's key and check that it's allowed. */ | |
720 | if (fetch_key(p, sin, &ky, &k)) goto done; | |
721 | ||
722 | /* Read the caller's ephemeral key. */ | |
723 | R = G_CREATE(k.g); W = G_CREATE(k.g); | |
724 | U = G_CREATE(k.g); V = G_CREATE(k.g); | |
725 | Y = G_CREATE(k.g); Z = G_CREATE(k.g); | |
726 | if (G_FROMBUF(k.g, bin, U)) { | |
727 | complain(LOG_WARNING, "failed to read ephemeral vector from %s:%d", | |
728 | inet_ntoa(sin->sin_addr), ntohs(sin->sin_port)); | |
729 | goto done; | |
730 | } | |
731 | D( debug_ge("U", k.g, U); ) | |
732 | if (BLEFT(bin)) { | |
733 | complain(LOG_WARNING, "trailing junk in request from %s:%d", | |
734 | inet_ntoa(sin->sin_addr), ntohs(sin->sin_port)); | |
735 | goto done; | |
736 | } | |
737 | ||
738 | /* Ephemeral Diffie--Hellman. Choose v in GF(q) at random; compute | |
739 | * V = v P and -Y = (-v) U. | |
740 | */ | |
741 | v = mprand_range(v, k.g->r, &rand_global, 0); | |
742 | G_EXP(k.g, V, k.g->g, v); | |
743 | D( debug_mp("v", v); debug_ge("V", k.g, V); ) | |
26b2a043 | 744 | v = mp_sub(v, k.g->r, v); |
7f5ea7d3 MW |
745 | G_EXP(k.g, Y, U, v); |
746 | D( debug_ge("-Y", k.g, Y); ) | |
747 | ||
748 | /* DLIES. Choose r in GF(q) at random; compute R = r P and Z = r X. Mask | |
749 | * the clue R as W = R - Y. (Doing the subtraction here makes life easier | |
750 | * at the other end, since we can determine -Y by negating v whereas the | |
751 | * recipient must subtract vectors which may be less efficient.) | |
752 | */ | |
753 | r = mprand_range(r, k.g->r, &rand_global, 0); | |
754 | G_EXP(k.g, R, k.g->g, r); | |
755 | D( debug_mp("r", r); debug_ge("R", k.g, R); ) | |
26b2a043 | 756 | G_EXP(k.g, Z, k.X, r); |
7f5ea7d3 MW |
757 | G_MUL(k.g, W, R, Y); |
758 | D( debug_ge("Z", k.g, Z); debug_ge("W", k.g, W); ) | |
759 | ||
760 | /* Derive encryption and integrity keys. */ | |
761 | derive(&k, R, Z, "cipher", k.cc->name, k.cc->keysz, &kk, &ksz); | |
762 | c = GC_INIT(k.cc, kk, ksz); | |
763 | derive(&k, R, Z, "mac", k.mc->name, k.mc->keysz, &kk, &ksz); | |
764 | m = GM_KEY(k.mc, kk, ksz); | |
765 | ||
766 | /* Build the ciphertext and compute a MAC tag over it. */ | |
767 | rc = 0; | |
768 | if (G_TOBUF(k.g, bout, V) || | |
769 | G_TOBUF(k.g, bout, W)) | |
770 | goto done; | |
771 | if ((t = buf_get(bout, k.tagsz)) == 0) goto done; | |
772 | sz = ky->k->u.k.sz; | |
773 | if (BENSURE(bout, sz)) goto done; | |
774 | GC_ENCRYPT(c, ky->k->u.k.k, BCUR(bout), sz); | |
775 | h = GM_INIT(m); | |
776 | GH_HASH(h, BCUR(bout), sz); | |
777 | tt = GH_DONE(h, 0); memcpy(t, tt, k.tagsz); | |
778 | BSTEP(bout, sz); | |
779 | ||
780 | done: | |
781 | /* Clear everything up and go home. */ | |
782 | if (R) G_DESTROY(k.g, R); | |
783 | if (U) G_DESTROY(k.g, U); | |
784 | if (V) G_DESTROY(k.g, V); | |
785 | if (W) G_DESTROY(k.g, W); | |
786 | if (Y) G_DESTROY(k.g, Y); | |
787 | if (Z) G_DESTROY(k.g, Z); | |
788 | if (c) GC_DESTROY(c); | |
789 | if (m) GM_DESTROY(m); | |
790 | if (h) GH_DESTROY(h); | |
791 | if (r) MP_DROP(r); | |
792 | if (v) MP_DROP(v); | |
793 | k_free(&k); | |
794 | return (rc); | |
795 | } | |
796 | ||
247f344a MW |
797 | static int dolisten(int argc, char *argv[]) |
798 | { | |
799 | int sk; | |
7f5ea7d3 | 800 | char *p; |
247f344a MW |
801 | char *aspec; |
802 | ssize_t n; | |
247f344a MW |
803 | fd_set fdin; |
804 | struct sockaddr_in sin; | |
247f344a MW |
805 | socklen_t len; |
806 | buf bin, bout; | |
247f344a | 807 | FILE *fp = 0; |
247f344a | 808 | unsigned bucket = 0, toks; |
7f5ea7d3 | 809 | time_t last = 0; |
247f344a MW |
810 | |
811 | /* Set up the socket address. */ | |
812 | sin.sin_family = AF_INET; | |
813 | aspec = xstrdup(argv[0]); | |
814 | if ((p = strchr(aspec, ':')) == 0) { | |
815 | p = aspec; | |
816 | sin.sin_addr.s_addr = INADDR_ANY; | |
817 | } else { | |
818 | *p++ = 0; | |
819 | resolve(aspec, &sin.sin_addr); | |
820 | } | |
821 | sin.sin_port = htons(getport(p)); | |
822 | ||
823 | /* Create and set up the socket itself. */ | |
824 | if ((sk = socket(PF_INET, SOCK_DGRAM, 0)) < 0 || | |
825 | fdflags(sk, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC) || | |
826 | bind(sk, (struct sockaddr *)&sin, sizeof(sin))) | |
827 | die(1, "failed to create socket: %s", strerror(errno)); | |
828 | ||
829 | /* That's enough initialization. If we should fork, then do that. */ | |
830 | if (flags & f_daemon) { | |
831 | if (pidfile && (fp = fopen(pidfile, "w")) == 0) | |
832 | die(1, "failed to open pidfile `%s': %s", pidfile, strerror(errno)); | |
833 | openlog(QUIS, LOG_PID, LOG_DAEMON); | |
834 | if (daemonize()) | |
835 | die(1, "failed to become background process: %s", strerror(errno)); | |
836 | if (pidfile) { fprintf(fp, "%ld\n", (long)getpid()); fclose(fp); } | |
837 | flags |= f_syslog; | |
838 | } | |
839 | ||
840 | for (;;) { | |
841 | ||
247f344a MW |
842 | /* Wait for something to happen. */ |
843 | FD_ZERO(&fdin); | |
844 | FD_SET(sk, &fdin); | |
845 | if (select(sk + 1, &fdin, 0, 0, 0) < 0) | |
846 | die(1, "select failed: %s", strerror(errno)); | |
847 | noise_timer(RAND_GLOBAL); | |
848 | ||
849 | /* Fetch a packet. */ | |
850 | len = sizeof(sin); | |
851 | n = recvfrom(sk, ibuf, sizeof(ibuf), 0, (struct sockaddr *)&sin, &len); | |
852 | if (n < 0) { | |
853 | if (errno != EAGAIN && errno != EINTR) | |
854 | complain(LOG_ERR, "unexpected receive error: %s", strerror(errno)); | |
7f5ea7d3 | 855 | continue; |
247f344a MW |
856 | } |
857 | ||
858 | /* Refill the bucket, and see whether we should reject this packet. */ | |
859 | now = time(0); | |
860 | if (bucket && now != last) { | |
861 | toks = (now - last)*RATE_REFILL; | |
862 | bucket = bucket < toks ? 0 : bucket - toks; | |
863 | } | |
864 | last = now; | |
865 | if (bucket > RATE_CREDIT && | |
866 | grand_range(&rand_global, bucket - RATE_CREDIT)) | |
7f5ea7d3 | 867 | continue; |
247f344a MW |
868 | bucket++; |
869 | ||
870 | /* Set up the input buffer for parsing the request. */ | |
871 | buf_init(&bin, ibuf, n); | |
247f344a | 872 | buf_init(&bout, obuf, sizeof(obuf)); |
7f5ea7d3 MW |
873 | |
874 | /* Handle the client's message. */ | |
875 | if (respond_v0(&bin, &bout, &sin)) continue; | |
247f344a MW |
876 | |
877 | /* Send the reply packet back to the caller. */ | |
7f5ea7d3 | 878 | if (!BOK(&bout)) goto bad; |
247f344a MW |
879 | if (sendto(sk, BBASE(&bout), BLEN(&bout), 0, |
880 | (struct sockaddr *)&sin, len) < 0) { | |
881 | complain(LOG_ERR, "failed to send response to %s:%d: %s", | |
882 | inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), | |
883 | strerror(errno)); | |
7f5ea7d3 | 884 | continue; |
247f344a MW |
885 | } |
886 | ||
7f5ea7d3 | 887 | continue; |
247f344a MW |
888 | |
889 | bad: | |
890 | /* Report a problem building the reply. */ | |
891 | complain(LOG_ERR, "failed to construct response to %s:%d", | |
892 | inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); | |
247f344a MW |
893 | } |
894 | ||
895 | return (-1); | |
896 | } | |
897 | ||
898 | /*----- Sending requests and processing responses -------------------------*/ | |
899 | ||
900 | struct query { | |
901 | struct query *next; | |
4efa5091 | 902 | const char *tag; |
247f344a MW |
903 | octet *k; |
904 | size_t sz; | |
905 | struct server *s; | |
906 | }; | |
907 | ||
908 | struct server { | |
909 | struct server *next; | |
910 | struct sockaddr_in sin; | |
911 | struct kinfo k; | |
4efa5091 | 912 | const struct client_protocol *proto; |
247f344a MW |
913 | mp *u; |
914 | ge *U; | |
915 | octet *h; | |
916 | }; | |
917 | ||
4efa5091 MW |
918 | struct client_protocol { |
919 | const char *name; | |
920 | int (*setup)(struct query *, struct server *); | |
921 | int (*receive)(struct query *, struct server *, buf *, buf *); | |
922 | int (*retransmit)(struct query *, struct server *, buf *); | |
923 | }; | |
924 | ||
247f344a MW |
925 | /* Record a successful fetch of key material for a query Q. The data starts |
926 | * at K and is SZ bytes long. The data is copied: it's safe to overwrite it. | |
927 | */ | |
4efa5091 MW |
928 | static int donequery(struct query *q, struct server *s, |
929 | const void *k, size_t sz) | |
930 | { | |
931 | octet *tt; | |
932 | ghash *h = 0; | |
933 | int diffp; | |
934 | ||
935 | /* If we have a hash, check that the fragment matches it. */ | |
936 | if (s && s->h) { | |
937 | h = GH_INIT(s->k.hc); | |
938 | GH_HASH(h, k, sz); | |
939 | tt = GH_DONE(h, 0); | |
940 | diffp = memcmp(tt, s->h, h->ops->c->hashsz); | |
941 | GH_DESTROY(h); | |
942 | if (diffp) { | |
943 | moan("response from %s:%d doesn't match hash", | |
944 | inet_ntoa(s->sin.sin_addr), ntohs(s->sin.sin_port)); | |
945 | return (-1); | |
946 | } | |
947 | } | |
948 | ||
949 | /* Stash a copy of the key fragment for later. */ | |
950 | q->k = xmalloc(sz); | |
951 | memcpy(q->k, k, sz); | |
952 | q->sz = sz; nq--; | |
953 | ||
954 | /* All good. */ | |
955 | return (0); | |
956 | } | |
957 | ||
958 | static int setup_v0(struct query *q, struct server *s) | |
959 | { | |
960 | /* Choose an ephemeral private key u. Let x be our private key. We | |
961 | * compute U = u P and transmit this. | |
962 | */ | |
963 | s->u = mprand_range(MP_NEW, s->k.g->r, &rand_global, 0); | |
964 | s->U = G_CREATE(s->k.g); | |
965 | G_EXP(s->k.g, s->U, s->k.g->g, s->u); | |
966 | D( debug_mp("u", s->u); debug_ge("U", s->k.g, s->U); ) | |
967 | ||
968 | return (0); | |
969 | } | |
970 | ||
971 | static int retransmit_v0(struct query *q, struct server *s, buf *bout) | |
972 | { | |
973 | buf_putstrz(bout, q->tag); | |
974 | G_TOBUF(s->k.g, bout, s->U); | |
975 | return (0); | |
976 | } | |
977 | ||
978 | static int receive_v0(struct query *q, struct server *s, buf *bin, buf *bout) | |
979 | { | |
980 | ge *R, *V = 0, *W = 0, *Y = 0, *Z = 0; | |
981 | octet *kk, *t, *tt; | |
982 | gcipher *c = 0; | |
983 | gmac *m = 0; | |
984 | ghash *h = 0; | |
985 | size_t n, ksz; | |
986 | octet *p; | |
987 | int rc = -1; | |
988 | ||
989 | R = G_CREATE(s->k.g); | |
990 | V = G_CREATE(s->k.g); W = G_CREATE(s->k.g); | |
991 | Y = G_CREATE(s->k.g); Z = G_CREATE(s->k.g); | |
992 | if (G_FROMBUF(s->k.g, bin, V)) { | |
993 | moan("invalid Diffie--Hellman vector from %s:%d", | |
994 | inet_ntoa(s->sin.sin_addr), ntohs(s->sin.sin_port)); | |
995 | goto done; | |
996 | } | |
997 | if (G_FROMBUF(s->k.g, bin, W)) { | |
998 | moan("invalid clue vector from %s:%d", | |
999 | inet_ntoa(s->sin.sin_addr), ntohs(s->sin.sin_port)); | |
1000 | goto done; | |
1001 | } | |
1002 | D( debug_ge("V", s->k.g, V); debug_ge("W", s->k.g, W); ) | |
1003 | ||
1004 | /* We have V and W from the server; determine Y = u V, R = W + Y and | |
1005 | * Z = x R, and then derive the symmetric keys. | |
1006 | */ | |
1007 | G_EXP(s->k.g, Y, V, s->u); | |
1008 | G_MUL(s->k.g, R, W, Y); | |
1009 | G_EXP(s->k.g, Z, R, s->k.x); | |
1010 | D( debug_ge("R", s->k.g, R); | |
1011 | debug_ge("Y", s->k.g, Y); | |
1012 | debug_ge("Z", s->k.g, Z); ) | |
1013 | derive(&s->k, R, Z, "cipher", s->k.cc->name, s->k.cc->keysz, &kk, &ksz); | |
1014 | c = GC_INIT(s->k.cc, kk, ksz); | |
3fe48a41 | 1015 | derive(&s->k, R, Z, "mac", s->k.mc->name, s->k.mc->keysz, &kk, &ksz); |
4efa5091 MW |
1016 | m = GM_KEY(s->k.mc, kk, ksz); |
1017 | ||
1018 | /* Find where the MAC tag is. */ | |
1019 | if ((t = buf_get(bin, s->k.tagsz)) == 0) { | |
1020 | moan("missing tag from %s:%d", | |
1021 | inet_ntoa(s->sin.sin_addr), ntohs(s->sin.sin_port)); | |
1022 | goto done; | |
1023 | } | |
1024 | ||
1025 | /* Check the integrity of the ciphertext against the tag. */ | |
1026 | p = BCUR(bin); n = BLEFT(bin); | |
1027 | h = GM_INIT(m); | |
1028 | GH_HASH(h, p, n); | |
1029 | tt = GH_DONE(h, 0); | |
1030 | if (!ct_memeq(t, tt, s->k.tagsz)) { | |
1031 | moan("incorrect tag from %s:%d", | |
1032 | inet_ntoa(s->sin.sin_addr), ntohs(s->sin.sin_port)); | |
1033 | goto done; | |
1034 | } | |
1035 | ||
1036 | /* Decrypt the result and declare this server done. */ | |
1037 | GC_DECRYPT(c, p, p, n); | |
1038 | rc = donequery(q, s, p, n); | |
1039 | ||
1040 | done: | |
1041 | /* Clear up and go home. */ | |
1042 | if (R) G_DESTROY(s->k.g, R); | |
1043 | if (V) G_DESTROY(s->k.g, V); | |
1044 | if (W) G_DESTROY(s->k.g, W); | |
1045 | if (Y) G_DESTROY(s->k.g, Y); | |
1046 | if (Z) G_DESTROY(s->k.g, Z); | |
1047 | if (c) GC_DESTROY(c); | |
1048 | if (m) GM_DESTROY(m); | |
1049 | if (h) GH_DESTROY(h); | |
1050 | return (rc); | |
1051 | } | |
1052 | ||
1053 | static const struct client_protocol prototab[] = { | |
1054 | { "v0", setup_v0, receive_v0, retransmit_v0 }, | |
1055 | { 0 } | |
1056 | }; | |
247f344a MW |
1057 | |
1058 | /* Initialize a query to a remote server. */ | |
1059 | static struct query *qinit_net(const char *tag, const char *spec) | |
1060 | { | |
1061 | struct query *q; | |
1062 | struct server *s, **stail; | |
1063 | dstr d = DSTR_INIT, dd = DSTR_INIT; | |
4efa5091 | 1064 | const struct client_protocol *proto; |
247f344a MW |
1065 | hex_ctx hc; |
1066 | char *p, *pp, ch; | |
1067 | ||
1068 | /* Allocate the query block. */ | |
1069 | q = CREATE(struct query); | |
4efa5091 | 1070 | q->tag = tag; |
247f344a MW |
1071 | stail = &q->s; |
1072 | ||
1073 | /* Put the spec somewhere we can hack at it. */ | |
1074 | dstr_puts(&d, spec); | |
1075 | p = d.buf; | |
1076 | ||
1077 | /* Parse the query spec. Entries have the form ADDRESS:PORT[=TAG][#HASH] | |
1078 | * and are separated by `;'. | |
1079 | */ | |
1080 | while (*p) { | |
1081 | ||
1082 | /* Allocate a new server node. */ | |
1083 | s = CREATE(struct server); | |
1084 | s->sin.sin_family = AF_INET; | |
1085 | ||
1086 | /* Extract the server address. */ | |
1087 | if ((pp = strchr(p, ':')) == 0) | |
1088 | die(1, "invalid syntax: missing `:PORT'"); | |
1089 | *pp++ = 0; | |
1090 | resolve(p, &s->sin.sin_addr); | |
1091 | ||
1092 | /* Extract the port number. */ | |
1093 | p = pp; | |
1094 | while (isdigit((unsigned char)*pp)) pp++; | |
1095 | ch = *pp; *pp++ = 0; | |
1096 | s->sin.sin_port = htons(getport(p)); | |
1097 | ||
4efa5091 MW |
1098 | /* See if there's a protocol name. */ |
1099 | if (ch != '?') | |
1100 | p = "v0"; | |
1101 | else { | |
1102 | p = pp; | |
1103 | pp += strcspn(pp, ";#="); | |
1104 | ch = *pp; *pp++ = 0; | |
1105 | } | |
1106 | for (proto = prototab; proto->name; proto++) | |
1107 | if (strcmp(proto->name, p) == 0) goto found_proto; | |
1108 | die(1, "unknown protocol name `%s'", p); | |
1109 | found_proto: | |
1110 | s->proto = proto; | |
1111 | ||
247f344a MW |
1112 | /* If there's a key tag then extract that; otherwise use a default. */ |
1113 | if (ch != '=') | |
1114 | p = "udpkey-kem"; | |
1115 | else { | |
1116 | p = pp; | |
1117 | pp += strcspn(pp, ";#"); | |
1118 | ch = *pp; *pp++ = 0; | |
1119 | } | |
1120 | if (loadkey(p, &s->k, 1)) exit(1); | |
1121 | D( debug_mp("x", s->k.x); debug_ge("X", s->k.g, s->k.X); ) | |
1122 | ||
247f344a MW |
1123 | /* Link the server on. */ |
1124 | *stail = s; stail = &s->next; | |
1125 | ||
1126 | /* If there's a trailing hash then extract it. */ | |
1127 | if (ch != '#') | |
1128 | s->h = 0; | |
1129 | else { | |
1130 | p = pp; | |
1131 | while (*pp == '-' || isxdigit((unsigned char)*pp)) pp++; | |
1132 | hex_init(&hc); | |
1133 | DRESET(&dd); | |
1134 | hex_decode(&hc, p, pp - p, &dd); | |
1135 | if (dd.len != s->k.hc->hashsz) die(1, "incorrect hash length"); | |
1136 | s->h = xmalloc(dd.len); | |
1137 | memcpy(s->h, dd.buf, dd.len); | |
1138 | ch = *pp++; | |
1139 | } | |
1140 | ||
4efa5091 MW |
1141 | /* Initialize the protocol. */ |
1142 | if (s->proto->setup(q, s)) die(1, "failed to initialize protocol"); | |
1143 | ||
247f344a MW |
1144 | /* If there are more servers, then continue parsing. */ |
1145 | if (!ch) break; | |
1146 | else if (ch != ';') die(1, "invalid syntax: expected `;'"); | |
1147 | p = pp; | |
1148 | } | |
1149 | ||
1150 | /* Terminate the server list and return. */ | |
1151 | *stail = 0; | |
1152 | q->k = 0; | |
1153 | dstr_destroy(&d); | |
1154 | dstr_destroy(&dd); | |
1155 | return (q); | |
1156 | } | |
1157 | ||
1158 | /* Handle a `query' to a local file. */ | |
1159 | static struct query *qinit_file(const char *tag, const char *file) | |
1160 | { | |
1161 | struct query *q; | |
1162 | void *k; | |
1163 | size_t sz; | |
1164 | ||
1165 | /* Snarf the file. */ | |
1166 | q = CREATE(struct query); | |
1167 | if (snarf(file, &k, &sz)) | |
1168 | die(1, "failed to read `%s': %s", file, strerror(errno)); | |
1169 | q->s = 0; | |
4efa5091 | 1170 | donequery(q, 0, k, sz); |
247f344a MW |
1171 | return (q); |
1172 | } | |
1173 | ||
c33ded25 | 1174 | /* Retransmission and timeout parameters. */ |
247f344a MW |
1175 | #define TO_NEXT(t) (((t) + 2)*4/3) /* Timeout growth function */ |
1176 | #define TO_MAX 30 /* When to give up */ | |
1177 | ||
1178 | static int doquery(int argc, char *argv[]) | |
1179 | { | |
1180 | struct query *q = 0, *qq, **qtail = &qq; | |
1181 | struct server *s = 0; | |
1182 | const char *tag = argv[0]; | |
1183 | octet *p; | |
1184 | int i; | |
1185 | int sk; | |
1186 | fd_set fdin; | |
1187 | struct timeval now, when, tv; | |
1188 | struct sockaddr_in sin; | |
247f344a MW |
1189 | socklen_t len; |
1190 | unsigned next = 0; | |
1191 | buf bin, bout; | |
4efa5091 | 1192 | size_t n, j; |
247f344a MW |
1193 | ssize_t nn; |
1194 | ||
1195 | /* Create a socket. We just use the one socket for everything. We don't | |
1196 | * care which port we get allocated. | |
1197 | */ | |
1198 | if ((sk = socket(PF_INET, SOCK_DGRAM, 0)) < 0 || | |
1199 | fdflags(sk, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC)) | |
1200 | die(1, "failed to create socket: %s", strerror(errno)); | |
1201 | ||
1202 | /* Parse the query target specifications. The adjustments of `nq' aren't | |
1203 | * in the right order but that doesn't matter. | |
1204 | */ | |
1205 | for (i = 1; i < argc; i++) { | |
1206 | if (*argv[i] == '.' || *argv[i] == '/') q = qinit_file(tag, argv[i]); | |
1207 | else if (strchr(argv[i], ':')) q = qinit_net(tag, argv[i]); | |
1208 | else die(1, "unrecognized query target `%s'", argv[i]); | |
1209 | *qtail = q; qtail = &q->next; nq++; | |
1210 | } | |
1211 | *qtail = 0; | |
1212 | ||
1213 | /* Find the current time so we can compute retransmission times properly. | |
1214 | */ | |
1215 | gettimeofday(&now, 0); | |
1216 | when = now; | |
1217 | ||
1218 | /* Continue retransmitting until we have all the answers. */ | |
1219 | while (nq) { | |
1220 | ||
1221 | /* Work out when we next want to wake up. */ | |
1222 | if (TV_CMP(&now, >=, &when)) { | |
1223 | do { | |
1224 | if (next >= TO_MAX) die(1, "no responses: giving up"); | |
1225 | next = TO_NEXT(next); | |
1226 | TV_ADDL(&when, &when, next, 0); | |
1227 | } while (TV_CMP(&when, <=, &now)); | |
1228 | for (q = qq; q; q = q->next) { | |
1229 | if (q->k) continue; | |
1230 | for (s = q->s; s; s = s->next) { | |
1231 | buf_init(&bout, obuf, sizeof(obuf)); | |
4efa5091 | 1232 | if (s->proto->retransmit(q, s, &bout)) continue; |
247f344a MW |
1233 | if (BBAD(&bout)) { |
1234 | moan("overflow while constructing request!"); | |
1235 | continue; | |
1236 | } | |
1237 | sendto(sk, BBASE(&bout), BLEN(&bout), 0, | |
1238 | (struct sockaddr *)&s->sin, sizeof(s->sin)); | |
1239 | } | |
1240 | } | |
1241 | } | |
1242 | ||
1243 | /* Wait until something interesting happens. */ | |
1244 | FD_ZERO(&fdin); | |
1245 | FD_SET(sk, &fdin); | |
1246 | TV_SUB(&tv, &when, &now); | |
1247 | if (select(sk + 1, &fdin, 0, 0, &tv) < 0) | |
1248 | die(1, "select failed: %s", strerror(errno)); | |
1249 | gettimeofday(&now, 0); | |
1250 | ||
1251 | /* If we have an input event, process incoming packets. */ | |
1252 | if (FD_ISSET(sk, &fdin)) { | |
1253 | for (;;) { | |
1254 | ||
1255 | /* Read a packet and capture its address. */ | |
1256 | len = sizeof(sin); | |
1257 | nn = recvfrom(sk, ibuf, sizeof(ibuf), 0, | |
1258 | (struct sockaddr *)&sin, &len); | |
1259 | if (nn < 0) { | |
1260 | if (errno == EAGAIN) break; | |
1261 | else if (errno == EINTR) continue; | |
1262 | else { | |
1263 | moan("error receiving reply: %s", strerror(errno)); | |
4efa5091 | 1264 | continue; |
247f344a MW |
1265 | } |
1266 | } | |
1267 | ||
1f27bade | 1268 | /* See whether this corresponds to any of our servers. Don't just |
c33ded25 | 1269 | * check the active servers, since this may be late a reply caused by |
247f344a MW |
1270 | * retransmissions or similar. |
1271 | */ | |
1272 | for (q = qq; q; q = q->next) { | |
1273 | for (s = q->s; s; s = s->next) { | |
1274 | if (s->sin.sin_addr.s_addr == sin.sin_addr.s_addr && | |
1275 | s->sin.sin_port == sin.sin_port) | |
1276 | goto found; | |
1277 | } | |
1278 | } | |
1279 | moan("received reply from unexpected source %s:%d", | |
1280 | inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); | |
4efa5091 | 1281 | continue; |
247f344a MW |
1282 | |
1283 | found: | |
1284 | /* If the query we found has now been satisfied, ignore this packet. | |
1285 | */ | |
4efa5091 | 1286 | if (q->k) continue; |
247f344a | 1287 | |
4efa5091 MW |
1288 | /* Parse the reply, and either finish the job or get a message to |
1289 | * send back to the server. | |
247f344a | 1290 | */ |
4efa5091 MW |
1291 | buf_init(&bin, ibuf, nn); |
1292 | buf_init(&bout, obuf, sizeof(obuf)); | |
1293 | if (s->proto->receive(q, s, &bin, &bout)) continue; | |
1294 | if (q->k) continue; | |
1295 | if (!BLEN(&bout) && s->proto->retransmit(q, s, &bout)) continue; | |
1296 | if (BBAD(&bout)) { | |
1297 | moan("overflow while constructing request!"); | |
1298 | continue; | |
247f344a | 1299 | } |
4efa5091 MW |
1300 | sendto(sk, BBASE(&bout), BLEN(&bout), 0, |
1301 | (struct sockaddr *)&s->sin, sizeof(s->sin)); | |
247f344a MW |
1302 | } |
1303 | } | |
1304 | } | |
1305 | ||
1306 | /* Check that all of the responses match up and XOR them together. */ | |
1307 | n = qq->sz; | |
1308 | if (n > BUFSZ) die(1, "response too large"); | |
1309 | memset(obuf, 0, n); | |
1310 | for (q = qq; q; q = q->next) { | |
1311 | if (!q->k) die(1, "INTERNAL: query not complete"); | |
1312 | if (q->sz != n) die(1, "inconsistent response sizes"); | |
1313 | for (j = 0; j < n; j++) obuf[j] ^= q->k[j]; | |
1314 | } | |
1315 | ||
1316 | /* Write out the completed answer. */ | |
1317 | p = obuf; | |
1318 | while (n) { | |
1319 | if ((nn = write(STDOUT_FILENO, p, n)) < 0) | |
1320 | die(1, "error writing response: %s", strerror(errno)); | |
1321 | p += nn; n -= nn; | |
1322 | } | |
1323 | return (0); | |
1324 | } | |
1325 | ||
1326 | /*----- Main program ------------------------------------------------------*/ | |
1327 | ||
1328 | static void usage(FILE *fp) | |
1329 | { | |
1330 | pquis(fp, "Usage: \n\ | |
461b1e7f | 1331 | $ [-OPTS] LABEL {ADDR:PORT[=TAG][#HASH];... | FILE} ...\n\ |
247f344a MW |
1332 | $ [-OPTS] -l [ADDR:]PORT\n\ |
1333 | "); | |
1334 | } | |
1335 | ||
1336 | static void version(FILE *fp) | |
a2fd0b74 | 1337 | { pquis(fp, "$, version " VERSION "\n"); } |
247f344a MW |
1338 | |
1339 | static void help(FILE *fp) | |
1340 | { | |
1341 | version(fp); | |
1342 | putc('\n', fp); | |
1343 | usage(fp); | |
1344 | fputs("\n\ | |
1345 | Options:\n\ | |
1346 | \n\ | |
1347 | -d, --daemon Run in the background while listening.\n\ | |
1348 | -k, --keyring=FILE Read keys from FILE. [default = `keyring']\n\ | |
1349 | -l, --listen Listen for incoming requests and serve keys.\n\ | |
1350 | -p, --pidfile=FILE Write process id to FILE if in daemon mode.\n\ | |
1351 | -r, --random=FILE Key random number generator with contents of FILE.\n\ | |
1352 | ", fp); | |
1353 | } | |
1354 | ||
1355 | int main(int argc, char *argv[]) | |
1356 | { | |
1357 | int argmin, argmax; | |
1358 | void *k; | |
1359 | size_t sz; | |
1360 | ||
1361 | ego(argv[0]); | |
1362 | for (;;) { | |
1363 | static const struct option opts[] = { | |
1364 | { "help", 0, 0, 'h' }, | |
1365 | { "version", 0, 0, 'v' }, | |
1366 | { "usage", 0, 0, 'u' }, | |
1367 | { "daemon", 0, 0, 'd' }, | |
1368 | { "keyfile", OPTF_ARGREQ, 0, 'k' }, | |
1369 | { "listen", 0, 0, 'l' }, | |
1370 | { "pidfile", OPTF_ARGREQ, 0, 'p' }, | |
1371 | { "random", OPTF_ARGREQ, 0, 'r' }, | |
1372 | { 0 } | |
1373 | }; | |
1374 | ||
1375 | int i = mdwopt(argc, argv, "hvu" "dk:lp:r:", opts, 0, 0, 0); | |
1376 | if (i < 0) break; | |
1377 | ||
1378 | switch (i) { | |
1379 | case 'h': help(stdout); exit(0); | |
1380 | case 'v': version(stdout); exit(0); | |
1381 | case 'u': usage(stdout); exit(0); | |
1382 | ||
1383 | case 'd': flags |= f_daemon; break; | |
1384 | case 'k': kfname = optarg; break; | |
1385 | case 'l': flags |= f_listen; break; | |
1386 | case 'p': pidfile = optarg; break; | |
1387 | case 'r': | |
1388 | if (snarf(optarg, &k, &sz)) | |
1389 | die(1, "failed to read `%s': %s", optarg, strerror(errno)); | |
1390 | rand_key(RAND_GLOBAL, k, sz); | |
1391 | break; | |
1392 | ||
1393 | default: flags |= f_bogus; break; | |
1394 | } | |
1395 | } | |
1396 | ||
1397 | argv += optind; argc -= optind; | |
1398 | if (flags & f_listen) argmin = argmax = 1; | |
1399 | else argmin = 2, argmax = -1; | |
1400 | if ((flags & f_bogus) || argc < argmin || (argmax >= 0 && argc > argmax)) | |
1401 | { usage(stderr); exit(1); } | |
1402 | ||
1403 | fwatch_init(&kfwatch, kfname); | |
1404 | kf = CREATE(key_file); | |
1405 | if (key_open(kf, kfname, KOPEN_READ, keymoan, 0)) | |
1406 | die(1, "failed to open keyring file `%s'", kfname); | |
1407 | ||
1408 | rand_noisesrc(RAND_GLOBAL, &noise_source); | |
1409 | rand_seed(RAND_GLOBAL, 512); | |
1410 | ||
1411 | if (flags & f_listen) return dolisten(argc, argv); | |
1412 | else return doquery(argc, argv); | |
1413 | } | |
1414 | ||
1415 | /*----- That's all, folks -------------------------------------------------*/ |