From 8c69e467527c5ee484c9a921e9b5fd18c0c49b12 Mon Sep 17 00:00:00 2001
From: Stef Walter <stefw@gnome.org>
Date: Fri, 29 Mar 2013 13:17:29 +0100
Subject: [PATCH] Don't respect timezones for CKA_START_DATE or CKA_END_DATE

The PKCS#11 specification does not note what timezone these dates
are in. In addition the time values are not represented in PKCS#11.

So don't reinterpret certificate dates, other than filling in the
century for dates that have a two digit year.

Lastly, these are low resolution optional fields so not being all
strict about timezones here is appropriate.

https://bugs.freedesktop.org/show_bug.cgi?id=62825
---
 common/asn1.c              | 332 -------------------------------------
 common/asn1.h              |   6 -
 trust/builder.c            | 100 ++++++++---
 trust/tests/test-builder.c |  14 +-
 4 files changed, 81 insertions(+), 371 deletions(-)

diff --git a/common/asn1.c b/common/asn1.c
index c98d959..45d91ab 100644
--- a/common/asn1.c
+++ b/common/asn1.c
@@ -192,338 +192,6 @@ p11_asn1_encode (node_asn *asn,
 	return der;
 }
 
-static int
-atoin (const char *p,
-       int digits)
-{
-	int ret = 0, base = 1;
-	while(--digits >= 0) {
-		if (p[digits] < '0' || p[digits] > '9')
-			return -1;
-		ret += (p[digits] - '0') * base;
-		base *= 10;
-	}
-	return ret;
-}
-
-static int
-two_to_four_digit_year (int year)
-{
-	time_t now;
-	struct tm tm;
-	int century, current;
-
-	return_val_if_fail (year >= 0 && year <= 99, -1);
-
-	/* Get the current year */
-	now = time (NULL);
-	return_val_if_fail (now >= 0, -1);
-	if (!gmtime_r (&now, &tm))
-		return_val_if_reached (-1);
-
-	current = (tm.tm_year % 100);
-	century = (tm.tm_year + 1900) - current;
-
-	/*
-	 * Check if it's within 40 years before the
-	 * current date.
-	 */
-	if (current < 40) {
-		if (year < current)
-			return century + year;
-		if (year > 100 - (40 - current))
-			return (century - 100) + year;
-	} else {
-		if (year < current && year > (current - 40))
-			return century + year;
-	}
-
-	/*
-	 * If it's after then adjust for overflows to
-	 * the next century.
-	 */
-	if (year < current)
-		return century + 100 + year;
-	else
-		return century + year;
-}
-
-static int
-parse_utc_time (const char *time,
-                size_t n_time,
-                struct tm *when,
-                int *offset)
-{
-	const char *p, *e;
-	int year;
-
-	assert (when != NULL);
-	assert (time != NULL);
-	assert (offset != NULL);
-
-	/* YYMMDDhhmmss.ffff Z | +0000 */
-	if (n_time < 6 || n_time >= 28)
-		return 0;
-
-	/* Reset everything to default legal values */
-	memset (when, 0, sizeof (*when));
-	*offset = 0;
-	when->tm_mday = 1;
-
-	/* Select the digits part of it */
-	p = time;
-	for (e = p; *e >= '0' && *e <= '9'; ++e);
-
-	if (p + 2 <= e) {
-		year = atoin (p, 2);
-		p += 2;
-
-		/*
-		 * 40 years in the past is our century. 60 years
-		 * in the future is the next century.
-		 */
-		when->tm_year = two_to_four_digit_year (year) - 1900;
-	}
-	if (p + 2 <= e) {
-		when->tm_mon = atoin (p, 2) - 1;
-		p += 2;
-	}
-	if (p + 2 <= e) {
-		when->tm_mday = atoin (p, 2);
-		p += 2;
-	}
-	if (p + 2 <= e) {
-		when->tm_hour = atoin (p, 2);
-		p += 2;
-	}
-	if (p + 2 <= e) {
-		when->tm_min = atoin (p, 2);
-		p += 2;
-	}
-	if (p + 2 <= e) {
-		when->tm_sec = atoin (p, 2);
-		p += 2;
-	}
-
-	if (when->tm_year < 0 || when->tm_year > 9999 ||
-	    when->tm_mon < 0 || when->tm_mon > 11 ||
-	    when->tm_mday < 1 || when->tm_mday > 31 ||
-	    when->tm_hour < 0 || when->tm_hour > 23 ||
-	    when->tm_min < 0 || when->tm_min > 59 ||
-	    when->tm_sec < 0 || when->tm_sec > 59)
-		return 0;
-
-	/* Make sure all that got parsed */
-	if (p != e)
-		return 0;
-
-	/* Now the remaining optional stuff */
-	e = time + n_time;
-
-	/* See if there's a fraction, and discard it if so */
-	if (p < e && *p == '.' && p + 5 <= e)
-		p += 5;
-
-	/* See if it's UTC */
-	if (p < e && *p == 'Z') {
-		p += 1;
-
-	/* See if it has a timezone */
-	} else if ((*p == '-' || *p == '+') && p + 3 <= e) {
-		int off, neg;
-
-		neg = *p == '-';
-		++p;
-
-		off = atoin (p, 2) * 3600;
-		if (off < 0 || off > 86400)
-			return 0;
-		p += 2;
-
-		if (p + 2 <= e) {
-			off += atoin (p, 2) * 60;
-			p += 2;
-		}
-
-		/* Use TZ offset */
-		if (neg)
-			*offset = 0 - off;
-		else
-			*offset = off;
-	}
-
-	/* Make sure everything got parsed */
-	if (p != e)
-		return 0;
-
-	return 1;
-}
-
-static int
-parse_general_time (const char *time,
-                    size_t n_time,
-                    struct tm *when,
-                    int *offset)
-{
-	const char *p, *e;
-
-	assert (time != NULL);
-	assert (when != NULL);
-	assert (offset != NULL);
-
-	/* YYYYMMDDhhmmss.ffff Z | +0000 */
-	if (n_time < 8 || n_time >= 30)
-		return 0;
-
-	/* Reset everything to default legal values */
-	memset (when, 0, sizeof (*when));
-	*offset = 0;
-	when->tm_mday = 1;
-
-	/* Select the digits part of it */
-	p = time;
-	for (e = p; *e >= '0' && *e <= '9'; ++e);
-
-	if (p + 4 <= e) {
-		when->tm_year = atoin (p, 4) - 1900;
-		p += 4;
-	}
-	if (p + 2 <= e) {
-		when->tm_mon = atoin (p, 2) - 1;
-		p += 2;
-	}
-	if (p + 2 <= e) {
-		when->tm_mday = atoin (p, 2);
-		p += 2;
-	}
-	if (p + 2 <= e) {
-		when->tm_hour = atoin (p, 2);
-		p += 2;
-	}
-	if (p + 2 <= e) {
-		when->tm_min = atoin (p, 2);
-		p += 2;
-	}
-	if (p + 2 <= e) {
-		when->tm_sec = atoin (p, 2);
-		p += 2;
-	}
-
-	if (when->tm_year < 0 || when->tm_year > 9999 ||
-	    when->tm_mon < 0 || when->tm_mon > 11 ||
-	    when->tm_mday < 1 || when->tm_mday > 31 ||
-	    when->tm_hour < 0 || when->tm_hour > 23 ||
-	    when->tm_min < 0 || when->tm_min > 59 ||
-	    when->tm_sec < 0 || when->tm_sec > 59)
-		return 0;
-
-	/* Make sure all that got parsed */
-	if (p != e)
-		return 0;
-
-	/* Now the remaining optional stuff */
-	e = time + n_time;
-
-	/* See if there's a fraction, and discard it if so */
-	if (p < e && *p == '.' && p + 5 <= e)
-		p += 5;
-
-	/* See if it's UTC */
-	if (p < e && *p == 'Z') {
-		p += 1;
-
-	/* See if it has a timezone */
-	} else if ((*p == '-' || *p == '+') && p + 3 <= e) {
-		int off, neg;
-
-		neg = *p == '-';
-		++p;
-
-		off = atoin (p, 2) * 3600;
-		if (off < 0 || off > 86400)
-			return 0;
-		p += 2;
-
-		if (p + 2 <= e) {
-			off += atoin (p, 2) * 60;
-			p += 2;
-		}
-
-		/* Use TZ offset */
-		if (neg)
-			*offset = 0 - off;
-		else
-			*offset = off;
-	}
-
-	/* Make sure everything got parsed */
-	if (p != e)
-		return 0;
-
-	return 1;
-}
-
-static time_t
-when_and_offset_to_time_t (struct tm *when,
-                           int tz_offset)
-{
-	time_t timet;
-
-	/* A 32-bit time, cannot represent this time */
-	if (sizeof (time_t) <= 4 && when->tm_year >= 138) {
-		return -1;
-
-	/* Convert to seconds since epoch */
-	} else {
-		timet = timegm (when);
-		return_val_if_fail (timet >= 0, -1);
-		timet += tz_offset;
-	}
-
-	if (!gmtime_r (&timet, when))
-		return_val_if_reached (-1);
-
-	return timet;
-}
-
-time_t
-p11_asn1_parse_utc (const char *time_str,
-                    struct tm *when)
-{
-	struct tm dummy;
-	int tz_offset;
-	int ret;
-
-	if (!when)
-		when = &dummy;
-
-	ret = parse_utc_time (time_str, strlen (time_str),
-	                      when, &tz_offset);
-	if (!ret)
-		return -1;
-
-	return when_and_offset_to_time_t (when, tz_offset);
-}
-
-time_t
-p11_asn1_parse_general (const char *time_str,
-                        struct tm *when)
-{
-	struct tm dummy;
-	int tz_offset;
-	int ret;
-
-	if (!when)
-		when = &dummy;
-
-	ret = parse_general_time (time_str, strlen (time_str),
-	                          when, &tz_offset);
-	if (!ret)
-		return -1;
-
-	return when_and_offset_to_time_t (when, tz_offset);
-}
-
 ssize_t
 p11_asn1_tlv_length (const unsigned char *data,
                      size_t length)
diff --git a/common/asn1.h b/common/asn1.h
index c79e8f6..1bd7dd1 100644
--- a/common/asn1.h
+++ b/common/asn1.h
@@ -55,12 +55,6 @@ node_asn *       p11_asn1_create                    (p11_dict *asn1_defs,
 unsigned char *  p11_asn1_encode                    (node_asn *asn,
                                                      size_t *der_len);
 
-time_t           p11_asn1_parse_utc                 (const char *time_str,
-                                                     struct tm *when);
-
-time_t           p11_asn1_parse_general             (const char *time_str,
-                                                     struct tm *when);
-
 ssize_t          p11_asn1_tlv_length                (const unsigned char *data,
                                                      size_t length);
 
diff --git a/trust/builder.c b/trust/builder.c
index e41d73f..15999bb 100644
--- a/trust/builder.c
+++ b/trust/builder.c
@@ -227,16 +227,72 @@ calc_check_value (const unsigned char *data,
 	memcpy (check_value, checksum, 3);
 }
 
+static int
+atoin (const char *p,
+       int digits)
+{
+	int ret = 0, base = 1;
+	while(--digits >= 0) {
+		if (p[digits] < '0' || p[digits] > '9')
+			return -1;
+		ret += (p[digits] - '0') * base;
+		base *= 10;
+	}
+	return ret;
+}
+
+static int
+century_for_two_digit_year (int year)
+{
+	time_t now;
+	struct tm tm;
+	int century, current;
+
+	return_val_if_fail (year >= 0 && year <= 99, -1);
+
+	/* Get the current year */
+	now = time (NULL);
+	return_val_if_fail (now >= 0, -1);
+	if (!gmtime_r (&now, &tm))
+		return_val_if_reached (-1);
+
+	current = (tm.tm_year % 100);
+	century = (tm.tm_year + 1900) - current;
+
+	/*
+	 * Check if it's within 40 years before the
+	 * current date.
+	 */
+	if (current < 40) {
+		if (year < current)
+			return century;
+		if (year > 100 - (40 - current))
+			return century - 100;
+	} else {
+		if (year < current && year > (current - 40))
+			return century;
+	}
+
+	/*
+	 * If it's after then adjust for overflows to
+	 * the next century.
+	 */
+	if (year < current)
+		return century + 100;
+	else
+		return century;
+}
+
 static bool
 calc_date (node_asn *node,
            const char *field,
            CK_DATE *date)
 {
 	node_asn *choice;
-	struct tm when;
 	char buf[64];
-	time_t timet;
+	int century;
 	char *sub;
+	int year;
 	int len;
 	int ret;
 
@@ -252,39 +308,43 @@ calc_date (node_asn *node,
 
 	sub = strconcat (field, ".", buf, NULL);
 
+	/*
+	 * So here we take a shortcut and just copy the date from the
+	 * certificate into the CK_DATE. This doesn't take into account
+	 * time zones. However the PKCS#11 spec does not say what timezone
+	 * the dates are in. In the PKCS#11 value have a day resolution,
+	 * and time zones aren't that critical.
+	 */
+
 	if (strcmp (buf, "generalTime") == 0) {
 		len = sizeof (buf) - 1;
 		ret = asn1_read_value (node, sub, buf, &len);
 		return_val_if_fail (ret == ASN1_SUCCESS, false);
-		timet = p11_asn1_parse_general (buf, &when);
+		return_val_if_fail (len >= 8, false);
+
+		/* Same as first 8 characters of date */
+		memcpy (date, buf, 8);
 
 	} else if (strcmp (buf, "utcTime") == 0) {
 		len = sizeof (buf) - 1;
 		ret = asn1_read_value (node, sub, buf, &len);
 		return_val_if_fail (ret == ASN1_SUCCESS, false);
-		timet = p11_asn1_parse_utc (buf, &when);
+		return_val_if_fail (len >= 6, false);
+
+		year = atoin (buf, 2);
+		return_val_if_fail (year > 0, false);
+
+		century = century_for_two_digit_year (year);
+		return_val_if_fail (century >= 0, false);
+
+		snprintf ((char *)date->year, 3, "%02d", century);
+		memcpy (((char *)date) + 2, buf, 6);
 
 	} else {
 		return_val_if_reached (false);
 	}
 
 	free (sub);
-
-	if (timet < 0)
-		return false;
-
-	assert (sizeof (date->year) == 4);
-	snprintf ((char *)buf, 5, "%04d", 1900 + when.tm_year);
-	memcpy (date->year, buf, 4);
-
-	assert (sizeof (date->month) == 2);
-	snprintf ((char *)buf, 3, "%02d", when.tm_mon + 1);
-	memcpy (date->month, buf, 2);
-
-	assert (sizeof (date->day) == 2);
-	snprintf ((char *)buf, 3, "%02d", when.tm_mday);
-	memcpy (date->day, buf, 2);
-
 	return true;
 }
 
diff --git a/trust/tests/test-builder.c b/trust/tests/test-builder.c
index 7cab1f6..a875b96 100644
--- a/trust/tests/test-builder.c
+++ b/trust/tests/test-builder.c
@@ -552,7 +552,7 @@ test_build_distant_end_date (CuTest *cu)
 	};
 
 	CK_ATTRIBUTE expected[] = {
-		{ CKA_END_DATE, },
+		{ CKA_END_DATE, "20671229", 8 },
 		{ CKA_START_DATE, "20130327", 8 },
 		{ CKA_INVALID },
 	};
@@ -562,18 +562,6 @@ test_build_distant_end_date (CuTest *cu)
 
 	setup (cu);
 
-	/*
-	 * On a 32-bit system, the end date will be too big to compute with
-	 * libc. So it'll be empty, since this is an optional field.
-	 */
-	if (sizeof (time_t) <= 4) {
-		expected[0].pValue = "";
-		expected[0].ulValueLen = 0;
-	} else {
-		expected[0].pValue = "20671229";
-		expected[0].ulValueLen = 8;
-	}
-
 	attrs = NULL;
 	rv = p11_builder_build (test.builder, test.index, &attrs, p11_attrs_dup (input));
 	CuAssertIntEquals (cu, CKR_OK, rv);
-- 
2.40.0