@@ -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+
20642150TEST_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.
26172645static 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.
26502669static 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 ();
0 commit comments