| // Copyright 2020 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| #include "tink/experimental/pqcrypto/kem/subtle/cecpq2_hkdf_sender_kem_boringssl.h" |
| |
| #include <string> |
| #include <utility> |
| |
| #include "gtest/gtest.h" |
| #include "absl/memory/memory.h" |
| #include "absl/status/status.h" |
| #include "openssl/curve25519.h" |
| #include "openssl/hrss.h" |
| #include "openssl/sha.h" |
| #include "tink/config/tink_fips.h" |
| #include "tink/experimental/pqcrypto/kem/subtle/cecpq2_hkdf_recipient_kem_boringssl.h" |
| #include "tink/experimental/pqcrypto/kem/subtle/cecpq2_subtle_boringssl_util.h" |
| #include "tink/subtle/common_enums.h" |
| #include "tink/subtle/hkdf.h" |
| #include "tink/subtle/random.h" |
| #include "tink/subtle/subtle_util.h" |
| #include "tink/util/secret_data.h" |
| #include "tink/util/status.h" |
| #include "tink/util/statusor.h" |
| #include "tink/util/test_matchers.h" |
| #include "tink/util/test_util.h" |
| |
| using ::crypto::tink::test::IsOk; |
| using ::crypto::tink::test::StatusIs; |
| using ::testing::HasSubstr; |
| |
| namespace crypto { |
| namespace tink { |
| namespace subtle { |
| namespace { |
| |
| // This test evaluates the creation of a Cecpq2HkdfSenderKemBoringSsl instance |
| // with an unknown curve type parameter. It should fail with an |
| // absl::StatusCode::kUnimplemented error. |
| TEST(Cecpq2HkdfSenderKemBoringSslTest, TestUnknownCurve) { |
| if (IsFipsModeEnabled()) { |
| GTEST_SKIP() << "Not supported in FIPS-only mode"; |
| } |
| |
| auto status_or_cecpq2_key = |
| pqc::GenerateCecpq2Keypair(EllipticCurveType::CURVE25519); |
| ASSERT_TRUE(status_or_cecpq2_key.ok()); |
| auto cecpq2_key_pair = std::move(status_or_cecpq2_key).value(); |
| |
| // Creating an instance of Cecpq2HkdfSenderKemBoringSsl specifying an unknown |
| // curve |
| auto status_or_sender_kem = Cecpq2HkdfSenderKemBoringSsl::New( |
| EllipticCurveType::UNKNOWN_CURVE, cecpq2_key_pair.x25519_key_pair.pub_x, |
| cecpq2_key_pair.x25519_key_pair.pub_y, |
| cecpq2_key_pair.hrss_key_pair.hrss_public_key_marshaled); |
| |
| // The instance creation above should fail with an unimplemented algorithm |
| // error given the UNKNOWN_CURVE parameter |
| EXPECT_EQ(absl::StatusCode::kUnimplemented, |
| status_or_sender_kem.status().code()); |
| } |
| |
| // This test evaluates the case where an unsupported curve (NIST_P256) is |
| // specified. This test should fail with an absl::StatusCode::kUnimplemented |
| // error. |
| TEST(Cecpq2HkdfSenderKemBoringSslTest, TestUnsupportedCurve) { |
| if (IsFipsModeEnabled()) { |
| GTEST_SKIP() << "Not supported in FIPS-only mode"; |
| } |
| |
| auto status_or_cecpq2_key = |
| pqc::GenerateCecpq2Keypair(EllipticCurveType::CURVE25519); |
| ASSERT_TRUE(status_or_cecpq2_key.ok()); |
| auto cecpq2_key_pair = std::move(status_or_cecpq2_key).value(); |
| |
| // Creating an instance of Cecpq2HkdfSenderKemBoringSsl specifying a |
| // unsupported curve |
| auto status_or_sender_kem = Cecpq2HkdfSenderKemBoringSsl::New( |
| EllipticCurveType::NIST_P256, cecpq2_key_pair.x25519_key_pair.pub_x, |
| cecpq2_key_pair.x25519_key_pair.pub_y, |
| cecpq2_key_pair.hrss_key_pair.hrss_public_key_marshaled); |
| |
| // This test should fail with an unimplemented algorithm error |
| EXPECT_EQ(absl::StatusCode::kUnimplemented, |
| status_or_sender_kem.status().code()); |
| } |
| |
| // This test checks that an error is triggered if an output key lenth smaller |
| // than 32 bytes is specified. |
| TEST(Cecpq2HkdfSenderKemBoringSslTest, TestNotPostQuantumSecureKeyLength) { |
| if (IsFipsModeEnabled()) { |
| GTEST_SKIP() << "Not supported in FIPS-only mode"; |
| } |
| |
| // Declaring auxiliary parameters |
| std::string salt_hex = "0b0b0b0b"; |
| std::string info_hex = "0b0b0b0b0b0b0b0b"; |
| |
| // Not post-quantum secure output key length |
| int out_len = 31; |
| |
| auto status_or_cecpq2_key = |
| pqc::GenerateCecpq2Keypair(EllipticCurveType::CURVE25519); |
| ASSERT_TRUE(status_or_cecpq2_key.ok()); |
| auto cecpq2_key_pair = std::move(status_or_cecpq2_key).value(); |
| |
| // Creating an instance of Cecpq2HkdfSenderKemBoringSsl |
| auto status_or_sender_kem = Cecpq2HkdfSenderKemBoringSsl::New( |
| EllipticCurveType::CURVE25519, cecpq2_key_pair.x25519_key_pair.pub_x, |
| cecpq2_key_pair.x25519_key_pair.pub_y, |
| cecpq2_key_pair.hrss_key_pair.hrss_public_key_marshaled); |
| ASSERT_THAT(status_or_sender_kem, IsOk()); |
| auto sender_kem = std::move(status_or_sender_kem.value()); |
| |
| // Generating a symmetric key |
| auto status_or_kem_key = sender_kem->GenerateKey( |
| HashType::SHA256, test::HexDecodeOrDie(salt_hex), |
| test::HexDecodeOrDie(info_hex), out_len, EcPointFormat::COMPRESSED); |
| |
| EXPECT_THAT(status_or_kem_key.status(), |
| StatusIs(absl::StatusCode::kInvalidArgument, |
| HasSubstr("not post-quantum secure"))); |
| } |
| |
| // This test evaluates if a Sender can successfully generate a symmetric key. |
| TEST(Cecpq2HkdfSenderKemBoringSslTest, TestGenerateKey) { |
| if (IsFipsModeEnabled()) { |
| GTEST_SKIP() << "Not supported in FIPS-only mode"; |
| } |
| |
| // Declaring auxiliary parameters |
| std::string salt_hex = "0b0b0b0b"; |
| std::string info_hex = "0b0b0b0b0b0b0b0b"; |
| int out_len = 32; |
| |
| auto status_or_cecpq2_key = |
| pqc::GenerateCecpq2Keypair(EllipticCurveType::CURVE25519); |
| ASSERT_TRUE(status_or_cecpq2_key.ok()); |
| auto cecpq2_key_pair = std::move(status_or_cecpq2_key).value(); |
| |
| // Creating an instance of Cecpq2HkdfSenderKemBoringSsl |
| auto status_or_sender_kem = Cecpq2HkdfSenderKemBoringSsl::New( |
| EllipticCurveType::CURVE25519, cecpq2_key_pair.x25519_key_pair.pub_x, |
| cecpq2_key_pair.x25519_key_pair.pub_y, |
| cecpq2_key_pair.hrss_key_pair.hrss_public_key_marshaled); |
| ASSERT_THAT(status_or_sender_kem, IsOk()); |
| auto sender_kem = std::move(status_or_sender_kem.value()); |
| |
| // Generating a symmetric key |
| auto status_or_kem_key = sender_kem->GenerateKey( |
| HashType::SHA256, test::HexDecodeOrDie(salt_hex), |
| test::HexDecodeOrDie(info_hex), out_len, EcPointFormat::COMPRESSED); |
| |
| // Asserting that the symmetric key has been successfully generated |
| ASSERT_THAT(status_or_kem_key, IsOk()); |
| auto kem_key = std::move(status_or_kem_key.value()); |
| EXPECT_FALSE(kem_key->get_kem_bytes().empty()); |
| EXPECT_EQ(kem_key->get_symmetric_key().size(), out_len); |
| } |
| |
| // This test evaluates the whole KEM flow: from Sender to Recipient. This test |
| // should successfully generate an encapsulated shared secret that matches with |
| // a decapsulated shared secret. |
| TEST(Cecpq2HkdfSenderKemBoringSslTest, TestSenderRecipientFullFlowSuccess) { |
| if (IsFipsModeEnabled()) { |
| GTEST_SKIP() << "Not supported in FIPS-only mode"; |
| } |
| |
| // Declaring auxiliary parameters |
| std::string salt_hex = "0b0b0b0b"; |
| std::string info_hex = "0b0b0b0b0b0b0b0b"; |
| int out_len = 32; |
| |
| auto status_or_cecpq2_key = |
| pqc::GenerateCecpq2Keypair(EllipticCurveType::CURVE25519); |
| ASSERT_TRUE(status_or_cecpq2_key.ok()); |
| auto cecpq2_key_pair = std::move(status_or_cecpq2_key).value(); |
| |
| // Creating an instance of Cecpq2HkdfSenderKemBoringSsl |
| auto status_or_sender_kem = Cecpq2HkdfSenderKemBoringSsl::New( |
| EllipticCurveType::CURVE25519, cecpq2_key_pair.x25519_key_pair.pub_x, |
| cecpq2_key_pair.x25519_key_pair.pub_y, |
| cecpq2_key_pair.hrss_key_pair.hrss_public_key_marshaled); |
| ASSERT_TRUE(status_or_sender_kem.ok()); |
| auto sender_kem = std::move(status_or_sender_kem.value()); |
| |
| // Generating sender's shared secret |
| auto status_or_kem_key = sender_kem->GenerateKey( |
| HashType::SHA256, test::HexDecodeOrDie(salt_hex), |
| test::HexDecodeOrDie(info_hex), out_len, EcPointFormat::COMPRESSED); |
| ASSERT_TRUE(status_or_kem_key.ok()); |
| auto kem_key = std::move(status_or_kem_key.value()); |
| |
| // Initializing recipient's KEM data structure using recipient's private keys |
| auto status_or_recipient_kem = Cecpq2HkdfRecipientKemBoringSsl::New( |
| EllipticCurveType::CURVE25519, cecpq2_key_pair.x25519_key_pair.priv, |
| std::move(cecpq2_key_pair.hrss_key_pair.hrss_private_key_seed)); |
| ASSERT_TRUE(status_or_recipient_kem.ok()); |
| auto recipient_kem = std::move(status_or_recipient_kem.value()); |
| |
| // Generating recipient's shared secret |
| auto status_or_shared_secret = recipient_kem->GenerateKey( |
| kem_key->get_kem_bytes(), HashType::SHA256, |
| test::HexDecodeOrDie(salt_hex), test::HexDecodeOrDie(info_hex), out_len, |
| EcPointFormat::COMPRESSED); |
| ASSERT_TRUE(status_or_shared_secret.ok()); |
| |
| // Asserting that both shared secrets match |
| EXPECT_EQ(test::HexEncode( |
| util::SecretDataAsStringView(kem_key->get_symmetric_key())), |
| test::HexEncode( |
| util::SecretDataAsStringView(status_or_shared_secret.value()))); |
| } |
| |
| // This test evaluates the whole KEM flow as in |
| // TestSenderRecipientFullFlowSuccess with the difference that the caller's |
| // public key is erased after Cecpq2HkdfSenderKemBoringSsl object is created. |
| // This test would detect if the caller's public key buffers are being used |
| // by Cecpq2HkdfSenderKemBoringSsl instead of Cecpq2HkdfSenderKemBoringSsl |
| // having its own explicit copy. |
| TEST(Cecpq2HkdfSenderKemBoringSslTest, TestFullFlowErasedCallersPublicKey) { |
| if (IsFipsModeEnabled()) { |
| GTEST_SKIP() << "Not supported in FIPS-only mode"; |
| } |
| |
| // Declaring auxiliary parameters |
| std::string salt_hex = "0b0b0b0b"; |
| std::string info_hex = "0b0b0b0b0b0b0b0b"; |
| int out_len = 32; |
| |
| auto status_or_cecpq2_key = |
| pqc::GenerateCecpq2Keypair(EllipticCurveType::CURVE25519); |
| ASSERT_TRUE(status_or_cecpq2_key.ok()); |
| auto cecpq2_key_pair = std::move(status_or_cecpq2_key).value(); |
| |
| // Creating an instance of Cecpq2HkdfSenderKemBoringSsl |
| auto status_or_sender_kem = Cecpq2HkdfSenderKemBoringSsl::New( |
| EllipticCurveType::CURVE25519, cecpq2_key_pair.x25519_key_pair.pub_x, |
| cecpq2_key_pair.x25519_key_pair.pub_y, |
| cecpq2_key_pair.hrss_key_pair.hrss_public_key_marshaled); |
| ASSERT_THAT(status_or_sender_kem, IsOk()); |
| auto sender_kem = std::move(status_or_sender_kem.value()); |
| |
| // Erasing caller's public key buffers |
| cecpq2_key_pair.x25519_key_pair.pub_x.clear(); |
| cecpq2_key_pair.x25519_key_pair.pub_y.clear(); |
| cecpq2_key_pair.hrss_key_pair.hrss_public_key_marshaled.clear(); |
| |
| // Generating sender's shared secret |
| auto status_or_kem_key = sender_kem->GenerateKey( |
| HashType::SHA256, test::HexDecodeOrDie(salt_hex), |
| test::HexDecodeOrDie(info_hex), out_len, EcPointFormat::COMPRESSED); |
| ASSERT_THAT(status_or_kem_key, IsOk()); |
| auto kem_key = std::move(status_or_kem_key.value()); |
| |
| // Initializing recipient's KEM data structure using recipient's private keys |
| auto status_or_recipient_kem = Cecpq2HkdfRecipientKemBoringSsl::New( |
| EllipticCurveType::CURVE25519, cecpq2_key_pair.x25519_key_pair.priv, |
| std::move(cecpq2_key_pair.hrss_key_pair.hrss_private_key_seed)); |
| ASSERT_THAT(status_or_recipient_kem, IsOk()); |
| auto recipient_kem = std::move(status_or_recipient_kem.value()); |
| |
| // Generating recipient's shared secret |
| auto status_or_shared_secret = recipient_kem->GenerateKey( |
| kem_key->get_kem_bytes(), HashType::SHA256, |
| test::HexDecodeOrDie(salt_hex), test::HexDecodeOrDie(info_hex), out_len, |
| EcPointFormat::COMPRESSED); |
| ASSERT_THAT(status_or_shared_secret, IsOk()); |
| |
| // Asserting that both shared secrets match |
| EXPECT_EQ(test::HexEncode( |
| util::SecretDataAsStringView(kem_key->get_symmetric_key())), |
| test::HexEncode( |
| util::SecretDataAsStringView(status_or_shared_secret.value()))); |
| } |
| |
| // This test evaluates the whole KEM flow: from Sender to Recipient. This test |
| // is essentially the same as TestSenderRecipientFullFlowSuccess with the |
| // difference that we alter bytes of the kem_bytes thus preventing the two |
| // shared secrets to match. |
| TEST(Cecpq2HkdfSenderKemBoringSslTest, TestSenderRecipientFullFlowFailure) { |
| if (IsFipsModeEnabled()) { |
| GTEST_SKIP() << "Not supported in FIPS-only mode"; |
| } |
| |
| // Declaring auxiliary parameters |
| std::string info_hex = "0b0b0b0b0b0b0b0b"; |
| std::string salt_hex = "0b0b0b0b"; |
| int out_len = 32; |
| |
| auto status_or_cecpq2_key = |
| pqc::GenerateCecpq2Keypair(EllipticCurveType::CURVE25519); |
| ASSERT_TRUE(status_or_cecpq2_key.ok()); |
| auto cecpq2_key_pair = std::move(status_or_cecpq2_key).value(); |
| |
| // Initializing sender's KEM data structure using recipient's public keys |
| auto status_or_sender_kem = Cecpq2HkdfSenderKemBoringSsl::New( |
| EllipticCurveType::CURVE25519, cecpq2_key_pair.x25519_key_pair.pub_x, |
| cecpq2_key_pair.x25519_key_pair.pub_y, |
| cecpq2_key_pair.hrss_key_pair.hrss_public_key_marshaled); |
| ASSERT_THAT(status_or_sender_kem, IsOk()); |
| auto sender_kem = std::move(status_or_sender_kem.value()); |
| |
| // Generating sender's shared secret (using salt_hex1) |
| auto status_or_kem_key = sender_kem->GenerateKey( |
| HashType::SHA256, test::HexDecodeOrDie(salt_hex), |
| test::HexDecodeOrDie(info_hex), out_len, EcPointFormat::COMPRESSED); |
| ASSERT_THAT(status_or_kem_key, IsOk()); |
| auto kem_key = std::move(status_or_kem_key.value()); |
| |
| // Initializing recipient's KEM data structure using recipient's private keys |
| auto status_or_recipient_kem = Cecpq2HkdfRecipientKemBoringSsl::New( |
| EllipticCurveType::CURVE25519, cecpq2_key_pair.x25519_key_pair.priv, |
| std::move(cecpq2_key_pair.hrss_key_pair.hrss_private_key_seed)); |
| ASSERT_THAT(status_or_recipient_kem, IsOk()); |
| auto recipient_kem = std::move(status_or_recipient_kem.value()); |
| |
| // Here, we corrupt kem_bytes (we change all bytes to "a") so that |
| // the HRSS shared secret is not successfully recovered |
| std::string kem_bytes = kem_key->get_kem_bytes(); |
| for (int i = 0; i < HRSS_CIPHERTEXT_BYTES; i++) |
| kem_bytes[X25519_PUBLIC_VALUE_LEN + i] = 'a'; |
| |
| // Generating the defective recipient's shared secret |
| auto status_or_shared_secret = recipient_kem->GenerateKey( |
| kem_bytes, HashType::SHA256, test::HexDecodeOrDie(salt_hex), |
| test::HexDecodeOrDie(info_hex), out_len, EcPointFormat::COMPRESSED); |
| |
| // With very high probability, the shared secrets should not match |
| EXPECT_NE(test::HexEncode( |
| util::SecretDataAsStringView(kem_key->get_symmetric_key())), |
| test::HexEncode( |
| util::SecretDataAsStringView(status_or_shared_secret.value()))); |
| } |
| |
| } // namespace |
| } // namespace subtle |
| } // namespace tink |
| } // namespace crypto |