diff --git a/src/ssl_asn1.c b/src/ssl_asn1.c index 58aed807d0..8dd158ef19 100644 --- a/src/ssl_asn1.c +++ b/src/ssl_asn1.c @@ -4293,6 +4293,14 @@ static int wolfssl_utctime_year(const unsigned char* str, int len, int* year) ret = 0; } + if (ret == 1) { + if ((str[0] < '0') || (str[0] > '9') || + (str[1] < '0') || (str[1] > '9')) { + WOLFSSL_MSG("Invalid characters in UTC year."); + ret = 0; + } + } + if (ret == 1) { int tm_year; /* 2-digit year. */ @@ -4334,6 +4342,16 @@ static int wolfssl_gentime_year(const unsigned char* str, int len, int* year) ret = 0; } + if (ret == 1) { + if ((str[0] < '0') || (str[0] > '9') || + (str[1] < '0') || (str[1] > '9') || + (str[2] < '0') || (str[2] > '9') || + (str[3] < '0') || (str[3] > '9')) { + WOLFSSL_MSG("Invalid characters in generalized year."); + ret = 0; + } + } + if (ret == 1) { int tm_year; /* 4-digit year. */ @@ -4363,11 +4381,12 @@ static int wolfssl_asn1_time_to_tm(const WOLFSSL_ASN1_TIME* asnTime, const unsigned char* asn1TimeBuf; int asn1TimeBufLen; int i = 0; -#ifdef XMKTIME + /* Parse into a local struct so the caller's tm is only written on + * success. Avoids leaving a partially-populated struct behind when + * the input fails validation. */ struct tm localTm; - XMEMSET(&localTm, 0, sizeof localTm); -#endif + XMEMSET(&localTm, 0, sizeof(localTm)); /* Get the string buffer - fixed array, can't fail. */ asn1TimeBuf = wolfSSL_ASN1_TIME_get_data(asnTime); @@ -4378,15 +4397,12 @@ static int wolfssl_asn1_time_to_tm(const WOLFSSL_ASN1_TIME* asnTime, ret = 0; } if (ret == 1) { - /* Zero out values in broken-down time. */ - XMEMSET(tm, 0, sizeof(struct tm)); - if (asnTime->type == WOLFSSL_V_ASN1_UTCTIME) { /* Get year from UTC TIME string. */ int tm_year; if ((ret = wolfssl_utctime_year(asn1TimeBuf, asn1TimeBufLen, &tm_year)) == 1) { - tm->tm_year = tm_year; + localTm.tm_year = tm_year; /* Month starts after year - 2 characters. */ i = 2; } @@ -4396,7 +4412,7 @@ static int wolfssl_asn1_time_to_tm(const WOLFSSL_ASN1_TIME* asnTime, int tm_year; if ((ret = wolfssl_gentime_year(asn1TimeBuf, asn1TimeBufLen, &tm_year)) == 1) { - tm->tm_year = tm_year; + localTm.tm_year = tm_year; /* Month starts after year - 4 characters. */ i = 4; } @@ -4406,26 +4422,51 @@ static int wolfssl_asn1_time_to_tm(const WOLFSSL_ASN1_TIME* asnTime, WOLFSSL_MSG("asnTime->type is invalid."); ret = 0; } - } - if (ret == 1) { + } + + if (ret == 1) { + int j; + /* Validate 10 digits: MMDDHHMMSS. Length was already checked + * (>= UTCTIME_LEN or >= GENTIME_LEN), so i+10 is in range. */ + for (j = i; j < i + 10; j++) { + if (asn1TimeBuf[j] < '0' || asn1TimeBuf[j] > '9') { + WOLFSSL_MSG("Non-digit in ASN.1 TIME."); + ret = 0; + break; + } + } + } + + if (ret == 1) { /* Fill in rest of broken-down time from string. */ /* January is 0 not 1 */ - tm->tm_mon = (asn1TimeBuf[i] - '0') * 10; i++; - tm->tm_mon += (asn1TimeBuf[i] - '0') - 1; i++; - tm->tm_mday = (asn1TimeBuf[i] - '0') * 10; i++; - tm->tm_mday += (asn1TimeBuf[i] - '0'); i++; - tm->tm_hour = (asn1TimeBuf[i] - '0') * 10; i++; - tm->tm_hour += (asn1TimeBuf[i] - '0'); i++; - tm->tm_min = (asn1TimeBuf[i] - '0') * 10; i++; - tm->tm_min += (asn1TimeBuf[i] - '0'); i++; - tm->tm_sec = (asn1TimeBuf[i] - '0') * 10; i++; - tm->tm_sec += (asn1TimeBuf[i] - '0'); + localTm.tm_mon = (asn1TimeBuf[i] - '0') * 10; i++; + localTm.tm_mon += (asn1TimeBuf[i] - '0') - 1; i++; + localTm.tm_mday = (asn1TimeBuf[i] - '0') * 10; i++; + localTm.tm_mday += (asn1TimeBuf[i] - '0'); i++; + localTm.tm_hour = (asn1TimeBuf[i] - '0') * 10; i++; + localTm.tm_hour += (asn1TimeBuf[i] - '0'); i++; + localTm.tm_min = (asn1TimeBuf[i] - '0') * 10; i++; + localTm.tm_min += (asn1TimeBuf[i] - '0'); i++; + localTm.tm_sec = (asn1TimeBuf[i] - '0') * 10; i++; + localTm.tm_sec += (asn1TimeBuf[i] - '0'); + } + if (ret == 1) { + /* Range-check broken-down fields. ValidateGmtime returns 0 on + * success. */ + if (ValidateGmtime(&localTm)) { + WOLFSSL_MSG("Out-of-range field in ASN.1 TIME."); + ret = 0; + } + } + + if (ret == 1) { + /* Publish to caller. */ + XMEMCPY(tm, &localTm, sizeof(*tm)); #ifdef XMKTIME - XMEMCPY(&localTm, tm, sizeof(struct tm)); - /* Call XMKTIME on tm to get tm_wday and tm_yday fields populated. - Note that localTm is used here to avoid modifying other fields, - such as tm_isdst/tm_gmtoff. */ + /* XMKTIME may set tm_isdst/tm_gmtoff on localTm; call after the + * copy so those fields stay zero in the caller's tm. */ XMKTIME(&localTm); tm->tm_wday = localTm.tm_wday; tm->tm_yday = localTm.tm_yday; diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 088eb47ea5..bff1d5ed26 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -14664,7 +14664,7 @@ int GetTimeString(byte* date, int format, char* buf, int len, int dateLen) #endif /* WOLFSSL_ASN_TIME_STRING */ /* Check time struct for valid values. Returns 0 for success */ -static int ValidateGmtime(struct tm* inTime) +int ValidateGmtime(struct tm* inTime) { int ret = 1; if ((inTime != NULL) && diff --git a/wolfssl/wolfcrypt/asn.h b/wolfssl/wolfcrypt/asn.h index b3028c9c99..ff94a5eb3c 100644 --- a/wolfssl/wolfcrypt/asn.h +++ b/wolfssl/wolfcrypt/asn.h @@ -2452,6 +2452,7 @@ WOLFSSL_LOCAL int GetFormattedTime_ex(void* currTime, byte* buf, word32 len, byt WOLFSSL_LOCAL int ExtractDate(const unsigned char* date, unsigned char format, wolfssl_tm* certTime, int* idx, int len); WOLFSSL_LOCAL int DateGreaterThan(const struct tm* a, const struct tm* b); +WOLFSSL_LOCAL int ValidateGmtime(struct tm* inTime); WOLFSSL_LOCAL int wc_ValidateDate(const byte* date, byte format, int dateType, int len); #ifndef NO_ASN_TIME