upstream: add valid-before="[time]" authorized_keys option. A

simple way of giving a key an expiry date. ok markus@

OpenBSD-Commit-ID: 1793b4dd5184fa87f42ed33c7b0f4f02bc877947
This commit is contained in:
djm@openbsd.org 2018-03-12 00:52:01 +00:00 committed by Damien Miller
parent fbd733ab7a
commit bf0fbf2b11
8 changed files with 128 additions and 56 deletions

View File

@ -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 <djm@mindrot.org> * Copyright (c) 2018 Damien Miller <djm@mindrot.org>
* *
@ -311,6 +311,7 @@ sshauthopt_parse(const char *opts, const char **errstrp)
int r; int r;
struct sshauthopt *ret = NULL; struct sshauthopt *ret = NULL;
const char *errstr = "unknown error"; const char *errstr = "unknown error";
uint64_t valid_before;
if (errstrp != NULL) if (errstrp != NULL)
*errstrp = NULL; *errstrp = NULL;
@ -366,6 +367,19 @@ sshauthopt_parse(const char *opts, const char **errstrp)
&errstr); &errstr);
if (ret->required_from_host_keys == NULL) if (ret->required_from_host_keys == NULL)
goto fail; 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")) { } else if (opt_match(&opts, "environment")) {
if (ret->nenv > INT_MAX) { if (ret->nenv > INT_MAX) {
errstr = "too many environment strings"; errstr = "too many environment strings";
@ -572,6 +586,13 @@ sshauthopt_merge(const struct sshauthopt *primary,
OPTFLAG(permit_user_rc); OPTFLAG(permit_user_rc);
#undef OPTFLAG #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 * When both multiple forced-command are specified, only
* proceed if they are identical, otherwise fail. * proceed if they are identical, otherwise fail.
@ -631,6 +652,7 @@ sshauthopt_copy(const struct sshauthopt *orig)
OPTSCALAR(restricted); OPTSCALAR(restricted);
OPTSCALAR(cert_authority); OPTSCALAR(cert_authority);
OPTSCALAR(force_tun_device); OPTSCALAR(force_tun_device);
OPTSCALAR(valid_before);
#undef OPTSCALAR #undef OPTSCALAR
#define OPTSTRING(x) \ #define OPTSTRING(x) \
do { \ do { \
@ -751,14 +773,15 @@ sshauthopt_serialise(const struct sshauthopt *opts, struct sshbuf *m,
{ {
int r = SSH_ERR_INTERNAL_ERROR; 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 || 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_agent_forwarding_flag)) != 0 ||
(r = sshbuf_put_u8(m, opts->permit_x11_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_pty_flag)) != 0 ||
(r = sshbuf_put_u8(m, opts->permit_user_rc)) != 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->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; return r;
/* tunnel number can be negative to indicate "unset" */ /* tunnel number can be negative to indicate "unset" */
@ -815,6 +838,9 @@ sshauthopt_deserialise(struct sshbuf *m, struct sshauthopt **optsp)
OPT_FLAG(cert_authority); OPT_FLAG(cert_authority);
#undef OPT_FLAG #undef OPT_FLAG
if ((r = sshbuf_get_u64(m, &opts->valid_before)) != 0)
goto out;
/* tunnel number can be negative to indicate "unset" */ /* tunnel number can be negative to indicate "unset" */
if ((r = sshbuf_get_u8(m, &f)) != 0 || if ((r = sshbuf_get_u8(m, &f)) != 0 ||
(r = sshbuf_get_u32(m, &tmp)) != 0) (r = sshbuf_get_u32(m, &tmp)) != 0)

View File

@ -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 <djm@mindrot.org> * Copyright (c) 2018 Damien Miller <djm@mindrot.org>
@ -37,6 +37,9 @@ struct sshauthopt {
/* "restrict" keyword was invoked */ /* "restrict" keyword was invoked */
int restricted; int restricted;
/* key/principal expiry date */
uint64_t valid_before;
/* Certificate-related options */ /* Certificate-related options */
int cert_authority; int cert_authority;
char *cert_principals; char *cert_principals;

28
auth.c
View File

@ -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. * 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 && int do_permitopen = opts->npermitopen > 0 &&
(options.allow_tcp_forwarding & FORWARD_LOCAL) != 0; (options.allow_tcp_forwarding & FORWARD_LOCAL) != 0;
size_t i; 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 */ /* 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->permit_agent_forwarding_flag ? " agent-forwarding" : "",
opts->force_command == NULL ? "" : " command", opts->force_command == NULL ? "" : " command",
do_env ? " environment" : "", do_env ? " environment" : "",
opts->valid_before == 0 ? "" : "expires",
do_permitopen ? " permitopen" : "", do_permitopen ? " permitopen" : "",
opts->permit_port_forwarding_flag ? " port-forwarding" : "", opts->permit_port_forwarding_flag ? " port-forwarding" : "",
opts->cert_principals == NULL ? "" : " principals", opts->cert_principals == NULL ? "" : " principals",
opts->permit_pty_flag ? " pty" : "", opts->permit_pty_flag ? " pty" : "",
opts->force_tun_device == -1 ? "" : " tun=", 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_user_rc ? " user-rc" : "",
opts->permit_x11_forwarding_flag ? " x11-forwarding" : ""); 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. */ /* 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) { if (opts->cert_principals != NULL) {
debug("%s: authorized principals: \"%s\"", debug("%s: authorized principals: \"%s\"",
loc, opts->cert_principals); 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_ip = ssh_remote_ipaddr(ssh);
const char *remote_host = auth_get_canonical_hostname(ssh, const char *remote_host = auth_get_canonical_hostname(ssh,
options.use_dns); 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 */ /* Consistency checks */
if (opts->cert_principals != NULL && !opts->cert_authority) { if (opts->cert_principals != NULL && !opts->cert_authority) {
debug("%s: principals on non-CA key", loc); debug("%s: principals on non-CA key", loc);

55
misc.c
View File

@ -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) 2000 Markus Friedl. All rights reserved.
* Copyright (c) 2005,2006 Damien Miller. 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; *val = (int)num;
return errstr; 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);
}

4
misc.h
View File

@ -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 <ylo@cs.hut.fi> * Author: Tatu Ylonen <ylo@cs.hut.fi>
@ -75,6 +75,8 @@ void lowercase(char *s);
int unix_listener(const char *, int, int); int unix_listener(const char *, int, int);
int valid_domain(char *, int, const char **); int valid_domain(char *, int, const char **);
const char *atoi_err(const char *, int *); 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); void sock_set_v6only(int);

View File

@ -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 <ylo@cs.hut.fi> .\" Author: Tatu Ylonen <ylo@cs.hut.fi>
.\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland .\" 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 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" 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 .Dt SSH-KEYGEN 1
.Os .Os
.Sh NAME .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 The start time may be specified as the string
.Dq always .Dq always
to indicate the certificate has no specified start time, 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 a relative time (to the current time) consisting of a minus sign followed by
an interval in the format described in the an interval in the format described in the
TIME FORMATS section of TIME FORMATS section of
.Xr sshd_config 5 . .Xr sshd_config 5 .
.Pp .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 a relative time starting with a plus character or the string
.Dq forever .Dq forever
to indicate that the certificate has no expirty date. to indicate that the certificate has no expirty date.

View File

@ -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 <ylo@cs.hut.fi> * Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland * Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -1798,40 +1798,6 @@ parse_relative_time(const char *s, time_t now)
return now + (u_int64_t)(secs * mul); 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 static void
parse_cert_times(char *timespec) parse_cert_times(char *timespec)
{ {
@ -1867,15 +1833,15 @@ parse_cert_times(char *timespec)
cert_valid_from = parse_relative_time(from, now); cert_valid_from = parse_relative_time(from, now);
else if (strcmp(from, "always") == 0) else if (strcmp(from, "always") == 0)
cert_valid_from = 0; cert_valid_from = 0;
else else if (parse_absolute_time(from, &cert_valid_from) != 0)
cert_valid_from = parse_absolute_time(from); fatal("Invalid from time \"%s\"", from);
if (*to == '-' || *to == '+') if (*to == '-' || *to == '+')
cert_valid_to = parse_relative_time(to, now); cert_valid_to = parse_relative_time(to, now);
else if (strcmp(to, "forever") == 0) else if (strcmp(to, "forever") == 0)
cert_valid_to = ~(u_int64_t)0; cert_valid_to = ~(u_int64_t)0;
else else if (parse_absolute_time(to, &cert_valid_to) != 0)
cert_valid_to = parse_absolute_time(to); fatal("Invalid to time \"%s\"", to);
if (cert_valid_to <= cert_valid_from) if (cert_valid_to <= cert_valid_from)
fatal("Empty certificate validity interval"); fatal("Empty certificate validity interval");

8
sshd.8
View File

@ -33,8 +33,8 @@
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" 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 $ .\" $OpenBSD: sshd.8,v 1.297 2018/03/12 00:52:01 djm Exp $
.Dd $Mdocdate: March 3 2018 $ .Dd $Mdocdate: March 12 2018 $
.Dt SSHD 8 .Dt SSHD 8
.Os .Os
.Sh NAME .Sh NAME
@ -602,6 +602,10 @@ Enables execution of
previously disabled by the previously disabled by the
.Cm restrict .Cm restrict
option. 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 .It Cm X11-forwarding
Permits X11 forwarding previously disabled by the Permits X11 forwarding previously disabled by the
.Cm restrict .Cm restrict