Dmitry Belyavskiy c5140c
diff --git a/misc.c b/misc.c
Dmitry Belyavskiy c5140c
index a8e87430..f2135803 100644
Dmitry Belyavskiy c5140c
--- a/misc.c
Dmitry Belyavskiy c5140c
+++ b/misc.c
Dmitry Belyavskiy c5140c
@@ -2399,15 +2399,26 @@ parse_absolute_time(const char *s, uint64_t *tp)
Dmitry Belyavskiy c5140c
 	struct tm tm;
Dmitry Belyavskiy c5140c
 	time_t tt;
Dmitry Belyavskiy c5140c
 	char buf[32], *fmt;
Dmitry Belyavskiy c5140c
+	const char *cp;
Dmitry Belyavskiy c5140c
+	size_t l;
Dmitry Belyavskiy c5140c
+	int is_utc = 0;
Dmitry Belyavskiy c5140c
 
Dmitry Belyavskiy c5140c
 	*tp = 0;
Dmitry Belyavskiy c5140c
 
Dmitry Belyavskiy c5140c
+	l = strlen(s);
Dmitry Belyavskiy c5140c
+	if (l > 1 && strcasecmp(s + l - 1, "Z") == 0) {
Dmitry Belyavskiy c5140c
+		is_utc = 1;
Dmitry Belyavskiy c5140c
+		l--;
Dmitry Belyavskiy c5140c
+	} else if (l > 3 && strcasecmp(s + l - 3, "UTC") == 0) {
Dmitry Belyavskiy c5140c
+		is_utc = 1;
Dmitry Belyavskiy c5140c
+		l -= 3;
Dmitry Belyavskiy c5140c
+	}
Dmitry Belyavskiy c5140c
 	/*
Dmitry Belyavskiy c5140c
 	 * POSIX strptime says "The application shall ensure that there
Dmitry Belyavskiy c5140c
 	 * is white-space or other non-alphanumeric characters between
Dmitry Belyavskiy c5140c
 	 * any two conversion specifications" so arrange things this way.
Dmitry Belyavskiy c5140c
 	 */
Dmitry Belyavskiy c5140c
-	switch (strlen(s)) {
Dmitry Belyavskiy c5140c
+	switch (l) {
Dmitry Belyavskiy c5140c
 	case 8: /* YYYYMMDD */
Dmitry Belyavskiy c5140c
 		fmt = "%Y-%m-%d";
Dmitry Belyavskiy c5140c
 		snprintf(buf, sizeof(buf), "%.4s-%.2s-%.2s", s, s + 4, s + 6);
Dmitry Belyavskiy c5140c
@@ -2427,10 +2438,15 @@ parse_absolute_time(const char *s, uint64_t *tp)
Dmitry Belyavskiy c5140c
 	}
Dmitry Belyavskiy c5140c
 
Dmitry Belyavskiy c5140c
 	memset(&tm, 0, sizeof(tm));
Dmitry Belyavskiy c5140c
-	if (strptime(buf, fmt, &tm) == NULL)
Dmitry Belyavskiy c5140c
-		return SSH_ERR_INVALID_FORMAT;
Dmitry Belyavskiy c5140c
-	if ((tt = mktime(&tm)) < 0)
Dmitry Belyavskiy c5140c
+	if ((cp = strptime(buf, fmt, &tm)) == NULL || *cp != '\0')
Dmitry Belyavskiy c5140c
 		return SSH_ERR_INVALID_FORMAT;
Dmitry Belyavskiy c5140c
+	if (is_utc) {
Dmitry Belyavskiy c5140c
+		if ((tt = timegm(&tm)) < 0)
Dmitry Belyavskiy c5140c
+			return SSH_ERR_INVALID_FORMAT;
Dmitry Belyavskiy c5140c
+	} else {
Dmitry Belyavskiy c5140c
+		if ((tt = mktime(&tm)) < 0)
Dmitry Belyavskiy c5140c
+			return SSH_ERR_INVALID_FORMAT;
Dmitry Belyavskiy c5140c
+	}
Dmitry Belyavskiy c5140c
 	/* success */
Dmitry Belyavskiy c5140c
 	*tp = (uint64_t)tt;
Dmitry Belyavskiy c5140c
 	return 0;
Dmitry Belyavskiy c5140c
diff --git a/regress/unittests/misc/test_convtime.c b/regress/unittests/misc/test_convtime.c
Dmitry Belyavskiy c5140c
index ef6fd77d..4794dbd9 100644
Dmitry Belyavskiy c5140c
--- a/regress/unittests/misc/test_convtime.c
Dmitry Belyavskiy c5140c
+++ b/regress/unittests/misc/test_convtime.c
Dmitry Belyavskiy c5140c
@@ -20,6 +20,7 @@
Dmitry Belyavskiy c5140c
 
Dmitry Belyavskiy c5140c
 #include "log.h"
Dmitry Belyavskiy c5140c
 #include "misc.h"
Dmitry Belyavskiy c5140c
+#include "ssherr.h"
Dmitry Belyavskiy c5140c
 
Dmitry Belyavskiy c5140c
 void test_convtime(void);
Dmitry Belyavskiy c5140c
 
Dmitry Belyavskiy c5140c
@@ -27,6 +28,7 @@ void
Dmitry Belyavskiy c5140c
 test_convtime(void)
Dmitry Belyavskiy c5140c
 {
Dmitry Belyavskiy c5140c
 	char buf[1024];
Dmitry Belyavskiy c5140c
+	uint64_t t;
Dmitry Belyavskiy c5140c
 
Dmitry Belyavskiy c5140c
 	TEST_START("misc_convtime");
Dmitry Belyavskiy c5140c
 	ASSERT_INT_EQ(convtime("0"), 0);
Dmitry Belyavskiy c5140c
@@ -56,4 +58,64 @@ test_convtime(void)
Dmitry Belyavskiy c5140c
 	ASSERT_INT_EQ(convtime("3550w5d3h14m8s"), -1);
Dmitry Belyavskiy c5140c
 #endif
Dmitry Belyavskiy c5140c
 	TEST_DONE();
Dmitry Belyavskiy c5140c
+
Dmitry Belyavskiy c5140c
+	/* XXX timezones/DST make verification of this tricky */
Dmitry Belyavskiy c5140c
+	/* XXX maybe setenv TZ and tzset() to make it unambiguous? */
Dmitry Belyavskiy c5140c
+	TEST_START("misc_parse_absolute_time");
Dmitry Belyavskiy c5140c
+	ASSERT_INT_EQ(parse_absolute_time("20000101", &t), 0);
Dmitry Belyavskiy c5140c
+	ASSERT_INT_EQ(parse_absolute_time("200001011223", &t), 0);
Dmitry Belyavskiy c5140c
+	ASSERT_INT_EQ(parse_absolute_time("20000101122345", &t), 0);
Dmitry Belyavskiy c5140c
+
Dmitry Belyavskiy c5140c
+	/* forced UTC TZ */
Dmitry Belyavskiy c5140c
+	ASSERT_INT_EQ(parse_absolute_time("20000101Z", &t), 0);
Dmitry Belyavskiy c5140c
+	ASSERT_U64_EQ(t, 946684800);
Dmitry Belyavskiy c5140c
+	ASSERT_INT_EQ(parse_absolute_time("200001011223Z", &t), 0);
Dmitry Belyavskiy c5140c
+	ASSERT_U64_EQ(t, 946729380);
Dmitry Belyavskiy c5140c
+	ASSERT_INT_EQ(parse_absolute_time("20000101122345Z", &t), 0);
Dmitry Belyavskiy c5140c
+	ASSERT_U64_EQ(t, 946729425);
Dmitry Belyavskiy c5140c
+	ASSERT_INT_EQ(parse_absolute_time("20000101UTC", &t), 0);
Dmitry Belyavskiy c5140c
+	ASSERT_U64_EQ(t, 946684800);
Dmitry Belyavskiy c5140c
+	ASSERT_INT_EQ(parse_absolute_time("200001011223UTC", &t), 0);
Dmitry Belyavskiy c5140c
+	ASSERT_U64_EQ(t, 946729380);
Dmitry Belyavskiy c5140c
+	ASSERT_INT_EQ(parse_absolute_time("20000101122345UTC", &t), 0);
Dmitry Belyavskiy c5140c
+	ASSERT_U64_EQ(t, 946729425);
Dmitry Belyavskiy c5140c
+
Dmitry Belyavskiy c5140c
+	/* Bad month */
Dmitry Belyavskiy c5140c
+	ASSERT_INT_EQ(parse_absolute_time("20001301", &t),
Dmitry Belyavskiy c5140c
+	    SSH_ERR_INVALID_FORMAT);
Dmitry Belyavskiy c5140c
+	ASSERT_INT_EQ(parse_absolute_time("20000001", &t),
Dmitry Belyavskiy c5140c
+	    SSH_ERR_INVALID_FORMAT);
Dmitry Belyavskiy c5140c
+	/* Incomplete */
Dmitry Belyavskiy c5140c
+	ASSERT_INT_EQ(parse_absolute_time("2", &t),
Dmitry Belyavskiy c5140c
+	    SSH_ERR_INVALID_FORMAT);
Dmitry Belyavskiy c5140c
+	ASSERT_INT_EQ(parse_absolute_time("2000", &t),
Dmitry Belyavskiy c5140c
+	    SSH_ERR_INVALID_FORMAT);
Dmitry Belyavskiy c5140c
+	ASSERT_INT_EQ(parse_absolute_time("20000", &t),
Dmitry Belyavskiy c5140c
+	    SSH_ERR_INVALID_FORMAT);
Dmitry Belyavskiy c5140c
+	ASSERT_INT_EQ(parse_absolute_time("200001", &t),
Dmitry Belyavskiy c5140c
+	    SSH_ERR_INVALID_FORMAT);
Dmitry Belyavskiy c5140c
+	ASSERT_INT_EQ(parse_absolute_time("2000010", &t),
Dmitry Belyavskiy c5140c
+	    SSH_ERR_INVALID_FORMAT);
Dmitry Belyavskiy c5140c
+	ASSERT_INT_EQ(parse_absolute_time("200001010", &t),
Dmitry Belyavskiy c5140c
+	    SSH_ERR_INVALID_FORMAT);
Dmitry Belyavskiy c5140c
+	/* Bad day, hour, minute, second */
Dmitry Belyavskiy c5140c
+	ASSERT_INT_EQ(parse_absolute_time("20000199", &t),
Dmitry Belyavskiy c5140c
+	    SSH_ERR_INVALID_FORMAT);
Dmitry Belyavskiy c5140c
+	ASSERT_INT_EQ(parse_absolute_time("200001019900", &t),
Dmitry Belyavskiy c5140c
+	    SSH_ERR_INVALID_FORMAT);
Dmitry Belyavskiy c5140c
+	ASSERT_INT_EQ(parse_absolute_time("200001010099", &t),
Dmitry Belyavskiy c5140c
+	    SSH_ERR_INVALID_FORMAT);
Dmitry Belyavskiy c5140c
+	ASSERT_INT_EQ(parse_absolute_time("20000101000099", &t),
Dmitry Belyavskiy c5140c
+	    SSH_ERR_INVALID_FORMAT);
Dmitry Belyavskiy c5140c
+	/* Invalid TZ specifier */
Dmitry Belyavskiy c5140c
+	ASSERT_INT_EQ(parse_absolute_time("20000101ZZ", &t),
Dmitry Belyavskiy c5140c
+	    SSH_ERR_INVALID_FORMAT);
Dmitry Belyavskiy c5140c
+	ASSERT_INT_EQ(parse_absolute_time("20000101PDT", &t),
Dmitry Belyavskiy c5140c
+	    SSH_ERR_INVALID_FORMAT);
Dmitry Belyavskiy c5140c
+	ASSERT_INT_EQ(parse_absolute_time("20000101U", &t),
Dmitry Belyavskiy c5140c
+	    SSH_ERR_INVALID_FORMAT);
Dmitry Belyavskiy c5140c
+	ASSERT_INT_EQ(parse_absolute_time("20000101UTCUTC", &t),
Dmitry Belyavskiy c5140c
+	    SSH_ERR_INVALID_FORMAT);
Dmitry Belyavskiy c5140c
+
Dmitry Belyavskiy c5140c
+	TEST_DONE();
Dmitry Belyavskiy c5140c
 }
Dmitry Belyavskiy c5140c
diff --git a/ssh-keygen.1 b/ssh-keygen.1
Dmitry Belyavskiy c5140c
index 5f429813..6aeab1cb 100644
Dmitry Belyavskiy c5140c
--- a/ssh-keygen.1
Dmitry Belyavskiy c5140c
+++ b/ssh-keygen.1
Dmitry Belyavskiy c5140c
@@ -511,8 +511,11 @@ Print the full public key to standard output after signature verification.
Dmitry Belyavskiy c5140c
 .It Cm verify-time Ns = Ns Ar timestamp
Dmitry Belyavskiy c5140c
 Specifies a time to use when validating signatures instead of the current
Dmitry Belyavskiy c5140c
 time.
Dmitry Belyavskiy c5140c
-The time may be specified as a date in YYYYMMDD format or a time
Dmitry Belyavskiy c5140c
-in YYYYMMDDHHMM[SS] format.
Dmitry Belyavskiy c5140c
+The time may be specified as a date or time in the YYYYMMDD[Z] or
Dmitry Belyavskiy c5140c
+in YYYYMMDDHHMM[SS][Z] formats.
Dmitry Belyavskiy c5140c
+Dates and times will be interpreted in the current system time zone unless
Dmitry Belyavskiy c5140c
+suffixed with a Z character, which causes them to be interpreted in the
Dmitry Belyavskiy c5140c
+UTC time zone.
Dmitry Belyavskiy c5140c
 .El
Dmitry Belyavskiy c5140c
 .Pp
Dmitry Belyavskiy c5140c
 The
Dmitry Belyavskiy c5140c
@@ -603,31 +606,67 @@ A validity interval may consist of a single time, indicating that the
Dmitry Belyavskiy c5140c
 certificate is valid beginning now and expiring at that time, or may consist
Dmitry Belyavskiy c5140c
 of two times separated by a colon to indicate an explicit time interval.
Dmitry Belyavskiy c5140c
 .Pp
Dmitry Belyavskiy c5140c
-The start time may be specified as the string
Dmitry Belyavskiy c5140c
+The start time may be specified as:
Dmitry Belyavskiy c5140c
+.Bl -bullet -compact
Dmitry Belyavskiy c5140c
+.It
Dmitry Belyavskiy c5140c
+The string
Dmitry Belyavskiy c5140c
 .Dq always
Dmitry Belyavskiy c5140c
-to indicate the certificate has no specified start time,
Dmitry Belyavskiy c5140c
-a date in YYYYMMDD format, a time in YYYYMMDDHHMM[SS] format,
Dmitry Belyavskiy c5140c
-a relative time (to the current time) consisting of a minus sign followed by
Dmitry Belyavskiy c5140c
-an interval in the format described in the
Dmitry Belyavskiy c5140c
+to indicate the certificate has no specified start time.
Dmitry Belyavskiy c5140c
+.It
Dmitry Belyavskiy c5140c
+A date or time in the system time zone formatted as YYYYMMDD or
Dmitry Belyavskiy c5140c
+YYYYMMDDHHMM[SS].
Dmitry Belyavskiy c5140c
+.It
Dmitry Belyavskiy c5140c
+A date or time in the UTC time zone as YYYYMMDDZ or YYYYMMDDHHMM[SS]Z.
Dmitry Belyavskiy c5140c
+.It
Dmitry Belyavskiy c5140c
+A relative time before the current system time consisting of a minus sign
Dmitry Belyavskiy c5140c
+followed by an interval in the format described in the
Dmitry Belyavskiy c5140c
 TIME FORMATS section of
Dmitry Belyavskiy c5140c
 .Xr sshd_config 5 .
Dmitry Belyavskiy c5140c
+.It
Dmitry Belyavskiy c5140c
+A raw seconds since epoch (Jan 1 1970 00:00:00 UTC) as a hexadecimal
Dmitry Belyavskiy c5140c
+number beginning with
Dmitry Belyavskiy c5140c
+.Dq 0x .
Dmitry Belyavskiy c5140c
+.El
Dmitry Belyavskiy c5140c
 .Pp
Dmitry Belyavskiy c5140c
-The end time may be specified as a YYYYMMDD date, a YYYYMMDDHHMM[SS] time,
Dmitry Belyavskiy c5140c
-a relative time starting with a plus character or the string
Dmitry Belyavskiy c5140c
+The end time may be specified similarly to the start time:
Dmitry Belyavskiy c5140c
+.Bl -bullet -compact
Dmitry Belyavskiy c5140c
+.It
Dmitry Belyavskiy c5140c
+The string
Dmitry Belyavskiy c5140c
 .Dq forever
Dmitry Belyavskiy c5140c
-to indicate that the certificate has no expiry date.
Dmitry Belyavskiy c5140c
+to indicate the certificate has no specified end time.
Dmitry Belyavskiy c5140c
+.It
Dmitry Belyavskiy c5140c
+A date or time in the system time zone formatted as YYYYMMDD or
Dmitry Belyavskiy c5140c
+YYYYMMDDHHMM[SS].
Dmitry Belyavskiy c5140c
+.It
Dmitry Belyavskiy c5140c
+A date or time in the UTC time zone as YYYYMMDDZ or YYYYMMDDHHMM[SS]Z.
Dmitry Belyavskiy c5140c
+.It
Dmitry Belyavskiy c5140c
+A relative time after the current system time consisting of a plus sign
Dmitry Belyavskiy c5140c
+followed by an interval in the format described in the
Dmitry Belyavskiy c5140c
+TIME FORMATS section of
Dmitry Belyavskiy c5140c
+.Xr sshd_config 5 .
Dmitry Belyavskiy c5140c
+.It
Dmitry Belyavskiy c5140c
+A raw seconds since epoch (Jan 1 1970 00:00:00 UTC) as a hexadecimal
Dmitry Belyavskiy c5140c
+number beginning with
Dmitry Belyavskiy c5140c
+.Dq 0x .
Dmitry Belyavskiy c5140c
+.El
Dmitry Belyavskiy c5140c
 .Pp
Dmitry Belyavskiy c5140c
 For example:
Dmitry Belyavskiy c5140c
-.Dq +52w1d
Dmitry Belyavskiy c5140c
-(valid from now to 52 weeks and one day from now),
Dmitry Belyavskiy c5140c
-.Dq -4w:+4w
Dmitry Belyavskiy c5140c
-(valid from four weeks ago to four weeks from now),
Dmitry Belyavskiy c5140c
-.Dq 20100101123000:20110101123000
Dmitry Belyavskiy c5140c
-(valid from 12:30 PM, January 1st, 2010 to 12:30 PM, January 1st, 2011),
Dmitry Belyavskiy c5140c
-.Dq -1d:20110101
Dmitry Belyavskiy c5140c
-(valid from yesterday to midnight, January 1st, 2011),
Dmitry Belyavskiy c5140c
-.Dq -1m:forever
Dmitry Belyavskiy c5140c
-(valid from one minute ago and never expiring).
Dmitry Belyavskiy c5140c
+.Bl -tag -width Ds
Dmitry Belyavskiy c5140c
+.It +52w1d
Dmitry Belyavskiy c5140c
+Valid from now to 52 weeks and one day from now.
Dmitry Belyavskiy c5140c
+.It -4w:+4w
Dmitry Belyavskiy c5140c
+Valid from four weeks ago to four weeks from now.
Dmitry Belyavskiy c5140c
+.It 20100101123000:20110101123000
Dmitry Belyavskiy c5140c
+Valid from 12:30 PM, January 1st, 2010 to 12:30 PM, January 1st, 2011.
Dmitry Belyavskiy c5140c
+.It 20100101123000Z:20110101123000Z
Dmitry Belyavskiy c5140c
+Similar, but interpreted in the UTC time zone rather than the system time zone.
Dmitry Belyavskiy c5140c
+.It -1d:20110101
Dmitry Belyavskiy c5140c
+Valid from yesterday to midnight, January 1st, 2011.
Dmitry Belyavskiy c5140c
+.It 0x1:0x2000000000
Dmitry Belyavskiy c5140c
+Valid from roughly early 1970 to May 2033.
Dmitry Belyavskiy c5140c
+.It -1m:forever
Dmitry Belyavskiy c5140c
+Valid from one minute ago and never expiring.
Dmitry Belyavskiy c5140c
+.El
Dmitry Belyavskiy c5140c
 .It Fl v
Dmitry Belyavskiy c5140c
 Verbose mode.
Dmitry Belyavskiy c5140c
 Causes
Dmitry Belyavskiy c5140c
@@ -1206,7 +1245,10 @@ signature object and presented on the verification command-line must
Dmitry Belyavskiy c5140c
 match the specified list before the key will be considered acceptable.
Dmitry Belyavskiy c5140c
 .It Cm valid-after Ns = Ns "timestamp"
Dmitry Belyavskiy c5140c
 Indicates that the key is valid for use at or after the specified timestamp,
Dmitry Belyavskiy c5140c
-which may be a date in YYYYMMDD format or a time in YYYYMMDDHHMM[SS] format.
Dmitry Belyavskiy c5140c
+which may be a date or time in the YYYYMMDD[Z] or YYYYMMDDHHMM[SS][Z] formats.
Dmitry Belyavskiy c5140c
+Dates and times will be interpreted in the current system time zone unless
Dmitry Belyavskiy c5140c
+suffixed with a Z character, which causes them to be interpreted in the UTC
Dmitry Belyavskiy c5140c
+time zone.
Dmitry Belyavskiy c5140c
 .It Cm valid-before Ns = Ns "timestamp"
Dmitry Belyavskiy c5140c
 Indicates that the key is valid for use at or before the specified timestamp.
Dmitry Belyavskiy c5140c
 .El
Dmitry Belyavskiy c5140c
diff --git a/ssh-keygen.c b/ssh-keygen.c
Dmitry Belyavskiy c5140c
index 20b321cc..9b2beda0 100644
Dmitry Belyavskiy c5140c
--- a/ssh-keygen.c
Dmitry Belyavskiy c5140c
+++ b/ssh-keygen.c
Dmitry Belyavskiy c5140c
@@ -1916,6 +1916,21 @@ parse_relative_time(const char *s, time_t now)
Dmitry Belyavskiy c5140c
 	return now + (u_int64_t)(secs * mul);
Dmitry Belyavskiy c5140c
 }
Dmitry Belyavskiy c5140c
 
Dmitry Belyavskiy c5140c
+static void
Dmitry Belyavskiy c5140c
+parse_hex_u64(const char *s, uint64_t *up)
Dmitry Belyavskiy c5140c
+{
Dmitry Belyavskiy c5140c
+	char *ep;
Dmitry Belyavskiy c5140c
+	unsigned long long ull;
Dmitry Belyavskiy c5140c
+
Dmitry Belyavskiy c5140c
+	errno = 0;
Dmitry Belyavskiy c5140c
+	ull = strtoull(s, &ep, 16);
Dmitry Belyavskiy c5140c
+	if (*s == '\0' || *ep != '\0')
Dmitry Belyavskiy c5140c
+		fatal("Invalid certificate time: not a number");
Dmitry Belyavskiy c5140c
+	if (errno == ERANGE && ull == ULONG_MAX)
Dmitry Belyavskiy c5140c
+		fatal_fr(SSH_ERR_SYSTEM_ERROR, "Invalid certificate time");
Dmitry Belyavskiy c5140c
+	*up = (uint64_t)ull;
Dmitry Belyavskiy c5140c
+}
Dmitry Belyavskiy c5140c
+
Dmitry Belyavskiy c5140c
 static void
Dmitry Belyavskiy c5140c
 parse_cert_times(char *timespec)
Dmitry Belyavskiy c5140c
 {
Dmitry Belyavskiy c5140c
@@ -1938,8 +1953,8 @@ parse_cert_times(char *timespec)
Dmitry Belyavskiy c5140c
 
Dmitry Belyavskiy c5140c
 	/*
Dmitry Belyavskiy c5140c
 	 * from:to, where
Dmitry Belyavskiy c5140c
-	 * from := [+-]timespec | YYYYMMDD | YYYYMMDDHHMMSS | "always"
Dmitry Belyavskiy c5140c
-	 *   to := [+-]timespec | YYYYMMDD | YYYYMMDDHHMMSS | "forever"
Dmitry Belyavskiy c5140c
+	 * from := [+-]timespec | YYYYMMDD | YYYYMMDDHHMMSS | 0x... | "always"
Dmitry Belyavskiy c5140c
+	 *   to := [+-]timespec | YYYYMMDD | YYYYMMDDHHMMSS | 0x... | "forever"
Dmitry Belyavskiy c5140c
 	 */
Dmitry Belyavskiy c5140c
 	from = xstrdup(timespec);
Dmitry Belyavskiy c5140c
 	to = strchr(from, ':');
Dmitry Belyavskiy c5140c
@@ -1951,6 +1966,8 @@ parse_cert_times(char *timespec)
Dmitry Belyavskiy c5140c
 		cert_valid_from = parse_relative_time(from, now);
Dmitry Belyavskiy c5140c
 	else if (strcmp(from, "always") == 0)
Dmitry Belyavskiy c5140c
 		cert_valid_from = 0;
Dmitry Belyavskiy c5140c
+	else if (strncmp(from, "0x", 2) == 0)
Dmitry Belyavskiy c5140c
+		parse_hex_u64(from, &cert_valid_from);
Dmitry Belyavskiy c5140c
 	else if (parse_absolute_time(from, &cert_valid_from) != 0)
Dmitry Belyavskiy c5140c
 		fatal("Invalid from time \"%s\"", from);
Dmitry Belyavskiy c5140c
 
Dmitry Belyavskiy c5140c
@@ -1958,6 +1975,8 @@ parse_cert_times(char *timespec)
Dmitry Belyavskiy c5140c
 		cert_valid_to = parse_relative_time(to, now);
Dmitry Belyavskiy c5140c
 	else if (strcmp(to, "forever") == 0)
Dmitry Belyavskiy c5140c
 		cert_valid_to = ~(u_int64_t)0;
Dmitry Belyavskiy c5140c
+	else if (strncmp(to, "0x", 2) == 0)
Dmitry Belyavskiy c5140c
+		parse_hex_u64(to, &cert_valid_to);
Dmitry Belyavskiy c5140c
 	else if (parse_absolute_time(to, &cert_valid_to) != 0)
Dmitry Belyavskiy c5140c
 		fatal("Invalid to time \"%s\"", to);
Dmitry Belyavskiy c5140c
 
Dmitry Belyavskiy c5140c
diff --git a/sshd.8 b/sshd.8
Dmitry Belyavskiy c5140c
index 2b50514e..8ccc5bc0 100644
Dmitry Belyavskiy c5140c
--- a/sshd.8
Dmitry Belyavskiy c5140c
+++ b/sshd.8
Dmitry Belyavskiy c5140c
@@ -533,8 +533,9 @@ controlled via the
Dmitry Belyavskiy c5140c
 option.
Dmitry Belyavskiy c5140c
 .It Cm expiry-time="timespec"
Dmitry Belyavskiy c5140c
 Specifies a time after which the key will not be accepted.
Dmitry Belyavskiy c5140c
-The time may be specified as a YYYYMMDD date or a YYYYMMDDHHMM[SS] time
Dmitry Belyavskiy c5140c
-in the system time-zone.
Dmitry Belyavskiy c5140c
+The time may be specified as a YYYYMMDD[Z] date or a YYYYMMDDHHMM[SS][Z] time.
Dmitry Belyavskiy c5140c
+Dates and times will be interpreted in the system time zone unless suffixed
Dmitry Belyavskiy c5140c
+by a Z character, in which case they will be interpreted in the UTC time zone.
Dmitry Belyavskiy c5140c
 .It Cm from="pattern-list"
Dmitry Belyavskiy c5140c
 Specifies that in addition to public key authentication, either the canonical
Dmitry Belyavskiy c5140c
 name of the remote host or its IP address must be present in the