提交 04e62715 编写于 作者: R Rich Salz

Introduce ASN1_TIME_set_string_X509 API

Make funcs to deal with non-null-term'd string
in both asn1_generalizedtime_to_tm() and asn1_utctime_to_tm().

Fixes issue #3444.

This one is used to enforce strict format (RFC 5280) check and to
convert GeneralizedTime to UTCTime.

apps/ca has been changed to use the new API.

Test cases and documentation are updated/added
Signed-off-by: NPaul Yang <paulyang.inf@gmail.com>
Reviewed-by: NKurt Roeckx <kurt@openssl.org>
Reviewed-by: NRich Salz <rsalz@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/3566)
上级 7aefa754
......@@ -2662,14 +2662,14 @@ int set_cert_times(X509 *x, const char *startdate, const char *enddate,
if (X509_gmtime_adj(X509_getm_notBefore(x), 0) == NULL)
return 0;
} else {
if (!ASN1_TIME_set_string(X509_getm_notBefore(x), startdate))
if (!ASN1_TIME_set_string_X509(X509_getm_notBefore(x), startdate))
return 0;
}
if (enddate == NULL) {
if (X509_time_adj_ex(X509_getm_notAfter(x), days, 0, NULL)
== NULL)
return 0;
} else if (!ASN1_TIME_set_string(X509_getm_notAfter(x), enddate)) {
} else if (!ASN1_TIME_set_string_X509(X509_getm_notAfter(x), enddate)) {
return 0;
}
return 1;
......
......@@ -805,7 +805,7 @@ end_of_options:
if (startdate == NULL)
ERR_clear_error();
}
if (startdate && !ASN1_TIME_set_string(NULL, startdate)) {
if (startdate && !ASN1_TIME_set_string_X509(NULL, startdate)) {
BIO_printf(bio_err,
"start date is invalid, it should be YYMMDDHHMMSSZ or YYYYMMDDHHMMSSZ\n");
goto end;
......@@ -818,7 +818,7 @@ end_of_options:
if (enddate == NULL)
ERR_clear_error();
}
if (enddate && !ASN1_TIME_set_string(NULL, enddate)) {
if (enddate && !ASN1_TIME_set_string_X509(NULL, enddate)) {
BIO_printf(bio_err,
"end date is invalid, it should be YYMMDDHHMMSSZ or YYYYMMDDHHMMSSZ\n");
goto end;
......
......@@ -22,7 +22,7 @@ int asn1_generalizedtime_to_tm(struct tm *tm, const ASN1_GENERALIZEDTIME *d)
static const int min[9] = { 0, 0, 1, 1, 0, 0, 0, 0, 0 };
static const int max[9] = { 99, 99, 12, 31, 23, 59, 59, 12, 59 };
char *a;
int n, i, l, o;
int n, i, l, o, min_l = 13, strict = 0;
if (d->type != V_ASN1_GENERALIZEDTIME)
return (0);
......@@ -34,10 +34,26 @@ int asn1_generalizedtime_to_tm(struct tm *tm, const ASN1_GENERALIZEDTIME *d)
* as YYYY. This stuff treats everything as a two digit field so make
* first two fields 00 to 99
*/
if (l < 13)
/*
* 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 time zone
* 4. fractional seconds are not allowed in GeneralizedTime
*/
if (d->flags & ASN1_STRING_FLAG_X509_TIME) {
min_l = 15;
strict = 1;
}
if (l < min_l)
goto err;
for (i = 0; i < 7; i++) {
if ((i == 6) && ((a[o] == 'Z') || (a[o] == '+') || (a[o] == '-'))) {
if (!strict && (i == 6) && ((a[o] == 'Z') || (a[o] == '+') || (a[o] == '-'))) {
i++;
if (tm)
tm->tm_sec = 0;
......@@ -46,13 +62,15 @@ int asn1_generalizedtime_to_tm(struct tm *tm, const ASN1_GENERALIZEDTIME *d)
if ((a[o] < '0') || (a[o] > '9'))
goto err;
n = a[o] - '0';
if (++o > l)
/* incomplete 2-digital number */
if (++o == l)
goto err;
if ((a[o] < '0') || (a[o] > '9'))
goto err;
n = (n * 10) + a[o] - '0';
if (++o > l)
/* no more bytes to read, but we haven't seen time-zone yet */
if (++o == l)
goto err;
if ((n < min[i]) || (n > max[i]))
......@@ -88,22 +106,39 @@ int asn1_generalizedtime_to_tm(struct tm *tm, const ASN1_GENERALIZEDTIME *d)
* digits.
*/
if (a[o] == '.') {
if (++o > l)
if (strict)
/* RFC 5280 forbids fractional seconds */
goto err;
if (++o == l)
goto err;
i = o;
while ((a[o] >= '0') && (a[o] <= '9') && (o <= l))
while ((o < l) && (a[o] >= '0') && (a[o] <= '9'))
o++;
/* Must have at least one digit after decimal point */
if (i == o)
goto err;
/* no more bytes to read, but we haven't seen time-zone yet */
if (o == l)
goto err;
}
if (a[o] == 'Z')
/*
* 'o' will never point to '\0' at this point, the only chance
* 'o' can point th '\0' is either the subsequent if or the first
* else if is true.
*/
if (a[o] == 'Z') {
o++;
else if ((a[o] == '+') || (a[o] == '-')) {
} else if (!strict && ((a[o] == '+') || (a[o] == '-'))) {
int offsign = a[o] == '-' ? 1 : -1, offset = 0;
o++;
if (o + 4 > l)
/*
* if not equal, no need to do subsequent checks
* since the following for-loop will add 'o' by 4
* and the final return statement will check if 'l'
* and 'o' are equal.
*/
if (o + 4 != l)
goto err;
for (i = 7; i < 9; i++) {
if ((a[o] < '0') || (a[o] > '9'))
......@@ -146,6 +181,8 @@ int ASN1_GENERALIZEDTIME_set_string(ASN1_GENERALIZEDTIME *s, const char *str)
t.type = V_ASN1_GENERALIZEDTIME;
t.length = strlen(str);
t.data = (unsigned char *)str;
t.flags = 0;
if (ASN1_GENERALIZEDTIME_check(&t)) {
if (s != NULL) {
if (!ASN1_STRING_set((ASN1_STRING *)s, str, t.length))
......
......@@ -107,7 +107,6 @@ ASN1_GENERALIZEDTIME *ASN1_TIME_to_generalizedtime(const ASN1_TIME *t,
return NULL;
}
int ASN1_TIME_set_string(ASN1_TIME *s, const char *str)
{
ASN1_TIME t;
......@@ -130,6 +129,65 @@ int ASN1_TIME_set_string(ASN1_TIME *s, const char *str)
return 1;
}
int ASN1_TIME_set_string_X509(ASN1_TIME *s, const char *str)
{
ASN1_TIME t;
struct tm tm;
int rv = 0;
t.length = strlen(str);
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...
*/
if (s != NULL && t.type == V_ASN1_GENERALIZEDTIME) {
if (!asn1_generalizedtime_to_tm(&tm, &t))
goto out;
if (tm.tm_year >= 50 && tm.tm_year < 150) {
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 || ASN1_STRING_copy((ASN1_STRING *)s, (ASN1_STRING *)&t))
rv = 1;
if (t.data != (unsigned char *)str)
OPENSSL_free(t.data);
out:
return rv;
}
int ASN1_TIME_to_tm(const ASN1_TIME *s, struct tm *tm)
{
if (s == NULL) {
......
......@@ -18,7 +18,7 @@ int asn1_utctime_to_tm(struct tm *tm, const ASN1_UTCTIME *d)
static const int min[8] = { 0, 1, 1, 0, 0, 0, 0, 0 };
static const int max[8] = { 99, 12, 31, 23, 59, 59, 12, 59 };
char *a;
int n, i, l, o;
int n, i, l, o, min_l = 11, strict = 0;
if (d->type != V_ASN1_UTCTIME)
return (0);
......@@ -26,10 +26,24 @@ int asn1_utctime_to_tm(struct tm *tm, const ASN1_UTCTIME *d)
a = (char *)d->data;
o = 0;
if (l < 11)
/*
* 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 time zone
*/
if (d->flags & ASN1_STRING_FLAG_X509_TIME) {
min_l = 13;
strict = 1;
}
if (l < min_l)
goto err;
for (i = 0; i < 6; i++) {
if ((i == 5) && ((a[o] == 'Z') || (a[o] == '+') || (a[o] == '-'))) {
if (!strict && (i == 5) && ((a[o] == 'Z') || (a[o] == '+') || (a[o] == '-'))) {
i++;
if (tm)
tm->tm_sec = 0;
......@@ -38,13 +52,15 @@ int asn1_utctime_to_tm(struct tm *tm, const ASN1_UTCTIME *d)
if ((a[o] < '0') || (a[o] > '9'))
goto err;
n = a[o] - '0';
if (++o > l)
/* incomplete 2-digital number */
if (++o == l)
goto err;
if ((a[o] < '0') || (a[o] > '9'))
goto err;
n = (n * 10) + a[o] - '0';
if (++o > l)
/* no more bytes to read, but we haven't seen time-zone yet */
if (++o == l)
goto err;
if ((n < min[i]) || (n > max[i]))
......@@ -72,12 +88,18 @@ int asn1_utctime_to_tm(struct tm *tm, const ASN1_UTCTIME *d)
}
}
}
if (a[o] == 'Z')
/*
* 'o' will never point to '\0' at this point, the only chance
* 'o' can point th '\0' is either the subsequent if or the first
* else if is true.
*/
if (a[o] == 'Z') {
o++;
else if ((a[o] == '+') || (a[o] == '-')) {
} else if (!strict && ((a[o] == '+') || (a[o] == '-'))) {
int offsign = a[o] == '-' ? 1 : -1, offset = 0;
o++;
if (o + 4 > l)
if (o + 4 != l)
goto err;
for (i = 6; i < 8; i++) {
if ((a[o] < '0') || (a[o] > '9'))
......@@ -99,6 +121,9 @@ int asn1_utctime_to_tm(struct tm *tm, const ASN1_UTCTIME *d)
}
if (offset && !OPENSSL_gmtime_adj(tm, 0, offset * offsign))
return 0;
} else {
/* not Z, or not +/- in non-strict mode */
return 0;
}
return o == l;
err:
......@@ -117,6 +142,8 @@ int ASN1_UTCTIME_set_string(ASN1_UTCTIME *s, const char *str)
t.type = V_ASN1_UTCTIME;
t.length = strlen(str);
t.data = (unsigned char *)str;
t.flags = 0;
if (ASN1_UTCTIME_check(&t)) {
if (s != NULL) {
if (!ASN1_STRING_set((ASN1_STRING *)s, str, t.length))
......
......@@ -2,7 +2,8 @@
=head1 NAME
ASN1_TIME_set, ASN1_TIME_adj, ASN1_TIME_check, ASN1_TIME_set_string,
ASN1_TIME_set, ASN1_TIME_adj, ASN1_TIME_check,
ASN1_TIME_set_string, ASN1_TIME_set_string_X509,
ASN1_TIME_print, ASN1_TIME_to_tm, ASN1_TIME_diff - ASN.1 Time functions
=head1 SYNOPSIS
......@@ -11,6 +12,7 @@ ASN1_TIME_print, ASN1_TIME_to_tm, ASN1_TIME_diff - ASN.1 Time functions
ASN1_TIME *ASN1_TIME_adj(ASN1_TIME *s, time_t t,
int offset_day, long offset_sec);
int ASN1_TIME_set_string(ASN1_TIME *s, const char *str);
int ASN1_TIME_set_string_X509(ASN1_TIME *s, const char *str);
int ASN1_TIME_check(const ASN1_TIME *t);
int ASN1_TIME_print(BIO *b, const ASN1_TIME *s);
int ASN1_TIME_to_tm(const ASN1_TIME *s, struct tm *tm);
......@@ -33,7 +35,15 @@ and returned.
ASN1_TIME_set_string() sets ASN1_TIME structure B<s> to the time
represented by string B<str> which must be in appropriate ASN.1 time
format (for example YYMMDDHHMMSSZ or YYYYMMDDHHMMSSZ).
format (for example YYMMDDHHMMSSZ or YYYYMMDDHHMMSSZ). If B<s> is NULL
this function performs a format check on B<str> only.
ASN1_TIME_set_string_X509() sets ASN1_TIME structure B<s> to the time
represented by string B<str> which must be in appropriate time format
that RFC 5280 requires, which means it only allows YYMMDDHHMMSSZ and
YYYYMMDDHHMMSSZ (leap second is rejected), all other ASN.1 time format
are not allowed. If B<s> is NULL this function performs a format check
on B<str> only.
ASN1_TIME_check() checks the syntax of ASN1_TIME structure B<s>.
......@@ -122,8 +132,8 @@ Determine if one time is later or sooner than the current time:
ASN1_TIME_set() and ASN1_TIME_adj() return a pointer to an ASN1_TIME structure
or NULL if an error occurred.
ASN1_TIME_set_string() returns 1 if the time value is successfully set and
0 otherwise.
ASN1_TIME_set_string() and ASN1_TIME_set_string_X509() return 1 if the time
value is successfully set and 0 otherwise.
ASN1_TIME_check() returns 1 if the structure is syntactically correct and 0
otherwise.
......@@ -140,6 +150,7 @@ pass ASN1_TIME structure has invalid syntax for example.
=head1 HISTORY
The ASN1_TIME_to_tm() function was added in OpenSSL 1.1.1.
The ASN1_TIME_set_string_X509() function was added in OpenSSL 1.1.1.
=head1 COPYRIGHT
......
......@@ -141,6 +141,8 @@ DEFINE_STACK_OF(X509_ALGOR)
# 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;
......@@ -628,6 +630,7 @@ int ASN1_TIME_check(const ASN1_TIME *t);
ASN1_GENERALIZEDTIME *ASN1_TIME_to_generalizedtime(const ASN1_TIME *t,
ASN1_GENERALIZEDTIME **out);
int ASN1_TIME_set_string(ASN1_TIME *s, const char *str);
int ASN1_TIME_set_string_X509(ASN1_TIME *s, const char *str);
int ASN1_TIME_to_tm(const ASN1_TIME *s, struct tm *tm);
int i2a_ASN1_INTEGER(BIO *bp, const ASN1_INTEGER *a);
......
......@@ -74,6 +74,7 @@ static int test_offset(int idx)
at.data = (unsigned char*)testdata->data;
at.length = strlen(testdata->data);
at.type = testdata->type;
at.flags = 0;
if (!TEST_true(ASN1_TIME_diff(&day, &sec, &the_asn1_time, &at))) {
TEST_info("ASN1_TIME_diff() failed for %s\n", at.data);
......
......@@ -25,6 +25,99 @@ typedef struct {
int expected;
} TESTDATA;
typedef struct {
const char *data;
/* 0 for check-only mode, 1 for set-string mode */
int set_string;
/* 0 for error, 1 if succeed */
int expected;
/*
* The following 2 fields are ignored if set_string field is set to '0'
* (in check only mode).
*
* But they can still be ignored explicitly in set-string mode by:
* setting -1 to expected_type and setting NULL to expected_string.
*
* It's useful in a case of set-string mode but the expected result
* is a 'parsing error'.
*/
int expected_type;
const char *expected_string;
} TESTDATA_FORMAT;
/*
* Actually, the "loose" mode has been tested in
* those time-compare-cases, so we may not test it again.
*/
static TESTDATA_FORMAT x509_format_tests[] = {
/* GeneralizedTime */
{
/* good format, check only */
"20170217180105Z", 0, 1, -1, NULL,
},
{
/* SS is missing, check only */
"201702171801Z", 0, 0, -1, NULL,
},
{
/* fractional seconds, check only */
"20170217180105.001Z", 0, 0, -1, NULL,
},
{
/* time zone, check only */
"20170217180105+0800", 0, 0, -1, NULL,
},
{
/* SS is missing, set string */
"201702171801Z", 1, 0, -1, NULL,
},
{
/* fractional seconds, set string */
"20170217180105.001Z", 1, 0, -1, NULL,
},
{
/* time zone, set string */
"20170217180105+0800", 1, 0, -1, NULL,
},
{
/* good format, check returned 'turned' string */
"20170217180154Z", 1, 1, V_ASN1_UTCTIME, "170217180154Z",
},
{
/* good format, check returned string */
"20510217180154Z", 1, 1, V_ASN1_GENERALIZEDTIME, "20510217180154Z",
},
{
/* good format but out of UTC range, check returned string */
"19230419180154Z", 1, 1, V_ASN1_GENERALIZEDTIME, "19230419180154Z",
},
/* UTC */
{
/* SS is missing, check only */
"1702171801Z", 0, 0, -1, NULL,
},
{
/* time zone, check only */
"170217180154+0800", 0, 0, -1, NULL,
},
{
/* SS is missing, set string */
"1702171801Z", 1, 0, -1, NULL,
},
{
/* time zone, set string */
"170217180154+0800", 1, 0, -1, NULL,
},
{
/* 2017, good format, check returned string */
"170217180154Z", 1, 1, V_ASN1_UTCTIME, "170217180154Z",
},
{
/* 1998, good format, check returned string */
"981223180154Z", 1, 1, V_ASN1_UTCTIME, "981223180154Z",
},
};
static TESTDATA x509_cmp_tests[] = {
{
"20170217180154Z", V_ASN1_GENERALIZEDTIME,
......@@ -153,6 +246,7 @@ static int test_x509_cmp_time(int idx)
t.type = x509_cmp_tests[idx].type;
t.data = (unsigned char*)(x509_cmp_tests[idx].data);
t.length = strlen(x509_cmp_tests[idx].data);
t.flags = 0;
result = X509_cmp_time(&t, &x509_cmp_tests[idx].cmp_time);
if (!TEST_int_eq(result, x509_cmp_tests[idx].expected)) {
......@@ -187,8 +281,57 @@ static int test_x509_cmp_time_current()
return failed == 0;
}
static int test_x509_time(int idx)
{
ASN1_TIME *t = NULL;
int result, rv = 0;
if (x509_format_tests[idx].set_string) {
/* set-string mode */
t = ASN1_TIME_new();
if (t == NULL) {
TEST_info("test_x509_time(%d) failed: internal error\n", idx);
return 0;
}
}
result = ASN1_TIME_set_string_X509(t, x509_format_tests[idx].data);
/* time string parsing result is always checked against what's expected */
if (!TEST_int_eq(result, x509_format_tests[idx].expected)) {
TEST_info("test_x509_time(%d) failed: expected %d, got %d\n",
idx, x509_format_tests[idx].expected, result);
goto out;
}
/* if t is not NULL but expected_type is ignored(-1), it is an 'OK' case */
if (t != NULL && x509_format_tests[idx].expected_type != -1) {
if (!TEST_int_eq(t->type, x509_format_tests[idx].expected_type)) {
TEST_info("test_x509_time(%d) failed: expected_type %d, got %d\n",
idx, x509_format_tests[idx].expected_type, t->type);
goto out;
}
}
/* if t is not NULL but expected_string is NULL, it is an 'OK' case too */
if (t != NULL && x509_format_tests[idx].expected_string) {
if (!TEST_str_eq((const char *)t->data,
x509_format_tests[idx].expected_string)) {
TEST_info("test_x509_time(%d) failed: expected_string %s, got %s\n",
idx, x509_format_tests[idx].expected_string, t->data);
goto out;
}
}
rv = 1;
out:
if (t != NULL)
ASN1_TIME_free(t);
return rv;
}
void register_tests()
{
ADD_TEST(test_x509_cmp_time_current);
ADD_ALL_TESTS(test_x509_cmp_time, OSSL_NELEM(x509_cmp_tests));
ADD_ALL_TESTS(test_x509_time, OSSL_NELEM(x509_format_tests));
}
......@@ -4298,3 +4298,4 @@ UI_dup_user_data 4240 1_1_1 EXIST::FUNCTION:UI
UI_method_get_data_destructor 4241 1_1_1 EXIST::FUNCTION:UI
ERR_load_strings_const 4242 1_1_1 EXIST::FUNCTION:
ASN1_TIME_to_tm 4243 1_1_1 EXIST::FUNCTION:
ASN1_TIME_set_string_X509 4244 1_1_1 EXIST::FUNCTION:
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册