From c31299ab84e9df20bcc2daad2b5afc83b4759426 Mon Sep 17 00:00:00 2001 From: djm Date: Fri, 23 Jul 2021 03:37:52 +0000 Subject: [PATCH] Let allowed signers files used by ssh-keygen(1) signatures support key lifetimes, and allow the verification mode to specify a signature time to check at. This is intended for use by git to support signing objects using ssh keys. ok dtucker@ --- usr.bin/ssh/auth2-hostbased.c | 4 +-- usr.bin/ssh/auth2-pubkey.c | 6 ++-- usr.bin/ssh/ssh-keygen.1 | 25 +++++++++++++-- usr.bin/ssh/ssh-keygen.c | 58 ++++++++++++++++++++++++++++++----- usr.bin/ssh/sshkey.c | 32 ++++++++++++------- usr.bin/ssh/sshkey.h | 4 ++- usr.bin/ssh/sshsig.h | 6 ++-- 7 files changed, 104 insertions(+), 31 deletions(-) diff --git a/usr.bin/ssh/auth2-hostbased.c b/usr.bin/ssh/auth2-hostbased.c index 3f201f35bea..cd9523954b2 100644 --- a/usr.bin/ssh/auth2-hostbased.c +++ b/usr.bin/ssh/auth2-hostbased.c @@ -1,4 +1,4 @@ -/* $OpenBSD: auth2-hostbased.c,v 1.46 2021/01/27 10:05:28 djm Exp $ */ +/* $OpenBSD: auth2-hostbased.c,v 1.47 2021/07/23 03:37:52 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * @@ -212,7 +212,7 @@ hostbased_key_allowed(struct ssh *ssh, struct passwd *pw, debug2_f("access allowed by auth_rhosts2"); if (sshkey_is_cert(key) && - sshkey_cert_check_authority(key, 1, 0, 0, lookup, &reason)) { + sshkey_cert_check_authority_now(key, 1, 0, 0, lookup, &reason)) { error("%s", reason); auth_debug_add("%s", reason); return 0; diff --git a/usr.bin/ssh/auth2-pubkey.c b/usr.bin/ssh/auth2-pubkey.c index 7c6fe33c2f3..527802b7a4f 100644 --- a/usr.bin/ssh/auth2-pubkey.c +++ b/usr.bin/ssh/auth2-pubkey.c @@ -1,4 +1,4 @@ -/* $OpenBSD: auth2-pubkey.c,v 1.108 2021/06/08 06:54:40 djm Exp $ */ +/* $OpenBSD: auth2-pubkey.c,v 1.109 2021/07/23 03:37:52 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * @@ -671,7 +671,7 @@ check_authkey_line(struct ssh *ssh, struct passwd *pw, struct sshkey *key, reason = "Certificate does not contain an authorized principal"; goto fail_reason; } - if (sshkey_cert_check_authority(key, 0, 0, 0, + if (sshkey_cert_check_authority_now(key, 0, 0, 0, keyopts->cert_principals == NULL ? pw->pw_name : NULL, &reason) != 0) goto fail_reason; @@ -791,7 +791,7 @@ user_cert_trusted_ca(struct ssh *ssh, struct passwd *pw, struct sshkey *key, } if (use_authorized_principals && principals_opts == NULL) fatal_f("internal error: missing principals_opts"); - if (sshkey_cert_check_authority(key, 0, 1, 0, + if (sshkey_cert_check_authority_now(key, 0, 1, 0, use_authorized_principals ? NULL : pw->pw_name, &reason) != 0) goto fail_reason; diff --git a/usr.bin/ssh/ssh-keygen.1 b/usr.bin/ssh/ssh-keygen.1 index 4e737274533..9bfbcdc77f3 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.213 2021/05/12 11:34:30 dtucker Exp $ +.\" $OpenBSD: ssh-keygen.1,v 1.214 2021/07/23 03:37:52 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: May 12 2021 $ +.Dd $Mdocdate: July 23 2021 $ .Dt SSH-KEYGEN 1 .Os .Sh NAME @@ -147,10 +147,12 @@ .Ar .Nm ssh-keygen .Fl Y Cm find-principals +.Op Fl O Ar option .Fl s Ar signature_file .Fl f Ar allowed_signers_file .Nm ssh-keygen .Fl Y Cm check-novalidate +.Op Fl O Ar option .Fl n Ar namespace .Fl s Ar signature_file .Nm ssh-keygen @@ -160,6 +162,7 @@ .Ar .Nm ssh-keygen .Fl Y Cm verify +.Op Fl O Ar option .Fl f Ar allowed_signers_file .Fl I Ar signer_identity .Fl n Ar namespace @@ -530,6 +533,17 @@ Please note that this information is potentially sensitive. By default, this information is discarded. .El .Pp +When performing signature-related options using the +.Fl Y +flag, the following options are accepted: +.Bl -tag -width Ds +.It Cm verify-time Ns = Ns Ar timestamp +Specifies a time to use when validating signatures instead of the current +time. +The time may be specified as a date in YYYYMMDD format or a time +in YYYYMMDDHHMM[SS] format. +.El +.Pp The .Fl O option may be specified multiple times. @@ -1134,11 +1148,16 @@ are case-insensitive): .It Cm cert-authority Indicates that this key is accepted as a certificate authority (CA) and that certificates signed by this CA may be accepted for verification. -.It Cm namespaces="namespace-list" +.It Cm namespaces Ns = Ns "namespace-list" Specifies a pattern-list of namespaces that are accepted for this key. If this option is present, the signature namespace embedded in the signature object and presented on the verification command-line must match the specified list before the key will be considered acceptable. +.It Cm valid-after Ns = Ns "timestamp" +Indicates that the key is valid for use at or after the specified timestamp, +which may be a date in YYYYMMDD format or a time in YYYYMMDDHHMM[SS] format, +.It Cm valid-before Ns = Ns "timestamp" +Indicates that the key is valid for use at or before the specified timestamp. .El .Pp When verifying signatures made by certificates, the expected principal diff --git a/usr.bin/ssh/ssh-keygen.c b/usr.bin/ssh/ssh-keygen.c index a5cd17b57bb..dc70fb017e0 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.431 2021/07/09 09:55:56 djm Exp $ */ +/* $OpenBSD: ssh-keygen.c,v 1.432 2021/07/23 03:37:52 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1994 Tatu Ylonen , Espoo, Finland @@ -2649,15 +2649,49 @@ done: return ret; } +static int +sig_process_opts(char * const *opts, size_t nopts, uint64_t *verify_timep) +{ + size_t i; + time_t now; + + *verify_timep = 0; + for (i = 0; i < nopts; i++) { + if (strncasecmp(opts[i], "verify-time=", 12) == 0) { + if (parse_absolute_time(opts[i] + 12, + verify_timep) != 0 || *verify_timep == 0) { + error("Invalid \"verify-time\" option"); + return SSH_ERR_INVALID_ARGUMENT; + } + } else { + error("Invalid option \"%s\"", opts[i]); + return SSH_ERR_INVALID_ARGUMENT; + } + } + if (*verify_timep == 0) { + if ((now = time(NULL)) < 0) { + error("Time is before epoch"); + return SSH_ERR_INVALID_ARGUMENT; + } + *verify_timep = (uint64_t)now; + } + return 0; +} + static int sig_verify(const char *signature, const char *sig_namespace, - const char *principal, const char *allowed_keys, const char *revoked_keys) + const char *principal, const char *allowed_keys, const char *revoked_keys, + char * const *opts, size_t nopts) { int r, ret = -1; struct sshbuf *sigbuf = NULL, *abuf = NULL; struct sshkey *sign_key = NULL; char *fp = NULL; struct sshkey_sig_details *sig_details = NULL; + uint64_t verify_time = 0; + + if (sig_process_opts(opts, nopts, &verify_time) != 0) + goto done; /* error already logged */ memset(&sig_details, 0, sizeof(sig_details)); if ((r = sshbuf_load_file(signature, &abuf)) != 0) { @@ -2692,7 +2726,7 @@ sig_verify(const char *signature, const char *sig_namespace, } if (allowed_keys != NULL && (r = sshsig_check_allowed_keys(allowed_keys, - sign_key, principal, sig_namespace)) != 0) { + sign_key, principal, sig_namespace, verify_time)) != 0) { debug3_fr(r, "sshsig_check_allowed_keys"); goto done; } @@ -2726,11 +2760,17 @@ done: } static int -sig_find_principals(const char *signature, const char *allowed_keys) { +sig_find_principals(const char *signature, const char *allowed_keys, + char * const *opts, size_t nopts) +{ int r, ret = -1; struct sshbuf *sigbuf = NULL, *abuf = NULL; struct sshkey *sign_key = NULL; char *principals = NULL, *cp, *tmp; + uint64_t verify_time = 0; + + if (sig_process_opts(opts, nopts, &verify_time) != 0) + goto done; /* error already logged */ if ((r = sshbuf_load_file(signature, &abuf)) != 0) { error_r(r, "Couldn't read signature file"); @@ -2745,7 +2785,7 @@ sig_find_principals(const char *signature, const char *allowed_keys) { goto done; } if ((r = sshsig_find_principals(allowed_keys, sign_key, - &principals)) != 0) { + verify_time, &principals)) != 0) { if (r != SSH_ERR_KEY_NOT_FOUND) error_fr(r, "sshsig_find_principal"); goto done; @@ -3354,7 +3394,8 @@ main(int argc, char **argv) "missing allowed keys file"); exit(1); } - return sig_find_principals(ca_key_path, identity_file); + return sig_find_principals(ca_key_path, identity_file, + opts, nopts); } else if (strncmp(sign_op, "sign", 4) == 0) { if (cert_principals == NULL || *cert_principals == '\0') { @@ -3376,7 +3417,7 @@ main(int argc, char **argv) exit(1); } return sig_verify(ca_key_path, cert_principals, - NULL, NULL, NULL); + NULL, NULL, NULL, opts, nopts); } else if (strncmp(sign_op, "verify", 6) == 0) { if (cert_principals == NULL || *cert_principals == '\0') { @@ -3400,7 +3441,8 @@ main(int argc, char **argv) exit(1); } return sig_verify(ca_key_path, cert_principals, - cert_key_id, identity_file, rr_hostname); + cert_key_id, identity_file, rr_hostname, + opts, nopts); } error("Unsupported operation for -Y: \"%s\"", sign_op); usage(); diff --git a/usr.bin/ssh/sshkey.c b/usr.bin/ssh/sshkey.c index 3e2fdbdb092..193f6ec11ff 100644 --- a/usr.bin/ssh/sshkey.c +++ b/usr.bin/ssh/sshkey.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshkey.c,v 1.118 2021/07/12 06:08:57 dtucker Exp $ */ +/* $OpenBSD: sshkey.c,v 1.119 2021/07/23 03:37:52 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * Copyright (c) 2008 Alexander von Gernler. All rights reserved. @@ -3025,10 +3025,9 @@ sshkey_certify(struct sshkey *k, struct sshkey *ca, const char *alg, int sshkey_cert_check_authority(const struct sshkey *k, int want_host, int require_principal, int wildcard_pattern, - const char *name, const char **reason) + uint64_t verify_time, const char *name, const char **reason) { u_int i, principal_matches; - time_t now = time(NULL); if (reason == NULL) return SSH_ERR_INVALID_ARGUMENT; @@ -3047,16 +3046,11 @@ sshkey_cert_check_authority(const struct sshkey *k, return SSH_ERR_KEY_CERT_INVALID; } } - if (now < 0) { - /* yikes - system clock before epoch! */ - *reason = "Certificate invalid: not yet valid"; - return SSH_ERR_KEY_CERT_INVALID; - } - if ((u_int64_t)now < k->cert->valid_after) { + if (verify_time < k->cert->valid_after) { *reason = "Certificate invalid: not yet valid"; return SSH_ERR_KEY_CERT_INVALID; } - if ((u_int64_t)now >= k->cert->valid_before) { + if (verify_time >= k->cert->valid_before) { *reason = "Certificate invalid: expired"; return SSH_ERR_KEY_CERT_INVALID; } @@ -3088,6 +3082,22 @@ sshkey_cert_check_authority(const struct sshkey *k, return 0; } +int +sshkey_cert_check_authority_now(const struct sshkey *k, + int want_host, int require_principal, int wildcard_pattern, + const char *name, const char **reason) +{ + time_t now; + + if ((now = time(NULL)) < 0) { + /* yikes - system clock before epoch! */ + *reason = "Certificate invalid: not yet valid"; + return SSH_ERR_KEY_CERT_INVALID; + } + return sshkey_cert_check_authority(k, want_host, require_principal, + wildcard_pattern, (uint64_t)now, name, reason); +} + int sshkey_cert_check_host(const struct sshkey *key, const char *host, int wildcard_principals, const char *ca_sign_algorithms, @@ -3095,7 +3105,7 @@ sshkey_cert_check_host(const struct sshkey *key, const char *host, { int r; - if ((r = sshkey_cert_check_authority(key, 1, 0, wildcard_principals, + if ((r = sshkey_cert_check_authority_now(key, 1, 0, wildcard_principals, host, reason)) != 0) return r; if (sshbuf_len(key->cert->critical) != 0) { diff --git a/usr.bin/ssh/sshkey.h b/usr.bin/ssh/sshkey.h index 18b11b5db9a..ecae86e8eb7 100644 --- a/usr.bin/ssh/sshkey.h +++ b/usr.bin/ssh/sshkey.h @@ -1,4 +1,4 @@ -/* $OpenBSD: sshkey.h,v 1.49 2021/01/26 00:49:30 djm Exp $ */ +/* $OpenBSD: sshkey.h,v 1.50 2021/07/23 03:37:52 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. @@ -190,6 +190,8 @@ int sshkey_to_certified(struct sshkey *); int sshkey_drop_cert(struct sshkey *); int sshkey_cert_copy(const struct sshkey *, struct sshkey *); int sshkey_cert_check_authority(const struct sshkey *, int, int, int, + uint64_t, const char *, const char **); +int sshkey_cert_check_authority_now(const struct sshkey *, int, int, int, const char *, const char **); int sshkey_cert_check_host(const struct sshkey *, const char *, int , const char *, const char **); diff --git a/usr.bin/ssh/sshsig.h b/usr.bin/ssh/sshsig.h index 67794a97147..b725c7d7acd 100644 --- a/usr.bin/ssh/sshsig.h +++ b/usr.bin/ssh/sshsig.h @@ -1,4 +1,4 @@ -/* $OpenBSD: sshsig.h,v 1.9 2020/08/31 00:17:41 djm Exp $ */ +/* $OpenBSD: sshsig.h,v 1.10 2021/07/23 03:37:52 djm Exp $ */ /* * Copyright (c) 2019 Google LLC * @@ -86,7 +86,7 @@ int sshsig_dearmor(struct sshbuf *sig, struct sshbuf **out); * an allowed_keys file. Returns 0 on success. */ int sshsig_check_allowed_keys(const char *path, const struct sshkey *sign_key, - const char *principal, const char *ns); + const char *principal, const char *ns, uint64_t verify_time); /* Parse zero or more allowed_keys signature options */ struct sshsigopt *sshsigopt_parse(const char *opts, @@ -102,6 +102,6 @@ int sshsig_get_pubkey(struct sshbuf *signature, struct sshkey **pubkey); * 0 on success. */ int sshsig_find_principals(const char *path, const struct sshkey *sign_key, - char **principal); + uint64_t verify_time, char **principal); #endif /* SSHSIG_H */ -- 2.20.1