2012-06-11 6 views

ответ

7

Времена хранятся в виде строки внутри, в формате YYmmddHHMMSS или YYYYmmddHHMMSS.

В конце строки есть место для долей секунд и часовой пояс, но давайте проигнорируем это на данный момент и получим некоторый (непроверенный) код.

Примечание: также увидеть ответ Брайана Олсона ниже, который обсуждает неопределенное поведение из-за 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); 
} 
+8

Этот код неверен, так как он полагается на неопределенное поведение с 'i ++' (или '++ i') в том же выражении: порядок оценки не гарантирован. –

1

ответ Яна в основном работает в этой ситуации, однако, аккумулятор 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); 
} 
+0

i ++ подразумевает, что i увеличивается с шагом ПОСЛЕ завершения инструкции. Это означает, что в течение года, скажем, это 2014 год, это составляет 322 (2222) внутри структуры tm, поэтому правильный ответ i ++ для первого и ++ i для каждого последующего. –

+3

Нет, ваш код _still_ неправ. Вы просто не можете _not_ иметь более одного «i ++» в одном выражении, так как порядок оценки не гарантируется (слева направо и справа налево). –

3

Я не согласен с Яном и Джеком. Кто-то действительно скопировал и использовал данный код, где я работаю, и он терпит неудачу. Вот почему, от стандарта C99:

Между предыдущей и следующей точкой последовательности объект должен бы его сохраненное значение изменено в самый раз по оценке выражения « - ISO/IEC 9899.: 1999, "Языки программирования - C", раздел 6.5, пункт 1.

При составлении данного кода, GCC (версия 4.1.2) говорит, девять раз,

предупреждение: операция на ' i 'может быть undefin ed

Код имеет неопределенное поведение. Ошибка, которую я на самом деле видел, был годом «13», считающимся 11. Это связано с тем, что:

Результатом оператора postfix ++ является значение операнда. После того, как результат будет получен, значение операнда будет увеличено. [...] Побочный эффект обновления сохраненного значения операнда должен быть равен между предыдущей и следующей точками последовательности. - Там же, раздел 6.5.2.4, пункт 2.

Оба экземпляра ул [я ++] в:

t.tm_year = (ул [я ++] - '0') * 10 + (str [i ++] - '0');

читать «1» в «13», потому что они оба произошли до обновления i. Все строки, обновляющие i несколько раз, имеют те же проблемы.

Легкое решение состоит в том, чтобы избавиться от «i» и заменить все эти строки на один вызов sscanf().

Даже с этим исправлением, мне не понравился бы код.В дополнение к игнорированию суффикса часового пояса он не проверяет наличие ошибок или неожиданных значений. Сертификаты - это механизм безопасности, а код безопасности имеет строгие требования к надежности. Угловые случаи, в которых ваша программа не обрабатывается правильно, - это те, которые ваши нападающие заполняют.

+0

* «Легкое решение состоит в том, чтобы избавиться от« i »и заменить все эти строки на один вызов sscanf()» * - вам, вероятно, следует предоставить пример, поскольку его простой в использовании 'sscanf' неправильно. Нет смысла торговать одной ошибкой для другой. – jww

6

Ну, я не знаю обо всем остальном, но этот код просто неверен для случаев, когда ASN1_TIME находится в формате UTCTime: YYMMDDHHMMSSZ.

Я попытался и вернул значение неправильно, даже с исправлением от ++ 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; 
     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); 
} 
+0

rfc 5280 говорит, что 1 - время ввода в UTC, и поэтому 'mktime()' может возвращать неверный результат здесь ('mktime()' ожидает время ввода в локальном часовом поясе). 2- 'YY> = 50' должно быть интерпретировано как' 19YY' 3- '99991231235959Z' - специальное значение. Вот пример кода, который может быть исправлен. (Https://stackoverflow.com/a/47015958/4279). – jfs

4

Из кода 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 *, вы можете получить разницу с текущим временем.

1

time_t может иметь более узкий диапазон, чем ASN1_TIME, и поэтому функции ASN1_TIME_* могут быть более надежными. Например, чтобы сравнить времена, вы можете использовать ASN1_TIME_diff() (это позволяет избежать возможных проблем безопасности при переполнении, если используется time_t). Для печати в удобочитаемом формате, вызовите ASN1_TIME_print() и т.д.

До сих пор ни один из ответов не следуют rfc 5280, который указывает, что входные времена в UTC (mktime() ожидает время в местной временной зоны, т.е., ответы неверны, если местный часовой пояс не UTC). Also:

Соответствующие системы должны интерпретировать поле год (YY) следующим образом: где ГГ больше или равна 50, год ДОЛЖНА быть истолковано как 19YY; и Где YY меньше 50, год ДОЛЖЕН быть интерпретирован как 20YY.

i.e., 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.