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)
This commit is contained in:
Bob Beck
2025-11-20 17:06:11 -07:00
committed by Neil Horman
parent 441a2c105c
commit 61c876198d
3 changed files with 46 additions and 69 deletions

View File

@@ -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;
/* 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;
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;
t.flags = ASN1_STRING_FLAG_X509_TIME;
t.type = V_ASN1_UTCTIME;
if (!ASN1_TIME_check(&t)) {
t.type = V_ASN1_GENERALIZEDTIME;
if (!ASN1_TIME_check(&t))
goto out;
}
/*
* 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);
return ASN1_TIME_set_string(s, str);
}
if (s == NULL || ASN1_STRING_copy((ASN1_STRING *)s, (ASN1_STRING *)&t))
rv = 1;
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)