Подтвердить что ты не робот

Преобразование ASN1_TIME в time_t

Как преобразовать формат ASN1_TIME в time_t? Я хотел преобразовать возвращаемое значение X509_get_notAfter() в секунды.

4b9b3361

Ответ 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.