如何将ASN1_TIME
转换为time_t
格式?我想将X509_get_notAfter()
的返回值转换为秒。
答案 0 :(得分:8)
时间以内部格式存储为字符串,格式为YYmmddHHMMSS
或YYYYmmddHHMMSS
。
在字符串的末尾有几分之一秒和时区的空间,但是现在让我们忽略它,并且有一些(未经测试的)代码。
注意 :另请参阅下面的Bryan Olson的回答,该回答讨论了由i++
引起的未定义行为。另请参阅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);
}
答案 1 :(得分:7)
好吧,我不知道其余的,但是对于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);
}
答案 2 :(得分:5)
我不同意Jan和Jack的观点。实际上有人在我工作的地方复制并使用了给定的代码,但它失败了。这就是为什么,从C99标准:
在上一个和下一个序列点之间,一个对象应该 通过评估将其存储值最多修改一次 一个表达。“ - ISO / IEC 9899:1999,“编程语言 - C”,第6.5节,第1条。
编译给定代码时,gcc(版本4.1.2)说九次,
警告:'i'上的操作可能未定义。
代码具有未定义的行为。我实际看到的错误是年份“13”被读为11.这是因为:
postfix ++运算符的结果是操作数的值。 获得结果后,操作数的值递增。 [...] 更新操作数存储值的副作用应为 发生在前一个和下一个序列点之间。 - 同上,第6.5.2.4节,第2条。
str [i ++]的两个实例:
t.tm_year =(str [i ++] - '0')* 10 +(str [i ++] - '0');
读取“13”中的“1”,因为它们都发生在i的更新之前。多次更新i的所有行都有相同的问题。
简单的解决方法是摆脱'i'并通过一次调用sscanf()来替换所有这些行。
即使有了这个修复,我也不喜欢这些代码。除了忽略时区后缀之外,它不会检查错误或意外值。证书是一种安全机制,安全代码对健壮性有严格的要求。你的程序没有正确处理的极端情况是你的攻击者填充它的那些。
答案 3 :(得分:1)
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);
}
答案 4 :(得分:1)
time_t
的范围可能比ASN1_TIME
更窄,因此ASN1_TIME_*
函数可能是更强大的替代方案。例如,要比较时间,您可以使用ASN1_TIME_diff()
(如果使用time_t
,这可以避免溢出时可能出现的安全问题)。要以人类可读的格式打印,请致电ASN1_TIME_print()
等
到目前为止,没有一个答案跟随rfc 5280,它指定输入时间是UTC(mktime()
预计当地时区的时间,即如果本地时区不是UTC则答案不正确)。 Also:
合规系统必须按如下方式解释年份字段(YY): 如果YY大于或等于50,则年份应为 解释为19YY;和 如果YY小于50,则年份应解释为20YY。
即,if (tm_year < 70) tm_year += 100;
违反了rfc。此答案使用year += year < 50 ? 2000 : 1900
。
此外,99991231235959Z
in the input表示证书没有明确定义的到期日期(函数应返回(time_t)-1
- 错误)。
将UTCTime或GeneralizedTime字符串(ASN1_TIME*
)转换为seconds since 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://tools.ietf.org/html/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);
}
其中posix_time()
用于将分解的UTC时间转换为日历时间。 Seconds Since the Epoch:
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's answer。
答案 5 :(得分:1)
我知道为时已晚,openssl
引入了一个功能ASN1_TIME_to_tm,但是我不得不使用没有此方法的旧版openssl。
我看到了这个问题的各种可能答案,他们正在解析代码中的时间字符串,但是我对这种方法不满意,因为我认为我可能会以某种方式错过解析逻辑中的某些内容,并且我的代码可能会中断或无法处理所有内容极端案例。因此,我为C ++实现了该函数,该函数仅使用openssl
函数来实现转换。
它使用ASN1_TIME_diff计算从纪元开始的秒数。为了获得ASN1_TIME
的时代,我使用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;
}