summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDamien Miller <djm@mindrot.org>2013-01-18 11:44:04 +1100
committerDamien Miller <djm@mindrot.org>2013-01-18 11:44:04 +1100
commitf3747bf4014a450c9aaf1d88b010f6e579d10072 (patch)
tree0b1e1b497da13eb815e16a0f43be09e873e6a243
parentb26699bbadaffa1b1de2f6b0e175b77aba337de5 (diff)
- 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@
-rw-r--r--ChangeLog11
-rw-r--r--Makefile.in4
-rw-r--r--PROTOCOL.krl164
-rw-r--r--auth.c15
-rw-r--r--key.c40
-rw-r--r--key.h6
-rw-r--r--krl.c1227
-rw-r--r--krl.h63
-rw-r--r--ssh-keygen.1118
-rw-r--r--ssh-keygen.c257
-rw-r--r--sshd_config.513
11 files changed, 1884 insertions, 34 deletions
diff --git a/ChangeLog b/ChangeLog
index 686fe896..65403d6e 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 8765b7ef..74eeab57 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 00000000..e8caa452
--- /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 f5e2d3d2..d978f027 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 7e909970..4cc5c5d3 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 <ylo@cs.hut.fi>, 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;
@@ -1646,6 +1650,12 @@ key_to_blob(const Key *key, u_char **blobp, u_int *lenp)
}
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,
u_char **sigp, u_int *lenp,
@@ -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 39e5577f..ebdf4567 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 00000000..48505702
--- /dev/null
+++ b/krl.c
@@ -0,0 +1,1227 @@
+/*
+ * Copyright (c) 2012 Damien Miller <djm@mindrot.org>
+ *
+ * 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 <sys/types.h>
+#include <sys/param.h>
+#include <sys/tree.h>
+#include <sys/queue.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#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(&sect);
+
+ /* 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(&sect, bitmap);
+ BN_free(bitmap);
+ bitmap = NULL;
+ break;
+ }
+ buffer_put_char(buf, state);
+ buffer_put_string(buf,
+ buffer_ptr(&sect), buffer_len(&sect));
+ }
+
+ /* 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(&sect);
+ 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(&sect, bitmap_start);