diff options
author | wconner <wconner@google.com> | 2023-06-02 12:27:09 -0700 |
---|---|---|
committer | Copybara-Service <copybara-worker@google.com> | 2023-06-02 12:28:30 -0700 |
commit | 114bfd679a86a7e8cacee73e99f885497747590f (patch) | |
tree | b725bd6911e09e512701e110dd4003e02c5c5dd7 /cc/aead | |
parent | 5ed139419ed738945c5cdedd1becd0b6b1612db8 (diff) | |
download | tink-114bfd679a86a7e8cacee73e99f885497747590f.tar.gz |
Add AES-GCM C++ key type.
PiperOrigin-RevId: 537380498
Diffstat (limited to 'cc/aead')
-rw-r--r-- | cc/aead/BUILD.bazel | 34 | ||||
-rw-r--r-- | cc/aead/CMakeLists.txt | 33 | ||||
-rw-r--r-- | cc/aead/aead_key.h | 5 | ||||
-rw-r--r-- | cc/aead/aes_gcm_key.cc | 107 | ||||
-rw-r--r-- | cc/aead/aes_gcm_key.h | 84 | ||||
-rw-r--r-- | cc/aead/aes_gcm_key_test.cc | 285 |
6 files changed, 545 insertions, 3 deletions
diff --git a/cc/aead/BUILD.bazel b/cc/aead/BUILD.bazel index 6f5401928..6b12ca75b 100644 --- a/cc/aead/BUILD.bazel +++ b/cc/aead/BUILD.bazel @@ -357,6 +357,7 @@ cc_library( deps = [ ":aead_parameters", "//:key", + "@com_google_absl//absl/strings", ], ) @@ -373,6 +374,24 @@ cc_library( ], ) +cc_library( + name = "aes_gcm_key", + srcs = ["aes_gcm_key.cc"], + hdrs = ["aes_gcm_key.h"], + include_prefix = "tink/aead", + deps = [ + ":aead_key", + ":aes_gcm_parameters", + "//: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( @@ -717,3 +736,18 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "aes_gcm_key_test", + srcs = ["aes_gcm_key_test.cc"], + deps = [ + ":aes_gcm_key", + ":aes_gcm_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/aead/CMakeLists.txt b/cc/aead/CMakeLists.txt index d8d9c956f..803b72d35 100644 --- a/cc/aead/CMakeLists.txt +++ b/cc/aead/CMakeLists.txt @@ -333,6 +333,7 @@ tink_cc_library( aead_key.h DEPS tink::aead::aead_parameters + absl::strings tink::core::key ) @@ -348,6 +349,23 @@ tink_cc_library( tink::util::statusor ) +tink_cc_library( + NAME aes_gcm_key + SRCS + aes_gcm_key.cc + aes_gcm_key.h + DEPS + tink::aead::aead_key + tink::aead::aes_gcm_parameters + 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( @@ -677,3 +695,18 @@ tink_cc_test( tink::util::statusor tink::util::test_matchers ) + +tink_cc_test( + NAME aes_gcm_key_test + SRCS + aes_gcm_key_test.cc + DEPS + tink::aead::aes_gcm_key + tink::aead::aes_gcm_parameters + gmock + absl::optional + tink::core::partial_key_access + tink::core::restricted_data + tink::util::statusor + tink::util::test_matchers +) diff --git a/cc/aead/aead_key.h b/cc/aead/aead_key.h index 64f7880b3..869ddc83c 100644 --- a/cc/aead/aead_key.h +++ b/cc/aead/aead_key.h @@ -17,8 +17,7 @@ #ifndef TINK_AEAD_AEAD_KEY_H_ #define TINK_AEAD_AEAD_KEY_H_ -#include <string> - +#include "absl/strings/string_view.h" #include "tink/aead/aead_parameters.h" #include "tink/key.h" @@ -40,7 +39,7 @@ class AeadKey : public Key { // may be a prefix of another). To avoid this, built-in Tink keys use the // convention that the prefix is either '0x00<big endian key id>' or // '0x01<big endian key id>'. - virtual std::string GetOutputPrefix() const = 0; + virtual absl::string_view GetOutputPrefix() const = 0; const AeadParameters& GetParameters() const override = 0; diff --git a/cc/aead/aes_gcm_key.cc b/cc/aead/aes_gcm_key.cc new file mode 100644 index 000000000..4e93443c0 --- /dev/null +++ b/cc/aead/aes_gcm_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/aead/aes_gcm_key.h" + +#include <string> + +#include "absl/strings/escaping.h" +#include "absl/types/optional.h" +#include "tink/aead/aes_gcm_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 AesGcmParameters& parameters, absl::optional<int> id_requirement) { + switch (parameters.GetVariant()) { + case AesGcmParameters::Variant::kNoPrefix: + return std::string(""); // Empty prefix. + case AesGcmParameters::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 AesGcmParameters::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<AesGcmKey> AesGcmKey::Create(const AesGcmParameters& 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-GCM 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 AesGcmKey(parameters, key_bytes, id_requirement, + *std::move(output_prefix)); +} + +bool AesGcmKey::operator==(const Key& other) const { + const AesGcmKey* that = dynamic_cast<const AesGcmKey*>(&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/aead/aes_gcm_key.h b/cc/aead/aes_gcm_key.h new file mode 100644 index 000000000..8159ea738 --- /dev/null +++ b/cc/aead/aes_gcm_key.h @@ -0,0 +1,84 @@ +// 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_AEAD_AES_GCM_KEY_H_ +#define TINK_AEAD_AES_GCM_KEY_H_ + +#include <string> +#include <utility> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "tink/aead/aead_key.h" +#include "tink/aead/aes_gcm_parameters.h" +#include "tink/partial_key_access_token.h" +#include "tink/restricted_data.h" +#include "tink/util/statusor.h" + +namespace crypto { +namespace tink { + +// Represents an AEAD that uses AES-GCM. +class AesGcmKey : public AeadKey { + public: + // Copyable and movable. + AesGcmKey(const AesGcmKey& other) = default; + AesGcmKey& operator=(const AesGcmKey& other) = default; + AesGcmKey(AesGcmKey&& other) = default; + AesGcmKey& operator=(AesGcmKey&& other) = default; + + // Creates a new AES-GCM key. If the parameters specify a variant that uses + // a prefix, then the id is used to compute this prefix. + static util::StatusOr<AesGcmKey> Create(const AesGcmParameters& parameters, + const RestrictedData& key_bytes, + absl::optional<int> id_requirement, + PartialKeyAccessToken token); + + // Returns the underlying AES key. + util::StatusOr<RestrictedData> GetKeyBytes( + PartialKeyAccessToken token) const { + return key_bytes_; + } + + absl::string_view GetOutputPrefix() const override { return output_prefix_; } + + const AesGcmParameters& GetParameters() const override { return parameters_; } + + absl::optional<int> GetIdRequirement() const override { + return id_requirement_; + } + + bool operator==(const Key& other) const override; + + private: + AesGcmKey(const AesGcmParameters& 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)) {} + + AesGcmParameters parameters_; + RestrictedData key_bytes_; + absl::optional<int> id_requirement_; + std::string output_prefix_; +}; + +} // namespace tink +} // namespace crypto + +#endif // TINK_AEAD_AES_GCM_KEY_H_ diff --git a/cc/aead/aes_gcm_key_test.cc b/cc/aead/aes_gcm_key_test.cc new file mode 100644 index 000000000..d5434aa12 --- /dev/null +++ b/cc/aead/aes_gcm_key_test.cc @@ -0,0 +1,285 @@ +// 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/aead/aes_gcm_key.h" + +#include <string> +#include <tuple> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/types/optional.h" +#include "tink/aead/aes_gcm_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::Range; +using ::testing::TestWithParam; +using ::testing::Values; + +struct TestCase { + AesGcmParameters::Variant variant; + absl::optional<int> id_requirement; + std::string output_prefix; +}; + +using AesGcmKeyTest = TestWithParam<std::tuple<int, int, TestCase>>; + +INSTANTIATE_TEST_SUITE_P( + AesGcmKeyTestSuite, AesGcmKeyTest, + Combine(Values(16, 24, 32), Range(12, 16), + Values(TestCase{AesGcmParameters::Variant::kTink, 0x02030400, + std::string("\x01\x02\x03\x04\x00", 5)}, + TestCase{AesGcmParameters::Variant::kCrunchy, 0x01030005, + std::string("\x00\x01\x03\x00\x05", 5)}, + TestCase{AesGcmParameters::Variant::kNoPrefix, absl::nullopt, + ""}))); + +TEST_P(AesGcmKeyTest, CreateSucceeds) { + int key_size; + int iv_and_tag_size; // NOTE: There's no requirement for IV size == tag size. + TestCase test_case; + std::tie(key_size, iv_and_tag_size, test_case) = GetParam(); + + util::StatusOr<AesGcmParameters> params = + AesGcmParameters::Builder() + .SetKeySizeInBytes(key_size) + .SetIvSizeInBytes(iv_and_tag_size) + .SetTagSizeInBytes(iv_and_tag_size) + .SetVariant(test_case.variant) + .Build(); + ASSERT_THAT(params, IsOk()); + + RestrictedData secret = RestrictedData(key_size); + util::StatusOr<AesGcmKey> key = AesGcmKey::Create( + *params, secret, test_case.id_requirement, GetPartialKeyAccess()); + ASSERT_THAT(key.status(), 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(AesGcmKeyTest, CreateKeyWithMismatchedKeySizeFails) { + // Key size parameter is 32 bytes. + util::StatusOr<AesGcmParameters> params = + AesGcmParameters::Builder() + .SetKeySizeInBytes(32) + .SetIvSizeInBytes(16) + .SetTagSizeInBytes(16) + .SetVariant(AesGcmParameters::Variant::kTink) + .Build(); + ASSERT_THAT(params, IsOk()); + + // Key material is 16 bytes (another valid key length). + RestrictedData mismatched_secret = RestrictedData(/*num_random_bytes=*/16); + + EXPECT_THAT(AesGcmKey::Create(*params, mismatched_secret, + /*id_requirement=*/123, GetPartialKeyAccess()) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(AesGcmKeyTest, CreateKeyWithInvalidIdRequirementFails) { + util::StatusOr<AesGcmParameters> no_prefix_params = + AesGcmParameters::Builder() + .SetKeySizeInBytes(32) + .SetIvSizeInBytes(16) + .SetTagSizeInBytes(16) + .SetVariant(AesGcmParameters::Variant::kNoPrefix) + .Build(); + ASSERT_THAT(no_prefix_params, IsOk()); + + util::StatusOr<AesGcmParameters> tink_params = + AesGcmParameters::Builder() + .SetKeySizeInBytes(32) + .SetIvSizeInBytes(16) + .SetTagSizeInBytes(16) + .SetVariant(AesGcmParameters::Variant::kTink) + .Build(); + ASSERT_THAT(tink_params, IsOk()); + + RestrictedData secret = RestrictedData(/*num_random_bytes=*/32); + + EXPECT_THAT(AesGcmKey::Create(*no_prefix_params, secret, + /*id_requirement=*/123, GetPartialKeyAccess()) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT( + AesGcmKey::Create(*tink_params, secret, + /*id_requirement=*/absl::nullopt, GetPartialKeyAccess()) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST_P(AesGcmKeyTest, GetKeyBytes) { + int key_size; + int iv_and_tag_size; // NOTE: There's no requirement for IV size == tag size. + TestCase test_case; + std::tie(key_size, iv_and_tag_size, test_case) = GetParam(); + + util::StatusOr<AesGcmParameters> params = + AesGcmParameters::Builder() + .SetKeySizeInBytes(key_size) + .SetIvSizeInBytes(iv_and_tag_size) + .SetTagSizeInBytes(iv_and_tag_size) + .SetVariant(test_case.variant) + .Build(); + ASSERT_THAT(params, IsOk()); + + RestrictedData secret = RestrictedData(key_size); + + util::StatusOr<AesGcmKey> key = AesGcmKey::Create( + *params, secret, test_case.id_requirement, GetPartialKeyAccess()); + ASSERT_THAT(key.status(), IsOk()); + + EXPECT_THAT(key->GetKeyBytes(GetPartialKeyAccess()), IsOkAndHolds(secret)); +} + +TEST_P(AesGcmKeyTest, KeyEquals) { + int key_size; + int iv_and_tag_size; // NOTE: There's no requirement for IV size == tag size. + TestCase test_case; + std::tie(key_size, iv_and_tag_size, test_case) = GetParam(); + + util::StatusOr<AesGcmParameters> params = + AesGcmParameters::Builder() + .SetKeySizeInBytes(key_size) + .SetIvSizeInBytes(iv_and_tag_size) + .SetTagSizeInBytes(iv_and_tag_size) + .SetVariant(test_case.variant) + .Build(); + ASSERT_THAT(params, IsOk()); + + RestrictedData secret = RestrictedData(key_size); + util::StatusOr<AesGcmKey> key = AesGcmKey::Create( + *params, secret, test_case.id_requirement, GetPartialKeyAccess()); + ASSERT_THAT(key, IsOk()); + + util::StatusOr<AesGcmKey> other_key = AesGcmKey::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(AesGcmKeyTest, DifferentVariantNotEqual) { + util::StatusOr<AesGcmParameters> crunchy_params = + AesGcmParameters::Builder() + .SetKeySizeInBytes(32) + .SetIvSizeInBytes(16) + .SetTagSizeInBytes(16) + .SetVariant(AesGcmParameters::Variant::kCrunchy) + .Build(); + ASSERT_THAT(crunchy_params, IsOk()); + + util::StatusOr<AesGcmParameters> tink_params = + AesGcmParameters::Builder() + .SetKeySizeInBytes(32) + .SetIvSizeInBytes(16) + .SetTagSizeInBytes(16) + .SetVariant(AesGcmParameters::Variant::kTink) + .Build(); + ASSERT_THAT(tink_params, IsOk()); + + RestrictedData secret = RestrictedData(/*num_random_bytes=*/32); + + util::StatusOr<AesGcmKey> key = + AesGcmKey::Create(*crunchy_params, secret, /*id_requirement=*/0x01020304, + GetPartialKeyAccess()); + ASSERT_THAT(key.status(), IsOk()); + + util::StatusOr<AesGcmKey> other_key = + AesGcmKey::Create(*tink_params, secret, /*id_requirement=*/0x01020304, + GetPartialKeyAccess()); + ASSERT_THAT(other_key.status(), IsOk()); + + EXPECT_TRUE(*key != *other_key); + EXPECT_TRUE(*other_key != *key); + EXPECT_FALSE(*key == *other_key); + EXPECT_FALSE(*other_key == *key); +} + +TEST(AesGcmKeyTest, DifferentSecretDataNotEqual) { + util::StatusOr<AesGcmParameters> params = + AesGcmParameters::Builder() + .SetKeySizeInBytes(32) + .SetIvSizeInBytes(16) + .SetTagSizeInBytes(16) + .SetVariant(AesGcmParameters::Variant::kTink) + .Build(); + ASSERT_THAT(params, IsOk()); + + RestrictedData secret1 = RestrictedData(/*num_random_bytes=*/32); + RestrictedData secret2 = RestrictedData(/*num_random_bytes=*/32); + + util::StatusOr<AesGcmKey> key = AesGcmKey::Create( + *params, secret1, /*id_requirement=*/0x01020304, GetPartialKeyAccess()); + ASSERT_THAT(key.status(), IsOk()); + + util::StatusOr<AesGcmKey> other_key = AesGcmKey::Create( + *params, secret2, /*id_requirement=*/0x01020304, GetPartialKeyAccess()); + ASSERT_THAT(other_key.status(), IsOk()); + + EXPECT_TRUE(*key != *other_key); + EXPECT_TRUE(*other_key != *key); + EXPECT_FALSE(*key == *other_key); + EXPECT_FALSE(*other_key == *key); +} + +TEST(AesGcmKeyTest, DifferentIdRequirementNotEqual) { + util::StatusOr<AesGcmParameters> params = + AesGcmParameters::Builder() + .SetKeySizeInBytes(32) + .SetIvSizeInBytes(16) + .SetTagSizeInBytes(16) + .SetVariant(AesGcmParameters::Variant::kTink) + .Build(); + ASSERT_THAT(params, IsOk()); + + RestrictedData secret = RestrictedData(/*num_random_bytes=*/32); + + util::StatusOr<AesGcmKey> key = AesGcmKey::Create( + *params, secret, /*id_requirement=*/0x01020304, GetPartialKeyAccess()); + ASSERT_THAT(key.status(), IsOk()); + + util::StatusOr<AesGcmKey> other_key = AesGcmKey::Create( + *params, secret, /*id_requirement=*/0x02030405, GetPartialKeyAccess()); + ASSERT_THAT(other_key.status(), 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 |