Skip to content

Commit de832df

Browse files
author
Ubuntu
committed
Support non-empty context strings in ML-DSA EVP sign/verify
Plumb FIPS 204 context strings through the EVP_DigestSign/EVP_DigestVerify code path for ML-DSA. The lower-level ml_dsa_*_sign/verify functions already accept ctx_string parameters but they were hardcoded to (NULL, 0) in the EVP layer. Changes: - Extend PQDSA_PKEY_CTX with a context[255] buffer and context_len - Add pkey_pqdsa_ctrl handling EVP_PKEY_CTRL_SIGNING_CONTEXT and EVP_PKEY_CTRL_GET_SIGNING_CONTEXT (reusing the existing generic EVP_PKEY_CTX_set_signature_context API from Ed25519ph) - Add pkey_pqdsa_copy to support EVP_PKEY_CTX_dup with context state - Pass dctx->context/context_len to pqdsa_sign_message and pqdsa_verify_message instead of NULL, 0 - Update Wycheproof test helpers to use EVP_PKEY_CTX_set_signature_context via EVP_DigestSign/Verify instead of manually computing ExternalMu - Add ContextString unit test covering: round-trip sign+verify with context, mismatched context failure, empty-context backward compatibility, >255 byte rejection, and max-length (255 byte) acceptance All three ML-DSA variants (44, 65, 87) are covered. Default behavior (empty context string) is unchanged.
1 parent a75e930 commit de832df

3 files changed

Lines changed: 168 additions & 104 deletions

File tree

crypto/evp_extra/p_pqdsa_test.cc

Lines changed: 104 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -2061,6 +2061,92 @@ TEST_P(PQDSAParameterTest, SIGOperations) {
20612061
md_ctx_verify.Reset();
20622062
}
20632063

2064+
TEST_P(PQDSAParameterTest, ContextString) {
2065+
// ---- 1. Setup: generate key pair ----
2066+
bssl::UniquePtr<EVP_PKEY> pkey(generate_key_pair(GetParam().nid));
2067+
ASSERT_TRUE(pkey);
2068+
2069+
std::vector<uint8_t> msg = {0x48, 0x65, 0x6c, 0x6c, 0x6f}; // "Hello"
2070+
uint8_t ctx_bytes[] = {0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74}; // "Context"
2071+
2072+
// ---- 2. Sign with context, verify with same context ----
2073+
bssl::ScopedEVP_MD_CTX md_ctx;
2074+
EVP_PKEY_CTX *pkey_ctx = nullptr;
2075+
ASSERT_TRUE(EVP_DigestSignInit(md_ctx.get(), &pkey_ctx, nullptr, nullptr,
2076+
pkey.get()));
2077+
ASSERT_TRUE(EVP_PKEY_CTX_set_signature_context(pkey_ctx, ctx_bytes,
2078+
sizeof(ctx_bytes)));
2079+
2080+
size_t sig_len = 0;
2081+
ASSERT_TRUE(EVP_DigestSign(md_ctx.get(), nullptr, &sig_len, msg.data(),
2082+
msg.size()));
2083+
std::vector<uint8_t> sig(sig_len);
2084+
ASSERT_TRUE(EVP_DigestSign(md_ctx.get(), sig.data(), &sig_len, msg.data(),
2085+
msg.size()));
2086+
2087+
bssl::ScopedEVP_MD_CTX md_ctx_verify;
2088+
EVP_PKEY_CTX *verify_pkey_ctx = nullptr;
2089+
ASSERT_TRUE(EVP_DigestVerifyInit(md_ctx_verify.get(), &verify_pkey_ctx,
2090+
nullptr, nullptr, pkey.get()));
2091+
ASSERT_TRUE(EVP_PKEY_CTX_set_signature_context(verify_pkey_ctx, ctx_bytes,
2092+
sizeof(ctx_bytes)));
2093+
ASSERT_TRUE(EVP_DigestVerify(md_ctx_verify.get(), sig.data(), sig_len,
2094+
msg.data(), msg.size()));
2095+
2096+
// ---- 3. Mismatched context string causes verification failure ----
2097+
md_ctx_verify.Reset();
2098+
verify_pkey_ctx = nullptr;
2099+
uint8_t wrong_ctx[] = {0x57, 0x72, 0x6f, 0x6e, 0x67}; // "Wrong"
2100+
ASSERT_TRUE(EVP_DigestVerifyInit(md_ctx_verify.get(), &verify_pkey_ctx,
2101+
nullptr, nullptr, pkey.get()));
2102+
ASSERT_TRUE(EVP_PKEY_CTX_set_signature_context(verify_pkey_ctx, wrong_ctx,
2103+
sizeof(wrong_ctx)));
2104+
ASSERT_FALSE(EVP_DigestVerify(md_ctx_verify.get(), sig.data(), sig_len,
2105+
msg.data(), msg.size()));
2106+
2107+
// ---- 4. Verify with no context fails for signature made with context ----
2108+
md_ctx_verify.Reset();
2109+
ASSERT_TRUE(EVP_DigestVerifyInit(md_ctx_verify.get(), nullptr, nullptr,
2110+
nullptr, pkey.get()));
2111+
ASSERT_FALSE(EVP_DigestVerify(md_ctx_verify.get(), sig.data(), sig_len,
2112+
msg.data(), msg.size()));
2113+
2114+
// ---- 5. Default (empty context) remains unchanged ----
2115+
md_ctx.Reset();
2116+
ASSERT_TRUE(EVP_DigestSignInit(md_ctx.get(), nullptr, nullptr, nullptr,
2117+
pkey.get()));
2118+
std::vector<uint8_t> sig_no_ctx(sig_len);
2119+
size_t sig_no_ctx_len = sig_len;
2120+
ASSERT_TRUE(EVP_DigestSign(md_ctx.get(), sig_no_ctx.data(), &sig_no_ctx_len,
2121+
msg.data(), msg.size()));
2122+
2123+
md_ctx_verify.Reset();
2124+
ASSERT_TRUE(EVP_DigestVerifyInit(md_ctx_verify.get(), nullptr, nullptr,
2125+
nullptr, pkey.get()));
2126+
ASSERT_TRUE(EVP_DigestVerify(md_ctx_verify.get(), sig_no_ctx.data(),
2127+
sig_no_ctx_len, msg.data(), msg.size()));
2128+
2129+
// ---- 6. Context string > 255 bytes is rejected ----
2130+
md_ctx.Reset();
2131+
pkey_ctx = nullptr;
2132+
ASSERT_TRUE(EVP_DigestSignInit(md_ctx.get(), &pkey_ctx, nullptr, nullptr,
2133+
pkey.get()));
2134+
uint8_t long_ctx[256];
2135+
OPENSSL_memset(long_ctx, 0x41, sizeof(long_ctx));
2136+
ASSERT_FALSE(EVP_PKEY_CTX_set_signature_context(pkey_ctx, long_ctx,
2137+
sizeof(long_ctx)));
2138+
2139+
// ---- 7. Max length context (255 bytes) is accepted ----
2140+
md_ctx.Reset();
2141+
pkey_ctx = nullptr;
2142+
ASSERT_TRUE(EVP_DigestSignInit(md_ctx.get(), &pkey_ctx, nullptr, nullptr,
2143+
pkey.get()));
2144+
uint8_t max_ctx[255];
2145+
OPENSSL_memset(max_ctx, 0x42, sizeof(max_ctx));
2146+
ASSERT_TRUE(EVP_PKEY_CTX_set_signature_context(pkey_ctx, max_ctx,
2147+
sizeof(max_ctx)));
2148+
}
2149+
20642150
TEST_P(PQDSAParameterTest, ParsePublicKey) {
20652151
// Test the example public key kPublicKey encodes correctly as kPublicKeySPKI
20662152
// Public key version of d2i_PrivateKey as part of the EVPExtraTest Gtest
@@ -2551,125 +2637,47 @@ INSTANTIATE_TEST_SUITE_P(
25512637
return params.param.name;
25522638
});
25532639

2554-
// ComputeMLDSAExternalMu formats |pk|, |ctx|, and |msg_ctx| to the ExternalMu
2555-
// format expected by |EVP_PKEY_verify|. For more information, see the docstring
2556-
// for |EVP_PKEY_verify|.
2557-
//
2558-
// It returns true on success and false on error.
2559-
static bool ComputeMLDSAExternalMu(const std::vector<uint8_t> &pk,
2560-
const std::vector<uint8_t> &msg_ctx,
2561-
const std::vector<uint8_t> &msg,
2562-
std::vector<uint8_t> &mu_out) {
2563-
// Ensure |msg_ctx| <= 255 to be representable by a uint8
2564-
// https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf#algorithm.4
2565-
if (msg_ctx.size() > 255) {
2566-
return false;
2567-
}
2568-
2569-
// Compute tr = SHAKE256(pk)
2570-
// https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf#algorithm.8
2571-
std::vector<uint8_t> tr(64);
2572-
bssl::ScopedEVP_MD_CTX md_ctx_pk;
2573-
if (!EVP_DigestInit_ex(md_ctx_pk.get(), EVP_shake256(), nullptr) ||
2574-
!EVP_DigestUpdate(md_ctx_pk.get(), pk.data(), pk.size()) ||
2575-
!EVP_DigestFinalXOF(md_ctx_pk.get(), tr.data(), tr.size())) {
2576-
return false;
2577-
}
2578-
2579-
// Compute mu = SHAKE256(tr || 0 || |msg_ctx| || msg_ctx || M)
2580-
// https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf#subsection.5.3
2581-
mu_out.resize(64);
2582-
bssl::ScopedEVP_MD_CTX md_ctx_mu;
2583-
if (!EVP_DigestInit_ex(md_ctx_mu.get(), EVP_shake256(), nullptr) ||
2584-
!EVP_DigestUpdate(md_ctx_mu.get(), tr.data(), tr.size())) {
2585-
return false;
2586-
}
2587-
2588-
// Add 0 byte for "pure" mode, distinguished from "pre-hash" mode
2589-
// https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf#subsection.5.4
2590-
uint8_t zero = 0;
2591-
if (!EVP_DigestUpdate(md_ctx_mu.get(), &zero, 1)) {
2592-
return false;
2593-
}
2594-
2595-
// Add |msg_ctx|, msg_ctx, and msg, in that order
2596-
uint8_t ctx_len = static_cast<uint8_t>(msg_ctx.size());
2597-
if (!EVP_DigestUpdate(md_ctx_mu.get(), &ctx_len, 1)) {
2598-
return false;
2599-
}
2600-
if (!msg_ctx.empty() &&
2601-
!EVP_DigestUpdate(md_ctx_mu.get(), msg_ctx.data(), msg_ctx.size())) {
2602-
return false;
2603-
}
2604-
if (!EVP_DigestUpdate(md_ctx_mu.get(), msg.data(), msg.size()) ||
2605-
!EVP_DigestFinalXOF(md_ctx_mu.get(), mu_out.data(), mu_out.size())) {
2606-
return false;
2607-
}
2608-
2609-
return true;
2610-
}
2611-
26122640
// VerifyMLDSAWithContext verifies that |sig| is a valid signature for |msg|
2613-
// with context |msg_ctx|. We need this wrapper because |EVP_DigestVerify| does
2614-
// not support verification with contexts.
2641+
// with context |msg_ctx| using the EVP_DigestVerify path with
2642+
// EVP_PKEY_CTX_set_signature_context.
26152643
//
26162644
// It returns one on success and zero on error.
26172645
static int VerifyMLDSAWithContext(EVP_PKEY *pkey,
26182646
const std::vector<uint8_t> &pk,
26192647
const std::vector<uint8_t> &sig,
26202648
const std::vector<uint8_t> &msg,
26212649
const std::vector<uint8_t> &msg_ctx) {
2622-
// If there's a non-empty context string, do ExternalMu verification
2623-
if (!msg_ctx.empty()) {
2624-
std::vector<uint8_t> mu;
2625-
if (!ComputeMLDSAExternalMu(pk, msg_ctx, msg, mu)) {
2626-
return 0;
2627-
}
2628-
bssl::UniquePtr<EVP_PKEY_CTX> pkey_ctx(EVP_PKEY_CTX_new(pkey, nullptr));
2629-
if (!pkey_ctx || !EVP_PKEY_verify_init(pkey_ctx.get())) {
2630-
return 0;
2631-
}
2632-
return EVP_PKEY_verify(pkey_ctx.get(), sig.data(), sig.size(), mu.data(),
2633-
mu.size());
2634-
}
2635-
2636-
// Otherwise, do standard verification
26372650
bssl::ScopedEVP_MD_CTX md_ctx;
2638-
if (!EVP_DigestVerifyInit(md_ctx.get(), nullptr, nullptr, nullptr, pkey)) {
2651+
EVP_PKEY_CTX *pkey_ctx = nullptr;
2652+
if (!EVP_DigestVerifyInit(md_ctx.get(), &pkey_ctx, nullptr, nullptr, pkey)) {
2653+
return 0;
2654+
}
2655+
if (!msg_ctx.empty() &&
2656+
!EVP_PKEY_CTX_set_signature_context(pkey_ctx, msg_ctx.data(),
2657+
msg_ctx.size())) {
26392658
return 0;
26402659
}
26412660
return EVP_DigestVerify(md_ctx.get(), sig.data(), sig.size(), msg.data(),
26422661
msg.size());
26432662
}
26442663

26452664
// SignMLDSAWithContext produces a signature |sig| for message |msg| with
2646-
// context |msg_ctx|. We need this wrapper because |EVP_DigestSign| does not
2647-
// support signing with contexts.
2665+
// context |msg_ctx| using the EVP_DigestSign path with
2666+
// EVP_PKEY_CTX_set_signature_context.
26482667
//
26492668
// It returns one on success and zero on error.
26502669
static int SignMLDSAWithContext(EVP_PKEY *pkey, std::vector<uint8_t> &sig,
26512670
const std::vector<uint8_t> &pk,
26522671
const std::vector<uint8_t> &msg,
26532672
const std::vector<uint8_t> &msg_ctx) {
2654-
// If there's a non-empty context string, do ExternalMu signing
2655-
if (!msg_ctx.empty()) {
2656-
std::vector<uint8_t> mu;
2657-
if (!ComputeMLDSAExternalMu(pk, msg_ctx, msg, mu)) {
2658-
return 0;
2659-
}
2660-
bssl::UniquePtr<EVP_PKEY_CTX> pkey_ctx(EVP_PKEY_CTX_new(pkey, nullptr));
2661-
if (!pkey_ctx || !EVP_PKEY_sign_init(pkey_ctx.get())) {
2662-
return 0;
2663-
}
2664-
2665-
size_t sig_len = sig.size();
2666-
return EVP_PKEY_sign(pkey_ctx.get(), sig.data(), &sig_len, mu.data(),
2667-
mu.size());
2668-
}
2669-
2670-
// Otherwise, do standard signing
26712673
bssl::ScopedEVP_MD_CTX md_ctx;
2672-
if (!EVP_DigestSignInit(md_ctx.get(), nullptr, nullptr, nullptr, pkey)) {
2674+
EVP_PKEY_CTX *pkey_ctx = nullptr;
2675+
if (!EVP_DigestSignInit(md_ctx.get(), &pkey_ctx, nullptr, nullptr, pkey)) {
2676+
return 0;
2677+
}
2678+
if (!msg_ctx.empty() &&
2679+
!EVP_PKEY_CTX_set_signature_context(pkey_ctx, msg_ctx.data(),
2680+
msg_ctx.size())) {
26732681
return 0;
26742682
}
26752683
size_t sig_len = sig.size();

crypto/fipsmodule/evp/p_pqdsa.c

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
typedef struct {
1717
const PQDSA *pqdsa;
18+
uint8_t context[255];
19+
size_t context_len;
1820
} PQDSA_PKEY_CTX;
1921

2022
static int pkey_pqdsa_init(EVP_PKEY_CTX *ctx) {
@@ -33,6 +35,58 @@ static void pkey_pqdsa_cleanup(EVP_PKEY_CTX *ctx) {
3335
OPENSSL_free(ctx->data);
3436
}
3537

38+
static int pkey_pqdsa_copy(EVP_PKEY_CTX *dst, EVP_PKEY_CTX *src) {
39+
if (!pkey_pqdsa_init(dst)) {
40+
return 0;
41+
}
42+
43+
PQDSA_PKEY_CTX *dctx = dst->data;
44+
PQDSA_PKEY_CTX *sctx = src->data;
45+
GUARD_PTR(dctx);
46+
GUARD_PTR(sctx);
47+
48+
dctx->pqdsa = sctx->pqdsa;
49+
OPENSSL_memcpy(dctx->context, sctx->context, sizeof(sctx->context));
50+
dctx->context_len = sctx->context_len;
51+
52+
return 1;
53+
}
54+
55+
static int pkey_pqdsa_ctrl(EVP_PKEY_CTX *ctx, int type, int p1, void *p2) {
56+
GUARD_PTR(ctx);
57+
PQDSA_PKEY_CTX *dctx = (PQDSA_PKEY_CTX *)ctx->data;
58+
switch (type) {
59+
case EVP_PKEY_CTRL_SIGNING_CONTEXT: {
60+
EVP_PKEY_CTX_SIGNATURE_CONTEXT_PARAMS *params = p2;
61+
if (!params || !dctx || params->context_len > sizeof(dctx->context)) {
62+
OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_BUFFER_SIZE);
63+
return 0;
64+
}
65+
OPENSSL_memcpy(dctx->context, params->context, params->context_len);
66+
dctx->context_len = params->context_len;
67+
break;
68+
}
69+
case EVP_PKEY_CTRL_GET_SIGNING_CONTEXT: {
70+
EVP_PKEY_CTX_SIGNATURE_CONTEXT_PARAMS *params = p2;
71+
if (!params || !dctx) {
72+
return 0;
73+
}
74+
if (dctx->context_len == 0) {
75+
params->context = NULL;
76+
params->context_len = 0;
77+
} else {
78+
params->context = dctx->context;
79+
params->context_len = dctx->context_len;
80+
}
81+
return 1;
82+
}
83+
default:
84+
OPENSSL_PUT_ERROR(EVP, EVP_R_COMMAND_NOT_SUPPORTED);
85+
return 0;
86+
}
87+
return 1;
88+
}
89+
3690
static int pkey_pqdsa_keygen(EVP_PKEY_CTX *ctx, EVP_PKEY *pkey) {
3791
GUARD_PTR(ctx);
3892
PQDSA_PKEY_CTX *dctx = ctx->data;
@@ -108,7 +162,7 @@ static int pkey_pqdsa_sign_generic(EVP_PKEY_CTX *ctx, uint8_t *sig,
108162

109163
// RAW sign mode
110164
if (!sign_digest) {
111-
if (!pqdsa->method->pqdsa_sign_message(key->private_key, sig, sig_len, message, message_len, NULL, 0)) {
165+
if (!pqdsa->method->pqdsa_sign_message(key->private_key, sig, sig_len, message, message_len, dctx->context_len > 0 ? dctx->context : NULL, dctx->context_len)) {
112166
OPENSSL_PUT_ERROR(EVP, ERR_R_INTERNAL_ERROR);
113167
return 0;
114168
}
@@ -187,7 +241,7 @@ static int pkey_pqdsa_verify_generic(EVP_PKEY_CTX *ctx, const uint8_t *sig,
187241
// RAW verify mode
188242
if(!verify_digest) {
189243
if (sig_len != pqdsa->signature_len ||
190-
!pqdsa->method->pqdsa_verify_message(key->public_key, sig, sig_len, message, message_len, NULL, 0)) {
244+
!pqdsa->method->pqdsa_verify_message(key->public_key, sig, sig_len, message, message_len, dctx->context_len > 0 ? dctx->context : NULL, dctx->context_len)) {
191245
OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_SIGNATURE);
192246
return 0;
193247
}
@@ -357,7 +411,7 @@ EVP_PKEY *EVP_PKEY_pqdsa_new_raw_private_key(int nid, const uint8_t *in, size_t
357411
DEFINE_METHOD_FUNCTION(EVP_PKEY_METHOD, EVP_PKEY_pqdsa_pkey_meth) {
358412
out->pkey_id = EVP_PKEY_PQDSA;
359413
out->init = pkey_pqdsa_init;
360-
out->copy = NULL;
414+
out->copy = pkey_pqdsa_copy;
361415
out->cleanup = pkey_pqdsa_cleanup;
362416
out->keygen = pkey_pqdsa_keygen;
363417
out->sign_init = NULL;
@@ -371,7 +425,7 @@ DEFINE_METHOD_FUNCTION(EVP_PKEY_METHOD, EVP_PKEY_pqdsa_pkey_meth) {
371425
out->decrypt = NULL;
372426
out->derive = NULL;
373427
out->paramgen = NULL;
374-
out->ctrl = NULL;
428+
out->ctrl = pkey_pqdsa_ctrl;
375429
out->ctrl_str = NULL;
376430
out->keygen_deterministic = NULL;
377431
out->encapsulate_deterministic = NULL;

include/openssl/evp.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -833,8 +833,10 @@ OPENSSL_EXPORT int EVP_PKEY_CTX_get_signature_md(EVP_PKEY_CTX *ctx,
833833
// be copied to an internal buffer allowing for the caller to free it
834834
// afterwards.
835835
//
836-
// EVP_PKEY_ED25519PH is the only key type that currently supports setting a
837-
// a signature context that is used in computing the HashEdDSA signature.
836+
// EVP_PKEY_ED25519PH and EVP_PKEY_PQDSA are the key types that currently
837+
// support setting a signature context. For Ed25519ph, the context is used in
838+
// computing the HashEdDSA signature. For ML-DSA (PQDSA), the context string is
839+
// used per FIPS 204 sections 5.2-5.3. The maximum context length is 255 bytes.
838840
//
839841
// It returns one on success or zero on error.
840842
OPENSSL_EXPORT int EVP_PKEY_CTX_set_signature_context(EVP_PKEY_CTX *ctx,
@@ -845,8 +847,8 @@ OPENSSL_EXPORT int EVP_PKEY_CTX_set_signature_context(EVP_PKEY_CTX *ctx,
845847
// buffer containing the signing context octet string (which may be NULL) and
846848
// writes the length to |*context_len|.
847849
//
848-
// EVP_PKEY_ED25519PH is the only key type that currently supports retrieving a
849-
// a signature context that is used in computing the HashEdDSA signature.
850+
// EVP_PKEY_ED25519PH and EVP_PKEY_PQDSA are the key types that currently
851+
// support retrieving a signature context.
850852
//
851853
// It returns one on success or zero on error.
852854
OPENSSL_EXPORT int EVP_PKEY_CTX_get0_signature_context(EVP_PKEY_CTX *ctx,

0 commit comments

Comments
 (0)