diff --git a/cc/experimental/pqcrypto/signature/slh_dsa_public_key.cc b/cc/experimental/pqcrypto/signature/slh_dsa_public_key.cc new file mode 100644 index 0000000000..9f600e571c --- /dev/null +++ b/cc/experimental/pqcrypto/signature/slh_dsa_public_key.cc @@ -0,0 +1,99 @@ +// Copyright 2024 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/experimental/pqcrypto/signature/slh_dsa_public_key.h" + +#include + +#include "absl/status/status.h" +#include "absl/strings/escaping.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "tink/experimental/pqcrypto/signature/slh_dsa_parameters.h" +#include "tink/key.h" +#include "tink/partial_key_access_token.h" +#include "tink/subtle/subtle_util.h" +#include "tink/util/status.h" +#include "tink/util/statusor.h" + +namespace crypto { +namespace tink { +namespace { + +util::StatusOr ComputeOutputPrefix( + const SlhDsaParameters& parameters, absl::optional id_requirement) { + switch (parameters.GetVariant()) { + case SlhDsaParameters::Variant::kNoPrefix: + return std::string(""); // Empty prefix. + case SlhDsaParameters::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 SlhDsaPublicKey::Create( + const SlhDsaParameters& parameters, absl::string_view public_key_bytes, + absl::optional 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"); + } + // Only 32-byte public keys are supported at the moment. + if (public_key_bytes.size() != 32) { + return util::Status(absl::StatusCode::kInvalidArgument, + "Invalid public key size. Only 32-byte keys are " + "currently supported."); + } + util::StatusOr output_prefix = + ComputeOutputPrefix(parameters, id_requirement); + if (!output_prefix.ok()) { + return output_prefix.status(); + } + return SlhDsaPublicKey(parameters, public_key_bytes, id_requirement, + *output_prefix); +} + +bool SlhDsaPublicKey::operator==(const Key& other) const { + const SlhDsaPublicKey* that = dynamic_cast(&other); + if (that == nullptr) { + return false; + } + return GetParameters() == that->GetParameters() && + public_key_bytes_ == that->public_key_bytes_ && + id_requirement_ == that->id_requirement_; +} + +} // namespace tink +} // namespace crypto diff --git a/cc/experimental/pqcrypto/signature/slh_dsa_public_key.h b/cc/experimental/pqcrypto/signature/slh_dsa_public_key.h new file mode 100644 index 0000000000..24fe2fe16b --- /dev/null +++ b/cc/experimental/pqcrypto/signature/slh_dsa_public_key.h @@ -0,0 +1,85 @@ +// Copyright 2024 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_EXPERIMENTAL_PQCRYPTO_SIGNATURE_SLH_DSA_PUBLIC_KEY_H_ +#define TINK_EXPERIMENTAL_PQCRYPTO_SIGNATURE_SLH_DSA_PUBLIC_KEY_H_ + +#include + +#include "absl/base/attributes.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "tink/experimental/pqcrypto/signature/slh_dsa_parameters.h" +#include "tink/key.h" +#include "tink/partial_key_access_token.h" +#include "tink/signature/signature_public_key.h" +#include "tink/util/statusor.h" + +namespace crypto { +namespace tink { + +// Representation of the verification function for the SLH-DSA digital signature +// primitive. +class SlhDsaPublicKey : public SignaturePublicKey { + public: + // Copyable and movable. + SlhDsaPublicKey(const SlhDsaPublicKey& other) = default; + SlhDsaPublicKey& operator=(const SlhDsaPublicKey& other) = default; + SlhDsaPublicKey(SlhDsaPublicKey&& other) = default; + SlhDsaPublicKey& operator=(SlhDsaPublicKey&& other) = default; + + // Creates a new SLH-DSA public key from `public_key_bytes`. If the + // `parameters` specify a variant that uses a prefix, then `id_requirement` is + // used to compute this prefix. + static util::StatusOr Create( + const SlhDsaParameters& parameters, absl::string_view public_key_bytes, + absl::optional id_requirement, PartialKeyAccessToken token); + + absl::string_view GetPublicKeyBytes(PartialKeyAccessToken token) const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return public_key_bytes_; + } + + absl::string_view GetOutputPrefix() const override { return output_prefix_; } + + const SlhDsaParameters& GetParameters() const override { return parameters_; } + + absl::optional GetIdRequirement() const override { + return id_requirement_; + } + + bool operator==(const Key& other) const override; + + private: + explicit SlhDsaPublicKey(const SlhDsaParameters& parameters, + absl::string_view public_key_bytes, + absl::optional id_requirement, + absl::string_view output_prefix) + : parameters_(parameters), + public_key_bytes_(public_key_bytes), + id_requirement_(id_requirement), + output_prefix_(output_prefix) {} + + SlhDsaParameters parameters_; + std::string public_key_bytes_; + absl::optional id_requirement_; + std::string output_prefix_; +}; + +} // namespace tink +} // namespace crypto + +#endif // TINK_EXPERIMENTAL_PQCRYPTO_SIGNATURE_SLH_DSA_PUBLIC_KEY_H_ diff --git a/cc/experimental/pqcrypto/signature/slh_dsa_public_key_test.cc b/cc/experimental/pqcrypto/signature/slh_dsa_public_key_test.cc new file mode 100644 index 0000000000..efbaa95815 --- /dev/null +++ b/cc/experimental/pqcrypto/signature/slh_dsa_public_key_test.cc @@ -0,0 +1,212 @@ +// Copyright 2024 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/experimental/pqcrypto/signature/slh_dsa_public_key.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/status/status.h" +#include "absl/types/optional.h" +#include "tink/experimental/pqcrypto/signature/slh_dsa_parameters.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::HasSubstr; +using ::testing::TestWithParam; +using ::testing::Values; + +struct TestCase { + SlhDsaParameters::Variant variant; + absl::optional id_requirement; + std::string output_prefix; +}; + +using SlhDsaPublicKeyTest = TestWithParam; + +INSTANTIATE_TEST_SUITE_P( + SlhDsaPublicKeyTestSuite, SlhDsaPublicKeyTest, + Values(TestCase{SlhDsaParameters::Variant::kTink, 0x02030400, + std::string("\x01\x02\x03\x04\x00", 5)}, + TestCase{SlhDsaParameters::Variant::kTink, 0x03050709, + std::string("\x01\x03\x05\x07\x09", 5)}, + TestCase{SlhDsaParameters::Variant::kNoPrefix, absl::nullopt, ""})); + +TEST_P(SlhDsaPublicKeyTest, CreatePublicKeyWorks) { + TestCase test_case = GetParam(); + + util::StatusOr params = SlhDsaParameters::Create( + SlhDsaParameters::HashType::kSha2, /*private_key_size_in_bytes=*/64, + SlhDsaParameters::SignatureType::kSmallSignature, test_case.variant); + ASSERT_THAT(params, IsOk()); + + std::string public_key_bytes = subtle::Random::GetRandomBytes(32); + util::StatusOr public_key = + SlhDsaPublicKey::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(SlhDsaPublicKeyTest, CreateWithInvalidPublicKeyLengthFails) { + util::StatusOr params = SlhDsaParameters::Create( + SlhDsaParameters::HashType::kSha2, /*private_key_size_in_bytes=*/64, + SlhDsaParameters::SignatureType::kSmallSignature, + SlhDsaParameters::Variant::kTink); + ASSERT_THAT(params, IsOk()); + + std::string public_key_bytes = subtle::Random::GetRandomBytes(31); + + EXPECT_THAT( + SlhDsaPublicKey::Create(*params, public_key_bytes, + /*id_requirement=*/123, GetPartialKeyAccess()) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Invalid public key size"))); +} + +TEST(SlhDsaPublicKeyTest, CreateKeyWithNoIdRequirementWithTinkParamsFails) { + util::StatusOr tink_params = SlhDsaParameters::Create( + SlhDsaParameters::HashType::kSha2, /*private_key_size_in_bytes=*/64, + SlhDsaParameters::SignatureType::kSmallSignature, + SlhDsaParameters::Variant::kTink); + ASSERT_THAT(tink_params, IsOk()); + + std::string public_key_bytes = subtle::Random::GetRandomBytes(32); + + EXPECT_THAT(SlhDsaPublicKey::Create(*tink_params, public_key_bytes, + /*id_requirement=*/absl::nullopt, + GetPartialKeyAccess()) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("key without ID requirement with parameters " + "with ID requirement"))); +} + +TEST(SlhDsaPublicKeyTest, CreateKeyWithIdRequirementWithNoPrefixParamsFails) { + util::StatusOr no_prefix_params = + SlhDsaParameters::Create(SlhDsaParameters::HashType::kSha2, + /*private_key_size_in_bytes=*/64, + SlhDsaParameters::SignatureType::kSmallSignature, + SlhDsaParameters::Variant::kNoPrefix); + ASSERT_THAT(no_prefix_params, IsOk()); + + std::string public_key_bytes = subtle::Random::GetRandomBytes(32); + + EXPECT_THAT( + SlhDsaPublicKey::Create(*no_prefix_params, public_key_bytes, + /*id_requirement=*/123, GetPartialKeyAccess()) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("key with ID requirement with parameters without ID " + "requirement"))); +} + +TEST_P(SlhDsaPublicKeyTest, PublicKeyEquals) { + TestCase test_case = GetParam(); + + util::StatusOr params = SlhDsaParameters::Create( + SlhDsaParameters::HashType::kSha2, /*private_key_size_in_bytes=*/64, + SlhDsaParameters::SignatureType::kSmallSignature, test_case.variant); + ASSERT_THAT(params, IsOk()); + + std::string public_key_bytes = subtle::Random::GetRandomBytes(32); + + util::StatusOr public_key = + SlhDsaPublicKey::Create(*params, public_key_bytes, + test_case.id_requirement, GetPartialKeyAccess()); + ASSERT_THAT(public_key, IsOk()); + + util::StatusOr other_public_key = + SlhDsaPublicKey::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(SlhDsaPublicKeyTest, DifferentPublicKeyBytesNotEqual) { + util::StatusOr params = + SlhDsaParameters::Create(SlhDsaParameters::HashType::kSha2, + /*private_key_size_in_bytes=*/64, + SlhDsaParameters::SignatureType::kSmallSignature, + SlhDsaParameters::Variant::kTink); + + std::string public_key_bytes1 = subtle::Random::GetRandomBytes(32); + std::string public_key_bytes2 = subtle::Random::GetRandomBytes(32); + + util::StatusOr public_key = SlhDsaPublicKey::Create( + *params, public_key_bytes1, /*id_requirement=*/0x01020304, + GetPartialKeyAccess()); + ASSERT_THAT(public_key, IsOk()); + + util::StatusOr other_public_key = SlhDsaPublicKey::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(SlhDsaPublicKeyTest, DifferentIdRequirementNotEqual) { + util::StatusOr params = + SlhDsaParameters::Create(SlhDsaParameters::HashType::kSha2, + /*private_key_size_in_bytes=*/64, + SlhDsaParameters::SignatureType::kSmallSignature, + SlhDsaParameters::Variant::kTink); + + std::string public_key_bytes = subtle::Random::GetRandomBytes(32); + + util::StatusOr public_key = SlhDsaPublicKey::Create( + *params, public_key_bytes, /*id_requirement=*/0x01020304, + GetPartialKeyAccess()); + ASSERT_THAT(public_key, IsOk()); + + util::StatusOr other_public_key = SlhDsaPublicKey::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