Skip to content

Commit

Permalink
Add ML-KEM768 KATs from BoringSSL
Browse files Browse the repository at this point in the history
Add KATs for ML-KEM768 under CCLA from https://boringssl.googlesource.com/boringssl/

These KATs test key generation, encapsulation, and decapsulation for the ML-KEM768 provider.

Relevant notes:
- Added functionality to the ML-KEM provider to export/import. These may not be fully implemented yet (see openssl#25885)
- Exposed some more low-level ML-KEM API's to allow for deterministic encapsulation/key generation
- Actually run 'mlkem_internal_test' with `make test`
- Fixes openssl/private#704
  • Loading branch information
andrewkdinh committed Nov 14, 2024
1 parent adcb259 commit 51147bf
Show file tree
Hide file tree
Showing 16 changed files with 5,185 additions and 203 deletions.
127 changes: 100 additions & 27 deletions crypto/mlkem/mlkem768.c
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,6 @@ static ossl_inline size_t encoded_public_key_size(int rank)
return encoded_vector_size(rank) + /* sizeof(rho)= */ 32;
}

/*
* MLKEM_ENCAP_ENTROPY is the number of bytes of uniformly random entropy
* necessary to encapsulate a secret. The entropy will be leaked to the
* decapsulating party.
*/
# define MLKEM_ENCAP_ENTROPY 32

/* MD&XOF handles */

/* Cache mgmt as per https://github.com/openssl/private/issues/700 */
Expand Down Expand Up @@ -893,24 +886,6 @@ static int mlkem_marshal_public_key(uint8_t *out,
return 1;
}

int ossl_mlkem768_recreate_public_key(const uint8_t *encoded_public_key,
ossl_mlkem768_public_key *ext_pub,
ossl_mlkem_ctx *mlkem_ctx)
{
struct public_key_RANK768 *pub = public_key_768_from_external(ext_pub);

print_hex(encoded_public_key, OSSL_MLKEM768_PUBLIC_KEY_BYTES, "Encoded key");
if (!vector_decode(&pub->t, encoded_public_key, kLog2Prime))
return 0;
memcpy(pub->rho, encoded_public_key + encoded_vector_size(RANK768), sizeof(pub->rho));
if (!matrix_expand(&pub->m, pub->rho, mlkem_ctx)
|| (!hash_h(pub->public_key_hash, encoded_public_key,
encoded_public_key_size(RANK768), mlkem_ctx)))
return 0;
print_hex((uint8_t *)pub, sizeof(public_key_RANK768), "recreated PK");
return 1;
}

static int mlkem_generate_key_external_seed(uint8_t *out_encoded_public_key,
private_key_RANK768 *priv,
const uint8_t *seed,
Expand Down Expand Up @@ -1082,7 +1057,6 @@ static int mlkem_encap_external_entropy(uint8_t *out_ciphertext,
* out_shared_secret[ossl_mlkem768_SHARED_SECRET_BYTES],
* entropy[MLKEM_ENCAP_ENTROPY])
*/
static
int ossl_mlkem768_encap_external_entropy(uint8_t *out_ciphertext,
uint8_t *out_shared_secret,
const ossl_mlkem768_public_key *public_key,
Expand All @@ -1109,7 +1083,8 @@ int ossl_mlkem768_encap(uint8_t *out_ciphertext,

/* TODO(ML-KEM): Review requested randomness strength */
if (RAND_bytes_ex(mlkem_ctx->libctx, entropy, MLKEM_ENCAP_ENTROPY, 256) != 1
|| !ossl_mlkem768_encap_external_entropy(out_ciphertext, out_shared_secret, public_key,
|| !ossl_mlkem768_encap_external_entropy(out_ciphertext,
out_shared_secret, public_key,
entropy, mlkem_ctx))
return 0;
print_hex((uint8_t *)public_key, sizeof(ossl_mlkem768_public_key), "PK");
Expand Down Expand Up @@ -1194,4 +1169,102 @@ int ossl_mlkem768_decap(uint8_t *out_shared_secret,
return mlkem_decap(out_shared_secret, ciphertext, priv, mlkem_ctx);
}

int ossl_mlkem768_marshal_public_key(uint8_t *out,
const struct ossl_mlkem768_public_key *public_key)
{
struct public_key_RANK768 *pub = public_key_768_from_external(public_key);

return mlkem_marshal_public_key(out, pub);
}

/*
* mlkem_parse_public_key_no_hash parses |in| into |pub| but doesn't calculate
* the value of |pub->public_key_hash|.
*/
static int mlkem_parse_public_key_no_hash(public_key_RANK768 *pub, uint8_t *in,
ossl_mlkem_ctx *mlkem_ctx)
{
if (vector_decode(&pub->t, in, kLog2Prime) != 1)
return 0;
memcpy(pub->rho, in + encoded_vector_size(RANK768), sizeof(pub->rho));
matrix_expand(&pub->m, pub->rho, mlkem_ctx);
return 1;
}

static int mlkem_parse_public_key(public_key_RANK768 *pub, uint8_t *in,
ossl_mlkem_ctx *mlkem_ctx)
{
if (!mlkem_parse_public_key_no_hash(pub, in, mlkem_ctx)
|| !hash_h(pub->public_key_hash, in, OSSL_MLKEM768_PUBLIC_KEY_BYTES,
mlkem_ctx))
return 0;
return 1;
}

int ossl_mlkem768_parse_public_key(struct ossl_mlkem768_public_key *public_key,
uint8_t *in, ossl_mlkem_ctx *mlkem_ctx)
{
struct public_key_RANK768 *pub = public_key_768_from_external(public_key);

return mlkem_parse_public_key(pub, in, mlkem_ctx);
}

static int mlkem_marshal_private_key(uint8_t *out,
const struct private_key_RANK768 *priv)
{
uint8_t *out_curr = out;

vector_encode(out_curr, &priv->s, kLog2Prime);
out_curr += encoded_vector_size(RANK768);

if (mlkem_marshal_public_key(out_curr, &priv->pub) != 1)
return 0;
out_curr += OSSL_MLKEM768_PUBLIC_KEY_BYTES;

memcpy(out_curr, priv->pub.public_key_hash,
sizeof(priv->pub.public_key_hash));
out_curr += sizeof(priv->pub.public_key_hash);

memcpy(out_curr, priv->fo_failure_secret, sizeof(priv->fo_failure_secret));
return 1;
}

int ossl_mlkem768_marshal_private_key(uint8_t *out,
struct ossl_mlkem768_private_key *private_key)
{
struct private_key_RANK768 *priv = private_key_768_from_external(private_key);

return mlkem_marshal_private_key(out, priv);
}

static int mlkem_parse_private_key(private_key_RANK768 *priv, uint8_t *in,
ossl_mlkem_ctx *mlkem_ctx)
{
uint8_t *in_curr = in;

if (!vector_decode(&priv->s, in_curr, kLog2Prime))
return 0;
in_curr += encoded_vector_size(RANK768);

if (!mlkem_parse_public_key_no_hash(&priv->pub, in_curr, mlkem_ctx))
return 0;
in_curr += OSSL_MLKEM768_PUBLIC_KEY_BYTES;

memcpy(priv->pub.public_key_hash, in_curr,
sizeof(priv->pub.public_key_hash));
in_curr += sizeof(priv->pub.public_key_hash);

memcpy(priv->fo_failure_secret, in_curr, sizeof(priv->fo_failure_secret));
return 1;
}

int ossl_mlkem768_parse_private_key(ossl_mlkem768_private_key *out_private_key,
uint8_t *in, ossl_mlkem_ctx *mlkem_ctx)
{
struct private_key_RANK768 *priv =
private_key_768_from_external(out_private_key);

return mlkem_parse_private_key(priv, in, mlkem_ctx);
}

#endif /* OPENSSL_NO_MLKEM */
125 changes: 89 additions & 36 deletions include/crypto/mlkem.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,44 +131,97 @@ extern "C" {
/* ossl_mlkem768_SHARED_SECRET_BYTES is the number of bytes in an ML-KEM shared secret. */
# define OSSL_MLKEM768_SHARED_SECRET_BYTES 32

/*
* ossl_mlkem768_encap encrypts a random shared secret for |public_key|, writes the
* ciphertext to |out_ciphertext|, and writes the random shared secret to
* |out_shared_secret|.
* it is assumed out_ciphertext has been allocated ossl_mlkem768_CIPHERTEXT_BYTES bytes
* and out_shared_secret has been allocated MLKEM_SHARED_SECRET_BYTES bytes
*/
int ossl_mlkem768_encap(uint8_t *out_ciphertext,
uint8_t *out_shared_secret,
const ossl_mlkem768_public_key *public_key,
ossl_mlkem_ctx *mlkem_ctx);
/*
* ossl_mlkem768_encap encrypts a random shared secret for |public_key|, writes
* the ciphertext to |out_ciphertext|, and writes the random shared secret to
* |out_shared_secret|. It is assumed out_ciphertext has been allocated
* ossl_mlkem768_CIPHERTEXT_BYTES bytes and out_shared_secret has been allocated
* MLKEM_SHARED_SECRET_BYTES bytes
*/
int ossl_mlkem768_encap(uint8_t *out_ciphertext,
uint8_t *out_shared_secret,
const ossl_mlkem768_public_key *public_key,
ossl_mlkem_ctx *mlkem_ctx);

/*
* ossl_mlkem768_decap decrypts a shared secret from |ciphertext| using |private_key|
* and writes it to |out_shared_secret|. If |ciphertext_len| is incorrect it
* returns 0, otherwise it returns 1. If |ciphertext| is invalid (but of the
* correct length), |out_shared_secret| is filled with a key that will always be
* the same for the same |ciphertext| and |private_key|, but which appears to be
* random unless one has access to |private_key|. These alternatives occur in
* constant time. Any subsequent symmetric encryption using |out_shared_secret|
* must use an authenticated encryption scheme in order to discover the
* decapsulation failure.
* it is assumed out_shared_secret has been allocated MLKEM_SHARED_SECRET_BYTES bytes
*/
int ossl_mlkem768_decap(uint8_t *out_shared_secret,
const uint8_t *ciphertext, size_t ciphertext_len,
const ossl_mlkem768_private_key *private_key,
ossl_mlkem_ctx *mlkem_ctx);
/*
* MLKEM_ENCAP_ENTROPY is the number of bytes of uniformly random entropy
* necessary to encapsulate a secret. The entropy will be leaked to the
* decapsulating party.
*/
# define MLKEM_ENCAP_ENTROPY 32

/*
* ossl_mlkem768_recreate_public_key recreates a fully formed ossl_mlkem768_public_key
* from an input |encoded_public_key| of size ossl_mlkem768_PUBLIC_KEY_BYTES.
* |pub| is expected to point to an allocated memory area of
* sizeof(ossl_mlkem768_public_key)
*/
int ossl_mlkem768_recreate_public_key(const uint8_t *encoded_public_key,
ossl_mlkem768_public_key *pub,
ossl_mlkem_ctx *mlkem_ctx);
/*
* The same as ossl_mlkem768_encap except this also uses a given entropy for
* deterministic output.
*/
int ossl_mlkem768_encap_external_entropy(uint8_t *out_ciphertext,
uint8_t *out_shared_secret,
const ossl_mlkem768_public_key *public_key,
const uint8_t *entropy,
ossl_mlkem_ctx *mlkem_ctx);

/*
* ossl_mlkem768_decap decrypts a shared secret from |ciphertext| using
* |private_key| and writes it to |out_shared_secret|. If |ciphertext_len| is
* incorrect it returns 0, otherwise it returns 1. If |ciphertext| is invalid
* (but of the correct length), |out_shared_secret| is filled with a key that
* will always be the same for the same |ciphertext| and |private_key|, but
* which appears to be random unless one has access to |private_key|. These
* alternatives occur in constant time. Any subsequent symmetric encryption
* using |out_shared_secret| must use an authenticated encryption scheme in
* order to discover the decapsulation failure. it is assumed out_shared_secret
* has been allocated MLKEM_SHARED_SECRET_BYTES bytes
*/
int ossl_mlkem768_decap(uint8_t *out_shared_secret,
const uint8_t *ciphertext, size_t ciphertext_len,
const ossl_mlkem768_private_key *private_key,
ossl_mlkem_ctx *mlkem_ctx);

/* Serialisation of keys. */

/*
* ossl_mlkem768_marshal_public_key serializes |public_key| to |out| in the
* standard format for ML-KEM-768 public keys. It returns one on success or zero
* on allocation error.
*/
int ossl_mlkem768_marshal_public_key(uint8_t *out,
const struct ossl_mlkem768_public_key *public_key);

/*
* ossl_mlkem768_parse_public_key parses a public key, in the format generated
* by |ossl_mlkem768_marshal_public_key|, from |in| and writes the result to
* |out_public_key|. It returns one on success or zero on parse error.
*/
int ossl_mlkem768_parse_public_key(struct ossl_mlkem768_public_key *out_public_key,
uint8_t *in,
ossl_mlkem_ctx *mlkem_ctx);

/*
* MLKEM768_PRIVATE_KEY_BYTES is the length of the data produced by
* |MLKEM768_marshal_private_key|.
*/
# define ossl_mlkem768_PRIVATE_KEY_BYTES 2400

/*
* ossl_mlkem768_marshal_public_key serializes |private_key| to |out| in the
* standard format for ML-KEM-768 private keys. It returns one on success or
* zero on allocation error. This format is verbose and should be avoided.
* Private keys should be stored as seeds and parsed using
* |ossl_mlkem768_private_key_from_seed|.
*/
int ossl_mlkem768_marshal_private_key(uint8_t *out,
struct ossl_mlkem768_private_key *private_key);

/*
* ossl_mlkem768_parse_private_key parses a private key, in NIST's format for
* private keys, from |in| and writes the result to |out_private_key|. It
* returns one on success or zero on parse error or if there are trailing bytes
* in |in|. This format is verbose and should be avoided. Private keys should be
* stored as seeds and parsed using |ossl_mlkem768_private_key_from_seed|.
*/
int ossl_mlkem768_parse_private_key(struct ossl_mlkem768_private_key *out_private_key,
uint8_t *in,
ossl_mlkem_ctx *mlkem_ctx);

# endif /* OPENSSL_NO_MLKEM */

Expand Down
5 changes: 2 additions & 3 deletions providers/implementations/include/prov/mlkem.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,10 @@

typedef struct mlkem768_key_st {
int keytype;
ossl_mlkem768_private_key seckey;
ossl_mlkem768_private_key privkey;
ossl_mlkem768_public_key pubkey;
uint8_t *encoded_pubkey;
int pubkey_initialized;
int seckey_initialized;
uint8_t *encoded_privkey;
ossl_mlkem_ctx *mlkem_ctx;
void *provctx;
} MLKEM768_KEY;
Expand Down
Loading

0 comments on commit 51147bf

Please sign in to comment.