From 1afb05b6035cfe7a748e9152e72832e760bab3dc Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Thu, 9 Oct 2025 18:27:42 -0400 Subject: [PATCH] Add serialization for SHA-2 digest contexts This commit introduces the ability to serialize and deserialize the internal state of SHA-2 digest contexts (SHA-256 and SHA-512 families). This functionality is exposed via the new OSSL_DIGEST_SERIALIZATION parameter, which can be used with EVP_MD_CTX_get_params() to retrieve the state and with EVP_DigestInit_ex2() to restore it into a new context. This allows an application to save the state of a hash operation and resume it later, which is useful for process migration or for saving the state of long- unning computations. A new test case has been added to verify this. Signed-off-by: Simo Sorce Reviewed-by: Shane Lontis Reviewed-by: Tomas Mraz Reviewed-by: Dmitry Belyavskiy (Merged from https://github.com/openssl/openssl/pull/28837) --- .clang-format | 1 + providers/implementations/digests/sha2_prov.c | 267 ++++++++++++++++-- .../include/prov/digestcommon.h | 15 + test/evp_extra_test2.c | 62 ++++ 4 files changed, 324 insertions(+), 21 deletions(-) diff --git a/.clang-format b/.clang-format index 18b16e0de00..fd8082991dd 100644 --- a/.clang-format +++ b/.clang-format @@ -150,6 +150,7 @@ StatementMacros: - "IMPLEMENT_DIGEST" - "IMPLEMENT_digest_functions" - "IMPLEMENT_digest_functions_with_settable_ctx" + - "IMPLEMENT_digest_functions_with_serialize" - "IMPLEMENT_dtls1_meth_func" - "IMPLEMENT_DYNAMIC_BIND_FN" - "IMPLEMENT_DYNAMIC_CHECK_FN" diff --git a/providers/implementations/digests/sha2_prov.c b/providers/implementations/digests/sha2_prov.c index 2ea2ee6ac97..fe179ca8f58 100644 --- a/providers/implementations/digests/sha2_prov.c +++ b/providers/implementations/digests/sha2_prov.c @@ -13,6 +13,7 @@ */ #include "internal/deprecated.h" +#include #include #include #include @@ -56,6 +57,216 @@ static int sha1_set_ctx_params(void *vctx, const OSSL_PARAM params[]) return 1; } +static const unsigned char sha256magic[] = "SHA256v1"; +#define SHA256MAGIC_LEN (sizeof(sha256magic) - 1) +#define SHA256_SERIALIZATION_LEN \ + ( \ + SHA256MAGIC_LEN /* magic */ \ + + sizeof(uint32_t) /* c->md_len */ \ + + sizeof(uint32_t) * 8 /* c->h */ \ + + sizeof(uint32_t) * 2 /* c->Nl + c->Nh */ \ + + sizeof(uint32_t) * SHA_LBLOCK /* c->data */ \ + + sizeof(uint32_t) /* c->num */ \ + ) + +static int SHA256_Serialize(SHA256_CTX *c, unsigned char *out, + size_t *outlen) +{ + unsigned char *p; + unsigned long i; + + if (out == NULL) { + if (outlen == NULL) + return 0; + + *outlen = SHA256_SERIALIZATION_LEN; + return 1; + } + + if (outlen != NULL && *outlen < SHA256_SERIALIZATION_LEN) + return 0; + + p = out; + + /* Magic code */ + memcpy(p, sha256magic, SHA256MAGIC_LEN); + p += SHA256MAGIC_LEN; + + /* md_len */ + p = OPENSSL_store_u32_le(p, c->md_len); + + /* h */ + for (i = 0; i < sizeof(c->h) / sizeof(SHA_LONG); i++) + p = OPENSSL_store_u32_le(p, c->h[i]); + + /* Nl, Nh */ + p = OPENSSL_store_u32_le(p, c->Nl); + p = OPENSSL_store_u32_le(p, c->Nh); + + /* data */ + for (i = 0; i < SHA_LBLOCK; i++) + p = OPENSSL_store_u32_le(p, c->data[i]); + + /* num */ + p = OPENSSL_store_u32_le(p, c->num); + + if (outlen != NULL) + *outlen = SHA256_SERIALIZATION_LEN; + + return 1; +} + +static int SHA256_Deserialize(SHA256_CTX *c, const unsigned char *in, + size_t inlen) +{ + const unsigned char *p; + uint32_t val; + unsigned long i; + + if (c == NULL || in == NULL || inlen != SHA256_SERIALIZATION_LEN) + return 0; + + /* Magic code check */ + if (memcmp(in, sha256magic, SHA256MAGIC_LEN) != 0) + return 0; + + p = in + SHA256MAGIC_LEN; + + /* md_len check */ + p = OPENSSL_load_u32_le(&val, p); + if ((unsigned int)val != c->md_len) { + return 0; + } + + /* h */ + for (i = 0; i < (sizeof(c->h) / sizeof(SHA_LONG)); i++) { + p = OPENSSL_load_u32_le(&val, p); + c->h[i] = (SHA_LONG)val; + } + + /* Nl, Nh */ + p = OPENSSL_load_u32_le(&val, p); + c->Nl = (SHA_LONG)val; + p = OPENSSL_load_u32_le(&val, p); + c->Nh = (SHA_LONG)val; + + /* data */ + for (i = 0; i < SHA_LBLOCK; i++) { + p = OPENSSL_load_u32_le(&val, p); + c->data[i] = (SHA_LONG)val; + } + + /* num */ + p = OPENSSL_load_u32_le(&val, p); + c->num = (unsigned int)val; + + return 1; +} + +static const unsigned char sha512magic[] = "SHA512v1"; +#define SHA512MAGIC_LEN (sizeof(sha512magic) - 1) +#define SHA512_SERIALIZATION_LEN \ + ( \ + SHA512MAGIC_LEN /* magic */ \ + + sizeof(uint32_t) /* c->md_len */ \ + + sizeof(uint64_t) * 8 /* c->h */ \ + + sizeof(uint64_t) * 2 /* c->Nl + c->Nh */ \ + + SHA512_CBLOCK /* c->u.d/c->u.p */ \ + + sizeof(uint32_t) /* c->num */ \ + ) + +static int SHA512_Serialize(SHA512_CTX *c, unsigned char *out, + size_t *outlen) +{ + unsigned char *p; + unsigned long i; + + if (out == NULL) { + if (outlen == NULL) + return 0; + + *outlen = SHA512_SERIALIZATION_LEN; + return 1; + } + + if (outlen != NULL && *outlen < SHA512_SERIALIZATION_LEN) + return 0; + + p = out; + + /* Magic code */ + memcpy(p, sha512magic, SHA512MAGIC_LEN); + p += SHA512MAGIC_LEN; + + /* md_len */ + p = OPENSSL_store_u32_le(p, c->md_len); + + /* h */ + for (i = 0; i < sizeof(c->h) / sizeof(SHA_LONG64); i++) + p = OPENSSL_store_u64_le(p, c->h[i]); + + /* Nl, Nh */ + p = OPENSSL_store_u64_le(p, c->Nl); + p = OPENSSL_store_u64_le(p, c->Nh); + + /* data */ + memcpy(p, c->u.p, SHA512_CBLOCK); + p += SHA512_CBLOCK; + + /* num */ + p = OPENSSL_store_u32_le(p, c->num); + + if (outlen != NULL) + *outlen = SHA512_SERIALIZATION_LEN; + + return 1; +} + +static int SHA512_Deserialize(SHA512_CTX *c, const unsigned char *in, + size_t inlen) +{ + const unsigned char *p; + uint32_t val32; + uint64_t val; + unsigned long i; + + if (c == NULL || in == NULL || inlen != SHA512_SERIALIZATION_LEN) + return 0; + + /* Magic code */ + if (memcmp(in, sha512magic, SHA512MAGIC_LEN) != 0) + return 0; + + p = in + SHA512MAGIC_LEN; + + /* md_len check */ + p = OPENSSL_load_u32_le(&val32, p); + if ((unsigned int)val32 != c->md_len) + return 0; + + /* h */ + for (i = 0; i < (sizeof(c->h) / sizeof(SHA_LONG64)); i++) { + p = OPENSSL_load_u64_le(&val, p); + c->h[i] = (SHA_LONG64)val; + } + + /* Nl, Nh */ + p = OPENSSL_load_u64_le(&val, p); + c->Nl = (SHA_LONG64)val; + p = OPENSSL_load_u64_le(&val, p); + c->Nh = (SHA_LONG64)val; + + /* data */ + memcpy(c->u.p, p, SHA512_CBLOCK); + p += SHA512_CBLOCK; + + /* num */ + p = OPENSSL_load_u32_le(&val32, p); + c->num = (unsigned int)val32; + + return 1; +} + /* ossl_sha1_functions */ IMPLEMENT_digest_functions_with_settable_ctx( sha1, SHA_CTX, SHA_CBLOCK, SHA_DIGEST_LENGTH, SHA2_FLAGS, @@ -63,34 +274,48 @@ IMPLEMENT_digest_functions_with_settable_ctx( sha1_settable_ctx_params, sha1_set_ctx_params) /* ossl_sha224_functions */ -IMPLEMENT_digest_functions(sha224, SHA256_CTX, - SHA256_CBLOCK, SHA224_DIGEST_LENGTH, SHA2_FLAGS, - SHA224_Init, SHA224_Update, SHA224_Final) +IMPLEMENT_digest_functions_with_serialize(sha224, SHA256_CTX, + SHA256_CBLOCK, SHA224_DIGEST_LENGTH, + SHA2_FLAGS, SHA224_Init, + SHA224_Update, SHA224_Final, + SHA256_Serialize, SHA256_Deserialize) /* ossl_sha256_functions */ -IMPLEMENT_digest_functions(sha256, SHA256_CTX, - SHA256_CBLOCK, SHA256_DIGEST_LENGTH, SHA2_FLAGS, - SHA256_Init, SHA256_Update, SHA256_Final) +IMPLEMENT_digest_functions_with_serialize(sha256, SHA256_CTX, + SHA256_CBLOCK, SHA256_DIGEST_LENGTH, + SHA2_FLAGS, SHA256_Init, + SHA256_Update, SHA256_Final, + SHA256_Serialize, SHA256_Deserialize) /* ossl_sha256_192_internal_functions */ -IMPLEMENT_digest_functions(sha256_192_internal, SHA256_CTX, - SHA256_CBLOCK, SHA256_192_DIGEST_LENGTH, SHA2_FLAGS, - ossl_sha256_192_init, SHA256_Update, SHA256_Final) +IMPLEMENT_digest_functions_with_serialize(sha256_192_internal, SHA256_CTX, + SHA256_CBLOCK, SHA256_192_DIGEST_LENGTH, + SHA2_FLAGS, ossl_sha256_192_init, + SHA256_Update, SHA256_Final, + SHA256_Serialize, SHA256_Deserialize) /* ossl_sha384_functions */ -IMPLEMENT_digest_functions(sha384, SHA512_CTX, - SHA512_CBLOCK, SHA384_DIGEST_LENGTH, SHA2_FLAGS, - SHA384_Init, SHA384_Update, SHA384_Final) +IMPLEMENT_digest_functions_with_serialize(sha384, SHA512_CTX, + SHA512_CBLOCK, SHA384_DIGEST_LENGTH, + SHA2_FLAGS, SHA384_Init, + SHA384_Update, SHA384_Final, + SHA512_Serialize, SHA512_Deserialize) /* ossl_sha512_functions */ -IMPLEMENT_digest_functions(sha512, SHA512_CTX, - SHA512_CBLOCK, SHA512_DIGEST_LENGTH, SHA2_FLAGS, - SHA512_Init, SHA512_Update, SHA512_Final) +IMPLEMENT_digest_functions_with_serialize(sha512, SHA512_CTX, + SHA512_CBLOCK, SHA512_DIGEST_LENGTH, + SHA2_FLAGS, SHA512_Init, + SHA512_Update, SHA512_Final, + SHA512_Serialize, SHA512_Deserialize) /* ossl_sha512_224_functions */ -IMPLEMENT_digest_functions(sha512_224, SHA512_CTX, - SHA512_CBLOCK, SHA224_DIGEST_LENGTH, SHA2_FLAGS, - sha512_224_init, SHA512_Update, SHA512_Final) +IMPLEMENT_digest_functions_with_serialize(sha512_224, SHA512_CTX, + SHA512_CBLOCK, SHA224_DIGEST_LENGTH, + SHA2_FLAGS, sha512_224_init, + SHA512_Update, SHA512_Final, + SHA512_Serialize, SHA512_Deserialize) /* ossl_sha512_256_functions */ -IMPLEMENT_digest_functions(sha512_256, SHA512_CTX, - SHA512_CBLOCK, SHA256_DIGEST_LENGTH, SHA2_FLAGS, - sha512_256_init, SHA512_Update, SHA512_Final) +IMPLEMENT_digest_functions_with_serialize(sha512_256, SHA512_CTX, + SHA512_CBLOCK, SHA256_DIGEST_LENGTH, + SHA2_FLAGS, sha512_256_init, + SHA512_Update, SHA512_Final, + SHA512_Serialize, SHA512_Deserialize) diff --git a/providers/implementations/include/prov/digestcommon.h b/providers/implementations/include/prov/digestcommon.h index 14adc5507f7..383fce3e43e 100644 --- a/providers/implementations/include/prov/digestcommon.h +++ b/providers/implementations/include/prov/digestcommon.h @@ -126,6 +126,21 @@ extern "C" { { OSSL_FUNC_DIGEST_SET_CTX_PARAMS, (void (*)(void))set_ctx_params }, \ PROV_DISPATCH_FUNC_DIGEST_CONSTRUCT_END +#define IMPLEMENT_digest_functions_with_serialize( \ + name, CTX, blksize, dgstsize, flags, init, upd, fin, \ + serialize, deserialize) \ + static OSSL_FUNC_digest_init_fn name##_internal_init; \ + static int name##_internal_init(void *ctx, const OSSL_PARAM params[]) \ + { \ + return ossl_prov_is_running() && init(ctx); \ + } \ + PROV_DISPATCH_FUNC_DIGEST_CONSTRUCT_START(name, CTX, blksize, dgstsize, flags, \ + upd, fin), \ + { OSSL_FUNC_DIGEST_INIT, (void (*)(void))name##_internal_init }, \ + { OSSL_FUNC_DIGEST_SERIALIZE, (void (*)(void))serialize }, \ + { OSSL_FUNC_DIGEST_DESERIALIZE, (void (*)(void))deserialize }, \ + PROV_DISPATCH_FUNC_DIGEST_CONSTRUCT_END + const OSSL_PARAM *ossl_digest_default_gettable_params(void *provctx); int ossl_digest_default_get_params(OSSL_PARAM params[], size_t blksz, size_t paramsz, unsigned long flags); diff --git a/test/evp_extra_test2.c b/test/evp_extra_test2.c index 66fc827db8a..5e278ae2600 100644 --- a/test/evp_extra_test2.c +++ b/test/evp_extra_test2.c @@ -3429,6 +3429,67 @@ end: return ret; } +static int test_evp_md_ctx_serialize(int tstid) +{ + static const char *algs[] = { + "SHA224", "SHA256", "SHA256-192", + "SHA384", "SHA512", "SHA512-224", "SHA512-256" + }; + OSSL_LIB_CTX *ctx = NULL; + EVP_MD_CTX *mdctx1 = NULL, *mdctx2 = NULL; + EVP_MD *md = NULL; + unsigned char *buf = NULL; + size_t buflen; + unsigned char d1[EVP_MAX_MD_SIZE], d2[EVP_MAX_MD_SIZE]; + unsigned int d1_len, d2_len; + int ret = 0; + const char *data1 = "some data"; + const char *data2 = "some more data"; + + if (!TEST_ptr(ctx = OSSL_LIB_CTX_new()) + || !TEST_ptr(md = EVP_MD_fetch(ctx, algs[tstid], NULL))) + goto end; + + mdctx1 = EVP_MD_CTX_new(); + mdctx2 = EVP_MD_CTX_new(); + + /* Initiate a digest with data */ + if (!TEST_ptr(mdctx2) || !TEST_ptr(mdctx1) + || !TEST_true(EVP_DigestInit_ex2(mdctx1, md, NULL)) + || !TEST_true(EVP_DigestUpdate(mdctx1, data1, strlen(data1)))) + goto end; + + /* Get required buffer size and serialize */ + if (!TEST_true(EVP_MD_CTX_serialize(mdctx1, NULL, &buflen)) + || !TEST_ptr(buf = OPENSSL_malloc(buflen)) + || !TEST_true(EVP_MD_CTX_serialize(mdctx1, buf, &buflen))) + goto end; + + /* Deserialize */ + if (!TEST_true(EVP_DigestInit_ex2(mdctx2, md, NULL)) + || !TEST_true(EVP_MD_CTX_deserialize(mdctx2, buf, buflen))) + goto end; + + /* Test that updating in parallel will now yield the same values */ + if (!TEST_true(EVP_DigestUpdate(mdctx1, data2, strlen(data2))) + || !TEST_true(EVP_DigestUpdate(mdctx2, data2, strlen(data2))) + || !TEST_true(EVP_DigestFinal_ex(mdctx1, d1, &d1_len)) + || !TEST_true(EVP_DigestFinal_ex(mdctx2, d2, &d2_len)) + || !TEST_uint_eq(d1_len, d2_len) + || !TEST_mem_eq(d1, d1_len, d2, d2_len)) + goto end; + + ret = 1; + +end: + OPENSSL_free(buf); + EVP_MD_CTX_free(mdctx1); + EVP_MD_CTX_free(mdctx2); + EVP_MD_free(md); + OSSL_LIB_CTX_free(ctx); + return ret; +} + #if !defined OPENSSL_NO_DES && !defined OPENSSL_NO_MD5 static int test_evp_pbe_alg_add(void) { @@ -3524,6 +3585,7 @@ int setup_tests(void) ADD_TEST(test_evp_md_ctx_dup); ADD_TEST(test_evp_md_ctx_copy); ADD_TEST(test_evp_md_ctx_copy2); + ADD_ALL_TESTS(test_evp_md_ctx_serialize, 7); ADD_ALL_TESTS(test_provider_unload_effective, 2); #if !defined OPENSSL_NO_DES && !defined OPENSSL_NO_MD5 ADD_TEST(test_evp_pbe_alg_add); -- 2.47.3