Commit 1afb05b603 for openssl.org
commit 1afb05b6035cfe7a748e9152e72832e760bab3dc
Author: Simo Sorce <simo@redhat.com>
Date: Thu Oct 9 18:27:42 2025 -0400
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 <simo@redhat.com>
Reviewed-by: Shane Lontis <shane.lontis@oracle.com>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Dmitry Belyavskiy <beldmit@gmail.com>
(Merged from https://github.com/openssl/openssl/pull/28837)
diff --git a/.clang-format b/.clang-format
index 18b16e0de0..fd8082991d 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 2ea2ee6ac9..fe179ca8f5 100644
--- a/providers/implementations/digests/sha2_prov.c
+++ b/providers/implementations/digests/sha2_prov.c
@@ -13,6 +13,7 @@
*/
#include "internal/deprecated.h"
+#include <openssl/byteorder.h>
#include <openssl/crypto.h>
#include <openssl/core_dispatch.h>
#include <openssl/evp.h>
@@ -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 14adc5507f..383fce3e43 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 66fc827db8..5e278ae260 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);