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)
This commit is contained in:
Simo Sorce
2025-10-09 18:27:42 -04:00
committed by Dmitry Belyavskiy
parent c1f66c1ec3
commit 1afb05b603
4 changed files with 324 additions and 21 deletions
+1
View File
@@ -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"
+246 -21
View File
@@ -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)
@@ -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);
+62
View File
@@ -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);