Two more private key checks.

- When a PKCS#8 has both seed and key cross check the implicit
  rejection value |z|

- When an import (EVP_PKEY_fromdata call) provides both a private
  and public key, fail if the redundant public key does not match
  the copy in the private key.

Reviewed-by: Shane Lontis <shane.lontis@oracle.com>
Reviewed-by: Tim Hudson <tjh@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/26656)
This commit is contained in:
Viktor Dukhovni 2025-02-07 19:16:33 +11:00 committed by Tomas Mraz
parent 2ea9903c16
commit b3dd681f07
4 changed files with 43 additions and 14 deletions

View file

@ -146,11 +146,12 @@ This format represents B<PKCS#8> objects in which both the FIPS 203 64-byte
B<(d, z)> seed and the decapsulation key B<dk> are present in the private key
as part of the DER encoding of the ASN.1 sequence:
PrivateKey ::= SEQUENCE {
seed OCTET STRING OPTIONAL,
expandedKey [1] IMPLICIT OCTET STRING OPTIONAL }
(WITH COMPONENTS {..., seed PRESENT } |
WITH COMPONENTS {..., expandedKey PRESENT })
ML-KEM-PrivateKey ::= CHOICE {
seed [0] IMPLICIT OCTET STRING (SIZE (64)),
expandedKey OCTET STRING (SIZE (1632 | 2400 | 3168)),
both SEQUENCE {
seed OCTET STRING (SIZE (64)),
expandedKey OCTET STRING (SIZE (1632 | 2400 | 3168)) } }
If the C<seed-priv> format is not included in the list, this format will not be
recognised on input.

View file

@ -21,11 +21,11 @@
* corresponding to the "either or both" variants of:
*
* ML-KEM-PrivateKey ::= CHOICE {
* seed [0] IMPLICIT OCTET STRING SIZE (64),
* expandedKey OCTET STRING SIZE (1632 | 2400 | 3168)
* seed [0] IMPLICIT OCTET STRING (SIZE (64)),
* expandedKey OCTET STRING (SIZE (1632 | 2400 | 3168)),
* both SEQUENCE {
* seed OCTET STRING SIZE (64),
* expandedKey OCTET STRING SIZE (1632 | 2400 | 3168) } }
* seed OCTET STRING (SIZE (64)),
* expandedKey OCTET STRING (SIZE (1632 | 2400 | 3168)) } }
*
* one more for a historical OQS encoding:
*

View file

@ -36,11 +36,11 @@ typedef struct {
* corresponding to the "either or both" variants of:
*
* ML-KEM-PrivateKey ::= CHOICE {
* seed [0] IMPLICIT OCTET STRING SIZE (64),
* expandedKey OCTET STRING SIZE (1632 | 2400 | 3168)
* seed [0] IMPLICIT OCTET STRING (SIZE (64)),
* expandedKey OCTET STRING (SIZE (1632 | 2400 | 3168)),
* both SEQUENCE {
* seed OCTET STRING SIZE (64),
* expandedKey OCTET STRING SIZE (1632 | 2400 | 3168) } }
* seed OCTET STRING (SIZE (64)),
* expandedKey OCTET STRING SIZE ((1632 | 2400 | 3168)) } }
*
* one more for a historical OQS encoding:
*

View file

@ -318,7 +318,7 @@ static int ml_kem_key_fromdata(ML_KEM_KEY *key,
{
const OSSL_PARAM *p = NULL;
const void *pubenc = NULL, *prvenc = NULL, *seedenc = NULL;
size_t publen = 0, prvlen = 0, seedlen = 0;
size_t publen = 0, prvlen = 0, seedlen = 0, puboff;
const ML_KEM_VINFO *v;
/* Invalid attempt to mutate a key, what is the right error to report? */
@ -370,6 +370,18 @@ static int ml_kem_key_fromdata(ML_KEM_KEY *key,
return 0;
}
/* Check any explicit public key against embedded value in private key */
if (publen > 0 && prvlen > 0) {
/* point to the ek offset in the DKpke||ek||H(ek)||z */
puboff = prvlen - ML_KEM_RANDOM_BYTES - ML_KEM_PKHASH_BYTES - publen;
if (memcmp(pubenc, (unsigned char *)prvenc + puboff, publen) != 0) {
ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_KEY,
"explicit %s public key does not match private",
v->algorithm_name);
return 0;
}
}
if (seedlen != 0 && (prvlen == 0 || key->prefer_seed))
return ossl_ml_kem_set_seed(seedenc, seedlen, key)
&& ossl_ml_kem_genkey(NULL, 0, key);
@ -427,6 +439,8 @@ void *ml_kem_load(const void *reference, size_t reference_sz)
{
ML_KEM_KEY *key = NULL;
uint8_t *encoded_dk;
uint8_t seed[ML_KEM_SEED_BYTES];
size_t zlen = ML_KEM_RANDOM_BYTES;
if (ossl_prov_is_running() && reference_sz == sizeof(key)) {
/* The contents of the reference is the address to our object */
@ -435,6 +449,20 @@ void *ml_kem_load(const void *reference, size_t reference_sz)
key->encoded_dk = NULL;
/* We grabbed, so we detach it */
*(ML_KEM_KEY **)reference = NULL;
/*
* Reject |z| mismatch between seed and key, the seed buffer holds |z|
* followed by |d|.
*/
if (encoded_dk != NULL
&& ossl_ml_kem_encode_seed(seed, sizeof(seed), key)
&& memcmp(seed + sizeof(seed) - zlen,
encoded_dk + key->vinfo->prvkey_bytes - zlen,
zlen) != 0) {
ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_KEY,
"private %s key implicit rejection secret does"
" not match seed", key->vinfo->algorithm_name);
goto err;
}
/* Generate the key now, if it holds only a stashed seed. */
if (ossl_ml_kem_have_seed(key) &&
(encoded_dk == NULL || key->prefer_seed)) {