From 3ee50c5d9f95cd40df403cf1a91fdaffefa21cc5 Mon Sep 17 00:00:00 2001 From: Darren Tucker Date: Thu, 6 Sep 2012 21:18:11 +1000 Subject: - jmc@cvs.openbsd.org 2012/08/15 18:25:50 [ssh-keygen.1] a little more info on certificate validity; requested by Ross L Richardson, and provided by djm --- ssh-keygen.1 | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'ssh-keygen.1') diff --git a/ssh-keygen.1 b/ssh-keygen.1 index 03f927edf..1d5564640 100644 --- a/ssh-keygen.1 +++ b/ssh-keygen.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: ssh-keygen.1,v 1.109 2012/07/06 00:41:59 dtucker Exp $ +.\" $OpenBSD: ssh-keygen.1,v 1.110 2012/08/15 18:25:50 jmc Exp $ .\" .\" Author: Tatu Ylonen .\" Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -35,7 +35,7 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.Dd $Mdocdate: July 6 2012 $ +.Dd $Mdocdate: August 15 2012 $ .Dt SSH-KEYGEN 1 .Os .Sh NAME @@ -628,7 +628,9 @@ The option allows specification of certificate start and end times. A certificate that is presented at a time outside this range will not be considered valid. -By default, certificates have a maximum validity interval. +By default, certificates are valid from +.Ux +Epoch to the distant future. .Pp For certificates to be used for user or host authentication, the CA public key must be trusted by -- cgit v1.2.3 From f3747bf4014a450c9aaf1d88b010f6e579d10072 Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Fri, 18 Jan 2013 11:44:04 +1100 Subject: - djm@cvs.openbsd.org 2013/01/17 23:00:01 [auth.c key.c key.h ssh-keygen.1 ssh-keygen.c sshd_config.5] [krl.c krl.h PROTOCOL.krl] add support for Key Revocation Lists (KRLs). These are a compact way to represent lists of revoked keys and certificates, taking as little as a single bit of incremental cost to revoke a certificate by serial number. KRLs are loaded via the existing RevokedKeys sshd_config option. feedback and ok markus@ --- ChangeLog | 11 + Makefile.in | 4 +- PROTOCOL.krl | 164 ++++++++ auth.c | 15 +- key.c | 40 +- key.h | 6 +- krl.c | 1227 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ krl.h | 63 +++ ssh-keygen.1 | 118 +++++- ssh-keygen.c | 257 +++++++++++- sshd_config.5 | 13 +- 11 files changed, 1884 insertions(+), 34 deletions(-) create mode 100644 PROTOCOL.krl create mode 100644 krl.c create mode 100644 krl.h (limited to 'ssh-keygen.1') diff --git a/ChangeLog b/ChangeLog index 686fe8966..65403d6e7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +20130118 + - (djm) OpenBSD CVS Sync + - djm@cvs.openbsd.org 2013/01/17 23:00:01 + [auth.c key.c key.h ssh-keygen.1 ssh-keygen.c sshd_config.5] + [krl.c krl.h PROTOCOL.krl] + add support for Key Revocation Lists (KRLs). These are a compact way to + represent lists of revoked keys and certificates, taking as little as + a single bit of incremental cost to revoke a certificate by serial number. + KRLs are loaded via the existing RevokedKeys sshd_config option. + feedback and ok markus@ + 20130117 - (djm) [regress/cipher-speed.sh regress/integrity.sh regress/try-ciphers.sh] check for GCM support before testing GCM ciphers. diff --git a/Makefile.in b/Makefile.in index 8765b7efb..74eeab574 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,4 +1,4 @@ -# $Id: Makefile.in,v 1.329 2012/12/17 04:59:43 dtucker Exp $ +# $Id: Makefile.in,v 1.330 2013/01/18 00:44:04 djm Exp $ # uncomment if you run a non bourne compatable shell. Ie. csh #SHELL = @SH@ @@ -71,7 +71,7 @@ LIBSSH_OBJS=acss.o authfd.o authfile.o bufaux.o bufbn.o buffer.o \ monitor_fdpass.o rijndael.o ssh-dss.o ssh-ecdsa.o ssh-rsa.o dh.o \ kexdh.o kexgex.o kexdhc.o kexgexc.o bufec.o kexecdh.o kexecdhc.o \ msg.o progressmeter.o dns.o entropy.o gss-genr.o umac.o umac128.o \ - jpake.o schnorr.o ssh-pkcs11.o + jpake.o schnorr.o ssh-pkcs11.o krl.o SSHOBJS= ssh.o readconf.o clientloop.o sshtty.o \ sshconnect.o sshconnect1.o sshconnect2.o mux.o \ diff --git a/PROTOCOL.krl b/PROTOCOL.krl new file mode 100644 index 000000000..e8caa4527 --- /dev/null +++ b/PROTOCOL.krl @@ -0,0 +1,164 @@ +This describes the key/certificate revocation list format for OpenSSH. + +1. Overall format + +The KRL consists of a header and zero or more sections. The header is: + +#define KRL_MAGIC 0x5353484b524c0a00ULL /* "SSHKRL\n\0" */ +#define KRL_FORMAT_VERSION 1 + + uint64 KRL_MAGIC + uint32 KRL_FORMAT_VERSION + uint64 krl_version + uint64 generated_date + uint64 flags + string reserved + string comment + +Where "krl_version" is a version number that increases each time the KRL +is modified, "generated_date" is the time in seconds since 1970-01-01 +00:00:00 UTC that the KRL was generated, "comment" is an optional comment +and "reserved" an extension field whose contents are currently ignored. +No "flags" are currently defined. + +Following the header are zero or more sections, each consisting of: + + byte section_type + string section_data + +Where "section_type" indicates the type of the "section_data". An exception +to this is the KRL_SECTION_SIGNATURE section, that has a slightly different +format (see below). + +The available section types are: + +#define KRL_SECTION_CERTIFICATES 1 +#define KRL_SECTION_EXPLICIT_KEY 2 +#define KRL_SECTION_FINGERPRINT_SHA1 3 +#define KRL_SECTION_SIGNATURE 4 + +3. Certificate serial section + +These sections use type KRL_SECTION_CERTIFICATES to revoke certificates by +serial number or key ID. The consist of the CA key that issued the +certificates to be revoked and a reserved field whose contents is currently +ignored. + + string ca_key + string reserved + +Followed by one or more sections: + + byte cert_section_type + string cert_section_data + +The certificate section types are: + +#define KRL_SECTION_CERT_SERIAL_LIST 0x20 +#define KRL_SECTION_CERT_SERIAL_RANGE 0x21 +#define KRL_SECTION_CERT_SERIAL_BITMAP 0x22 +#define KRL_SECTION_CERT_KEY_ID 0x23 + +2.1 Certificate serial list section + +This section is identified as KRL_SECTION_CERT_SERIAL_LIST. It revokes +certificates by listing their serial numbers. The cert_section_data in this +case contains: + + uint64 revoked_cert_serial + uint64 ... + +This section may appear multiple times. + +2.2. Certificate serial range section + +These sections use type KRL_SECTION_CERT_SERIAL_RANGE and hold +a range of serial numbers of certificates: + + uint64 serial_min + uint64 serial_max + +All certificates in the range serial_min <= serial <= serial_max are +revoked. + +This section may appear multiple times. + +2.3. Certificate serial bitmap section + +Bitmap sections use type KRL_SECTION_CERT_SERIAL_BITMAP and revoke keys +by listing their serial number in a bitmap. + + uint64 serial_offset + mpint revoked_keys_bitmap + +A bit set at index N in the bitmap corresponds to revocation of a keys with +serial number (serial_offset + N). + +This section may appear multiple times. + +2.4. Revoked key ID sections + +KRL_SECTION_CERT_KEY_ID sections revoke particular certificate "key +ID" strings. This may be useful in revoking all certificates +associated with a particular identity, e.g. a host or a user. + + string key_id[0] + ... + +This section must contain at least one "key_id". This section may appear +multiple times. + +3. Explicit key sections + +These sections, identified as KRL_SECTION_EXPLICIT_KEY, revoke keys +(not certificates). They are less space efficient than serial numbers, +but are able to revoke plain keys. + + string public_key_blob[0] + .... + +This section must contain at least one "public_key_blob". The blob +must be a raw key (i.e. not a certificate). + +This section may appear multiple times. + +4. SHA1 fingerprint sections + +These sections, identified as KRL_SECTION_FINGERPRINT_SHA1, revoke +plain keys (i.e. not certificates) by listing their SHA1 hashes: + + string public_key_hash[0] + .... + +This section must contain at least one "public_key_hash". The hash blob +is obtained by taking the SHA1 hash of the public key blob. Hashes in +this section must appear in numeric order, treating each hash as a big- +endian integer. + +This section may appear multiple times. + +5. KRL signature sections + +The KRL_SECTION_SIGNATURE section serves a different purpose to the +preceeding ones: to provide cryptographic authentication of a KRL that +is retrieved over a channel that does not provide integrity protection. +Its format is slightly different to the previously-described sections: +in order to simplify the signature generation, it includes as a "body" +two string components instead of one. + + byte KRL_SECTION_SIGNATURE + string signature_key + string signature + +The signature is calculated over the entire KRL from the KRL_MAGIC +to this subsection's "signature_key", including both and using the +signature generation rules appropriate for the type of "signature_key". + +This section must appear last in the KRL. If multiple signature sections +appear, they must appear consecutively at the end of the KRL file. + +Implementations that retrieve KRLs over untrusted channels must verify +signatures. Signature sections are optional for KRLs distributed by +trusted means. + +$OpenBSD: PROTOCOL.krl,v 1.2 2013/01/18 00:24:58 djm Exp $ diff --git a/auth.c b/auth.c index f5e2d3d2e..d978f0271 100644 --- a/auth.c +++ b/auth.c @@ -1,4 +1,4 @@ -/* $OpenBSD: auth.c,v 1.99 2012/12/14 05:26:43 dtucker Exp $ */ +/* $OpenBSD: auth.c,v 1.100 2013/01/17 23:00:01 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * @@ -71,6 +71,7 @@ #endif #include "authfile.h" #include "monitor_wrap.h" +#include "krl.h" /* import */ extern ServerOptions options; @@ -640,7 +641,16 @@ auth_key_is_revoked(Key *key) if (options.revoked_keys_file == NULL) return 0; - + switch (ssh_krl_file_contains_key(options.revoked_keys_file, key)) { + case 0: + return 0; /* Not revoked */ + case -2: + break; /* Not a KRL */ + default: + goto revoked; + } + debug3("%s: treating %s as a key list", __func__, + options.revoked_keys_file); switch (key_in_file(key, options.revoked_keys_file, 0)) { case 0: /* key not revoked */ @@ -651,6 +661,7 @@ auth_key_is_revoked(Key *key) "authentication"); return 1; case 1: + revoked: /* Key revoked */ key_fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX); error("WARNING: authentication attempt with a revoked " diff --git a/key.c b/key.c index 7e9099703..4cc5c5d35 100644 --- a/key.c +++ b/key.c @@ -1,4 +1,4 @@ -/* $OpenBSD: key.c,v 1.99 2012/05/23 03:28:28 djm Exp $ */ +/* $OpenBSD: key.c,v 1.100 2013/01/17 23:00:01 djm Exp $ */ /* * read_bignum(): * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -55,6 +55,8 @@ #include "misc.h" #include "ssh2.h" +static int to_blob(const Key *, u_char **, u_int *, int); + static struct KeyCert * cert_new(void) { @@ -324,14 +326,15 @@ key_equal(const Key *a, const Key *b) } u_char* -key_fingerprint_raw(Key *k, enum fp_type dgst_type, u_int *dgst_raw_length) +key_fingerprint_raw(const Key *k, enum fp_type dgst_type, + u_int *dgst_raw_length) { const EVP_MD *md = NULL; EVP_MD_CTX ctx; u_char *blob = NULL; u_char *retval = NULL; u_int len = 0; - int nlen, elen, otype; + int nlen, elen; *dgst_raw_length = 0; @@ -371,10 +374,7 @@ key_fingerprint_raw(Key *k, enum fp_type dgst_type, u_int *dgst_raw_length) case KEY_ECDSA_CERT: case KEY_RSA_CERT: /* We want a fingerprint of the _key_ not of the cert */ - otype = k->type; - k->type = key_type_plain(k->type); - key_to_blob(k, &blob, &len); - k->type = otype; + to_blob(k, &blob, &len, 1); break; case KEY_UNSPEC: return retval; @@ -1587,18 +1587,19 @@ key_from_blob(const u_char *blob, u_int blen) return key; } -int -key_to_blob(const Key *key, u_char **blobp, u_int *lenp) +static int +to_blob(const Key *key, u_char **blobp, u_int *lenp, int force_plain) { Buffer b; - int len; + int len, type; if (key == NULL) { error("key_to_blob: key == NULL"); return 0; } buffer_init(&b); - switch (key->type) { + type = force_plain ? key_type_plain(key->type) : key->type; + switch (type) { case KEY_DSA_CERT_V00: case KEY_RSA_CERT_V00: case KEY_DSA_CERT: @@ -1609,7 +1610,8 @@ key_to_blob(const Key *key, u_char **blobp, u_int *lenp) buffer_len(&key->cert->certblob)); break; case KEY_DSA: - buffer_put_cstring(&b, key_ssh_name(key)); + buffer_put_cstring(&b, + key_ssh_name_from_type_nid(type, key->ecdsa_nid)); buffer_put_bignum2(&b, key->dsa->p); buffer_put_bignum2(&b, key->dsa->q); buffer_put_bignum2(&b, key->dsa->g); @@ -1617,14 +1619,16 @@ key_to_blob(const Key *key, u_char **blobp, u_int *lenp) break; #ifdef OPENSSL_HAS_ECC case KEY_ECDSA: - buffer_put_cstring(&b, key_ssh_name(key)); + buffer_put_cstring(&b, + key_ssh_name_from_type_nid(type, key->ecdsa_nid)); buffer_put_cstring(&b, key_curve_nid_to_name(key->ecdsa_nid)); buffer_put_ecpoint(&b, EC_KEY_get0_group(key->ecdsa), EC_KEY_get0_public_key(key->ecdsa)); break; #endif case KEY_RSA: - buffer_put_cstring(&b, key_ssh_name(key)); + buffer_put_cstring(&b, + key_ssh_name_from_type_nid(type, key->ecdsa_nid)); buffer_put_bignum2(&b, key->rsa->e); buffer_put_bignum2(&b, key->rsa->n); break; @@ -1645,6 +1649,12 @@ key_to_blob(const Key *key, u_char **blobp, u_int *lenp) return len; } +int +key_to_blob(const Key *key, u_char **blobp, u_int *lenp) +{ + return to_blob(key, blobp, lenp, 0); +} + int key_sign( const Key *key, @@ -2024,7 +2034,7 @@ key_cert_check_authority(const Key *k, int want_host, int require_principal, } int -key_cert_is_legacy(Key *k) +key_cert_is_legacy(const Key *k) { switch (k->type) { case KEY_DSA_CERT_V00: diff --git a/key.h b/key.h index 39e5577f6..ebdf45677 100644 --- a/key.h +++ b/key.h @@ -1,4 +1,4 @@ -/* $OpenBSD: key.h,v 1.34 2012/05/23 03:28:28 djm Exp $ */ +/* $OpenBSD: key.h,v 1.35 2013/01/17 23:00:01 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. @@ -96,7 +96,7 @@ Key *key_demote(const Key *); int key_equal_public(const Key *, const Key *); int key_equal(const Key *, const Key *); char *key_fingerprint(Key *, enum fp_type, enum fp_rep); -u_char *key_fingerprint_raw(Key *, enum fp_type, u_int *); +u_char *key_fingerprint_raw(const Key *, enum fp_type, u_int *); const char *key_type(const Key *); const char *key_cert_type(const Key *); int key_write(const Key *, FILE *); @@ -114,7 +114,7 @@ int key_certify(Key *, Key *); void key_cert_copy(const Key *, struct Key *); int key_cert_check_authority(const Key *, int, int, const char *, const char **); -int key_cert_is_legacy(Key *); +int key_cert_is_legacy(const Key *); int key_ecdsa_nid_from_name(const char *); int key_curve_name_to_nid(const char *); diff --git a/krl.c b/krl.c new file mode 100644 index 000000000..485057029 --- /dev/null +++ b/krl.c @@ -0,0 +1,1227 @@ +/* + * 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.2 2013/01/18 00:24:58 djm Exp $ */ + +#include "includes.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "buffer.h" +#include "key.h" +#include "authfile.h" +#include "err.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)); + bzero(&rs, 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(ers); + 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__, contig, last_gap, next_gap, final, + cost_list, cost_range, cost_bitmap, 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__, + rs->lo, 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 = rs->lo; 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, *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, *blob; + u_int i, j, sig_off, sects_off, blen, format_version, 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", 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 */ + 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 ((blob = buffer_get_string_ret(§, + &blen)) == NULL) { + error("%s: buffer error", __func__); + goto out; + } + if (type == KRL_SECTION_FINGERPRINT_SHA1 && + blen != 20) { + error("%s: bad SHA1 length", __func__); + goto out; + } + if (revoke_blob( + type == KRL_SECTION_EXPLICIT_KEY ? + &krl->revoked_keys : &krl->revoked_sha1s, + blob, blen) != 0) + goto out; /* 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); + 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 */ + bzero(&rb, 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 */ + bzero(&rb, 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 */ + bzero(&rki, 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 */ + if (key_cert_is_legacy(key)) + return 0; + + bzero(&rs, 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; +} diff --git a/krl.h b/krl.h new file mode 100644 index 000000000..2c43f5bb2 --- /dev/null +++ b/krl.h @@ -0,0 +1,63 @@ +/* + * 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.h,v 1.2 2013/01/18 00:24:58 djm Exp $ */ + +#ifndef _KRL_H +#define _KRL_H + +/* Functions to manage key revocation lists */ + +#define KRL_MAGIC "SSHKRL\n\0" +#define KRL_FORMAT_VERSION 1 + +/* KRL section types */ +#define KRL_SECTION_CERTIFICATES 1 +#define KRL_SECTION_EXPLICIT_KEY 2 +#define KRL_SECTION_FINGERPRINT_SHA1 3 +#define KRL_SECTION_SIGNATURE 4 + +/* KRL_SECTION_CERTIFICATES subsection types */ +#define KRL_SECTION_CERT_SERIAL_LIST 0x20 +#define KRL_SECTION_CERT_SERIAL_RANGE 0x21 +#define KRL_SECTION_CERT_SERIAL_BITMAP 0x22 +#define KRL_SECTION_CERT_KEY_ID 0x23 + +struct ssh_krl; + +struct ssh_krl *ssh_krl_init(void); +void ssh_krl_free(struct ssh_krl *krl); +void ssh_krl_set_version(struct ssh_krl *krl, u_int64_t version); +void ssh_krl_set_sign_key(struct ssh_krl *krl, const Key *sign_key); +void ssh_krl_set_comment(struct ssh_krl *krl, const char *comment); +int ssh_krl_revoke_cert_by_serial(struct ssh_krl *krl, const Key *ca_key, + u_int64_t 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); +int ssh_krl_revoke_cert_by_key_id(struct ssh_krl *krl, const Key *ca_key, + const char *key_id); +int ssh_krl_revoke_key_explicit(struct ssh_krl *krl, const Key *key); +int ssh_krl_revoke_key_sha1(struct ssh_krl *krl, const Key *key); +int ssh_krl_revoke_key(struct ssh_krl *krl, const Key *key); +int ssh_krl_to_blob(struct ssh_krl *krl, Buffer *buf, const Key **sign_keys, + u_int nsign_keys); +int ssh_krl_from_blob(Buffer *buf, struct ssh_krl **krlp, + const Key **sign_ca_keys, u_int nsign_ca_keys); +int ssh_krl_check_key(struct ssh_krl *krl, const Key *key); +int ssh_krl_file_contains_key(const char *path, const Key *key); + +#endif /* _KRL_H */ + diff --git a/ssh-keygen.1 b/ssh-keygen.1 index 1d5564640..52f4b6ea6 100644 --- a/ssh-keygen.1 +++ b/ssh-keygen.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: ssh-keygen.1,v 1.110 2012/08/15 18:25:50 jmc Exp $ +.\" $OpenBSD: ssh-keygen.1,v 1.111 2013/01/17 23:00:01 djm Exp $ .\" .\" Author: Tatu Ylonen .\" Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -35,7 +35,7 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.Dd $Mdocdate: August 15 2012 $ +.Dd $Mdocdate: January 17 2013 $ .Dt SSH-KEYGEN 1 .Os .Sh NAME @@ -122,6 +122,17 @@ .Op Fl f Ar input_keyfile .Nm ssh-keygen .Fl A +.Nm ssh-keygen +.Fl k +.Fl f Ar krl_file +.Op Fl u +.Op Fl s ca_public +.Op Fl z version_number +.Ar +.Nm ssh-keygen +.Fl Q +.Fl f Ar krl_file +.Ar .Ek .Sh DESCRIPTION .Nm @@ -144,6 +155,13 @@ See the .Sx MODULI GENERATION section for details. .Pp +Finally, +.Nm +can be used to generate and update Key Revocation Lists, and to test whether +given keys have been revoked by one. See the +.Sx KEY REVOCATION LISTS +section for details. +.Pp Normally each user wishing to use SSH with public key authentication runs this once to create the authentication key in @@ -321,6 +339,17 @@ This option allows importing keys from other software, including several commercial SSH implementations. The default import format is .Dq RFC4716 . +.It Fl k +Generate a KRL file. +In this mode, +.Nm +will generate a KRL file at the location specified via the +.Fl f +flag that revokes every key or certificate presented on the command-line. +Keys/certificates to be revoked may be specified by public key file or +using the format described in the +.Sx KEY REVOCATION LISTS +section. .It Fl L Prints the contents of a certificate. .It Fl l @@ -448,6 +477,14 @@ Certify (sign) a public key using the specified CA key. Please see the .Sx CERTIFICATES section for details. +.Pp +When generating a KRL, +.Fl s +specifies a path to a CA public key file used to revoke certificated directly +by key ID or serial number. +See the +.Sx KEY REVOCATION LISTS +section for details. .It Fl T Ar output_file Test DH group exchange candidate primes (generated using the .Fl G @@ -485,6 +522,12 @@ For example: (valid from 12:30 PM, January 1st, 2010 to 12:30 PM, January 1st, 2011), .Dq -1d:20110101 (valid from yesterday to midnight, January 1st, 2011). +.It Fl u +Update a KRL. +When specified with +.Fl k , +keys listed via the command-line are added to the existing KRL rather than +a new KRL being created. .It Fl v Verbose mode. Causes @@ -504,6 +547,10 @@ OpenSSH format file and print an OpenSSH public key to stdout. Specifies a serial number to be embedded in the certificate to distinguish this certificate from others from the same CA. The default serial number is zero. +.Pp +When generating a KRL, the +.Fl z +flag is used to specify a KRL version number. .El .Sh MODULI GENERATION .Nm @@ -638,6 +685,73 @@ public key must be trusted by or .Xr ssh 1 . Please refer to those manual pages for details. +.Sh KEY REVOCATION LISTS +.Nm +is able to manage OpenSSH format Key Revocation Lists (KRLs). +These binary files specify keys or certificates to be revoked using a +compact format; taking as little a one bit per certificate if they are being +revoked by serial number. +.Pp +KRLs may be generated using the +.Fl k +flag. +This option reads one or more files from the command-line and generates a new +KRL. +The files may either contain a KRL specification (see below) or public keys, +listed one per line. +Plain public keys are revoked by listing their hash or contents in the KRL and +certificates revoked by serial number or key ID (if the serial is zero or +not available). +.Pp +Revoking keys using a KRL specification offers explicit control over the +types of record used to revoke keys and may be used to directly revoke +certificates by serial number or key ID without having the complete original +certificate on hand. +A KRL specification consists of lines containing one of the following directives +followed by a colon and some directive-specific information. +.Bl -tag -width Ds +.It Cm serial : Ar serial_number Op -serial_number +Revokes a certificate with the specified serial number. +Serial numbers are 64 bit values, not including zero and may be expressed +in decimal, hex or octal. +If two serial numbers are specified separated by a hyphen, then the range +of serial numbers including and between each is revoked. +The CA key must have been specified on the +.Nm +command-line using the +.Fl s +option. +.It Cm id : Ar key_id +Revokes a certificate with the specified key ID string. +The CA key must have been specified on the +.Nm +command-line using the +.Fl s +option. +.It Cm key : Ar public_key +Revokes the specified key. +In a certificate is listed, then it is revoked as a plain public key. +.It Cm sha1 : Ar public_key +Revokes the specified key by its SHA1 hash. +.El +.Pp +KRLs may be updated using the +.Fl u +flag in addition to +.Fl k . +When this option is specified, keys listed via the command-line are merged into +the KRL, adding to those already there. +.Pp +It is also possible, given a KRL, to test whether it revokes a particular key +(or keys). +The +.Fl Q +flag will query an existing KRL, testing each key specified on the commandline. +If any key listed on the command-line has been revoked (or an error encountered) +then +.Nm +will exit with a non-zero exit status. +A zero exit status will only be returned if no key was revoked. .Sh FILES .Bl -tag -width Ds -compact .It Pa ~/.ssh/identity diff --git a/ssh-keygen.c b/ssh-keygen.c index a19a2b085..861b04e2d 100644 --- a/ssh-keygen.c +++ b/ssh-keygen.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-keygen.c,v 1.222 2013/01/09 05:40:17 djm Exp $ */ +/* $OpenBSD: ssh-keygen.c,v 1.223 2013/01/17 23:00:01 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1994 Tatu Ylonen , Espoo, Finland @@ -48,8 +48,11 @@ #include "match.h" #include "hostfile.h" #include "dns.h" +#include "ssh.h" #include "ssh2.h" #include "ssh-pkcs11.h" +#include "atomicio.h" +#include "krl.h" /* Number of bits in the RSA/DSA key. This value can be set on the command line. */ #define DEFAULT_BITS 2048 @@ -1896,6 +1899,226 @@ do_show_cert(struct passwd *pw) exit(0); } +static void +load_krl(const char *path, struct ssh_krl **krlp) +{ + Buffer krlbuf; + int fd; + + buffer_init(&krlbuf); + if ((fd = open(path, O_RDONLY)) == -1) + fatal("open %s: %s", path, strerror(errno)); + if (!key_load_file(fd, path, &krlbuf)) + fatal("Unable to load KRL"); + close(fd); + /* XXX check sigs */ + if (ssh_krl_from_blob(&krlbuf, krlp, NULL, 0) != 0 || + *krlp == NULL) + fatal("Invalid KRL file"); + buffer_free(&krlbuf); +} + +static void +update_krl_from_file(struct passwd *pw, const char *file, const Key *ca, + struct ssh_krl *krl) +{ + Key *key = NULL; + u_long lnum = 0; + char *path, *cp, *ep, line[SSH_MAX_PUBKEY_BYTES]; + unsigned long long serial, serial2; + int i, was_explicit_key, was_sha1, r; + FILE *krl_spec; + + path = tilde_expand_filename(file, pw->pw_uid); + if (strcmp(path, "-") == 0) { + krl_spec = stdin; + free(path); + path = xstrdup("(standard input)"); + } else if ((krl_spec = fopen(path, "r")) == NULL) + fatal("fopen %s: %s", path, strerror(errno)); + + if (!quiet) + printf("Revoking from %s\n", path); + while (read_keyfile_line(krl_spec, path, line, sizeof(line), + &lnum) == 0) { + was_explicit_key = was_sha1 = 0; + cp = line + strspn(line, " \t"); + /* Trim trailing space, comments and strip \n */ + for (i = 0, r = -1; cp[i] != '\0'; i++) { + if (cp[i] == '#' || cp[i] == '\n') { + cp[i] = '\0'; + break; + } + if (cp[i] == ' ' || cp[i] == '\t') { + /* Remember the start of a span of whitespace */ + if (r == -1) + r = i; + } else + r = -1; + } + if (r != -1) + cp[r] = '\0'; + if (*cp == '\0') + continue; + if (strncasecmp(cp, "serial:", 7) == 0) { + if (ca == NULL) { + fatal("revoking certificated by serial number " + "requires specification of a CA key"); + } + cp += 7; + cp = cp + strspn(cp, " \t"); + errno = 0; + serial = strtoull(cp, &ep, 0); + if (*cp == '\0' || (*ep != '\0' && *ep != '-')) + fatal("%s:%lu: invalid serial \"%s\"", + path, lnum, cp); + if (errno == ERANGE && serial == ULLONG_MAX) + fatal("%s:%lu: serial out of range", + path, lnum); + serial2 = serial; + if (*ep == '-') { + cp = ep + 1; + errno = 0; + serial2 = strtoull(cp, &ep, 0); + if (*cp == '\0' || *ep != '\0') + fatal("%s:%lu: invalid serial \"%s\"", + path, lnum, cp); + if (errno == ERANGE && serial2 == ULLONG_MAX) + fatal("%s:%lu: serial out of range", + path, lnum); + if (serial2 <= serial) + fatal("%s:%lu: invalid serial range " + "%llu:%llu", path, lnum, + (unsigned long long)serial, + (unsigned long long)serial2); + } + if (ssh_krl_revoke_cert_by_serial_range(krl, + ca, serial, serial2) != 0) { + fatal("%s: revoke serial failed", + __func__); + } + } else if (strncasecmp(cp, "id:", 3) == 0) { + if (ca == NULL) { + fatal("revoking certificated by key ID " + "requires specification of a CA key"); + } + cp += 3; + cp = cp + strspn(cp, " \t"); + if (ssh_krl_revoke_cert_by_key_id(krl, ca, cp) != 0) + fatal("%s: revoke key ID failed", __func__); + } else { + if (strncasecmp(cp, "key:", 4) == 0) { + cp += 4; + cp = cp + strspn(cp, " \t"); + was_explicit_key = 1; + } else if (strncasecmp(cp, "sha1:", 5) == 0) { + cp += 5; + cp = cp + strspn(cp, " \t"); + was_sha1 = 1; + } else { + /* + * Just try to process the line as a key. + * Parsing will fail if it isn't. + */ + } + if ((key = key_new(KEY_UNSPEC)) == NULL) + fatal("key_new"); + if (key_read(key, &cp) != 1) + fatal("%s:%lu: invalid key", path, lnum); + if (was_explicit_key) + r = ssh_krl_revoke_key_explicit(krl, key); + else if (was_sha1) + r = ssh_krl_revoke_key_sha1(krl, key); + else + r = ssh_krl_revoke_key(krl, key); + if (r != 0) + fatal("%s: revoke key failed", __func__); + key_free(key); + } + } + if (strcmp(path, "-") != 0) + fclose(krl_spec); +} + +static void +do_gen_krl(struct passwd *pw, int updating, int argc, char **argv) +{ + struct ssh_krl *krl; + struct stat sb; + Key *ca = NULL; + int fd, i; + char *tmp; + Buffer kbuf; + + if (*identity_file == '\0') + fatal("KRL generation requires an output file"); + if (stat(identity_file, &sb) == -1) { + if (errno != ENOENT) + fatal("Cannot access KRL \"%s\": %s", + identity_file, strerror(errno)); + if (updating) + fatal("KRL \"%s\" does not exist", identity_file); + } + if (ca_key_path != NULL) { + tmp = tilde_expand_filename(ca_key_path, pw->pw_uid); + if ((ca = key_load_public(tmp, NULL)) == NULL) + fatal("Cannot load CA public key %s", tmp); + xfree(tmp); + } + + if (updating) + load_krl(identity_file, &krl); + else if ((krl = ssh_krl_init()) == NULL) + fatal("couldn't create KRL"); + + if (cert_serial != 0) + ssh_krl_set_version(krl, cert_serial); + if (identity_comment != NULL) + ssh_krl_set_comment(krl, identity_comment); + + for (i = 0; i < argc; i++) + update_krl_from_file(pw, argv[i], ca, krl); + + buffer_init(&kbuf); + if (ssh_krl_to_blob(krl, &kbuf, NULL, 0) != 0) + fatal("Couldn't generate KRL"); + if ((fd = open(identity_file, O_WRONLY|O_CREAT|O_TRUNC, 0644)) == -1) + fatal("open %s: %s", identity_file, strerror(errno)); + if (atomicio(vwrite, fd, buffer_ptr(&kbuf), buffer_len(&kbuf)) != + buffer_len(&kbuf)) + fatal("write %s: %s", identity_file, strerror(errno)); + close(fd); + buffer_free(&kbuf); + ssh_krl_free(krl); +} + +static void +do_check_krl(struct passwd *pw, int argc, char **argv) +{ + int i, r, ret = 0; + char *comment; + struct ssh_krl *krl; + Key *k; + + if (*identity_file == '\0') + fatal("KRL checking requires an input file"); + load_krl(identity_file, &krl); + for (i = 0; i < argc; i++) { + if ((k = key_load_public(argv[i], &comment)) == NULL) + fatal("Cannot load public key %s", argv[i]); + r = ssh_krl_check_key(krl, k); + printf("%s%s%s%s: %s\n", argv[i], + *comment ? " (" : "", comment, *comment ? ")" : "", + r == 0 ? "ok" : "REVOKED"); + if (r != 0) + ret = 1; + key_free(k); + free(comment); + } + ssh_krl_free(krl); + exit(ret); +} + static void usage(void) { @@ -1922,6 +2145,7 @@ usage(void) fprintf(stderr, " -J number Screen this number of moduli lines.\n"); fprintf(stderr, " -j number Start screening moduli at specified line.\n"); fprintf(stderr, " -K checkpt Write checkpoints to this file.\n"); + fprintf(stderr, " -k Generate a KRL file.\n"); fprintf(stderr, " -L Print the contents of a certificate.\n"); fprintf(stderr, " -l Show fingerprint of key file.\n"); fprintf(stderr, " -M memory Amount of memory (MB) to use for generating DH-GEX moduli.\n"); @@ -1931,6 +2155,7 @@ usage(void) fprintf(stderr, " -O option Specify a certificate option.\n"); fprintf(stderr, " -P phrase Provide old passphrase.\n"); fprintf(stderr, " -p Change passphrase of private key file.\n"); + fprintf(stderr, " -Q Test whether key(s) are revoked in KRL.\n"); fprintf(stderr, " -q Quiet.\n"); fprintf(stderr, " -R hostname Remove host from known_hosts file.\n"); fprintf(stderr, " -r hostname Print DNS resource record.\n"); @@ -1939,6 +2164,7 @@ usage(void) fprintf(stderr, " -T file Screen candidates for DH-GEX moduli.\n"); fprintf(stderr, " -t type Specify type of key to create.\n"); fprintf(stderr, " -V from:to Specify certificate validity interval.\n"); + fprintf(stderr, " -u Update KRL rather than creating a new one.\n"); fprintf(stderr, " -v Verbose.\n"); fprintf(stderr, " -W gen Generator to use for generating DH-GEX moduli.\n"); fprintf(stderr, " -y Read private key file and print public key.\n"); @@ -1955,14 +2181,14 @@ main(int argc, char **argv) { char dotsshdir[MAXPATHLEN], comment[1024], *passphrase1, *passphrase2; char *checkpoint = NULL; - char out_file[MAXPATHLEN], *rr_hostname = NULL, *ep; + char out_file[MAXPATHLEN], *ep, *rr_hostname = NULL; Key *private, *public; struct passwd *pw; struct stat st; int opt, type, fd; u_int32_t memory = 0, generator_wanted = 0, trials = 100; int do_gen_candidates = 0, do_screen_candidates = 0; - int gen_all_hostkeys = 0; + int gen_all_hostkeys = 0, gen_krl = 0, update_krl = 0, check_krl = 0; unsigned long start_lineno = 0, lines_to_process = 0; BIGNUM *start = NULL; FILE *f; @@ -1992,8 +2218,8 @@ main(int argc, char **argv) exit(1); } - while ((opt = getopt(argc, argv, "AegiqpclBHLhvxXyF:b:f:t:D:I:J:j:K:P:" - "m:N:n:O:C:r:g:R:T:G:M:S:s:a:V:W:z:")) != -1) { + while ((opt = getopt(argc, argv, "ABHLQXceghiklpquvxy" + "C:D:F:G:I:J:K:M:N:O:P:R:S:T:V:W:a:b:f:g:j:m:n:r:s:t:z:")) != -1) { switch (opt) { case 'A': gen_all_hostkeys = 1; @@ -2072,6 +2298,9 @@ main(int argc, char **argv) case 'N': identity_new_passphrase = optarg; break; + case 'Q': + check_krl = 1; + break; case 'O': add_cert_option(optarg); break; @@ -2090,6 +2319,9 @@ main(int argc, char **argv) cert_key_type = SSH2_CERT_TYPE_HOST; certflags_flags = 0; break; + case 'k': + gen_krl = 1; + break; case 'i': case 'X': /* import key */ @@ -2107,6 +2339,9 @@ main(int argc, char **argv) case 'D': pkcs11provider = optarg; break; + case 'u': + update_krl = 1; + break; case 'v': if (log_level == SYSLOG_LEVEL_INFO) log_level = SYSLOG_LEVEL_DEBUG1; @@ -2182,11 +2417,11 @@ main(int argc, char **argv) argc -= optind; if (ca_key_path != NULL) { - if (argc < 1) { + if (argc < 1 && !gen_krl) { printf("Too few arguments.\n"); usage(); } - } else if (argc > 0) { + } else if (argc > 0 && !gen_krl && !check_krl) { printf("Too many arguments.\n"); usage(); } @@ -2198,6 +2433,14 @@ main(int argc, char **argv) printf("Cannot use -l with -H or -R.\n"); usage(); } + if (gen_krl) { + do_gen_krl(pw, update_krl, argc, argv); + return (0); + } + if (check_krl) { + do_check_krl(pw, argc, argv); + return (0); + } if (ca_key_path != NULL) { if (cert_key_id == NULL) fatal("Must specify key id (-I) when certifying"); diff --git a/sshd_config.5 b/sshd_config.5 index e7bb0b55f..c8b814da6 100644 --- a/sshd_config.5 +++ b/sshd_config.5 @@ -33,8 +33,8 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.\" $OpenBSD: sshd_config.5,v 1.153 2013/01/08 18:49:04 markus Exp $ -.Dd $Mdocdate: January 8 2013 $ +.\" $OpenBSD: sshd_config.5,v 1.154 2013/01/17 23:00:01 djm Exp $ +.Dd $Mdocdate: January 17 2013 $ .Dt SSHD_CONFIG 5 .Os .Sh NAME @@ -994,10 +994,17 @@ The default is .Dq yes . Note that this option applies to protocol version 2 only. .It Cm RevokedKeys -Specifies a list of revoked public keys. +Specifies revoked public keys. Keys listed in this file will be refused for public key authentication. Note that if this file is not readable, then public key authentication will be refused for all users. +Keys may be specified as a text file, listing one public key per line, or as +an OpenSSH Key Revocation List (KRL) as generated by +.Xr ssh-keygen 1 +For more information on KRLs, see the +.Sx KEY REVOCATION LISTS +section in +.Xr ssh-keygen 1 . .It Cm RhostsRSAAuthentication Specifies whether rhosts or /etc/hosts.equiv authentication together with successful RSA host authentication is allowed. -- cgit v1.2.3 From ac5542b6b879636144cef61ea318ecf177518b50 Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Sun, 20 Jan 2013 22:33:02 +1100 Subject: - jmc@cvs.openbsd.org 2013/01/18 07:57:47 [ssh-keygen.1] tweak previous; --- ChangeLog | 4 ++++ ssh-keygen.1 | 31 ++++++++++++++++--------------- 2 files changed, 20 insertions(+), 15 deletions(-) (limited to 'ssh-keygen.1') diff --git a/ChangeLog b/ChangeLog index 9132fb7c5..e3a333d27 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,10 @@ - (djm) [cipher-aes.c cipher-ctr.c openbsd-compat/openssl-compat.h] Move prototypes for replacement ciphers to openssl-compat.h; fix EVP prototypes for openssl-1.0.0-fips. + - (djm) OpenBSD CVS Sync + - jmc@cvs.openbsd.org 2013/01/18 07:57:47 + [ssh-keygen.1] + tweak previous; 20130118 - (djm) OpenBSD CVS Sync diff --git a/ssh-keygen.1 b/ssh-keygen.1 index 52f4b6ea6..ac97678d3 100644 --- a/ssh-keygen.1 +++ b/ssh-keygen.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: ssh-keygen.1,v 1.111 2013/01/17 23:00:01 djm Exp $ +.\" $OpenBSD: ssh-keygen.1,v 1.112 2013/01/18 07:57:47 jmc Exp $ .\" .\" Author: Tatu Ylonen .\" Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -35,7 +35,7 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.Dd $Mdocdate: January 17 2013 $ +.Dd $Mdocdate: January 18 2013 $ .Dt SSH-KEYGEN 1 .Os .Sh NAME @@ -126,8 +126,8 @@ .Fl k .Fl f Ar krl_file .Op Fl u -.Op Fl s ca_public -.Op Fl z version_number +.Op Fl s Ar ca_public +.Op Fl z Ar version_number .Ar .Nm ssh-keygen .Fl Q @@ -158,7 +158,8 @@ section for details. Finally, .Nm can be used to generate and update Key Revocation Lists, and to test whether -given keys have been revoked by one. See the +given keys have been revoked by one. +See the .Sx KEY REVOCATION LISTS section for details. .Pp @@ -480,7 +481,7 @@ section for details. .Pp When generating a KRL, .Fl s -specifies a path to a CA public key file used to revoke certificated directly +specifies a path to a CA public key file used to revoke certificates directly by key ID or serial number. See the .Sx KEY REVOCATION LISTS @@ -499,6 +500,12 @@ for protocol version 1 and or .Dq rsa for protocol version 2. +.It Fl u +Update a KRL. +When specified with +.Fl k , +keys listed via the command-line are added to the existing KRL rather than +a new KRL being created. .It Fl V Ar validity_interval Specify a validity interval when signing a certificate. A validity interval may consist of a single time, indicating that the @@ -522,12 +529,6 @@ For example: (valid from 12:30 PM, January 1st, 2010 to 12:30 PM, January 1st, 2011), .Dq -1d:20110101 (valid from yesterday to midnight, January 1st, 2011). -.It Fl u -Update a KRL. -When specified with -.Fl k , -keys listed via the command-line are added to the existing KRL rather than -a new KRL being created. .It Fl v Verbose mode. Causes @@ -689,7 +690,7 @@ Please refer to those manual pages for details. .Nm is able to manage OpenSSH format Key Revocation Lists (KRLs). These binary files specify keys or certificates to be revoked using a -compact format; taking as little a one bit per certificate if they are being +compact format, taking as little a one bit per certificate if they are being revoked by serial number. .Pp KRLs may be generated using the @@ -712,7 +713,7 @@ followed by a colon and some directive-specific information. .Bl -tag -width Ds .It Cm serial : Ar serial_number Op -serial_number Revokes a certificate with the specified serial number. -Serial numbers are 64 bit values, not including zero and may be expressed +Serial numbers are 64-bit values, not including zero and may be expressed in decimal, hex or octal. If two serial numbers are specified separated by a hyphen, then the range of serial numbers including and between each is revoked. @@ -730,7 +731,7 @@ command-line using the option. .It Cm key : Ar public_key Revokes the specified key. -In a certificate is listed, then it is revoked as a plain public key. +If a certificate is listed, then it is revoked as a plain public key. .It Cm sha1 : Ar public_key Revokes the specified key by its SHA1 hash. .El -- cgit v1.2.3 From 072fdcd19821bbd36ee175475286fad17eaaa367 Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Sun, 20 Jan 2013 22:34:04 +1100 Subject: - jmc@cvs.openbsd.org 2013/01/18 08:39:04 [ssh-keygen.1] add -Q to the options list; ok djm --- ChangeLog | 3 +++ ssh-keygen.1 | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'ssh-keygen.1') diff --git a/ChangeLog b/ChangeLog index cc5c7f010..f9bdf2ab9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,6 +12,9 @@ - jmc@cvs.openbsd.org 2013/01/18 08:00:49 [sshd_config.5] tweak previous; + - jmc@cvs.openbsd.org 2013/01/18 08:39:04 + [ssh-keygen.1] + add -Q to the options list; ok djm 20130118 - (djm) OpenBSD CVS Sync diff --git a/ssh-keygen.1 b/ssh-keygen.1 index ac97678d3..ca0ab3537 100644 --- a/ssh-keygen.1 +++ b/ssh-keygen.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: ssh-keygen.1,v 1.112 2013/01/18 07:57:47 jmc Exp $ +.\" $OpenBSD: ssh-keygen.1,v 1.113 2013/01/18 08:39:04 jmc Exp $ .\" .\" Author: Tatu Ylonen .\" Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -455,6 +455,8 @@ creating a new private key. The program will prompt for the file containing the private key, for the old passphrase, and twice for the new passphrase. +.It Fl Q +Test whether keys have been revoked in a KRL. .It Fl q Silence .Nm ssh-keygen . -- cgit v1.2.3 From 881a7a2c5dc1d39b7e5f50e0d6c52d61b85491c3 Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Sun, 20 Jan 2013 22:34:46 +1100 Subject: - jmc@cvs.openbsd.org 2013/01/18 21:48:43 [ssh-keygen.1] command-line (adj.) -> command line (n.); --- ChangeLog | 3 +++ ssh-keygen.1 | 16 ++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) (limited to 'ssh-keygen.1') diff --git a/ChangeLog b/ChangeLog index f9bdf2ab9..1b05af704 100644 --- a/ChangeLog +++ b/ChangeLog @@ -15,6 +15,9 @@ - jmc@cvs.openbsd.org 2013/01/18 08:39:04 [ssh-keygen.1] add -Q to the options list; ok djm + - jmc@cvs.openbsd.org 2013/01/18 21:48:43 + [ssh-keygen.1] + command-line (adj.) -> command line (n.); 20130118 - (djm) OpenBSD CVS Sync diff --git a/ssh-keygen.1 b/ssh-keygen.1 index ca0ab3537..9d7a25cf4 100644 --- a/ssh-keygen.1 +++ b/ssh-keygen.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: ssh-keygen.1,v 1.113 2013/01/18 08:39:04 jmc Exp $ +.\" $OpenBSD: ssh-keygen.1,v 1.114 2013/01/18 21:48:43 jmc Exp $ .\" .\" Author: Tatu Ylonen .\" Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -346,7 +346,7 @@ In this mode, .Nm will generate a KRL file at the location specified via the .Fl f -flag that revokes every key or certificate presented on the command-line. +flag that revokes every key or certificate presented on the command line. Keys/certificates to be revoked may be specified by public key file or using the format described in the .Sx KEY REVOCATION LISTS @@ -506,7 +506,7 @@ for protocol version 2. Update a KRL. When specified with .Fl k , -keys listed via the command-line are added to the existing KRL rather than +keys listed via the command line are added to the existing KRL rather than a new KRL being created. .It Fl V Ar validity_interval Specify a validity interval when signing a certificate. @@ -698,7 +698,7 @@ revoked by serial number. KRLs may be generated using the .Fl k flag. -This option reads one or more files from the command-line and generates a new +This option reads one or more files from the command line and generates a new KRL. The files may either contain a KRL specification (see below) or public keys, listed one per line. @@ -721,14 +721,14 @@ If two serial numbers are specified separated by a hyphen, then the range of serial numbers including and between each is revoked. The CA key must have been specified on the .Nm -command-line using the +command line using the .Fl s option. .It Cm id : Ar key_id Revokes a certificate with the specified key ID string. The CA key must have been specified on the .Nm -command-line using the +command line using the .Fl s option. .It Cm key : Ar public_key @@ -742,7 +742,7 @@ KRLs may be updated using the .Fl u flag in addition to .Fl k . -When this option is specified, keys listed via the command-line are merged into +When this option is specified, keys listed via the command line are merged into the KRL, adding to those already there. .Pp It is also possible, given a KRL, to test whether it revokes a particular key @@ -750,7 +750,7 @@ It is also possible, given a KRL, to test whether it revokes a particular key The .Fl Q flag will query an existing KRL, testing each key specified on the commandline. -If any key listed on the command-line has been revoked (or an error encountered) +If any key listed on the command line has been revoked (or an error encountered) then .Nm will exit with a non-zero exit status. -- cgit v1.2.3 From a0a7ee8bf417c651f4f5bb8a8a8fb877c13011cd Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Sun, 20 Jan 2013 22:35:06 +1100 Subject: - jmc@cvs.openbsd.org 2013/01/19 07:13:25 [ssh-keygen.1] fix some formatting; ok djm --- ChangeLog | 3 +++ ssh-keygen.1 | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) (limited to 'ssh-keygen.1') diff --git a/ChangeLog b/ChangeLog index 1b05af704..518f441d6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18,6 +18,9 @@ - jmc@cvs.openbsd.org 2013/01/18 21:48:43 [ssh-keygen.1] command-line (adj.) -> command line (n.); + - jmc@cvs.openbsd.org 2013/01/19 07:13:25 + [ssh-keygen.1] + fix some formatting; ok djm 20130118 - (djm) OpenBSD CVS Sync diff --git a/ssh-keygen.1 b/ssh-keygen.1 index 9d7a25cf4..7da73e07c 100644 --- a/ssh-keygen.1 +++ b/ssh-keygen.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: ssh-keygen.1,v 1.114 2013/01/18 21:48:43 jmc Exp $ +.\" $OpenBSD: ssh-keygen.1,v 1.115 2013/01/19 07:13:25 jmc Exp $ .\" .\" Author: Tatu Ylonen .\" Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -35,7 +35,7 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.Dd $Mdocdate: January 18 2013 $ +.Dd $Mdocdate: January 19 2013 $ .Dt SSH-KEYGEN 1 .Os .Sh NAME @@ -713,7 +713,7 @@ certificate on hand. A KRL specification consists of lines containing one of the following directives followed by a colon and some directive-specific information. .Bl -tag -width Ds -.It Cm serial : Ar serial_number Op -serial_number +.It Cm serial : Ar serial_number Ns Op - Ns Ar serial_number Revokes a certificate with the specified serial number. Serial numbers are 64-bit values, not including zero and may be expressed in decimal, hex or octal. -- cgit v1.2.3