Преобразование ASN1_TIME в time_t
Как преобразовать формат ASN1_TIME
в time_t
? Я хотел преобразовать возвращаемое значение X509_get_notAfter()
в секунды.
Ответы
Ответ 1
Время хранится как строка внутри, в формате YYmmddHHMMSS
или YYYYmmddHHMMSS
.
В конце строки есть место для долей секунд и часового пояса, но на этот раз игнорировать и иметь некоторый (непроверенный) код.
Примечание: см. ниже ответ Брайана Олсона, в котором обсуждается поведение undefined из-за i++
. Также см. Ответ Seak, который устраняет поведение undefined.
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);
}
Ответ 2
Ну, я не знаю об остальном, но этот код просто неверен для случаев, когда ASN1_TIME находится в формате UTCTime: YYMMDDHHMMSSZ.
Я попытался и вернул значение неправильно, даже с исправлением от ++ я до я ++,
тем не менее... код не является примером хорошего кодирования.
Мне удалось это исправить, это были суммы типов 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);
}
Ответ 3
Из кода 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 *, вы можете получить разницу с текущим временем.
Ответ 4
Я должен не согласиться с Яном и Джеком. Кто-то действительно скопировал и использовал данный код, где я работаю, и он терпит неудачу. Вот почему, из стандарта C99:
Между предыдущей и следующей точкой последовательности объект должен имеют значение хранимой ценности, измененное не более чем одним раз путем оценки выражения ". - ISO/IEC 9899: 1999," Языки программирования - C", раздел 6.5, раздел 1.
При компиляции данного кода gcc (версия 4.1.2) говорит девять раз
предупреждение: операция "i может быть undefined.
Код имеет поведение undefined. Ошибка, которую я на самом деле видел, был годом "13", считающимся 11. Это потому, что:
Результатом оператора postfix ++ является значение операнда. После того, как результат будет получен, значение операнда будет увеличено. [...] Побочный эффект обновления сохраненного значения операнда должен происходят между предыдущей и следующей точкой последовательности. - Там же, раздел 6.5.2.4, пункт 2.
Оба экземпляра str [i ++] в:
t.tm_year = (str [i ++] - '0') * 10 + (str [i ++] - '0');
прочитайте "1" в "13", потому что они оба произошли до обновления i. Все строки, которые обновляют я несколько раз, имеют те же проблемы.
Легкое исправление - избавиться от 'i' и заменить все эти строки на один вызов sscanf().
Даже с этим исправлением мне не понравится код. В дополнение к игнорированию суффикса часового пояса он не проверяет наличие ошибок или неожиданных значений. Сертификаты - это механизм безопасности, а код безопасности имеет строгие требования к надежности. Угловые случаи, в которых ваша программа не обрабатывается правильно, - это те, на которых ваши нападающие заполняют ее.
Ответ 5
Ян ответ в основном работает в этой ситуации, однако аккумулятор 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);
}
Ответ 6
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.
i.e., 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://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 в календарное время. Секунды с эпохи:
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 answer.