diff options
author | wconner <wconner@google.com> | 2023-06-15 06:10:30 -0700 |
---|---|---|
committer | Copybara-Service <copybara-worker@google.com> | 2023-06-15 06:11:51 -0700 |
commit | 2e7c8390c1ea2f14399d573d03aa3df438fa6c69 (patch) | |
tree | 6ff6cda2842bd4d5a0aab268cc961a5809b967df /cc/daead | |
parent | 5d13c5cdf12576959aecc5472d45d4056134811c (diff) | |
download | tink-2e7c8390c1ea2f14399d573d03aa3df438fa6c69.tar.gz |
Add AES-SIV C++ key type.
PiperOrigin-RevId: 540560055
Diffstat (limited to 'cc/daead')
-rw-r--r-- | cc/daead/BUILD.bazel | 33 | ||||
-rw-r--r-- | cc/daead/CMakeLists.txt | 32 | ||||
-rw-r--r-- | cc/daead/aes_siv_key.cc | 107 | ||||
-rw-r--r-- | cc/daead/aes_siv_key.h | 83 | ||||
-rw-r--r-- | cc/daead/aes_siv_key_test.cc | 230 |
5 files changed, 485 insertions, 0 deletions
diff --git a/cc/daead/BUILD.bazel b/cc/daead/BUILD.bazel index f4452a78d..97427828a 100644 --- a/cc/daead/BUILD.bazel +++ b/cc/daead/BUILD.bazel @@ -137,6 +137,24 @@ cc_library( ], ) +cc_library( + name = "aes_siv_key", + srcs = ["aes_siv_key.cc"], + hdrs = ["aes_siv_key.h"], + include_prefix = "tink/daead", + deps = [ + ":aes_siv_parameters", + ":deterministic_aead_key", + "//:partial_key_access_token", + "//:restricted_data", + "//subtle:subtle_util", + "//util:status", + "//util:statusor", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:optional", + ], +) + # tests cc_test( @@ -255,3 +273,18 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "aes_siv_key_test", + srcs = ["aes_siv_key_test.cc"], + deps = [ + ":aes_siv_key", + ":aes_siv_parameters", + "//:partial_key_access", + "//:restricted_data", + "//util:statusor", + "//util:test_matchers", + "@com_google_absl//absl/types:optional", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/cc/daead/CMakeLists.txt b/cc/daead/CMakeLists.txt index e944696fb..3a1efc047 100644 --- a/cc/daead/CMakeLists.txt +++ b/cc/daead/CMakeLists.txt @@ -128,6 +128,23 @@ tink_cc_library( tink::util::statusor ) +tink_cc_library( + NAME aes_siv_key + SRCS + aes_siv_key.cc + aes_siv_key.h + DEPS + tink::daead::aes_siv_parameters + tink::daead::deterministic_aead_key + absl::strings + absl::optional + tink::core::partial_key_access_token + tink::core::restricted_data + tink::subtle::subtle_util + tink::util::status + tink::util::statusor +) + # tests tink_cc_test( @@ -240,3 +257,18 @@ tink_cc_test( tink::util::statusor tink::util::test_matchers ) + +tink_cc_test( + NAME aes_siv_key_test + SRCS + aes_siv_key_test.cc + DEPS + tink::daead::aes_siv_key + tink::daead::aes_siv_parameters + gmock + absl::optional + tink::core::partial_key_access + tink::core::restricted_data + tink::util::statusor + tink::util::test_matchers +) diff --git a/cc/daead/aes_siv_key.cc b/cc/daead/aes_siv_key.cc new file mode 100644 index 000000000..00582b24d --- /dev/null +++ b/cc/daead/aes_siv_key.cc @@ -0,0 +1,107 @@ +// Copyright 2023 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/daead/aes_siv_key.h" + +#include <string> + +#include "absl/strings/escaping.h" +#include "absl/types/optional.h" +#include "tink/daead/aes_siv_parameters.h" +#include "tink/partial_key_access_token.h" +#include "tink/restricted_data.h" +#include "tink/subtle/subtle_util.h" +#include "tink/util/status.h" +#include "tink/util/statusor.h" + +namespace crypto { +namespace tink { +namespace { + +util::StatusOr<std::string> ComputeOutputPrefix( + const AesSivParameters& parameters, absl::optional<int> id_requirement) { + switch (parameters.GetVariant()) { + case AesSivParameters::Variant::kNoPrefix: + return std::string(""); // Empty prefix. + case AesSivParameters::Variant::kCrunchy: + if (!id_requirement.has_value()) { + return util::Status( + absl::StatusCode::kInvalidArgument, + "id requirement must have value with kCrunchy or kLegacy"); + } + return absl::StrCat(absl::HexStringToBytes("00"), + subtle::BigEndian32(*id_requirement)); + case AesSivParameters::Variant::kTink: + if (!id_requirement.has_value()) { + return util::Status(absl::StatusCode::kInvalidArgument, + "id requirement must have value with kTink"); + } + return absl::StrCat(absl::HexStringToBytes("01"), + subtle::BigEndian32(*id_requirement)); + default: + return util::Status( + absl::StatusCode::kInvalidArgument, + absl::StrCat("Invalid variant: ", parameters.GetVariant())); + } +} + +} // namespace + +util::StatusOr<AesSivKey> AesSivKey::Create(const AesSivParameters& parameters, + const RestrictedData& key_bytes, + absl::optional<int> id_requirement, + PartialKeyAccessToken token) { + if (parameters.KeySizeInBytes() != key_bytes.size()) { + return util::Status(absl::StatusCode::kInvalidArgument, + "Key size does not match AES-SIV parameters"); + } + if (parameters.HasIdRequirement() && !id_requirement.has_value()) { + return util::Status( + absl::StatusCode::kInvalidArgument, + "Cannot create key without ID requirement with parameters with ID " + "requirement"); + } + if (!parameters.HasIdRequirement() && id_requirement.has_value()) { + return util::Status( + absl::StatusCode::kInvalidArgument, + "Cannot create key with ID requirement with parameters without ID " + "requirement"); + } + util::StatusOr<std::string> output_prefix = + ComputeOutputPrefix(parameters, id_requirement); + if (!output_prefix.ok()) { + return output_prefix.status(); + } + return AesSivKey(parameters, key_bytes, id_requirement, + *std::move(output_prefix)); +} + +bool AesSivKey::operator==(const Key& other) const { + const AesSivKey* that = dynamic_cast<const AesSivKey*>(&other); + if (that == nullptr) { + return false; + } + if (GetParameters() != that->GetParameters()) { + return false; + } + if (id_requirement_ != that->id_requirement_) { + return false; + } + return key_bytes_ == that->key_bytes_; +} + +} // namespace tink +} // namespace crypto diff --git a/cc/daead/aes_siv_key.h b/cc/daead/aes_siv_key.h new file mode 100644 index 000000000..8ca060d71 --- /dev/null +++ b/cc/daead/aes_siv_key.h @@ -0,0 +1,83 @@ +// Copyright 2023 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. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef TINK_DAEAD_AES_SIV_KEY_H_ +#define TINK_DAEAD_AES_SIV_KEY_H_ + +#include <string> +#include <utility> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "tink/daead/aes_siv_parameters.h" +#include "tink/daead/deterministic_aead_key.h" +#include "tink/partial_key_access_token.h" +#include "tink/restricted_data.h" +#include "tink/util/statusor.h" + +namespace crypto { +namespace tink { + +// Represents a Deterministic AEAD that uses AES-SIV. +class AesSivKey : public DeterministicAeadKey { + public: + // Copyable and movable. + AesSivKey(const AesSivKey& other) = default; + AesSivKey& operator=(const AesSivKey& other) = default; + AesSivKey(AesSivKey&& other) = default; + AesSivKey& operator=(AesSivKey&& other) = default; + + // Creates a new AES-SIV key. If the parameters specify a variant that uses + // a prefix, then the id is used to compute this prefix. + static util::StatusOr<AesSivKey> Create(const AesSivParameters& parameters, + const RestrictedData& key_bytes, + absl::optional<int> id_requirement, + PartialKeyAccessToken token); + + // Returns the underlying AES-SIV key. + util::StatusOr<RestrictedData> GetKeyBytes( + PartialKeyAccessToken token) const { + return key_bytes_; + } + + absl::string_view GetOutputPrefix() const override { return output_prefix_; } + + const AesSivParameters& GetParameters() const override { return parameters_; } + + absl::optional<int> GetIdRequirement() const override { + return id_requirement_; + } + + bool operator==(const Key& other) const override; + + private: + AesSivKey(const AesSivParameters& parameters, const RestrictedData& key_bytes, + absl::optional<int> id_requirement, std::string output_prefix) + : parameters_(parameters), + key_bytes_(key_bytes), + id_requirement_(id_requirement), + output_prefix_(std::move(output_prefix)) {} + + AesSivParameters parameters_; + RestrictedData key_bytes_; + absl::optional<int> id_requirement_; + std::string output_prefix_; +}; + +} // namespace tink +} // namespace crypto + +#endif // TINK_DAEAD_AES_SIV_KEY_H_ diff --git a/cc/daead/aes_siv_key_test.cc b/cc/daead/aes_siv_key_test.cc new file mode 100644 index 000000000..294525e47 --- /dev/null +++ b/cc/daead/aes_siv_key_test.cc @@ -0,0 +1,230 @@ +// Copyright 2023 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/daead/aes_siv_key.h" + +#include <string> +#include <tuple> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/types/optional.h" +#include "tink/daead/aes_siv_parameters.h" +#include "tink/partial_key_access.h" +#include "tink/restricted_data.h" +#include "tink/util/statusor.h" +#include "tink/util/test_matchers.h" + +namespace crypto { +namespace tink { +namespace { + +using ::crypto::tink::test::IsOk; +using ::crypto::tink::test::IsOkAndHolds; +using ::crypto::tink::test::StatusIs; +using ::testing::Combine; +using ::testing::Eq; +using ::testing::TestWithParam; +using ::testing::Values; + +struct TestCase { + AesSivParameters::Variant variant; + absl::optional<int> id_requirement; + std::string output_prefix; +}; + +using AesSivKeyTest = TestWithParam<std::tuple<int, TestCase>>; + +INSTANTIATE_TEST_SUITE_P( + AesSivKeyTestSuite, AesSivKeyTest, + Combine(Values(32, 48, 64), + Values(TestCase{AesSivParameters::Variant::kTink, 0x02030400, + std::string("\x01\x02\x03\x04\x00", 5)}, + TestCase{AesSivParameters::Variant::kCrunchy, 0x01030005, + std::string("\x00\x01\x03\x00\x05", 5)}, + TestCase{AesSivParameters::Variant::kNoPrefix, absl::nullopt, + ""}))); + +TEST_P(AesSivKeyTest, CreateSucceeds) { + int key_size; + TestCase test_case; + std::tie(key_size, test_case) = GetParam(); + + util::StatusOr<AesSivParameters> params = + AesSivParameters::Create(key_size, test_case.variant); + ASSERT_THAT(params, IsOk()); + + RestrictedData secret = RestrictedData(key_size); + util::StatusOr<AesSivKey> key = AesSivKey::Create( + *params, secret, test_case.id_requirement, GetPartialKeyAccess()); + ASSERT_THAT(key, IsOk()); + + EXPECT_THAT(key->GetParameters(), Eq(*params)); + EXPECT_THAT(key->GetIdRequirement(), Eq(test_case.id_requirement)); + EXPECT_THAT(key->GetOutputPrefix(), Eq(test_case.output_prefix)); +} + +TEST(AesSivKeyTest, CreateKeyWithMismatchedKeySizeFails) { + // Key size parameter is 64 bytes. + util::StatusOr<AesSivParameters> params = AesSivParameters::Create( + /*key_size_in_bytes=*/64, AesSivParameters::Variant::kTink); + ASSERT_THAT(params, IsOk()); + + // Key material is 32 bytes (another valid key length). + RestrictedData mismatched_secret = RestrictedData(/*num_random_bytes=*/32); + + EXPECT_THAT(AesSivKey::Create(*params, mismatched_secret, + /*id_requirement=*/123, GetPartialKeyAccess()) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(AesSivKeyTest, CreateKeyWithInvalidIdRequirementFails) { + util::StatusOr<AesSivParameters> no_prefix_params = AesSivParameters::Create( + /*key_size_in_bytes=*/64, AesSivParameters::Variant::kNoPrefix); + ASSERT_THAT(no_prefix_params, IsOk()); + + util::StatusOr<AesSivParameters> tink_params = AesSivParameters::Create( + /*key_size_in_bytes=*/64, AesSivParameters::Variant::kTink); + ASSERT_THAT(tink_params, IsOk()); + + RestrictedData secret = RestrictedData(/*num_random_bytes=*/64); + + EXPECT_THAT(AesSivKey::Create(*no_prefix_params, secret, + /*id_requirement=*/123, GetPartialKeyAccess()) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT( + AesSivKey::Create(*tink_params, secret, + /*id_requirement=*/absl::nullopt, GetPartialKeyAccess()) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST_P(AesSivKeyTest, GetKeyBytes) { + int key_size; + TestCase test_case; + std::tie(key_size, test_case) = GetParam(); + + util::StatusOr<AesSivParameters> params = + AesSivParameters::Create(key_size, test_case.variant); + ASSERT_THAT(params, IsOk()); + + RestrictedData secret = RestrictedData(key_size); + + util::StatusOr<AesSivKey> key = AesSivKey::Create( + *params, secret, test_case.id_requirement, GetPartialKeyAccess()); + ASSERT_THAT(key, IsOk()); + EXPECT_THAT(key->GetKeyBytes(GetPartialKeyAccess()), IsOkAndHolds(secret)); +} + +TEST_P(AesSivKeyTest, KeyEquals) { + int key_size; + TestCase test_case; + std::tie(key_size, test_case) = GetParam(); + + util::StatusOr<AesSivParameters> params = + AesSivParameters::Create(key_size, test_case.variant); + ASSERT_THAT(params, IsOk()); + + RestrictedData secret = RestrictedData(key_size); + util::StatusOr<AesSivKey> key = AesSivKey::Create( + *params, secret, test_case.id_requirement, GetPartialKeyAccess()); + ASSERT_THAT(key, IsOk()); + + util::StatusOr<AesSivKey> other_key = AesSivKey::Create( + *params, secret, test_case.id_requirement, GetPartialKeyAccess()); + ASSERT_THAT(other_key, IsOk()); + + EXPECT_TRUE(*key == *other_key); + EXPECT_TRUE(*other_key == *key); + EXPECT_FALSE(*key != *other_key); + EXPECT_FALSE(*other_key != *key); +} + +TEST(AesSivKeyTest, DifferentVariantNotEqual) { + util::StatusOr<AesSivParameters> crunchy_params = AesSivParameters::Create( + /*key_size_in_bytes=*/64, AesSivParameters::Variant::kCrunchy); + ASSERT_THAT(crunchy_params, IsOk()); + + util::StatusOr<AesSivParameters> tink_params = AesSivParameters::Create( + /*key_size_in_bytes=*/64, AesSivParameters::Variant::kTink); + ASSERT_THAT(tink_params, IsOk()); + + RestrictedData secret = RestrictedData(/*num_random_bytes=*/64); + + util::StatusOr<AesSivKey> key = + AesSivKey::Create(*crunchy_params, secret, /*id_requirement=*/0x01020304, + GetPartialKeyAccess()); + ASSERT_THAT(key, IsOk()); + + util::StatusOr<AesSivKey> other_key = + AesSivKey::Create(*tink_params, secret, /*id_requirement=*/0x01020304, + GetPartialKeyAccess()); + ASSERT_THAT(other_key, IsOk()); + + EXPECT_TRUE(*key != *other_key); + EXPECT_TRUE(*other_key != *key); + EXPECT_FALSE(*key == *other_key); + EXPECT_FALSE(*other_key == *key); +} + +TEST(AesSivKeyTest, DifferentSecretDataNotEqual) { + util::StatusOr<AesSivParameters> params = AesSivParameters::Create( + /*key_size_in_bytes=*/64, AesSivParameters::Variant::kTink); + ASSERT_THAT(params, IsOk()); + + RestrictedData secret1 = RestrictedData(/*num_random_bytes=*/64); + RestrictedData secret2 = RestrictedData(/*num_random_bytes=*/64); + + util::StatusOr<AesSivKey> key = AesSivKey::Create( + *params, secret1, /*id_requirement=*/0x01020304, GetPartialKeyAccess()); + ASSERT_THAT(key, IsOk()); + + util::StatusOr<AesSivKey> other_key = AesSivKey::Create( + *params, secret2, /*id_requirement=*/0x01020304, GetPartialKeyAccess()); + ASSERT_THAT(other_key, IsOk()); + + EXPECT_TRUE(*key != *other_key); + EXPECT_TRUE(*other_key != *key); + EXPECT_FALSE(*key == *other_key); + EXPECT_FALSE(*other_key == *key); +} + +TEST(AesSivKeyTest, DifferentIdRequirementNotEqual) { + util::StatusOr<AesSivParameters> params = AesSivParameters::Create( + /*key_size_in_bytes=*/64, AesSivParameters::Variant::kTink); + ASSERT_THAT(params, IsOk()); + + RestrictedData secret = RestrictedData(/*num_random_bytes=*/64); + + util::StatusOr<AesSivKey> key = AesSivKey::Create( + *params, secret, /*id_requirement=*/0x01020304, GetPartialKeyAccess()); + ASSERT_THAT(key, IsOk()); + + util::StatusOr<AesSivKey> other_key = AesSivKey::Create( + *params, secret, /*id_requirement=*/0x02030405, GetPartialKeyAccess()); + ASSERT_THAT(other_key, IsOk()); + + EXPECT_TRUE(*key != *other_key); + EXPECT_TRUE(*other_key != *key); + EXPECT_FALSE(*key == *other_key); + EXPECT_FALSE(*other_key == *key); +} + +} // namespace +} // namespace tink +} // namespace crypto |