447 lines
14 KiB
C
447 lines
14 KiB
C
/*
|
|
* Copyright (c) The mlkem-native project authors
|
|
* SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT
|
|
*/
|
|
|
|
/* References
|
|
* ==========
|
|
*
|
|
* - [FIPS140_3_IG]
|
|
* Implementation Guidance for FIPS 140-3 and the Cryptographic Module
|
|
* Validation Program
|
|
* National Institute of Standards and Technology
|
|
* https://csrc.nist.gov/projects/cryptographic-module-validation-program/fips-140-3-ig-announcements
|
|
*
|
|
* - [FIPS203]
|
|
* FIPS 203 Module-Lattice-Based Key-Encapsulation Mechanism Standard
|
|
* National Institute of Standards and Technology
|
|
* https://csrc.nist.gov/pubs/fips/203/final
|
|
*
|
|
* - [REF]
|
|
* CRYSTALS-Kyber C reference implementation
|
|
* Bos, Ducas, Kiltz, Lepoint, Lyubashevsky, Schanck, Schwabe, Seiler, Stehlé
|
|
* https://github.com/pq-crystals/kyber/tree/main/ref
|
|
*/
|
|
|
|
#include "kem.h"
|
|
|
|
#include "indcpa.h"
|
|
#include "randombytes.h"
|
|
#include "symmetric.h"
|
|
#include "verify.h"
|
|
|
|
/* Parameter set namespacing
|
|
* This is to facilitate building multiple instances
|
|
* of mlkem-native (e.g. with varying security levels)
|
|
* within a single compilation unit. */
|
|
#define mlk_check_pct MLK_ADD_PARAM_SET(mlk_check_pct) MLK_CONTEXT_PARAMETERS_2
|
|
/* End of parameter set namespacing */
|
|
|
|
/* Reference: Not implemented in the reference implementation @[REF]. */
|
|
MLK_EXTERNAL_API
|
|
MLK_MUST_CHECK_RETURN_VALUE
|
|
int mlk_kem_check_pk(const uint8_t pk[MLKEM_INDCCA_PUBLICKEYBYTES],
|
|
MLK_CONFIG_CONTEXT_PARAMETER_TYPE context)
|
|
{
|
|
int ret = 0;
|
|
MLK_ALLOC(p, mlk_polyvec, 1, context);
|
|
MLK_ALLOC(p_reencoded, uint8_t, MLKEM_POLYVECBYTES, context);
|
|
|
|
if (p == NULL || p_reencoded == NULL)
|
|
{
|
|
ret = MLK_ERR_OUT_OF_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
mlk_polyvec_frombytes(p, pk);
|
|
mlk_polyvec_reduce(p);
|
|
mlk_polyvec_tobytes(p_reencoded, p);
|
|
|
|
/* We use a constant-time memcmp here to avoid having to
|
|
* declassify the PK before the PCT has succeeded. */
|
|
ret = mlk_ct_memcmp(pk, p_reencoded, MLKEM_POLYVECBYTES) ? MLK_ERR_FAIL : 0;
|
|
|
|
cleanup:
|
|
/* Specification: Partially implements
|
|
* @[FIPS203, Section 3.3, Destruction of intermediate values] */
|
|
MLK_FREE(p_reencoded, uint8_t, MLKEM_POLYVECBYTES, context);
|
|
MLK_FREE(p, mlk_polyvec, 1, context);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Reference: Not implemented in the reference implementation @[REF]. */
|
|
MLK_EXTERNAL_API
|
|
MLK_MUST_CHECK_RETURN_VALUE
|
|
int mlk_kem_check_sk(const uint8_t sk[MLKEM_INDCCA_SECRETKEYBYTES],
|
|
MLK_CONFIG_CONTEXT_PARAMETER_TYPE context)
|
|
{
|
|
int ret = 0;
|
|
MLK_ALLOC(test, uint8_t, MLKEM_SYMBYTES, context);
|
|
|
|
if (test == NULL)
|
|
{
|
|
ret = MLK_ERR_OUT_OF_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* The parts of `sk` being hashed and compared here are public, so
|
|
* no public information is leaked through the runtime or the return value
|
|
* of this function.
|
|
*/
|
|
|
|
/* Declassify the public part of the secret key */
|
|
MLK_CT_TESTING_DECLASSIFY(sk + MLKEM_INDCPA_SECRETKEYBYTES,
|
|
MLKEM_INDCCA_PUBLICKEYBYTES);
|
|
MLK_CT_TESTING_DECLASSIFY(
|
|
sk + MLKEM_INDCCA_SECRETKEYBYTES - 2 * MLKEM_SYMBYTES, MLKEM_SYMBYTES);
|
|
|
|
mlk_hash_h(test, sk + MLKEM_INDCPA_SECRETKEYBYTES,
|
|
MLKEM_INDCCA_PUBLICKEYBYTES);
|
|
/* This doesn't have to be a constant-time memcmp, but it's the only place
|
|
* in the library where a normal memcmp would be used otherwise, so for sake
|
|
* of minimizing stdlib dependency, we use our constant-time one anyway. */
|
|
ret = mlk_ct_memcmp(sk + MLKEM_INDCCA_SECRETKEYBYTES - 2 * MLKEM_SYMBYTES,
|
|
test, MLKEM_SYMBYTES)
|
|
? MLK_ERR_FAIL
|
|
: 0;
|
|
|
|
cleanup:
|
|
/* Specification: Partially implements
|
|
* @[FIPS203, Section 3.3, Destruction of intermediate values] */
|
|
MLK_FREE(test, uint8_t, MLKEM_SYMBYTES, context);
|
|
return ret;
|
|
}
|
|
|
|
MLK_MUST_CHECK_RETURN_VALUE
|
|
static int mlk_check_pct(uint8_t const pk[MLKEM_INDCCA_PUBLICKEYBYTES],
|
|
uint8_t const sk[MLKEM_INDCCA_SECRETKEYBYTES],
|
|
MLK_CONFIG_CONTEXT_PARAMETER_TYPE context)
|
|
__contract__(
|
|
requires(memory_no_alias(pk, MLKEM_INDCCA_PUBLICKEYBYTES))
|
|
requires(memory_no_alias(sk, MLKEM_INDCCA_SECRETKEYBYTES))
|
|
ensures(return_value == 0 || return_value == MLK_ERR_FAIL ||
|
|
return_value == MLK_ERR_OUT_OF_MEMORY ||
|
|
return_value == MLK_ERR_RNG_FAIL)
|
|
);
|
|
|
|
#if defined(MLK_CONFIG_KEYGEN_PCT)
|
|
/* Specification:
|
|
* Partially implements 'Pairwise Consistency Test' @[FIPS140_3_IG, p.87] and
|
|
* @[FIPS203, Section 7.1, Pairwise Consistency]. */
|
|
|
|
/* Reference: Not implemented in the reference implementation @[REF]. */
|
|
MLK_MUST_CHECK_RETURN_VALUE
|
|
static int mlk_check_pct(uint8_t const pk[MLKEM_INDCCA_PUBLICKEYBYTES],
|
|
uint8_t const sk[MLKEM_INDCCA_SECRETKEYBYTES],
|
|
MLK_CONFIG_CONTEXT_PARAMETER_TYPE context)
|
|
{
|
|
int ret = 0;
|
|
MLK_ALLOC(ct, uint8_t, MLKEM_INDCCA_CIPHERTEXTBYTES, context);
|
|
MLK_ALLOC(ss_enc, uint8_t, MLKEM_SSBYTES, context);
|
|
MLK_ALLOC(ss_dec, uint8_t, MLKEM_SSBYTES, context);
|
|
|
|
if (ct == NULL || ss_enc == NULL || ss_dec == NULL)
|
|
{
|
|
ret = MLK_ERR_OUT_OF_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = mlk_kem_enc(ct, ss_enc, pk, context);
|
|
if (ret != 0)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = mlk_kem_dec(ss_dec, ct, sk, context);
|
|
if (ret != 0)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
#if defined(MLK_CONFIG_KEYGEN_PCT_BREAKAGE_TEST)
|
|
/* Deliberately break PCT for testing purposes */
|
|
if (mlk_break_pct())
|
|
{
|
|
ss_enc[0] = ~ss_enc[0];
|
|
}
|
|
#endif /* MLK_CONFIG_KEYGEN_PCT_BREAKAGE_TEST */
|
|
|
|
ret = mlk_ct_memcmp(ss_enc, ss_dec, MLKEM_SSBYTES);
|
|
/* The result of the PCT is public. */
|
|
MLK_CT_TESTING_DECLASSIFY(&ret, sizeof(ret));
|
|
|
|
if (ret != 0)
|
|
{
|
|
ret = MLK_ERR_FAIL;
|
|
}
|
|
|
|
cleanup:
|
|
|
|
/* Specification: Partially implements
|
|
* @[FIPS203, Section 3.3, Destruction of intermediate values] */
|
|
MLK_FREE(ss_dec, uint8_t, MLKEM_SSBYTES, context);
|
|
MLK_FREE(ss_enc, uint8_t, MLKEM_SSBYTES, context);
|
|
MLK_FREE(ct, uint8_t, MLKEM_INDCCA_CIPHERTEXTBYTES, context);
|
|
return ret;
|
|
}
|
|
#else /* MLK_CONFIG_KEYGEN_PCT */
|
|
MLK_MUST_CHECK_RETURN_VALUE
|
|
static int mlk_check_pct(uint8_t const pk[MLKEM_INDCCA_PUBLICKEYBYTES],
|
|
uint8_t const sk[MLKEM_INDCCA_SECRETKEYBYTES],
|
|
MLK_CONFIG_CONTEXT_PARAMETER_TYPE context)
|
|
{
|
|
/* Skip PCT */
|
|
((void)pk);
|
|
((void)sk);
|
|
#if defined(MLK_CONFIG_CONTEXT_PARAMETER)
|
|
((void)context);
|
|
#endif
|
|
return 0;
|
|
}
|
|
#endif /* !MLK_CONFIG_KEYGEN_PCT */
|
|
|
|
/* Reference: `crypto_kem_keypair_derand()` in the reference implementation
|
|
* @[REF].
|
|
* - We optionally include PCT which is not present in
|
|
* the reference code. */
|
|
MLK_EXTERNAL_API
|
|
int mlk_kem_keypair_derand(uint8_t pk[MLKEM_INDCCA_PUBLICKEYBYTES],
|
|
uint8_t sk[MLKEM_INDCCA_SECRETKEYBYTES],
|
|
const uint8_t coins[2 * MLKEM_SYMBYTES],
|
|
MLK_CONFIG_CONTEXT_PARAMETER_TYPE context)
|
|
{
|
|
int ret;
|
|
|
|
ret = mlk_indcpa_keypair_derand(pk, sk, coins, context);
|
|
if (ret != 0)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
mlk_memcpy(sk + MLKEM_INDCPA_SECRETKEYBYTES, pk, MLKEM_INDCCA_PUBLICKEYBYTES);
|
|
mlk_hash_h(sk + MLKEM_INDCCA_SECRETKEYBYTES - 2 * MLKEM_SYMBYTES, pk,
|
|
MLKEM_INDCCA_PUBLICKEYBYTES);
|
|
/* Value z for pseudo-random output on reject */
|
|
mlk_memcpy(sk + MLKEM_INDCCA_SECRETKEYBYTES - MLKEM_SYMBYTES,
|
|
coins + MLKEM_SYMBYTES, MLKEM_SYMBYTES);
|
|
|
|
/* Declassify public key */
|
|
MLK_CT_TESTING_DECLASSIFY(pk, MLKEM_INDCCA_PUBLICKEYBYTES);
|
|
|
|
/* Pairwise Consistency Test (PCT) @[FIPS140_3_IG, p.87] */
|
|
ret = mlk_check_pct(pk, sk, context);
|
|
if (ret != 0)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
if (ret != 0)
|
|
{
|
|
mlk_zeroize(pk, MLKEM_INDCCA_PUBLICKEYBYTES);
|
|
mlk_zeroize(sk, MLKEM_INDCCA_SECRETKEYBYTES);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#if !defined(MLK_CONFIG_NO_RANDOMIZED_API)
|
|
/* Reference: `crypto_kem_keypair()` in the reference implementation @[REF]
|
|
* - We zeroize the stack buffer */
|
|
MLK_EXTERNAL_API
|
|
int mlk_kem_keypair(uint8_t pk[MLKEM_INDCCA_PUBLICKEYBYTES],
|
|
uint8_t sk[MLKEM_INDCCA_SECRETKEYBYTES],
|
|
MLK_CONFIG_CONTEXT_PARAMETER_TYPE context)
|
|
{
|
|
int ret = 0;
|
|
MLK_ALLOC(coins, uint8_t, 2 * MLKEM_SYMBYTES, context);
|
|
|
|
if (coins == NULL)
|
|
{
|
|
ret = MLK_ERR_OUT_OF_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Acquire necessary randomness, and mark it as secret. */
|
|
if (mlk_randombytes(coins, 2 * MLKEM_SYMBYTES) != 0)
|
|
{
|
|
ret = MLK_ERR_RNG_FAIL;
|
|
goto cleanup;
|
|
}
|
|
|
|
MLK_CT_TESTING_SECRET(coins, 2 * MLKEM_SYMBYTES);
|
|
|
|
ret = mlk_kem_keypair_derand(pk, sk, coins, context);
|
|
|
|
cleanup:
|
|
/* Specification: Partially implements
|
|
* @[FIPS203, Section 3.3, Destruction of intermediate values] */
|
|
MLK_FREE(coins, uint8_t, 2 * MLKEM_SYMBYTES, context);
|
|
return ret;
|
|
}
|
|
#endif /* !MLK_CONFIG_NO_RANDOMIZED_API */
|
|
|
|
/* Reference: `crypto_kem_enc_derand()` in the reference implementation @[REF]
|
|
* - We include public key check
|
|
* - We include stack buffer zeroization */
|
|
MLK_EXTERNAL_API
|
|
int mlk_kem_enc_derand(uint8_t ct[MLKEM_INDCCA_CIPHERTEXTBYTES],
|
|
uint8_t ss[MLKEM_SSBYTES],
|
|
const uint8_t pk[MLKEM_INDCCA_PUBLICKEYBYTES],
|
|
const uint8_t coins[MLKEM_SYMBYTES],
|
|
MLK_CONFIG_CONTEXT_PARAMETER_TYPE context)
|
|
{
|
|
int ret = 0;
|
|
MLK_ALLOC(buf, uint8_t, 2 * MLKEM_SYMBYTES, context);
|
|
MLK_ALLOC(kr, uint8_t, 2 * MLKEM_SYMBYTES, context);
|
|
|
|
if (buf == NULL || kr == NULL)
|
|
{
|
|
ret = MLK_ERR_OUT_OF_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Specification: Implements @[FIPS203, Section 7.2, Modulus check] */
|
|
ret = mlk_kem_check_pk(pk, context);
|
|
if (ret != 0)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
mlk_memcpy(buf, coins, MLKEM_SYMBYTES);
|
|
|
|
/* Multitarget countermeasure for coins + contributory KEM */
|
|
mlk_hash_h(buf + MLKEM_SYMBYTES, pk, MLKEM_INDCCA_PUBLICKEYBYTES);
|
|
mlk_hash_g(kr, buf, 2 * MLKEM_SYMBYTES);
|
|
|
|
/* coins are in kr+MLKEM_SYMBYTES */
|
|
ret = mlk_indcpa_enc(ct, buf, pk, kr + MLKEM_SYMBYTES, context);
|
|
if (ret != 0)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
mlk_memcpy(ss, kr, MLKEM_SYMBYTES);
|
|
|
|
cleanup:
|
|
/* Specification: Partially implements
|
|
* @[FIPS203, Section 3.3, Destruction of intermediate values] */
|
|
MLK_FREE(kr, uint8_t, 2 * MLKEM_SYMBYTES, context);
|
|
MLK_FREE(buf, uint8_t, 2 * MLKEM_SYMBYTES, context);
|
|
return ret;
|
|
}
|
|
|
|
#if !defined(MLK_CONFIG_NO_RANDOMIZED_API)
|
|
/* Reference: `crypto_kem_enc()` in the reference implementation @[REF]
|
|
* - We include stack buffer zeroization */
|
|
MLK_EXTERNAL_API
|
|
int mlk_kem_enc(uint8_t ct[MLKEM_INDCCA_CIPHERTEXTBYTES],
|
|
uint8_t ss[MLKEM_SSBYTES],
|
|
const uint8_t pk[MLKEM_INDCCA_PUBLICKEYBYTES],
|
|
MLK_CONFIG_CONTEXT_PARAMETER_TYPE context)
|
|
{
|
|
int ret = 0;
|
|
MLK_ALLOC(coins, uint8_t, MLKEM_SYMBYTES, context);
|
|
|
|
if (coins == NULL)
|
|
{
|
|
ret = MLK_ERR_OUT_OF_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (mlk_randombytes(coins, MLKEM_SYMBYTES) != 0)
|
|
{
|
|
ret = MLK_ERR_RNG_FAIL;
|
|
goto cleanup;
|
|
}
|
|
|
|
MLK_CT_TESTING_SECRET(coins, MLKEM_SYMBYTES);
|
|
|
|
ret = mlk_kem_enc_derand(ct, ss, pk, coins, context);
|
|
|
|
cleanup:
|
|
/* Specification: Partially implements
|
|
* @[FIPS203, Section 3.3, Destruction of intermediate values] */
|
|
MLK_FREE(coins, uint8_t, MLKEM_SYMBYTES, context);
|
|
return ret;
|
|
}
|
|
#endif /* !MLK_CONFIG_NO_RANDOMIZED_API */
|
|
|
|
/* Reference: `crypto_kem_dec()` in the reference implementation @[REF]
|
|
* - We include secret key check
|
|
* - We include stack buffer zeroization */
|
|
MLK_EXTERNAL_API
|
|
int mlk_kem_dec(uint8_t ss[MLKEM_SSBYTES],
|
|
const uint8_t ct[MLKEM_INDCCA_CIPHERTEXTBYTES],
|
|
const uint8_t sk[MLKEM_INDCCA_SECRETKEYBYTES],
|
|
MLK_CONFIG_CONTEXT_PARAMETER_TYPE context)
|
|
{
|
|
int ret = 0;
|
|
uint8_t fail;
|
|
const uint8_t *pk = sk + MLKEM_INDCPA_SECRETKEYBYTES;
|
|
MLK_ALLOC(buf, uint8_t, 2 * MLKEM_SYMBYTES, context);
|
|
MLK_ALLOC(kr, uint8_t, 2 * MLKEM_SYMBYTES, context);
|
|
MLK_ALLOC(tmp, uint8_t, MLKEM_SYMBYTES + MLKEM_INDCCA_CIPHERTEXTBYTES,
|
|
context);
|
|
|
|
if (buf == NULL || kr == NULL || tmp == NULL)
|
|
{
|
|
ret = MLK_ERR_OUT_OF_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Specification: Implements @[FIPS203, Section 7.3, Hash check] */
|
|
ret = mlk_kem_check_sk(sk, context);
|
|
if (ret != 0)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = mlk_indcpa_dec(buf, ct, sk, context);
|
|
if (ret != 0)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Multitarget countermeasure for coins + contributory KEM */
|
|
mlk_memcpy(buf + MLKEM_SYMBYTES,
|
|
sk + MLKEM_INDCCA_SECRETKEYBYTES - 2 * MLKEM_SYMBYTES,
|
|
MLKEM_SYMBYTES);
|
|
mlk_hash_g(kr, buf, 2 * MLKEM_SYMBYTES);
|
|
|
|
/* Recompute and compare ciphertext */
|
|
/* coins are in kr+MLKEM_SYMBYTES */
|
|
ret = mlk_indcpa_enc(tmp, buf, pk, kr + MLKEM_SYMBYTES, context);
|
|
if (ret != 0)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
fail = mlk_ct_memcmp(ct, tmp, MLKEM_INDCCA_CIPHERTEXTBYTES);
|
|
|
|
/* Compute rejection key */
|
|
mlk_memcpy(tmp, sk + MLKEM_INDCCA_SECRETKEYBYTES - MLKEM_SYMBYTES,
|
|
MLKEM_SYMBYTES);
|
|
mlk_memcpy(tmp + MLKEM_SYMBYTES, ct, MLKEM_INDCCA_CIPHERTEXTBYTES);
|
|
mlk_hash_j(ss, tmp, MLKEM_SYMBYTES + MLKEM_INDCCA_CIPHERTEXTBYTES);
|
|
|
|
/* Copy true key to return buffer if fail is 0 */
|
|
mlk_ct_cmov_zero(ss, kr, MLKEM_SYMBYTES, fail);
|
|
|
|
cleanup:
|
|
/* Specification: Partially implements
|
|
* @[FIPS203, Section 3.3, Destruction of intermediate values] */
|
|
MLK_FREE(tmp, uint8_t, MLKEM_SYMBYTES + MLKEM_INDCCA_CIPHERTEXTBYTES,
|
|
context);
|
|
MLK_FREE(kr, uint8_t, 2 * MLKEM_SYMBYTES, context);
|
|
MLK_FREE(buf, uint8_t, 2 * MLKEM_SYMBYTES, context);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* To facilitate single-compilation-unit (SCU) builds, undefine all macros.
|
|
* Don't modify by hand -- this is auto-generated by scripts/autogen. */
|
|
#undef mlk_check_pct
|