From bf0fbf2b11a44f06a64b620af7d01ff171c28e13 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Mon, 12 Mar 2018 00:52:01 +0000 Subject: upstream: add valid-before="[time]" authorized_keys option. A simple way of giving a key an expiry date. ok markus@ OpenBSD-Commit-ID: 1793b4dd5184fa87f42ed33c7b0f4f02bc877947 --- auth-options.c | 32 +++++++++++++++++++++++++++++--- auth-options.h | 5 ++++- auth.c | 28 +++++++++++++++++++++++----- misc.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- misc.h | 4 +++- ssh-keygen.1 | 8 ++++---- ssh-keygen.c | 44 +++++--------------------------------------- sshd.8 | 8 ++++++-- 8 files changed, 128 insertions(+), 56 deletions(-) diff --git a/auth-options.c b/auth-options.c index 484e44b7..38211fa2 100644 --- a/auth-options.c +++ b/auth-options.c @@ -1,4 +1,4 @@ -/* $OpenBSD: auth-options.c,v 1.76 2018/03/03 03:15:51 djm Exp $ */ +/* $OpenBSD: auth-options.c,v 1.77 2018/03/12 00:52:01 djm Exp $ */ /* * Copyright (c) 2018 Damien Miller * @@ -311,6 +311,7 @@ sshauthopt_parse(const char *opts, const char **errstrp) int r; struct sshauthopt *ret = NULL; const char *errstr = "unknown error"; + uint64_t valid_before; if (errstrp != NULL) *errstrp = NULL; @@ -366,6 +367,19 @@ sshauthopt_parse(const char *opts, const char **errstrp) &errstr); if (ret->required_from_host_keys == NULL) goto fail; + } else if (opt_match(&opts, "valid-before")) { + if ((opt = opt_dequote(&opts, &errstr)) == NULL) + goto fail; + if (parse_absolute_time(opt, &valid_before) != 0 || + valid_before == 0) { + free(opt); + errstr = "invalid expires time"; + goto fail; + } + free(opt); + if (ret->valid_before == 0 || + valid_before < ret->valid_before) + ret->valid_before = valid_before; } else if (opt_match(&opts, "environment")) { if (ret->nenv > INT_MAX) { errstr = "too many environment strings"; @@ -572,6 +586,13 @@ sshauthopt_merge(const struct sshauthopt *primary, OPTFLAG(permit_user_rc); #undef OPTFLAG + /* Earliest expiry time should win */ + if (primary->valid_before != 0) + ret->valid_before = primary->valid_before; + if (additional->valid_before != 0 && + additional->valid_before < ret->valid_before) + ret->valid_before = additional->valid_before; + /* * When both multiple forced-command are specified, only * proceed if they are identical, otherwise fail. @@ -631,6 +652,7 @@ sshauthopt_copy(const struct sshauthopt *orig) OPTSCALAR(restricted); OPTSCALAR(cert_authority); OPTSCALAR(force_tun_device); + OPTSCALAR(valid_before); #undef OPTSCALAR #define OPTSTRING(x) \ do { \ @@ -751,14 +773,15 @@ sshauthopt_serialise(const struct sshauthopt *opts, struct sshbuf *m, { int r = SSH_ERR_INTERNAL_ERROR; - /* Flag options */ + /* Flag and simple integer options */ if ((r = sshbuf_put_u8(m, opts->permit_port_forwarding_flag)) != 0 || (r = sshbuf_put_u8(m, opts->permit_agent_forwarding_flag)) != 0 || (r = sshbuf_put_u8(m, opts->permit_x11_forwarding_flag)) != 0 || (r = sshbuf_put_u8(m, opts->permit_pty_flag)) != 0 || (r = sshbuf_put_u8(m, opts->permit_user_rc)) != 0 || (r = sshbuf_put_u8(m, opts->restricted)) != 0 || - (r = sshbuf_put_u8(m, opts->cert_authority)) != 0) + (r = sshbuf_put_u8(m, opts->cert_authority)) != 0 || + (r = sshbuf_put_u64(m, opts->valid_before)) != 0) return r; /* tunnel number can be negative to indicate "unset" */ @@ -815,6 +838,9 @@ sshauthopt_deserialise(struct sshbuf *m, struct sshauthopt **optsp) OPT_FLAG(cert_authority); #undef OPT_FLAG + if ((r = sshbuf_get_u64(m, &opts->valid_before)) != 0) + goto out; + /* tunnel number can be negative to indicate "unset" */ if ((r = sshbuf_get_u8(m, &f)) != 0 || (r = sshbuf_get_u32(m, &tmp)) != 0) diff --git a/auth-options.h b/auth-options.h index 16871d75..bf59b30b 100644 --- a/auth-options.h +++ b/auth-options.h @@ -1,4 +1,4 @@ -/* $OpenBSD: auth-options.h,v 1.25 2018/03/03 03:15:51 djm Exp $ */ +/* $OpenBSD: auth-options.h,v 1.26 2018/03/12 00:52:01 djm Exp $ */ /* * Copyright (c) 2018 Damien Miller @@ -37,6 +37,9 @@ struct sshauthopt { /* "restrict" keyword was invoked */ int restricted; + /* key/principal expiry date */ + uint64_t valid_before; + /* Certificate-related options */ int cert_authority; char *cert_principals; diff --git a/auth.c b/auth.c index 041a09e3..63366768 100644 --- a/auth.c +++ b/auth.c @@ -1,4 +1,4 @@ -/* $OpenBSD: auth.c,v 1.126 2018/03/03 03:15:51 djm Exp $ */ +/* $OpenBSD: auth.c,v 1.127 2018/03/12 00:52:01 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * @@ -1004,20 +1004,21 @@ auth_log_authopts(const char *loc, const struct sshauthopt *opts, int do_remote) int do_permitopen = opts->npermitopen > 0 && (options.allow_tcp_forwarding & FORWARD_LOCAL) != 0; size_t i; - char msg[1024], tbuf[32]; + char msg[1024], buf[64]; - snprintf(tbuf, sizeof(tbuf), "%d", opts->force_tun_device); + snprintf(buf, sizeof(buf), "%d", opts->force_tun_device); /* Try to keep this alphabetically sorted */ - snprintf(msg, sizeof(msg), "key options:%s%s%s%s%s%s%s%s%s%s%s", + snprintf(msg, sizeof(msg), "key options:%s%s%s%s%s%s%s%s%s%s%s%s", opts->permit_agent_forwarding_flag ? " agent-forwarding" : "", opts->force_command == NULL ? "" : " command", do_env ? " environment" : "", + opts->valid_before == 0 ? "" : "expires", do_permitopen ? " permitopen" : "", opts->permit_port_forwarding_flag ? " port-forwarding" : "", opts->cert_principals == NULL ? "" : " principals", opts->permit_pty_flag ? " pty" : "", opts->force_tun_device == -1 ? "" : " tun=", - opts->force_tun_device == -1 ? "" : tbuf, + opts->force_tun_device == -1 ? "" : buf, opts->permit_user_rc ? " user-rc" : "", opts->permit_x11_forwarding_flag ? " x11-forwarding" : ""); @@ -1036,6 +1037,10 @@ auth_log_authopts(const char *loc, const struct sshauthopt *opts, int do_remote) } /* Go into a little more details for the local logs. */ + if (opts->valid_before != 0) { + format_absolute_time(opts->valid_before, buf, sizeof(buf)); + debug("%s: expires at %s", loc, buf); + } if (opts->cert_principals != NULL) { debug("%s: authorized principals: \"%s\"", loc, opts->cert_principals); @@ -1089,7 +1094,20 @@ auth_authorise_keyopts(struct ssh *ssh, struct passwd *pw, const char *remote_ip = ssh_remote_ipaddr(ssh); const char *remote_host = auth_get_canonical_hostname(ssh, options.use_dns); + time_t now = time(NULL); + char buf[64]; + /* + * Check keys/principals file expiry time. + * NB. validity interval in certificate is handled elsewhere. + */ + if (opts->valid_before && now > 0 && + opts->valid_before < (uint64_t)now) { + format_absolute_time(opts->valid_before, buf, sizeof(buf)); + debug("%s: entry expired at %s", loc, buf); + auth_debug_add("%s: entry expired at %s", loc, buf); + return -1; + } /* Consistency checks */ if (opts->cert_principals != NULL && !opts->cert_authority) { debug("%s: principals on non-CA key", loc); diff --git a/misc.c b/misc.c index fbc36310..874dcc8a 100644 --- a/misc.c +++ b/misc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: misc.c,v 1.126 2018/03/07 23:53:08 djm Exp $ */ +/* $OpenBSD: misc.c,v 1.127 2018/03/12 00:52:01 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2005,2006 Damien Miller. All rights reserved. @@ -1976,3 +1976,56 @@ atoi_err(const char *nptr, int *val) *val = (int)num; return errstr; } + +int +parse_absolute_time(const char *s, uint64_t *tp) +{ + struct tm tm; + time_t tt; + char buf[32], *fmt; + + *tp = 0; + + /* + * POSIX strptime says "The application shall ensure that there + * is white-space or other non-alphanumeric characters between + * any two conversion specifications" so arrange things this way. + */ + switch (strlen(s)) { + case 8: /* YYYYMMDD */ + fmt = "%Y-%m-%d"; + snprintf(buf, sizeof(buf), "%.4s-%.2s-%.2s", s, s + 4, s + 6); + break; + case 12: /* YYYYMMDDHHMM */ + fmt = "%Y-%m-%dT%H:%M"; + snprintf(buf, sizeof(buf), "%.4s-%.2s-%.2sT%.2s:%.2s", + s, s + 4, s + 6, s + 8, s + 10); + break; + case 14: /* YYYYMMDDHHMMSS */ + fmt = "%Y-%m-%dT%H:%M:%S"; + snprintf(buf, sizeof(buf), "%.4s-%.2s-%.2sT%.2s:%.2s:%.2s", + s, s + 4, s + 6, s + 8, s + 10, s + 12); + break; + default: + return SSH_ERR_INVALID_FORMAT; + } + + memset(&tm, 0, sizeof(tm)); + if (strptime(buf, fmt, &tm) == NULL) + return SSH_ERR_INVALID_FORMAT; + if ((tt = mktime(&tm)) < 0) + return SSH_ERR_INVALID_FORMAT; + /* success */ + *tp = (uint64_t)tt; + return 0; +} + +void +format_absolute_time(uint64_t t, char *buf, size_t len) +{ + time_t tt = t > INT_MAX ? INT_MAX : t; /* XXX revisit in 2038 :P */ + struct tm tm; + + localtime_r(&tt, &tm); + strftime(buf, len, "%Y-%m-%dT%H:%M:%S", &tm); +} diff --git a/misc.h b/misc.h index 8f778067..cdafea73 100644 --- a/misc.h +++ b/misc.h @@ -1,4 +1,4 @@ -/* $OpenBSD: misc.h,v 1.70 2018/01/08 15:21:49 markus Exp $ */ +/* $OpenBSD: misc.h,v 1.71 2018/03/12 00:52:01 djm Exp $ */ /* * Author: Tatu Ylonen @@ -75,6 +75,8 @@ void lowercase(char *s); int unix_listener(const char *, int, int); int valid_domain(char *, int, const char **); const char *atoi_err(const char *, int *); +int parse_absolute_time(const char *, uint64_t *); +void format_absolute_time(uint64_t, char *, size_t); void sock_set_v6only(int); diff --git a/ssh-keygen.1 b/ssh-keygen.1 index f925eb2d..3525d7d1 100644 --- a/ssh-keygen.1 +++ b/ssh-keygen.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: ssh-keygen.1,v 1.146 2018/01/25 03:34:43 djm Exp $ +.\" $OpenBSD: ssh-keygen.1,v 1.147 2018/03/12 00:52:01 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: January 25 2018 $ +.Dd $Mdocdate: March 12 2018 $ .Dt SSH-KEYGEN 1 .Os .Sh NAME @@ -588,13 +588,13 @@ of two times separated by a colon to indicate an explicit time interval. The start time may be specified as the string .Dq always to indicate the certificate has no specified start time, -a date in YYYYMMDD format, a time in YYYYMMDDHHMMSS format, +a date in YYYYMMDD format, a time in YYYYMMDDHHMM[SS] format, a relative time (to the current time) consisting of a minus sign followed by an interval in the format described in the TIME FORMATS section of .Xr sshd_config 5 . .Pp -The end time may be specified as a YYYYMMDD date, a YYYYMMDDHHMMSS time, +The end time may be specified as a YYYYMMDD date, a YYYYMMDDHHMM[SS] time, a relative time starting with a plus character or the string .Dq forever to indicate that the certificate has no expirty date. diff --git a/ssh-keygen.c b/ssh-keygen.c index d80930ee..9aac64fc 100644 --- a/ssh-keygen.c +++ b/ssh-keygen.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-keygen.c,v 1.313 2018/02/23 15:58:38 markus Exp $ */ +/* $OpenBSD: ssh-keygen.c,v 1.314 2018/03/12 00:52:01 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1994 Tatu Ylonen , Espoo, Finland @@ -1798,40 +1798,6 @@ parse_relative_time(const char *s, time_t now) return now + (u_int64_t)(secs * mul); } -static u_int64_t -parse_absolute_time(const char *s) -{ - struct tm tm; - time_t tt; - char buf[32], *fmt; - - /* - * POSIX strptime says "The application shall ensure that there - * is white-space or other non-alphanumeric characters between - * any two conversion specifications" so arrange things this way. - */ - switch (strlen(s)) { - case 8: - fmt = "%Y-%m-%d"; - snprintf(buf, sizeof(buf), "%.4s-%.2s-%.2s", s, s + 4, s + 6); - break; - case 14: - fmt = "%Y-%m-%dT%H:%M:%S"; - snprintf(buf, sizeof(buf), "%.4s-%.2s-%.2sT%.2s:%.2s:%.2s", - s, s + 4, s + 6, s + 8, s + 10, s + 12); - break; - default: - fatal("Invalid certificate time format \"%s\"", s); - } - - memset(&tm, 0, sizeof(tm)); - if (strptime(buf, fmt, &tm) == NULL) - fatal("Invalid certificate time %s", s); - if ((tt = mktime(&tm)) < 0) - fatal("Certificate time %s cannot be represented", s); - return (u_int64_t)tt; -} - static void parse_cert_times(char *timespec) { @@ -1867,15 +1833,15 @@ parse_cert_times(char *timespec) cert_valid_from = parse_relative_time(from, now); else if (strcmp(from, "always") == 0) cert_valid_from = 0; - else - cert_valid_from = parse_absolute_time(from); + else if (parse_absolute_time(from, &cert_valid_from) != 0) + fatal("Invalid from time \"%s\"", from); if (*to == '-' || *to == '+') cert_valid_to = parse_relative_time(to, now); else if (strcmp(to, "forever") == 0) cert_valid_to = ~(u_int64_t)0; - else - cert_valid_to = parse_absolute_time(to); + else if (parse_absolute_time(to, &cert_valid_to) != 0) + fatal("Invalid to time \"%s\"", to); if (cert_valid_to <= cert_valid_from) fatal("Empty certificate validity interval"); diff --git a/sshd.8 b/sshd.8 index 0d52cc50..f973cc38 100644 --- a/sshd.8 +++ b/sshd.8 @@ -33,8 +33,8 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.\" $OpenBSD: sshd.8,v 1.296 2018/03/03 06:37:53 dtucker Exp $ -.Dd $Mdocdate: March 3 2018 $ +.\" $OpenBSD: sshd.8,v 1.297 2018/03/12 00:52:01 djm Exp $ +.Dd $Mdocdate: March 12 2018 $ .Dt SSHD 8 .Os .Sh NAME @@ -602,6 +602,10 @@ Enables execution of previously disabled by the .Cm restrict option. +.It Cm valid-before="timespec" +Specifies a time after which the key will not be accepted. +The time may be specified as a YYYYMMDD date or a YYYYMMDDHHMM[SS] time +in the system time-zone. .It Cm X11-forwarding Permits X11 forwarding previously disabled by the .Cm restrict -- cgit v1.2.3