diff --git a/src/ssl.c b/src/ssl.c index 763757e21fc..65b56d6d7c8 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -3868,13 +3868,20 @@ int wolfssl_local_IsValidFQDN(const char* name, word32 nameSz) if (name == NULL || nameSz == 0) return 0; + /* Bound before touching name[]: a valid name is at most 253 octets + * plus an optional trailing dot, so longer sizes are rejected here + * without reading past the caller's buffer. + */ + if (nameSz > 254) + return 0; + /* Strip a single optional trailing dot before measuring. "example.com." * is the absolute form of the same FQDN. */ if (name[nameSz - 1] == '.') --nameSz; - if (nameSz < 1 || nameSz > 253) + if (nameSz > 253) return 0; for (i = 0; i < nameSz; i++) { diff --git a/tests/api/check-api-guards.py b/tests/api/check-api-guards.py index 9d132309fc2..f1bbca134a0 100755 --- a/tests/api/check-api-guards.py +++ b/tests/api/check-api-guards.py @@ -64,6 +64,13 @@ 'wolfSSL_CertManagerVerify': (['NO_FILESYSTEM'], []), 'wolfSSL_CertManagerLoadCRL': (['NO_FILESYSTEM'], []), 'wolfSSL_CertManagerLoadCRLFile': (['NO_FILESYSTEM'], []), + # src/ssl.c: implemented under HAVE_SECURE_RENEGOTIATION || + # HAVE_SERVER_RENEGOTIATION_INFO; the substring match passes a call + # site guarded by the full disjunction. + 'wolfSSL_SSL_renegotiate_pending': ([], ['HAVE_SECURE_RENEGOTIATION']), + # src/ssl_load.c: implemented under !NO_FILESYSTEM && !NO_CERTS + 'wolfSSL_CTX_use_PrivateKey_file': (['NO_FILESYSTEM', 'NO_CERTS'], []), + 'wolfSSL_CTX_use_certificate_file': (['NO_FILESYSTEM', 'NO_CERTS'], []), } diff --git a/tests/api/test_aes.c b/tests/api/test_aes.c index aa09218a255..fc39310e45f 100644 --- a/tests/api/test_aes.c +++ b/tests/api/test_aes.c @@ -13965,3 +13965,203 @@ int test_wc_AesEcb_RetCodeChecked(void) #endif /* WOLF_CRYPTO_CB && !NO_AES && HAVE_AES_ECB && WOLFSSL_AES_128 && * !WOLF_CRYPTO_CB_ONLY_AES && (WOLFSSL_AES_COUNTER || HAVE_AESGCM) */ + +/* MC/DC pairs the 2026 upstream aes.c changes left open: the GcmInit/ + * GcmSetIV argument-clause tails, the zero-length key id, the CTR + * leftover-keystream loop with an empty request, and the XTS unset-key + * guard on both sides. + * + * Excluded under HAVE_SELFTEST: the CAVP self-test leg compiles the frozen + * wolfCrypt 4.1.0 aes.c, whose GCM-stream/XTS/CTR paths predate these + * guards, so the open aes.c this measures is not the one built (same + * exclusion as the AesFeatureCoverage GCM/CCM blocks). FIPS modules also + * use different AES paths and do not provide every public size constant. */ +int test_wc_AesReworkDecisionCoverage(void) +{ + EXPECT_DECLS; +#if !defined(NO_AES) && !defined(HAVE_SELFTEST) && !defined(HAVE_FIPS) + Aes aes; + byte key16[16]; + byte iv[WC_AES_BLOCK_SIZE]; + byte ivFixed[AES_IV_FIXED_SZ]; +#if defined(HAVE_AESGCM) + byte tag[WC_AES_BLOCK_SIZE]; +#endif +#if defined(WOLF_PRIVATE_KEY_ID) + byte id[8]; +#endif +#if defined(HAVE_AESGCM) || defined(WOLFSSL_AES_COUNTER) || \ + defined(WOLFSSL_AES_CFB) || defined(WOLFSSL_AES_XTS) + byte in[32]; + byte out[32]; +#endif +#if !defined(WC_NO_RNG) + WC_RNG rng; +#endif +#if defined(WOLFSSL_AES_XTS) + XtsAes xaes; +#endif + + XMEMSET(key16, 1, sizeof(key16)); + XMEMSET(iv, 2, sizeof(iv)); + XMEMSET(ivFixed, 3, sizeof(ivFixed)); +#if defined(HAVE_AESGCM) + XMEMSET(tag, 4, sizeof(tag)); +#endif +#if defined(WOLF_PRIVATE_KEY_ID) + XMEMSET(id, 4, sizeof(id)); +#endif +#if defined(HAVE_AESGCM) || defined(WOLFSSL_AES_COUNTER) || \ + defined(WOLFSSL_AES_CFB) || \ + defined(WOLFSSL_AES_XTS) + XMEMSET(in, 5, sizeof(in)); +#endif +#if !defined(WC_NO_RNG) + XMEMSET(&rng, 0, sizeof(rng)); +#endif + + /* wc_AesGcmEncryptInit() -> wc_AesGcmInit(): (ivSz > 0) false with iv + * NULL, the clause tail (stream API only). */ +#if defined(HAVE_AESGCM) && defined(WOLFSSL_AESGCM_STREAM) + ExpectIntEQ(wc_AesInit(&aes, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesGcmEncryptInit(&aes, key16, sizeof(key16), NULL, 0), + 0); + /* (ivSz > 0) true with iv NULL: the clause fires. */ + ExpectIntEQ(wc_AesGcmEncryptInit(&aes, key16, sizeof(key16), NULL, 12), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + wc_AesFree(&aes); +#endif + /* wc_AesGcmSetIV(): ivFixedSz == AES_IV_FIXED_SZ (clause tail); the + * remaining IV bytes come from the RNG. */ +#if defined(HAVE_AESGCM) && !defined(WC_NO_RNG) + ExpectIntEQ(wc_AesInit(&aes, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesGcmSetKey(&aes, key16, sizeof(key16)), 0); + ExpectIntEQ(wc_InitRng_ex(&rng, HEAP_HINT, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesGcmSetIV(&aes, GCM_NONCE_MIN_SZ, ivFixed, + sizeof(ivFixed), &rng), 0); + /* (ivFixedSz != AES_IV_FIXED_SZ) true: the clause fires. */ + ExpectIntEQ(wc_AesGcmSetIV(&aes, 12, ivFixed, 12, &rng), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* rng == NULL side of the guard, isolated with a valid fixed part. */ + ExpectIntEQ(wc_AesGcmSetIV(&aes, GCM_NONCE_MIN_SZ, ivFixed, + sizeof(ivFixed), NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* ivFixed == NULL: the whole IV comes from the RNG. */ + ExpectIntEQ(wc_AesGcmSetIV(&aes, 12, NULL, 0, &rng), 0); + /* Invalid IV size: not 8, 12, or 16. */ + ExpectIntEQ(wc_AesGcmSetIV(&aes, 13, ivFixed, 12, &rng), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + wc_AesFree(&aes); + DoExpectIntEQ(wc_FreeRng(&rng), 0); +#endif + + /* wc_AesInit_Id(): non-NULL id with zero length is a valid tag. */ +#if defined(WOLF_PRIVATE_KEY_ID) + ExpectIntEQ(wc_AesInit_Id(&aes, id, 0, NULL, INVALID_DEVID), 0); + wc_AesFree(&aes); +#endif + + /* CTR leftover-keystream loop: a partial block leaves aes->left, an + * empty request evaluates the loop with sz == 0, draining left to zero + * with data pending evaluates the loop with left == 0. */ +#if defined(WOLFSSL_AES_COUNTER) + ExpectIntEQ(wc_AesInit(&aes, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesSetKey(&aes, key16, sizeof(key16), iv, AES_ENCRYPTION), + 0); + ExpectIntEQ(wc_AesSetIV(&aes, iv), 0); + ExpectIntEQ(wc_AesCtrEncrypt(&aes, out, in, 5), 0); + ExpectIntEQ(wc_AesCtrEncrypt(&aes, out, in, 0), 0); + ExpectIntEQ(wc_AesCtrEncrypt(&aes, out, in, 3), 0); + ExpectIntEQ(wc_AesCtrEncrypt(&aes, out, in, 8), 0); + ExpectIntEQ(wc_AesCtrEncrypt(&aes, out, in, 3), 0); + wc_AesFree(&aes); +#endif + + /* CFB decrypt chunk loop: exactly two blocks drains sz below the loop + * threshold inside the loop, evaluating the false side. */ +#if defined(WOLFSSL_AES_CFB) + ExpectIntEQ(wc_AesInit(&aes, NULL, INVALID_DEVID), 0); + /* CFB always uses the forward cipher for both directions. */ + ExpectIntEQ(wc_AesSetKey(&aes, key16, sizeof(key16), iv, AES_ENCRYPTION), + 0); + ExpectIntEQ(wc_AesSetIV(&aes, iv), 0); + ExpectIntEQ(wc_AesCfbDecrypt(&aes, out, in, 2 * WC_AES_BLOCK_SIZE), 0); + wc_AesFree(&aes); +#endif + +#if defined(WOLFSSL_AES_XTS) + XMEMSET(&xaes, 0, sizeof(xaes)); + /* keylen == 0 side of the unset-key guard, both directions. */ + ExpectIntEQ(wc_AesXtsEncrypt(&xaes, out, in, sizeof(in), iv, sizeof(iv)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesXtsDecrypt(&xaes, out, in, sizeof(in), iv, sizeof(iv)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* rounds == 0 side: a key length with no software schedule. */ + xaes.aes.keylen = (int)sizeof(key16); + ExpectIntEQ(wc_AesXtsEncrypt(&xaes, out, in, sizeof(in), iv, sizeof(iv)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesXtsDecrypt(&xaes, out, in, sizeof(in), iv, sizeof(iv)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* keylen == 0 with rounds set: the OR's first operand alone. */ + xaes.aes.keylen = 0; + xaes.aes.rounds = 10; + ExpectIntEQ(wc_AesXtsEncrypt(&xaes, out, in, sizeof(in), iv, sizeof(iv)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesXtsDecrypt(&xaes, out, in, sizeof(in), iv, sizeof(iv)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); +#endif + + /* wc_AesGcmInit(): direct argument checks - (ivSz > 0) with iv NULL is + * the last clause, (ivSz == 0) with iv NULL is the clean path. */ +#if defined(HAVE_AESGCM) && defined(WOLFSSL_AESGCM_STREAM) + ExpectIntEQ(wc_AesInit(&aes, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesGcmInit(&aes, key16, sizeof(key16), NULL, 0), 0); + ExpectIntEQ(wc_AesGcmInit(&aes, key16, sizeof(key16), NULL, 12), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + wc_AesFree(&aes); +#endif + + /* wc_AesGcmSetIV(): (ivFixed == NULL) with ivFixedSz == 0 - the pair + * for the (ivFixed != NULL) clause against the size-mismatch failure + * above. */ +#if defined(HAVE_AESGCM) && !defined(WC_NO_RNG) + ExpectIntEQ(wc_AesInit(&aes, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesGcmSetKey(&aes, key16, sizeof(key16)), 0); + ExpectIntEQ(wc_InitRng_ex(&rng, HEAP_HINT, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesGcmSetIV(&aes, 12, NULL, 0, &rng), 0); + wc_AesFree(&aes); + DoExpectIntEQ(wc_FreeRng(&rng), 0); +#endif + + /* GCM tag-failure output wipe: a corrupted tag with out non-NULL and + * sz > 0 wipes the unauthenticated plaintext (AES_GCM_AUTH_E). */ +#if defined(HAVE_AESGCM) + ExpectIntEQ(wc_AesInit(&aes, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesGcmSetKey(&aes, key16, sizeof(key16)), 0); + ExpectIntEQ(wc_AesGcmEncrypt(&aes, out, in, sizeof(in), iv, + sizeof(iv), tag, sizeof(tag), NULL, 0), 0); + tag[0] ^= 0x01; + ExpectIntEQ(wc_AesGcmDecrypt(&aes, out, out, sizeof(in), iv, + sizeof(iv), tag, sizeof(tag), NULL, 0), + WC_NO_ERR_TRACE(AES_GCM_AUTH_E)); + wc_AesFree(&aes); +#endif + + /* CFB leftover-drain loop: a partial decrypt leaves aes->left, an empty + * request evaluates the loop with sz == 0, and a call longer than the + * leftover exits with left == 0 while sz is still positive. */ +#if defined(WOLFSSL_AES_CFB) + ExpectIntEQ(wc_AesInit(&aes, NULL, INVALID_DEVID), 0); + /* CFB always uses the forward cipher for both directions. */ + ExpectIntEQ(wc_AesSetKey(&aes, key16, sizeof(key16), iv, AES_ENCRYPTION), + 0); + ExpectIntEQ(wc_AesSetIV(&aes, iv), 0); + ExpectIntEQ(wc_AesCfbDecrypt(&aes, out, in, 5), 0); + ExpectIntEQ(wc_AesCfbDecrypt(&aes, out, in, 0), 0); + ExpectIntEQ(wc_AesCfbDecrypt(&aes, out, in, 3), 0); + ExpectIntEQ(wc_AesCfbDecrypt(&aes, out, in, 10), 0); + wc_AesFree(&aes); +#endif +#endif /* !NO_AES && !HAVE_SELFTEST && !HAVE_FIPS */ + return EXPECT_RESULT(); +} diff --git a/tests/api/test_aes.h b/tests/api/test_aes.h index f0b39d3f27d..bd353c0b0b2 100644 --- a/tests/api/test_aes.h +++ b/tests/api/test_aes.h @@ -113,6 +113,7 @@ int test_wc_AesGcm_MonteCarlo(void); int test_wc_AesCcm_MonteCarlo(void); int test_wc_AesCfb_MonteCarlo(void); int test_wc_AesOfb_MonteCarlo(void); +int test_wc_AesReworkDecisionCoverage(void); int test_wc_GmacSetKey(void); int test_wc_GmacUpdate(void); @@ -192,6 +193,7 @@ int test_wc_CryptoCb_AesKeyWrapEcbCompose(void); #define TEST_AES_DECLS \ TEST_DECL_GROUP("aes", test_wc_AesSetKey), \ + TEST_DECL_GROUP("aes", test_wc_AesReworkDecisionCoverage), \ TEST_DECL_GROUP("aes", test_wc_AesSetIV), \ TEST_DECL_GROUP("aes", test_wc_AesSetIV_RestartsStream), \ TEST_DECL_GROUP("aes", test_wc_AesEncryptDecryptDirect), \ diff --git a/tests/api/test_asn.c b/tests/api/test_asn.c index 94c9409a3e9..bb34443e1a2 100644 --- a/tests/api/test_asn.c +++ b/tests/api/test_asn.c @@ -5311,3 +5311,56 @@ int test_SetCertificatePolicies_no_empty_qualifiers(void) #endif return EXPECT_RESULT(); } + +/* + * MC/DC decision coverage for the PKCS#8 key-creation/encryption guards in + * wolfcrypt/src/asn.c: the LENGTH_ONLY_E size-query row of + * wc_CreatePKCS8Key, the GetAlgoV2-failure row of wc_EncryptPKCS8Key_ex, + * the undersized-buffer row, and the NULL-hmacOid PBES2 row. A raw key + * buffer is used (no cert-buffer dependency) so the rows run in every + * variant. + */ +int test_wc_Pkcs8EncryptDecisionCoverage(void) +{ + EXPECT_DECLS; +#if defined(HAVE_PKCS8) && !defined(NO_PWDBASED) && \ + defined(HAVE_AES_CBC) && !defined(NO_AES) && defined(WOLFSSL_AES_128) && \ + defined(HAVE_ECC) && !defined(HAVE_SELFTEST) && !defined(HAVE_FIPS) + WC_RNG rng; + byte key[32]; + byte out[512]; + byte salt[8]; + word32 outSz; + + XMEMSET(key, 3, sizeof(key)); + XMEMSET(salt, 4, sizeof(salt)); + XMEMSET(&rng, 0, sizeof(rng)); + ExpectIntEQ(wc_InitRng_ex(&rng, HEAP_HINT, INVALID_DEVID), 0); + + /* wc_CreatePKCS8Key size query: out==NULL returns LENGTH_ONLY_E, the + * (ret == LENGTH_ONLY_E) clause's true side. */ + outSz = sizeof(out); + ExpectIntEQ(wc_CreatePKCS8Key(NULL, &outSz, key, sizeof(key), RSAk, + NULL, 0), WC_NO_ERR_TRACE(LENGTH_ONLY_E)); + + /* wc_EncryptPKCS8Key_ex: a bogus PBES2 encAlgId fails GetAlgoV2, so + * the (ret == 0) operand of the encOid check takes its false side. */ + outSz = sizeof(out); + ExpectIntNE(wc_EncryptPKCS8Key_ex(key, sizeof(key), out, &outSz, + "pw", 2, 2, PBES2, 0x7FFFFFFF, salt, sizeof(salt), 1000, 0, &rng, + HEAP_HINT), 0); + + /* Same call with a valid encAlgId and an undersized output buffer: + * the encoding fails, so the PBES2 hmacOid check's (ret == 0) + * operand takes its false side. (The (hmacOidBuf != NULL) operand's + * false side is structurally dead: a successful PBES2 call always + * carries an hmac OID, since PBKDF2 requires a hash. */ + outSz = 16; + ExpectIntNE(wc_EncryptPKCS8Key_ex(key, sizeof(key), out, &outSz, + "pw", 2, 2, PBES2, AES128CBCb, salt, sizeof(salt), 1000, + WC_HASH_TYPE_SHA256, &rng, HEAP_HINT), 0); + + ExpectIntEQ(wc_FreeRng(&rng), 0); +#endif /* HAVE_AES_CBC && !NO_AES && WOLFSSL_AES_128 && !FIPS */ + return EXPECT_RESULT(); +} diff --git a/tests/api/test_asn.h b/tests/api/test_asn.h index b61c09fb99c..97446e582d1 100644 --- a/tests/api/test_asn.h +++ b/tests/api/test_asn.h @@ -61,6 +61,7 @@ int test_wc_DecodeExtKeyUsage_ssh_oid_collision(void); int test_wc_DecodeExtKeyUsage_oidSumCollision(void); int test_wc_AsnDecisionCoverage(void); int test_wc_AsnFeatureCoverage(void); +int test_wc_Pkcs8EncryptDecisionCoverage(void); int test_wc_AltNameNewEx(void); #define TEST_ASN_DECLS \ @@ -101,6 +102,7 @@ int test_wc_AltNameNewEx(void); TEST_DECL_GROUP("asn", test_wc_DecodeExtKeyUsage_oidSumCollision), \ TEST_DECL_GROUP("asn", test_wc_AsnDecisionCoverage), \ TEST_DECL_GROUP("asn", test_wc_AsnFeatureCoverage), \ + TEST_DECL_GROUP("asn", test_wc_Pkcs8EncryptDecisionCoverage), \ TEST_DECL_GROUP("asn", test_wc_AltNameNewEx) #endif /* WOLFCRYPT_TEST_ASN_H */ diff --git a/tests/api/test_certman.c b/tests/api/test_certman.c index 98df4e11d90..84cfa0bd975 100644 --- a/tests/api/test_certman.c +++ b/tests/api/test_certman.c @@ -4573,3 +4573,62 @@ int test_wolfSSL_CertManagerNameConstraint_skid_disambiguates(void) #endif return EXPECT_RESULT(); } +int test_certman_decision_coverage(void) +{ + EXPECT_DECLS; + /* Refs WOLFSSL_LOCAL certman fns, hidden from the .so - shared builds + * compile this out; WOLFSSL_TEST_STATIC_BUILD is set for static-only + * builds (configure) and by the MC/DC variant. */ +#if defined(WOLFSSL_TEST_STATIC_BUILD) && !defined(WOLFCRYPT_ONLY) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && !defined(NO_CERTS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL_CERT_MANAGER* cm = NULL; + byte hash[SIGNER_DIGEST_SIZE]; + Signer signer; + Signer* hs = NULL; + + XMEMSET(hash, 0, sizeof(hash)); + XMEMSET(&signer, 0, sizeof(signer)); + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + cm = ctx->cm; + ExpectNotNull(cm); + + /* --- 2700:0/1 AlreadySigner: cm==NULL || hash==NULL ------------------ */ + (void)AlreadySigner(NULL, hash); + (void)AlreadySigner(cm, NULL); + (void)AlreadySigner(cm, hash); + + /* --- 2812:0/1 GetCA: cm==NULL || hash==NULL -------------------------- */ + (void)GetCA(NULL, hash); + (void)GetCA(cm, NULL); + (void)GetCA(cm, hash); + + /* --- 3037:0/1 AddSigner: cm==NULL || s==NULL ------------------------- */ + (void)AddSigner(NULL, &signer); + (void)AddSigner(cm, NULL); + /* The all-valid case inserts the Signer into the cm's CA table, which the + * cm then owns and frees on ctx-free; use a heap Signer and remove it + * before freeing so ownership stays clean. */ + hs = (Signer*)XMALLOC(sizeof(Signer), cm->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (hs != NULL) { + XMEMSET(hs, 0, sizeof(Signer)); + (void)AddSigner(cm, hs); + /* RemoveCA frees the signer it removes; no XFREE after it. */ + (void)RemoveCA(cm, hs->subjectKeyIdHash, WOLFSSL_USER_CA); + } + + /* --- 3352/3372:0/1 RemoveCA: cm==NULL || hash==NULL ------------------ */ + (void)RemoveCA(NULL, hash, WOLFSSL_USER_CA); + (void)RemoveCA(cm, NULL, WOLFSSL_USER_CA); + (void)RemoveCA(cm, hash, WOLFSSL_USER_CA); + + /* --- 3399:0/1/2 SetCAType: cm==NULL || hash==NULL || type range ------ */ + (void)SetCAType(NULL, hash, WOLFSSL_USER_CA); + (void)SetCAType(cm, NULL, WOLFSSL_USER_CA); + (void)SetCAType(cm, hash, WOLFSSL_USER_CA); + (void)SetCAType(cm, hash, 0); + + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_certman.h b/tests/api/test_certman.h index 4f26a0b2974..c6bca0f7639 100644 --- a/tests/api/test_certman.h +++ b/tests/api/test_certman.h @@ -25,6 +25,7 @@ #include int test_wolfSSL_CertManagerAPI(void); +int test_certman_decision_coverage(void); int test_wolfSSL_CertManagerLoadCABuffer(void); int test_wolfSSL_CertManagerLoadCABuffer_ex(void); int test_wolfSSL_CertManagerLoadCABufferType(void); @@ -109,6 +110,7 @@ int test_wolfSSL_CertManagerNameConstraint_skid_disambiguates(void); TEST_DECL_GROUP("certman", \ test_wolfSSL_CertManagerNameConstraint_valid_chain), \ TEST_DECL_GROUP("certman", \ - test_wolfSSL_CertManagerNameConstraint_skid_disambiguates) + test_wolfSSL_CertManagerNameConstraint_skid_disambiguates), \ + TEST_DECL_GROUP("certman", test_certman_decision_coverage) #endif /* WOLFCRYPT_TEST_CERTMAN_H */ diff --git a/tests/api/test_dh.c b/tests/api/test_dh.c index 2a71224ffc0..28a37b1979e 100644 --- a/tests/api/test_dh.c +++ b/tests/api/test_dh.c @@ -1137,3 +1137,41 @@ int test_wc_DhGenerateKeyPair_CheckDhLN(void) #endif return EXPECT_RESULT(); } + +/* Decision-coverage drivers for the ssl_load.c DH argument guards left + * unpaired by the functional tests. Each block names the + * source line:condition it closes. */ +int test_dh_decision_coverage(void) +{ + EXPECT_DECLS; +#if !defined(WOLFCRYPT_ONLY) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_DH) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte p[512]; + byte g[8]; + byte bigP[4096]; + + XMEMSET(p, 0, sizeof(p)); + XMEMSET(g, 0, sizeof(g)); + XMEMSET(bigP, 0, sizeof(bigP)); + p[0] = 0x01; + g[0] = 0x02; + bigP[0] = 0x01; + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + /* --- 6024/6311:0/1 wolfSSL_CTX_SetTmpDH: ctx/p/g NULL guards ---------- */ + (void)wolfSSL_CTX_SetTmpDH(NULL, p, (int)sizeof(p), g, (int)sizeof(g)); + (void)wolfSSL_CTX_SetTmpDH(ctx, NULL, 0, g, (int)sizeof(g)); + (void)wolfSSL_CTX_SetTmpDH(ctx, p, (int)sizeof(p), NULL, 0); + + /* --- 5848:0/1 wolfSSL_SetTmpDH: pSz < minDhKeySz || > maxDhKeySz ----- */ + (void)wolfSSL_SetTmpDH(ssl, p, 1, g, (int)sizeof(g)); + (void)wolfSSL_SetTmpDH(ssl, bigP, (int)sizeof(bigP), g, (int)sizeof(g)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_dh.h b/tests/api/test_dh.h index 3aa50855eeb..c78797b038f 100644 --- a/tests/api/test_dh.h +++ b/tests/api/test_dh.h @@ -38,6 +38,7 @@ int test_wc_DhCheckPrivKey(void); int test_wc_DhCheckKeyPair(void); int test_wc_DhGenerateParams_and_ExportRaw(void); int test_wc_DhGenerateKeyPair_CheckDhLN(void); +int test_dh_decision_coverage(void); #define TEST_DH_DECLS \ TEST_DECL_GROUP("dh", test_wc_DhPublicKeyDecode), \ @@ -53,6 +54,7 @@ int test_wc_DhGenerateKeyPair_CheckDhLN(void); TEST_DECL_GROUP("dh", test_wc_DhCheckPrivKey), \ TEST_DECL_GROUP("dh", test_wc_DhCheckKeyPair), \ TEST_DECL_GROUP("dh", test_wc_DhGenerateParams_and_ExportRaw), \ - TEST_DECL_GROUP("dh", test_wc_DhGenerateKeyPair_CheckDhLN) + TEST_DECL_GROUP("dh", test_wc_DhGenerateKeyPair_CheckDhLN), \ + TEST_DECL_GROUP("dh", test_dh_decision_coverage) #endif /* WOLFCRYPT_TEST_DH_H */ diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index 6e6474a6905..32d56038cc1 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -8641,6 +8641,7 @@ static int test_dtls_ch_reject_cb(WOLFSSL* ssl, void* ctx) return TEST_DTLS_CH_REJECT_E; } +#if !defined(WOLFSSL_NO_TLS12) struct test_dtls_ch_flip_ctx { int calls; int disableRet; @@ -8657,6 +8658,7 @@ static int test_dtls_ch_flip_cb(WOLFSSL* ssl, void* ctx) flip->enableRet = wolfSSL_enable_cookie(ssl); return 0; } +#endif /* !WOLFSSL_NO_TLS12 */ static int test_dtls_no_cookie_setup(struct test_memio_ctx* io, WOLFSSL_CTX** ctx_c, WOLFSSL_CTX** ctx_s, WOLFSSL** ssl_c, WOLFSSL** ssl_s) diff --git a/tests/api/test_falcon.c b/tests/api/test_falcon.c index 93f79e4c4ab..3b86452a6c5 100644 --- a/tests/api/test_falcon.c +++ b/tests/api/test_falcon.c @@ -1446,6 +1446,15 @@ int test_wc_FalconDecisionCoverage(void) falcon_key key; byte out[64]; word32 outLen; +#if defined(WC_FALCON_HAVE_NATIVE_SIGN) && defined(WC_RNG_SEED_CB) && \ + defined(HAVE_HASHDRBG) && !defined(NO_SHA256) && \ + !defined(HAVE_SELFTEST) && !defined(HAVE_FIPS) && \ + !defined(HAVE_INTEL_RDRAND) && !defined(CUSTOM_RAND_GENERATE_BLOCK) && \ + !defined(WOLFSSL_RNG_USE_FULL_SEED) && defined(WOLFSSL_DRBG_SHA512) + WC_RNG rng; + int i; + int rngInited = 0; +#endif /* Buffers sized for a raw Falcon-512 private/public import so the ret==0 * arm of wc_falcon_import_private_key is reachable without keygen. */ /* Sized for the largest level built; the imports below use the exact size @@ -1579,16 +1588,16 @@ int test_wc_FalconDecisionCoverage(void) * The (!prvKeySet) F + valid-rng fall-through into the native signer is * owned by test_wc_falcon_sign_vfy (real key). */ { - WC_RNG rng; + WC_RNG rng2; byte msg[4]; - XMEMSET(&rng, 0, sizeof(rng)); + XMEMSET(&rng2, 0, sizeof(rng2)); XMEMSET(msg, 0, sizeof(msg)); XMEMSET(&key, 0, sizeof(key)); ExpectIntEQ(wc_falcon_init(&key), 0); ExpectIntEQ(wc_falcon_set_level(&key, falcon_levels[0]), 0); outLen = (word32)sizeof(out); ExpectIntEQ(wc_falcon_sign_msg(msg, (word32)sizeof(msg), out, &outLen, - &key, &rng), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); /* !prvKeySet T */ + &key, &rng2), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); /* !prvKeySet T */ key.prvKeySet = 1; /* !prvKeySet F */ outLen = (word32)sizeof(out); ExpectIntEQ(wc_falcon_sign_msg(msg, (word32)sizeof(msg), out, &outLen, @@ -1683,6 +1692,54 @@ int test_wc_FalconDecisionCoverage(void) WC_NO_ERR_TRACE(BUFFER_E)); } #endif /* WOLF_PRIVATE_KEY_ID */ + + /* ---- wc_falcon_make_key: (f[u] >= lim || f[u] <= -lim || + * g[u] >= lim || g[u] <= -lim) COMP_TRIM encodability check ----- + * Each keygen attempt samples (f,g) and rejects out-of-range + * coefficients; the deterministic seed callback makes the attempt + * stream reproducible, and enough attempts fire each bound operand. */ +#if defined(WC_FALCON_HAVE_NATIVE_SIGN) && defined(WC_RNG_SEED_CB) && \ + defined(HAVE_HASHDRBG) && !defined(NO_SHA256) && \ + !defined(HAVE_SELFTEST) && !defined(HAVE_FIPS) && \ + !defined(HAVE_INTEL_RDRAND) && !defined(CUSTOM_RAND_GENERATE_BLOCK) && \ + !defined(WOLFSSL_RNG_USE_FULL_SEED) && defined(WOLFSSL_DRBG_SHA512) + XMEMSET(&rng, 0, sizeof(rng)); + DoExpectIntEQ(wc_SetSeed_Cb(falcon_det_seed_cb), 0); + if (wc_InitRng(&rng) == 0) { + rngInited = 1; + } + DoExpectIntEQ(rngInited, 1); + for (i = 0; i < 64; i++) { + XMEMSET(&key, 0, sizeof(key)); + ExpectIntEQ(wc_falcon_init(&key), 0); + ExpectIntEQ(wc_falcon_set_level(&key, falcon_levels[0]), 0); + ExpectIntEQ(wc_falcon_make_key(&key, &rng), 0); + wc_falcon_free(&key); + } + if (rngInited) { + DoExpectIntEQ(wc_FreeRng(&rng), 0); + } + DoExpectIntEQ(wc_SetSeed_Cb(WC_GENERATE_SEED_DEFAULT), 0); +#endif /* det seed-cb guard */ + + /* ---- wc_Falcon_KeyToDer / wc_Falcon_PrivateKeyToDer: + * (key == NULL) || !falcon_level_ok(key) ------------------------ + * key==NULL is owned by the error-paths test; here the level operand is + * flipped: a fresh init (level 0) fails falcon_level_ok in the guard, + * a valid level passes the guard and fails later on the unset key + * material (same code, different decision outcome). */ + XMEMSET(&key, 0, sizeof(key)); + ExpectIntEQ(wc_falcon_init(&key), 0); + ExpectIntEQ(wc_Falcon_KeyToDer(&key, out, (word32)sizeof(out)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_Falcon_PrivateKeyToDer(&key, out, (word32)sizeof(out)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_falcon_set_level(&key, falcon_levels[0]), 0); + ExpectIntEQ(wc_Falcon_KeyToDer(&key, out, (word32)sizeof(out)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_Falcon_PrivateKeyToDer(&key, out, (word32)sizeof(out)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + wc_falcon_free(&key); #endif /* HAVE_FALCON */ return EXPECT_RESULT(); } @@ -1696,7 +1753,7 @@ int test_wc_FalconDecisionCoverage(void) #ifdef TEST_FALCON_CB_FREE /* What the free callback saw, so the test can check the contract rather than * just that something fired. */ -typedef struct { +typedef struct FalconCbFreeCtx { int frees; /* matching free callbacks seen */ int badObj; /* callback was handed the wrong object */ int wiped; /* callback saw a key already cleaned up */ diff --git a/tests/api/test_kdf.c b/tests/api/test_kdf.c index 68a09be92d7..24b898d5190 100644 --- a/tests/api/test_kdf.c +++ b/tests/api/test_kdf.c @@ -41,6 +41,9 @@ #ifdef WOLF_CRYPTO_CB #include #endif +#ifndef NO_DH + #include +#endif #include #include @@ -55,7 +58,8 @@ /* ------------------------------------------------------------------ */ /* WOLF_CRYPTO_CB support for wc_KDA_KDF_twostep_cmac's dispatch guard */ /* ------------------------------------------------------------------ */ -#if defined(HAVE_CMAC_KDF) && defined(WOLF_CRYPTO_CB) +#if defined(HAVE_CMAC_KDF) && defined(WOLF_CRYPTO_CB) && \ + defined(WOLFSSL_USER_SETTINGS) #define TEST_KDF_CRYPTOCB_DEVID 0x4b444630 /* "KDF0" */ /* Toggled by the test below: when set, the callback fails outright instead @@ -83,7 +87,7 @@ static int test_kdf_cryptocb(int cbDevId, wc_CryptoInfo* info, void* ctx) } return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); } -#endif /* HAVE_CMAC_KDF && WOLF_CRYPTO_CB */ +#endif /* HAVE_CMAC_KDF && WOLF_CRYPTO_CB && WOLFSSL_USER_SETTINGS */ /* * MC/DC decision coverage: negative / argument-check / bad-selector / @@ -631,7 +635,7 @@ int test_wc_KdfDecisionCoverage(void) sizeof(z), fixedInfo, sizeof(fixedInfo), output, sizeof(output), HEAP_HINT, INVALID_DEVID), 0); -#if defined(WOLF_CRYPTO_CB) +#if defined(WOLF_CRYPTO_CB) && defined(WOLFSSL_USER_SETTINGS) /* devId != INVALID_DEVID: dispatch taken. Independence pair for * the CRYPTOCB_UNAVAILABLE fall-through guard: succeeds, then fails * outright, both via the SAME registered devId. */ @@ -651,7 +655,7 @@ int test_wc_KdfDecisionCoverage(void) test_kdf_cryptocb_force_fail = 0; wc_CryptoCb_UnRegisterDevice(TEST_KDF_CRYPTOCB_DEVID); -#endif /* WOLF_CRYPTO_CB */ +#endif /* WOLF_CRYPTO_CB && WOLFSSL_USER_SETTINGS */ } #endif /* HAVE_CMAC_KDF && WOLFSSL_AES_128 */ @@ -929,3 +933,119 @@ int test_wc_KdfFeatureCoverage(void) #endif /* !HAVE_FIPS && !HAVE_SELFTEST */ return EXPECT_RESULT(); } /* END test_wc_KdfFeatureCoverage */ + +/* + * MC/DC decision coverage for the wolfcrypt/src/cryptocb.c dispatch guards: + * each wc_CryptoCb_* operation starts with + * dev = wc_CryptoCb_FindDevice(devId, algoType); + * if (dev && dev->cb) { ... } + * The four decisions below had never seen a registered device, so both + * operands were uncovered. Three calls per site: + * - an unregistered devId: dev == NULL (operand 0 false) + * - a registered device with a real, declining callback: both operands + * true, the body runs and the callback declines + * - a registered device with a NULL callback: operand 1 false + * Every call returns CRYPTOCB_UNAVAILABLE in all three rows. + */ +#if defined(WOLF_CRYPTO_CB) && defined(WOLFSSL_USER_SETTINGS) +#define TEST_CRYPTOCB_DISPATCH_DEVID 0x44495350 /* "DISP" */ + +static int test_cryptocb_dispatch_decline_cb(int cbDevId, wc_CryptoInfo* info, + void* ctx) +{ + (void)cbDevId; + (void)info; + (void)ctx; + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); +} +#endif /* WOLF_CRYPTO_CB && WOLFSSL_USER_SETTINGS */ + +int test_wc_CryptoCbDispatchDecisionCoverage(void) +{ + EXPECT_DECLS; +#if defined(WOLF_CRYPTO_CB) && defined(WOLFSSL_USER_SETTINGS) + byte in[8] = {0}; + byte out[16] = {0}; + byte tag[16] = {0}; + byte passwd[8] = {0}; + byte salt[8] = {0}; + word32 agreeSz = 0; + +#ifndef NO_DH + DhKey dh; + XMEMSET(&dh, 0, sizeof(dh)); + + /* --- wc_CryptoCb_Dh: (dev) && (dev->cb). --- */ + dh.devId = TEST_CRYPTOCB_DISPATCH_DEVID + 1; /* unregistered slot */ + agreeSz = sizeof(out); + ExpectIntEQ(wc_CryptoCb_Dh(&dh, in, 1, in, 1, out, &agreeSz), + WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)); + dh.devId = TEST_CRYPTOCB_DISPATCH_DEVID; + ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_CRYPTOCB_DISPATCH_DEVID, + test_cryptocb_dispatch_decline_cb, NULL), 0); + agreeSz = sizeof(out); + ExpectIntEQ(wc_CryptoCb_Dh(&dh, in, 1, in, 1, out, &agreeSz), + WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)); + wc_CryptoCb_UnRegisterDevice(TEST_CRYPTOCB_DISPATCH_DEVID); + ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_CRYPTOCB_DISPATCH_DEVID, + NULL, NULL), 0); + agreeSz = sizeof(out); + ExpectIntEQ(wc_CryptoCb_Dh(&dh, in, 1, in, 1, out, &agreeSz), + WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)); + wc_CryptoCb_UnRegisterDevice(TEST_CRYPTOCB_DISPATCH_DEVID); +#endif /* !NO_DH */ + +#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305) + /* --- wc_CryptoCb_Chacha20Poly1305Encrypt / Decrypt: (dev) && (dev->cb). + * The buffers are never dereferenced: the declining callback and the + * skipped body both leave them untouched. --- */ + ExpectIntEQ(wc_CryptoCb_Chacha20Poly1305Encrypt( + TEST_CRYPTOCB_DISPATCH_DEVID + 1, in, in, NULL, 0, in, sizeof(in), + out, tag), WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)); + ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_CRYPTOCB_DISPATCH_DEVID, + test_cryptocb_dispatch_decline_cb, NULL), 0); + ExpectIntEQ(wc_CryptoCb_Chacha20Poly1305Encrypt( + TEST_CRYPTOCB_DISPATCH_DEVID, in, in, NULL, 0, in, sizeof(in), + out, tag), WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)); + ExpectIntEQ(wc_CryptoCb_Chacha20Poly1305Decrypt( + TEST_CRYPTOCB_DISPATCH_DEVID, in, in, NULL, 0, in, sizeof(in), + tag, out), WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)); + wc_CryptoCb_UnRegisterDevice(TEST_CRYPTOCB_DISPATCH_DEVID); + ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_CRYPTOCB_DISPATCH_DEVID, + NULL, NULL), 0); + ExpectIntEQ(wc_CryptoCb_Chacha20Poly1305Encrypt( + TEST_CRYPTOCB_DISPATCH_DEVID, in, in, NULL, 0, in, sizeof(in), + out, tag), WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)); + ExpectIntEQ(wc_CryptoCb_Chacha20Poly1305Decrypt( + TEST_CRYPTOCB_DISPATCH_DEVID, in, in, NULL, 0, in, sizeof(in), + tag, out), WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)); + wc_CryptoCb_UnRegisterDevice(TEST_CRYPTOCB_DISPATCH_DEVID); +#endif /* HAVE_CHACHA && HAVE_POLY1305 */ + +#if defined(HAVE_PBKDF2) && !defined(NO_HMAC) && !defined(NO_PWDBASED) + /* --- wc_CryptoCb_Pbkdf2: (dev) && (dev->cb). --- */ + ExpectIntEQ(wc_CryptoCb_Pbkdf2(out, passwd, (int)sizeof(passwd), salt, + (int)sizeof(salt), 1000, 32, WC_HASH_TYPE_SHA256, + TEST_CRYPTOCB_DISPATCH_DEVID + 1), + WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)); + ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_CRYPTOCB_DISPATCH_DEVID, + test_cryptocb_dispatch_decline_cb, NULL), 0); + ExpectIntEQ(wc_CryptoCb_Pbkdf2(out, passwd, (int)sizeof(passwd), salt, + (int)sizeof(salt), 1000, 32, WC_HASH_TYPE_SHA256, + TEST_CRYPTOCB_DISPATCH_DEVID), + WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)); + wc_CryptoCb_UnRegisterDevice(TEST_CRYPTOCB_DISPATCH_DEVID); + ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_CRYPTOCB_DISPATCH_DEVID, + NULL, NULL), 0); + ExpectIntEQ(wc_CryptoCb_Pbkdf2(out, passwd, (int)sizeof(passwd), salt, + (int)sizeof(salt), 1000, 32, WC_HASH_TYPE_SHA256, + TEST_CRYPTOCB_DISPATCH_DEVID), + WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)); + wc_CryptoCb_UnRegisterDevice(TEST_CRYPTOCB_DISPATCH_DEVID); +#endif /* HAVE_PBKDF2 && !NO_HMAC && !NO_PWDBASED */ + + (void)out; + (void)tag; +#endif /* WOLF_CRYPTO_CB && WOLFSSL_USER_SETTINGS */ + return EXPECT_RESULT(); +} /* END test_wc_CryptoCbDispatchDecisionCoverage */ diff --git a/tests/api/test_kdf.h b/tests/api/test_kdf.h index 3adf4ce0b6f..8f2b43c8015 100644 --- a/tests/api/test_kdf.h +++ b/tests/api/test_kdf.h @@ -26,9 +26,11 @@ int test_wc_KdfDecisionCoverage(void); int test_wc_KdfFeatureCoverage(void); +int test_wc_CryptoCbDispatchDecisionCoverage(void); #define TEST_KDF_DECLS \ TEST_DECL_GROUP("kdf", test_wc_KdfDecisionCoverage), \ - TEST_DECL_GROUP("kdf", test_wc_KdfFeatureCoverage) + TEST_DECL_GROUP("kdf", test_wc_KdfFeatureCoverage), \ + TEST_DECL_GROUP("kdf", test_wc_CryptoCbDispatchDecisionCoverage) #endif /* WOLFCRYPT_TEST_KDF_H */ diff --git a/tests/api/test_mldsa.c b/tests/api/test_mldsa.c index ec00aae4bc1..343582fb13f 100644 --- a/tests/api/test_mldsa.c +++ b/tests/api/test_mldsa.c @@ -31445,6 +31445,539 @@ int test_wc_MldsaDerDecisionCoverage(void) return EXPECT_RESULT(); } +#if defined(WOLFSSL_HAVE_MLDSA) && defined(WOLFSSL_MLDSA_PUBLIC_KEY) && \ + defined(WOLFSSL_MLDSA_PRIVATE_KEY) && \ + !defined(WOLF_CRYPTO_CB_ONLY_MLDSA) && \ + !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLDSA_NO_SIGN) && !defined(WOLFSSL_MLDSA_NO_VERIFY) +/* DER length: short form, or 0x82 two-byte long form. Every crafted + * buffer below stays under 64 KiB, so two length bytes always suffice. */ +static word32 mldsa_test_der_len_sz(word32 len) +{ + if (len < 0x80) { + return 1; + } + return 3; +} + +static word32 mldsa_test_der_put_len(byte* p, word32 len) +{ + if (len < 0x80) { + p[0] = (byte)len; + return 1; + } + p[0] = 0x82; + p[1] = (byte)(len >> 8); + p[2] = (byte)len; + return 3; +} + +#if defined(WOLFSSL_MLDSA_NO_ASN1) +/* SEQUENCE { SEQUENCE { OID } BIT STRING }. BIT STRING content is the + * unused-bits byte followed by the raw public key. */ +static word32 mldsa_test_build_pub_der(byte* buf, const byte* oid, + word32 oidLen, const byte* pub, word32 pubLen) +{ + word32 bitSz = 1 + pubLen; + word32 bitTlv = 1 + mldsa_test_der_len_sz(bitSz) + bitSz; + word32 innerContent = 1 + mldsa_test_der_len_sz(oidLen) + oidLen + + bitTlv; + word32 innerTlv = 1 + mldsa_test_der_len_sz(innerContent) + innerContent; + byte* p = buf; + + *p++ = 0x30; + p += mldsa_test_der_put_len(p, innerTlv); + *p++ = 0x30; + p += mldsa_test_der_put_len(p, innerContent); + *p++ = 0x06; + p += mldsa_test_der_put_len(p, oidLen); + XMEMCPY(p, oid, oidLen); + p += oidLen; + *p++ = 0x03; + p += mldsa_test_der_put_len(p, bitSz); + *p++ = 0x00; + XMEMCPY(p, pub, pubLen); + p += pubLen; + + return (word32)(p - buf); +} +#endif /* WOLFSSL_MLDSA_NO_ASN1 */ + +#if !defined(WOLFSSL_MLDSA_NO_ASN1) +/* SEQUENCE of INTEGER 0, a SEQUENCE of OID, and nested OCTET STRINGs + * wrapping the private key. Private key only: no [1] public key + * component. */ +static word32 mldsa_test_build_priv_der(byte* buf, const byte* oid, + word32 oidLen, const byte* priv, word32 privLen) +{ + word32 innerOctetTlv = 1 + mldsa_test_der_len_sz(privLen) + privLen; + word32 outerOctetTlv = 1 + mldsa_test_der_len_sz(innerOctetTlv) + + innerOctetTlv; + word32 algoTlv = 1 + mldsa_test_der_len_sz(2 + oidLen) + 2 + oidLen; + /* INTEGER 0 (version) is 3 bytes: 0x02 0x01 0x00. */ + word32 outerContent = 3 + algoTlv + outerOctetTlv; + byte* p = buf; + + *p++ = 0x30; + p += mldsa_test_der_put_len(p, outerContent); + *p++ = 0x02; + *p++ = 0x01; + *p++ = 0x00; + *p++ = 0x30; + p += mldsa_test_der_put_len(p, 2 + oidLen); + *p++ = 0x06; + p += mldsa_test_der_put_len(p, oidLen); + XMEMCPY(p, oid, oidLen); + p += oidLen; + *p++ = 0x04; + p += mldsa_test_der_put_len(p, innerOctetTlv); + *p++ = 0x04; + p += mldsa_test_der_put_len(p, privLen); + XMEMCPY(p, priv, privLen); + p += privLen; + + return (word32)(p - buf); +} +#endif /* !WOLFSSL_MLDSA_NO_ASN1 */ + +/* One keygen + sign + verify round trip, plus a verify of the signature + * with its z region corrupted (16 bytes of 0xFF past the commit push the + * decoded z coefficients above the gamma1 check bound). Returns 0 on + * success, negative on any mismatch. */ +static int mldsa_test_round_trip(WC_RNG* rng, byte level) +{ + static const byte msg[4] = { 'M', 'C', 'D', 'C' }; + wc_MlDsaKey key; + byte sig[MLDSA_MAX_SIG_SIZE]; + word32 sigLen; + int res = 0; + int ret; + + XMEMSET(&key, 0, sizeof(key)); + XMEMSET(sig, 0, sizeof(sig)); + + ret = wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_MlDsaKey_SetParams(&key, level); + } + if (ret == 0) { + ret = wc_MlDsaKey_MakeKey(&key, rng); + } + if (ret == 0) { + sigLen = (word32)sizeof(sig); + ret = wc_MlDsaKey_SignCtx(&key, NULL, 0, sig, &sigLen, msg, + (word32)sizeof(msg), rng); + } + if (ret == 0) { + res = 1; + ret = wc_MlDsaKey_VerifyCtx(&key, sig, sigLen, NULL, 0, msg, + (word32)sizeof(msg), &res); + if (ret == 0 && res != 1) { + ret = -1234; /* good signature must verify */ + } + } + if (ret == 0) { + XMEMSET(sig + MLDSA_TR_SZ + 8, 0xFF, 16); + res = 0; + ret = wc_MlDsaKey_VerifyCtx(&key, sig, sigLen, NULL, 0, msg, + (word32)sizeof(msg), &res); + if (ret == 0 && res != 0) { + ret = -1235; /* corrupted signature must not verify */ + } + } + wc_MlDsaKey_Free(&key); + + return ret; +} + +/* CheckKey on a generated key pair (13612:0/13612:1 pairs: the valid key + * gives (T,F) of the x != 0 check, the t1-corrupted key gives (T,T)), + * plus the import-side s1 range validation (ImportPrivRaw rejects an + * out-of-range s1 before CheckKey ever sees it). s1Corrupt is the first + * s1 encoded byte: 0x07 for eta 2 (s1[0] = 2 - 7 = -5), 0x0F for eta 4 + * (s1[0] = 4 - 15 = -11). Returns 0 on success. */ +static int mldsa_test_check_key_corrupt(WC_RNG* rng, byte level, + byte s1Corrupt) +{ + wc_MlDsaKey key; + byte priv[MLDSA_MAX_KEY_SIZE]; + byte pub[MLDSA_MAX_PUB_KEY_SIZE]; + word32 privLen; + word32 pubLen; + word32 s1Off = MLDSA_PUB_SEED_SZ + MLDSA_K_SZ + MLDSA_TR_SZ; + byte s1Byte; + int ret; + + XMEMSET(&key, 0, sizeof(key)); + XMEMSET(priv, 0, sizeof(priv)); + XMEMSET(pub, 0, sizeof(pub)); + + ret = wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_MlDsaKey_SetParams(&key, level); + } + if (ret == 0) { + ret = wc_MlDsaKey_MakeKey(&key, rng); + } + if (ret == 0) { + privLen = (word32)sizeof(priv); + ret = wc_MlDsaKey_ExportPrivRaw(&key, priv, &privLen); + } + if (ret == 0) { + pubLen = (word32)sizeof(pub); + ret = wc_MlDsaKey_ExportPubRaw(&key, pub, &pubLen); + } + if (ret == 0) { + /* Valid pair: recomputed t1 matches the stored t1. */ + ret = wc_MlDsaKey_CheckKey(&key); + } + + s1Byte = priv[s1Off]; + if (ret == 0) { + /* Out-of-range s1: the private key import validates the + * coefficient range and rejects the key. */ + priv[s1Off] = s1Corrupt; + wc_MlDsaKey_Free(&key); + XMEMSET(&key, 0, sizeof(key)); + ret = wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_MlDsaKey_SetParams(&key, level); + } + if (ret == 0) { + ret = wc_MlDsaKey_ImportPrivRaw(&key, priv, privLen); + if (ret == WC_NO_ERR_TRACE(PUBLIC_KEY_E)) { + ret = 0; /* expected rejection; continue */ + } + else if (ret != 0) { + ret = -1236; /* out-of-range s1 must be rejected */ + } + } + } + + if (ret == 0) { + priv[s1Off] = s1Byte; + pub[MLDSA_PUB_SEED_SZ] ^= 0xFF; + wc_MlDsaKey_Free(&key); + XMEMSET(&key, 0, sizeof(key)); + ret = wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_MlDsaKey_SetParams(&key, level); + } + if (ret == 0) { + ret = wc_MlDsaKey_ImportPrivRaw(&key, priv, privLen); + } + if (ret == 0) { + ret = wc_MlDsaKey_ImportPubRaw(&key, pub, pubLen); + } + if (ret == 0) { + ret = wc_MlDsaKey_CheckKey(&key); + if (ret == WC_NO_ERR_TRACE(PUBLIC_KEY_E)) { + ret = 0; /* expected failure; continue */ + } + else if (ret != 0) { + ret = -1237; /* t1 mismatch must fail the check */ + } + } + } + wc_MlDsaKey_Free(&key); + + return ret; +} +#endif /* WOLFSSL_HAVE_MLDSA && WOLFSSL_MLDSA_PUBLIC_KEY && ... */ + +/* ===================================================================== + * MC/DC coverage supplement for the 2026-10 wc_mldsa.c rework (PR + * #11214 cbonly-mldsa plus follow-ups). The rework added the AVX2 + * matrix/secret samplers (one independent SHAKE stream per polynomial + * group), the s1Len-dispatched secret expansion, the hand-rolled DER + * length/OID parsers (WOLFSSL_MLDSA_NO_ASN1), and the CheckKey + * coefficient range checks. This function drives the public API to the + * newly uncovered decision sides: + * + * - 32 keygen + sign + verify round trips per parameter set: the + * rejection-sampling loops only observe each "only polynomial i + * incomplete" state when that polynomial is the last to complete in + * its group (~1/4 per group), so the repetition is what covers them. + * Also reaches the s1Len dispatch arms of mldsa_expand_s (AVX2 + * build). + * - Corrupted-signature verifies: the gamma1 range check fails, giving + * the valid == 0 side of the verify (ret == 0) && valid gates. + * - CheckKey on corrupted keys: the ret != 0 side of the + * (ret == 0) && (x != 0) mismatch check, and the x != 0 side. + * - Crafted public key DERs (hand-rolled parser build only): per-level + * OIDs, an 8-byte OID, a corrupted 9-byte OID, 0x80/0x83 length + * forms, a non-minimal 0x82 length, and a length overflow. + * - Crafted private key DERs: private-only encodings in the full + * (private + public) and private-only forms. + * ===================================================================== */ +int test_wc_MldsaReworkDecisionCoverage(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_HAVE_MLDSA) && defined(WOLFSSL_MLDSA_PUBLIC_KEY) && \ + defined(WOLFSSL_MLDSA_PRIVATE_KEY) && \ + !defined(WOLF_CRYPTO_CB_ONLY_MLDSA) && \ + !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLDSA_NO_SIGN) && !defined(WOLFSSL_MLDSA_NO_VERIFY) + WC_RNG rng; + int rngInited = 0; + int i; + + XMEMSET(&rng, 0, sizeof(rng)); + ExpectIntEQ(wc_InitRng(&rng), 0); + if (EXPECT_SUCCESS()) { + rngInited = 1; + } + + /* Keygen + sign + verify round trips. The per-iteration corrupted + * verify carries the valid == 0 side of the verify gates; the + * repetition carries the sampler "last polynomial to complete" + * sides. */ +#ifndef WOLFSSL_NO_ML_DSA_44 + for (i = 0; i < 32; i++) { + ExpectIntEQ(mldsa_test_round_trip(&rng, WC_ML_DSA_44), 0); + } +#endif +#ifndef WOLFSSL_NO_ML_DSA_65 + for (i = 0; i < 32; i++) { + ExpectIntEQ(mldsa_test_round_trip(&rng, WC_ML_DSA_65), 0); + } +#endif +#ifndef WOLFSSL_NO_ML_DSA_87 + for (i = 0; i < 32; i++) { + ExpectIntEQ(mldsa_test_round_trip(&rng, WC_ML_DSA_87), 0); + } +#endif + + /* CheckKey: valid pair, import-side s1 range check, corrupted t1. */ +#if defined(WOLFSSL_MLDSA_CHECK_KEY) && !defined(WOLFSSL_NO_ML_DSA_44) + ExpectIntEQ(mldsa_test_check_key_corrupt(&rng, WC_ML_DSA_44, 0x07), 0); +#endif +#if defined(WOLFSSL_MLDSA_CHECK_KEY) && !defined(WOLFSSL_NO_ML_DSA_65) + ExpectIntEQ(mldsa_test_check_key_corrupt(&rng, WC_ML_DSA_65, 0x0F), 0); +#endif +#if defined(WOLFSSL_MLDSA_CHECK_KEY) && !defined(WOLFSSL_NO_ML_DSA_87) + ExpectIntEQ(mldsa_test_check_key_corrupt(&rng, WC_ML_DSA_87, 0x07), 0); +#endif + + /* Hand-rolled DER parser (WOLFSSL_MLDSA_NO_ASN1 build): crafted + * public key encodings. The raw public key bytes are not validated + * on import, so zero-filled payloads of the right size suffice. */ +#if defined(WOLFSSL_MLDSA_NO_ASN1) + { + static const byte oid44[] = { + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x11 + }; + static const byte oid65[] = { + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x12 + }; + static const byte oid87[] = { + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x13 + }; + static const byte oid44Bad[] = { + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x99 + }; + byte pub[WC_MLDSA_87_PUB_KEY_SIZE]; + byte der[WC_MLDSA_87_PUB_KEY_SIZE + 64]; + wc_MlDsaKey key; + word32 idx; + word32 derLen; + + XMEMSET(pub, 0, sizeof(pub)); + + XMEMSET(&key, 0, sizeof(key)); + ExpectIntEQ(wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID), 0); + + #ifndef WOLFSSL_NO_ML_DSA_44 + /* Real 44 OID: the level match and the OID table hits. */ + ExpectIntEQ(wc_MlDsaKey_SetParams(&key, WC_ML_DSA_44), 0); + derLen = mldsa_test_build_pub_der(der, oid44, + (word32)sizeof(oid44), pub, WC_MLDSA_44_PUB_KEY_SIZE); + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PublicKeyDecode(&key, der, derLen, &idx), + 0); + wc_MlDsaKey_Free(&key); + XMEMSET(&key, 0, sizeof(key)); + ExpectIntEQ(wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID), 0); + #endif + #ifndef WOLFSSL_NO_ML_DSA_65 + /* Real 65 OID: 44 table miss, 65 table hit. */ + ExpectIntEQ(wc_MlDsaKey_SetParams(&key, WC_ML_DSA_65), 0); + derLen = mldsa_test_build_pub_der(der, oid65, + (word32)sizeof(oid65), pub, WC_MLDSA_65_PUB_KEY_SIZE); + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PublicKeyDecode(&key, der, derLen, &idx), + 0); + wc_MlDsaKey_Free(&key); + XMEMSET(&key, 0, sizeof(key)); + ExpectIntEQ(wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID), 0); + #endif + #ifndef WOLFSSL_NO_ML_DSA_87 + /* Real 87 OID: 44 and 65 table misses, 87 table hit. */ + ExpectIntEQ(wc_MlDsaKey_SetParams(&key, WC_ML_DSA_87), 0); + derLen = mldsa_test_build_pub_der(der, oid87, + (word32)sizeof(oid87), pub, WC_MLDSA_87_PUB_KEY_SIZE); + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PublicKeyDecode(&key, der, derLen, &idx), + 0); + wc_MlDsaKey_Free(&key); + XMEMSET(&key, 0, sizeof(key)); + ExpectIntEQ(wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID), 0); + #endif + + #ifndef WOLFSSL_NO_ML_DSA_44 + /* 8-byte OID: every table length check misses. */ + ExpectIntEQ(wc_MlDsaKey_SetParams(&key, WC_ML_DSA_44), 0); + derLen = mldsa_test_build_pub_der(der, oid44, + (word32)sizeof(oid44) - 1, pub, WC_MLDSA_44_PUB_KEY_SIZE); + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PublicKeyDecode(&key, der, derLen, &idx), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_MlDsaKey_Free(&key); + XMEMSET(&key, 0, sizeof(key)); + ExpectIntEQ(wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID), 0); + + /* Corrupted 9-byte OID: lengths match, contents do not. */ + ExpectIntEQ(wc_MlDsaKey_SetParams(&key, WC_ML_DSA_44), 0); + derLen = mldsa_test_build_pub_der(der, oid44Bad, + (word32)sizeof(oid44Bad), pub, WC_MLDSA_44_PUB_KEY_SIZE); + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PublicKeyDecode(&key, der, derLen, &idx), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_MlDsaKey_Free(&key); + XMEMSET(&key, 0, sizeof(key)); + ExpectIntEQ(wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID), 0); + #endif + + /* Indefinite length form (0x80) in the outer SEQUENCE. */ + der[0] = 0x30; + der[1] = 0x80; + der[2] = 0x30; + der[3] = 0x02; + der[4] = 0x06; + der[5] = 0x01; + der[6] = 0x06; + derLen = 7; + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PublicKeyDecode(&key, der, derLen, &idx), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_MlDsaKey_Free(&key); + XMEMSET(&key, 0, sizeof(key)); + ExpectIntEQ(wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID), 0); + + /* Two-byte long form (0x83) in the outer SEQUENCE. */ + der[0] = 0x30; + der[1] = 0x83; + der[2] = 0x00; + der[3] = 0x02; + der[4] = 0x30; + der[5] = 0x02; + der[6] = 0x06; + der[7] = 0x01; + der[8] = 0x06; + derLen = 9; + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PublicKeyDecode(&key, der, derLen, &idx), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_MlDsaKey_Free(&key); + XMEMSET(&key, 0, sizeof(key)); + ExpectIntEQ(wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID), 0); + + /* Non-minimal 0x82 length: 0x0005 < 0x100 is rejected, and the + * index plus declared length stays inside the buffer. */ + der[0] = 0x30; + der[1] = 0x82; + der[2] = 0x00; + der[3] = 0x05; + derLen = 4; + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PublicKeyDecode(&key, der, derLen, &idx), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_MlDsaKey_Free(&key); + XMEMSET(&key, 0, sizeof(key)); + ExpectIntEQ(wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID), 0); + + /* Declared length runs past the end of the buffer. */ + der[0] = 0x30; + der[1] = 0x82; + der[2] = 0x05; + der[3] = 0x27; + XMEMSET(der + 4, 0xAB, 16); + derLen = 20; + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PublicKeyDecode(&key, der, derLen, &idx), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_MlDsaKey_Free(&key); + } +#endif /* WOLFSSL_MLDSA_NO_ASN1 */ + + /* Private key DER: private-only encodings. The full (private + + * public) form reaches the "public key included in the private key" + * split; the bare private form reaches the private-only import. + * Template-engine path: all variants (the NO_ASN1 build hides the + * PrivateKeyDecode declaration, so this block is excluded there). */ +#if !defined(WOLFSSL_MLDSA_NO_ASN1) + #ifndef WOLFSSL_NO_ML_DSA_44 + { + static const byte oid44b[] = { + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x11 + }; + byte priv[WC_MLDSA_44_KEY_SIZE]; + byte pub[WC_MLDSA_44_PUB_KEY_SIZE]; + byte both[WC_MLDSA_44_PRV_KEY_SIZE]; + byte der[WC_MLDSA_44_PRV_KEY_SIZE + 64]; + wc_MlDsaKey key; + word32 idx; + word32 derLen; + word32 privLen; + word32 pubLen; + + XMEMSET(priv, 0, sizeof(priv)); + XMEMSET(pub, 0, sizeof(pub)); + + XMEMSET(&key, 0, sizeof(key)); + ExpectIntEQ(wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(&key, WC_ML_DSA_44), 0); + ExpectIntEQ(wc_MlDsaKey_MakeKey(&key, &rng), 0); + privLen = (word32)sizeof(priv); + ExpectIntEQ(wc_MlDsaKey_ExportPrivRaw(&key, priv, &privLen), 0); + pubLen = (word32)sizeof(pub); + ExpectIntEQ(wc_MlDsaKey_ExportPubRaw(&key, pub, &pubLen), 0); + + XMEMSET(&key, 0, sizeof(key)); + ExpectIntEQ(wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(&key, WC_ML_DSA_44), 0); + + /* Full form: private + public in one OCTET STRING. */ + XMEMCPY(both, priv, WC_MLDSA_44_KEY_SIZE); + XMEMCPY(both + WC_MLDSA_44_KEY_SIZE, pub, WC_MLDSA_44_PUB_KEY_SIZE); + derLen = mldsa_test_build_priv_der(der, oid44b, + (word32)sizeof(oid44b), both, WC_MLDSA_44_PRV_KEY_SIZE); + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PrivateKeyDecode(&key, der, derLen, &idx), + 0); + wc_MlDsaKey_Free(&key); + + /* Bare form: private key only. */ + XMEMSET(&key, 0, sizeof(key)); + ExpectIntEQ(wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(&key, WC_ML_DSA_44), 0); + derLen = mldsa_test_build_priv_der(der, oid44b, + (word32)sizeof(oid44b), priv, WC_MLDSA_44_KEY_SIZE); + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PrivateKeyDecode(&key, der, derLen, &idx), + 0); + wc_MlDsaKey_Free(&key); + } + #endif /* !WOLFSSL_NO_ML_DSA_44 */ +#endif /* !WOLFSSL_MLDSA_NO_ASN1 */ + if (rngInited) { + DoExpectIntEQ(wc_FreeRng(&rng), 0); + } +#endif /* WOLFSSL_HAVE_MLDSA && WOLFSSL_MLDSA_PUBLIC_KEY && ... */ + + return EXPECT_RESULT(); +} + #if defined(WOLFSSL_HAVE_MLDSA) && defined(WOLF_CRYPTO_CB) && \ defined(WOLF_CRYPTO_CB_FREE) #define TEST_MLDSA_CB_FREE diff --git a/tests/api/test_mldsa.h b/tests/api/test_mldsa.h index 82aa9cf3ac8..049a4106fc8 100644 --- a/tests/api/test_mldsa.h +++ b/tests/api/test_mldsa.h @@ -68,6 +68,7 @@ int test_wc_MldsaFeatureCoverage(void); int test_mldsa_legacy_shim(void); int test_wc_MldsaDecisionCoverage2(void); int test_wc_MldsaDerDecisionCoverage(void); +int test_wc_MldsaReworkDecisionCoverage(void); int test_mldsa_cb_free(void); int test_wc_MlDsaKey_seed_service_indicator(void); @@ -104,6 +105,7 @@ int test_wc_MlDsaKey_SetPrecompA(void); TEST_DECL_GROUP("mldsa", test_wc_MldsaDecisionCoverage2), \ TEST_DECL_GROUP("mldsa", test_wc_MlDsaKey_seed_service_indicator), \ TEST_DECL_GROUP("mldsa", test_wc_MldsaDerDecisionCoverage), \ + TEST_DECL_GROUP("mldsa", test_wc_MldsaReworkDecisionCoverage), \ TEST_DECL_GROUP("mldsa", test_mldsa_cb_free), \ TEST_DECL_GROUP("mldsa", test_wc_MlDsaKey_SetPrecompA) diff --git a/tests/api/test_mlkem.c b/tests/api/test_mlkem.c index 84493f5472b..3ba6454c221 100644 --- a/tests/api/test_mlkem.c +++ b/tests/api/test_mlkem.c @@ -4260,12 +4260,125 @@ int test_wc_MlkemFeatureCoverage(void) return EXPECT_RESULT(); } /* END test_wc_MlkemFeatureCoverage */ +/* DER TLV writer for the hand-crafted ML-KEM private-key shapes (RFC 9935 + * Section 6): a seed-only [0] under the privateKey OCTET STRING, or a + * SEQUENCE carrying both the seed and the expanded key. */ +#if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_NO_ML_KEM) && \ + defined(WC_MLKEM_HAVE_NATIVE) && defined(WOLFSSL_ASN_TEMPLATE) +static word32 test_mlkem_der_tlv(byte* p, byte tag, word32 len) +{ + word32 i = 0; + p[i++] = tag; + if (len < 128) { + p[i++] = (byte)len; + } + else if (len < 256) { + p[i++] = (byte)0x81; + p[i++] = (byte)len; + } + else { + p[i++] = (byte)0x82; + p[i++] = (byte)(len >> 8); + p[i++] = (byte)(len & 0xFF); + } + return i; +} + +/* OneAsymmetricKey header: SEQUENCE { INTEGER 0, SEQUENCE { OID } }. + * Returns the position after the header (16 bytes for the ML-KEM OIDs). */ +static byte* test_mlkem_der_header(byte* p, int t) +{ + p[0] = 0x02; + p[1] = 0x01; + p[2] = 0x00; + p += 3; + p += test_mlkem_der_tlv(p, 0x30, 11); + p += test_mlkem_der_tlv(p, 0x06, 9); + p[0] = 96; p[1] = 134; p[2] = 72; p[3] = 1; + p[4] = 101; p[5] = 3; p[6] = 4; p[7] = 4; + /* Level index: the library OIDs end in 1/2/3 for 512/768/1024. */ + p[8] = (byte)((t == WC_ML_KEM_512) ? 1 : + (t == WC_ML_KEM_768) ? 2 : 3); + p += 9; + return p; +} + +/* OneAsymmetricKey { 0, ML-KEM-, OCTET STRING { [0] { seed } } }. + * extra adds trailing bytes inside the privateKey OCTET STRING so the + * template parse fails after the seed has been reported. */ +static word32 test_mlkem_der_seed_only(byte* der, int t, + const byte* seed, word32 seedLen, int extra) +{ + word32 seedTlvSz = 2 + seedLen; + word32 pkeySz = 2 + seedTlvSz + (word32)extra; + word32 seqSz = 3 + 16 + pkeySz; + byte* p = der; + + p += test_mlkem_der_tlv(p, 0x30, seqSz); + p = test_mlkem_der_header(p, t); + p += test_mlkem_der_tlv(p, 0x04, seedTlvSz + (word32)extra); + p += test_mlkem_der_tlv(p, (byte)(ASN_CONTEXT_SPECIFIC | ASN_PKEY_SEED), + seedLen); + XMEMCPY(p, seed, seedLen); + p += seedLen; + if (extra > 0) { + XMEMSET(p, 0, (word32)extra); + p += (word32)extra; + } + return (word32)(p - der); +} + +/* OneAsymmetricKey { 0, ML-KEM-, OCTET STRING { SEQUENCE { + * OCTET STRING { seed }, OCTET STRING { expanded key } } } }. */ +static word32 test_mlkem_der_both(byte* der, int t, + const byte* seed, word32 seedLen, + const byte* key, word32 keyLen) +{ + word32 keyTlvSz = 3 + keyLen; + word32 bothSz = 5 + 3 + seedLen + keyTlvSz; + word32 pkeySz = 5 + bothSz; + word32 seqSz = 3 + 16 + pkeySz; + byte* p = der; + + p += test_mlkem_der_tlv(p, 0x30, seqSz); + p = test_mlkem_der_header(p, t); + p += test_mlkem_der_tlv(p, 0x04, bothSz); + p += test_mlkem_der_tlv(p, 0x30, 3 + seedLen + keyTlvSz); + p += test_mlkem_der_tlv(p, 0x04, seedLen); + XMEMCPY(p, seed, seedLen); + p += seedLen; + p += test_mlkem_der_tlv(p, 0x04, keyLen); + XMEMCPY(p, key, keyLen); + p += keyLen; + return (word32)(p - der); +} +#endif /* WOLFSSL_HAVE_MLKEM && WOLFSSL_ASN_TEMPLATE */ + int test_wc_MlkemDecisionCoverage(void) { EXPECT_DECLS; #if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_NO_ML_KEM) && \ defined(WC_MLKEM_HAVE_NATIVE) MlKemKey* key = NULL; + MlKemKey* key2 = NULL; + MlKemKey* adopt = NULL; + MlKemKey* fresh = NULL; + MlKemKey unset; + byte der[WC_ML_KEM_MAX_PRIVATE_KEY_SIZE + 256]; + word32 idx = 0; + word32 sz = 0; +#if defined(WOLFSSL_ASN_TEMPLATE) && !defined(WOLFSSL_MLKEM_NO_MAKE_KEY) + MlKemKey* seedKey = NULL; + byte expanded[WC_ML_KEM_MAX_PRIVATE_KEY_SIZE]; + word32 privLen = 0; + word32 derSz = 0; +#endif +#if defined(USE_INTEL_SPEEDUP) && defined(WOLF_CRYPTO_CB) && \ + !defined(WOLFSSL_MLKEM_NO_ASN1) && !defined(WOLFSSL_MLKEM_NO_MAKE_KEY) && \ + !defined(WOLF_CRYPTO_CB_FIND) && !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) && \ + !defined(WOLFSSL_MLKEM_NO_DECAPSULATE) + word32 ctLen = 0; +#endif #ifndef WC_NO_CONSTRUCTORS MlKemKey* newKey = NULL; #endif @@ -4410,9 +4523,181 @@ int test_wc_MlkemDecisionCoverage(void) ExpectIntEQ(wc_MlKemKey_EncodePrivateKey(key, out, sizeof(out)), WC_NO_ERR_TRACE(BAD_STATE_E)); +#if !defined(WOLFSSL_MLKEM_NO_ASN1) + /* --- DER encode/decode decisions: the (ret == 0) operand of the ToDer + * output blocks, the decode NULL rows, and the type-set / adopt-type + * checks. --- */ + XMEMSET(&unset, 0, sizeof(unset)); + XMEMSET(der, 0, sizeof(der)); + + /* ToDer: a key with no type set fails the oid lookup before the + * output blocks, so (ret == 0) is false with output != NULL; the + * populated key below takes the true side. */ + ExpectIntNE(wc_MlKemKey_PublicKeyToDer(&unset, out, sizeof(out), 0), + 0); + ExpectIntNE(wc_MlKemKey_PrivateKeyToDer(&unset, out, sizeof(out)), 0); + + key2 = (MlKemKey*)XMALLOC(sizeof(*key2), NULL, DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(key2); + ExpectIntEQ(wc_MlKemKey_Init(key2, t, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlKemKey_MakeKey(key2, &rng), 0); + + /* Public key DER: the NULL rows, then the valid decode. ToDer returns + * the encoding length on success, so query it first and match it. + * withAlg=1: the SubjectPublicKeyInfo wrapper the decode expects; the + * decode consumes exactly sz bytes, so pass sz, not sizeof(der). */ + sz = wc_MlKemKey_PublicKeyToDer(key2, NULL, 0, 1); + ExpectIntGT(sz, 0); + ExpectIntEQ(wc_MlKemKey_PublicKeyToDer(key2, der, sizeof(der), 1), sz); + idx = 0; + ExpectIntEQ(wc_MlKemKey_PublicKeyDecode(NULL, der, sizeof(der), &idx), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + idx = 0; + ExpectIntEQ(wc_MlKemKey_PublicKeyDecode(key2, NULL, sizeof(der), &idx), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + idx = 0; + ExpectIntEQ(wc_MlKemKey_PublicKeyDecode(key2, der, sizeof(der), NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + fresh = (MlKemKey*)XMALLOC(sizeof(*fresh), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(fresh); + XMEMSET(fresh, 0, sizeof(*fresh)); + ExpectIntEQ(wc_MlKemKey_Init(fresh, WC_ML_KEM_TYPE_UNSET, NULL, + INVALID_DEVID), 0); + idx = 0; + ExpectIntEQ(wc_MlKemKey_PublicKeyDecode(fresh, der, sz, &idx), 0); + wc_MlKemKey_Free(fresh); + XFREE(fresh, NULL, DYNAMIC_TYPE_TMP_BUFFER); + idx = 0; + ExpectIntEQ(wc_MlKemKey_PublicKeyDecode(key2, der, sz, &idx), 0); + + /* Private key DER: the NULL rows, the valid decode (type already + * set, so the adopt check takes its false side), and the adopt: + * an initialized key with no type takes the DER's type. */ + sz = wc_MlKemKey_PrivateKeyToDer(key2, NULL, 0); + ExpectIntGT(sz, 0); + ExpectIntEQ(wc_MlKemKey_PrivateKeyToDer(key2, der, sizeof(der)), sz); + idx = 0; + ExpectIntEQ(wc_MlKemKey_PrivateKeyDecode(NULL, der, sizeof(der), &idx), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + idx = 0; + ExpectIntEQ(wc_MlKemKey_PrivateKeyDecode(key2, NULL, sizeof(der), &idx), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + idx = 0; + ExpectIntEQ(wc_MlKemKey_PrivateKeyDecode(key2, der, sizeof(der), NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + idx = 0; + ExpectIntEQ(wc_MlKemKey_PrivateKeyDecode(key2, der, sz, &idx), 0); + adopt = (MlKemKey*)XMALLOC(sizeof(*adopt), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(adopt); + XMEMSET(adopt, 0, sizeof(*adopt)); + ExpectIntEQ(wc_MlKemKey_Init(adopt, WC_ML_KEM_TYPE_UNSET, NULL, + INVALID_DEVID), 0); + idx = 0; + ExpectIntEQ(wc_MlKemKey_PrivateKeyDecode(adopt, der, sz, &idx), 0); + wc_MlKemKey_Free(adopt); + XFREE(adopt, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif /* !WOLFSSL_MLKEM_NO_ASN1 */ + + /* --- RFC 9935 Section 6 privateKey shapes: seed-only and "both". + * The seed expands through the real keygen so the "both" key agrees + * with its seed. --- */ +#if defined(WOLFSSL_ASN_TEMPLATE) && !defined(WOLFSSL_MLKEM_NO_MAKE_KEY) + seedKey = (MlKemKey*)XMALLOC(sizeof(*seedKey), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(seedKey); + ExpectIntEQ(wc_MlKemKey_Init(seedKey, t, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlKemKey_MakeKeyWithRandom(seedKey, rndMk, + sizeof(rndMk)), SEED_OK); + ExpectIntEQ(wc_MlKemKey_PrivateKeySize(seedKey, &privLen), 0); + ExpectIntEQ(wc_MlKemKey_EncodePrivateKey(seedKey, expanded, privLen), + 0); + + adopt = (MlKemKey*)XMALLOC(sizeof(*adopt), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(adopt); + + /* Seed-only shape. Library bug (canary): the ASN template parser + * rejects the RFC 9935 seed-only shape with ASN_PARSE_E even on a + * valid DER, so the (ret == 0) && (seed != NULL) true side stays + * uncovered until the parser is fixed; expect the failure. */ + derSz = test_mlkem_der_seed_only(der, t, rndMk, sizeof(rndMk), 0); + XMEMSET(adopt, 0, sizeof(*adopt)); + ExpectIntEQ(wc_MlKemKey_Init(adopt, t, NULL, INVALID_DEVID), 0); + idx = 0; + ExpectIntNE(wc_MlKemKey_PrivateKeyDecode(adopt, der, derSz, &idx), 0); + wc_MlKemKey_Free(adopt); + + /* Seed-only shape with a trailing byte inside the privateKey OCTET + * STRING: the template parse fails after the seed is reported, so + * (ret == 0) is false with seed != NULL. */ + derSz = test_mlkem_der_seed_only(der, t, rndMk, sizeof(rndMk), 1); + XMEMSET(adopt, 0, sizeof(*adopt)); + ExpectIntEQ(wc_MlKemKey_Init(adopt, t, NULL, INVALID_DEVID), 0); + idx = 0; + ExpectIntNE(wc_MlKemKey_PrivateKeyDecode(adopt, der, derSz, &idx), 0); + wc_MlKemKey_Free(adopt); + + /* "Both" shape, seed and expanded key agree. Library bug (canary): + * the parser rejects this shape too (ASN_PARSE_E), so the + * (expandedLen != privKeyLen) false side stays uncovered until + * fixed; expect the failure. */ + derSz = test_mlkem_der_both(der, t, rndMk, sizeof(rndMk), + expanded, privLen); + XMEMSET(adopt, 0, sizeof(*adopt)); + ExpectIntEQ(wc_MlKemKey_Init(adopt, t, NULL, INVALID_DEVID), 0); + idx = 0; + ExpectIntNE(wc_MlKemKey_PrivateKeyDecode(adopt, der, derSz, &idx), 0); + wc_MlKemKey_Free(adopt); + + /* "Both" shape with the expanded key one byte short of the key's + * size: (ret == 0) && (expandedLen != privKeyLen) true side. */ + derSz = test_mlkem_der_both(der, t, rndMk, sizeof(rndMk), + expanded, privLen - 1); + XMEMSET(adopt, 0, sizeof(*adopt)); + ExpectIntEQ(wc_MlKemKey_Init(adopt, t, NULL, INVALID_DEVID), 0); + idx = 0; + ExpectIntEQ(wc_MlKemKey_PrivateKeyDecode(adopt, der, derSz, &idx), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_MlKemKey_Free(adopt); + + /* "Both" shape with a short seed and the short key: the seed block + * fails first, so (ret == 0) is false with the mismatch held. */ + derSz = test_mlkem_der_both(der, t, rndMk, 32, expanded, privLen - 1); + XMEMSET(adopt, 0, sizeof(*adopt)); + ExpectIntEQ(wc_MlKemKey_Init(adopt, t, NULL, INVALID_DEVID), 0); + idx = 0; + ExpectIntEQ(wc_MlKemKey_PrivateKeyDecode(adopt, der, derSz, &idx), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_MlKemKey_Free(adopt); + XFREE(adopt, NULL, DYNAMIC_TYPE_TMP_BUFFER); + + wc_MlKemKey_Free(seedKey); + XFREE(seedKey, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif /* WOLFSSL_ASN_TEMPLATE && !WOLFSSL_MLKEM_NO_MAKE_KEY */ + + /* --- mlkem_derive_secret: (ret == 0) && MLKEM_PRF_SW_ONLY(prf). The SW + * decaps shows the true side; a prf with a non-default devId shows + * the false side (the SW-only fast path is skipped and the software + * sponge runs instead). --- */ +#if defined(USE_INTEL_SPEEDUP) && defined(WOLF_CRYPTO_CB) && \ + !defined(WOLFSSL_MLKEM_NO_ASN1) && !defined(WOLFSSL_MLKEM_NO_MAKE_KEY) && \ + !defined(WOLF_CRYPTO_CB_FIND) && !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) && \ + !defined(WOLFSSL_MLKEM_NO_DECAPSULATE) + ExpectIntEQ(wc_MlKemKey_CipherTextSize(key2, &ctLen), 0); + ExpectIntEQ(wc_MlKemKey_Encapsulate(key2, ct, ss, &rng), 0); + ExpectIntEQ(wc_MlKemKey_Decapsulate(key2, ss, ct, ctLen), 0); + key2->prf.devId = 0; + ExpectIntEQ(wc_MlKemKey_Decapsulate(key2, ss, ct, ctLen), 0); + key2->prf.devId = INVALID_DEVID; +#endif + DoExpectIntEQ(wc_FreeRng(&rng), 0); wc_MlKemKey_Free(key); XFREE(key, NULL, DYNAMIC_TYPE_TMP_BUFFER); + wc_MlKemKey_Free(key2); + XFREE(key2, NULL, DYNAMIC_TYPE_TMP_BUFFER); /* Silence -Werror unused warnings when make-key / encapsulate / decapsulate * are individually compiled out (the buffers are used only in those @@ -4421,6 +4706,12 @@ int test_wc_MlkemDecisionCoverage(void) (void)ss; (void)rndMk; (void)rndEnc; + (void)der; + (void)idx; + (void)sz; + (void)fresh; + (void)adopt; + (void)unset; #endif return EXPECT_RESULT(); } /* END test_wc_MlkemDecisionCoverage */ diff --git a/tests/api/test_pkcs7.c b/tests/api/test_pkcs7.c index 33edd70075f..bd0ad64c96e 100644 --- a/tests/api/test_pkcs7.c +++ b/tests/api/test_pkcs7.c @@ -9027,3 +9027,88 @@ int test_wc_PKCS7_VerifySignedData_NoDigestParams(void) #endif /* HAVE_PKCS7 && !NO_RSA && !NO_SHA256 && USE_CERT_BUFFERS_2048 */ return EXPECT_RESULT(); } + +/* + * MC/DC decision coverage for the explicit-SKID parse in the SignedData + * signerInfo decode (wolfcrypt/src/pkcs7.c): the [0] constructed SKID + * marker is driven with truncations and one corruption of the explicit + * fixture. The (ret == 0) operands of the idx+1/inSz and GetASNTag checks + * take their false side from the first truncation; the inSz check's true + * side from the second; the inner GetLength's false side from the tag + * corruption. + */ +int test_wc_PKCS7_SkidParseDecisionCoverage(void) +{ + EXPECT_DECLS; +#if defined(HAVE_PKCS7) && !defined(NO_RSA) && !defined(NO_SHA256) + PKCS7* pkcs7 = NULL; + byte msg[sizeof(explicitSKIDSignedData)]; + word32 sz = (word32)sizeof(explicitSKIDSignedData); + /* legacy explicit [0] SKID marker: 0xA0 len 0x04 len */ + static const byte explicitMarker[] = { 0xA0, 0x16, 0x04, 0x14 }; + int marker = -1; + + marker = test_PKCS7_findBytes(explicitSKIDSignedData, (int)sz, + explicitMarker, (int)sizeof(explicitMarker)); + ExpectIntGE(marker, 0); + ExpectIntLE(marker, (int)(sz - sizeof(explicitMarker))); + if (marker < 0 || (word32)marker > sz - sizeof(explicitMarker)) + return EXPECT_RESULT(); + + /* Truncate after the [0] tag: the [0] length read fails, so the + * (ret == 0) operands of the inSz and GetASNTag checks are false. */ + XMEMCPY(msg, explicitSKIDSignedData, (size_t)marker + 1); + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntNE(wc_PKCS7_VerifySignedData(pkcs7, msg, (word32)marker + 1), + 0); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + /* Truncate after the [0] length: idx+1 > inSz fires BUFFER_E. */ + XMEMCPY(msg, explicitSKIDSignedData, (size_t)marker + 2); + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntNE(wc_PKCS7_VerifySignedData(pkcs7, msg, (word32)marker + 2), + 0); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + /* Corrupt the inner OCTET STRING tag: the tag check fails first, so + * the inner GetLength check's (ret == 0) operand is false. */ + XMEMCPY(msg, explicitSKIDSignedData, sz); + msg[marker + 2] = 0x02; + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntNE(wc_PKCS7_VerifySignedData(pkcs7, msg, sz), 0); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; +#endif /* HAVE_PKCS7 && !NO_RSA && !NO_SHA256 */ + return EXPECT_RESULT(); +} + +/* + * MC/DC decision coverage for the SignedData certificates-footer decode + * (wolfcrypt/src/pkcs7.c): a non-NULL footer pointer with a zero footer + * size drives the (pkiMsg2Sz == 0) operand's true side, which falls back + * to the whole message and verifies normally. + */ +int test_wc_PKCS7_CertFooterDecisionCoverage(void) +{ + EXPECT_DECLS; +#if defined(HAVE_PKCS7) && !defined(NO_RSA) && !defined(NO_SHA256) + PKCS7* pkcs7 = NULL; + word32 sz = (word32)sizeof(explicitSKIDSignedData); + + /* Footer supplied with size 0: the (pkiMsg2Sz == 0) operand's true + * side falls back to the whole message, which verifies normally. */ + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntEQ(wc_PKCS7_VerifySignedData_ex(pkcs7, NULL, 0, + (byte*)explicitSKIDSignedData, sz, + (byte*)explicitSKIDSignedData, 0), 0); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; +#endif /* HAVE_PKCS7 && !NO_RSA && !NO_SHA256 */ + return EXPECT_RESULT(); +} diff --git a/tests/api/test_pkcs7.h b/tests/api/test_pkcs7.h index b1958d84d0c..82aa8fad496 100644 --- a/tests/api/test_pkcs7.h +++ b/tests/api/test_pkcs7.h @@ -32,6 +32,8 @@ int test_wc_PKCS7_EncodeData(void); int test_wc_PKCS7_EncodeSignedData(void); int test_wc_PKCS7_EncodeSignedData_SKID(void); int test_wc_PKCS7_VerifySignedData_ExplicitSKID(void); +int test_wc_PKCS7_SkidParseDecisionCoverage(void); +int test_wc_PKCS7_CertFooterDecisionCoverage(void); int test_wc_PKCS7_EncodeSignedData_AttribOverflow(void); int test_wc_PKCS7_EncodeAuthEnvelopedData_AttribOverflow(void); #if defined(HAVE_PKCS7) && defined(WC_RSA_PSS) && !defined(NO_RSA) && \ @@ -141,6 +143,8 @@ int test_wc_PKCS7_VerifySignedData_NoDigestParams(void); TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_EncodeSignedData), \ TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_EncodeSignedData_SKID), \ TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_VerifySignedData_ExplicitSKID), \ + TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_SkidParseDecisionCoverage), \ + TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_CertFooterDecisionCoverage), \ TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_EncodeSignedData_AttribOverflow), \ TEST_PKCS7_RSA_PSS_SD_DECL \ TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_EncodeSignedData_ex), \ diff --git a/tests/api/test_random.c b/tests/api/test_random.c index bd21dd9b4a3..be4162b9b2e 100644 --- a/tests/api/test_random.c +++ b/tests/api/test_random.c @@ -1686,3 +1686,243 @@ int test_wc_DrbgFeatureCoverage(void) #endif return EXPECT_RESULT(); } + +/* The 2026 RNG rework (DRBG threading, lock rework) added per-width + * getter/stir arms and init flags. Drive those new decisions through the + * public API; the type/pointer pairings the API alone cannot reach are + * poked on a live instance and restored before the next use (same idiom as + * the reseedCtr pokes in test_wc_DrbgFeatureCoverage). */ +int test_wc_DrbgReworkDecisionCoverage(void) +{ + EXPECT_DECLS; +#if defined(HAVE_HASHDRBG) && !defined(WC_NO_RNG) && \ + !defined(CUSTOM_RAND_GENERATE_BLOCK) && \ + !defined(HAVE_SELFTEST) && !defined(HAVE_FIPS) + WC_RNG rng; + byte seed[32]; + byte nonce[16]; + wc_drbg_reseed_ctr_t ctr; + void* savedPtr = NULL; + int savedType = 0; + + XMEMSET(seed, 7, sizeof(seed)); + XMEMSET(nonce, 9, sizeof(nonce)); + +#if defined(WOLFSSL_DRBG_SHA512) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) + /* SHA-512 width: SHA-256 clause false / SHA-512 clause true. */ + (void)wc_Sha256Drbg_Disable(); + XMEMSET(&rng, 0, sizeof(rng)); + ExpectIntEQ(wc_InitRng_ex(&rng, HEAP_HINT, INVALID_DEVID), 0); + if (rng.drbgType == WC_DRBG_SHA512) { + ExpectIntEQ(wc_RNG_DRBG_Present(&rng), 1); + /* drbg512-NULL side of the SHA-512 clause. */ + savedPtr = rng.drbg512; + rng.drbg512 = NULL; + ExpectIntEQ(wc_RNG_DRBG_Present(&rng), 0); + rng.drbg512 = (struct DRBG_SHA512 *)savedPtr; + /* SHA-256 arm skipped (type mismatch), SHA-512 arm taken. */ + ExpectIntEQ(wc_RNG_DRBG_GetReseedCtr(&rng, &ctr), 0); + ExpectIntEQ(wc_RNG_DRBG_ScheduleReseed(&rng), 0); + /* SHA-256 clause false with drbg512 non-NULL (type poke). */ + savedType = rng.drbgType; + rng.drbgType = WC_DRBG_SHA256; + ExpectIntEQ(wc_RNG_DRBG_Present(&rng), 0); + ExpectIntEQ(wc_RNG_DRBG_ScheduleReseed(&rng), + WC_NO_ERR_TRACE(WRONG_TYPE_OBJECT_E)); + rng.drbgType = savedType; + } + DoExpectIntEQ(wc_FreeRng(&rng), 0); + (void)wc_Sha256Drbg_Enable(); +#endif + + /* SHA-256 width: SHA-256 clause true / SHA-512 clause false. */ +#if defined(WOLFSSL_DRBG_SHA512) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) + (void)wc_Sha512Drbg_Disable(); +#endif + XMEMSET(&rng, 0, sizeof(rng)); + ExpectIntEQ(wc_InitRng_ex(&rng, HEAP_HINT, INVALID_DEVID), 0); + if (rng.drbgType == WC_DRBG_SHA256) { + ExpectIntEQ(wc_RNG_DRBG_Present(&rng), 1); + /* drbg-NULL side of the SHA-256 clause. */ + savedPtr = rng.drbg; + rng.drbg = NULL; + ExpectIntEQ(wc_RNG_DRBG_Present(&rng), 0); + rng.drbg = (struct DRBG *)savedPtr; + /* SHA-256 arm taken, SHA-512 arm skipped. */ + ExpectIntEQ(wc_RNG_DRBG_GetReseedCtr(&rng, &ctr), 0); + ExpectIntEQ(wc_RNG_DRBG_ScheduleReseed(&rng), 0); + /* SHA-256 arm false with drbg non-NULL (type poke): both arms + * fall through to the no-DRBG error. */ + savedType = rng.drbgType; + rng.drbgType = WC_DRBG_SHA512; + ExpectIntEQ(wc_RNG_DRBG_ScheduleReseed(&rng), + WC_NO_ERR_TRACE(WRONG_TYPE_OBJECT_E)); + rng.drbgType = savedType; + /* SHA-256 arm skipped (drbg NULL): the Present() guard turns the + * type/pointer mismatch into WRONG_TYPE_OBJECT_E before the arms. + * (The arm's drbg-NULL side is unreachable -- Present forces it.) */ + rng.drbg = NULL; + ExpectIntEQ(wc_RNG_DRBG_GetReseedCtr(&rng, &ctr), + WC_NO_ERR_TRACE(WRONG_TYPE_OBJECT_E)); + rng.drbg = (struct DRBG *)savedPtr; + } + /* No-DRBG instance: both arms false (zeroed type, NULL pointers). */ + DoExpectIntEQ(wc_FreeRng(&rng), 0); + XMEMSET(&rng, 0, sizeof(rng)); + ExpectIntEQ(wc_RNG_DRBG_Present(&rng), 0); + DoExpectIntEQ(wc_FreeRng(&rng), 0); + + /* Re-initialise for the nonce-driven entry points. */ + XMEMSET(&rng, 0, sizeof(rng)); + ExpectIntEQ(wc_InitRng_ex(&rng, HEAP_HINT, INVALID_DEVID), 0); + + /* wc_RNG_DRBG_Reseed_Nonce: "nonce == NULL && nonceSz > 0" pair. */ + ExpectIntEQ(wc_RNG_DRBG_Reseed_Nonce(&rng, seed, sizeof(seed), NULL, + sizeof(nonce)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_RNG_DRBG_Reseed_Nonce(&rng, seed, sizeof(seed), nonce, + sizeof(nonce)), 0); + ExpectIntEQ(wc_RNG_DRBG_Reseed_Nonce(&rng, seed, sizeof(seed), nonce, 0), + 0); + + /* wc_RNG_DRBG_Stir_Nonce: rng/seed NULL pair, nonce pair, and the + * second-chunk decision (nonce non-NULL with zero size). */ + ExpectIntEQ(wc_RNG_DRBG_Stir_Nonce(NULL, seed, sizeof(seed), nonce, + sizeof(nonce)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_RNG_DRBG_Stir_Nonce(&rng, NULL, sizeof(seed), nonce, + sizeof(nonce)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_RNG_DRBG_Stir_Nonce(&rng, seed, sizeof(seed), NULL, + sizeof(nonce)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_RNG_DRBG_Stir_Nonce(&rng, seed, sizeof(seed), nonce, + sizeof(nonce)), 0); + ExpectIntEQ(wc_RNG_DRBG_Stir_Nonce(&rng, seed, sizeof(seed), nonce, 0), + 0); + + /* wc_RNG_DRBG_Stir: valid seed only (no nonce chunk). */ + ExpectIntEQ(wc_RNG_DRBG_Stir(&rng, seed, sizeof(seed)), 0); + + DoExpectIntEQ(wc_FreeRng(&rng), 0); + + /* Init flags: USE_FULL_MUTEX and NO_AUTO_LOCK arms of _InitRng(). The + * condition is compiled in every build; only the mutex body is gated, + * so a build without the full mutex answers NOT_COMPILED_IN. */ + XMEMSET(&rng, 0, sizeof(rng)); +#if defined(WC_RNG_HAVE_LOCK_FULL_MUTEX) + ExpectIntEQ(wc_InitRng_ex2(&rng, HEAP_HINT, INVALID_DEVID, + WC_RNG_INIT_FLAG_USE_FULL_MUTEX), 0); +#else + ExpectIntEQ(wc_InitRng_ex2(&rng, HEAP_HINT, INVALID_DEVID, + WC_RNG_INIT_FLAG_USE_FULL_MUTEX), + WC_NO_ERR_TRACE(NOT_COMPILED_IN)); +#endif + DoExpectIntEQ(wc_FreeRng(&rng), 0); + + XMEMSET(&rng, 0, sizeof(rng)); + ExpectIntEQ(wc_InitRng_ex2(&rng, HEAP_HINT, INVALID_DEVID, + WC_RNG_INIT_FLAG_NO_AUTO_LOCK), 0); + DoExpectIntEQ(wc_FreeRng(&rng), 0); + + /* wc_InitRngNonce_ex2: "perso == NULL && persoSz != 0" pair. */ + XMEMSET(&rng, 0, sizeof(rng)); + ExpectIntEQ(wc_InitRngNonce_ex2(&rng, nonce, sizeof(nonce), NULL, + sizeof(nonce), HEAP_HINT, INVALID_DEVID, + 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + XMEMSET(&rng, 0, sizeof(rng)); + ExpectIntEQ(wc_InitRngNonce_ex2(&rng, nonce, sizeof(nonce), nonce, + sizeof(nonce), HEAP_HINT, INVALID_DEVID, + 0), 0); + DoExpectIntEQ(wc_FreeRng(&rng), 0); + + XMEMSET(&rng, 0, sizeof(rng)); + ExpectIntEQ(wc_InitRngNonce_ex2(&rng, nonce, sizeof(nonce), nonce, 0, + HEAP_HINT, INVALID_DEVID, 0), 0); + DoExpectIntEQ(wc_FreeRng(&rng), 0); + +#if defined(WOLFSSL_DRBG_SHA512) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) + /* Restore the SHA-512 DRBG for the rest of the process; it stays + * disabled through the nonce section so those entry points run on + * the SHA-256 width. */ + (void)wc_Sha512Drbg_Enable(); +#endif +#endif + return EXPECT_RESULT(); +} + +/* + * MC/DC decision coverage for the RNG init-flag guards, the TestSeed + * majority-test window guard, and the seed/stir NULL-argument guards + * (master drift, 2026-10-06). + */ +int test_wc_RngInitFlagsDecisionCoverage(void) +{ + EXPECT_DECLS; +#if defined(HAVE_HASHDRBG) && !defined(WC_NO_RNG) && \ + !defined(CUSTOM_RAND_GENERATE_BLOCK) && \ + !defined(HAVE_SELFTEST) && !defined(HAVE_FIPS) + WC_RNG rng; + OS_Seed os; + byte seed[32]; + byte out[16]; + + XMEMSET(&rng, 0, sizeof(rng)); + XMEMSET(&os, 0, sizeof(os)); + XMEMSET(seed, 7, sizeof(seed)); + XMEMSET(out, 0, sizeof(out)); + +#ifdef WC_RNG_HAVE_LOCK_FULL_MUTEX + /* USE_FULL_MUTEX: the full-mutex clause's true side. */ + ExpectIntEQ(wc_InitRng_ex2(&rng, HEAP_HINT, INVALID_DEVID, + WC_RNG_INIT_FLAG_USE_FULL_MUTEX), 0); + ExpectIntEQ(wc_FreeRng(&rng), 0); +#endif + +#ifdef WC_RNG_HAVE_AUTO_LOCK + /* USE_AUTO_LOCK: the auto-lock clause's true side. */ + XMEMSET(&rng, 0, sizeof(rng)); + ExpectIntEQ(wc_InitRng_ex2(&rng, HEAP_HINT, INVALID_DEVID, + WC_RNG_INIT_FLAG_USE_AUTO_LOCK), 0); + ExpectIntEQ(wc_FreeRng(&rng), 0); +#endif + + /* NO_AUTO_LOCK: the auto-lock clause's false side. */ + XMEMSET(&rng, 0, sizeof(rng)); + ExpectIntEQ(wc_InitRng_ex2(&rng, HEAP_HINT, INVALID_DEVID, + WC_RNG_INIT_FLAG_NO_AUTO_LOCK), 0); + ExpectIntEQ(wc_FreeRng(&rng), 0); + +#ifdef WC_RNG_HAVE_NEXT_SEED + /* RECOVER_AND_PROMOTE_FROM_NEXT_SEED: the seedRng-non-NULL side of the + * primary-seed health-check clause. */ + XMEMSET(&rng, 0, sizeof(rng)); + ExpectIntEQ(wc_InitRng_ex2(&rng, HEAP_HINT, INVALID_DEVID, + WC_RNG_INIT_FLAG_RECOVER_AND_PROMOTE_FROM_NEXT_SEED), 0); + ExpectIntEQ(wc_FreeRng(&rng), 0); +#endif + + /* wc_RNG_TestSeed on a short seed: the window stays below the + * majority-test threshold, the cutoff > window/2 clause's false side. */ + (void)wc_RNG_TestSeed(seed, 16); + + /* wc_GenerateSeed NULL-argument guards. */ + ExpectIntEQ(wc_GenerateSeed(NULL, out, sizeof(out)), BAD_FUNC_ARG); + ExpectIntEQ(wc_GenerateSeed(&os, NULL, sizeof(out)), BAD_FUNC_ARG); + + /* wc_RNG_DRBG_Stir_Nonce with a valid rng and a NULL seed: the + * rng-NULL clause's false side. */ + XMEMSET(&rng, 0, sizeof(rng)); + ExpectIntEQ(wc_InitRng_ex2(&rng, HEAP_HINT, INVALID_DEVID, + WC_RNG_INIT_FLAG_NONE), 0); + ExpectIntEQ(wc_RNG_DRBG_Stir_Nonce(&rng, NULL, 0, out, sizeof(out)), + BAD_FUNC_ARG); + ExpectIntEQ(wc_FreeRng(&rng), 0); +#endif + return EXPECT_RESULT(); +} + diff --git a/tests/api/test_random.h b/tests/api/test_random.h index 84d7a6a289c..dd007fb83c4 100644 --- a/tests/api/test_random.h +++ b/tests/api/test_random.h @@ -45,6 +45,8 @@ int test_wc_RNG_CustomRandBlock(void); int test_wc_RNG_DrbgDisable(void); int test_wc_DrbgDecisionCoverage(void); int test_wc_DrbgFeatureCoverage(void); +int test_wc_DrbgReworkDecisionCoverage(void); +int test_wc_RngInitFlagsDecisionCoverage(void); int test_wc_Entropy_Get(void); #define TEST_RANDOM_DECLS \ @@ -69,6 +71,8 @@ int test_wc_Entropy_Get(void); TEST_DECL_GROUP("random", test_wc_RNG_DrbgDisable), \ TEST_DECL_GROUP("random", test_wc_DrbgDecisionCoverage), \ TEST_DECL_GROUP("random", test_wc_DrbgFeatureCoverage), \ - TEST_DECL_GROUP("random", test_wc_Entropy_Get) + TEST_DECL_GROUP("random", test_wc_DrbgReworkDecisionCoverage), \ + TEST_DECL_GROUP("random", test_wc_Entropy_Get), \ + TEST_DECL_GROUP("random", test_wc_RngInitFlagsDecisionCoverage) #endif /* WOLFCRYPT_TEST_RANDOM_H */ diff --git a/tests/api/test_session.c b/tests/api/test_session.c index 34614338c55..0ab44c51b09 100644 --- a/tests/api/test_session.c +++ b/tests/api/test_session.c @@ -3218,3 +3218,59 @@ int test_wolfSSL_session_cache_restore(void) #endif /* PERSIST_SESSION_CACHE && !NO_SESSION_CACHE && * !SESSION_CACHE_DYNAMIC_MEM && !TITAN_SESSION_CACHE && * !HUGE_SESSION_CACHE */ + +/* Decision-coverage drivers for the ssl_sess.c argument guards left + * unpaired by the functional tests. Each block names the + * source line:condition it closes. */ +int test_session_decision_coverage(void) +{ + EXPECT_DECLS; + /* Refs session fns whose definitions need OPENSSL_EXTRA/HAVE_EXT_CACHE + * and are hidden from the .so - shared builds compile this out; + * WOLFSSL_TEST_STATIC_BUILD is set for static-only builds (configure) + * and by the MC/DC variant. */ +#if defined(WOLFSSL_TEST_STATIC_BUILD) && !defined(NO_SESSION_CACHE) && \ + (defined(OPENSSL_EXTRA) || defined(HAVE_EXT_CACHE)) && \ + !defined(WOLFCRYPT_ONLY) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + WOLFSSL_SESSION* sess = NULL; + WOLFSSL_CIPHER cipher; + + XMEMSET(&cipher, 0, sizeof(cipher)); + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectNotNull(sess = wolfSSL_SESSION_new()); + + /* --- 1552:0/1/2 wolfSSL_SetSession: ssl==NULL || session==NULL || + * !session->isSetup ---------------------------------------------- */ + (void)wolfSSL_SetSession(NULL, sess); + (void)wolfSSL_SetSession(ssl, NULL); + (void)wolfSSL_SetSession(ssl, sess); + + /* --- 3296:0/1 wolfSSL_SSL_SESSION_set_timeout: ses==NULL || t<0 ------ */ + (void)wolfSSL_SSL_SESSION_set_timeout(NULL, 100); + (void)wolfSSL_SSL_SESSION_set_timeout(sess, -1); + (void)wolfSSL_SSL_SESSION_set_timeout(sess, 100); + + /* --- 3320:0/1 wolfSSL_SESSION_set_time: ses==NULL || t<0 ------------- */ + (void)wolfSSL_SESSION_set_time(NULL, 100); + (void)wolfSSL_SESSION_set_time(sess, -1); + (void)wolfSSL_SESSION_set_time(sess, 100); + + /* --- 3955:0/1 wolfSSL_SESSION_up_ref: session==NULL || type!=HEAP ---- */ + (void)wolfSSL_SESSION_up_ref(NULL); + (void)wolfSSL_SESSION_up_ref(sess); + + /* --- 4383:0/1 wolfSSL_SESSION_set_cipher: session==NULL || cipher==NULL */ + (void)wolfSSL_SESSION_set_cipher(NULL, &cipher); + (void)wolfSSL_SESSION_set_cipher(sess, NULL); + (void)wolfSSL_SESSION_set_cipher(sess, &cipher); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + wolfSSL_SESSION_free(sess); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_session.h b/tests/api/test_session.h index b13015cb9e0..c8be458f651 100644 --- a/tests/api/test_session.h +++ b/tests/api/test_session.h @@ -25,6 +25,7 @@ #include int test_wolfSSL_CTX_add_session(void); +int test_session_decision_coverage(void); int test_wolfSSL_CTX_add_session_ext_tls13(void); int test_wolfSSL_CTX_add_session_ext_dtls13(void); int test_wolfSSL_CTX_add_session_ext_tls12(void); @@ -65,6 +66,7 @@ int test_wolfSSL_SetServerID_resume(void); TEST_DECL_GROUP("session", test_wolfSSL_session_cache_restore), \ TEST_DECL_GROUP("session", test_wolfSSL_session_cache_client_geometry), \ TEST_DECL_GROUP("session", test_wolfSSL_get_session_default_ref), \ + TEST_DECL_GROUP("session", test_session_decision_coverage), \ TEST_DECL_GROUP("session", test_wolfSSL_SetServerID_resume) #endif /* WOLFCRYPT_TEST_SESSION_H */ diff --git a/tests/api/test_ssl_cert.c b/tests/api/test_ssl_cert.c index 1e30855b539..76e6637398b 100644 --- a/tests/api/test_ssl_cert.c +++ b/tests/api/test_ssl_cert.c @@ -5224,3 +5224,93 @@ int test_wolfSSL_dtls_api_more_guards(void) #endif return EXPECT_RESULT(); } +int test_ssl_cert_decision_coverage(void) +{ + EXPECT_DECLS; + /* Refs WOLFSSL_LOCAL certman fns, hidden from the .so - shared builds + * compile this out; WOLFSSL_TEST_STATIC_BUILD is set for static-only + * builds (configure) and by the MC/DC variant. */ +#if defined(WOLFSSL_TEST_STATIC_BUILD) && !defined(NO_CERTS) && \ + !defined(WOLFCRYPT_ONLY) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + /* --- 105:1/2 CTX_set_verify_depth: depth < 0 || depth > MAX ---------- */ + wolfSSL_CTX_set_verify_depth(ctx, -1); + wolfSSL_CTX_set_verify_depth(ctx, MAX_CHAIN_DEPTH + 1); + wolfSSL_CTX_set_verify_depth(ctx, 5); + + /* --- 738:1 CTX_SetCACb: ctx->cm != NULL ----------------------------- */ + { + WOLFSSL_CTX* nctx = wolfSSL_CTX_new(wolfSSLv23_client_method()); + WOLFSSL_CERT_MANAGER* saved; + + ExpectNotNull(nctx); + saved = nctx->cm; + nctx->cm = NULL; + wolfSSL_CTX_SetCACb(nctx, NULL); + nctx->cm = saved; + wolfSSL_CTX_free(nctx); + } + +#ifdef SESSION_CERTS + /* --- 2301:1 get_chain_X509: idx >= 0 -------------------------------- */ + { + WOLFSSL_X509_CHAIN chain; + + XMEMSET(&chain, 0, sizeof(chain)); + chain.count = 1; + (void)wolfSSL_get_chain_X509(NULL, 0); + (void)wolfSSL_get_chain_X509(&chain, -1); + (void)wolfSSL_get_chain_X509(&chain, 0); + } +#endif + + /* --- 984:1/1002:0/1 UnloadCertsKeys: keepCert, key, key->buffer ----- */ + { + DerBuffer* key; + + /* 984:1 weOwnCert && !keepCert */ + ssl->buffers.weOwnCert = 1; + ssl->buffers.certificate = NULL; + ssl->keepCert = 0; + (void)wolfSSL_UnloadCertsKeys(ssl); + ssl->buffers.weOwnCert = 1; + ssl->keepCert = 1; + (void)wolfSSL_UnloadCertsKeys(ssl); + ssl->keepCert = 0; + ssl->buffers.weOwnCert = 0; + + /* 1002:0/1 weOwnKey && key!=NULL && key->buffer!=NULL */ + ssl->buffers.weOwnKey = 1; + ssl->buffers.key = NULL; + (void)wolfSSL_UnloadCertsKeys(ssl); + + key = (DerBuffer*)XMALLOC(sizeof(DerBuffer), ssl->heap, + DYNAMIC_TYPE_DER); + XMEMSET(key, 0, sizeof(DerBuffer)); + key->type = PRIVATEKEY_TYPE; + ssl->buffers.key = key; + (void)wolfSSL_UnloadCertsKeys(ssl); + + key = (DerBuffer*)XMALLOC(sizeof(DerBuffer), ssl->heap, + DYNAMIC_TYPE_DER); + key->buffer = (byte*)XMALLOC(16, ssl->heap, DYNAMIC_TYPE_DER); + key->heap = ssl->heap; + key->length = 16; + key->type = PRIVATEKEY_TYPE; + key->dynType = DYNAMIC_TYPE_DER; + ssl->buffers.key = key; + (void)wolfSSL_UnloadCertsKeys(ssl); + ssl->buffers.weOwnKey = 0; + } + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_ssl_cert.h b/tests/api/test_ssl_cert.h index bb6978ea123..73ae90d8bc5 100644 --- a/tests/api/test_ssl_cert.h +++ b/tests/api/test_ssl_cert.h @@ -23,6 +23,7 @@ #define TESTS_API_SSL_CERT_H int test_wolfSSL_cert_api_arg_guards(void); +int test_ssl_cert_decision_coverage(void); int test_wolfSSL_crl_ocsp_api_arg_guards(void); int test_wolfSSL_ocsp_stapling_accessors(void); int test_wolfSSL_crl_io_mock(void); @@ -141,6 +142,7 @@ int test_wolfSSL_SESSION_get0_peer_full_chain(void); TEST_DECL_GROUP("ssl_cert", test_wolfSSL_load_pathological_files), \ TEST_DECL_GROUP("ssl_cert", test_wolfSSL_load_from_fifo), \ TEST_DECL_GROUP("ssl_cert", test_wolfSSL_dtls_api_more_guards), \ - TEST_DECL_GROUP("ssl_cert", test_wolfSSL_alloc_failure_sweep) + TEST_DECL_GROUP("ssl_cert", test_wolfSSL_alloc_failure_sweep), \ + TEST_DECL_GROUP("ssl_cert", test_ssl_cert_decision_coverage) #endif /* TESTS_API_SSL_CERT_H */ diff --git a/tests/api/test_ssl_crl_ocsp.c b/tests/api/test_ssl_crl_ocsp.c index eefae57313f..ec265b0e001 100644 --- a/tests/api/test_ssl_crl_ocsp.c +++ b/tests/api/test_ssl_crl_ocsp.c @@ -581,3 +581,70 @@ int test_wolfSSL_OCSP_parse_url_api(void) #endif return EXPECT_RESULT(); } +int test_ssl_crl_ocsp_decision_coverage(void) +{ + EXPECT_DECLS; +#if defined(HAVE_OCSP) && !defined(NO_ASN_TIME) + struct tm tm; +#endif +#if !defined(WOLFCRYPT_ONLY) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + +#ifdef HAVE_CRL + /* --- 77:0/1 wolfSSL_LoadCRLBuffer: ssl==NULL || ssl->ctx==NULL ------ */ + { + WOLFSSL* cssl = wolfSSL_new(ctx); + byte buf[4]; + + XMEMSET(buf, 0, sizeof(buf)); + (void)wolfSSL_LoadCRLBuffer(NULL, buf, (long)sizeof(buf), + CERT_FILETYPE); + (void)wolfSSL_LoadCRLBuffer(ssl, buf, (long)sizeof(buf), + CERT_FILETYPE); + ExpectNotNull(cssl); + cssl->ctx = NULL; + (void)wolfSSL_LoadCRLBuffer(cssl, buf, (long)sizeof(buf), + CERT_FILETYPE); + cssl->ctx = ctx; + wolfSSL_free(cssl); + } +#endif + +#if defined(HAVE_OCSP) && !defined(NO_ASN_TIME) + XMEMSET(&tm, 0, sizeof(tm)); + /* --- 917:1 get_ocsp_producedDate_tm: 2nd != (GENERALIZED vs default) - */ + ssl->ocspProducedDateFormat = ASN_GENERALIZED_TIME; + (void)wolfSSL_get_ocsp_producedDate_tm(ssl, &tm); + ssl->ocspProducedDateFormat = 0; + (void)wolfSSL_get_ocsp_producedDate_tm(ssl, &tm); +#endif + +#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) || defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) + /* --- 1120/1152/1187:1 CTX status cb/arg: ctx->cm == NULL ------------ */ + { + WOLFSSL_CTX* nctx = wolfSSL_CTX_new(wolfSSLv23_client_method()); + WOLFSSL_CERT_MANAGER* saved; + + ExpectNotNull(nctx); + saved = nctx->cm; + nctx->cm = NULL; + (void)wolfSSL_CTX_get_tlsext_status_cb(nctx, NULL); + (void)wolfSSL_CTX_set_tlsext_status_cb(nctx, NULL); + (void)wolfSSL_CTX_set_tlsext_status_arg(nctx, NULL); + nctx->cm = saved; + wolfSSL_CTX_free(nctx); + } + + /* --- 1276:0 set_tlsext_status_ocsp_resp_multi: ssl == NULL ----------- */ + (void)wolfSSL_set_tlsext_status_ocsp_resp_multi(NULL, NULL, 0, 0); +#endif + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_ssl_crl_ocsp.h b/tests/api/test_ssl_crl_ocsp.h index d3cbbb4d6b7..eacecf52119 100644 --- a/tests/api/test_ssl_crl_ocsp.h +++ b/tests/api/test_ssl_crl_ocsp.h @@ -30,6 +30,7 @@ int test_wolfSSL_tlsext_status_type(void); int test_wolfSSL_CTX_tlsext_status_cb(void); int test_wolfSSL_tlsext_status_ocsp_resp(void); int test_wolfSSL_OCSP_parse_url_api(void); +int test_ssl_crl_ocsp_decision_coverage(void); #define TEST_SSL_CRL_OCSP_DECLS \ TEST_DECL_GROUP("ssl_crl_ocsp", test_wolfSSL_ocsp_url_api), \ @@ -37,6 +38,7 @@ int test_wolfSSL_OCSP_parse_url_api(void); TEST_DECL_GROUP("ssl_crl_ocsp", test_wolfSSL_tlsext_status_type), \ TEST_DECL_GROUP("ssl_crl_ocsp", test_wolfSSL_CTX_tlsext_status_cb), \ TEST_DECL_GROUP("ssl_crl_ocsp", test_wolfSSL_tlsext_status_ocsp_resp), \ - TEST_DECL_GROUP("ssl_crl_ocsp", test_wolfSSL_OCSP_parse_url_api) + TEST_DECL_GROUP("ssl_crl_ocsp", test_wolfSSL_OCSP_parse_url_api), \ + TEST_DECL_GROUP("ssl_crl_ocsp", test_ssl_crl_ocsp_decision_coverage) #endif /* TESTS_API_SSL_CRL_OCSP_H */ diff --git a/tests/api/test_ssl_ext.c b/tests/api/test_ssl_ext.c index 746fd98ff62..91996f9bd8e 100644 --- a/tests/api/test_ssl_ext.c +++ b/tests/api/test_ssl_ext.c @@ -2275,3 +2275,586 @@ int test_wolfSSL_session_lifecycle_guards(void) #endif return EXPECT_RESULT(); } +int test_wolfSSL_EnableRequireExtendedMasterSecret_ext(void) +{ + EXPECT_DECLS; +#if defined(HAVE_EXTENDED_MASTER) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS) && \ + !defined(WOLFSSL_NO_TLS12) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL_CTX* ctxSrv = NULL; +#if defined(OPENSSL_EXTRA) || defined(WOLFSSL_EITHER_SIDE) + WOLFSSL_CTX* ctxV23 = NULL; +#endif +#ifdef WOLFSSL_TLS13 + WOLFSSL_CTX* ctx13 = NULL; +#endif + WOLFSSL* ssl = NULL; +#if !defined(NO_CERTS) && !defined(NO_RSA) && !defined(NO_FILESYSTEM) + WOLFSSL* sslSrv = NULL; +#endif + + ExpectIntEQ(wolfSSL_CTX_EnableExtendedMasterSecret(NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_EnableExtendedMasterSecret(NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_RequireExtendedMasterSecret(NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_RequireExtendedMasterSecret(NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* Client context: the re-arm branch is taken. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectIntEQ(wolfSSL_CTX_EnableExtendedMasterSecret(ctx), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_RequireExtendedMasterSecret(ctx), + WOLFSSL_SUCCESS); + + /* Server context: side is not the client end. */ + ExpectNotNull(ctxSrv = wolfSSL_CTX_new(wolfSSLv23_server_method())); + ExpectIntEQ(wolfSSL_CTX_EnableExtendedMasterSecret(ctxSrv), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_RequireExtendedMasterSecret(ctxSrv), + WOLFSSL_SUCCESS); + + /* Side-less context: the method exists but arms nothing. */ +#if defined(OPENSSL_EXTRA) || defined(WOLFSSL_EITHER_SIDE) + ExpectNotNull(ctxV23 = wolfSSL_CTX_new(wolfSSLv23_method())); + ExpectIntEQ(wolfSSL_CTX_EnableExtendedMasterSecret(ctxV23), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_RequireExtendedMasterSecret(ctxV23), + WOLFSSL_SUCCESS); +#endif + + /* TLS 1.3 client: EMS is not allowed for the version. */ +#ifdef WOLFSSL_TLS13 + ExpectNotNull(ctx13 = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectIntEQ(wolfSSL_CTX_EnableExtendedMasterSecret(ctx13), + WOLFSSL_SUCCESS); + wolfSSL_CTX_free(ctx13); +#endif + + /* Fresh objects: the state guard passes. */ + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_EnableExtendedMasterSecret(ssl), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_RequireExtendedMasterSecret(ssl), WOLFSSL_SUCCESS); +#if !defined(NO_CERTS) && !defined(NO_RSA) && !defined(NO_FILESYSTEM) + /* A server WOLFSSL needs a key and certificate set on the context. */ + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctxSrv, svrKeyFile, + CERT_FILETYPE), WOLFSSL_SUCCESS) + ; + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctxSrv, svrCertFile, + CERT_FILETYPE), WOLFSSL_SUCCESS + ); + ExpectNotNull(sslSrv = wolfSSL_new(ctxSrv)); + ExpectIntEQ(wolfSSL_EnableExtendedMasterSecret(sslSrv), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_RequireExtendedMasterSecret(sslSrv), + WOLFSSL_SUCCESS); + wolfSSL_free(sslSrv); +#endif + + if (EXPECT_SUCCESS()) { + /* A started handshake: the connect-state operand alone. */ + ssl->options.connectState = 0x7f; + ExpectIntEQ(wolfSSL_EnableExtendedMasterSecret(ssl), + WC_NO_ERR_TRACE(BAD_STATE_E)); + ExpectIntEQ(wolfSSL_RequireExtendedMasterSecret(ssl), + WC_NO_ERR_TRACE(BAD_STATE_E)); + + /* A started handshake: the accept-state operand alone. */ + ssl->options.connectState = CONNECT_BEGIN; + ssl->options.acceptState = 0x7f; + ExpectIntEQ(wolfSSL_EnableExtendedMasterSecret(ssl), + WC_NO_ERR_TRACE(BAD_STATE_E)); + ExpectIntEQ(wolfSSL_RequireExtendedMasterSecret(ssl), + WC_NO_ERR_TRACE(BAD_STATE_E)); + } + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + wolfSSL_CTX_free(ctxSrv); +#if defined(OPENSSL_EXTRA) || defined(WOLFSSL_EITHER_SIDE) + wolfSSL_CTX_free(ctxV23); +#endif +#endif + return EXPECT_RESULT(); +} + +int test_ssl_api_decision_coverage(void) +{ + EXPECT_DECLS; +#if !defined(WOLFCRYPT_ONLY) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + char buf[64]; + + XMEMSET(buf, 0, sizeof(buf)); + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectTrue(ssl->arrays != NULL); + + /* --- 2509:1 wolfSSL_Init: initRefCount == 0 (second call -> refcount>0) */ + (void)wolfSSL_Init(); + + /* --- 1187:0/1 wolfSSL_get_cipher_list: priority >= size || < 0 ------ */ + (void)wolfSSL_get_cipher_list(0); + (void)wolfSSL_get_cipher_list(10000); + (void)wolfSSL_get_cipher_list(-1); + + /* --- 1644:0/1 wolfSSL_CTX_GetDevId: ctx != NULL && devId == INVALID -- */ + (void)wolfSSL_CTX_GetDevId(NULL, ssl); + (void)wolfSSL_CTX_GetDevId(ctx, NULL); + (void)wolfSSL_CTX_GetDevId(ctx, ssl); + { + WOLFSSL* dssl = wolfSSL_new(ctx); + + ExpectNotNull(dssl); + dssl->devId = 0x1234; + (void)wolfSSL_CTX_GetDevId(ctx, dssl); + wolfSSL_free(dssl); + } + + /* --- 1688:0/1 wolfSSL_get_error: error == ZERO_RETURN || shutdownDone */ + ssl->error = 0; + ssl->options.shutdownDone = 0; + (void)wolfSSL_get_error(ssl, 0); + ssl->error = WC_NO_ERR_TRACE(ZERO_RETURN); + (void)wolfSSL_get_error(ssl, 0); + ssl->error = 0; + ssl->options.shutdownDone = 1; + (void)wolfSSL_get_error(ssl, 0); + ssl->error = WC_NO_ERR_TRACE(ZERO_RETURN); + (void)wolfSSL_get_error(ssl, 0); + ssl->error = 0; + ssl->options.shutdownDone = 0; + + /* --- 1708:0/1 wolfSSL_get_alert_history: ssl && h ------------------- */ + { + WOLFSSL_ALERT_HISTORY h; + + XMEMSET(&h, 0, sizeof(h)); + (void)wolfSSL_get_alert_history(NULL, &h); + (void)wolfSSL_get_alert_history(ssl, NULL); + (void)wolfSSL_get_alert_history(ssl, &h); + } + + /* --- 1730:0/1 wolfSSL_FreeArrays: ssl && handShakeState == DONE ------ */ + ssl->options.handShakeState = NULL_STATE; + wolfSSL_FreeArrays(ssl); + ssl->options.handShakeState = HANDSHAKE_DONE; + wolfSSL_FreeArrays(ssl); + wolfSSL_FreeArrays(NULL); + ssl->options.handShakeState = NULL_STATE; + + /* --- 1804:0/1/2/3 GetMacSecret: (CLIENT&&!v)||(SERVER&&v) ----------- */ + (void)wolfSSL_GetMacSecret(ssl, 1); + (void)wolfSSL_GetMacSecret(ssl, 0); + { + WOLFSSL* nssl = wolfSSL_new(ctx); + + ExpectNotNull(nssl); + nssl->options.side = WOLFSSL_NEITHER_END; + (void)wolfSSL_GetMacSecret(nssl, 1); + (void)wolfSSL_GetMacSecret(nssl, 0); + wolfSSL_free(nssl); + } +#if !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) && \ + !defined(NO_CERTS) && !defined(NO_RSA) + { + WOLFSSL_CTX* sctx = wolfSSL_CTX_new(wolfSSLv23_server_method()); + WOLFSSL* sssl = NULL; + + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(sctx, svrKeyFile, + CERT_FILETYPE), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(sctx, svrCertFile, + CERT_FILETYPE), + WOLFSSL_SUCCESS); + ExpectNotNull(sssl = wolfSSL_new(sctx)); + (void)wolfSSL_GetMacSecret(sssl, 1); + (void)wolfSSL_GetMacSecret(sssl, 0); + /* 5837:0 side==SERVER row for the EMS re-arm guard */ + wolfSSL_clear(sssl); + wolfSSL_free(sssl); + wolfSSL_CTX_free(sctx); + } +#endif + + /* --- 2241/2242 RestoreDowngrade: versionSet && ctx, failNoPSK||!min --- */ + ssl->options.versionSet = 0; + (void)wolfSSL_SetMinVersion(ssl, WOLFSSL_TLSV1_2); + ssl->options.versionSet = 1; + (void)wolfSSL_SetMinVersion(ssl, WOLFSSL_TLSV1_2); + ssl->options.failNoPSK = 1; + (void)wolfSSL_SetMinVersion(ssl, WOLFSSL_TLSV1_2); + ssl->options.failNoPSK = 0; + /* !minVersionSet=T row needs the OPENSSL_EXTRA set_min_proto_version + * path; uncloseable in this build (SetMinVersion always sets it to 1) */ + ssl->options.versionSet = 0; + { + WOLFSSL* cssl = wolfSSL_new(ctx); + + ExpectNotNull(cssl); + cssl->options.versionSet = 1; + cssl->ctx = NULL; + (void)wolfSSL_SetMinVersion(cssl, WOLFSSL_TLSV1_2); + cssl->ctx = ctx; + wolfSSL_free(cssl); + } + + /* --- 3325:0/1 wolfSSL_set_cipher_list: ssl==NULL || ssl->ctx==NULL --- */ + (void)wolfSSL_set_cipher_list(NULL, "ALL"); + (void)wolfSSL_set_cipher_list(ssl, "ALL"); + { + WOLFSSL* cssl = wolfSSL_new(ctx); + + ExpectNotNull(cssl); + cssl->ctx = NULL; + (void)wolfSSL_set_cipher_list(cssl, "ALL"); + cssl->ctx = ctx; + wolfSSL_free(cssl); + } + + /* --- 3399/3417/3428/3437 export_keying_material --------------------- */ +#ifdef HAVE_KEYING_MATERIAL + { + byte out[32]; + byte ctxbuf[8]; + WOLFSSL* kssl = wolfSSL_new(ctx); + + XMEMSET(ctxbuf, 0, sizeof(ctxbuf)); + ExpectNotNull(kssl); + kssl->options.handShakeDone = 1; + /* 3399: use_ctx && len>0 && (use_ctx ? ctx==NULL : 0) */ + (void)wolfSSL_export_keying_material(kssl, out, sizeof(out), + "label", 5, NULL, 0, 0); + (void)wolfSSL_export_keying_material(kssl, out, sizeof(out), + "label", 5, ctxbuf, (word32)sizeof( + ctxbuf), 1); + (void)wolfSSL_export_keying_material(kssl, out, sizeof(out), + "label", 5, NULL, 1, 1); + (void)wolfSSL_export_keying_material(kssl, out, sizeof(out), + "label", 5, NULL, 0, 1); + /* 3417: use_ctx && contextLen > MAX_16BIT */ + (void)wolfSSL_export_keying_material(kssl, out, sizeof(out), + "label", 5, ctxbuf, (word32)sizeof( + ctxbuf), 1); + (void)wolfSSL_export_keying_material(kssl, out, sizeof(out), + "label", 5, ctxbuf, (word32)( + WOLFSSL_MAX_16BIT + 1), 1); + /* 3428: saveArrays==0 || arrays==NULL (needs 3417 to pass) */ + (void)wolfSSL_export_keying_material(kssl, out, sizeof(out), + "label", 5, NULL, 0, 0); + wolfSSL_KeepArrays(kssl); + (void)wolfSSL_export_keying_material(kssl, out, sizeof(out), + "label", 5, NULL, 0, 0); + { + Arrays* saved = kssl->arrays; + + kssl->arrays = NULL; + (void)wolfSSL_export_keying_material(kssl, out, sizeof(out), + "label", 5, NULL, 0, 0); + kssl->arrays = saved; + } + /* 3437: forbidden label (needs 3428 to pass). The labels are the + * TLS PRF names; "master" is too short, "master secrex" is the + * same length with a mismatching tail. */ + (void)wolfSSL_export_keying_material(kssl, out, sizeof(out), + "master secret", 13, NULL, 0, 0); + (void)wolfSSL_export_keying_material(kssl, out, sizeof(out), + "master", 6, NULL, 0, 0); + (void)wolfSSL_export_keying_material(kssl, out, sizeof(out), + "master secrex", 13, NULL, 0, 0); + kssl->options.handShakeDone = 0; + wolfSSL_free(kssl); + } +#endif + + /* --- 3860/3869/3876/3892/3898/3904/3908 IsValidFQDN crafted names ---- */ + ExpectIntEQ(wolfssl_local_IsValidFQDN(NULL, 0), 0); + ExpectIntEQ(wolfssl_local_IsValidFQDN("x", 0), 0); + ExpectIntEQ(wolfssl_local_IsValidFQDN("a.com", 5), 1); + ExpectIntEQ(wolfssl_local_IsValidFQDN("a.com", 300), 0); + ExpectIntEQ(wolfssl_local_IsValidFQDN(".com", 4), 0); + ExpectIntEQ(wolfssl_local_IsValidFQDN("a-.com", 6), 0); + ExpectIntEQ(wolfssl_local_IsValidFQDN("a.com-", 6), 0); + ExpectIntEQ(wolfssl_local_IsValidFQDN("a.b_c", 5), 0); + ExpectIntEQ(wolfssl_local_IsValidFQDN("1.2", 3), 0); + ExpectIntEQ(wolfssl_local_IsValidFQDN("a[.com", 6), 0); + ExpectIntEQ(wolfssl_local_IsValidFQDN("a/.com", 6), 0); + ExpectIntEQ(wolfssl_local_IsValidFQDN("a.com..", 7), 0); + + /* --- 3927:0/1 wolfSSL_check_domain_name: !valid && != "localhost" ---- */ + (void)wolfSSL_check_domain_name(ssl, "localhost"); + (void)wolfSSL_check_domain_name(ssl, "a..com"); + (void)wolfSSL_check_domain_name(ssl, "a.com"); + +#if !defined(NO_PSK) + /* --- 4166/4177/4203:0/1 PSK identity: ssl==NULL || arrays==NULL ------ */ + (void)wolfSSL_get_psk_identity_hint(NULL); + (void)wolfSSL_get_psk_identity_hint(ssl); + (void)wolfSSL_get_psk_identity(NULL); + (void)wolfSSL_get_psk_identity(ssl); + (void)wolfSSL_use_psk_identity_hint(NULL, "h"); + (void)wolfSSL_use_psk_identity_hint(ssl, "h"); + { + WOLFSSL* pssl = wolfSSL_new(ctx); + Arrays* saved; + + ExpectNotNull(pssl); + saved = pssl->arrays; + pssl->arrays = NULL; + (void)wolfSSL_get_psk_identity_hint(pssl); + (void)wolfSSL_get_psk_identity(pssl); + (void)wolfSSL_use_psk_identity_hint(pssl, "h"); + pssl->arrays = saved; + wolfSSL_free(pssl); + } +#endif + + /* --- 5837:0/1 wolfSSL_clear EMS re-arm: side==CLIENT && !disableEMS -- */ + ssl->options.disableEMS = 1; + wolfSSL_clear(ssl); + ssl->options.disableEMS = 0; + wolfSSL_clear(ssl); + +#ifdef HAVE_SECURE_RENEGOTIATION + /* --- 6208:1 wolfSSL_session_reused: resuming || resumed -------------- */ + ssl->options.resuming = 1; + ssl->options.resumed = 0; + (void)wolfSSL_session_reused(ssl); + ssl->options.resuming = 0; + ssl->options.resumed = 1; + (void)wolfSSL_session_reused(ssl); + ssl->options.resuming = 1; + ssl->options.resumed = 1; + (void)wolfSSL_session_reused(ssl); + ssl->options.resuming = 0; + ssl->options.resumed = 0; +#endif + + /* --- 6363:0/1 CIPHER_get_version: cipher && cipher->ssl -------------- */ + { + WOLFSSL_CIPHER c; + + XMEMSET(&c, 0, sizeof(c)); + (void)wolfSSL_CIPHER_get_version(NULL); + c.ssl = NULL; + (void)wolfSSL_CIPHER_get_version(&c); + c.ssl = ssl; + (void)wolfSSL_CIPHER_get_version(&c); + } + + /* --- 6397:0/1/2/3 get_cipher_suite_from_name: 4-NULL ----------------- */ + { + byte c0 = 0, c1 = 0; + int flags = 0; + + (void)wolfSSL_get_cipher_suite_from_name(NULL, &c0, &c1, &flags); + (void)wolfSSL_get_cipher_suite_from_name("AES128-SHA", NULL, &c1, + &flags); + (void)wolfSSL_get_cipher_suite_from_name("AES128-SHA", &c0, NULL, + &flags); + (void)wolfSSL_get_cipher_suite_from_name("AES128-SHA", &c0, &c1, + NULL); + (void)wolfSSL_get_cipher_suite_from_name("AES128-SHA", &c0, &c1, + &flags); + } + + /* --- 6413:0/1 CIPHER_get_id: cipher && cipher->ssl ------------------- */ + { + WOLFSSL_CIPHER c; + + XMEMSET(&c, 0, sizeof(c)); + (void)wolfSSL_CIPHER_get_id(NULL); + c.ssl = NULL; + (void)wolfSSL_CIPHER_get_id(&c); + c.ssl = ssl; + (void)wolfSSL_CIPHER_get_id(&c); + } + + /* --- 6656:1/6662:1 get_curve_name X25519/X448: OID && cName==NULL ---- */ +#if defined(HAVE_CURVE25519) && defined(HAVE_FFDHE) + ssl->namedGroup = 0; + ssl->ecdhCurveOID = ECC_X25519_OID; + (void)wolfSSL_get_curve_name(ssl); + ssl->namedGroup = WOLFSSL_FFDHE_2048; + (void)wolfSSL_get_curve_name(ssl); + ssl->namedGroup = 0; + ssl->ecdhCurveOID = 0; +#endif +#if defined(HAVE_CURVE448) && defined(HAVE_FFDHE) + ssl->namedGroup = 0; + ssl->ecdhCurveOID = ECC_X448_OID; + (void)wolfSSL_get_curve_name(ssl); + ssl->namedGroup = WOLFSSL_FFDHE_2048; + (void)wolfSSL_get_curve_name(ssl); + ssl->namedGroup = 0; + ssl->ecdhCurveOID = 0; +#endif + + /* --- 7639:0/1 FindHashSig loop: set_options version change ----------- */ +#ifdef WOLFSSL_TLS13 + { + WOLFSSL_CTX* t13 = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); + WOLFSSL* tssl = wolfSSL_new(t13); + + ExpectNotNull(tssl); + (void)wolfSSL_set_cipher_list(tssl, "ALL"); + (void)wolfSSL_set_options(tssl, WOLFSSL_OP_NO_TLSv1_3); + wolfSSL_free(tssl); + wolfSSL_CTX_free(t13); + } +#endif + + /* --- 7827:0/1/2 SSL_renegotiate_pending: s&&done&&state!=DONE -------- */ +#if defined(HAVE_SECURE_RENEGOTIATION) || \ + defined(HAVE_SERVER_RENEGOTIATION_INFO) + (void)wolfSSL_SSL_renegotiate_pending(NULL); + ssl->options.handShakeDone = 0; + (void)wolfSSL_SSL_renegotiate_pending(ssl); + ssl->options.handShakeDone = 1; + ssl->options.handShakeState = HANDSHAKE_DONE; + (void)wolfSSL_SSL_renegotiate_pending(ssl); + ssl->options.handShakeState = SERVER_HELLODONE_COMPLETE; + (void)wolfSSL_SSL_renegotiate_pending(ssl); + ssl->options.handShakeDone = 0; + ssl->options.handShakeState = NULL_STATE; +#endif + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_ech_decision_coverage(void) +{ + EXPECT_DECLS; +#if defined(HAVE_ECH) && defined(WOLFSSL_TLS13) && \ + !defined(WOLFCRYPT_ONLY) && !defined(NO_WOLFSSL_CLIENT) && \ + defined(WOLFSSL_TEST_STATIC_BUILD) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + WOLFSSL_EchConfig cfg; + WOLFSSL_EchConfig* outCfg = NULL; + byte buf[64]; + byte out[256]; + word32 outLen = sizeof(out); + + XMEMSET(buf, 0, sizeof(buf)); + XMEMSET(&cfg, 0, sizeof(cfg)); + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + /* --- 349:0/1/2 SetRetryConfigs: ssl==NULL || configs==NULL || len==0 -- */ + (void)SetRetryConfigs(NULL, buf, sizeof(buf)); + (void)SetRetryConfigs(ssl, NULL, sizeof(buf)); + (void)SetRetryConfigs(ssl, buf, 0); + (void)SetRetryConfigs(ssl, buf, sizeof(buf)); + + /* --- 370:0/1/2 GetEchConfig: config==NULL || (out==NULL&&outLen==NULL) */ + (void)GetEchConfig(NULL, out, &outLen); + (void)GetEchConfig(&cfg, NULL, NULL); + (void)GetEchConfig(&cfg, out, &outLen); + + /* --- 626:0/1/2 SetEchConfigsEx: outCfg==NULL || cfgs==NULL || len<2 --- */ + (void)SetEchConfigsEx(NULL, ssl->heap, buf, sizeof(buf)); + (void)SetEchConfigsEx(&outCfg, ssl->heap, NULL, sizeof(buf)); + (void)SetEchConfigsEx(&outCfg, ssl->heap, buf, 1); + + /* --- 861:0/1/2 GetEchConfigsEx: cfgs==NULL || outLen==NULL || too small */ + (void)GetEchConfigsEx(NULL, out, &outLen); + (void)GetEchConfigsEx(&cfg, out, NULL); + (void)GetEchConfigsEx(&cfg, out, &outLen); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* + * MC/DC decision coverage for the SNI option/status guards and the + * certificate-authority NULL-argument chains in src/ssl_api_ext.c + * (master drift, 2026-10-06). The zeroed objects drive the + * extensions == NULL rows the public lifecycle never reaches. + */ +int test_ssl_api_ext_guards_decision_coverage(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SNI) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) && \ + defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) + WOLFSSL_CTX *ctx = NULL; + WOLFSSL *ssl = NULL; + WOLFSSL_CTX ctx0; + WOLFSSL ssl0; + const byte dn[] = { 0x31, 0x03, 0x01, 0x01, 0xFF, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }; + byte sni[16]; + word32 sniSz; + + XMEMSET(&ctx0, 0, sizeof(ctx0)); + XMEMSET(&ssl0, 0, sizeof(ssl0)); + XMEMSET(sni, 0, sizeof(sni)); + + ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); + if (ctx == NULL) + return EXPECT_RESULT(); + ssl = wolfSSL_new(ctx); + if (ssl == NULL) { + wolfSSL_CTX_free(ctx); + return EXPECT_RESULT(); + } + + /* SNI_SetOptions: (obj != NULL) && (obj->extensions != NULL). The + * NULL-object row, the valid-object row, and the zeroed-object row + * (extensions == NULL) show each clause's pair. */ + wolfSSL_SNI_SetOptions(NULL, WOLFSSL_SNI_HOST_NAME, 0); + wolfSSL_SNI_SetOptions(ssl, WOLFSSL_SNI_HOST_NAME, 0); + wolfSSL_SNI_SetOptions(&ssl0, WOLFSSL_SNI_HOST_NAME, 0); + wolfSSL_CTX_SNI_SetOptions(NULL, WOLFSSL_SNI_HOST_NAME, 0); + wolfSSL_CTX_SNI_SetOptions(ctx, WOLFSSL_SNI_HOST_NAME, 0); + wolfSSL_CTX_SNI_SetOptions(&ctx0, WOLFSSL_SNI_HOST_NAME, 0); + + /* SNI_GetRequest: the extensions-NULL row on the object side. */ + (void)wolfSSL_SNI_GetRequest(ssl, WOLFSSL_SNI_HOST_NAME, NULL); + (void)wolfSSL_SNI_GetRequest(&ssl0, WOLFSSL_SNI_HOST_NAME, NULL); + + /* SNI_GetFromBuffer: the *inOutSz > 0 clause's false side, then the + * all-valid row (a hello without SNI parses to none; the value is + * not the point, the clause is). */ + sniSz = 0; + ExpectIntEQ(wolfSSL_SNI_GetFromBuffer((const byte*)"x", 1, + WOLFSSL_SNI_HOST_NAME, sni, &sniSz), BAD_FUNC_ARG); + sniSz = sizeof(sni); + (void)wolfSSL_SNI_GetFromBuffer((const byte*)"x", 1, + WOLFSSL_SNI_HOST_NAME, sni, &sniSz); + + /* UseCertificateAuthority: ssl == NULL || dn == NULL || dnSz == 0 || + * dnSz > MAX -- each clause flips alone across this chain. */ + ExpectIntEQ(wolfSSL_UseCertificateAuthority(NULL, dn, 16), BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_UseCertificateAuthority(ssl, NULL, 16), BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_UseCertificateAuthority(ssl, dn, 0), BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_UseCertificateAuthority(ssl, dn, + WOLFSSL_MAX_16BIT), BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_UseCertificateAuthority(ssl, dn, 16), 0); + + /* CTX mirror. */ + ExpectIntEQ(wolfSSL_CTX_UseCertificateAuthority(NULL, dn, 16), + BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_CTX_UseCertificateAuthority(ctx, NULL, 16), + BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_CTX_UseCertificateAuthority(ctx, dn, 0), + BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_CTX_UseCertificateAuthority(ctx, dn, + WOLFSSL_MAX_16BIT), BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_CTX_UseCertificateAuthority(ctx, dn, 16), 0); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} /* END test_ssl_api_ext_guards_decision_coverage */ diff --git a/tests/api/test_ssl_ext.h b/tests/api/test_ssl_ext.h index f0aa7acae96..dd8092b002f 100644 --- a/tests/api/test_ssl_ext.h +++ b/tests/api/test_ssl_ext.h @@ -28,6 +28,9 @@ int test_wolfSSL_session_null_burndown(void); int test_wolfSSL_api_null_operands(void); int test_wolfSSL_public_null_operands(void); int test_wolfSSL_session_lifecycle_guards(void); +int test_ssl_api_decision_coverage(void); +int test_ech_decision_coverage(void); +int test_ssl_api_ext_guards_decision_coverage(void); int test_wolfSSL_NoTicketTLSv12_ext(void); int test_wolfSSL_CTX_UseMaxFragment_ext(void); @@ -38,6 +41,7 @@ int test_wolfSSL_CTX_set_TicketHint_ext(void); int test_wolfSSL_CTX_set_TicketHint_default_cb_limit(void); int test_wolfSSL_tlsext_max_fragment_length_ext(void); int test_wolfSSL_DisableExtendedMasterSecret_ext(void); +int test_wolfSSL_EnableRequireExtendedMasterSecret_ext(void); int test_wolfSSL_set_tlsext_host_name_ext(void); int test_wolfSSL_CTX_set_tlsext_servername_callback_ext(void); int test_wolfSSL_set_tlsext_debug_arg_ext(void); @@ -79,6 +83,8 @@ int test_wolfSSL_ticket_key_cb_renew_ext(void); test_wolfSSL_tlsext_max_fragment_length_ext), \ TEST_DECL_GROUP("ssl_ext", \ test_wolfSSL_DisableExtendedMasterSecret_ext), \ + TEST_DECL_GROUP("ssl_ext", \ + test_wolfSSL_EnableRequireExtendedMasterSecret_ext), \ TEST_DECL_GROUP("ssl_ext", test_wolfSSL_set_tlsext_host_name_ext), \ TEST_DECL_GROUP("ssl_ext", \ test_wolfSSL_CTX_set_tlsext_servername_callback_ext), \ @@ -124,6 +130,9 @@ int test_wolfSSL_ticket_key_cb_renew_ext(void); TEST_DECL_GROUP("ssl_ext", test_wolfSSL_session_null_burndown), \ TEST_DECL_GROUP("ssl_ext", test_wolfSSL_api_null_operands), \ TEST_DECL_GROUP("ssl_ext", test_wolfSSL_public_null_operands), \ - TEST_DECL_GROUP("ssl_ext", test_wolfSSL_session_lifecycle_guards) + TEST_DECL_GROUP("ssl_ext", test_wolfSSL_session_lifecycle_guards), \ + TEST_DECL_GROUP("ssl_ext", test_ssl_api_decision_coverage), \ + TEST_DECL_GROUP("ssl_ext", test_ech_decision_coverage), \ + TEST_DECL_GROUP("ssl_ext", test_ssl_api_ext_guards_decision_coverage) #endif /* TESTS_API_SSL_EXT_H */ diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index 694a0362554..bc9930f4f88 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -4852,3 +4852,457 @@ int test_tls12_aesgcm_record_nonce_unique(void) #endif return EXPECT_RESULT(); } + +/* Decision-coverage driver for the internal SetSSL_CTX() (src/internal.c). + * SetSSL_CTX() runs on every wolfSSL_new() and wolfSSL_write_dup() and on + * explicit context switches; the guards below are reached through those + * entry points plus direct calls with crafted state. */ +int test_internal_SetSSL_CTX_DecisionCoverage(void) +{ + EXPECT_DECLS; + /* Refs WOLFSSL_LOCAL SetSSL_CTX(), hidden from the .so - shared builds + * compile this out; WOLFSSL_TEST_STATIC_BUILD is set for static-only + * builds (configure) and by the MC/DC variant. */ +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ + !defined(WOLFCRYPT_ONLY) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && !defined(NO_PSK) && \ + !defined(NO_CERTS) && !defined(NO_DH) && !defined(NO_FILESYSTEM) + WOLFSSL_CTX *ctx = NULL; + WOLFSSL_CTX *ctxHint = NULL; + WOLFSSL *ssl = NULL; + + ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); + ExpectNotNull(ctx); + + /* 7864: !ssl || !ctx */ + ExpectIntEQ(SetSSL_CTX(NULL, ctx, 0), BAD_FUNC_ARG); /* cond0 T, cond1 F */ + ssl = wolfSSL_new(ctx); + ExpectNotNull(ssl); + ExpectIntEQ(SetSSL_CTX(ssl, NULL, 0), BAD_FUNC_ARG); /* cond0 F, cond1 T */ + ExpectIntEQ(SetSSL_CTX(ssl, ctx, 0), WOLFSSL_SUCCESS); /* both F */ + + /* 7870: ctx->server_hint[0] && ssl->arrays == NULL && !writeDup. + * ssl->arrays is allocated by ReinitSSL() before SetSSL_CTX() in the + * wolfSSL_new() path, so the guard's arrays-NULL half is only reached + * on the writeDup path; a zeroed WOLFSSL stands in for that state. + * With writeDup 1 the body never touches ssl->arrays. */ + ctxHint = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); + ExpectNotNull(ctxHint); + /* The guard (7870) tests only server_hint[0]; a single store keeps the + * state minimal and stays clear of strncpy range analysis on the + * macro-sized member. */ + ctxHint->server_hint[0] = 'h'; + { + WOLFSSL *raw = (WOLFSSL *)XMALLOC(sizeof(WOLFSSL), NULL, + DYNAMIC_TYPE_SSL); + ExpectNotNull(raw); + XMEMSET(raw, 0, sizeof(WOLFSSL)); + /* all T: hint set + arrays NULL + writeDup 0 -> rejected */ + ExpectIntEQ(SetSSL_CTX(raw, ctxHint, 0), BAD_FUNC_ARG); + /* cond0 F: hint unset, same state -> accepted */ + XMEMSET(raw, 0, sizeof(WOLFSSL)); + ExpectIntEQ(SetSSL_CTX(raw, ctx, 0), WOLFSSL_SUCCESS); + /* cond2 F: writeDup 1 skips the guard */ + XMEMSET(raw, 0, sizeof(WOLFSSL)); + ExpectIntEQ(SetSSL_CTX(raw, ctxHint, 1), WOLFSSL_SUCCESS); + XFREE(raw, NULL, DYNAMIC_TYPE_SSL); + /* cond1 F: hint set + arrays set -> accepted */ + ExpectIntEQ(SetSSL_CTX(ssl, ctxHint, 0), WOLFSSL_SUCCESS); + } + + /* 7910: !got_client_hello && !got_server_hello -> version from ctx. */ + { + WOLFSSL_CTX *ctx2 = wolfSSL_CTX_new(wolfTLSv1_2_client_method()); + ExpectNotNull(ctx2); + /* cond0 T / cond1 T: no hellos received -> version taken from ctx */ + ExpectIntEQ(SetSSL_CTX(ssl, ctx2, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(ssl->version.minor, TLSv1_2_MINOR); + /* cond0 F: client hello received -> version untouched */ + ssl->msgsReceived.got_client_hello = 1; + ExpectIntEQ(SetSSL_CTX(ssl, ctx2, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(ssl->version.minor, TLSv1_2_MINOR); + ssl->msgsReceived.got_client_hello = 0; + /* cond1 F: server hello received -> version untouched */ + ssl->msgsReceived.got_server_hello = 1; + ExpectIntEQ(SetSSL_CTX(ssl, ctx2, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(ssl->version.minor, TLSv1_2_MINOR); + ssl->msgsReceived.got_server_hello = 0; + ExpectIntEQ(SetSSL_CTX(ssl, ctx, 0), WOLFSSL_SUCCESS); + wolfSSL_CTX_free(ctx2); + } + + /* 7944: newSSL || ctx->method->side != WOLFSSL_NEITHER_END */ + { + WOLFSSL_CTX *ctxNoSide = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); + ExpectNotNull(ctxNoSide); + /* cond0 F / cond1 T: existing SSL, ctx with a defined side */ + ssl->options.side = WOLFSSL_NEITHER_END; + ExpectIntEQ(SetSSL_CTX(ssl, ctx, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(ssl->options.side, WOLFSSL_CLIENT_END); + /* cond1 F: existing SSL, ctx side poked to NEITHER */ + ctxNoSide->method->side = WOLFSSL_NEITHER_END; + ssl->options.side = WOLFSSL_CLIENT_END; + ExpectIntEQ(SetSSL_CTX(ssl, ctxNoSide, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(ssl->options.side, WOLFSSL_CLIENT_END); + /* cond0 T: newSSL (ctx poked NULL) with NEITHER side ctx: side + * still taken from the ctx method */ + ssl->options.side = WOLFSSL_NEITHER_END; + ssl->ctx = NULL; + ExpectIntEQ(SetSSL_CTX(ssl, ctxNoSide, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(ssl->options.side, WOLFSSL_NEITHER_END); + wolfSSL_CTX_free(ctxNoSide); + } + + /* 7955: haveDH && P.buffer && G.buffer. The condition reads + * ssl->options.haveDH, which was just assigned from ctx->haveDH, so the + * pairs poke ctx->haveDH (not the ssl field, which would be clobbered). + * Lengths stay 0 so the 8051 CopySSL_CTX_DhParams reads nothing. */ + { + byte dummy[4]; + XMEMSET(dummy, 0, sizeof(dummy)); + ctx->serverDH_P.length = 0; + ctx->serverDH_G.length = 0; + /* all T: haveDH on, both buffers set -> suites re-initialized */ + ctx->haveDH = 1; + ctx->serverDH_P.buffer = dummy; + ctx->serverDH_G.buffer = dummy; + ExpectIntEQ(SetSSL_CTX(ssl, ctx, 0), WOLFSSL_SUCCESS); + /* cond0 F: haveDH off, buffers fixed set */ + ctx->haveDH = 0; + ExpectIntEQ(SetSSL_CTX(ssl, ctx, 0), WOLFSSL_SUCCESS); + /* cond1 F: P NULL, haveDH on + G fixed set */ + ctx->haveDH = 1; + ctx->serverDH_P.buffer = NULL; + ExpectIntEQ(SetSSL_CTX(ssl, ctx, 0), WOLFSSL_SUCCESS); + /* cond2 F: G NULL, haveDH on + P fixed set */ + ctx->serverDH_P.buffer = dummy; + ctx->serverDH_G.buffer = NULL; + ExpectIntEQ(SetSSL_CTX(ssl, ctx, 0), WOLFSSL_SUCCESS); + ctx->serverDH_P.buffer = NULL; + ctx->serverDH_G.buffer = NULL; + ctx->haveDH = 0; + } + + /* 8066: cacheMessages = side == SERVER || keyType ed25519/ed448/sm2. + * SetSSL_CTX() takes the side from the ctx method (7944) and the keyType + * from the key loaded in the ctx (SetSSL_CTX_CertsAndKeys), so each pair + * is a ctx with the matching key loaded. */ +#if defined(HAVE_ED25519) && defined(HAVE_ED448) + { + WOLFSSL_CTX *ctxSrv = wolfSSL_CTX_new(wolfTLSv1_3_server_method()); + ExpectNotNull(ctxSrv); + /* a server ctx without a key makes SetSSL_CTX_CertsAndKeys return + * NO_PRIVATE_KEY before the 8066 assignment runs */ + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctxSrv, svrKeyFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctxSrv, svrCertFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + /* F for all four: client side, no key loaded */ + ExpectIntEQ(SetSSL_CTX(ssl, ctx, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(ssl->options.cacheMessages, 0); + /* cond0 T: server-side ctx */ + ExpectIntEQ(SetSSL_CTX(ssl, ctxSrv, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(ssl->options.cacheMessages, 1); + /* cond1 T: ed25519 key */ + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx, + "certs/ed25519/server-ed25519-priv.pem", CERT_FILETYPE), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx, + "certs/ed25519/server-ed25519-cert.pem", CERT_FILETYPE), + WOLFSSL_SUCCESS); + ExpectIntEQ(SetSSL_CTX(ssl, ctx, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(ssl->options.cacheMessages, 1); + /* cond2 T: ed448 key */ + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx, + "certs/ed448/server-ed448-priv.pem", CERT_FILETYPE), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx, + "certs/ed448/server-ed448-cert.pem", CERT_FILETYPE), + WOLFSSL_SUCCESS); + ExpectIntEQ(SetSSL_CTX(ssl, ctx, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(ssl->options.cacheMessages, 1); + /* cond3 T: sm2 key (structural gap when WOLFSSL_SM2 is off: no sm2 + * key can load, so keyType can never be sm2_sa_algo) */ +#ifdef HAVE_SM2 + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx, + "certs/sm2/server-sm2-priv.pem", CERT_FILETYPE), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx, + "certs/sm2/server-sm2-cert.pem", CERT_FILETYPE), + WOLFSSL_SUCCESS); + ExpectIntEQ(SetSSL_CTX(ssl, ctx, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(ssl->options.cacheMessages, 1); +#endif + wolfSSL_CTX_free(ctxSrv); + } +#endif + + /* 8099: ctx->mask != 0 && wolfSSL_set_options(ssl, mask) == 0. + * wolfSSL_set_options returns the resulting mask (old | op), which is + * nonzero whenever ctx->mask != 0, so the "== 0" half is a dead check; + * the "!= 0" half needs that dead half to be T to pair, so both halves + * are structurally uncoverable here. The pair below is left to exercise + * the mask!=0 path for robustness. */ + { + /* cond0 F: mask 0 -> set_options not called */ + ctx->mask = 0; + ExpectIntEQ(SetSSL_CTX(ssl, ctx, 0), WOLFSSL_SUCCESS); + /* cond0 T / cond1 F: mask set, set_options returns nonzero */ + (void)wolfSSL_CTX_set_options(ctx, WOLFSSL_OP_NO_TICKET); + ExpectTrue(ctx->mask != 0); + ExpectIntEQ(SetSSL_CTX(ssl, ctx, 0), WOLFSSL_SUCCESS); + ctx->mask = 0; + } + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + wolfSSL_CTX_free(ctxHint); +#endif + return EXPECT_RESULT(); +} + +/* Decision-coverage driver for CheckVersion() (src/internal.c), called with + * crafted protocol versions and poked option state. The version pairs are + * chosen so that lowerVersion/higherVersion land exactly where each target + * condition needs them. */ +int test_internal_CheckVersion_DecisionCoverage(void) +{ + EXPECT_DECLS; + /* Refs WOLFSSL_LOCAL CheckVersion(), hidden from the .so - shared + * builds compile this out; WOLFSSL_TEST_STATIC_BUILD is set for + * static-only builds (configure) and by the MC/DC variant. */ +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(HAVE_SECURE_RENEGOTIATION) && \ + !defined(WOLFSSL_NO_TLS12) + WOLFSSL_CTX *ctx = NULL; + WOLFSSL *ssl = NULL; + ProtocolVersion pv; + SecureRenegotiation scr; + + ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method()); + ExpectNotNull(ctx); + ssl = wolfSSL_new(ctx); + ExpectNotNull(ssl); + + XMEMSET(&pv, 0, sizeof(pv)); + XMEMSET(&scr, 0, sizeof(scr)); + + /* 35145: pv.major != DTLS_MAJOR || pv.minor == DTLS_BOGUS_MINOR */ + ssl->options.dtls = 1; + ssl->version.minor = DTLSv1_2_MINOR; + pv.major = SSLv3_MAJOR; + pv.minor = DTLSv1_2_MINOR; + ExpectIntEQ(CheckVersion(ssl, pv), VERSION_ERROR); /* cond0 T */ + pv.major = DTLS_MAJOR; + pv.minor = DTLS_BOGUS_MINOR; + ExpectIntEQ(CheckVersion(ssl, pv), VERSION_ERROR); /* cond0 F, cond1 T */ + pv.minor = DTLSv1_2_MINOR; + ExpectIntEQ(CheckVersion(ssl, pv), 0); /* both F */ + + /* 35175: (!dtls && pv.minor < minDowngrade) || + * (dtls && pv.minor > minDowngrade) */ + ssl->options.downgrade = 1; + /* TLS direction: version 1.2, pv 1.0 -> lowerVersion */ + ssl->options.dtls = 0; + ssl->version.minor = TLSv1_2_MINOR; + pv.major = SSLv3_MAJOR; + pv.minor = TLSv1_MINOR; + /* cond0 T / cond1 F: not dtls, pv.minor >= minDowngrade */ + ssl->options.minDowngrade = TLSv1_MINOR; + ExpectIntEQ(CheckVersion(ssl, pv), 0); + /* cond1 T: not dtls, pv.minor < minDowngrade (reset version: the + * previous call downgraded it) */ + ssl->version.minor = TLSv1_2_MINOR; + ssl->options.minDowngrade = TLSv1_2_MINOR; + ExpectIntEQ(CheckVersion(ssl, pv), VERSION_ERROR); + ssl->version.minor = TLSv1_2_MINOR; + ssl->options.minDowngrade = TLSv1_MINOR; + /* exercise the second disjunct's pv>minDowngrade path in the TLS + * direction (pv above minDowngrade, version above pv so lowerVersion + * holds); the dtls term itself is the negation of the leading !dtls + * term, so the tool cannot credit it independently */ + ssl->version.minor = TLSv1_2_MINOR; + pv.minor = TLSv1_MINOR; + ssl->options.minDowngrade = SSLv3_MINOR; + ExpectIntEQ(CheckVersion(ssl, pv), 0); + ssl->version.minor = TLSv1_2_MINOR; + ssl->options.minDowngrade = TLSv1_MINOR; + /* DTLS direction: version 1.2, pv 1.0 -> lowerVersion (DTLS minors are + * inverted: 1.0 = 0xff > 1.2 = 0xfd) */ + ssl->options.dtls = 1; + ssl->version.minor = DTLSv1_2_MINOR; + pv.major = DTLS_MAJOR; + pv.minor = DTLS_MINOR; + /* cond2 T / cond3 F: dtls, pv.minor <= minDowngrade */ + ssl->options.minDowngrade = DTLS_MINOR; + ExpectIntEQ(CheckVersion(ssl, pv), 0); + /* cond3 T: dtls, pv.minor > minDowngrade (reset version: the + * previous call downgraded it) */ + ssl->version.minor = DTLSv1_2_MINOR; + ssl->options.minDowngrade = DTLSv1_2_MINOR; + ExpectIntEQ(CheckVersion(ssl, pv), VERSION_ERROR); + ssl->version.minor = DTLSv1_2_MINOR; + ssl->options.minDowngrade = DTLSv1_2_MINOR; + + /* 35183: secure_renegotiation && ->enabled && handShakeDone */ + ssl->options.dtls = 0; + pv.major = SSLv3_MAJOR; + pv.minor = TLSv1_MINOR; + ssl->options.minDowngrade = TLSv1_MINOR; + /* cond0 F: no secure renegotiation state */ + ssl->version.minor = TLSv1_2_MINOR; + ssl->secure_renegotiation = NULL; + ExpectIntEQ(CheckVersion(ssl, pv), 0); + /* cond0 T / cond1 F: state present, not enabled */ + ssl->version.minor = TLSv1_2_MINOR; + scr.enabled = 0; + ssl->secure_renegotiation = &scr; + ExpectIntEQ(CheckVersion(ssl, pv), 0); + /* cond1 T / cond2 F: enabled, handshake not done */ + ssl->version.minor = TLSv1_2_MINOR; + scr.enabled = 1; + ssl->options.handShakeDone = 0; + ExpectIntEQ(CheckVersion(ssl, pv), 0); + /* all T: enabled + handshake done -> version change rejected */ + ssl->version.minor = TLSv1_2_MINOR; + ssl->options.handShakeDone = 1; + ExpectIntEQ(CheckVersion(ssl, pv), VERSION_ERROR); + ssl->options.handShakeDone = 0; + ssl->secure_renegotiation = NULL; + + /* 35227: !dtls && downgrade && mask > 0 (pv == version: no lower) */ + ssl->version.minor = DTLSv1_2_MINOR; + pv.major = DTLS_MAJOR; + pv.minor = DTLSv1_2_MINOR; + /* cond0 F: dtls on -> whole block skipped */ + ssl->options.dtls = 1; + ssl->options.downgrade = 1; + ssl->options.mask = WOLFSSL_OP_NO_TLSv1_2; + ExpectIntEQ(CheckVersion(ssl, pv), 0); + /* cond0 T / cond1 F: not dtls, downgrade off */ + ssl->options.dtls = 0; + ssl->options.downgrade = 0; + ssl->version.minor = TLSv1_2_MINOR; + pv.major = SSLv3_MAJOR; + pv.minor = TLSv1_2_MINOR; + ExpectIntEQ(CheckVersion(ssl, pv), 0); + /* cond1 T / cond2 F: downgrade on, mask 0 */ + ssl->options.downgrade = 1; + ssl->options.mask = 0; + ExpectIntEQ(CheckVersion(ssl, pv), 0); + /* all T: block entered */ + ssl->options.mask = WOLFSSL_OP_NO_TLSv1_2; + + /* 35230: version.minor == TLSv1_2 && (mask & OP_NO_TLSv1_2) */ + ExpectIntEQ(CheckVersion(ssl, pv), 0); /* both T: downgrade to 1.1 */ + ExpectIntEQ(ssl->version.minor, TLSv1_1_MINOR); + /* cond1 F: version 1.2 again, mask lacks the NO_TLSv1_2 bit */ + ssl->version.minor = TLSv1_2_MINOR; + pv.minor = TLSv1_2_MINOR; + ssl->options.mask = WOLFSSL_OP_NO_TLSv1_1; + ExpectIntEQ(CheckVersion(ssl, pv), 0); + /* cond0 F: version no longer 1.2 */ + ssl->version.minor = TLSv1_1_MINOR; + pv.minor = TLSv1_1_MINOR; + ExpectIntEQ(CheckVersion(ssl, pv), 0); + /* 35237: version.minor == TLSv1_1 && (mask & OP_NO_TLSv1_1) */ + /* reset version: the cond0-F call above downgraded it to 1.0, which + * would make pv a higher version */ + ssl->version.minor = TLSv1_1_MINOR; + ExpectIntEQ(CheckVersion(ssl, pv), 0); /* both T: downgrade to 1.0 */ + ExpectIntEQ(ssl->version.minor, TLSv1_MINOR); + ssl->options.mask = 0; + /* cond0 F for 35237: version 1.0, not 1.1 */ + pv.minor = TLSv1_MINOR; + ExpectIntEQ(CheckVersion(ssl, pv), 0); + /* 35245: version.minor == TLSv1 && (mask & OP_NO_TLSv1) */ + ssl->options.mask = WOLFSSL_OP_NO_TLSv1; + /* the downgrade to SSLv3 must survive the 35254 minDowngrade check */ + ssl->options.minDowngrade = SSLv3_MINOR; + ExpectIntEQ(CheckVersion(ssl, pv), 0); /* both T: downgrade to SSLv3 */ + ExpectIntEQ(ssl->version.minor, SSLv3_MINOR); + ssl->options.mask = 0; + ssl->options.minDowngrade = TLSv1_MINOR; + /* cond0 F for 35245: version SSLv3, not 1.0 */ + pv.minor = SSLv3_MINOR; + ExpectIntEQ(CheckVersion(ssl, pv), 0); + /* 35254: version.minor == SSLv3 && (mask & OP_NO_SSLv3) */ + ssl->options.mask = WOLFSSL_OP_NO_SSLv3; + ExpectIntEQ(CheckVersion(ssl, pv), VERSION_ERROR); /* both T */ + /* cond0 F: version no longer SSLv3 */ + pv.minor = TLSv1_MINOR; + ssl->version.minor = TLSv1_MINOR; + ExpectIntEQ(CheckVersion(ssl, pv), 0); + /* cond1 F: SSLv3 version, mask bit clear */ + pv.minor = SSLv3_MINOR; + ssl->version.minor = SSLv3_MINOR; + ssl->options.mask = 0; + ExpectIntEQ(CheckVersion(ssl, pv), 0); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* + * MC/DC decision coverage for the wolfSSL_SetTlsHmacInner content/CID + * guard (master drift, 2026-10-06). The dtls and cidTxSize clauses are + * only compiled under WOLFSSL_DTLS + WOLFSSL_DTLS_CID, so this test must + * run in the campaign's DTLS_CID variant to complete the pairs. + */ +int test_tls_hmac_inner_cid_decision_coverage(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CID) && !defined(WOLFSSL_NO_TLS12) && \ + defined(WOLFSSL_DTLS) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *dctx = NULL; + WOLFSSL *dssl = NULL; + byte cid[4] = { 1, 2, 3, 4 }; +#endif +#if defined(WOLFSSL_DTLS) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(WOLFSSL_NO_TLS12) + WOLFSSL_CTX *ctx = NULL; + WOLFSSL *ssl = NULL; + byte inner[WOLFSSL_TLS_HMAC_INNER_SZ]; + + ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); + if (ctx == NULL) + return EXPECT_RESULT(); + ssl = wolfSSL_new(ctx); + if (ssl == NULL) { + wolfSSL_CTX_free(ctx); + return EXPECT_RESULT(); + } + /* content != dtls12_cid, dtls F: the dtls clause's false side. */ + ExpectIntEQ(wolfSSL_SetTlsHmacInner(ssl, inner, 0, application_data, 0), + 0); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + +#if defined(WOLFSSL_DTLS_CID) && !defined(WOLFSSL_NO_TLS12) + dctx = wolfSSL_CTX_new(wolfDTLSv1_2_client_method()); + if (dctx == NULL) + return EXPECT_RESULT(); + dssl = wolfSSL_new(dctx); + if (dssl == NULL) { + wolfSSL_CTX_free(dctx); + return EXPECT_RESULT(); + } + /* content != dtls12_cid, dtls T, cidTxSize F. */ + ExpectIntEQ(wolfSSL_SetTlsHmacInner(dssl, inner, 0, application_data, 0), + 0); + /* dtls T, cidTxSize T. */ + ExpectIntEQ(wolfSSL_dtls_cid_use(dssl), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_dtls_cid_set(dssl, cid, sizeof(cid)), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_SetTlsHmacInner(dssl, inner, 0, application_data, 0), + 0); + /* content == dtls12_cid: the first clause's true side. */ + ExpectIntEQ(wolfSSL_SetTlsHmacInner(dssl, inner, 0, dtls12_cid, 0), + BAD_FUNC_ARG); + wolfSSL_free(dssl); + wolfSSL_CTX_free(dctx); +#endif /* WOLFSSL_DTLS_CID */ +#endif /* WOLFSSL_DTLS */ + return EXPECT_RESULT(); +} /* END test_tls_hmac_inner_cid_decision_coverage */ diff --git a/tests/api/test_tls.h b/tests/api/test_tls.h index 0c629ad536a..2b1c221341a 100644 --- a/tests/api/test_tls.h +++ b/tests/api/test_tls.h @@ -76,11 +76,14 @@ int test_tls12_ecdhe_rsa_ecdsa_client_cert(void); int test_wolfSSL_alert_type_string(void); int test_wolfSSL_alert_desc_string(void); int test_record_size_matches_build_message(void); +int test_internal_SetSSL_CTX_DecisionCoverage(void); +int test_internal_CheckVersion_DecisionCoverage(void); int test_record_size_preserves_build_msg_state(void); int test_record_size_cache_invalidated_on_renegotiation(void); int test_wolfSSL_get_shared_ciphers(void); int test_tls12_aesgcm_record_nonce_unique(void); int test_tls_param_flags_crl_check(void); +int test_tls_hmac_inner_cid_decision_coverage(void); #define TEST_TLS_DECLS \ TEST_DECL_GROUP("tls", test_utils_memio_move_message), \ @@ -144,6 +147,9 @@ int test_tls_param_flags_crl_check(void); test_record_size_cache_invalidated_on_renegotiation), \ TEST_DECL_GROUP("tls", test_wolfSSL_get_shared_ciphers), \ TEST_DECL_GROUP("tls", test_tls12_aesgcm_record_nonce_unique), \ - TEST_DECL_GROUP("tls", test_tls_param_flags_crl_check) + TEST_DECL_GROUP("tls", test_internal_SetSSL_CTX_DecisionCoverage), \ + TEST_DECL_GROUP("tls", test_internal_CheckVersion_DecisionCoverage), \ + TEST_DECL_GROUP("tls", test_tls_param_flags_crl_check), \ + TEST_DECL_GROUP("tls", test_tls_hmac_inner_cid_decision_coverage) #endif /* TESTS_API_TEST_TLS_H */ diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index 9e015bc56cc..0849107c98d 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -14851,3 +14851,94 @@ int test_tls13_export_client_key_update(void) #endif return EXPECT_RESULT(); } + +/* + * MC/DC decision coverage for the TLS 1.3 exporter version guards and + * FindSuiteSSL's per-byte suite comparison. DeriveExporterSecret's version + * check is reached through wolfSSL_export_keying_material after a completed + * handshake; the mismatched-minor probes overwrite ssl->version.minor on the + * completed object so the guard fires. + */ +int test_tls13_exporter_suite_decision_coverage(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(HAVE_KEYING_MATERIAL) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + (defined(WOLFSSL_TLS13) || defined(WOLFSSL_DTLS13)) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte out[64]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(out, 0, sizeof(out)); + +#ifdef WOLFSSL_TLS13 + /* (!dtls) && (minor == TLSv1_3_MINOR): both true on the completed + * handshake; the mismatched minor takes the second operand's true side. */ + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + wolfSSL_KeepArrays(ssl_c); + wolfSSL_KeepArrays(ssl_s); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); +#if defined(HAVE_KEYING_MATERIAL) + ExpectIntEQ(wolfSSL_export_keying_material(ssl_c, out, sizeof(out), + "MCDC", 4, NULL, 0, 0), WOLFSSL_SUCCESS); + /* Stay on the TLS 1.3 exporter path while failing its exact-version + * check. A TLS 1.0 minor selects the TLS 1.2 exporter instead. */ + ssl_c->version.minor = TLSv1_3_MINOR + 1; + ExpectIntEQ(wolfSSL_export_keying_material(ssl_c, out, sizeof(out), + "MCDC", 4, NULL, 0, 0), WOLFSSL_FAILURE); +#endif /* HAVE_KEYING_MATERIAL */ + +#if defined(WOLFSSL_TEST_STATIC_BUILD) + byte probe[2]; + + /* FindSuiteSSL: (suites[i] == suite[0]) && (suites[i+1] == suite[1]). + * The object's own first suite is the both-true row; its first byte with + * 0xFF as the second is the first-true/second-false row (no cipher + * suite's second byte is 0xFF). */ + ExpectIntEQ(FindSuiteSSL(ssl_c, (byte*)ssl_c->suites->suites), 1); + probe[0] = ssl_c->suites->suites[0]; + probe[1] = 0xFF; + ExpectIntEQ(FindSuiteSSL(ssl_c, probe), 0); +#endif /* WOLFSSL_TEST_STATIC_BUILD */ + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); + /* NULL the pointers: the DTLS 1.3 block below reuses this driver, + * and test_memio_setup only creates a ctx when the slot is NULL. */ + ctx_c = NULL; + ctx_s = NULL; + ssl_c = NULL; + ssl_s = NULL; +#endif /* WOLFSSL_TLS13 */ + +#ifdef WOLFSSL_DTLS13 + /* (dtls) && (minor == DTLSv1_3_MINOR): both true on the completed + * handshake; the mismatched minor takes the second operand's true side. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + wolfSSL_KeepArrays(ssl_c); + wolfSSL_KeepArrays(ssl_s); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_export_keying_material(ssl_c, out, sizeof(out), + "MCDC", 4, NULL, 0, 0), WOLFSSL_SUCCESS); + ssl_c->version.minor = DTLSv1_3_MINOR - 1; + ExpectIntEQ(wolfSSL_export_keying_material(ssl_c, out, sizeof(out), + "MCDC", 4, NULL, 0, 0), WOLFSSL_FAILURE); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif /* WOLFSSL_DTLS13 */ +#endif /* HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES */ + return EXPECT_RESULT(); +} /* END test_tls13_exporter_suite_decision_coverage */ diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index 64155f5c416..0b710f08265 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -172,6 +172,7 @@ int test_tls13_export_server_ticket_after_import(void); int test_tls13_export_client_ticket_after_import(void); int test_tls13_export_server_key_update(void); int test_tls13_export_client_key_update(void); +int test_tls13_exporter_suite_decision_coverage(void); #define TEST_TLS13_DECLS \ TEST_DECL_GROUP("tls13", test_tls13_apis), \ @@ -322,6 +323,7 @@ int test_tls13_export_client_key_update(void); TEST_DECL_GROUP("tls13", test_tls13_export_server_ticket_after_import), \ TEST_DECL_GROUP("tls13", test_tls13_export_client_ticket_after_import), \ TEST_DECL_GROUP("tls13", test_tls13_export_server_key_update), \ - TEST_DECL_GROUP("tls13", test_tls13_export_client_key_update) + TEST_DECL_GROUP("tls13", test_tls13_export_client_key_update), \ + TEST_DECL_GROUP("tls13", test_tls13_exporter_suite_decision_coverage) #endif /* WOLFCRYPT_TEST_TLS13_H */ diff --git a/tests/api/test_x509.c b/tests/api/test_x509.c index 81fd53d541f..ae86d0d0d6e 100644 --- a/tests/api/test_x509.c +++ b/tests/api/test_x509.c @@ -1417,3 +1417,69 @@ int test_x509_REQ_sign_mldsa(void) #endif return EXPECT_RESULT(); } +int test_x509_decision_coverage(void) +{ + EXPECT_DECLS; +#if !defined(WOLFCRYPT_ONLY) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_TLS) && !defined(NO_FILESYSTEM) && !defined(NO_RSA) && \ + (defined(OPENSSL_EXTRA) || defined(KEEP_PEER_CERT) || \ + defined(KEEP_OUR_CERT)) + WOLFSSL_X509* x509 = NULL; + WOLFSSL_X509* fresh = NULL; + WOLFSSL_X509* loaded = NULL; + int sz = 0; + byte buf[512]; + + XMEMSET(buf, 0, sizeof(buf)); + + /* --- 4223:0/1 d2i_X509orX509REQ (via wolfSSL_d2i_X509): in/len -------- */ + { + const unsigned char* in = NULL; + + (void)wolfSSL_d2i_X509(&x509, &in, 0); + in = buf; + (void)wolfSSL_d2i_X509(&x509, &in, 0); + in = buf; + (void)wolfSSL_d2i_X509(&x509, &in, (int)sizeof(buf)); + } + ExpectNotNull(fresh = wolfSSL_X509_new()); + + /* --- 4439:0/1 get_pubkey_buffer: x509==NULL || bufSz==NULL ----------- */ + (void)wolfSSL_X509_get_pubkey_buffer(NULL, buf, &sz); + (void)wolfSSL_X509_get_pubkey_buffer(fresh, buf, NULL); + + /* --- 4530:0/1/2 get_der: x509==NULL || derCert==NULL || outSz==NULL --- */ + (void)wolfSSL_X509_get_der(NULL, &sz); + (void)wolfSSL_X509_get_der(fresh, &sz); + (void)wolfSSL_X509_get_der(fresh, NULL); + (void)wolfSSL_X509_get_der(loaded, &sz); + (void)wolfSSL_X509_get_der(loaded, NULL); + + /* --- 6090:0/1 load_certificate_file: fname==NULL || format ----------- */ + (void)wolfSSL_X509_load_certificate_file(NULL, WOLFSSL_FILETYPE_PEM); + (void)wolfSSL_X509_load_certificate_file(svrCertFile, 99); + (void)wolfSSL_X509_load_certificate_file(svrCertFile, + WOLFSSL_FILETYPE_ASN1); + loaded = wolfSSL_X509_load_certificate_file(svrCertFile, + WOLFSSL_FILETYPE_PEM); + ExpectNotNull(loaded); + (void)wolfSSL_X509_get_der(loaded, &sz); + + /* --- 4474:0/1 get_pubkey_buffer: buf!=NULL && pubKeyX509!=NULL ------- */ + (void)wolfSSL_X509_get_pubkey_buffer(fresh, buf, &sz); + (void)wolfSSL_X509_get_pubkey_buffer(loaded, buf, &sz); + (void)wolfSSL_X509_get_pubkey_buffer(loaded, NULL, &sz); + + /* --- 16145:0/1/2 check_ip_asc: x==NULL || derCert==NULL || ipasc==NULL */ + (void)wolfSSL_X509_check_ip_asc(NULL, "127.0.0.1", 0); + (void)wolfSSL_X509_check_ip_asc(fresh, "127.0.0.1", 0); + (void)wolfSSL_X509_check_ip_asc(fresh, NULL, 0); + (void)wolfSSL_X509_check_ip_asc(loaded, "127.0.0.1", 0); + (void)wolfSSL_X509_check_ip_asc(loaded, NULL, 0); + + wolfSSL_X509_free(x509); + wolfSSL_X509_free(fresh); + wolfSSL_X509_free(loaded); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_x509.h b/tests/api/test_x509.h index 00b95f46d09..d475b120a38 100644 --- a/tests/api/test_x509.h +++ b/tests/api/test_x509.h @@ -34,6 +34,7 @@ int test_x509_ReqCertFromX509_skid_overflow(void); int test_x509_ReqCertFromX509_skid_boundary(void); int test_x509_ReqCertFromX509_ext_critical(void); int test_x509_REQ_sign_mldsa(void); +int test_x509_decision_coverage(void); #define TEST_X509_DECLS \ TEST_DECL_GROUP("x509", test_x509_rfc2818_verification_callback), \ @@ -47,6 +48,7 @@ int test_x509_REQ_sign_mldsa(void); TEST_DECL_GROUP("x509", test_x509_ReqCertFromX509_skid_overflow), \ TEST_DECL_GROUP("x509", test_x509_ReqCertFromX509_skid_boundary), \ TEST_DECL_GROUP("x509", test_x509_ReqCertFromX509_ext_critical), \ - TEST_DECL_GROUP("x509", test_x509_REQ_sign_mldsa) + TEST_DECL_GROUP("x509", test_x509_REQ_sign_mldsa), \ + TEST_DECL_GROUP("x509", test_x509_decision_coverage) #endif /* WOLFCRYPT_TEST_X509_H */ diff --git a/tests/include.am b/tests/include.am index 0d27b2a34e9..f58fcbd9b61 100644 --- a/tests/include.am +++ b/tests/include.am @@ -252,6 +252,7 @@ EXTRA_DIST += \ tests/unit-mcdc/test_tfm_whitebox.c \ tests/unit-mcdc/test_tls13_null_whitebox.c \ tests/unit-mcdc/test_tls13_whitebox.c \ + tests/unit-mcdc/test_tls_whitebox.c \ tests/unit-mcdc/test_tsp_fault_whitebox.c \ tests/unit-mcdc/test_tsp_whitebox.c \ tests/unit-mcdc/test_wc_encrypt_whitebox.c \ diff --git a/tests/unit-mcdc/mcdc_fault_cryptocb.h b/tests/unit-mcdc/mcdc_fault_cryptocb.h index ef96e70d786..edab7136021 100644 --- a/tests/unit-mcdc/mcdc_fault_cryptocb.h +++ b/tests/unit-mcdc/mcdc_fault_cryptocb.h @@ -95,6 +95,8 @@ typedef struct McdcCbState { int failed; /* matching operations actually failed */ int seen; /* every operation the device was offered */ int armed; /* zero disarms without unregistering the device */ + int once; /* answer failCode on the first match only, then let + * later matches run in software */ } McdcCbState; static McdcCbState mcdc_cb_state; @@ -153,6 +155,16 @@ static WC_INLINE void mcdc_cb_after(int n) mcdc_cb_state.after = n; } +/* Answer WC_PENDING_E on the FIRST matching operation only, then let later + * matches run in software. The async state machines (wc_RsaFunction / + * wc_RsaDirect) save their state and resume on re-invocation; the second + * dispatch must complete, so a persistent PENDING answer would loop forever. */ +static WC_INLINE void mcdc_cb_pending_once(int pkType) +{ + mcdc_cb_answer_pk(pkType, WC_PENDING_E); + mcdc_cb_state.once = 1; +} + static WC_INLINE void mcdc_cb_disarm(void) { mcdc_cb_state.armed = 0; @@ -218,16 +230,30 @@ static WC_INLINE int mcdc_cb_callback(int devId, wc_CryptoInfo* info, void* ctx) } st->failed++; + if (st->once) { + st->once = 0; + st->armed = 0; + } return st->failCode; } /* Registration is global and idempotent; the state is reset separately so a - * vector can re-arm without re-registering. */ + * vector can re-arm without re-registering. A white-box binary that never + * calls wolfSSL_Init() has an all-zero device table (no free slot), so a + * BUFFER_E answer means "uninitialised": init it once and retry. */ static WC_INLINE int mcdc_cb_install(void) { + int rc; + mcdc_cb_reset(); - return wc_CryptoCb_RegisterDevice(MCDC_CB_DEVID, mcdc_cb_callback, - &mcdc_cb_state); + rc = wc_CryptoCb_RegisterDevice(MCDC_CB_DEVID, mcdc_cb_callback, + &mcdc_cb_state); + if (rc == WC_NO_ERR_TRACE(BUFFER_E)) { + wc_CryptoCb_Init(); + rc = wc_CryptoCb_RegisterDevice(MCDC_CB_DEVID, mcdc_cb_callback, + &mcdc_cb_state); + } + return rc; } static WC_INLINE void mcdc_cb_uninstall(void) diff --git a/tests/unit-mcdc/run-whitebox-smoke.sh b/tests/unit-mcdc/run-whitebox-smoke.sh index a566b5636f8..4c9dceadf63 100755 --- a/tests/unit-mcdc/run-whitebox-smoke.sh +++ b/tests/unit-mcdc/run-whitebox-smoke.sh @@ -68,6 +68,12 @@ for tu in "$HERE"/*_whitebox.c; do -DUSE_CERT_BUFFERS_2048 -DUSE_CERT_BUFFERS_256 $TARGETFLAGS \ -o "$work/$name.bin" "$tu" "$work/t.a" -lm -lpthread $LDEXTRA ) \ >"$work/$name.log" 2>&1; then + # A TU that is expected to build here and does not is a + # regression - print its build log now, before the workdir is + # cleaned at exit, or the exact error is lost with it. + if [ -f "$EXPECTED" ] && grep -qx "$name" "$EXPECTED"; then + echo "BUILD FAIL $name"; tail -5 "$work/$name.log" | sed 's/^/ /' + fi skip+=("$name"); continue fi # These TUs report row failures as text and still exit 0, so that the diff --git a/tests/unit-mcdc/test_aes_whitebox.c b/tests/unit-mcdc/test_aes_whitebox.c index 6183d24d82b..b84cbee4aec 100644 --- a/tests/unit-mcdc/test_aes_whitebox.c +++ b/tests/unit-mcdc/test_aes_whitebox.c @@ -57,6 +57,11 @@ #include +/* WOLF_CRYPTO_CB fault device: answers WC_PENDING_E to one selected cipher + * operation, so the wc_AesGcmEncrypt_ex / wc_AesCcmEncrypt_ex nonce-advance + * guard's PENDING arm is reached without a real asynchronous backend. */ +#include "mcdc_fault_cryptocb.h" + #ifndef INVALID_DEVID #define INVALID_DEVID (-2) #endif @@ -1174,6 +1179,114 @@ static void wb_aarch64_ctr_leftover(void) { WB_NOTE("aarch64 base CTR body not compiled in this variant; skipped"); } #endif +/* ------------------------------------------------------------------------- * + * GCM/CCM _ex nonce-advance PENDING arms. + * + * if (ret == 0 || ret == WC_NO_ERR_TRACE(WC_PENDING_E)) (15190 GCM, 16046 CCM) + * + * ret is the return of the inner wc_AesGcmEncrypt / wc_AesCcmEncrypt. The + * ret==0 arm is the ordinary software path; the PENDING arm needs the inner + * op to report WC_PENDING_E, which only an asynchronous backend does. A real + * async backend is unavailable here, so the WOLF_CRYPTO_CB fault device + * (mcdc_fault_cryptocb.h) answers WC_PENDING_E to the one cipher op: the _ex + * wrapper is called on an Aes whose devId routes the inner encrypt through the + * device. The baseline (device uninstalled) supplies the ret==0 row; the armed + * device supplies the PENDING row. + * ------------------------------------------------------------------------- */ +static void wb_gcmccm_pending(void) +{ +#if defined(HAVE_AESGCM) && defined(HAVE_AESCCM) && defined(WOLF_CRYPTO_CB) + Aes aes; + WC_RNG rng; + byte key[16]; + byte in[32]; + byte out[32]; + byte iv[12]; + byte tag[16]; + byte aad[8]; + + XMEMSET(key, 0x11, sizeof(key)); + XMEMSET(in, 0x22, sizeof(in)); + XMEMSET(out, 0, sizeof(out)); + XMEMSET(iv, 0x33, sizeof(iv)); + XMEMSET(tag, 0, sizeof(tag)); + XMEMSET(aad, 0x44, sizeof(aad)); + + if (wc_InitRng(&rng) != 0) { + WB_NOTE("wc_InitRng failed (GCM/CCM PENDING skipped)"); + wb_fail = 1; + return; + } + + /* Zero the device table (a zeroed slot has devId==0, not INVALID_DEVID, + * so registration finds no free slot until this runs). */ + wc_CryptoCb_Init(); + + /* GCM: baseline (ret==0) then PENDING. */ + mcdc_cb_uninstall(); + if (wc_AesInit(&aes, NULL, MCDC_CB_DEVID) == 0 && + wc_AesGcmSetKey(&aes, key, sizeof(key)) == 0 && + wc_AesGcmSetIV(&aes, sizeof(iv), NULL, 0, &rng) == 0) { + (void)wc_AesGcmEncrypt_ex(&aes, out, in, sizeof(in), + iv, sizeof(iv), tag, sizeof(tag), aad, sizeof(aad)); + wc_AesFree(&aes); + } + mcdc_cb_install(); + mcdc_cb_fail_algo(WC_ALGO_TYPE_CIPHER, WC_PENDING_E); + if (wc_AesInit(&aes, NULL, MCDC_CB_DEVID) == 0 && + wc_AesGcmSetKey(&aes, key, sizeof(key)) == 0 && + wc_AesGcmSetIV(&aes, sizeof(iv), NULL, 0, &rng) == 0) { + (void)wc_AesGcmEncrypt_ex(&aes, out, in, sizeof(in), + iv, sizeof(iv), tag, sizeof(tag), aad, sizeof(aad)); + wc_AesFree(&aes); + } + /* Third row: a plain (non-PENDING) error makes the decision FALSE, which + * is the missing half of each operand's MC/DC pair (ret==0 and ret==PENDING + * both leave the decision TRUE). */ + mcdc_cb_fail_algo(WC_ALGO_TYPE_CIPHER, WC_HW_E); + if (wc_AesInit(&aes, NULL, MCDC_CB_DEVID) == 0 && + wc_AesGcmSetKey(&aes, key, sizeof(key)) == 0 && + wc_AesGcmSetIV(&aes, sizeof(iv), NULL, 0, &rng) == 0) { + (void)wc_AesGcmEncrypt_ex(&aes, out, in, sizeof(in), + iv, sizeof(iv), tag, sizeof(tag), aad, sizeof(aad)); + wc_AesFree(&aes); + } + + /* CCM: baseline (ret==0) then PENDING. */ + mcdc_cb_disarm(); + if (wc_AesInit(&aes, NULL, MCDC_CB_DEVID) == 0 && + wc_AesCcmSetKey(&aes, key, sizeof(key)) == 0 && + wc_AesCcmSetNonce(&aes, iv, sizeof(iv)) == 0) { + (void)wc_AesCcmEncrypt_ex(&aes, out, in, sizeof(in), + iv, sizeof(iv), tag, sizeof(tag), aad, sizeof(aad)); + wc_AesFree(&aes); + } + mcdc_cb_fail_algo(WC_ALGO_TYPE_CIPHER, WC_PENDING_E); + if (wc_AesInit(&aes, NULL, MCDC_CB_DEVID) == 0 && + wc_AesCcmSetKey(&aes, key, sizeof(key)) == 0 && + wc_AesCcmSetNonce(&aes, iv, sizeof(iv)) == 0) { + (void)wc_AesCcmEncrypt_ex(&aes, out, in, sizeof(in), + iv, sizeof(iv), tag, sizeof(tag), aad, sizeof(aad)); + wc_AesFree(&aes); + } + /* Third row: plain error -> decision FALSE (see the GCM note). */ + mcdc_cb_fail_algo(WC_ALGO_TYPE_CIPHER, WC_HW_E); + if (wc_AesInit(&aes, NULL, MCDC_CB_DEVID) == 0 && + wc_AesCcmSetKey(&aes, key, sizeof(key)) == 0 && + wc_AesCcmSetNonce(&aes, iv, sizeof(iv)) == 0) { + (void)wc_AesCcmEncrypt_ex(&aes, out, in, sizeof(in), + iv, sizeof(iv), tag, sizeof(tag), aad, sizeof(aad)); + wc_AesFree(&aes); + } + mcdc_cb_uninstall(); + + wc_FreeRng(&rng); + WB_NOTE("GCM/CCM _ex PENDING nonce-advance arms exercised"); +#else + WB_NOTE("GCM/CCM/WOLF_CRYPTO_CB not all on; PENDING arms skipped"); +#endif +} + int main(void) { setvbuf(stdout, NULL, _IONBF, 0); @@ -1196,6 +1309,7 @@ int main(void) wb_ccm_roll_auth_ret(); wb_ccm_aesni_dispatch(); wb_aarch64_ctr_leftover(); + wb_gcmccm_pending(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); /* Setup failures are surfaced as skips, not test failures: the harness * treats a nonzero exit as a failed variant and discards its coverage. */ diff --git a/tests/unit-mcdc/test_asn_keys_whitebox.c b/tests/unit-mcdc/test_asn_keys_whitebox.c index 7b223a544b0..1aea22613ca 100644 --- a/tests/unit-mcdc/test_asn_keys_whitebox.c +++ b/tests/unit-mcdc/test_asn_keys_whitebox.c @@ -54,6 +54,17 @@ #include #include +/* The vectors below decode the DER test keys/certs from certs_test.h, which + * only compiles those arrays when the cert-buffer macros are set. The campaign + * build defines them; define them here too (idempotent) so this TU is + * self-contained and builds under a plain configuration as well. */ +#ifndef USE_CERT_BUFFERS_2048 + #define USE_CERT_BUFFERS_2048 +#endif +#ifndef USE_CERT_BUFFERS_256 + #define USE_CERT_BUFFERS_256 +#endif + #include static int wb_fail = 0; @@ -730,14 +741,18 @@ static void wb_get_key_oid(void) { WB_NOTE("HAVE_PKCS8/12 off; wc_GetKeyOID skip * Section A10: wc_EncryptPKCS8Key_ex() argument/salt/version checks. * * ARGUED UNREACHABLE, do not re-open (suite the exclusion record + - * db/exclusions.json): :10805 BOTH operands. GetAlgoV2() (asn.c:10713-10748) + * db/exclusions.json): :10852 BOTH operands. GetAlgoV2() (asn.c:10251-10297) * assigns *oid on every switch arm that returns 0 and leaves the caller's * initialiser untouched only on the default arm, which returns ALGO_ID_E. - * encOid == NULL at :10805 therefore implies ret != 0, so the AND is never + * encOid == NULL at :10852 therefore implies ret != 0, so the AND is never * true: cond 1 has no true row and cond 0 has no (true, true) row. - * :10724 key==NULL||outSz==NULL||password==NULL - * :10731 ret==0 && (salt==NULL||saltSz==0) - * :10735 ret==0 && version==PKCS5v2 + * :10839 key==NULL||outSz==NULL||password==NULL + * :10846 ret==0 && (salt==NULL||saltSz==0) + * :10850 ret==0 && version==PKCS5v2 + * The PBES1 vectors need !NO_DES3 (CheckAlgo's PBES1 arms are DES-only). + * Under NO_DES3 the version!=PKCS5v2 rows of :10850/:11666 come from no + * arm - PBES2 is the only ret==0 arm - a build-config residual covered by + * DES-enabled builds. * ===================================================================== */ #if defined(HAVE_PKCS8) && !defined(NO_PWDBASED) static void wb_encrypt_pkcs8_key_ex(void) @@ -751,7 +766,7 @@ static void wb_encrypt_pkcs8_key_ex(void) XMEMSET(key, 0x11, sizeof(key)); XMEMSET(salt, 0x22, sizeof(salt)); - WB_NOTE("wc_EncryptPKCS8Key_ex(): key/outSz/password NULL OR [:10724]"); + WB_NOTE("wc_EncryptPKCS8Key_ex(): key/outSz/password NULL OR [:10839]"); ret = wc_EncryptPKCS8Key_ex(NULL, sizeof(key), NULL, &outSz, "pw", 2, PKCS5, PBES1_SHA1_DES, 0, NULL, 0, 1000, 0, NULL, NULL); WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "key==NULL"); @@ -762,71 +777,87 @@ static void wb_encrypt_pkcs8_key_ex(void) PKCS5, PBES1_SHA1_DES, 0, NULL, 0, 1000, 0, NULL, NULL); WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "password==NULL"); - WB_NOTE("wc_EncryptPKCS8Key_ex(): ret==0&&(salt==NULL||saltSz==0) [:10731]; " - "ret==0&&version==PKCS5v2 [:10735] (size-only calls, out==NULL)"); - /* PBES1 (PKCS5, non-PBES2): version!=PKCS5v2 -> :10735 2nd operand false. - * salt provided -> :10731 both operands false. */ + WB_NOTE("wc_EncryptPKCS8Key_ex(): ret==0&&(salt==NULL||saltSz==0) [:10846]; " + "ret==0&&version==PKCS5v2 [:10850] (size-only calls, out==NULL)"); +#ifndef NO_DES3 + /* PBES1 (PKCS5, non-PBES2): version!=PKCS5v2 -> :10850 2nd operand false. + * salt provided -> :10846 both operands false. */ outSz = 0; ret = wc_EncryptPKCS8Key_ex(key, sizeof(key), NULL, &outSz, "pw", 2, PKCS5, PBES1_SHA1_DES, 0, salt, sizeof(salt), 1000, 0, NULL, NULL); WB_CHECK(ret == WC_NO_ERR_TRACE(LENGTH_ONLY_E), - "PBES1, salt provided (10731 both false, 10735 false)"); - /* PBES1, salt==NULL -> :10731 1st operand of inner OR true (genSalt path + "PBES1, salt provided (10846 both false, 10850 false)"); + /* PBES1, salt==NULL -> :10846 1st operand of inner OR true (genSalt path * needs RNG later, but out==NULL returns before RNG is touched). */ outSz = 0; ret = wc_EncryptPKCS8Key_ex(key, sizeof(key), NULL, &outSz, "pw", 2, PKCS5, PBES1_SHA1_DES, 0, NULL, 0, 1000, 0, NULL, NULL); WB_CHECK(ret == WC_NO_ERR_TRACE(LENGTH_ONLY_E), - "PBES1, salt==NULL (10731 salt==NULL true)"); + "PBES1, salt==NULL (10846 salt==NULL true)"); +#endif #ifdef WOLFSSL_AES_128 - /* PBES2 (pbeOid==PBES2): version==PKCS5v2 -> :10735 true. */ + /* PBES2 (pbeOid==PBES2): version==PKCS5v2 -> :10850 true. */ outSz = 0; ret = wc_EncryptPKCS8Key_ex(key, sizeof(key), NULL, &outSz, "pw", 2, PKCS5, PBES2, AES128CBCb, salt, sizeof(salt), 1000, 0, NULL, NULL); - WB_CHECK(ret != 0, "PBES2 (10735 version==PKCS5v2 true; the PBES2 path " + WB_CHECK(ret != 0, "PBES2 (10850 version==PKCS5v2 true; the PBES2 path " "reaches the CBC-IV RNG call even for a size-only request, so a " "NULL rng is rejected rather than returning LENGTH_ONLY_E)"); + /* PBES2, salt==NULL / saltSz==0: the :10846 rows PBES1 supplies in DES + * builds. Both fail at the CBC-IV RNG call with a NULL rng, after the + * guard has already set genSalt - the rows stand. */ + outSz = 0; + ret = wc_EncryptPKCS8Key_ex(key, sizeof(key), NULL, &outSz, "pw", 2, + PKCS5, PBES2, AES128CBCb, NULL, 0, 1000, 0, NULL, NULL); + WB_CHECK(ret != 0, "PBES2, salt==NULL (10846 salt==NULL true)"); + outSz = 0; + ret = wc_EncryptPKCS8Key_ex(key, sizeof(key), NULL, &outSz, "pw", 2, + PKCS5, PBES2, AES128CBCb, salt, 0, 1000, 0, NULL, NULL); + WB_CHECK(ret != 0, "PBES2, saltSz==0 (10846 saltSz==0 true)"); #endif /* EncryptContent() directly: outSz==NULL sets ret before the PKCS#5 - * version dispatch, driving :11531's leading operand false. The two - * calls above already supply its true rows through - * wc_EncryptPKCS8Key_ex(). */ - WB_NOTE("EncryptContent(): ret==0 && version==PKCS5v2 [:11531]"); + * version dispatch, driving :11666's leading operand false. The calls + * above already supply its true rows through wc_EncryptPKCS8Key_ex(). */ + WB_NOTE("EncryptContent(): ret==0 && version==PKCS5v2 [:11666]"); ret = EncryptContent(key, sizeof(key), NULL, NULL, "pw", 2, PKCS5, PBES1_SHA1_DES, 0, salt, sizeof(salt), 1000, 0, NULL, NULL); WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), - ":11531 1st operand false (outSz==NULL)"); + ":11666 1st operand false (outSz==NULL)"); +#ifndef NO_DES3 outSz = 0; ret = EncryptContent(key, sizeof(key), NULL, &outSz, "pw", 2, PKCS5, PBES1_SHA1_DES, 0, salt, sizeof(salt), 1000, 0, NULL, NULL); WB_CHECK(ret == WC_NO_ERR_TRACE(LENGTH_ONLY_E), - ":11531 2nd operand false (PBES1)"); + ":11666 2nd operand false (PBES1)"); +#endif #ifdef WOLFSSL_AES_128 outSz = 0; ret = EncryptContent(key, sizeof(key), NULL, &outSz, "pw", 2, PKCS5, PBES2, AES128CBCb, salt, sizeof(salt), 1000, 0, NULL, NULL); - WB_CHECK(ret != 0, ":11531 both operands true (PBES2 dispatch)"); + WB_CHECK(ret != 0, ":11666 both operands true (PBES2 dispatch)"); #endif - /* :10799 third operand (`saltSz == 0`). Every public caller passes a +#ifndef NO_DES3 + /* :10846 third operand (`saltSz == 0`). Every public caller passes a * salt pointer together with its real length, or neither; a non-NULL * pointer with a zero length is the combination the OR's second operand * exists for. The salt-provided call above is the row it pairs against. */ - WB_NOTE("wc_EncryptPKCS8Key_ex(): salt pointer with saltSz==0 [:10799" + WB_NOTE("wc_EncryptPKCS8Key_ex(): salt pointer with saltSz==0 [:10846" " third operand]"); outSz = 0; ret = wc_EncryptPKCS8Key_ex(key, sizeof(key), NULL, &outSz, "pw", 2, PKCS5, PBES1_SHA1_DES, 0, salt, 0, 1000, 0, NULL, NULL); WB_CHECK(ret == WC_NO_ERR_TRACE(LENGTH_ONLY_E), - ":10799 salt != NULL with saltSz == 0 still generates a salt"); + ":10846 salt != NULL with saltSz == 0 still generates a salt"); +#endif - /* :11650 second operand's false row. The size-only calls above all set + /* :11697 second operand's false row. The size-only calls above all set * ret to LENGTH_ONLY_E at the preceding `out == NULL` branch, so they * never reach this check with ret == 0; only a real encode with a large * enough buffer does. PBES1 needs no RNG (the CBC IV is derived from the * password), so this runs with rng == NULL. */ - WB_NOTE("EncryptContent(): full encode into a big-enough buffer [:11650" + WB_NOTE("EncryptContent(): full encode into a big-enough buffer [:11697" " second operand false]"); outSz = 0; (void)EncryptContent(key, sizeof(key), NULL, &outSz, "pw", 2, PKCS5, @@ -834,18 +865,18 @@ static void wb_encrypt_pkcs8_key_ex(void) if (outSz > 1 && outSz <= sizeof(encOut)) { word32 room = outSz - 1; - /* Both halves of :11650's second operand have to be in THIS binary: + /* Both halves of :11697's second operand have to be in THIS binary: * the too-small row lives in test_asn_fault_whitebox.c as well, but * a pair completed across two binaries proves nothing. */ ret = EncryptContent(key, sizeof(key), encOut, &room, "pw", 2, PKCS5, PBES1_SHA1_DES, 0, salt, sizeof(salt), 1000, 0, NULL, NULL); WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), - ":11650 second operand true (one byte short)"); + ":11697 second operand true (one byte short)"); room = (word32)sizeof(encOut); ret = EncryptContent(key, sizeof(key), encOut, &room, "pw", 2, PKCS5, PBES1_SHA1_DES, 0, salt, sizeof(salt), 1000, 0, NULL, NULL); - WB_CHECK(ret > 0, ":11650 PBES1 encode succeeds with room to spare"); + WB_CHECK(ret > 0, ":11697 PBES1 encode succeeds with room to spare"); } else { WB_NOTE("PBES1 size query out of range; :11650 row skipped"); diff --git a/tests/unit-mcdc/test_dtls13_role_whitebox.c b/tests/unit-mcdc/test_dtls13_role_whitebox.c index d6503190287..8dddd4c3f0b 100644 --- a/tests/unit-mcdc/test_dtls13_role_whitebox.c +++ b/tests/unit-mcdc/test_dtls13_role_whitebox.c @@ -57,7 +57,7 @@ !defined(WOLFCRYPT_ONLY) && !defined(NO_TLS) static int g_checks; -#define WB_NOTE(what) do { g_checks++; (void)(what); } while (0) +#define WB_NOTE(what) do {g_checks++; (void)(what);} while (0) /* The handshake types these decisions discriminate on. */ static const byte kTypes[] = { @@ -66,7 +66,7 @@ static const byte kTypes[] = { finished, session_ticket, key_update, 200 /* not a handshake type */ }; -static const int kSides[2] = { WOLFSSL_CLIENT_END, WOLFSSL_SERVER_END }; +static const int kSides[2] = {WOLFSSL_CLIENT_END, WOLFSSL_SERVER_END}; /* Reset only what these functions read. Nothing is allocated or owned, so * there is no teardown and no ordering between vectors. */ @@ -107,7 +107,8 @@ static void wb_accept_fragmented(WOLFSSL* ssl, WOLFSSL_CTX* ctx) #endif ssl->options.dtlsStateful = (byte)stateful; WB_NOTE(Dtls13AcceptFragmented(ssl, - (enum HandShakeType)kTypes[t])); + (enum HandShakeType) + kTypes[t])); } } } @@ -122,9 +123,9 @@ static void wb_accept_fragmented(WOLFSSL* ssl, WOLFSSL_CTX* ctx) * Sweeping (side x type x epoch) pairs every arm. */ static void wb_check_epoch(WOLFSSL* ssl, WOLFSSL_CTX* ctx) { - static const word32 kEpochs[] = { 0, DTLS13_EPOCH_EARLYDATA, - DTLS13_EPOCH_HANDSHAKE, - DTLS13_EPOCH_TRAFFIC0, 7 }; + static const word32 kEpochs[] = {0, DTLS13_EPOCH_EARLYDATA, + DTLS13_EPOCH_HANDSHAKE, + DTLS13_EPOCH_TRAFFIC0, 7}; size_t t, e; int s; @@ -134,7 +135,7 @@ static void wb_check_epoch(WOLFSSL* ssl, WOLFSSL_CTX* ctx) wb_reset(ssl, ctx, kSides[s]); ssl->keys.curEpoch64 = w64From32(0x0, kEpochs[e]); WB_NOTE(Dtls13CheckEpoch(ssl, - (enum HandShakeType)kTypes[t])); + (enum HandShakeType)kTypes[t])); } } } @@ -185,7 +186,8 @@ static void wb_set_epoch_keys(WOLFSSL* ssl, WOLFSSL_CTX* ctx) for (a = 0; a < 3; a++) { /* no epoch yet: the early-return arm */ wb_reset(ssl, ctx, kSides[s]); - WB_NOTE(Dtls13SetEpochKeys(ssl, w64From32(0x0, DTLS13_EPOCH_HANDSHAKE), + WB_NOTE(Dtls13SetEpochKeys(ssl, w64From32(0x0, + DTLS13_EPOCH_HANDSHAKE), kEncSides[a])); /* an epoch that exists, with each stored side in turn, so @@ -198,15 +200,395 @@ static void wb_set_epoch_keys(WOLFSSL* ssl, WOLFSSL_CTX* ctx) ssl->dtls13Epochs[0].side = (byte)kEncSides[b]; ssl->dtls13Epochs[0].isValid = 1; WB_NOTE(Dtls13SetEpochKeys(ssl, - w64From32(0x0, DTLS13_EPOCH_HANDSHAKE), - kEncSides[a])); + w64From32(0x0, DTLS13_EPOCH_HANDSHAKE + ), + kEncSides[a])); } } } } +/* ------------------------------------------- guard rows :1364/1572/1270 */ +static void wb_encrypt_rn_guard(WOLFSSL* ssl) +{ + byte hdr[16]; + + WB_NOTE(Dtls13EncryptRecordNumber(NULL, hdr, sizeof(hdr))); + WB_NOTE(Dtls13EncryptRecordNumber(ssl, NULL, sizeof(hdr))); + WB_NOTE(Dtls13EncryptRecordNumber(ssl, hdr, sizeof(hdr))); +} + +static void wb_parse_unified_guard(WOLFSSL* ssl) +{ + Dtls13UnifiedHdrInfo info; + byte hdr[8]; + + XMEMSET(hdr, 0, sizeof(hdr)); + WB_NOTE(Dtls13ParseUnifiedRecordLayer(ssl, NULL, sizeof(hdr), &info)); + WB_NOTE(Dtls13ParseUnifiedRecordLayer(ssl, hdr, 0, &info)); + hdr[0] = DTLS13_FIXED_BITS; + WB_NOTE(Dtls13ParseUnifiedRecordLayer(ssl, hdr, sizeof(hdr), &info)); +} + +static void wb_unified_cid_bit(void) +{ + WB_NOTE(Dtls13UnifiedHeaderCIDPresent(DTLS13_FIXED_BITS | + DTLS13_CID_BIT)); + WB_NOTE(Dtls13UnifiedHeaderCIDPresent(DTLS13_FIXED_BITS)); + WB_NOTE(Dtls13UnifiedHeaderCIDPresent(0x00)); +} + +/* ------------------------------------------- frag/send-now rows :452/478/481 */ +static void wb_frag_in_output_buffer(WOLFSSL* ssl) +{ + byte mem[16]; + + ssl->buffers.outputBuffer.buffer = mem + 8; + ssl->buffers.outputBuffer.bufferSize = 4; + /* inside: both operands false */ + WB_NOTE(FragIsInOutputBuffer(ssl, mem + 8)); + /* before the buffer: operand 0 true */ + WB_NOTE(FragIsInOutputBuffer(ssl, mem)); + /* exactly at the end: operand 1 true */ + WB_NOTE(FragIsInOutputBuffer(ssl, mem + 12)); + ssl->buffers.outputBuffer.buffer = NULL; + ssl->buffers.outputBuffer.bufferSize = 0; +} + +static void wb_send_now_rows(WOLFSSL* ssl) +{ + enum HandShakeType types[7]; + byte hsState; + int i; + + types[0] = client_hello; + types[1] = hello_retry_request; + types[2] = finished; + types[3] = session_ticket; + types[4] = key_update; + types[5] = certificate_request; + types[6] = server_hello; + + /* groupMessages off: operand 0 true */ + ssl->options.groupMessages = 0; + WB_NOTE(Dtls13SendNow(ssl, server_hello)); + /* groupMessages on, sending fragments: operand 1 true */ + ssl->options.groupMessages = 1; + ssl->dtls13SendingFragments = 1; + WB_NOTE(Dtls13SendNow(ssl, server_hello)); + ssl->dtls13SendingFragments = 0; + /* send-now handshake types: one per operand true */ + for (i = 0; i < 5; i++) { + WB_NOTE(Dtls13SendNow(ssl, types[i])); + } + /* certificate_request: done (operand 5 true) and not done + * (operand 6 true) */ + hsState = ssl->options.handShakeState; + ssl->options.handShakeState = HANDSHAKE_DONE; + WB_NOTE(Dtls13SendNow(ssl, certificate_request)); + ssl->options.handShakeState = hsState; + WB_NOTE(Dtls13SendNow(ssl, certificate_request)); + /* all false: server_hello */ + WB_NOTE(Dtls13SendNow(ssl, server_hello)); + ssl->options.groupMessages = 0; +} + +/* ------------------------------------------- rtx/ack rows :869/873/898/932/991/1009 */ +static void wb_rtx_msg_recvd(WOLFSSL* ssl) +{ + DtlsMsg msg; + DtlsFragBucket b1, b2; + Dtls13RtxRecord* rtx; + byte* rtxData; + Dtls13RecordNumber* rn; + byte implicitAck = 0; + byte sendMoreAcks; + + XMEMSET(&msg, 0, sizeof(msg)); + XMEMSET(&b1, 0, sizeof(b1)); + XMEMSET(&b2, 0, sizeof(b2)); + + /* -- Dtls13DetectDisruption :869/873 -- */ + /* two buckets, fragOffset past the end */ + b1.m.m.next = &b2; + b1.m.m.offset = 0; + b1.m.m.sz = 10; + b2.m.m.next = NULL; + b2.m.m.offset = 10; + b2.m.m.sz = 10; + msg.fragBucketList = &b1; + ssl->dtls_rx_msg_list = &msg; + WB_NOTE(Dtls13DetectDisruption(ssl, 25)); + /* one bucket, fragOffset past the end */ + b1.m.m.next = NULL; + WB_NOTE(Dtls13DetectDisruption(ssl, 25)); + /* one bucket, fragOffset == offset + sz */ + WB_NOTE(Dtls13DetectDisruption(ssl, 10)); + /* empty frag list */ + msg.fragBucketList = NULL; + WB_NOTE(Dtls13DetectDisruption(ssl, 10)); + ssl->dtls_rx_msg_list = NULL; + + /* -- Dtls13SaveOrFlushClientHello :932 (via server_hello) -- */ + /* Early in the function: the static-build memory pool is freshest + * here and the rtx record must be heap-allocated. */ + ssl->options.handShakeDone = 0; + /* a raw zeroed ssl has side == 0, which is no side at all */ + ssl->options.side = WOLFSSL_CLIENT_END; + ssl->options.connectState = CLIENT_HELLO_SENT; + ssl->options.downgrade = 1; + rtxData = (byte*)XMALLOC(8, ssl->heap, DYNAMIC_TYPE_DTLS_MSG); + rtx = (Dtls13RtxRecord*)XMALLOC(sizeof(*rtx), ssl->heap, + DYNAMIC_TYPE_DTLS_MSG); + if (rtxData != NULL && rtx != NULL) { + XMEMSET(rtxData, 0x42, 8); + XMEMSET(rtx, 0, sizeof(*rtx)); + rtx->data = rtxData; + rtx->length = 8; + rtx->handshakeType = client_hello; + ssl->dtls13Rtx.rtxRecords = rtx; + ssl->dtls13Rtx.rtxRecordTailPtr = &ssl->dtls13Rtx.rtxRecords; + /* operand 1 false: minDowngrade below DTLSv1_2_MINOR */ + ssl->options.minDowngrade = 0; + WB_NOTE(Dtls13SaveOrFlushClientHello(ssl)); + /* operand 1 true: the record is consumed, so a fresh one */ + rtxData = (byte*)XMALLOC(8, ssl->heap, DYNAMIC_TYPE_DTLS_MSG); + rtx = (Dtls13RtxRecord*)XMALLOC(sizeof(*rtx), ssl->heap, + DYNAMIC_TYPE_DTLS_MSG); + if (rtxData != NULL && rtx != NULL) { + XMEMSET(rtxData, 0x42, 8); + XMEMSET(rtx, 0, sizeof(*rtx)); + rtx->data = rtxData; + rtx->length = 8; + rtx->handshakeType = client_hello; + ssl->dtls13Rtx.rtxRecords = rtx; + ssl->dtls13Rtx.rtxRecordTailPtr = &ssl->dtls13Rtx.rtxRecords; + ssl->options.minDowngrade = DTLSv1_2_MINOR; + WB_NOTE(Dtls13SaveOrFlushClientHello(ssl)); + } else { + XFREE(rtxData, ssl->heap, DYNAMIC_TYPE_DTLS_MSG); + XFREE(rtx, ssl->heap, DYNAMIC_TYPE_DTLS_MSG); + } + } else { + XFREE(rtxData, ssl->heap, DYNAMIC_TYPE_DTLS_MSG); + XFREE(rtx, ssl->heap, DYNAMIC_TYPE_DTLS_MSG); + } + ssl->options.downgrade = 0; + ssl->options.connectState = NULL_STATE; + + /* -- Dtls13RtxMsgRecvd :991 + Dtls13RtxRemoveCurAck :898 -- */ + sendMoreAcks = ssl->options.dtls13SendMoreAcks; + ssl->options.dtls13SendMoreAcks = 1; + ssl->options.handShakeDone = 1; + rn = (Dtls13RecordNumber*)XMALLOC(sizeof(*rn), ssl->heap, + DYNAMIC_TYPE_DTLS_MSG); + if (rn == NULL) { + return; + } + /* matching epoch + seq: both operands true, the node is freed */ + XMEMSET(rn, 0, sizeof(*rn)); + ssl->dtls13Rtx.seenRecords = rn; + ssl->dtls13Rtx.seenRecordsCount = 1; + WB_NOTE(Dtls13RtxMsgRecvd(ssl, certificate_request, 10, &implicitAck)); + /* epoch mismatch: operand 0 false */ + XMEMSET(rn, 0, sizeof(*rn)); + rn->epoch.n = 1; + ssl->dtls13Rtx.seenRecords = rn; + ssl->dtls13Rtx.seenRecordsCount = 1; + WB_NOTE(Dtls13RtxMsgRecvd(ssl, certificate_request, 10, &implicitAck)); + /* seq mismatch: operand 1 false */ + XMEMSET(rn, 0, sizeof(*rn)); + rn->seq.n = 1; + ssl->dtls13Rtx.seenRecords = rn; + ssl->dtls13Rtx.seenRecordsCount = 1; + WB_NOTE(Dtls13RtxMsgRecvd(ssl, certificate_request, 10, &implicitAck)); + XFREE(rn, ssl->heap, DYNAMIC_TYPE_DTLS_MSG); + ssl->dtls13Rtx.seenRecords = NULL; + ssl->dtls13Rtx.seenRecordsCount = 0; + /* 991 operand 1 false: handshake not done */ + ssl->options.handShakeDone = 0; + WB_NOTE(Dtls13RtxMsgRecvd(ssl, certificate_request, 0, &implicitAck)); + /* 991 operand 2 false: not a certificate_request */ + ssl->options.handShakeDone = 1; + WB_NOTE(Dtls13RtxMsgRecvd(ssl, server_hello, 0, &implicitAck)); + /* 991 operand 0 false: peer behind expected */ + ssl->keys.dtls_expected_peer_handshake_number = 1; + WB_NOTE(Dtls13RtxMsgRecvd(ssl, server_hello, 0, &implicitAck)); + ssl->keys.dtls_expected_peer_handshake_number = 0; + + /* -- 1009 -- */ + ssl->options.handShakeDone = 1; + /* sendMoreAcks on + disruption detected: both operands true */ + b1.m.m.next = &b2; + msg.fragBucketList = &b1; + ssl->dtls_rx_msg_list = &msg; + WB_NOTE(Dtls13RtxMsgRecvd(ssl, server_hello, 25, &implicitAck)); + /* fragOffset 0: no disruption */ + WB_NOTE(Dtls13RtxMsgRecvd(ssl, server_hello, 0, &implicitAck)); + /* sendMoreAcks off: operand 0 false */ + ssl->options.dtls13SendMoreAcks = 0; + WB_NOTE(Dtls13RtxMsgRecvd(ssl, server_hello, 25, &implicitAck)); + +restore: + ssl->options.dtls13SendMoreAcks = sendMoreAcks; + ssl->options.handShakeDone = 0; + ssl->options.downgrade = 0; + ssl->options.side = 0; + ssl->options.connectState = NULL_STATE; + ssl->dtls_rx_msg_list = NULL; +} + /* ---------------------------------------------------------------- main */ + +/* Cluster E/F: options and epoch guards in dtls13.c. */ +static void wb_opts_epoch_rows(WOLFSSL* ssl) +{ + int save_maxFrag; + word32 save_fragOff, save_msgLen, save_fragType; + ProtocolVersion save_version; + int save_side, save_serverState, save_hsDone, save_seenUnified; + w64wrapper save_curEpoch, save_dtls13Epoch; + Dtls13Epoch save_e0; + Dtls13RecordNumber* save_seen; + Dtls13Epoch* save_encEpoch; + Dtls13RecordNumber* seen; + byte buf[13]; + word32 idx; + + save_maxFrag = ssl->max_fragment; + save_fragOff = ssl->dtls13FragOffset; + save_msgLen = ssl->dtls13MessageLength; + save_fragType = ssl->dtls13FragHandshakeType; + save_version = ssl->version; + save_side = ssl->options.side; + save_serverState = ssl->options.serverState; + save_hsDone = ssl->options.handShakeDone; + save_seenUnified = ssl->options.seenUnifiedHdr; + save_curEpoch = ssl->keys.curEpoch64; + save_dtls13Epoch = ssl->dtls13Epoch; + save_e0 = ssl->dtls13Epochs[0]; + save_seen = ssl->dtls13Rtx.seenRecords; + save_encEpoch = ssl->dtls13EncryptEpoch; + + /* -- Dtls13SendFragmentedInternal :1074 c0/c1/c2 -- + * maxFragment = min(MAX_RECORD_SIZE, ssl->max_fragment), so c1-T + * (maxFragment > MAX_RECORD_SIZE) is structurally unreachable: + * residual, documented in the GAPS note. */ + ssl->dtls13FragHandshakeType = application_data; + ssl->max_fragment = 10; + WB_NOTE(Dtls13SendFragmentedInternal(ssl)); + ssl->max_fragment = 1000; + ssl->dtls13FragOffset = 200; + ssl->dtls13MessageLength = 100; + WB_NOTE(Dtls13SendFragmentedInternal(ssl)); + ssl->dtls13FragOffset = 0; + WB_NOTE(Dtls13SendFragmentedInternal(ssl)); + + /* -- Dtls13CheckEpoch :1791 c0/c1, :1814 c1 -- */ + ssl->version.major = SSLv3_MAJOR; + ssl->version.minor = DTLSv1_3_MINOR; + ssl->keys.curEpoch64.n = 0; + ssl->options.handShakeDone = 0; + ssl->options.side = WOLFSSL_CLIENT_END; + ssl->options.serverState = 0; + WB_NOTE(Dtls13CheckEpoch(ssl, encrypted_extensions)); + ssl->options.side = WOLFSSL_SERVER_END; + WB_NOTE(Dtls13CheckEpoch(ssl, encrypted_extensions)); + ssl->options.side = WOLFSSL_CLIENT_END; + ssl->options.serverState = SERVER_HELLO_COMPLETE; + WB_NOTE(Dtls13CheckEpoch(ssl, encrypted_extensions)); + ssl->options.serverState = 0; + WB_NOTE(Dtls13CheckEpoch(ssl, certificate)); + ssl->options.serverState = SERVER_HELLO_COMPLETE; + WB_NOTE(Dtls13CheckEpoch(ssl, certificate)); + + /* -- Dtls13GetEpoch :2312 c1-F -- */ + ssl->dtls13Epochs[0].epochNumber.n = 5; + ssl->dtls13Epochs[0].isValid = 0; + WB_NOTE(Dtls13GetEpoch(ssl, w64From32(0, 5))); + /* -- Dtls13SetOlderEpochSide :2327 c0/c1 -- */ + ssl->dtls13Epochs[0].epochNumber.n = 1; + ssl->dtls13Epochs[0].isValid = 0; + WB_NOTE(Dtls13SetOlderEpochSide(ssl, w64From32(0, 2), + ENCRYPT_SIDE_ONLY)); + ssl->dtls13Epochs[0].isValid = 1; + WB_NOTE(Dtls13SetOlderEpochSide(ssl, w64From32(0, 2), + ENCRYPT_SIDE_ONLY)); + ssl->dtls13Epochs[0].epochNumber.n = 5; + WB_NOTE(Dtls13SetOlderEpochSide(ssl, w64From32(0, 2), + ENCRYPT_SIDE_ONLY)); + + /* -- Dtls13RtxTimeout :2959 c2 -- */ + seen = (Dtls13RecordNumber*)XMALLOC(sizeof(*seen), ssl->heap, + DYNAMIC_TYPE_DTLS_MSG); + if (seen != NULL) { + XMEMSET(seen, 0, sizeof(*seen)); + ssl->dtls13Rtx.seenRecords = seen; + ssl->options.serverState = 0; + ssl->dtls13EncryptEpoch = NULL; + ssl->options.seenUnifiedHdr = 1; + WB_NOTE(Dtls13RtxTimeout(ssl)); + ssl->options.seenUnifiedHdr = 0; + WB_NOTE(Dtls13RtxTimeout(ssl)); + XFREE(seen, ssl->heap, DYNAMIC_TYPE_DTLS_MSG); + } + + /* -- DoDtls13NewConnectionId :3071 c0 -- */ + buf[0] = 0; + buf[1] = 5; + buf[2] = 4; + buf[3] = 1; + buf[4] = 2; + buf[5] = 3; + buf[6] = 4; + buf[7] = cid_spare; + idx = 0; + WB_NOTE(DoDtls13NewConnectionId(ssl, buf, &idx, 8)); + /* c0-F: a second usable cid sees newCid != NULL */ + buf[0] = 0; + buf[1] = 10; + buf[2] = 4; + buf[3] = 1; + buf[4] = 2; + buf[5] = 3; + buf[6] = 4; + buf[7] = 4; + buf[8] = 5; + buf[9] = 6; + buf[10] = 7; + buf[11] = 8; + buf[12] = cid_spare; + idx = 0; + WB_NOTE(DoDtls13NewConnectionId(ssl, buf, &idx, 13)); + + /* -- SendDtls13Ack :3207 c2 -- */ + ssl->dtls13Epochs[0].epochNumber.n = 0; + ssl->dtls13Epochs[0].isValid = 1; + ssl->dtls13EncryptEpoch = &ssl->dtls13Epochs[0]; + ssl->options.side = WOLFSSL_SERVER_END; + ssl->options.handShakeDone = 0; + ssl->dtls13Epoch.n = DTLS13_EPOCH_TRAFFIC0; + WB_NOTE(SendDtls13Ack(ssl)); + ssl->dtls13Epoch.n = DTLS13_EPOCH_HANDSHAKE; + WB_NOTE(SendDtls13Ack(ssl)); + + ssl->max_fragment = save_maxFrag; + ssl->dtls13FragOffset = save_fragOff; + ssl->dtls13MessageLength = save_msgLen; + ssl->dtls13FragHandshakeType = save_fragType; + ssl->version = save_version; + ssl->options.side = save_side; + ssl->options.serverState = save_serverState; + ssl->options.handShakeDone = save_hsDone; + ssl->options.seenUnifiedHdr = save_seenUnified; + ssl->keys.curEpoch64 = save_curEpoch; + ssl->dtls13Epoch = save_dtls13Epoch; + ssl->dtls13Epochs[0] = save_e0; + ssl->dtls13Rtx.seenRecords = save_seen; + ssl->dtls13EncryptEpoch = save_encEpoch; +} + int main(void) { WOLFSSL_CTX* ctx = NULL; @@ -233,6 +615,13 @@ int main(void) wb_check_epoch(ssl, ctx); wb_save_or_flush(ssl, ctx); wb_set_epoch_keys(ssl, ctx); + wb_encrypt_rn_guard(ssl); + wb_parse_unified_guard(ssl); + wb_unified_cid_bit(); + wb_frag_in_output_buffer(ssl); + wb_send_now_rows(ssl); + wb_opts_epoch_rows(ssl); + wb_rtx_msg_recvd(ssl); printf("dtls13 role white-box: %d vectors driven\n", g_checks); diff --git a/tests/unit-mcdc/test_dtls_whitebox.c b/tests/unit-mcdc/test_dtls_whitebox.c index 1ba380f6c88..1ec1885fb4e 100644 --- a/tests/unit-mcdc/test_dtls_whitebox.c +++ b/tests/unit-mcdc/test_dtls_whitebox.c @@ -76,7 +76,7 @@ static int g_checks; -#define WB_NOTE(what) do { g_checks++; (void)(what); } while (0) +#define WB_NOTE(what) do {g_checks++; (void)(what);} while (0) /* ------------------------------------------------------------------ helpers */ @@ -102,8 +102,8 @@ static void wb_create_dtls12_cookie(WOLFSSL* ssl) ProtocolVersion pv; WolfSSL_CH ch; byte cookie[DTLS_COOKIE_SZ]; - static const byte secret[] = { 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a }; - const byte random[RAN_LEN] = { 0 }; + static const byte secret[] = {0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a}; + const byte random[RAN_LEN] = {0}; pv.major = DTLS_MAJOR; pv.minor = DTLSv1_2_MINOR; @@ -129,9 +129,9 @@ static void wb_find_ext_by_type(void) WolfSSL_ConstVector exts; int tlsxFound = 0; /* type 0x002b (supported_versions), length 0x0002, body 2 bytes: valid. */ - static const byte ok[] = { 0x00, 0x2b, 0x00, 0x02, 0x03, 0x04 }; + static const byte ok[] = {0x00, 0x2b, 0x00, 0x02, 0x03, 0x04}; /* Same header, but the length claims 0x00ff with only 2 bytes present. */ - static const byte overrun[] = { 0x00, 0x2b, 0x00, 0xff, 0x03, 0x04 }; + static const byte overrun[] = {0x00, 0x2b, 0x00, 0xff, 0x03, 0x04}; exts.elements = ok; exts.size = (word32)sizeof(ok); @@ -153,7 +153,7 @@ static void wb_client_hello_sanity(void) { ProtocolVersion pv; WolfSSL_CH ch; - const byte minors[3] = { DTLSv1_2_MINOR, DTLS_MINOR, 0x0f }; + const byte minors[3] = {DTLSv1_2_MINOR, DTLS_MINOR, 0x0f}; size_t i; for (i = 0; i < sizeof(minors) / sizeof(minors[0]); i++) { @@ -178,9 +178,9 @@ static void wb_check_supported_version(WOLFSSL* ssl) WolfSSL_CH ch; byte isTls13 = 0; /* supported_versions carrying a single TLS 1.3 entry */ - static const byte with_sv[] = { 0x00, 0x2b, 0x00, 0x03, 0x02, 0x03, 0x04 }; + static const byte with_sv[] = {0x00, 0x2b, 0x00, 0x03, 0x02, 0x03, 0x04}; /* server_name (0x0000) only: parses cleanly, but no supported_versions */ - static const byte without_sv[] = { 0x00, 0x00, 0x00, 0x01, 0x00 }; + static const byte without_sv[] = {0x00, 0x00, 0x00, 0x01, 0x00}; pv.major = DTLS_MAJOR; pv.minor = DTLSv1_2_MINOR; @@ -246,18 +246,22 @@ static void wb_stateless_reply_have_flags(WOLFSSL* ssl) * that failed to parse would show up as a MISSING pair rather than a false * pass. */ static const byte ext_sa[] = { /* signature_algorithms 0x000d */ - 0x00, 0x0d, 0x00, 0x04, 0x00, 0x02, 0x08, 0x04 }; + 0x00, 0x0d, 0x00, 0x04, 0x00, 0x02, 0x08, 0x04 + }; static const byte ext_sg[] = { /* supported_groups 0x000a */ - 0x00, 0x0a, 0x00, 0x04, 0x00, 0x02, 0x00, 0x17 }; + 0x00, 0x0a, 0x00, 0x04, 0x00, 0x02, 0x00, 0x17 + }; static const byte ext_ks[] = { /* key_share 0x0033 */ - 0x00, 0x33, 0x00, 0x06, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00 }; + 0x00, 0x33, 0x00, 0x06, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00 + }; /* one row per vector: which of KS / SA / SG to include */ - static const struct { byte ks, sa, sg; const char* what; } rows[] = { - { 1, 1, 1, "all three present -> every operand false" }, - { 0, 1, 1, "no key_share -> operand 0 true" }, - { 1, 0, 1, "no sig_algs -> operand 1 true" }, - { 1, 1, 0, "no supported_grps -> operand 2 true" }, + struct wb_reply_row { byte ks, sa, sg; const char* what; }; + static const struct wb_reply_row rows[] = { + {1, 1, 1, "all three present -> every operand false"}, + {0, 1, 1, "no key_share -> operand 0 true"}, + {1, 0, 1, "no sig_algs -> operand 1 true"}, + {1, 1, 0, "no supported_grps -> operand 2 true"}, }; for (i = 0; i < sizeof(rows) / sizeof(rows[0]); i++) { @@ -265,12 +269,18 @@ static void wb_stateless_reply_have_flags(WOLFSSL* ssl) word32 n = 0; byte suite[2]; - if (rows[i].sa) { XMEMCPY(exts + n, ext_sa, sizeof(ext_sa)); - n += (word32)sizeof(ext_sa); } - if (rows[i].sg) { XMEMCPY(exts + n, ext_sg, sizeof(ext_sg)); - n += (word32)sizeof(ext_sg); } - if (rows[i].ks) { XMEMCPY(exts + n, ext_ks, sizeof(ext_ks)); - n += (word32)sizeof(ext_ks); } + if (rows[i].sa) { + XMEMCPY(exts + n, ext_sa, sizeof(ext_sa)); + n += (word32)sizeof(ext_sa); + } + if (rows[i].sg) { + XMEMCPY(exts + n, ext_sg, sizeof(ext_sg)); + n += (word32)sizeof(ext_sg); + } + if (rows[i].ks) { + XMEMCPY(exts + n, ext_ks, sizeof(ext_ks)); + n += (word32)sizeof(ext_ks); + } pv.major = DTLS_MAJOR; pv.minor = DTLSv1_3_MINOR; @@ -289,6 +299,428 @@ static void wb_stateless_reply_have_flags(WOLFSSL* ssl) } #endif /* WOLFSSL_DTLS13 && !NO_WOLFSSL_SERVER */ +/* ------------------------------- SendStatelessReplyDtls13 PSK rows :637/841/845/863/867/876 */ +/* Compiled under WOLFSSL_DTLS13 && !NO_WOLFSSL_SERVER && !NO_PSK. */ +#if defined(WOLFSSL_DTLS13) && !defined(NO_WOLFSSL_SERVER) && !defined(NO_PSK) +/* PSK server callback: returns a fixed key length (configurable for the + * >MAX_PSK_KEY_LEN row). FindPskSuite reads ssl->options.server_psk_cb, so the + * callback is set directly on the ssl object - no ctx plumbing needed. */ +static int wb_psk_keylen = 16; +static unsigned int wb_psk_cb(WOLFSSL* ssl, const char* identity, + byte* key, word32 max_key_len) +{ + (void)ssl; (void)identity; (void)max_key_len; + XMEMSET(key, 0x42, 16); + return (unsigned int)wb_psk_keylen; +} + +static void wb_psk_send(WOLFSSL* ssl, const byte* exts, word32 n, + byte suite0, byte suite1) +{ + ProtocolVersion pv; + WolfSSL_CH ch; + byte suite[2]; + + pv.major = DTLS_MAJOR; + pv.minor = DTLSv1_3_MINOR; + wb_ch_init(&ch, &pv); + suite[0] = suite0; + suite[1] = suite1; + ch.cipherSuite.elements = suite; + ch.cipherSuite.size = 2; + ch.extension.elements = exts; + ch.extension.size = n; + WB_NOTE(SendStatelessReplyDtls13(ssl, &ch)); +} + +/* The PSK path: a valid pre_shared_key extension (identity "A") plus a + * psk_key_exchange_modes extension. The server PSK callback returns a 16-byte + * key, so FindPskSuite sets pskInfo.isValid when the CH suite matches the + * default PSK cipher (TLS_AES_128_GCM_SHA256 = 0x1301). The modes byte selects + * the PSK_DHE_KE / PSK_KE branch, and the noPskDheKePolicy / onlyPskDheKe + * options flip the second operand of each. Dropping SG / KS / SA in the + * no-usable-mode fallback drives the !haveKS / !haveSA / !haveSG operands. */ +static void wb_stateless_reply_psk(WOLFSSL* ssl) +{ + static const byte ext_sa[] = {0x00, 0x0d, 0x00, 0x04, 0x00, 0x02, 0x08, 0x04 + }; + static const byte ext_sg[] = {0x00, 0x0a, 0x00, 0x04, 0x00, 0x02, 0x00, 0x17 + }; + static const byte ext_ks[] = {0x00, 0x33, 0x00, 0x06, 0x00, 0x04, 0x00, 0x17 + , 0x00, 0x00}; + static const byte ext_psk[] = { + 0x00, 0x29, 0x00, 0x09, 0x00, 0x07, 0x00, 0x01, 0x41, + 0x00, 0x00, 0x00, 0x00 + }; + static const byte ext_modes_dhe[] = {0x00, 0x2d, 0x00, 0x02, 0x01, 0x01}; + static const byte ext_modes_psk[] = {0x00, 0x2d, 0x00, 0x02, 0x01, 0x00}; + byte exts[64]; + word32 n; + + ssl->options.server_psk_cb = wb_psk_cb; + ssl->options.noPskDheKePolicy = 0; + ssl->options.onlyPskDheKe = 0; + /* The stateless reply refines the suite list from the server's + * configured suites (WOLFSSL_SUITES(ssl)); an empty list makes + * FindPskSuiteFromExt's loop a no-op (pskInfo.isValid stays 0). + * wolfSSL_set_cipher_list does not populate ssl->suites in this + * build, so set the PSK cipher suite directly. */ + if (ssl->suites == NULL) { + ssl->suites = (Suites*)XMALLOC(sizeof(Suites), ssl->heap, + DYNAMIC_TYPE_TMP_BUFFER); + XMEMSET(ssl->suites, 0, sizeof(Suites)); + } + ssl->suites->suites[0] = 0x13; + ssl->suites->suites[1] = 0x01; + ssl->suites->suiteSz = 2; + + /* V1: PSK + DHE + all exts -> 637 c0-T, 841 c1-T, 845 c0-T c1-T */ + n = 0; + XMEMCPY(exts + n, ext_sa, sizeof(ext_sa)); n += sizeof(ext_sa); + XMEMCPY(exts + n, ext_sg, sizeof(ext_sg)); n += sizeof(ext_sg); + XMEMCPY(exts + n, ext_ks, sizeof(ext_ks)); n += sizeof(ext_ks); + XMEMCPY(exts + n, ext_psk, sizeof(ext_psk)); n += sizeof(ext_psk); + XMEMCPY(exts + n, ext_modes_dhe, sizeof(ext_modes_dhe)); n += sizeof( + ext_modes_dhe); + wb_psk_send(ssl, exts, n, 0x13, 0x01); + + /* V2: PSK + PSK_KE + all exts -> 845 c0-F, 863 c0-T c1-T */ + n = 0; + XMEMCPY(exts + n, ext_sa, sizeof(ext_sa)); n += sizeof(ext_sa); + XMEMCPY(exts + n, ext_sg, sizeof(ext_sg)); n += sizeof(ext_sg); + XMEMCPY(exts + n, ext_ks, sizeof(ext_ks)); n += sizeof(ext_ks); + XMEMCPY(exts + n, ext_psk, sizeof(ext_psk)); n += sizeof(ext_psk); + XMEMCPY(exts + n, ext_modes_psk, sizeof(ext_modes_psk)); n += sizeof( + ext_modes_psk); + wb_psk_send(ssl, exts, n, 0x13, 0x01); + + /* V3: PSK + DHE + noPskDheKePolicy=1 -> 845 c0-T c1-F, 863 c0-F */ + ssl->options.noPskDheKePolicy = 1; + n = 0; + XMEMCPY(exts + n, ext_sa, sizeof(ext_sa)); n += sizeof(ext_sa); + XMEMCPY(exts + n, ext_sg, sizeof(ext_sg)); n += sizeof(ext_sg); + XMEMCPY(exts + n, ext_ks, sizeof(ext_ks)); n += sizeof(ext_ks); + XMEMCPY(exts + n, ext_psk, sizeof(ext_psk)); n += sizeof(ext_psk); + XMEMCPY(exts + n, ext_modes_dhe, sizeof(ext_modes_dhe)); n += sizeof( + ext_modes_dhe); + wb_psk_send(ssl, exts, n, 0x13, 0x01); + ssl->options.noPskDheKePolicy = 0; + + /* V4: PSK + PSK_KE + onlyPskDheKe=1 -> 863 c0-T c1-F */ + ssl->options.onlyPskDheKe = 1; + n = 0; + XMEMCPY(exts + n, ext_sa, sizeof(ext_sa)); n += sizeof(ext_sa); + XMEMCPY(exts + n, ext_sg, sizeof(ext_sg)); n += sizeof(ext_sg); + XMEMCPY(exts + n, ext_ks, sizeof(ext_ks)); n += sizeof(ext_ks); + XMEMCPY(exts + n, ext_psk, sizeof(ext_psk)); n += sizeof(ext_psk); + XMEMCPY(exts + n, ext_modes_psk, sizeof(ext_modes_psk)); n += sizeof( + ext_modes_psk); + wb_psk_send(ssl, exts, n, 0x13, 0x01); + ssl->options.onlyPskDheKe = 0; + + /* V5: no usable mode + no SG -> 867 c2-T */ + ssl->options.noPskDheKePolicy = 1; + n = 0; + XMEMCPY(exts + n, ext_sa, sizeof(ext_sa)); n += sizeof(ext_sa); + XMEMCPY(exts + n, ext_ks, sizeof(ext_ks)); n += sizeof(ext_ks); + XMEMCPY(exts + n, ext_psk, sizeof(ext_psk)); n += sizeof(ext_psk); + XMEMCPY(exts + n, ext_modes_dhe, sizeof(ext_modes_dhe)); n += sizeof( + ext_modes_dhe); + wb_psk_send(ssl, exts, n, 0x13, 0x01); + + /* V6: no usable mode + no KS -> 867 c0-T */ + n = 0; + XMEMCPY(exts + n, ext_sa, sizeof(ext_sa)); n += sizeof(ext_sa); + XMEMCPY(exts + n, ext_sg, sizeof(ext_sg)); n += sizeof(ext_sg); + XMEMCPY(exts + n, ext_psk, sizeof(ext_psk)); n += sizeof(ext_psk); + XMEMCPY(exts + n, ext_modes_dhe, sizeof(ext_modes_dhe)); n += sizeof( + ext_modes_dhe); + wb_psk_send(ssl, exts, n, 0x13, 0x01); + + /* V7: no usable mode + no SA -> 867 c1-T */ + n = 0; + XMEMCPY(exts + n, ext_sg, sizeof(ext_sg)); n += sizeof(ext_sg); + XMEMCPY(exts + n, ext_ks, sizeof(ext_ks)); n += sizeof(ext_ks); + XMEMCPY(exts + n, ext_psk, sizeof(ext_psk)); n += sizeof(ext_psk); + XMEMCPY(exts + n, ext_modes_dhe, sizeof(ext_modes_dhe)); n += sizeof( + ext_modes_dhe); + wb_psk_send(ssl, exts, n, 0x13, 0x01); + ssl->options.noPskDheKePolicy = 0; + + /* V8: 637 c0-F -> callback returns > MAX_PSK_KEY_LEN (64) */ + wb_psk_keylen = 65; + n = 0; + XMEMCPY(exts + n, ext_sa, sizeof(ext_sa)); n += sizeof(ext_sa); + XMEMCPY(exts + n, ext_sg, sizeof(ext_sg)); n += sizeof(ext_sg); + XMEMCPY(exts + n, ext_ks, sizeof(ext_ks)); n += sizeof(ext_ks); + XMEMCPY(exts + n, ext_psk, sizeof(ext_psk)); n += sizeof(ext_psk); + XMEMCPY(exts + n, ext_modes_dhe, sizeof(ext_modes_dhe)); n += sizeof( + ext_modes_dhe); + wb_psk_send(ssl, exts, n, 0x13, 0x01); + wb_psk_keylen = 16; + + ssl->options.server_psk_cb = NULL; +} +#endif /* WOLFSSL_DTLS13 && !NO_WOLFSSL_SERVER && !NO_PSK */ + +/* ------------------------------------------- CID rows :1210/1233/1397/1398/1408/1462/1535 */ +#ifdef WOLFSSL_DTLS_CID +/* Fixed-size stand-in for ConnectionID (flexible array member); the same + * layout, so a cast is safe. */ +typedef struct WbCid { + byte length; + byte id[4]; +} WbCid; + +static void wb_cid_get_rows(WOLFSSL* ssl) +{ + WbCid id; + CIDInfo info; + byte buf[16]; + unsigned char* c0 = NULL; + int rx; + + XMEMSET(&info, 0, sizeof(info)); + XMEMSET(&id, 0, sizeof(id)); + ssl->dtlsCidInfo = &info; + + /* id == NULL: operand 0 true, tx and rx */ + for (rx = 0; rx < 2; rx++) { + WB_NOTE(DtlsCidGet(ssl, buf, sizeof(buf), rx)); + WB_NOTE(DtlsCidGet0(ssl, &c0, rx)); + } + /* id->length == 0: operand 1 true */ + id.length = 0; + info.tx = (ConnectionID*)&id; + info.rx = (ConnectionID*)&id; + for (rx = 0; rx < 2; rx++) { + WB_NOTE(DtlsCidGet(ssl, buf, sizeof(buf), rx)); + WB_NOTE(DtlsCidGet0(ssl, &c0, rx)); + } + /* accepting partner: a real id */ + id.length = 4; + id.id[0] = 0x01; + id.id[1] = 0x02; + id.id[2] = 0x03; + id.id[3] = 0x04; + for (rx = 0; rx < 2; rx++) { + WB_NOTE(DtlsCidGet(ssl, buf, sizeof(buf), rx)); + WB_NOTE(DtlsCidGet0(ssl, &c0, rx)); + } + ssl->dtlsCidInfo = NULL; +} + +static void wb_cid_replace_tx_rows(WOLFSSL* ssl) +{ + static const byte cid[] = {0x01, 0x02, 0x03}; + + WB_NOTE(DtlsCidReplaceTx(NULL, cid, sizeof(cid))); + WB_NOTE(DtlsCidReplaceTx(ssl, NULL, sizeof(cid))); + WB_NOTE(DtlsCidReplaceTx(ssl, cid, 0)); + WB_NOTE(DtlsCidReplaceTx(ssl, cid, sizeof(cid))); +} + +/* input layout: [cidSz][cid bytes] */ +static void wb_cid_parse_rows(WOLFSSL* ssl) +{ + WbCid id; + CIDInfo* info; + SecureRenegotiation* scr = NULL; + byte input[8]; + byte side; + byte hsState; + byte hsDone; + + if (wolfSSL_dtls_cid_use(ssl) != WOLFSSL_SUCCESS) { + return; + } + info = DtlsCidGetInfo(ssl); + if (info == NULL) { + return; + } + id.length = 4; + id.id[0] = 0x0a; + id.id[1] = 0x0b; + id.id[2] = 0x0c; + id.id[3] = 0x0d; + info->tx = (ConnectionID*)&id; + info->negotiated = 1; + + input[0] = 4; + XMEMCPY(input + 1, id.id, 4); + + /* client side, tx set: re-handshake guard, all clauses true */ + WB_NOTE(TLSX_ConnectionID_Parse(ssl, input, sizeof(input), 1)); + + /* client side, tx NULL + negotiated: guard operand 1 true */ + info->tx = NULL; + WB_NOTE(TLSX_ConnectionID_Parse(ssl, input, sizeof(input), 1)); + info->tx = (ConnectionID*)&id; + + /* server side: guard false, cid compare rows */ + side = ssl->options.side; + ssl->options.side = WOLFSSL_SERVER_END; + WB_NOTE(TLSX_ConnectionID_Parse(ssl, input, sizeof(input), 1)); + input[0] = 3; + WB_NOTE(TLSX_ConnectionID_Parse(ssl, input, sizeof(input), 1)); + input[0] = 4; + input[1] = 0xff; + WB_NOTE(TLSX_ConnectionID_Parse(ssl, input, sizeof(input), 1)); + input[1] = id.id[0]; + ssl->options.side = side; + + /* client side + HRR complete: guard operand 1 false */ + ssl->options.serverState = SERVER_HELLO_RETRY_REQUEST_COMPLETE; + WB_NOTE(TLSX_ConnectionID_Parse(ssl, input, sizeof(input), 1)); + ssl->options.serverState = NULL_STATE; + + /* client side + in-progress SCR: guard operand 2 false */ + scr = (SecureRenegotiation*)XMALLOC(sizeof(*scr), ssl->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (scr != NULL) { + XMEMSET(scr, 0, sizeof(*scr)); + scr->enabled = 1; + ssl->secure_renegotiation = scr; + hsState = ssl->options.handShakeState; + hsDone = ssl->options.handShakeDone; + ssl->options.handShakeDone = 1; + ssl->options.handShakeState = SERVER_HELLO_COMPLETE; + WB_NOTE(TLSX_ConnectionID_Parse(ssl, input, sizeof(input), 1)); + ssl->secure_renegotiation = NULL; + XFREE(scr, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + ssl->options.handShakeDone = hsDone; + ssl->options.handShakeState = hsState; + } + info->tx = NULL; + info->negotiated = 0; +} + +static void wb_cid_check_rows(WOLFSSL* ssl) +{ + WbCid id; + CIDInfo info; + byte input[8]; + + input[0] = 0x0a; + input[1] = 0x0b; + input[2] = 0x0c; + input[3] = 0x0d; + XMEMSET(&info, 0, sizeof(info)); + XMEMSET(&id, 0, sizeof(id)); + + /* info == NULL: operand 0 true */ + ssl->dtlsCidInfo = NULL; + WB_NOTE(DtlsCIDCheck(ssl, input, sizeof(input))); + + /* info->rx == NULL: operand 1 true */ + ssl->dtlsCidInfo = &info; + WB_NOTE(DtlsCIDCheck(ssl, input, sizeof(input))); + + /* rx->length == 0: operand 2 true */ + id.length = 0; + info.rx = (ConnectionID*)&id; + WB_NOTE(DtlsCIDCheck(ssl, input, sizeof(input))); + + /* accepting partner: rx set + matching input */ + id.length = 4; + XMEMCPY(id.id, input, 4); + WB_NOTE(DtlsCIDCheck(ssl, input, sizeof(input))); + ssl->dtlsCidInfo = NULL; +} +#endif /* WOLFSSL_DTLS_CID */ + +/* ------------------------------------------- cookie rows :307/330 */ +#ifdef WOLFSSL_DTLS13 +static void wb_cookie_rows(WOLFSSL* ssl) +{ + WolfSSL_CH ch; + ProtocolVersion pv; + Hmac hmac; + static const byte random[RAN_LEN] = {0}; + byte cookieGood = 0; + byte secret[32]; + byte secret2[32]; + byte hash[32]; + byte mac[32]; + byte cookie[64]; + byte cookie12[DTLS_COOKIE_SZ]; + int ret; + + pv.major = DTLS_MAJOR; + pv.minor = DTLSv1_2_MINOR; + wb_ch_init(&ch, &pv); + ch.random = random; + XMEMSET(secret, 0x42, sizeof(secret)); + XMEMSET(secret2, 0x7e, sizeof(secret2)); + XMEMSET(hash, 0x11, sizeof(hash)); + + /* -- isTls13 = 1 :307 -- */ + /* no secrets: TlsCheckCookie -> COOKIE_ERROR. The length is + * big-endian on the wire; ato16 reads it that way. */ + ch.cookieExt.elements = cookie; + cookie[0] = 0; + cookie[1] = 4; + XMEMSET(cookie + 2, 0, 4); + ch.cookieExt.size = 6; + WB_NOTE(CheckDtlsCookie(ssl, &ch, 1, &cookieGood)); + + /* primary secret set, short cookie -> HRR_COOKIE_ERROR */ + ssl->buffers.tls13CookieSecret.buffer = secret; + ssl->buffers.tls13CookieSecret.length = sizeof(secret); + WB_NOTE(CheckDtlsCookie(ssl, &ch, 1, &cookieGood)); + + /* primary secret set, valid cookie -> ret > 0 */ + ret = wc_HmacInit(&hmac, ssl->heap, INVALID_DEVID); + if (ret == 0) { + ret = wc_HmacSetKey(&hmac, WC_SHA256, secret, sizeof(secret)); + } + if (ret == 0) { + ret = wc_HmacUpdate(&hmac, hash, sizeof(hash)); + } + if (ret == 0) { + ret = wc_HmacFinal(&hmac, mac); + } + wc_HmacFree(&hmac); + if (ret == 0) { + XMEMCPY(cookie, hash, 32); + XMEMCPY(cookie + 32, mac, 32); + cookie[0] = 0; + cookie[1] = 64; + ch.cookieExt.size = 66; + WB_NOTE(CheckDtlsCookie(ssl, &ch, 1, &cookieGood)); + } + ssl->buffers.tls13CookieSecret.buffer = NULL; + ssl->buffers.tls13CookieSecret.length = 0; + + /* -- isTls13 = 0 :330 -- */ + ssl->buffers.dtlsCookieSecret.buffer = secret; + ssl->buffers.dtlsCookieSecret.length = sizeof(secret); + /* good cookie: elements is a SEPARATE buffer from ch.dtls12cookie, + * precomputed with the same secret so the constant compare matches */ + ch.cookie.elements = cookie12; + ch.cookie.size = DTLS_COOKIE_SZ; + ch.dtls12cookieSet = 0; + ret = CreateDtls12Cookie(ssl, &ch, secret, sizeof(secret), cookie12); + if (ret == 0) { + WB_NOTE(CheckDtlsCookie(ssl, &ch, 0, &cookieGood)); + } + /* bad cookie, no secondary: operand 1 false */ + XMEMSET(cookie12, 0, DTLS_COOKIE_SZ); + ch.dtls12cookieSet = 0; + WB_NOTE(CheckDtlsCookie(ssl, &ch, 0, &cookieGood)); + /* bad cookie, secondary length > 0: operand 2 true */ + ssl->buffers.dtlsCookieSecretSecondary.buffer = secret2; + ssl->buffers.dtlsCookieSecretSecondary.length = sizeof(secret2); + WB_NOTE(CheckDtlsCookie(ssl, &ch, 0, &cookieGood)); + /* bad cookie, secondary length == 0: operand 2 false */ + ssl->buffers.dtlsCookieSecretSecondary.length = 0; + WB_NOTE(CheckDtlsCookie(ssl, &ch, 0, &cookieGood)); + ssl->buffers.dtlsCookieSecretSecondary.buffer = NULL; + ssl->buffers.dtlsCookieSecret.buffer = NULL; + ssl->buffers.dtlsCookieSecret.length = 0; +} +#endif /* WOLFSSL_DTLS13 */ + /* ---------------------------------------------------------------- main */ int main(void) @@ -334,9 +766,17 @@ int main(void) #endif #ifdef WOLFSSL_DTLS_CID wb_cid_get_size(ssl); + wb_cid_get_rows(ssl); + wb_cid_replace_tx_rows(ssl); + wb_cid_parse_rows(ssl); + wb_cid_check_rows(ssl); #endif #if defined(WOLFSSL_DTLS13) && !defined(NO_WOLFSSL_SERVER) wb_stateless_reply_have_flags(ssl); + wb_stateless_reply_psk(ssl); +#endif +#ifdef WOLFSSL_DTLS13 + wb_cookie_rows(ssl); #endif printf("dtls white-box: %d vectors driven\n", g_checks); diff --git a/tests/unit-mcdc/test_random_fault_whitebox.c b/tests/unit-mcdc/test_random_fault_whitebox.c index 40d1333fea9..8aa3b7fc71e 100644 --- a/tests/unit-mcdc/test_random_fault_whitebox.c +++ b/tests/unit-mcdc/test_random_fault_whitebox.c @@ -144,6 +144,10 @@ #define MCDC_FH_NO_AES #define MCDC_FH_NO_HMAC +/* Keep misc.c's ForceZero() out of this TU so the definition below (with the + * Uninstantiate-fault gate) is the one random.c's calls bind to. */ +#define WOLFSSL_NO_FORCE_ZERO + #include "mcdc_fault_hash.h" /* random.c is #included AFTER the interposers are installed. */ @@ -154,6 +158,53 @@ static int wb_fail = 0; #define WB_NOTE(msg) do { printf(" [wb] %s\n", (msg)); } while (0) +/* -------------------------------------------------------------------------- + * Faultable ForceZero (the WOLFSSL_NO_FORCE_ZERO above keeps misc.c's copy + * out of this TU). When armed for a specific size the wipe is skipped, so + * the zero-check in Hash_DRBG_Uninstantiate() / Hash512_DRBG_Uninstantiate() + * deterministically reports DRBG_FAILURE. The size match keeps every other + * wipe in this TU (digests, V/C, output buffers) intact. Same body as + * misc.c's, gate first. + * ----------------------------------------------------------------------- */ +static size_t mcdc_fz_arm_len = 0; + +MCDC_FH_MAYBE_UNUSED static void mcdc_fz_arm(size_t len) +{ + mcdc_fz_arm_len = len; +} + +MCDC_FH_MAYBE_UNUSED static void mcdc_fz_disarm(void) +{ + mcdc_fz_arm_len = 0; +} + +void ForceZero(void *mem, size_t len) +{ + byte *zb = (byte *)mem; + unsigned long *zl; + + if (mcdc_fz_arm_len != 0 && len == mcdc_fz_arm_len) + return; + + WC_BARRIER_DATA(mem); + while ((len != 0) && + ((wc_ptr_t)zb & (wc_ptr_t)(sizeof(unsigned long) - 1U))) { + *zb++ = 0; + --len; + } + zl = (unsigned long *)zb; + while (len >= sizeof(unsigned long)) { + *zl++ = 0; + len -= sizeof(unsigned long); + } + zb = (byte *)zl; + while (len) { + *zb++ = 0; + --len; + } + WC_BARRIER_DATA(mem); +} + /* -------------------------------------------------------------------------- * 1. Hash_DRBG_Init(): "Hash_df(V) == DRBG_SUCCESS && Hash_df(C) == * DRBG_SUCCESS" (~1077). Both operands paired in this binary. @@ -960,6 +1011,24 @@ static void wb_seed_cb_entropy_failure(void) (void)wc_FreeRng(&rng); } + /* Vector D: the same failing entropy source with the full-mutex flag + * set, pairing the (false, true) row of _InitRng()'s + * "ret==0 && (flags & WC_RNG_INIT_FLAG_USE_FULL_MUTEX)" guard (~3366). + * Without WC_RNG_HAVE_LOCK_FULL_MUTEX the flag is refused at the entry + * (NOT_COMPILED_IN) before the guard, so the row is a build-config + * residual there. */ +#ifdef WC_RNG_HAVE_LOCK_FULL_MUTEX + XMEMSET(&rng, 0, sizeof(rng)); + wb_seed_broken = 1; + ret = wc_InitRng_ex2(&rng, NULL, INVALID_DEVID, + WC_RNG_INIT_FLAG_USE_FULL_MUTEX); + wb_seed_broken = 0; + if (ret == 0) { + WB_NOTE("wc_InitRng_ex2 USE_FULL_MUTEX succeeded with failing entropy"); + wb_fail = 1; + } +#endif + /* Put the library default back, so nothing later in this binary runs on * the test callback. Mirrors random.c's own seedCb initialiser. */ #ifndef HAVE_FIPS @@ -976,6 +1045,268 @@ static void wb_seed_cb_entropy_failure(void) "failure vectors skipped"); } #endif +/* -------------------------------------------------------------------------- + * 8. _InitRng() flag guards: + * :3366 ret==0 && (flags & WC_RNG_INIT_FLAG_USE_FULL_MUTEX) + * (the (false, true) row is Vector D above) + * :3443 ret==0 && !(flags & NO_AUTO_LOCK) && + * (WC_RNG_AUTO_LOCK_DEFAULT || + * (flags & WC_RNG_INIT_FLAG_USE_AUTO_LOCK)) (cond 3) + * ----------------------------------------------------------------------- */ +#if !defined(WC_NO_RNG) +static void wb_init_flags(void) +{ + WC_RNG rng; + int ret; + +#ifdef WC_RNG_HAVE_LOCK_FULL_MUTEX + /* :3366 (true, true): full-mutex flag, init succeeds. */ + XMEMSET(&rng, 0, sizeof(rng)); + ret = wc_InitRng_ex2(&rng, NULL, INVALID_DEVID, + WC_RNG_INIT_FLAG_USE_FULL_MUTEX); + if (ret != 0) { + WB_NOTE("wc_InitRng_ex2 USE_FULL_MUTEX failed"); + wb_fail = 1; + } + else { + (void)wc_FreeRng(&rng); + } +#else + /* Without WC_RNG_HAVE_LOCK_FULL_MUTEX the flag is refused at the entry + * with NOT_COMPILED_IN, before the ~3366 guard: the guard's flag-true + * rows are a build-config residual in this variant. */ +#if !defined(WC_RNG_HAVE_AUTO_LOCK) + (void)rng; + (void)ret; +#endif +#endif + +#ifdef WC_RNG_HAVE_AUTO_LOCK + /* :3443 cond 3 (flags & USE_AUTO_LOCK) true row: auto-lock flag, init + * succeeds. The (false) row is every plain init in this suite. */ + XMEMSET(&rng, 0, sizeof(rng)); + ret = wc_InitRng_ex2(&rng, NULL, INVALID_DEVID, + WC_RNG_INIT_FLAG_USE_AUTO_LOCK); + if (ret != 0) { + WB_NOTE("wc_InitRng_ex2 USE_AUTO_LOCK failed"); + wb_fail = 1; + } + else { + (void)wc_FreeRng(&rng); + } +#endif + + WB_NOTE("_InitRng flag-guard rows driven"); +} +#else +static void wb_init_flags(void) { WB_NOTE("WC_NO_RNG; flag vectors skipped"); } +#endif + +/* -------------------------------------------------------------------------- + * 9. wc_RNG_HealthTest() / wc_RNG_HealthTest_SHA512(): + * :6273 (Hash_DRBG_Uninstantiate(drbg) != 0) && (ret == 0) + * :6765 (Hash512_DRBG_Uninstantiate(drbg) != 0) && (ret == 0) + * + * The Uninstantiate return is the zero-check of ForceZero(); the faultable + * ForceZero above skips exactly the DRBG-struct wipe when armed, so + * Uninstantiate reports DRBG_FAILURE deterministically. Rows: + * (F, T) baseline: everything succeeds, uninstantiate clean + * (T, T) valid test, wipe faulted: ret==0, Uninstantiate fails + * (T, F) first Hash_df faulted by mcdc_fault_hash.h so Instantiate fails + * (ret=DRBG_FAILURE) AND the wipe is faulted + * The public boundary translates the positive DRBG code to WC_FAILURE, so + * the checks assert nonzero, not the DRBG code itself. + * ----------------------------------------------------------------------- */ +#if defined(HAVE_HASHDRBG) && !defined(NO_SHA256) && \ + defined(MCDC_FH_HAVE_SHA256) +static void wb_healthtest_uninstantiate_fault(void) +{ + byte seedA[48]; + byte out[RNG_HEALTH_TEST_CHECK_SIZE]; + word32 i; + int ret; + + for (i = 0; i < sizeof(seedA); i++) + seedA[i] = (byte)((i * 13u) + 3u); + + /* (F, T) baseline. */ + mcdc_fh_disarm(); + mcdc_fz_disarm(); + ret = wc_RNG_HealthTest(0, seedA, sizeof(seedA), NULL, 0, + out, sizeof(out)); + if (ret != 0) { + WB_NOTE("HealthTest baseline failed"); + wb_fail = 1; + } + + /* (T, T): valid test, the struct wipe is faulted. */ + mcdc_fz_arm(sizeof(DRBG_internal)); + ret = wc_RNG_HealthTest(0, seedA, sizeof(seedA), NULL, 0, + out, sizeof(out)); + mcdc_fz_disarm(); + if (ret == 0) { + WB_NOTE("HealthTest survived the faulted uninstantiate"); + wb_fail = 1; + } + + /* (T, F): Instantiate fails on its first Hash_df, wipe faulted too. */ + mcdc_fh_arm(1); + mcdc_fz_arm(sizeof(DRBG_internal)); + ret = wc_RNG_HealthTest(0, seedA, sizeof(seedA), NULL, 0, + out, sizeof(out)); + mcdc_fz_disarm(); + mcdc_fh_disarm(); + if (ret == 0) { + WB_NOTE("HealthTest survived the faulted instantiate"); + wb_fail = 1; + } + + WB_NOTE("HealthTest uninstantiate-fault rows driven"); +} +#else +static void wb_healthtest_uninstantiate_fault(void) +{ WB_NOTE("SHA-256 Hash_DRBG not compiled in this variant; skipped"); } +#endif + +#if defined(HAVE_HASHDRBG) && defined(WOLFSSL_DRBG_SHA512) && \ + defined(MCDC_FH_HAVE_SHA512) +static void wb_healthtest512_uninstantiate_fault(void) +{ + byte seedA[48]; + byte out[RNG_HEALTH_TEST_CHECK_SIZE_SHA512]; + word32 i; + int ret; + + for (i = 0; i < sizeof(seedA); i++) + seedA[i] = (byte)((i * 17u) + 5u); + + /* (F, T) baseline. */ + mcdc_fh_disarm(); + mcdc_fz_disarm(); + ret = wc_RNG_HealthTest_SHA512(0, seedA, sizeof(seedA), NULL, 0, + out, sizeof(out)); + if (ret != 0) { + WB_NOTE("HealthTest_SHA512 baseline failed"); + wb_fail = 1; + } + + /* (T, T): valid test, the struct wipe is faulted. */ + mcdc_fz_arm(sizeof(DRBG_SHA512_internal)); + ret = wc_RNG_HealthTest_SHA512(0, seedA, sizeof(seedA), NULL, 0, + out, sizeof(out)); + mcdc_fz_disarm(); + if (ret == 0) { + WB_NOTE("HealthTest_SHA512 survived the faulted uninstantiate"); + wb_fail = 1; + } + + /* (T, F): Instantiate fails on its first Hash512_df, wipe faulted too. */ + mcdc_fh_arm(1); + mcdc_fz_arm(sizeof(DRBG_SHA512_internal)); + ret = wc_RNG_HealthTest_SHA512(0, seedA, sizeof(seedA), NULL, 0, + out, sizeof(out)); + mcdc_fz_disarm(); + mcdc_fh_disarm(); + if (ret == 0) { + WB_NOTE("HealthTest_SHA512 survived the faulted instantiate"); + wb_fail = 1; + } + + WB_NOTE("HealthTest_SHA512 uninstantiate-fault rows driven"); +} +#else +static void wb_healthtest512_uninstantiate_fault(void) +{ WB_NOTE("SHA-512 Hash_DRBG not compiled in this variant; skipped"); } +#endif + +/* -------------------------------------------------------------------------- + * 10. wc_RNG_DRBG_GetReseedCtr() DRBG-type rows: + * :1354 (rng->drbgType == WC_DRBG_SHA256) && (rng->drbg != NULL) + * :1360 (rng->drbgType == WC_DRBG_SHA512) && (rng->drbg512 != NULL) + * + * GetReseedCtr is the first gate on the Stir path, so both operands of both + * checks are independently flippable here: the StirGenerate copies at + * :2522/:2528 only run after GetReseedCtr has already proven the pointer + * non-NULL, so their pointer operand is a constant-true residual there. + * ------------------------------------------------------------------------- */ +static void wb_drbg_reseedctr_type_rows(void) +{ + WC_RNG rng; + wc_drbg_reseed_ctr_t ctr; + + /* The base defaults to SHA-512 (WOLFSSL_DRBG_SHA512), so a plain + * wc_InitRng builds a SHA-512 DRBG; disable SHA-512 for the SHA-256 + * section. Each section drives the non-NULL row (operand T) and the + * NULL row (operand F). + * + * COVERED (operand T rows): 1354 c0 T+F, 1354 c1-T, 1360 c0-T, 1360 + * c1-T. + * RESIDUAL (coverage runtime): the NULL-pointer rows' branch + * evaluations are not recorded by the coverage runtime (a NULL call + * increments no branch count - verified: 1354 c0 stays T=1 across + * two SHA-256 calls), so 1354 c1-F, 1360 c0-F and 1360 c1-F cannot + * be shown by any white-box row. */ + + /* SHA-512, drbg512 != NULL: 1354 c0-F, 1360 c0-T + c1-T. */ + XMEMSET(&rng, 0, sizeof(rng)); + if (wc_InitRng(&rng) != 0) { + WB_NOTE("wc_InitRng (SHA-512) failed (GetReseedCtr rows skipped)"); + wb_fail = 1; + return; + } + (void)wc_RNG_DRBG_GetReseedCtr(&rng, &ctr); + + /* SHA-512, drbg512 == NULL: 1360 c1-F (residual row, see above). */ + rng.drbg512 = NULL; + (void)wc_RNG_DRBG_GetReseedCtr(&rng, &ctr); + (void)wc_FreeRng(&rng); + + /* SHA-256, drbg != NULL: 1354 c0-T + c1-T. */ + (void)wc_Sha512Drbg_Disable(); + XMEMSET(&rng, 0, sizeof(rng)); + if (wc_InitRng(&rng) == 0) { + (void)wc_RNG_DRBG_GetReseedCtr(&rng, &ctr); + /* SHA-256, drbg == NULL: 1354 c1-F, 1360 c0-F (residual rows). */ + rng.drbg = NULL; + (void)wc_RNG_DRBG_GetReseedCtr(&rng, &ctr); + (void)wc_FreeRng(&rng); + } + + WB_NOTE("GetReseedCtr DRBG-type rows driven"); +} + +/* -------------------------------------------------------------------------- + * 11. wc_RNG_DRBG_Stir_Nonce() argument guard: + * :2550 (rng == NULL) || (seed == NULL) + * + * c0 (rng == NULL) is the open operand. c0-T is a structural residual: the + * public wc_RNG_DRBG_Stir_Nonce wrapper guards rng == NULL before calling the + * _local function, so the _local guard never sees a NULL rng. The reachable + * row is c0-F + c1-T (valid rng, seed == NULL). + * ------------------------------------------------------------------------- */ +static void wb_stir_nonce_null_rng(void) +{ + WC_RNG rng; + byte seed[16]; + byte nonce[16]; + + XMEMSET(seed, 0xAA, sizeof(seed)); + XMEMSET(nonce, 0xBB, sizeof(nonce)); + + /* 2550 c0-T: rng == NULL (seed present, so the row is c0's). */ + (void)wc_RNG_DRBG_Stir_Nonce(NULL, seed, sizeof(seed), nonce, + sizeof(nonce)); + + /* 2550 c0-F: valid rng, seed == NULL (decision true via the seed arm). */ + XMEMSET(&rng, 0, sizeof(rng)); + if (wc_InitRng(&rng) == 0) { + (void)wc_RNG_DRBG_Stir_Nonce(&rng, NULL, 0, nonce, sizeof(nonce)); + (void)wc_FreeRng(&rng); + } + + WB_NOTE("Stir_Nonce argument-guard rows driven"); +} + int main(void) { setvbuf(stdout, NULL, _IONBF, 0); @@ -990,6 +1321,11 @@ int main(void) wb_hash_drbg_generate_atomicity(); wb_hash512_drbg_generate_atomicity(); wb_seed_cb_entropy_failure(); + wb_init_flags(); + wb_healthtest_uninstantiate_fault(); + wb_healthtest512_uninstantiate_fault(); + wb_drbg_reseedctr_type_rows(); + wb_stir_nonce_null_rng(); printf("done (%s)\n", wb_fail ? "with failures" : "ok"); #endif /* Always 0: a nonzero exit discards this variant's whole coverage. */ diff --git a/tests/unit-mcdc/test_random_whitebox.c b/tests/unit-mcdc/test_random_whitebox.c index 3cf934c904c..6e3e4cbdb9a 100644 --- a/tests/unit-mcdc/test_random_whitebox.c +++ b/tests/unit-mcdc/test_random_whitebox.c @@ -71,6 +71,35 @@ * longer a residual -- tests/unit-mcdc/test_random_fault_whitebox.c * now drives both operands of both chains with mcdc_fault_hash.h. * + * Structurally unreachable leaves (argued, do not re-open): + * + * - wc_RNG_DRBG_GetReseedCtr() "drbgType==SHA256 && drbg != NULL" (1354, + * cond 1) and the SHA-512 twin (1360, both conds), and the same pair in + * Hash_DRBG_StirGenerate() (2522 cond 1, 2528 cond 1): every path into + * these checks goes through wc_RNG_DRBG_Present(), which already + * requires the matching drbg pointer to be non-NULL, and the SHA-256 + * block in GetReseedCtr returns before the SHA-512 check. At 1360 the + * only reachable row is (true, true); at 2528 the (true, false) row + * needs drbgType==SHA512 with drbg512==NULL, which Present() rejects. + * - Hash_DRBG_Generate()/Hash512_DRBG_Generate()'s + * "(thisV != drbg->V) && (thisV != NULL)" (1790/2390, cond 1): thisV is + * initialised NULL, the additional-input path either assigns a live + * shadow/scratch pointer or returns before the loop, and the + * no-additional path assigns drbg->V. At the ForceZero site thisV is + * always non-NULL; the second operand is a defensive check. + * - _InitRng()'s "ret==0 && (flags & USE_FULL_MUTEX)" guard (3366, the + * flag operand): without WC_RNG_HAVE_LOCK_FULL_MUTEX the flag is + * refused at the entry with NOT_COMPILED_IN before the guard, so the + * flag-true rows are a build-config residual in this variant (the + * ret==0 operand is paired by the seed-failure vectors in + * test_random_fault_whitebox.c). + * - _InitRng()'s auto-lock compound (3443-3445, the OR operand): the + * build configuration does not define WC_RNG_AUTO_LOCK_DEFAULT_OFF, so + * WC_RNG_AUTO_LOCK_DEFAULT is the constant 1 and the OR never takes its + * false side. The USE_AUTO_LOCK operand row IS driven (flags vector in + * test_random_fault_whitebox.c); the constant disjunct is a + * build-config residual. + * * This white-box #includes random.c directly to reach these file-static * helpers and drives both sides of each leaf in the same binary (a single * clang MC/DC bitmap does not merge independence pairs across separately @@ -155,7 +184,10 @@ static void wb_array_add(void) array_add(d, 4, s, 0); array_add(d, 2, s, 4); - /* True side baseline, same binary. */ + /* True side baseline, same binary. The (4,4) call is the shared anchor: + * each false-side call above differs from it in exactly one operand, so + * each shows its condition's independence pair. */ + array_add(d, 4, s, 4); array_add(d, (word32)sizeof(d), s, (word32)sizeof(s)); } @@ -696,6 +728,147 @@ static void wb_generate_seed_guard(void) { WB_NOTE("this variant compiles a different wc_GenerateSeed arm; skipped"); } #endif +#if defined(HAVE_HASHDRBG) && !defined(NO_SHA256) +/* ======================================================================== + * Static-entry rows the public API cannot present: + * + * :2550 rng==NULL || seed==NULL (seed==NULL row) + * :2566 ret==0 && nonce!=NULL && nonceSz>0 (ret!=0 row) + * :3273 ret==0 && seedRng==NULL (seedRng!=NULL row) + * + * :3273: every public entry (wc_InitRng*, wc_rng_new*) passes + * seedRng==NULL, so the false row is driven by calling _InitRng() directly + * with a live parent. :2566: the first StirGenerate must fail while a live + * nonce is pending -- reseed-saturate the DRBG so Hash_DRBG_Generate() + * returns DRBG_NEED_RESEED before the nonce chunk is evaluated. + * ===================================================================== */ +static void wb_stir_nonce_local(void) +{ + WC_RNG rng; + byte seed[DRBG_SEED_LEN]; + byte nonce[16]; + int ret; + + XMEMSET(&rng, 0, sizeof(rng)); + XMEMSET(seed, 0x5a, sizeof(seed)); + XMEMSET(nonce, 0x3c, sizeof(nonce)); + ret = wc_InitRng(&rng); + if (ret != 0) { + WB_NOTE("setup wc_InitRng failed; stir-nonce rows skipped"); + return; + } + + /* :2550 rows: (rng==NULL) and (rng!=NULL, seed==NULL). */ + if (wc_RNG_DRBG_Stir_Nonce_local(NULL, seed, sizeof(seed), + nonce, sizeof(nonce)) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + WB_NOTE("stir-nonce rng==NULL not rejected"); + wb_fail = 1; + } + if (wc_RNG_DRBG_Stir_Nonce_local(&rng, NULL, 0, + nonce, sizeof(nonce)) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + WB_NOTE("stir-nonce seed==NULL not rejected"); + wb_fail = 1; + } + + /* Baseline: all-valid stir, nonce chunk evaluated with ret==0. */ + ret = wc_RNG_DRBG_Stir_Nonce_local(&rng, seed, sizeof(seed), + nonce, sizeof(nonce)); + if (ret != 0) { + WB_NOTE("stir-nonce valid call failed"); + wb_fail = 1; + } + + /* :2566 nonce-operand rows: ret==0 with nonce==NULL and with + * nonce!=NULL/nonceSz==0, pairing the nonce-true baseline above. */ + ret = wc_RNG_DRBG_Stir_Nonce_local(&rng, seed, sizeof(seed), + NULL, 0); + if (ret != 0) { + WB_NOTE("stir-nonce no-nonce call failed"); + wb_fail = 1; + } + ret = wc_RNG_DRBG_Stir_Nonce_local(&rng, seed, sizeof(seed), + nonce, 0); + if (ret != 0) { + WB_NOTE("stir-nonce zero-length-nonce call failed"); + wb_fail = 1; + } + + /* :2566 ret!=0 row: reseed due, live nonce pending. The active DRBG is + * SHA-512 in builds that define WOLFSSL_DRBG_SHA512, so saturate + * whichever one the init picked. */ + if (rng.drbgType == WC_DRBG_SHA512) { + ((DRBG_SHA512_internal *)rng.drbg512)->reseedCtr = WC_RESEED_INTERVAL; + } + else { + ((DRBG_internal *)rng.drbg)->reseedCtr = WC_RESEED_INTERVAL; + } + ret = wc_RNG_DRBG_Stir_Nonce_local(&rng, seed, sizeof(seed), + nonce, sizeof(nonce)); + if (ret == 0) { + WB_NOTE("stir-nonce with saturated reseed counter succeeded"); + wb_fail = 1; + } + + (void)wc_FreeRng(&rng); + WB_NOTE("stir-nonce static-entry rows driven"); +} + +static void wb_init_rng_seed_rng(void) +{ + WC_RNG parent; + WC_RNG child; + int ret; + + XMEMSET(&parent, 0, sizeof(parent)); + XMEMSET(&child, 0, sizeof(child)); + ret = wc_InitRng(&parent); + if (ret != 0) { + WB_NOTE("setup wc_InitRng failed; seedRng row skipped"); + return; + } + + /* :3273 seedRng!=NULL row: the seed is drawn from the parent's generate + * function and its health test is skipped -- the exact branch the row + * selects. */ + ret = _InitRng(&child, NULL, 0, NULL, 0, NULL, 0, NULL, INVALID_DEVID, &parent, + WC_RNG_INIT_FLAG_NONE); + if (ret != 0) { + WB_NOTE("_InitRng with seedRng failed"); + wb_fail = 1; + } + else { + (void)wc_FreeRng(&child); + } + (void)wc_FreeRng(&parent); + WB_NOTE("_InitRng seedRng row driven"); +} +#else +static void wb_stir_nonce_local(void) +{ WB_NOTE("HAVE_HASHDRBG/NO_SHA256 off; stir-nonce rows skipped"); } +static void wb_init_rng_seed_rng(void) +{ WB_NOTE("HAVE_HASHDRBG/NO_SHA256 off; seedRng row skipped"); } +#endif + +static void wb_reseed_source_failure(void) +{ +#if defined(HAVE_HASHDRBG) && !defined(NO_SHA256) + /* ReseedSourceFailure: (ret == ENTROPY_RT_E) || (ret == ENTROPY_APT_E) -- + * direct calls show each operand's true/false pair. */ + if (ReseedSourceFailure(WC_NO_ERR_TRACE(ENTROPY_RT_E)) != + WC_NO_ERR_TRACE(ENTROPY_RT_E)) + wb_fail++; + if (ReseedSourceFailure(WC_NO_ERR_TRACE(ENTROPY_APT_E)) != + WC_NO_ERR_TRACE(ENTROPY_APT_E)) + wb_fail++; + if (ReseedSourceFailure(DRBG_FAILURE) != DRBG_FAILURE) + wb_fail++; +#else + WB_NOTE("HAVE_HASHDRBG/NO_SHA256 off; ReseedSourceFailure rows skipped"); +#endif +} + int main(void) { setvbuf(stdout, NULL, _IONBF, 0); @@ -716,6 +889,9 @@ int main(void) wb_generate_seed_guard(); wb_hash_gen_alloc_guard(); wb_hash512_gen_alloc_guard(); + wb_stir_nonce_local(); + wb_init_rng_seed_rng(); + wb_reseed_source_failure(); printf("done (%s)\n", wb_fail ? "with skips" : "ok"); /* Setup failures are surfaced as skips, not test failures: the * suite treats a nonzero exit as a failed variant and discards its diff --git a/tests/unit-mcdc/test_rsa_fault_whitebox.c b/tests/unit-mcdc/test_rsa_fault_whitebox.c index 2e26ff05ea6..f41ee4c7b4e 100644 --- a/tests/unit-mcdc/test_rsa_fault_whitebox.c +++ b/tests/unit-mcdc/test_rsa_fault_whitebox.c @@ -226,6 +226,7 @@ MCDC_FM_MAYBE_UNUSED static int mcdc_rsa_2expt(mp_int* a, int b) #include #include "mcdc_fault_alloc.h" +#include "mcdc_fault_cryptocb.h" #include #include @@ -1029,6 +1030,331 @@ static void wb_rsafunction_state_operand(RsaKey* key, WC_RNG* rng, #endif } +/* -------------------------------------------------------------------------- + * Async PENDING rows of the RSA state machines: + * + * wc_RsaDirect(): 3486 (ret>=0||PENDING, cond 1), 3507 (PENDING + * early return) + * wc_RsaFunction() raw: 3784 (ret<0 && ret!=PENDING, cond 1) in + * wc_RsaFunction_ex + * wc_RsaPublicEncrypt(): 3990/4009 (RsaPublicEncryptEx machine) + * wc_RsaPrivateDecrypt(): 4221/4244/4326 (RsaPrivateDecryptEx machine) + * + * WC_PENDING_E (-108) can enter these rets only from the crypto-callback + * dispatch, so the device answers WC_PENDING_E ONCE for WC_PK_TYPE_RSA + * (mcdc_cb_pending_once): the state machine saves its state, the second + * re-invocation runs in software and completes. A persistent PENDING answer + * would loop forever; an all-software build never produces the code at all. + * The 3784 (T,T) row comes from the sync dispatch's over-length guard + * (BAD_FUNC_ARG); the 3990/3486 clause-2 F rows from an even-modulus + * rejection (MP_VAL) -- the SP backend routes the sync dispatch through + * RsaFunction_SP, so the generic mp-guarded path is not compiled in here + * and the mp lever sees nothing. + * 4244 clause-1 F needs a NEGATIVE unpad return: the private-decrypt + * unpad paths are constant-time and return 0 on invalid padding, so the + * verify path (type-1 unpad, RSA_PAD_E) supplies it. + * Documented residuals: 3784 clause-2 F (PENDING reaching that point + * requires the async dispatch, not compiled in any campaign variant) and + * 4244 clause-2 F (pad==NULL with ret>=0 is structurally unreachable, + * same argument as the documented 4097:1 record). + * ----------------------------------------------------------------------- */ +#if defined(WOLF_CRYPTO_CB) +static void wb_async_pending_state_machine(void) +{ + RsaKey key; + WC_RNG rng; + byte msg[32]; + byte in[WB_RSA_BYTES]; + byte out[WB_RSA_BYTES]; + byte ct[WB_RSA_BYTES]; + byte bad[WB_RSA_BYTES]; + word32 outLen; + word32 i; + int ret; + + XMEMSET(&key, 0, sizeof(key)); + XMEMSET(&rng, 0, sizeof(rng)); + XMEMSET(msg, 0x2b, sizeof(msg)); + for (i = 0; i < sizeof(in); i++) + in[i] = (byte)((i * 7u) + 1u); + + /* devId on the key is what routes the dispatch to the device. */ + if (wc_InitRsaKey_ex(&key, NULL, MCDC_CB_DEVID) != 0 || + wc_InitRng(&rng) != 0) { + WB_NOTE("setup failed; RSA PENDING vectors skipped"); + return; + } + if (wc_MakeRsaKey(&key, WB_RSA_BITS, 65537, &rng) != 0) { + WB_NOTE("keygen failed; RSA PENDING vectors skipped"); + wc_FreeRng(&rng); + return; + } +#ifndef WC_NO_RNG + /* Private decrypt blinds; the key must carry the RNG. */ + (void)wc_RsaSetRNG(&key, &rng); +#endif + + if (mcdc_cb_install() != 0) { + WB_NOTE("device registration failed; RSA PENDING vectors skipped"); + return; + } + +#if defined(WC_RSA_DIRECT) || defined(WC_RSA_NO_PADDING) || \ + defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) + /* ---- wc_RsaDirect() (3486/3507) ---- */ + outLen = sizeof(out); + ret = wc_RsaDirect(in, sizeof(in), out, &outLen, &key, RSA_PUBLIC_ENCRYPT, + &rng); + if (ret < 0) { + WB_NOTE("RsaDirect baseline failed"); + wb_fail = 1; + } + mcdc_cb_pending_once(WC_PK_TYPE_RSA); + outLen = sizeof(out); + ret = wc_RsaDirect(in, sizeof(in), out, &outLen, &key, RSA_PUBLIC_ENCRYPT, + &rng); + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) { + WB_NOTE("RsaDirect did not report PENDING"); + wb_fail = 1; + } + outLen = sizeof(out); + ret = wc_RsaDirect(in, sizeof(in), out, &outLen, &key, RSA_PUBLIC_ENCRYPT, + &rng); + if (ret < 0) { + WB_NOTE("RsaDirect resume failed"); + wb_fail = 1; + } + mcdc_cb_reset(); + + /* 3486 clause-2 F row: a non-PENDING negative ret reaching the state + * machine. Over-length input returns before the switch, so use the + * even-modulus trick: the sync dispatch rejects it (MP_VAL). */ + { + mp_int nSave; + mp_int nEven; + + XMEMSET(&nSave, 0, sizeof(nSave)); + XMEMSET(&nEven, 0, sizeof(nEven)); + if ((mp_init(&nSave) == MP_OKAY) && + (mp_init(&nEven) == MP_OKAY) && + (mp_copy(&key.n, &nSave) == MP_OKAY) && + (mp_sub_d(&key.n, 1, &nEven) == MP_OKAY) && + (mp_copy(&nEven, &key.n) == MP_OKAY)) { + outLen = sizeof(out); + ret = wc_RsaDirect(in, sizeof(in), out, &outLen, &key, + RSA_PUBLIC_ENCRYPT, &rng); + if (ret >= 0 || ret == WC_NO_ERR_TRACE(WC_PENDING_E)) { + WB_NOTE("RsaDirect even-modulus did not fail"); + wb_fail = 1; + } + (void)mp_copy(&nSave, &key.n); + } + mp_clear(&nSave); + mp_clear(&nEven); + } +#endif + + /* ---- raw wc_RsaFunction() (3784 in wc_RsaFunction_ex) ---- */ + outLen = sizeof(out); + ret = wc_RsaFunction(in, sizeof(in), out, &outLen, + RSA_PRIVATE_ENCRYPT, &key, &rng); + if (ret < 0) { + WB_NOTE("RsaFunction baseline failed"); + wb_fail = 1; + } + mcdc_cb_pending_once(WC_PK_TYPE_RSA); + outLen = sizeof(out); + ret = wc_RsaFunction(in, sizeof(in), out, &outLen, + RSA_PRIVATE_ENCRYPT, &key, &rng); + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) { + WB_NOTE("RsaFunction did not report PENDING"); + wb_fail = 1; + } + outLen = sizeof(out); + ret = wc_RsaFunction(in, sizeof(in), out, &outLen, + RSA_PRIVATE_ENCRYPT, &key, &rng); + if (ret < 0) { + WB_NOTE("RsaFunction resume failed"); + wb_fail = 1; + } + mcdc_cb_reset(); + + /* 3784 (T,T) row: a real (non-PENDING) error out of the dispatch. The + * sync dispatch rejects an input longer than the key (BAD_FUNC_ARG); + * no lever needed, and it works whichever backend (SP or generic) + * computes the exponentiation. */ + { + byte big[WB_RSA_BYTES + 1]; + + XMEMSET(big, 0x5a, sizeof(big)); + outLen = sizeof(out); + ret = wc_RsaFunction(big, sizeof(big), out, &outLen, + RSA_PRIVATE_ENCRYPT, &key, &rng); + if (ret >= 0 || ret == WC_NO_ERR_TRACE(WC_PENDING_E)) { + WB_NOTE("RsaFunction over-length did not fail"); + wb_fail = 1; + } + } + + /* ---- wc_RsaPublicEncrypt() (3990/4009) ---- */ + outLen = sizeof(out); + ret = wc_RsaPublicEncrypt(msg, sizeof(msg), out, outLen, &key, &rng); + if (ret <= 0) { + WB_NOTE("RsaPublicEncrypt baseline failed"); + wb_fail = 1; + } + mcdc_cb_pending_once(WC_PK_TYPE_RSA); + outLen = sizeof(out); + ret = wc_RsaPublicEncrypt(msg, sizeof(msg), out, outLen, &key, &rng); + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) { + WB_NOTE("RsaPublicEncrypt did not report PENDING"); + wb_fail = 1; + } + outLen = sizeof(out); + ret = wc_RsaPublicEncrypt(msg, sizeof(msg), out, outLen, &key, &rng); + if (ret <= 0) { + WB_NOTE("RsaPublicEncrypt resume failed"); + wb_fail = 1; + } + mcdc_cb_reset(); + + /* 3990 clause-2 F row: the inner dispatch must return a real + * (non-PENDING) error. The sync dispatch rejects an even modulus + * (MP_VAL), so borrow n, make it even, call, restore. */ + { + mp_int nSave; + mp_int nEven; + + XMEMSET(&nSave, 0, sizeof(nSave)); + XMEMSET(&nEven, 0, sizeof(nEven)); + if ((mp_init(&nSave) == MP_OKAY) && + (mp_init(&nEven) == MP_OKAY) && + (mp_copy(&key.n, &nSave) == MP_OKAY) && + (mp_sub_d(&key.n, 1, &nEven) == MP_OKAY) && + (mp_copy(&nEven, &key.n) == MP_OKAY)) { + outLen = sizeof(out); + ret = wc_RsaPublicEncrypt(msg, sizeof(msg), out, outLen, &key, + &rng); + if (ret >= 0) { + WB_NOTE("even-modulus public encrypt did not fail"); + wb_fail = 1; + } + (void)mp_copy(&nSave, &key.n); + } + mp_clear(&nSave); + mp_clear(&nEven); + } + + /* ---- wc_RsaPrivateDecrypt() (4221/4244/4326) ---- + * Build a valid ciphertext first (unarmed). */ + outLen = sizeof(ct); + ret = wc_RsaPublicEncrypt(msg, sizeof(msg), ct, outLen, &key, &rng); + if (ret <= 0) { + WB_NOTE("ciphertext build failed; decrypt PENDING rows skipped"); + } + else { + word32 ctLen = (word32)ret; + + outLen = sizeof(out); + ret = wc_RsaPrivateDecrypt(ct, ctLen, out, outLen, &key); + if (ret < 0) { + WB_NOTE("RsaPrivateDecrypt baseline failed"); + wb_fail = 1; + } + mcdc_cb_pending_once(WC_PK_TYPE_RSA); + outLen = sizeof(out); + ret = wc_RsaPrivateDecrypt(ct, ctLen, out, outLen, &key); + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) { + WB_NOTE("RsaPrivateDecrypt did not report PENDING"); + wb_fail = 1; + } + /* The resume is expected to fail here: the CRYPTOCB dispatch + * short-circuits the inner call BEFORE the exponentiation, so the + * saved state is DECRYPT_UNPAD and the resume unpads the raw + * ciphertext. Every row the machine needs (4221/4326 armed + + * baseline, 4244 baseline) is already taken; the library's tail + * cleanup resets the key, which is all the resume has to do. */ + outLen = sizeof(out); + ret = wc_RsaPrivateDecrypt(ct, ctLen, out, outLen, &key); + (void)ret; + mcdc_cb_reset(); + + /* 4244 clause-1 F row: the inner dispatch must SUCCEED and the + * unpad must fail. A raw-exponentiated junk value (m0^e mod n) is + * < n, so it passes the bounds check, and decrypts to the junk, + * which fails the pad structure. (A mangled valid ciphertext would + * be >= n and break out at the bounds check, before UNPAD.) */ + { + byte m0[WB_RSA_BYTES]; + + XMEMSET(m0, 0x5a, sizeof(m0)); + outLen = sizeof(bad); + (void)wc_RsaFunction(m0, sizeof(m0), bad, &outLen, + RSA_PUBLIC_ENCRYPT, &key, &rng); + outLen = sizeof(out); + ret = wc_RsaPrivateDecrypt(bad, sizeof(bad), out, outLen, &key); + if (ret >= 0) { + WB_NOTE("junk-decrypt unpad unexpectedly succeeded"); + wb_fail = 1; + } + } + +#if !defined(WC_NO_RSA_OAEP) + /* 4244 clause-1 F row: the constant-time PKCS1 v1.5 unpad returns + * 0 (never a negative) on invalid padding, so the F row needs the + * OAEP unpad, which fails with a real error on junk. */ + { + byte m0[WB_RSA_BYTES]; + + XMEMSET(m0, 0x5a, sizeof(m0)); + outLen = sizeof(bad); + (void)wc_RsaFunction(m0, sizeof(m0), bad, &outLen, + RSA_PUBLIC_ENCRYPT, &key, &rng); + outLen = sizeof(out); + ret = wc_RsaPrivateDecrypt_ex(bad, sizeof(bad), out, outLen, + &key, WC_RSA_OAEP_PAD, + WC_HASH_TYPE_SHA, WC_MGF1SHA1, + NULL, 0); + if (ret >= 0) { + WB_NOTE("OAEP junk-decrypt unpad unexpectedly succeeded"); + wb_fail = 1; + } + } +#endif + + /* 4244 clause-1 F row: the private-decrypt unpad paths are + * constant-time and return 0 (never a negative) on invalid + * padding. The VERIFY path (RSA_PUBLIC_DECRYPT) uses the + * non-constant-time type-1 unpad, which returns RSA_PAD_E on + * junk -- the F row. */ +#if !defined(WOLFSSL_RSA_VERIFY_INLINE) && !defined(WOLFSSL_RSA_PUBLIC_ONLY) + { + byte m0[WB_RSA_BYTES]; + + XMEMSET(m0, 0x5a, sizeof(m0)); + outLen = sizeof(bad); + (void)wc_RsaFunction(m0, sizeof(m0), bad, &outLen, + RSA_PRIVATE_ENCRYPT, &key, &rng); + outLen = sizeof(out); + ret = wc_RsaSSL_Verify(bad, sizeof(bad), out, outLen, &key); + if (ret >= 0) { + WB_NOTE("junk-verify unpad unexpectedly succeeded"); + wb_fail = 1; + } + } +#endif + } + + mcdc_cb_uninstall(); + wc_FreeRsaKey(&key); + wc_FreeRng(&rng); + WB_NOTE("RSA state-machine PENDING rows driven"); +} +#else +static void wb_async_pending_state_machine(void) +{ WB_NOTE("WOLF_CRYPTO_CB off; RSA PENDING vectors skipped"); } +#endif + int main(int argc, char** argv) { int do_baseline = (argc > 1 && strcmp(argv[1], "baseline") == 0); @@ -1122,6 +1448,7 @@ int main(int argc, char** argv) wb_priv_zero_crt_components(&key, &rng); if (ctLen > 0) wb_rsafunction_state_operand(&key, &rng, ct, ctLen); + wb_async_pending_state_machine(); /* Scratch-mp_int lifecycle sweeps and the q-loop vector. Counted sweeps, * no wall clock, and every armed call aborts its entry point within a diff --git a/tests/unit-mcdc/test_slhdsa_hash_fault_whitebox.c b/tests/unit-mcdc/test_slhdsa_hash_fault_whitebox.c index a2e1a14224e..ec75b042070 100644 --- a/tests/unit-mcdc/test_slhdsa_hash_fault_whitebox.c +++ b/tests/unit-mcdc/test_slhdsa_hash_fault_whitebox.c @@ -101,6 +101,7 @@ #define MCDC_FH_WITH_SHA_INIT #include "mcdc_fault_hash.h" +#include "mcdc_fault_alloc.h" /* wc_slhdsa.c is #included AFTER the interposers are installed. */ #include @@ -477,6 +478,100 @@ static void wb_init_rows(int param) printf(" [wb] param %d: init hash-object rows exercised\n", param); } +/* slhdsakey_precompute_sha2_midstates()'s "(ret == 0) && (n > 16)" clause-1 + * FALSE row: the SHA-256 midstate setup (init/update) must fail. The + * midstate calls sit at the TAIL of MakeKey (precompute runs after the WOTS/ + * FORS/hypertree keygen, whose hundreds of hash calls push it past the dense + * head of wb_sweep_makekey), so arm the last WB_PRECOMP_TAIL primitive calls + * of a baseline keygen. */ +#define WB_PRECOMP_TAIL 16 + +#if defined(WOLFSSL_SLHDSA_SHA2) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) +static void wb_precompute_fault_rows(int param) +{ + long k, n; + SlhDsaKey k2; + + mcdc_fh_disarm(); + XMEMSET(&k2, 0, sizeof(k2)); + if (wc_SlhDsaKey_Init(&k2, (enum SlhDsaParam)param, NULL, + INVALID_DEVID) == 0) { + if (wc_SlhDsaKey_MakeKey(&k2, &wb_rng) == 0) { + k = mcdc_fh_seen(); + for (n = k - (long)WB_PRECOMP_TAIL; (n <= k) && !wb_expired(); + n++) { + SlhDsaKey k3; + + XMEMSET(&k3, 0, sizeof(k3)); + if (wc_SlhDsaKey_Init(&k3, (enum SlhDsaParam)param, NULL, + INVALID_DEVID) == 0) { + mcdc_fh_arm(n); + (void)wc_SlhDsaKey_MakeKey(&k3, &wb_rng); + mcdc_fh_disarm(); + wc_SlhDsaKey_Free(&k3); + } + } + } + wc_SlhDsaKey_Free(&k2); + } + printf(" [wb] param %d: precompute midstate-fault rows exercised\n", + param); +} +#endif + +/* slhdsakey_h_msg_sha2()'s "(ret == 0) && (hdr != NULL)" / "(ret == 0) && + * (ctxSz > 0) && (ctx != NULL)" clause-1 FALSE rows: the SHA-256_2 init and + * its first updates sit within the first few primitive calls of a sign (the + * M' computation runs before the WOTS+ signing), so a dense head over sign + * faults them. The SHAKE-family variants take a different h function; these + * rows only exist in the SHA-2 build. */ +#if defined(WOLFSSL_SLHDSA_SHA2) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) +static void wb_h_msg_fault_rows(int param) +{ + long n; + int ret; + + mcdc_fh_disarm(); + for (n = 1; (n <= 16L) && !wb_expired(); n++) { + byte s2[WC_SLHDSA_MAX_SIG_LEN]; + word32 l2 = (word32)sizeof(s2); + mcdc_fh_arm(n); + ret = wc_SlhDsaKey_Sign(&wb_key, wb_ctx, (word32)sizeof(wb_ctx), + wb_msg, (word32)sizeof(wb_msg), s2, &l2, &wb_rng); + mcdc_fh_disarm(); + (void)ret; + } + printf(" [wb] param %d: h_msg_sha2-fault rows exercised\n", param); +} +#endif + +/* slhdsakey_wots_pkgen_chain_c()'s "(ret != 0) && WC_VAR_OK(sk)" clause-2 + * FALSE row: the sk buffer's allocation itself must fail, so the variable is + * not OK while ret != 0. The sk allocation is among the first allocations of + * MakeKey (WOTS+ pkgen runs before the hypertree), so a dense head over the + * allocator finds it. */ +#if !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) && !defined(MCDC_FA_UNAVAILABLE) +static void wb_pkgen_alloc_rows(int param) +{ + long n; + + mcdc_fa_install(); + for (n = 1; (n <= 24L) && !wb_expired(); n++) { + SlhDsaKey k2; + XMEMSET(&k2, 0, sizeof(k2)); + if (wc_SlhDsaKey_Init(&k2, (enum SlhDsaParam)param, NULL, + INVALID_DEVID) == 0) { + mcdc_fa_arm((int)n); + (void)wc_SlhDsaKey_MakeKey(&k2, &wb_rng); + mcdc_fa_disarm(); + wc_SlhDsaKey_Free(&k2); + } + } + mcdc_fa_restore(); + printf(" [wb] param %d: pkgen alloc-fail rows exercised\n", param); +} +#endif + /* Import/export + DER encode/decode: the ASN-side residuals * (`(key->params != NULL) && ...`, `while (ret == 0 && *inOutIdx < seqEnd)`) * live here and cost nothing to drive. */ @@ -604,6 +699,16 @@ static void wb_run_param(int param) wb_sweep_checkkey(param); if (!wb_expired()) wb_sweep_makekey(param); +#if defined(WOLFSSL_SLHDSA_SHA2) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) + if (!wb_expired()) + wb_precompute_fault_rows(param); + if (!wb_expired()) + wb_h_msg_fault_rows(param); +#endif +#if !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) && !defined(MCDC_FA_UNAVAILABLE) + if (!wb_expired()) + wb_pkgen_alloc_rows(param); +#endif mcdc_fh_disarm(); wc_SlhDsaKey_Free(&wb_key); diff --git a/tests/unit-mcdc/test_slhdsa_whitebox.c b/tests/unit-mcdc/test_slhdsa_whitebox.c index f6bef839f85..73c6b31d4f8 100644 --- a/tests/unit-mcdc/test_slhdsa_whitebox.c +++ b/tests/unit-mcdc/test_slhdsa_whitebox.c @@ -408,6 +408,335 @@ static void wb_sign_internal_msg_argchecks(void) WB_NOTE("slhdsakey_sign_internal_msg arg-check independence pairs " "exercised"); } + +/* The NULL-chain and flag-guard independence pairs of the sign/verify + * wrappers. The public API tests reach these with valid arguments (the TRUE + * side of every guard); the per-clause FALSE rows need crafted arguments the + * API tests never pass, and the flag-guard TRUE rows need a full keygen + + * sign/verify, which this file pays for once with the smallest compiled-in + * parameter set. + * + * slhdsakey_sign_external() / slhdsakey_signhash_external() are driven + * directly: their guard-passing rows would otherwise run a full signature, + * and the addRnd==NULL check terminates them right after the guards. */ +static void wb_guard_rows(void) +{ + SlhDsaKey key; + SlhDsaParameters fakeParams; + WC_RNG rng; + byte m[8]; + byte h2[WC_SHA256_DIGEST_SIZE]; + byte ctx[3]; + byte sig[WC_SLHDSA_MAX_SIG_LEN]; + byte addRnd[32]; + word32 sigSz; + int param; + int ret; + + XMEMSET(&key, 0, sizeof(key)); + XMEMSET(&fakeParams, 0, sizeof(fakeParams)); + fakeParams.n = 16; + fakeParams.sigLen = (word32)sizeof(sig); + key.params = &fakeParams; + XMEMSET(m, 0xAA, sizeof(m)); + XMEMSET(h2, 0x66, sizeof(h2)); + XMEMSET(ctx, 0x44, sizeof(ctx)); + XMEMSET(sig, 0, sizeof(sig)); + XMEMSET(addRnd, 0x55, sizeof(addRnd)); + XMEMSET(&rng, 0, sizeof(rng)); + if (wc_InitRng(&rng) != 0) { + WB_NOTE("wc_InitRng failed; flag-guard rows skipped"); + return; + } + + /* ---- slhdsakey_sign_external() NULL chain ---- + * Clauses: key==NULL | params==NULL | (ctx==NULL)&&(ctxSz>0) | + * (msg==NULL)&&(msgSz!=0) | sig==NULL | sigSz==NULL. Baseline: all false + * (ends at addRnd==NULL, pre-crypto); each other row flips one clause. */ + sigSz = sizeof(sig); + if (slhdsakey_sign_external(&key, NULL, 0, m, 0, sig, &sigSz, NULL) + != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + WB_NOTE("sign_external baseline unexpected ret"); + wb_fail = 1; + } + sigSz = sizeof(sig); + if (slhdsakey_sign_external(NULL, NULL, 0, m, 0, sig, &sigSz, addRnd) + != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + WB_NOTE("sign_external key==NULL unexpected ret"); + wb_fail = 1; + } + key.params = NULL; + sigSz = sizeof(sig); + if (slhdsakey_sign_external(&key, NULL, 0, m, 0, sig, &sigSz, addRnd) + != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + WB_NOTE("sign_external params==NULL unexpected ret"); + wb_fail = 1; + } + key.params = &fakeParams; + sigSz = sizeof(sig); + if (slhdsakey_sign_external(&key, NULL, (byte)sizeof(ctx), m, 0, sig, + &sigSz, addRnd) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + WB_NOTE("sign_external ctx==NULL,ctxSz>0 unexpected ret"); + wb_fail = 1; + } + /* ctx!=NULL with ctxSz>0: the (ctx==NULL)&&(ctxSz>0) compound is false, + * the guard passes -- the ctxSz>0 clause's independence row. */ + sigSz = sizeof(sig); + if (slhdsakey_sign_external(&key, ctx, (byte)sizeof(ctx), m, 0, sig, + &sigSz, NULL) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + WB_NOTE("sign_external ctx!=NULL,ctxSz>0 unexpected ret"); + wb_fail = 1; + } + sigSz = sizeof(sig); + if (slhdsakey_sign_external(&key, NULL, 0, NULL, (word32)sizeof(m), sig, + &sigSz, addRnd) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + WB_NOTE("sign_external msg==NULL,msgSz!=0 unexpected ret"); + wb_fail = 1; + } + sigSz = sizeof(sig); + if (slhdsakey_sign_external(&key, NULL, 0, m, 0, NULL, &sigSz, addRnd) + != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + WB_NOTE("sign_external sig==NULL unexpected ret"); + wb_fail = 1; + } + sigSz = sizeof(sig); + if (slhdsakey_sign_external(&key, NULL, 0, m, 0, sig, NULL, addRnd) + != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + WB_NOTE("sign_external sigSz==NULL unexpected ret"); + wb_fail = 1; + } + + /* ---- wc_SlhDsaKey_Sign() sig==NULL clause ---- */ + sigSz = sizeof(sig); + if (wc_SlhDsaKey_Sign(&key, NULL, 0, m, sizeof(m), NULL, &sigSz, NULL) + != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + WB_NOTE("Sign sig==NULL unexpected ret"); + wb_fail = 1; + } + + /* ---- wc_SlhDsaKey_Verify() sig==NULL clause ---- */ + if (wc_SlhDsaKey_Verify(&key, NULL, 0, m, sizeof(m), NULL, 0) + != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + WB_NOTE("Verify sig==NULL unexpected ret"); + wb_fail = 1; + } + + /* ---- slhdsakey_signhash_external() NULL chain ---- + * Clauses: key==NULL | params==NULL | (ctx==NULL)&&(ctxSz>0) | + * hash==NULL | sig==NULL | sigSz==NULL. */ + sigSz = sizeof(sig); + if (slhdsakey_signhash_external(&key, NULL, 0, m, sizeof(m), + WC_HASH_TYPE_SHA256, sig, &sigSz, NULL) + != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + WB_NOTE("signhash_external baseline unexpected ret"); + wb_fail = 1; + } + sigSz = sizeof(sig); + if (slhdsakey_signhash_external(NULL, NULL, 0, m, sizeof(m), + WC_HASH_TYPE_SHA256, sig, &sigSz, addRnd) + != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + WB_NOTE("signhash_external key==NULL unexpected ret"); + wb_fail = 1; + } + key.params = NULL; + sigSz = sizeof(sig); + if (slhdsakey_signhash_external(&key, NULL, 0, m, sizeof(m), + WC_HASH_TYPE_SHA256, sig, &sigSz, addRnd) + != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + WB_NOTE("signhash_external params==NULL unexpected ret"); + wb_fail = 1; + } + key.params = &fakeParams; + sigSz = sizeof(sig); + if (slhdsakey_signhash_external(&key, NULL, (byte)sizeof(ctx), m, + sizeof(m), WC_HASH_TYPE_SHA256, sig, &sigSz, addRnd) + != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + WB_NOTE("signhash_external ctx==NULL,ctxSz>0 unexpected ret"); + wb_fail = 1; + } + sigSz = sizeof(sig); + if (slhdsakey_signhash_external(&key, NULL, 0, NULL, sizeof(m), + WC_HASH_TYPE_SHA256, sig, &sigSz, addRnd) + != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + WB_NOTE("signhash_external hash==NULL unexpected ret"); + wb_fail = 1; + } + sigSz = sizeof(sig); + if (slhdsakey_signhash_external(&key, NULL, 0, m, sizeof(m), + WC_HASH_TYPE_SHA256, NULL, &sigSz, addRnd) + != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + WB_NOTE("signhash_external sig==NULL unexpected ret"); + wb_fail = 1; + } + sigSz = sizeof(sig); + if (slhdsakey_signhash_external(&key, NULL, 0, m, sizeof(m), + WC_HASH_TYPE_SHA256, sig, NULL, addRnd) + != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + WB_NOTE("signhash_external sigSz==NULL unexpected ret"); + wb_fail = 1; + } + + /* ---- flag guards: (ret == 0) && ((flags & FLAG) == 0) ---- + * Clause 1 FALSE row: a NULL-chain failure (sig==NULL) leaves ret != 0 + * at the guard. One call per wrapper. */ + sigSz = sizeof(sig); + (void)wc_SlhDsaKey_SignWithRandom(&key, NULL, 0, m, sizeof(m), NULL, + &sigSz, addRnd); + (void)wc_SlhDsaKey_SignMsgWithRandom(&key, m, sizeof(m), NULL, &sigSz, + addRnd); + sigSz = sizeof(sig); + (void)wc_SlhDsaKey_SignHashWithRandom(&key, NULL, 0, m, sizeof(m), + WC_HASH_TYPE_SHA256, NULL, &sigSz, addRnd); + sigSz = sizeof(sig); + (void)wc_SlhDsaKey_SignHash(&key, NULL, 0, m, sizeof(m), + WC_HASH_TYPE_SHA256, NULL, &sigSz, NULL); + (void)wc_SlhDsaKey_Verify(&key, NULL, 0, m, sizeof(m), NULL, 0); + (void)wc_SlhDsaKey_VerifyMsg(&key, m, sizeof(m), NULL, 0); + (void)wc_SlhDsaKey_VerifyHash(&key, NULL, 0, m, sizeof(m), + WC_HASH_TYPE_SHA256, NULL, 0); + + /* Clause 2 TRUE row: a key with the right parameters but no key + * material passes the argument checks and dies at the flag guard with + * MISSING_KEY. One call per wrapper. */ + key.flags = 0; + sigSz = sizeof(sig); + if (wc_SlhDsaKey_SignWithRandom(&key, NULL, 0, m, sizeof(m), sig, + &sigSz, addRnd) != WC_NO_ERR_TRACE(MISSING_KEY)) { + WB_NOTE("SignWithRandom flag guard unexpected ret"); + wb_fail = 1; + } + sigSz = sizeof(sig); + if (wc_SlhDsaKey_SignMsgWithRandom(&key, m, sizeof(m), sig, &sigSz, + addRnd) != WC_NO_ERR_TRACE(MISSING_KEY)) { + WB_NOTE("SignMsgWithRandom flag guard unexpected ret"); + wb_fail = 1; + } + sigSz = sizeof(sig); + if (wc_SlhDsaKey_SignHashWithRandom(&key, NULL, 0, m, sizeof(m), + WC_HASH_TYPE_SHA256, sig, &sigSz, addRnd) + != WC_NO_ERR_TRACE(MISSING_KEY)) { + WB_NOTE("SignHashWithRandom flag guard unexpected ret"); + wb_fail = 1; + } + sigSz = sizeof(sig); + if (wc_SlhDsaKey_SignHash(&key, NULL, 0, m, sizeof(m), + WC_HASH_TYPE_SHA256, sig, &sigSz, &rng) + != WC_NO_ERR_TRACE(MISSING_KEY)) { + WB_NOTE("SignHash flag guard unexpected ret"); + wb_fail = 1; + } + if (wc_SlhDsaKey_Verify(&key, NULL, 0, m, sizeof(m), sig, sizeof(sig)) + != WC_NO_ERR_TRACE(MISSING_KEY)) { + WB_NOTE("Verify flag guard unexpected ret"); + wb_fail = 1; + } + if (wc_SlhDsaKey_VerifyMsg(&key, m, sizeof(m), sig, sizeof(sig)) + != WC_NO_ERR_TRACE(MISSING_KEY)) { + WB_NOTE("VerifyMsg flag guard unexpected ret"); + wb_fail = 1; + } + if (wc_SlhDsaKey_VerifyHash(&key, NULL, 0, m, sizeof(m), + WC_HASH_TYPE_SHA256, sig, sizeof(sig)) + != WC_NO_ERR_TRACE(MISSING_KEY)) { + WB_NOTE("VerifyHash flag guard unexpected ret"); + wb_fail = 1; + } + + /* Clause 2 FALSE rows: a real key carries the flags, the guards pass + * and the operations run to completion. One keygen on the smallest + * compiled-in set, shared by all six wrappers. */ + XMEMSET(&key, 0, sizeof(key)); + param = -1; +#ifndef WC_SLHDSA_ALL_NO_128F + param = SLHDSA_SHAKE128F; +#endif +#if defined(WOLFSSL_SLHDSA_SHA2) && !defined(WC_SLHDSA_ALL_NO_128F) + if (param < 0) + param = SLHDSA_SHA2_128F; +#endif +#ifndef WC_SLHDSA_ALL_NO_128S + if (param < 0) + param = SLHDSA_SHAKE128S; +#endif + if (param < 0) { + WB_NOTE("no 128-bit parameter set compiled in; TRUE rows skipped"); + wc_FreeRng(&rng); + return; + } + if (wc_SlhDsaKey_Init(&key, (enum SlhDsaParam)param, NULL, INVALID_DEVID) + != 0 || wc_SlhDsaKey_MakeKey(&key, &rng) != 0) { + WB_NOTE("keygen failed; flag-guard TRUE rows skipped"); + wc_FreeRng(&rng); + wc_SlhDsaKey_Free(&key); + return; + } + + sigSz = sizeof(sig); + ret = wc_SlhDsaKey_SignWithRandom(&key, ctx, (byte)sizeof(ctx), m, + sizeof(m), sig, &sigSz, addRnd); + if (ret != 0) { + WB_NOTE("SignWithRandom full sign failed"); + wb_fail = 1; + } + /* Verify immediately: the next signs overwrite sig. */ + if (wc_SlhDsaKey_Verify(&key, ctx, (byte)sizeof(ctx), m, sizeof(m), + sig, sigSz) != 0) { + WB_NOTE("Verify full verify failed"); + wb_fail = 1; + } + sigSz = sizeof(sig); + if (wc_SlhDsaKey_SignMsgWithRandom(&key, m, sizeof(m), sig, &sigSz, + addRnd) != 0) { + WB_NOTE("SignMsgWithRandom full sign failed"); + wb_fail = 1; + } + /* VerifyMsg immediately: the next signs overwrite sig. */ + if (wc_SlhDsaKey_VerifyMsg(&key, m, sizeof(m), sig, sigSz) != 0) { + WB_NOTE("VerifyMsg full verify failed"); + wb_fail = 1; + } + sigSz = sizeof(sig); + if (wc_SlhDsaKey_SignHashWithRandom(&key, ctx, (byte)sizeof(ctx), h2, + sizeof(h2), WC_HASH_TYPE_SHA256, sig, &sigSz, addRnd) != 0) { + WB_NOTE("SignHashWithRandom full sign failed"); + wb_fail = 1; + } + sigSz = sizeof(sig); + if (wc_SlhDsaKey_SignHash(&key, ctx, (byte)sizeof(ctx), h2, sizeof(h2), + WC_HASH_TYPE_SHA256, sig, &sigSz, &rng) != 0) { + WB_NOTE("SignHash full sign failed"); + wb_fail = 1; + } + if (wc_SlhDsaKey_VerifyHash(&key, ctx, (byte)sizeof(ctx), h2, sizeof(h2), + WC_HASH_TYPE_SHA256, sig, sigSz) != 0) { + WB_NOTE("VerifyHash full verify failed"); + wb_fail = 1; + } + + /* slhdsakey_validate_prehash(): clause 1 (hashSz != expectedLen) FALSE + * partner needs ret != 0 at the check. From the public API the switch + * default is unreachable in the campaign build: check_hash_for_n + * rejects every hash type the switch does not compile in, and + * validate_prehash runs only when that check passed. The white-box + * includes the TU, so call the static directly with a type the switch + * rejects (MD5 is off in every campaign variant; the enum member is + * unconditional). */ + { + const byte* oid = NULL; + byte oidLen = 0; + + if (slhdsakey_validate_prehash(sizeof(h2), WC_HASH_TYPE_MD5, &oid, + &oidLen) != WC_NO_ERR_TRACE(NOT_COMPILED_IN)) { + WB_NOTE("validate_prehash MD5 unexpected ret"); + wb_fail = 1; + } + } + + wc_SlhDsaKey_Free(&key); + wc_FreeRng(&rng); + + WB_NOTE("slhdsa guard independence pairs exercised"); +} #endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */ #endif /* WOLFSSL_HAVE_SLHDSA */ @@ -431,6 +760,7 @@ int main(void) #endif #ifndef WOLFSSL_SLHDSA_VERIFY_ONLY wb_sign_internal_msg_argchecks(); + wb_guard_rows(); #endif printf("done (%s)\n", wb_fail ? "with skips" : "ok"); /* Setup failures are surfaced as skips, not test failures: the harness diff --git a/tests/unit-mcdc/test_sp_x86_64_whitebox.c b/tests/unit-mcdc/test_sp_x86_64_whitebox.c index 0594ea1b4e0..1b300eb0a32 100644 --- a/tests/unit-mcdc/test_sp_x86_64_whitebox.c +++ b/tests/unit-mcdc/test_sp_x86_64_whitebox.c @@ -1251,6 +1251,18 @@ static void wb_run_dispatch_256(void) pp2.x[0] = 1; pp2.y[0] = 1; pp2.z[0] = 1; (void)WB_CHECK(sp_256_add_points_4(&pp1, &pp2, tmp2)); } + { + sp_point_256 zp1; + sp_point_256 zp2; + sp_digit ztmp[48]; + + XMEMSET(&zp1, 0, sizeof(zp1)); + XMEMSET(&zp2, 0, sizeof(zp2)); + XMEMSET(ztmp, 0, sizeof(ztmp)); + /* All-zero points: the add yields the point at infinity, driving + * the z==0 and x&&y==0 true sides of add_points_4's checks. */ + (void)WB_CHECK(sp_256_add_points_4(&zp1, &zp2, ztmp)); + } { sp_point_256 pt; diff --git a/tests/unit-mcdc/test_tls_whitebox.c b/tests/unit-mcdc/test_tls_whitebox.c new file mode 100644 index 00000000000..2285e293e95 --- /dev/null +++ b/tests/unit-mcdc/test_tls_whitebox.c @@ -0,0 +1,228 @@ +/* + * tls.c white-box supplement: MC/DC rows for the file-static helpers the + * public API cannot reach with the operand sides the campaign union never + * showed (master drift, 2026-10-06): the Hmac size guards, the ALPN + * constructor/find/set NULL rows, and the SNI parse length/cacheOnly/ + * find-miss rows. + * + * This white-box #includes src/tls.c directly to reach these file-static + * helpers and drives both sides of each leaf in the same binary (a single + * clang MC/DC bitmap does not merge independence pairs across separately + * compiled binaries, so each row below is paired with a same-binary + * baseline call). + * + * Crash-safety: Hmac_OuterHash() only touches the hash object after the + * size guard passes; Hmac_UpdateFinal_CT()'s oversized-macLen row returns + * at its first statement before any buffer use; the ALPN NULL rows return + * before any allocation; the SNI rows feed well-formed buffers to a real + * object. + */ + +#include + +#include + +static int wb_fail = 0; +#define WB_NOTE(msg) do {printf(" [wb] %s\n", (msg));} while (0) + +/* Hmac_OuterHash: (digestSz >= 0) && (blockSz >= 0). A macType with no + * hash-table entry makes both sizes negative: the false sides; the + * SHA-256 row is the same-binary true baseline. */ +static void wb_hmac_outer_hash(void) +{ + Hmac hmac; + unsigned char mac[64]; + + XMEMSET(&hmac, 0, sizeof(hmac)); + XMEMSET(mac, 0, sizeof(mac)); + hmac.macType = WC_SHA256; + (void)Hmac_OuterHash(&hmac, mac); + hmac.macType = (enum wc_HashType)100; + (void)Hmac_OuterHash(&hmac, mac); +} + +/* Hmac_UpdateFinal_CT: the macLen > sizeof(innerHash) clause. The + * oversized row returns at the guard before any buffer use. */ +static void wb_hmac_updatefinal_ct(void) +{ + Hmac hmac; + byte in[16]; + byte digest[WC_MAX_DIGEST_SIZE]; + byte header[4]; + + XMEMSET(&hmac, 0, sizeof(hmac)); + XMEMSET(in, 0, sizeof(in)); + hmac.macType = WC_SHA256; + (void)Hmac_UpdateFinal_CT(&hmac, digest, in, sizeof(in), 32, + header, sizeof(header)); + (void)Hmac_UpdateFinal_CT(&hmac, digest, in, sizeof(in), + (int)sizeof(hmac.innerHash) + 1, + header, sizeof(header)); +} + +#if defined(HAVE_ALPN) + +/* TLSX_ALPN_New: the protocol_name == NULL clause's true side. */ +static void wb_alpn_new_null(void) +{ + if (TLSX_ALPN_New(NULL, 0, NULL) != NULL) + wb_fail++; +} + +/* TLSX_ALPN_Find: (list == NULL) || (protocol_name == NULL) -- the NULL +* list row, the NULL name row on a live node, and the valid baseline. */ +static void wb_alpn_find_nulls(void) +{ + ALPN alpn; + char name[] = "http/1.1"; + + XMEMSET(&alpn, 0, sizeof(alpn)); + alpn.protocol_name = name; + alpn.protocol_nameSz = (word16)sizeof(name) - 1; + (void)TLSX_ALPN_Find(NULL, name, alpn.protocol_nameSz); + (void)TLSX_ALPN_Find(&alpn, NULL, 0); + (void)TLSX_ALPN_Find(&alpn, name, alpn.protocol_nameSz); +} + +/* TLSX_SetALPN: (extensions == NULL) || (data == NULL) -- both NULL rows + * and the valid baseline (pushed onto a local list, freed below). */ +static void wb_set_alpn_nulls(void) +{ + TLSX extensions; + const byte data[] = "http/1.1"; + + XMEMSET(&extensions, 0, sizeof(extensions)); + (void)TLSX_SetALPN(NULL, data, (word16)sizeof(data) - 1, NULL); + (void)TLSX_SetALPN(&extensions, NULL, 0, NULL); + (void)TLSX_SetALPN(&extensions, data, (word16)sizeof(data) - 1, NULL); + TLSX_FreeAll(&extensions, NULL); +} + +/* TLSX_UseALPN: the extensions == NULL clause's true side. */ +static void wb_use_alpn_null(void) +{ + const byte data[] = "http/1.1"; + /* options is unused: the extensions-NULL guard fires first. */ + (void)TLSX_UseALPN(NULL, data, (word16)sizeof(data) - 1, 0, NULL); +} + +/* ALPN_find_match: the extension->data == NULL clause's true side -- an + * ALPN extension with a NULL payload on a real object. */ +static void wb_alpn_find_match_null_data(void) +{ + WOLFSSL_CTX *ctx = NULL; + WOLFSSL *ssl = NULL; + TLSX *pext = NULL; + const byte *sel = NULL; + byte selLen = 0; + const byte alpnVal[] = {5, 'h', 't', 't', 'p'}; + + ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); + if (ctx == NULL) + return; + ssl = wolfSSL_new(ctx); + if (ssl == NULL) { + wolfSSL_CTX_free(ctx); + return; + } + (void)TLSX_Push(&ssl->extensions, TLSX_APPLICATION_LAYER_PROTOCOL, + NULL, ssl->heap); + (void)ALPN_find_match(ssl, &pext, &sel, &selLen, alpnVal, + (word16)sizeof(alpnVal)); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +} + +#else + +static void wb_alpn_new_null(void) +{ + WB_NOTE("HAVE_ALPN off; ALPN_New rows skipped"); +} +static void wb_alpn_find_nulls(void) +{ + WB_NOTE("HAVE_ALPN off; ALPN_Find rows skipped"); +} +static void wb_set_alpn_nulls(void) +{ + WB_NOTE("HAVE_ALPN off; SetALPN rows skipped"); +} +static void wb_use_alpn_null(void) +{ + WB_NOTE("HAVE_ALPN off; UseALPN rows skipped"); +} +static void wb_alpn_find_match_null_data(void) +{ + WB_NOTE("HAVE_ALPN off; ALPN_find_match rows skipped"); +} + +#endif /* HAVE_ALPN */ + +#if defined(HAVE_SNI) && !defined(NO_WOLFSSL_SERVER) + +/* TLSX_SNI_Parse: the size == 0 row (length matches OPAQUE16_LEN + size, + * the second clause fires), the SNI_Find miss/hit rows, and the cacheOnly + * row (a configured sniRecvCb forces it; the callback itself is invoked + * by the caller of Parse, not inside it, so a sentinel pointer is safe). */ +static void wb_sni_parse_rows(void) +{ + WOLFSSL_CTX *ctx = NULL; + WOLFSSL *ssl = NULL; + byte in[8]; + + ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method()); + if (ctx == NULL) + return; + ssl = wolfSSL_new(ctx); + if (ssl == NULL) { + wolfSSL_CTX_free(ctx); + return; + } + + c16toa(0, in); + (void)TLSX_SNI_Parse(ssl, in, OPAQUE16_LEN, 1); + + /* Well-formed host-name SNI, nothing configured: the find-miss row. */ + in[0] = 0; in[1] = 4; + in[2] = WOLFSSL_SNI_HOST_NAME; + in[3] = 0; in[4] = 1; + in[5] = 'a'; + (void)TLSX_SNI_Parse(ssl, in, 6, 1); + + /* Configured: the find-hit row. */ + (void)TLSX_UseSNI(&ssl->extensions, WOLFSSL_SNI_HOST_NAME, "a", 1, + ssl->heap); + (void)TLSX_SNI_Parse(ssl, in, 6, 1); + + /* cacheOnly row. */ + ctx->sniRecvCb = (CallbackSniRecv)1; + (void)TLSX_SNI_Parse(ssl, in, 6, 1); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +} + +#else + +static void wb_sni_parse_rows(void) +{ + WB_NOTE("HAVE_SNI/NO_WOLFSSL_SERVER off; SNI parse rows skipped"); +} + +#endif /* HAVE_SNI && !NO_WOLFSSL_SERVER */ + +int main(void) +{ + setvbuf(stdout, NULL, _IONBF, 0); + printf("tls.c white-box supplement\n"); + wb_hmac_outer_hash(); + wb_hmac_updatefinal_ct(); + wb_alpn_new_null(); + wb_alpn_find_nulls(); + wb_set_alpn_nulls(); + wb_use_alpn_null(); + wb_alpn_find_match_null_data(); + wb_sni_parse_rows(); + printf("done (%s)\n", wb_fail ? "with failures" : "ok"); + return 0; +} diff --git a/tests/unit-mcdc/test_wc_mldsa_whitebox.c b/tests/unit-mcdc/test_wc_mldsa_whitebox.c index bbf6b11200e..797306d5400 100644 --- a/tests/unit-mcdc/test_wc_mldsa_whitebox.c +++ b/tests/unit-mcdc/test_wc_mldsa_whitebox.c @@ -1558,6 +1558,149 @@ static void wb_check_key_range(void) } #endif +/* ------------------------------------------------------------------ * + * SignCtxWithSeed's msg guard (11670/11677): the (msg == NULL) && + * (msgLen != 0) compound clause and the empty-message canonicalization + * (msg == NULL, msgLen == 0 -> readable stand-in). A valid key keeps + * the rows on the guard itself. + * ------------------------------------------------------------------ */ +#if !defined(WOLFSSL_MLDSA_NO_SIGN) && !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) +static void wb_sign_ctx_msg_guard_rows(void) +{ + static const byte seed[32] = { + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, + 0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f, + 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17, + 0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f + }; + wc_MlDsaKey key; + static byte sig[MLDSA_MAX_SIG_SIZE]; + word32 sigLen = (word32)sizeof(sig); + int ret; + + if (wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID) != 0) { + return; + } + if (wc_MlDsaKey_SetParams(&key, WC_ML_DSA_44) != 0 || + wc_MlDsaKey_MakeKeyFromSeed(&key, seed) != 0) { + wc_MlDsaKey_Free(&key); + return; + } + + /* (msg == NULL) && (msgLen != 0): both clauses true -> guard fires. */ + ret = wc_MlDsaKey_SignCtxWithSeed(&key, NULL, 0, sig, &sigLen, NULL, + 8, seed); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + WB_NOTE("SignCtxWithSeed (NULL msg, len!=0) expected BAD_FUNC_ARG"); + } + + /* (msg == NULL) && (msgLen == 0): clause 2 false -> canonicalize to + * the empty stand-in and sign for real. */ + sigLen = (word32)sizeof(sig); + ret = wc_MlDsaKey_SignCtxWithSeed(&key, NULL, 0, sig, &sigLen, NULL, + 0, seed); + if (ret != 0) { + WB_NOTE("SignCtxWithSeed (NULL msg, len==0) expected 0"); + } + + wc_MlDsaKey_Free(&key); + printf(" [wb] SignCtxWithSeed msg-guard rows exercised\n"); +} +#else +static void wb_sign_ctx_msg_guard_rows(void) +{ + printf(" [wb] SignCtxWithSeed msg-guard rows skipped (not compiled)\n"); +} +#endif + + + +/* ------------------------------------------------------------------ * + * wc_MlDsaKey_PrivateKeyDecode's keyType/autoKeyType dispatch and the + * pubKey/privKey length cascade (13592, 13672). Inputs are the + * library's own encodings of a freshly generated key: the active + * template parser walks the key data, so hand-crafted junk bytes + * trip it. Traditional form = the bare inner OCTET STRING of the + * PKCS8 DER (no OID -> autoKeyType ANONk); priv-only = the PKCS8 DER + * itself (pubKeyLen 0, privKeyLen != 0). + * Residuals: 13667 c1 is structural (the arm only runs inside the + * pubKeyLen == 0 branch, where its first clause is always false); + * 13672 c0-F likewise (the cascade only runs when pubKeyLen == 0); + * 13672 c1-F needs a successful seed-only decode, and the template + * parse rejects the hand-crafted seed-only DER (ASN_PARSE_E). + * ------------------------------------------------------------------ */ +#if !defined(WOLFSSL_MLDSA_NO_ASN1) && defined(WOLFSSL_MLDSA_PRIVATE_KEY) \ + && defined(WOLFSSL_MLDSA_PUBLIC_KEY) && !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) + +static void wb_decode_dispatch_rows(void) +{ + static const byte seed[32] = { + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, + 0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f, + 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17, + 0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f + }; + byte der[MLDSA_MAX_BOTH_KEY_DER_SIZE]; + wc_MlDsaKey key; + word32 derLen = (word32)sizeof(der); + word32 idx = 0; + int n; + int ret; + + if (wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID) != 0) { + return; + } + if (wc_MlDsaKey_SetParams(&key, WC_ML_DSA_44) != 0 || + wc_MlDsaKey_MakeKeyFromSeed(&key, seed) != 0) { + wc_MlDsaKey_Free(&key); + return; + } + n = wc_MlDsaKey_PrivateKeyToDer(&key, der, derLen); + wc_MlDsaKey_Free(&key); + if (n <= 0) { + WB_NOTE("PrivateKeyToDer failed"); + return; + } + + /* (a) Traditional form: the bare inner OCTET STRING of the PKCS8 + * DER (bytes 24..2588: 04 82 0a 00 + 2560 key bytes). No OID -> + * autoKeyType ANONk with an explicit level -> the + * (keyType != ANONk) && (autoKeyType == ANONk) arm. */ + if (wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID) != 0) { + return; + } + if (wc_MlDsaKey_SetParams(&key, WC_ML_DSA_44) != 0) { + wc_MlDsaKey_Free(&key); + return; + } + idx = 0; + (void)wc_MlDsaKey_PrivateKeyDecode(&key, der + 24, 2564, &idx); + wc_MlDsaKey_Free(&key); + + /* (b) Private key only: (pubKeyLen == 0) && (privKeyLen != 0) -> + * the ImportPrivRaw arm. */ + if (wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID) != 0) { + return; + } + if (wc_MlDsaKey_SetParams(&key, WC_ML_DSA_44) != 0) { + wc_MlDsaKey_Free(&key); + return; + } + idx = 0; + ret = wc_MlDsaKey_PrivateKeyDecode(&key, der, (word32)n, &idx); + if (ret != 0) { + WB_NOTE("decode priv-only expected 0"); + } + wc_MlDsaKey_Free(&key); + + printf(" [wb] PrivateKeyDecode dispatch rows exercised\n"); +} +#else +static void wb_decode_dispatch_rows(void) +{ + printf(" [wb] PrivateKeyDecode dispatch rows skipped (not compiled)\n"); +} +#endif int main(void) { /* Unbuffered: on a timeout the process is killed and anything still @@ -1606,6 +1749,8 @@ int main(void) wb_arg_guards(); wb_verify_invalid(); wb_check_key_range(); + wb_sign_ctx_msg_guard_rows(); + wb_decode_dispatch_rows(); printf("done (%d note%s)\n", wb_notes, (wb_notes == 1) ? "" : "s"); return 0; #endif