Commit 61c876198d for openssl.org

commit 61c876198d6f48f146098c5b50ce110ff2b10e7e
Author: Bob Beck <beck@openssl.org>
Date:   Thu Nov 20 17:06:11 2025 -0700

    Remove the ASN1_STRING_FLAG_X509_TIME flag

    It's only use was to do some somewhat confused cruftery
    inside of ossl_asn1_time_to_tm as a special case to
    implement ASN1_TIME_set_string_X509.

    As it turns out, you don't need the cruftery of a special
    case inside of ossl_asn1_time_to_tm to implement this
    function, so the flag is completely unnecessary.

    This removes flag, and simplifies this to work without it.

    It removes the cruft only from ossl_asn1_time_to_tm,
    minimally. This function really needs some cleanup and
    makes my eyes bleed but I am resisting the temptation
    to do that with this PR and making this a the minimal
    change needed for review. I will clean up that function
    in a follow on pr.

    As tests on the behaviour of ASN1_TIME_set_string_X509
    were added with it, Beyonce dances happily for me and I
    only need to pass the existing tests, not write as bunch
    of new ones.. .

    Reviewed-by: Neil Horman <nhorman@openssl.org>
    Reviewed-by: Nikola Pajkovsky <nikolap@openssl.org>
    (Merged from https://github.com/openssl/openssl/pull/29187)

diff --git a/CHANGES.md b/CHANGES.md
index 23046079ec..afd7f71010 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -76,6 +76,10 @@ OpenSSL 4.0

    *Bob Beck*

+* The ASN1_STRING_FLAG_X509_TIME define has been removed.
+
+   *Bob Beck*
+
  * various function parameters have been constified,
    in particular for X509-related functions.

diff --git a/crypto/asn1/a_time.c b/crypto/asn1/a_time.c
index 51536eb630..26e9b15899 100644
--- a/crypto/asn1/a_time.c
+++ b/crypto/asn1/a_time.c
@@ -79,33 +79,20 @@ int ossl_asn1_time_to_tm(struct tm *tm, const ASN1_TIME *d)
     static const int max[9] = { 99, 99, 12, 31, 23, 59, 59, 12, 59 };
     static const int mdays[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
     char *a;
-    int n, i, i2, l, o, min_l, strict = 0, end = 6, btz = 5, md;
+    int n, i, i2, l, o, min_l, end = 6, btz = 5, md;
     struct tm tmp;
 #if defined(CHARSET_EBCDIC)
     const char upper_z = 0x5A, num_zero = 0x30, period = 0x2E, minus = 0x2D, plus = 0x2B;
 #else
     const char upper_z = 'Z', num_zero = '0', period = '.', minus = '-', plus = '+';
 #endif
-    /*
-     * ASN1_STRING_FLAG_X509_TIME is used to enforce RFC 5280
-     * time string format, in which:
-     *
-     * 1. "seconds" is a 'MUST'
-     * 2. "Zulu" timezone is a 'MUST'
-     * 3. "+|-" is not allowed to indicate a timezone
-     */
+
     if (d->type == V_ASN1_UTCTIME) {
         min_l = 13;
-        if (d->flags & ASN1_STRING_FLAG_X509_TIME) {
-            strict = 1;
-        }
     } else if (d->type == V_ASN1_GENERALIZEDTIME) {
         end = 7;
         btz = 6;
         min_l = 15;
-        if (d->flags & ASN1_STRING_FLAG_X509_TIME) {
-            strict = 1;
-        }
     } else {
         return 0;
     }
@@ -124,7 +111,7 @@ int ossl_asn1_time_to_tm(struct tm *tm, const ASN1_TIME *d)
     if (l < min_l)
         goto err;
     for (i = 0; i < end; i++) {
-        if (!strict && (i == btz) && ((a[o] == upper_z) || (a[o] == plus) || (a[o] == minus))) {
+        if ((i == btz) && ((a[o] == upper_z) || (a[o] == plus) || (a[o] == minus))) {
             i++;
             break;
         }
@@ -190,9 +177,6 @@ int ossl_asn1_time_to_tm(struct tm *tm, const ASN1_TIME *d)
      * digits.
      */
     if (d->type == V_ASN1_GENERALIZEDTIME && a[o] == period) {
-        if (strict)
-            /* RFC 5280 forbids fractional seconds */
-            goto err;
         if (++o == l)
             goto err;
         i = o;
@@ -213,7 +197,7 @@ int ossl_asn1_time_to_tm(struct tm *tm, const ASN1_TIME *d)
      */
     if (a[o] == upper_z) {
         o++;
-    } else if (!strict && ((a[o] == plus) || (a[o] == minus))) {
+    } else if (((a[o] == plus) || (a[o] == minus))) {
         int offsign = a[o] == minus ? 1 : -1;
         int offset = 0;

@@ -249,7 +233,7 @@ int ossl_asn1_time_to_tm(struct tm *tm, const ASN1_TIME *d)
         if (offset && !OPENSSL_gmtime_adj(&tmp, 0, offset * offsign))
             goto err;
     } else {
-        /* not Z, or not +/- in non-strict mode */
+        /* not Z, or not +/- */
         goto err;
     }
     if (o == l) {
@@ -385,62 +369,53 @@ int ASN1_TIME_set_string_X509(ASN1_TIME *s, const char *str)
 {
     ASN1_TIME t;
     struct tm tm;
-    int rv = 0;
     size_t len;

-    if ((len = strlen(str)) >= INT_MAX)
-        goto out;
-    t.length = (int)len;
-    t.data = (unsigned char *)str;
-    t.flags = ASN1_STRING_FLAG_X509_TIME;
-
-    t.type = V_ASN1_UTCTIME;
-
-    if (!ASN1_TIME_check(&t)) {
+    /* RFC 5280 4.1.2.5: Valid RFC5280 times must be either length 13 or 15. */
+    len = strlen(str);
+    switch (len) {
+    case 13:
+        t.type = V_ASN1_UTCTIME;
+        break;
+    case 15:
         t.type = V_ASN1_GENERALIZEDTIME;
-        if (!ASN1_TIME_check(&t))
-            goto out;
+        break;
+    default:
+        return 0;
     }

+    /* RFC 5280 4.1.2.5 Valid RFC5280 times must end in 'Z'. */
+    if (str[len - 1] != 0x5A)
+        return 0;
+
+    t.length = (int)len;
+    t.data = (unsigned char *)str;
+
     /*
-     * Per RFC 5280 (section 4.1.2.5.), the valid input time
-     * strings should be encoded with the following rules:
-     *
-     * 1. UTC: YYMMDDHHMMSSZ, if YY < 50 (20YY) --> UTC: YYMMDDHHMMSSZ
-     * 2. UTC: YYMMDDHHMMSSZ, if YY >= 50 (19YY) --> UTC: YYMMDDHHMMSSZ
-     * 3. G'd: YYYYMMDDHHMMSSZ, if YYYY >= 2050 --> G'd: YYYYMMDDHHMMSSZ
-     * 4. G'd: YYYYMMDDHHMMSSZ, if YYYY < 2050 --> UTC: YYMMDDHHMMSSZ
-     *
-     * Only strings of the 4th rule should be reformatted, but since a
-     * UTC can only present [1950, 2050), so if the given time string
-     * is less than 1950 (e.g. 19230419000000Z), we do nothing...
+     * RFC 5280 Section 4.1.2.5 The following function is permissive
+     * and allows time zone offsets and time zones not Z, etc. As we
+     * have already failed and excluded anything not the correct length
+     * for the type, and anything not ending in a 'Z', Our time may
+     * not be any of these other cases, and still parse as a time.
      */
+    if (!ossl_asn1_time_to_tm(&tm, &t))
+        return 0;

-    if (s != NULL && t.type == V_ASN1_GENERALIZEDTIME) {
-        if (!ossl_asn1_time_to_tm(&tm, &t))
-            goto out;
-        if (is_utc(tm.tm_year)) {
-            t.length -= 2;
-            /*
-             * it's OK to let original t.data go since that's assigned
-             * to a piece of memory allocated outside of this function.
-             * new t.data would be freed after ASN1_STRING_copy is done.
-             */
-            t.data = OPENSSL_zalloc(t.length + 1);
-            if (t.data == NULL)
-                goto out;
-            memcpy(t.data, str + 2, t.length);
-            t.type = V_ASN1_UTCTIME;
-        }
-    }
+    if (s != NULL) {
+        /*
+         * Unlike every other type of nonconforming to RFC5280 time
+         * string, which causes this function to fail, a 15 character
+         * input string that ends up being in the UTC time range is not
+         * rejected. Instead, two digits of the year is removed from
+         * start of the input string so the result is a UTC time.
+         */
+        if (is_utc(tm.tm_year) && len == 15)
+            return ASN1_TIME_set_string(s, str + 2);

-    if (s == NULL || ASN1_STRING_copy((ASN1_STRING *)s, (ASN1_STRING *)&t))
-        rv = 1;
+        return ASN1_TIME_set_string(s, str);
+    }

-    if (t.data != (unsigned char *)str)
-        OPENSSL_free(t.data);
-out:
-    return rv;
+    return 1;
 }

 int ASN1_TIME_to_tm(const ASN1_TIME *s, struct tm *tm)
diff --git a/include/openssl/asn1.h.in b/include/openssl/asn1.h.in
index 1b7dd66d68..08d1c27817 100644
--- a/include/openssl/asn1.h.in
+++ b/include/openssl/asn1.h.in
@@ -160,8 +160,6 @@ extern "C" {
 #define ASN1_STRING_FLAG_MSTRING 0x040
 /* String is embedded and only content should be freed */
 #define ASN1_STRING_FLAG_EMBED 0x080
-/* String should be parsed in RFC 5280's time format */
-#define ASN1_STRING_FLAG_X509_TIME 0x100
 /* This is the base type that holds just about everything :-) */
 struct asn1_string_st {
     int length;