Add test cases

This commit is contained in:
Paul Adelsbach
2026-03-30 11:31:31 -07:00
parent 452652bbce
commit ec9b6cf469
6 changed files with 333 additions and 99 deletions
+1 -1
View File
@@ -76,7 +76,7 @@ jobs:
-DWOLFSSL_EXTRA_PQC_HYBRIDS:BOOL=yes -DWOLFSSL_TLS_NO_MLKEM_STANDALONE:BOOL=no \
..
cmake --build .
ctest -j $(nproc)
ctest -j $(nproc) --output-on-failure
cmake --install .
# clean up
+1 -1
View File
@@ -13751,7 +13751,7 @@ static void CopyDateToASN1_TIME(const byte* srcDate, int srcDateLen,
{
if (srcDateLen >= 2) {
/* Clamp the date length to the maximum allowed size.
* This needs to match the size of WOLFSSL_ASN1_TIME minus the
* This needs to match the size of WOLFSSL_ASN1_TIME minus the
* the type and length fields. */
const int maxSz = CTC_DATE_SIZE - 2;
const int copySz = (int)min(srcDate[1], maxSz);
+128 -92
View File
@@ -663,67 +663,7 @@ int test_wolfSSL_X509_set_name(void)
return EXPECT_RESULT();
}
int test_wolfSSL_X509_set_notAfter(void)
{
EXPECT_DECLS;
#if (defined(OPENSSL_ALL) || defined(WOLFSSL_APACHE_HTTPD)) \
&& !defined(NO_ASN_TIME) && !defined(USER_TIME) && \
!defined(TIME_OVERRIDES) && !defined(NO_CERTS) && \
defined(WOLFSSL_CERT_GEN) && defined(WOLFSSL_CERT_REQ) &&\
!defined(TIME_T_NOT_64BIT) && !defined(NO_64BIT) && !defined(NO_BIO)
/* Generalized time will overflow time_t if not long */
X509* x = NULL;
BIO* bio = NULL;
ASN1_TIME *asn_time = NULL;
ASN1_TIME *time_check = NULL;
const int year = 365*24*60*60;
const int day = 24*60*60;
const int hour = 60*60;
const int mini = 60;
int offset_day;
unsigned char buf[25];
time_t t;
/*
* Setup asn_time. APACHE HTTPD uses time(NULL)
*/
t = (time_t)107 * year + 31 * day + 34 * hour + 30 * mini + 7 * day;
offset_day = 7;
/*
* Free these.
*/
asn_time = wolfSSL_ASN1_TIME_adj(NULL, t, offset_day, 0);
ExpectNotNull(asn_time);
ExpectNotNull(x = X509_new());
ExpectNotNull(bio = BIO_new(BIO_s_mem()));
/*
* Tests
*/
ExpectTrue(wolfSSL_X509_set_notAfter(x, asn_time));
/* time_check is simply (ANS1_TIME*)x->notAfter */
ExpectNotNull(time_check = X509_get_notAfter(x));
/* ANS1_TIME_check validates by checking if argument can be parsed */
ExpectIntEQ(ASN1_TIME_check(time_check), WOLFSSL_SUCCESS);
/* Convert to human readable format and compare to intended date */
ExpectIntEQ(ASN1_TIME_print(bio, time_check), 1);
ExpectIntEQ(BIO_read(bio, buf, sizeof(buf)), 24);
ExpectIntEQ(XMEMCMP(buf, "Jan 20 10:30:00 2077 GMT", sizeof(buf) - 1), 0);
ExpectFalse(wolfSSL_X509_set_notAfter(NULL, NULL));
ExpectFalse(wolfSSL_X509_set_notAfter(x, NULL));
ExpectFalse(wolfSSL_X509_set_notAfter(NULL, asn_time));
/*
* Cleanup
*/
XFREE(asn_time, NULL, DYNAMIC_TYPE_OPENSSL);
X509_free(x);
BIO_free(bio);
#endif
return EXPECT_RESULT();
}
int test_wolfSSL_X509_set_notBefore(void)
int test_wolfSSL_X509_set_notAfterBefore(void)
{
EXPECT_DECLS;
#if (defined(OPENSSL_ALL) || defined(WOLFSSL_APACHE_HTTPD)) \
@@ -732,55 +672,151 @@ int test_wolfSSL_X509_set_notBefore(void)
defined(WOLFSSL_CERT_GEN) && defined(WOLFSSL_CERT_REQ) && !defined(NO_BIO)
X509* x = NULL;
BIO* bio = NULL;
ASN1_TIME *asn_time = NULL;
ASN1_TIME *time_check = NULL;
const int year = 365*24*60*60;
const int day = 24*60*60;
const int hour = 60*60;
ASN1_TIME* asn_time = NULL;
ASN1_TIME* time_check = NULL;
WOLFSSL_ASN1_TIME crafted_time;
WOLFSSL_ASN1_TIME* retrieved = NULL;
const byte* raw = NULL;
const int year = 365 * 24 * 60 * 60;
const int day = 24 * 60 * 60;
const int hour = 60 * 60;
const int mini = 60;
int offset_day;
unsigned char buf[25];
time_t t;
const unsigned char valid_utc[] = "250101120000Z";
const int valid_utc_len = 13;
int i;
/*
* Setup asn_time. APACHE HTTPD uses time(NULL)
*/
t = (time_t)49 * year + 125 * day + 20 * hour + 30 * mini + 7 * day;
offset_day = 7;
/*
* Free these.
*/
asn_time = wolfSSL_ASN1_TIME_adj(NULL, t, offset_day, 0);
ExpectNotNull(asn_time);
ExpectNotNull(x = X509_new());
ExpectNotNull(bio = BIO_new(BIO_s_mem()));
ExpectIntEQ(ASN1_TIME_check(asn_time), WOLFSSL_SUCCESS);
/*
* Main Tests
*/
/* --- notBefore: set, get, validate, print --- */
{
time_t t = (time_t)49 * year + 125 * day + 20 * hour +
30 * mini + 7 * day;
asn_time = wolfSSL_ASN1_TIME_adj(NULL, t, 7, 0);
}
ExpectNotNull(asn_time);
ExpectIntEQ(ASN1_TIME_check(asn_time), WOLFSSL_SUCCESS);
ExpectTrue(wolfSSL_X509_set_notBefore(x, asn_time));
/* time_check == (ANS1_TIME*)x->notBefore */
ExpectNotNull(time_check = X509_get_notBefore(x));
/* ANS1_TIME_check validates by checking if argument can be parsed */
ExpectIntEQ(ASN1_TIME_check(time_check), WOLFSSL_SUCCESS);
/* Convert to human readable format and compare to intended date */
ExpectIntEQ(ASN1_TIME_print(bio, time_check), 1);
ExpectIntEQ(BIO_read(bio, buf, sizeof(buf)), 24);
ExpectIntEQ(XMEMCMP(buf, "May 8 20:30:00 2019 GMT", sizeof(buf) - 1), 0);
/* wolfSSL_X509_notBefore returns [type][length][data...] */
ExpectNotNull(raw = wolfSSL_X509_notBefore(x));
ExpectIntEQ(raw[0], time_check->type);
ExpectIntEQ(raw[1], time_check->length);
ExpectIntEQ(XMEMCMP(&raw[2], time_check->data, time_check->length), 0);
XFREE(asn_time, NULL, DYNAMIC_TYPE_OPENSSL);
asn_time = NULL;
/* --- notAfter: set, get, validate, print (needs 64-bit time_t) --- */
#if !defined(TIME_T_NOT_64BIT) && !defined(NO_64BIT)
{
time_t t = (time_t)107 * year + 31 * day + 34 * hour +
30 * mini + 7 * day;
asn_time = wolfSSL_ASN1_TIME_adj(NULL, t, 7, 0);
}
ExpectNotNull(asn_time);
ExpectTrue(wolfSSL_X509_set_notAfter(x, asn_time));
ExpectNotNull(time_check = X509_get_notAfter(x));
ExpectIntEQ(ASN1_TIME_check(time_check), WOLFSSL_SUCCESS);
ExpectIntEQ(ASN1_TIME_print(bio, time_check), 1);
ExpectIntEQ(BIO_read(bio, buf, sizeof(buf)), 24);
ExpectIntEQ(XMEMCMP(buf, "Jan 20 10:30:00 2077 GMT", sizeof(buf) - 1), 0);
/* wolfSSL_X509_notAfter returns [type][length][data...] */
ExpectNotNull(raw = wolfSSL_X509_notAfter(x));
ExpectIntEQ(raw[0], time_check->type);
ExpectIntEQ(raw[1], time_check->length);
ExpectIntEQ(XMEMCMP(&raw[2], time_check->data, time_check->length), 0);
XFREE(asn_time, NULL, DYNAMIC_TYPE_OPENSSL);
asn_time = NULL;
#endif
/* --- NULL parameter tests --- */
XMEMSET(&crafted_time, 0, sizeof(crafted_time));
crafted_time.type = ASN_UTC_TIME;
crafted_time.length = valid_utc_len;
XMEMCPY(crafted_time.data, valid_utc, valid_utc_len);
ExpectFalse(wolfSSL_X509_set_notAfter(NULL, NULL));
ExpectFalse(wolfSSL_X509_set_notAfter(x, NULL));
ExpectFalse(wolfSSL_X509_set_notAfter(NULL, &crafted_time));
ExpectFalse(wolfSSL_X509_set_notBefore(NULL, NULL));
ExpectFalse(wolfSSL_X509_set_notBefore(x, NULL));
ExpectFalse(wolfSSL_X509_set_notBefore(NULL, asn_time));
ExpectFalse(wolfSSL_X509_set_notBefore(NULL, &crafted_time));
ExpectNull(X509_get_notBefore(NULL));
ExpectNull(X509_get_notAfter(NULL));
ExpectNull(wolfSSL_X509_notBefore(NULL));
ExpectNull(wolfSSL_X509_notAfter(NULL));
/* --- Malicious length > CTC_DATE_SIZE via set_notAfter ---
* The function blindly propagates t->length into the x509 struct.
* A fixed implementation would reject this or clamp to CTC_DATE_SIZE. */
/* --- Length > CTC_DATE_SIZE is rejected by the bounds check --- */
XMEMSET(&crafted_time, 0, sizeof(crafted_time));
crafted_time.type = ASN_UTC_TIME;
crafted_time.length = 255;
XMEMCPY(crafted_time.data, valid_utc, valid_utc_len);
ExpectIntEQ(wolfSSL_X509_set_notAfter(x, &crafted_time),
WOLFSSL_FAILURE);
crafted_time.length = 128;
ExpectIntEQ(wolfSSL_X509_set_notBefore(x, &crafted_time),
WOLFSSL_FAILURE);
/* --- Negative length is rejected --- */
crafted_time.length = -1;
ExpectIntEQ(wolfSSL_X509_set_notAfter(x, &crafted_time),
WOLFSSL_FAILURE);
/* --- Fixed-size copy leaks sentinel bytes beyond valid length ---
* Even when t->length is correct (13 for UTCTime), XMEMCPY copies
* a full CTC_DATE_SIZE (32) bytes from the source. */
XMEMSET(&crafted_time, 0, sizeof(crafted_time));
crafted_time.type = ASN_UTC_TIME;
crafted_time.length = valid_utc_len;
XMEMCPY(crafted_time.data, valid_utc, valid_utc_len);
for (i = valid_utc_len; i < CTC_DATE_SIZE; i++) {
crafted_time.data[i] = 0xDE;
}
ExpectIntEQ(wolfSSL_X509_set_notAfter(x, &crafted_time), WOLFSSL_SUCCESS);
ExpectNotNull(retrieved = X509_get_notAfter(x));
ExpectBufEQ(retrieved->data, valid_utc, valid_utc_len);
for (i = valid_utc_len; i < CTC_DATE_SIZE; i++) {
ExpectIntEQ(retrieved->data[i], 0xDE);
}
/* --- Boundary: length CTC_DATE_SIZE - 2 (accepted) --- */
XMEMSET(&crafted_time, 0, sizeof(crafted_time));
crafted_time.type = ASN_GENERALIZED_TIME;
crafted_time.length = CTC_DATE_SIZE - 2;
XMEMSET(crafted_time.data, 'A', CTC_DATE_SIZE - 2);
ExpectIntEQ(wolfSSL_X509_set_notAfter(x, &crafted_time),
WOLFSSL_SUCCESS);
ExpectNotNull(retrieved = X509_get_notAfter(x));
ExpectIntEQ(retrieved->length, CTC_DATE_SIZE - 2);
/* wolfSSL_X509_notAfter must also succeed at this boundary */
ExpectNotNull(raw = wolfSSL_X509_notAfter(x));
/* --- Boundary: length CTC_DATE_SIZE - 1 (rejected) --- */
crafted_time.length = CTC_DATE_SIZE - 1;
ExpectIntEQ(wolfSSL_X509_set_notAfter(x, &crafted_time),
WOLFSSL_FAILURE);
/* --- Boundary: length CTC_DATE_SIZE (rejected) --- */
crafted_time.length = CTC_DATE_SIZE;
ExpectIntEQ(wolfSSL_X509_set_notAfter(x, &crafted_time),
WOLFSSL_FAILURE);
/*
* Cleanup
*/
XFREE(asn_time, NULL, DYNAMIC_TYPE_OPENSSL);
X509_free(x);
BIO_free(bio);
#endif
+2 -4
View File
@@ -37,8 +37,7 @@ int test_wolfSSL_X509_check_email(void);
int test_wolfSSL_X509(void);
int test_wolfSSL_X509_get0_tbs_sigalg(void);
int test_wolfSSL_X509_set_name(void);
int test_wolfSSL_X509_set_notAfter(void);
int test_wolfSSL_X509_set_notBefore(void);
int test_wolfSSL_X509_set_notAfterBefore(void);
int test_wolfSSL_X509_set_version(void);
int test_wolfSSL_X509_get_serialNumber(void);
int test_wolfSSL_get_tbs(void);
@@ -69,8 +68,7 @@ int test_wolfSSL_X509_cmp(void);
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509), \
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_get0_tbs_sigalg), \
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_set_name), \
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_set_notAfter), \
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_set_notBefore), \
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_set_notAfterBefore), \
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_set_version), \
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_get_serialNumber), \
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_get_tbs), \
+198
View File
@@ -434,3 +434,201 @@ int test_x509_set_serialNumber(void)
return TEST_SKIPPED;
#endif /* OPENSSL_EXTRA || OPENSSL_EXTRA_X509_SMALL */
}
/*
* Test: CopyDateToASN1_TIME clamps attacker-controlled time field length.
*
* Attack chain:
* 1. Attacker crafts a DER certificate with notBefore UTCTime length byte
* set to 0x1F (31) instead of 0x0D (13). The first 13 bytes are a valid
* "YYMMDDHHMMSSZ" string (passes ExtractDate 'Z'-at-position-12 check),
* followed by 18 sentinel bytes (0xDE). Parent SEQUENCE lengths are
* adjusted so the DER is structurally valid.
* 2. The malicious cert is presented as the server cert in a TLS handshake
* (via memio -- no sockets needed).
* 3. The client parses the cert. CopyDateToASN1_TIME() in internal.c must
* clamp the length to CTC_DATE_SIZE - 2 (30) so that downstream code
* in wolfSSL_X509_notBefore() can safely prepend type+length at offset
* 0-1 of the 32-byte notBeforeData without overflowing.
*
* The test verifies that notBefore.length <= CTC_DATE_SIZE - 2 (30),
* regardless of the attacker's wire value (31).
*/
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
(defined(OPENSSL_EXTRA) || defined(OPENSSL_ALL)) && \
!defined(NO_RSA) && !defined(NO_WOLFSSL_CLIENT) && \
!defined(NO_WOLFSSL_SERVER)
/* Verify callback that accepts all certificates regardless of errors. */
static int accept_all_verify_cb(int preverify, WOLFSSL_X509_STORE_CTX* store)
{
(void)preverify;
(void)store;
return 1;
}
/*
* Craft a malicious DER certificate by inflating the notBefore UTCTime length.
*
* Scans for the Validity SEQUENCE (pattern: 0x30 XX 0x17 0x0D), inflates the
* notBefore length by 'inflate' bytes, inserts sentinel bytes (0xDE), and
* adjusts all parent SEQUENCE lengths.
*
* out: caller-supplied buffer, must be at least origSz + inflate bytes.
* outSz: set to the new cert size on success.
* Returns 0 on success, -1 on failure.
*/
static int craft_malicious_time_cert(const byte* orig, int origSz,
byte* out, int* outSz, int inflate)
{
int i;
int validityOff = -1;
int notBeforeLenOff; /* offset of the notBefore length byte */
int notBeforeDataEnd; /* offset just past the 13-byte time data */
word16 seqLen;
/* Scan for Validity SEQUENCE: 0x30 XX 0x17 0x0D */
for (i = 0; i < origSz - 3; i++) {
if (orig[i] == 0x30 && orig[i + 2] == 0x17 && orig[i + 3] == 0x0D) {
validityOff = i;
break;
}
}
if (validityOff < 0) {
return -1;
}
notBeforeLenOff = validityOff + 3; /* the 0x0D byte */
notBeforeDataEnd = notBeforeLenOff + 1 + 13; /* tag(1) was at +2, data starts at +4 */
/* Build the new buffer:
* [0 .. notBeforeLenOff-1] unchanged prefix
* [notBeforeLenOff] inflated length byte
* [notBeforeLenOff+1 .. notBeforeDataEnd-1] original 13 time bytes
* <insert 'inflate' sentinel bytes here>
* [notBeforeDataEnd .. origSz-1] remainder of cert
*/
/* Copy prefix including the length byte position */
XMEMCPY(out, orig, notBeforeDataEnd);
/* Patch the notBefore UTCTime length byte */
out[notBeforeLenOff] = (byte)(0x0D + inflate);
/* Insert sentinel bytes */
XMEMSET(out + notBeforeDataEnd, 0xDE, inflate);
/* Copy the rest of the cert (notAfter field onward) */
XMEMCPY(out + notBeforeDataEnd + inflate,
orig + notBeforeDataEnd,
origSz - notBeforeDataEnd);
/* Fix Validity SEQUENCE length (single-byte encoding at validityOff+1) */
out[validityOff + 1] = (byte)(orig[validityOff + 1] + inflate);
/* Fix TBSCertificate SEQUENCE length (2-byte big-endian at offset 6-7,
* format: 30 82 XX XX) */
seqLen = ((word16)orig[6] << 8) | orig[7];
seqLen += (word16)inflate;
out[6] = (byte)(seqLen >> 8);
out[7] = (byte)(seqLen & 0xFF);
/* Fix Certificate SEQUENCE length (2-byte big-endian at offset 2-3,
* format: 30 82 XX XX) */
seqLen = ((word16)orig[2] << 8) | orig[3];
seqLen += (word16)inflate;
out[2] = (byte)(seqLen >> 8);
out[3] = (byte)(seqLen & 0xFF);
*outSz = origSz + inflate;
return 0;
}
#endif /* HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES && ... */
int test_x509_time_field_overread_via_tls(void)
{
EXPECT_DECLS;
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
(defined(OPENSSL_EXTRA) || defined(OPENSSL_ALL)) && \
!defined(NO_RSA) && !defined(NO_WOLFSSL_CLIENT) && \
!defined(NO_WOLFSSL_SERVER)
struct test_memio_ctx test_ctx;
WOLFSSL_CTX* ctx_c = NULL;
WOLFSSL_CTX* ctx_s = NULL;
WOLFSSL* ssl_c = NULL;
WOLFSSL* ssl_s = NULL;
WOLFSSL_X509* peer = NULL;
WOLFSSL_ASN1_TIME* notBefore = NULL;
/*
* Inflate notBefore length by 18 bytes: 13 + 18 = 31.
* CopyDecodedToX509() sets notBefore.length = min(31, MAX_DATE_SZ) = 31
* because it trusts the raw ASN.1 length byte from the wire.
* A valid UTCTime is only 13 bytes.
*/
const int INFLATE = 18;
byte malicious_der[sizeof_server_cert_der_2048 + 18];
int malicious_der_sz = 0;
/* --- Step 1: Craft malicious certificate --- */
ExpectIntEQ(craft_malicious_time_cert(
server_cert_der_2048, (int)sizeof_server_cert_der_2048,
malicious_der, &malicious_der_sz, INFLATE), 0);
ExpectIntEQ(malicious_der_sz,
(int)sizeof_server_cert_der_2048 + INFLATE);
/* --- Step 2: Set up TLS via memio --- */
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
ExpectIntEQ(test_memio_setup_ex(&test_ctx, &ctx_c, &ctx_s,
&ssl_c, &ssl_s,
wolfTLSv1_2_client_method, wolfTLSv1_2_server_method,
(byte*)ca_cert_der_2048, (int)sizeof_ca_cert_der_2048,
malicious_der, malicious_der_sz,
(byte*)server_key_der_2048, (int)sizeof_server_key_der_2048), 0);
/* Client verify callback accepts all errors (signature is broken
* because we modified the TBSCertificate without re-signing).
* Must be set on ssl_c (not ctx_c) because the SSL object was already
* created from ctx_c inside test_memio_setup_ex(). */
if (ssl_c != NULL) {
wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_PEER,
accept_all_verify_cb);
}
/* --- Step 3: Perform TLS handshake --- */
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
/* --- Step 4: Verify CopyDecodedToX509 does not trust wire length --- */
#ifdef KEEP_PEER_CERT
ExpectNotNull(peer = wolfSSL_get_peer_certificate(ssl_c));
/*
* X509_get_notBefore returns &x509->notBefore directly (no copy).
* CopyDecodedToX509() set notBefore.length = min(wireLength, 32) = 31
* because it trusts the raw ASN.1 length byte from the attacker's cert.
*
* The data buffer is CTC_DATE_SIZE (32) bytes, and the notBeforeData
* encoding prepends type+length at offset 0-1, leaving 30 bytes for
* content. So the maximum safe length is CTC_DATE_SIZE - 2 = 30.
*
* This assertion FAILS on the buggy code (length > 30) and will PASS
* once CopyDateToASN1_TIME clamps to the buffer capacity.
*/
if (peer != NULL) {
notBefore = wolfSSL_X509_get_notBefore(peer);
}
ExpectNotNull(notBefore);
ExpectIntLE(notBefore->length, CTC_DATE_SIZE - 2); /* max: 30 */
wolfSSL_X509_free(peer);
#endif /* KEEP_PEER_CERT */
wolfSSL_free(ssl_s);
wolfSSL_free(ssl_c);
wolfSSL_CTX_free(ctx_s);
wolfSSL_CTX_free(ctx_c);
#endif /* compile guards */
return EXPECT_RESULT();
}
+3 -1
View File
@@ -26,11 +26,13 @@ int test_x509_rfc2818_verification_callback(void);
int test_x509_GetCAByAKID(void);
int test_x509_set_serialNumber(void);
int test_x509_verify_cert_hostname_check(void);
int test_x509_time_field_overread_via_tls(void);
#define TEST_X509_DECLS \
TEST_DECL_GROUP("x509", test_x509_rfc2818_verification_callback), \
TEST_DECL_GROUP("x509", test_x509_GetCAByAKID), \
TEST_DECL_GROUP("x509", test_x509_set_serialNumber), \
TEST_DECL_GROUP("x509", test_x509_verify_cert_hostname_check)
TEST_DECL_GROUP("x509", test_x509_verify_cert_hostname_check), \
TEST_DECL_GROUP("x509", test_x509_time_field_overread_via_tls)
#endif /* WOLFCRYPT_TEST_X509_H */