Add ASN1_TIME_set_string_X509

rust-openssl uses this function when targetting OpenSSL 1.1.x.

Change-Id: Ifeb1b65be9976358f9ee636ed23c1a931e03b275
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/60609
Auto-Submit: David Benjamin <davidben@google.com>
Reviewed-by: Bob Beck <bbe@google.com>
Commit-Queue: Bob Beck <bbe@google.com>
diff --git a/crypto/asn1/a_time.c b/crypto/asn1/a_time.c
index 87bf092..d8ec85f 100644
--- a/crypto/asn1/a_time.c
+++ b/crypto/asn1/a_time.c
@@ -61,6 +61,7 @@
 #include <time.h>
 
 #include <openssl/asn1t.h>
+#include <openssl/bytestring.h>
 #include <openssl/err.h>
 #include <openssl/mem.h>
 
@@ -82,6 +83,10 @@
   return ASN1_TIME_adj(s, time, 0, 0);
 }
 
+static int fits_in_utc_time(const struct tm *tm) {
+  return 50 <= tm->tm_year && tm->tm_year < 150;
+}
+
 ASN1_TIME *ASN1_TIME_adj(ASN1_TIME *s, int64_t posix_time, int offset_day,
                          long offset_sec) {
   struct tm tm;
@@ -95,7 +100,7 @@
       return NULL;
     }
   }
-  if ((tm.tm_year >= 50) && (tm.tm_year < 150)) {
+  if (fits_in_utc_time(&tm)) {
     return ASN1_UTCTIME_adj(s, posix_time, offset_day, offset_sec);
   }
   return ASN1_GENERALIZEDTIME_adj(s, posix_time, offset_day, offset_sec);
@@ -171,6 +176,34 @@
          ASN1_GENERALIZEDTIME_set_string(s, str);
 }
 
+int ASN1_TIME_set_string_X509(ASN1_TIME *s, const char *str) {
+  CBS cbs;
+  CBS_init(&cbs, (const uint8_t*)str, strlen(str));
+  int type;
+  struct tm tm;
+  if (CBS_parse_utc_time(&cbs, /*out_tm=*/NULL,
+                         /*allow_timezone_offset=*/0)) {
+    type = V_ASN1_UTCTIME;
+  } else if (CBS_parse_generalized_time(&cbs, &tm,
+                                        /*allow_timezone_offset=*/0)) {
+    type = V_ASN1_GENERALIZEDTIME;
+    if (fits_in_utc_time(&tm)) {
+      type = V_ASN1_UTCTIME;
+      CBS_skip(&cbs, 2);
+    }
+  } else {
+    return 0;
+  }
+
+  if (s != NULL) {
+    if (!ASN1_STRING_set(s, CBS_data(&cbs), CBS_len(&cbs))) {
+      return 0;
+    }
+    s->type = type;
+  }
+  return 1;
+}
+
 static int asn1_time_to_tm(struct tm *tm, const ASN1_TIME *t,
                            int allow_timezone_offset) {
   if (t == NULL) {
diff --git a/crypto/asn1/asn1_test.cc b/crypto/asn1/asn1_test.cc
index c7e0bf0..94f2272 100644
--- a/crypto/asn1/asn1_test.cc
+++ b/crypto/asn1/asn1_test.cc
@@ -1100,6 +1100,32 @@
   EXPECT_EQ(V_ASN1_GENERALIZEDTIME, ASN1_STRING_type(s.get()));
   EXPECT_EQ("19700101000000Z", ASN1StringToStdString(s.get()));
 
+  // |ASN1_TIME_set_string_X509| behaves similarly except it additionally
+  // converts GeneralizedTime to UTCTime if it fits.
+  ASSERT_TRUE(ASN1_TIME_set_string_X509(s.get(), "700101000000Z"));
+  EXPECT_EQ(V_ASN1_UTCTIME, ASN1_STRING_type(s.get()));
+  EXPECT_EQ("700101000000Z", ASN1StringToStdString(s.get()));
+
+  ASSERT_TRUE(ASN1_TIME_set_string_X509(s.get(), "19700101000000Z"));
+  EXPECT_EQ(V_ASN1_UTCTIME, ASN1_STRING_type(s.get()));
+  EXPECT_EQ("700101000000Z", ASN1StringToStdString(s.get()));
+
+  ASSERT_TRUE(ASN1_TIME_set_string_X509(s.get(), "19500101000000Z"));
+  EXPECT_EQ(V_ASN1_UTCTIME, ASN1_STRING_type(s.get()));
+  EXPECT_EQ("500101000000Z", ASN1StringToStdString(s.get()));
+
+  ASSERT_TRUE(ASN1_TIME_set_string_X509(s.get(), "19491231235959Z"));
+  EXPECT_EQ(V_ASN1_GENERALIZEDTIME, ASN1_STRING_type(s.get()));
+  EXPECT_EQ("19491231235959Z", ASN1StringToStdString(s.get()));
+
+  ASSERT_TRUE(ASN1_TIME_set_string_X509(s.get(), "20491231235959Z"));
+  EXPECT_EQ(V_ASN1_UTCTIME, ASN1_STRING_type(s.get()));
+  EXPECT_EQ("491231235959Z", ASN1StringToStdString(s.get()));
+
+  ASSERT_TRUE(ASN1_TIME_set_string_X509(s.get(), "20500101000000Z"));
+  EXPECT_EQ(V_ASN1_GENERALIZEDTIME, ASN1_STRING_type(s.get()));
+  EXPECT_EQ("20500101000000Z", ASN1StringToStdString(s.get()));
+
   // Invalid inputs are rejected.
   EXPECT_FALSE(ASN1_UTCTIME_set_string(s.get(), "nope"));
   EXPECT_FALSE(ASN1_UTCTIME_set_string(s.get(), "19700101000000Z"));
@@ -1111,17 +1137,26 @@
   // to anything.
   EXPECT_TRUE(ASN1_UTCTIME_set_string(nullptr, "700101000000Z"));
   EXPECT_TRUE(ASN1_TIME_set_string(nullptr, "700101000000Z"));
+  EXPECT_TRUE(ASN1_TIME_set_string_X509(nullptr, "700101000000Z"));
   EXPECT_TRUE(ASN1_GENERALIZEDTIME_set_string(nullptr, "19700101000000Z"));
   EXPECT_TRUE(ASN1_TIME_set_string(nullptr, "19700101000000Z"));
+  EXPECT_TRUE(ASN1_TIME_set_string_X509(nullptr, "19700101000000Z"));
+  // Test an input |ASN1_TIME_set_string_X509| won't convert to UTCTime.
+  EXPECT_TRUE(ASN1_GENERALIZEDTIME_set_string(nullptr, "20500101000000Z"));
+  EXPECT_TRUE(ASN1_TIME_set_string(nullptr, "20500101000000Z"));
+  EXPECT_TRUE(ASN1_TIME_set_string_X509(nullptr, "20500101000000Z"));
   EXPECT_FALSE(ASN1_UTCTIME_set_string(nullptr, "nope"));
   EXPECT_FALSE(ASN1_GENERALIZEDTIME_set_string(nullptr, "nope"));
   EXPECT_FALSE(ASN1_TIME_set_string(nullptr, "nope"));
+  EXPECT_FALSE(ASN1_TIME_set_string_X509(nullptr, "nope"));
 
   // Timezone offsets are not allowed by DER.
   EXPECT_FALSE(ASN1_UTCTIME_set_string(nullptr, "700101000000-0400"));
   EXPECT_FALSE(ASN1_TIME_set_string(nullptr, "700101000000-0400"));
+  EXPECT_FALSE(ASN1_TIME_set_string_X509(nullptr, "700101000000-0400"));
   EXPECT_FALSE(ASN1_GENERALIZEDTIME_set_string(nullptr, "19700101000000-0400"));
   EXPECT_FALSE(ASN1_TIME_set_string(nullptr, "19700101000000-0400"));
+  EXPECT_FALSE(ASN1_TIME_set_string_X509(nullptr, "19700101000000-0400"));
 }
 
 TEST(ASN1Test, AdjTime) {
diff --git a/include/openssl/asn1.h b/include/openssl/asn1.h
index d128c8d..c9f265a 100644
--- a/include/openssl/asn1.h
+++ b/include/openssl/asn1.h
@@ -1355,6 +1355,11 @@
 // GeneralizedTime. If |str| is neither, it returns zero.
 OPENSSL_EXPORT int ASN1_TIME_set_string(ASN1_TIME *s, const char *str);
 
+// ASN1_TIME_set_string_X509 behaves like |ASN1_TIME_set_string| except it
+// additionally converts GeneralizedTime to UTCTime if it is in the range where
+// UTCTime is used. See RFC 5280, section 4.1.2.5.
+OPENSSL_EXPORT int ASN1_TIME_set_string_X509(ASN1_TIME *s, const char *str);
+
 // ASN1_TIME_to_time_t converts |t| to a time_t value in |out|. On
 // success, one is returned. On failure zero is returned. This function
 // will fail if the time can not be represented in a time_t.