1 | /* $NetBSD: krl.c,v 1.14 2019/04/20 17:16:40 christos Exp $ */ |
2 | |
3 | /* |
4 | * Copyright (c) 2012 Damien Miller <djm@mindrot.org> |
5 | * |
6 | * Permission to use, copy, modify, and distribute this software for any |
7 | * purpose with or without fee is hereby granted, provided that the above |
8 | * copyright notice and this permission notice appear in all copies. |
9 | * |
10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 | */ |
18 | |
19 | /* $OpenBSD: krl.c,v 1.42 2018/09/12 01:21:34 djm Exp $ */ |
20 | |
21 | #include "includes.h" |
22 | __RCSID("$NetBSD: krl.c,v 1.14 2019/04/20 17:16:40 christos Exp $" ); |
23 | #include <sys/param.h> /* MIN */ |
24 | #include <sys/types.h> |
25 | #include <sys/tree.h> |
26 | #include <sys/queue.h> |
27 | |
28 | #include <errno.h> |
29 | #include <fcntl.h> |
30 | #include <limits.h> |
31 | #include <string.h> |
32 | #include <time.h> |
33 | #include <unistd.h> |
34 | |
35 | #include "sshbuf.h" |
36 | #include "ssherr.h" |
37 | #include "sshkey.h" |
38 | #include "authfile.h" |
39 | #include "misc.h" |
40 | #include "log.h" |
41 | #include "digest.h" |
42 | #include "bitmap.h" |
43 | |
44 | #include "krl.h" |
45 | |
46 | /* #define DEBUG_KRL */ |
47 | #ifdef DEBUG_KRL |
48 | # define KRL_DBG(x) debug3 x |
49 | #else |
50 | # define KRL_DBG(x) |
51 | #endif |
52 | |
53 | /* |
54 | * Trees of revoked serial numbers, key IDs and keys. This allows |
55 | * quick searching, querying and producing lists in canonical order. |
56 | */ |
57 | |
58 | /* Tree of serial numbers. XXX make smarter: really need a real sparse bitmap */ |
59 | struct revoked_serial { |
60 | u_int64_t lo, hi; |
61 | RB_ENTRY(revoked_serial) tree_entry; |
62 | }; |
63 | static int serial_cmp(struct revoked_serial *a, struct revoked_serial *b); |
64 | RB_HEAD(revoked_serial_tree, revoked_serial); |
65 | RB_GENERATE_STATIC(revoked_serial_tree, revoked_serial, tree_entry, serial_cmp); |
66 | |
67 | /* Tree of key IDs */ |
68 | struct revoked_key_id { |
69 | char *key_id; |
70 | RB_ENTRY(revoked_key_id) tree_entry; |
71 | }; |
72 | static int key_id_cmp(struct revoked_key_id *a, struct revoked_key_id *b); |
73 | RB_HEAD(revoked_key_id_tree, revoked_key_id); |
74 | RB_GENERATE_STATIC(revoked_key_id_tree, revoked_key_id, tree_entry, key_id_cmp); |
75 | |
76 | /* Tree of blobs (used for keys and fingerprints) */ |
77 | struct revoked_blob { |
78 | u_char *blob; |
79 | size_t len; |
80 | RB_ENTRY(revoked_blob) tree_entry; |
81 | }; |
82 | static int blob_cmp(struct revoked_blob *a, struct revoked_blob *b); |
83 | RB_HEAD(revoked_blob_tree, revoked_blob); |
84 | RB_GENERATE_STATIC(revoked_blob_tree, revoked_blob, tree_entry, blob_cmp); |
85 | |
86 | /* Tracks revoked certs for a single CA */ |
87 | struct revoked_certs { |
88 | struct sshkey *ca_key; |
89 | struct revoked_serial_tree revoked_serials; |
90 | struct revoked_key_id_tree revoked_key_ids; |
91 | TAILQ_ENTRY(revoked_certs) entry; |
92 | }; |
93 | TAILQ_HEAD(revoked_certs_list, revoked_certs); |
94 | |
95 | struct ssh_krl { |
96 | u_int64_t krl_version; |
97 | u_int64_t generated_date; |
98 | u_int64_t flags; |
99 | char *; |
100 | struct revoked_blob_tree revoked_keys; |
101 | struct revoked_blob_tree revoked_sha1s; |
102 | struct revoked_blob_tree revoked_sha256s; |
103 | struct revoked_certs_list revoked_certs; |
104 | }; |
105 | |
106 | /* Return equal if a and b overlap */ |
107 | static int |
108 | serial_cmp(struct revoked_serial *a, struct revoked_serial *b) |
109 | { |
110 | if (a->hi >= b->lo && a->lo <= b->hi) |
111 | return 0; |
112 | return a->lo < b->lo ? -1 : 1; |
113 | } |
114 | |
115 | static int |
116 | key_id_cmp(struct revoked_key_id *a, struct revoked_key_id *b) |
117 | { |
118 | return strcmp(a->key_id, b->key_id); |
119 | } |
120 | |
121 | static int |
122 | blob_cmp(struct revoked_blob *a, struct revoked_blob *b) |
123 | { |
124 | int r; |
125 | |
126 | if (a->len != b->len) { |
127 | if ((r = memcmp(a->blob, b->blob, MINIMUM(a->len, b->len))) != 0) |
128 | return r; |
129 | return a->len > b->len ? 1 : -1; |
130 | } else |
131 | return memcmp(a->blob, b->blob, a->len); |
132 | } |
133 | |
134 | struct ssh_krl * |
135 | ssh_krl_init(void) |
136 | { |
137 | struct ssh_krl *krl; |
138 | |
139 | if ((krl = calloc(1, sizeof(*krl))) == NULL) |
140 | return NULL; |
141 | RB_INIT(&krl->revoked_keys); |
142 | RB_INIT(&krl->revoked_sha1s); |
143 | RB_INIT(&krl->revoked_sha256s); |
144 | TAILQ_INIT(&krl->revoked_certs); |
145 | return krl; |
146 | } |
147 | |
148 | static void |
149 | revoked_certs_free(struct revoked_certs *rc) |
150 | { |
151 | struct revoked_serial *rs, *trs; |
152 | struct revoked_key_id *rki, *trki; |
153 | |
154 | RB_FOREACH_SAFE(rs, revoked_serial_tree, &rc->revoked_serials, trs) { |
155 | RB_REMOVE(revoked_serial_tree, &rc->revoked_serials, rs); |
156 | free(rs); |
157 | } |
158 | RB_FOREACH_SAFE(rki, revoked_key_id_tree, &rc->revoked_key_ids, trki) { |
159 | RB_REMOVE(revoked_key_id_tree, &rc->revoked_key_ids, rki); |
160 | free(rki->key_id); |
161 | free(rki); |
162 | } |
163 | sshkey_free(rc->ca_key); |
164 | } |
165 | |
166 | void |
167 | ssh_krl_free(struct ssh_krl *krl) |
168 | { |
169 | struct revoked_blob *rb, *trb; |
170 | struct revoked_certs *rc, *trc; |
171 | |
172 | if (krl == NULL) |
173 | return; |
174 | |
175 | free(krl->comment); |
176 | RB_FOREACH_SAFE(rb, revoked_blob_tree, &krl->revoked_keys, trb) { |
177 | RB_REMOVE(revoked_blob_tree, &krl->revoked_keys, rb); |
178 | free(rb->blob); |
179 | free(rb); |
180 | } |
181 | RB_FOREACH_SAFE(rb, revoked_blob_tree, &krl->revoked_sha1s, trb) { |
182 | RB_REMOVE(revoked_blob_tree, &krl->revoked_sha1s, rb); |
183 | free(rb->blob); |
184 | free(rb); |
185 | } |
186 | RB_FOREACH_SAFE(rb, revoked_blob_tree, &krl->revoked_sha256s, trb) { |
187 | RB_REMOVE(revoked_blob_tree, &krl->revoked_sha256s, rb); |
188 | free(rb->blob); |
189 | free(rb); |
190 | } |
191 | TAILQ_FOREACH_SAFE(rc, &krl->revoked_certs, entry, trc) { |
192 | TAILQ_REMOVE(&krl->revoked_certs, rc, entry); |
193 | revoked_certs_free(rc); |
194 | } |
195 | } |
196 | |
197 | void |
198 | ssh_krl_set_version(struct ssh_krl *krl, u_int64_t version) |
199 | { |
200 | krl->krl_version = version; |
201 | } |
202 | |
203 | int |
204 | (struct ssh_krl *krl, const char *) |
205 | { |
206 | free(krl->comment); |
207 | if ((krl->comment = strdup(comment)) == NULL) |
208 | return SSH_ERR_ALLOC_FAIL; |
209 | return 0; |
210 | } |
211 | |
212 | /* |
213 | * Find the revoked_certs struct for a CA key. If allow_create is set then |
214 | * create a new one in the tree if one did not exist already. |
215 | */ |
216 | static int |
217 | revoked_certs_for_ca_key(struct ssh_krl *krl, const struct sshkey *ca_key, |
218 | struct revoked_certs **rcp, int allow_create) |
219 | { |
220 | struct revoked_certs *rc; |
221 | int r; |
222 | |
223 | *rcp = NULL; |
224 | TAILQ_FOREACH(rc, &krl->revoked_certs, entry) { |
225 | if ((ca_key == NULL && rc->ca_key == NULL) || |
226 | sshkey_equal(rc->ca_key, ca_key)) { |
227 | *rcp = rc; |
228 | return 0; |
229 | } |
230 | } |
231 | if (!allow_create) |
232 | return 0; |
233 | /* If this CA doesn't exist in the list then add it now */ |
234 | if ((rc = calloc(1, sizeof(*rc))) == NULL) |
235 | return SSH_ERR_ALLOC_FAIL; |
236 | if (ca_key == NULL) |
237 | rc->ca_key = NULL; |
238 | else if ((r = sshkey_from_private(ca_key, &rc->ca_key)) != 0) { |
239 | free(rc); |
240 | return r; |
241 | } |
242 | RB_INIT(&rc->revoked_serials); |
243 | RB_INIT(&rc->revoked_key_ids); |
244 | TAILQ_INSERT_TAIL(&krl->revoked_certs, rc, entry); |
245 | KRL_DBG(("%s: new CA %s" , __func__, |
246 | ca_key == NULL ? "*" : sshkey_type(ca_key))); |
247 | *rcp = rc; |
248 | return 0; |
249 | } |
250 | |
251 | static int |
252 | insert_serial_range(struct revoked_serial_tree *rt, u_int64_t lo, u_int64_t hi) |
253 | { |
254 | struct revoked_serial rs, *ers, *crs, *irs; |
255 | |
256 | KRL_DBG(("%s: insert %" PRIu64":%" PRIu64, __func__, lo, hi)); |
257 | memset(&rs, 0, sizeof(rs)); |
258 | rs.lo = lo; |
259 | rs.hi = hi; |
260 | ers = RB_NFIND(revoked_serial_tree, rt, &rs); |
261 | if (ers == NULL || serial_cmp(ers, &rs) != 0) { |
262 | /* No entry matches. Just insert */ |
263 | if ((irs = malloc(sizeof(rs))) == NULL) |
264 | return SSH_ERR_ALLOC_FAIL; |
265 | memcpy(irs, &rs, sizeof(*irs)); |
266 | ers = RB_INSERT(revoked_serial_tree, rt, irs); |
267 | if (ers != NULL) { |
268 | KRL_DBG(("%s: bad: ers != NULL" , __func__)); |
269 | /* Shouldn't happen */ |
270 | free(irs); |
271 | return SSH_ERR_INTERNAL_ERROR; |
272 | } |
273 | ers = irs; |
274 | } else { |
275 | KRL_DBG(("%s: overlap found %" PRIu64":%" PRIu64, __func__, |
276 | ers->lo, ers->hi)); |
277 | /* |
278 | * The inserted entry overlaps an existing one. Grow the |
279 | * existing entry. |
280 | */ |
281 | if (ers->lo > lo) |
282 | ers->lo = lo; |
283 | if (ers->hi < hi) |
284 | ers->hi = hi; |
285 | } |
286 | |
287 | /* |
288 | * The inserted or revised range might overlap or abut adjacent ones; |
289 | * coalesce as necessary. |
290 | */ |
291 | |
292 | /* Check predecessors */ |
293 | while ((crs = RB_PREV(revoked_serial_tree, rt, ers)) != NULL) { |
294 | KRL_DBG(("%s: pred %" PRIu64":%" PRIu64, __func__, |
295 | crs->lo, crs->hi)); |
296 | if (ers->lo != 0 && crs->hi < ers->lo - 1) |
297 | break; |
298 | /* This entry overlaps. */ |
299 | if (crs->lo < ers->lo) { |
300 | ers->lo = crs->lo; |
301 | KRL_DBG(("%s: pred extend %" PRIu64":%" PRIu64, __func__, |
302 | ers->lo, ers->hi)); |
303 | } |
304 | RB_REMOVE(revoked_serial_tree, rt, crs); |
305 | free(crs); |
306 | } |
307 | /* Check successors */ |
308 | while ((crs = RB_NEXT(revoked_serial_tree, rt, ers)) != NULL) { |
309 | KRL_DBG(("%s: succ %" PRIu64":%" PRIu64, __func__, crs->lo, |
310 | crs->hi)); |
311 | if (ers->hi != (u_int64_t)-1 && crs->lo > ers->hi + 1) |
312 | break; |
313 | /* This entry overlaps. */ |
314 | if (crs->hi > ers->hi) { |
315 | ers->hi = crs->hi; |
316 | KRL_DBG(("%s: succ extend %" PRIu64":%" PRIu64, __func__, |
317 | ers->lo, ers->hi)); |
318 | } |
319 | RB_REMOVE(revoked_serial_tree, rt, crs); |
320 | free(crs); |
321 | } |
322 | KRL_DBG(("%s: done, final %" PRIu64":%" PRIu64, __func__, ers->lo, |
323 | ers->hi)); |
324 | return 0; |
325 | } |
326 | |
327 | int |
328 | ssh_krl_revoke_cert_by_serial(struct ssh_krl *krl, const struct sshkey *ca_key, |
329 | u_int64_t serial) |
330 | { |
331 | return ssh_krl_revoke_cert_by_serial_range(krl, ca_key, serial, serial); |
332 | } |
333 | |
334 | int |
335 | ssh_krl_revoke_cert_by_serial_range(struct ssh_krl *krl, |
336 | const struct sshkey *ca_key, u_int64_t lo, u_int64_t hi) |
337 | { |
338 | struct revoked_certs *rc; |
339 | int r; |
340 | |
341 | if (lo > hi || lo == 0) |
342 | return SSH_ERR_INVALID_ARGUMENT; |
343 | if ((r = revoked_certs_for_ca_key(krl, ca_key, &rc, 1)) != 0) |
344 | return r; |
345 | return insert_serial_range(&rc->revoked_serials, lo, hi); |
346 | } |
347 | |
348 | int |
349 | ssh_krl_revoke_cert_by_key_id(struct ssh_krl *krl, const struct sshkey *ca_key, |
350 | const char *key_id) |
351 | { |
352 | struct revoked_key_id *rki, *erki; |
353 | struct revoked_certs *rc; |
354 | int r; |
355 | |
356 | if ((r = revoked_certs_for_ca_key(krl, ca_key, &rc, 1)) != 0) |
357 | return r; |
358 | |
359 | KRL_DBG(("%s: revoke %s" , __func__, key_id)); |
360 | if ((rki = calloc(1, sizeof(*rki))) == NULL || |
361 | (rki->key_id = strdup(key_id)) == NULL) { |
362 | free(rki); |
363 | return SSH_ERR_ALLOC_FAIL; |
364 | } |
365 | erki = RB_INSERT(revoked_key_id_tree, &rc->revoked_key_ids, rki); |
366 | if (erki != NULL) { |
367 | free(rki->key_id); |
368 | free(rki); |
369 | } |
370 | return 0; |
371 | } |
372 | |
373 | /* Convert "key" to a public key blob without any certificate information */ |
374 | static int |
375 | plain_key_blob(const struct sshkey *key, u_char **blob, size_t *blen) |
376 | { |
377 | struct sshkey *kcopy; |
378 | int r; |
379 | |
380 | if ((r = sshkey_from_private(key, &kcopy)) != 0) |
381 | return r; |
382 | if (sshkey_is_cert(kcopy)) { |
383 | if ((r = sshkey_drop_cert(kcopy)) != 0) { |
384 | sshkey_free(kcopy); |
385 | return r; |
386 | } |
387 | } |
388 | r = sshkey_to_blob(kcopy, blob, blen); |
389 | sshkey_free(kcopy); |
390 | return r; |
391 | } |
392 | |
393 | /* Revoke a key blob. Ownership of blob is transferred to the tree */ |
394 | static int |
395 | revoke_blob(struct revoked_blob_tree *rbt, u_char *blob, size_t len) |
396 | { |
397 | struct revoked_blob *rb, *erb; |
398 | |
399 | if ((rb = calloc(1, sizeof(*rb))) == NULL) |
400 | return SSH_ERR_ALLOC_FAIL; |
401 | rb->blob = blob; |
402 | rb->len = len; |
403 | erb = RB_INSERT(revoked_blob_tree, rbt, rb); |
404 | if (erb != NULL) { |
405 | free(rb->blob); |
406 | free(rb); |
407 | } |
408 | return 0; |
409 | } |
410 | |
411 | int |
412 | ssh_krl_revoke_key_explicit(struct ssh_krl *krl, const struct sshkey *key) |
413 | { |
414 | u_char *blob; |
415 | size_t len; |
416 | int r; |
417 | |
418 | debug3("%s: revoke type %s" , __func__, sshkey_type(key)); |
419 | if ((r = plain_key_blob(key, &blob, &len)) != 0) |
420 | return r; |
421 | return revoke_blob(&krl->revoked_keys, blob, len); |
422 | } |
423 | |
424 | static int |
425 | revoke_by_hash(struct revoked_blob_tree *target, const u_char *p, size_t len) |
426 | { |
427 | u_char *blob; |
428 | int r; |
429 | |
430 | /* need to copy hash, as revoke_blob steals ownership */ |
431 | if ((blob = malloc(len)) == NULL) |
432 | return SSH_ERR_SYSTEM_ERROR; |
433 | memcpy(blob, p, len); |
434 | if ((r = revoke_blob(target, blob, len)) != 0) { |
435 | free(blob); |
436 | return r; |
437 | } |
438 | return 0; |
439 | } |
440 | |
441 | int |
442 | ssh_krl_revoke_key_sha1(struct ssh_krl *krl, const u_char *p, size_t len) |
443 | { |
444 | debug3("%s: revoke by sha1" , __func__); |
445 | if (len != 20) |
446 | return SSH_ERR_INVALID_FORMAT; |
447 | return revoke_by_hash(&krl->revoked_sha1s, p, len); |
448 | } |
449 | |
450 | int |
451 | ssh_krl_revoke_key_sha256(struct ssh_krl *krl, const u_char *p, size_t len) |
452 | { |
453 | debug3("%s: revoke by sha256" , __func__); |
454 | if (len != 32) |
455 | return SSH_ERR_INVALID_FORMAT; |
456 | return revoke_by_hash(&krl->revoked_sha256s, p, len); |
457 | } |
458 | |
459 | int |
460 | ssh_krl_revoke_key(struct ssh_krl *krl, const struct sshkey *key) |
461 | { |
462 | /* XXX replace with SHA256? */ |
463 | if (!sshkey_is_cert(key)) |
464 | return ssh_krl_revoke_key_explicit(krl, key); |
465 | |
466 | if (key->cert->serial == 0) { |
467 | return ssh_krl_revoke_cert_by_key_id(krl, |
468 | key->cert->signature_key, |
469 | key->cert->key_id); |
470 | } else { |
471 | return ssh_krl_revoke_cert_by_serial(krl, |
472 | key->cert->signature_key, |
473 | key->cert->serial); |
474 | } |
475 | } |
476 | |
477 | /* |
478 | * Select the most compact section type to emit next in a KRL based on |
479 | * the current section type, the run length of contiguous revoked serial |
480 | * numbers and the gaps from the last and to the next revoked serial. |
481 | * Applies a mostly-accurate bit cost model to select the section type |
482 | * that will minimise the size of the resultant KRL. |
483 | */ |
484 | static int |
485 | choose_next_state(int current_state, u_int64_t contig, int final, |
486 | u_int64_t last_gap, u_int64_t next_gap, int *force_new_section) |
487 | { |
488 | int new_state; |
489 | u_int64_t cost, cost_list, cost_range, cost_bitmap, cost_bitmap_restart; |
490 | |
491 | /* |
492 | * Avoid unsigned overflows. |
493 | * The limits are high enough to avoid confusing the calculations. |
494 | */ |
495 | contig = MINIMUM(contig, 1ULL<<31); |
496 | last_gap = MINIMUM(last_gap, 1ULL<<31); |
497 | next_gap = MINIMUM(next_gap, 1ULL<<31); |
498 | |
499 | /* |
500 | * Calculate the cost to switch from the current state to candidates. |
501 | * NB. range sections only ever contain a single range, so their |
502 | * switching cost is independent of the current_state. |
503 | */ |
504 | cost_list = cost_bitmap = cost_bitmap_restart = 0; |
505 | cost_range = 8; |
506 | switch (current_state) { |
507 | case KRL_SECTION_CERT_SERIAL_LIST: |
508 | cost_bitmap_restart = cost_bitmap = 8 + 64; |
509 | break; |
510 | case KRL_SECTION_CERT_SERIAL_BITMAP: |
511 | cost_list = 8; |
512 | cost_bitmap_restart = 8 + 64; |
513 | break; |
514 | case KRL_SECTION_CERT_SERIAL_RANGE: |
515 | case 0: |
516 | cost_bitmap_restart = cost_bitmap = 8 + 64; |
517 | cost_list = 8; |
518 | } |
519 | |
520 | /* Estimate base cost in bits of each section type */ |
521 | cost_list += 64 * contig + (final ? 0 : 8+64); |
522 | cost_range += (2 * 64) + (final ? 0 : 8+64); |
523 | cost_bitmap += last_gap + contig + (final ? 0 : MINIMUM(next_gap, 8+64)); |
524 | cost_bitmap_restart += contig + (final ? 0 : MINIMUM(next_gap, 8+64)); |
525 | |
526 | /* Convert to byte costs for actual comparison */ |
527 | cost_list = (cost_list + 7) / 8; |
528 | cost_bitmap = (cost_bitmap + 7) / 8; |
529 | cost_bitmap_restart = (cost_bitmap_restart + 7) / 8; |
530 | cost_range = (cost_range + 7) / 8; |
531 | |
532 | /* Now pick the best choice */ |
533 | *force_new_section = 0; |
534 | new_state = KRL_SECTION_CERT_SERIAL_BITMAP; |
535 | cost = cost_bitmap; |
536 | if (cost_range < cost) { |
537 | new_state = KRL_SECTION_CERT_SERIAL_RANGE; |
538 | cost = cost_range; |
539 | } |
540 | if (cost_list < cost) { |
541 | new_state = KRL_SECTION_CERT_SERIAL_LIST; |
542 | cost = cost_list; |
543 | } |
544 | if (cost_bitmap_restart < cost) { |
545 | new_state = KRL_SECTION_CERT_SERIAL_BITMAP; |
546 | *force_new_section = 1; |
547 | cost = cost_bitmap_restart; |
548 | } |
549 | KRL_DBG(("%s: contig %llu last_gap %llu next_gap %llu final %d, costs:" |
550 | "list %llu range %llu bitmap %llu new bitmap %llu, " |
551 | "selected 0x%02x%s" , __func__, (long long unsigned)contig, |
552 | (long long unsigned)last_gap, (long long unsigned)next_gap, final, |
553 | (long long unsigned)cost_list, (long long unsigned)cost_range, |
554 | (long long unsigned)cost_bitmap, |
555 | (long long unsigned)cost_bitmap_restart, new_state, |
556 | *force_new_section ? " restart" : "" )); |
557 | return new_state; |
558 | } |
559 | |
560 | static int |
561 | put_bitmap(struct sshbuf *buf, struct bitmap *bitmap) |
562 | { |
563 | size_t len; |
564 | u_char *blob; |
565 | int r; |
566 | |
567 | len = bitmap_nbytes(bitmap); |
568 | if ((blob = malloc(len)) == NULL) |
569 | return SSH_ERR_ALLOC_FAIL; |
570 | if (bitmap_to_string(bitmap, blob, len) != 0) { |
571 | free(blob); |
572 | return SSH_ERR_INTERNAL_ERROR; |
573 | } |
574 | r = sshbuf_put_bignum2_bytes(buf, blob, len); |
575 | free(blob); |
576 | return r; |
577 | } |
578 | |
579 | /* Generate a KRL_SECTION_CERTIFICATES KRL section */ |
580 | static int |
581 | revoked_certs_generate(struct revoked_certs *rc, struct sshbuf *buf) |
582 | { |
583 | int final, force_new_sect, r = SSH_ERR_INTERNAL_ERROR; |
584 | u_int64_t i, contig, gap, last = 0, bitmap_start = 0; |
585 | struct revoked_serial *rs, *nrs; |
586 | struct revoked_key_id *rki; |
587 | int next_state, state = 0; |
588 | struct sshbuf *sect; |
589 | struct bitmap *bitmap = NULL; |
590 | |
591 | if ((sect = sshbuf_new()) == NULL) |
592 | return SSH_ERR_ALLOC_FAIL; |
593 | |
594 | /* Store the header: optional CA scope key, reserved */ |
595 | if (rc->ca_key == NULL) { |
596 | if ((r = sshbuf_put_string(buf, NULL, 0)) != 0) |
597 | goto out; |
598 | } else { |
599 | if ((r = sshkey_puts(rc->ca_key, buf)) != 0) |
600 | goto out; |
601 | } |
602 | if ((r = sshbuf_put_string(buf, NULL, 0)) != 0) |
603 | goto out; |
604 | |
605 | /* Store the revoked serials. */ |
606 | for (rs = RB_MIN(revoked_serial_tree, &rc->revoked_serials); |
607 | rs != NULL; |
608 | rs = RB_NEXT(revoked_serial_tree, &rc->revoked_serials, rs)) { |
609 | KRL_DBG(("%s: serial %llu:%llu state 0x%02x" , __func__, |
610 | (long long unsigned)rs->lo, (long long unsigned)rs->hi, |
611 | state)); |
612 | |
613 | /* Check contiguous length and gap to next section (if any) */ |
614 | nrs = RB_NEXT(revoked_serial_tree, &rc->revoked_serials, rs); |
615 | final = nrs == NULL; |
616 | gap = nrs == NULL ? 0 : nrs->lo - rs->hi; |
617 | contig = 1 + (rs->hi - rs->lo); |
618 | |
619 | /* Choose next state based on these */ |
620 | next_state = choose_next_state(state, contig, final, |
621 | state == 0 ? 0 : rs->lo - last, gap, &force_new_sect); |
622 | |
623 | /* |
624 | * If the current section is a range section or has a different |
625 | * type to the next section, then finish it off now. |
626 | */ |
627 | if (state != 0 && (force_new_sect || next_state != state || |
628 | state == KRL_SECTION_CERT_SERIAL_RANGE)) { |
629 | KRL_DBG(("%s: finish state 0x%02x" , __func__, state)); |
630 | switch (state) { |
631 | case KRL_SECTION_CERT_SERIAL_LIST: |
632 | case KRL_SECTION_CERT_SERIAL_RANGE: |
633 | break; |
634 | case KRL_SECTION_CERT_SERIAL_BITMAP: |
635 | if ((r = put_bitmap(sect, bitmap)) != 0) |
636 | goto out; |
637 | bitmap_free(bitmap); |
638 | bitmap = NULL; |
639 | break; |
640 | } |
641 | if ((r = sshbuf_put_u8(buf, state)) != 0 || |
642 | (r = sshbuf_put_stringb(buf, sect)) != 0) |
643 | goto out; |
644 | sshbuf_reset(sect); |
645 | } |
646 | |
647 | /* If we are starting a new section then prepare it now */ |
648 | if (next_state != state || force_new_sect) { |
649 | KRL_DBG(("%s: start state 0x%02x" , __func__, |
650 | next_state)); |
651 | state = next_state; |
652 | sshbuf_reset(sect); |
653 | switch (state) { |
654 | case KRL_SECTION_CERT_SERIAL_LIST: |
655 | case KRL_SECTION_CERT_SERIAL_RANGE: |
656 | break; |
657 | case KRL_SECTION_CERT_SERIAL_BITMAP: |
658 | if ((bitmap = bitmap_new()) == NULL) { |
659 | r = SSH_ERR_ALLOC_FAIL; |
660 | goto out; |
661 | } |
662 | bitmap_start = rs->lo; |
663 | if ((r = sshbuf_put_u64(sect, |
664 | bitmap_start)) != 0) |
665 | goto out; |
666 | break; |
667 | } |
668 | } |
669 | |
670 | /* Perform section-specific processing */ |
671 | switch (state) { |
672 | case KRL_SECTION_CERT_SERIAL_LIST: |
673 | for (i = 0; i < contig; i++) { |
674 | if ((r = sshbuf_put_u64(sect, rs->lo + i)) != 0) |
675 | goto out; |
676 | } |
677 | break; |
678 | case KRL_SECTION_CERT_SERIAL_RANGE: |
679 | if ((r = sshbuf_put_u64(sect, rs->lo)) != 0 || |
680 | (r = sshbuf_put_u64(sect, rs->hi)) != 0) |
681 | goto out; |
682 | break; |
683 | case KRL_SECTION_CERT_SERIAL_BITMAP: |
684 | if (rs->lo - bitmap_start > INT_MAX) { |
685 | error("%s: insane bitmap gap" , __func__); |
686 | goto out; |
687 | } |
688 | for (i = 0; i < contig; i++) { |
689 | if (bitmap_set_bit(bitmap, |
690 | rs->lo + i - bitmap_start) != 0) { |
691 | r = SSH_ERR_ALLOC_FAIL; |
692 | goto out; |
693 | } |
694 | } |
695 | break; |
696 | } |
697 | last = rs->hi; |
698 | } |
699 | /* Flush the remaining section, if any */ |
700 | if (state != 0) { |
701 | KRL_DBG(("%s: serial final flush for state 0x%02x" , |
702 | __func__, state)); |
703 | switch (state) { |
704 | case KRL_SECTION_CERT_SERIAL_LIST: |
705 | case KRL_SECTION_CERT_SERIAL_RANGE: |
706 | break; |
707 | case KRL_SECTION_CERT_SERIAL_BITMAP: |
708 | if ((r = put_bitmap(sect, bitmap)) != 0) |
709 | goto out; |
710 | bitmap_free(bitmap); |
711 | bitmap = NULL; |
712 | break; |
713 | } |
714 | if ((r = sshbuf_put_u8(buf, state)) != 0 || |
715 | (r = sshbuf_put_stringb(buf, sect)) != 0) |
716 | goto out; |
717 | } |
718 | KRL_DBG(("%s: serial done " , __func__)); |
719 | |
720 | /* Now output a section for any revocations by key ID */ |
721 | sshbuf_reset(sect); |
722 | RB_FOREACH(rki, revoked_key_id_tree, &rc->revoked_key_ids) { |
723 | KRL_DBG(("%s: key ID %s" , __func__, rki->key_id)); |
724 | if ((r = sshbuf_put_cstring(sect, rki->key_id)) != 0) |
725 | goto out; |
726 | } |
727 | if (sshbuf_len(sect) != 0) { |
728 | if ((r = sshbuf_put_u8(buf, KRL_SECTION_CERT_KEY_ID)) != 0 || |
729 | (r = sshbuf_put_stringb(buf, sect)) != 0) |
730 | goto out; |
731 | } |
732 | r = 0; |
733 | out: |
734 | bitmap_free(bitmap); |
735 | sshbuf_free(sect); |
736 | return r; |
737 | } |
738 | |
739 | int |
740 | ssh_krl_to_blob(struct ssh_krl *krl, struct sshbuf *buf, |
741 | const struct sshkey **sign_keys, u_int nsign_keys) |
742 | { |
743 | int r = SSH_ERR_INTERNAL_ERROR; |
744 | struct revoked_certs *rc; |
745 | struct revoked_blob *rb; |
746 | struct sshbuf *sect; |
747 | u_char *sblob = NULL; |
748 | size_t slen, i; |
749 | |
750 | if (krl->generated_date == 0) |
751 | krl->generated_date = time(NULL); |
752 | |
753 | if ((sect = sshbuf_new()) == NULL) |
754 | return SSH_ERR_ALLOC_FAIL; |
755 | |
756 | /* Store the header */ |
757 | if ((r = sshbuf_put(buf, KRL_MAGIC, sizeof(KRL_MAGIC) - 1)) != 0 || |
758 | (r = sshbuf_put_u32(buf, KRL_FORMAT_VERSION)) != 0 || |
759 | (r = sshbuf_put_u64(buf, krl->krl_version)) != 0 || |
760 | (r = sshbuf_put_u64(buf, krl->generated_date)) != 0 || |
761 | (r = sshbuf_put_u64(buf, krl->flags)) != 0 || |
762 | (r = sshbuf_put_string(buf, NULL, 0)) != 0 || |
763 | (r = sshbuf_put_cstring(buf, krl->comment)) != 0) |
764 | goto out; |
765 | |
766 | /* Store sections for revoked certificates */ |
767 | TAILQ_FOREACH(rc, &krl->revoked_certs, entry) { |
768 | sshbuf_reset(sect); |
769 | if ((r = revoked_certs_generate(rc, sect)) != 0) |
770 | goto out; |
771 | if ((r = sshbuf_put_u8(buf, KRL_SECTION_CERTIFICATES)) != 0 || |
772 | (r = sshbuf_put_stringb(buf, sect)) != 0) |
773 | goto out; |
774 | } |
775 | |
776 | /* Finally, output sections for revocations by public key/hash */ |
777 | sshbuf_reset(sect); |
778 | RB_FOREACH(rb, revoked_blob_tree, &krl->revoked_keys) { |
779 | KRL_DBG(("%s: key len %zu " , __func__, rb->len)); |
780 | if ((r = sshbuf_put_string(sect, rb->blob, rb->len)) != 0) |
781 | goto out; |
782 | } |
783 | if (sshbuf_len(sect) != 0) { |
784 | if ((r = sshbuf_put_u8(buf, KRL_SECTION_EXPLICIT_KEY)) != 0 || |
785 | (r = sshbuf_put_stringb(buf, sect)) != 0) |
786 | goto out; |
787 | } |
788 | sshbuf_reset(sect); |
789 | RB_FOREACH(rb, revoked_blob_tree, &krl->revoked_sha1s) { |
790 | KRL_DBG(("%s: hash len %zu " , __func__, rb->len)); |
791 | if ((r = sshbuf_put_string(sect, rb->blob, rb->len)) != 0) |
792 | goto out; |
793 | } |
794 | if (sshbuf_len(sect) != 0) { |
795 | if ((r = sshbuf_put_u8(buf, |
796 | KRL_SECTION_FINGERPRINT_SHA1)) != 0 || |
797 | (r = sshbuf_put_stringb(buf, sect)) != 0) |
798 | goto out; |
799 | } |
800 | sshbuf_reset(sect); |
801 | RB_FOREACH(rb, revoked_blob_tree, &krl->revoked_sha256s) { |
802 | KRL_DBG(("%s: hash len %zu " , __func__, rb->len)); |
803 | if ((r = sshbuf_put_string(sect, rb->blob, rb->len)) != 0) |
804 | goto out; |
805 | } |
806 | if (sshbuf_len(sect) != 0) { |
807 | if ((r = sshbuf_put_u8(buf, |
808 | KRL_SECTION_FINGERPRINT_SHA256)) != 0 || |
809 | (r = sshbuf_put_stringb(buf, sect)) != 0) |
810 | goto out; |
811 | } |
812 | |
813 | for (i = 0; i < nsign_keys; i++) { |
814 | KRL_DBG(("%s: signature key %s" , __func__, |
815 | sshkey_ssh_name(sign_keys[i]))); |
816 | if ((r = sshbuf_put_u8(buf, KRL_SECTION_SIGNATURE)) != 0 || |
817 | (r = sshkey_puts(sign_keys[i], buf)) != 0) |
818 | goto out; |
819 | |
820 | if ((r = sshkey_sign(sign_keys[i], &sblob, &slen, |
821 | sshbuf_ptr(buf), sshbuf_len(buf), NULL, 0)) != 0) |
822 | goto out; |
823 | KRL_DBG(("%s: signature sig len %zu" , __func__, slen)); |
824 | if ((r = sshbuf_put_string(buf, sblob, slen)) != 0) |
825 | goto out; |
826 | } |
827 | |
828 | r = 0; |
829 | out: |
830 | free(sblob); |
831 | sshbuf_free(sect); |
832 | return r; |
833 | } |
834 | |
835 | static void |
836 | format_timestamp(u_int64_t timestamp, char *ts, size_t nts) |
837 | { |
838 | time_t t; |
839 | struct tm *tm; |
840 | |
841 | t = timestamp; |
842 | tm = localtime(&t); |
843 | if (tm == NULL) |
844 | strlcpy(ts, "<INVALID>" , nts); |
845 | else { |
846 | *ts = '\0'; |
847 | strftime(ts, nts, "%Y%m%dT%H%M%S" , tm); |
848 | } |
849 | } |
850 | |
851 | static int |
852 | parse_revoked_certs(struct sshbuf *buf, struct ssh_krl *krl) |
853 | { |
854 | int r = SSH_ERR_INTERNAL_ERROR; |
855 | u_char type; |
856 | const u_char *blob; |
857 | size_t blen, nbits; |
858 | struct sshbuf *subsect = NULL; |
859 | u_int64_t serial, serial_lo, serial_hi; |
860 | struct bitmap *bitmap = NULL; |
861 | char *key_id = NULL; |
862 | struct sshkey *ca_key = NULL; |
863 | |
864 | if ((subsect = sshbuf_new()) == NULL) |
865 | return SSH_ERR_ALLOC_FAIL; |
866 | |
867 | /* Header: key, reserved */ |
868 | if ((r = sshbuf_get_string_direct(buf, &blob, &blen)) != 0 || |
869 | (r = sshbuf_skip_string(buf)) != 0) |
870 | goto out; |
871 | if (blen != 0 && (r = sshkey_from_blob(blob, blen, &ca_key)) != 0) |
872 | goto out; |
873 | |
874 | while (sshbuf_len(buf) > 0) { |
875 | sshbuf_free(subsect); |
876 | subsect = NULL; |
877 | if ((r = sshbuf_get_u8(buf, &type)) != 0 || |
878 | (r = sshbuf_froms(buf, &subsect)) != 0) |
879 | goto out; |
880 | KRL_DBG(("%s: subsection type 0x%02x" , __func__, type)); |
881 | /* sshbuf_dump(subsect, stderr); */ |
882 | |
883 | switch (type) { |
884 | case KRL_SECTION_CERT_SERIAL_LIST: |
885 | while (sshbuf_len(subsect) > 0) { |
886 | if ((r = sshbuf_get_u64(subsect, &serial)) != 0) |
887 | goto out; |
888 | if ((r = ssh_krl_revoke_cert_by_serial(krl, |
889 | ca_key, serial)) != 0) |
890 | goto out; |
891 | } |
892 | break; |
893 | case KRL_SECTION_CERT_SERIAL_RANGE: |
894 | if ((r = sshbuf_get_u64(subsect, &serial_lo)) != 0 || |
895 | (r = sshbuf_get_u64(subsect, &serial_hi)) != 0) |
896 | goto out; |
897 | if ((r = ssh_krl_revoke_cert_by_serial_range(krl, |
898 | ca_key, serial_lo, serial_hi)) != 0) |
899 | goto out; |
900 | break; |
901 | case KRL_SECTION_CERT_SERIAL_BITMAP: |
902 | if ((bitmap = bitmap_new()) == NULL) { |
903 | r = SSH_ERR_ALLOC_FAIL; |
904 | goto out; |
905 | } |
906 | if ((r = sshbuf_get_u64(subsect, &serial_lo)) != 0 || |
907 | (r = sshbuf_get_bignum2_bytes_direct(subsect, |
908 | &blob, &blen)) != 0) |
909 | goto out; |
910 | if (bitmap_from_string(bitmap, blob, blen) != 0) { |
911 | r = SSH_ERR_INVALID_FORMAT; |
912 | goto out; |
913 | } |
914 | nbits = bitmap_nbits(bitmap); |
915 | for (serial = 0; serial < (u_int64_t)nbits; serial++) { |
916 | if (serial > 0 && serial_lo + serial == 0) { |
917 | error("%s: bitmap wraps u64" , __func__); |
918 | r = SSH_ERR_INVALID_FORMAT; |
919 | goto out; |
920 | } |
921 | if (!bitmap_test_bit(bitmap, serial)) |
922 | continue; |
923 | if ((r = ssh_krl_revoke_cert_by_serial(krl, |
924 | ca_key, serial_lo + serial)) != 0) |
925 | goto out; |
926 | } |
927 | bitmap_free(bitmap); |
928 | bitmap = NULL; |
929 | break; |
930 | case KRL_SECTION_CERT_KEY_ID: |
931 | while (sshbuf_len(subsect) > 0) { |
932 | if ((r = sshbuf_get_cstring(subsect, |
933 | &key_id, NULL)) != 0) |
934 | goto out; |
935 | if ((r = ssh_krl_revoke_cert_by_key_id(krl, |
936 | ca_key, key_id)) != 0) |
937 | goto out; |
938 | free(key_id); |
939 | key_id = NULL; |
940 | } |
941 | break; |
942 | default: |
943 | error("Unsupported KRL certificate section %u" , type); |
944 | r = SSH_ERR_INVALID_FORMAT; |
945 | goto out; |
946 | } |
947 | if (sshbuf_len(subsect) > 0) { |
948 | error("KRL certificate section contains unparsed data" ); |
949 | r = SSH_ERR_INVALID_FORMAT; |
950 | goto out; |
951 | } |
952 | } |
953 | |
954 | r = 0; |
955 | out: |
956 | if (bitmap != NULL) |
957 | bitmap_free(bitmap); |
958 | free(key_id); |
959 | sshkey_free(ca_key); |
960 | sshbuf_free(subsect); |
961 | return r; |
962 | } |
963 | |
964 | static int |
965 | blob_section(struct sshbuf *sect, struct revoked_blob_tree *target_tree, |
966 | size_t expected_len) |
967 | { |
968 | u_char *rdata = NULL; |
969 | size_t rlen = 0; |
970 | int r; |
971 | |
972 | while (sshbuf_len(sect) > 0) { |
973 | if ((r = sshbuf_get_string(sect, &rdata, &rlen)) != 0) |
974 | return r; |
975 | if (expected_len != 0 && rlen != expected_len) { |
976 | error("%s: bad length" , __func__); |
977 | free(rdata); |
978 | return SSH_ERR_INVALID_FORMAT; |
979 | } |
980 | if ((r = revoke_blob(target_tree, rdata, rlen)) != 0) { |
981 | free(rdata); |
982 | return r; |
983 | } |
984 | } |
985 | return 0; |
986 | } |
987 | |
988 | /* Attempt to parse a KRL, checking its signature (if any) with sign_ca_keys. */ |
989 | int |
990 | ssh_krl_from_blob(struct sshbuf *buf, struct ssh_krl **krlp, |
991 | const struct sshkey **sign_ca_keys, size_t nsign_ca_keys) |
992 | { |
993 | struct sshbuf *copy = NULL, *sect = NULL; |
994 | struct ssh_krl *krl = NULL; |
995 | char timestamp[64]; |
996 | int r = SSH_ERR_INTERNAL_ERROR, sig_seen; |
997 | struct sshkey *key = NULL, **ca_used = NULL, **tmp_ca_used; |
998 | u_char type; |
999 | const u_char *blob; |
1000 | size_t i, j, sig_off, sects_off, blen, nca_used; |
1001 | u_int format_version; |
1002 | |
1003 | nca_used = 0; |
1004 | *krlp = NULL; |
1005 | if (sshbuf_len(buf) < sizeof(KRL_MAGIC) - 1 || |
1006 | memcmp(sshbuf_ptr(buf), KRL_MAGIC, sizeof(KRL_MAGIC) - 1) != 0) { |
1007 | debug3("%s: not a KRL" , __func__); |
1008 | return SSH_ERR_KRL_BAD_MAGIC; |
1009 | } |
1010 | |
1011 | /* Take a copy of the KRL buffer so we can verify its signature later */ |
1012 | if ((copy = sshbuf_fromb(buf)) == NULL) { |
1013 | r = SSH_ERR_ALLOC_FAIL; |
1014 | goto out; |
1015 | } |
1016 | if ((r = sshbuf_consume(copy, sizeof(KRL_MAGIC) - 1)) != 0) |
1017 | goto out; |
1018 | |
1019 | if ((krl = ssh_krl_init()) == NULL) { |
1020 | error("%s: alloc failed" , __func__); |
1021 | goto out; |
1022 | } |
1023 | |
1024 | if ((r = sshbuf_get_u32(copy, &format_version)) != 0) |
1025 | goto out; |
1026 | if (format_version != KRL_FORMAT_VERSION) { |
1027 | r = SSH_ERR_INVALID_FORMAT; |
1028 | goto out; |
1029 | } |
1030 | if ((r = sshbuf_get_u64(copy, &krl->krl_version)) != 0 || |
1031 | (r = sshbuf_get_u64(copy, &krl->generated_date)) != 0 || |
1032 | (r = sshbuf_get_u64(copy, &krl->flags)) != 0 || |
1033 | (r = sshbuf_skip_string(copy)) != 0 || |
1034 | (r = sshbuf_get_cstring(copy, &krl->comment, NULL)) != 0) |
1035 | goto out; |
1036 | |
1037 | format_timestamp(krl->generated_date, timestamp, sizeof(timestamp)); |
1038 | debug("KRL version %llu generated at %s%s%s" , |
1039 | (long long unsigned)krl->krl_version, timestamp, |
1040 | *krl->comment ? ": " : "" , krl->comment); |
1041 | |
1042 | /* |
1043 | * 1st pass: verify signatures, if any. This is done to avoid |
1044 | * detailed parsing of data whose provenance is unverified. |
1045 | */ |
1046 | sig_seen = 0; |
1047 | if (sshbuf_len(buf) < sshbuf_len(copy)) { |
1048 | /* Shouldn't happen */ |
1049 | r = SSH_ERR_INTERNAL_ERROR; |
1050 | goto out; |
1051 | } |
1052 | sects_off = sshbuf_len(buf) - sshbuf_len(copy); |
1053 | while (sshbuf_len(copy) > 0) { |
1054 | if ((r = sshbuf_get_u8(copy, &type)) != 0 || |
1055 | (r = sshbuf_get_string_direct(copy, &blob, &blen)) != 0) |
1056 | goto out; |
1057 | KRL_DBG(("%s: first pass, section 0x%02x" , __func__, type)); |
1058 | if (type != KRL_SECTION_SIGNATURE) { |
1059 | if (sig_seen) { |
1060 | error("KRL contains non-signature section " |
1061 | "after signature" ); |
1062 | r = SSH_ERR_INVALID_FORMAT; |
1063 | goto out; |
1064 | } |
1065 | /* Not interested for now. */ |
1066 | continue; |
1067 | } |
1068 | sig_seen = 1; |
1069 | /* First string component is the signing key */ |
1070 | if ((r = sshkey_from_blob(blob, blen, &key)) != 0) { |
1071 | r = SSH_ERR_INVALID_FORMAT; |
1072 | goto out; |
1073 | } |
1074 | if (sshbuf_len(buf) < sshbuf_len(copy)) { |
1075 | /* Shouldn't happen */ |
1076 | r = SSH_ERR_INTERNAL_ERROR; |
1077 | goto out; |
1078 | } |
1079 | sig_off = sshbuf_len(buf) - sshbuf_len(copy); |
1080 | /* Second string component is the signature itself */ |
1081 | if ((r = sshbuf_get_string_direct(copy, &blob, &blen)) != 0) { |
1082 | r = SSH_ERR_INVALID_FORMAT; |
1083 | goto out; |
1084 | } |
1085 | /* Check signature over entire KRL up to this point */ |
1086 | if ((r = sshkey_verify(key, blob, blen, |
1087 | sshbuf_ptr(buf), sig_off, NULL, 0)) != 0) |
1088 | goto out; |
1089 | /* Check if this key has already signed this KRL */ |
1090 | for (i = 0; i < nca_used; i++) { |
1091 | if (sshkey_equal(ca_used[i], key)) { |
1092 | error("KRL signed more than once with " |
1093 | "the same key" ); |
1094 | r = SSH_ERR_INVALID_FORMAT; |
1095 | goto out; |
1096 | } |
1097 | } |
1098 | /* Record keys used to sign the KRL */ |
1099 | tmp_ca_used = recallocarray(ca_used, nca_used, nca_used + 1, |
1100 | sizeof(*ca_used)); |
1101 | if (tmp_ca_used == NULL) { |
1102 | r = SSH_ERR_ALLOC_FAIL; |
1103 | goto out; |
1104 | } |
1105 | ca_used = tmp_ca_used; |
1106 | ca_used[nca_used++] = key; |
1107 | key = NULL; |
1108 | } |
1109 | |
1110 | if (sshbuf_len(copy) != 0) { |
1111 | /* Shouldn't happen */ |
1112 | r = SSH_ERR_INTERNAL_ERROR; |
1113 | goto out; |
1114 | } |
1115 | |
1116 | /* |
1117 | * 2nd pass: parse and load the KRL, skipping the header to the point |
1118 | * where the section start. |
1119 | */ |
1120 | sshbuf_free(copy); |
1121 | if ((copy = sshbuf_fromb(buf)) == NULL) { |
1122 | r = SSH_ERR_ALLOC_FAIL; |
1123 | goto out; |
1124 | } |
1125 | if ((r = sshbuf_consume(copy, sects_off)) != 0) |
1126 | goto out; |
1127 | while (sshbuf_len(copy) > 0) { |
1128 | sshbuf_free(sect); |
1129 | sect = NULL; |
1130 | if ((r = sshbuf_get_u8(copy, &type)) != 0 || |
1131 | (r = sshbuf_froms(copy, §)) != 0) |
1132 | goto out; |
1133 | KRL_DBG(("%s: second pass, section 0x%02x" , __func__, type)); |
1134 | |
1135 | switch (type) { |
1136 | case KRL_SECTION_CERTIFICATES: |
1137 | if ((r = parse_revoked_certs(sect, krl)) != 0) |
1138 | goto out; |
1139 | break; |
1140 | case KRL_SECTION_EXPLICIT_KEY: |
1141 | if ((r = blob_section(sect, |
1142 | &krl->revoked_keys, 0)) != 0) |
1143 | goto out; |
1144 | break; |
1145 | case KRL_SECTION_FINGERPRINT_SHA1: |
1146 | if ((r = blob_section(sect, |
1147 | &krl->revoked_sha1s, 20)) != 0) |
1148 | goto out; |
1149 | break; |
1150 | case KRL_SECTION_FINGERPRINT_SHA256: |
1151 | if ((r = blob_section(sect, |
1152 | &krl->revoked_sha256s, 32)) != 0) |
1153 | goto out; |
1154 | break; |
1155 | case KRL_SECTION_SIGNATURE: |
1156 | /* Handled above, but still need to stay in synch */ |
1157 | sshbuf_free(sect); |
1158 | sect = NULL; |
1159 | if ((r = sshbuf_skip_string(copy)) != 0) |
1160 | goto out; |
1161 | break; |
1162 | default: |
1163 | error("Unsupported KRL section %u" , type); |
1164 | r = SSH_ERR_INVALID_FORMAT; |
1165 | goto out; |
1166 | } |
1167 | if (sect != NULL && sshbuf_len(sect) > 0) { |
1168 | error("KRL section contains unparsed data" ); |
1169 | r = SSH_ERR_INVALID_FORMAT; |
1170 | goto out; |
1171 | } |
1172 | } |
1173 | |
1174 | /* Check that the key(s) used to sign the KRL weren't revoked */ |
1175 | sig_seen = 0; |
1176 | for (i = 0; i < nca_used; i++) { |
1177 | if (ssh_krl_check_key(krl, ca_used[i]) == 0) |
1178 | sig_seen = 1; |
1179 | else { |
1180 | sshkey_free(ca_used[i]); |
1181 | ca_used[i] = NULL; |
1182 | } |
1183 | } |
1184 | if (nca_used && !sig_seen) { |
1185 | error("All keys used to sign KRL were revoked" ); |
1186 | r = SSH_ERR_KEY_REVOKED; |
1187 | goto out; |
1188 | } |
1189 | |
1190 | /* If we have CA keys, then verify that one was used to sign the KRL */ |
1191 | if (sig_seen && nsign_ca_keys != 0) { |
1192 | sig_seen = 0; |
1193 | for (i = 0; !sig_seen && i < nsign_ca_keys; i++) { |
1194 | for (j = 0; j < nca_used; j++) { |
1195 | if (ca_used[j] == NULL) |
1196 | continue; |
1197 | if (sshkey_equal(ca_used[j], sign_ca_keys[i])) { |
1198 | sig_seen = 1; |
1199 | break; |
1200 | } |
1201 | } |
1202 | } |
1203 | if (!sig_seen) { |
1204 | r = SSH_ERR_SIGNATURE_INVALID; |
1205 | error("KRL not signed with any trusted key" ); |
1206 | goto out; |
1207 | } |
1208 | } |
1209 | |
1210 | *krlp = krl; |
1211 | r = 0; |
1212 | out: |
1213 | if (r != 0) |
1214 | ssh_krl_free(krl); |
1215 | for (i = 0; i < nca_used; i++) |
1216 | sshkey_free(ca_used[i]); |
1217 | free(ca_used); |
1218 | sshkey_free(key); |
1219 | sshbuf_free(copy); |
1220 | sshbuf_free(sect); |
1221 | return r; |
1222 | } |
1223 | |
1224 | /* Checks certificate serial number and key ID revocation */ |
1225 | static int |
1226 | is_cert_revoked(const struct sshkey *key, struct revoked_certs *rc) |
1227 | { |
1228 | struct revoked_serial rs, *ers; |
1229 | struct revoked_key_id rki, *erki; |
1230 | |
1231 | /* Check revocation by cert key ID */ |
1232 | memset(&rki, 0, sizeof(rki)); |
1233 | rki.key_id = key->cert->key_id; |
1234 | erki = RB_FIND(revoked_key_id_tree, &rc->revoked_key_ids, &rki); |
1235 | if (erki != NULL) { |
1236 | KRL_DBG(("%s: revoked by key ID" , __func__)); |
1237 | return SSH_ERR_KEY_REVOKED; |
1238 | } |
1239 | |
1240 | /* |
1241 | * Zero serials numbers are ignored (it's the default when the |
1242 | * CA doesn't specify one). |
1243 | */ |
1244 | if (key->cert->serial == 0) |
1245 | return 0; |
1246 | |
1247 | memset(&rs, 0, sizeof(rs)); |
1248 | rs.lo = rs.hi = key->cert->serial; |
1249 | ers = RB_FIND(revoked_serial_tree, &rc->revoked_serials, &rs); |
1250 | if (ers != NULL) { |
1251 | KRL_DBG(("%s: revoked serial %llu matched %llu:%llu" , __func__, |
1252 | key->cert->serial, ers->lo, ers->hi)); |
1253 | return SSH_ERR_KEY_REVOKED; |
1254 | } |
1255 | return 0; |
1256 | } |
1257 | |
1258 | /* Checks whether a given key/cert is revoked. Does not check its CA */ |
1259 | static int |
1260 | is_key_revoked(struct ssh_krl *krl, const struct sshkey *key) |
1261 | { |
1262 | struct revoked_blob rb, *erb; |
1263 | struct revoked_certs *rc; |
1264 | int r; |
1265 | |
1266 | /* Check explicitly revoked hashes first */ |
1267 | memset(&rb, 0, sizeof(rb)); |
1268 | if ((r = sshkey_fingerprint_raw(key, SSH_DIGEST_SHA1, |
1269 | &rb.blob, &rb.len)) != 0) |
1270 | return r; |
1271 | erb = RB_FIND(revoked_blob_tree, &krl->revoked_sha1s, &rb); |
1272 | free(rb.blob); |
1273 | if (erb != NULL) { |
1274 | KRL_DBG(("%s: revoked by key SHA1" , __func__)); |
1275 | return SSH_ERR_KEY_REVOKED; |
1276 | } |
1277 | memset(&rb, 0, sizeof(rb)); |
1278 | if ((r = sshkey_fingerprint_raw(key, SSH_DIGEST_SHA256, |
1279 | &rb.blob, &rb.len)) != 0) |
1280 | return r; |
1281 | erb = RB_FIND(revoked_blob_tree, &krl->revoked_sha256s, &rb); |
1282 | free(rb.blob); |
1283 | if (erb != NULL) { |
1284 | KRL_DBG(("%s: revoked by key SHA256" , __func__)); |
1285 | return SSH_ERR_KEY_REVOKED; |
1286 | } |
1287 | |
1288 | /* Next, explicit keys */ |
1289 | memset(&rb, 0, sizeof(rb)); |
1290 | if ((r = plain_key_blob(key, &rb.blob, &rb.len)) != 0) |
1291 | return r; |
1292 | erb = RB_FIND(revoked_blob_tree, &krl->revoked_keys, &rb); |
1293 | free(rb.blob); |
1294 | if (erb != NULL) { |
1295 | KRL_DBG(("%s: revoked by explicit key" , __func__)); |
1296 | return SSH_ERR_KEY_REVOKED; |
1297 | } |
1298 | |
1299 | if (!sshkey_is_cert(key)) |
1300 | return 0; |
1301 | |
1302 | /* Check cert revocation for the specified CA */ |
1303 | if ((r = revoked_certs_for_ca_key(krl, key->cert->signature_key, |
1304 | &rc, 0)) != 0) |
1305 | return r; |
1306 | if (rc != NULL) { |
1307 | if ((r = is_cert_revoked(key, rc)) != 0) |
1308 | return r; |
1309 | } |
1310 | /* Check cert revocation for the wildcard CA */ |
1311 | if ((r = revoked_certs_for_ca_key(krl, NULL, &rc, 0)) != 0) |
1312 | return r; |
1313 | if (rc != NULL) { |
1314 | if ((r = is_cert_revoked(key, rc)) != 0) |
1315 | return r; |
1316 | } |
1317 | |
1318 | KRL_DBG(("%s: %llu no match" , __func__, key->cert->serial)); |
1319 | return 0; |
1320 | } |
1321 | |
1322 | int |
1323 | ssh_krl_check_key(struct ssh_krl *krl, const struct sshkey *key) |
1324 | { |
1325 | int r; |
1326 | |
1327 | KRL_DBG(("%s: checking key" , __func__)); |
1328 | if ((r = is_key_revoked(krl, key)) != 0) |
1329 | return r; |
1330 | if (sshkey_is_cert(key)) { |
1331 | debug2("%s: checking CA key" , __func__); |
1332 | if ((r = is_key_revoked(krl, key->cert->signature_key)) != 0) |
1333 | return r; |
1334 | } |
1335 | KRL_DBG(("%s: key okay" , __func__)); |
1336 | return 0; |
1337 | } |
1338 | |
1339 | int |
1340 | ssh_krl_file_contains_key(const char *path, const struct sshkey *key) |
1341 | { |
1342 | struct sshbuf *krlbuf = NULL; |
1343 | struct ssh_krl *krl = NULL; |
1344 | int oerrno = 0, r, fd; |
1345 | |
1346 | if (path == NULL) |
1347 | return 0; |
1348 | |
1349 | if ((krlbuf = sshbuf_new()) == NULL) |
1350 | return SSH_ERR_ALLOC_FAIL; |
1351 | if ((fd = open(path, O_RDONLY)) == -1) { |
1352 | r = SSH_ERR_SYSTEM_ERROR; |
1353 | oerrno = errno; |
1354 | goto out; |
1355 | } |
1356 | if ((r = sshkey_load_file(fd, krlbuf)) != 0) { |
1357 | oerrno = errno; |
1358 | goto out; |
1359 | } |
1360 | if ((r = ssh_krl_from_blob(krlbuf, &krl, NULL, 0)) != 0) |
1361 | goto out; |
1362 | debug2("%s: checking KRL %s" , __func__, path); |
1363 | r = ssh_krl_check_key(krl, key); |
1364 | out: |
1365 | if (fd != -1) |
1366 | close(fd); |
1367 | sshbuf_free(krlbuf); |
1368 | ssh_krl_free(krl); |
1369 | if (r != 0) |
1370 | errno = oerrno; |
1371 | return r; |
1372 | } |
1373 | |