2012-06-11 12 views

Respuesta

7

Las horas se almacenan como una cadena internamente, en el formato YYmmddHHMMSS o YYYYmmddHHMMSS.

Al final de la cadena hay espacio para fracciones de segundos y zona horaria, pero ignorémoslo por el momento y tengamos algún código (no probado).

Nota: ver también la respuesta de Bryan Olson a continuación, que analiza el comportamiento indefinido debido a las i++ 's. También vea la respuesta de Seak, que elimina el comportamiento indefinido.

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

Este código es incorrecto ya que se basa en el comportamiento indefinido de tener 'i ++' (o '++ i') en la misma expresión: el orden de evaluación no está garantizado. –

1

respuesta de Jan trabaja sobre todo en esta situación, sin embargo, el acumulador i debe utilizar constantemente 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 ++ implica que i se incrementa DESPUÉS de que la declaración se complete. Esto significa que para el año, digamos que es 2014, esto termina siendo 322 (2222) dentro de la estructura tm, por lo que la respuesta correcta es i ++ para la primera y ++ i para cada subsecuente. –

+3

No, su código está _totalmente equivocado. Simplemente no puede tener más de un 'i ++' en la misma expresión, ya que no se garantiza el orden de evaluación (de izquierda a derecha o de derecha a izquierda). –

3

puedo estar de acuerdo con Jan y Jack. Alguien realmente copió y usó el código dado donde yo trabajo, y falla. He aquí por qué, de la norma C99:

entre el anterior y el siguiente punto de la secuencia de un objeto se tener su valor almacenado modificado como máximo una vez por la evaluación de una expresión " - ISO/IEC 9899.: 1999, "Lenguajes de programación - C", Sección 6.5, Cláusula 1.

al compilar el código dado, gcc (versión 4.1.2) dice, nueve veces,

aDVERTENCIA: la operación de ' puedo estar indefinido ed.

El código tiene un comportamiento indefinido. El error que realmente vi fue el año "13" leído como 11. Eso es porque:

El resultado del operador postfix ++ es el valor del operando. Después de obtener el resultado, el valor del operando se incrementa. [...] El efecto secundario de actualizar el valor almacenado del operando será entre el punto de secuencia anterior y el siguiente. - Ibid, Sección 6.5.2.4, Cláusula 2.

ambas instancias de str [i ++] en:

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

lee el '1' en "13", porque ambos ocurrieron antes de la actualización de i. Todas las líneas que se actualizan varias veces tienen los mismos problemas.

La solución más fácil es deshacerse de 'i' y reemplazar todas esas líneas con una sola llamada a sscanf().

Incluso con esa corrección, no me gustaría el código.Además de ignorar un sufijo de zona horaria, no verifica si hay errores o valores inesperados. Los certificados son un mecanismo de seguridad y el código de seguridad tiene requisitos estrictos de solidez. Los casos de esquina que tu programa no maneja correctamente son los que tus atacantes llenan.

+0

* "La solución más fácil es deshacerse de 'i' y reemplazar todas esas líneas con una sola llamada a sscanf()" * - probablemente debería proporcionar un ejemplo ya que es fácil de usar 'sscanf' incorrectamente. No tiene sentido que intercambie un error por otro. – jww

6

Bueno, no sé sobre el resto, pero ese código es simplemente incorrecto para los casos en que el ASN1_TIME está en formato UTCTime: YYMMDDHHMMSSZ.

He intentado y devuelve el valor incorrecto, incluso con la corrección de ++ i a i ++, sin embargo ... el código no es un ejemplo de buena codificación.

me las arreglo para solucionarlo, que era la suma de los tipos 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); 
} 
+0

rfc 5280 dice que 1- el tiempo de entrada está en UTC y por lo tanto 'mktime()' puede devolver un resultado incorrecto aquí ('mktime()' espera el tiempo de entrada en la zona horaria local). 2- 'YY> = 50' se interpretará como' 19YY' 3- '99991231235959Z' es un valor especial. Aquí hay un [ejemplo de código de cómo se pueden solucionar estos problemas] (https://stackoverflow.com/a/47015958/4279). – jfs

4

a partir del código OpenSSL, que parece ser una mala idea:

/* 
* 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. 
*/ 

en cuenta que puede use ASN1_TIME_diff() para obtener la cantidad de días/segundos entre dos ASN1_TIME *. Si pasa NULL como ASN1_TIME * desde, puede obtener la diferencia de la hora actual.

1

time_t pueden tener un rango más estrecho que ASN1_TIME y por lo tanto ASN1_TIME_* funciones podrían ser una alternativa más robusta. Por ejemplo, para comparar tiempos, puede usar ASN1_TIME_diff() (esto evita posibles problemas de seguridad con desbordamiento si se usa time_t). Para imprimir en un formato legible por humanos, llame ASN1_TIME_print(), etc.

Hasta el momento ninguna de las respuestas siguen rfc 5280 que especifica que los tiempos de entrada están en UTC (mktime() espera vez en la zona horaria local, es decir, las respuestas son incorrectas si el la zona horaria local no es UTC). Also:

sistemas conformes debe interpretar el campo año (YY) de la siguiente manera: donde yy es mayor que o igual a 50, el año serán interpretarse como 19aa; y Donde YY es menor que 50, el año SE DEBERÁ interpretar como 20YY.

es decir, if (tm_year < 70) tm_year += 100; viola el rfc. Esta respuesta usa year += year < 50 ? 2000 : 1900.

Además, 99991231235959Z in the input significa que el certificado no tiene una fecha de vencimiento bien definida (la función debe devolver (time_t)-1 - un error).

para convertir cadenas UTCTime o GeneralizedTime (ASN1_TIME*) a 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); 
} 

donde posix_time() se utiliza para convertir la hora UTC-rota hasta el tiempo del calendario. 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 y year_for_leap son de @DTiedy's answer.

Cuestiones relacionadas