upstream: allow key revocation by SHA256 hash and allow ssh-keygen

to create KRLs using SHA256/base64 key fingerprints; ok markus@

OpenBSD-Commit-ID: a0590fd34e7f1141f2873ab3acc57442560e6a94
This commit is contained in:
djm@openbsd.org 2018-09-12 01:21:34 +00:00 committed by Damien Miller
parent 50e2687ee0
commit 9405c6214f
5 changed files with 193 additions and 49 deletions

View File

@ -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 $

126
krl.c
View File

@ -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 "includes.h"
@ -96,6 +96,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;
};
@ -136,6 +137,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;
}
@ -178,6 +180,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);
@ -408,25 +415,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,
@ -762,6 +791,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__,
@ -914,6 +955,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
@ -925,9 +989,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;
@ -1068,24 +1132,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 */
@ -1150,7 +1209,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);
@ -1210,6 +1268,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));

6
krl.h
View File

@ -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);

View File

@ -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 <ylo@cs.hut.fi>
.\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, 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

View File

@ -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 <ylo@cs.hut.fi>
* Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -2079,6 +2079,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)
@ -2086,9 +2121,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);
@ -2103,7 +2139,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++) {
@ -2168,6 +2204,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;
@ -2177,7 +2218,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.
@ -2190,13 +2234,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);
}
}