1 // Copyright 2020 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15 ///////////////////////////////////////////////////////////////////////////////
16
17 #include "tink/experimental/pqcrypto/kem/subtle/cecpq2_hkdf_sender_kem_boringssl.h"
18
19 #include <string>
20 #include <utility>
21
22 #include "gtest/gtest.h"
23 #include "absl/memory/memory.h"
24 #include "absl/status/status.h"
25 #include "openssl/curve25519.h"
26 #include "openssl/hrss.h"
27 #include "openssl/sha.h"
28 #include "tink/config/tink_fips.h"
29 #include "tink/experimental/pqcrypto/kem/subtle/cecpq2_hkdf_recipient_kem_boringssl.h"
30 #include "tink/experimental/pqcrypto/kem/subtle/cecpq2_subtle_boringssl_util.h"
31 #include "tink/subtle/common_enums.h"
32 #include "tink/subtle/hkdf.h"
33 #include "tink/subtle/random.h"
34 #include "tink/subtle/subtle_util.h"
35 #include "tink/util/secret_data.h"
36 #include "tink/util/status.h"
37 #include "tink/util/statusor.h"
38 #include "tink/util/test_matchers.h"
39 #include "tink/util/test_util.h"
40
41 using ::crypto::tink::test::IsOk;
42 using ::crypto::tink::test::StatusIs;
43 using ::testing::HasSubstr;
44
45 namespace crypto {
46 namespace tink {
47 namespace subtle {
48 namespace {
49
50 // This test evaluates the creation of a Cecpq2HkdfSenderKemBoringSsl instance
51 // with an unknown curve type parameter. It should fail with an
52 // absl::StatusCode::kUnimplemented error.
TEST(Cecpq2HkdfSenderKemBoringSslTest,TestUnknownCurve)53 TEST(Cecpq2HkdfSenderKemBoringSslTest, TestUnknownCurve) {
54 if (IsFipsModeEnabled()) {
55 GTEST_SKIP() << "Not supported in FIPS-only mode";
56 }
57
58 auto status_or_cecpq2_key =
59 pqc::GenerateCecpq2Keypair(EllipticCurveType::CURVE25519);
60 ASSERT_TRUE(status_or_cecpq2_key.ok());
61 auto cecpq2_key_pair = std::move(status_or_cecpq2_key).value();
62
63 // Creating an instance of Cecpq2HkdfSenderKemBoringSsl specifying an unknown
64 // curve
65 auto status_or_sender_kem = Cecpq2HkdfSenderKemBoringSsl::New(
66 EllipticCurveType::UNKNOWN_CURVE, cecpq2_key_pair.x25519_key_pair.pub_x,
67 cecpq2_key_pair.x25519_key_pair.pub_y,
68 cecpq2_key_pair.hrss_key_pair.hrss_public_key_marshaled);
69
70 // The instance creation above should fail with an unimplemented algorithm
71 // error given the UNKNOWN_CURVE parameter
72 EXPECT_EQ(absl::StatusCode::kUnimplemented,
73 status_or_sender_kem.status().code());
74 }
75
76 // This test evaluates the case where an unsupported curve (NIST_P256) is
77 // specified. This test should fail with an absl::StatusCode::kUnimplemented
78 // error.
TEST(Cecpq2HkdfSenderKemBoringSslTest,TestUnsupportedCurve)79 TEST(Cecpq2HkdfSenderKemBoringSslTest, TestUnsupportedCurve) {
80 if (IsFipsModeEnabled()) {
81 GTEST_SKIP() << "Not supported in FIPS-only mode";
82 }
83
84 auto status_or_cecpq2_key =
85 pqc::GenerateCecpq2Keypair(EllipticCurveType::CURVE25519);
86 ASSERT_TRUE(status_or_cecpq2_key.ok());
87 auto cecpq2_key_pair = std::move(status_or_cecpq2_key).value();
88
89 // Creating an instance of Cecpq2HkdfSenderKemBoringSsl specifying a
90 // unsupported curve
91 auto status_or_sender_kem = Cecpq2HkdfSenderKemBoringSsl::New(
92 EllipticCurveType::NIST_P256, cecpq2_key_pair.x25519_key_pair.pub_x,
93 cecpq2_key_pair.x25519_key_pair.pub_y,
94 cecpq2_key_pair.hrss_key_pair.hrss_public_key_marshaled);
95
96 // This test should fail with an unimplemented algorithm error
97 EXPECT_EQ(absl::StatusCode::kUnimplemented,
98 status_or_sender_kem.status().code());
99 }
100
101 // This test checks that an error is triggered if an output key lenth smaller
102 // than 32 bytes is specified.
TEST(Cecpq2HkdfSenderKemBoringSslTest,TestNotPostQuantumSecureKeyLength)103 TEST(Cecpq2HkdfSenderKemBoringSslTest, TestNotPostQuantumSecureKeyLength) {
104 if (IsFipsModeEnabled()) {
105 GTEST_SKIP() << "Not supported in FIPS-only mode";
106 }
107
108 // Declaring auxiliary parameters
109 std::string salt_hex = "0b0b0b0b";
110 std::string info_hex = "0b0b0b0b0b0b0b0b";
111
112 // Not post-quantum secure output key length
113 int out_len = 31;
114
115 auto status_or_cecpq2_key =
116 pqc::GenerateCecpq2Keypair(EllipticCurveType::CURVE25519);
117 ASSERT_TRUE(status_or_cecpq2_key.ok());
118 auto cecpq2_key_pair = std::move(status_or_cecpq2_key).value();
119
120 // Creating an instance of Cecpq2HkdfSenderKemBoringSsl
121 auto status_or_sender_kem = Cecpq2HkdfSenderKemBoringSsl::New(
122 EllipticCurveType::CURVE25519, cecpq2_key_pair.x25519_key_pair.pub_x,
123 cecpq2_key_pair.x25519_key_pair.pub_y,
124 cecpq2_key_pair.hrss_key_pair.hrss_public_key_marshaled);
125 ASSERT_THAT(status_or_sender_kem, IsOk());
126 auto sender_kem = std::move(status_or_sender_kem.value());
127
128 // Generating a symmetric key
129 auto status_or_kem_key = sender_kem->GenerateKey(
130 HashType::SHA256, test::HexDecodeOrDie(salt_hex),
131 test::HexDecodeOrDie(info_hex), out_len, EcPointFormat::COMPRESSED);
132
133 EXPECT_THAT(status_or_kem_key.status(),
134 StatusIs(absl::StatusCode::kInvalidArgument,
135 HasSubstr("not post-quantum secure")));
136 }
137
138 // This test evaluates if a Sender can successfully generate a symmetric key.
TEST(Cecpq2HkdfSenderKemBoringSslTest,TestGenerateKey)139 TEST(Cecpq2HkdfSenderKemBoringSslTest, TestGenerateKey) {
140 if (IsFipsModeEnabled()) {
141 GTEST_SKIP() << "Not supported in FIPS-only mode";
142 }
143
144 // Declaring auxiliary parameters
145 std::string salt_hex = "0b0b0b0b";
146 std::string info_hex = "0b0b0b0b0b0b0b0b";
147 int out_len = 32;
148
149 auto status_or_cecpq2_key =
150 pqc::GenerateCecpq2Keypair(EllipticCurveType::CURVE25519);
151 ASSERT_TRUE(status_or_cecpq2_key.ok());
152 auto cecpq2_key_pair = std::move(status_or_cecpq2_key).value();
153
154 // Creating an instance of Cecpq2HkdfSenderKemBoringSsl
155 auto status_or_sender_kem = Cecpq2HkdfSenderKemBoringSsl::New(
156 EllipticCurveType::CURVE25519, cecpq2_key_pair.x25519_key_pair.pub_x,
157 cecpq2_key_pair.x25519_key_pair.pub_y,
158 cecpq2_key_pair.hrss_key_pair.hrss_public_key_marshaled);
159 ASSERT_THAT(status_or_sender_kem, IsOk());
160 auto sender_kem = std::move(status_or_sender_kem.value());
161
162 // Generating a symmetric key
163 auto status_or_kem_key = sender_kem->GenerateKey(
164 HashType::SHA256, test::HexDecodeOrDie(salt_hex),
165 test::HexDecodeOrDie(info_hex), out_len, EcPointFormat::COMPRESSED);
166
167 // Asserting that the symmetric key has been successfully generated
168 ASSERT_THAT(status_or_kem_key, IsOk());
169 auto kem_key = std::move(status_or_kem_key.value());
170 EXPECT_FALSE(kem_key->get_kem_bytes().empty());
171 EXPECT_EQ(kem_key->get_symmetric_key().size(), out_len);
172 }
173
174 // This test evaluates the whole KEM flow: from Sender to Recipient. This test
175 // should successfully generate an encapsulated shared secret that matches with
176 // a decapsulated shared secret.
TEST(Cecpq2HkdfSenderKemBoringSslTest,TestSenderRecipientFullFlowSuccess)177 TEST(Cecpq2HkdfSenderKemBoringSslTest, TestSenderRecipientFullFlowSuccess) {
178 if (IsFipsModeEnabled()) {
179 GTEST_SKIP() << "Not supported in FIPS-only mode";
180 }
181
182 // Declaring auxiliary parameters
183 std::string salt_hex = "0b0b0b0b";
184 std::string info_hex = "0b0b0b0b0b0b0b0b";
185 int out_len = 32;
186
187 auto status_or_cecpq2_key =
188 pqc::GenerateCecpq2Keypair(EllipticCurveType::CURVE25519);
189 ASSERT_TRUE(status_or_cecpq2_key.ok());
190 auto cecpq2_key_pair = std::move(status_or_cecpq2_key).value();
191
192 // Creating an instance of Cecpq2HkdfSenderKemBoringSsl
193 auto status_or_sender_kem = Cecpq2HkdfSenderKemBoringSsl::New(
194 EllipticCurveType::CURVE25519, cecpq2_key_pair.x25519_key_pair.pub_x,
195 cecpq2_key_pair.x25519_key_pair.pub_y,
196 cecpq2_key_pair.hrss_key_pair.hrss_public_key_marshaled);
197 ASSERT_TRUE(status_or_sender_kem.ok());
198 auto sender_kem = std::move(status_or_sender_kem.value());
199
200 // Generating sender's shared secret
201 auto status_or_kem_key = sender_kem->GenerateKey(
202 HashType::SHA256, test::HexDecodeOrDie(salt_hex),
203 test::HexDecodeOrDie(info_hex), out_len, EcPointFormat::COMPRESSED);
204 ASSERT_TRUE(status_or_kem_key.ok());
205 auto kem_key = std::move(status_or_kem_key.value());
206
207 // Initializing recipient's KEM data structure using recipient's private keys
208 auto status_or_recipient_kem = Cecpq2HkdfRecipientKemBoringSsl::New(
209 EllipticCurveType::CURVE25519, cecpq2_key_pair.x25519_key_pair.priv,
210 std::move(cecpq2_key_pair.hrss_key_pair.hrss_private_key_seed));
211 ASSERT_TRUE(status_or_recipient_kem.ok());
212 auto recipient_kem = std::move(status_or_recipient_kem.value());
213
214 // Generating recipient's shared secret
215 auto status_or_shared_secret = recipient_kem->GenerateKey(
216 kem_key->get_kem_bytes(), HashType::SHA256,
217 test::HexDecodeOrDie(salt_hex), test::HexDecodeOrDie(info_hex), out_len,
218 EcPointFormat::COMPRESSED);
219 ASSERT_TRUE(status_or_shared_secret.ok());
220
221 // Asserting that both shared secrets match
222 EXPECT_EQ(test::HexEncode(
223 util::SecretDataAsStringView(kem_key->get_symmetric_key())),
224 test::HexEncode(
225 util::SecretDataAsStringView(status_or_shared_secret.value())));
226 }
227
228 // This test evaluates the whole KEM flow as in
229 // TestSenderRecipientFullFlowSuccess with the difference that the caller's
230 // public key is erased after Cecpq2HkdfSenderKemBoringSsl object is created.
231 // This test would detect if the caller's public key buffers are being used
232 // by Cecpq2HkdfSenderKemBoringSsl instead of Cecpq2HkdfSenderKemBoringSsl
233 // having its own explicit copy.
TEST(Cecpq2HkdfSenderKemBoringSslTest,TestFullFlowErasedCallersPublicKey)234 TEST(Cecpq2HkdfSenderKemBoringSslTest, TestFullFlowErasedCallersPublicKey) {
235 if (IsFipsModeEnabled()) {
236 GTEST_SKIP() << "Not supported in FIPS-only mode";
237 }
238
239 // Declaring auxiliary parameters
240 std::string salt_hex = "0b0b0b0b";
241 std::string info_hex = "0b0b0b0b0b0b0b0b";
242 int out_len = 32;
243
244 auto status_or_cecpq2_key =
245 pqc::GenerateCecpq2Keypair(EllipticCurveType::CURVE25519);
246 ASSERT_TRUE(status_or_cecpq2_key.ok());
247 auto cecpq2_key_pair = std::move(status_or_cecpq2_key).value();
248
249 // Creating an instance of Cecpq2HkdfSenderKemBoringSsl
250 auto status_or_sender_kem = Cecpq2HkdfSenderKemBoringSsl::New(
251 EllipticCurveType::CURVE25519, cecpq2_key_pair.x25519_key_pair.pub_x,
252 cecpq2_key_pair.x25519_key_pair.pub_y,
253 cecpq2_key_pair.hrss_key_pair.hrss_public_key_marshaled);
254 ASSERT_THAT(status_or_sender_kem, IsOk());
255 auto sender_kem = std::move(status_or_sender_kem.value());
256
257 // Erasing caller's public key buffers
258 cecpq2_key_pair.x25519_key_pair.pub_x.clear();
259 cecpq2_key_pair.x25519_key_pair.pub_y.clear();
260 cecpq2_key_pair.hrss_key_pair.hrss_public_key_marshaled.clear();
261
262 // Generating sender's shared secret
263 auto status_or_kem_key = sender_kem->GenerateKey(
264 HashType::SHA256, test::HexDecodeOrDie(salt_hex),
265 test::HexDecodeOrDie(info_hex), out_len, EcPointFormat::COMPRESSED);
266 ASSERT_THAT(status_or_kem_key, IsOk());
267 auto kem_key = std::move(status_or_kem_key.value());
268
269 // Initializing recipient's KEM data structure using recipient's private keys
270 auto status_or_recipient_kem = Cecpq2HkdfRecipientKemBoringSsl::New(
271 EllipticCurveType::CURVE25519, cecpq2_key_pair.x25519_key_pair.priv,
272 std::move(cecpq2_key_pair.hrss_key_pair.hrss_private_key_seed));
273 ASSERT_THAT(status_or_recipient_kem, IsOk());
274 auto recipient_kem = std::move(status_or_recipient_kem.value());
275
276 // Generating recipient's shared secret
277 auto status_or_shared_secret = recipient_kem->GenerateKey(
278 kem_key->get_kem_bytes(), HashType::SHA256,
279 test::HexDecodeOrDie(salt_hex), test::HexDecodeOrDie(info_hex), out_len,
280 EcPointFormat::COMPRESSED);
281 ASSERT_THAT(status_or_shared_secret, IsOk());
282
283 // Asserting that both shared secrets match
284 EXPECT_EQ(test::HexEncode(
285 util::SecretDataAsStringView(kem_key->get_symmetric_key())),
286 test::HexEncode(
287 util::SecretDataAsStringView(status_or_shared_secret.value())));
288 }
289
290 // This test evaluates the whole KEM flow: from Sender to Recipient. This test
291 // is essentially the same as TestSenderRecipientFullFlowSuccess with the
292 // difference that we alter bytes of the kem_bytes thus preventing the two
293 // shared secrets to match.
TEST(Cecpq2HkdfSenderKemBoringSslTest,TestSenderRecipientFullFlowFailure)294 TEST(Cecpq2HkdfSenderKemBoringSslTest, TestSenderRecipientFullFlowFailure) {
295 if (IsFipsModeEnabled()) {
296 GTEST_SKIP() << "Not supported in FIPS-only mode";
297 }
298
299 // Declaring auxiliary parameters
300 std::string info_hex = "0b0b0b0b0b0b0b0b";
301 std::string salt_hex = "0b0b0b0b";
302 int out_len = 32;
303
304 auto status_or_cecpq2_key =
305 pqc::GenerateCecpq2Keypair(EllipticCurveType::CURVE25519);
306 ASSERT_TRUE(status_or_cecpq2_key.ok());
307 auto cecpq2_key_pair = std::move(status_or_cecpq2_key).value();
308
309 // Initializing sender's KEM data structure using recipient's public keys
310 auto status_or_sender_kem = Cecpq2HkdfSenderKemBoringSsl::New(
311 EllipticCurveType::CURVE25519, cecpq2_key_pair.x25519_key_pair.pub_x,
312 cecpq2_key_pair.x25519_key_pair.pub_y,
313 cecpq2_key_pair.hrss_key_pair.hrss_public_key_marshaled);
314 ASSERT_THAT(status_or_sender_kem, IsOk());
315 auto sender_kem = std::move(status_or_sender_kem.value());
316
317 // Generating sender's shared secret (using salt_hex1)
318 auto status_or_kem_key = sender_kem->GenerateKey(
319 HashType::SHA256, test::HexDecodeOrDie(salt_hex),
320 test::HexDecodeOrDie(info_hex), out_len, EcPointFormat::COMPRESSED);
321 ASSERT_THAT(status_or_kem_key, IsOk());
322 auto kem_key = std::move(status_or_kem_key.value());
323
324 // Initializing recipient's KEM data structure using recipient's private keys
325 auto status_or_recipient_kem = Cecpq2HkdfRecipientKemBoringSsl::New(
326 EllipticCurveType::CURVE25519, cecpq2_key_pair.x25519_key_pair.priv,
327 std::move(cecpq2_key_pair.hrss_key_pair.hrss_private_key_seed));
328 ASSERT_THAT(status_or_recipient_kem, IsOk());
329 auto recipient_kem = std::move(status_or_recipient_kem.value());
330
331 // Here, we corrupt kem_bytes (we change all bytes to "a") so that
332 // the HRSS shared secret is not successfully recovered
333 std::string kem_bytes = kem_key->get_kem_bytes();
334 for (int i = 0; i < HRSS_CIPHERTEXT_BYTES; i++)
335 kem_bytes[X25519_PUBLIC_VALUE_LEN + i] = 'a';
336
337 // Generating the defective recipient's shared secret
338 auto status_or_shared_secret = recipient_kem->GenerateKey(
339 kem_bytes, HashType::SHA256, test::HexDecodeOrDie(salt_hex),
340 test::HexDecodeOrDie(info_hex), out_len, EcPointFormat::COMPRESSED);
341
342 // With very high probability, the shared secrets should not match
343 EXPECT_NE(test::HexEncode(
344 util::SecretDataAsStringView(kem_key->get_symmetric_key())),
345 test::HexEncode(
346 util::SecretDataAsStringView(status_or_shared_secret.value())));
347 }
348
349 } // namespace
350 } // namespace subtle
351 } // namespace tink
352 } // namespace crypto
353