blob: 46158307048d2d39a2b44698ea0e242bfc1fd593 [file] [log] [blame]
// 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