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);