如何转换ASN1_TIME
为time_t
格式?我想将返回值转换X509_get_notAfter()
为秒。
7 回答
时间在内部存储为字符串,格式为YYmmddHHMMSS
or YYYYmmddHHMMSS
。
在字符串的末尾有几分之一秒和时区的空间,但现在让我们忽略它,并有一些(未经测试的)代码。
注意:另请参阅下面 Bryan Olson 的回答,其中讨论了由于i++
's 导致的未定义行为。另请参阅 Seak 的答案,它消除了未定义的行为。
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);
}
从 openssl 代码来看,这似乎是一个坏主意:
/*
* 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.
*/
请注意,您可以使用ASN1_TIME_diff()来获取两个 ASN1_TIME* 之间的天数/秒数。如果将 NULL 作为 ASN1_TIME *from 传递,则可以得到与当前时间的差异。
好吧,我不知道其余的,但是对于 ASN1_TIME 为 UTCTime 格式的情况,该代码是错误的:YYMMDDHHMMSSZ。
我尝试并返回错误的值,即使从 ++i 到 i++ 的更正,但是……该代码不是良好编码的示例。
我设法修复它,它是 char 类型的总和:
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);
}
我不得不不同意简和杰克的观点。有人实际上在我工作的地方复制并使用了给定的代码,但它失败了。这就是为什么,从 C99 标准:
在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的评估修改一次。”——ISO/IEC 9899:1999,“Programming Languages - C”,第 6.5 节,第 1 节。
编译给定代码时,gcc(4.1.2 版)说了九次,
警告:对“i”的操作可能未定义。
代码具有未定义的行为。我实际看到的错误是“13”年被读作 11。那是因为:
后缀 ++ 运算符的结果是操作数的值。得到结果后,操作数的值递增。[...] 更新操作数存储值的副作用应发生在前一个序列点和下一个序列点之间。-- 同上,第 6.5.2.4 节,第 2 条。
str[i++] 的两个实例都在:
t.tm_year = (str[i++] - '0') * 10 + (str[i++] - '0');
读取“13”中的“1”,因为它们都发生在 i 更新之前。多次更新 i 的所有行都有相同的问题。
最简单的解决方法是去掉“i”,并通过一次调用 sscanf() 替换所有这些行。
即使有了这个修复,我也不喜欢这个代码。除了忽略时区后缀之外,它不会检查错误或意外值。证书是一种安全机制,安全码对健壮性有严格的要求。您的程序未正确处理的极端情况是您的攻击者填充的情况。
Jan 的回答主要适用于这种情况,但是,累加器i
应始终使用i++
:
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);
}
time_t
可能有一个更窄的范围ASN1_TIME
,因此 ASN1_TIME_*
函数可能是更健壮的替代方案。例如,要比较时间,您可以使用ASN1_TIME_diff()
(这可以避免使用溢出时可能出现的安全问题time_t
)。以人类可读的格式打印,调用ASN1_TIME_print()
等。
到目前为止,没有一个答案遵循rfc 5280,它指定输入时间为 UTC(mktime()
预计本地时区的时间,即,如果本地时区不是 UTC,则答案不正确)。还有:
符合标准的系统必须将年份字段 (YY) 解释如下: 如果 YY 大于或等于 50,则年份应解释为 19YY;且 YY 小于 50 时,年份应解释为 20YY。
即,if (tm_year < 70) tm_year += 100;
违反了 rfc。这个答案使用year += year < 50 ? 2000 : 1900
.
此外,99991231235959Z
在输入中意味着证书没有明确定义的到期日期(该函数应该返回(time_t)-1
- 一个错误)。
要将 UTCTime 或 GeneralizedTime 字符串 ( ASN1_TIME*
)转换为自 Epoch ( time_t
) 以来的秒数:
typedef unsigned U;
time_t ASN1_TIME_to_posix_time(const ASN1_TIME* time) {
if(!time) return -1;
const char *s = (const char*)time->data;
if (!s) return -1;
U two_digits_to_uint() // nested function: gcc extension
{
U n = 10 * (*s++ - '0');
return n + (*s++ - '0');
}
U year, month, day, hour, min, sec;
switch(time->type) {
// https://www.rfc-editor.org/rfc/rfc5280#section-4.1.2.5.1
case V_ASN1_UTCTIME: // YYMMDDHHMMSSZ
year = two_digits_to_uint();
year += year < 50 ? 2000 : 1900;
break;
case V_ASN1_GENERALIZEDTIME: // YYYYMMDDHHMMSSZ
year = 100 * two_digits_to_uint();
year += two_digits_to_uint();
break;
default:
return -1; // error
}
month = two_digits_to_uint();
day = two_digits_to_uint();
hour = two_digits_to_uint();
min = two_digits_to_uint();
sec = two_digits_to_uint();
if (*s != 'Z') return -1;
if (year == 9999 && month == 12 && day == 31 && hour == 23 && min == 59
&& sec == 59) // 99991231235959Z rfc 5280
return -1;
return posix_time(year, month, day, hour, min, sec);
}
whereposix_time()
用于将分解的 UTC 时间转换为日历时间。自纪元以来的秒数:
time_t posix_time(U year, U month, U day, U hour, U min, U sec)
{
if (year < 1970 || month < 1 || month > 12 || day < 1 || day > 31
|| hour > 23 || min > 59 || sec > 60)
return -1;
// days upto months for non-leap years
static const U month_day[13] =
{-1, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
year -= 1900;
// number of Februaries since 1900
const U year_for_leap = (month > 2) ? year + 1 : year;
// XXX may overflow
return sec + min*60 + hour*3600 + (month_day[month] + day - 1)*86400 +
(year-70)*31536000 + ((year_for_leap-69)/4)*86400 -
((year_for_leap-1)/100)*86400 + ((year_for_leap+299)/400)*86400;
}
month_day
并且year_for_leap
来自@DTiedy 的回答。
我知道为时已晚,并且openssl
引入了一个函数ASN1_TIME_to_tm但我不得不使用没有此方法的旧版本的 openssl。
我看到了这个问题的各种可能答案,他们正在他们的代码中解析时间字符串,但我对这种方法并不满意,因为我认为我可能会在解析逻辑中遗漏一些东西,我的代码可能会中断或可能无法处理所有极端情况。所以,我实现了 C++ 的函数,openssl
它只使用函数来实现转换。
它使用ASN1_TIME_diff从纪元计算秒数。为了获得ASN1_TIME
epoch,我使用了 ASN1_TIME_SET,time_t
参数为 0。
随意评论和测试。
bool _ASN1_TIME_to_tm(const ASN1_TIME *pTime, struct tm *pTm)
{
int days = 0, seconds = 0;
ASN1_TIME *epochTime = ASN1_TIME_new();
ASN1_TIME_set(epochTime, time_t(0));
if (!ASN1_TIME_diff(&days, &seconds, epochTime, pTime))
return false;
time_t sinceEpoch = time_t(86400LL * days + seconds); // No of seconds in a day = 86400
gmtime_r(&sinceEpoch, pTm);
std::cout << "DateTime: " << TOS::convertTmToStr(*pTm) << std::endl;
ASN1_TIME_free(epochTime);
return true;
}
或进行更多检查的代码:
bool _ASN1_TIME_to_tm(const ASN1_TIME *pTime, struct tm *pTm)
{
bool result = false;
time_t sinceEpoch = 0;
int days = 0, seconds = 0;
if (!pTime)
return false;
ASN1_TIME *epochTime = ASN1_TIME_new();
if (!epochTime)
return false;
do {
if (!ASN1_TIME_set(epochTime, time_t(0)))
break;
if (!ASN1_TIME_diff(&days, &seconds, epochTime, pTime))
break;
// No of seconds in a day = 86400
sinceEpoch = time_t(86400LL * days + seconds);
gmtime_r(&sinceEpoch, pTm);
std::cout << "DateTime: " << TOS::convertTmToStr(*pTm) << std::endl;
result = true;
} while (0);
ASN1_TIME_free(epochTime);
return result;
}