Source code

Revision control

Copy as Markdown

Other Tools

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
#include "gtest/gtest.h"
#include "blapi.h"
#include "nss_scoped_ptrs.h"
#include "kat/kyber768_kat.h"
#include "testvectors_base/test-structs.h"
#include "testvectors/ml-kem-keygen-vectors.h"
#include "testvectors/ml-kem-encap-vectors.h"
#include "testvectors/ml-kem-decap-vectors.h"
namespace nss_test {
size_t get_ciphertext_length(KyberParams param) {
size_t len = 0;
switch (param) {
case params_kyber768_round3:
case params_kyber768_round3_test_mode:
case params_ml_kem768:
case params_ml_kem768_test_mode:
len = KYBER768_CIPHERTEXT_BYTES;
break;
case params_ml_kem1024:
case params_ml_kem1024_test_mode:
len = MLKEM1024_CIPHERTEXT_BYTES;
break;
case params_kyber_invalid:
break;
}
return len;
}
size_t get_private_key_length(KyberParams param) {
size_t len = 0;
switch (param) {
case params_kyber768_round3:
case params_kyber768_round3_test_mode:
case params_ml_kem768:
case params_ml_kem768_test_mode:
len = KYBER768_PRIVATE_KEY_BYTES;
break;
case params_ml_kem1024:
case params_ml_kem1024_test_mode:
len = MLKEM1024_PRIVATE_KEY_BYTES;
break;
case params_kyber_invalid:
break;
}
return len;
}
size_t get_public_key_length(KyberParams param) {
size_t len = 0;
switch (param) {
case params_kyber768_round3:
case params_kyber768_round3_test_mode:
case params_ml_kem768:
case params_ml_kem768_test_mode:
len = KYBER768_PUBLIC_KEY_BYTES;
break;
case params_ml_kem1024:
case params_ml_kem1024_test_mode:
len = MLKEM1024_PUBLIC_KEY_BYTES;
break;
case params_kyber_invalid:
break;
}
return len;
}
class KyberTest : public ::testing::Test {};
class KyberSelfTest : public KyberTest,
public ::testing::WithParamInterface<KyberParams> {};
TEST_P(KyberSelfTest, ConsistencyTest) {
const KyberParams& param(GetParam());
ScopedSECItem privateKey(
SECITEM_AllocItem(nullptr, nullptr, MAX_ML_KEM_PRIVATE_KEY_LENGTH));
ScopedSECItem publicKey(
SECITEM_AllocItem(nullptr, nullptr, MAX_ML_KEM_PUBLIC_KEY_LENGTH));
ScopedSECItem ciphertext(
SECITEM_AllocItem(nullptr, nullptr, MAX_ML_KEM_CIPHER_LENGTH));
ScopedSECItem secret(
SECITEM_AllocItem(nullptr, nullptr, KYBER_SHARED_SECRET_BYTES));
ScopedSECItem secret2(
SECITEM_AllocItem(nullptr, nullptr, KYBER_SHARED_SECRET_BYTES));
privateKey->len = get_private_key_length(param);
publicKey->len = get_public_key_length(param);
SECStatus rv =
Kyber_NewKey(param, nullptr, privateKey.get(), publicKey.get());
EXPECT_EQ(SECSuccess, rv);
ciphertext->len = get_ciphertext_length(param);
rv = Kyber_Encapsulate(param, nullptr, publicKey.get(), ciphertext.get(),
secret.get());
EXPECT_EQ(SECSuccess, rv);
rv = Kyber_Decapsulate(param, privateKey.get(), ciphertext.get(),
secret2.get());
EXPECT_EQ(SECSuccess, rv);
EXPECT_EQ(secret->len, KYBER_SHARED_SECRET_BYTES);
EXPECT_EQ(secret2->len, KYBER_SHARED_SECRET_BYTES);
EXPECT_EQ(0, memcmp(secret->data, secret2->data, KYBER_SHARED_SECRET_BYTES));
}
TEST_P(KyberSelfTest, InvalidParameterTest) {
const KyberParams& param(GetParam());
ScopedSECItem privateKey(
SECITEM_AllocItem(nullptr, nullptr, MAX_ML_KEM_PRIVATE_KEY_LENGTH));
ScopedSECItem publicKey(
SECITEM_AllocItem(nullptr, nullptr, MAX_ML_KEM_PUBLIC_KEY_LENGTH));
ScopedSECItem ciphertext(
SECITEM_AllocItem(nullptr, nullptr, MAX_ML_KEM_CIPHER_LENGTH));
ScopedSECItem secret(
SECITEM_AllocItem(nullptr, nullptr, KYBER_SHARED_SECRET_BYTES));
privateKey->len = get_private_key_length(param);
publicKey->len = get_public_key_length(param);
SECStatus rv = Kyber_NewKey(params_kyber_invalid, nullptr, privateKey.get(),
publicKey.get());
EXPECT_EQ(SECFailure, rv);
rv = Kyber_NewKey(param, nullptr, privateKey.get(), publicKey.get());
EXPECT_EQ(SECSuccess, rv);
ciphertext->len = get_ciphertext_length(param);
rv = Kyber_Encapsulate(params_kyber_invalid, nullptr, publicKey.get(),
ciphertext.get(), secret.get());
EXPECT_EQ(SECFailure, rv);
rv = Kyber_Encapsulate(param, nullptr, publicKey.get(), ciphertext.get(),
secret.get());
EXPECT_EQ(SECSuccess, rv);
rv = Kyber_Decapsulate(params_kyber_invalid, privateKey.get(),
ciphertext.get(), secret.get());
EXPECT_EQ(SECFailure, rv);
rv = Kyber_Decapsulate(param, privateKey.get(), ciphertext.get(),
secret.get());
EXPECT_EQ(SECSuccess, rv);
}
TEST_P(KyberSelfTest, InvalidPublicKeyTest) {
const KyberParams& param(GetParam());
ScopedSECItem shortBuffer(SECITEM_AllocItem(nullptr, nullptr, 7));
ScopedSECItem privateKey(
SECITEM_AllocItem(nullptr, nullptr, MAX_ML_KEM_PRIVATE_KEY_LENGTH));
privateKey->len = get_private_key_length(param);
SECStatus rv =
Kyber_NewKey(param, nullptr, privateKey.get(), shortBuffer.get());
EXPECT_EQ(SECFailure, rv); // short publicKey buffer
}
TEST_P(KyberSelfTest, InvalidCiphertextTest) {
const KyberParams& param(GetParam());
ScopedSECItem shortBuffer(SECITEM_AllocItem(nullptr, nullptr, 7));
ScopedSECItem privateKey(
SECITEM_AllocItem(nullptr, nullptr, MAX_ML_KEM_PRIVATE_KEY_LENGTH));
ScopedSECItem publicKey(
SECITEM_AllocItem(nullptr, nullptr, MAX_ML_KEM_PUBLIC_KEY_LENGTH));
ScopedSECItem ciphertext(
SECITEM_AllocItem(nullptr, nullptr, MAX_ML_KEM_CIPHER_LENGTH));
ScopedSECItem secret(
SECITEM_AllocItem(nullptr, nullptr, KYBER_SHARED_SECRET_BYTES));
ScopedSECItem secret2(
SECITEM_AllocItem(nullptr, nullptr, KYBER_SHARED_SECRET_BYTES));
privateKey->len = get_private_key_length(param);
publicKey->len = get_public_key_length(param);
SECStatus rv =
Kyber_NewKey(param, nullptr, privateKey.get(), publicKey.get());
EXPECT_EQ(SECSuccess, rv);
ciphertext->len = get_ciphertext_length(param);
rv = Kyber_Encapsulate(param, nullptr, publicKey.get(), shortBuffer.get(),
secret.get());
EXPECT_EQ(SECFailure, rv); // short ciphertext input
rv = Kyber_Encapsulate(param, nullptr, publicKey.get(), ciphertext.get(),
secret.get());
EXPECT_EQ(SECSuccess, rv);
// Modify a random byte in the ciphertext
size_t pos;
rv = RNG_GenerateGlobalRandomBytes((uint8_t*)&pos, sizeof(pos));
EXPECT_EQ(SECSuccess, rv);
uint8_t byte;
rv = RNG_GenerateGlobalRandomBytes((uint8_t*)&byte, sizeof(byte));
EXPECT_EQ(SECSuccess, rv);
size_t ct_len = get_ciphertext_length(param);
EXPECT_EQ(ciphertext->len, ct_len);
ciphertext->data[pos % ct_len] ^= (byte | 1);
rv = Kyber_Decapsulate(param, privateKey.get(), ciphertext.get(),
secret2.get());
EXPECT_EQ(SECSuccess, rv);
EXPECT_EQ(secret->len, KYBER_SHARED_SECRET_BYTES);
EXPECT_EQ(secret2->len, KYBER_SHARED_SECRET_BYTES);
EXPECT_NE(0, memcmp(secret->data, secret2->data, KYBER_SHARED_SECRET_BYTES));
}
TEST_P(KyberSelfTest, InvalidPrivateKeyTest) {
const KyberParams& param(GetParam());
ScopedSECItem shortBuffer(SECITEM_AllocItem(nullptr, nullptr, 7));
ScopedSECItem privateKey(
SECITEM_AllocItem(nullptr, nullptr, MAX_ML_KEM_PRIVATE_KEY_LENGTH));
ScopedSECItem publicKey(
SECITEM_AllocItem(nullptr, nullptr, MAX_ML_KEM_PUBLIC_KEY_LENGTH));
ScopedSECItem ciphertext(
SECITEM_AllocItem(nullptr, nullptr, MAX_ML_KEM_CIPHER_LENGTH));
ScopedSECItem secret(
SECITEM_AllocItem(nullptr, nullptr, KYBER_SHARED_SECRET_BYTES));
ScopedSECItem secret2(
SECITEM_AllocItem(nullptr, nullptr, KYBER_SHARED_SECRET_BYTES));
privateKey->len = get_private_key_length(param);
publicKey->len = get_public_key_length(param);
SECStatus rv =
Kyber_NewKey(param, nullptr, shortBuffer.get(), publicKey.get());
EXPECT_EQ(SECFailure, rv); // short privateKey buffer
rv = Kyber_NewKey(param, nullptr, privateKey.get(), publicKey.get());
EXPECT_EQ(SECSuccess, rv);
ciphertext->len = get_ciphertext_length(param);
rv = Kyber_Encapsulate(param, nullptr, publicKey.get(), ciphertext.get(),
secret.get());
EXPECT_EQ(SECSuccess, rv);
// Modify a random byte in the private key
size_t pos;
rv = RNG_GenerateGlobalRandomBytes((uint8_t*)&pos, sizeof(pos));
EXPECT_EQ(SECSuccess, rv);
uint8_t byte;
rv = RNG_GenerateGlobalRandomBytes((uint8_t*)&byte, sizeof(byte));
EXPECT_EQ(SECSuccess, rv);
// Modifying the implicit rejection key will not cause decapsulation failure.
size_t pvk_len = get_private_key_length(param);
size_t puk_len = get_public_key_length(param);
EXPECT_EQ(privateKey->len, pvk_len);
size_t ir_pos = pvk_len - (pos % KYBER_SHARED_SECRET_BYTES) - 1;
uint8_t ir_pos_old = privateKey->data[ir_pos];
privateKey->data[ir_pos] ^= (byte | 1);
rv = Kyber_Decapsulate(param, privateKey.get(), ciphertext.get(),
secret2.get());
EXPECT_EQ(SECSuccess, rv);
EXPECT_EQ(secret->len, KYBER_SHARED_SECRET_BYTES);
EXPECT_EQ(secret2->len, KYBER_SHARED_SECRET_BYTES);
EXPECT_EQ(0, memcmp(secret->data, secret2->data, KYBER_SHARED_SECRET_BYTES));
// Fix the private key
privateKey->data[ir_pos] = ir_pos_old;
// For ML-KEM when modifying the public key, the key must be rejected.
// Kyber will decapsulate without an error in these cases
size_t pk_pos = pvk_len - 2 * KYBER_SHARED_SECRET_BYTES - (pos % puk_len) - 1;
uint8_t pk_pos_old = privateKey->data[pk_pos];
privateKey->data[pk_pos] ^= (byte | 1);
rv = Kyber_Decapsulate(param, privateKey.get(), ciphertext.get(),
secret2.get());
if (param == params_kyber768_round3) {
EXPECT_EQ(SECSuccess, rv);
} else {
EXPECT_EQ(SECFailure, rv);
}
// Fix the key again.
privateKey->data[pk_pos] = pk_pos_old;
// For ML-KEM when modifying the public key hash, the key must be rejected.
// Kyber will decapsulate without an error in these cases
size_t pk_hash_pos = pvk_len - KYBER_SHARED_SECRET_BYTES -
(pos % KYBER_SHARED_SECRET_BYTES) - 1;
privateKey->data[pk_hash_pos] ^= (byte | 1);
rv = Kyber_Decapsulate(param, privateKey.get(), ciphertext.get(),
secret2.get());
if (param == params_kyber768_round3) {
EXPECT_EQ(SECSuccess, rv);
} else {
EXPECT_EQ(SECFailure, rv);
}
}
TEST_P(KyberSelfTest, DecapsulationWithModifiedRejectionKeyTest) {
const KyberParams& param(GetParam());
ScopedSECItem privateKey(
SECITEM_AllocItem(nullptr, nullptr, MAX_ML_KEM_PRIVATE_KEY_LENGTH));
ScopedSECItem publicKey(
SECITEM_AllocItem(nullptr, nullptr, MAX_ML_KEM_PUBLIC_KEY_LENGTH));
ScopedSECItem ciphertext(
SECITEM_AllocItem(nullptr, nullptr, MAX_ML_KEM_CIPHER_LENGTH));
ScopedSECItem secret(
SECITEM_AllocItem(nullptr, nullptr, KYBER_SHARED_SECRET_BYTES));
ScopedSECItem secret2(
SECITEM_AllocItem(nullptr, nullptr, KYBER_SHARED_SECRET_BYTES));
ScopedSECItem secret3(
SECITEM_AllocItem(nullptr, nullptr, KYBER_SHARED_SECRET_BYTES));
privateKey->len = get_private_key_length(param);
publicKey->len = get_public_key_length(param);
SECStatus rv =
Kyber_NewKey(param, nullptr, privateKey.get(), publicKey.get());
EXPECT_EQ(SECSuccess, rv);
ciphertext->len = get_ciphertext_length(param);
rv = Kyber_Encapsulate(param, nullptr, publicKey.get(), ciphertext.get(),
secret.get());
EXPECT_EQ(SECSuccess, rv);
// Modify a random byte in the ciphertext and decapsulate it
size_t pos;
rv = RNG_GenerateGlobalRandomBytes((uint8_t*)&pos, sizeof(pos));
EXPECT_EQ(SECSuccess, rv);
uint8_t byte;
rv = RNG_GenerateGlobalRandomBytes((uint8_t*)&byte, sizeof(byte));
EXPECT_EQ(SECSuccess, rv);
size_t ct_len = get_ciphertext_length(param);
EXPECT_EQ(ciphertext->len, ct_len);
ciphertext->data[pos % ct_len] ^= (byte | 1);
rv = Kyber_Decapsulate(param, privateKey.get(), ciphertext.get(),
secret2.get());
EXPECT_EQ(SECSuccess, rv);
// Now, modify a random byte in the implicit rejection key and try
// the decapsulation again. The result should be different.
rv = RNG_GenerateGlobalRandomBytes((uint8_t*)&pos, sizeof(pos));
EXPECT_EQ(SECSuccess, rv);
rv = RNG_GenerateGlobalRandomBytes((uint8_t*)&byte, sizeof(byte));
EXPECT_EQ(SECSuccess, rv);
size_t pvk_len = get_private_key_length(param);
pos =
(pvk_len - KYBER_SHARED_SECRET_BYTES) + (pos % KYBER_SHARED_SECRET_BYTES);
EXPECT_EQ(privateKey->len, pvk_len);
privateKey->data[pos] ^= (byte | 1);
rv = Kyber_Decapsulate(param, privateKey.get(), ciphertext.get(),
secret3.get());
EXPECT_EQ(SECSuccess, rv);
EXPECT_EQ(secret2->len, KYBER_SHARED_SECRET_BYTES);
EXPECT_EQ(secret3->len, KYBER_SHARED_SECRET_BYTES);
EXPECT_NE(0, memcmp(secret2->data, secret3->data, KYBER_SHARED_SECRET_BYTES));
}
#ifdef NSS_DISABLE_KYBER
INSTANTIATE_TEST_SUITE_P(SelfTests, KyberSelfTest,
::testing::Values(params_ml_kem768,
params_ml_kem1024));
#else
INSTANTIATE_TEST_SUITE_P(SelfTests, KyberSelfTest,
::testing::Values(params_ml_kem768, params_ml_kem1024,
params_kyber768_round3));
#endif
TEST(Kyber768Test, KnownAnswersTest) {
ScopedSECItem privateKey(
SECITEM_AllocItem(nullptr, nullptr, MAX_ML_KEM_PRIVATE_KEY_LENGTH));
ScopedSECItem publicKey(
SECITEM_AllocItem(nullptr, nullptr, MAX_ML_KEM_PUBLIC_KEY_LENGTH));
ScopedSECItem ciphertext(
SECITEM_AllocItem(nullptr, nullptr, MAX_ML_KEM_CIPHER_LENGTH));
ScopedSECItem secret(
SECITEM_AllocItem(nullptr, nullptr, KYBER_SHARED_SECRET_BYTES));
ScopedSECItem secret2(
SECITEM_AllocItem(nullptr, nullptr, KYBER_SHARED_SECRET_BYTES));
SECStatus rv;
uint8_t digest[SHA256_LENGTH];
for (const auto& kat : KyberKATs) {
SECItem keypair_seed = {siBuffer, (unsigned char*)kat.newKeySeed,
sizeof kat.newKeySeed};
SECItem enc_seed = {siBuffer, (unsigned char*)kat.encapsSeed,
sizeof kat.encapsSeed};
privateKey->len = get_private_key_length(kat.params);
publicKey->len = get_public_key_length(kat.params);
ciphertext->len = get_ciphertext_length(kat.params);
rv = Kyber_NewKey(kat.params, &keypair_seed, privateKey.get(),
publicKey.get());
EXPECT_EQ(SECSuccess, rv);
SHA256_HashBuf(digest, privateKey->data, privateKey->len);
EXPECT_EQ(0, memcmp(kat.privateKeyDigest, digest, sizeof digest));
SHA256_HashBuf(digest, publicKey->data, publicKey->len);
EXPECT_EQ(0, memcmp(kat.publicKeyDigest, digest, sizeof digest));
rv = Kyber_Encapsulate(kat.params, &enc_seed, publicKey.get(),
ciphertext.get(), secret.get());
EXPECT_EQ(SECSuccess, rv);
SHA256_HashBuf(digest, ciphertext->data, ciphertext->len);
EXPECT_EQ(0, memcmp(kat.ciphertextDigest, digest, sizeof digest));
EXPECT_EQ(secret->len, KYBER_SHARED_SECRET_BYTES);
EXPECT_EQ(0, memcmp(kat.secret, secret->data, secret->len));
rv = Kyber_Decapsulate(kat.params, privateKey.get(), ciphertext.get(),
secret2.get());
EXPECT_EQ(SECSuccess, rv);
EXPECT_EQ(secret2->len, KYBER_SHARED_SECRET_BYTES);
EXPECT_EQ(0, memcmp(secret->data, secret2->data, secret2->len));
}
}
TEST(MlKemKeyGen, KnownAnswersTest) {
ScopedSECItem privateKey(
SECITEM_AllocItem(nullptr, nullptr, MAX_ML_KEM_PRIVATE_KEY_LENGTH));
ScopedSECItem publicKey(
SECITEM_AllocItem(nullptr, nullptr, MAX_ML_KEM_PUBLIC_KEY_LENGTH));
uint8_t digest[SHA3_256_LENGTH];
for (const auto& kat : MlKemKeyGenTests) {
SECItem keypair_seed = {siBuffer, (unsigned char*)kat.seed.data(),
(unsigned int)kat.seed.size()};
privateKey->len = get_private_key_length(kat.params);
publicKey->len = get_public_key_length(kat.params);
SECStatus rv = Kyber_NewKey(kat.params, &keypair_seed, privateKey.get(),
publicKey.get());
EXPECT_EQ(SECSuccess, rv);
rv = SHA3_256_HashBuf(digest, privateKey->data, privateKey->len);
EXPECT_EQ(SECSuccess, rv);
EXPECT_EQ(kat.privateKeyDigest.size(), sizeof(digest));
EXPECT_EQ(0, memcmp(kat.privateKeyDigest.data(), digest, sizeof(digest)));
rv = SHA3_256_HashBuf(digest, publicKey->data, publicKey->len);
EXPECT_EQ(SECSuccess, rv);
EXPECT_EQ(kat.publicKeyDigest.size(), sizeof(digest));
EXPECT_EQ(0, memcmp(kat.publicKeyDigest.data(), digest, sizeof(digest)));
}
}
TEST(MlKemEncap, KnownAnswersTest) {
ScopedSECItem ciphertext(
SECITEM_AllocItem(nullptr, nullptr, MAX_ML_KEM_CIPHER_LENGTH));
ScopedSECItem secret(
SECITEM_AllocItem(nullptr, nullptr, KYBER_SHARED_SECRET_BYTES));
uint8_t digest[SHA3_256_LENGTH];
for (const auto& kat : MlKemEncapTests) {
SECItem seed = {siBuffer, (unsigned char*)kat.entropy.data(),
(unsigned int)kat.entropy.size()};
SECItem publicKey = {siBuffer, (unsigned char*)kat.publicKey.data(),
(unsigned int)kat.publicKey.size()};
ciphertext->len = get_ciphertext_length(kat.params);
// Only valid tests for now
EXPECT_TRUE(kat.expectedResult);
SECStatus rv = Kyber_Encapsulate(kat.params, &seed, &publicKey,
ciphertext.get(), secret.get());
EXPECT_EQ(SECSuccess, rv);
rv = SHA3_256_HashBuf(digest, ciphertext->data, ciphertext->len);
EXPECT_EQ(SECSuccess, rv);
EXPECT_EQ(kat.cipherTextDigest.size(), sizeof(digest));
EXPECT_EQ(0, memcmp(kat.cipherTextDigest.data(), digest, sizeof(digest)));
EXPECT_EQ(kat.secret.size(), secret->len);
EXPECT_EQ(0, memcmp(kat.secret.data(), secret->data, secret->len));
}
}
TEST(MlKemDecap, KnownAnswersTest) {
ScopedSECItem secret(
SECITEM_AllocItem(nullptr, nullptr, KYBER_SHARED_SECRET_BYTES));
for (const auto& kat : MlKemDecapTests) {
SECItem ciphertext = {siBuffer, (unsigned char*)kat.cipherText.data(),
(unsigned int)kat.cipherText.size()};
SECItem privateKey = {siBuffer, (unsigned char*)kat.privateKey.data(),
(unsigned int)kat.privateKey.size()};
// Only valid tests for now
EXPECT_TRUE(kat.expectedResult);
SECStatus rv =
Kyber_Decapsulate(kat.params, &privateKey, &ciphertext, secret.get());
EXPECT_EQ(SECSuccess, rv);
EXPECT_EQ(secret->len, KYBER_SHARED_SECRET_BYTES);
EXPECT_EQ(
0, memcmp(secret->data, kat.secret.data(), KYBER_SHARED_SECRET_BYTES));
}
}
} // namespace nss_test