blob: 867bd202a75bf76ce6a4a29dcaa0ac910fc2eee4 [file] [log] [blame]
// Copyright 2019 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/subtle/prf/hkdf_streaming_prf.h"
#include <string>
#include <utility>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/status/status.h"
#include "tink/config/tink_fips.h"
#include "tink/subtle/hkdf.h"
#include "tink/subtle/random.h"
#include "tink/util/input_stream_util.h"
#include "tink/util/secret_data.h"
#include "tink/util/test_matchers.h"
#include "tink/util/test_util.h"
namespace crypto {
namespace tink {
namespace subtle {
namespace {
using ::crypto::tink::test::HexDecodeOrDie;
using ::crypto::tink::test::IsOk;
using ::crypto::tink::test::StatusIs;
using ::testing::Eq;
using ::testing::Ge;
using ::testing::Ne;
using ::testing::Not;
using ::testing::SizeIs;
// GENERIC TESTS ===============================================================
//
// These should be satisfied for any streaming prf which generates enough
// output.
TEST(HkdfStreamingPrf, Basic) {
if (IsFipsModeEnabled()) {
GTEST_SKIP() << "Not supported in FIPS-only mode";
}
auto streaming_prf_or = HkdfStreamingPrf::New(
SHA512, util::SecretDataFromStringView("key0123456"), "salt");
ASSERT_THAT(streaming_prf_or, IsOk());
std::unique_ptr<InputStream> stream =
streaming_prf_or.value()->ComputePrf("input");
auto result_or = ReadBytesFromStream(10, stream.get());
ASSERT_THAT(result_or, IsOk());
EXPECT_THAT(result_or.value(), SizeIs(10));
}
TEST(HkdfStreamingPrf, DifferentInputsGiveDifferentvalues) {
if (IsFipsModeEnabled()) {
GTEST_SKIP() << "Not supported in FIPS-only mode";
}
auto streaming_prf_or = HkdfStreamingPrf::New(
SHA512, util::SecretDataFromStringView("key0123456"), "salt");
ASSERT_THAT(streaming_prf_or, IsOk());
std::unique_ptr<InputStream> stream =
streaming_prf_or.value()->ComputePrf("input");
auto result_or = ReadBytesFromStream(10, stream.get());
ASSERT_THAT(result_or, IsOk());
// Different input.
std::unique_ptr<InputStream> stream2 =
streaming_prf_or.value()->ComputePrf("input2");
auto result_or2 = ReadBytesFromStream(10, stream2.get());
ASSERT_THAT(result_or2, IsOk());
EXPECT_THAT(result_or2.value(), Ne(result_or.value()));
}
TEST(HkdfStreamingPrf, SameInputTwice) {
if (IsFipsModeEnabled()) {
GTEST_SKIP() << "Not supported in FIPS-only mode";
}
auto streaming_prf_or = HkdfStreamingPrf::New(
SHA512, util::SecretDataFromStringView("key0123456"), "salt");
ASSERT_THAT(streaming_prf_or, IsOk());
std::unique_ptr<InputStream> stream =
streaming_prf_or.value()->ComputePrf("input");
auto result_or = ReadBytesFromStream(10, stream.get());
ASSERT_THAT(result_or, IsOk());
// Same input.
std::unique_ptr<InputStream> stream2 =
streaming_prf_or.value()->ComputePrf("input");
auto result_or2 = ReadBytesFromStream(10, stream2.get());
ASSERT_THAT(result_or2, IsOk());
EXPECT_THAT(result_or2.value(), Eq(result_or.value()));
}
// STREAM HANDLING TESTS =======================================================
//
// These check that the buffer handling of the implementation is correct. They
// should be satisfied with any input stream.
// Tests that after Backing up the full stream, we get back the same data.
TEST(HkdfStreamingPrf, BackupFullStream) {
if (IsFipsModeEnabled()) {
GTEST_SKIP() << "Not supported in FIPS-only mode";
}
auto streaming_prf_or = HkdfStreamingPrf::New(
SHA256, util::SecretDataFromStringView("key0123456"), "salt");
ASSERT_THAT(streaming_prf_or, IsOk());
std::unique_ptr<InputStream> stream =
streaming_prf_or.value()->ComputePrf("input");
const void* data;
crypto::tink::util::StatusOr<int> result = stream->Next(&data);
ASSERT_THAT(result, IsOk());
int bytes_read = result.value();
std::string first_read =
std::string(static_cast<const char*>(data), bytes_read);
stream->BackUp(bytes_read);
result = stream->Next(&data);
ASSERT_THAT(result, IsOk());
// We typically read at least as many bytes the second time -- strictly
// speaking this might not be satisfied by every InputStream, but it usually
// will be.
ASSERT_THAT(result.value(), Ge(bytes_read));
std::string second_read =
std::string(static_cast<const char*>(data), bytes_read);
EXPECT_THAT(first_read, Eq(second_read));
}
// Tests that after Backing up half the stream, we get back the same data.
TEST(HkdfStreamingPrf, BackupHalf) {
if (IsFipsModeEnabled()) {
GTEST_SKIP() << "Not supported in FIPS-only mode";
}
auto streaming_prf_or = HkdfStreamingPrf::New(
SHA256, util::SecretDataFromStringView("key0123456"), "salt");
ASSERT_THAT(streaming_prf_or, IsOk());
std::unique_ptr<InputStream> stream =
streaming_prf_or.value()->ComputePrf("input");
const void* data;
crypto::tink::util::StatusOr<int> result = stream->Next(&data);
ASSERT_THAT(result, IsOk());
int bytes_read = result.value();
int backup_amount = bytes_read / 2;
std::string first_read =
std::string(static_cast<const char*>(data) + bytes_read - backup_amount,
backup_amount);
stream->BackUp(backup_amount);
result = stream->Next(&data);
ASSERT_THAT(result, IsOk());
// We typically read at least as many bytes the second time -- strictly
// speaking this might not be satisfied by every InputStream, but it usually
// will be.
ASSERT_THAT(result.value(), Ge(backup_amount));
std::string second_read =
std::string(static_cast<const char*>(data), backup_amount);
EXPECT_THAT(first_read, Eq(second_read));
}
// Tests that after Position is correct initially (i.e., 0).
TEST(HkdfStreamingPrf, PositionOneRead) {
if (IsFipsModeEnabled()) {
GTEST_SKIP() << "Not supported in FIPS-only mode";
}
auto streaming_prf_or = HkdfStreamingPrf::New(
SHA256, util::SecretDataFromStringView("key0123456"), "salt");
ASSERT_THAT(streaming_prf_or, IsOk());
std::unique_ptr<InputStream> stream =
streaming_prf_or.value()->ComputePrf("input");
EXPECT_THAT(stream->Position(), Eq(0));
}
// Tests that after Position is correct after a single read.
TEST(HkdfStreamingPrf, PositionSingleRead) {
if (IsFipsModeEnabled()) {
GTEST_SKIP() << "Not supported in FIPS-only mode";
}
auto streaming_prf_or = HkdfStreamingPrf::New(
SHA256, util::SecretDataFromStringView("key0123456"), "salt");
ASSERT_THAT(streaming_prf_or, IsOk());
std::unique_ptr<InputStream> stream =
streaming_prf_or.value()->ComputePrf("input");
const void* data;
crypto::tink::util::StatusOr<int> result = stream->Next(&data);
ASSERT_THAT(result, IsOk());
EXPECT_THAT(stream->Position(), Eq(result.value()));
}
// Tests that after Position is correct after a two reads.
TEST(HkdfStreamingPrf, PositionTwoReads) {
if (IsFipsModeEnabled()) {
GTEST_SKIP() << "Not supported in FIPS-only mode";
}
auto streaming_prf_or = HkdfStreamingPrf::New(
SHA256, util::SecretDataFromStringView("key0123456"), "salt");
ASSERT_THAT(streaming_prf_or, IsOk());
std::unique_ptr<InputStream> stream =
streaming_prf_or.value()->ComputePrf("input");
const void* data;
crypto::tink::util::StatusOr<int> result = stream->Next(&data);
ASSERT_THAT(result, IsOk());
crypto::tink::util::StatusOr<int> result2 = stream->Next(&data);
ASSERT_THAT(result, IsOk());
EXPECT_THAT(stream->Position(), Eq(result.value() + result2.value()));
}
// Tests that we can backup the first read completely.
TEST(HkdfStreamingPrf, BackupSingleRead) {
if (IsFipsModeEnabled()) {
GTEST_SKIP() << "Not supported in FIPS-only mode";
}
auto streaming_prf_or = HkdfStreamingPrf::New(
SHA256, util::SecretDataFromStringView("key0123456"), "salt");
ASSERT_THAT(streaming_prf_or, IsOk());
std::unique_ptr<InputStream> stream =
streaming_prf_or.value()->ComputePrf("input");
const void* data;
crypto::tink::util::StatusOr<int> result = stream->Next(&data);
ASSERT_THAT(result, IsOk());
stream->BackUp(result.value());
EXPECT_THAT(stream->Position(), Eq(0));
}
// Tests that we can backup the second read completely.
TEST(HkdfStreamingPrf, BackupSecondRead) {
if (IsFipsModeEnabled()) {
GTEST_SKIP() << "Not supported in FIPS-only mode";
}
auto streaming_prf_or = HkdfStreamingPrf::New(
SHA256, util::SecretDataFromStringView("key0123456"), "salt");
ASSERT_THAT(streaming_prf_or, IsOk());
std::unique_ptr<InputStream> stream =
streaming_prf_or.value()->ComputePrf("input");
const void* data;
crypto::tink::util::StatusOr<int> result = stream->Next(&data);
ASSERT_THAT(result, IsOk());
crypto::tink::util::StatusOr<int> result2 = stream->Next(&data);
ASSERT_THAT(result, IsOk());
stream->BackUp(result2.value());
EXPECT_THAT(stream->Position(), Eq(result.value()));
}
// Tests that we can partially backup and position is correct.
TEST(HkdfStreamingPrf, PartialBackup) {
if (IsFipsModeEnabled()) {
GTEST_SKIP() << "Not supported in FIPS-only mode";
}
auto streaming_prf_or = HkdfStreamingPrf::New(
SHA256, util::SecretDataFromStringView("key0123456"), "salt");
ASSERT_THAT(streaming_prf_or, IsOk());
std::unique_ptr<InputStream> stream =
streaming_prf_or.value()->ComputePrf("input");
const void* data;
crypto::tink::util::StatusOr<int> result = stream->Next(&data);
ASSERT_THAT(result, IsOk());
stream->BackUp(result.value() / 2);
EXPECT_THAT(stream->Position(), Eq(result.value() - result.value() / 2));
}
// HKDF Specific tests =========================================================
// Tests which only apply for Hkdf.
TEST(HkdfStreamingPrf, ExhaustInput) {
if (IsFipsModeEnabled()) {
GTEST_SKIP() << "Not supported in FIPS-only mode";
}
auto streaming_prf_or = HkdfStreamingPrf::New(
SHA512, util::SecretDataFromStringView("key0123456"), "salt");
ASSERT_THAT(streaming_prf_or, IsOk());
const int max_output_length = 255 * (512 / 8);
std::unique_ptr<InputStream> stream =
streaming_prf_or.value()->ComputePrf("input");
auto result_or = ReadBytesFromStream(max_output_length, stream.get());
ASSERT_THAT(result_or, IsOk());
EXPECT_THAT(result_or.value(), SizeIs(max_output_length));
result_or = ReadBytesFromStream(50, stream.get());
ASSERT_THAT(result_or, Not(IsOk()));
}
// TEST VECTORS AND COMPARISON =================================================
// These test are Hkdf specific. We test with the test vectors from RFC 5869 and
// compare with our implementation.
TEST(HkdfStreamingPrf, TestVector1) {
if (IsFipsModeEnabled()) {
GTEST_SKIP() << "Not supported in FIPS-only mode";
}
// https://tools.ietf.org/html/rfc5869#appendix-A.1
HashType hash = SHA256;
util::SecretData ikm = util::SecretDataFromStringView(
HexDecodeOrDie("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"));
std::string salt = HexDecodeOrDie("000102030405060708090a0b0c");
std::string info = HexDecodeOrDie("f0f1f2f3f4f5f6f7f8f9");
std::string expected_result = HexDecodeOrDie(
"3cb25f25faacd57a90434f64d0362f2a"
"2d2d0a90cf1a5a4c5db02d56ecc4c5bf"
"34007208d5b887185865");
auto streaming_prf_or = HkdfStreamingPrf::New(hash, ikm, salt);
ASSERT_THAT(streaming_prf_or, IsOk());
std::unique_ptr<InputStream> stream =
streaming_prf_or.value()->ComputePrf(info);
auto result_or = ReadBytesFromStream(expected_result.size(), stream.get());
ASSERT_THAT(result_or, IsOk());
EXPECT_THAT(result_or.value(), Eq(expected_result));
}
crypto::tink::util::StatusOr<std::string> ComputeWithHkdfStreamingPrf(
HashType hash, util::SecretData ikm, std::string salt, std::string info,
int length) {
auto streaming_prf_or = HkdfStreamingPrf::New(hash, std::move(ikm), salt);
if (!streaming_prf_or.status().ok()) {
return streaming_prf_or.status();
}
std::unique_ptr<InputStream> stream =
streaming_prf_or.value()->ComputePrf(info);
return ReadBytesFromStream(length, stream.get());
}
TEST(HkdfStreamingPrf, TestVector2) {
if (IsFipsModeEnabled()) {
GTEST_SKIP() << "Not supported in FIPS-only mode";
}
// https://tools.ietf.org/html/rfc5869#appendix-A.2
HashType hash = SHA256;
util::SecretData ikm = util::SecretDataFromStringView(
HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"
"101112131415161718191a1b1c1d1e1f"
"202122232425262728292a2b2c2d2e2f"
"303132333435363738393a3b3c3d3e3f"
"404142434445464748494a4b4c4d4e4f"));
std::string salt = HexDecodeOrDie(
"606162636465666768696a6b6c6d6e6f"
"707172737475767778797a7b7c7d7e7f"
"808182838485868788898a8b8c8d8e8f"
"909192939495969798999a9b9c9d9e9f"
"a0a1a2a3a4a5a6a7a8a9aaabacadaeaf");
std::string info = HexDecodeOrDie(
"b0b1b2b3b4b5b6b7b8b9babbbcbdbebf"
"c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
"d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"
"e0e1e2e3e4e5e6e7e8e9eaebecedeeef"
"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
std::string expected_result = HexDecodeOrDie(
"b11e398dc80327a1c8e7f78c596a4934"
"4f012eda2d4efad8a050cc4c19afa97c"
"59045a99cac7827271cb41c65e590e09"
"da3275600c2f09b8367793a9aca3db71"
"cc30c58179ec3e87c14c01d5c1f3434f"
"1d87");
auto result_or = ComputeWithHkdfStreamingPrf(hash, std::move(ikm), salt, info,
expected_result.size());
ASSERT_THAT(result_or, IsOk());
EXPECT_THAT(result_or.value(), Eq(expected_result));
}
TEST(HkdfStreamingPrf, TestVector3) {
if (IsFipsModeEnabled()) {
GTEST_SKIP() << "Not supported in FIPS-only mode";
}
// https://tools.ietf.org/html/rfc5869#appendix-A.3
HashType hash = SHA256;
util::SecretData ikm = util::SecretDataFromStringView(
HexDecodeOrDie("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"));
std::string salt = HexDecodeOrDie("");
std::string info = HexDecodeOrDie("");
std::string expected_result = HexDecodeOrDie(
"8da4e775a563c18f715f802a063c5a31"
"b8a11f5c5ee1879ec3454e5f3c738d2d"
"9d201395faa4b61a96c8");
auto result_or = ComputeWithHkdfStreamingPrf(hash, std::move(ikm), salt, info,
expected_result.size());
ASSERT_THAT(result_or, IsOk());
EXPECT_THAT(result_or.value(), Eq(expected_result));
}
TEST(HkdfStreamingPrf, TestVector4) {
if (IsFipsModeEnabled()) {
GTEST_SKIP() << "Not supported in FIPS-only mode";
}
// https://tools.ietf.org/html/rfc5869#appendix-A.4
HashType hash = SHA1;
util::SecretData ikm =
util::SecretDataFromStringView(HexDecodeOrDie("0b0b0b0b0b0b0b0b0b0b0b"));
std::string salt = HexDecodeOrDie("000102030405060708090a0b0c");
std::string info = HexDecodeOrDie("f0f1f2f3f4f5f6f7f8f9");
std::string expected_result = HexDecodeOrDie(
"085a01ea1b10f36933068b56efa5ad81"
"a4f14b822f5b091568a9cdd4f155fda2"
"c22e422478d305f3f896");
auto result_or = ComputeWithHkdfStreamingPrf(hash, std::move(ikm), salt, info,
expected_result.size());
ASSERT_THAT(result_or, IsOk());
EXPECT_THAT(result_or.value(), Eq(expected_result));
}
TEST(HkdfStreamingPrf, TestVector5) {
if (IsFipsModeEnabled()) {
GTEST_SKIP() << "Not supported in FIPS-only mode";
}
// https://tools.ietf.org/html/rfc5869#appendix-A.5
HashType hash = SHA1;
util::SecretData ikm = util::SecretDataFromStringView(
HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"
"101112131415161718191a1b1c1d1e1f"
"202122232425262728292a2b2c2d2e2f"
"303132333435363738393a3b3c3d3e3f"
"404142434445464748494a4b4c4d4e4f"));
std::string salt = HexDecodeOrDie(
"606162636465666768696a6b6c6d6e6f"
"707172737475767778797a7b7c7d7e7f"
"808182838485868788898a8b8c8d8e8f"
"909192939495969798999a9b9c9d9e9f"
"a0a1a2a3a4a5a6a7a8a9aaabacadaeaf");
std::string info = HexDecodeOrDie(
"b0b1b2b3b4b5b6b7b8b9babbbcbdbebf"
"c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
"d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"
"e0e1e2e3e4e5e6e7e8e9eaebecedeeef"
"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
std::string expected_result = HexDecodeOrDie(
"0bd770a74d1160f7c9f12cd5912a06eb"
"ff6adcae899d92191fe4305673ba2ffe"
"8fa3f1a4e5ad79f3f334b3b202b2173c"
"486ea37ce3d397ed034c7f9dfeb15c5e"
"927336d0441f4c4300e2cff0d0900b52"
"d3b4");
auto result_or = ComputeWithHkdfStreamingPrf(hash, std::move(ikm), salt, info,
expected_result.size());
ASSERT_THAT(result_or, IsOk());
EXPECT_THAT(result_or.value(), Eq(expected_result));
}
TEST(HkdfStreamingPrf, TestVector6) {
if (IsFipsModeEnabled()) {
GTEST_SKIP() << "Not supported in FIPS-only mode";
}
// https://tools.ietf.org/html/rfc5869#appendix-A.6
HashType hash = SHA1;
util::SecretData ikm = util::SecretDataFromStringView(
HexDecodeOrDie("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"));
std::string salt = HexDecodeOrDie("");
std::string info = HexDecodeOrDie("");
std::string expected_result = HexDecodeOrDie(
"0ac1af7002b3d761d1e55298da9d0506"
"b9ae52057220a306e07b6b87e8df21d0"
"ea00033de03984d34918");
auto result_or = ComputeWithHkdfStreamingPrf(hash, std::move(ikm), salt, info,
expected_result.size());
ASSERT_THAT(result_or, IsOk());
EXPECT_THAT(result_or.value(), Eq(expected_result));
}
TEST(HkdfStreamingPrf, TestVector7) {
if (IsFipsModeEnabled()) {
GTEST_SKIP() << "Not supported in FIPS-only mode";
}
// https://tools.ietf.org/html/rfc5869#appendix-A.7
HashType hash = SHA1;
util::SecretData ikm = util::SecretDataFromStringView(
HexDecodeOrDie("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"));
// Since HMAC anyhow pads, this is the same as an absent salt.
std::string salt = HexDecodeOrDie("");
std::string info = HexDecodeOrDie("");
std::string expected_result = HexDecodeOrDie(
"0ac1af7002b3d761d1e55298da9d0506"
"b9ae52057220a306e07b6b87e8df21d0"
"ea00033de03984d34918");
auto result_or = ComputeWithHkdfStreamingPrf(hash, std::move(ikm), salt, info,
expected_result.size());
ASSERT_THAT(result_or, IsOk());
EXPECT_THAT(result_or.value(), Eq(expected_result));
}
TEST(HkdfStreamingPrf, TestAgainstHkdfUtil) {
if (IsFipsModeEnabled()) {
GTEST_SKIP() << "Not supported in FIPS-only mode";
}
HashType hash = SHA1;
util::SecretData ikm = Random::GetRandomKeyBytes(123);
std::string salt = Random::GetRandomBytes(234);
std::string info = Random::GetRandomBytes(345);
auto streaming_result_or = ComputeWithHkdfStreamingPrf(
hash, ikm, salt, info, 456);
ASSERT_THAT(streaming_result_or, IsOk());
auto compute_hkdf_result_or = Hkdf::ComputeHkdf(
hash, ikm, salt, info, 456);
ASSERT_THAT(compute_hkdf_result_or, IsOk());
util::SecretData compute_hkdf_result =
std::move(compute_hkdf_result_or).value();
EXPECT_THAT(streaming_result_or.value(),
Eq(util::SecretDataAsStringView(compute_hkdf_result)));
}
TEST(HkdfStreamingPrf, TestFipsOnly) {
if (!IsFipsModeEnabled()) {
GTEST_SKIP() << "Only supported in FIPS-only mode";
}
HashType hash = SHA1;
util::SecretData ikm = Random::GetRandomKeyBytes(123);
std::string salt = Random::GetRandomBytes(234);
std::string info = Random::GetRandomBytes(345);
EXPECT_THAT(HkdfStreamingPrf::New(hash, std::move(ikm), salt).status(),
StatusIs(absl::StatusCode::kInternal));
}
} // namespace
} // namespace subtle
} // namespace tink
} // namespace crypto