Merge pull request #11228 from aidangarske/fenrir-fixes-9961

Encode default cert validity as UTCTime through 2049
pull/11090/head
Sean Parkinson 2026-08-25 17:16:56 +10:00 committed by GitHub
commit 94dca6fdd4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 226 additions and 57 deletions

View File

@ -520,25 +520,25 @@ int test_wolfSSL_X509_sign(void)
* with the MSB set. See GenerateInteger in asn.c */
#ifndef USE_CERT_BUFFERS_1024
#ifndef WOLFSSL_ALT_NAMES
/* Valid case - size should be 781-786 with 16 byte serial number */
ExpectTrue((781 + snSz <= ret) && (ret <= 781 + 5 + snSz));
/* Valid case - size should be 777-782 with 16 byte serial number */
ExpectTrue((777 + snSz <= ret) && (ret <= 777 + 5 + snSz));
#elif defined(WOLFSSL_IP_ALT_NAME)
/* Valid case - size should be 955-960 with 16 byte serial number */
ExpectTrue((939 + snSz <= ret) && (ret <= 939 + 5 + snSz));
/* Valid case - size should be 951-956 with 16 byte serial number */
ExpectTrue((935 + snSz <= ret) && (ret <= 935 + 5 + snSz));
#else
/* Valid case - size should be 926-931 with 16 byte serial number */
ExpectTrue((910 + snSz <= ret) && (ret <= 910 + 5 + snSz));
/* Valid case - size should be 922-927 with 16 byte serial number */
ExpectTrue((906 + snSz <= ret) && (ret <= 906 + 5 + snSz));
#endif
#else
#ifndef WOLFSSL_ALT_NAMES
/* Valid case - size should be 537-542 with 16 byte serial number */
ExpectTrue((521 + snSz <= ret) && (ret <= 521 + 5 + snSz));
/* Valid case - size should be 533-538 with 16 byte serial number */
ExpectTrue((517 + snSz <= ret) && (ret <= 517 + 5 + snSz));
#elif defined(OPENSSL_ALL) || defined(WOLFSSL_IP_ALT_NAME)
/* Valid case - size should be 695-670 with 16 byte serial number */
ExpectTrue((679 + snSz <= ret) && (ret <= 679 + 5 + snSz));
/* Valid case - size should be 691-696 with 16 byte serial number */
ExpectTrue((675 + snSz <= ret) && (ret <= 675 + 5 + snSz));
#else
/* Valid case - size should be 666-671 with 16 byte serial number */
ExpectTrue((650 + snSz <= ret) && (ret <= 650 + 5 + snSz));
/* Valid case - size should be 662-667 with 16 byte serial number */
ExpectTrue((646 + snSz <= ret) && (ret <= 646 + 5 + snSz));
#endif
#endif
/* check that issuer name is as expected after signature */

View File

@ -28300,15 +28300,32 @@ static WC_INLINE byte itob(int number)
}
/* write time to output, format */
static void SetTime(struct tm* date, byte* output)
/* RFC 5280: validity dates through 2049 encode as UTCTime, 2050 and later as
* GeneralizedTime. date->tm_year holds the full year here. */
static byte ValidityTimeFormat(const struct tm* date)
{
if (date->tm_year >= 1950 && date->tm_year < 2050)
return ASN_UTC_TIME;
return ASN_GENERALIZED_TIME;
}
/* write time value to output in the given ASN.1 format */
static void SetTime(struct tm* date, byte* output, byte format)
{
int i = 0;
int year = date->tm_year;
output[i++] = itob((date->tm_year % 10000) / 1000);
output[i++] = itob((date->tm_year % 1000) / 100);
output[i++] = itob((date->tm_year % 100) / 10);
output[i++] = itob( date->tm_year % 10);
if (format == ASN_UTC_TIME) {
year %= 100;
output[i++] = itob((year / 10) % 10);
output[i++] = itob( year % 10);
}
else {
output[i++] = itob((year % 10000) / 1000);
output[i++] = itob((year % 1000) / 100);
output[i++] = itob((year % 100) / 10);
output[i++] = itob( year % 10);
}
output[i++] = itob(date->tm_mon / 10);
output[i++] = itob(date->tm_mon % 10);
@ -30071,6 +30088,8 @@ static int SetValidity(byte* before, byte* after, int daysValid)
{
#ifndef NO_ASN_TIME
int ret = 0;
byte format;
word32 timeSz;
time_t now;
time_t then;
struct tm* tmpTime;
@ -30101,7 +30120,12 @@ static int SetValidity(byte* before, byte* after, int daysValid)
localTime.tm_year += 1900;
localTime.tm_mon += 1;
SetTime(&localTime, before);
format = ValidityTimeFormat(&localTime);
timeSz = (format == ASN_UTC_TIME) ? ASN_UTC_TIME_SIZE - 1
: ASN_GEN_TIME_SZ;
before[0] = format;
SetLength(timeSz, before + 1);
SetTime(&localTime, before + 2, format);
/* add daysValid of seconds */
then = now + (daysValid * (time_t)86400);
@ -30118,7 +30142,12 @@ static int SetValidity(byte* before, byte* after, int daysValid)
localTime.tm_year += 1900;
localTime.tm_mon += 1;
SetTime(&localTime, after);
format = ValidityTimeFormat(&localTime);
timeSz = (format == ASN_UTC_TIME) ? ASN_UTC_TIME_SIZE - 1
: ASN_GEN_TIME_SZ;
after[0] = format;
SetLength(timeSz, after + 1);
SetTime(&localTime, after + 2, format);
}
return ret;
@ -30811,6 +30840,8 @@ static int MakeAnyCert(Cert* cert, byte* derBuffer, word32 derSz,
int ret = 0;
word32 issRawLen = 0;
word32 sbjRawLen = 0;
byte localBefore[MAX_DATE_SIZE];
byte localAfter[MAX_DATE_SIZE];
/* Unused without PQC */
(void)falconKey;
@ -31036,16 +31067,35 @@ static int MakeAnyCert(Cert* cert, byte* derBuffer, word32 derSz,
}
else
{
/* Don't put out UTC before data. */
dataASN[X509CERTASN_IDX_TBS_VALIDITY_NOTB_UTC].noOut = 1;
/* Make space for before date data. */
SetASN_Buffer(&dataASN[X509CERTASN_IDX_TBS_VALIDITY_NOTB_GT],
NULL, ASN_GEN_TIME_SZ);
/* Don't put out UTC after data. */
dataASN[X509CERTASN_IDX_TBS_VALIDITY_NOTA_UTC].noOut = 1;
/* Make space for after date data. */
SetASN_Buffer(&dataASN[X509CERTASN_IDX_TBS_VALIDITY_NOTA_GT],
NULL, ASN_GEN_TIME_SZ);
/* Compute default validity dates; SetValidity picks UTCTime or
* Generalized Time per RFC 5280 based on the year. */
ret = SetValidity(localBefore, localAfter, cert->daysValid);
if (ret == 0) {
if (localBefore[0] == ASN_UTC_TIME) {
SetASN_Buffer(
&dataASN[X509CERTASN_IDX_TBS_VALIDITY_NOTB_UTC],
localBefore + 2, ASN_UTC_TIME_SIZE - 1);
dataASN[X509CERTASN_IDX_TBS_VALIDITY_NOTB_GT].noOut = 1;
}
else {
dataASN[X509CERTASN_IDX_TBS_VALIDITY_NOTB_UTC].noOut = 1;
SetASN_Buffer(
&dataASN[X509CERTASN_IDX_TBS_VALIDITY_NOTB_GT],
localBefore + 2, ASN_GEN_TIME_SZ);
}
if (localAfter[0] == ASN_UTC_TIME) {
SetASN_Buffer(
&dataASN[X509CERTASN_IDX_TBS_VALIDITY_NOTA_UTC],
localAfter + 2, ASN_UTC_TIME_SIZE - 1);
dataASN[X509CERTASN_IDX_TBS_VALIDITY_NOTA_GT].noOut = 1;
}
else {
dataASN[X509CERTASN_IDX_TBS_VALIDITY_NOTA_UTC].noOut = 1;
SetASN_Buffer(
&dataASN[X509CERTASN_IDX_TBS_VALIDITY_NOTA_GT],
localAfter + 2, ASN_GEN_TIME_SZ);
}
}
}
if (sbjRawLen > 0) {
/* Put in encoded subject name. */
@ -31083,7 +31133,9 @@ static int MakeAnyCert(Cert* cert, byte* derBuffer, word32 derSz,
X509CERTASN_IDX_SIGNATURE);
/* Calculate encoded certificate body size. */
ret = SizeASN_Items(x509CertASN, dataASN, x509CertASN_Length, &sz);
if (ret >= 0) {
ret = SizeASN_Items(x509CertASN, dataASN, x509CertASN_Length, &sz);
}
}
/* Check buffer is big enough for encoded data. */
if ((ret == 0) && (sz > derSz)) {
@ -31114,18 +31166,6 @@ static int MakeAnyCert(Cert* cert, byte* derBuffer, word32 derSz,
dataASN[X509CERTASN_IDX_TBS_SUBJECT_SEQ].data.buffer.length,
&cert->subject, cert->heap);
}
if (ret >= 0) {
if (cert->beforeDateSz == 0 || cert->afterDateSz == 0)
{
/* Encode validity into buffer. */
/* safe casts -- the pointers are actually inside derBuffer. */
ret = SetValidity(
(byte*)(wc_ptr_t)dataASN[X509CERTASN_IDX_TBS_VALIDITY_NOTB_GT]
.data.buffer.data,
(byte*)(wc_ptr_t)dataASN[X509CERTASN_IDX_TBS_VALIDITY_NOTA_GT]
.data.buffer.data, cert->daysValid);
}
}
if (ret >= 0) {
/* Encode public key into buffer. */
/* safe cast -- the pointer is actually inside derBuffer. */

View File

@ -5911,10 +5911,6 @@ int SetNameEx(byte* output, word32 outputSz, CertName* name, void* heap)
/* Set Date validity from now until now + daysValid
* return size in bytes written to output, 0 on error */
/* TODO https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.5
* "MUST always encode certificate validity dates through the year 2049 as
* UTCTime; certificate validity dates in 2050 or later MUST be encoded as
* GeneralizedTime." */
static int SetValidity(byte* output, int daysValid)
{
#ifndef NO_ASN_TIME
@ -5922,6 +5918,8 @@ static int SetValidity(byte* output, int daysValid)
byte after[MAX_DATE_SIZE];
word32 beforeSz, afterSz, seqSz;
word32 timeSz;
byte format;
time_t now;
time_t then;
@ -5941,9 +5939,6 @@ static int SetValidity(byte* output, int daysValid)
now = wc_Time(0);
/* before now */
before[0] = ASN_GENERALIZED_TIME;
beforeSz = SetLength(ASN_GEN_TIME_SZ, before + 1) + 1; /* gen tag */
/* subtract 1 day of seconds for more compliance */
then = now - 86400;
expandedTime = XGMTIME(&then, tmpTime);
@ -5957,11 +5952,13 @@ static int SetValidity(byte* output, int daysValid)
localTime.tm_year += 1900;
localTime.tm_mon += 1;
SetTime(&localTime, before + beforeSz);
beforeSz += ASN_GEN_TIME_SZ;
after[0] = ASN_GENERALIZED_TIME;
afterSz = SetLength(ASN_GEN_TIME_SZ, after + 1) + 1; /* gen tag */
format = ValidityTimeFormat(&localTime);
timeSz = (format == ASN_UTC_TIME) ? ASN_UTC_TIME_SIZE - 1
: ASN_GEN_TIME_SZ;
before[0] = format;
beforeSz = SetLength(timeSz, before + 1) + 1;
SetTime(&localTime, before + beforeSz, format);
beforeSz += timeSz;
/* add daysValid of seconds */
then = now + (daysValid * (time_t)86400);
@ -5976,8 +5973,13 @@ static int SetValidity(byte* output, int daysValid)
localTime.tm_year += 1900;
localTime.tm_mon += 1;
SetTime(&localTime, after + afterSz);
afterSz += ASN_GEN_TIME_SZ;
format = ValidityTimeFormat(&localTime);
timeSz = (format == ASN_UTC_TIME) ? ASN_UTC_TIME_SIZE - 1
: ASN_GEN_TIME_SZ;
after[0] = format;
afterSz = SetLength(timeSz, after + 1) + 1;
SetTime(&localTime, after + afterSz, format);
afterSz += timeSz;
/* headers and output */
seqSz = SetSequence(beforeSz + afterSz, output);

View File

@ -30821,6 +30821,120 @@ exit_rsa_even_mod:
#endif /* WOLFSSL_HAVE_SP_RSA */
#if defined(WOLFSSL_CERT_GEN) && !defined(NO_ASN_TIME) && !defined(WOLFSSL_NO_MALLOC)
#ifdef WOLFSSL_TEST_CERT
/* RFC 5280: validity dates through 2049 encode as UTCTime, 2050 and later as
* GeneralizedTime. Returns 0 when the decoded date TLV honors that rule. */
static int cert_time_format_ok(const byte* dateTLV)
{
int year;
if (dateTLV == NULL)
return -1;
if (dateTLV[0] == ASN_UTC_TIME)
return 0;
if (dateTLV[0] != ASN_GENERALIZED_TIME)
return -1;
year = (dateTLV[2] - '0') * 1000 + (dateTLV[3] - '0') * 100 +
(dateTLV[4] - '0') * 10 + (dateTLV[5] - '0');
return ((year < 1950) || (year >= 2050)) ? 0 : -1;
}
/* Decode the calendar year from an ASN.1 date TLV, inverting the two-digit
* UTCTime year, so a wrong encoded year is caught. Returns -1 on error. */
static int cert_date_year(const byte* dateTLV)
{
int yy;
if (dateTLV == NULL)
return -1;
if (dateTLV[0] == ASN_UTC_TIME) {
yy = (dateTLV[2] - '0') * 10 + (dateTLV[3] - '0');
return (yy < 50) ? 2000 + yy : 1900 + yy;
}
if (dateTLV[0] == ASN_GENERALIZED_TIME) {
return (dateTLV[2] - '0') * 1000 + (dateTLV[3] - '0') * 100 +
(dateTLV[4] - '0') * 10 + (dateTLV[5] - '0');
}
return -1;
}
static time_t certGenBoundaryTime;
static time_t cert_gen_boundary_time_cb(time_t* t)
{
if (t != NULL)
*t = certGenBoundaryTime;
return certGenBoundaryTime;
}
/* Deterministically exercise the RFC 5280 UTCTime/GeneralizedTime boundary by
* forcing the clock to fixed mid-2049 and mid-2050 dates. Those epochs need a
* 64-bit time_t, so 32-bit-time_t builds skip the check. */
static wc_test_ret_t cert_gen_time_boundary_test(Cert* cert, byte* der,
RsaKey* key, WC_RNG* rng, DecodedCert* decode)
{
static const struct {
time_t now;
int daysValid;
byte beforeTag;
byte afterTag;
int beforeYear;
int afterYear;
} cases[] = {
{ (time_t)2508710400UL, 100, ASN_UTC_TIME, ASN_UTC_TIME,
2049, 2049 },
{ (time_t)2540246400UL, 100, ASN_GENERALIZED_TIME, ASN_GENERALIZED_TIME,
2050, 2050 },
{ (time_t)2508710400UL, 400, ASN_UTC_TIME, ASN_GENERALIZED_TIME,
2049, 2050 }
};
wc_test_ret_t ret = 0;
int tSz = (int)sizeof(time_t);
int tSigned = ((time_t)-1 < 0);
int certSz;
int i;
/* The mid-2050 epoch needs a time_t that reaches past 2038: 64-bit, or
* unsigned 32-bit (good to 2106). Signed 32-bit cannot, so skip it. */
if ((tSz < 8) && tSigned)
return 0;
for (i = 0; i < (int)(sizeof(cases) / sizeof(cases[0])); i++) {
certGenBoundaryTime = cases[i].now;
ret = wc_SetTimeCb(cert_gen_boundary_time_cb);
if (ret != 0)
return WC_TEST_RET_ENC_EC(ret);
cert->daysValid = cases[i].daysValid;
ret = 0;
WC_TEST_RSA_ASYNC_DO(&key->asyncDev,
wc_MakeSelfCert(cert, der, FOURK_BUF, key, rng));
(void)wc_SetTimeCb(NULL);
if (ret < 0)
return WC_TEST_RET_ENC_EC(ret);
certSz = (int)ret;
InitDecodedCert(decode, der, certSz, HEAP_HINT);
ret = ParseCert(decode, CERT_TYPE, NO_VERIFY, 0);
if (ret == 0) {
if ((decode->beforeDate == NULL) || (decode->afterDate == NULL) ||
(decode->beforeDate[0] != cases[i].beforeTag) ||
(decode->afterDate[0] != cases[i].afterTag) ||
(cert_date_year(decode->beforeDate) != cases[i].beforeYear) ||
(cert_date_year(decode->afterDate) != cases[i].afterYear)) {
ret = WC_TEST_RET_ENC_NC;
}
}
else {
ret = WC_TEST_RET_ENC_EC(ret);
}
FreeDecodedCert(decode);
if (ret != 0)
return ret;
}
return 0;
}
#endif
static wc_test_ret_t rsa_certgen_test(RsaKey* key, RsaKey* keypub, WC_RNG* rng, byte* tmp)
{
#if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_NO_MALLOC)
@ -30947,6 +31061,12 @@ static wc_test_ret_t rsa_certgen_test(RsaKey* key, RsaKey* keypub, WC_RNG* rng,
FreeDecodedCert(decode);
ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_rsa);
}
/* Verify the generated validity dates use the RFC 5280 time format. */
if ((cert_time_format_ok(decode->beforeDate) != 0) ||
(cert_time_format_ok(decode->afterDate) != 0)) {
FreeDecodedCert(decode);
ERROR_OUT(WC_TEST_RET_ENC_NC, exit_rsa);
}
FreeDecodedCert(decode);
#endif
@ -30956,6 +31076,13 @@ static wc_test_ret_t rsa_certgen_test(RsaKey* key, RsaKey* keypub, WC_RNG* rng,
goto exit_rsa;
}
#ifdef WOLFSSL_TEST_CERT
ret = cert_gen_time_boundary_test(myCert, der, key, rng, decode);
if (ret != 0) {
goto exit_rsa;
}
#endif
/* Setup Certificate */
ret = wc_InitCert_ex(myCert, HEAP_HINT, devId);
if (ret < 0) {