Files
wolfssl/tests/api/test_x509.c
T
Sean Parkinson 26a2b793dc Regression testing fixes
1. Side-aware ML-KEM in TLS (tls.c, tls13.c, ssl.c, internal.h):
TLSX_IsGroupSupported/TLSX_UseSupportedCurve take a `side` arg; new
TLSX_IsMlKemGroupSupported + client/server support macros. A build only
capable of one ML-KEM op no longer advertises groups it can't use for
its role.

2. NO_ASN_TIME support (ssl_asn1.c, ssl.h, settings.h): data-only
ASN1_TIME APIs now compile without system time; OCSP responder
auto-disabled under NO_ASN_TIME.

3. SP ECC (sp_*.c, sp_x86_64_asm.asm): curve `b` constants and
sp_ecc_is_point_* always compiled (point-check available in more
configs); asm movsxd -> movsx.

4. configure.ac: BUILD_MEMUSE fixed to trigger on != "xno".

5. Test fixes: HRR-aware TLS 1.3 memio tests (new
test_memio_msg_is_hello_retry_request); tightened build guards
(Ed25519/Ed448 key-import, AES decrypt, XMSS heights, SP sizes,
static-PSK).
2026-06-04 18:29:24 +10:00

1064 lines
39 KiB
C

/* test_x509.c
*
* Copyright (C) 2006-2026 wolfSSL Inc.
*
* This file is part of wolfSSL.
*
* wolfSSL is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* wolfSSL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
*/
#include <tests/unit.h>
#ifdef NO_INLINE
#include <wolfssl/wolfcrypt/misc.h>
#else
#define WOLFSSL_MISC_INCLUDED
#include <wolfcrypt/src/misc.c>
#endif
#include <wolfssl/wolfcrypt/types.h>
#include <tests/api/api.h>
#include <tests/api/test_x509.h>
#include <tests/utils.h>
#include <wolfssl/openssl/ssl.h>
#include <wolfssl/openssl/x509.h>
#include <wolfssl/openssl/x509v3.h>
#include <wolfssl/internal.h>
#include <wolfssl/wolfcrypt/asn.h>
#include <wolfssl/wolfcrypt/asn_public.h>
#if defined(OPENSSL_ALL) && \
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES)
#define HAVE_TEST_X509_RFC2818_VERIFICATION_CALLBACK
/* callback taken and simplified from
* include/boost/asio/ssl/impl/rfc2818_verification.ipp
* version: boost-1.84.0 */
static int rfc2818_verification_callback(int preverify,
WOLFSSL_X509_STORE_CTX* store)
{
EXPECT_DECLS;
int depth;
X509* cert;
GENERAL_NAMES* gens;
byte address_bytes[] = { 127, 0, 0, 1 };
X509_NAME* name;
int i;
ASN1_STRING* common_name = 0;
int matches = 0;
/* Don't bother looking at certificates that have
* failed pre-verification. */
if (!preverify)
return 0;
/* We're only interested in checking the certificate at
* the end of the chain. */
depth = X509_STORE_CTX_get_error_depth(store);
if (depth > 0)
return 1;
/* Try converting the host name to an address. If it is an address then we
* need to look for an IP address in the certificate rather than a
* host name. */
cert = X509_STORE_CTX_get_current_cert(store);
/* Go through the alternate names in the certificate looking for matching
* DNS or IP address entries. */
gens = (GENERAL_NAMES*)X509_get_ext_d2i(
cert, NID_subject_alt_name, NULL, NULL);
for (i = 0; i < sk_GENERAL_NAME_num(gens); ++i) {
GENERAL_NAME* gen = sk_GENERAL_NAME_value(gens, i);
if (gen->type == GEN_DNS) {
ASN1_IA5STRING* domain = gen->d.dNSName;
if (domain->type == V_ASN1_IA5STRING && domain->data &&
domain->length &&
XSTRCMP(domain->data, "example.com") == 0)
matches++;
}
else if (gen->type == GEN_IPADD)
{
ASN1_OCTET_STRING* ip_address = gen->d.iPAddress;
if (ip_address->type == V_ASN1_OCTET_STRING && ip_address->data &&
ip_address->length == sizeof(address_bytes) &&
XMEMCMP(address_bytes, ip_address->data, 4) == 0)
matches++;
}
}
GENERAL_NAMES_free(gens);
/* No match in the alternate names, so try the common names. We should only
* use the "most specific" common name, which is the last one in
* the list. */
name = X509_get_subject_name(cert);
i = -1;
while ((i = X509_NAME_get_index_by_NID(name, NID_commonName, i)) >= 0)
{
X509_NAME_ENTRY* name_entry = X509_NAME_get_entry(name, i);
common_name = X509_NAME_ENTRY_get_data(name_entry);
}
if (common_name && common_name->data && common_name->length)
{
if (XSTRCMP(common_name->data, "www.wolfssl.com") == 0)
matches++;
}
ExpectIntEQ(matches, 3);
return matches == 3;
}
#endif
int test_x509_rfc2818_verification_callback(void)
{
EXPECT_DECLS;
#ifdef HAVE_TEST_X509_RFC2818_VERIFICATION_CALLBACK
struct test_memio_ctx test_ctx;
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfTLS_client_method, wolfTLS_server_method), 0);
ExpectIntEQ(wolfSSL_use_certificate_file(ssl_c, cliCertFile,
WOLFSSL_FILETYPE_PEM), 1);
ExpectIntEQ(wolfSSL_use_PrivateKey_file(ssl_c, cliKeyFile,
WOLFSSL_FILETYPE_PEM), 1);
ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile, NULL), 1);
wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER,
rfc2818_verification_callback);
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
wolfSSL_free(ssl_s);
wolfSSL_free(ssl_c);
wolfSSL_CTX_free(ctx_s);
wolfSSL_CTX_free(ctx_c);
#endif
return EXPECT_RESULT();
}
/* Basic unit coverage for GetCAByAKID.
*
* These tests construct a minimal WOLFSSL_CERT_MANAGER and Signer objects in
* memory and then call GetCAByAKID directly, verifying that:
* - a NULL or incomplete input returns NULL,
* - a matching issuer/serial pair returns the expected Signer, and
* - a non-matching pair returns NULL.
*
* These tests are intended to check the behaviour of the lookup logic itself;
* they do not exercise certificate parsing or real CA loading.
*/
int test_x509_GetCAByAKID(void)
{
EXPECT_DECLS;
#ifdef WOLFSSL_AKID_NAME
WOLFSSL_CERT_MANAGER cm;
Signer signerA;
Signer signerB;
Signer* found;
byte issuerBuf[] = { 0x01, 0x02, 0x03, 0x04 };
byte serialBuf[] = { 0x0a, 0x0b, 0x0c, 0x0d };
byte wrongSerial[] = { 0x07, 0x07, 0x07, 0x07 };
byte issuerHash[SIGNER_DIGEST_SIZE];
byte serialHash[SIGNER_DIGEST_SIZE];
word32 row;
XMEMSET(&cm, 0, sizeof(cm));
XMEMSET(&signerA, 0, sizeof(signerA));
XMEMSET(&signerB, 0, sizeof(signerB));
XMEMSET(issuerHash, 0, sizeof(issuerHash));
XMEMSET(serialHash, 0, sizeof(serialHash));
/* Initialize CA mutex so GetCAByAKID can lock/unlock it. */
ExpectIntEQ(wc_InitMutex(&cm.caLock), 0);
/* Place both signers into the same CA table bucket. */
row = 0;
cm.caTable[row] = &signerA;
signerA.next = &signerB;
signerB.next = NULL;
/* Pre-compute the expected name and serial hashes using the same helper
* that GetCAByAKID uses internally. */
ExpectIntEQ(CalcHashId(issuerBuf, sizeof(issuerBuf), issuerHash), 0);
ExpectIntEQ(CalcHashId(serialBuf, sizeof(serialBuf), serialHash), 0);
/* Configure signerA as the matching signer. */
XMEMCPY(signerA.issuerNameHash, issuerHash, SIGNER_DIGEST_SIZE);
XMEMCPY(signerA.serialHash, serialHash, SIGNER_DIGEST_SIZE);
/* Configure signerB with different hashes so it should not match. */
XMEMSET(signerB.issuerNameHash, 0x11, SIGNER_DIGEST_SIZE);
XMEMSET(signerB.serialHash, 0x22, SIGNER_DIGEST_SIZE);
/* 1) NULL manager should yield NULL. */
found = GetCAByAKID(NULL, issuerBuf, (word32)sizeof(issuerBuf),
serialBuf, (word32)sizeof(serialBuf));
ExpectNull(found);
/* 2) NULL issuer should yield NULL. */
found = GetCAByAKID(&cm, NULL, (word32)sizeof(issuerBuf),
serialBuf, (word32)sizeof(serialBuf));
ExpectNull(found);
/* 3) NULL serial should yield NULL. */
found = GetCAByAKID(&cm, issuerBuf, (word32)sizeof(issuerBuf),
NULL, (word32)sizeof(serialBuf));
ExpectNull(found);
/* 4) Zero-length issuer/serial should yield NULL. */
found = GetCAByAKID(&cm, issuerBuf, 0, serialBuf, (word32)sizeof(serialBuf));
ExpectNull(found);
found = GetCAByAKID(&cm, issuerBuf, (word32)sizeof(issuerBuf),
serialBuf, 0);
ExpectNull(found);
/* 5) Non-matching serial should yield NULL. */
found = GetCAByAKID(&cm, issuerBuf, (word32)sizeof(issuerBuf),
wrongSerial, (word32)sizeof(wrongSerial));
ExpectNull(found);
/* 6) Matching issuer/serial should return signerA. */
found = GetCAByAKID(&cm, issuerBuf, (word32)sizeof(issuerBuf),
serialBuf, (word32)sizeof(serialBuf));
ExpectPtrEq(found, &signerA);
wc_FreeMutex(&cm.caLock);
#endif /* WOLFSSL_AKID_NAME */
return EXPECT_RESULT();
}
/* Regression test: wolfSSL_X509_verify_cert() must honour the hostname set via
* X509_VERIFY_PARAM_set1_host(). Before the fix the hostname was stored in
* ctx->param->hostName but never consulted, so any chain-valid certificate
* would pass regardless of hostname mismatch (RFC 6125 sec. 6.4.1 violation).
*
* Uses existing PEM fixtures:
* svrCertFile - CN=www.wolfssl.com, SAN DNS=example.com, SAN IP=127.0.0.1
* caCertFile - CA that signed svrCertFile
*/
int test_x509_verify_cert_hostname_check(void)
{
EXPECT_DECLS;
#if defined(OPENSSL_EXTRA) && !defined(NO_FILESYSTEM) && !defined(NO_RSA)
WOLFSSL_X509_STORE* store = NULL;
WOLFSSL_X509_STORE_CTX* ctx = NULL;
WOLFSSL_X509* ca = NULL;
WOLFSSL_X509* leaf = NULL;
WOLFSSL_X509_VERIFY_PARAM* param = NULL;
ExpectNotNull(store = wolfSSL_X509_STORE_new());
ExpectNotNull(ca = wolfSSL_X509_load_certificate_file(caCertFile,
SSL_FILETYPE_PEM));
ExpectIntEQ(wolfSSL_X509_STORE_add_cert(store, ca), WOLFSSL_SUCCESS);
ExpectNotNull(leaf = wolfSSL_X509_load_certificate_file(svrCertFile,
SSL_FILETYPE_PEM));
/* Case 1: no hostname constraint - must succeed. */
ExpectNotNull(ctx = wolfSSL_X509_STORE_CTX_new());
ExpectIntEQ(wolfSSL_X509_STORE_CTX_init(ctx, store, leaf, NULL),
WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_X509_verify_cert(ctx), WOLFSSL_SUCCESS);
wolfSSL_X509_STORE_CTX_free(ctx);
ctx = NULL;
/* Case 2: hostname matches a SAN DNS entry - must succeed. */
ExpectNotNull(ctx = wolfSSL_X509_STORE_CTX_new());
ExpectIntEQ(wolfSSL_X509_STORE_CTX_init(ctx, store, leaf, NULL),
WOLFSSL_SUCCESS);
param = wolfSSL_X509_STORE_CTX_get0_param(ctx);
ExpectNotNull(param);
ExpectIntEQ(wolfSSL_X509_VERIFY_PARAM_set1_host(param, "example.com",
XSTRLEN("example.com")), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_X509_verify_cert(ctx), WOLFSSL_SUCCESS);
wolfSSL_X509_STORE_CTX_free(ctx);
ctx = NULL;
/* Case 3: hostname does not match - must FAIL with the right error code. */
ExpectNotNull(ctx = wolfSSL_X509_STORE_CTX_new());
ExpectIntEQ(wolfSSL_X509_STORE_CTX_init(ctx, store, leaf, NULL),
WOLFSSL_SUCCESS);
param = wolfSSL_X509_STORE_CTX_get0_param(ctx);
ExpectNotNull(param);
ExpectIntEQ(wolfSSL_X509_VERIFY_PARAM_set1_host(param, "wrong.com",
XSTRLEN("wrong.com")), WOLFSSL_SUCCESS);
ExpectIntNE(wolfSSL_X509_verify_cert(ctx), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_X509_STORE_CTX_get_error(ctx),
X509_V_ERR_HOSTNAME_MISMATCH);
ExpectIntEQ(wolfSSL_X509_STORE_CTX_get_error_depth(ctx), 0);
wolfSSL_X509_STORE_CTX_free(ctx);
ctx = NULL;
#ifdef WOLFSSL_IP_ALT_NAME
/* Case 4: IP matches a SAN IP entry - must succeed. */
ExpectNotNull(ctx = wolfSSL_X509_STORE_CTX_new());
ExpectIntEQ(wolfSSL_X509_STORE_CTX_init(ctx, store, leaf, NULL),
WOLFSSL_SUCCESS);
param = wolfSSL_X509_STORE_CTX_get0_param(ctx);
ExpectNotNull(param);
ExpectIntEQ(wolfSSL_X509_VERIFY_PARAM_set1_ip_asc(param, "127.0.0.1"),
WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_X509_verify_cert(ctx), WOLFSSL_SUCCESS);
wolfSSL_X509_STORE_CTX_free(ctx);
ctx = NULL;
/* Case 5: IP does not match - must FAIL with the right error code. */
ExpectNotNull(ctx = wolfSSL_X509_STORE_CTX_new());
ExpectIntEQ(wolfSSL_X509_STORE_CTX_init(ctx, store, leaf, NULL),
WOLFSSL_SUCCESS);
param = wolfSSL_X509_STORE_CTX_get0_param(ctx);
ExpectNotNull(param);
ExpectIntEQ(wolfSSL_X509_VERIFY_PARAM_set1_ip_asc(param, "192.168.1.1"),
WOLFSSL_SUCCESS);
ExpectIntNE(wolfSSL_X509_verify_cert(ctx), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_X509_STORE_CTX_get_error(ctx),
X509_V_ERR_IP_ADDRESS_MISMATCH);
ExpectIntEQ(wolfSSL_X509_STORE_CTX_get_error_depth(ctx), 0);
wolfSSL_X509_STORE_CTX_free(ctx);
ctx = NULL;
#endif /* WOLFSSL_IP_ALT_NAME */
wolfSSL_X509_free(leaf);
wolfSSL_X509_free(ca);
wolfSSL_X509_STORE_free(store);
#endif /* OPENSSL_EXTRA && !NO_FILESYSTEM && !NO_RSA */
return EXPECT_RESULT();
}
int test_x509_set_serialNumber(void)
{
#if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL)
EXPECT_DECLS;
WOLFSSL_X509* x509 = NULL;
WOLFSSL_ASN1_INTEGER* s = NULL;
#if defined(OPENSSL_EXTRA_X509_SMALL)
WOLFSSL_ASN1_INTEGER asnInt;
#endif
ExpectNotNull(x509 = wolfSSL_X509_new());
#if defined(OPENSSL_EXTRA_X509_SMALL)
XMEMSET(&asnInt, 0, sizeof(asnInt));
asnInt.data = asnInt.intData;
asnInt.isDynamic = 0;
asnInt.dataMax = (unsigned int)sizeof(asnInt.intData);
s = &asnInt;
#else
ExpectNotNull(s = wolfSSL_ASN1_INTEGER_new());
#endif
/* --- invalid inputs that must be rejected --- */
/* NULL x509 */
ExpectIntEQ(X509_set_serialNumber(NULL, s), WOLFSSL_FAILURE);
/* NULL s */
ExpectIntEQ(X509_set_serialNumber(x509, NULL), WOLFSSL_FAILURE);
if (s != NULL) {
/* length == 0: too short */
s->length = 0;
s->data[0] = ASN_INTEGER;
s->data[1] = 0;
ExpectIntEQ(wolfSSL_X509_set_serialNumber(x509, s),
WOLFSSL_FAILURE);
/* length == 1: still too short */
s->length = 1;
s->data[0] = ASN_INTEGER;
s->data[1] = 0;
ExpectIntEQ(wolfSSL_X509_set_serialNumber(x509, s),
WOLFSSL_FAILURE);
/* length == 2: still rejected - the guard requires length >= 3 */
s->length = 2;
s->data[0] = ASN_INTEGER;
s->data[1] = 0;
ExpectIntEQ(wolfSSL_X509_set_serialNumber(x509, s),
WOLFSSL_FAILURE);
/* wrong type byte */
s->length = 4;
s->data[0] = 0x00; /* not ASN_INTEGER */
s->data[1] = 2; /* length field */
s->data[2] = 0x01;
s->data[3] = 0x02;
ExpectIntEQ(wolfSSL_X509_set_serialNumber(x509, s),
WOLFSSL_FAILURE);
/* mismatched length byte (data[1] != s->length - 2) */
s->length = 4;
s->data[0] = ASN_INTEGER;
s->data[1] = 99; /* claims 99 bytes but s->length - 2 == 2 */
s->data[2] = 0x01;
s->data[3] = 0x02;
ExpectIntEQ(wolfSSL_X509_set_serialNumber(x509, s),
WOLFSSL_FAILURE);
/* --- valid two-byte serial number --- */
s->length = 4;
s->data[0] = ASN_INTEGER;
s->data[1] = 2;
s->data[2] = 0x01;
s->data[3] = 0x02;
ExpectIntEQ(wolfSSL_X509_set_serialNumber(x509, s),
WOLFSSL_SUCCESS);
ExpectIntEQ(x509->serialSz, 2);
/* NUL terminator must be placed right after the copied data */
ExpectIntEQ(x509->serial[x509->serialSz], 0);
ExpectIntEQ(x509->serial[0], 0x01);
ExpectIntEQ(x509->serial[1], 0x02);
}
#if !defined(OPENSSL_EXTRA_X509_SMALL)
wolfSSL_ASN1_INTEGER_free(s);
#endif
wolfSSL_X509_free(x509);
return EXPECT_RESULT();
#else
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 */
if (notBeforeDataEnd >= origSz) {
return -1;
}
/* 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();
}
/* Test that CertFromX509 rejects an oversized raw AuthorityKeyIdentifier
* extension. Before the fix, the guard checked authKeyIdSz (the [0]
* keyIdentifier sub-field) but the WOLFSSL_AKID_NAME branch copied
* authKeyIdSrcSz (the full extension) bytes, causing a heap overflow. */
int test_x509_CertFromX509_akid_overflow(void)
{
EXPECT_DECLS;
#if defined(WOLFSSL_AKID_NAME) && defined(WOLFSSL_CERT_GEN) && \
defined(WOLFSSL_CERT_EXT) && !defined(NO_BIO) && \
(defined(OPENSSL_EXTRA) || defined(OPENSSL_ALL)) && defined(HAVE_ECC)
/* DER builder helpers -- write into a flat buffer */
#ifdef WOLFSSL_SMALL_STACK
unsigned char* buf = NULL;
#else
unsigned char buf[16384];
#endif
size_t pos = 0;
size_t akid_val_len;
unsigned char* akid_val = NULL;
WOLFSSL_X509* x = NULL;
WOLFSSL_BIO* bio = NULL;
#ifdef WOLFSSL_SMALL_STACK
buf = (unsigned char*)XMALLOC(16384, NULL, DYNAMIC_TYPE_TMP_BUFFER);
ExpectNotNull(buf);
if (buf == NULL)
return EXPECT_RESULT();
#endif
#define PUT1(b) do { buf[pos++] = (b); } while(0)
#define PUTN(p, n) do { XMEMCPY(buf + pos, (p), (n)); pos += (n); } while(0)
/* Emit tag + definite-length header, return header size */
#define TLV_HDR(tag, n, out, hlen) do { \
size_t _i = 0; \
(out)[_i++] = (tag); \
if ((n) < 0x80u) { (out)[_i++] = (unsigned char)(n); } \
else if ((n) < 0x100u) { (out)[_i++] = 0x81; \
(out)[_i++] = (unsigned char)(n); } \
else if ((n) < 0x10000u) { (out)[_i++] = 0x82; \
(out)[_i++] = (unsigned char)((n)>>8); \
(out)[_i++] = (unsigned char)(n); } \
(hlen) = _i; \
} while(0)
/* Wrap [start, pos) in-place with a TLV header */
#define WRAP(start, tag) do { \
size_t _len = pos - (start); \
unsigned char _hdr[6]; size_t _hlen; \
TLV_HDR((tag), _len, _hdr, _hlen); \
XMEMMOVE(buf + (start) + _hlen, buf + (start), _len); \
XMEMCPY(buf + (start), _hdr, _hlen); \
pos += _hlen; \
} while(0)
/* ---- Build AKID extension value ---- */
{
size_t akid_start = pos;
size_t s;
int i;
/* [0] keyIdentifier: 20 bytes (small, passes old check) */
s = pos;
for (i = 0; i < 20; i++) PUT1(0x41);
WRAP(s, 0x80);
/* [1] authorityCertIssuer: one URI of ~4000 bytes
* This makes authKeyIdSrcSz >> sizeof(cert->akid) (~1628) */
s = pos;
{
const char* pfx = "http://e/";
PUTN(pfx, (size_t)XSTRLEN(pfx));
for (i = 0; i < 4000; i++) PUT1('Z');
}
WRAP(s, 0x86); /* GeneralName [6] URI */
WRAP(s, 0xA1); /* [1] IMPLICIT */
/* [2] authorityCertSerialNumber */
s = pos;
PUT1(0x01);
WRAP(s, 0x82);
WRAP(akid_start, 0x30); /* SEQUENCE */
akid_val_len = pos - akid_start;
akid_val = (unsigned char*)XMALLOC(akid_val_len, NULL,
DYNAMIC_TYPE_TMP_BUFFER);
ExpectNotNull(akid_val);
if (akid_val != NULL)
XMEMCPY(akid_val, buf + akid_start, akid_val_len);
}
/* ---- Build minimal self-signed v3 certificate ---- */
pos = 0;
{
size_t tbs_start = pos;
size_t s;
/* version [0] EXPLICIT INTEGER 2 (v3) */
PUT1(0xA0); PUT1(0x03); PUT1(0x02); PUT1(0x01); PUT1(0x02);
/* serialNumber INTEGER 1 */
PUT1(0x02); PUT1(0x01); PUT1(0x01);
/* signature: ecdsa-with-SHA256 */
s = pos;
{
unsigned char oid[] = {0x06,0x08,0x2A,0x86,0x48,0xCE,
0x3D,0x04,0x03,0x02};
PUTN(oid, sizeof(oid));
}
WRAP(s, 0x30);
/* issuer: CN=A */
s = pos;
{
size_t rdn = pos, atv = pos;
unsigned char cn[] = {0x06,0x03,0x55,0x04,0x03};
PUTN(cn, sizeof(cn));
PUT1(0x0C); PUT1(0x01); PUT1('A');
WRAP(atv, 0x30); WRAP(rdn, 0x31); WRAP(s, 0x30);
}
/* validity */
s = pos;
{
unsigned char t1[] = {0x17,0x0D,'2','5','0','1','0','1',
'0','0','0','0','0','0','Z'};
unsigned char t2[] = {0x17,0x0D,'3','5','0','1','0','1',
'0','0','0','0','0','0','Z'};
PUTN(t1, sizeof(t1)); PUTN(t2, sizeof(t2));
}
WRAP(s, 0x30);
/* subject: CN=A */
s = pos;
{
size_t rdn = pos, atv = pos;
unsigned char cn[] = {0x06,0x03,0x55,0x04,0x03};
PUTN(cn, sizeof(cn));
PUT1(0x0C); PUT1(0x01); PUT1('A');
WRAP(atv, 0x30); WRAP(rdn, 0x31); WRAP(s, 0x30);
}
/* subjectPublicKeyInfo: EC P-256 with dummy point */
s = pos;
{
size_t alg = pos, bs;
unsigned char ecpk[] = {0x06,0x07,0x2A,0x86,0x48,0xCE,
0x3D,0x02,0x01};
unsigned char p256[] = {0x06,0x08,0x2A,0x86,0x48,0xCE,
0x3D,0x03,0x01,0x07};
PUTN(ecpk, sizeof(ecpk));
PUTN(p256, sizeof(p256));
WRAP(alg, 0x30);
bs = pos;
PUT1(0x00); PUT1(0x04);
/* Use P-256 generator point (valid on-curve point) so that
* builds with WOLFSSL_VALIDATE_ECC_IMPORT accept the key. */
{
static const unsigned char p256G[64] = {
0x6B,0x17,0xD1,0xF2,0xE1,0x2C,0x42,0x47,
0xF8,0xBC,0xE6,0xE5,0x63,0xA4,0x40,0xF2,
0x77,0x03,0x7D,0x81,0x2D,0xEB,0x33,0xA0,
0xF4,0xA1,0x39,0x45,0xD8,0x98,0xC2,0x96,
0x4F,0xE3,0x42,0xE2,0xFE,0x1A,0x7F,0x9B,
0x8E,0xE7,0xEB,0x4A,0x7C,0x0F,0x9E,0x16,
0x2B,0xCE,0x33,0x57,0x6B,0x31,0x5E,0xCE,
0xCB,0xB6,0x40,0x68,0x37,0xBF,0x51,0xF5
};
PUTN(p256G, sizeof(p256G));
}
WRAP(bs, 0x03);
}
WRAP(s, 0x30);
/* extensions [3] */
{
size_t exts_outer = pos, exts_seq = pos, ext = pos, ev;
unsigned char akid_oid[] = {0x06,0x03,0x55,0x1D,0x23};
PUTN(akid_oid, sizeof(akid_oid));
ev = pos;
if (akid_val != NULL)
PUTN(akid_val, akid_val_len);
WRAP(ev, 0x04);
WRAP(ext, 0x30);
WRAP(exts_seq, 0x30);
WRAP(exts_outer, 0xA3);
}
WRAP(tbs_start, 0x30);
/* signatureAlgorithm */
s = pos;
{
unsigned char oid[] = {0x06,0x08,0x2A,0x86,0x48,0xCE,
0x3D,0x04,0x03,0x02};
PUTN(oid, sizeof(oid));
}
WRAP(s, 0x30);
/* signatureValue: dummy */
s = pos;
{
size_t sig;
PUT1(0x00);
sig = pos;
PUT1(0x02); PUT1(0x01); PUT1(0x01);
PUT1(0x02); PUT1(0x01); PUT1(0x01);
WRAP(sig, 0x30);
}
WRAP(s, 0x03);
WRAP(0, 0x30); /* outer Certificate SEQUENCE */
}
/* Parse the crafted certificate */
x = wolfSSL_X509_d2i(NULL, buf, (int)pos);
ExpectNotNull(x);
/* Attempt re-encode via i2d_X509_bio -- must fail gracefully, not
* overflow. Before the fix this would write ~4000 bytes past the
* end of cert->akid[]. */
bio = wolfSSL_BIO_new(wolfSSL_BIO_s_mem());
ExpectNotNull(bio);
ExpectIntEQ(wolfSSL_i2d_X509_bio(bio, x), 0);
wolfSSL_BIO_free(bio);
wolfSSL_X509_free(x);
XFREE(akid_val, NULL, DYNAMIC_TYPE_TMP_BUFFER);
#ifdef WOLFSSL_SMALL_STACK
XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER);
#endif
#undef PUT1
#undef PUTN
#undef TLV_HDR
#undef WRAP
#endif
return EXPECT_RESULT();
}
/* Test that ReqCertFromX509() rejects a CSR with an oversized
* SubjectKeyIdentifier (> CTC_MAX_SKID_SIZE = 32 bytes). Before the fix
* this would cause a heap buffer overflow into cert->skid[32]. */
int test_x509_ReqCertFromX509_skid_overflow(void)
{
EXPECT_DECLS;
#if defined(WOLFSSL_CERT_REQ) && defined(WOLFSSL_CERT_GEN) && \
defined(WOLFSSL_CERT_EXT) && !defined(NO_BIO) && \
(defined(OPENSSL_EXTRA) || defined(OPENSSL_ALL)) && \
defined(HAVE_ECC)
/* Minimal DER-encoded CSR (PKCS#10) containing a SubjectKeyIdentifier
* extension with a 64-byte value -- double the 32-byte CTC_MAX_SKID_SIZE
* destination buffer.
*
* Structure:
* SEQUENCE {
* CertificationRequestInfo SEQUENCE {
* version INTEGER 0
* subject: CN=Test
* subjectPKInfo: EC P-256 (generator point)
* attributes [0] {
* SEQUENCE {
* OID 1.2.840.113549.1.9.14 (extensionRequest)
* SET { SEQUENCE { -- Extensions
* SEQUENCE { -- Extension
* OID 2.5.29.14 (subjectKeyIdentifier)
* OCTET STRING { OCTET STRING (64 bytes of 0x41) }
* }
* }}
* }
* }
* }
* signatureAlgorithm: ecdsa-with-SHA256
* signatureValue: dummy
* }
*/
static const unsigned char crafted_csr_der[] = {
0x30, 0x81, 0xE7, 0x30, 0x81, 0xCD, 0x02, 0x01,
0x00, 0x30, 0x0F, 0x31, 0x0D, 0x30, 0x0B, 0x06,
0x03, 0x55, 0x04, 0x03, 0x0C, 0x04, 0x54, 0x65,
0x73, 0x74, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07,
0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06,
0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01,
0x07, 0x03, 0x42, 0x00, 0x04, 0x6B, 0x17, 0xD1,
0xF2, 0xE1, 0x2C, 0x42, 0x47, 0xF8, 0xBC, 0xE6,
0xE5, 0x63, 0xA4, 0x40, 0xF2, 0x77, 0x03, 0x7D,
0x81, 0x2D, 0xEB, 0x33, 0xA0, 0xF4, 0xA1, 0x39,
0x45, 0xD8, 0x98, 0xC2, 0x96, 0x4F, 0xE3, 0x42,
0xE2, 0xFE, 0x1A, 0x7F, 0x9B, 0x8E, 0xE7, 0xEB,
0x4A, 0x7C, 0x0F, 0x9E, 0x16, 0x2B, 0xCE, 0x33,
0x57, 0x6B, 0x31, 0x5E, 0xCE, 0xCB, 0xB6, 0x40,
0x68, 0x37, 0xBF, 0x51, 0xF5, 0xA0, 0x5C, 0x30,
0x5A, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7,
0x0D, 0x01, 0x09, 0x0E, 0x31, 0x4D, 0x30, 0x4B,
0x30, 0x49, 0x06, 0x03, 0x55, 0x1D, 0x0E, 0x04,
0x42, 0x04, 0x40,
/* 64 bytes of 0x41 -- oversized SKID value */
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
/* end of SKID payload */
0x30, 0x0A, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE,
0x3D, 0x04, 0x03, 0x02, 0x03, 0x09, 0x00, 0x30,
0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01
};
WOLFSSL_X509* req = NULL;
WOLFSSL_BIO* bio = NULL;
/* Step 1: Parse the crafted CSR -- this should succeed (the parser
* dynamically allocates subjKeyId to the parsed size). */
req = wolfSSL_X509_REQ_d2i(NULL, crafted_csr_der,
(int)sizeof(crafted_csr_der));
ExpectNotNull(req);
/* Step 2: Attempt to re-encode. Before the fix, this triggered a
* heap buffer overflow in ReqCertFromX509() writing 64 bytes into
* cert->skid[32]. With the fix, it must return failure. */
bio = wolfSSL_BIO_new(wolfSSL_BIO_s_mem());
ExpectNotNull(bio);
ExpectIntNE(wolfSSL_i2d_X509_REQ_bio(bio, req), WOLFSSL_SUCCESS);
wolfSSL_BIO_free(bio);
wolfSSL_X509_free(req);
#endif
return EXPECT_RESULT();
}
/* Positive / boundary companion to test_x509_ReqCertFromX509_skid_overflow.
* Verifies that a CSR with a SubjectKeyIdentifier of exactly
* CTC_MAX_SKID_SIZE (32) bytes -- the boundary of the bounds check in
* ReqCertFromX509() -- is accepted and that the SKID round-trips with the
* correct length and bytes through the sign / re-encode / re-parse path.
* This kills boundary mutations (">" -> ">=") and copy-size mutations on
* the XMEMCPY into cert->skid that the negative test alone cannot catch. */
int test_x509_ReqCertFromX509_skid_boundary(void)
{
EXPECT_DECLS;
#if defined(WOLFSSL_CERT_REQ) && defined(WOLFSSL_CERT_GEN) && \
defined(WOLFSSL_CERT_EXT) && !defined(NO_BIO) && \
(defined(OPENSSL_EXTRA) || defined(OPENSSL_ALL)) && \
defined(HAVE_ECC) && defined(USE_CERT_BUFFERS_256)
WOLFSSL_EVP_PKEY* priv = NULL;
WOLFSSL_EVP_PKEY* pub = NULL;
WOLFSSL_X509* req = NULL;
WOLFSSL_X509* parsed = NULL;
WOLFSSL_X509_NAME* name = NULL;
unsigned char* der = NULL;
int derSz = 0;
const unsigned char* ecPriv = ecc_clikey_der_256;
const unsigned char* ecPub = ecc_clikeypub_der_256;
unsigned char expected_skid[CTC_MAX_SKID_SIZE];
XMEMSET(expected_skid, 0x41, sizeof(expected_skid));
/* Load a real ECC keypair so that the CSR can actually be signed
* (ReqCertFromX509() is invoked from the signing path). */
ExpectNotNull(priv = wolfSSL_d2i_PrivateKey(EVP_PKEY_EC, NULL, &ecPriv,
(long)sizeof_ecc_clikey_der_256));
ExpectNotNull(pub = wolfSSL_d2i_PUBKEY(NULL, &ecPub,
(long)sizeof_ecc_clikeypub_der_256));
ExpectNotNull(req = wolfSSL_X509_REQ_new());
ExpectNotNull(name = wolfSSL_X509_NAME_new());
ExpectIntEQ(wolfSSL_X509_NAME_add_entry_by_txt(name, "commonName",
MBSTRING_UTF8, (const byte*)"Test", 4, -1, 0),
WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_X509_REQ_set_subject_name(req, name), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_X509_REQ_set_pubkey(req, pub), WOLFSSL_SUCCESS);
/* Inject exactly CTC_MAX_SKID_SIZE bytes of SKID directly on the req
* (tests/api/test_x509.c already includes <wolfssl/internal.h>). This
* is the boundary value of the bounds check in ReqCertFromX509(). */
if (EXPECT_SUCCESS() && req != NULL) {
req->subjKeyId = (byte*)XMALLOC(sizeof(expected_skid), NULL,
DYNAMIC_TYPE_X509_EXT);
ExpectNotNull(req->subjKeyId);
if (req->subjKeyId != NULL) {
XMEMCPY(req->subjKeyId, expected_skid, sizeof(expected_skid));
req->subjKeyIdSz = (word32)sizeof(expected_skid);
}
}
/* Signing invokes wolfssl_x509_make_der() -> ReqCertFromX509(), which
* executes the bounds check and the XMEMCPY into cert->skid. A
* ">=" boundary mutation would make this step fail for a 32-byte
* SKID. */
ExpectIntEQ(wolfSSL_X509_REQ_sign(req, priv, wolfSSL_EVP_sha256()),
WOLFSSL_SUCCESS);
/* Re-encode the now-signed CSR and parse it back to verify the SKID
* round-trips with the correct length and bytes. This verifies the
* output of the XMEMCPY in ReqCertFromX509(). */
ExpectIntGT((derSz = wolfSSL_i2d_X509_REQ(req, &der)), 0);
ExpectNotNull(der);
ExpectNotNull(parsed = wolfSSL_X509_REQ_d2i(NULL, der, derSz));
if (parsed != NULL) {
ExpectIntEQ((int)parsed->subjKeyIdSz, CTC_MAX_SKID_SIZE);
ExpectNotNull(parsed->subjKeyId);
if (parsed->subjKeyId != NULL) {
ExpectIntEQ(XMEMCMP(parsed->subjKeyId, expected_skid,
CTC_MAX_SKID_SIZE), 0);
}
}
wolfSSL_X509_free(parsed);
XFREE(der, NULL, DYNAMIC_TYPE_OPENSSL);
wolfSSL_X509_NAME_free(name);
wolfSSL_X509_free(req);
wolfSSL_EVP_PKEY_free(pub);
wolfSSL_EVP_PKEY_free(priv);
#endif
return EXPECT_RESULT();
}