|
1 | 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. |
2 | 2 | // SPDX-License-Identifier: Apache-2.0 OR ISC |
3 | 3 |
|
| 4 | +#include <algorithm> |
4 | 5 | #include <gtest/gtest.h> |
5 | 6 | #include <openssl/base.h> |
6 | 7 | #include <openssl/bio.h> |
7 | 8 | #include <openssl/evp.h> |
| 9 | +#include <openssl/experimental/kem_deterministic_api.h> |
8 | 10 | #include <openssl/mem.h> |
9 | 11 | #include <openssl/pem.h> |
10 | 12 | #include <openssl/pkcs8.h> |
11 | 13 | #include <openssl/ssl.h> |
12 | 14 | #include "../fipsmodule/evp/internal.h" |
13 | 15 | #include "../fipsmodule/kem/internal.h" |
| 16 | +#include "../test/file_test.h" |
14 | 17 | #include "../test/test_util.h" |
15 | | -#include <openssl/experimental/kem_deterministic_api.h> |
| 18 | +#include "../test/wycheproof_util.h" |
16 | 19 |
|
17 | 20 |
|
18 | 21 | // https://datatracker.ietf.org/doc/draft-ietf-lamps-kyber-certificates/ |
@@ -927,3 +930,259 @@ TEST(KEMTest, InvalidSeedLength) { |
927 | 930 |
|
928 | 931 | OPENSSL_free(der_priv); |
929 | 932 | } |
| 933 | + |
| 934 | + |
| 935 | +// Wycheproof test vector mapping for KEMs |
| 936 | +struct WycheproofKEM { |
| 937 | + const char name[20]; |
| 938 | + const int nid; |
| 939 | + size_t ciphertext_len; |
| 940 | + size_t shared_secret_len; |
| 941 | + const char *encaps_test; |
| 942 | + const char *decaps_seed_test; |
| 943 | + const char *decaps_noseed_test; |
| 944 | +}; |
| 945 | + |
| 946 | +//= third_party/vectors/vectors_spec.md#wycheproof |
| 947 | +//# AWS-LC MUST test against `testvectors_v1/mlkem_1024_test.txt`. |
| 948 | +//# AWS-LC MUST test against `testvectors_v1/mlkem_512_test.txt`. |
| 949 | +//# AWS-LC MUST test against `testvectors_v1/mlkem_768_test.txt`. |
| 950 | +//# AWS-LC MUST test against `testvectors_v1/mlkem_1024_encaps_test.txt`. |
| 951 | +//# AWS-LC MUST test against `testvectors_v1/mlkem_1024_semi_expanded_decaps_test.txt`. |
| 952 | +//# AWS-LC MUST test against `testvectors_v1/mlkem_512_encaps_test.txt`. |
| 953 | +//# AWS-LC MUST test against `testvectors_v1/mlkem_512_semi_expanded_decaps_test.txt`. |
| 954 | +//# AWS-LC MUST test against `testvectors_v1/mlkem_768_encaps_test.txt`. |
| 955 | +//# AWS-LC MUST test against `testvectors_v1/mlkem_768_semi_expanded_decaps_test.txt`. |
| 956 | +static const struct WycheproofKEM kWycheproofKEMs[] = { |
| 957 | + { |
| 958 | + "ML-KEM-512", |
| 959 | + NID_MLKEM512, |
| 960 | + 768, |
| 961 | + 32, |
| 962 | + "mlkem_512_encaps_test.txt", |
| 963 | + "mlkem_512_test.txt", |
| 964 | + "mlkem_512_semi_expanded_decaps_test.txt", |
| 965 | + }, |
| 966 | + { |
| 967 | + "ML-KEM-768", |
| 968 | + NID_MLKEM768, |
| 969 | + 1088, |
| 970 | + 32, |
| 971 | + "mlkem_768_encaps_test.txt", |
| 972 | + "mlkem_768_test.txt", |
| 973 | + "mlkem_768_semi_expanded_decaps_test.txt", |
| 974 | + }, |
| 975 | + { |
| 976 | + "ML-KEM-1024", |
| 977 | + NID_MLKEM1024, |
| 978 | + 1568, |
| 979 | + 32, |
| 980 | + "mlkem_1024_encaps_test.txt", |
| 981 | + "mlkem_1024_test.txt", |
| 982 | + "mlkem_1024_semi_expanded_decaps_test.txt", |
| 983 | + }, |
| 984 | +}; |
| 985 | + |
| 986 | +class WycheproofKEMTest : public testing::TestWithParam<WycheproofKEM> {}; |
| 987 | + |
| 988 | +INSTANTIATE_TEST_SUITE_P( |
| 989 | + All, WycheproofKEMTest, testing::ValuesIn(kWycheproofKEMs), |
| 990 | + [](const testing::TestParamInfo<WycheproofKEM> ¶ms) -> std::string { |
| 991 | + std::string name = params.param.name; |
| 992 | + // Replace dashes with underscores for valid C++ test names |
| 993 | + std::replace(name.begin(), name.end(), '-', '_'); |
| 994 | + return name; |
| 995 | + }); |
| 996 | + |
| 997 | +TEST_P(WycheproofKEMTest, Encaps) { |
| 998 | + std::string test_path = |
| 999 | + std::string(kWycheproofV1Path) + GetParam().encaps_test; |
| 1000 | + FileTestGTest(test_path.c_str(), [&](FileTest *t) { |
| 1001 | + std::vector<uint8_t> ek, m, expected_k, expected_c; |
| 1002 | + std::string param_set; |
| 1003 | + |
| 1004 | + ASSERT_TRUE(t->GetInstruction(¶m_set, "parameterSet")); |
| 1005 | + ASSERT_EQ(param_set, GetParam().name); |
| 1006 | + |
| 1007 | + ASSERT_TRUE(t->GetBytes(&ek, "ek")); |
| 1008 | + ASSERT_TRUE(t->GetBytes(&m, "m")); |
| 1009 | + ASSERT_TRUE(t->GetBytes(&expected_k, "K")); |
| 1010 | + ASSERT_TRUE(t->GetBytes(&expected_c, "c")); |
| 1011 | + |
| 1012 | + WycheproofResult result; |
| 1013 | + ASSERT_TRUE(GetWycheproofResult(t, &result)); |
| 1014 | + |
| 1015 | + bssl::UniquePtr<EVP_PKEY> pkey( |
| 1016 | + EVP_PKEY_kem_new_raw_public_key(GetParam().nid, ek.data(), ek.size())); |
| 1017 | + |
| 1018 | + if (!result.IsValid() && result.HasFlag("ModulusOverflow")) { |
| 1019 | + if (pkey) { |
| 1020 | + // FIPS 203 only requires doing this check before encapsulation. |
| 1021 | + fprintf(stderr, |
| 1022 | + "WARNING: Successfully imported %s encapsulation key with " |
| 1023 | + "ModulusOverflow. This is allowed by FIPS 203.\n", |
| 1024 | + param_set.c_str()); |
| 1025 | + } |
| 1026 | + } |
| 1027 | + if (pkey) { |
| 1028 | + bssl::UniquePtr<EVP_PKEY_CTX> ctx(EVP_PKEY_CTX_new(pkey.get(), nullptr)); |
| 1029 | + ASSERT_TRUE(ctx); |
| 1030 | + |
| 1031 | + // Perform deterministic encapsulation using the m field as seed |
| 1032 | + // see https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.203.pdf#algorithm.17 |
| 1033 | + std::vector<uint8_t> ciphertext(GetParam().ciphertext_len); |
| 1034 | + std::vector<uint8_t> shared_secret(GetParam().shared_secret_len); |
| 1035 | + size_t ciphertext_len = ciphertext.size(); |
| 1036 | + size_t shared_secret_len = shared_secret.size(); |
| 1037 | + size_t seed_len = m.size(); |
| 1038 | + int encaps_result = |
| 1039 | + EVP_PKEY_encapsulate_deterministic(ctx.get(), ciphertext.data(), &ciphertext_len, |
| 1040 | + shared_secret.data(), &shared_secret_len, m.data(), &seed_len); |
| 1041 | + |
| 1042 | + if (result.IsValid()) { |
| 1043 | + EXPECT_TRUE(encaps_result); |
| 1044 | + EXPECT_EQ(Bytes(ciphertext.data(), ciphertext_len), Bytes(expected_c)); |
| 1045 | + EXPECT_EQ(Bytes(shared_secret.data(), shared_secret_len), |
| 1046 | + Bytes(expected_k)); |
| 1047 | + } else { |
| 1048 | + EXPECT_FALSE(encaps_result) |
| 1049 | + << "Expected encapsulation to fail for flags: " |
| 1050 | + << result.StringifyFlags(); |
| 1051 | + } |
| 1052 | + } |
| 1053 | + }); |
| 1054 | +} |
| 1055 | + |
| 1056 | +TEST_P(WycheproofKEMTest, DecapsSeed) { |
| 1057 | + std::string test_path = |
| 1058 | + std::string(kWycheproofV1Path) + GetParam().decaps_seed_test; |
| 1059 | + FileTestGTest(test_path.c_str(), [&](FileTest *t) { |
| 1060 | + std::vector<uint8_t> ek, seed, expected_k, ciphertext; |
| 1061 | + std::string param_set; |
| 1062 | + |
| 1063 | + ASSERT_TRUE(t->GetInstruction(¶m_set, "parameterSet")); |
| 1064 | + ASSERT_EQ(param_set, GetParam().name); |
| 1065 | + |
| 1066 | + ASSERT_TRUE(t->GetBytes(&expected_k, "K")); |
| 1067 | + ASSERT_TRUE(t->GetBytes(&ciphertext, "c")); |
| 1068 | + |
| 1069 | + WycheproofResult result; |
| 1070 | + ASSERT_TRUE(GetWycheproofResult(t, &result)); |
| 1071 | + ASSERT_TRUE(t->GetBytes(&seed, "seed")); |
| 1072 | + |
| 1073 | + // Initialize using provided seed |
| 1074 | + bssl::UniquePtr<EVP_PKEY_CTX> ctx( |
| 1075 | + EVP_PKEY_CTX_new_id(EVP_PKEY_KEM, nullptr)); |
| 1076 | + ASSERT_TRUE(ctx); |
| 1077 | + ASSERT_TRUE(EVP_PKEY_CTX_kem_set_params(ctx.get(), GetParam().nid)); |
| 1078 | + EVP_PKEY *raw = nullptr; |
| 1079 | + ASSERT_TRUE(EVP_PKEY_keygen_init(ctx.get())); |
| 1080 | + size_t seed_len = seed.size(); |
| 1081 | + int keygen_result = EVP_PKEY_keygen_deterministic(ctx.get(), &raw, seed.data(), &seed_len); |
| 1082 | + |
| 1083 | + // For invalid test cases, key generation might fail |
| 1084 | + if (!result.IsValid() && !keygen_result) { |
| 1085 | + // Expected failure in key generation for invalid cases |
| 1086 | + return; |
| 1087 | + } |
| 1088 | + |
| 1089 | + ASSERT_TRUE(keygen_result); |
| 1090 | + ASSERT_TRUE(raw); |
| 1091 | + bssl::UniquePtr<EVP_PKEY> pkey(raw); |
| 1092 | + |
| 1093 | + // Verify the generated public key matches the expected public key (if provided) |
| 1094 | + if (t->HasAttribute("ek")) { |
| 1095 | + ASSERT_TRUE(t->GetBytes(&ek, "ek")); |
| 1096 | + size_t actual_ek_len = 0; |
| 1097 | + ASSERT_TRUE( |
| 1098 | + EVP_PKEY_get_raw_public_key(pkey.get(), nullptr, &actual_ek_len)); |
| 1099 | + ASSERT_EQ(actual_ek_len, ek.size()); |
| 1100 | + std::vector<uint8_t> actual_ek(actual_ek_len); |
| 1101 | + ASSERT_TRUE(EVP_PKEY_get_raw_public_key(pkey.get(), actual_ek.data(), |
| 1102 | + &actual_ek_len)); |
| 1103 | + EXPECT_EQ(Bytes(actual_ek), Bytes(ek)); |
| 1104 | + } |
| 1105 | + |
| 1106 | + // Perform decapsulation |
| 1107 | + ctx.reset(EVP_PKEY_CTX_new(pkey.get(), nullptr)); |
| 1108 | + ASSERT_TRUE(ctx); |
| 1109 | + std::vector<uint8_t> shared_secret(GetParam().shared_secret_len); |
| 1110 | + size_t shared_secret_len = shared_secret.size(); |
| 1111 | + int decaps_result = EVP_PKEY_decapsulate( |
| 1112 | + ctx.get(), shared_secret.data(), &shared_secret_len, ciphertext.data(), |
| 1113 | + ciphertext.size()); |
| 1114 | + |
| 1115 | + if (result.IsValid()) { |
| 1116 | + EXPECT_TRUE(decaps_result); |
| 1117 | + EXPECT_EQ(Bytes(shared_secret.data(), shared_secret_len), |
| 1118 | + Bytes(expected_k)); |
| 1119 | + } else { |
| 1120 | + EXPECT_FALSE(decaps_result) |
| 1121 | + << "Expected decapsulation to fail for flags: " |
| 1122 | + << result.StringifyFlags(); |
| 1123 | + } |
| 1124 | + }); |
| 1125 | +} |
| 1126 | + |
| 1127 | +// Test decapsulation with expanded decaps keys |
| 1128 | +TEST_P(WycheproofKEMTest, DecapsNoSeed) { |
| 1129 | + std::string test_path = |
| 1130 | + std::string(kWycheproofV1Path) + GetParam().decaps_noseed_test; |
| 1131 | + FileTestGTest(test_path.c_str(), [&](FileTest *t) { |
| 1132 | + std::vector<uint8_t> dk, ciphertext; |
| 1133 | + std::string param_set; |
| 1134 | + |
| 1135 | + ASSERT_TRUE(t->GetInstruction(¶m_set, "parameterSet")); |
| 1136 | + ASSERT_EQ(param_set, GetParam().name); |
| 1137 | + |
| 1138 | + ASSERT_TRUE(t->GetBytes(&dk, "dk")); |
| 1139 | + ASSERT_TRUE(t->GetBytes(&ciphertext, "c")); |
| 1140 | + |
| 1141 | + WycheproofResult result; |
| 1142 | + ASSERT_TRUE(GetWycheproofResult(t, &result)); |
| 1143 | + |
| 1144 | + // Create key from raw private key bytes |
| 1145 | + bssl::UniquePtr<EVP_PKEY> pkey( |
| 1146 | + EVP_PKEY_kem_new_raw_secret_key(GetParam().nid, dk.data(), dk.size())); |
| 1147 | + |
| 1148 | + // Key creation should fail for incorrect key length |
| 1149 | + if (result.HasFlag("IncorrectDecapsulationKeyLength")) { |
| 1150 | + EXPECT_FALSE(pkey) |
| 1151 | + << "Expected key creation to fail for incorrect key length"; |
| 1152 | + return; |
| 1153 | + } |
| 1154 | + |
| 1155 | + // Warn if we successfully imported an invalid private key |
| 1156 | + if (pkey && result.HasFlag("InvalidDecapsulationKey")) { |
| 1157 | + fprintf(stderr, |
| 1158 | + "WARNING: Successfully imported correct-length-but-invalid %s " |
| 1159 | + "decapsulation key. This is allowed by FIPS 203.\n", |
| 1160 | + param_set.c_str()); |
| 1161 | + } |
| 1162 | + |
| 1163 | + // For valid test cases, key creation should succeed |
| 1164 | + if (result.IsValid()) { |
| 1165 | + ASSERT_TRUE(pkey) << "Key creation failed unexpectedly for flags: " |
| 1166 | + << result.StringifyFlags(); |
| 1167 | + } |
| 1168 | + |
| 1169 | + // Perform decapsulation |
| 1170 | + bssl::UniquePtr<EVP_PKEY_CTX> ctx(EVP_PKEY_CTX_new(pkey.get(), nullptr)); |
| 1171 | + ASSERT_TRUE(ctx); |
| 1172 | + |
| 1173 | + std::vector<uint8_t> shared_secret(GetParam().shared_secret_len); |
| 1174 | + size_t shared_secret_len = shared_secret.size(); |
| 1175 | + int decaps_result = EVP_PKEY_decapsulate( |
| 1176 | + ctx.get(), shared_secret.data(), &shared_secret_len, ciphertext.data(), |
| 1177 | + ciphertext.size()); |
| 1178 | + |
| 1179 | + if (result.IsValid()) { |
| 1180 | + EXPECT_TRUE(decaps_result) |
| 1181 | + << "Expected decapsulation to succeed for valid test case"; |
| 1182 | + } else { |
| 1183 | + EXPECT_FALSE(decaps_result) |
| 1184 | + << "Expected decapsulation to fail for flags: " |
| 1185 | + << result.StringifyFlags(); |
| 1186 | + } |
| 1187 | + }); |
| 1188 | +} |
0 commit comments