- 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@
diff --git a/ssh-keygen.c b/ssh-keygen.c
index a19a2b0..861b04e 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 <ylo@cs.hut.fi>
  * Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, 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
@@ -1897,6 +1900,226 @@
 }
 
 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)
 {
 	fprintf(stderr, "usage: %s [options]\n", __progname);
@@ -1922,6 +2145,7 @@
 	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 @@
 	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 @@
 	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 @@
 {
 	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 @@
 		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 @@
 		case 'N':
 			identity_new_passphrase = optarg;
 			break;
+		case 'Q':
+			check_krl = 1;
+			break;
 		case 'O':
 			add_cert_option(optarg);
 			break;
@@ -2090,6 +2319,9 @@
 			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 @@
 		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 @@
 	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 @@
 		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");