[openssh-commits] [openssh] 50/87: upstream: Add RBTREE index over keyblob for KRL CA sections

git+noreply at mindrot.org git+noreply at mindrot.org
Fri Oct 30 20:21:27 AEDT 2026


This is an automated email from the git hooks/post-receive script.

djm pushed a commit to branch master
in repository openssh.

commit 271edd1ee6898f6c12b242a06d0a5d8ac46d2520
Author: djm at openbsd.org <djm at openbsd.org>
AuthorDate: Fri Oct 30 05:33:18 2026 +0000

    upstream: Add RBTREE index over keyblob for KRL CA sections
    
    This avoid O(N^2) lookups which can grow pretty large when revoking many
    separate CA keys.
    
    Anthropic ANT-2026-L7T0QBTG
    
    ok tb@
    
    OpenBSD-Commit-ID: ac51ccb89cb26c9ee36b973a114220692052eecb
---
 krl.c | 142 +++++++++++++++++++++++++++++++++++++++++++++++-------------------
 1 file changed, 102 insertions(+), 40 deletions(-)

diff --git a/krl.c b/krl.c
index 9a3327752..7deac2218 100644
--- a/krl.c
+++ b/krl.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: krl.c,v 1.66 2026/10/17 11:47:42 djm Exp $ */
+/* $OpenBSD: krl.c,v 1.67 2026/10/30 05:33:18 djm Exp $ */
 /*
  * Copyright (c) 2012 Damien Miller <djm at mindrot.org>
  *
@@ -78,14 +78,23 @@ 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 */
+/*
+ * Tracks revoked certs for a single CA. These are held in a list, to preserve
+ * their order, and in a tree indexed by the serialised CA key for searching.
+ */
 struct revoked_certs {
 	struct sshkey *ca_key;
+	u_char *ca_blob;
+	size_t ca_blob_len;
 	struct revoked_serial_tree revoked_serials;
 	struct revoked_key_id_tree revoked_key_ids;
 	TAILQ_ENTRY(revoked_certs) entry;
+	RB_ENTRY(revoked_certs) tree_entry;
 };
 TAILQ_HEAD(revoked_certs_list, revoked_certs);
+static int ca_cmp(struct revoked_certs *a, struct revoked_certs *b);
+RB_HEAD(revoked_certs_tree, revoked_certs);
+RB_GENERATE_STATIC(revoked_certs_tree, revoked_certs, tree_entry, ca_cmp)
 
 struct ssh_krl {
 	uint64_t krl_version;
@@ -96,6 +105,7 @@ struct ssh_krl {
 	struct revoked_blob_tree revoked_sha1s;
 	struct revoked_blob_tree revoked_sha256s;
 	struct revoked_certs_list revoked_certs;
+	struct revoked_certs_tree revoked_certs_by_ca;
 };
 
 /* Return equal if a and b overlap */
@@ -126,6 +136,20 @@ blob_cmp(struct revoked_blob *a, struct revoked_blob *b)
 		return memcmp(a->blob, b->blob, a->len);
 }
 
+/* NB. the wildcard CA has a NULL, zero-length blob */
+static int
+ca_cmp(struct revoked_certs *a, struct revoked_certs *b)
+{
+	size_t len = MINIMUM(a->ca_blob_len, b->ca_blob_len);
+	int r;
+
+	if (len != 0 && (r = memcmp(a->ca_blob, b->ca_blob, len)) != 0)
+		return r;
+	if (a->ca_blob_len == b->ca_blob_len)
+		return 0;
+	return a->ca_blob_len > b->ca_blob_len ? 1 : -1;
+}
+
 struct ssh_krl *
 ssh_krl_init(void)
 {
@@ -137,6 +161,7 @@ ssh_krl_init(void)
 	RB_INIT(&krl->revoked_sha1s);
 	RB_INIT(&krl->revoked_sha256s);
 	TAILQ_INIT(&krl->revoked_certs);
+	RB_INIT(&krl->revoked_certs_by_ca);
 	return krl;
 }
 
@@ -158,6 +183,7 @@ revoked_certs_free(struct revoked_certs *rc)
 		free(rki);
 	}
 	sshkey_free(rc->ca_key);
+	free(rc->ca_blob);
 	freezero(rc, sizeof(*rc));
 }
 
@@ -188,6 +214,7 @@ ssh_krl_free(struct ssh_krl *krl)
 	}
 	TAILQ_FOREACH_SAFE(rc, &krl->revoked_certs, entry, trc) {
 		TAILQ_REMOVE(&krl->revoked_certs, rc, entry);
+		RB_REMOVE(revoked_certs_tree, &krl->revoked_certs_by_ca, rc);
 		revoked_certs_free(rc);
 	}
 	free(krl);
@@ -216,34 +243,43 @@ static int
 revoked_certs_for_ca_key(struct ssh_krl *krl, const struct sshkey *ca_key,
     struct revoked_certs **rcp, int allow_create)
 {
-	struct revoked_certs *rc;
+	struct revoked_certs find, *rc = NULL;
 	int r;
 
 	*rcp = NULL;
-	TAILQ_FOREACH(rc, &krl->revoked_certs, entry) {
-		if ((ca_key == NULL && rc->ca_key == NULL) ||
-		    sshkey_equal(rc->ca_key, ca_key)) {
-			*rcp = rc;
-			return 0;
-		}
+	memset(&find, 0, sizeof(find));
+	if (ca_key != NULL && (r = sshkey_to_blob(ca_key,
+	    &find.ca_blob, &find.ca_blob_len)) != 0)
+		return r;
+	if ((rc = RB_FIND(revoked_certs_tree, &krl->revoked_certs_by_ca,
+	    &find)) != NULL || !allow_create) {
+		r = 0;
+		goto out;
 	}
-	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 SSH_ERR_ALLOC_FAIL;
-	if (ca_key == NULL)
-		rc->ca_key = NULL;
-	else if ((r = sshkey_from_private(ca_key, &rc->ca_key)) != 0) {
+	/* If this CA doesn't exist in the tree then add it now */
+	if ((rc = calloc(1, sizeof(*rc))) == NULL) {
+		r = SSH_ERR_ALLOC_FAIL;
+		goto out;
+	}
+	if (ca_key != NULL &&
+	    (r = sshkey_from_private(ca_key, &rc->ca_key)) != 0) {
 		free(rc);
-		return r;
+		rc = NULL;
+		goto out;
 	}
+	rc->ca_blob = find.ca_blob;
+	rc->ca_blob_len = find.ca_blob_len;
+	find.ca_blob = NULL;
 	RB_INIT(&rc->revoked_serials);
 	RB_INIT(&rc->revoked_key_ids);
 	TAILQ_INSERT_TAIL(&krl->revoked_certs, rc, entry);
+	RB_INSERT(revoked_certs_tree, &krl->revoked_certs_by_ca, rc);
 	KRL_DBG(("new CA %s", ca_key == NULL ? "*" : sshkey_type(ca_key)));
+	r = 0;
+ out:
+	free(find.ca_blob);
 	*rcp = rc;
-	return 0;
+	return r;
 }
 
 static int
@@ -325,6 +361,25 @@ insert_serial_range(struct revoked_serial_tree *rt, uint64_t lo, uint64_t hi)
 	return 0;
 }
 
+/*
+ * Revoke a range of serials. If *rcp is NULL, then it will be set to the
+ * revoked_certs for ca_key, so callers making multiple revocations for the
+ * same CA need only search for it once.
+ */
+static int
+revoke_serial_range(struct ssh_krl *krl, const struct sshkey *ca_key,
+    struct revoked_certs **rcp, uint64_t lo, uint64_t hi)
+{
+	int r;
+
+	if (lo > hi || lo == 0)
+		return SSH_ERR_INVALID_ARGUMENT;
+	if (*rcp == NULL &&
+	    (r = revoked_certs_for_ca_key(krl, ca_key, rcp, 1)) != 0)
+		return r;
+	return insert_serial_range(&(*rcp)->revoked_serials, lo, hi);
+}
+
 int
 ssh_krl_revoke_cert_by_serial(struct ssh_krl *krl, const struct sshkey *ca_key,
     uint64_t serial)
@@ -336,25 +391,21 @@ int
 ssh_krl_revoke_cert_by_serial_range(struct ssh_krl *krl,
     const struct sshkey *ca_key, uint64_t lo, uint64_t hi)
 {
-	struct revoked_certs *rc;
-	int r;
+	struct revoked_certs *rc = NULL;
 
-	if (lo > hi || lo == 0)
-		return SSH_ERR_INVALID_ARGUMENT;
-	if ((r = revoked_certs_for_ca_key(krl, ca_key, &rc, 1)) != 0)
-		return r;
-	return insert_serial_range(&rc->revoked_serials, lo, hi);
+	return revoke_serial_range(krl, ca_key, &rc, lo, hi);
 }
 
-int
-ssh_krl_revoke_cert_by_key_id(struct ssh_krl *krl, const struct sshkey *ca_key,
-    const char *key_id)
+/* Revoke a key ID. *rcp is treated as per revoke_serial_range() */
+static int
+revoke_key_id(struct ssh_krl *krl, const struct sshkey *ca_key,
+    struct revoked_certs **rcp, const char *key_id)
 {
 	struct revoked_key_id *rki, *erki;
-	struct revoked_certs *rc;
 	int r;
 
-	if ((r = revoked_certs_for_ca_key(krl, ca_key, &rc, 1)) != 0)
+	if (*rcp == NULL &&
+	    (r = revoked_certs_for_ca_key(krl, ca_key, rcp, 1)) != 0)
 		return r;
 
 	KRL_DBG(("revoke %s", key_id));
@@ -363,7 +414,7 @@ ssh_krl_revoke_cert_by_key_id(struct ssh_krl *krl, const struct sshkey *ca_key,
 		free(rki);
 		return SSH_ERR_ALLOC_FAIL;
 	}
-	erki = RB_INSERT(revoked_key_id_tree, &rc->revoked_key_ids, rki);
+	erki = RB_INSERT(revoked_key_id_tree, &(*rcp)->revoked_key_ids, rki);
 	if (erki != NULL) {
 		free(rki->key_id);
 		free(rki);
@@ -371,6 +422,15 @@ ssh_krl_revoke_cert_by_key_id(struct ssh_krl *krl, const struct sshkey *ca_key,
 	return 0;
 }
 
+int
+ssh_krl_revoke_cert_by_key_id(struct ssh_krl *krl, const struct sshkey *ca_key,
+    const char *key_id)
+{
+	struct revoked_certs *rc = NULL;
+
+	return revoke_key_id(krl, ca_key, &rc, key_id);
+}
+
 /* Convert "key" to a public key blob without any certificate information */
 static int
 plain_key_blob(const struct sshkey *key, u_char **blob, size_t *blen)
@@ -897,6 +957,7 @@ parse_revoked_certs(struct sshbuf *buf, struct ssh_krl *krl)
 	struct bitmap *bitmap = NULL;
 	char *key_id = NULL;
 	struct sshkey *ca_key = NULL;
+	struct revoked_certs *rc = NULL;
 
 	if ((subsect = sshbuf_new()) == NULL)
 		return SSH_ERR_ALLOC_FAIL;
@@ -922,8 +983,8 @@ parse_revoked_certs(struct sshbuf *buf, struct ssh_krl *krl)
 			while (sshbuf_len(subsect) > 0) {
 				if ((r = sshbuf_get_u64(subsect, &serial)) != 0)
 					goto out;
-				if ((r = ssh_krl_revoke_cert_by_serial(krl,
-				    ca_key, serial)) != 0)
+				if ((r = revoke_serial_range(krl, ca_key, &rc,
+				    serial, serial)) != 0)
 					goto out;
 			}
 			break;
@@ -931,8 +992,8 @@ parse_revoked_certs(struct sshbuf *buf, struct ssh_krl *krl)
 			if ((r = sshbuf_get_u64(subsect, &serial_lo)) != 0 ||
 			    (r = sshbuf_get_u64(subsect, &serial_hi)) != 0)
 				goto out;
-			if ((r = ssh_krl_revoke_cert_by_serial_range(krl,
-			    ca_key, serial_lo, serial_hi)) != 0)
+			if ((r = revoke_serial_range(krl, ca_key, &rc,
+			    serial_lo, serial_hi)) != 0)
 				goto out;
 			break;
 		case KRL_SECTION_CERT_SERIAL_BITMAP:
@@ -957,8 +1018,9 @@ parse_revoked_certs(struct sshbuf *buf, struct ssh_krl *krl)
 				}
 				if (!bitmap_test_bit(bitmap, serial))
 					continue;
-				if ((r = ssh_krl_revoke_cert_by_serial(krl,
-				    ca_key, serial_lo + serial)) != 0)
+				if ((r = revoke_serial_range(krl, ca_key, &rc,
+				    serial_lo + serial,
+				    serial_lo + serial)) != 0)
 					goto out;
 			}
 			bitmap_free(bitmap);
@@ -969,8 +1031,8 @@ parse_revoked_certs(struct sshbuf *buf, struct ssh_krl *krl)
 				if ((r = sshbuf_get_cstring(subsect,
 				    &key_id, NULL)) != 0)
 					goto out;
-				if ((r = ssh_krl_revoke_cert_by_key_id(krl,
-				    ca_key, key_id)) != 0)
+				if ((r = revoke_key_id(krl, ca_key, &rc,
+				    key_id)) != 0)
 					goto out;
 				free(key_id);
 				key_id = NULL;

-- 
To stop receiving notification emails like this one, please contact
djm at mindrot.org.


More information about the openssh-commits mailing list