diff --git a/doc/dox_comments/header_files/ecc.h b/doc/dox_comments/header_files/ecc.h index 74c700321d..f46299c344 100644 --- a/doc/dox_comments/header_files/ecc.h +++ b/doc/dox_comments/header_files/ecc.h @@ -2151,9 +2151,9 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, the encryption type specified by ctx. \return 0 Returned upon successfully decrypting the input message - \return BAD_FUNC_ARG Returned if privKey, pubKey, msg, msgSz, out, - or outSz are NULL, or the ctx object specifies an unsupported - encryption type + \return BAD_FUNC_ARG Returned if privKey, msg, msgSz, out, or outSz + are NULL (or pubKey is NULL when built with WOLFSSL_ECIES_OLD), or + the ctx object specifies an unsupported encryption type \return BAD_ENC_STATE_E Returned if the ctx object given is in a state that is not appropriate for decryption \return BUFFER_E Returned if the supplied output buffer is too @@ -2166,8 +2166,11 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, \param privKey pointer to the ecc_key object containing the private key to use for decryption - \param pubKey pointer to the ecc_key object containing the public - key of the peer with whom one wishes to communicate + \param pubKey only used when built with WOLFSSL_ECIES_OLD: pointer to + the ecc_key object containing the public key of the peer with whom one + wishes to communicate. In the default message format the sender's + ephemeral public key is read from the start of msg instead and pubKey + is ignored (it may be NULL and is left unmodified) \param msg pointer to the buffer holding the ciphertext to decrypt \param msgSz size of the buffer to decrypt \param out pointer to the buffer in which to store the decrypted plaintext diff --git a/tests/api/test_ecc.c b/tests/api/test_ecc.c index 95ed354da4..07f6c1987f 100644 --- a/tests/api/test_ecc.c +++ b/tests/api/test_ecc.c @@ -1756,6 +1756,79 @@ int test_wc_ecc_encryptDecrypt(void) return EXPECT_RESULT(); } /* END test_wc_ecc_encryptDecrypt */ +/* + * In the default ECIES message format the sender's ephemeral public key is + * carried in the message, so wc_ecc_decrypt() must not free or overwrite a + * caller-supplied pubKey object. Confirm the object is byte-for-byte preserved + * across a decrypt. + */ +int test_wc_ecc_decrypt_pubkey_preserved(void) +{ + EXPECT_DECLS; +#if defined(HAVE_ECC) && defined(HAVE_ECC_ENCRYPT) && !defined(WC_NO_RNG) && \ + !defined(WOLFSSL_ECIES_OLD) && defined(HAVE_ECC_KEY_EXPORT) && \ + defined(HAVE_ECC_KEY_IMPORT) && \ + (defined(HAVE_AES_CBC) || \ + (defined(HAVE_AESGCM) && (defined(WOLFSSL_ECIES_GEN_IV) || \ + defined(WOLFSSL_ECIES_STATIC_GCM_NONCE)))) && defined(WOLFSSL_AES_128) + ecc_key cliKey; + ecc_key srvKey; + ecc_key pubKey; + WC_RNG rng; + const char* msg = "EccBlock Size 16"; + word32 msgSz = (word32)XSTRLEN("EccBlock Size 16"); + byte out[KEY20 * 2 + 1 + (sizeof("EccBlock Size 16") - 1) + + WC_SHA256_DIGEST_SIZE]; + word32 outSz = (word32)sizeof(out); + byte plain[sizeof("EccBlock Size 16")]; + word32 plainSz = (word32)sizeof(plain); + byte before[ECC_BUFSIZE]; + byte after[ECC_BUFSIZE]; + word32 beforeSz = (word32)sizeof(before); + word32 afterSz = (word32)sizeof(after); + + XMEMSET(&rng, 0, sizeof(rng)); + XMEMSET(&cliKey, 0, sizeof(cliKey)); + XMEMSET(&srvKey, 0, sizeof(srvKey)); + XMEMSET(&pubKey, 0, sizeof(pubKey)); + + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectIntEQ(wc_ecc_init(&cliKey), 0); + ExpectIntEQ(wc_ecc_make_key(&rng, KEY20, &cliKey), 0); + ExpectIntEQ(wc_ecc_init(&srvKey), 0); + ExpectIntEQ(wc_ecc_make_key(&rng, KEY20, &srvKey), 0); + ExpectIntEQ(wc_ecc_init(&pubKey), 0); + /* Load a public key distinct from the sender's ephemeral (embedded in the + * message) so that overwriting pubKey would be detectable. */ + ExpectIntEQ(wc_ecc_export_x963(&srvKey, before, &beforeSz), 0); + ExpectIntEQ(wc_ecc_import_x963(before, beforeSz, &pubKey), 0); + +#if defined(ECC_TIMING_RESISTANT) && (!defined(HAVE_FIPS) || \ + (!defined(HAVE_FIPS_VERSION) || (HAVE_FIPS_VERSION != 2))) && \ + !defined(HAVE_SELFTEST) + ExpectIntEQ(wc_ecc_set_rng(&srvKey, &rng), 0); + ExpectIntEQ(wc_ecc_set_rng(&cliKey, &rng), 0); +#endif + + ExpectIntEQ(wc_ecc_encrypt(&cliKey, &srvKey, (byte*)msg, msgSz, out, + &outSz, NULL), 0); + ExpectIntEQ(wc_ecc_decrypt(&srvKey, &pubKey, out, outSz, plain, &plainSz, + NULL), 0); + ExpectIntEQ(XMEMCMP(msg, plain, msgSz), 0); + + /* the caller's pubKey object must be unchanged after the decrypt */ + ExpectIntEQ(wc_ecc_export_x963(&pubKey, after, &afterSz), 0); + ExpectIntEQ(afterSz, beforeSz); + ExpectIntEQ(XMEMCMP(before, after, beforeSz), 0); + + wc_ecc_free(&pubKey); + wc_ecc_free(&srvKey); + wc_ecc_free(&cliKey); + DoExpectIntEQ(wc_FreeRng(&rng), 0); +#endif + return EXPECT_RESULT(); +} /* END test_wc_ecc_decrypt_pubkey_preserved */ + /* * Testing ECIES with the AES-256-GCM DEM. Exercises, each with its own * single-use client/server ctx pair: @@ -2477,6 +2550,25 @@ int test_wc_ecc_get_curve_id_from_oid(void) ExpectIntEQ(wc_ecc_get_curve_id_from_oid(oid, 0), ECC_CURVE_INVALID); /* Good Case */ ExpectIntEQ(wc_ecc_get_curve_id_from_oid(oid, len), ECC_SECP256R1); + +#ifdef HAVE_OID_DECODING + { + /* An OID with more sub-identifiers than the internal decode array can + * hold must be rejected, not decoded past the end of that array. The + * first byte decodes to two arcs and every following byte to one, so + * MAX_OID_SZ bytes yield well over the MAX_OID_SZ/2 element capacity. + * Run under ASan to catch a regression. */ + byte longOid[MAX_OID_SZ]; + word32 i; + + longOid[0] = 0x2A; + for (i = 1; i < (word32)sizeof(longOid); i++) + longOid[i] = 0x01; + + ExpectIntEQ(wc_ecc_get_curve_id_from_oid(longOid, sizeof(longOid)), + WC_NO_ERR_TRACE(BUFFER_E)); + } +#endif #endif return EXPECT_RESULT(); } /* END test_wc_ecc_get_curve_id_from_oid */ diff --git a/tests/api/test_ecc.h b/tests/api/test_ecc.h index 31d475f160..cb3889ea59 100644 --- a/tests/api/test_ecc.h +++ b/tests/api/test_ecc.h @@ -55,6 +55,7 @@ int test_wc_ecc_ctx_set_peer_salt(void); int test_wc_ecc_ctx_set_info(void); int test_wc_ecc_ctx_getters(void); int test_wc_ecc_encryptDecrypt(void); +int test_wc_ecc_decrypt_pubkey_preserved(void); int test_wc_ecc_ecies_gcm(void); int test_wc_ecc_ecies_gcm_no_rng(void); int test_wc_ecc_ecies_cryptocb(void); @@ -104,6 +105,7 @@ int test_wc_EccDecisionCoverage4(void); TEST_DECL_GROUP("ecc", test_wc_ecc_ctx_set_info), \ TEST_DECL_GROUP("ecc", test_wc_ecc_ctx_getters), \ TEST_DECL_GROUP("ecc", test_wc_ecc_encryptDecrypt), \ + TEST_DECL_GROUP("ecc", test_wc_ecc_decrypt_pubkey_preserved), \ TEST_DECL_GROUP("ecc", test_wc_ecc_ecies_gcm), \ TEST_DECL_GROUP("ecc", test_wc_ecc_ecies_gcm_no_rng), \ TEST_DECL_GROUP("ecc", test_wc_ecc_ecies_cryptocb), \ diff --git a/tests/api/test_pkcs7.c b/tests/api/test_pkcs7.c index e0b1fd85c8..f40dfa4592 100644 --- a/tests/api/test_pkcs7.c +++ b/tests/api/test_pkcs7.c @@ -4498,6 +4498,93 @@ int test_wc_PKCS7_EncodeDecodeEnvelopedData(void) } #endif /* !NO_AES && HAVE_AES_CBC && WOLFSSL_AES_256 && HAVE_AES_KEYWRAP */ +#if !defined(NO_RSA) && !defined(NO_AES) && defined(HAVE_AES_CBC) && \ + defined(WOLFSSL_AES_256) && defined(ASN_BER_TO_DER) && \ + !defined(NO_PKCS7_STREAM) + /* A BER EnvelopedData whose encryptedContent is a multi-segment + * indefinite-length OCTET STRING must never report more plaintext than it + * placed in the caller's buffer. Encode >1 segment (content > the 4096-byte + * streaming chunk), then decode with a full and an undersized output buffer. + * Run under ASan. */ + { + /* 6000 spans two 4096-byte streaming segments (4096 + 1904) without + * being an exact multiple of the chunk size. */ + const word32 bigSz = 6000; + const word32 halfSz = 4096; /* one segment: smaller than the total */ + byte* bigContent = NULL; + byte* berOut = NULL; + byte* plainFull = NULL; + byte* plainSmall = NULL; + int berSz = 0; + int dSz; + word32 j; + + bigContent = (byte*)XMALLOC(bigSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + berOut = (byte*)XMALLOC(bigSz + FOURK_BUF, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + plainFull = (byte*)XMALLOC(bigSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + /* exact-size small buffer so any over-write faults under ASan */ + plainSmall = (byte*)XMALLOC(halfSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(bigContent); + ExpectNotNull(berOut); + ExpectNotNull(plainFull); + ExpectNotNull(plainSmall); + if (bigContent != NULL) { + for (j = 0; j < bigSz; j++) + bigContent[j] = (byte)j; + } + + /* encode as BER (streaming) so encryptedContent is fragmented */ + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, rsaCert, rsaCertSz), 0); + if (pkcs7 != NULL) { + pkcs7->content = bigContent; + pkcs7->contentSz = bigSz; + pkcs7->contentOID = DATA; + pkcs7->encryptOID = AES256CBCb; + pkcs7->privateKey = rsaPrivKey; + pkcs7->privateKeySz = rsaPrivKeySz; + } + ExpectIntEQ(wc_PKCS7_SetStreamMode(pkcs7, 1, NULL, NULL, NULL), 0); + ExpectIntGT((berSz = wc_PKCS7_EncodeEnvelopedData(pkcs7, berOut, + bigSz + FOURK_BUF)), 0); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + /* full-size output buffer: all segments returned and content matches */ + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, rsaCert, rsaCertSz), 0); + if (pkcs7 != NULL) { + pkcs7->privateKey = rsaPrivKey; + pkcs7->privateKeySz = rsaPrivKeySz; + } + dSz = wc_PKCS7_DecodeEnvelopedData(pkcs7, berOut, (word32)berSz, + plainFull, bigSz); + ExpectIntEQ(dSz, (int)bigSz); + ExpectIntEQ(XMEMCMP(plainFull, bigContent, bigSz), 0); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + /* undersized output buffer: must fail, not report a length past it */ + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, rsaCert, rsaCertSz), 0); + if (pkcs7 != NULL) { + pkcs7->privateKey = rsaPrivKey; + pkcs7->privateKeySz = rsaPrivKeySz; + } + dSz = wc_PKCS7_DecodeEnvelopedData(pkcs7, berOut, (word32)berSz, + plainSmall, halfSz); + ExpectIntLT(dSz, 0); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + XFREE(bigContent, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(berOut, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(plainFull, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(plainSmall, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + } +#endif /* multi-segment BER bounds regression */ + #ifndef NO_RSA XFREE(rsaCert, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); XFREE(rsaPrivKey, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); @@ -4550,6 +4637,63 @@ int test_wc_PKCS7_EncodeDecodeEnvelopedData(void) return EXPECT_RESULT(); } /* END test_wc_PKCS7_EncodeDecodeEnvelopedData() */ +/* + * The BER streaming encoder encrypts the content one 4096-byte octet chunk at a + * time into a working buffer, and the final chunk additionally carries the + * block cipher pad. Content that is an exact multiple of the chunk length makes + * that last chunk the largest one the buffer has to hold. Encode such sizes and + * confirm they succeed; run under ASan. + */ +int test_wc_PKCS7_stream_encode_chunk_boundary(void) +{ + EXPECT_DECLS; +#if defined(HAVE_PKCS7) && !defined(NO_AES) && defined(HAVE_AES_CBC) && \ + defined(WOLFSSL_AES_256) && defined(HAVE_AES_KEYWRAP) && \ + defined(ASN_BER_TO_DER) && !defined(NO_PKCS7_STREAM) + /* multiples of the encoder's private BER_OCTET_LENGTH (4096) */ + static const word32 contentSizes[] = { 4096, 8192 }; + static const byte keyId[] = { 0x00 }; + word32 i; + + for (i = 0; i < (word32)XELEM_CNT(contentSizes); i++) { + PKCS7* pkcs7 = NULL; + byte* content = NULL; + byte* ber = NULL; + word32 contentSz = contentSizes[i]; + word32 berSz = contentSz + FOURK_BUF; + word32 j; + + ExpectNotNull(content = (byte*)XMALLOC(contentSz, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER)); + ExpectNotNull(ber = (byte*)XMALLOC(berSz, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER)); + if (content != NULL) { + for (j = 0; j < contentSz; j++) + content[j] = (byte)j; + } + + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + if (pkcs7 != NULL) { + pkcs7->content = content; + pkcs7->contentSz = contentSz; + pkcs7->contentOID = DATA; + pkcs7->encryptOID = AES256CBCb; + } + ExpectIntGT(wc_PKCS7_AddRecipient_KEKRI(pkcs7, AES256_WRAP, + (byte*)defKey, sizeof(defKey), (byte*)keyId, sizeof(keyId), + NULL, NULL, 0, NULL, 0, 0), 0); + ExpectIntEQ(wc_PKCS7_SetSignerIdentifierType(pkcs7, CMS_SKID), 0); + ExpectIntEQ(wc_PKCS7_SetStreamMode(pkcs7, 1, NULL, NULL, NULL), 0); + ExpectIntGT(wc_PKCS7_EncodeEnvelopedData(pkcs7, ber, berSz), 0); + + wc_PKCS7_Free(pkcs7); + XFREE(content, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(ber, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + } +#endif + return EXPECT_RESULT(); +} /* END test_wc_PKCS7_stream_encode_chunk_boundary() */ + #if defined(HAVE_PKCS7) && defined(HAVE_ECC) && defined(HAVE_X963_KDF) && \ !defined(NO_SHA256) && defined(WOLFSSL_AES_256) diff --git a/tests/api/test_pkcs7.h b/tests/api/test_pkcs7.h index bf66118e08..73fe981ca5 100644 --- a/tests/api/test_pkcs7.h +++ b/tests/api/test_pkcs7.h @@ -58,6 +58,7 @@ int test_wc_PKCS7_VerifySignedData_ECC(void); int test_wc_PKCS7_VerifySignedData_ECC_TamperedAttribs(void); int test_wc_PKCS7_DecodeEnvelopedData_stream(void); int test_wc_PKCS7_EncodeDecodeEnvelopedData(void); +int test_wc_PKCS7_stream_encode_chunk_boundary(void); int test_wc_PKCS7_SetAESKeyWrapUnwrapCb(void); int test_wc_PKCS7_GetEnvelopedDataKariRid(void); int test_wc_PKCS7_EncodeEncryptedData(void); @@ -155,6 +156,8 @@ int test_wc_PKCS7_VerifySignedData_NoDigestParams(void); #define TEST_PKCS7_ENCRYPTED_DATA_DECLS \ TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_DecodeEnvelopedData_stream), \ TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_EncodeDecodeEnvelopedData), \ + TEST_DECL_GROUP("pkcs7_ed", \ + test_wc_PKCS7_stream_encode_chunk_boundary), \ TEST_PKCS7_RSA_PSS_ED_DECL \ TEST_PKCS7_KTRI_BADRSAPAD_DECL \ TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_SetAESKeyWrapUnwrapCb), \ diff --git a/wolfcrypt/src/aes.c b/wolfcrypt/src/aes.c index e96e4b1842..e51aeff2e1 100644 --- a/wolfcrypt/src/aes.c +++ b/wolfcrypt/src/aes.c @@ -17069,7 +17069,8 @@ static WARN_UNUSED_RESULT int wc_AesFeedbackCFB1( * out buffer to hold result of encryption (must be at least as large as input * buffer) * in buffer to encrypt (packed to left, i.e. 101 is 0x90) - * sz size of input buffer in bits (0x1 would be size of 1 and 0xFF size of 8) + * sz number of bits to process, e.g. 1 processes one bit and 8 one byte; + * in and out must hold at least (sz + 7) / 8 bytes * * returns 0 on success and negative values on failure */ @@ -17100,8 +17101,9 @@ int wc_AesCfb8Encrypt(Aes* aes, byte* out, const byte* in, word32 sz) * aes structure holding key to use for encryption * out buffer to hold result of encryption (must be at least as large as input * buffer) - * in buffer to encrypt - * sz size of input buffer in bits (0x1 would be size of 1 and 0xFF size of 8) + * in buffer to decrypt (packed to left, i.e. 101 is 0x90) + * sz number of bits to process, e.g. 1 processes one bit and 8 one byte; + * in and out must hold at least (sz + 7) / 8 bytes * * returns 0 on success and negative values on failure */ diff --git a/wolfcrypt/src/ecc.c b/wolfcrypt/src/ecc.c index 3a56e8159e..81c8c54afc 100644 --- a/wolfcrypt/src/ecc.c +++ b/wolfcrypt/src/ecc.c @@ -4642,7 +4642,8 @@ int wc_ecc_get_curve_id_from_oid(const byte* oid, word32 len) return BAD_FUNC_ARG; #ifdef HAVE_OID_DECODING - decOidSz = (word32)sizeof(decOid); + /* in elements, not bytes */ + decOidSz = (word32)(sizeof(decOid) / sizeof(decOid[0])); ret = DecodeObjectId(oid, len, decOid, &decOidSz); if (ret != 0) { return ret; @@ -16092,17 +16093,12 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, #endif #ifndef WOLFSSL_ECIES_OLD - if (pubKey == NULL) { - WC_ALLOC_VAR_EX(peerKey, ecc_key, 1, ctx->heap, - DYNAMIC_TYPE_ECC_BUFFER, ret=MEMORY_E); - pubKey = peerKey; - } - else { - /* if a public key was passed in we should free it here before init - * and import */ - wc_ecc_free(pubKey); - } + /* The ephemeral public key comes from the message; parse it into the + * local key object so a caller-supplied pubKey is left untouched. */ + WC_ALLOC_VAR_EX(peerKey, ecc_key, 1, ctx->heap, + DYNAMIC_TYPE_ECC_BUFFER, ret=MEMORY_E); if (ret == 0) { + pubKey = peerKey; ret = wc_ecc_init_ex(pubKey, privKey->heap, INVALID_DEVID); } if (ret == 0) { diff --git a/wolfcrypt/src/evp.c b/wolfcrypt/src/evp.c index a85d7603d7..9f6a8f8cbf 100644 --- a/wolfcrypt/src/evp.c +++ b/wolfcrypt/src/evp.c @@ -594,7 +594,7 @@ static int fillBuff(WOLFSSL_EVP_CIPHER_CTX *ctx, const unsigned char *in, int sz if (sz > 0) { int fill; - if ((sz+ctx->bufUsed) > ctx->block_size) { + if (sz > ctx->block_size - ctx->bufUsed) { fill = ctx->block_size - ctx->bufUsed; } else { fill = sz; @@ -4895,22 +4895,6 @@ static int wolfSSL_evp_digest_pk_init(WOLFSSL_EVP_MD_CTX *ctx, return WOLFSSL_SUCCESS; } -/* Update an EVP_DigestSign/Verify operation. - * Update a digest for RSA and ECC keys, or HMAC for HMAC key. - */ -static int wolfssl_evp_digest_pk_update(WOLFSSL_EVP_MD_CTX *ctx, - const void *d, unsigned int cnt) -{ - if (ctx->isHMAC) { - if (wc_HmacUpdate(&ctx->hash.hmac, (const byte *)d, cnt) != 0) - return WOLFSSL_FAILURE; - - return WOLFSSL_SUCCESS; - } - else - return wolfSSL_EVP_DigestUpdate(ctx, d, cnt); -} - /* Finalize an EVP_DigestSign/Verify operation - common part only. * Finalize a digest for RSA and ECC keys, or HMAC for HMAC key. * Copies the digest so that you can keep updating. @@ -5053,14 +5037,14 @@ int wolfSSL_EVP_DigestSignInit(WOLFSSL_EVP_MD_CTX *ctx, int wolfSSL_EVP_DigestSignUpdate(WOLFSSL_EVP_MD_CTX *ctx, const void *d, - unsigned int cnt) + size_t cnt) { WOLFSSL_ENTER("EVP_DigestSignUpdate"); if (ctx == NULL || d == NULL) return WOLFSSL_FAILURE; - return wolfssl_evp_digest_pk_update(ctx, d, cnt); + return wolfSSL_EVP_DigestUpdate(ctx, d, cnt); } int wolfSSL_EVP_DigestSignFinal(WOLFSSL_EVP_MD_CTX *ctx, unsigned char *sig, @@ -5179,7 +5163,7 @@ int wolfSSL_EVP_DigestSign(WOLFSSL_EVP_MD_CTX *ctx, unsigned char *sigret, if (sigret != NULL) { if (tbs == NULL) return WOLFSSL_FAILURE; - if (wolfSSL_EVP_DigestSignUpdate(ctx, tbs, (unsigned int)tbslen) + if (wolfSSL_EVP_DigestSignUpdate(ctx, tbs, tbslen) != WOLFSSL_SUCCESS) return WOLFSSL_FAILURE; } @@ -5209,7 +5193,7 @@ int wolfSSL_EVP_DigestVerifyUpdate(WOLFSSL_EVP_MD_CTX *ctx, const void *d, if (ctx == NULL || d == NULL) return WOLFSSL_FAILURE; - return wolfssl_evp_digest_pk_update(ctx, d, (unsigned int)cnt); + return wolfSSL_EVP_DigestUpdate(ctx, d, cnt); } @@ -6171,14 +6155,15 @@ void wolfSSL_EVP_init(void) WOLFSSL_ENTER("EVP_CIPHER_MD_CTX_copy_ex"); wolfSSL_EVP_MD_CTX_cleanup(out); XMEMCPY(out, in, sizeof(WOLFSSL_EVP_MD_CTX)); + /* Zero hash context after shallow copy to prevent shared sub-pointers + * with src, even if the pctx allocation below fails. The hash Copy + * function will perform the proper deep copy. */ + XMEMSET(&out->hash, 0, sizeof(out->hash)); if (in->pctx != NULL) { out->pctx = wolfSSL_EVP_PKEY_CTX_new(in->pctx->pkey, NULL); if (out->pctx == NULL) return WOLFSSL_FAILURE; } - /* Zero hash context after shallow copy to prevent shared sub-pointers - * with src. The hash Copy function will perform the proper deep copy. */ - XMEMSET(&out->hash, 0, sizeof(out->hash)); return wolfSSL_EVP_MD_Copy_Hasher(out, (WOLFSSL_EVP_MD_CTX*)in); } #ifndef NO_AES @@ -11710,15 +11695,14 @@ int wolfSSL_EVP_MD_type(const WOLFSSL_EVP_MD* type) return ret; } - /* WOLFSSL_SUCCESS on ok, WOLFSSL_FAILURE on failure */ - int wolfSSL_EVP_DigestUpdate(WOLFSSL_EVP_MD_CTX* ctx, const void* data, - size_t sz) + /* Update the digest with at most a word32 of data. + * WOLFSSL_SUCCESS on ok, WOLFSSL_FAILURE on failure */ + static int wolfssl_evp_digest_update_chunk(WOLFSSL_EVP_MD_CTX* ctx, + const void* data, word32 sz) { int ret = WC_NO_ERR_TRACE(WOLFSSL_FAILURE); enum wc_HashType macType; - WOLFSSL_ENTER("EVP_DigestUpdate"); - macType = EvpMd2MacType(wolfSSL_EVP_MD_CTX_md(ctx)); switch (macType) { case WC_HASH_TYPE_MD4: @@ -11877,6 +11861,49 @@ int wolfSSL_EVP_MD_type(const WOLFSSL_EVP_MD* type) return ret; } + /* WOLFSSL_SUCCESS on ok, WOLFSSL_FAILURE on failure */ + int wolfSSL_EVP_DigestUpdate(WOLFSSL_EVP_MD_CTX* ctx, const void* data, + size_t sz) + { + int ret; + + WOLFSSL_ENTER("EVP_DigestUpdate"); + + if (ctx == NULL) + return WOLFSSL_FAILURE; + + /* The underlying update functions take a word32 length. Feed the data + * in chunks so the whole of sz is hashed instead of sz mod 2^32. + * Detect the narrowing by round-tripping rather than comparing against + * a constant, so this holds for any width of size_t. */ + do { + word32 chunk = (word32)sz; + if ((size_t)chunk != sz) + chunk = WC_MAX_UINT_OF(word32); + + #ifndef NO_HMAC + if (ctx->isHMAC) { + if (wc_HmacUpdate(&ctx->hash.hmac, (const byte*)data, + chunk) != 0) { + return WOLFSSL_FAILURE; + } + } + else + #endif + { + /* pass the sub-call's code through, e.g. NOT_COMPILED_IN */ + ret = wolfssl_evp_digest_update_chunk(ctx, data, chunk); + if (ret != WOLFSSL_SUCCESS) + return ret; + } + + data = (const byte*)data + chunk; + sz -= chunk; + } while (sz > 0); + + return WOLFSSL_SUCCESS; + } + /* WOLFSSL_SUCCESS on ok */ static int wolfSSL_EVP_DigestFinal_Common(WOLFSSL_EVP_MD_CTX* ctx, unsigned char* md, unsigned int* s, enum wc_HashType macType) diff --git a/wolfcrypt/src/pkcs7.c b/wolfcrypt/src/pkcs7.c index 9ef0130ebf..e66b652356 100644 --- a/wolfcrypt/src/pkcs7.c +++ b/wolfcrypt/src/pkcs7.c @@ -1795,16 +1795,18 @@ static int EncodeAttributes(EncodedAttrib* ea, int eaSz, PKCS7Attrib* attribs, int attribsSz) { int i; - int maxSz; word32 allAttribsSz = 0; if (eaSz < 0 || attribsSz < 0) { return BAD_FUNC_ARG; } - maxSz = (int)min((word32)eaSz, (word32)attribsSz); + /* every attribute must fit in the output array; do not silently drop */ + if (attribsSz > eaSz) { + return BUFFER_E; + } - for (i = 0; i < maxSz; i++) + for (i = 0; i < attribsSz; i++) { word32 attribSz = 0; word32 boundSz = 0; @@ -3316,7 +3318,9 @@ static int wc_PKCS7_EncodeContentStream(wc_PKCS7* pkcs7, ESD* esd, void* aes, } } - encContentOut = (byte *)XMALLOC(BER_OCTET_LENGTH + MAX_OCTET_STR_SZ, + /* the final chunk carries the trailing pad, so both buffers hold a + * full octet chunk plus padSz */ + encContentOut = (byte *)XMALLOC(BER_OCTET_LENGTH + padSz, heap, DYNAMIC_TYPE_PKCS7); contentData = (byte *)XMALLOC(BER_OCTET_LENGTH + padSz, heap, DYNAMIC_TYPE_PKCS7); @@ -14836,8 +14840,12 @@ int wc_PKCS7_DecodeEnvelopedData(wc_PKCS7* pkcs7, byte* in, localIdx += (word32)encryptedContentSz; /* keep track of total encrypted content size */ - pkcs7->totalEncryptedContentSz += - (word32)encryptedContentSz; + if (!WC_SAFE_SUM_WORD32(pkcs7->totalEncryptedContentSz, + (word32)encryptedContentSz, + pkcs7->totalEncryptedContentSz)) { + ret = BUFFER_E; + break; + } if (localIdx + ASN_INDEF_END_SZ <= pkiMsgSz) { if (pkiMsg[localIdx] == ASN_EOC && @@ -14867,17 +14875,34 @@ int wc_PKCS7_DecodeEnvelopedData(wc_PKCS7* pkcs7, byte* in, } #endif - /* save last decrypted string to handle padding (this output - * flush happens outside of the while loop in the case that - * the indef end was found) */ + /* flush this decrypted segment (the last segment is + * flushed outside of the while loop, once the indef end + * has been found and padding stripped) */ if (ret == 0) { #ifdef ASN_BER_TO_DER if (pkcs7->streamOutCb) { ret = pkcs7->streamOutCb(pkcs7, pkcs7->cachedEncryptedContent, (word32)encryptedContentSz, pkcs7->streamCtx); + if (ret != 0) + break; } + else #endif /* ASN_BER_TO_DER */ + { + /* copy segment to output; the return value counts + * every segment so each one must be written out */ + word32 outIdx = pkcs7->totalEncryptedContentSz - + (word32)encryptedContentSz; + if (output == NULL || + pkcs7->totalEncryptedContentSz > outputSz) { + ret = BUFFER_E; + break; + } + XMEMCPY(output + outIdx, + pkcs7->cachedEncryptedContent, + (word32)encryptedContentSz); + } } idx = localIdx; @@ -14964,12 +14989,17 @@ int wc_PKCS7_DecodeEnvelopedData(wc_PKCS7* pkcs7, byte* in, else #endif /* ASN_BER_TO_DER */ { - if (output == NULL || (word32)(encryptedContentSz - padLen) > + /* the return value counts every segment minus padding, so it + * must be bounded by outputSz */ + word32 outIdx = pkcs7->totalEncryptedContentSz - + (word32)encryptedContentSz; + if (output == NULL || + (pkcs7->totalEncryptedContentSz - (word32)padLen) > outputSz) { ret = BUFFER_E; break; } - XMEMCPY(output, encryptedContent, + XMEMCPY(output + outIdx, encryptedContent, (word32)encryptedContentSz - padLen); } diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index a600356d9e..21ac9c83c2 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -795,6 +795,7 @@ static int Hash_gen(DRBG_internal* drbg, byte* out, word32 outSz, const byte* V) XMEMCPY(data, V, DRBG_SEED_LEN); #ifdef WOLFSSL_CHECK_MEM_ZERO wc_MemZero_Add("Hash_gen data", data, DRBG_SEED_LEN); + wc_MemZero_Add("Hash_gen digest", digest, WC_SHA256_DIGEST_SIZE); #endif for (i = 0; i < len; i++) { #ifndef WOLFSSL_SMALL_STACK_CACHE @@ -832,9 +833,11 @@ static int Hash_gen(DRBG_internal* drbg, byte* out, word32 outSz, const byte* V) } } ForceZero(data, DRBG_SEED_LEN); + ForceZero(digest, WC_SHA256_DIGEST_SIZE); #if (!defined(WOLFSSL_SMALL_STACK) || defined(WOLFSSL_SMALL_STACK_CACHE)) && \ defined(WOLFSSL_CHECK_MEM_ZERO) wc_MemZero_Check(data, DRBG_SEED_LEN); + wc_MemZero_Check(digest, WC_SHA256_DIGEST_SIZE); #endif #ifndef WOLFSSL_SMALL_STACK_CACHE @@ -1395,6 +1398,7 @@ static int Hash512_gen(DRBG_SHA512_internal* drbg, byte* out, word32 outSz, XMEMCPY(data, V, DRBG_SHA512_SEED_LEN); #ifdef WOLFSSL_CHECK_MEM_ZERO wc_MemZero_Add("Hash512_gen data", data, DRBG_SHA512_SEED_LEN); + wc_MemZero_Add("Hash512_gen digest", digest, WC_SHA512_DIGEST_SIZE); #endif for (i = 0; i < len; i++) { #ifndef WOLFSSL_SMALL_STACK_CACHE @@ -1431,9 +1435,11 @@ static int Hash512_gen(DRBG_SHA512_internal* drbg, byte* out, word32 outSz, } } ForceZero(data, DRBG_SHA512_SEED_LEN); + ForceZero(digest, WC_SHA512_DIGEST_SIZE); #if (!defined(WOLFSSL_SMALL_STACK) || defined(WOLFSSL_SMALL_STACK_CACHE)) && \ defined(WOLFSSL_CHECK_MEM_ZERO) wc_MemZero_Check(data, DRBG_SHA512_SEED_LEN); + wc_MemZero_Check(digest, WC_SHA512_DIGEST_SIZE); #endif #ifndef WOLFSSL_SMALL_STACK_CACHE diff --git a/wolfcrypt/src/rng_bank.c b/wolfcrypt/src/rng_bank.c index c17e30eda1..92a380b7bf 100644 --- a/wolfcrypt/src/rng_bank.c +++ b/wolfcrypt/src/rng_bank.c @@ -997,11 +997,8 @@ WOLFSSL_API int wc_rng_bank_reseed(struct wc_rng_bank *bank, "for DRBG #%d returned %d.", n, ret); #endif (void)wc_rng_bank_checkin(bank, &drbg); - if ((ret == WC_NO_ERR_TRACE(WC_TIMEOUT_E)) || - (ret == WC_NO_ERR_TRACE(INTERRUPTED_E))) - { + if (ret != 0) return ret; - } ret = WC_CHECK_FOR_INTR_SIGNALS(); if (ret == WC_NO_ERR_TRACE(INTERRUPTED_E)) return ret; diff --git a/wolfcrypt/src/srp.c b/wolfcrypt/src/srp.c index 44c48d45ef..13d70e545e 100644 --- a/wolfcrypt/src/srp.c +++ b/wolfcrypt/src/srp.c @@ -326,6 +326,13 @@ int wc_SrpSetUsername(Srp* srp, const byte* username, word32 size) if (!srp || !username) return BAD_FUNC_ARG; + if (srp->user) { + ForceZero(srp->user, srp->userSz); + XFREE(srp->user, srp->heap, DYNAMIC_TYPE_SRP); + srp->user = NULL; + srp->userSz = 0; + } + /* +1 for NULL char */ srp->user = (byte*)XMALLOC(size + 1, srp->heap, DYNAMIC_TYPE_SRP); if (srp->user == NULL) @@ -676,6 +683,13 @@ static int wc_SrpSetKey(Srp* srp, byte* secret, word32 size) XMEMSET(digest, 0, SRP_MAX_DIGEST_SIZE); + if (srp->key) { + ForceZero(srp->key, srp->keySz); + XFREE(srp->key, srp->heap, DYNAMIC_TYPE_SRP); + srp->key = NULL; + srp->keySz = 0; + } + srp->key = (byte*)XMALLOC(2 * (word32)digestSz, srp->heap, DYNAMIC_TYPE_SRP); if (srp->key == NULL) return MEMORY_E; diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index e2fa72b69e..8aaae1578d 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -34067,6 +34067,14 @@ static wc_test_ret_t srp_test_digest(SrpType dgstType) if (!r) r = wc_SrpVerifyPeersProof(cli, serverProof, serverProofSz); + /* Regression: a second wc_SrpSetUsername()/wc_SrpComputeKey() must release + * (and, for the key, zeroise) the buffer from the first call rather than + * leaking it. The exchange above is already verified; these repeat calls + * exercise the overwrite path so ASan flags a leak if it regresses. */ + if (!r) r = wc_SrpSetUsername(cli, username, usernameSz); + if (!r) r = wc_SrpComputeKey(cli, clientPubKey, clientPubKeySz, + serverPubKey, serverPubKeySz); + /* Negative test: corrupted proof must be rejected with SRP_VERIFY_E. */ if (!r) { int rNeg; @@ -70144,6 +70152,123 @@ static wc_test_ret_t pkcs7authenveloped_run_vectors(byte* rsaCert, word32 rsaCer return ret; } +#if !defined(NO_RSA) && !defined(NO_AES) && defined(HAVE_AESGCM) && \ + defined(HAVE_AES_KEYWRAP) && defined(WOLFSSL_AES_128) +/* Boundary test for the fixed-size auth/unauth attribute arrays in + * wc_PKCS7_EncodeAuthEnvelopedData(): filling them to capacity must encode, + * while requesting one attribute more than fits must fail cleanly instead of + * writing past the arrays. Run under ASan to catch a regression. */ +static wc_test_ret_t pkcs7_authenv_attribs_boundary_test(byte* rsaCert, + word32 rsaCertSz, byte* rsaPrivKey, word32 rsaPrivKeySz) +{ + wc_test_ret_t ret = 0; + wc_PKCS7* pkcs7 = NULL; + byte* enveloped = NULL; + int envSz; + byte content[] = "authenv attribs boundary test"; + + /* eight distinct, well-formed attribute TLVs (OID + PrintableString) */ + static const byte oid0[] = { 0x06,0x03, 0x55,0x04,0x03 }; + static const byte oid1[] = { 0x06,0x03, 0x55,0x04,0x04 }; + static const byte oid2[] = { 0x06,0x03, 0x55,0x04,0x05 }; + static const byte oid3[] = { 0x06,0x03, 0x55,0x04,0x06 }; + static const byte oid4[] = { 0x06,0x03, 0x55,0x04,0x07 }; + static const byte oid5[] = { 0x06,0x03, 0x55,0x04,0x08 }; + static const byte oid6[] = { 0x06,0x03, 0x55,0x04,0x09 }; + static const byte oid7[] = { 0x06,0x03, 0x55,0x04,0x0a }; + static const byte val[] = { 0x13,0x01, 0x30 }; + PKCS7Attrib attribs[8] = { + { oid0, sizeof(oid0), val, sizeof(val) }, + { oid1, sizeof(oid1), val, sizeof(val) }, + { oid2, sizeof(oid2), val, sizeof(val) }, + { oid3, sizeof(oid3), val, sizeof(val) }, + { oid4, sizeof(oid4), val, sizeof(val) }, + { oid5, sizeof(oid5), val, sizeof(val) }, + { oid6, sizeof(oid6), val, sizeof(val) }, + { oid7, sizeof(oid7), val, sizeof(val) } + }; + + enveloped = (byte*)XMALLOC(PKCS7_BUF_SIZE, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (enveloped == NULL) + return WC_TEST_RET_ENC_ERRNO; + + /* contentOID == DATA so no contentType attribute is auto-added; all + * MAX_AUTH_ATTRIBS_SZ slots are available to the user attributes. */ + + /* exactly MAX_AUTH_ATTRIBS_SZ authenticated attributes: must encode */ + pkcs7 = wc_PKCS7_New(HEAP_HINT, devId); + if (pkcs7 == NULL) + ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out); + ret = wc_PKCS7_InitWithCert(pkcs7, rsaCert, rsaCertSz); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + pkcs7->content = content; + pkcs7->contentSz = (word32)XSTRLEN((char*)content); + pkcs7->contentOID = DATA; + pkcs7->encryptOID = AES128GCMb; + pkcs7->privateKey = rsaPrivKey; + pkcs7->privateKeySz = rsaPrivKeySz; + pkcs7->authAttribs = attribs; + pkcs7->authAttribsSz = MAX_AUTH_ATTRIBS_SZ; + envSz = wc_PKCS7_EncodeAuthEnvelopedData(pkcs7, enveloped, PKCS7_BUF_SIZE); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + if (envSz <= 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(envSz), out); + + /* one more authenticated attribute than fits: must fail, not overrun */ + pkcs7 = wc_PKCS7_New(HEAP_HINT, devId); + if (pkcs7 == NULL) + ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out); + ret = wc_PKCS7_InitWithCert(pkcs7, rsaCert, rsaCertSz); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + pkcs7->content = content; + pkcs7->contentSz = (word32)XSTRLEN((char*)content); + pkcs7->contentOID = DATA; + pkcs7->encryptOID = AES128GCMb; + pkcs7->privateKey = rsaPrivKey; + pkcs7->privateKeySz = rsaPrivKeySz; + pkcs7->authAttribs = attribs; + pkcs7->authAttribsSz = MAX_AUTH_ATTRIBS_SZ + 1; + envSz = wc_PKCS7_EncodeAuthEnvelopedData(pkcs7, enveloped, PKCS7_BUF_SIZE); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + if (envSz >= 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + + /* one more unauthenticated attribute than fits: must fail, not overrun */ + pkcs7 = wc_PKCS7_New(HEAP_HINT, devId); + if (pkcs7 == NULL) + ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out); + ret = wc_PKCS7_InitWithCert(pkcs7, rsaCert, rsaCertSz); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + pkcs7->content = content; + pkcs7->contentSz = (word32)XSTRLEN((char*)content); + pkcs7->contentOID = DATA; + pkcs7->encryptOID = AES128GCMb; + pkcs7->privateKey = rsaPrivKey; + pkcs7->privateKeySz = rsaPrivKeySz; + pkcs7->unauthAttribs = attribs; + pkcs7->unauthAttribsSz = MAX_UNAUTH_ATTRIBS_SZ + 1; + envSz = wc_PKCS7_EncodeAuthEnvelopedData(pkcs7, enveloped, PKCS7_BUF_SIZE); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + if (envSz >= 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + + ret = 0; + +out: + if (pkcs7 != NULL) + wc_PKCS7_Free(pkcs7); + XFREE(enveloped, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} +#endif /* RSA + AESGCM + keywrap + AES128 */ + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t pkcs7authenveloped_test(void) { wc_test_ret_t ret = 0; @@ -70221,6 +70346,13 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t pkcs7authenveloped_test(void) eccCert, (word32)eccCertSz, eccPrivKey, (word32)eccPrivKeySz); +#if !defined(NO_RSA) && !defined(NO_AES) && defined(HAVE_AESGCM) && \ + defined(HAVE_AES_KEYWRAP) && defined(WOLFSSL_AES_128) + if (ret == 0) + ret = pkcs7_authenv_attribs_boundary_test(rsaCert, (word32)rsaCertSz, + rsaPrivKey, (word32)rsaPrivKeySz); +#endif + #ifndef NO_RSA XFREE(rsaCert, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); XFREE(rsaPrivKey, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); diff --git a/wolfssl/openssl/evp.h b/wolfssl/openssl/evp.h index 74e8a8ba12..97026369de 100644 --- a/wolfssl/openssl/evp.h +++ b/wolfssl/openssl/evp.h @@ -865,7 +865,7 @@ WOLFSSL_API int wolfSSL_EVP_DigestFinal_ex(WOLFSSL_EVP_MD_CTX* ctx, WOLFSSL_API int wolfSSL_EVP_DigestFinalXOF(WOLFSSL_EVP_MD_CTX* ctx, unsigned char* md, size_t sz); WOLFSSL_API int wolfSSL_EVP_DigestSignUpdate(WOLFSSL_EVP_MD_CTX *ctx, - const void *d, unsigned int cnt); + const void *d, size_t cnt); WOLFSSL_API int wolfSSL_EVP_DigestSignFinal(WOLFSSL_EVP_MD_CTX *ctx, unsigned char *sig, size_t *siglen); WOLFSSL_API int wolfSSL_EVP_DigestSign(WOLFSSL_EVP_MD_CTX *ctx,