ASN1_TIME to time_t conversion
Times are stored as a string internally, on the format YYmmddHHMMSS
or YYYYmmddHHMMSS
.
At the end of the string there is room for fractions of seconds and timezone, but let's ignore that for now, and have some (untested) code.
Note: also see Bryan Olson's answer below, which discusses the undefined behavior due to the i++
's. Also see Seak's answer which removes the undefined behavior.
static time_t ASN1_GetTimeT(ASN1_TIME* time)
{
struct tm t;
const char* str = (const char*) time->data;
size_t i = 0;
memset(&t, 0, sizeof(t));
if (time->type == V_ASN1_UTCTIME) /* two digit year */
{
t.tm_year = (str[i++] - '0') * 10 + (str[++i] - '0');
if (t.tm_year < 70)
t.tm_year += 100;
}
else if (time->type == V_ASN1_GENERALIZEDTIME) /* four digit year */
{
t.tm_year = (str[i++] - '0') * 1000 + (str[++i] - '0') * 100 + (str[++i] - '0') * 10 + (str[++i] - '0');
t.tm_year -= 1900;
}
t.tm_mon = ((str[i++] - '0') * 10 + (str[++i] - '0')) - 1; // -1 since January is 0 not 1.
t.tm_mday = (str[i++] - '0') * 10 + (str[++i] - '0');
t.tm_hour = (str[i++] - '0') * 10 + (str[++i] - '0');
t.tm_min = (str[i++] - '0') * 10 + (str[++i] - '0');
t.tm_sec = (str[i++] - '0') * 10 + (str[++i] - '0');
/* Note: we did not adjust the time based on time zone information */
return mktime(&t);
}
From the openssl code, it seems to be a bad idea:
/*
* FIXME: mktime assumes the current timezone
* instead of UTC, and unless we rewrite OpenSSL
* in Lisp we cannot locally change the timezone
* without possibly interfering with other parts
* of the program. timegm, which uses UTC, is
* non-standard.
* Also time_t is inappropriate for general
* UTC times because it may a 32 bit type.
*/
Note that you can use ASN1_TIME_diff() to get the number of days / seconds between two ASN1_TIME*. If you pass NULL as ASN1_TIME *from, you can get the difference from current time.
I have to disagree with Jan and Jack. Someone actually copied and used the given code where I work, and it fails. Here's why, from the C99 standard:
Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression." -- ISO/IEC 9899:1999, "Programming Languages - C", Section 6.5, Clause 1.
When compiling the given code, gcc (version 4.1.2) says, nine times,
warning: operation on ‘i’ may be undefined.
The code has undefined behavior. The bug I actually saw was year "13" being read as 11. That's because:
The result of the postfix ++ operator is the value of the operand. After the result is obtained, the value of the operand is incremented. [...] The side effect of updating the stored value of the operand shall occur between the previous and the next sequence point. -- Ibid, Section 6.5.2.4, Clause 2.
Both instances of str[i++] in:
t.tm_year = (str[i++] - '0') * 10 + (str[i++] - '0');
read the '1' in "13", because they both happened before the update of i. All the lines that update i multiple times have the same problems.
The easy fix is to get rid of 'i' and replace all those lines with a single call to sscanf().
Even with that fix, I wouldn't like the code. In addition to ignoring a timezone suffix, it doesn't check for errors or unexpected values. Certificates are a security mechanism, and security code has strict requirements for robustness. The corner cases that your program does not handled correctly are the ones your attackers fill feed it.
Well, I don't know about the rest, but that code is just wrong for the cases the ASN1_TIME is in UTCTime format : YYMMDDHHMMSSZ.
I tried and returns the value wrong, even with the correction from ++i to i++, nevertheless ... the code is not an example of good coding.
I manage to fix it, it was the sums of the char types:
static time_t ASN1_GetTimeT(ASN1_TIME* time){
struct tm t;
const char* str = (const char*) time->data;
size_t i = 0;
memset(&t, 0, sizeof(t));
if (time->type == V_ASN1_UTCTIME) {/* two digit year */
t.tm_year = (str[i++] - '0') * 10;
t.tm_year += (str[i++] - '0');
if (t.tm_year < 70)
t.tm_year += 100;
} else if (time->type == V_ASN1_GENERALIZEDTIME) {/* four digit year */
t.tm_year = (str[i++] - '0') * 1000;
t.tm_year+= (str[i++] - '0') * 100;
t.tm_year+= (str[i++] - '0') * 10;
t.tm_year+= (str[i++] - '0');
t.tm_year -= 1900;
}
t.tm_mon = (str[i++] - '0') * 10;
t.tm_mon += (str[i++] - '0') - 1; // -1 since January is 0 not 1.
t.tm_mday = (str[i++] - '0') * 10;
t.tm_mday+= (str[i++] - '0');
t.tm_hour = (str[i++] - '0') * 10;
t.tm_hour+= (str[i++] - '0');
t.tm_min = (str[i++] - '0') * 10;
t.tm_min += (str[i++] - '0');
t.tm_sec = (str[i++] - '0') * 10;
t.tm_sec += (str[i++] - '0');
/* Note: we did not adjust the time based on time zone information */
return mktime(&t);
}