From 493207c87f1a6cd8d3767f92beb267a9a8b0b666 Mon Sep 17 00:00:00 2001 From: manastasova <44320407+manastasova@users.noreply.github.com> Date: Mon, 24 Feb 2025 10:19:37 -0800 Subject: [PATCH] SHAKE Incremental Byte Squeezes && EVP_ Tests (#2155) ### Issues: Resolves #CryptoAlg-2835 && Resolves #CryptoAlg-2836 ### Description of changes: AWS-LC supports SHA3 and SHAKE algorithms. SHAKE, as defined in FIPS202, is an extendible output function, where the output data could be generated by multiple calls to "squeeze" function. Currently, there are two main features (internal and external APIs support) that are not supported by AWS-LC: - (Internal APIs) Currently, AWS-LC supports incremental XOF output (internal only via SHAKE_Squeeze) generation when the requested data is a multiple of the |block_size|. I.e., ceil(requested_output_bytes/block_size) full blocks will be generated and directly outputted without internal buffering. Thus, it is the user's (other algorithm) responsibility to manage output data when the request is not a multiple of the |block_size|. - Currently, AWS-LC does not support external (EVP_Digest) APIs for incremental squeezes. This PR add both features for incremental squeezes in arbitrary length output requests (up to a byte): - Internal APIs (SHAKE_) changes: - Add support for incremental |SHAKE_Squeeze| with arbitrary size (up to a byte) output requests via internal buffer. - Add KECCAK600_CTX struct |state| field updates and checks yo ensure functions are called only in the corresponding bitstate, e.g., |KECCAK1600_STATE_SQUEEZE| allows further bitstate squeezes, |KECCAK1600_STATE_FINAL| does not allow further squeezes, etc. - External APIs (EVP_Digest) changes: - Add external API support for incremental |SHAKE_Squeeze| with arbitrary size (up to a byte) output requests via |EVP_DigestSqueeze|. - Restrict the number of calls to |EVP_DigestFinalXOF| to only one. For incremental squeeze functionality |EVP_DigestSqueeze| should be used. - Restrict the use of |EVP_DigestFinal| to hash algorithms only. For XOF algorithms |EVP_DigestFinalXOF| and |EVP_DigestSqueeze| should be used. This PR adds more tests for EVP_Digest XOF functionality (all test are running through the entire NIST Test Vector list): - External APIs (EVP_Digest) additional tests: - Test Final - Assert fail when |EVP_DigestFinal| is called for XOF algorithms - Test Absorb - Assert success when |EVP_DigestUpdate| is called byte-by-byte - Test Squeeze - Assert success when |EVP_DigestSqueeze| is called byte-by-byte - Test Squeeze - Assert success when |EVP_DigestSqueeze| is called in set byte increments - Test Squeeze with random Input - Assert success when |EVP_DigestSqueeze| is called on a random message - Test Squeeze with random Input - Assert success when |EVP_DigestSqueeze| is called on a random message in set byte increments - Test Final XOF without Update - Assert fail when |EVP_DigestFinalXOF| is called as a streaming API ### Call-outs: Service indicator is updated: - Inside SHAKE_Squeeze (Streaming XOF Squeezes output generation **does not update** the service indicator after each extendable output update); ### Testing: _./crypto/crypto_test --gtest_filter="SHAKETest.*"_ By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license and the ISC license. --------- Co-authored-by: Jake Massimo --- crypto/digest_extra/digest_extra.c | 12 +- crypto/fipsmodule/digest/digest.c | 25 +- crypto/fipsmodule/digest/digests.c | 153 ++++++++--- crypto/fipsmodule/digest/internal.h | 17 +- crypto/fipsmodule/evp/digestsign.c | 5 +- crypto/fipsmodule/ml_dsa/ml_dsa_ref/sign.c | 12 +- crypto/fipsmodule/sha/internal.h | 2 +- crypto/fipsmodule/sha/sha3.c | 41 ++- crypto/fipsmodule/sha/sha3_test.cc | 292 ++++++++++++++++++++- include/openssl/digest.h | 9 +- 10 files changed, 500 insertions(+), 68 deletions(-) diff --git a/crypto/digest_extra/digest_extra.c b/crypto/digest_extra/digest_extra.c index 28254f9c9e..f65faf26c2 100644 --- a/crypto/digest_extra/digest_extra.c +++ b/crypto/digest_extra/digest_extra.c @@ -254,8 +254,10 @@ const EVP_MD *EVP_get_digestbyname(const char *name) { static void blake2b256_init(EVP_MD_CTX *ctx) { BLAKE2B256_Init(ctx->md_data); } -static void blake2b256_update(EVP_MD_CTX *ctx, const void *data, size_t len) { +static int blake2b256_update(EVP_MD_CTX *ctx, const void *data, size_t len) { + // BLAKE2B256_Update is a void function BLAKE2B256_Update(ctx->md_data, data, len); + return 1; } static void blake2b256_final(EVP_MD_CTX *ctx, uint8_t *md) { @@ -271,14 +273,15 @@ static const EVP_MD evp_md_blake2b256 = { blake2b256_final, BLAKE2B_CBLOCK, sizeof(BLAKE2B_CTX), - /*finalXOf*/ NULL, + /*finalXOf*/ NULL, + /*squeezeXOf*/ NULL }; const EVP_MD *EVP_blake2b256(void) { return &evp_md_blake2b256; } static void null_init(EVP_MD_CTX *ctx) {} -static void null_update(EVP_MD_CTX *ctx, const void *data, size_t count) {} +static int null_update(EVP_MD_CTX *ctx, const void *data, size_t count) { return 1;} static void null_final(EVP_MD_CTX *ctx, unsigned char *md) {} @@ -291,7 +294,8 @@ static const EVP_MD evp_md_null = { null_final, 0, sizeof(EVP_MD_CTX), - NULL, + /*finalXOf*/ NULL, + /*squeezeXOf*/ NULL }; const EVP_MD *EVP_md_null(void) { return &evp_md_null; } diff --git a/crypto/fipsmodule/digest/digest.c b/crypto/fipsmodule/digest/digest.c index 8e279ca58f..65a302456a 100644 --- a/crypto/fipsmodule/digest/digest.c +++ b/crypto/fipsmodule/digest/digest.c @@ -135,6 +135,11 @@ void EVP_MD_CTX_free(EVP_MD_CTX *ctx) { void EVP_MD_CTX_destroy(EVP_MD_CTX *ctx) { EVP_MD_CTX_free(ctx); } +// EVP_DigestFinalXOF is a single-call XOF output generation function. +// The |ctx->digest| check prevents calling EVP_DigestFinalXOF consecutively. +// To catch single-call XOF EVP_DigestFinalXOF calls after |EVP_DigestSqueeze|, +// the return |SHAKE_Final| value is used (the check is internally performed via +// the |KECCAK1600_CTX *ctx| state flag). int EVP_DigestFinalXOF(EVP_MD_CTX *ctx, uint8_t *out, size_t len) { if (ctx->digest == NULL) { return 0; @@ -143,8 +148,23 @@ int EVP_DigestFinalXOF(EVP_MD_CTX *ctx, uint8_t *out, size_t len) { OPENSSL_PUT_ERROR(DIGEST, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); return 0; } - ctx->digest->finalXOF(ctx, out, len); + int ok = ctx->digest->finalXOF(ctx, out, len); EVP_MD_CTX_cleanse(ctx); + return ok; +} + +// EVP_DigestSqueeze is a streaming XOF output squeeze function +// It can be called multiple times to generate an output of length +// |len| bytes. +int EVP_DigestSqueeze(EVP_MD_CTX *ctx, uint8_t *out, size_t len) { + if (ctx->digest == NULL) { + return 0; + } + if ((EVP_MD_flags(ctx->digest) & EVP_MD_FLAG_XOF) == 0) { + OPENSSL_PUT_ERROR(DIGEST, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); + return 0; + } + ctx->digest->squeezeXOF(ctx, out, len); return 1; } @@ -273,8 +293,7 @@ int EVP_DigestUpdate(EVP_MD_CTX *ctx, const void *data, size_t len) { if (ctx->update == NULL) { return 0; } - ctx->update(ctx, data, len); - return 1; + return ctx->update(ctx, data, len); } int EVP_DigestFinal_ex(EVP_MD_CTX *ctx, uint8_t *md_out, unsigned int *size) { diff --git a/crypto/fipsmodule/digest/digests.c b/crypto/fipsmodule/digest/digests.c index d869723685..1f796143bf 100644 --- a/crypto/fipsmodule/digest/digests.c +++ b/crypto/fipsmodule/digest/digests.c @@ -80,8 +80,11 @@ static void md4_init(EVP_MD_CTX *ctx) { CHECK(MD4_Init(ctx->md_data)); } -static void md4_update(EVP_MD_CTX *ctx, const void *data, size_t count) { - CHECK(MD4_Update(ctx->md_data, data, count)); +static int md4_update(EVP_MD_CTX *ctx, const void *data, size_t count) { + // MD4_Update always returns 1. Internally called function + // |crypto_md32_update| is void. For test consistency and future + // compatibility, the return value is propagated and returned + return MD4_Update(ctx->md_data, data, count); } static void md4_final(EVP_MD_CTX *ctx, uint8_t *out) { @@ -95,6 +98,7 @@ DEFINE_METHOD_FUNCTION(EVP_MD, EVP_md4) { out->init = md4_init; out->update = md4_update; out->final = md4_final; + out->squeezeXOF = NULL; out->finalXOF = NULL; out->block_size = 64; out->ctx_size = sizeof(MD4_CTX); @@ -105,8 +109,11 @@ static void md5_init(EVP_MD_CTX *ctx) { CHECK(MD5_Init(ctx->md_data)); } -static void md5_update(EVP_MD_CTX *ctx, const void *data, size_t count) { - CHECK(MD5_Update(ctx->md_data, data, count)); +static int md5_update(EVP_MD_CTX *ctx, const void *data, size_t count) { + // MD5_Update always returns 1. Internally called function + // |crypto_md32_update| is void. For test consistency and future + // compatibility, the return value is propagated and returned + return MD5_Update(ctx->md_data, data, count); } static void md5_final(EVP_MD_CTX *ctx, uint8_t *out) { @@ -120,6 +127,7 @@ DEFINE_METHOD_FUNCTION(EVP_MD, EVP_md5) { out->init = md5_init; out->update = md5_update; out->final = md5_final; + out->squeezeXOF = NULL; out->finalXOF = NULL; out->block_size = 64; out->ctx_size = sizeof(MD5_CTX); @@ -130,8 +138,11 @@ static void ripemd160_init(EVP_MD_CTX *ctx) { CHECK(RIPEMD160_Init(ctx->md_data)); } -static void ripemd160_update(EVP_MD_CTX *ctx, const void *data, size_t count) { - CHECK(RIPEMD160_Update(ctx->md_data, data, count)); +static int ripemd160_update(EVP_MD_CTX *ctx, const void *data, size_t count) { + // RIPEMD160_Update always returns 1. Internally called function + // |crypto_md32_update| is void. For test consistency and future + // compatibility, the return value is propagated and returned + return RIPEMD160_Update(ctx->md_data, data, count); } static void ripemd160_final(EVP_MD_CTX *ctx, uint8_t *out) { @@ -145,6 +156,7 @@ DEFINE_METHOD_FUNCTION(EVP_MD, EVP_ripemd160) { out->init = ripemd160_init; out->update = ripemd160_update; out->final = ripemd160_final; + out->squeezeXOF = NULL; out->finalXOF = NULL; out->block_size = 64; out->ctx_size = sizeof(RIPEMD160_CTX); @@ -155,8 +167,11 @@ static void sha1_init(EVP_MD_CTX *ctx) { CHECK(SHA1_Init(ctx->md_data)); } -static void sha1_update(EVP_MD_CTX *ctx, const void *data, size_t count) { - CHECK(SHA1_Update(ctx->md_data, data, count)); +static int sha1_update(EVP_MD_CTX *ctx, const void *data, size_t count) { + // SHA1_Update always returns 1. Internally called function + // |crypto_md32_update| is void. For test consistency and future + // compatibility, the return value is propagated and returned + return SHA1_Update(ctx->md_data, data, count); } static void sha1_final(EVP_MD_CTX *ctx, uint8_t *md) { @@ -170,6 +185,7 @@ DEFINE_METHOD_FUNCTION(EVP_MD, EVP_sha1) { out->init = sha1_init; out->update = sha1_update; out->final = sha1_final; + out->squeezeXOF = NULL; out->finalXOF = NULL; out->block_size = 64; out->ctx_size = sizeof(SHA_CTX); @@ -180,8 +196,11 @@ static void sha224_init(EVP_MD_CTX *ctx) { CHECK(SHA224_Init(ctx->md_data)); } -static void sha224_update(EVP_MD_CTX *ctx, const void *data, size_t count) { - CHECK(SHA224_Update(ctx->md_data, data, count)); +static int sha224_update(EVP_MD_CTX *ctx, const void *data, size_t count) { + // SHA224_Update always returns 1. Internally called function + // |crypto_md32_update| through |SHA256_Update| is void. For test consistency + // and future compatibility, the return value is propagated and returned + return SHA224_Update(ctx->md_data, data, count); } static void sha224_final(EVP_MD_CTX *ctx, uint8_t *md) { @@ -195,6 +214,7 @@ DEFINE_METHOD_FUNCTION(EVP_MD, EVP_sha224) { out->init = sha224_init; out->update = sha224_update; out->final = sha224_final; + out->squeezeXOF = NULL; out->finalXOF = NULL; out->block_size = 64; out->ctx_size = sizeof(SHA256_CTX); @@ -205,8 +225,11 @@ static void sha256_init(EVP_MD_CTX *ctx) { CHECK(SHA256_Init(ctx->md_data)); } -static void sha256_update(EVP_MD_CTX *ctx, const void *data, size_t count) { - CHECK(SHA256_Update(ctx->md_data, data, count)); +static int sha256_update(EVP_MD_CTX *ctx, const void *data, size_t count) { + // SHA256_Update always returns 1. Internally called function + // |crypto_md32_update| is void. For test consistency and future + // compatibility, the return value is propagated and returned + return SHA256_Update(ctx->md_data, data, count); } static void sha256_final(EVP_MD_CTX *ctx, uint8_t *md) { @@ -220,6 +243,7 @@ DEFINE_METHOD_FUNCTION(EVP_MD, EVP_sha256) { out->init = sha256_init; out->update = sha256_update; out->final = sha256_final; + out->squeezeXOF = NULL; out->finalXOF = NULL; out->block_size = 64; out->ctx_size = sizeof(SHA256_CTX); @@ -230,8 +254,11 @@ static void sha384_init(EVP_MD_CTX *ctx) { CHECK(SHA384_Init(ctx->md_data)); } -static void sha384_update(EVP_MD_CTX *ctx, const void *data, size_t count) { - CHECK(SHA384_Update(ctx->md_data, data, count)); +static int sha384_update(EVP_MD_CTX *ctx, const void *data, size_t count) { + // SHA384_Update always returns 1. Internally called function + // |SHA512_Update| always returns 1. For test consistency + // and future compatibility, the return value is propagated and returned + return SHA384_Update(ctx->md_data, data, count); } static void sha384_final(EVP_MD_CTX *ctx, uint8_t *md) { @@ -245,6 +272,7 @@ DEFINE_METHOD_FUNCTION(EVP_MD, EVP_sha384) { out->init = sha384_init; out->update = sha384_update; out->final = sha384_final; + out->squeezeXOF = NULL; out->finalXOF = NULL; out->block_size = 128; out->ctx_size = sizeof(SHA512_CTX); @@ -255,8 +283,10 @@ static void sha512_init(EVP_MD_CTX *ctx) { CHECK(SHA512_Init(ctx->md_data)); } -static void sha512_update(EVP_MD_CTX *ctx, const void *data, size_t count) { - CHECK(SHA512_Update(ctx->md_data, data, count)); +static int sha512_update(EVP_MD_CTX *ctx, const void *data, size_t count) { + // SHA512_Update always returns 1. For test consistency + // and future compatibility, the return value is propagated and returned + return SHA512_Update(ctx->md_data, data, count); } static void sha512_final(EVP_MD_CTX *ctx, uint8_t *md) { @@ -270,6 +300,7 @@ DEFINE_METHOD_FUNCTION(EVP_MD, EVP_sha512) { out->init = sha512_init; out->update = sha512_update; out->final = sha512_final; + out->squeezeXOF = NULL; out->finalXOF = NULL; out->block_size = 128; out->ctx_size = sizeof(SHA512_CTX); @@ -280,8 +311,11 @@ static void sha512_224_init(EVP_MD_CTX *ctx) { CHECK(SHA512_224_Init(ctx->md_data)); } -static void sha512_224_update(EVP_MD_CTX *ctx, const void *data, size_t count) { - CHECK(SHA512_224_Update(ctx->md_data, data, count)); +static int sha512_224_update(EVP_MD_CTX *ctx, const void *data, size_t count) { + // SHA512_224_Update always returns 1. Internally called function + // |SHA512_Update| always returns 1. For test consistency + // and future compatibility, the return value is propagated and returned + return SHA512_224_Update(ctx->md_data, data, count); } static void sha512_224_final(EVP_MD_CTX *ctx, uint8_t *md) { @@ -304,8 +338,11 @@ static void sha512_256_init(EVP_MD_CTX *ctx) { CHECK(SHA512_256_Init(ctx->md_data)); } -static void sha512_256_update(EVP_MD_CTX *ctx, const void *data, size_t count) { - CHECK(SHA512_256_Update(ctx->md_data, data, count)); +static int sha512_256_update(EVP_MD_CTX *ctx, const void *data, size_t count) { + // SHA512_256_Update always returns 1. Internally called function + // |SHA512_Update| always returns 1. For test consistency + // and future compatibility, the return value is propagated and returned + return SHA512_256_Update(ctx->md_data, data, count); } static void sha512_256_final(EVP_MD_CTX *ctx, uint8_t *md) { @@ -319,6 +356,7 @@ DEFINE_METHOD_FUNCTION(EVP_MD, EVP_sha512_256) { out->init = sha512_256_init; out->update = sha512_256_update; out->final = sha512_256_final; + out->squeezeXOF = NULL; out->finalXOF = NULL; out->block_size = 128; out->ctx_size = sizeof(SHA512_CTX); @@ -329,8 +367,9 @@ static void sha3_224_init(EVP_MD_CTX *ctx) { CHECK(SHA3_Init(ctx->md_data, SHA3_224_DIGEST_BITLENGTH)); } -static void sha3_224_update(EVP_MD_CTX *ctx, const void *data, size_t count) { - CHECK(SHA3_Update(ctx->md_data, data, count)); +static int sha3_224_update(EVP_MD_CTX *ctx, const void *data, size_t count) { + // SHA3_Update returns 1 on success and 0 on failure. + return SHA3_Update(ctx->md_data, data, count); } static void sha3_224_final(EVP_MD_CTX *ctx, uint8_t *md) { @@ -344,6 +383,7 @@ DEFINE_METHOD_FUNCTION(EVP_MD, EVP_sha3_224) { out->init = sha3_224_init; out->update = sha3_224_update; out->final = sha3_224_final; + out->squeezeXOF = NULL; out->finalXOF = NULL; out->block_size = SHA3_BLOCKSIZE(SHA3_224_DIGEST_BITLENGTH); out->ctx_size = sizeof(KECCAK1600_CTX); @@ -354,8 +394,9 @@ static void sha3_256_init(EVP_MD_CTX *ctx) { CHECK(SHA3_Init(ctx->md_data, SHA3_256_DIGEST_BITLENGTH)); } -static void sha3_256_update(EVP_MD_CTX *ctx, const void *data, size_t count) { - CHECK(SHA3_Update(ctx->md_data, data, count)); +static int sha3_256_update(EVP_MD_CTX *ctx, const void *data, size_t count) { + // SHA3_Update returns 1 on success and 0 on failure. + return SHA3_Update(ctx->md_data, data, count); } static void sha3_256_final(EVP_MD_CTX *ctx, uint8_t *md) { @@ -369,6 +410,7 @@ DEFINE_METHOD_FUNCTION(EVP_MD, EVP_sha3_256) { out->init = sha3_256_init; out->update = sha3_256_update; out->final = sha3_256_final; + out->squeezeXOF = NULL; out->finalXOF = NULL; out->block_size = SHA3_BLOCKSIZE(SHA3_256_DIGEST_BITLENGTH); out->ctx_size = sizeof(KECCAK1600_CTX); @@ -379,8 +421,9 @@ static void sha3_384_init(EVP_MD_CTX *ctx) { CHECK(SHA3_Init(ctx->md_data, SHA3_384_DIGEST_BITLENGTH)); } -static void sha3_384_update(EVP_MD_CTX *ctx, const void *data, size_t count) { - CHECK(SHA3_Update(ctx->md_data, data, count)); +static int sha3_384_update(EVP_MD_CTX *ctx, const void *data, size_t count) { + // SHA3_Update returns 1 on success and 0 on failure. + return SHA3_Update(ctx->md_data, data, count); } static void sha3_384_final(EVP_MD_CTX *ctx, uint8_t *md) { @@ -394,6 +437,7 @@ DEFINE_METHOD_FUNCTION(EVP_MD, EVP_sha3_384) { out->init = sha3_384_init; out->update = sha3_384_update; out->final = sha3_384_final; + out->squeezeXOF = NULL; out->finalXOF = NULL; out->block_size = SHA3_BLOCKSIZE(SHA3_384_DIGEST_BITLENGTH); out->ctx_size = sizeof(KECCAK1600_CTX); @@ -404,8 +448,9 @@ static void sha3_512_init(EVP_MD_CTX *ctx) { CHECK(SHA3_Init(ctx->md_data, SHA3_512_DIGEST_BITLENGTH)); } -static void sha3_512_update(EVP_MD_CTX *ctx, const void *data, size_t count) { - CHECK(SHA3_Update(ctx->md_data, data, count)); +static int sha3_512_update(EVP_MD_CTX *ctx, const void *data, size_t count) { + // SHA3_Update returns 1 on success and 0 on failure. + return SHA3_Update(ctx->md_data, data, count); } static void sha3_512_final(EVP_MD_CTX *ctx, uint8_t *md) { @@ -419,6 +464,7 @@ DEFINE_METHOD_FUNCTION(EVP_MD, EVP_sha3_512) { out->init = sha3_512_init; out->update = sha3_512_update; out->final = sha3_512_final; + out->squeezeXOF = NULL; out->finalXOF = NULL; out->block_size = SHA3_BLOCKSIZE(SHA3_512_DIGEST_BITLENGTH); out->ctx_size = sizeof(KECCAK1600_CTX); @@ -429,12 +475,21 @@ static void shake128_init(EVP_MD_CTX *ctx) { CHECK(SHAKE_Init(ctx->md_data, SHAKE128_BLOCKSIZE)); } -static void shake128_update(EVP_MD_CTX *ctx, const void *data, size_t count) { - CHECK(SHAKE_Absorb(ctx->md_data, data, count)); +// shake128_update returns 1 on success and 0 on failure, returned +// from |SHAKE_Absorb|, to restrict update calls after |squeezeXOF|. +static int shake128_update(EVP_MD_CTX *ctx, const void *data, size_t count) { + return SHAKE_Absorb(ctx->md_data, data, count); } -static void shake128_final(EVP_MD_CTX *ctx, uint8_t *md, size_t len) { - CHECK(SHAKE_Final(md, ctx->md_data, len)); +// shake128_final returns 1 on success and 0 on failure, +// returned from |SHAKE_Final|, to restrict single-call SHAKE_Final +// calls after |squeezeXOF|. +static int shake128_final(EVP_MD_CTX *ctx, uint8_t *md, size_t len) { + return SHAKE_Final(md, ctx->md_data, len); +} + +static void shake128_squeeze(EVP_MD_CTX *ctx, uint8_t *md, size_t len) { + CHECK(SHAKE_Squeeze(md, ctx->md_data, len)); } DEFINE_METHOD_FUNCTION(EVP_MD, EVP_shake128) { @@ -444,22 +499,31 @@ DEFINE_METHOD_FUNCTION(EVP_MD, EVP_shake128) { out->init = shake128_init; out->update = shake128_update; out->final = NULL; + out->squeezeXOF = shake128_squeeze; out->finalXOF = shake128_final; out->block_size = SHAKE128_BLOCKSIZE; out->ctx_size = sizeof(KECCAK1600_CTX); } - static void shake256_init(EVP_MD_CTX *ctx) { CHECK(SHAKE_Init(ctx->md_data, SHAKE256_BLOCKSIZE)); } -static void shake256_update(EVP_MD_CTX *ctx, const void *data, size_t count) { - CHECK(SHAKE_Absorb(ctx->md_data, data, count)); +// shake256_update returns 1 on success and 0 on failure, returned +// from |SHAKE_Absorb|, to restrict update calls after |squeezeXOF|. +static int shake256_update(EVP_MD_CTX *ctx, const void *data, size_t count) { + return SHAKE_Absorb(ctx->md_data, data, count); } -static void shake256_finalXOF(EVP_MD_CTX *ctx, uint8_t *md, size_t len) { - CHECK(SHAKE_Final(md, ctx->md_data, len)); +// shake256_final returns 1 on success and 0 on failure, +// returned from |SHAKE_Final|, to restrict single-call SHAKE_Final +// calls after |squeezeXOF|. +static int shake256_final(EVP_MD_CTX *ctx, uint8_t *md, size_t len) { + return SHAKE_Final(md, ctx->md_data, len); +} + +static void shake256_squeeze(EVP_MD_CTX *ctx, uint8_t *md, size_t len) { + CHECK(SHAKE_Squeeze(md, ctx->md_data, len)); } DEFINE_METHOD_FUNCTION(EVP_MD, EVP_shake256) { @@ -469,12 +533,12 @@ DEFINE_METHOD_FUNCTION(EVP_MD, EVP_shake256) { out->init = shake256_init; out->update = shake256_update; out->final = NULL; - out->finalXOF = shake256_finalXOF; + out->squeezeXOF = shake256_squeeze; + out->finalXOF = shake256_final; out->block_size = SHAKE256_BLOCKSIZE; out->ctx_size = sizeof(KECCAK1600_CTX); } - typedef struct { MD5_CTX md5; SHA_CTX sha1; @@ -485,11 +549,15 @@ static void md5_sha1_init(EVP_MD_CTX *md_ctx) { CHECK(MD5_Init(&ctx->md5) && SHA1_Init(&ctx->sha1)); } -static void md5_sha1_update(EVP_MD_CTX *md_ctx, const void *data, +static int md5_sha1_update(EVP_MD_CTX *md_ctx, const void *data, size_t count) { MD5_SHA1_CTX *ctx = md_ctx->md_data; - CHECK(MD5_Update(&ctx->md5, data, count) && - SHA1_Update(&ctx->sha1, data, count)); + // MD5_Update and SHA1_Update always return 1. Internally called function + // |crypto_md32_update| always returns 1. For test consistency + // and future compatibility, the return value is propagated and returned + int ok = MD5_Update(&ctx->md5, data, count) && + SHA1_Update(&ctx->sha1, data, count); + return ok; } static void md5_sha1_final(EVP_MD_CTX *md_ctx, uint8_t *out) { @@ -505,6 +573,7 @@ DEFINE_METHOD_FUNCTION(EVP_MD, EVP_md5_sha1) { out->init = md5_sha1_init; out->update = md5_sha1_update; out->final = md5_sha1_final; + out->squeezeXOF = NULL; out->finalXOF = NULL; out->block_size = 64; out->ctx_size = sizeof(MD5_SHA1_CTX); diff --git a/crypto/fipsmodule/digest/internal.h b/crypto/fipsmodule/digest/internal.h index 148e467077..116ebe265e 100644 --- a/crypto/fipsmodule/digest/internal.h +++ b/crypto/fipsmodule/digest/internal.h @@ -79,7 +79,12 @@ struct env_md_st { void (*init)(EVP_MD_CTX *ctx); // update hashes |len| bytes of |data| into the state in |ctx->md_data|. - void (*update)(EVP_MD_CTX *ctx, const void *data, size_t count); + // Digest update functions propagate the internal functions return value. + // update calls after |final| are restricted via |ctx| check (|final| + // cleanses the |ctx|). Digest XOF update returns 1 on success and 0 on + // failure. Failures can only occur on a digest XOF update if called after + // |squeezeXOF| or |finalXOF|. + int (*update)(EVP_MD_CTX *ctx, const void *data, size_t count); // final completes the hash and writes |md_size| bytes of digest to |out|. void (*final)(EVP_MD_CTX *ctx, uint8_t *out); @@ -90,9 +95,15 @@ struct env_md_st { // ctx_size contains the size, in bytes, of the state of the hash function. unsigned ctx_size; - // finalXOF completes the hash and writes |len| bytes of digest extended output + // finalXOF completes the hash and writes |len| bytes of digest extended + // output to |out|. Digest XOF finalXOF function propagates the return + // value from |SHAKE_Final|, that is 1 on success and 0 on failure, + // to restrict single-call finalXOF calls after |squeezeXOF|. + int (*finalXOF)(EVP_MD_CTX *ctx, uint8_t *out, size_t len); + + // squeezeXOF incrementally generates |len| bytes of digest extended output // to |out|. - void (*finalXOF)(EVP_MD_CTX *ctx, uint8_t *out, size_t len); + void (*squeezeXOF)(EVP_MD_CTX *ctx, uint8_t *out, size_t len); }; // evp_md_pctx_ops contains function pointers to allow the |pctx| member of diff --git a/crypto/fipsmodule/evp/digestsign.c b/crypto/fipsmodule/evp/digestsign.c index f804f0b272..a3d9ec2774 100644 --- a/crypto/fipsmodule/evp/digestsign.c +++ b/crypto/fipsmodule/evp/digestsign.c @@ -94,9 +94,10 @@ static int uses_prehash(EVP_MD_CTX *ctx, enum evp_sign_verify_t op) { : (ctx->pctx->pmeth->verify != NULL); } -static void hmac_update(EVP_MD_CTX *ctx, const void *data, size_t count) { +static int hmac_update(EVP_MD_CTX *ctx, const void *data, size_t count) { HMAC_PKEY_CTX *hctx = ctx->pctx->data; - CHECK(HMAC_Update(&hctx->ctx, data, count)); + // HMAC_Update returns 1 on success and 0 on failure. + return HMAC_Update(&hctx->ctx, data, count); } static int HMAC_DigestFinal_ex(EVP_MD_CTX *ctx, uint8_t *out_sig, diff --git a/crypto/fipsmodule/ml_dsa/ml_dsa_ref/sign.c b/crypto/fipsmodule/ml_dsa/ml_dsa_ref/sign.c index c0474c5b33..bfcc5e9bd3 100644 --- a/crypto/fipsmodule/ml_dsa/ml_dsa_ref/sign.c +++ b/crypto/fipsmodule/ml_dsa/ml_dsa_ref/sign.c @@ -198,9 +198,9 @@ int ml_dsa_sign_internal(ml_dsa_params *params, if (!external_mu) { //constuct mu = h(tr | m') when not in prehash mode SHAKE_Init(&state, SHAKE256_BLOCKSIZE); - SHA3_Update(&state, tr, ML_DSA_TRBYTES); - SHA3_Update(&state, pre, prelen); - SHA3_Update(&state, m, mlen); + SHAKE_Absorb(&state, tr, ML_DSA_TRBYTES); + SHAKE_Absorb(&state, pre, prelen); + SHAKE_Absorb(&state, m, mlen); SHAKE_Final(mu, &state, ML_DSA_CRHBYTES); } else { @@ -477,9 +477,9 @@ int ml_dsa_verify_internal(ml_dsa_params *params, // Like crypto_sign_signature_internal, the processing of M' is performed // here, as opposed to within the external function. SHAKE_Init(&state, SHAKE256_BLOCKSIZE); - SHA3_Update(&state, tr, ML_DSA_TRBYTES); - SHA3_Update(&state, pre, prelen); - SHA3_Update(&state, m, mlen); + SHAKE_Absorb(&state, tr, ML_DSA_TRBYTES); + SHAKE_Absorb(&state, pre, prelen); + SHAKE_Absorb(&state, m, mlen); SHAKE_Final(mu, &state, ML_DSA_CRHBYTES); } else { diff --git a/crypto/fipsmodule/sha/internal.h b/crypto/fipsmodule/sha/internal.h index 73d389cb61..e0a4ba38a2 100644 --- a/crypto/fipsmodule/sha/internal.h +++ b/crypto/fipsmodule/sha/internal.h @@ -92,7 +92,7 @@ struct keccak_st { size_t buf_load; // used bytes in below buffer uint8_t buf[SHA3_MAX_BLOCKSIZE]; // should have at least the max data block size bytes uint8_t pad; // padding character - uint8_t state; // denotes the keccak phase (absorb, squeeze, final) + uint8_t state; // denotes the keccak phase (absorb, squeeze, final) }; // Define SHA{n}[_{variant}]_ASM if sha{n}_block_data_order[_{variant}] is diff --git a/crypto/fipsmodule/sha/sha3.c b/crypto/fipsmodule/sha/sha3.c index 16b098023a..2859c0ed4f 100644 --- a/crypto/fipsmodule/sha/sha3.c +++ b/crypto/fipsmodule/sha/sha3.c @@ -292,8 +292,8 @@ int SHAKE_Absorb(KECCAK1600_CTX *ctx, const void *data, size_t len) { } // SHAKE_Final is to be called once to finalize absorb and squeeze phases -// |ctx->state| restricts consecutive calls to FIPS202_Finalize -// Function SHAKE_Squeeze should be used for incremental XOF output +// |ctx->state| restricts consecutive calls to |FIPS202_Finalize|. +// Function |SHAKE_Squeeze| should be used for incremental XOF output. int SHAKE_Final(uint8_t *md, KECCAK1600_CTX *ctx, size_t len) { if (ctx == NULL || md == NULL) { return 0; @@ -312,12 +312,13 @@ int SHAKE_Final(uint8_t *md, KECCAK1600_CTX *ctx, size_t len) { ctx->state = KECCAK1600_STATE_FINAL; FIPS_service_indicator_update_state(); - return 1; } // SHAKE_Squeeze can be called multiple time for incremental XOF output int SHAKE_Squeeze(uint8_t *md, KECCAK1600_CTX *ctx, size_t len) { + size_t block_bytes; + if (ctx == NULL || md == NULL) { return 0; } @@ -339,9 +340,39 @@ int SHAKE_Squeeze(uint8_t *md, KECCAK1600_CTX *ctx, size_t len) { return 0; } } + // Process previous data from output buffer if any + if (ctx->buf_load != 0) { + if (len <= ctx->buf_load) { + OPENSSL_memcpy(md, ctx->buf + ctx->block_size - ctx->buf_load, len); + ctx->buf_load -= len; + return 1; + } else { + OPENSSL_memcpy(md, ctx->buf + ctx->block_size - ctx->buf_load, ctx->buf_load); + md += ctx->buf_load; + len -= ctx->buf_load; + ctx->buf_load = 0; + } + } + + // Process all full size output requested blocks + if (len > ctx->block_size) { + block_bytes = ctx->block_size * (len / ctx->block_size); + Keccak1600_Squeeze(ctx->A, md, block_bytes, ctx->block_size, ctx->state); + md += block_bytes; + len -= block_bytes; + ctx->state = KECCAK1600_STATE_SQUEEZE; + } - Keccak1600_Squeeze(ctx->A, md, len, ctx->block_size, ctx->state); - ctx->state = KECCAK1600_STATE_SQUEEZE; + if (len > 0) { + // Process an additional block if output length is not a multiple of block size. + // Generated output is store in |ctx->buf|. Only requested bytes are transfered + // to the output. The 'unused' output data is kept for processing in a sequenctual + // call to SHAKE_Squeeze (incremental byte-wise SHAKE_Squeeze) + Keccak1600_Squeeze(ctx->A, ctx->buf, ctx->block_size, ctx->block_size, ctx->state); + OPENSSL_memcpy(md, ctx->buf, len); + ctx->buf_load = ctx->block_size - len; // how much there is still in buffer to be consumed + ctx->state = KECCAK1600_STATE_SQUEEZE; + } //FIPS_service_indicator_update_state(); return 1; diff --git a/crypto/fipsmodule/sha/sha3_test.cc b/crypto/fipsmodule/sha/sha3_test.cc index 310a5af9c3..23041a54ee 100644 --- a/crypto/fipsmodule/sha/sha3_test.cc +++ b/crypto/fipsmodule/sha/sha3_test.cc @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 OR ISC #include +#include #include - #include #include @@ -11,6 +11,82 @@ #include "../../test/test_util.h" #include "internal.h" +// Set values for input/output lengths used in +// |NISTTestVectors_SHAKESqueeze| test function +#define RAND_BYTES 256 +#define RAND_OUT_BYTES 256 + +// Table containing the length of the output to squeeze for the +// initial call, followed by a output length for each subsequent call. +static const struct { + size_t startsz, incsz; +} stride_tests[] = { + // Test Edge Cases for SHAKE128 with blocksize of 168B + { 1, 1 }, + { 8, 8 }, + { 9, 9 }, + { 10, 10 }, + { 1, 168 }, + { 1, 168/2 }, + { 1, 168/2-1 }, + { 1, 168/2+1 }, + { 1, 168*3 }, + { 168/2 - 1, 168 }, + { 168/2 - 1, 168-1 }, + { 168/2 - 1, 168+1 }, + { 168/2, 168 }, + { 168/2, 168-1 }, + { 168/2, 168+1 }, + { 168/2 + 1, 168 }, + { 168/2 + 1, 168-1 }, + { 168/2 + 1, 168+1 }, + { 168, 2 }, + { 168, 168 }, + { 168-1, 168 }, + { 168-1, 168-1 }, + { 168-1, 168+1 }, + { 168+1, 168 }, + { 168+1, 168-1 }, + { 168+1, 168+1 }, + { 168*3, 168 }, + { 168*3, 168 + 1 }, + { 168*3, 168 - 1 }, + { 168*3, 168/2 }, + { 168*3, 168/2 + 1 }, + { 168*3, 168/2 - 1 }, + // Test Edge Cases for SHAKE256 with blocksize of 136B + { 1, 136 }, + { 1, 136/2 }, + { 1, 136/2-1 }, + { 1, 136/2+1 }, + { 1, 136*3 }, + { 8, 8 }, + { 9, 9 }, + { 10, 10 }, + { 136/2 - 1, 136 }, + { 136/2 - 1, 136-1 }, + { 136/2 - 1, 136+1 }, + { 136/2, 136 }, + { 136/2, 136-1 }, + { 136/2, 136+1 }, + { 136/2 + 1, 136 }, + { 136/2 + 1, 136-1 }, + { 136/2 + 1, 136+1 }, + { 136, 2 }, + { 136, 136 }, + { 136-1, 136 }, + { 136-1, 136-1 }, + { 136-1, 136+1 }, + { 136+1, 136 }, + { 136+1, 136-1 }, + { 136+1, 136+1 }, + { 136*3, 136 }, + { 136*3, 136 + 1 }, + { 136*3, 136 - 1 }, + { 136*3, 136/2 }, + { 136*3, 136/2 + 1 }, + { 136*3, 136/2 - 1 } +}; // SHA3TestVector corresponds to one test case of the NIST published file // SHA3_256ShortMsg.txt. @@ -34,6 +110,14 @@ class SHA3TestVector { ASSERT_EQ(Bytes(digest.get(), EVP_MD_size(algorithm)), Bytes(digest_.data(), EVP_MD_size(algorithm))); + + // Test XOF-specific Digest functions with non XOF algorithms + // Assert failure when |EVP_DigestSqueeze| or |EVP_DigestFinalXOF| + // are called with digests different from XOF digests + ASSERT_TRUE(EVP_DigestInit(ctx.get(), algorithm)); + ASSERT_TRUE(EVP_DigestUpdate(ctx.get(), msg_.data(), msg_.size())); + ASSERT_FALSE(EVP_DigestSqueeze(ctx.get(), digest.get(), digest_length)); + ASSERT_FALSE(EVP_DigestFinalXOF(ctx.get(), digest.get(), digest_length)); } void NISTTestVectors_SingleShot(const EVP_MD *algorithm) const { @@ -67,6 +151,200 @@ class SHA3TestVector { Bytes(digest_.data(), digest_length)); } + // Test SHAKE Squeeze functionality through |EVP_Digest| APIs + void NISTTestVectors_SHAKESqueeze(const EVP_MD *algorithm) const { + uint8_t random_bytes[RAND_BYTES]; + size_t sqd_bytes = 0, cur_test = 0, to_sq_bytes = 0; + + uint32_t digest_length = out_len_ / 8; + std::unique_ptr digest(new uint8_t[digest_length]); + std::unique_ptr digest_stream(new uint8_t[RAND_OUT_BYTES]); + std::unique_ptr digest_signle_shot(new uint8_t[RAND_OUT_BYTES]); + + bssl::ScopedEVP_MD_CTX ctx; + + // Test Final XOF + // Assert fail when |EVP_DigestFinalXOF| is called as a streaming API + ASSERT_TRUE(EVP_DigestInit(ctx.get(), algorithm)); + ASSERT_TRUE(EVP_DigestUpdate(ctx.get(), msg_.data(), msg_.size())); + ASSERT_TRUE(EVP_DigestFinalXOF(ctx.get(), digest.get(), digest_length)); + EXPECT_EQ(Bytes(digest.get(), digest_length), + Bytes(digest_.data(), digest_length)); + + ASSERT_FALSE(EVP_DigestFinalXOF(ctx.get(), digest.get(), digest_length)); + ASSERT_FALSE(EVP_DigestSqueeze(ctx.get(), digest.get(), digest_length)); + + // Test the one-shot + // Assert success when |EVP_Digest| is called + OPENSSL_memset(digest.get(), 0, digest_length); + ASSERT_TRUE(EVP_Digest(msg_.data(), msg_.size(), digest.get(), + &digest_length, algorithm, nullptr)); + EXPECT_EQ(Bytes(digest.get(), digest_length), + Bytes(digest_.data(), digest_length)); + + // Test Final + // Assert fail when |EVP_DigestFinal| is called for XOF algorithms + OPENSSL_memset(digest.get(), 0, digest_length); + ASSERT_TRUE(EVP_DigestInit(ctx.get(), algorithm)); + ASSERT_TRUE(EVP_DigestUpdate(ctx.get(), msg_.data(), msg_.size())); + + ASSERT_FALSE(EVP_DigestFinal(ctx.get(), digest.get(), &digest_length)); + ASSERT_FALSE(EVP_DigestFinalXOF(ctx.get(), digest.get(), digest_length)); + + ASSERT_TRUE(EVP_DigestInit(ctx.get(), algorithm)); + ASSERT_TRUE(EVP_DigestUpdate(ctx.get(), msg_.data(), msg_.size())); + ASSERT_TRUE(EVP_DigestFinalXOF(ctx.get(), digest.get(), digest_length)); + + EXPECT_EQ(Bytes(digest.get(), digest_length), + Bytes(digest_.data(), digest_length)); + + ASSERT_FALSE(EVP_DigestFinalXOF(ctx.get(), digest.get(), digest_length)); + + // Test Final XOF after Squeeze + // Assert fail when |EVP_DigestFinalXOF| is called after |EVP_DigestSqueeze| + ASSERT_TRUE(EVP_DigestInit_ex(ctx.get(), algorithm, NULL)); + ASSERT_TRUE(EVP_DigestSqueeze(ctx.get(), digest.get(), digest_length/2)); + ASSERT_FALSE(EVP_DigestFinalXOF(ctx.get(), digest.get() + digest_length/2, + digest_length/2)); + + // Test Update after Squeeze + // Assert fail when |EVP_DigestUpdate| is called after |EVP_DigestSqueeze| + ASSERT_TRUE(EVP_DigestInit_ex(ctx.get(), algorithm, NULL)); + ASSERT_TRUE(EVP_DigestSqueeze(ctx.get(), digest.get(), digest_length)); + ASSERT_FALSE(EVP_DigestUpdate(ctx.get(), msg_.data(), msg_.size())); + + // Test Absorb + // Assert success when |EVP_DigestUpdate| is called byte-by-byte + OPENSSL_memset(digest.get(), 0, digest_length); + ASSERT_TRUE(EVP_DigestInit_ex(ctx.get(), algorithm, NULL)); + ASSERT_TRUE(EVP_DigestUpdate(ctx.get(), nullptr, 0)); + for (const char p : msg_) { + ASSERT_TRUE(EVP_DigestUpdate(ctx.get(), &p, 1)); + } + + ASSERT_TRUE(EVP_DigestFinalXOF(ctx.get(), digest.get(), digest_length)); + EXPECT_EQ(Bytes(digest.get(), digest_length), + Bytes(digest_.data(), digest_length)); + + // Test Squeeze + // Assert success when |EVP_DigestSqueeze| is called byte-by-byte + OPENSSL_memset(digest.get(), 0, digest_length); + ASSERT_TRUE(EVP_DigestInit(ctx.get(), algorithm)); + ASSERT_TRUE(EVP_DigestUpdate(ctx.get(), msg_.data(), msg_.size())); + + for (size_t i = 0; i < digest_length; i++) { + ASSERT_TRUE(EVP_DigestSqueeze(ctx.get(), digest.get() + i, 1)); + } + + EXPECT_EQ(Bytes(digest.get(), digest_length), + Bytes(digest_.data(), digest_length)); + + // Test Squeeze + // Assert success when |EVP_DigestSqueeze| is called in set byte increments + for (cur_test = 0, sqd_bytes = 0; cur_test < (int) (sizeof(stride_tests)/sizeof(stride_tests[0])); cur_test++, sqd_bytes = 0) { + to_sq_bytes = stride_tests[cur_test].startsz; + OPENSSL_memset(digest.get(), 0, digest_length); + ASSERT_TRUE(EVP_DigestInit_ex(ctx.get(), algorithm, NULL)); + ASSERT_TRUE(EVP_DigestUpdate(ctx.get(), msg_.data(), msg_.size())); + + while (sqd_bytes < digest_length) { + if ((sqd_bytes + to_sq_bytes) > digest_length) { + to_sq_bytes = digest_length - sqd_bytes; + } + ASSERT_TRUE(EVP_DigestSqueeze(ctx.get(), digest.get() + sqd_bytes, to_sq_bytes)); + sqd_bytes += to_sq_bytes; + to_sq_bytes = stride_tests[cur_test].incsz; + } + EXPECT_EQ(Bytes(digest.get(), digest_length), + Bytes(digest_.data(), digest_length)); + } + + // Test Squeeze Exhaustive + // Assert success when |EVP_DigestSqueeze| is called in all possible byte increments + for (to_sq_bytes = 1; to_sq_bytes < digest_length; to_sq_bytes++) { + OPENSSL_memset(digest.get(), 0, digest_length); + ASSERT_TRUE(EVP_DigestInit_ex(ctx.get(), algorithm, NULL)); + ASSERT_TRUE(EVP_DigestUpdate(ctx.get(), msg_.data(), msg_.size())); + + for (sqd_bytes = 0; sqd_bytes <= digest_length - to_sq_bytes; sqd_bytes+=to_sq_bytes) { + ASSERT_TRUE(EVP_DigestSqueeze(ctx.get(), digest.get() + sqd_bytes, to_sq_bytes)); + } + + if ((digest_length - sqd_bytes) > 0) { + ASSERT_TRUE(EVP_DigestSqueeze(ctx.get(), digest.get() + sqd_bytes, digest_length - sqd_bytes)); + } + + EXPECT_EQ(Bytes(digest.get(), digest_length), + Bytes(digest_.data(), digest_length)); + } + + // Test Squeeze with random Input + // Assert success when |EVP_DigestSqueeze| is called on a random message + ASSERT_TRUE(RAND_bytes(random_bytes, RAND_BYTES)); + + ASSERT_TRUE(EVP_DigestInit_ex(ctx.get(), algorithm, NULL)); + ASSERT_TRUE(EVP_DigestUpdate(ctx.get(), random_bytes, RAND_BYTES)); + + for (size_t i = 0; i < RAND_OUT_BYTES; i++) { + ASSERT_TRUE(EVP_DigestSqueeze(ctx.get(), digest_stream.get() + i, 1)); + } + + ASSERT_TRUE(EVP_DigestInit_ex(ctx.get(), algorithm, NULL)); + ASSERT_TRUE(EVP_DigestUpdate(ctx.get(), random_bytes, RAND_BYTES)); + ASSERT_TRUE(EVP_DigestFinalXOF(ctx.get(), digest_signle_shot.get(), RAND_OUT_BYTES)); + + EXPECT_EQ(EncodeHex(bssl::MakeConstSpan(digest_stream.get(), RAND_OUT_BYTES)), + EncodeHex(bssl::MakeConstSpan(digest_signle_shot.get(), RAND_OUT_BYTES))); + + // Test Squeeze with random Input + // Assert success when |EVP_DigestSqueeze| is called on a random message + // in set byte increments + for (cur_test = 0, sqd_bytes = 0; cur_test < (int) (sizeof(stride_tests)/sizeof(stride_tests[0])); cur_test++, sqd_bytes = 0) { + to_sq_bytes = stride_tests[cur_test].startsz; + OPENSSL_memset(digest_stream.get(), 0, RAND_OUT_BYTES); + OPENSSL_memset(digest_signle_shot.get(), 0, RAND_OUT_BYTES); + + ASSERT_TRUE(RAND_bytes(random_bytes, RAND_BYTES)); + + // Incremental Squeezes + ASSERT_TRUE(EVP_DigestInit(ctx.get(), algorithm)); + ASSERT_TRUE(EVP_DigestUpdate(ctx.get(), random_bytes, RAND_BYTES)); + + while (sqd_bytes < RAND_OUT_BYTES) { + if ((sqd_bytes + to_sq_bytes) > RAND_OUT_BYTES) { + to_sq_bytes = RAND_OUT_BYTES - sqd_bytes; + } + ASSERT_TRUE(EVP_DigestSqueeze(ctx.get(), digest_stream.get() + sqd_bytes, to_sq_bytes)); + sqd_bytes += to_sq_bytes; + to_sq_bytes = stride_tests[cur_test].incsz; + } + + // Single-Shot Squeeze + ASSERT_TRUE(EVP_DigestInit_ex(ctx.get(), algorithm, NULL)); + ASSERT_TRUE(EVP_DigestUpdate(ctx.get(), random_bytes, RAND_BYTES)); + ASSERT_TRUE(EVP_DigestFinalXOF(ctx.get(), digest_signle_shot.get(), RAND_OUT_BYTES)); + + EXPECT_EQ(EncodeHex(bssl::MakeConstSpan(digest_stream.get(), RAND_OUT_BYTES)), + EncodeHex(bssl::MakeConstSpan(digest_signle_shot.get(), RAND_OUT_BYTES))); + } + + // Test Final XOF without Update + // Assert fail when |EVP_DigestFinalXOF| is called as a streaming API + OPENSSL_memset(digest_signle_shot.get(), 0, RAND_OUT_BYTES); + OPENSSL_memset(digest_stream.get(), 0, RAND_OUT_BYTES); + + ASSERT_TRUE(EVP_DigestInit(ctx.get(), algorithm)); + ASSERT_TRUE(EVP_DigestFinalXOF(ctx.get(), digest_signle_shot.get(), RAND_OUT_BYTES)); + + ASSERT_TRUE(EVP_DigestInit(ctx.get(), algorithm)); + ASSERT_TRUE(EVP_DigestSqueeze(ctx.get(), digest_stream.get(), RAND_OUT_BYTES/2)); + ASSERT_TRUE(EVP_DigestSqueeze(ctx.get(), digest_stream.get() + RAND_OUT_BYTES/2, + RAND_OUT_BYTES/2)); + + EXPECT_EQ(EncodeHex(bssl::MakeConstSpan(digest_stream.get(), RAND_OUT_BYTES)), + EncodeHex(bssl::MakeConstSpan(digest_signle_shot.get(), RAND_OUT_BYTES))); +} + private: uint32_t len_; uint32_t out_len_; @@ -231,4 +509,16 @@ TEST(SHAKETest, NISTTestVectors) { EXPECT_TRUE(test_vec.ReadFromFileTest(t)); test_vec.NISTTestVectors_SHAKE(EVP_shake256()); }); + FileTestGTest("crypto/fipsmodule/sha/testvectors/SHAKE128VariableOut.txt", + [](FileTest *t) { + SHA3TestVector test_vec; + EXPECT_TRUE(test_vec.ReadFromFileTest(t)); + test_vec.NISTTestVectors_SHAKESqueeze(EVP_shake128()); + }); + FileTestGTest("crypto/fipsmodule/sha/testvectors/SHAKE256VariableOut.txt", + [](FileTest *t) { + SHA3TestVector test_vec; + EXPECT_TRUE(test_vec.ReadFromFileTest(t)); + test_vec.NISTTestVectors_SHAKESqueeze(EVP_shake256()); + }); } diff --git a/include/openssl/digest.h b/include/openssl/digest.h index 83d4189b71..6952cf8691 100644 --- a/include/openssl/digest.h +++ b/include/openssl/digest.h @@ -294,6 +294,13 @@ OPENSSL_EXPORT void EVP_MD_CTX_destroy(EVP_MD_CTX *ctx); OPENSSL_EXPORT int EVP_DigestFinalXOF(EVP_MD_CTX *ctx, uint8_t *out, size_t len); +// EVP_DigestSqueeze provides byte-wise streaming XOF output generation for +// XOF digests, writing |len| bytes of extended output to |out|. It can be +// called multiple times with arbitrary length |len| output requests. +// It returns one on success and zero on error. +OPENSSL_EXPORT int EVP_DigestSqueeze(EVP_MD_CTX *ctx, uint8_t *out, + size_t len); + // EVP_MD_meth_get_flags calls |EVP_MD_flags|. OPENSSL_EXPORT uint32_t EVP_MD_meth_get_flags(const EVP_MD *md); @@ -339,7 +346,7 @@ struct env_md_ctx_st { // |digest->update|. |digest->update| operates against |md_data| above, but // |HMAC_CTX| maintains its own data state in |HMAC_CTX->md_ctx|. // |HMAC_Update| also has an additional state transition to handle. - void (*update)(EVP_MD_CTX *ctx, const void *data, size_t count); + int (*update)(EVP_MD_CTX *ctx, const void *data, size_t count); // pctx is an opaque (at this layer) pointer to additional context that // EVP_PKEY functions may store in this object.