ASN1_TIME到time_t的转换

时间:2012-06-11 06:49:39

标签: openssl ssl-certificate asn.1

如何将ASN1_TIME转换为time_t格式?我想将X509_get_notAfter()的返回值转换为秒。

6 个答案:

答案 0 :(得分:8)

时间以内部格式存储为字符串,格式为YYmmddHHMMSSYYYYmmddHHMMSS

在字符串的末尾有几分之一秒和时区的空间,但是现在让我们忽略它,并且有一些(未经测试的)代码。

注意 :另请参阅下面的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 Epochtime_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_dayyear_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;
}