/* * Copyright (c) 2012 Damien Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* $OpenBSD: krl.c,v 1.15 2014/04/28 03:09:18 djm Exp $ */ #include #include #include #include #include #include #include #include #include #include #include "buffer.h" #include "key.h" #include "authfile.h" #include "misc.h" #include "log.h" #include "xmalloc.h" #include "krl.h" /* #define DEBUG_KRL */ #ifdef DEBUG_KRL # define KRL_DBG(x) debug3 x #else # define KRL_DBG(x) #endif /* * Trees of revoked serial numbers, key IDs and keys. This allows * quick searching, querying and producing lists in canonical order. */ /* Tree of serial numbers. XXX make smarter: really need a real sparse bitmap */ struct revoked_serial { u_int64_t lo, hi; RB_ENTRY(revoked_serial) tree_entry; }; static int serial_cmp(struct revoked_serial *a, struct revoked_serial *b); RB_HEAD(revoked_serial_tree, revoked_serial); RB_GENERATE_STATIC(revoked_serial_tree, revoked_serial, tree_entry, serial_cmp); /* Tree of key IDs */ struct revoked_key_id { char *key_id; RB_ENTRY(revoked_key_id) tree_entry; }; static int key_id_cmp(struct revoked_key_id *a, struct revoked_key_id *b); RB_HEAD(revoked_key_id_tree, revoked_key_id); RB_GENERATE_STATIC(revoked_key_id_tree, revoked_key_id, tree_entry, key_id_cmp); /* Tree of blobs (used for keys and fingerprints) */ struct revoked_blob { u_char *blob; u_int len; RB_ENTRY(revoked_blob) tree_entry; }; static int blob_cmp(struct revoked_blob *a, struct revoked_blob *b); RB_HEAD(revoked_blob_tree, revoked_blob); RB_GENERATE_STATIC(revoked_blob_tree, revoked_blob, tree_entry, blob_cmp); /* Tracks revoked certs for a single CA */ struct revoked_certs { Key *ca_key; struct revoked_serial_tree revoked_serials; struct revoked_key_id_tree revoked_key_ids; TAILQ_ENTRY(revoked_certs) entry; }; TAILQ_HEAD(revoked_certs_list, revoked_certs); struct ssh_krl { u_int64_t krl_version; u_int64_t generated_date; u_int64_t flags; char *comment; struct revoked_blob_tree revoked_keys; struct revoked_blob_tree revoked_sha1s; struct revoked_certs_list revoked_certs; }; /* Return equal if a and b overlap */ static int serial_cmp(struct revoked_serial *a, struct revoked_serial *b) { if (a->hi >= b->lo && a->lo <= b->hi) return 0; return a->lo < b->lo ? -1 : 1; } static int key_id_cmp(struct revoked_key_id *a, struct revoked_key_id *b) { return strcmp(a->key_id, b->key_id); } static int blob_cmp(struct revoked_blob *a, struct revoked_blob *b) { int r; if (a->len != b->len) { if ((r = memcmp(a->blob, b->blob, MIN(a->len, b->len))) != 0) return r; return a->len > b->len ? 1 : -1; } else return memcmp(a->blob, b->blob, a->len); } struct ssh_krl * ssh_krl_init(void) { struct ssh_krl *krl; if ((krl = calloc(1, sizeof(*krl))) == NULL) return NULL; RB_INIT(&krl->revoked_keys); RB_INIT(&krl->revoked_sha1s); TAILQ_INIT(&krl->revoked_certs); return krl; } static void revoked_certs_free(struct revoked_certs *rc) { struct revoked_serial *rs, *trs; struct revoked_key_id *rki, *trki; RB_FOREACH_SAFE(rs, revoked_serial_tree, &rc->revoked_serials, trs) { RB_REMOVE(revoked_serial_tree, &rc->revoked_serials, rs); free(rs); } RB_FOREACH_SAFE(rki, revoked_key_id_tree, &rc->revoked_key_ids, trki) { RB_REMOVE(revoked_key_id_tree, &rc->revoked_key_ids, rki); free(rki->key_id); free(rki); } if (rc->ca_key != NULL) key_free(rc->ca_key); } void ssh_krl_free(struct ssh_krl *krl) { struct revoked_blob *rb, *trb; struct revoked_certs *rc, *trc; if (krl == NULL) return; free(krl->comment); RB_FOREACH_SAFE(rb, revoked_blob_tree, &krl->revoked_keys, trb) { RB_REMOVE(revoked_blob_tree, &krl->revoked_keys, rb); free(rb->blob); free(rb); } RB_FOREACH_SAFE(rb, revoked_blob_tree, &krl->revoked_sha1s, trb) { RB_REMOVE(revoked_blob_tree, &krl->revoked_sha1s, rb); free(rb->blob); free(rb); } TAILQ_FOREACH_SAFE(rc, &krl->revoked_certs, entry, trc) { TAILQ_REMOVE(&krl->revoked_certs, rc, entry); revoked_certs_free(rc); } } void ssh_krl_set_version(struct ssh_krl *krl, u_int64_t version) { krl->krl_version = version; } void ssh_krl_set_comment(struct ssh_krl *krl, const char *comment) { free(krl->comment); if ((krl->comment = strdup(comment)) == NULL) fatal("%s: strdup", __func__); } /* * Find the revoked_certs struct for a CA key. If allow_create is set then * create a new one in the tree if one did not exist already. */ static int revoked_certs_for_ca_key(struct ssh_krl *krl, const Key *ca_key, struct revoked_certs **rcp, int allow_create) { struct revoked_certs *rc; *rcp = NULL; TAILQ_FOREACH(rc, &krl->revoked_certs, entry) { if (key_equal(rc->ca_key, ca_key)) { *rcp = rc; return 0; } } if (!allow_create) return 0; /* If this CA doesn't exist in the list then add it now */ if ((rc = calloc(1, sizeof(*rc))) == NULL) return -1; if ((rc->ca_key = key_from_private(ca_key)) == NULL) { free(rc); return -1; } RB_INIT(&rc->revoked_serials); RB_INIT(&rc->revoked_key_ids); TAILQ_INSERT_TAIL(&krl->revoked_certs, rc, entry); debug3("%s: new CA %s", __func__, key_type(ca_key)); *rcp = rc; return 0; } static int insert_serial_range(struct revoked_serial_tree *rt, u_int64_t lo, u_int64_t hi) { struct revoked_serial rs, *ers, *crs, *irs; KRL_DBG(("%s: insert %llu:%llu", __func__, lo, hi)); memset(&rs, 0, sizeof(rs)); rs.lo = lo; rs.hi = hi; ers = RB_NFIND(revoked_serial_tree, rt, &rs); if (ers == NULL || serial_cmp(ers, &rs) != 0) { /* No entry matches. Just insert */ if ((irs = malloc(sizeof(rs))) == NULL) return -1; memcpy(irs, &rs, sizeof(*irs)); ers = RB_INSERT(revoked_serial_tree, rt, irs); if (ers != NULL) { KRL_DBG(("%s: bad: ers != NULL", __func__)); /* Shouldn't happen */ free(irs); return -1; } ers = irs; } else { KRL_DBG(("%s: overlap found %llu:%llu", __func__, ers->lo, ers->hi)); /* * The inserted entry overlaps an existing one. Grow the * existing entry. */ if (ers->lo > lo) ers->lo = lo; if (ers->hi < hi) ers->hi = hi; } /* * The inserted or revised range might overlap or abut adjacent ones; * coalesce as necessary. */ /* Check predecessors */ while ((crs = RB_PREV(revoked_serial_tree, rt, ers)) != NULL) { KRL_DBG(("%s: pred %llu:%llu", __func__, crs->lo, crs->hi)); if (ers->lo != 0 && crs->hi < ers->lo - 1) break; /* This entry overlaps. */ if (crs->lo < ers->lo) { ers->lo = crs->lo; KRL_DBG(("%s: pred extend %llu:%llu", __func__, ers->lo, ers->hi)); } RB_REMOVE(revoked_serial_tree, rt, crs); free(crs); } /* Check successors */ while ((crs = RB_NEXT(revoked_serial_tree, rt, ers)) != NULL) { KRL_DBG(("%s: succ %llu:%llu", __func__, crs->lo, crs->hi)); if (ers->hi != (u_int64_t)-1 && crs->lo > ers->hi + 1) break; /* This entry overlaps. */ if (crs->hi > ers->hi) { ers->hi = crs->hi; KRL_DBG(("%s: succ extend %llu:%llu", __func__, ers->lo, ers->hi)); } RB_REMOVE(revoked_serial_tree, rt, crs); free(crs); } KRL_DBG(("%s: done, final %llu:%llu", __func__, ers->lo, ers->hi)); return 0; } int ssh_krl_revoke_cert_by_serial(struct ssh_krl *krl, const Key *ca_key, u_int64_t serial) { return ssh_krl_revoke_cert_by_serial_range(krl, ca_key, serial, serial); } int ssh_krl_revoke_cert_by_serial_range(struct ssh_krl *krl, const Key *ca_key, u_int64_t lo, u_int64_t hi) { struct revoked_certs *rc; if (lo > hi || lo == 0) return -1; if (revoked_certs_for_ca_key(krl, ca_key, &rc, 1) != 0) return -1; return insert_serial_range(&rc->revoked_serials, lo, hi); } int ssh_krl_revoke_cert_by_key_id(struct ssh_krl *krl, const Key *ca_key, const char *key_id) { struct revoked_key_id *rki, *erki; struct revoked_certs *rc; if (revoked_certs_for_ca_key(krl, ca_key, &rc, 1) != 0) return -1; debug3("%s: revoke %s", __func__, key_id); if ((rki = calloc(1, sizeof(*rki))) == NULL || (rki->key_id = strdup(key_id)) == NULL) { free(rki); fatal("%s: strdup", __func__); } erki = RB_INSERT(revoked_key_id_tree, &rc->revoked_key_ids, rki); if (erki != NULL) { free(rki->key_id); free(rki); } return 0; } /* Convert "key" to a public key blob without any certificate information */ static int plain_key_blob(const Key *key, u_char **blob, u_int *blen) { Key *kcopy; int r; if ((kcopy = key_from_private(key)) == NULL) return -1; if (key_is_cert(kcopy)) { if (key_drop_cert(kcopy) != 0) { error("%s: key_drop_cert", __func__); key_free(kcopy); return -1; } } r = key_to_blob(kcopy, blob, blen); free(kcopy); return r == 0 ? -1 : 0; } /* Revoke a key blob. Ownership of blob is transferred to the tree */ static int revoke_blob(struct revoked_blob_tree *rbt, u_char *blob, u_int len) { struct revoked_blob *rb, *erb; if ((rb = calloc(1, sizeof(*rb))) == NULL) return -1; rb->blob = blob; rb->len = len; erb = RB_INSERT(revoked_blob_tree, rbt, rb); if (erb != NULL) { free(rb->blob); free(rb); } return 0; } int ssh_krl_revoke_key_explicit(struct ssh_krl *krl, const Key *key) { u_char *blob; u_int len; debug3("%s: revoke type %s", __func__, key_type(key)); if (plain_key_blob(key, &blob, &len) != 0) return -1; return revoke_blob(&krl->revoked_keys, blob, len); } int ssh_krl_revoke_key_sha1(struct ssh_krl *krl, const Key *key) { u_char *blob; u_int len; debug3("%s: revoke type %s by sha1", __func__, key_type(key)); if ((blob = key_fingerprint_raw(key, SSH_FP_SHA1, &len)) == NULL) return -1; return revoke_blob(&krl->revoked_sha1s, blob, len); } int ssh_krl_revoke_key(struct ssh_krl *krl, const Key *key) { if (!key_is_cert(key)) return ssh_krl_revoke_key_sha1(krl, key); if (key_cert_is_legacy(key) || key->cert->serial == 0) { return ssh_krl_revoke_cert_by_key_id(krl, key->cert->signature_key, key->cert->key_id); } else { return ssh_krl_revoke_cert_by_serial(krl, key->cert->signature_key, key->cert->serial); } } /* * Select a copact next section type to emit in a KRL based on the * current section type, the run length of contiguous revoked serial * numbers and the gaps from the last and to the next revoked serial. * Applies a mostly-accurate bit cost model to select the section type * that will minimise the size of the resultant KRL. */ static int choose_next_state(int current_state, u_int64_t contig, int final, u_int64_t last_gap, u_int64_t next_gap, int *force_new_section) { int new_state; u_int64_t cost, cost_list, cost_range, cost_bitmap, cost_bitmap_restart; /* * Avoid unsigned overflows. * The limits are high enough to avoid confusing the calculations. */ contig = MIN(contig, 1ULL<<31); last_gap = MIN(last_gap, 1ULL<<31); next_gap = MIN(next_gap, 1ULL<<31); /* * Calculate the cost to switch from the current state to candidates. * NB. range sections only ever contain a single range, so their * switching cost is independent of the current_state. */ cost_list = cost_bitmap = cost_bitmap_restart = 0; cost_range = 8; switch (current_state) { case KRL_SECTION_CERT_SERIAL_LIST: cost_bitmap_restart = cost_bitmap = 8 + 64; break; case KRL_SECTION_CERT_SERIAL_BITMAP: cost_list = 8; cost_bitmap_restart = 8 + 64; break; case KRL_SECTION_CERT_SERIAL_RANGE: case 0: cost_bitmap_restart = cost_bitmap = 8 + 64; cost_list = 8; } /* Estimate base cost in bits of each section type */ cost_list += 64 * contig + (final ? 0 : 8+64); cost_range += (2 * 64) + (final ? 0 : 8+64); cost_bitmap += last_gap + contig + (final ? 0 : MIN(next_gap, 8+64)); cost_bitmap_restart += contig + (final ? 0 : MIN(next_gap, 8+64)); /* Convert to byte costs for actual comparison */ cost_list = (cost_list + 7) / 8; cost_bitmap = (cost_bitmap + 7) / 8; cost_bitmap_restart = (cost_bitmap_restart + 7) / 8; cost_range = (cost_range + 7) / 8; /* Now pick the best choice */ *force_new_section = 0; new_state = KRL_SECTION_CERT_SERIAL_BITMAP; cost = cost_bitmap; if (cost_range < cost) { new_state = KRL_SECTION_CERT_SERIAL_RANGE; cost = cost_range; } if (cost_list < cost) { new_state = KRL_SECTION_CERT_SERIAL_LIST; cost = cost_list; } if (cost_bitmap_restart < cost) { new_state = KRL_SECTION_CERT_SERIAL_BITMAP; *force_new_section = 1; cost = cost_bitmap_restart; } debug3("%s: contig %llu last_gap %llu next_gap %llu final %d, costs:" "list %llu range %llu bitmap %llu new bitmap %llu, " "selected 0x%02x%s", __func__, (long long unsigned)contig, (long long unsigned)last_gap, (long long unsigned)next_gap, final, (long long unsigned)cost_list, (long long unsigned)cost_range, (long long unsigned)cost_bitmap, (long long unsigned)cost_bitmap_restart, new_state, *force_new_section ? " restart" : ""); return new_state; } /* Generate a KRL_SECTION_CERTIFICATES KRL section */ static int revoked_certs_generate(struct revoked_certs *rc, Buffer *buf) { int final, force_new_sect, r = -1; u_int64_t i, contig, gap, last = 0, bitmap_start = 0; struct revoked_serial *rs, *nrs; struct revoked_key_id *rki; int next_state, state = 0; Buffer sect; u_char *kblob = NULL; u_int klen; BIGNUM *bitmap = NULL; /* Prepare CA scope key blob if we have one supplied */ if (key_to_blob(rc->ca_key, &kblob, &klen) == 0) return -1; buffer_init(§); /* Store the header */ buffer_put_string(buf, kblob, klen); buffer_put_string(buf, NULL, 0); /* Reserved */ free(kblob); /* Store the revoked serials. */ for (rs = RB_MIN(revoked_serial_tree, &rc->revoked_serials); rs != NULL; rs = RB_NEXT(revoked_serial_tree, &rc->revoked_serials, rs)) { debug3("%s: serial %llu:%llu state 0x%02x", __func__, (long long unsigned)rs->lo, (long long unsigned)rs->hi, state); /* Check contiguous length and gap to next section (if any) */ nrs = RB_NEXT(revoked_serial_tree, &rc->revoked_serials, rs); final = nrs == NULL; gap = nrs == NULL ? 0 : nrs->lo - rs->hi; contig = 1 + (rs->hi - rs->lo); /* Choose next state based on these */ next_state = choose_next_state(state, contig, final, state == 0 ? 0 : rs->lo - last, gap, &force_new_sect); /* * If the current section is a range section or has a different * type to the next section, then finish it off now. */ if (state != 0 && (force_new_sect || next_state != state || state == KRL_SECTION_CERT_SERIAL_RANGE)) { debug3("%s: finish state 0x%02x", __func__, state); switch (state) { case KRL_SECTION_CERT_SERIAL_LIST: case KRL_SECTION_CERT_SERIAL_RANGE: break; case KRL_SECTION_CERT_SERIAL_BITMAP: buffer_put_bignum2(§, bitmap); BN_free(bitmap); bitmap = NULL; break; } buffer_put_char(buf, state); buffer_put_string(buf, buffer_ptr(§), buffer_len(§)); } /* If we are starting a new section then prepare it now */ if (next_state != state || force_new_sect) { debug3("%s: start state 0x%02x", __func__, next_state); state = next_state; buffer_clear(§); switch (state) { case KRL_SECTION_CERT_SERIAL_LIST: case KRL_SECTION_CERT_SERIAL_RANGE: break; case KRL_SECTION_CERT_SERIAL_BITMAP: if ((bitmap = BN_new()) == NULL) goto out; bitmap_start = rs->lo; buffer_put_int64(§, bitmap_start); break; } } /* Perform section-specific processing */ switch (state) { case KRL_SECTION_CERT_SERIAL_LIST: for (i = 0; i < contig; i++) buffer_put_int64(§, rs->lo + i); break; case KRL_SECTION_CERT_SERIAL_RANGE: buffer_put_int64(§, rs->lo); buffer_put_int64(§, rs->hi); break; case KRL_SECTION_CERT_SERIAL_BITMAP: if (rs->lo - bitmap_start > INT_MAX) { error("%s: insane bitmap gap", __func__); goto out; } for (i = 0; i < contig; i++) { if (BN_set_bit(bitmap, rs->lo + i - bitmap_start) != 1) goto out; } break; } last = rs->hi; } /* Flush the remaining section, if any */ if (state != 0) { debug3("%s: serial final flush for state 0x%02x", __func__, state); switch (state) { case KRL_SECTION_CERT_SERIAL_LIST: case KRL_SECTION_CERT_SERIAL_RANGE: break; case KRL_SECTION_CERT_SERIAL_BITMAP: buffer_put_bignum2(§, bitmap); BN_free(bitmap); bitmap = NULL; break; } buffer_put_char(buf, state); buffer_put_string(buf, buffer_ptr(§), buffer_len(§)); } debug3("%s: serial done ", __func__); /* Now output a section for any revocations by key ID */ buffer_clear(§); RB_FOREACH(rki, revoked_key_id_tree, &rc->revoked_key_ids) { debug3("%s: key ID %s", __func__, rki->key_id); buffer_put_cstring(§, rki->key_id); } if (buffer_len(§) != 0) { buffer_put_char(buf, KRL_SECTION_CERT_KEY_ID); buffer_put_string(buf, buffer_ptr(§), buffer_len(§)); } r = 0; out: if (bitmap != NULL) BN_free(bitmap); buffer_free(§); return r; } int ssh_krl_to_blob(struct ssh_krl *krl, Buffer *buf, const Key **sign_keys, u_int nsign_keys) { int r = -1; struct revoked_certs *rc; struct revoked_blob *rb; Buffer sect; u_char *kblob = NULL, *sblob = NULL; u_int klen, slen, i; if (krl->generated_date == 0) krl->generated_date = time(NULL); buffer_init(§); /* Store the header */ buffer_append(buf, KRL_MAGIC, sizeof(KRL_MAGIC) - 1); buffer_put_int(buf, KRL_FORMAT_VERSION); buffer_put_int64(buf, krl->krl_version); buffer_put_int64(buf, krl->generated_date); buffer_put_int64(buf, krl->flags); buffer_put_string(buf, NULL, 0); buffer_put_cstring(buf, krl->comment ? krl->comment : ""); /* Store sections for revoked certificates */ TAILQ_FOREACH(rc, &krl->revoked_certs, entry) { if (revoked_certs_generate(rc, §) != 0) goto out; buffer_put_char(buf, KRL_SECTION_CERTIFICATES); buffer_put_string(buf, buffer_ptr(§), buffer_len(§)); } /* Finally, output sections for revocations by public key/hash */ buffer_clear(§); RB_FOREACH(rb, revoked_blob_tree, &krl->revoked_keys) { debug3("%s: key len %u ", __func__, rb->len); buffer_put_string(§, rb->blob, rb->len); } if (buffer_len(§) != 0) { buffer_put_char(buf, KRL_SECTION_EXPLICIT_KEY); buffer_put_string(buf, buffer_ptr(§), buffer_len(§)); } buffer_clear(§); RB_FOREACH(rb, revoked_blob_tree, &krl->revoked_sha1s) { debug3("%s: hash len %u ", __func__, rb->len); buffer_put_string(§, rb->blob, rb->len); } if (buffer_len(§) != 0) { buffer_put_char(buf, KRL_SECTION_FINGERPRINT_SHA1); buffer_put_string(buf, buffer_ptr(§), buffer_len(§)); } for (i = 0; i < nsign_keys; i++) { if (key_to_blob(sign_keys[i], &kblob, &klen) == 0) goto out; debug3("%s: signature key len %u", __func__, klen); buffer_put_char(buf, KRL_SECTION_SIGNATURE); buffer_put_string(buf, kblob, klen); if (key_sign(sign_keys[i], &sblob, &slen, buffer_ptr(buf), buffer_len(buf)) == -1) goto out; debug3("%s: signature sig len %u", __func__, slen); buffer_put_string(buf, sblob, slen); } r = 0; out: free(kblob); free(sblob); buffer_free(§); return r; } static void format_timestamp(u_int64_t timestamp, char *ts, size_t nts) { time_t t; struct tm *tm; t = timestamp; tm = localtime(&t); *ts = '\0'; strftime(ts, nts, "%Y%m%dT%H%M%S", tm); } static int parse_revoked_certs(Buffer *buf, struct ssh_krl *krl) { int ret = -1, nbits; u_char type; const u_char *blob; u_int blen; Buffer subsect; u_int64_t serial, serial_lo, serial_hi; BIGNUM *bitmap = NULL; char *key_id = NULL; Key *ca_key = NULL; buffer_init(&subsect); if ((blob = buffer_get_string_ptr_ret(buf, &blen)) == NULL || buffer_get_string_ptr_ret(buf, NULL) == NULL) { /* reserved */ error("%s: buffer error", __func__); goto out; } if ((ca_key = key_from_blob(blob, blen)) == NULL) goto out; while (buffer_len(buf) > 0) { if (buffer_get_char_ret(&type, buf) != 0 || (blob = buffer_get_string_ptr_ret(buf, &blen)) == NULL) { error("%s: buffer error", __func__); goto out; } buffer_clear(&subsect); buffer_append(&subsect, blob, blen); debug3("%s: subsection type 0x%02x", __func__, type); /* buffer_dump(&subsect); */ switch (type) { case KRL_SECTION_CERT_SERIAL_LIST: while (buffer_len(&subsect) > 0) { if (buffer_get_int64_ret(&serial, &subsect) != 0) { error("%s: buffer error", __func__); goto out; } if (ssh_krl_revoke_cert_by_serial(krl, ca_key, serial) != 0) { error("%s: update failed", __func__); goto out; } } break; case KRL_SECTION_CERT_SERIAL_RANGE: if (buffer_get_int64_ret(&serial_lo, &subsect) != 0 || buffer_get_int64_ret(&serial_hi, &subsect) != 0) { error("%s: buffer error", __func__); goto out; } if (ssh_krl_revoke_cert_by_serial_range(krl, ca_key, serial_lo, serial_hi) != 0) { error("%s: update failed", __func__); goto out; } break; case KRL_SECTION_CERT_SERIAL_BITMAP: if ((bitmap = BN_new()) == NULL) { error("%s: BN_new", __func__); goto out; } if (buffer_get_int64_ret(&serial_lo, &subsect) != 0 || buffer_get_bignum2_ret(&subsect, bitmap) != 0) { error("%s: buffer error", __func__); goto out; } if ((nbits = BN_num_bits(bitmap)) < 0) { error("%s: bitmap bits < 0", __func__); goto out; } for (serial = 0; serial < (u_int)nbits; serial++) { if (serial > 0 && serial_lo + serial == 0) { error("%s: bitmap wraps u64", __func__); goto out; } if (!BN_is_bit_set(bitmap, serial)) continue; if (ssh_krl_revoke_cert_by_serial(krl, ca_key, serial_lo + serial) != 0) { error("%s: update failed", __func__); goto out; } } BN_free(bitmap); bitmap = NULL; break; case KRL_SECTION_CERT_KEY_ID: while (buffer_len(&subsect) > 0) { if ((key_id = buffer_get_cstring_ret(&subsect, NULL)) == NULL) { error("%s: buffer error", __func__); goto out; } if (ssh_krl_revoke_cert_by_key_id(krl, ca_key, key_id) != 0) { error("%s: update failed", __func__); goto out; } free(key_id); key_id = NULL; } break; default: error("Unsupported KRL certificate section %u", type); goto out; } if (buffer_len(&subsect) > 0) { error("KRL certificate section contains unparsed data"); goto out; } } ret = 0; out: if (ca_key != NULL) key_free(ca_key); if (bitmap != NULL) BN_free(bitmap); free(key_id); buffer_free(&subsect); return ret; } /* Attempt to parse a KRL, checking its signature (if any) with sign_ca_keys. */ int ssh_krl_from_blob(Buffer *buf, struct ssh_krl **krlp, const Key **sign_ca_keys, u_int nsign_ca_keys) { Buffer copy, sect; struct ssh_krl *krl; char timestamp[64]; int ret = -1, r, sig_seen; Key *key = NULL, **ca_used = NULL; u_char type, *rdata = NULL; const u_char *blob; u_int i, j, sig_off, sects_off, rlen, blen, format_version, nca_used; nca_used = 0; *krlp = NULL; if (buffer_len(buf) < sizeof(KRL_MAGIC) - 1 || memcmp(buffer_ptr(buf), KRL_MAGIC, sizeof(KRL_MAGIC) - 1) != 0) { debug3("%s: not a KRL", __func__); /* * Return success but a NULL *krlp here to signal that the * file might be a simple list of keys. */ return 0; } /* Take a copy of the KRL buffer so we can verify its signature later */ buffer_init(©); buffer_append(©, buffer_ptr(buf), buffer_len(buf)); buffer_init(§); buffer_consume(©, sizeof(KRL_MAGIC) - 1); if ((krl = ssh_krl_init()) == NULL) { error("%s: alloc failed", __func__); goto out; } if (buffer_get_int_ret(&format_version, ©) != 0) { error("%s: KRL truncated", __func__); goto out; } if (format_version != KRL_FORMAT_VERSION) { error("%s: KRL unsupported format version %u", __func__, format_version); goto out; } if (buffer_get_int64_ret(&krl->krl_version, ©) != 0 || buffer_get_int64_ret(&krl->generated_date, ©) != 0 || buffer_get_int64_ret(&krl->flags, ©) != 0 || buffer_get_string_ptr_ret(©, NULL) == NULL || /* reserved */ (krl->comment = buffer_get_cstring_ret(©, NULL)) == NULL) { error("%s: buffer error", __func__); goto out; } format_timestamp(krl->generated_date, timestamp, sizeof(timestamp)); debug("KRL version %llu generated at %s%s%s", (long long unsigned)krl->krl_version, timestamp, *krl->comment ? ": " : "", krl->comment); /* * 1st pass: verify signatures, if any. This is done to avoid * detailed parsing of data whose provenance is unverified. */ sig_seen = 0; sects_off = buffer_len(buf) - buffer_len(©); while (buffer_len(©) > 0) { if (buffer_get_char_ret(&type, ©) != 0 || (blob = buffer_get_string_ptr_ret(©, &blen)) == NULL) { error("%s: buffer error", __func__); goto out; } debug3("%s: first pass, section 0x%02x", __func__, type); if (type != KRL_SECTION_SIGNATURE) { if (sig_seen) { error("KRL contains non-signature section " "after signature"); goto out; } /* Not interested for now. */ continue; } sig_seen = 1; /* First string component is the signing key */ if ((key = key_from_blob(blob, blen)) == NULL) { error("%s: invalid signature key", __func__); goto out; } sig_off = buffer_len(buf) - buffer_len(©); /* Second string component is the signature itself */ if ((blob = buffer_get_string_ptr_ret(©, &blen)) == NULL) { error("%s: buffer error", __func__); goto out; } /* Check signature over entire KRL up to this point */ if (key_verify(key, blob, blen, buffer_ptr(buf), buffer_len(buf) - sig_off) != 1) { error("bad signaure on KRL"); goto out; } /* Check if this key has already signed this KRL */ for (i = 0; i < nca_used; i++) { if (key_equal(ca_used[i], key)) { error("KRL signed more than once with " "the same key"); goto out; } } /* Record keys used to sign the KRL */ ca_used = xrealloc(ca_used, nca_used + 1, sizeof(*ca_used)); ca_used[nca_used++] = key; key = NULL; break; } /* * 2nd pass: parse and load the KRL, skipping the header to the point * where the section start. */ buffer_append(©, (u_char*)buffer_ptr(buf) + sects_off, buffer_len(buf) - sects_off); while (buffer_len(©) > 0) { if (buffer_get_char_ret(&type, ©) != 0 || (blob = buffer_get_string_ptr_ret(©, &blen)) == NULL) { error("%s: buffer error", __func__); goto out; } debug3("%s: second pass, section 0x%02x", __func__, type); buffer_clear(§); buffer_append(§, blob, blen); switch (type) { case KRL_SECTION_CERTIFICATES: if ((r = parse_revoked_certs(§, krl)) != 0) goto out; break; case KRL_SECTION_EXPLICIT_KEY: case KRL_SECTION_FINGERPRINT_SHA1: while (buffer_len(§) > 0) { if ((rdata = buffer_get_string_ret(§, &rlen)) == NULL) { error("%s: buffer error", __func__); goto out; } if (type == KRL_SECTION_FINGERPRINT_SHA1 && rlen != 20) { error("%s: bad SHA1 length", __func__); goto out; } if (revoke_blob( type == KRL_SECTION_EXPLICIT_KEY ? &krl->revoked_keys : &krl->revoked_sha1s, rdata, rlen) != 0) goto out; rdata = NULL; /* revoke_blob frees blob */ } break; case KRL_SECTION_SIGNATURE: /* Handled above, but still need to stay in synch */ buffer_clear(§); if ((blob = buffer_get_string_ptr_ret(©, &blen)) == NULL) { error("%s: buffer error", __func__); goto out; } break; default: error("Unsupported KRL section %u", type); goto out; } if (buffer_len(§) > 0) { error("KRL section contains unparsed data"); goto out; } } /* Check that the key(s) used to sign the KRL weren't revoked */ sig_seen = 0; for (i = 0; i < nca_used; i++) { if (ssh_krl_check_key(krl, ca_used[i]) == 0) sig_seen = 1; else { key_free(ca_used[i]); ca_used[i] = NULL; } } if (nca_used && !sig_seen) { error("All keys used to sign KRL were revoked"); goto out; } /* If we have CA keys, then verify that one was used to sign the KRL */ if (sig_seen && nsign_ca_keys != 0) { sig_seen = 0; for (i = 0; !sig_seen && i < nsign_ca_keys; i++) { for (j = 0; j < nca_used; j++) { if (ca_used[j] == NULL) continue; if (key_equal(ca_used[j], sign_ca_keys[i])) { sig_seen = 1; break; } } } if (!sig_seen) { error("KRL not signed with any trusted key"); goto out; } } *krlp = krl; ret = 0; out: if (ret != 0) ssh_krl_free(krl); for (i = 0; i < nca_used; i++) { if (ca_used[i] != NULL) key_free(ca_used[i]); } free(ca_used); free(rdata); if (key != NULL) key_free(key); buffer_free(©); buffer_free(§); return ret; } /* Checks whether a given key/cert is revoked. Does not check its CA */ static int is_key_revoked(struct ssh_krl *krl, const Key *key) { struct revoked_blob rb, *erb; struct revoked_serial rs, *ers; struct revoked_key_id rki, *erki; struct revoked_certs *rc; /* Check explicitly revoked hashes first */ memset(&rb, 0, sizeof(rb)); if ((rb.blob = key_fingerprint_raw(key, SSH_FP_SHA1, &rb.len)) == NULL) return -1; erb = RB_FIND(revoked_blob_tree, &krl->revoked_sha1s, &rb); free(rb.blob); if (erb != NULL) { debug("%s: revoked by key SHA1", __func__); return -1; } /* Next, explicit keys */ memset(&rb, 0, sizeof(rb)); if (plain_key_blob(key, &rb.blob, &rb.len) != 0) return -1; erb = RB_FIND(revoked_blob_tree, &krl->revoked_keys, &rb); free(rb.blob); if (erb != NULL) { debug("%s: revoked by explicit key", __func__); return -1; } if (!key_is_cert(key)) return 0; /* Check cert revocation */ if (revoked_certs_for_ca_key(krl, key->cert->signature_key, &rc, 0) != 0) return -1; if (rc == NULL) return 0; /* No entry for this CA */ /* Check revocation by cert key ID */ memset(&rki, 0, sizeof(rki)); rki.key_id = key->cert->key_id; erki = RB_FIND(revoked_key_id_tree, &rc->revoked_key_ids, &rki); if (erki != NULL) { debug("%s: revoked by key ID", __func__); return -1; } /* * Legacy cert formats lack serial numbers. Zero serials numbers * are ignored (it's the default when the CA doesn't specify one). */ if (key_cert_is_legacy(key) || key->cert->serial == 0) return 0; memset(&rs, 0, sizeof(rs)); rs.lo = rs.hi = key->cert->serial; ers = RB_FIND(revoked_serial_tree, &rc->revoked_serials, &rs); if (ers != NULL) { KRL_DBG(("%s: %llu matched %llu:%llu", __func__, key->cert->serial, ers->lo, ers->hi)); debug("%s: revoked by serial", __func__); return -1; } KRL_DBG(("%s: %llu no match", __func__, key->cert->serial)); return 0; } int ssh_krl_check_key(struct ssh_krl *krl, const Key *key) { int r; debug2("%s: checking key", __func__); if ((r = is_key_revoked(krl, key)) != 0) return r; if (key_is_cert(key)) { debug2("%s: checking CA key", __func__); if ((r = is_key_revoked(krl, key->cert->signature_key)) != 0) return r; } debug3("%s: key okay", __func__); return 0; } /* Returns 0 on success, -1 on error or key revoked, -2 if path is not a KRL */ int ssh_krl_file_contains_key(const char *path, const Key *key) { Buffer krlbuf; struct ssh_krl *krl; int revoked, fd; if (path == NULL) return 0; if ((fd = open(path, O_RDONLY)) == -1) { error("open %s: %s", path, strerror(errno)); error("Revoked keys file not accessible - refusing public key " "authentication"); return -1; } buffer_init(&krlbuf); if (!key_load_file(fd, path, &krlbuf)) { close(fd); buffer_free(&krlbuf); error("Revoked keys file not readable - refusing public key " "authentication"); return -1; } close(fd); if (ssh_krl_from_blob(&krlbuf, &krl, NULL, 0) != 0) { buffer_free(&krlbuf); error("Invalid KRL, refusing public key " "authentication"); return -1; } buffer_free(&krlbuf); if (krl == NULL) { debug3("%s: %s is not a KRL file", __func__, path); return -2; } debug2("%s: checking KRL %s", __func__, path); revoked = ssh_krl_check_key(krl, key) != 0; ssh_krl_free(krl); return revoked ? -1 : 0; }