From db7579df4bf3e912878c52e8162f2b551c2ec711 Mon Sep 17 00:00:00 2001 From: wconner Date: Tue, 1 Aug 2023 11:44:36 -0700 Subject: Add Ed25519 private key type. PiperOrigin-RevId: 552871063 --- cc/signature/BUILD.bazel | 34 +++++ cc/signature/CMakeLists.txt | 33 +++++ cc/signature/ed25519_private_key.cc | 61 +++++++++ cc/signature/ed25519_private_key.h | 64 ++++++++++ cc/signature/ed25519_private_key_test.cc | 208 +++++++++++++++++++++++++++++++ 5 files changed, 400 insertions(+) create mode 100644 cc/signature/ed25519_private_key.cc create mode 100644 cc/signature/ed25519_private_key.h create mode 100644 cc/signature/ed25519_private_key_test.cc diff --git a/cc/signature/BUILD.bazel b/cc/signature/BUILD.bazel index 22158a142..5dd6b13ec 100644 --- a/cc/signature/BUILD.bazel +++ b/cc/signature/BUILD.bazel @@ -461,6 +461,24 @@ cc_library( ], ) +cc_library( + name = "ed25519_private_key", + srcs = ["ed25519_private_key.cc"], + hdrs = ["ed25519_private_key.h"], + include_prefix = "tink/signature", + deps = [ + ":ed25519_public_key", + ":signature_private_key", + "//:insecure_secret_key_access", + "//:partial_key_access_token", + "//:restricted_data", + "//internal:ec_util", + "//util:secret_data", + "//util:statusor", + "@boringssl//:crypto", + ], +) + # tests cc_test( @@ -856,3 +874,19 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "ed25519_private_key_test", + srcs = ["ed25519_private_key_test.cc"], + deps = [ + ":ed25519_private_key", + ":ed25519_public_key", + "//:insecure_secret_key_access", + "//:partial_key_access", + "//internal:ec_util", + "//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 8693762eb..4e3864862 100644 --- a/cc/signature/CMakeLists.txt +++ b/cc/signature/CMakeLists.txt @@ -439,6 +439,23 @@ tink_cc_library( tink::util::statusor ) +tink_cc_library( + NAME ed25519_private_key + SRCS + ed25519_private_key.cc + ed25519_private_key.h + DEPS + tink::signature::ed25519_public_key + tink::signature::signature_private_key + crypto + tink::core::insecure_secret_key_access + tink::core::partial_key_access_token + tink::core::restricted_data + tink::internal::ec_util + tink::util::secret_data + tink::util::statusor +) + # tests tink_cc_test( @@ -818,3 +835,19 @@ tink_cc_test( tink::util::statusor tink::util::test_matchers ) + +tink_cc_test( + NAME ed25519_private_key_test + SRCS + ed25519_private_key_test.cc + DEPS + tink::signature::ed25519_private_key + tink::signature::ed25519_public_key + gmock + tink::core::insecure_secret_key_access + tink::core::partial_key_access + tink::internal::ec_util + tink::subtle::random + tink::util::statusor + tink::util::test_matchers +) diff --git a/cc/signature/ed25519_private_key.cc b/cc/signature/ed25519_private_key.cc new file mode 100644 index 000000000..1e040fa9f --- /dev/null +++ b/cc/signature/ed25519_private_key.cc @@ -0,0 +1,61 @@ +// 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_private_key.h" + +#include "openssl/crypto.h" +#include "tink/insecure_secret_key_access.h" +#include "tink/internal/ec_util.h" +#include "tink/partial_key_access_token.h" +#include "tink/util/secret_data.h" + +namespace crypto { +namespace tink { + +util::StatusOr Ed25519PrivateKey::Create( + const Ed25519PublicKey& public_key, const RestrictedData& private_key_bytes, + PartialKeyAccessToken token) { + if (private_key_bytes.size() != 32) { + return util::Status(absl::StatusCode::kInvalidArgument, + "Ed25519 private key length must be 32 bytes."); + } + // Confirm that private key and public key are a valid Ed25519 key pair. + util::StatusOr> key_pair = + internal::NewEd25519Key(util::SecretDataFromStringView( + private_key_bytes.GetSecret(InsecureSecretKeyAccess::Get()))); + absl::string_view expected_public_key = public_key.GetPublicKeyBytes(token); + if (CRYPTO_memcmp(expected_public_key.data(), (*key_pair)->public_key.data(), + 32) != 0) { + return util::Status(absl::StatusCode::kInvalidArgument, + "Invalid public key for private key bytes."); + } + return Ed25519PrivateKey(public_key, private_key_bytes); +} + +bool Ed25519PrivateKey::operator==(const Key& other) const { + const Ed25519PrivateKey* that = + dynamic_cast(&other); + if (that == nullptr) { + return false; + } + if (public_key_ != that->public_key_) { + return false; + } + return private_key_bytes_ == that->private_key_bytes_; +} + +} // namespace tink +} // namespace crypto diff --git a/cc/signature/ed25519_private_key.h b/cc/signature/ed25519_private_key.h new file mode 100644 index 000000000..d7cd9dcc4 --- /dev/null +++ b/cc/signature/ed25519_private_key.h @@ -0,0 +1,64 @@ +// 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_PRIVATE_KEY_H_ +#define TINK_SIGNATURE_ED25519_PRIVATE_KEY_H_ + +#include "tink/partial_key_access_token.h" +#include "tink/restricted_data.h" +#include "tink/signature/ed25519_public_key.h" +#include "tink/signature/signature_private_key.h" +#include "tink/util/statusor.h" + +namespace crypto { +namespace tink { + +class Ed25519PrivateKey : public SignaturePrivateKey { + public: + // Copyable and movable. + Ed25519PrivateKey(const Ed25519PrivateKey& other) = default; + Ed25519PrivateKey& operator=(const Ed25519PrivateKey& other) = default; + Ed25519PrivateKey(Ed25519PrivateKey&& other) = default; + Ed25519PrivateKey& operator=(Ed25519PrivateKey&& other) = default; + + // Creates a new Ed25519 private key from `private_key_bytes`. Returns an + // error if `public_key` does not belong to the same key pair as + // `private_key_bytes`. + static util::StatusOr Create( + const Ed25519PublicKey& public_key, + const RestrictedData& private_key_bytes, PartialKeyAccessToken token); + + const RestrictedData& GetPrivateKeyBytes(PartialKeyAccessToken token) const { + return private_key_bytes_; + } + + const Ed25519PublicKey& GetPublicKey() const override { return public_key_; } + + bool operator==(const Key& other) const override; + + private: + explicit Ed25519PrivateKey(const Ed25519PublicKey& public_key, + const RestrictedData& private_key_bytes) + : public_key_(public_key), private_key_bytes_(private_key_bytes) {} + + Ed25519PublicKey public_key_; + RestrictedData private_key_bytes_; +}; + +} // namespace tink +} // namespace crypto + +#endif // TINK_SIGNATURE_ED25519_PRIVATE_KEY_H_ diff --git a/cc/signature/ed25519_private_key_test.cc b/cc/signature/ed25519_private_key_test.cc new file mode 100644 index 000000000..8aaeff198 --- /dev/null +++ b/cc/signature/ed25519_private_key_test.cc @@ -0,0 +1,208 @@ +// 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_private_key.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "tink/insecure_secret_key_access.h" +#include "tink/internal/ec_util.h" +#include "tink/partial_key_access.h" +#include "tink/signature/ed25519_public_key.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 id_requirement; + std::string output_prefix; +}; + +using Ed25519PrivateKeyTest = TestWithParam; + +INSTANTIATE_TEST_SUITE_P( + Ed25519PrivateKeyTestSuite, Ed25519PrivateKeyTest, + 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(Ed25519PrivateKeyTest, CreateSucceeds) { + TestCase test_case = GetParam(); + + util::StatusOr params = + Ed25519Parameters::Create(test_case.variant); + ASSERT_THAT(params, IsOk()); + + util::StatusOr> key_pair = + internal::NewEd25519Key(); + ASSERT_THAT(key_pair, IsOk()); + + util::StatusOr public_key = + Ed25519PublicKey::Create(*params, (*key_pair)->public_key, + test_case.id_requirement, GetPartialKeyAccess()); + ASSERT_THAT(public_key, IsOk()); + + RestrictedData private_key_bytes = + RestrictedData((*key_pair)->private_key, InsecureSecretKeyAccess::Get()); + + util::StatusOr private_key = Ed25519PrivateKey::Create( + *public_key, private_key_bytes, GetPartialKeyAccess()); + ASSERT_THAT(private_key, IsOk()); + + EXPECT_THAT(private_key->GetParameters(), Eq(*params)); + EXPECT_THAT(private_key->GetIdRequirement(), Eq(test_case.id_requirement)); + EXPECT_THAT(private_key->GetPublicKey(), Eq(*public_key)); + EXPECT_THAT(private_key->GetOutputPrefix(), Eq(test_case.output_prefix)); + EXPECT_THAT(private_key->GetPrivateKeyBytes(GetPartialKeyAccess()), + Eq(private_key_bytes)); +} + +TEST(Ed25519PrivateKeyTest, CreateWithMismatchedPublicKeyFails) { + util::StatusOr params = + Ed25519Parameters::Create(Ed25519Parameters::Variant::kTink); + ASSERT_THAT(params, IsOk()); + + util::StatusOr> key_pair = + internal::NewEd25519Key(); + ASSERT_THAT(key_pair, IsOk()); + + std::string public_key_bytes = subtle::Random::GetRandomBytes(32); + util::StatusOr public_key = + Ed25519PublicKey::Create(*params, public_key_bytes, + /*id_requirement=*/123, GetPartialKeyAccess()); + ASSERT_THAT(public_key, IsOk()); + + RestrictedData private_key_bytes = + RestrictedData((*key_pair)->private_key, InsecureSecretKeyAccess::Get()); + + EXPECT_THAT(Ed25519PrivateKey::Create(*public_key, private_key_bytes, + GetPartialKeyAccess()) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(Ed25519PrivateKeyTest, CreateWithInvalidPrivateKeyLengthFails) { + util::StatusOr params = + Ed25519Parameters::Create(Ed25519Parameters::Variant::kTink); + ASSERT_THAT(params, IsOk()); + + util::StatusOr> key_pair = + internal::NewEd25519Key(); + ASSERT_THAT(key_pair, IsOk()); + + util::StatusOr public_key = + Ed25519PublicKey::Create(*params, (*key_pair)->public_key, + /*id_requirement=*/123, GetPartialKeyAccess()); + ASSERT_THAT(public_key, IsOk()); + + RestrictedData private_key_bytes = RestrictedData( + (*key_pair)->private_key.substr(0, 31), InsecureSecretKeyAccess::Get()); + + EXPECT_THAT(Ed25519PrivateKey::Create(*public_key, private_key_bytes, + GetPartialKeyAccess()) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST_P(Ed25519PrivateKeyTest, KeyEquals) { + TestCase test_case = GetParam(); + + util::StatusOr params = + Ed25519Parameters::Create(test_case.variant); + ASSERT_THAT(params, IsOk()); + + util::StatusOr> key_pair = + internal::NewEd25519Key(); + ASSERT_THAT(key_pair, IsOk()); + + util::StatusOr public_key = + Ed25519PublicKey::Create(*params, (*key_pair)->public_key, + test_case.id_requirement, GetPartialKeyAccess()); + ASSERT_THAT(public_key, IsOk()); + + RestrictedData private_key_bytes = + RestrictedData((*key_pair)->private_key, InsecureSecretKeyAccess::Get()); + + util::StatusOr private_key = Ed25519PrivateKey::Create( + *public_key, private_key_bytes, GetPartialKeyAccess()); + ASSERT_THAT(private_key, IsOk()); + + util::StatusOr other_private_key = + Ed25519PrivateKey::Create(*public_key, private_key_bytes, + GetPartialKeyAccess()); + ASSERT_THAT(other_private_key, IsOk()); + + EXPECT_TRUE(*private_key == *other_private_key); + EXPECT_TRUE(*other_private_key == *private_key); + EXPECT_FALSE(*private_key != *other_private_key); + EXPECT_FALSE(*other_private_key != *private_key); +} + +TEST(Ed25519PrivateKeyTest, DifferentPublicKeyNotEqual) { + util::StatusOr params = + Ed25519Parameters::Create(Ed25519Parameters::Variant::kTink); + ASSERT_THAT(params, IsOk()); + + util::StatusOr> key_pair = + internal::NewEd25519Key(); + ASSERT_THAT(key_pair, IsOk()); + + util::StatusOr public_key123 = + Ed25519PublicKey::Create(*params, (*key_pair)->public_key, + /*id_requirement=*/123, GetPartialKeyAccess()); + ASSERT_THAT(public_key123, IsOk()); + + util::StatusOr public_key456 = + Ed25519PublicKey::Create(*params, (*key_pair)->public_key, + /*id_requirement=*/456, GetPartialKeyAccess()); + ASSERT_THAT(public_key456, IsOk()); + + RestrictedData private_key_bytes = + RestrictedData((*key_pair)->private_key, InsecureSecretKeyAccess::Get()); + + util::StatusOr private_key = Ed25519PrivateKey::Create( + *public_key123, private_key_bytes, GetPartialKeyAccess()); + ASSERT_THAT(private_key, IsOk()); + + util::StatusOr other_private_key = + Ed25519PrivateKey::Create(*public_key456, private_key_bytes, + GetPartialKeyAccess()); + ASSERT_THAT(other_private_key, IsOk()); + + EXPECT_TRUE(*private_key != *other_private_key); + EXPECT_TRUE(*other_private_key != *private_key); + EXPECT_FALSE(*private_key == *other_private_key); + EXPECT_FALSE(*other_private_key == *private_key); +} + +} // namespace +} // namespace tink +} // namespace crypto -- cgit v1.2.3