Reimplement ASN1_TIME_print with the new parser.

No sense in keeping two around. This does cause the functions to reject
some previously accepted invalid inputs. These were intentionally
accepted by
https://boringssl-review.googlesource.com/c/boringssl/+/13082 for
an old version of M2Crypto, but I belive we no longer need to be
compatible with that.

Update-Note: ASN1_TIME_print, ASN1_UTCTIME_print, and
ASN1_GENERALIZEDTIME_print will no longer accept various invalid inputs.

Change-Id: I4606d0b39585a19eb4b984ac809706e497a3f799
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/53090
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Bob Beck <bbe@google.com>
diff --git a/crypto/asn1/a_strex.c b/crypto/asn1/a_strex.c
index 52d979e..e6aacf0 100644
--- a/crypto/asn1/a_strex.c
+++ b/crypto/asn1/a_strex.c
@@ -60,8 +60,10 @@
 #include <ctype.h>
 #include <inttypes.h>
 #include <string.h>
+#include <time.h>
 
 #include <openssl/bio.h>
+#include <openssl/bytestring.h>
 #include <openssl/mem.h>
 
 #include "internal.h"
@@ -464,7 +466,7 @@
   if (tm->type == V_ASN1_GENERALIZEDTIME) {
     return ASN1_GENERALIZEDTIME_print(bp, tm);
   }
-  BIO_write(bp, "Bad time value", 14);
+  BIO_puts(bp, "Bad time value");
   return 0;
 }
 
@@ -472,136 +474,29 @@
                                     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
 
 int ASN1_GENERALIZEDTIME_print(BIO *bp, const ASN1_GENERALIZEDTIME *tm) {
-  char *v;
-  int gmt = 0;
-  int i;
-  int y = 0, M = 0, d = 0, h = 0, m = 0, s = 0;
-  char *f = NULL;
-  int f_len = 0;
-
-  i = tm->length;
-  v = (char *)tm->data;
-
-  if (i < 12) {
-    goto err;
-  }
-  if (v[i - 1] == 'Z') {
-    gmt = 1;
-  }
-  for (i = 0; i < 12; i++) {
-    if ((v[i] > '9') || (v[i] < '0')) {
-      goto err;
-    }
-  }
-  y = (v[0] - '0') * 1000 + (v[1] - '0') * 100 + (v[2] - '0') * 10 +
-      (v[3] - '0');
-  M = (v[4] - '0') * 10 + (v[5] - '0');
-  if ((M > 12) || (M < 1)) {
-    goto err;
-  }
-  d = (v[6] - '0') * 10 + (v[7] - '0');
-  h = (v[8] - '0') * 10 + (v[9] - '0');
-  m = (v[10] - '0') * 10 + (v[11] - '0');
-  if (tm->length >= 14 && (v[12] >= '0') && (v[12] <= '9') && (v[13] >= '0') &&
-      (v[13] <= '9')) {
-    s = (v[12] - '0') * 10 + (v[13] - '0');
-    // Check for fractions of seconds.
-    if (tm->length >= 15 && v[14] == '.') {
-      int l = tm->length;
-      f = &v[14];  // The decimal point.
-      f_len = 1;
-      while (14 + f_len < l && f[f_len] >= '0' && f[f_len] <= '9') {
-        ++f_len;
-      }
-    }
-  }
-
-  if (BIO_printf(bp, "%s %2d %02d:%02d:%02d%.*s %d%s", mon[M - 1], d, h, m, s,
-                 f_len, f, y, (gmt) ? " GMT" : "") <= 0) {
-    return 0;
-  } else {
-    return 1;
-  }
-err:
-  BIO_write(bp, "Bad time value", 14);
-  return 0;
-}
-
-// consume_two_digits is a helper function for ASN1_UTCTIME_print. If |*v|,
-// assumed to be |*len| bytes long, has two leading digits, updates |*out| with
-// their value, updates |v| and |len|, and returns one. Otherwise, returns
-// zero.
-static int consume_two_digits(int *out, const char **v, int *len) {
-  if (*len < 2 || !isdigit((unsigned char)((*v)[0])) ||
-      !isdigit((unsigned char)((*v)[1]))) {
-    return 0;
-  }
-  *out = ((*v)[0] - '0') * 10 + ((*v)[1] - '0');
-  *len -= 2;
-  *v += 2;
-  return 1;
-}
-
-// consume_zulu_timezone is a helper function for ASN1_UTCTIME_print. If |*v|,
-// assumed to be |*len| bytes long, starts with "Z" then it updates |*v| and
-// |*len| and returns one. Otherwise returns zero.
-static int consume_zulu_timezone(const char **v, int *len) {
-  if (*len == 0 || (*v)[0] != 'Z') {
+  CBS cbs;
+  CBS_init(&cbs, tm->data, tm->length);
+  struct tm utc;
+  if (!CBS_parse_generalized_time(&cbs, &utc, /*allow_timezone_offset=*/0)) {
+    BIO_puts(bp, "Bad time value");
     return 0;
   }
 
-  *len -= 1;
-  *v += 1;
-  return 1;
+  return BIO_printf(bp, "%s %2d %02d:%02d:%02d %d GMT", mon[utc.tm_mon],
+                    utc.tm_mday, utc.tm_hour, utc.tm_min, utc.tm_sec,
+                    utc.tm_year + 1900) > 0;
 }
 
 int ASN1_UTCTIME_print(BIO *bp, const ASN1_UTCTIME *tm) {
-  const char *v = (const char *)tm->data;
-  int len = tm->length;
-  int Y = 0, M = 0, D = 0, h = 0, m = 0, s = 0;
-
-  // YYMMDDhhmm are required to be present.
-  if (!consume_two_digits(&Y, &v, &len) || !consume_two_digits(&M, &v, &len) ||
-      !consume_two_digits(&D, &v, &len) || !consume_two_digits(&h, &v, &len) ||
-      !consume_two_digits(&m, &v, &len)) {
-    goto err;
-  }
-  // https://tools.ietf.org/html/rfc5280, section 4.1.2.5.1, requires seconds
-  // to be present, but historically this code has forgiven its absence.
-  consume_two_digits(&s, &v, &len);
-
-  // https://tools.ietf.org/html/rfc5280, section 4.1.2.5.1, specifies this
-  // interpretation of the year.
-  if (Y < 50) {
-    Y += 2000;
-  } else {
-    Y += 1900;
-  }
-  if (M > 12 || M == 0) {
-    goto err;
-  }
-  if (D > 31 || D == 0) {
-    goto err;
-  }
-  if (h > 23 || m > 59 || s > 60) {
-    goto err;
+  CBS cbs;
+  CBS_init(&cbs, tm->data, tm->length);
+  struct tm utc;
+  if (!CBS_parse_utc_time(&cbs, &utc, /*allow_timezone_offset=*/0)) {
+    BIO_puts(bp, "Bad time value");
+    return 0;
   }
 
-  // https://tools.ietf.org/html/rfc5280, section 4.1.2.5.1, requires the "Z"
-  // to be present, but historically this code has forgiven its absence.
-  const int is_gmt = consume_zulu_timezone(&v, &len);
-
-  // https://tools.ietf.org/html/rfc5280, section 4.1.2.5.1, does not permit
-  // the specification of timezones using the +hhmm / -hhmm syntax, which is
-  // the only other thing that might legitimately be found at the end.
-  if (len) {
-    goto err;
-  }
-
-  return BIO_printf(bp, "%s %2d %02d:%02d:%02d %d%s", mon[M - 1], D, h, m, s, Y,
-                    is_gmt ? " GMT" : "") > 0;
-
-err:
-  BIO_write(bp, "Bad time value", 14);
-  return 0;
+  return BIO_printf(bp, "%s %2d %02d:%02d:%02d %d GMT", mon[utc.tm_mon],
+                    utc.tm_mday, utc.tm_hour, utc.tm_min, utc.tm_sec,
+                    utc.tm_year + 1900) > 0;
 }
diff --git a/crypto/asn1/asn1_test.cc b/crypto/asn1/asn1_test.cc
index c4f47bb..442c1ea 100644
--- a/crypto/asn1/asn1_test.cc
+++ b/crypto/asn1/asn1_test.cc
@@ -23,6 +23,7 @@
 
 #include <openssl/asn1.h>
 #include <openssl/asn1t.h>
+#include <openssl/bio.h>
 #include <openssl/bytestring.h>
 #include <openssl/err.h>
 #include <openssl/mem.h>
@@ -926,28 +927,46 @@
   return day == 0 && sec ==0;
 }
 
+static std::string PrintStringToBIO(const ASN1_STRING *str,
+                                    int (*print_func)(BIO *,
+                                                      const ASN1_STRING *)) {
+  const uint8_t *data;
+  size_t len;
+  bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_mem()));
+  if (!bio ||  //
+      !print_func(bio.get(), str) ||
+      !BIO_mem_contents(bio.get(), &data, &len)) {
+    ADD_FAILURE() << "Could not print to BIO";
+    return "";
+  }
+  return std::string(data, data + len);
+}
+
 TEST(ASN1Test, SetTime) {
   static const struct {
     time_t time;
     const char *generalized;
     const char *utc;
+    const char *printed;
   } kTests[] = {
-    {-631152001, "19491231235959Z", nullptr},
-    {-631152000, "19500101000000Z", "500101000000Z"},
-    {0, "19700101000000Z", "700101000000Z"},
-    {981173106, "20010203040506Z", "010203040506Z"},
-    {951804000, "20000229060000Z", "000229060000Z"},
+    {-631152001, "19491231235959Z", nullptr, "Dec 31 23:59:59 1949 GMT"},
+    {-631152000, "19500101000000Z", "500101000000Z",
+     "Jan  1 00:00:00 1950 GMT"},
+    {0, "19700101000000Z", "700101000000Z", "Jan  1 00:00:00 1970 GMT"},
+    {981173106, "20010203040506Z", "010203040506Z", "Feb  3 04:05:06 2001 GMT"},
+    {951804000, "20000229060000Z", "000229060000Z", "Feb 29 06:00:00 2000 GMT"},
 #if defined(OPENSSL_64_BIT)
     // TODO(https://crbug.com/boringssl/416): These cases overflow 32-bit
     // |time_t| and do not consistently work on 32-bit platforms. For now,
     // disable the tests on 32-bit. Re-enable them once the bug is fixed.
-    {2524607999, "20491231235959Z", "491231235959Z"},
-    {2524608000, "20500101000000Z", nullptr},
+    {2524607999, "20491231235959Z", "491231235959Z",
+     "Dec 31 23:59:59 2049 GMT"},
+    {2524608000, "20500101000000Z", nullptr, "Jan  1 00:00:00 2050 GMT"},
     // Test boundary conditions.
-    {-62167219200, "00000101000000Z", nullptr},
-    {-62167219201, nullptr, nullptr},
-    {253402300799, "99991231235959Z", nullptr},
-    {253402300800, nullptr, nullptr},
+    {-62167219200, "00000101000000Z", nullptr, "Jan  1 00:00:00 0 GMT"},
+    {-62167219201, nullptr, nullptr, nullptr},
+    {253402300799, "99991231235959Z", nullptr, "Dec 31 23:59:59 9999 GMT"},
+    {253402300800, nullptr, nullptr, nullptr},
 #endif
   };
   for (const auto &t : kTests) {
@@ -966,6 +985,8 @@
       EXPECT_EQ(V_ASN1_UTCTIME, ASN1_STRING_type(utc.get()));
       EXPECT_EQ(t.utc, ASN1StringToStdString(utc.get()));
       EXPECT_TRUE(ASN1Time_check_time_t(utc.get(), t.time));
+      EXPECT_EQ(PrintStringToBIO(utc.get(), &ASN1_UTCTIME_print), t.printed);
+      EXPECT_EQ(PrintStringToBIO(utc.get(), &ASN1_TIME_print), t.printed);
     } else {
       EXPECT_FALSE(utc);
     }
@@ -977,6 +998,11 @@
       EXPECT_EQ(V_ASN1_GENERALIZEDTIME, ASN1_STRING_type(generalized.get()));
       EXPECT_EQ(t.generalized, ASN1StringToStdString(generalized.get()));
       EXPECT_TRUE(ASN1Time_check_time_t(generalized.get(), t.time));
+      EXPECT_EQ(
+          PrintStringToBIO(generalized.get(), &ASN1_GENERALIZEDTIME_print),
+          t.printed);
+      EXPECT_EQ(PrintStringToBIO(generalized.get(), &ASN1_TIME_print),
+                t.printed);
     } else {
       EXPECT_FALSE(generalized);
     }
diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc
index ce70ae3..fe4a775 100644
--- a/crypto/x509/x509_test.cc
+++ b/crypto/x509/x509_test.cc
@@ -2129,10 +2129,10 @@
     {"000000000000Z", "Bad time value"},
     {"999999999999Z", "Bad time value"},
 
-    // Missing components. Not legal RFC 5280, but permitted.
-    {"090303125425", "Mar  3 12:54:25 2009"},
-    {"9003031254", "Mar  3 12:54:00 1990"},
-    {"9003031254Z", "Mar  3 12:54:00 1990 GMT"},
+    // Missing components.
+    {"090303125425", "Bad time value"},
+    {"9003031254", "Bad time value"},
+    {"9003031254Z", "Bad time value"},
 
     // GENERALIZEDTIME confused for UTCTIME.
     {"20090303125425Z", "Bad time value"},
diff --git a/include/openssl/bytestring.h b/include/openssl/bytestring.h
index 846ab24..1d47f60 100644
--- a/include/openssl/bytestring.h
+++ b/include/openssl/bytestring.h
@@ -362,16 +362,17 @@
 // corresponding time in UTC. This function does not compute |out_tm->tm_wday|
 // or |out_tm->tm_yday|.
 OPENSSL_EXPORT int CBS_parse_generalized_time(const CBS *cbs, struct tm *out_tm,
-                                              int allow_timezeone_offset);
+                                              int allow_timezone_offset);
 
 // CBS_parse_utc_time returns one if |cbs| is a valid DER-encoded, ASN.1
 // UTCTime body within the limitations imposed by RFC 5280, or zero otherwise.
 // If |allow_timezone_offset| is non-zero, four-digit timezone offsets, which
 // would not be allowed by DER, are permitted. On success, if |out_tm| is
 // non-NULL, |*out_tm| will be zeroed, and then set to the corresponding time
-// in UTC. This function does not compute |out_tm->tm_wday| or |out_tm->tm_yday|.
+// in UTC. This function does not compute |out_tm->tm_wday| or
+// |out_tm->tm_yday|.
 OPENSSL_EXPORT int CBS_parse_utc_time(const CBS *cbs, struct tm *out_tm,
-                                      int allow_timezeone_offset);
+                                      int allow_timezone_offset);
 
 // CRYPTO ByteBuilder.
 //