/* 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 #ifdef NO_INLINE #include #else #define WOLFSSL_MISC_INCLUDED #include #endif #include #include #include #include #include #include #include #include #include #include #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 * * [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)) /* 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 ). 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(); }