我该如何将 ASN1_TIME
转换为 time_t
格式?我想将 X509_get_notAfter()
的返回值转换为秒。
我该如何将 ASN1_TIME
转换为 time_t
格式?我想将 X509_get_notAfter()
的返回值转换为秒。
从 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* 之间的天数/秒数差。如果您将 ASN1_TIME *from 参数设置为 NULL,则可以获取与当前时间的差值。时间以字符串形式内部存储,格式为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);
}
嗯,我不知道其他情况,但是对于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);
}
mktime()
在这里可能返回错误的结果(mktime()
期望本地时区的输入时间)。2- YY >= 50
应解释为19YY
。3- 99991231235959Z
是一个特殊值。这里有一个代码示例,说明如何解决这些问题。 - jfssscanf
不正确很容易。没有必要为了解决一个问题而引入另一个问题。 - jww我知道现在可能太晚了,而且 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;
}
epochTime
。 - Gilstime_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);
}
posix_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;
}
在这种情况下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);
}
i++
(或++i
)在同一表达式中的顺序不确定,因此此代码是错误的,依赖于未定义的行为。需要修改。 - Paul J. Lucas