diff options
author | wconner <wconner@google.com> | 2023-08-01 07:03:34 -0700 |
---|---|---|
committer | Copybara-Service <copybara-worker@google.com> | 2023-08-01 07:05:41 -0700 |
commit | 3d025c6568b892514bdebde87102405ab5c79369 (patch) | |
tree | 232feead7ce0fe73160826475804f62296f25a8b | |
parent | 67ffa92455c836967cf02f5aa615310822cee42b (diff) | |
download | tink-3d025c6568b892514bdebde87102405ab5c79369.tar.gz |
Add Ed25519 public key type.
PiperOrigin-RevId: 552790750
-rw-r--r-- | cc/signature/BUILD.bazel | 31 | ||||
-rw-r--r-- | cc/signature/CMakeLists.txt | 30 | ||||
-rw-r--r-- | cc/signature/ed25519_public_key.cc | 108 | ||||
-rw-r--r-- | cc/signature/ed25519_public_key.h | 81 | ||||
-rw-r--r-- | cc/signature/ed25519_public_key_test.cc | 215 |
5 files changed, 465 insertions, 0 deletions
diff --git a/cc/signature/BUILD.bazel b/cc/signature/BUILD.bazel index 7f31b48a8..22158a142 100644 --- a/cc/signature/BUILD.bazel +++ b/cc/signature/BUILD.bazel @@ -443,6 +443,24 @@ cc_library( ], ) +cc_library( + name = "ed25519_public_key", + srcs = ["ed25519_public_key.cc"], + hdrs = ["ed25519_public_key.h"], + include_prefix = "tink/signature", + deps = [ + ":ed25519_parameters", + ":signature_public_key", + "//:partial_key_access_token", + "//subtle:subtle_util", + "//util:status", + "//util:statusor", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:optional", + ], +) + # tests cc_test( @@ -825,3 +843,16 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "ed25519_public_key_test", + srcs = ["ed25519_public_key_test.cc"], + deps = [ + ":ed25519_public_key", + "//:partial_key_access", + "//subtle:random", + "//util:statusor", + "//util:test_matchers", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/cc/signature/CMakeLists.txt b/cc/signature/CMakeLists.txt index 68a8fc6aa..8693762eb 100644 --- a/cc/signature/CMakeLists.txt +++ b/cc/signature/CMakeLists.txt @@ -422,6 +422,23 @@ tink_cc_library( tink::util::statusor ) +tink_cc_library( + NAME ed25519_public_key + SRCS + ed25519_public_key.cc + ed25519_public_key.h + DEPS + tink::signature::ed25519_parameters + tink::signature::signature_public_key + absl::core_headers + absl::strings + absl::optional + tink::core::partial_key_access_token + tink::subtle::subtle_util + tink::util::status + tink::util::statusor +) + # tests tink_cc_test( @@ -788,3 +805,16 @@ tink_cc_test( tink::util::statusor tink::util::test_matchers ) + +tink_cc_test( + NAME ed25519_public_key_test + SRCS + ed25519_public_key_test.cc + DEPS + tink::signature::ed25519_public_key + gmock + tink::core::partial_key_access + tink::subtle::random + tink::util::statusor + tink::util::test_matchers +) diff --git a/cc/signature/ed25519_public_key.cc b/cc/signature/ed25519_public_key.cc new file mode 100644 index 000000000..fce923933 --- /dev/null +++ b/cc/signature/ed25519_public_key.cc @@ -0,0 +1,108 @@ +// 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/signature/ed25519_public_key.h" + +#include <string> + +#include "absl/base/attributes.h" +#include "absl/strings/escaping.h" +#include "absl/types/optional.h" +#include "tink/partial_key_access_token.h" +#include "tink/signature/ed25519_parameters.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 Ed25519Parameters& parameters, absl::optional<int> id_requirement) { + switch (parameters.GetVariant()) { + case Ed25519Parameters::Variant::kNoPrefix: + return std::string(""); // Empty prefix. + case Ed25519Parameters::Variant::kLegacy: + ABSL_FALLTHROUGH_INTENDED; + case Ed25519Parameters::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 Ed25519Parameters::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<Ed25519PublicKey> Ed25519PublicKey::Create( + const Ed25519Parameters& parameters, absl::string_view public_key_bytes, + absl::optional<int> id_requirement, PartialKeyAccessToken token) { + 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"); + } + if (public_key_bytes.size() != 32) { + return util::Status(absl::StatusCode::kInvalidArgument, + "Ed25519 public key length must be 32 bytes."); + } + util::StatusOr<std::string> output_prefix = + ComputeOutputPrefix(parameters, id_requirement); + if (!output_prefix.ok()) { + return output_prefix.status(); + } + return Ed25519PublicKey(parameters, public_key_bytes, id_requirement, + *output_prefix); +} + +bool Ed25519PublicKey::operator==(const Key& other) const { + const Ed25519PublicKey* that = dynamic_cast<const Ed25519PublicKey*>(&other); + if (that == nullptr) { + return false; + } + if (GetParameters() != that->GetParameters()) { + return false; + } + if (id_requirement_ != that->id_requirement_) { + return false; + } + return public_key_bytes_ == that->public_key_bytes_; +} + +} // namespace tink +} // namespace crypto diff --git a/cc/signature/ed25519_public_key.h b/cc/signature/ed25519_public_key.h new file mode 100644 index 000000000..5deae86a2 --- /dev/null +++ b/cc/signature/ed25519_public_key.h @@ -0,0 +1,81 @@ +// 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_SIGNATURE_ED25519_PUBLIC_KEY_H_ +#define TINK_SIGNATURE_ED25519_PUBLIC_KEY_H_ + +#include <string> + +#include "absl/types/optional.h" +#include "tink/partial_key_access_token.h" +#include "tink/signature/ed25519_parameters.h" +#include "tink/signature/signature_public_key.h" +#include "tink/util/statusor.h" + +namespace crypto { +namespace tink { + +class Ed25519PublicKey : public SignaturePublicKey { + public: + // Copyable and movable. + Ed25519PublicKey(const Ed25519PublicKey& other) = default; + Ed25519PublicKey& operator=(const Ed25519PublicKey& other) = default; + Ed25519PublicKey(Ed25519PublicKey&& other) = default; + Ed25519PublicKey& operator=(Ed25519PublicKey&& other) = default; + + // Creates a new Ed25519 public key from `public_key_bytes`. If `parameters` + // specify a variant that uses a prefix, then `id_requirement` is used to + // compute this prefix. + static util::StatusOr<Ed25519PublicKey> Create( + const Ed25519Parameters& parameters, absl::string_view public_key_bytes, + absl::optional<int> id_requirement, PartialKeyAccessToken token); + + absl::string_view GetPublicKeyBytes(PartialKeyAccessToken token) const { + return public_key_bytes_; + } + + absl::string_view GetOutputPrefix() const override { return output_prefix_; } + + const Ed25519Parameters& GetParameters() const override { + return parameters_; + } + + absl::optional<int> GetIdRequirement() const override { + return id_requirement_; + } + + bool operator==(const Key& other) const override; + + private: + explicit Ed25519PublicKey(const Ed25519Parameters& parameters, + absl::string_view public_key_bytes, + absl::optional<int> id_requirement, + absl::string_view output_prefix) + : parameters_(parameters), + public_key_bytes_(public_key_bytes), + id_requirement_(id_requirement), + output_prefix_(output_prefix) {} + + Ed25519Parameters parameters_; + std::string public_key_bytes_; + absl::optional<int> id_requirement_; + std::string output_prefix_; +}; + +} // namespace tink +} // namespace crypto + +#endif // TINK_SIGNATURE_ED25519_PUBLIC_KEY_H_ diff --git a/cc/signature/ed25519_public_key_test.cc b/cc/signature/ed25519_public_key_test.cc new file mode 100644 index 000000000..c96ad35cd --- /dev/null +++ b/cc/signature/ed25519_public_key_test.cc @@ -0,0 +1,215 @@ +// 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/signature/ed25519_public_key.h" + +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "tink/partial_key_access.h" +#include "tink/subtle/random.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::StatusIs; +using ::testing::Eq; +using ::testing::TestWithParam; +using ::testing::Values; + +struct TestCase { + Ed25519Parameters::Variant variant; + absl::optional<int> id_requirement; + std::string output_prefix; +}; + +using Ed25519PublicKeyTest = TestWithParam<TestCase>; + +INSTANTIATE_TEST_SUITE_P( + Ed25519PublicKeyTestSuite, Ed25519PublicKeyTest, + Values(TestCase{Ed25519Parameters::Variant::kTink, 0x02030400, + std::string("\x01\x02\x03\x04\x00", 5)}, + TestCase{Ed25519Parameters::Variant::kCrunchy, 0x01030005, + std::string("\x00\x01\x03\x00\x05", 5)}, + TestCase{Ed25519Parameters::Variant::kLegacy, 0x07080910, + std::string("\x00\x07\x08\x09\x10", 5)}, + TestCase{Ed25519Parameters::Variant::kNoPrefix, absl::nullopt, ""})); + +TEST_P(Ed25519PublicKeyTest, CreateSucceeds) { + TestCase test_case = GetParam(); + + util::StatusOr<Ed25519Parameters> params = + Ed25519Parameters::Create(test_case.variant); + ASSERT_THAT(params, IsOk()); + + std::string public_key_bytes = subtle::Random::GetRandomBytes(32); + util::StatusOr<Ed25519PublicKey> public_key = + Ed25519PublicKey::Create(*params, public_key_bytes, + test_case.id_requirement, GetPartialKeyAccess()); + ASSERT_THAT(public_key, IsOk()); + + EXPECT_THAT(public_key->GetParameters(), Eq(*params)); + EXPECT_THAT(public_key->GetIdRequirement(), Eq(test_case.id_requirement)); + EXPECT_THAT(public_key->GetOutputPrefix(), Eq(test_case.output_prefix)); + EXPECT_THAT(public_key->GetPublicKeyBytes(GetPartialKeyAccess()), + Eq(public_key_bytes)); +} + +TEST(Ed25519PublicKeyTest, CreateWithInvalidPublicKeyLength) { + util::StatusOr<Ed25519Parameters> params = + Ed25519Parameters::Create(Ed25519Parameters::Variant::kTink); + ASSERT_THAT(params, IsOk()); + + std::string public_key_bytes = subtle::Random::GetRandomBytes(31); + + EXPECT_THAT( + Ed25519PublicKey::Create(*params, public_key_bytes, + /*id_requirement=*/123, GetPartialKeyAccess()) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(Ed25519PublicKeyTest, CreateKeyWithInvalidIdRequirementFails) { + util::StatusOr<Ed25519Parameters> no_prefix_params = + Ed25519Parameters::Create(Ed25519Parameters::Variant::kNoPrefix); + ASSERT_THAT(no_prefix_params, IsOk()); + + util::StatusOr<Ed25519Parameters> tink_params = + Ed25519Parameters::Create(Ed25519Parameters::Variant::kTink); + ASSERT_THAT(tink_params, IsOk()); + + std::string public_key_bytes = subtle::Random::GetRandomBytes(32); + + EXPECT_THAT( + Ed25519PublicKey::Create(*no_prefix_params, public_key_bytes, + /*id_requirement=*/123, GetPartialKeyAccess()) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument)); + + EXPECT_THAT(Ed25519PublicKey::Create(*tink_params, public_key_bytes, + /*id_requirement=*/absl::nullopt, + GetPartialKeyAccess()) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST_P(Ed25519PublicKeyTest, KeyEquals) { + TestCase test_case = GetParam(); + + util::StatusOr<Ed25519Parameters> params = + Ed25519Parameters::Create(test_case.variant); + ASSERT_THAT(params, IsOk()); + + std::string public_key_bytes = subtle::Random::GetRandomBytes(32); + + util::StatusOr<Ed25519PublicKey> public_key = + Ed25519PublicKey::Create(*params, public_key_bytes, + test_case.id_requirement, GetPartialKeyAccess()); + ASSERT_THAT(public_key, IsOk()); + + util::StatusOr<Ed25519PublicKey> other_public_key = + Ed25519PublicKey::Create(*params, public_key_bytes, + test_case.id_requirement, GetPartialKeyAccess()); + ASSERT_THAT(other_public_key, IsOk()); + + EXPECT_TRUE(*public_key == *other_public_key); + EXPECT_TRUE(*other_public_key == *public_key); + EXPECT_FALSE(*public_key != *other_public_key); + EXPECT_FALSE(*other_public_key != *public_key); +} + +TEST(Ed25519PublicKeyTest, DifferentVariantNotEqual) { + util::StatusOr<Ed25519Parameters> crunchy_params = + Ed25519Parameters::Create(Ed25519Parameters::Variant::kCrunchy); + ASSERT_THAT(crunchy_params, IsOk()); + + util::StatusOr<Ed25519Parameters> tink_params = + Ed25519Parameters::Create(Ed25519Parameters::Variant::kTink); + ASSERT_THAT(tink_params, IsOk()); + + std::string public_key_bytes = subtle::Random::GetRandomBytes(32); + + util::StatusOr<Ed25519PublicKey> public_key = Ed25519PublicKey::Create( + *crunchy_params, public_key_bytes, /*id_requirement=*/0x01020304, + GetPartialKeyAccess()); + ASSERT_THAT(public_key, IsOk()); + + util::StatusOr<Ed25519PublicKey> other_public_key = Ed25519PublicKey::Create( + *tink_params, public_key_bytes, /*id_requirement=*/0x01020304, + GetPartialKeyAccess()); + ASSERT_THAT(other_public_key, IsOk()); + + EXPECT_TRUE(*public_key != *other_public_key); + EXPECT_TRUE(*other_public_key != *public_key); + EXPECT_FALSE(*public_key == *other_public_key); + EXPECT_FALSE(*other_public_key == *public_key); +} + +TEST(Ed25519PublicKeyTest, DifferentPublicKeyBytesNotEqual) { + util::StatusOr<Ed25519Parameters> params = + Ed25519Parameters::Create(Ed25519Parameters::Variant::kTink); + ASSERT_THAT(params, IsOk()); + + std::string public_key_bytes1 = subtle::Random::GetRandomBytes(32); + std::string public_key_bytes2 = subtle::Random::GetRandomBytes(32); + + util::StatusOr<Ed25519PublicKey> public_key = Ed25519PublicKey::Create( + *params, public_key_bytes1, /*id_requirement=*/0x01020304, + GetPartialKeyAccess()); + ASSERT_THAT(public_key, IsOk()); + + util::StatusOr<Ed25519PublicKey> other_public_key = Ed25519PublicKey::Create( + *params, public_key_bytes2, /*id_requirement=*/0x01020304, + GetPartialKeyAccess()); + ASSERT_THAT(other_public_key, IsOk()); + + EXPECT_TRUE(*public_key != *other_public_key); + EXPECT_TRUE(*other_public_key != *public_key); + EXPECT_FALSE(*public_key == *other_public_key); + EXPECT_FALSE(*other_public_key == *public_key); +} + +TEST(Ed25519PublicKeyTest, DifferentIdRequirementNotEqual) { + util::StatusOr<Ed25519Parameters> params = + Ed25519Parameters::Create(Ed25519Parameters::Variant::kTink); + ASSERT_THAT(params, IsOk()); + + std::string public_key_bytes = subtle::Random::GetRandomBytes(32); + + util::StatusOr<Ed25519PublicKey> public_key = Ed25519PublicKey::Create( + *params, public_key_bytes, /*id_requirement=*/0x01020304, + GetPartialKeyAccess()); + ASSERT_THAT(public_key, IsOk()); + + util::StatusOr<Ed25519PublicKey> other_public_key = Ed25519PublicKey::Create( + *params, public_key_bytes, /*id_requirement=*/0x02030405, + GetPartialKeyAccess()); + ASSERT_THAT(other_public_key, IsOk()); + + EXPECT_TRUE(*public_key != *other_public_key); + EXPECT_TRUE(*other_public_key != *public_key); + EXPECT_FALSE(*public_key == *other_public_key); + EXPECT_FALSE(*other_public_key == *public_key); +} + +} // namespace +} // namespace tink +} // namespace crypto |