From: djm Date: Wed, 12 Sep 2018 01:21:34 +0000 (+0000) Subject: allow key revocation by SHA256 hash and allow ssh-keygen to create KRLs X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=76f3571aafb0385356306d4a5c6ecf40a2ab6a98;p=openbsd allow key revocation by SHA256 hash and allow ssh-keygen to create KRLs using SHA256/base64 key fingerprints; ok markus@ --- diff --git a/usr.bin/ssh/PROTOCOL.krl b/usr.bin/ssh/PROTOCOL.krl index f319bad21d3..115f80e5d54 100644 --- a/usr.bin/ssh/PROTOCOL.krl +++ b/usr.bin/ssh/PROTOCOL.krl @@ -36,6 +36,7 @@ The available section types are: #define KRL_SECTION_EXPLICIT_KEY 2 #define KRL_SECTION_FINGERPRINT_SHA1 3 #define KRL_SECTION_SIGNATURE 4 +#define KRL_SECTION_FINGERPRINT_SHA256 5 2. Certificate section @@ -127,18 +128,19 @@ must be a raw key (i.e. not a certificate). This section may appear multiple times. -4. SHA1 fingerprint sections +4. SHA1/SHA256 fingerprint sections -These sections, identified as KRL_SECTION_FINGERPRINT_SHA1, revoke -plain keys (i.e. not certificates) by listing their SHA1 hashes: +These sections, identified as KRL_SECTION_FINGERPRINT_SHA1 and +KRL_SECTION_FINGERPRINT_SHA256, revoke plain keys (i.e. not +certificates) by listing their 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. +is obtained by taking the SHA1 or SHA256 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. @@ -166,4 +168,4 @@ 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.4 2018/04/10 00:10:49 djm Exp $ +$OpenBSD: PROTOCOL.krl,v 1.5 2018/09/12 01:21:34 djm Exp $ diff --git a/usr.bin/ssh/krl.c b/usr.bin/ssh/krl.c index 19c27e49767..4d032474207 100644 --- a/usr.bin/ssh/krl.c +++ b/usr.bin/ssh/krl.c @@ -14,7 +14,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -/* $OpenBSD: krl.c,v 1.41 2017/12/18 02:25:15 djm Exp $ */ +/* $OpenBSD: krl.c,v 1.42 2018/09/12 01:21:34 djm Exp $ */ #include #include @@ -94,6 +94,7 @@ struct ssh_krl { char *comment; struct revoked_blob_tree revoked_keys; struct revoked_blob_tree revoked_sha1s; + struct revoked_blob_tree revoked_sha256s; struct revoked_certs_list revoked_certs; }; @@ -134,6 +135,7 @@ ssh_krl_init(void) return NULL; RB_INIT(&krl->revoked_keys); RB_INIT(&krl->revoked_sha1s); + RB_INIT(&krl->revoked_sha256s); TAILQ_INIT(&krl->revoked_certs); return krl; } @@ -176,6 +178,11 @@ ssh_krl_free(struct ssh_krl *krl) free(rb->blob); free(rb); } + RB_FOREACH_SAFE(rb, revoked_blob_tree, &krl->revoked_sha256s, trb) { + RB_REMOVE(revoked_blob_tree, &krl->revoked_sha256s, 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); @@ -406,25 +413,47 @@ ssh_krl_revoke_key_explicit(struct ssh_krl *krl, const struct sshkey *key) return revoke_blob(&krl->revoked_keys, blob, len); } -int -ssh_krl_revoke_key_sha1(struct ssh_krl *krl, const struct sshkey *key) +static int +revoke_by_hash(struct revoked_blob_tree *target, const u_char *p, size_t len) { u_char *blob; - size_t len; int r; - debug3("%s: revoke type %s by sha1", __func__, sshkey_type(key)); - if ((r = sshkey_fingerprint_raw(key, SSH_DIGEST_SHA1, - &blob, &len)) != 0) + /* need to copy hash, as revoke_blob steals ownership */ + if ((blob = malloc(len)) == NULL) + return SSH_ERR_SYSTEM_ERROR; + memcpy(blob, p, len); + if ((r = revoke_blob(target, blob, len)) != 0) { + free(blob); return r; - return revoke_blob(&krl->revoked_sha1s, blob, len); + } + return 0; +} + +int +ssh_krl_revoke_key_sha1(struct ssh_krl *krl, const u_char *p, size_t len) +{ + debug3("%s: revoke by sha1", __func__); + if (len != 20) + return SSH_ERR_INVALID_FORMAT; + return revoke_by_hash(&krl->revoked_sha1s, p, len); +} + +int +ssh_krl_revoke_key_sha256(struct ssh_krl *krl, const u_char *p, size_t len) +{ + debug3("%s: revoke by sha256", __func__); + if (len != 32) + return SSH_ERR_INVALID_FORMAT; + return revoke_by_hash(&krl->revoked_sha256s, p, len); } int ssh_krl_revoke_key(struct ssh_krl *krl, const struct sshkey *key) { + /* XXX replace with SHA256? */ if (!sshkey_is_cert(key)) - return ssh_krl_revoke_key_sha1(krl, key); + return ssh_krl_revoke_key_explicit(krl, key); if (key->cert->serial == 0) { return ssh_krl_revoke_cert_by_key_id(krl, @@ -760,6 +789,18 @@ ssh_krl_to_blob(struct ssh_krl *krl, struct sshbuf *buf, (r = sshbuf_put_stringb(buf, sect)) != 0) goto out; } + sshbuf_reset(sect); + RB_FOREACH(rb, revoked_blob_tree, &krl->revoked_sha256s) { + KRL_DBG(("%s: hash len %zu ", __func__, rb->len)); + if ((r = sshbuf_put_string(sect, rb->blob, rb->len)) != 0) + goto out; + } + if (sshbuf_len(sect) != 0) { + if ((r = sshbuf_put_u8(buf, + KRL_SECTION_FINGERPRINT_SHA256)) != 0 || + (r = sshbuf_put_stringb(buf, sect)) != 0) + goto out; + } for (i = 0; i < nsign_keys; i++) { KRL_DBG(("%s: signature key %s", __func__, @@ -912,6 +953,29 @@ parse_revoked_certs(struct sshbuf *buf, struct ssh_krl *krl) return r; } +static int +blob_section(struct sshbuf *sect, struct revoked_blob_tree *target_tree, + size_t expected_len) +{ + u_char *rdata = NULL; + size_t rlen = 0; + int r; + + while (sshbuf_len(sect) > 0) { + if ((r = sshbuf_get_string(sect, &rdata, &rlen)) != 0) + return r; + if (expected_len != 0 && rlen != expected_len) { + error("%s: bad length", __func__); + free(rdata); + return SSH_ERR_INVALID_FORMAT; + } + if ((r = revoke_blob(target_tree, rdata, rlen)) != 0) { + free(rdata); + return r; + } + } + return 0; +} /* Attempt to parse a KRL, checking its signature (if any) with sign_ca_keys. */ int @@ -923,9 +987,9 @@ ssh_krl_from_blob(struct sshbuf *buf, struct ssh_krl **krlp, char timestamp[64]; int r = SSH_ERR_INTERNAL_ERROR, sig_seen; struct sshkey *key = NULL, **ca_used = NULL, **tmp_ca_used; - u_char type, *rdata = NULL; + u_char type; const u_char *blob; - size_t i, j, sig_off, sects_off, rlen, blen, nca_used; + size_t i, j, sig_off, sects_off, blen, nca_used; u_int format_version; nca_used = 0; @@ -1066,24 +1130,19 @@ ssh_krl_from_blob(struct sshbuf *buf, struct ssh_krl **krlp, goto out; break; case KRL_SECTION_EXPLICIT_KEY: + if ((r = blob_section(sect, + &krl->revoked_keys, 0)) != 0) + goto out; + break; case KRL_SECTION_FINGERPRINT_SHA1: - while (sshbuf_len(sect) > 0) { - if ((r = sshbuf_get_string(sect, - &rdata, &rlen)) != 0) - goto out; - if (type == KRL_SECTION_FINGERPRINT_SHA1 && - rlen != 20) { - error("%s: bad SHA1 length", __func__); - r = SSH_ERR_INVALID_FORMAT; - goto out; - } - if ((r = revoke_blob( - type == KRL_SECTION_EXPLICIT_KEY ? - &krl->revoked_keys : &krl->revoked_sha1s, - rdata, rlen)) != 0) - goto out; - rdata = NULL; /* revoke_blob frees rdata */ - } + if ((r = blob_section(sect, + &krl->revoked_sha1s, 20)) != 0) + goto out; + break; + case KRL_SECTION_FINGERPRINT_SHA256: + if ((r = blob_section(sect, + &krl->revoked_sha256s, 32)) != 0) + goto out; break; case KRL_SECTION_SIGNATURE: /* Handled above, but still need to stay in synch */ @@ -1148,7 +1207,6 @@ ssh_krl_from_blob(struct sshbuf *buf, struct ssh_krl **krlp, for (i = 0; i < nca_used; i++) sshkey_free(ca_used[i]); free(ca_used); - free(rdata); sshkey_free(key); sshbuf_free(copy); sshbuf_free(sect); @@ -1208,6 +1266,16 @@ is_key_revoked(struct ssh_krl *krl, const struct sshkey *key) KRL_DBG(("%s: revoked by key SHA1", __func__)); return SSH_ERR_KEY_REVOKED; } + memset(&rb, 0, sizeof(rb)); + if ((r = sshkey_fingerprint_raw(key, SSH_DIGEST_SHA256, + &rb.blob, &rb.len)) != 0) + return r; + erb = RB_FIND(revoked_blob_tree, &krl->revoked_sha256s, &rb); + free(rb.blob); + if (erb != NULL) { + KRL_DBG(("%s: revoked by key SHA256", __func__)); + return SSH_ERR_KEY_REVOKED; + } /* Next, explicit keys */ memset(&rb, 0, sizeof(rb)); diff --git a/usr.bin/ssh/krl.h b/usr.bin/ssh/krl.h index 675496cc48a..815a1df4eba 100644 --- a/usr.bin/ssh/krl.h +++ b/usr.bin/ssh/krl.h @@ -14,7 +14,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -/* $OpenBSD: krl.h,v 1.5 2015/12/30 23:46:14 djm Exp $ */ +/* $OpenBSD: krl.h,v 1.6 2018/09/12 01:21:34 djm Exp $ */ #ifndef _KRL_H #define _KRL_H @@ -29,6 +29,7 @@ #define KRL_SECTION_EXPLICIT_KEY 2 #define KRL_SECTION_FINGERPRINT_SHA1 3 #define KRL_SECTION_SIGNATURE 4 +#define KRL_SECTION_FINGERPRINT_SHA256 5 /* KRL_SECTION_CERTIFICATES subsection types */ #define KRL_SECTION_CERT_SERIAL_LIST 0x20 @@ -51,7 +52,8 @@ int ssh_krl_revoke_cert_by_serial_range(struct ssh_krl *krl, int ssh_krl_revoke_cert_by_key_id(struct ssh_krl *krl, const struct sshkey *ca_key, const char *key_id); int ssh_krl_revoke_key_explicit(struct ssh_krl *krl, const struct sshkey *key); -int ssh_krl_revoke_key_sha1(struct ssh_krl *krl, const struct sshkey *key); +int ssh_krl_revoke_key_sha1(struct ssh_krl *krl, const u_char *p, size_t len); +int ssh_krl_revoke_key_sha256(struct ssh_krl *krl, const u_char *p, size_t len); int ssh_krl_revoke_key(struct ssh_krl *krl, const struct sshkey *key); int ssh_krl_to_blob(struct ssh_krl *krl, struct sshbuf *buf, const struct sshkey **sign_keys, u_int nsign_keys); diff --git a/usr.bin/ssh/ssh-keygen.1 b/usr.bin/ssh/ssh-keygen.1 index dd6e7e5a8a3..d1aad6f2038 100644 --- a/usr.bin/ssh/ssh-keygen.1 +++ b/usr.bin/ssh/ssh-keygen.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: ssh-keygen.1,v 1.148 2018/08/08 01:16:01 djm Exp $ +.\" $OpenBSD: ssh-keygen.1,v 1.149 2018/09/12 01:21:34 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 8 2018 $ +.Dd $Mdocdate: September 12 2018 $ .Dt SSH-KEYGEN 1 .Os .Sh NAME @@ -814,7 +814,20 @@ option. Revokes the specified 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. +Revokes the specified key by including its SHA1 hash in the KRL. +.It Cm sha256 : Ar public_key +Revokes the specified key by including its SHA256 hash in the KRL. +KRLs that revoke keys by SHA256 hash are not supported by OpenSSH versions +prior to 7.9. +.It Cm hash : Ar fingerprint +Revokes a key using by fingerprint hash, as obtained from a +.Xr sshd 8 +authentication log message or the +.Nm +.Fl l +flag. +Only SHA256 fingerprints are supported here and resultant KRLs are +not supported by OpenSSH versions prior to 7.9. .El .Pp KRLs may be updated using the diff --git a/usr.bin/ssh/ssh-keygen.c b/usr.bin/ssh/ssh-keygen.c index 12a5addb957..39ccf8eba05 100644 --- a/usr.bin/ssh/ssh-keygen.c +++ b/usr.bin/ssh/ssh-keygen.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-keygen.c,v 1.319 2018/08/08 01:16:01 djm Exp $ */ +/* $OpenBSD: ssh-keygen.c,v 1.320 2018/09/12 01:21:34 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1994 Tatu Ylonen , Espoo, Finland @@ -2063,6 +2063,41 @@ load_krl(const char *path, struct ssh_krl **krlp) sshbuf_free(krlbuf); } +static void +hash_to_blob(const char *cp, u_char **blobp, size_t *lenp, + const char *file, u_long lnum) +{ + char *tmp; + size_t tlen; + struct sshbuf *b; + int r; + + if (strncmp(cp, "SHA256:", 7) != 0) + fatal("%s:%lu: unsupported hash algorithm", file, lnum); + cp += 7; + + /* + * OpenSSH base64 hashes omit trailing '=' + * characters; put them back for decode. + */ + tlen = strlen(cp); + tmp = xmalloc(tlen + 4 + 1); + strlcpy(tmp, cp, tlen + 1); + while ((tlen % 4) != 0) { + tmp[tlen++] = '='; + tmp[tlen] = '\0'; + } + if ((b = sshbuf_new()) == NULL) + fatal("%s: sshbuf_new failed", __func__); + if ((r = sshbuf_b64tod(b, tmp)) != 0) + fatal("%s:%lu: decode hash failed: %s", file, lnum, ssh_err(r)); + free(tmp); + *lenp = sshbuf_len(b); + *blobp = xmalloc(*lenp); + memcpy(*blobp, sshbuf_ptr(b), *lenp); + sshbuf_free(b); +} + static void update_krl_from_file(struct passwd *pw, const char *file, int wild_ca, const struct sshkey *ca, struct ssh_krl *krl) @@ -2070,9 +2105,10 @@ update_krl_from_file(struct passwd *pw, const char *file, int wild_ca, struct sshkey *key = NULL; u_long lnum = 0; char *path, *cp, *ep, *line = NULL; - size_t linesize = 0; + u_char *blob = NULL; + size_t blen = 0, linesize = 0; unsigned long long serial, serial2; - int i, was_explicit_key, was_sha1, r; + int i, was_explicit_key, was_sha1, was_sha256, was_hash, r; FILE *krl_spec; path = tilde_expand_filename(file, pw->pw_uid); @@ -2087,7 +2123,7 @@ update_krl_from_file(struct passwd *pw, const char *file, int wild_ca, printf("Revoking from %s\n", path); while (getline(&line, &linesize, krl_spec) != -1) { lnum++; - was_explicit_key = was_sha1 = 0; + was_explicit_key = was_sha1 = was_sha256 = was_hash = 0; cp = line + strspn(line, " \t"); /* Trim trailing space, comments and strip \n */ for (i = 0, r = -1; cp[i] != '\0'; i++) { @@ -2152,6 +2188,11 @@ update_krl_from_file(struct passwd *pw, const char *file, int wild_ca, 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, "hash:", 5) == 0) { + cp += 5; + cp = cp + strspn(cp, " \t"); + hash_to_blob(cp, &blob, &blen, file, lnum); + r = ssh_krl_revoke_key_sha256(krl, blob, blen); } else { if (strncasecmp(cp, "key:", 4) == 0) { cp += 4; @@ -2161,7 +2202,10 @@ update_krl_from_file(struct passwd *pw, const char *file, int wild_ca, cp += 5; cp = cp + strspn(cp, " \t"); was_sha1 = 1; - } else { + } else if (strncasecmp(cp, "sha256:", 7) == 0) { + cp += 7; + cp = cp + strspn(cp, " \t"); + was_sha256 = 1; /* * Just try to process the line as a key. * Parsing will fail if it isn't. @@ -2174,13 +2218,28 @@ update_krl_from_file(struct passwd *pw, const char *file, int wild_ca, path, lnum, ssh_err(r)); 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 + else if (was_sha1) { + if (sshkey_fingerprint_raw(key, + SSH_DIGEST_SHA1, &blob, &blen) != 0) { + fatal("%s:%lu: fingerprint failed", + file, lnum); + } + r = ssh_krl_revoke_key_sha1(krl, blob, blen); + } else if (was_sha256) { + if (sshkey_fingerprint_raw(key, + SSH_DIGEST_SHA256, &blob, &blen) != 0) { + fatal("%s:%lu: fingerprint failed", + file, lnum); + } + r = ssh_krl_revoke_key_sha256(krl, blob, blen); + } else r = ssh_krl_revoke_key(krl, key); if (r != 0) fatal("%s: revoke key failed: %s", __func__, ssh_err(r)); + freezero(blob, blen); + blob = NULL; + blen = 0; sshkey_free(key); } }