Merge pull request #735 from tink-crypto:dependabot/pip/python/examples/idna-3.7 PiperOrigin-RevId: 625622842
diff --git a/cc/BUILD.bazel b/cc/BUILD.bazel index 85db708..aea68c5 100644 --- a/cc/BUILD.bazel +++ b/cc/BUILD.bazel
@@ -365,8 +365,6 @@ ":keyset_reader", "//proto:tink_cc_proto", "//util:enums", - "//util:errors", - "//util:protobuf_helper", "//util:status", "//util:statusor", "@com_google_absl//absl/memory", @@ -903,9 +901,11 @@ srcs = ["core/json_keyset_reader_test.cc"], deps = [ ":json_keyset_reader", + ":keyset_reader", "//proto:aes_eax_cc_proto", "//proto:aes_gcm_cc_proto", "//proto:tink_cc_proto", + "//util:statusor", "//util:test_matchers", "//util:test_util", "@com_google_absl//absl/status",
diff --git a/cc/CMakeLists.txt b/cc/CMakeLists.txt index 93122c3..2f4a3db 100644 --- a/cc/CMakeLists.txt +++ b/cc/CMakeLists.txt
@@ -343,8 +343,6 @@ absl::strings rapidjson tink::util::enums - tink::util::errors - tink::util::protobuf_helper tink::util::status tink::util::statusor tink::proto::tink_cc_proto @@ -847,9 +845,11 @@ core/json_keyset_reader_test.cc DEPS tink::core::json_keyset_reader + tink::core::keyset_reader gmock absl::status absl::strings + tink::util::statusor tink::util::test_matchers tink::util::test_util tink::proto::aes_eax_cc_proto
diff --git a/cc/core/json_keyset_reader.cc b/cc/core/json_keyset_reader.cc index 84c1d8b..d15ebc0 100644 --- a/cc/core/json_keyset_reader.cc +++ b/cc/core/json_keyset_reader.cc
@@ -31,6 +31,7 @@ #include "include/rapidjson/document.h" #include "include/rapidjson/error/en.h" #include "include/rapidjson/rapidjson.h" +#include "include/rapidjson/reader.h" #include "tink/keyset_reader.h" #include "tink/util/enums.h" #include "tink/util/status.h" @@ -242,7 +243,8 @@ serialized_keyset = &serialized_keyset_from_stream; } rapidjson::Document json_doc(rapidjson::kObjectType); - if (json_doc.Parse(serialized_keyset->c_str()).HasParseError()) { + if (json_doc.Parse<rapidjson::kParseIterativeFlag>(serialized_keyset->c_str()) + .HasParseError()) { return util::Status( absl::StatusCode::kInvalidArgument, absl::StrCat( @@ -268,7 +270,8 @@ serialized_keyset = &serialized_keyset_from_stream; } rapidjson::Document json_doc; - if (json_doc.Parse(serialized_keyset->c_str()).HasParseError()) { + if (json_doc.Parse<rapidjson::kParseIterativeFlag>(serialized_keyset->c_str()) + .HasParseError()) { return util::Status( absl::StatusCode::kInvalidArgument, absl::StrCat("Invalid JSON EncryptedKeyset: Error (offset ",
diff --git a/cc/core/json_keyset_reader_test.cc b/cc/core/json_keyset_reader_test.cc index 6fc0ba8..e9725ee 100644 --- a/cc/core/json_keyset_reader_test.cc +++ b/cc/core/json_keyset_reader_test.cc
@@ -29,11 +29,13 @@ #include "absl/status/status.h" #include "absl/strings/escaping.h" #include "absl/strings/substitute.h" +#include "tink/util/statusor.h" #include "tink/util/test_matchers.h" #include "tink/util/test_util.h" #include "proto/aes_eax.pb.h" #include "proto/aes_gcm.pb.h" #include "proto/tink.pb.h" +#include "tink/keyset_reader.h" namespace crypto { namespace tink { @@ -397,6 +399,23 @@ EXPECT_THAT(read_result, Not(IsOk())); } + +TEST_F(JsonKeysetReaderTest, parseRecursiveJsonStringFails) { + std::string recursive_json; + for (int i = 0; i < 1000000; i++) { + recursive_json.append("{\"a\":"); + } + recursive_json.append("1"); + for (int i = 0; i < 1000000; i++) { + recursive_json.append("}"); + } + util::StatusOr<std::unique_ptr<KeysetReader>> reader = + JsonKeysetReader::New(recursive_json); + ASSERT_THAT(reader, IsOk()); + util::StatusOr<std::unique_ptr<Keyset>> keyset = (*reader)->Read(); + EXPECT_THAT(keyset, Not(IsOk())); +} + } // namespace } // namespace tink } // namespace crypto
diff --git a/cc/core/keyset_handle_builder_test.cc b/cc/core/keyset_handle_builder_test.cc index 2e0de9d..88be9bb 100644 --- a/cc/core/keyset_handle_builder_test.cc +++ b/cc/core/keyset_handle_builder_test.cc
@@ -617,6 +617,45 @@ ASSERT_THAT(handle.status(), IsOk()); } +TEST_F(KeysetHandleBuilderTest, + MergeTwoKeysetsWithTheSameIdButNoIdRequirementWorks) { + util::StatusOr<AesCmacParameters> params = AesCmacParameters::Create( + /*key_size_in_bytes=*/32, /*cryptographic_tag_size_in_bytes=*/16, + AesCmacParameters::Variant::kNoPrefix); + ASSERT_THAT(params, IsOk()); + + KeysetHandleBuilder::Entry entry1 = + KeysetHandleBuilder::Entry::CreateFromParams( + absl::make_unique<AesCmacParameters>(std::move(*params)), + KeyStatus::kEnabled, /*is_primary=*/true); + entry1.SetFixedId(123); + util::StatusOr<KeysetHandle> handle1 = + KeysetHandleBuilder().AddEntry(std::move(entry1)).Build(); + ASSERT_THAT(handle1.status(), IsOk()); + + KeysetHandleBuilder::Entry entry2 = + KeysetHandleBuilder::Entry::CreateFromParams( + absl::make_unique<AesCmacParameters>(std::move(*params)), + KeyStatus::kEnabled, /*is_primary=*/true); + entry2.SetFixedId(123); + util::StatusOr<KeysetHandle> handle2 = + KeysetHandleBuilder().AddEntry(std::move(entry2)).Build(); + ASSERT_THAT(handle2.status(), IsOk()); + + // handle1 and handle2 each contain one key with the same ID, but no ID + // requirement. We can add them to a new keyset because they will get new, + // random and distinct IDs. + util::StatusOr<KeysetHandle> handle12 = + KeysetHandleBuilder() + .AddEntry(KeysetHandleBuilder::Entry::CreateFromKey( + (*handle1)[0].GetKey(), KeyStatus::kEnabled, /*is_primary=*/true)) + .AddEntry(KeysetHandleBuilder::Entry::CreateFromKey( + (*handle2)[0].GetKey(), KeyStatus::kEnabled, + /*is_primary=*/false)) + .Build(); + ASSERT_THAT(handle12.status(), IsOk()); +} + TEST_F(KeysetHandleBuilderTest, CreateBuilderEntryFromCopyableKey) { Keyset keyset; Keyset::Key key;
diff --git a/cc/experimental/pqcrypto/signature/slh_dsa_parameters.cc b/cc/experimental/pqcrypto/signature/slh_dsa_parameters.cc new file mode 100644 index 0000000..b859681 --- /dev/null +++ b/cc/experimental/pqcrypto/signature/slh_dsa_parameters.cc
@@ -0,0 +1,71 @@ +// 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_parameters.h" + +#include "absl/status/status.h" +#include "tink/parameters.h" +#include "tink/util/status.h" +#include "tink/util/statusor.h" + +namespace crypto { +namespace tink { + +util::StatusOr<SlhDsaParameters> SlhDsaParameters::Create( + HashType hash_type, int private_key_size_in_bytes, + SignatureType signature_type, Variant variant) { + // Validate HashType - only SHA2 is currently supported. + if (hash_type != HashType::kSha2) { + return util::Status( + absl::StatusCode::kInvalidArgument, + "Cannot create Slh-DSA parameters with unknown HashType."); + } + + if (private_key_size_in_bytes != 64) { + return util::Status(absl::StatusCode::kInvalidArgument, + "Invalid private key size. Only 64-bytes keys are " + "currently supported."); + } + + // Validate SignatureType - only SmallSignature is currently supported. + if (signature_type != SignatureType::kSmallSignature) { + return util::Status( + absl::StatusCode::kInvalidArgument, + "Cannot create SLH-DSA parameters with unknown SignatureType."); + } + + // Validate Variant. + if (variant != Variant::kTink && variant != Variant::kNoPrefix) { + return util::Status( + absl::StatusCode::kInvalidArgument, + "Cannot create SLH-DSA parameters with unknown Variant."); + } + return SlhDsaParameters(hash_type, private_key_size_in_bytes, signature_type, + variant); +} + +bool SlhDsaParameters::operator==(const Parameters& other) const { + const SlhDsaParameters* that = dynamic_cast<const SlhDsaParameters*>(&other); + if (that == nullptr) { + return false; + } + return hash_type_ == that->hash_type_ && + private_key_size_in_bytes_ == that->private_key_size_in_bytes_ && + signature_type_ == that->signature_type_ && variant_ == that->variant_; +} + +} // namespace tink +} // namespace crypto
diff --git a/cc/experimental/pqcrypto/signature/slh_dsa_parameters.h b/cc/experimental/pqcrypto/signature/slh_dsa_parameters.h new file mode 100644 index 0000000..1134f43 --- /dev/null +++ b/cc/experimental/pqcrypto/signature/slh_dsa_parameters.h
@@ -0,0 +1,102 @@ +// 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_PARAMETERS_H_ +#define TINK_EXPERIMENTAL_PQCRYPTO_SIGNATURE_SLH_DSA_PARAMETERS_H_ + +#include "tink/parameters.h" +#include "tink/signature/signature_parameters.h" +#include "tink/util/statusor.h" + +namespace crypto { +namespace tink { + +// Representation of the parameters sets for the Stateless Hash-Based Digital +// Signature Standard (SLH-DSA) described at +// https://csrc.nist.gov/pubs/fips/205/ipd. +// +// Note that only the SLH-DSA-SHA2-128s set is currently supported. +class SlhDsaParameters : public SignatureParameters { + public: + // Describes the output prefix prepended to the signature. + enum class Variant : int { + // Prepends '0x01<big endian key id>' to signature. + kTink = 1, + // Does not prepend any prefix (i.e., keys must have no ID requirement). + kNoPrefix = 2, + // Added to guard from failures that may be caused by future expansions. + kDoNotUseInsteadUseDefaultWhenWritingSwitchStatements = 20, + }; + + // Description of the hash function used for this algorithm. + enum class HashType : int { + // The 128-bit security level variant uses SHA256. The 192-bit and 256-bit + // variants require both SHA-256 and SHA-512 in their implementation. + kSha2 = 1, + kShake = 2, + kDoNotUseInsteadUseDefaultWhenWritingSwitchStatements = 20, + }; + + // Description of the signature type. kFastSigning parameters sets + // have significantly faster signing, but kSmallSignature come with faster + // verification and smaller signatures. + enum class SignatureType : int { + kFastSigning = 1, + kSmallSignature = 2, + kDoNotUseInsteadUseDefaultWhenWritingSwitchStatements = 20, + }; + + // Copyable and movable. + SlhDsaParameters(const SlhDsaParameters& other) = default; + SlhDsaParameters& operator=(const SlhDsaParameters& other) = default; + SlhDsaParameters(SlhDsaParameters&& other) = default; + SlhDsaParameters& operator=(SlhDsaParameters&& other) = default; + + // Creates SLH-DSA parameters instances. + static util::StatusOr<SlhDsaParameters> Create(HashType hash_type, + int private_key_size_in_bytes, + SignatureType signature_type, + Variant variant); + + HashType GetHashType() const { return hash_type_; } + int GetPrivateKeySizeInBytes() const { return private_key_size_in_bytes_; } + SignatureType GetSignatureType() const { return signature_type_; } + Variant GetVariant() const { return variant_; } + + bool HasIdRequirement() const override { + return variant_ != Variant::kNoPrefix; + } + + bool operator==(const Parameters& other) const override; + + private: + explicit SlhDsaParameters(HashType hash_type, int private_key_size_in_bytes, + SignatureType signature_type, Variant variant) + : hash_type_(hash_type), + private_key_size_in_bytes_(private_key_size_in_bytes), + signature_type_(signature_type), + variant_(variant) {} + + HashType hash_type_; + int private_key_size_in_bytes_; + SignatureType signature_type_; + Variant variant_; +}; + +} // namespace tink +} // namespace crypto + +#endif // TINK_EXPERIMENTAL_PQCRYPTO_SIGNATURE_SLH_DSA_PARAMETERS_H_
diff --git a/cc/experimental/pqcrypto/signature/slh_dsa_parameters_test.cc b/cc/experimental/pqcrypto/signature/slh_dsa_parameters_test.cc new file mode 100644 index 0000000..b265c0e --- /dev/null +++ b/cc/experimental/pqcrypto/signature/slh_dsa_parameters_test.cc
@@ -0,0 +1,211 @@ +// 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_parameters.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/status/status.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::IsTrue; +using ::testing::TestWithParam; +using ::testing::Values; + +struct VariantTestCase { + SlhDsaParameters::Variant variant; + bool has_id_requirement; +}; + +using SlhDsaParametersTest = TestWithParam<VariantTestCase>; + +INSTANTIATE_TEST_SUITE_P( + SlhDsaParametersTestSuite, SlhDsaParametersTest, + Values(VariantTestCase{SlhDsaParameters::Variant::kTink, + /*has_id_requirement=*/true}, + VariantTestCase{SlhDsaParameters::Variant::kNoPrefix, + /*has_id_requirement=*/false})); + +TEST_P(SlhDsaParametersTest, CreateSlhDsa128Sha2SmallSignatureWorks) { + VariantTestCase test_case = GetParam(); + + util::StatusOr<SlhDsaParameters> parameters = SlhDsaParameters::Create( + SlhDsaParameters::HashType::kSha2, /*private_key_size_in_bytes=*/64, + SlhDsaParameters::SignatureType::kSmallSignature, test_case.variant); + ASSERT_THAT(parameters, IsOk()); + + EXPECT_THAT(parameters->GetHashType(), Eq(SlhDsaParameters::HashType::kSha2)); + EXPECT_THAT(parameters->GetPrivateKeySizeInBytes(), Eq(64)); + EXPECT_THAT(parameters->GetSignatureType(), + Eq(SlhDsaParameters::SignatureType::kSmallSignature)); + EXPECT_THAT(parameters->GetVariant(), Eq(test_case.variant)); + EXPECT_THAT(parameters->HasIdRequirement(), Eq(test_case.has_id_requirement)); +} + +TEST(SlhDsaParametersTest, CreateWithInvalidVariantFails) { + EXPECT_THAT( + SlhDsaParameters::Create( + SlhDsaParameters::HashType::kSha2, /*private_key_size_in_bytes=*/64, + SlhDsaParameters::SignatureType::kSmallSignature, + SlhDsaParameters::Variant:: + kDoNotUseInsteadUseDefaultWhenWritingSwitchStatements) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(SlhDsaParametersTest, CreateWithInvalidHashTypeFails) { + EXPECT_THAT(SlhDsaParameters::Create( + SlhDsaParameters::HashType:: + kDoNotUseInsteadUseDefaultWhenWritingSwitchStatements, + /*private_key_size_in_bytes=*/64, + SlhDsaParameters::SignatureType::kSmallSignature, + SlhDsaParameters::Variant::kTink) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(SlhDsaParametersTest, CreateWithUnsupportedHashTypeFails) { + EXPECT_THAT( + SlhDsaParameters::Create(SlhDsaParameters::HashType::kShake, + /*private_key_size_in_bytes=*/64, + SlhDsaParameters::SignatureType::kSmallSignature, + SlhDsaParameters::Variant::kTink) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(SlhDsaParametersTest, CreateWithInvalidSignatureTypeFails) { + EXPECT_THAT(SlhDsaParameters::Create( + SlhDsaParameters::HashType::kSha2, + /*private_key_size_in_bytes=*/64, + SlhDsaParameters::SignatureType:: + kDoNotUseInsteadUseDefaultWhenWritingSwitchStatements, + SlhDsaParameters::Variant::kTink) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(SlhDsaParametersTest, CreateWithUnsupportedSignatureTypeFails) { + EXPECT_THAT( + SlhDsaParameters::Create(SlhDsaParameters::HashType::kSha2, + /*private_key_size_in_bytes=*/64, + SlhDsaParameters::SignatureType::kFastSigning, + SlhDsaParameters::Variant::kTink) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(SlhDsaParametersTest, CreateWithInvalidKeySizeFails) { + EXPECT_THAT( + SlhDsaParameters::Create(SlhDsaParameters::HashType::kSha2, + /*private_key_size_in_bytes=*/31, + SlhDsaParameters::SignatureType::kSmallSignature, + SlhDsaParameters::Variant::kTink) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(SlhDsaParametersTest, CreateWithUnsupportedKeySizeFails) { + EXPECT_THAT( + SlhDsaParameters::Create(SlhDsaParameters::HashType::kSha2, + /*private_key_size_in_bytes=*/128, + SlhDsaParameters::SignatureType::kSmallSignature, + SlhDsaParameters::Variant::kNoPrefix) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(SlhDsaParametersTest, CopyConstructor) { + util::StatusOr<SlhDsaParameters> parameters = + SlhDsaParameters::Create(SlhDsaParameters::HashType::kSha2, + /*private_key_size_in_bytes=*/64, + SlhDsaParameters::SignatureType::kSmallSignature, + SlhDsaParameters::Variant::kTink); + ASSERT_THAT(parameters, IsOk()); + + SlhDsaParameters copy(*parameters); + + EXPECT_THAT(copy.GetHashType(), Eq(SlhDsaParameters::HashType::kSha2)); + EXPECT_THAT(copy.GetPrivateKeySizeInBytes(), Eq(64)); + EXPECT_THAT(copy.GetSignatureType(), + Eq(SlhDsaParameters::SignatureType::kSmallSignature)); + EXPECT_THAT(copy.GetVariant(), Eq(SlhDsaParameters::Variant::kTink)); + EXPECT_THAT(copy.HasIdRequirement(), IsTrue()); +} + +TEST(SlhDsaParametersTest, CopyAssignment) { + util::StatusOr<SlhDsaParameters> parameters = + SlhDsaParameters::Create(SlhDsaParameters::HashType::kSha2, + /*private_key_size_in_bytes=*/64, + SlhDsaParameters::SignatureType::kSmallSignature, + SlhDsaParameters::Variant::kTink); + ASSERT_THAT(parameters, IsOk()); + + SlhDsaParameters copy = *parameters; + EXPECT_THAT(copy.GetHashType(), Eq(SlhDsaParameters::HashType::kSha2)); + EXPECT_THAT(copy.GetPrivateKeySizeInBytes(), Eq(64)); + EXPECT_THAT(copy.GetSignatureType(), + Eq(SlhDsaParameters::SignatureType::kSmallSignature)); + EXPECT_THAT(copy.GetVariant(), Eq(SlhDsaParameters::Variant::kTink)); + EXPECT_THAT(copy.HasIdRequirement(), IsTrue()); +} + +TEST_P(SlhDsaParametersTest, ParametersEquals) { + VariantTestCase test_case = GetParam(); + + util::StatusOr<SlhDsaParameters> parameters = SlhDsaParameters::Create( + SlhDsaParameters::HashType::kSha2, /*private_key_size_in_bytes=*/64, + SlhDsaParameters::SignatureType::kSmallSignature, test_case.variant); + ASSERT_THAT(parameters, IsOk()); + + util::StatusOr<SlhDsaParameters> other_parameters = SlhDsaParameters::Create( + SlhDsaParameters::HashType::kSha2, /*private_key_size_in_bytes=*/64, + SlhDsaParameters::SignatureType::kSmallSignature, test_case.variant); + ASSERT_THAT(other_parameters, IsOk()); + + EXPECT_TRUE(*parameters == *other_parameters); + EXPECT_TRUE(*other_parameters == *parameters); + EXPECT_FALSE(*parameters != *other_parameters); + EXPECT_FALSE(*other_parameters != *parameters); +} + +TEST(SlhDsaParametersTest, DifferentVariantNotEqual) { + util::StatusOr<SlhDsaParameters> parameters = SlhDsaParameters::Create( + SlhDsaParameters::HashType::kSha2, /*private_key_size_in_bytes=*/64, + SlhDsaParameters::SignatureType::kSmallSignature, + SlhDsaParameters::Variant::kNoPrefix); + + util::StatusOr<SlhDsaParameters> other_parameters = + SlhDsaParameters::Create(SlhDsaParameters::HashType::kSha2, + /*private_key_size_in_bytes=*/64, + SlhDsaParameters::SignatureType::kSmallSignature, + SlhDsaParameters::Variant::kTink); + + EXPECT_TRUE(*parameters != *other_parameters); + EXPECT_FALSE(*parameters == *other_parameters); +} + +} // namespace +} // namespace tink +} // namespace crypto
diff --git a/cc/experimental/pqcrypto/signature/slh_dsa_private_key.cc b/cc/experimental/pqcrypto/signature/slh_dsa_private_key.cc new file mode 100644 index 0000000..e92889a --- /dev/null +++ b/cc/experimental/pqcrypto/signature/slh_dsa_private_key.cc
@@ -0,0 +1,92 @@ +// 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_private_key.h" + +#include <cstdint> +#include <string> + +#include "absl/status/status.h" +#include "absl/strings/string_view.h" +#include "openssl/boringssl/src/include/openssl/mem.h" +#define OPENSSL_UNSTABLE_EXPERIMENTAL_SPX +#include "openssl/experimental/spx.h" +#undef OPENSSL_UNSTABLE_EXPERIMENTAL_SPX +#include "tink/experimental/pqcrypto/signature/slh_dsa_public_key.h" +#include "tink/insecure_secret_key_access.h" +#include "tink/key.h" +#include "tink/partial_key_access_token.h" +#include "tink/restricted_data.h" +#include "tink/util/status.h" +#include "tink/util/statusor.h" + +namespace crypto { +namespace tink { + +util::StatusOr<SlhDsaPrivateKey> SlhDsaPrivateKey::Create( + const SlhDsaPublicKey& public_key, const RestrictedData& private_key_bytes, + PartialKeyAccessToken token) { + // Only 64-byte private keys are currently supported. + if (private_key_bytes.size() != SPX_SECRET_KEY_BYTES) { + return util::Status(absl::StatusCode::kInvalidArgument, + "SLH-DSA private key length must be 64 bytes."); + } + + if (public_key.GetParameters().GetPrivateKeySizeInBytes() != + private_key_bytes.size()) { + return util::Status(absl::StatusCode::kInvalidArgument, + "Private key size does not match parameters"); + } + // Confirm that the private key and public key are a valid SLH-DSA key pair. + std::string public_key_bytes_regen; + public_key_bytes_regen.resize(SPX_PUBLIC_KEY_BYTES); + std::string private_key_bytes_regen; + private_key_bytes_regen.resize(SPX_SECRET_KEY_BYTES); + + absl::string_view expected_private_key_bytes = + private_key_bytes.GetSecret(InsecureSecretKeyAccess::Get()); + SPX_generate_key_from_seed( + reinterpret_cast<uint8_t*>(public_key_bytes_regen.data()), + reinterpret_cast<uint8_t*>(private_key_bytes_regen.data()), + // Uses the first 48 bytes of the private key as seed. + reinterpret_cast<const uint8_t*>(expected_private_key_bytes.data())); + + absl::string_view expected_public_key_bytes = + public_key.GetPublicKeyBytes(token); + + if (CRYPTO_memcmp(expected_public_key_bytes.data(), + public_key_bytes_regen.data(), SPX_PUBLIC_KEY_BYTES) != 0 || + CRYPTO_memcmp(expected_private_key_bytes.data(), + private_key_bytes_regen.data(), + SPX_SECRET_KEY_BYTES) != 0) { + return util::Status(absl::StatusCode::kInvalidArgument, + "Invalid SLH-DSA key pair"); + } + + return SlhDsaPrivateKey(public_key, private_key_bytes); +} + +bool SlhDsaPrivateKey::operator==(const Key& other) const { + const SlhDsaPrivateKey* that = dynamic_cast<const SlhDsaPrivateKey*>(&other); + if (that == nullptr) { + return false; + } + return public_key_ == that->public_key_ && + private_key_bytes_ == that->private_key_bytes_; +} + +} // namespace tink +} // namespace crypto
diff --git a/cc/experimental/pqcrypto/signature/slh_dsa_private_key.h b/cc/experimental/pqcrypto/signature/slh_dsa_private_key.h new file mode 100644 index 0000000..4e5b37c --- /dev/null +++ b/cc/experimental/pqcrypto/signature/slh_dsa_private_key.h
@@ -0,0 +1,65 @@ +// 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_PRIVATE_KEY_H_ +#define TINK_EXPERIMENTAL_PQCRYPTO_SIGNATURE_SLH_DSA_PRIVATE_KEY_H_ + +#include "tink/key.h" +#include "tink/partial_key_access_token.h" +#include "tink/restricted_data.h" +#include "tink/experimental/pqcrypto/signature/slh_dsa_public_key.h" +#include "tink/signature/signature_private_key.h" +#include "tink/util/statusor.h" + +namespace crypto { +namespace tink { + +class SlhDsaPrivateKey : public SignaturePrivateKey { + public: + // Copyable and movable. + SlhDsaPrivateKey(const SlhDsaPrivateKey& other) = default; + SlhDsaPrivateKey& operator=(const SlhDsaPrivateKey& other) = default; + SlhDsaPrivateKey(SlhDsaPrivateKey&& other) = default; + SlhDsaPrivateKey& operator=(SlhDsaPrivateKey&& other) = default; + + // Creates a new SLH-DSA 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<SlhDsaPrivateKey> Create( + const SlhDsaPublicKey& public_key, + const RestrictedData& private_key_bytes, PartialKeyAccessToken token); + + const RestrictedData& GetPrivateKeyBytes(PartialKeyAccessToken token) const { + return private_key_bytes_; + } + + const SlhDsaPublicKey& GetPublicKey() const override { return public_key_; } + + bool operator==(const Key& other) const override; + + private: + explicit SlhDsaPrivateKey(const SlhDsaPublicKey& public_key, + const RestrictedData& private_key_bytes) + : public_key_(public_key), private_key_bytes_(private_key_bytes) {} + + SlhDsaPublicKey public_key_; + RestrictedData private_key_bytes_; +}; + +} // namespace tink +} // namespace crypto + +#endif // TINK_EXPERIMENTAL_PQCRYPTO_SIGNATURE_SLH_DSA_PRIVATE_KEY_H_
diff --git a/cc/experimental/pqcrypto/signature/slh_dsa_private_key_test.cc b/cc/experimental/pqcrypto/signature/slh_dsa_private_key_test.cc new file mode 100644 index 0000000..cea2641 --- /dev/null +++ b/cc/experimental/pqcrypto/signature/slh_dsa_private_key_test.cc
@@ -0,0 +1,279 @@ +// 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_private_key.h" + +#include <cstdint> +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/status/status.h" +#include "absl/types/optional.h" +#define OPENSSL_UNSTABLE_EXPERIMENTAL_SPX +#include "openssl/experimental/spx.h" +#undef OPENSSL_UNSTABLE_EXPERIMENTAL_SPX +#include "tink/experimental/pqcrypto/signature/slh_dsa_parameters.h" +#include "tink/experimental/pqcrypto/signature/slh_dsa_public_key.h" +#include "tink/insecure_secret_key_access.h" +#include "tink/partial_key_access.h" +#include "tink/restricted_data.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<int> id_requirement; + std::string output_prefix; +}; + +using SlhDsaPrivateKeyTest = TestWithParam<TestCase>; + +INSTANTIATE_TEST_SUITE_P( + SlhDsaPrivateKeyTestSuite, SlhDsaPrivateKeyTest, + 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(SlhDsaPrivateKeyTest, CreateSucceeds) { + TestCase test_case = GetParam(); + + util::StatusOr<SlhDsaParameters> parameters = SlhDsaParameters::Create( + SlhDsaParameters::HashType::kSha2, + /*private_key_size_in_bytes=*/SPX_SECRET_KEY_BYTES, + SlhDsaParameters::SignatureType::kSmallSignature, test_case.variant); + ASSERT_THAT(parameters, IsOk()); + + std::string public_key_bytes; + public_key_bytes.resize(SPX_PUBLIC_KEY_BYTES); + std::string private_key_bytes; + private_key_bytes.resize(SPX_SECRET_KEY_BYTES); + + SPX_generate_key(reinterpret_cast<uint8_t *>(public_key_bytes.data()), + reinterpret_cast<uint8_t *>(private_key_bytes.data())); + + util::StatusOr<SlhDsaPublicKey> public_key = + SlhDsaPublicKey::Create(*parameters, public_key_bytes, + test_case.id_requirement, GetPartialKeyAccess()); + ASSERT_THAT(public_key, IsOk()); + + RestrictedData restricted_private_key_bytes = + RestrictedData(private_key_bytes, InsecureSecretKeyAccess::Get()); + util::StatusOr<SlhDsaPrivateKey> private_key = SlhDsaPrivateKey::Create( + *public_key, restricted_private_key_bytes, GetPartialKeyAccess()); + ASSERT_THAT(private_key, IsOk()); + + EXPECT_THAT(private_key->GetParameters(), Eq(*parameters)); + 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(restricted_private_key_bytes)); +} + +TEST(SlhDsaPrivateKeyTest, CreateWithInvalidPrivateKeyLengthFails) { + util::StatusOr<SlhDsaParameters> parameters = SlhDsaParameters::Create( + SlhDsaParameters::HashType::kSha2, + /*private_key_size_in_bytes=*/SPX_SECRET_KEY_BYTES, + SlhDsaParameters::SignatureType::kSmallSignature, + SlhDsaParameters::Variant::kTink); + ASSERT_THAT(parameters, IsOk()); + + util::StatusOr<SlhDsaPublicKey> public_key = SlhDsaPublicKey::Create( + *parameters, subtle::Random::GetRandomBytes(SPX_PUBLIC_KEY_BYTES), + /*id_requirement=*/123, GetPartialKeyAccess()); + ASSERT_THAT(public_key, IsOk()); + + RestrictedData restricted_private_key_bytes = RestrictedData( + subtle::Random::GetRandomBytes(63), InsecureSecretKeyAccess::Get()); + EXPECT_THAT( + SlhDsaPrivateKey::Create(*public_key, restricted_private_key_bytes, + GetPartialKeyAccess()) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("SLH-DSA private key length must be " + "64 bytes"))); +} + +TEST(SlhDsaPrivateKeyTest, CreateWithMismatchedPairFails) { + util::StatusOr<SlhDsaParameters> parameters = SlhDsaParameters::Create( + SlhDsaParameters::HashType::kSha2, + /*private_key_size_in_bytes=*/SPX_SECRET_KEY_BYTES, + SlhDsaParameters::SignatureType::kSmallSignature, + SlhDsaParameters::Variant::kTink); + ASSERT_THAT(parameters, IsOk()); + + std::string public_key_bytes; + public_key_bytes.resize(SPX_PUBLIC_KEY_BYTES); + std::string private_key_bytes; + private_key_bytes.resize(SPX_SECRET_KEY_BYTES); + + SPX_generate_key(reinterpret_cast<uint8_t *>(public_key_bytes.data()), + reinterpret_cast<uint8_t *>(private_key_bytes.data())); + + util::StatusOr<SlhDsaPublicKey> public_key = + SlhDsaPublicKey::Create(*parameters, public_key_bytes, + /*id_requirement=*/123, GetPartialKeyAccess()); + ASSERT_THAT(public_key, IsOk()); + + // Generate a new key pair. + SPX_generate_key(reinterpret_cast<uint8_t *>(public_key_bytes.data()), + reinterpret_cast<uint8_t *>(private_key_bytes.data())); + RestrictedData restricted_private_key_bytes = + RestrictedData(private_key_bytes, InsecureSecretKeyAccess::Get()); + + // Creating the private key using the different private_key_bytes should fail. + EXPECT_THAT( + SlhDsaPrivateKey::Create(*public_key, restricted_private_key_bytes, + GetPartialKeyAccess()) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Invalid SLH-DSA key pair"))); +} + +TEST(SlhDsaPrivateKeyTest, CreateWithModifiedPrivateKeyFails) { + util::StatusOr<SlhDsaParameters> parameters = SlhDsaParameters::Create( + SlhDsaParameters::HashType::kSha2, + /*private_key_size_in_bytes=*/SPX_SECRET_KEY_BYTES, + SlhDsaParameters::SignatureType::kSmallSignature, + SlhDsaParameters::Variant::kTink); + ASSERT_THAT(parameters, IsOk()); + + std::string public_key_bytes; + public_key_bytes.resize(SPX_PUBLIC_KEY_BYTES); + std::string private_key_bytes; + private_key_bytes.resize(SPX_SECRET_KEY_BYTES); + + SPX_generate_key(reinterpret_cast<uint8_t *>(public_key_bytes.data()), + reinterpret_cast<uint8_t *>(private_key_bytes.data())); + + util::StatusOr<SlhDsaPublicKey> public_key = + SlhDsaPublicKey::Create(*parameters, public_key_bytes, + /*id_requirement=*/123, GetPartialKeyAccess()); + ASSERT_THAT(public_key, IsOk()); + + // Replace last 16 bytes of the private key bytes with random bytes. + private_key_bytes.replace(/*seed_size=*/48, /*pk_root_size=*/16, + subtle::Random::GetRandomBytes(16)); + RestrictedData restricted_private_key_bytes = + RestrictedData(private_key_bytes, InsecureSecretKeyAccess::Get()); + + EXPECT_THAT( + SlhDsaPrivateKey::Create(*public_key, restricted_private_key_bytes, + GetPartialKeyAccess()) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Invalid SLH-DSA key pair"))); +} + +TEST_P(SlhDsaPrivateKeyTest, KeyEquals) { + TestCase test_case = GetParam(); + + util::StatusOr<SlhDsaParameters> parameters = SlhDsaParameters::Create( + SlhDsaParameters::HashType::kSha2, + /*private_key_size_in_bytes=*/SPX_SECRET_KEY_BYTES, + SlhDsaParameters::SignatureType::kSmallSignature, test_case.variant); + ASSERT_THAT(parameters, IsOk()); + + std::string public_key_bytes; + public_key_bytes.resize(SPX_PUBLIC_KEY_BYTES); + std::string private_key_bytes; + private_key_bytes.resize(SPX_SECRET_KEY_BYTES); + + SPX_generate_key(reinterpret_cast<uint8_t *>(public_key_bytes.data()), + reinterpret_cast<uint8_t *>(private_key_bytes.data())); + + util::StatusOr<SlhDsaPublicKey> public_key = + SlhDsaPublicKey::Create(*parameters, public_key_bytes, + test_case.id_requirement, GetPartialKeyAccess()); + ASSERT_THAT(public_key, IsOk()); + + RestrictedData restricted_private_key_bytes = + RestrictedData(private_key_bytes, InsecureSecretKeyAccess::Get()); + util::StatusOr<SlhDsaPrivateKey> private_key = SlhDsaPrivateKey::Create( + *public_key, restricted_private_key_bytes, GetPartialKeyAccess()); + ASSERT_THAT(private_key, IsOk()); + + util::StatusOr<SlhDsaPrivateKey> other_private_key = SlhDsaPrivateKey::Create( + *public_key, restricted_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(SlhDsaPrivateKeyTest, DifferentPublicKeyNotEqual) { + util::StatusOr<SlhDsaParameters> parameters = SlhDsaParameters::Create( + SlhDsaParameters::HashType::kSha2, + /*private_key_size_in_bytes=*/SPX_SECRET_KEY_BYTES, + SlhDsaParameters::SignatureType::kSmallSignature, + SlhDsaParameters::Variant::kTink); + ASSERT_THAT(parameters, IsOk()); + + std::string public_key_bytes; + public_key_bytes.resize(SPX_PUBLIC_KEY_BYTES); + std::string private_key_bytes; + private_key_bytes.resize(parameters->GetPrivateKeySizeInBytes()); + + SPX_generate_key(reinterpret_cast<uint8_t *>(public_key_bytes.data()), + reinterpret_cast<uint8_t *>(private_key_bytes.data())); + + util::StatusOr<SlhDsaPublicKey> public_key123 = + SlhDsaPublicKey::Create(*parameters, public_key_bytes, + /*id_requirement=*/123, GetPartialKeyAccess()); + ASSERT_THAT(public_key123, IsOk()); + + util::StatusOr<SlhDsaPublicKey> public_key456 = + SlhDsaPublicKey::Create(*parameters, public_key_bytes, + /*id_requirement=*/456, GetPartialKeyAccess()); + ASSERT_THAT(public_key456, IsOk()); + + RestrictedData restricted_private_key_bytes = + RestrictedData(private_key_bytes, InsecureSecretKeyAccess::Get()); + + util::StatusOr<SlhDsaPrivateKey> private_key = SlhDsaPrivateKey::Create( + *public_key123, restricted_private_key_bytes, GetPartialKeyAccess()); + ASSERT_THAT(private_key, IsOk()); + + util::StatusOr<SlhDsaPrivateKey> other_private_key = SlhDsaPrivateKey::Create( + *public_key456, restricted_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
diff --git a/cc/experimental/pqcrypto/signature/slh_dsa_proto_serialization.cc b/cc/experimental/pqcrypto/signature/slh_dsa_proto_serialization.cc new file mode 100644 index 0000000..57931f8 --- /dev/null +++ b/cc/experimental/pqcrypto/signature/slh_dsa_proto_serialization.cc
@@ -0,0 +1,462 @@ +// 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_proto_serialization.h" + +#include "absl/status/status.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "tink/experimental/pqcrypto/signature/slh_dsa_parameters.h" +#include "tink/experimental/pqcrypto/signature/slh_dsa_private_key.h" +#include "tink/experimental/pqcrypto/signature/slh_dsa_public_key.h" +#include "tink/insecure_secret_key_access.h" +#include "tink/internal/key_parser.h" +#include "tink/internal/key_serializer.h" +#include "tink/internal/mutable_serialization_registry.h" +#include "tink/internal/parameters_parser.h" +#include "tink/internal/parameters_serializer.h" +#include "tink/internal/proto_key_serialization.h" +#include "tink/internal/proto_parameters_serialization.h" +#include "tink/partial_key_access.h" +#include "tink/restricted_data.h" +#include "tink/secret_key_access_token.h" +#include "tink/util/status.h" +#include "tink/util/statusor.h" +#include "proto/experimental/pqcrypto/slh_dsa.pb.h" +#include "proto/tink.pb.h" + +namespace crypto { +namespace tink { +namespace { + +using ::google::crypto::tink::KeyData; +using ::google::crypto::tink::OutputPrefixType; +using ::google::crypto::tink::SlhDsaHashType; +using ::google::crypto::tink::SlhDsaKeyFormat; +using ::google::crypto::tink::SlhDsaParams; +using ::google::crypto::tink::SlhDsaSignatureType; + +using SlhDsaProtoParametersParserImpl = + internal::ParametersParserImpl<internal::ProtoParametersSerialization, + SlhDsaParameters>; +using SlhDsaProtoParametersSerializerImpl = + internal::ParametersSerializerImpl<SlhDsaParameters, + internal::ProtoParametersSerialization>; +using SlhDsaProtoPublicKeyParserImpl = + internal::KeyParserImpl<internal::ProtoKeySerialization, SlhDsaPublicKey>; +using SlhDsaProtoPublicKeySerializerImpl = + internal::KeySerializerImpl<SlhDsaPublicKey, + internal::ProtoKeySerialization>; +using SlhDsaProtoPrivateKeyParserImpl = + internal::KeyParserImpl<internal::ProtoKeySerialization, SlhDsaPrivateKey>; +using SlhDsaProtoPrivateKeySerializerImpl = + internal::KeySerializerImpl<SlhDsaPrivateKey, + internal::ProtoKeySerialization>; + +const absl::string_view kPrivateTypeUrl = + "type.googleapis.com/google.crypto.tink.SlhDsaPrivateKey"; +const absl::string_view kPublicTypeUrl = + "type.googleapis.com/google.crypto.tink.SlhDsaPublicKey"; + +util::StatusOr<SlhDsaParameters::Variant> ToVariant( + OutputPrefixType output_prefix_type) { + switch (output_prefix_type) { + case OutputPrefixType::RAW: + return SlhDsaParameters::Variant::kNoPrefix; + case OutputPrefixType::TINK: + return SlhDsaParameters::Variant::kTink; + default: + return util::Status(absl::StatusCode::kInvalidArgument, + "Could not determine SlhDsaParameters::Variant"); + } +} + +util::StatusOr<OutputPrefixType> ToOutputPrefixType( + SlhDsaParameters::Variant variant) { + switch (variant) { + case SlhDsaParameters::Variant::kNoPrefix: + return OutputPrefixType::RAW; + case SlhDsaParameters::Variant::kTink: + return OutputPrefixType::TINK; + default: + return util::Status(absl::StatusCode::kInvalidArgument, + "Could not determine output prefix type"); + } +} + +util::StatusOr<SlhDsaParameters::HashType> ToHashType( + SlhDsaHashType proto_hash_type) { + switch (proto_hash_type) { + case SlhDsaHashType::SHA2: + return SlhDsaParameters::HashType::kSha2; + case SlhDsaHashType::SHAKE: + return SlhDsaParameters::HashType::kShake; + default: + return util::Status(absl::StatusCode::kInvalidArgument, + "Could not determine SlhDsaParameters::HashType"); + } +} + +util::StatusOr<SlhDsaHashType> ToProtoHashType( + SlhDsaParameters::HashType hash_type) { + switch (hash_type) { + case SlhDsaParameters::HashType::kSha2: + return SlhDsaHashType::SHA2; + case SlhDsaParameters::HashType::kShake: + return SlhDsaHashType::SHAKE; + default: + return util::Status(absl::StatusCode::kInvalidArgument, + "Could not determine SlhDsaHashType"); + } +} + +util::StatusOr<SlhDsaParameters::SignatureType> ToSignatureType( + SlhDsaSignatureType proto_signature_type) { + switch (proto_signature_type) { + case SlhDsaSignatureType::FAST_SIGNING: + return SlhDsaParameters::SignatureType::kFastSigning; + case SlhDsaSignatureType::SMALL_SIGNATURE: + return SlhDsaParameters::SignatureType::kSmallSignature; + default: + return util::Status( + absl::StatusCode::kInvalidArgument, + "Could not determine SlhDsaParameters::SignatureType"); + } +} + +util::StatusOr<SlhDsaSignatureType> ToProtoSignatureType( + SlhDsaParameters::SignatureType signature_type) { + switch (signature_type) { + case SlhDsaParameters::SignatureType::kFastSigning: + return SlhDsaSignatureType::FAST_SIGNING; + case SlhDsaParameters::SignatureType::kSmallSignature: + return SlhDsaSignatureType::SMALL_SIGNATURE; + default: + return util::Status(absl::StatusCode::kInvalidArgument, + "Could not determine SlhDsaSignatureType"); + } +} + +util::StatusOr<SlhDsaParameters> ToParameters( + OutputPrefixType output_prefix_type, const SlhDsaParams& params) { + util::StatusOr<SlhDsaParameters::Variant> variant = + ToVariant(output_prefix_type); + if (!variant.ok()) { + return variant.status(); + } + + util::StatusOr<SlhDsaParameters::HashType> hash_type = + ToHashType(params.hash_type()); + if (!hash_type.ok()) { + return hash_type.status(); + } + + util::StatusOr<SlhDsaParameters::SignatureType> signature_type = + ToSignatureType(params.sig_type()); + if (!signature_type.ok()) { + return signature_type.status(); + } + + return SlhDsaParameters::Create(*hash_type, params.key_size(), + *signature_type, *variant); +} + +util::StatusOr<SlhDsaParams> FromParameters( + const SlhDsaParameters& parameters) { + /* Only SLH-DSA-SHA2-128s is currently supported*/ + util::StatusOr<SlhDsaHashType> hash_type = + ToProtoHashType(parameters.GetHashType()); + if (!hash_type.ok()) { + return hash_type.status(); + } + + util::StatusOr<SlhDsaSignatureType> signature_type = + ToProtoSignatureType(parameters.GetSignatureType()); + if (!signature_type.ok()) { + return signature_type.status(); + } + + SlhDsaParams params; + params.set_key_size(parameters.GetPrivateKeySizeInBytes()); + params.set_hash_type(*hash_type); + params.set_sig_type(*signature_type); + + return params; +} + +util::StatusOr<SlhDsaParameters> ParseParameters( + const internal::ProtoParametersSerialization& serialization) { + if (serialization.GetKeyTemplate().type_url() != kPrivateTypeUrl) { + return util::Status(absl::StatusCode::kInvalidArgument, + "Wrong type URL when parsing SlhDsaParameters."); + } + + SlhDsaKeyFormat proto_key_format; + if (!proto_key_format.ParseFromString( + serialization.GetKeyTemplate().value())) { + return util::Status(absl::StatusCode::kInvalidArgument, + "Failed to parse SlhDsaKeyFormat proto"); + } + if (proto_key_format.version() != 0) { + return util::Status(absl::StatusCode::kInvalidArgument, + "Only version 0 keys are accepted."); + } + + if (!proto_key_format.has_params()) { + return util::Status(absl::StatusCode::kInvalidArgument, + "SlhDsaKeyFormat proto is missing params field."); + } + + return ToParameters(serialization.GetKeyTemplate().output_prefix_type(), + proto_key_format.params()); +} + +util::StatusOr<SlhDsaPublicKey> ParsePublicKey( + const internal::ProtoKeySerialization& serialization, + absl::optional<SecretKeyAccessToken> token) { + if (serialization.TypeUrl() != kPublicTypeUrl) { + return util::Status(absl::StatusCode::kInvalidArgument, + "Wrong type URL when parsing SlhDsaPublicKey."); + } + + google::crypto::tink::SlhDsaPublicKey proto_key; + const RestrictedData& restricted_data = serialization.SerializedKeyProto(); + if (!proto_key.ParseFromString( + restricted_data.GetSecret(InsecureSecretKeyAccess::Get()))) { + return util::Status(absl::StatusCode::kInvalidArgument, + "Failed to parse SlhDsaPublicKey proto"); + } + if (proto_key.version() != 0) { + return util::Status(absl::StatusCode::kInvalidArgument, + "Only version 0 keys are accepted."); + } + + util::StatusOr<SlhDsaParameters> parameters = + ToParameters(serialization.GetOutputPrefixType(), proto_key.params()); + if (!parameters.ok()) { + return parameters.status(); + } + + return SlhDsaPublicKey::Create(*parameters, proto_key.key_value(), + serialization.IdRequirement(), + GetPartialKeyAccess()); +} + +util::StatusOr<SlhDsaPrivateKey> ParsePrivateKey( + const internal::ProtoKeySerialization& serialization, + absl::optional<SecretKeyAccessToken> token) { + if (serialization.TypeUrl() != kPrivateTypeUrl) { + return util::Status(absl::StatusCode::kInvalidArgument, + "Wrong type URL when parsing SlhDsaPrivateKey."); + } + if (!token.has_value()) { + return util::Status(absl::StatusCode::kPermissionDenied, + "SecretKeyAccess is required"); + } + google::crypto::tink::SlhDsaPrivateKey proto_key; + const RestrictedData& restricted_data = serialization.SerializedKeyProto(); + if (!proto_key.ParseFromString(restricted_data.GetSecret(*token))) { + return util::Status(absl::StatusCode::kInvalidArgument, + "Failed to parse SlhDsaPrivateKey proto"); + } + if (proto_key.version() != 0) { + return util::Status(absl::StatusCode::kInvalidArgument, + "Only version 0 keys are accepted."); + } + + util::StatusOr<SlhDsaParameters> parameters = ToParameters( + serialization.GetOutputPrefixType(), proto_key.public_key().params()); + if (!parameters.ok()) { + return parameters.status(); + } + + util::StatusOr<SlhDsaPublicKey> public_key = SlhDsaPublicKey::Create( + *parameters, proto_key.public_key().key_value(), + serialization.IdRequirement(), GetPartialKeyAccess()); + if (!public_key.ok()) { + return public_key.status(); + } + + return SlhDsaPrivateKey::Create(*public_key, + RestrictedData(proto_key.key_value(), *token), + GetPartialKeyAccess()); +} + +util::StatusOr<internal::ProtoParametersSerialization> SerializeParameters( + const SlhDsaParameters& parameters) { + util::StatusOr<OutputPrefixType> output_prefix_type = + ToOutputPrefixType(parameters.GetVariant()); + if (!output_prefix_type.ok()) { + return output_prefix_type.status(); + } + + util::StatusOr<SlhDsaParams> params = FromParameters(parameters); + if (!params.ok()) { + return params.status(); + } + SlhDsaKeyFormat proto_key_format; + *proto_key_format.mutable_params() = *params; + proto_key_format.set_version(0); + + return internal::ProtoParametersSerialization::Create( + kPrivateTypeUrl, *output_prefix_type, + proto_key_format.SerializeAsString()); +} + +util::StatusOr<internal::ProtoKeySerialization> SerializePublicKey( + const SlhDsaPublicKey& key, absl::optional<SecretKeyAccessToken> token) { + util::StatusOr<SlhDsaParams> params = FromParameters(key.GetParameters()); + if (!params.ok()) { + return params.status(); + } + + google::crypto::tink::SlhDsaPublicKey proto_key; + proto_key.set_version(0); + *proto_key.mutable_params() = *params; + proto_key.set_key_value(key.GetPublicKeyBytes(GetPartialKeyAccess())); + + util::StatusOr<OutputPrefixType> output_prefix_type = + ToOutputPrefixType(key.GetParameters().GetVariant()); + if (!output_prefix_type.ok()) { + return output_prefix_type.status(); + } + + RestrictedData restricted_output = RestrictedData( + proto_key.SerializeAsString(), InsecureSecretKeyAccess::Get()); + return internal::ProtoKeySerialization::Create( + kPublicTypeUrl, restricted_output, KeyData::ASYMMETRIC_PUBLIC, + *output_prefix_type, key.GetIdRequirement()); +} + +util::StatusOr<internal::ProtoKeySerialization> SerializePrivateKey( + const SlhDsaPrivateKey& key, absl::optional<SecretKeyAccessToken> token) { + if (!token.has_value()) { + return util::Status(absl::StatusCode::kPermissionDenied, + "SecretKeyAccess is required"); + } + util::StatusOr<RestrictedData> restricted_input = + key.GetPrivateKeyBytes(GetPartialKeyAccess()); + if (!restricted_input.ok()) { + return restricted_input.status(); + } + + util::StatusOr<SlhDsaParams> params = + FromParameters(key.GetPublicKey().GetParameters()); + if (!params.ok()) { + return params.status(); + } + + google::crypto::tink::SlhDsaPublicKey proto_public_key; + proto_public_key.set_version(0); + *proto_public_key.mutable_params() = *params; + proto_public_key.set_key_value( + key.GetPublicKey().GetPublicKeyBytes(GetPartialKeyAccess())); + + google::crypto::tink::SlhDsaPrivateKey proto_private_key; + proto_private_key.set_version(0); + *proto_private_key.mutable_public_key() = proto_public_key; + proto_private_key.set_key_value(restricted_input->GetSecret(*token)); + + util::StatusOr<OutputPrefixType> output_prefix_type = + ToOutputPrefixType(key.GetPublicKey().GetParameters().GetVariant()); + if (!output_prefix_type.ok()) { + return output_prefix_type.status(); + } + + RestrictedData restricted_output = + RestrictedData(proto_private_key.SerializeAsString(), *token); + return internal::ProtoKeySerialization::Create( + kPrivateTypeUrl, restricted_output, KeyData::ASYMMETRIC_PRIVATE, + *output_prefix_type, key.GetIdRequirement()); +} + +SlhDsaProtoParametersParserImpl& SlhDsaProtoParametersParser() { + static auto parser = + new SlhDsaProtoParametersParserImpl(kPrivateTypeUrl, ParseParameters); + return *parser; +} + +SlhDsaProtoParametersSerializerImpl& SlhDsaProtoParametersSerializer() { + static auto serializer = new SlhDsaProtoParametersSerializerImpl( + kPrivateTypeUrl, SerializeParameters); + return *serializer; +} + +SlhDsaProtoPublicKeyParserImpl& SlhDsaProtoPublicKeyParser() { + static auto* parser = + new SlhDsaProtoPublicKeyParserImpl(kPublicTypeUrl, ParsePublicKey); + return *parser; +} + +SlhDsaProtoPublicKeySerializerImpl& SlhDsaProtoPublicKeySerializer() { + static auto* serializer = + new SlhDsaProtoPublicKeySerializerImpl(SerializePublicKey); + return *serializer; +} + +SlhDsaProtoPrivateKeyParserImpl& SlhDsaProtoPrivateKeyParser() { + static auto* parser = + new SlhDsaProtoPrivateKeyParserImpl(kPrivateTypeUrl, ParsePrivateKey); + return *parser; +} + +SlhDsaProtoPrivateKeySerializerImpl& SlhDsaProtoPrivateKeySerializer() { + static auto* serializer = + new SlhDsaProtoPrivateKeySerializerImpl(SerializePrivateKey); + return *serializer; +} + +} // namespace + +util::Status RegisterSlhDsaProtoSerialization() { + util::Status status = + internal::MutableSerializationRegistry::GlobalInstance() + .RegisterParametersParser(&SlhDsaProtoParametersParser()); + if (!status.ok()) { + return status; + } + + status = + internal::MutableSerializationRegistry::GlobalInstance() + .RegisterParametersSerializer(&SlhDsaProtoParametersSerializer()); + if (!status.ok()) { + return status; + } + + status = internal::MutableSerializationRegistry::GlobalInstance() + .RegisterKeyParser(&SlhDsaProtoPublicKeyParser()); + if (!status.ok()) { + return status; + } + + status = internal::MutableSerializationRegistry::GlobalInstance() + .RegisterKeySerializer(&SlhDsaProtoPublicKeySerializer()); + if (!status.ok()) { + return status; + } + + status = internal::MutableSerializationRegistry::GlobalInstance() + .RegisterKeyParser(&SlhDsaProtoPrivateKeyParser()); + if (!status.ok()) { + return status; + } + + return internal::MutableSerializationRegistry::GlobalInstance() + .RegisterKeySerializer(&SlhDsaProtoPrivateKeySerializer()); +} + +} // namespace tink +} // namespace crypto
diff --git a/cc/experimental/pqcrypto/signature/slh_dsa_proto_serialization.h b/cc/experimental/pqcrypto/signature/slh_dsa_proto_serialization.h new file mode 100644 index 0000000..59f5c62 --- /dev/null +++ b/cc/experimental/pqcrypto/signature/slh_dsa_proto_serialization.h
@@ -0,0 +1,31 @@ +// 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_PROTO_SERIALIZATION_H_ +#define TINK_EXPERIMENTAL_PQCRYPTO_SIGNATURE_SLH_DSA_PROTO_SERIALIZATION_H_ + +#include "tink/util/status.h" + +namespace crypto { +namespace tink { + +// Registers proto parsers and serializers for SLH-DSA parameters and keys. +crypto::tink::util::Status RegisterSlhDsaProtoSerialization(); + +} // namespace tink +} // namespace crypto + +#endif // TINK_EXPERIMENTAL_PQCRYPTO_SIGNATURE_SLH_DSA_PROTO_SERIALIZATION_H_
diff --git a/cc/experimental/pqcrypto/signature/slh_dsa_proto_serialization_test.cc b/cc/experimental/pqcrypto/signature/slh_dsa_proto_serialization_test.cc new file mode 100644 index 0000000..b516ac6 --- /dev/null +++ b/cc/experimental/pqcrypto/signature/slh_dsa_proto_serialization_test.cc
@@ -0,0 +1,731 @@ +// 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_proto_serialization.h" + +#include <cstdint> +#include <memory> +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/status/status.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#define OPENSSL_UNSTABLE_EXPERIMENTAL_SPX +#include "openssl/experimental/spx.h" +#undef OPENSSL_UNSTABLE_EXPERIMENTAL_SPX +#include "tink/experimental/pqcrypto/signature/slh_dsa_parameters.h" +#include "tink/experimental/pqcrypto/signature/slh_dsa_private_key.h" +#include "tink/experimental/pqcrypto/signature/slh_dsa_public_key.h" +#include "tink/insecure_secret_key_access.h" +#include "tink/internal/mutable_serialization_registry.h" +#include "tink/internal/proto_key_serialization.h" +#include "tink/internal/proto_parameters_serialization.h" +#include "tink/internal/serialization.h" +#include "tink/key.h" +#include "tink/parameters.h" +#include "tink/partial_key_access.h" +#include "tink/restricted_data.h" +#include "tink/subtle/random.h" +#include "tink/util/statusor.h" +#include "tink/util/test_matchers.h" +#include "proto/experimental/pqcrypto/slh_dsa.pb.h" +#include "proto/tink.pb.h" + +namespace crypto { +namespace tink { +namespace { + +using ::crypto::tink::subtle::Random; +using ::crypto::tink::test::IsOk; +using ::crypto::tink::test::StatusIs; +using ::google::crypto::tink::KeyData; +using ::google::crypto::tink::OutputPrefixType; +using ::google::crypto::tink::SlhDsaHashType; +using ::google::crypto::tink::SlhDsaKeyFormat; +using ::google::crypto::tink::SlhDsaParams; +using ::google::crypto::tink::SlhDsaSignatureType; +using ::testing::Eq; +using ::testing::HasSubstr; +using ::testing::IsTrue; +using ::testing::NotNull; +using ::testing::TestWithParam; +using ::testing::Values; + +const absl::string_view kPrivateTypeUrl = + "type.googleapis.com/google.crypto.tink.SlhDsaPrivateKey"; +const absl::string_view kPublicTypeUrl = + "type.googleapis.com/google.crypto.tink.SlhDsaPublicKey"; + +struct TestCase { + SlhDsaParameters::Variant variant; + OutputPrefixType output_prefix_type; + absl::optional<int> id_requirement; + std::string output_prefix; +}; + +class SlhDsaProtoSerializationTest : public TestWithParam<TestCase> { + protected: + SlhDsaProtoSerializationTest() { + internal::MutableSerializationRegistry::GlobalInstance().Reset(); + } +}; + +TEST_F(SlhDsaProtoSerializationTest, RegisterTwiceSucceeds) { + ASSERT_THAT(RegisterSlhDsaProtoSerialization(), IsOk()); + ASSERT_THAT(RegisterSlhDsaProtoSerialization(), IsOk()); +} + +INSTANTIATE_TEST_SUITE_P( + SlhDsaProtoSerializationTestSuite, SlhDsaProtoSerializationTest, + Values(TestCase{SlhDsaParameters::Variant::kTink, OutputPrefixType::TINK, + 0x02030400, std::string("\x01\x02\x03\x04\x00", 5)}, + TestCase{SlhDsaParameters::Variant::kTink, OutputPrefixType::TINK, + 0x03050709, std::string("\x01\x03\x05\x07\x09", 5)}, + TestCase{SlhDsaParameters::Variant::kNoPrefix, OutputPrefixType::RAW, + absl::nullopt, ""})); + +TEST_P(SlhDsaProtoSerializationTest, + ParseSlhDsa128Sha2SmallSignatureParametersWorks) { + TestCase test_case = GetParam(); + ASSERT_THAT(RegisterSlhDsaProtoSerialization(), IsOk()); + + SlhDsaKeyFormat key_format_proto; + SlhDsaParams& params = *key_format_proto.mutable_params(); + params.set_sig_type(SlhDsaSignatureType::SMALL_SIGNATURE); + params.set_hash_type(SlhDsaHashType::SHA2); + params.set_key_size(64); + + util::StatusOr<internal::ProtoParametersSerialization> serialization = + internal::ProtoParametersSerialization::Create( + kPrivateTypeUrl, test_case.output_prefix_type, + key_format_proto.SerializeAsString()); + ASSERT_THAT(serialization, IsOk()); + + util::StatusOr<std::unique_ptr<Parameters>> parameters = + internal::MutableSerializationRegistry::GlobalInstance().ParseParameters( + *serialization); + ASSERT_THAT(parameters, IsOk()); + EXPECT_EQ((*parameters)->HasIdRequirement(), + test_case.id_requirement.has_value()); + + const SlhDsaParameters* slh_dsa_parameters = + dynamic_cast<const SlhDsaParameters*>(parameters->get()); + ASSERT_THAT(slh_dsa_parameters, NotNull()); + EXPECT_THAT(slh_dsa_parameters->GetVariant(), Eq(test_case.variant)); + EXPECT_THAT(slh_dsa_parameters->GetPrivateKeySizeInBytes(), Eq(64)); + EXPECT_THAT(slh_dsa_parameters->GetSignatureType(), + Eq(SlhDsaParameters::SignatureType::kSmallSignature)); + EXPECT_THAT(slh_dsa_parameters->GetHashType(), + Eq(SlhDsaParameters::HashType::kSha2)); +} + +TEST_F(SlhDsaProtoSerializationTest, + ParseParametersWithInvalidSerializationFails) { + ASSERT_THAT(RegisterSlhDsaProtoSerialization(), IsOk()); + + util::StatusOr<internal::ProtoParametersSerialization> serialization = + internal::ProtoParametersSerialization::Create( + kPrivateTypeUrl, OutputPrefixType::RAW, "invalid_serialization"); + ASSERT_THAT(serialization, IsOk()); + + EXPECT_THAT(internal::MutableSerializationRegistry::GlobalInstance() + .ParseParameters(*serialization) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Failed to parse SlhDsaKeyFormat proto"))); +} + +TEST_F(SlhDsaProtoSerializationTest, ParseParametersWithInvalidVersionFails) { + ASSERT_THAT(RegisterSlhDsaProtoSerialization(), IsOk()); + + SlhDsaKeyFormat key_format_proto; + key_format_proto.set_version(1); + SlhDsaParams& params = *key_format_proto.mutable_params(); + params.set_sig_type(SlhDsaSignatureType::SMALL_SIGNATURE); + params.set_hash_type(SlhDsaHashType::SHA2); + params.set_key_size(64); + + util::StatusOr<internal::ProtoParametersSerialization> serialization = + internal::ProtoParametersSerialization::Create( + kPrivateTypeUrl, OutputPrefixType::RAW, + key_format_proto.SerializeAsString()); + ASSERT_THAT(serialization, IsOk()); + + util::StatusOr<std::unique_ptr<Parameters>> parameters = + internal::MutableSerializationRegistry::GlobalInstance().ParseParameters( + *serialization); + EXPECT_THAT(parameters.status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Only version 0 keys are accepted"))); +} + +TEST_F(SlhDsaProtoSerializationTest, + ParseParametersKeyFormatWithoutParamsFails) { + ASSERT_THAT(RegisterSlhDsaProtoSerialization(), IsOk()); + + SlhDsaKeyFormat key_format_proto; + util::StatusOr<internal::ProtoParametersSerialization> serialization = + internal::ProtoParametersSerialization::Create( + kPrivateTypeUrl, OutputPrefixType::RAW, + key_format_proto.SerializeAsString()); + ASSERT_THAT(serialization, IsOk()); + + util::StatusOr<std::unique_ptr<Parameters>> parameters = + internal::MutableSerializationRegistry::GlobalInstance().ParseParameters( + *serialization); + + ASSERT_THAT(parameters.status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("SlhDsaKeyFormat proto is missing params"))); +} + +TEST_F(SlhDsaProtoSerializationTest, + ParseParametersWithUnkownOutputPrefixFails) { + ASSERT_THAT(RegisterSlhDsaProtoSerialization(), IsOk()); + + SlhDsaKeyFormat key_format_proto; + SlhDsaParams& params = *key_format_proto.mutable_params(); + params.set_sig_type(SlhDsaSignatureType::SMALL_SIGNATURE); + params.set_hash_type(SlhDsaHashType::SHA2); + params.set_key_size(64); + + util::StatusOr<internal::ProtoParametersSerialization> serialization = + internal::ProtoParametersSerialization::Create( + kPrivateTypeUrl, OutputPrefixType::UNKNOWN_PREFIX, + key_format_proto.SerializeAsString()); + ASSERT_THAT(serialization, IsOk()); + + util::StatusOr<std::unique_ptr<Parameters>> parameters = + internal::MutableSerializationRegistry::GlobalInstance().ParseParameters( + *serialization); + EXPECT_THAT( + parameters.status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Could not determine SlhDsaParameters::Variant"))); +} + +TEST_F(SlhDsaProtoSerializationTest, ParseParametersWithUnkownSigTypeFails) { + ASSERT_THAT(RegisterSlhDsaProtoSerialization(), IsOk()); + + SlhDsaKeyFormat key_format_proto; + SlhDsaParams& params = *key_format_proto.mutable_params(); + params.set_sig_type(SlhDsaSignatureType::SLH_DSA_SIGNATURE_TYPE_UNSPECIFIED); + params.set_hash_type(SlhDsaHashType::SHA2); + params.set_key_size(64); + + util::StatusOr<internal::ProtoParametersSerialization> serialization = + internal::ProtoParametersSerialization::Create( + kPrivateTypeUrl, OutputPrefixType::RAW, + key_format_proto.SerializeAsString()); + ASSERT_THAT(serialization, IsOk()); + + util::StatusOr<std::unique_ptr<Parameters>> parameters = + internal::MutableSerializationRegistry::GlobalInstance().ParseParameters( + *serialization); + EXPECT_THAT( + parameters.status(), + StatusIs( + absl::StatusCode::kInvalidArgument, + HasSubstr("Could not determine SlhDsaParameters::SignatureType"))); +} + +TEST_F(SlhDsaProtoSerializationTest, ParseParametersWithUnkownHashTypeFails) { + ASSERT_THAT(RegisterSlhDsaProtoSerialization(), IsOk()); + + SlhDsaKeyFormat key_format_proto; + SlhDsaParams& params = *key_format_proto.mutable_params(); + params.set_sig_type(SlhDsaSignatureType::SMALL_SIGNATURE); + params.set_hash_type(SlhDsaHashType::SLH_DSA_HASH_TYPE_UNSPECIFIED); + params.set_key_size(64); + + util::StatusOr<internal::ProtoParametersSerialization> serialization = + internal::ProtoParametersSerialization::Create( + kPrivateTypeUrl, OutputPrefixType::RAW, + key_format_proto.SerializeAsString()); + ASSERT_THAT(serialization, IsOk()); + + util::StatusOr<std::unique_ptr<Parameters>> parameters = + internal::MutableSerializationRegistry::GlobalInstance().ParseParameters( + *serialization); + EXPECT_THAT( + parameters.status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Could not determine SlhDsaParameters::HashType"))); +} + +TEST_P(SlhDsaProtoSerializationTest, + SerializeSlhDsa128Sha2SmallSignatureParametersWorks) { + TestCase test_case = GetParam(); + ASSERT_THAT(RegisterSlhDsaProtoSerialization(), IsOk()); + + util::StatusOr<SlhDsaParameters> parameters = SlhDsaParameters::Create( + SlhDsaParameters::HashType::kSha2, /*private_key_size_in_bytes=*/64, + SlhDsaParameters::SignatureType::kSmallSignature, test_case.variant); + ASSERT_THAT(parameters, IsOk()); + + util::StatusOr<std::unique_ptr<Serialization>> serialization = + internal::MutableSerializationRegistry::GlobalInstance() + .SerializeParameters<internal::ProtoParametersSerialization>( + *parameters); + ASSERT_THAT(serialization, IsOk()); + EXPECT_THAT((*serialization)->ObjectIdentifier(), Eq(kPrivateTypeUrl)); + + const internal::ProtoParametersSerialization* proto_serialization = + dynamic_cast<const internal::ProtoParametersSerialization*>( + serialization->get()); + ASSERT_THAT(proto_serialization, NotNull()); + EXPECT_THAT(proto_serialization->GetKeyTemplate().type_url(), + Eq(kPrivateTypeUrl)); + EXPECT_THAT(proto_serialization->GetKeyTemplate().output_prefix_type(), + Eq(test_case.output_prefix_type)); + + SlhDsaKeyFormat key_format; + ASSERT_THAT( + key_format.ParseFromString(proto_serialization->GetKeyTemplate().value()), + IsTrue()); + ASSERT_TRUE(key_format.has_params()); + EXPECT_THAT(key_format.params().hash_type(), Eq(SlhDsaHashType::SHA2)); + EXPECT_THAT(key_format.params().sig_type(), + Eq(SlhDsaSignatureType::SMALL_SIGNATURE)); + EXPECT_THAT(key_format.params().key_size(), Eq(64)); +} + +TEST_P(SlhDsaProtoSerializationTest, ParsePublicKeyWorks) { + TestCase test_case = GetParam(); + ASSERT_THAT(RegisterSlhDsaProtoSerialization(), IsOk()); + + SlhDsaParams params; + params.set_sig_type(SlhDsaSignatureType::SMALL_SIGNATURE); + params.set_hash_type(SlhDsaHashType::SHA2); + params.set_key_size(64); + + std::string raw_key_bytes = Random::GetRandomBytes(32); + google::crypto::tink::SlhDsaPublicKey key_proto; + key_proto.set_version(0); + key_proto.set_key_value(raw_key_bytes); + *key_proto.mutable_params() = params; + RestrictedData serialized_key = RestrictedData( + key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get()); + + util::StatusOr<internal::ProtoKeySerialization> serialization = + internal::ProtoKeySerialization::Create( + kPublicTypeUrl, serialized_key, KeyData::ASYMMETRIC_PUBLIC, + test_case.output_prefix_type, test_case.id_requirement); + ASSERT_THAT(serialization, IsOk()); + + util::StatusOr<std::unique_ptr<Key>> key = + internal::MutableSerializationRegistry::GlobalInstance().ParseKey( + *serialization, /*token=*/absl::nullopt); + ASSERT_THAT(key, IsOk()); + EXPECT_THAT((*key)->GetIdRequirement(), Eq(test_case.id_requirement)); + EXPECT_THAT((*key)->GetParameters().HasIdRequirement(), + test_case.id_requirement.has_value()); + + util::StatusOr<SlhDsaParameters> expected_parameters = + SlhDsaParameters::Create( + SlhDsaParameters::HashType::kSha2, /*private_key_size_in_bytes=*/64, + SlhDsaParameters::SignatureType::kSmallSignature, test_case.variant); + ASSERT_THAT(expected_parameters, IsOk()); + + util::StatusOr<SlhDsaPublicKey> expected_key = + SlhDsaPublicKey::Create(*expected_parameters, raw_key_bytes, + test_case.id_requirement, GetPartialKeyAccess()); + ASSERT_THAT(expected_key, IsOk()); + + EXPECT_THAT(**key, Eq(*expected_key)); +} + +TEST_F(SlhDsaProtoSerializationTest, + ParsePublicKeyWithInvalidSerializationFails) { + ASSERT_THAT(RegisterSlhDsaProtoSerialization(), IsOk()); + + RestrictedData serialized_key = + RestrictedData("invalid_serialization", InsecureSecretKeyAccess::Get()); + + util::StatusOr<internal::ProtoKeySerialization> serialization = + internal::ProtoKeySerialization::Create(kPublicTypeUrl, serialized_key, + KeyData::ASYMMETRIC_PUBLIC, + OutputPrefixType::TINK, + /*id_requirement=*/0x23456789); + ASSERT_THAT(serialization, IsOk()); + + util::StatusOr<std::unique_ptr<Key>> key = + internal::MutableSerializationRegistry::GlobalInstance().ParseKey( + *serialization, InsecureSecretKeyAccess::Get()); + EXPECT_THAT(key.status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Failed to parse SlhDsaPublicKey proto"))); +} + +TEST_F(SlhDsaProtoSerializationTest, ParsePublicKeyWithInvalidVersionFails) { + ASSERT_THAT(RegisterSlhDsaProtoSerialization(), IsOk()); + + SlhDsaParams params; + params.set_sig_type(SlhDsaSignatureType::SMALL_SIGNATURE); + params.set_hash_type(SlhDsaHashType::SHA2); + params.set_key_size(64); + + std::string raw_key_bytes = Random::GetRandomBytes(32); + google::crypto::tink::SlhDsaPublicKey key_proto; + key_proto.set_version(1); + key_proto.set_key_value(raw_key_bytes); + *key_proto.mutable_params() = params; + RestrictedData serialized_key = RestrictedData( + key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get()); + + util::StatusOr<internal::ProtoKeySerialization> serialization = + internal::ProtoKeySerialization::Create(kPublicTypeUrl, serialized_key, + KeyData::ASYMMETRIC_PUBLIC, + OutputPrefixType::TINK, + /*id_requirement=*/0x23456789); + ASSERT_THAT(serialization, IsOk()); + + util::StatusOr<std::unique_ptr<Key>> key = + internal::MutableSerializationRegistry::GlobalInstance().ParseKey( + *serialization, /*token=*/absl::nullopt); + EXPECT_THAT(key.status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Only version 0 keys are accepted"))); +} + +TEST_P(SlhDsaProtoSerializationTest, SerializePublicKeyWorks) { + TestCase test_case = GetParam(); + ASSERT_THAT(RegisterSlhDsaProtoSerialization(), IsOk()); + + util::StatusOr<SlhDsaParameters> parameters = SlhDsaParameters::Create( + SlhDsaParameters::HashType::kSha2, /*private_key_size_in_bytes=*/64, + SlhDsaParameters::SignatureType::kSmallSignature, test_case.variant); + ASSERT_THAT(parameters, IsOk()); + + std::string raw_key_bytes = Random::GetRandomBytes(32); + util::StatusOr<SlhDsaPublicKey> key = + SlhDsaPublicKey::Create(*parameters, raw_key_bytes, + test_case.id_requirement, GetPartialKeyAccess()); + ASSERT_THAT(key, IsOk()); + + util::StatusOr<std::unique_ptr<Serialization>> serialization = + internal::MutableSerializationRegistry::GlobalInstance() + .SerializeKey<internal::ProtoKeySerialization>( + *key, /*token=*/absl::nullopt); + ASSERT_THAT(serialization, IsOk()); + EXPECT_THAT((*serialization)->ObjectIdentifier(), Eq(kPublicTypeUrl)); + + const internal::ProtoKeySerialization* proto_serialization = + dynamic_cast<const internal::ProtoKeySerialization*>( + serialization->get()); + ASSERT_THAT(proto_serialization, NotNull()); + EXPECT_THAT(proto_serialization->TypeUrl(), Eq(kPublicTypeUrl)); + EXPECT_THAT(proto_serialization->KeyMaterialType(), + Eq(KeyData::ASYMMETRIC_PUBLIC)); + EXPECT_THAT(proto_serialization->GetOutputPrefixType(), + Eq(test_case.output_prefix_type)); + EXPECT_THAT(proto_serialization->IdRequirement(), + Eq(test_case.id_requirement)); + + google::crypto::tink::SlhDsaPublicKey proto_key; + ASSERT_THAT(proto_key.ParseFromString( + proto_serialization->SerializedKeyProto().GetSecret( + InsecureSecretKeyAccess::Get())), + IsTrue()); + EXPECT_THAT(proto_key.version(), Eq(0)); + EXPECT_THAT(proto_key.key_value(), Eq(raw_key_bytes)); + EXPECT_THAT(proto_key.has_params(), IsTrue()); + EXPECT_THAT(proto_key.params().key_size(), Eq(64)); + EXPECT_THAT(proto_key.params().hash_type(), Eq(SlhDsaHashType::SHA2)); + EXPECT_THAT(proto_key.params().sig_type(), + Eq(SlhDsaSignatureType::SMALL_SIGNATURE)); +} + +TEST_P(SlhDsaProtoSerializationTest, ParsePrivateKeyWorks) { + TestCase test_case = GetParam(); + ASSERT_THAT(RegisterSlhDsaProtoSerialization(), IsOk()); + + std::string public_key_bytes; + public_key_bytes.resize(SPX_PUBLIC_KEY_BYTES); + std::string private_key_bytes; + private_key_bytes.resize(SPX_SECRET_KEY_BYTES); + + SPX_generate_key(reinterpret_cast<uint8_t*>(public_key_bytes.data()), + reinterpret_cast<uint8_t*>(private_key_bytes.data())); + + SlhDsaParams params; + params.set_sig_type(SlhDsaSignatureType::SMALL_SIGNATURE); + params.set_hash_type(SlhDsaHashType::SHA2); + params.set_key_size(64); + + google::crypto::tink::SlhDsaPublicKey public_key_proto; + public_key_proto.set_version(0); + public_key_proto.set_key_value(public_key_bytes); + *public_key_proto.mutable_params() = params; + + google::crypto::tink::SlhDsaPrivateKey private_key_proto; + private_key_proto.set_version(0); + *private_key_proto.mutable_public_key() = public_key_proto; + private_key_proto.set_key_value(private_key_bytes); + + RestrictedData serialized_key = RestrictedData( + private_key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get()); + + util::StatusOr<internal::ProtoKeySerialization> serialization = + internal::ProtoKeySerialization::Create( + kPrivateTypeUrl, serialized_key, KeyData::ASYMMETRIC_PRIVATE, + test_case.output_prefix_type, test_case.id_requirement); + ASSERT_THAT(serialization, IsOk()); + + util::StatusOr<std::unique_ptr<Key>> private_key = + internal::MutableSerializationRegistry::GlobalInstance().ParseKey( + *serialization, InsecureSecretKeyAccess::Get()); + ASSERT_THAT(private_key, IsOk()); + + EXPECT_THAT((*private_key)->GetIdRequirement(), Eq(test_case.id_requirement)); + EXPECT_THAT((*private_key)->GetParameters().HasIdRequirement(), + test_case.id_requirement.has_value()); + + util::StatusOr<SlhDsaParameters> expected_parameters = + SlhDsaParameters::Create( + SlhDsaParameters::HashType::kSha2, /*private_key_size_in_bytes=*/64, + SlhDsaParameters::SignatureType::kSmallSignature, test_case.variant); + ASSERT_THAT(expected_parameters, IsOk()); + + util::StatusOr<SlhDsaPublicKey> expected_public_key = + SlhDsaPublicKey::Create(*expected_parameters, public_key_bytes, + test_case.id_requirement, GetPartialKeyAccess()); + ASSERT_THAT(expected_public_key, IsOk()); + + util::StatusOr<SlhDsaPrivateKey> expected_private_key = + SlhDsaPrivateKey::Create( + *expected_public_key, + RestrictedData(private_key_bytes, InsecureSecretKeyAccess::Get()), + GetPartialKeyAccess()); + ASSERT_THAT(expected_private_key, IsOk()); + + EXPECT_THAT(**private_key, Eq(*expected_private_key)); +} + +TEST_F(SlhDsaProtoSerializationTest, ParsePrivateKeyWithInvalidSerialization) { + ASSERT_THAT(RegisterSlhDsaProtoSerialization(), IsOk()); + + RestrictedData serialized_key = + RestrictedData("invalid_serialization", InsecureSecretKeyAccess::Get()); + + util::StatusOr<internal::ProtoKeySerialization> serialization = + internal::ProtoKeySerialization::Create(kPrivateTypeUrl, serialized_key, + KeyData::ASYMMETRIC_PRIVATE, + OutputPrefixType::TINK, + /*id_requirement=*/0x23456789); + ASSERT_THAT(serialization, IsOk()); + + util::StatusOr<std::unique_ptr<Key>> key = + internal::MutableSerializationRegistry::GlobalInstance().ParseKey( + *serialization, InsecureSecretKeyAccess::Get()); + EXPECT_THAT(key.status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Failed to parse SlhDsaPrivateKey proto"))); +} + +TEST_F(SlhDsaProtoSerializationTest, ParsePrivateKeyWithInvalidVersion) { + ASSERT_THAT(RegisterSlhDsaProtoSerialization(), IsOk()); + + std::string public_key_bytes; + public_key_bytes.resize(SPX_PUBLIC_KEY_BYTES); + std::string private_key_bytes; + private_key_bytes.resize(SPX_SECRET_KEY_BYTES); + + SPX_generate_key(reinterpret_cast<uint8_t*>(public_key_bytes.data()), + reinterpret_cast<uint8_t*>(private_key_bytes.data())); + + SlhDsaParams params; + params.set_sig_type(SlhDsaSignatureType::SMALL_SIGNATURE); + params.set_hash_type(SlhDsaHashType::SHA2); + params.set_key_size(64); + + google::crypto::tink::SlhDsaPublicKey public_key_proto; + public_key_proto.set_version(0); + public_key_proto.set_key_value(public_key_bytes); + *public_key_proto.mutable_params() = params; + + google::crypto::tink::SlhDsaPrivateKey private_key_proto; + private_key_proto.set_version(1); + *private_key_proto.mutable_public_key() = public_key_proto; + private_key_proto.set_key_value(private_key_bytes); + + RestrictedData serialized_key = RestrictedData( + private_key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get()); + + util::StatusOr<internal::ProtoKeySerialization> serialization = + internal::ProtoKeySerialization::Create(kPrivateTypeUrl, serialized_key, + KeyData::ASYMMETRIC_PRIVATE, + OutputPrefixType::TINK, + /*id_requirement=*/0x23456789); + ASSERT_THAT(serialization, IsOk()); + + util::StatusOr<std::unique_ptr<Key>> key = + internal::MutableSerializationRegistry::GlobalInstance().ParseKey( + *serialization, InsecureSecretKeyAccess::Get()); + EXPECT_THAT(key.status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Only version 0 keys are accepted"))); +} + +TEST_F(SlhDsaProtoSerializationTest, ParsePrivateKeyNoSecretKeyAccess) { + ASSERT_THAT(RegisterSlhDsaProtoSerialization(), IsOk()); + + std::string public_key_bytes; + public_key_bytes.resize(SPX_PUBLIC_KEY_BYTES); + std::string private_key_bytes; + private_key_bytes.resize(SPX_SECRET_KEY_BYTES); + + SPX_generate_key(reinterpret_cast<uint8_t*>(public_key_bytes.data()), + reinterpret_cast<uint8_t*>(private_key_bytes.data())); + + SlhDsaParams params; + params.set_sig_type(SlhDsaSignatureType::SMALL_SIGNATURE); + params.set_hash_type(SlhDsaHashType::SHA2); + params.set_key_size(64); + + google::crypto::tink::SlhDsaPublicKey public_key_proto; + public_key_proto.set_version(0); + public_key_proto.set_key_value(public_key_bytes); + *public_key_proto.mutable_params() = params; + + google::crypto::tink::SlhDsaPrivateKey private_key_proto; + private_key_proto.set_version(0); + *private_key_proto.mutable_public_key() = public_key_proto; + private_key_proto.set_key_value(private_key_bytes); + + RestrictedData serialized_key = RestrictedData( + private_key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get()); + + util::StatusOr<internal::ProtoKeySerialization> serialization = + internal::ProtoKeySerialization::Create(kPrivateTypeUrl, serialized_key, + KeyData::ASYMMETRIC_PRIVATE, + OutputPrefixType::TINK, + /*id_requirement=*/0x23456789); + ASSERT_THAT(serialization, IsOk()); + + util::StatusOr<std::unique_ptr<Key>> key = + internal::MutableSerializationRegistry::GlobalInstance().ParseKey( + *serialization, /*token=*/absl::nullopt); + EXPECT_THAT(key.status(), StatusIs(absl::StatusCode::kPermissionDenied)); +} + +TEST_P(SlhDsaProtoSerializationTest, SerializePrivateKey) { + TestCase test_case = GetParam(); + ASSERT_THAT(RegisterSlhDsaProtoSerialization(), IsOk()); + + std::string public_key_bytes; + public_key_bytes.resize(SPX_PUBLIC_KEY_BYTES); + std::string private_key_bytes; + private_key_bytes.resize(SPX_SECRET_KEY_BYTES); + + SPX_generate_key(reinterpret_cast<uint8_t*>(public_key_bytes.data()), + reinterpret_cast<uint8_t*>(private_key_bytes.data())); + + util::StatusOr<SlhDsaParameters> parameters = SlhDsaParameters::Create( + SlhDsaParameters::HashType::kSha2, /*private_key_size_in_bytes=*/64, + SlhDsaParameters::SignatureType::kSmallSignature, test_case.variant); + ASSERT_THAT(parameters, IsOk()); + + util::StatusOr<SlhDsaPublicKey> public_key = + SlhDsaPublicKey::Create(*parameters, public_key_bytes, + test_case.id_requirement, GetPartialKeyAccess()); + ASSERT_THAT(public_key, IsOk()); + + util::StatusOr<SlhDsaPrivateKey> private_key = SlhDsaPrivateKey::Create( + *public_key, + RestrictedData(private_key_bytes, InsecureSecretKeyAccess::Get()), + GetPartialKeyAccess()); + ASSERT_THAT(private_key, IsOk()); + + util::StatusOr<std::unique_ptr<Serialization>> serialization = + internal::MutableSerializationRegistry::GlobalInstance() + .SerializeKey<internal::ProtoKeySerialization>( + *private_key, InsecureSecretKeyAccess::Get()); + ASSERT_THAT(serialization, IsOk()); + EXPECT_THAT((*serialization)->ObjectIdentifier(), Eq(kPrivateTypeUrl)); + + const internal::ProtoKeySerialization* proto_serialization = + dynamic_cast<const internal::ProtoKeySerialization*>( + serialization->get()); + ASSERT_THAT(proto_serialization, NotNull()); + EXPECT_THAT(proto_serialization->TypeUrl(), Eq(kPrivateTypeUrl)); + EXPECT_THAT(proto_serialization->KeyMaterialType(), + Eq(KeyData::ASYMMETRIC_PRIVATE)); + EXPECT_THAT(proto_serialization->GetOutputPrefixType(), + Eq(test_case.output_prefix_type)); + EXPECT_THAT(proto_serialization->IdRequirement(), + Eq(test_case.id_requirement)); + + google::crypto::tink::SlhDsaPrivateKey proto_key; + ASSERT_THAT(proto_key.ParseFromString( + proto_serialization->SerializedKeyProto().GetSecret( + InsecureSecretKeyAccess::Get())), + IsTrue()); + EXPECT_THAT(proto_key.version(), Eq(0)); + EXPECT_THAT(proto_key.key_value(), Eq(private_key_bytes)); + EXPECT_THAT(proto_key.has_public_key(), IsTrue()); + EXPECT_THAT(proto_key.public_key().version(), Eq(0)); + EXPECT_THAT(proto_key.public_key().key_value(), Eq(public_key_bytes)); + EXPECT_THAT(proto_key.public_key().has_params(), IsTrue()); + EXPECT_THAT(proto_key.public_key().params().key_size(), Eq(64)); + EXPECT_THAT(proto_key.public_key().params().hash_type(), + Eq(SlhDsaHashType::SHA2)); + EXPECT_THAT(proto_key.public_key().params().sig_type(), + Eq(SlhDsaSignatureType::SMALL_SIGNATURE)); +} + +TEST_F(SlhDsaProtoSerializationTest, SerializePrivateKeyNoSecretKeyAccess) { + ASSERT_THAT(RegisterSlhDsaProtoSerialization(), IsOk()); + + std::string public_key_bytes; + public_key_bytes.resize(SPX_PUBLIC_KEY_BYTES); + std::string private_key_bytes; + private_key_bytes.resize(SPX_SECRET_KEY_BYTES); + + SPX_generate_key(reinterpret_cast<uint8_t*>(public_key_bytes.data()), + reinterpret_cast<uint8_t*>(private_key_bytes.data())); + + util::StatusOr<SlhDsaParameters> parameters = SlhDsaParameters::Create( + SlhDsaParameters::HashType::kSha2, /*private_key_size_in_bytes=*/64, + SlhDsaParameters::SignatureType::kSmallSignature, + SlhDsaParameters::Variant::kTink); + ASSERT_THAT(parameters, IsOk()); + + util::StatusOr<SlhDsaPublicKey> public_key = + SlhDsaPublicKey::Create(*parameters, public_key_bytes, + /*id_requirement=*/123, GetPartialKeyAccess()); + ASSERT_THAT(public_key, IsOk()); + + util::StatusOr<SlhDsaPrivateKey> private_key = SlhDsaPrivateKey::Create( + *public_key, + RestrictedData(private_key_bytes, InsecureSecretKeyAccess::Get()), + GetPartialKeyAccess()); + ASSERT_THAT(private_key, IsOk()); + + util::StatusOr<std::unique_ptr<Serialization>> serialization = + internal::MutableSerializationRegistry::GlobalInstance() + .SerializeKey<internal::ProtoKeySerialization>( + *private_key, /*token=*/absl::nullopt); + ASSERT_THAT(serialization.status(), + StatusIs(absl::StatusCode::kPermissionDenied, + HasSubstr("SecretKeyAccess is required"))); +} + +} // namespace +} // namespace tink +} // namespace crypto
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 0000000..9f600e5 --- /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 <string> + +#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<std::string> ComputeOutputPrefix( + const SlhDsaParameters& parameters, absl::optional<int> 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> SlhDsaPublicKey::Create( + const SlhDsaParameters& 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"); + } + // 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<std::string> 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<const SlhDsaPublicKey*>(&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 0000000..24fe2fe --- /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 <string> + +#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<SlhDsaPublicKey> Create( + const SlhDsaParameters& parameters, absl::string_view public_key_bytes, + absl::optional<int> 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<int> 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<int> 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<int> 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 0000000..efbaa95 --- /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 <string> + +#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<int> id_requirement; + std::string output_prefix; +}; + +using SlhDsaPublicKeyTest = TestWithParam<TestCase>; + +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<SlhDsaParameters> 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<SlhDsaPublicKey> 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<SlhDsaParameters> 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<SlhDsaParameters> 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<SlhDsaParameters> 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<SlhDsaParameters> 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<SlhDsaPublicKey> public_key = + SlhDsaPublicKey::Create(*params, public_key_bytes, + test_case.id_requirement, GetPartialKeyAccess()); + ASSERT_THAT(public_key, IsOk()); + + util::StatusOr<SlhDsaPublicKey> 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<SlhDsaParameters> 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<SlhDsaPublicKey> public_key = SlhDsaPublicKey::Create( + *params, public_key_bytes1, /*id_requirement=*/0x01020304, + GetPartialKeyAccess()); + ASSERT_THAT(public_key, IsOk()); + + util::StatusOr<SlhDsaPublicKey> 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<SlhDsaParameters> 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<SlhDsaPublicKey> public_key = SlhDsaPublicKey::Create( + *params, public_key_bytes, /*id_requirement=*/0x01020304, + GetPartialKeyAccess()); + ASSERT_THAT(public_key, IsOk()); + + util::StatusOr<SlhDsaPublicKey> 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
diff --git a/cc/internal/BUILD.bazel b/cc/internal/BUILD.bazel index edd7a44..c3c7bb3 100644 --- a/cc/internal/BUILD.bazel +++ b/cc/internal/BUILD.bazel
@@ -1297,6 +1297,25 @@ ) cc_library( + name = "safe_stringops", + hdrs = ["safe_stringops.h"], + include_prefix = "tink/internal", + deps = [ + ":call_with_core_dump_protection", + "@boringssl//:crypto", + ], +) + +cc_test( + name = "safe_stringops_test", + srcs = ["safe_stringops_test.cc"], + deps = [ + ":safe_stringops", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( name = "bn_encoding_util", srcs = ["bn_encoding_util.cc"], hdrs = ["bn_encoding_util.h"],
diff --git a/cc/internal/CMakeLists.txt b/cc/internal/CMakeLists.txt index 6a7b393..4b807f9 100644 --- a/cc/internal/CMakeLists.txt +++ b/cc/internal/CMakeLists.txt
@@ -1244,6 +1244,24 @@ ) tink_cc_library( + NAME safe_stringops + SRCS + safe_stringops.h + DEPS + crypto + tink::internal::call_with_core_dump_protection +) + +tink_cc_test( + NAME safe_stringops_test + SRCS + safe_stringops_test.cc + DEPS + tink::internal::safe_stringops + gmock +) + +tink_cc_library( NAME bn_encoding_util SRCS bn_encoding_util.cc
diff --git a/cc/internal/safe_stringops.h b/cc/internal/safe_stringops.h new file mode 100644 index 0000000..6a341d5 --- /dev/null +++ b/cc/internal/safe_stringops.h
@@ -0,0 +1,55 @@ +// 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_INTERNAL_SAFE_STRINGOPS_H_ +#define TINK_INTERNAL_SAFE_STRINGOPS_H_ + +#include <cstring> + +#include "tink/internal/call_with_core_dump_protection.h" +#include "openssl/crypto.h" + +namespace crypto { +namespace tink { +namespace internal { + +// Equivalents of regular memcpy/memmove, which do not leak contents of the +// arguments in the core dump. + +inline void* SafeMemCopy(void* dst, const void* src, size_t n) { + return CallWithCoreDumpProtection( + [dst, src, n]() { return memcpy(dst, src, n); }); +} + +inline void* SafeMemMove(void* dst, const void* src, size_t n) { + return CallWithCoreDumpProtection( + [dst, src, n]() { return memmove(dst, src, n); }); +} + +// Test equality of two memory areas. +// Not only protects from leaking any info about the contents in the core dump, +// but also is safe for crypto material (const time). + +inline int SafeCryptoMemEquals(const void* s1, const void* s2, size_t n) { + return CallWithCoreDumpProtection( + [s1, s2, n]() { return CRYPTO_memcmp(s1, s2, n) == 0; }); +} + +} // namespace internal +} // namespace tink +} // namespace crypto + +#endif // TINK_INTERNAL_SAFE_STRINGOPS_H_
diff --git a/cc/internal/safe_stringops_test.cc b/cc/internal/safe_stringops_test.cc new file mode 100644 index 0000000..f23dc66 --- /dev/null +++ b/cc/internal/safe_stringops_test.cc
@@ -0,0 +1,89 @@ +// 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/internal/safe_stringops.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace crypto { +namespace tink { +namespace internal { +namespace { + +using ::testing::StrEq; + +TEST(MemCopyTest, Regular) { + char src[] = "hello"; + char dst[] = "world"; + EXPECT_THAT(dst, StrEq("world")); + static_assert(sizeof(src) == sizeof(dst), "size mismatch"); + EXPECT_EQ(SafeMemCopy(dst, src, sizeof(src)), dst); + EXPECT_THAT(dst, StrEq("hello")); +} + +TEST(MemMoveTest, Regular) { + char src[] = "hello"; + char dst[] = "world"; + EXPECT_THAT(dst, StrEq("world")); + static_assert(sizeof(src) == sizeof(dst), "size mismatch"); + EXPECT_EQ(SafeMemMove(dst, src, sizeof(src)), dst); + EXPECT_THAT(dst, StrEq("hello")); +} + +TEST(MemMoveTest, NoMove) { + char mem[] = "hello"; + EXPECT_THAT(mem, StrEq("hello")); + EXPECT_EQ(SafeMemMove(mem, mem, sizeof(mem)), mem); + EXPECT_THAT(mem, StrEq("hello")); +} + +TEST(MemmoveTest, OverlapSuffix) { + char mem[] = "hello"; + EXPECT_THAT(mem, StrEq("hello")); + EXPECT_EQ(SafeMemMove(&mem[1], mem, sizeof(mem) - 2), &mem[1]); + EXPECT_THAT(mem, StrEq("hhell")); +} + +TEST(MemMoveTest, OverlapPrefix) { + char mem[] = "hello"; + EXPECT_THAT(mem, StrEq("hello")); + EXPECT_EQ(SafeMemMove(mem, &mem[1], sizeof(mem) - 2), mem); + EXPECT_THAT(mem, StrEq("elloo")); +} + +TEST(MemEqualsTest, Equal) { + char a[] = "hello"; + char b[] = "hello"; + EXPECT_NE(a, b); + static_assert(sizeof(a) == sizeof(b), "size mismatch"); + EXPECT_TRUE(SafeCryptoMemEquals(a, b, sizeof(a))); + EXPECT_TRUE(SafeCryptoMemEquals(b, a, sizeof(a))); +} + +TEST(MemEqualsTest, Unequal) { + char a[] = "hello"; + char b[] = "hellu"; + EXPECT_NE(a, b); + static_assert(sizeof(a) == sizeof(b), "size mismatch"); + EXPECT_FALSE(SafeCryptoMemEquals(a, b, sizeof(a))); + EXPECT_FALSE(SafeCryptoMemEquals(b, a, sizeof(a))); +} + +} // namespace +} // namespace internal +} // namespace tink +} // namespace crypto
diff --git a/cc/jwt/BUILD.bazel b/cc/jwt/BUILD.bazel index ed25b79..8136429 100644 --- a/cc/jwt/BUILD.bazel +++ b/cc/jwt/BUILD.bazel
@@ -210,6 +210,102 @@ ], ) +cc_library( + name = "jwt_hmac_parameters", + srcs = ["jwt_hmac_parameters.cc"], + hdrs = ["jwt_hmac_parameters.h"], + include_prefix = "tink/jwt", + deps = [ + ":jwt_mac_parameters", + "//:parameters", + "//util:status", + "//util:statusor", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", + ], +) + +cc_library( + name = "jwt_hmac_key", + srcs = ["jwt_hmac_key.cc"], + hdrs = ["jwt_hmac_key.h"], + include_prefix = "tink/jwt", + deps = [ + ":jwt_hmac_parameters", + ":jwt_mac_key", + "//:key", + "//:partial_key_access_token", + "//:restricted_data", + "//util:status", + "//util:statusor", + "@com_google_absl//absl/base:endian", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:string_view", + "@com_google_absl//absl/types:optional", + ], +) + +cc_library( + name = "jwt_hmac_proto_serialization", + srcs = ["jwt_hmac_proto_serialization.cc"], + hdrs = ["jwt_hmac_proto_serialization.h"], + include_prefix = "tink/jwt", + deps = [ + ":jwt_hmac_key", + ":jwt_hmac_parameters", + "//:partial_key_access", + "//:restricted_data", + "//:secret_key_access_token", + "//internal:key_parser", + "//internal:key_serializer", + "//internal:mutable_serialization_registry", + "//internal:parameters_parser", + "//internal:parameters_serializer", + "//internal:proto_key_serialization", + "//internal:proto_parameters_serialization", + "//proto:common_cc_proto", + "//proto:jwt_hmac_cc_proto", + "//proto:tink_cc_proto", + "//util:status", + "//util:statusor", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings:string_view", + "@com_google_absl//absl/types:optional", + ], +) + +cc_library( + name = "jwt_signature_parameters", + hdrs = ["jwt_signature_parameters.h"], + include_prefix = "tink/jwt", + deps = ["//:parameters"], +) + +cc_library( + name = "jwt_signature_public_key", + hdrs = ["jwt_signature_public_key.h"], + include_prefix = "tink/jwt", + deps = [ + ":jwt_signature_parameters", + "//:key", + "@com_google_absl//absl/types:optional", + ], +) + +cc_library( + name = "jwt_signature_private_key", + hdrs = ["jwt_signature_private_key.h"], + include_prefix = "tink/jwt", + deps = [ + ":jwt_signature_parameters", + ":jwt_signature_public_key", + "//:key", + "//:private_key", + "@com_google_absl//absl/types:optional", + ], +) + # tests cc_test( @@ -363,3 +459,60 @@ "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "jwt_hmac_parameters_test", + srcs = ["jwt_hmac_parameters_test.cc"], + deps = [ + ":jwt_hmac_parameters", + "//util:statusor", + "//util:test_matchers", + "@com_google_absl//absl/status", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "jwt_hmac_key_test", + srcs = ["jwt_hmac_key_test.cc"], + deps = [ + ":jwt_hmac_key", + ":jwt_hmac_parameters", + "//:partial_key_access", + "//:restricted_data", + "//util:statusor", + "//util:test_matchers", + "@com_google_absl//absl/status", + "@com_google_absl//absl/types:optional", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "jwt_hmac_proto_serialization_test", + srcs = ["jwt_hmac_proto_serialization_test.cc"], + deps = [ + ":jwt_hmac_key", + ":jwt_hmac_parameters", + ":jwt_hmac_proto_serialization", + "//:insecure_secret_key_access", + "//:key", + "//:parameters", + "//:partial_key_access", + "//:restricted_data", + "//internal:mutable_serialization_registry", + "//internal:proto_key_serialization", + "//internal:proto_parameters_serialization", + "//internal:serialization", + "//proto:common_cc_proto", + "//proto:jwt_hmac_cc_proto", + "//proto:tink_cc_proto", + "//subtle:random", + "//util:statusor", + "//util:test_matchers", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings:string_view", + "@com_google_absl//absl/types:optional", + "@com_google_googletest//:gtest_main", + ], +)
diff --git a/cc/jwt/CMakeLists.txt b/cc/jwt/CMakeLists.txt index 8024aa6..637a66b 100644 --- a/cc/jwt/CMakeLists.txt +++ b/cc/jwt/CMakeLists.txt
@@ -190,6 +190,98 @@ tink::core::key ) +tink_cc_library( + NAME jwt_hmac_parameters + SRCS + jwt_hmac_parameters.cc + jwt_hmac_parameters.h + DEPS + tink::jwt::jwt_mac_parameters + absl::status + absl::strings + tink::core::parameters + tink::util::status + tink::util::statusor +) + +tink_cc_library( + NAME jwt_hmac_key + SRCS + jwt_hmac_key.cc + jwt_hmac_key.h + DEPS + tink::jwt::jwt_hmac_parameters + tink::jwt::jwt_mac_key + absl::endian + absl::status + absl::strings + absl::string_view + absl::optional + tink::core::key + tink::core::partial_key_access_token + tink::core::restricted_data + tink::util::status + tink::util::statusor +) + +tink_cc_library( + NAME jwt_hmac_proto_serialization + SRCS + jwt_hmac_proto_serialization.cc + jwt_hmac_proto_serialization.h + DEPS + tink::jwt::jwt_hmac_key + tink::jwt::jwt_hmac_parameters + absl::status + absl::string_view + absl::optional + tink::core::partial_key_access + tink::core::restricted_data + tink::core::secret_key_access_token + tink::internal::key_parser + tink::internal::key_serializer + tink::internal::mutable_serialization_registry + tink::internal::parameters_parser + tink::internal::parameters_serializer + tink::internal::proto_key_serialization + tink::internal::proto_parameters_serialization + tink::util::status + tink::util::statusor + tink::proto::common_cc_proto + tink::proto::jwt_hmac_cc_proto + tink::proto::tink_cc_proto +) + +tink_cc_library( + NAME jwt_signature_parameters + SRCS + jwt_signature_parameters.h + DEPS + tink::core::parameters +) + +tink_cc_library( + NAME jwt_signature_public_key + SRCS + jwt_signature_public_key.h + DEPS + tink::jwt::jwt_signature_parameters + absl::optional + tink::core::key +) + +tink_cc_library( + NAME jwt_signature_private_key + SRCS + jwt_signature_private_key.h + DEPS + tink::jwt::jwt_signature_parameters + tink::jwt::jwt_signature_public_key + absl::optional + tink::core::key + tink::core::private_key +) + # tests tink_cc_test( @@ -338,3 +430,60 @@ tink::internal::fips_utils tink::util::test_matchers ) + +tink_cc_test( + NAME jwt_hmac_parameters_test + SRCS + jwt_hmac_parameters_test.cc + DEPS + tink::jwt::jwt_hmac_parameters + gmock + absl::status + tink::util::statusor + tink::util::test_matchers +) + +tink_cc_test( + NAME jwt_hmac_key_test + SRCS + jwt_hmac_key_test.cc + DEPS + tink::jwt::jwt_hmac_key + tink::jwt::jwt_hmac_parameters + gmock + absl::status + absl::optional + tink::core::partial_key_access + tink::core::restricted_data + tink::util::statusor + tink::util::test_matchers +) + +tink_cc_test( + NAME jwt_hmac_proto_serialization_test + SRCS + jwt_hmac_proto_serialization_test.cc + DEPS + tink::jwt::jwt_hmac_key + tink::jwt::jwt_hmac_parameters + tink::jwt::jwt_hmac_proto_serialization + gmock + absl::status + absl::string_view + absl::optional + tink::core::insecure_secret_key_access + tink::core::key + tink::core::parameters + tink::core::partial_key_access + tink::core::restricted_data + tink::internal::mutable_serialization_registry + tink::internal::proto_key_serialization + tink::internal::proto_parameters_serialization + tink::internal::serialization + tink::subtle::random + tink::util::statusor + tink::util::test_matchers + tink::proto::common_cc_proto + tink::proto::jwt_hmac_cc_proto + tink::proto::tink_cc_proto +)
diff --git a/cc/jwt/jwt_hmac_key.cc b/cc/jwt/jwt_hmac_key.cc new file mode 100644 index 0000000..cd191a5 --- /dev/null +++ b/cc/jwt/jwt_hmac_key.cc
@@ -0,0 +1,151 @@ +// 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/jwt/jwt_hmac_key.h" + +#include <string> + +#include "absl/base/internal/endian.h" +#include "absl/status/status.h" +#include "absl/strings/escaping.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "tink/jwt/jwt_hmac_parameters.h" +#include "tink/key.h" +#include "tink/partial_key_access_token.h" +#include "tink/restricted_data.h" +#include "tink/util/status.h" +#include "tink/util/statusor.h" + +namespace crypto { +namespace tink { + +JwtHmacKey::Builder& JwtHmacKey::Builder::SetParameters( + const JwtHmacParameters& parameters) { + parameters_ = parameters; + return *this; +} + +JwtHmacKey::Builder& JwtHmacKey::Builder::SetKeyBytes( + const RestrictedData& key_bytes) { + key_bytes_ = key_bytes; + return *this; +} + +JwtHmacKey::Builder& JwtHmacKey::Builder::SetIdRequirement(int id_requirement) { + id_requirement_ = id_requirement; + return *this; +} + +JwtHmacKey::Builder& JwtHmacKey::Builder::SetCustomKid( + absl::string_view custom_kid) { + custom_kid_ = custom_kid.data(); + return *this; +} + +util::StatusOr<absl::optional<std::string>> JwtHmacKey::Builder::ComputeKid() { + switch (parameters_->GetKidStrategy()) { + case JwtHmacParameters::KidStrategy::kBase64EncodedKeyId: { + if (custom_kid_.has_value()) { + return util::Status( + absl::StatusCode::kInvalidArgument, + "Custom kid must not be set for KidStrategy::kBase64EncodedKeyId."); + } + std::string base64_kid; + char buffer[4]; + absl::big_endian::Store32(buffer, *id_requirement_); + absl::WebSafeBase64Escape(absl::string_view(buffer, 4), &base64_kid); + return base64_kid; + } + case JwtHmacParameters::KidStrategy::kCustom: { + if (!custom_kid_.has_value()) { + return util::Status(absl::StatusCode::kInvalidArgument, + "Custom kid must be set for KidStrategy::kCustom."); + } + return custom_kid_; + } + case JwtHmacParameters::KidStrategy::kIgnored: { + if (custom_kid_.has_value()) { + return util::Status( + absl::StatusCode::kInvalidArgument, + "Custom kid must not be set for KidStrategy::kIgnored."); + } + return absl::nullopt; + } + default: + // Should be unreachable if all valid kid strategies have been handled. + return util::Status(absl::StatusCode::kFailedPrecondition, + "Unknown kid strategy."); + } +} + +util::StatusOr<JwtHmacKey> JwtHmacKey::Builder::Build( + PartialKeyAccessToken token) { + if (!parameters_.has_value()) { + return util::Status(absl::StatusCode::kInvalidArgument, + "JWT HMAC parameters must be specified."); + } + if (!key_bytes_.has_value()) { + return util::Status(absl::StatusCode::kInvalidArgument, + "JWT HMAC key bytes must be specified."); + } + if (parameters_->KeySizeInBytes() != key_bytes_->size()) { + return util::Status( + absl::StatusCode::kInvalidArgument, + "Actual JWT HMAC key size does not match size specified in " + "the 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<absl::optional<std::string>> kid = ComputeKid(); + if (!kid.ok()) { + return kid.status(); + } + return JwtHmacKey(*parameters_, *key_bytes_, id_requirement_, *kid); +} + +bool JwtHmacKey::operator==(const Key& other) const { + const JwtHmacKey* that = dynamic_cast<const JwtHmacKey*>(&other); + if (that == nullptr) { + return false; + } + if (parameters_ != that->parameters_) { + return false; + } + if (key_bytes_ != that->key_bytes_) { + return false; + } + if (id_requirement_ != that->id_requirement_) { + return false; + } + if (kid_ != that->kid_) { + return false; + } + return true; +} + +} // namespace tink +} // namespace crypto
diff --git a/cc/jwt/jwt_hmac_key.h b/cc/jwt/jwt_hmac_key.h new file mode 100644 index 0000000..7ab3360 --- /dev/null +++ b/cc/jwt/jwt_hmac_key.h
@@ -0,0 +1,107 @@ +// 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_JWT_JWT_HMAC_KEY_H_ +#define TINK_JWT_JWT_HMAC_KEY_H_ + +#include <string> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "tink/jwt/jwt_hmac_parameters.h" +#include "tink/jwt/jwt_mac_key.h" +#include "tink/key.h" +#include "tink/partial_key_access_token.h" +#include "tink/restricted_data.h" +#include "tink/util/statusor.h" + +namespace crypto { +namespace tink { + +// Represents functions to authenticate and verify JWTs using HMAC. +class JwtHmacKey : public JwtMacKey { + public: + // Creates JWT HMAC key instances. + class Builder { + public: + // Copyable and movable. + Builder(const Builder& other) = default; + Builder& operator=(const Builder& other) = default; + Builder(Builder&& other) = default; + Builder& operator=(Builder&& other) = default; + + // Creates initially empty parameters builder. + Builder() = default; + + Builder& SetParameters(const JwtHmacParameters& parameters); + Builder& SetKeyBytes(const RestrictedData& key_bytes); + Builder& SetIdRequirement(int id_requirement); + Builder& SetCustomKid(absl::string_view custom_kid); + + // Creates JWT HMAC key object from this builder. + util::StatusOr<JwtHmacKey> Build(PartialKeyAccessToken token); + + private: + util::StatusOr<absl::optional<std::string>> ComputeKid(); + + absl::optional<JwtHmacParameters> parameters_; + absl::optional<RestrictedData> key_bytes_; + absl::optional<int> id_requirement_; + absl::optional<std::string> custom_kid_; + }; + + // Copyable and movable. + JwtHmacKey(const JwtHmacKey& other) = default; + JwtHmacKey& operator=(const JwtHmacKey& other) = default; + JwtHmacKey(JwtHmacKey&& other) = default; + JwtHmacKey& operator=(JwtHmacKey&& other) = default; + + const RestrictedData& GetKeyBytes(PartialKeyAccessToken token) const { + return key_bytes_; + } + + const JwtHmacParameters& GetParameters() const override { + return parameters_; + } + + absl::optional<int> GetIdRequirement() const override { + return id_requirement_; + } + + absl::optional<std::string> GetKid() const override { return kid_; } + + bool operator==(const Key& other) const override; + + private: + JwtHmacKey(const JwtHmacParameters& parameters, + const RestrictedData& key_bytes, + absl::optional<int> id_requirement, + absl::optional<std::string> kid) + : parameters_(parameters), + key_bytes_(key_bytes), + id_requirement_(id_requirement), + kid_(kid) {} + + JwtHmacParameters parameters_; + RestrictedData key_bytes_; + absl::optional<int> id_requirement_; + absl::optional<std::string> kid_; +}; + +} // namespace tink +} // namespace crypto + +#endif // TINK_JWT_JWT_HMAC_KEY_H_
diff --git a/cc/jwt/jwt_hmac_key_test.cc b/cc/jwt/jwt_hmac_key_test.cc new file mode 100644 index 0000000..e64035c --- /dev/null +++ b/cc/jwt/jwt_hmac_key_test.cc
@@ -0,0 +1,418 @@ +// 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/jwt/jwt_hmac_key.h" + +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/status/status.h" +#include "absl/types/optional.h" +#include "tink/jwt/jwt_hmac_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::StatusIs; +using ::testing::Eq; +using ::testing::HasSubstr; +using ::testing::TestWithParam; +using ::testing::Values; + +struct TestCase { + int key_size_in_bytes; + JwtHmacParameters::KidStrategy kid_strategy; + JwtHmacParameters::Algorithm algorithm; + absl::optional<std::string> custom_kid; + absl::optional<int> id_requirement; + absl::optional<std::string> expected_kid; +}; + +using JwtHmacKeyTest = TestWithParam<TestCase>; + +INSTANTIATE_TEST_SUITE_P( + JwtHmacKeyTestSuite, JwtHmacKeyTest, + Values(TestCase{/*key_size_in_bytes=*/16, + JwtHmacParameters::KidStrategy::kBase64EncodedKeyId, + JwtHmacParameters::Algorithm::kHs256, + /*custom_kid=*/absl::nullopt, /*id_requirement=*/123, + /*expected_kid=*/"AAAAew"}, + TestCase{/*key_size_in_bytes=*/32, + JwtHmacParameters::KidStrategy::kCustom, + JwtHmacParameters::Algorithm::kHs384, + /*custom_kid=*/"custom_kid", + /*id_requirement=*/absl::nullopt, + /*expected_kid=*/"custom_kid"}, + TestCase{/*key_size_in_bytes=*/32, + JwtHmacParameters::KidStrategy::kIgnored, + JwtHmacParameters::Algorithm::kHs512, + /*custom_kid=*/absl::nullopt, + /*id_requirement=*/absl::nullopt, + /*expected_kid=*/absl::nullopt})); + +TEST_P(JwtHmacKeyTest, CreateSucceeds) { + TestCase test_case = GetParam(); + + util::StatusOr<JwtHmacParameters> params = JwtHmacParameters::Create( + test_case.key_size_in_bytes, test_case.kid_strategy, test_case.algorithm); + ASSERT_THAT(params, IsOk()); + + RestrictedData secret = RestrictedData(test_case.key_size_in_bytes); + JwtHmacKey::Builder builder = + JwtHmacKey::Builder().SetParameters(*params).SetKeyBytes(secret); + if (test_case.id_requirement.has_value()) { + builder.SetIdRequirement(*test_case.id_requirement); + } + if (test_case.custom_kid.has_value()) { + builder.SetCustomKid(*test_case.custom_kid); + } + util::StatusOr<JwtHmacKey> key = builder.Build(GetPartialKeyAccess()); + ASSERT_THAT(key, IsOk()); + + EXPECT_THAT(key->GetParameters(), Eq(*params)); + EXPECT_THAT(key->GetKeyBytes(GetPartialKeyAccess()), Eq(secret)); + EXPECT_THAT(key->GetIdRequirement(), Eq(test_case.id_requirement)); + EXPECT_THAT(key->GetKid(), Eq(test_case.expected_kid)); +} + +TEST(JwtHmacKeyTest, CreateKeyWithMismatchedKeySizeFails) { + // Key size parameter is 32 bytes. + util::StatusOr<JwtHmacParameters> params = JwtHmacParameters::Create( + /*key_size_in_bytes=*/32, + JwtHmacParameters::KidStrategy::kBase64EncodedKeyId, + JwtHmacParameters::Algorithm::kHs256); + ASSERT_THAT(params, IsOk()); + + // Key material is 16 bytes (another valid key length). + RestrictedData mismatched_secret = RestrictedData(/*num_random_bytes=*/16); + JwtHmacKey::Builder builder = JwtHmacKey::Builder() + .SetParameters(*params) + .SetKeyBytes(mismatched_secret) + .SetIdRequirement(123); + + EXPECT_THAT(builder.Build(GetPartialKeyAccess()).status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Actual JWT HMAC key size does not match"))); +} + +TEST(JwtHmacKeyTest, CreateKeyWithoutKeyBytesFails) { + util::StatusOr<JwtHmacParameters> params = JwtHmacParameters::Create( + /*key_size_in_bytes=*/32, + JwtHmacParameters::KidStrategy::kBase64EncodedKeyId, + JwtHmacParameters::Algorithm::kHs256); + ASSERT_THAT(params, IsOk()); + + JwtHmacKey::Builder builder = + JwtHmacKey::Builder().SetParameters(*params).SetIdRequirement(123); + + EXPECT_THAT(builder.Build(GetPartialKeyAccess()).status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("JWT HMAC key bytes must be specified"))); +} + +TEST(JwtHmacKeyTest, CreateKeyWithoutParametersFails) { + util::StatusOr<JwtHmacParameters> params = JwtHmacParameters::Create( + /*key_size_in_bytes=*/32, + JwtHmacParameters::KidStrategy::kBase64EncodedKeyId, + JwtHmacParameters::Algorithm::kHs256); + ASSERT_THAT(params, IsOk()); + + RestrictedData secret = RestrictedData(/*num_random_bytes=*/32); + JwtHmacKey::Builder builder = + JwtHmacKey::Builder().SetKeyBytes(secret).SetIdRequirement(123); + + EXPECT_THAT(builder.Build(GetPartialKeyAccess()).status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("JWT HMAC parameters must be specified"))); +} + +TEST(JwtHmacKeyTest, CreateBase64EncodedKidWithoutIdRequirementFails) { + util::StatusOr<JwtHmacParameters> params = JwtHmacParameters::Create( + /*key_size_in_bytes=*/32, + JwtHmacParameters::KidStrategy::kBase64EncodedKeyId, + JwtHmacParameters::Algorithm::kHs256); + ASSERT_THAT(params, IsOk()); + + RestrictedData secret = RestrictedData(/*num_random_bytes=*/32); + JwtHmacKey::Builder builder = + JwtHmacKey::Builder().SetParameters(*params).SetKeyBytes(secret); + + EXPECT_THAT(builder.Build(GetPartialKeyAccess()).status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Cannot create key without ID requirement " + "with parameters with ID requirement"))); +} + +TEST(JwtHmacKeyTest, CreateBase64EncodedKidWithCustomKidFails) { + util::StatusOr<JwtHmacParameters> params = JwtHmacParameters::Create( + /*key_size_in_bytes=*/32, + JwtHmacParameters::KidStrategy::kBase64EncodedKeyId, + JwtHmacParameters::Algorithm::kHs256); + ASSERT_THAT(params, IsOk()); + + RestrictedData secret = RestrictedData(/*num_random_bytes=*/32); + JwtHmacKey::Builder builder = JwtHmacKey::Builder() + .SetParameters(*params) + .SetKeyBytes(secret) + .SetIdRequirement(123) + .SetCustomKid("custom_kid"); + + EXPECT_THAT(builder.Build(GetPartialKeyAccess()).status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Custom kid must not be set for " + "KidStrategy::kBase64EncodedKeyId"))); +} + +TEST(JwtHmacKeyTest, CreateCustomKidWithIdRequirementFails) { + util::StatusOr<JwtHmacParameters> params = JwtHmacParameters::Create( + /*key_size_in_bytes=*/32, JwtHmacParameters::KidStrategy::kCustom, + JwtHmacParameters::Algorithm::kHs256); + ASSERT_THAT(params, IsOk()); + + RestrictedData secret = RestrictedData(/*num_random_bytes=*/32); + JwtHmacKey::Builder builder = JwtHmacKey::Builder() + .SetParameters(*params) + .SetKeyBytes(secret) + .SetCustomKid("custom_kid") + .SetIdRequirement(123); + + EXPECT_THAT(builder.Build(GetPartialKeyAccess()).status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Cannot create key with ID requirement with " + "parameters without ID requirement"))); +} + +TEST(JwtHmacKeyTest, CreateCustomKidWithoutCustomKidFails) { + util::StatusOr<JwtHmacParameters> params = JwtHmacParameters::Create( + /*key_size_in_bytes=*/32, JwtHmacParameters::KidStrategy::kCustom, + JwtHmacParameters::Algorithm::kHs256); + ASSERT_THAT(params, IsOk()); + + RestrictedData secret = RestrictedData(/*num_random_bytes=*/32); + JwtHmacKey::Builder builder = + JwtHmacKey::Builder().SetParameters(*params).SetKeyBytes(secret); + + EXPECT_THAT(builder.Build(GetPartialKeyAccess()).status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Custom kid must be set"))); +} + +TEST(JwtHmacKeyTest, CreateIgnoredKidWithIdRequirementFails) { + util::StatusOr<JwtHmacParameters> params = JwtHmacParameters::Create( + /*key_size_in_bytes=*/32, JwtHmacParameters::KidStrategy::kIgnored, + JwtHmacParameters::Algorithm::kHs256); + ASSERT_THAT(params, IsOk()); + + RestrictedData secret = RestrictedData(/*num_random_bytes=*/32); + JwtHmacKey::Builder builder = JwtHmacKey::Builder() + .SetParameters(*params) + .SetKeyBytes(secret) + .SetIdRequirement(123); + + EXPECT_THAT(builder.Build(GetPartialKeyAccess()).status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Cannot create key with ID requirement with " + "parameters without ID requirement"))); +} + +TEST(JwtHmacKeyTest, CreateIgnoredKidWithCustomKidFails) { + util::StatusOr<JwtHmacParameters> params = JwtHmacParameters::Create( + /*key_size_in_bytes=*/32, JwtHmacParameters::KidStrategy::kIgnored, + JwtHmacParameters::Algorithm::kHs256); + ASSERT_THAT(params, IsOk()); + + RestrictedData secret = RestrictedData(/*num_random_bytes=*/32); + JwtHmacKey::Builder builder = JwtHmacKey::Builder() + .SetParameters(*params) + .SetKeyBytes(secret) + .SetCustomKid("custom_kid"); + + EXPECT_THAT( + builder.Build(GetPartialKeyAccess()).status(), + StatusIs( + absl::StatusCode::kInvalidArgument, + HasSubstr("Custom kid must not be set for KidStrategy::kIgnored"))); +} + +TEST_P(JwtHmacKeyTest, KeyEquals) { + TestCase test_case = GetParam(); + + util::StatusOr<JwtHmacParameters> params = JwtHmacParameters::Create( + test_case.key_size_in_bytes, test_case.kid_strategy, test_case.algorithm); + ASSERT_THAT(params, IsOk()); + + RestrictedData secret = RestrictedData(test_case.key_size_in_bytes); + JwtHmacKey::Builder builder = + JwtHmacKey::Builder().SetParameters(*params).SetKeyBytes(secret); + if (test_case.id_requirement.has_value()) { + builder.SetIdRequirement(*test_case.id_requirement); + } + if (test_case.custom_kid.has_value()) { + builder.SetCustomKid(*test_case.custom_kid); + } + util::StatusOr<JwtHmacKey> key = builder.Build(GetPartialKeyAccess()); + ASSERT_THAT(key, IsOk()); + + JwtHmacKey::Builder other_builder = + JwtHmacKey::Builder().SetParameters(*params).SetKeyBytes(secret); + if (test_case.id_requirement.has_value()) { + other_builder.SetIdRequirement(*test_case.id_requirement); + } + if (test_case.custom_kid.has_value()) { + other_builder.SetCustomKid(*test_case.custom_kid); + } + util::StatusOr<JwtHmacKey> other_key = + other_builder.Build(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(JwtHmacKeyTest, DifferentParametersNotEqual) { + util::StatusOr<JwtHmacParameters> params = JwtHmacParameters::Create( + /*key_size_in_bytes=*/32, + JwtHmacParameters::KidStrategy::kBase64EncodedKeyId, + JwtHmacParameters::Algorithm::kHs256); + ASSERT_THAT(params, IsOk()); + + util::StatusOr<JwtHmacParameters> other_params = JwtHmacParameters::Create( + /*key_size_in_bytes=*/32, + JwtHmacParameters::KidStrategy::kBase64EncodedKeyId, + JwtHmacParameters::Algorithm::kHs384); + ASSERT_THAT(other_params, IsOk()); + + RestrictedData secret = RestrictedData(/*num_random_bytes=*/32); + + util::StatusOr<JwtHmacKey> key = JwtHmacKey::Builder() + .SetParameters(*params) + .SetKeyBytes(secret) + .SetIdRequirement(123) + .Build(GetPartialKeyAccess()); + ASSERT_THAT(key, IsOk()); + + util::StatusOr<JwtHmacKey> other_key = JwtHmacKey::Builder() + .SetParameters(*other_params) + .SetKeyBytes(secret) + .SetIdRequirement(123) + .Build(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(JwtHmacKeyTest, DifferentSecretDataNotEqual) { + util::StatusOr<JwtHmacParameters> params = JwtHmacParameters::Create( + /*key_size_in_bytes=*/32, + JwtHmacParameters::KidStrategy::kBase64EncodedKeyId, + JwtHmacParameters::Algorithm::kHs256); + ASSERT_THAT(params, IsOk()); + + RestrictedData secret = RestrictedData(/*num_random_bytes=*/32); + RestrictedData other_secret = RestrictedData(/*num_random_bytes=*/32); + + util::StatusOr<JwtHmacKey> key = JwtHmacKey::Builder() + .SetParameters(*params) + .SetKeyBytes(secret) + .SetIdRequirement(123) + .Build(GetPartialKeyAccess()); + ASSERT_THAT(key, IsOk()); + + util::StatusOr<JwtHmacKey> other_key = JwtHmacKey::Builder() + .SetParameters(*params) + .SetKeyBytes(other_secret) + .SetIdRequirement(123) + .Build(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(JwtHmacKeyTest, DifferentIdRequirementNotEqual) { + util::StatusOr<JwtHmacParameters> params = JwtHmacParameters::Create( + /*key_size_in_bytes=*/32, + JwtHmacParameters::KidStrategy::kBase64EncodedKeyId, + JwtHmacParameters::Algorithm::kHs256); + ASSERT_THAT(params, IsOk()); + + RestrictedData secret = RestrictedData(/*num_random_bytes=*/32); + + util::StatusOr<JwtHmacKey> key = JwtHmacKey::Builder() + .SetParameters(*params) + .SetKeyBytes(secret) + .SetIdRequirement(123) + .Build(GetPartialKeyAccess()); + ASSERT_THAT(key, IsOk()); + + util::StatusOr<JwtHmacKey> other_key = JwtHmacKey::Builder() + .SetParameters(*params) + .SetKeyBytes(secret) + .SetIdRequirement(456) + .Build(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(JwtHmacKeyTest, DifferentCustomKidNotEqual) { + util::StatusOr<JwtHmacParameters> params = JwtHmacParameters::Create( + /*key_size_in_bytes=*/32, JwtHmacParameters::KidStrategy::kCustom, + JwtHmacParameters::Algorithm::kHs256); + ASSERT_THAT(params, IsOk()); + + RestrictedData secret = RestrictedData(/*num_random_bytes=*/32); + + util::StatusOr<JwtHmacKey> key = JwtHmacKey::Builder() + .SetParameters(*params) + .SetKeyBytes(secret) + .SetCustomKid("custom_kid") + .Build(GetPartialKeyAccess()); + ASSERT_THAT(key, IsOk()); + + util::StatusOr<JwtHmacKey> other_key = JwtHmacKey::Builder() + .SetParameters(*params) + .SetKeyBytes(secret) + .SetCustomKid("other_custom_kid") + .Build(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
diff --git a/cc/jwt/jwt_hmac_parameters.cc b/cc/jwt/jwt_hmac_parameters.cc new file mode 100644 index 0000000..ae7a332 --- /dev/null +++ b/cc/jwt/jwt_hmac_parameters.cc
@@ -0,0 +1,77 @@ +// 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/jwt/jwt_hmac_parameters.h" + +#include <set> + +#include "absl/status/status.h" +#include "absl/strings/str_cat.h" +#include "tink/parameters.h" +#include "tink/util/status.h" +#include "tink/util/statusor.h" + +namespace crypto { +namespace tink { + +util::StatusOr<JwtHmacParameters> JwtHmacParameters::Create( + int key_size_in_bytes, KidStrategy kid_strategy, Algorithm algorithm) { + if (key_size_in_bytes < 16) { + return util::Status( + absl::StatusCode::kInvalidArgument, + absl::StrCat("Key size should be at least 16 bytes, got ", + key_size_in_bytes, " bytes.")); + } + static const std::set<KidStrategy>* kSupportedKidStrategies = + new std::set<KidStrategy>({KidStrategy::kBase64EncodedKeyId, + KidStrategy::kIgnored, KidStrategy::kCustom}); + if (kSupportedKidStrategies->find(kid_strategy) == + kSupportedKidStrategies->end()) { + return util::Status( + absl::StatusCode::kInvalidArgument, + "Cannot create JWT HMAC parameters with unknown kid strategy."); + } + static const std::set<Algorithm>* kSupportedAlgorithms = + new std::set<Algorithm>( + {Algorithm::kHs256, Algorithm::kHs384, Algorithm::kHs512}); + if (kSupportedAlgorithms->find(algorithm) == kSupportedAlgorithms->end()) { + return util::Status( + absl::StatusCode::kInvalidArgument, + "Cannot create JWT HMAC parameters with unknown algorithm."); + } + return JwtHmacParameters(key_size_in_bytes, kid_strategy, algorithm); +} + +bool JwtHmacParameters::operator==(const Parameters& other) const { + const JwtHmacParameters* that = + dynamic_cast<const JwtHmacParameters*>(&other); + if (that == nullptr) { + return false; + } + if (key_size_in_bytes_ != that->key_size_in_bytes_) { + return false; + } + if (kid_strategy_ != that->kid_strategy_) { + return false; + } + if (algorithm_ != that->algorithm_) { + return false; + } + return true; +} + +} // namespace tink +} // namespace crypto
diff --git a/cc/jwt/jwt_hmac_parameters.h b/cc/jwt/jwt_hmac_parameters.h new file mode 100644 index 0000000..4755bd4 --- /dev/null +++ b/cc/jwt/jwt_hmac_parameters.h
@@ -0,0 +1,115 @@ +// 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_JWT_JWT_HMAC_PARAMETERS_H_ +#define TINK_JWT_JWT_HMAC_PARAMETERS_H_ + +#include "tink/jwt/jwt_mac_parameters.h" +#include "tink/parameters.h" +#include "tink/util/statusor.h" + +namespace crypto { +namespace tink { + +// Describes the parameters of an `JwtHmacKey`. +class JwtHmacParameters : public JwtMacParameters { + public: + // Strategy for handling the "kid" header. + enum class KidStrategy : int { + // The `kid` is the URL safe (RFC 4648 Section 5) base64-encoded big-endian + // `key_id` in the keyset. + // + // In `ComputeMacAndEncode()`, Tink always adds the `kid`. + // + // In `VerifyMacAndDecode()`, Tink checks that the `kid` is present and + // equal to this value. + // + // NOTE: This strategy is recommended by Tink. + kBase64EncodedKeyId = 1, + // The `kid` header is ignored. + // + // In `ComputeMacAndEncode()`, Tink does not write a `kid` header. + // + // In `VerifyMacAndDecode()`, Tink ignores the `kid` header. + kIgnored = 2, + // The `kid` is fixed. It can be obtained by calling `key.GetKid()`. + // + // In `ComputeMacAndEncode()`, Tink writes the `kid` header to the + // value given by `key.getCustomKid()`. + // + // In `VerifyMacAndDecode()`, if the `kid` is present, it must match + // `key.GetKid()`. If the `kid` is absent, it will be accepted. + // + // NOTE: Tink does not allow random generation of `JwtHmacKey` objects from + // parameters objects with `KidStrategy::kCustom`. + kCustom = 3, + kDoNotUseInsteadUseDefaultWhenWritingSwitchStatements = 20, + }; + + // MAC computation algorithm. + enum class Algorithm : int { + kHs256 = 1, + kHs384 = 2, + kHs512 = 3, + kDoNotUseInsteadUseDefaultWhenWritingSwitchStatements = 20, + }; + + // Copyable and movable. + JwtHmacParameters(const JwtHmacParameters& other) = default; + JwtHmacParameters& operator=(const JwtHmacParameters& other) = default; + JwtHmacParameters(JwtHmacParameters&& other) = default; + JwtHmacParameters& operator=(JwtHmacParameters&& other) = default; + + // Creates JWT HMAC parameters object. Returns an error status if + // `key_size_in_bytes` is less than 16 bytes, if `kid_strategy` is invalid, or + // if `algorithm` is invalid. + static util::StatusOr<JwtHmacParameters> Create(int key_size_in_bytes, + KidStrategy kid_strategy, + Algorithm algorithm); + + int KeySizeInBytes() const { return key_size_in_bytes_; } + + KidStrategy GetKidStrategy() const { return kid_strategy_; } + + Algorithm GetAlgorithm() const { return algorithm_; } + + bool AllowKidAbsent() const override { + return kid_strategy_ == KidStrategy::kCustom || + kid_strategy_ == KidStrategy::kIgnored; + } + + bool HasIdRequirement() const override { + return kid_strategy_ == KidStrategy::kBase64EncodedKeyId; + } + + bool operator==(const Parameters& other) const override; + + private: + JwtHmacParameters(int key_size_in_bytes, KidStrategy kid_strategy, + Algorithm algorithm) + : key_size_in_bytes_(key_size_in_bytes), + kid_strategy_(kid_strategy), + algorithm_(algorithm) {} + + int key_size_in_bytes_; + KidStrategy kid_strategy_; + Algorithm algorithm_; +}; + +} // namespace tink +} // namespace crypto + +#endif // TINK_JWT_JWT_HMAC_PARAMETERS_H_
diff --git a/cc/jwt/jwt_hmac_parameters_test.cc b/cc/jwt/jwt_hmac_parameters_test.cc new file mode 100644 index 0000000..0b25889 --- /dev/null +++ b/cc/jwt/jwt_hmac_parameters_test.cc
@@ -0,0 +1,222 @@ +// 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/jwt/jwt_hmac_parameters.h" + +#include <tuple> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/status/status.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::Combine; +using ::testing::Eq; +using ::testing::HasSubstr; +using ::testing::TestWithParam; +using ::testing::Values; + +struct KidStrategyTuple { + JwtHmacParameters::KidStrategy kid_strategy; + bool allowed_kid_absent; + bool has_id_requirement; +}; + +using JwtHmacParametersTest = TestWithParam< + std::tuple<int, KidStrategyTuple, JwtHmacParameters::Algorithm>>; + +INSTANTIATE_TEST_SUITE_P( + JwtHmacParametersTestSuite, JwtHmacParametersTest, + Combine(Values(16, 32), + Values( + KidStrategyTuple{ + JwtHmacParameters::KidStrategy::kBase64EncodedKeyId, + /*allowed_kid_absent=*/false, /*has_id_requirement=*/true}, + KidStrategyTuple{JwtHmacParameters::KidStrategy::kCustom, + /*allowed_kid_absent=*/true, + /*has_id_requirement=*/false}, + KidStrategyTuple{JwtHmacParameters::KidStrategy::kIgnored, + /*allowed_kid_absent=*/true, + /*has_id_requirement=*/false}), + Values(JwtHmacParameters::Algorithm::kHs256, + JwtHmacParameters::Algorithm::kHs384, + JwtHmacParameters::Algorithm::kHs512))); + +TEST_P(JwtHmacParametersTest, Create) { + int key_size_in_bytes; + KidStrategyTuple tuple; + JwtHmacParameters::Algorithm algorithm; + std::tie(key_size_in_bytes, tuple, algorithm) = GetParam(); + + util::StatusOr<JwtHmacParameters> parameters = JwtHmacParameters::Create( + key_size_in_bytes, tuple.kid_strategy, algorithm); + ASSERT_THAT(parameters, IsOk()); + + EXPECT_THAT(parameters->KeySizeInBytes(), Eq(key_size_in_bytes)); + EXPECT_THAT(parameters->GetKidStrategy(), Eq(tuple.kid_strategy)); + EXPECT_THAT(parameters->AllowKidAbsent(), Eq(tuple.allowed_kid_absent)); + EXPECT_THAT(parameters->HasIdRequirement(), Eq(tuple.has_id_requirement)); + EXPECT_THAT(parameters->GetAlgorithm(), Eq(algorithm)); +} + +TEST(JwtHmacParametersTest, CreateWithInvalidKidStrategyFails) { + EXPECT_THAT(JwtHmacParameters::Create( + /*key_size_in_bytes=*/16, + JwtHmacParameters::KidStrategy:: + kDoNotUseInsteadUseDefaultWhenWritingSwitchStatements, + JwtHmacParameters::Algorithm::kHs512) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("unknown kid strategy"))); +} + +TEST(JwtHmacParametersTest, CreateWithInvalidAlgorithmFails) { + EXPECT_THAT(JwtHmacParameters::Create( + /*key_size_in_bytes=*/16, + JwtHmacParameters::KidStrategy::kBase64EncodedKeyId, + JwtHmacParameters::Algorithm:: + kDoNotUseInsteadUseDefaultWhenWritingSwitchStatements) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("unknown algorithm"))); +} + +TEST(JwtHmacParametersTest, CreateWithInvalidKeySizeFails) { + EXPECT_THAT(JwtHmacParameters::Create( + /*key_size_in_bytes=*/15, + JwtHmacParameters::KidStrategy::kBase64EncodedKeyId, + JwtHmacParameters::Algorithm::kHs512) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Key size should be at least 16 bytes"))); +} + +TEST(JwtHmacParametersTest, CopyConstructor) { + util::StatusOr<JwtHmacParameters> parameters = JwtHmacParameters::Create( + /*key_size_in_bytes=*/16, + JwtHmacParameters::KidStrategy::kBase64EncodedKeyId, + JwtHmacParameters::Algorithm::kHs512); + ASSERT_THAT(parameters, IsOk()); + + JwtHmacParameters copy(*parameters); + + EXPECT_THAT(copy.KeySizeInBytes(), Eq(parameters->KeySizeInBytes())); + EXPECT_THAT(copy.GetKidStrategy(), Eq(parameters->GetKidStrategy())); + EXPECT_THAT(copy.GetAlgorithm(), Eq(parameters->GetAlgorithm())); + EXPECT_THAT(copy.AllowKidAbsent(), Eq(parameters->AllowKidAbsent())); + EXPECT_THAT(copy.HasIdRequirement(), Eq(parameters->HasIdRequirement())); +} + +TEST(JwtHmacParametersTest, CopyAssignment) { + util::StatusOr<JwtHmacParameters> parameters = JwtHmacParameters::Create( + /*key_size_in_bytes=*/16, + JwtHmacParameters::KidStrategy::kBase64EncodedKeyId, + JwtHmacParameters::Algorithm::kHs512); + ASSERT_THAT(parameters, IsOk()); + + JwtHmacParameters copy = *parameters; + + EXPECT_THAT(copy.KeySizeInBytes(), Eq(parameters->KeySizeInBytes())); + EXPECT_THAT(copy.GetKidStrategy(), Eq(parameters->GetKidStrategy())); + EXPECT_THAT(copy.GetAlgorithm(), Eq(parameters->GetAlgorithm())); + EXPECT_THAT(copy.AllowKidAbsent(), Eq(parameters->AllowKidAbsent())); + EXPECT_THAT(copy.HasIdRequirement(), Eq(parameters->HasIdRequirement())); +} + +TEST_P(JwtHmacParametersTest, ParametersEquals) { + int key_size_in_bytes; + KidStrategyTuple tuple; + JwtHmacParameters::Algorithm algorithm; + std::tie(key_size_in_bytes, tuple, algorithm) = GetParam(); + + util::StatusOr<JwtHmacParameters> parameters = JwtHmacParameters::Create( + key_size_in_bytes, tuple.kid_strategy, algorithm); + ASSERT_THAT(parameters, IsOk()); + + util::StatusOr<JwtHmacParameters> other_parameters = + JwtHmacParameters::Create(key_size_in_bytes, tuple.kid_strategy, + algorithm); + ASSERT_THAT(other_parameters, IsOk()); + + EXPECT_TRUE(*parameters == *other_parameters); + EXPECT_TRUE(*other_parameters == *parameters); + EXPECT_FALSE(*parameters != *other_parameters); + EXPECT_FALSE(*other_parameters != *parameters); +} + +TEST(JwtHmacParametersTest, KeySizeNotEqual) { + util::StatusOr<JwtHmacParameters> parameters = JwtHmacParameters::Create( + /*key_size_in_bytes=*/16, + JwtHmacParameters::KidStrategy::kBase64EncodedKeyId, + JwtHmacParameters::Algorithm::kHs256); + ASSERT_THAT(parameters, IsOk()); + + util::StatusOr<JwtHmacParameters> other_parameters = + JwtHmacParameters::Create( + /*key_size_in_bytes=*/32, + JwtHmacParameters::KidStrategy::kBase64EncodedKeyId, + JwtHmacParameters::Algorithm::kHs256); + ASSERT_THAT(other_parameters, IsOk()); + + EXPECT_TRUE(*parameters != *other_parameters); + EXPECT_FALSE(*parameters == *other_parameters); +} + +TEST(JwtHmacParametersTest, KidStrategyNotEqual) { + util::StatusOr<JwtHmacParameters> parameters = JwtHmacParameters::Create( + /*key_size_in_bytes=*/16, + JwtHmacParameters::KidStrategy::kBase64EncodedKeyId, + JwtHmacParameters::Algorithm::kHs256); + ASSERT_THAT(parameters, IsOk()); + + util::StatusOr<JwtHmacParameters> other_parameters = + JwtHmacParameters::Create( + /*key_size_in_bytes=*/16, JwtHmacParameters::KidStrategy::kCustom, + JwtHmacParameters::Algorithm::kHs256); + ASSERT_THAT(other_parameters, IsOk()); + + EXPECT_TRUE(*parameters != *other_parameters); + EXPECT_FALSE(*parameters == *other_parameters); +} + +TEST(JwtHmacParametersTest, AlgorithmNotEqual) { + util::StatusOr<JwtHmacParameters> parameters = JwtHmacParameters::Create( + /*key_size_in_bytes=*/16, + JwtHmacParameters::KidStrategy::kBase64EncodedKeyId, + JwtHmacParameters::Algorithm::kHs256); + ASSERT_THAT(parameters, IsOk()); + + util::StatusOr<JwtHmacParameters> other_parameters = + JwtHmacParameters::Create( + /*key_size_in_bytes=*/16, + JwtHmacParameters::KidStrategy::kBase64EncodedKeyId, + JwtHmacParameters::Algorithm::kHs384); + ASSERT_THAT(other_parameters, IsOk()); + + EXPECT_TRUE(*parameters != *other_parameters); + EXPECT_FALSE(*parameters == *other_parameters); +} + +} // namespace +} // namespace tink +} // namespace crypto
diff --git a/cc/jwt/jwt_hmac_proto_serialization.cc b/cc/jwt/jwt_hmac_proto_serialization.cc new file mode 100644 index 0000000..53fd660 --- /dev/null +++ b/cc/jwt/jwt_hmac_proto_serialization.cc
@@ -0,0 +1,326 @@ +// 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/jwt/jwt_hmac_proto_serialization.h" + +#include <string> + +#include "absl/status/status.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "tink/internal/key_parser.h" +#include "tink/internal/key_serializer.h" +#include "tink/internal/mutable_serialization_registry.h" +#include "tink/internal/parameters_parser.h" +#include "tink/internal/parameters_serializer.h" +#include "tink/internal/proto_key_serialization.h" +#include "tink/internal/proto_parameters_serialization.h" +#include "tink/jwt/jwt_hmac_key.h" +#include "tink/jwt/jwt_hmac_parameters.h" +#include "tink/partial_key_access.h" +#include "tink/restricted_data.h" +#include "tink/secret_key_access_token.h" +#include "tink/util/status.h" +#include "tink/util/statusor.h" +#include "proto/common.pb.h" +#include "proto/jwt_hmac.pb.h" +#include "proto/tink.pb.h" + +namespace crypto { +namespace tink { +namespace { + +using ::google::crypto::tink::JwtHmacAlgorithm; +using ::google::crypto::tink::JwtHmacKeyFormat; +using ::google::crypto::tink::OutputPrefixType; + +using JwtHmacProtoParametersParserImpl = + internal::ParametersParserImpl<internal::ProtoParametersSerialization, + JwtHmacParameters>; +using JwtHmacProtoParametersSerializerImpl = + internal::ParametersSerializerImpl<JwtHmacParameters, + internal::ProtoParametersSerialization>; +using JwtHmacProtoKeyParserImpl = + internal::KeyParserImpl<internal::ProtoKeySerialization, JwtHmacKey>; +using JwtHmacProtoKeySerializerImpl = + internal::KeySerializerImpl<JwtHmacKey, internal::ProtoKeySerialization>; + +const absl::string_view kTypeUrl = + "type.googleapis.com/google.crypto.tink.JwtHmacKey"; + +util::StatusOr<JwtHmacParameters::KidStrategy> ToKidStrategy( + OutputPrefixType output_prefix_type, bool has_custom_kid) { + switch (output_prefix_type) { + case OutputPrefixType::RAW: + if (has_custom_kid) { + return JwtHmacParameters::KidStrategy::kCustom; + } + return JwtHmacParameters::KidStrategy::kIgnored; + case OutputPrefixType::TINK: + return JwtHmacParameters::KidStrategy::kBase64EncodedKeyId; + default: + return util::Status(absl::StatusCode::kInvalidArgument, + "Invalid OutputPrefixType for JwtHmacKeyFormat."); + } +} + +util::StatusOr<OutputPrefixType> ToOutputPrefixType( + JwtHmacParameters::KidStrategy kid_strategy) { + switch (kid_strategy) { + case JwtHmacParameters::KidStrategy::kCustom: + return OutputPrefixType::RAW; + case JwtHmacParameters::KidStrategy::kIgnored: + return OutputPrefixType::RAW; + case JwtHmacParameters::KidStrategy::kBase64EncodedKeyId: + return OutputPrefixType::TINK; + default: + return util::Status( + absl::StatusCode::kInvalidArgument, + "Could not determine JwtHmacParameters::KidStrategy."); + } +} + +util::StatusOr<JwtHmacParameters::Algorithm> FromProtoAlgorithm( + JwtHmacAlgorithm algorithm) { + switch (algorithm) { + case JwtHmacAlgorithm::HS256: + return JwtHmacParameters::Algorithm::kHs256; + case JwtHmacAlgorithm::HS384: + return JwtHmacParameters::Algorithm::kHs384; + case JwtHmacAlgorithm::HS512: + return JwtHmacParameters::Algorithm::kHs512; + default: + return util::Status(absl::StatusCode::kInvalidArgument, + "Could not determine JwtHmacAlgorithm."); + } +} + +util::StatusOr<JwtHmacAlgorithm> ToProtoAlgorithm( + JwtHmacParameters::Algorithm algorithm) { + switch (algorithm) { + case JwtHmacParameters::Algorithm::kHs256: + return JwtHmacAlgorithm::HS256; + case JwtHmacParameters::Algorithm::kHs384: + return JwtHmacAlgorithm::HS384; + case JwtHmacParameters::Algorithm::kHs512: + return JwtHmacAlgorithm::HS512; + default: + return util::Status(absl::StatusCode::kInvalidArgument, + "Could not determine JwtHmacParameters::Algorithm"); + } +} + +util::StatusOr<JwtHmacParameters> ToParameters( + int key_size_in_bytes, OutputPrefixType output_prefix_type, + JwtHmacAlgorithm proto_algorithm, bool has_custom_kid) { + util::StatusOr<JwtHmacParameters::KidStrategy> kid_strategy = + ToKidStrategy(output_prefix_type, has_custom_kid); + if (!kid_strategy.ok()) { + return kid_strategy.status(); + } + util::StatusOr<JwtHmacParameters::Algorithm> algorithm = + FromProtoAlgorithm(proto_algorithm); + if (!algorithm.ok()) { + return algorithm.status(); + } + return JwtHmacParameters::Create(key_size_in_bytes, *kid_strategy, + *algorithm); +} + +util::StatusOr<JwtHmacParameters> ParseParameters( + const internal::ProtoParametersSerialization& serialization) { + if (serialization.GetKeyTemplate().type_url() != kTypeUrl) { + return util::Status(absl::StatusCode::kInvalidArgument, + "Wrong type URL when parsing JwtHmacParameters."); + } + JwtHmacKeyFormat proto_key_format; + if (!proto_key_format.ParseFromString( + serialization.GetKeyTemplate().value())) { + return util::Status(absl::StatusCode::kInvalidArgument, + "Failed to parse JwtHmacKeyFormat proto."); + } + if (proto_key_format.version() != 0) { + return util::Status( + absl::StatusCode::kInvalidArgument, + "Parsing JwtHmacParameters failed: only version 0 is accepted."); + } + + return ToParameters(proto_key_format.key_size(), + serialization.GetKeyTemplate().output_prefix_type(), + proto_key_format.algorithm(), /*has_custom_kid=*/false); +} + +util::StatusOr<internal::ProtoParametersSerialization> SerializeParameters( + const JwtHmacParameters& parameters) { + if (parameters.GetKidStrategy() == JwtHmacParameters::KidStrategy::kCustom) { + return util::Status( + absl::StatusCode::kInvalidArgument, + "Unable to serialize JwtHmacParameters::KidStrategy::kCustom."); + } + util::StatusOr<OutputPrefixType> output_prefix_type = + ToOutputPrefixType(parameters.GetKidStrategy()); + if (!output_prefix_type.ok()) { + return output_prefix_type.status(); + } + util::StatusOr<JwtHmacAlgorithm> proto_algorithm = + ToProtoAlgorithm(parameters.GetAlgorithm()); + if (!proto_algorithm.ok()) { + return proto_algorithm.status(); + } + + JwtHmacKeyFormat format; + format.set_version(0); + format.set_key_size(parameters.KeySizeInBytes()); + format.set_algorithm(*proto_algorithm); + + return internal::ProtoParametersSerialization::Create( + kTypeUrl, *output_prefix_type, format.SerializeAsString()); +} + +util::StatusOr<JwtHmacKey> ParseKey( + const internal::ProtoKeySerialization& serialization, + absl::optional<SecretKeyAccessToken> token) { + if (!token.has_value()) { + return util::Status(absl::StatusCode::kInvalidArgument, + "SecretKeyAccess is required."); + } + if (serialization.TypeUrl() != kTypeUrl) { + return util::Status(absl::StatusCode::kInvalidArgument, + "Wrong type URL when parsing JwtHmacKey."); + } + + google::crypto::tink::JwtHmacKey proto_key; + const RestrictedData& restricted_data = serialization.SerializedKeyProto(); + if (!proto_key.ParseFromString(restricted_data.GetSecret(*token))) { + return util::Status(absl::StatusCode::kInvalidArgument, + "Failed to parse JwtHmacKey proto."); + } + if (proto_key.version() != 0) { + return util::Status( + absl::StatusCode::kInvalidArgument, + "Parsing JwtHmacKey failed: only version 0 is accepted."); + } + + util::StatusOr<JwtHmacParameters> parameters = ToParameters( + proto_key.key_value().length(), serialization.GetOutputPrefixType(), + proto_key.algorithm(), proto_key.has_custom_kid()); + if (!parameters.ok()) { + return parameters.status(); + } + + JwtHmacKey::Builder builder = + JwtHmacKey::Builder() + .SetParameters(*parameters) + .SetKeyBytes(RestrictedData(proto_key.key_value(), *token)); + if (serialization.IdRequirement().has_value()) { + builder.SetIdRequirement(*serialization.IdRequirement()); + } + if (proto_key.has_custom_kid()) { + builder.SetCustomKid(proto_key.custom_kid().value()); + } + return builder.Build(GetPartialKeyAccess()); +} + +util::StatusOr<internal::ProtoKeySerialization> SerializeKey( + const JwtHmacKey& key, absl::optional<SecretKeyAccessToken> token) { + if (!token.has_value()) { + return util::Status(absl::StatusCode::kInvalidArgument, + "SecretKeyAccess is required."); + } + util::StatusOr<RestrictedData> restricted_input = + key.GetKeyBytes(GetPartialKeyAccess()); + if (!restricted_input.ok()) { + return restricted_input.status(); + } + util::StatusOr<JwtHmacAlgorithm> proto_algorithm = + ToProtoAlgorithm(key.GetParameters().GetAlgorithm()); + if (!proto_algorithm.ok()) { + return proto_algorithm.status(); + } + + google::crypto::tink::JwtHmacKey proto_key; + proto_key.set_version(0); + proto_key.set_key_value(restricted_input->GetSecret(*token)); + proto_key.set_algorithm(*proto_algorithm); + if (key.GetParameters().GetKidStrategy() == + JwtHmacParameters::KidStrategy::kCustom) { + proto_key.mutable_custom_kid()->set_value(*key.GetKid()); + } + + util::StatusOr<OutputPrefixType> output_prefix_type = + ToOutputPrefixType(key.GetParameters().GetKidStrategy()); + if (!output_prefix_type.ok()) { + return output_prefix_type.status(); + } + + RestrictedData restricted_output = + RestrictedData(proto_key.SerializeAsString(), *token); + return internal::ProtoKeySerialization::Create( + kTypeUrl, restricted_output, google::crypto::tink::KeyData::SYMMETRIC, + *output_prefix_type, key.GetIdRequirement()); +} + +JwtHmacProtoParametersParserImpl* JwtHmacProtoParametersParser() { + static auto* parser = + new JwtHmacProtoParametersParserImpl(kTypeUrl, ParseParameters); + return parser; +} + +JwtHmacProtoParametersSerializerImpl* JwtHmacProtoParametersSerializer() { + static auto* serializer = + new JwtHmacProtoParametersSerializerImpl(kTypeUrl, SerializeParameters); + return serializer; +} + +JwtHmacProtoKeyParserImpl* JwtHmacProtoKeyParser() { + static auto* parser = new JwtHmacProtoKeyParserImpl(kTypeUrl, ParseKey); + return parser; +} + +JwtHmacProtoKeySerializerImpl* JwtHmacProtoKeySerializer() { + static auto* serializer = new JwtHmacProtoKeySerializerImpl(SerializeKey); + return serializer; +} + +} // namespace + +util::Status RegisterJwtHmacProtoSerialization() { + util::Status status = + internal::MutableSerializationRegistry::GlobalInstance() + .RegisterParametersParser(JwtHmacProtoParametersParser()); + if (!status.ok()) { + return status; + } + + status = + internal::MutableSerializationRegistry::GlobalInstance() + .RegisterParametersSerializer(JwtHmacProtoParametersSerializer()); + if (!status.ok()) { + return status; + } + + status = internal::MutableSerializationRegistry::GlobalInstance() + .RegisterKeyParser(JwtHmacProtoKeyParser()); + if (!status.ok()) { + return status; + } + + return internal::MutableSerializationRegistry::GlobalInstance() + .RegisterKeySerializer(JwtHmacProtoKeySerializer()); +} + +} // namespace tink +} // namespace crypto
diff --git a/cc/jwt/jwt_hmac_proto_serialization.h b/cc/jwt/jwt_hmac_proto_serialization.h new file mode 100644 index 0000000..aebba79 --- /dev/null +++ b/cc/jwt/jwt_hmac_proto_serialization.h
@@ -0,0 +1,31 @@ +// 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_JWT_JWT_HMAC_PROTO_SERIALIZATION_H_ +#define TINK_JWT_JWT_HMAC_PROTO_SERIALIZATION_H_ + +#include "tink/util/status.h" + +namespace crypto { +namespace tink { + +// Registers proto parsers and serializers for JWT HMAC parameters and keys. +crypto::tink::util::Status RegisterJwtHmacProtoSerialization(); + +} // namespace tink +} // namespace crypto + +#endif // TINK_JWT_JWT_HMAC_PROTO_SERIALIZATION_H_
diff --git a/cc/jwt/jwt_hmac_proto_serialization_test.cc b/cc/jwt/jwt_hmac_proto_serialization_test.cc new file mode 100644 index 0000000..c427772 --- /dev/null +++ b/cc/jwt/jwt_hmac_proto_serialization_test.cc
@@ -0,0 +1,643 @@ +// 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/jwt/jwt_hmac_proto_serialization.h" + +#include <memory> +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/status/status.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "tink/insecure_secret_key_access.h" +#include "tink/internal/mutable_serialization_registry.h" +#include "tink/internal/proto_key_serialization.h" +#include "tink/internal/proto_parameters_serialization.h" +#include "tink/internal/serialization.h" +#include "tink/jwt/jwt_hmac_key.h" +#include "tink/jwt/jwt_hmac_parameters.h" +#include "tink/key.h" +#include "tink/parameters.h" +#include "tink/partial_key_access.h" +#include "tink/restricted_data.h" +#include "tink/subtle/random.h" +#include "tink/util/statusor.h" +#include "tink/util/test_matchers.h" +#include "proto/common.pb.h" +#include "proto/jwt_hmac.pb.h" +#include "proto/tink.pb.h" + +namespace crypto { +namespace tink { +namespace { + +using ::crypto::tink::subtle::Random; +using ::crypto::tink::test::IsOk; +using ::crypto::tink::test::StatusIs; +using ::google::crypto::tink::JwtHmacAlgorithm; +using ::google::crypto::tink::JwtHmacKeyFormat; +using ::google::crypto::tink::KeyData; +using ::google::crypto::tink::OutputPrefixType; +using ::testing::Eq; +using ::testing::HasSubstr; +using ::testing::IsFalse; +using ::testing::IsTrue; +using ::testing::NotNull; +using ::testing::TestWithParam; +using ::testing::Values; + +const absl::string_view kTypeUrl = + "type.googleapis.com/google.crypto.tink.JwtHmacKey"; + +struct TestCase { + JwtHmacParameters::KidStrategy strategy; + OutputPrefixType output_prefix_type; + JwtHmacParameters::Algorithm algorithm; + JwtHmacAlgorithm proto_algorithm; + int key_size; + absl::optional<std::string> expected_kid; + absl::optional<int> id; + std::string output_prefix; +}; + +class JwtHmacProtoSerializationTest : public TestWithParam<TestCase> { + protected: + void SetUp() override { + internal::MutableSerializationRegistry::GlobalInstance().Reset(); + } +}; + +TEST_F(JwtHmacProtoSerializationTest, RegisterTwiceSucceeds) { + EXPECT_THAT(RegisterJwtHmacProtoSerialization(), IsOk()); + EXPECT_THAT(RegisterJwtHmacProtoSerialization(), IsOk()); +} + +INSTANTIATE_TEST_SUITE_P( + JwtHmacProtoSerializationTestSuite, JwtHmacProtoSerializationTest, + Values(TestCase{JwtHmacParameters::KidStrategy::kBase64EncodedKeyId, + OutputPrefixType::TINK, + JwtHmacParameters::Algorithm::kHs256, + JwtHmacAlgorithm::HS256, + /*key_size=*/16, /*expected_kid=*/"AgMEAA", + /*id=*/0x02030400, + /*output_prefix=*/std::string("\x01\x02\x03\x04\x00", 5)}, + TestCase{JwtHmacParameters::KidStrategy::kIgnored, + OutputPrefixType::RAW, JwtHmacParameters::Algorithm::kHs384, + JwtHmacAlgorithm::HS384, + /*key_size=*/32, /*expected_kid=*/absl::nullopt, + /*id=*/absl::nullopt, /*output_prefix=*/""}, + TestCase{JwtHmacParameters::KidStrategy::kIgnored, + OutputPrefixType::RAW, JwtHmacParameters::Algorithm::kHs512, + JwtHmacAlgorithm::HS512, + /*key_size=*/32, /*expected_kid=*/absl::nullopt, + /*id=*/absl::nullopt, /*output_prefix=*/""})); + +TEST_P(JwtHmacProtoSerializationTest, ParseParameters) { + TestCase test_case = GetParam(); + ASSERT_THAT(RegisterJwtHmacProtoSerialization(), IsOk()); + + JwtHmacKeyFormat format; + format.set_version(0); + format.set_key_size(test_case.key_size); + format.set_algorithm(test_case.proto_algorithm); + + util::StatusOr<internal::ProtoParametersSerialization> serialization = + internal::ProtoParametersSerialization::Create( + kTypeUrl, test_case.output_prefix_type, format.SerializeAsString()); + ASSERT_THAT(serialization, IsOk()); + + util::StatusOr<std::unique_ptr<Parameters>> parsed = + internal::MutableSerializationRegistry::GlobalInstance().ParseParameters( + *serialization); + ASSERT_THAT(parsed, IsOk()); + EXPECT_THAT((*parsed)->HasIdRequirement(), test_case.id.has_value()); + + util::StatusOr<JwtHmacParameters> expected = JwtHmacParameters::Create( + test_case.key_size, test_case.strategy, test_case.algorithm); + ASSERT_THAT(expected, IsOk()); + EXPECT_THAT(**parsed, Eq(*expected)); +} + +TEST_F(JwtHmacProtoSerializationTest, ParseParametersWithInvalidSerialization) { + ASSERT_THAT(RegisterJwtHmacProtoSerialization(), IsOk()); + + util::StatusOr<internal::ProtoParametersSerialization> serialization = + internal::ProtoParametersSerialization::Create( + kTypeUrl, OutputPrefixType::RAW, "invalid_serialization"); + ASSERT_THAT(serialization, IsOk()); + + util::StatusOr<std::unique_ptr<Parameters>> params = + internal::MutableSerializationRegistry::GlobalInstance().ParseParameters( + *serialization); + EXPECT_THAT(params.status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Failed to parse JwtHmacKeyFormat proto"))); +} + +TEST_F(JwtHmacProtoSerializationTest, ParseParametersWithInvalidVersion) { + ASSERT_THAT(RegisterJwtHmacProtoSerialization(), IsOk()); + + JwtHmacKeyFormat format; + format.set_version(1); // Invalid version number. + format.set_key_size(32); + format.set_algorithm(JwtHmacAlgorithm::HS256); + + util::StatusOr<internal::ProtoParametersSerialization> serialization = + internal::ProtoParametersSerialization::Create( + kTypeUrl, OutputPrefixType::RAW, format.SerializeAsString()); + ASSERT_THAT(serialization, IsOk()); + + util::StatusOr<std::unique_ptr<Parameters>> params = + internal::MutableSerializationRegistry::GlobalInstance().ParseParameters( + *serialization); + EXPECT_THAT(params.status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("only version 0 is accepted"))); +} + +TEST_F(JwtHmacProtoSerializationTest, ParseParametersWithUnknownAlgorithm) { + ASSERT_THAT(RegisterJwtHmacProtoSerialization(), IsOk()); + + JwtHmacKeyFormat format; + format.set_version(0); + format.set_key_size(32); + format.set_algorithm(JwtHmacAlgorithm::HS_UNKNOWN); + + util::StatusOr<internal::ProtoParametersSerialization> serialization = + internal::ProtoParametersSerialization::Create( + kTypeUrl, OutputPrefixType::RAW, format.SerializeAsString()); + ASSERT_THAT(serialization, IsOk()); + + util::StatusOr<std::unique_ptr<Parameters>> params = + internal::MutableSerializationRegistry::GlobalInstance().ParseParameters( + *serialization); + EXPECT_THAT(params.status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Could not determine JwtHmacAlgorithm"))); +} + +using JwtHmacParsePrefixTest = TestWithParam<OutputPrefixType>; + +INSTANTIATE_TEST_SUITE_P(JwtHmacParsePrefixTestSuite, JwtHmacParsePrefixTest, + Values(OutputPrefixType::CRUNCHY, + OutputPrefixType::LEGACY, + OutputPrefixType::UNKNOWN_PREFIX)); + +TEST_P(JwtHmacParsePrefixTest, ParseParametersWithInvalidPrefix) { + OutputPrefixType invalid_output_prefix_type = GetParam(); + internal::MutableSerializationRegistry::GlobalInstance().Reset(); + ASSERT_THAT(RegisterJwtHmacProtoSerialization(), IsOk()); + + JwtHmacKeyFormat format; + format.set_version(0); + format.set_key_size(32); + format.set_algorithm(JwtHmacAlgorithm::HS256); + + util::StatusOr<internal::ProtoParametersSerialization> serialization = + internal::ProtoParametersSerialization::Create( + kTypeUrl, invalid_output_prefix_type, format.SerializeAsString()); + ASSERT_THAT(serialization, IsOk()); + + util::StatusOr<std::unique_ptr<Parameters>> params = + internal::MutableSerializationRegistry::GlobalInstance().ParseParameters( + *serialization); + EXPECT_THAT( + params.status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Invalid OutputPrefixType for JwtHmacKeyFormat"))); +} + +TEST_P(JwtHmacProtoSerializationTest, SerializeParameters) { + TestCase test_case = GetParam(); + ASSERT_THAT(RegisterJwtHmacProtoSerialization(), IsOk()); + + util::StatusOr<JwtHmacParameters> parameters = JwtHmacParameters::Create( + test_case.key_size, test_case.strategy, test_case.algorithm); + ASSERT_THAT(parameters, IsOk()); + + util::StatusOr<std::unique_ptr<Serialization>> serialization = + internal::MutableSerializationRegistry::GlobalInstance() + .SerializeParameters<internal::ProtoParametersSerialization>( + *parameters); + ASSERT_THAT(serialization, IsOk()); + EXPECT_THAT((*serialization)->ObjectIdentifier(), Eq(kTypeUrl)); + + const internal::ProtoParametersSerialization* proto_serialization = + dynamic_cast<const internal::ProtoParametersSerialization*>( + serialization->get()); + ASSERT_THAT(proto_serialization, NotNull()); + EXPECT_THAT(proto_serialization->GetKeyTemplate().type_url(), Eq(kTypeUrl)); + EXPECT_THAT(proto_serialization->GetKeyTemplate().output_prefix_type(), + Eq(test_case.output_prefix_type)); + + JwtHmacKeyFormat format; + ASSERT_THAT( + format.ParseFromString(proto_serialization->GetKeyTemplate().value()), + IsTrue()); + EXPECT_THAT(format.version(), Eq(0)); + EXPECT_THAT(format.key_size(), Eq(test_case.key_size)); + EXPECT_THAT(format.algorithm(), Eq(test_case.proto_algorithm)); +} + +TEST_F(JwtHmacProtoSerializationTest, SerializeParametersWithCustomKidFails) { + ASSERT_THAT(RegisterJwtHmacProtoSerialization(), IsOk()); + + util::StatusOr<JwtHmacParameters> parameters = JwtHmacParameters::Create( + /*key_size_in_bytes=*/32, JwtHmacParameters::KidStrategy::kCustom, + JwtHmacParameters::Algorithm::kHs256); + ASSERT_THAT(parameters, IsOk()); + + util::StatusOr<std::unique_ptr<Serialization>> serialization = + internal::MutableSerializationRegistry::GlobalInstance() + .SerializeParameters<internal::ProtoParametersSerialization>( + *parameters); + EXPECT_THAT( + serialization.status(), + StatusIs( + absl::StatusCode::kInvalidArgument, + HasSubstr( + "Unable to serialize JwtHmacParameters::KidStrategy::kCustom"))); +} + +TEST_P(JwtHmacProtoSerializationTest, ParseKeyWithoutCustomKid) { + TestCase test_case = GetParam(); + ASSERT_THAT(RegisterJwtHmacProtoSerialization(), IsOk()); + + std::string raw_key_bytes = Random::GetRandomBytes(test_case.key_size); + google::crypto::tink::JwtHmacKey key_proto; + key_proto.set_version(0); + key_proto.set_algorithm(test_case.proto_algorithm); + key_proto.set_key_value(raw_key_bytes); + RestrictedData serialized_key = RestrictedData( + key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get()); + + util::StatusOr<internal::ProtoKeySerialization> serialization = + internal::ProtoKeySerialization::Create( + kTypeUrl, serialized_key, KeyData::SYMMETRIC, + test_case.output_prefix_type, test_case.id); + ASSERT_THAT(serialization, IsOk()); + + util::StatusOr<std::unique_ptr<Key>> parsed_key = + internal::MutableSerializationRegistry::GlobalInstance().ParseKey( + *serialization, InsecureSecretKeyAccess::Get()); + ASSERT_THAT(parsed_key, IsOk()); + EXPECT_THAT((*parsed_key)->GetParameters().HasIdRequirement(), + test_case.id.has_value()); + EXPECT_THAT((*parsed_key)->GetIdRequirement(), Eq(test_case.id)); + + util::StatusOr<JwtHmacParameters> expected_parameters = + JwtHmacParameters::Create(test_case.key_size, test_case.strategy, + test_case.algorithm); + ASSERT_THAT(expected_parameters, IsOk()); + + JwtHmacKey::Builder builder = + JwtHmacKey::Builder() + .SetParameters(*expected_parameters) + .SetKeyBytes( + RestrictedData(raw_key_bytes, InsecureSecretKeyAccess::Get())); + if (test_case.id.has_value()) { + builder.SetIdRequirement(*test_case.id); + } + util::StatusOr<JwtHmacKey> expected_key = + builder.Build(GetPartialKeyAccess()); + ASSERT_THAT(expected_key, IsOk()); + EXPECT_THAT(**parsed_key, Eq(*expected_key)); +} + +TEST_F(JwtHmacProtoSerializationTest, ParseKeyWithCustomKid) { + ASSERT_THAT(RegisterJwtHmacProtoSerialization(), IsOk()); + + std::string raw_key_bytes = Random::GetRandomBytes(32); + google::crypto::tink::JwtHmacKey key_proto; + key_proto.set_version(0); + key_proto.set_algorithm(JwtHmacAlgorithm::HS256); + key_proto.set_key_value(raw_key_bytes); + key_proto.mutable_custom_kid()->set_value("custom_kid"); + RestrictedData serialized_key = RestrictedData( + key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get()); + + util::StatusOr<internal::ProtoKeySerialization> serialization = + internal::ProtoKeySerialization::Create( + kTypeUrl, serialized_key, KeyData::SYMMETRIC, OutputPrefixType::RAW, + /*id_requirement=*/absl::nullopt); + ASSERT_THAT(serialization, IsOk()); + + util::StatusOr<std::unique_ptr<Key>> parsed_key = + internal::MutableSerializationRegistry::GlobalInstance().ParseKey( + *serialization, InsecureSecretKeyAccess::Get()); + ASSERT_THAT(parsed_key, IsOk()); + EXPECT_THAT((*parsed_key)->GetParameters().HasIdRequirement(), IsFalse()); + EXPECT_THAT((*parsed_key)->GetIdRequirement(), Eq(absl::nullopt)); + + util::StatusOr<JwtHmacParameters> expected_parameters = + JwtHmacParameters::Create(/*key_size_in_bytes=*/32, + JwtHmacParameters::KidStrategy::kCustom, + JwtHmacParameters::Algorithm::kHs256); + ASSERT_THAT(expected_parameters, IsOk()); + + util::StatusOr<JwtHmacKey> expected_key = + JwtHmacKey::Builder() + .SetParameters(*expected_parameters) + .SetKeyBytes( + RestrictedData(raw_key_bytes, InsecureSecretKeyAccess::Get())) + .SetCustomKid(key_proto.custom_kid().value()) + .Build(GetPartialKeyAccess()); + ASSERT_THAT(expected_key, IsOk()); + EXPECT_THAT(**parsed_key, Eq(*expected_key)); +} + +TEST_F(JwtHmacProtoSerializationTest, ParseTinkKeyWithCustomKidFails) { + ASSERT_THAT(RegisterJwtHmacProtoSerialization(), IsOk()); + + std::string raw_key_bytes = Random::GetRandomBytes(32); + google::crypto::tink::JwtHmacKey key_proto; + key_proto.set_version(0); + key_proto.set_algorithm(JwtHmacAlgorithm::HS256); + key_proto.set_key_value(raw_key_bytes); + key_proto.mutable_custom_kid()->set_value("custom_kid"); + RestrictedData serialized_key = RestrictedData( + key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get()); + + util::StatusOr<internal::ProtoKeySerialization> serialization = + internal::ProtoKeySerialization::Create( + kTypeUrl, serialized_key, KeyData::SYMMETRIC, OutputPrefixType::TINK, + /*id_requirement=*/123); + ASSERT_THAT(serialization, IsOk()); + + util::StatusOr<std::unique_ptr<Key>> key = + internal::MutableSerializationRegistry::GlobalInstance().ParseKey( + *serialization, InsecureSecretKeyAccess::Get()); + // Omitting expectation on specific error message since the error occurs + // downstream while building JwtHmacKey object. + EXPECT_THAT(key.status(), StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST_F(JwtHmacProtoSerializationTest, ParseKeyWithInvalidSerialization) { + ASSERT_THAT(RegisterJwtHmacProtoSerialization(), IsOk()); + + std::string raw_key_bytes = Random::GetRandomBytes(32); + google::crypto::tink::JwtHmacKey key_proto; + key_proto.set_version(0); + key_proto.set_algorithm(JwtHmacAlgorithm::HS256); + key_proto.set_key_value(raw_key_bytes); + RestrictedData serialized_key = + RestrictedData("invalid_serialization", InsecureSecretKeyAccess::Get()); + + util::StatusOr<internal::ProtoKeySerialization> serialization = + internal::ProtoKeySerialization::Create( + kTypeUrl, serialized_key, KeyData::SYMMETRIC, OutputPrefixType::RAW, + /*id_requirement=*/absl::nullopt); + ASSERT_THAT(serialization, IsOk()); + + util::StatusOr<std::unique_ptr<Key>> key = + internal::MutableSerializationRegistry::GlobalInstance().ParseKey( + *serialization, InsecureSecretKeyAccess::Get()); + EXPECT_THAT(key.status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Failed to parse JwtHmacKey proto"))); +} + +TEST_F(JwtHmacProtoSerializationTest, ParseKeyWithInvalidVersion) { + ASSERT_THAT(RegisterJwtHmacProtoSerialization(), IsOk()); + + std::string raw_key_bytes = Random::GetRandomBytes(32); + google::crypto::tink::JwtHmacKey key_proto; + key_proto.set_version(1); // Invalid version number. + key_proto.set_algorithm(JwtHmacAlgorithm::HS256); + key_proto.set_key_value(raw_key_bytes); + RestrictedData serialized_key = RestrictedData( + key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get()); + + util::StatusOr<internal::ProtoKeySerialization> serialization = + internal::ProtoKeySerialization::Create( + kTypeUrl, serialized_key, KeyData::SYMMETRIC, OutputPrefixType::RAW, + /*id_requirement=*/absl::nullopt); + ASSERT_THAT(serialization, IsOk()); + + util::StatusOr<std::unique_ptr<Key>> key = + internal::MutableSerializationRegistry::GlobalInstance().ParseKey( + *serialization, InsecureSecretKeyAccess::Get()); + EXPECT_THAT( + key.status(), + StatusIs( + absl::StatusCode::kInvalidArgument, + HasSubstr("Parsing JwtHmacKey failed: only version 0 is accepted"))); +} + +TEST_P(JwtHmacParsePrefixTest, ParseKeyWithInvalidPrefix) { + OutputPrefixType invalid_output_prefix_type = GetParam(); + internal::MutableSerializationRegistry::GlobalInstance().Reset(); + ASSERT_THAT(RegisterJwtHmacProtoSerialization(), IsOk()); + + std::string raw_key_bytes = Random::GetRandomBytes(32); + google::crypto::tink::JwtHmacKey key_proto; + key_proto.set_version(0); + key_proto.set_algorithm(JwtHmacAlgorithm::HS256); + key_proto.set_key_value(raw_key_bytes); + RestrictedData serialized_key = RestrictedData( + key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get()); + + util::StatusOr<internal::ProtoKeySerialization> serialization = + internal::ProtoKeySerialization::Create(kTypeUrl, serialized_key, + KeyData::SYMMETRIC, + invalid_output_prefix_type, + /*id_requirement=*/0x23456789); + ASSERT_THAT(serialization, IsOk()); + + util::StatusOr<std::unique_ptr<Key>> key = + internal::MutableSerializationRegistry::GlobalInstance().ParseKey( + *serialization, InsecureSecretKeyAccess::Get()); + EXPECT_THAT( + key.status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Invalid OutputPrefixType for JwtHmacKeyFormat"))); +} + +TEST_F(JwtHmacProtoSerializationTest, ParseKeyWithUnknownAlgorithm) { + ASSERT_THAT(RegisterJwtHmacProtoSerialization(), IsOk()); + + std::string raw_key_bytes = Random::GetRandomBytes(32); + google::crypto::tink::JwtHmacKey key_proto; + key_proto.set_version(0); + key_proto.set_algorithm(JwtHmacAlgorithm::HS_UNKNOWN); + key_proto.set_key_value(raw_key_bytes); + RestrictedData serialized_key = RestrictedData( + key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get()); + + util::StatusOr<internal::ProtoKeySerialization> serialization = + internal::ProtoKeySerialization::Create( + kTypeUrl, serialized_key, KeyData::SYMMETRIC, OutputPrefixType::RAW, + /*id_requirement=*/absl::nullopt); + ASSERT_THAT(serialization, IsOk()); + + util::StatusOr<std::unique_ptr<Key>> key = + internal::MutableSerializationRegistry::GlobalInstance().ParseKey( + *serialization, InsecureSecretKeyAccess::Get()); + EXPECT_THAT(key.status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Could not determine JwtHmacAlgorithm"))); +} + +TEST_F(JwtHmacProtoSerializationTest, ParseKeyWithoutSecretKeyAccess) { + ASSERT_THAT(RegisterJwtHmacProtoSerialization(), IsOk()); + + std::string raw_key_bytes = Random::GetRandomBytes(32); + google::crypto::tink::JwtHmacKey key_proto; + key_proto.set_version(0); + key_proto.set_algorithm(JwtHmacAlgorithm::HS256); + key_proto.set_key_value(raw_key_bytes); + RestrictedData serialized_key = RestrictedData( + key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get()); + + util::StatusOr<internal::ProtoKeySerialization> serialization = + internal::ProtoKeySerialization::Create( + kTypeUrl, serialized_key, KeyData::SYMMETRIC, OutputPrefixType::RAW, + /*id_requirement=*/absl::nullopt); + ASSERT_THAT(serialization, IsOk()); + + util::StatusOr<std::unique_ptr<Key>> key = + internal::MutableSerializationRegistry::GlobalInstance().ParseKey( + *serialization, /*token=*/absl::nullopt); + EXPECT_THAT(key.status(), StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("SecretKeyAccess is required"))); +} + +TEST_P(JwtHmacProtoSerializationTest, SerializeKeyWithoutCustomKid) { + TestCase test_case = GetParam(); + ASSERT_THAT(RegisterJwtHmacProtoSerialization(), IsOk()); + + util::StatusOr<JwtHmacParameters> parameters = JwtHmacParameters::Create( + test_case.key_size, test_case.strategy, test_case.algorithm); + ASSERT_THAT(parameters, IsOk()); + + std::string raw_key_bytes = Random::GetRandomBytes(test_case.key_size); + JwtHmacKey::Builder builder = + JwtHmacKey::Builder() + .SetParameters(*parameters) + .SetKeyBytes( + RestrictedData(raw_key_bytes, InsecureSecretKeyAccess::Get())); + if (test_case.id.has_value()) { + builder.SetIdRequirement(*test_case.id); + } + util::StatusOr<JwtHmacKey> key = builder.Build(GetPartialKeyAccess()); + ASSERT_THAT(key, IsOk()); + + util::StatusOr<std::unique_ptr<Serialization>> serialization = + internal::MutableSerializationRegistry::GlobalInstance() + .SerializeKey<internal::ProtoKeySerialization>( + *key, InsecureSecretKeyAccess::Get()); + ASSERT_THAT(serialization, IsOk()); + EXPECT_THAT((*serialization)->ObjectIdentifier(), Eq(kTypeUrl)); + + const internal::ProtoKeySerialization* proto_serialization = + dynamic_cast<const internal::ProtoKeySerialization*>( + serialization->get()); + ASSERT_THAT(proto_serialization, NotNull()); + EXPECT_THAT(proto_serialization->TypeUrl(), Eq(kTypeUrl)); + EXPECT_THAT(proto_serialization->KeyMaterialType(), Eq(KeyData::SYMMETRIC)); + EXPECT_THAT(proto_serialization->GetOutputPrefixType(), + Eq(test_case.output_prefix_type)); + EXPECT_THAT(proto_serialization->IdRequirement(), Eq(test_case.id)); + + google::crypto::tink::JwtHmacKey proto_key; + ASSERT_THAT(proto_key.ParseFromString( + proto_serialization->SerializedKeyProto().GetSecret( + InsecureSecretKeyAccess::Get())), + IsTrue()); + EXPECT_THAT(proto_key.version(), Eq(0)); + EXPECT_THAT(proto_key.key_value(), Eq(raw_key_bytes)); + EXPECT_THAT(proto_key.algorithm(), Eq(test_case.proto_algorithm)); + EXPECT_THAT(proto_key.has_custom_kid(), IsFalse()); +} + +TEST_F(JwtHmacProtoSerializationTest, SerializeKeyWithCustomKid) { + ASSERT_THAT(RegisterJwtHmacProtoSerialization(), IsOk()); + + util::StatusOr<JwtHmacParameters> parameters = JwtHmacParameters::Create( + /*key_size_in_bytes=*/32, JwtHmacParameters::KidStrategy::kCustom, + JwtHmacParameters::Algorithm::kHs256); + ASSERT_THAT(parameters, IsOk()); + + std::string raw_key_bytes = Random::GetRandomBytes(32); + util::StatusOr<JwtHmacKey> key = + JwtHmacKey::Builder() + .SetParameters(*parameters) + .SetKeyBytes( + RestrictedData(raw_key_bytes, InsecureSecretKeyAccess::Get())) + .SetCustomKid("custom_kid") + .Build(GetPartialKeyAccess()); + ASSERT_THAT(key, IsOk()); + + util::StatusOr<std::unique_ptr<Serialization>> serialization = + internal::MutableSerializationRegistry::GlobalInstance() + .SerializeKey<internal::ProtoKeySerialization>( + *key, InsecureSecretKeyAccess::Get()); + ASSERT_THAT(serialization, IsOk()); + EXPECT_THAT((*serialization)->ObjectIdentifier(), Eq(kTypeUrl)); + + const internal::ProtoKeySerialization* proto_serialization = + dynamic_cast<const internal::ProtoKeySerialization*>( + serialization->get()); + ASSERT_THAT(proto_serialization, NotNull()); + EXPECT_THAT(proto_serialization->TypeUrl(), Eq(kTypeUrl)); + EXPECT_THAT(proto_serialization->KeyMaterialType(), Eq(KeyData::SYMMETRIC)); + EXPECT_THAT(proto_serialization->GetOutputPrefixType(), + Eq(OutputPrefixType::RAW)); + EXPECT_THAT(proto_serialization->IdRequirement(), Eq(absl::nullopt)); + + google::crypto::tink::JwtHmacKey proto_key; + ASSERT_THAT(proto_key.ParseFromString( + proto_serialization->SerializedKeyProto().GetSecret( + InsecureSecretKeyAccess::Get())), + IsTrue()); + EXPECT_THAT(proto_key.version(), Eq(0)); + EXPECT_THAT(proto_key.key_value(), Eq(raw_key_bytes)); + EXPECT_THAT(proto_key.algorithm(), Eq(JwtHmacAlgorithm::HS256)); + ASSERT_THAT(proto_key.has_custom_kid(), IsTrue()); + EXPECT_THAT(proto_key.custom_kid().value(), Eq(*key->GetKid())); +} + +TEST_F(JwtHmacProtoSerializationTest, SerializeKeyWithoutSecretKeyAccess) { + ASSERT_THAT(RegisterJwtHmacProtoSerialization(), IsOk()); + + util::StatusOr<JwtHmacParameters> parameters = JwtHmacParameters::Create( + /*key_size_in_bytes=*/32, JwtHmacParameters::KidStrategy::kIgnored, + JwtHmacParameters::Algorithm::kHs256); + ASSERT_THAT(parameters, IsOk()); + + std::string raw_key_bytes = Random::GetRandomBytes(32); + util::StatusOr<JwtHmacKey> key = + JwtHmacKey::Builder() + .SetParameters(*parameters) + .SetKeyBytes( + RestrictedData(raw_key_bytes, InsecureSecretKeyAccess::Get())) + .Build(GetPartialKeyAccess()); + ASSERT_THAT(key, IsOk()); + + util::StatusOr<std::unique_ptr<Serialization>> serialization = + internal::MutableSerializationRegistry::GlobalInstance() + .SerializeKey<internal::ProtoKeySerialization>( + *key, /*token=*/absl::nullopt); + ASSERT_THAT(serialization.status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("SecretKeyAccess is required"))); +} + +} // namespace +} // namespace tink +} // namespace crypto
diff --git a/cc/jwt/jwt_signature_parameters.h b/cc/jwt/jwt_signature_parameters.h new file mode 100644 index 0000000..ddc97ef --- /dev/null +++ b/cc/jwt/jwt_signature_parameters.h
@@ -0,0 +1,35 @@ +// 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_JWT_JWT_SIGNATURE_PARAMETERS_H_ +#define TINK_JWT_JWT_SIGNATURE_PARAMETERS_H_ + +#include "tink/parameters.h" + +namespace crypto { +namespace tink { + +// Describes a JWT signature key pair without the randomly chosen key material. +class JwtSignatureParameters : public Parameters { + // Returns true if verification is allowed for tokens without a `kid` header. + virtual bool AllowKidAbsent() const = 0; +}; + +} // namespace tink +} // namespace crypto + + +#endif // TINK_JWT_JWT_SIGNATURE_PARAMETERS_H_
diff --git a/cc/jwt/jwt_signature_private_key.h b/cc/jwt/jwt_signature_private_key.h new file mode 100644 index 0000000..b63722b --- /dev/null +++ b/cc/jwt/jwt_signature_private_key.h
@@ -0,0 +1,54 @@ +// 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_JWT_JWT_SIGNATURE_PRIVATE_KEY_H_ +#define TINK_JWT_JWT_SIGNATURE_PRIVATE_KEY_H_ + +#include <string> + +#include "absl/types/optional.h" +#include "tink/jwt/jwt_signature_parameters.h" +#include "tink/jwt/jwt_signature_public_key.h" +#include "tink/key.h" +#include "tink/private_key.h" + +namespace crypto { +namespace tink { + +// Represents the signing function for a JWT Signature primitive. +class JwtSignaturePrivateKey : public PrivateKey { + public: + const JwtSignaturePublicKey& GetPublicKey() const override = 0; + + absl::optional<std::string> GetKid() const { + return GetPublicKey().GetKid(); + } + + absl::optional<int> GetIdRequirement() const override { + return GetPublicKey().GetIdRequirement(); + } + + const JwtSignatureParameters& GetParameters() const override { + return GetPublicKey().GetParameters(); + } + + bool operator==(const Key& other) const override = 0; +}; + +} // namespace tink +} // namespace crypto + +#endif // TINK_JWT_JWT_SIGNATURE_PRIVATE_KEY_H_
diff --git a/cc/jwt/jwt_signature_public_key.h b/cc/jwt/jwt_signature_public_key.h new file mode 100644 index 0000000..e2e670c --- /dev/null +++ b/cc/jwt/jwt_signature_public_key.h
@@ -0,0 +1,59 @@ +// 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_JWT_JWT_SIGNATURE_PUBLIC_KEY_H_ +#define TINK_JWT_JWT_SIGNATURE_PUBLIC_KEY_H_ + +#include <string> + +#include "absl/types/optional.h" +#include "tink/jwt/jwt_signature_parameters.h" +#include "tink/key.h" + +namespace crypto { +namespace tink { + +// Represents the verification function for a JWT Signature primitive. +class JwtSignaturePublicKey : public Key { + public: + // Returns the `kid` to be used for this key + // (https://www.rfc-editor.org/rfc/rfc7517#section-4.5). + // + // Note that the `kid` is not necessarily related to Tink's key ID in the + // keyset. + // + // If present, this `kid` will be written into the `kid` header during + // `ComputeMacAndEncode()`. If absent, no `kid` will be written. + // + // If present, and the `kid` header is present, the contents of the + // `kid` header need to match the return value of this function for + // validation to succeed in `VerifyMacAndDecode()`. + // + // Note that `GetParameters().AllowKidAbsent()` specifies whether or not + // omitting the `kid` header is allowed. Of course, if + // `GetParameters().AllowKidAbsent()` returns false, then `GetKid()` must + // return a non-empty value. + virtual absl::optional<std::string> GetKid() const = 0; + + const JwtSignatureParameters& GetParameters() const override = 0; + + bool operator==(const Key& other) const override = 0; +}; + +} // namespace tink +} // namespace crypto + +#endif // TINK_JWT_JWT_SIGNATURE_PUBLIC_KEY_H_
diff --git a/cc/prf/prf_set.h b/cc/prf/prf_set.h index bdbbbb6..27b1ced 100644 --- a/cc/prf/prf_set.h +++ b/cc/prf/prf_set.h
@@ -29,7 +29,7 @@ namespace tink { // The PRF interface is an abstraction for an element of a pseudo random -// function family, selected by a key. It has the following property: +// function family, selected by a key. It has the following properties: // * It is deterministic. PRF.compute(input, length) will always return the // same output if the same key is used. PRF.compute(input, length1) will be // a prefix of PRF.compute(input, length2) if length1 < length2 and the same
diff --git a/cc/proto/aes_cmac_prf.proto b/cc/proto/aes_cmac_prf.proto index cfc4cf1..dd8dcae 100644 --- a/cc/proto/aes_cmac_prf.proto +++ b/cc/proto/aes_cmac_prf.proto
@@ -20,7 +20,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_cmac_prf_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_cmac_prf_go_proto"; // key_type: type.googleapis.com/google.crypto.tink.AesCmacPrfKey message AesCmacPrfKey {
diff --git a/cc/proto/aes_ctr.proto b/cc/proto/aes_ctr.proto index 23028c6..a873097 100644 --- a/cc/proto/aes_ctr.proto +++ b/cc/proto/aes_ctr.proto
@@ -20,7 +20,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_ctr_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_ctr_go_proto"; message AesCtrParams { uint32 iv_size = 1;
diff --git a/cc/proto/aes_ctr_hmac_aead.proto b/cc/proto/aes_ctr_hmac_aead.proto index a95a80d..7059346 100644 --- a/cc/proto/aes_ctr_hmac_aead.proto +++ b/cc/proto/aes_ctr_hmac_aead.proto
@@ -23,7 +23,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_ctr_hmac_aead_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_ctr_hmac_aead_go_proto"; message AesCtrHmacAeadKeyFormat { AesCtrKeyFormat aes_ctr_key_format = 1;
diff --git a/cc/proto/aes_ctr_hmac_streaming.proto b/cc/proto/aes_ctr_hmac_streaming.proto index 2d5678b..599e267 100644 --- a/cc/proto/aes_ctr_hmac_streaming.proto +++ b/cc/proto/aes_ctr_hmac_streaming.proto
@@ -23,7 +23,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_ctr_hmac_streaming_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_ctr_hmac_streaming_go_proto"; message AesCtrHmacStreamingParams { uint32 ciphertext_segment_size = 1;
diff --git a/cc/proto/aes_eax.proto b/cc/proto/aes_eax.proto index 82ff7ee..f086d49 100644 --- a/cc/proto/aes_eax.proto +++ b/cc/proto/aes_eax.proto
@@ -20,7 +20,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_eax_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_eax_go_proto"; // only allowing tag size in bytes = 16 message AesEaxParams {
diff --git a/cc/proto/aes_gcm.proto b/cc/proto/aes_gcm.proto index 459ec3d..1908c4a 100644 --- a/cc/proto/aes_gcm.proto +++ b/cc/proto/aes_gcm.proto
@@ -20,7 +20,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_gcm_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_gcm_go_proto"; option objc_class_prefix = "TINKPB"; message AesGcmKeyFormat {
diff --git a/cc/proto/aes_gcm_hkdf_streaming.proto b/cc/proto/aes_gcm_hkdf_streaming.proto index 2c445ae..a436abb 100644 --- a/cc/proto/aes_gcm_hkdf_streaming.proto +++ b/cc/proto/aes_gcm_hkdf_streaming.proto
@@ -24,7 +24,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_gcm_hkdf_streaming_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_gcm_hkdf_streaming_go_proto"; message AesGcmHkdfStreamingParams { uint32 ciphertext_segment_size = 1;
diff --git a/cc/proto/aes_gcm_siv.proto b/cc/proto/aes_gcm_siv.proto index 8cfea19..c663aee 100644 --- a/cc/proto/aes_gcm_siv.proto +++ b/cc/proto/aes_gcm_siv.proto
@@ -20,7 +20,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_gcm_siv_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_gcm_siv_go_proto"; // The only allowed IV size is 12 bytes and tag size is 16 bytes. // Thus, accept no params.
diff --git a/cc/subtle/common_enums_test.cc b/cc/subtle/common_enums_test.cc index 517d588..4e27b73 100644 --- a/cc/subtle/common_enums_test.cc +++ b/cc/subtle/common_enums_test.cc
@@ -29,7 +29,6 @@ EXPECT_EQ("NIST_P384", EnumToString(EllipticCurveType::NIST_P384)); EXPECT_EQ("NIST_P521", EnumToString(EllipticCurveType::NIST_P521)); EXPECT_EQ("UNKNOWN_CURVE", EnumToString(EllipticCurveType::UNKNOWN_CURVE)); - EXPECT_EQ("UNKNOWN_CURVE: 42", EnumToString((EllipticCurveType)42)); } TEST_F(CommonEnumsTest, testHashTypeToString) { @@ -39,7 +38,6 @@ EXPECT_EQ("SHA384", EnumToString(HashType::SHA384)); EXPECT_EQ("SHA512", EnumToString(HashType::SHA512)); EXPECT_EQ("UNKNOWN_HASH", EnumToString(HashType::UNKNOWN_HASH)); - EXPECT_EQ("UNKNOWN_HASH: 42", EnumToString((HashType)42)); } TEST_F(CommonEnumsTest, testEcPointFormatToString) { @@ -49,7 +47,6 @@ EnumToString(EcPointFormat::DO_NOT_USE_CRUNCHY_UNCOMPRESSED)); EXPECT_EQ("UNKNOWN_FORMAT", EnumToString(EcPointFormat::UNKNOWN_FORMAT)); - EXPECT_EQ("UNKNOWN_FORMAT: 42", EnumToString((EcPointFormat)42)); } } // namespace
diff --git a/cc/subtle/stateful_hmac_boringssl_test.cc b/cc/subtle/stateful_hmac_boringssl_test.cc index 3f761b3..fcd5d7b 100644 --- a/cc/subtle/stateful_hmac_boringssl_test.cc +++ b/cc/subtle/stateful_hmac_boringssl_test.cc
@@ -53,7 +53,6 @@ using ::testing::Eq; using ::testing::HasSubstr; using ::testing::SizeIs; -using ::testing::StrEq; struct TestVector { TestVector(std::string test_name, std::string hex_key, HashType hash_type,
diff --git a/cc/util/enums_test.cc b/cc/util/enums_test.cc index c60e146..b01f89b 100644 --- a/cc/util/enums_test.cc +++ b/cc/util/enums_test.cc
@@ -46,8 +46,6 @@ Enums::SubtleToProto(subtle::EllipticCurveType::CURVE25519)); EXPECT_EQ(pb::EllipticCurveType::UNKNOWN_CURVE, Enums::SubtleToProto(subtle::EllipticCurveType::UNKNOWN_CURVE)); - EXPECT_EQ(pb::EllipticCurveType::UNKNOWN_CURVE, - Enums::SubtleToProto((subtle::EllipticCurveType)42)); EXPECT_EQ(subtle::EllipticCurveType::NIST_P256, Enums::ProtoToSubtle(pb::EllipticCurveType::NIST_P256)); @@ -59,8 +57,6 @@ Enums::ProtoToSubtle(pb::EllipticCurveType::CURVE25519)); EXPECT_EQ(subtle::EllipticCurveType::UNKNOWN_CURVE, Enums::ProtoToSubtle(pb::EllipticCurveType::UNKNOWN_CURVE)); - EXPECT_EQ(subtle::EllipticCurveType::UNKNOWN_CURVE, - Enums::ProtoToSubtle((pb::EllipticCurveType)42)); // Check that enum conversion covers the entire range of the proto-enum. int count = 0; @@ -87,8 +83,6 @@ Enums::SubtleToProto(subtle::HashType::SHA512)); EXPECT_EQ(pb::HashType::UNKNOWN_HASH, Enums::SubtleToProto(subtle::HashType::UNKNOWN_HASH)); - EXPECT_EQ(pb::HashType::UNKNOWN_HASH, - Enums::SubtleToProto((subtle::HashType)42)); EXPECT_EQ(subtle::HashType::SHA1, Enums::ProtoToSubtle(pb::HashType::SHA1)); EXPECT_EQ(subtle::HashType::SHA224, @@ -101,8 +95,6 @@ Enums::ProtoToSubtle(pb::HashType::SHA512)); EXPECT_EQ(subtle::HashType::UNKNOWN_HASH, Enums::ProtoToSubtle(pb::HashType::UNKNOWN_HASH)); - EXPECT_EQ(subtle::HashType::UNKNOWN_HASH, - Enums::ProtoToSubtle((pb::HashType)42)); // Check that enum conversion covers the entire range of the proto-enum. int count = 0; @@ -147,8 +139,6 @@ Enums::ProtoToSubtle(pb::EcPointFormat::COMPRESSED)); EXPECT_EQ(subtle::EcPointFormat::UNKNOWN_FORMAT, Enums::ProtoToSubtle(pb::EcPointFormat::UNKNOWN_FORMAT)); - EXPECT_EQ(subtle::EcPointFormat::UNKNOWN_FORMAT, - Enums::ProtoToSubtle((pb::EcPointFormat)42)); // Check that enum conversion covers the entire range of the proto-enum. int count = 0; @@ -202,8 +192,6 @@ EXPECT_EQ( "UNKNOWN_STATUS", std::string(Enums::KeyStatusName(pb::KeyStatusType::UNKNOWN_STATUS))); - EXPECT_EQ("UNKNOWN_STATUS", - std::string(Enums::KeyStatusName((pb::KeyStatusType)42))); EXPECT_EQ(pb::KeyStatusType::ENABLED, Enums::KeyStatus("ENABLED")); EXPECT_EQ(pb::KeyStatusType::DISABLED, Enums::KeyStatus("DISABLED"));
diff --git a/cc/util/secret_data.h b/cc/util/secret_data.h index 66e3003..dd2fa5f 100644 --- a/cc/util/secret_data.h +++ b/cc/util/secret_data.h
@@ -18,11 +18,11 @@ #define TINK_UTIL_SECRET_DATA_H_ #include <cstddef> -#include <cstdint> +#include <cstdint> // IWYU pragma: keep #include <memory> #include <string> #include <type_traits> -#include <vector> +#include <vector> // IWYU pragma: keep #include "absl/strings/string_view.h" #include "tink/util/secret_data_internal.h"
diff --git a/docs/PRIMITIVES.md b/docs/PRIMITIVES.md index 53ba5ad..5a00eb7 100644 --- a/docs/PRIMITIVES.md +++ b/docs/PRIMITIVES.md
@@ -38,38 +38,7 @@ ## Pseudo Random Function Families -The PRF set primitive allows to redact data in a deterministic fashion, for -example personal identifiable information or internal IDs, or to come up with a -user ID from user information without revealing said information in the ID. This -allows someone with access to the output of the PRF without access to the key do -some types of analysis, while limiting others. - -Note that while in theory PRFs can be used in other ways, for example for -encryption or message authentication, the corresponding primitives should only -be used for these use cases. - -WARNING: Since PRFs operate deterministically on their input, using a PRF to -redact will not automatically provide anonymity, but only provide pseudonymity. -It is an important tool to build privacy aware systems, but has to be used -carefully. - -Minimal properties: - -- without knowledge of the key the PRF is indistinguishable from a random - function -- at least 128-bit security, also in multi-user scenarios (when an attacker is - not targeting a specific key, but any key from a set of up to 2<sup>32</sup> - keys) -- at least 16 byte of output available - -WARNING: While HMAC-SHA-2 and HKDF-SHA-2 behave like a cryptographically secure -hash function if the key is revealed, and still provide some protection against -revealing the input, AES-CMAC is only secure as long as the key is secure. - -Since Tink operates on key sets, this primitive exposes a corresponding set of -PRFs instead of a single PRF. The PRFs are indexed by a 32 bit key id. This can -be used to rotate the key used to redact a piece of information, without losing -the previous association. +See https://developers.google.com/tink/prf ## Hybrid Encryption
diff --git a/go/daead/BUILD.bazel b/go/daead/BUILD.bazel index 08c12b4..f7ec324 100644 --- a/go/daead/BUILD.bazel +++ b/go/daead/BUILD.bazel
@@ -36,6 +36,7 @@ name = "daead_test", srcs = [ "aes_siv_key_manager_test.go", + "daead_benchmark_test.go", "daead_factory_test.go", "daead_init_test.go", "daead_key_templates_test.go",
diff --git a/go/daead/daead_benchmark_test.go b/go/daead/daead_benchmark_test.go new file mode 100644 index 0000000..abc60bb --- /dev/null +++ b/go/daead/daead_benchmark_test.go
@@ -0,0 +1,71 @@ +// 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. +// +// ////////////////////////////////////////////////////////////////////////////// + +package daead_test + +import ( + "testing" + + "github.com/google/tink/go/daead" + "github.com/google/tink/go/keyset" + "github.com/google/tink/go/subtle/random" + tinkpb "github.com/google/tink/go/proto/tink_go_proto" +) + +// Benchmarks for Deterministic AEAD algorithms. + +func BenchmarkAESSIV(b *testing.B) { + const ( + plaintextSize = 16 * 1024 + associatedDataSize = 256 + ) + testCases := []struct { + name string + template *tinkpb.KeyTemplate + }{ + { + name: "AES256_SIV", + template: daead.AESSIVKeyTemplate(), + }, + } + for _, tc := range testCases { + b.Run(tc.name, func(b *testing.B) { + b.ReportAllocs() + + handle, err := keyset.NewHandle(tc.template) + if err != nil { + b.Fatal(err) + } + primitive, err := daead.New(handle) + if err != nil { + b.Fatal(err) + } + plaintext := random.GetRandomBytes(plaintextSize) + associatedData := random.GetRandomBytes(associatedDataSize) + b.ResetTimer() + for i := 0; i < b.N; i++ { + ciphertext, err := primitive.EncryptDeterministically(plaintext, associatedData) + if err != nil { + b.Fatal(err) + } + _, err = primitive.DecryptDeterministically(ciphertext, associatedData) + if err != nil { + b.Error(err) + } + } + }) + } +}
diff --git a/go/integration/gcpkms/BUILD.bazel b/go/integration/gcpkms/BUILD.bazel index ed6f7c4..9859a09 100644 --- a/go/integration/gcpkms/BUILD.bazel +++ b/go/integration/gcpkms/BUILD.bazel
@@ -23,6 +23,7 @@ go_test( name = "gcpkms_test", srcs = [ + "gcp_kms_aead_test.go", "gcp_kms_client_test.go", "gcp_kms_integration_test.go", ], @@ -31,10 +32,11 @@ "//testdata/gcp:credentials", "@google_root_pem//file", #keep ], + embed = [":gcpkms"], tags = ["manual"], deps = [ - ":gcpkms", "//aead", + "@org_golang_google_api//cloudkms/v1:cloudkms", "@org_golang_google_api//option", ], )
diff --git a/go/integration/gcpkms/gcp_kms_aead.go b/go/integration/gcpkms/gcp_kms_aead.go index ca4a043..410293e 100644 --- a/go/integration/gcpkms/gcp_kms_aead.go +++ b/go/integration/gcpkms/gcp_kms_aead.go
@@ -16,6 +16,8 @@ import ( "encoding/base64" + "fmt" + "hash/crc32" "google.golang.org/api/cloudkms/v1" @@ -24,45 +26,90 @@ // gcpAEAD represents a GCP KMS service to a particular URI. type gcpAEAD struct { - keyURI string - kms cloudkms.Service + keyName string + kms cloudkms.Service } var _ tink.AEAD = (*gcpAEAD)(nil) // newGCPAEAD returns a new GCP KMS service. -func newGCPAEAD(keyURI string, kms *cloudkms.Service) tink.AEAD { +func newGCPAEAD(keyName string, kms *cloudkms.Service) tink.AEAD { return &gcpAEAD{ - keyURI: keyURI, - kms: *kms, + keyName: keyName, + kms: *kms, } } -// Encrypt encrypts the plaintext with associatedData. +// Encrypt calls GCP KMS to encrypt the plaintext with associatedData and returns the resulting ciphertext. +// It returns an error if the call to KMS fails or if the response returned by KMS does not pass integrity verification +// (http://cloud.google.com/kms/docs/data-integrity-guidelines#calculating_and_verifying_checksums). func (a *gcpAEAD) Encrypt(plaintext, associatedData []byte) ([]byte, error) { req := &cloudkms.EncryptRequest{ - Plaintext: base64.URLEncoding.EncodeToString(plaintext), - AdditionalAuthenticatedData: base64.URLEncoding.EncodeToString(associatedData), + Plaintext: base64.URLEncoding.EncodeToString(plaintext), + PlaintextCrc32c: computeChecksum(plaintext), + AdditionalAuthenticatedData: base64.URLEncoding.EncodeToString(associatedData), + AdditionalAuthenticatedDataCrc32c: computeChecksum(associatedData), + // Send the integrity verification fields even if their value is 0. + ForceSendFields: []string{"PlaintextCrc32c", "AdditionalAuthenticatedDataCrc32c"}, } - resp, err := a.kms.Projects.Locations.KeyRings.CryptoKeys.Encrypt(a.keyURI, req).Do() + + resp, err := a.kms.Projects.Locations.KeyRings.CryptoKeys.Encrypt(a.keyName, req).Do() if err != nil { return nil, err } - return base64.StdEncoding.DecodeString(resp.Ciphertext) + if !resp.VerifiedPlaintextCrc32c { + return nil, fmt.Errorf("KMS request for %q is missing the checksum field plaintext_crc32c, and other information may be missing from the response. Please retry a limited number of times in case the error is transient", a.keyName) + } + if !resp.VerifiedAdditionalAuthenticatedDataCrc32c { + return nil, fmt.Errorf("KMS request for %q is missing the checksum field additional_authenticated_data_crc32c, and other information may be missing from the response. Please retry a limited number of times in case the error is transient", a.keyName) + } + ciphertext, err := base64.StdEncoding.DecodeString(resp.Ciphertext) + if err != nil { + return nil, err + } + if resp.CiphertextCrc32c != computeChecksum(ciphertext) { + return nil, fmt.Errorf("KMS response corrupted in transit for %q: the checksum in field ciphertext_crc32c did not match the data in field ciphertext. Please retry in case this is a transient error", a.keyName) + } + + return ciphertext, nil } -// Decrypt decrypts ciphertext with with associatedData. +// Decrypt calls GCP KMS to decrypt the ciphertext with with associatedData and returns the resulting plaintext. +// It returns an error if the call to KMS fails or if the response returned by KMS does not pass integrity verification +// (http://cloud.google.com/kms/docs/data-integrity-guidelines#calculating_and_verifying_checksums). func (a *gcpAEAD) Decrypt(ciphertext, associatedData []byte) ([]byte, error) { req := &cloudkms.DecryptRequest{ - Ciphertext: base64.URLEncoding.EncodeToString(ciphertext), - AdditionalAuthenticatedData: base64.URLEncoding.EncodeToString(associatedData), + Ciphertext: base64.URLEncoding.EncodeToString(ciphertext), + CiphertextCrc32c: computeChecksum(ciphertext), + AdditionalAuthenticatedData: base64.URLEncoding.EncodeToString(associatedData), + AdditionalAuthenticatedDataCrc32c: computeChecksum(associatedData), + // Send the integrity verification fields even if their value is 0. + ForceSendFields: []string{"CiphertextCrc32c", "AdditionalAuthenticatedDataCrc32c"}, } - resp, err := a.kms.Projects.Locations.KeyRings.CryptoKeys.Decrypt(a.keyURI, req).Do() + + resp, err := a.kms.Projects.Locations.KeyRings.CryptoKeys.Decrypt(a.keyName, req).Do() if err != nil { return nil, err } - return base64.StdEncoding.DecodeString(resp.Plaintext) + + plaintext, err := base64.StdEncoding.DecodeString(resp.Plaintext) + if err != nil { + return nil, err + } + if resp.PlaintextCrc32c != computeChecksum(plaintext) { + return nil, fmt.Errorf("KMS response corrupted in transit for %q: the checksum in field plaintext_crc32c did not match the data in field plaintext. Please retry in case this is a transient error", a.keyName) + } + return plaintext, nil +} + +// crc32cTable is used to compute checksums. It is defined as a package level variable to avoid +// re-computation on every CRC calculation. +var crc32cTable = crc32.MakeTable(crc32.Castagnoli) + +// computeChecksum returns the checksum that corresponds to the input value as an int64. +func computeChecksum(value []byte) int64 { + return int64(crc32.Checksum(value, crc32cTable)) }
diff --git a/go/integration/gcpkms/gcp_kms_aead_test.go b/go/integration/gcpkms/gcp_kms_aead_test.go new file mode 100644 index 0000000..d2461e3 --- /dev/null +++ b/go/integration/gcpkms/gcp_kms_aead_test.go
@@ -0,0 +1,270 @@ +// Copyright 2024 Google Inc. +// +// 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. + +package gcpkms + +import ( + "bytes" + "context" + "encoding/base64" + "encoding/json" + "hash/crc32" + "net/http" + "net/http/httptest" + "testing" + + "google.golang.org/api/cloudkms/v1" + "google.golang.org/api/option" +) + +func initializeServerWithResponse(ctx context.Context, t *testing.T, response any) (*httptest.Server, *cloudkms.Service) { + t.Helper() + var b []byte + switch r := response.(type) { + case *cloudkms.EncryptResponse, *cloudkms.DecryptResponse: + var err error + b, err = json.Marshal(r) + if err != nil { + t.Fatalf("unable to marshal response: %v", err) + } + default: + t.Fatalf("unsupported response type: %T", r) + } + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write(b) + })) + svc, err := cloudkms.NewService(ctx, option.WithoutAuthentication(), option.WithEndpoint(ts.URL)) + if err != nil { + t.Fatalf("unable to create client: %v", err) + } + return ts, svc +} + +func TestEncrypt_FailsWhenPlaintextUnverifed(t *testing.T) { + additionalData := []byte("additional data") + ciphertext := []byte("ciphertext") + ciphertextCrc32c := int64(crc32.Checksum(ciphertext, crc32.MakeTable(crc32.Castagnoli))) + + testcases := []struct { + name string + encryptResponse *cloudkms.EncryptResponse + }{ + { + name: "verified_plaintext_crc32c is false", + encryptResponse: &cloudkms.EncryptResponse{ + Ciphertext: base64.StdEncoding.EncodeToString(ciphertext), + CiphertextCrc32c: ciphertextCrc32c, + VerifiedPlaintextCrc32c: false, + VerifiedAdditionalAuthenticatedDataCrc32c: true, + }, + }, + { + name: "verified_plaintext_crc32c missing", + encryptResponse: &cloudkms.EncryptResponse{ + Ciphertext: base64.StdEncoding.EncodeToString(ciphertext), + CiphertextCrc32c: ciphertextCrc32c, + VerifiedAdditionalAuthenticatedDataCrc32c: true, + }, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + ts, svc := initializeServerWithResponse(ctx, t, tc.encryptResponse) + defer ts.Close() + + aead := newGCPAEAD("key name", svc) + // Encryption should fail for all plaintexts (empty or non-empty) + _, err := aead.Encrypt([]byte("plaintext"), additionalData) + if err == nil { + t.Errorf("a.Encrypt err = nil, want error") + } + _, err = aead.Encrypt([]byte(""), additionalData) + if err == nil { + t.Errorf("a.Encrypt err = nil, want error") + } + }) + } +} + +func TestEncrypt_FailsWhenAdditionalAuthenticatedDataUnverifed(t *testing.T) { + plaintext := []byte("plaintext") + ciphertext := []byte("ciphertext") + ciphertextCrc32c := int64(crc32.Checksum(ciphertext, crc32.MakeTable(crc32.Castagnoli))) + + testcases := []struct { + name string + encryptResponse *cloudkms.EncryptResponse + }{ + { + name: "verified_additional_authenticated_data_crc32c is false", + encryptResponse: &cloudkms.EncryptResponse{ + Ciphertext: base64.StdEncoding.EncodeToString(ciphertext), + CiphertextCrc32c: ciphertextCrc32c, + VerifiedPlaintextCrc32c: true, + VerifiedAdditionalAuthenticatedDataCrc32c: false, + }, + }, + { + name: "verified_additional_authenticated_data_crc32c missing", + encryptResponse: &cloudkms.EncryptResponse{ + Ciphertext: base64.StdEncoding.EncodeToString(ciphertext), + CiphertextCrc32c: ciphertextCrc32c, + VerifiedPlaintextCrc32c: true, + }, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + ts, svc := initializeServerWithResponse(ctx, t, tc.encryptResponse) + defer ts.Close() + + aead := newGCPAEAD("key name", svc) + // Encryption should fail for all additional authenticated data (empty or non-empty) + _, err := aead.Encrypt(plaintext, []byte("additional data")) + if err == nil { + t.Errorf("a.Encrypt err = nil, want error") + } + _, err = aead.Encrypt(plaintext, []byte("")) + if err == nil { + t.Errorf("a.Encrypt err = nil, want error") + } + }) + } +} + +func TestEncrypt_FailsWithInvalidCiphertextCrc32c(t *testing.T) { + testcases := []struct { + name string + encryptResponse *cloudkms.EncryptResponse + }{ + { + name: "ciphertext_crc32c does not match ciphertext", + encryptResponse: &cloudkms.EncryptResponse{ + Ciphertext: base64.StdEncoding.EncodeToString([]byte("ciphertext")), + CiphertextCrc32c: int64(1), + VerifiedPlaintextCrc32c: true, + VerifiedAdditionalAuthenticatedDataCrc32c: true, + }, + }, + { + name: "ciphertext_crc32c missing", + encryptResponse: &cloudkms.EncryptResponse{ + Ciphertext: base64.StdEncoding.EncodeToString([]byte("ciphertext")), + VerifiedPlaintextCrc32c: true, + VerifiedAdditionalAuthenticatedDataCrc32c: true, + }, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + ts, svc := initializeServerWithResponse(ctx, t, + tc.encryptResponse) + defer ts.Close() + + aead := newGCPAEAD("key name", svc) + _, err := aead.Encrypt([]byte("plaintext"), []byte("additional data")) + if err == nil { + t.Errorf("a.Encrypt err = nil, want error") + } + }) + } +} + +func TestEncrypt_Success(t *testing.T) { + ciphertext := []byte("ciphertext") + ciphertextCrc32c := int64(crc32.Checksum(ciphertext, crc32.MakeTable(crc32.Castagnoli))) + + ctx := context.Background() + ts, svc := initializeServerWithResponse(ctx, t, + &cloudkms.EncryptResponse{ + Ciphertext: base64.StdEncoding.EncodeToString(ciphertext), + CiphertextCrc32c: ciphertextCrc32c, + VerifiedPlaintextCrc32c: true, + VerifiedAdditionalAuthenticatedDataCrc32c: true, + }) + defer ts.Close() + + aead := newGCPAEAD("key name", svc) + gotCiphertext, err := aead.Encrypt([]byte("plaintext"), []byte("additional data")) + if err != nil { + t.Errorf("a.Encrypt err = %q, want nil", err) + } + if !bytes.Equal(gotCiphertext, ciphertext) { + t.Errorf("Returned ciphertext: %q, want: %q", gotCiphertext, ciphertext) + } +} + +func TestDecrypt_FailsWithInvalidPlaintextCrc32c(t *testing.T) { + testcases := []struct { + name string + decryptResponse *cloudkms.DecryptResponse + }{ + { + name: "plaintext_crc32c does not match plaintext", + decryptResponse: &cloudkms.DecryptResponse{ + Plaintext: base64.StdEncoding.EncodeToString([]byte("plaintext")), + PlaintextCrc32c: int64(1), + }, + }, + { + name: "plaintext_crc32c missing", + decryptResponse: &cloudkms.DecryptResponse{ + Plaintext: base64.StdEncoding.EncodeToString([]byte("plaintext")), + }, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + ts, svc := initializeServerWithResponse(ctx, t, + tc.decryptResponse) + defer ts.Close() + + aead := newGCPAEAD("key name", svc) + _, err := aead.Decrypt([]byte("ciphertext"), []byte("additional data")) + if err == nil { + t.Errorf("a.Decrypt err = nil, want error") + } + }) + } +} + +func TestDecrypt_Success(t *testing.T) { + plaintext := []byte("plaintext") + plaintextCrc32c := int64(crc32.Checksum(plaintext, crc32.MakeTable(crc32.Castagnoli))) + + ctx := context.Background() + ts, svc := initializeServerWithResponse(ctx, t, + &cloudkms.DecryptResponse{ + Plaintext: base64.StdEncoding.EncodeToString(plaintext), + PlaintextCrc32c: plaintextCrc32c, + }) + defer ts.Close() + + aead := newGCPAEAD("key name", svc) + gotPlaintext, err := aead.Decrypt([]byte("ciphertext"), []byte("additional data")) + if err != nil { + t.Errorf("a.Decrypt err = %q, want nil", err) + } + if !bytes.Equal(gotPlaintext, plaintext) { + t.Errorf("Returned plaitext: %q, want: %q", gotPlaintext, plaintext) + } +}
diff --git a/go/integration/gcpkms/gcp_kms_client.go b/go/integration/gcpkms/gcp_kms_client.go index 7e36bbe..cbab5a7 100644 --- a/go/integration/gcpkms/gcp_kms_client.go +++ b/go/integration/gcpkms/gcp_kms_client.go
@@ -77,6 +77,6 @@ return nil, errors.New("unsupported keyURI") } - uri := strings.TrimPrefix(keyURI, gcpPrefix) - return newGCPAEAD(uri, c.kms), nil + keyName := strings.TrimPrefix(keyURI, gcpPrefix) + return newGCPAEAD(keyName, c.kms), nil }
diff --git a/go/integration/gcpkms/gcp_kms_integration_test.go b/go/integration/gcpkms/gcp_kms_integration_test.go index 292bb4b..30ad657 100644 --- a/go/integration/gcpkms/gcp_kms_integration_test.go +++ b/go/integration/gcpkms/gcp_kms_integration_test.go
@@ -87,3 +87,57 @@ t.Error("a.Decrypt(ciphertext, []byte(\"invalid associatedData\")) err = nil, want error") } } + +func TestAead(t *testing.T) { + srcDir, ok := os.LookupEnv("TEST_SRCDIR") + if !ok { + t.Skip("TEST_SRCDIR not set") + } + workspaceDir, ok := os.LookupEnv("TEST_WORKSPACE") + if !ok { + t.Skip("TEST_WORKSPACE not set") + } + ctx := context.Background() + gcpClient, err := gcpkms.NewClientWithOptions( + ctx, keyURI, option.WithCredentialsFile(filepath.Join(srcDir, workspaceDir, credFile))) + if err != nil { + t.Fatalf("gcpkms.NewClientWithOptions() err = %q, want nil", err) + } + aead, err := gcpClient.GetAEAD(keyURI) + if err != nil { + t.Fatalf("gcpClient.GetAEAD(keyURI) err = %q, want nil", err) + } + + testcases := []struct { + name string + plaintext []byte + associatedData []byte + }{ + { + name: "empty_plaintext", + plaintext: []byte(""), + associatedData: []byte("authenticated data"), + }, + { + name: "empty_associated_data", + plaintext: []byte("plaintext"), + associatedData: []byte(""), + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + ciphertext, err := aead.Encrypt(tc.plaintext, tc.associatedData) + if err != nil { + t.Fatalf("aead.Encrypt(plaintext, associatedData) err = %q, want nil", err) + } + gotPlaintext, err := aead.Decrypt(ciphertext, tc.associatedData) + if err != nil { + t.Fatalf("aead.Decrypt(ciphertext, associatedData) err = %q, want nil", err) + } + if !bytes.Equal(gotPlaintext, tc.plaintext) { + t.Errorf("aead.Decrypt() = %q, want %q", gotPlaintext, tc.plaintext) + } + }) + } +}
diff --git a/go/mac/BUILD.bazel b/go/mac/BUILD.bazel index 0120395..ac76377 100644 --- a/go/mac/BUILD.bazel +++ b/go/mac/BUILD.bazel
@@ -40,6 +40,7 @@ srcs = [ "aes_cmac_key_manager_test.go", "hmac_key_manager_test.go", + "mac_benchmark_test.go", "mac_factory_test.go", "mac_init_test.go", "mac_key_templates_test.go",
diff --git a/go/mac/mac_benchmark_test.go b/go/mac/mac_benchmark_test.go new file mode 100644 index 0000000..514960a --- /dev/null +++ b/go/mac/mac_benchmark_test.go
@@ -0,0 +1,113 @@ +// 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. +// +// ////////////////////////////////////////////////////////////////////////////// + +package mac_test + +import ( + "testing" + + "github.com/google/tink/go/keyset" + "github.com/google/tink/go/mac" + "github.com/google/tink/go/subtle/random" + tinkpb "github.com/google/tink/go/proto/tink_go_proto" +) + +// Benchmarks for MAC algorithms. + +var benchmarkTestCases = []struct { + name string + template *tinkpb.KeyTemplate + dataSize uint32 +}{ + { + name: "HMAC_SHA256_16", + template: mac.HMACSHA256Tag128KeyTemplate(), + dataSize: 16, + }, { + name: "HMAC_SHA512_16", + template: mac.HMACSHA512Tag256KeyTemplate(), + dataSize: 16, + }, { + name: "AES_CMAC_16", + template: mac.AESCMACTag128KeyTemplate(), + dataSize: 16, + }, { + name: "HMAC_SHA256_16k", + template: mac.HMACSHA256Tag128KeyTemplate(), + dataSize: 16 * 1024, + }, { + name: "HMAC_SHA512_16k", + template: mac.HMACSHA512Tag256KeyTemplate(), + dataSize: 16 * 1024, + }, { + name: "AES_CMAC_16k", + template: mac.AESCMACTag128KeyTemplate(), + dataSize: 16 * 1024, + }, +} + +func BenchmarkComputeMac(b *testing.B) { + for _, tc := range benchmarkTestCases { + b.Run(tc.name, func(b *testing.B) { + b.ReportAllocs() + + handle, err := keyset.NewHandle(tc.template) + if err != nil { + b.Fatal(err) + } + primitive, err := mac.New(handle) + if err != nil { + b.Fatal(err) + } + data := random.GetRandomBytes(tc.dataSize) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := primitive.ComputeMAC(data) + if err != nil { + b.Error(err) + } + } + }) + } +} + +func BenchmarkVerifyMac(b *testing.B) { + for _, tc := range benchmarkTestCases { + b.Run(tc.name, func(b *testing.B) { + b.ReportAllocs() + + handle, err := keyset.NewHandle(tc.template) + if err != nil { + b.Fatal(err) + } + primitive, err := mac.New(handle) + if err != nil { + b.Fatal(err) + } + data := random.GetRandomBytes(tc.dataSize) + tag, err := primitive.ComputeMAC(data) + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err = primitive.VerifyMAC(tag, data); err != nil { + b.Error(err) + } + } + }) + } +}
diff --git a/go/prf/prf_set.go b/go/prf/prf_set.go index daf3084..c54ee41 100644 --- a/go/prf/prf_set.go +++ b/go/prf/prf_set.go
@@ -23,36 +23,42 @@ "github.com/google/tink/go/monitoring" ) -// The PRF interface is an abstraction for an element of a pseudo random -// function family, selected by a key. It has the following property: -// - It is deterministic. PRF.compute(input, length) will always return the -// same output if the same key is used. PRF.compute(input, length1) will be -// a prefix of PRF.compute(input, length2) if length1 < length2 and the same +// The PRF interface is an abstraction for an element of a pseudo-random +// function family, selected by a key. +// +// It has the following properties: +// - It is deterministic. ComputePRF(input, length) will always return the +// same output if the same key is used. ComputePRF(input, length1) will be a +// prefix of ComputePRF(input, length2) if length1 < length2 and the same // key is used. -// - It is indistinguishable from a random function: -// Given the evaluation of n different inputs, an attacker cannot -// distinguish between the PRF and random bytes on an input different from -// the n that are known. +// - It is indistinguishable from a random function. Given the evaluation of +// n different inputs, an attacker cannot distinguish between the PRF and +// random bytes on an input different from the n that are known. // // Use cases for PRF are deterministic redaction of PII, keyed hash functions, // creating sub IDs that do not allow joining with the original dataset without // knowing the key. -// While PRFs can be used in order to prove authenticity of a message, using the -// MAC interface is recommended for that use case, as it has support for +// +// While PRFs can be used in order to prove authenticity of a message, using +// the MAC interface is recommended for that use case, as it has support for // verification, avoiding the security problems that often happen during // verification, and having automatic support for key rotation. It also allows // for non-deterministic MAC algorithms. type PRF interface { // Computes the PRF selected by the underlying key on input and // returns the first outputLength bytes. + // // When choosing this parameter keep the birthday paradox in mind. // If you have 2^n different inputs that your system has to handle // set the output length (in bytes) to at least // ceil(n/4 + 4) - // This corresponds to 2*n + 32 bits, meaning a collision will occur with - // a probability less than 1:2^32. When in doubt, request a security review. - // Returns a non ok status if the algorithm fails or if the output of - // algorithm is less than outputLength. + // + // This corresponds to 2*n + 32 bits, meaning a collision will occur + // with a probability less than 1:2^32. When in doubt, request a + // security review. + // + // Returns a non-nil error if the algorithm fails or if the output of + // the underlying algorithm is less than outputLength. ComputePRF(input []byte, outputLength uint32) ([]byte, error) } @@ -74,10 +80,11 @@ return p, nil } -// Set is a set of PRFs. A Tink Keyset can be converted into a set of PRFs using this primitive. Every -// key in the keyset corresponds to a PRF in the prf.Set. -// Every PRF in the set is given an ID, which is the same ID as the key id in -// the Keyset. +// Set is a set of PRFs. +// +// A Tink Keyset can be converted into a set of PRFs using this primitive. +// Every key in the keyset corresponds to a PRF in the prf.Set. Every PRF in +// the set is given an ID, which is the same ID as the key id in the Keyset. type Set struct { // PrimaryID is the key ID marked as primary in the corresponding Keyset. PrimaryID uint32 @@ -85,7 +92,7 @@ PRFs map[uint32]PRF } -// ComputePrimaryPRF is equivalent to set.PRFs[set.PrimaryID].ComputePRF(input, outputLength). +// ComputePrimaryPRF is equivalent to set.PRFs[set.PrimaryID].ComputePRF(). func (s Set) ComputePrimaryPRF(input []byte, outputLength uint32) ([]byte, error) { prf, ok := s.PRFs[s.PrimaryID] if !ok {
diff --git a/java_src/proto/aes_cmac_prf.proto b/java_src/proto/aes_cmac_prf.proto index cfc4cf1..dd8dcae 100644 --- a/java_src/proto/aes_cmac_prf.proto +++ b/java_src/proto/aes_cmac_prf.proto
@@ -20,7 +20,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_cmac_prf_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_cmac_prf_go_proto"; // key_type: type.googleapis.com/google.crypto.tink.AesCmacPrfKey message AesCmacPrfKey {
diff --git a/java_src/proto/aes_ctr.proto b/java_src/proto/aes_ctr.proto index 23028c6..a873097 100644 --- a/java_src/proto/aes_ctr.proto +++ b/java_src/proto/aes_ctr.proto
@@ -20,7 +20,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_ctr_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_ctr_go_proto"; message AesCtrParams { uint32 iv_size = 1;
diff --git a/java_src/proto/aes_ctr_hmac_aead.proto b/java_src/proto/aes_ctr_hmac_aead.proto index a95a80d..7059346 100644 --- a/java_src/proto/aes_ctr_hmac_aead.proto +++ b/java_src/proto/aes_ctr_hmac_aead.proto
@@ -23,7 +23,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_ctr_hmac_aead_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_ctr_hmac_aead_go_proto"; message AesCtrHmacAeadKeyFormat { AesCtrKeyFormat aes_ctr_key_format = 1;
diff --git a/java_src/proto/aes_ctr_hmac_streaming.proto b/java_src/proto/aes_ctr_hmac_streaming.proto index 2d5678b..599e267 100644 --- a/java_src/proto/aes_ctr_hmac_streaming.proto +++ b/java_src/proto/aes_ctr_hmac_streaming.proto
@@ -23,7 +23,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_ctr_hmac_streaming_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_ctr_hmac_streaming_go_proto"; message AesCtrHmacStreamingParams { uint32 ciphertext_segment_size = 1;
diff --git a/java_src/proto/aes_eax.proto b/java_src/proto/aes_eax.proto index 82ff7ee..f086d49 100644 --- a/java_src/proto/aes_eax.proto +++ b/java_src/proto/aes_eax.proto
@@ -20,7 +20,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_eax_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_eax_go_proto"; // only allowing tag size in bytes = 16 message AesEaxParams {
diff --git a/java_src/proto/aes_gcm.proto b/java_src/proto/aes_gcm.proto index 459ec3d..1908c4a 100644 --- a/java_src/proto/aes_gcm.proto +++ b/java_src/proto/aes_gcm.proto
@@ -20,7 +20,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_gcm_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_gcm_go_proto"; option objc_class_prefix = "TINKPB"; message AesGcmKeyFormat {
diff --git a/java_src/proto/aes_gcm_hkdf_streaming.proto b/java_src/proto/aes_gcm_hkdf_streaming.proto index 2c445ae..a436abb 100644 --- a/java_src/proto/aes_gcm_hkdf_streaming.proto +++ b/java_src/proto/aes_gcm_hkdf_streaming.proto
@@ -24,7 +24,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_gcm_hkdf_streaming_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_gcm_hkdf_streaming_go_proto"; message AesGcmHkdfStreamingParams { uint32 ciphertext_segment_size = 1;
diff --git a/java_src/proto/aes_gcm_siv.proto b/java_src/proto/aes_gcm_siv.proto index 8cfea19..c663aee 100644 --- a/java_src/proto/aes_gcm_siv.proto +++ b/java_src/proto/aes_gcm_siv.proto
@@ -20,7 +20,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_gcm_siv_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_gcm_siv_go_proto"; // The only allowed IV size is 12 bytes and tag size is 16 bytes. // Thus, accept no params.
diff --git a/java_src/src/main/java/com/google/crypto/tink/prf/Prf.java b/java_src/src/main/java/com/google/crypto/tink/prf/Prf.java index 1bdf774..d365bd4 100644 --- a/java_src/src/main/java/com/google/crypto/tink/prf/Prf.java +++ b/java_src/src/main/java/com/google/crypto/tink/prf/Prf.java
@@ -20,14 +20,19 @@ import java.security.GeneralSecurityException; /** - * The PRF interface is an abstraction for an element of a pseudo random function family, selected - * by a key. It has the following properties: + * The Prf interface is an abstraction for an element of a pseudo random function family, selected + * by a key. * - * <p>- It is deterministic: PRF.compute(input, length) will always return the same output if the - * same key is used. PRF.compute(input, length1) will be a prefix of PRF.compute(input, length2) if - * length1 < length2 and the same key is used. - It is indistinguishable from a random function: - * Given the evaluation of n different inputs, an attacker cannot distinguish between the PRF and - * random bytes on an input different from the n that are known. + * <p>It has the following properties: + * + * <ul> + * <li>It is deterministic: {@link #compute(input, outputLength)} will always return the same + * output if the same key is used. {@code compute(input, length1)} will be a prefix of {@code + * compute(input, length2)} if {@code length1 < length2} and the same key is used. + * <li>It is indistinguishable from a random function: Given the evaluation of n different inputs, + * an attacker cannot distinguish between the PRF and random bytes on an input different from + * the n that are known. + * </ul> * * <p>Use cases for PRF are deterministic redaction of PII, keyed hash functions, creating sub IDs * that do not allow joining with the original dataset without knowing the key. While PRFs can be
diff --git a/java_src/tools/gen_java_test_rules.bzl b/java_src/tools/gen_java_test_rules.bzl index ab532d4..a60e19f 100644 --- a/java_src/tools/gen_java_test_rules.bzl +++ b/java_src/tools/gen_java_test_rules.bzl
@@ -21,7 +21,7 @@ """ -load("//third_party/bazel_rules/rules_java/java:java_test.bzl", "java_test") +load("@rules_java//java:defs.bzl", "java_test") def gen_java_test_rules( test_files,
diff --git a/java_src/tools/jar_jar.bzl b/java_src/tools/jar_jar.bzl index 939de65..dd6c6ee 100644 --- a/java_src/tools/jar_jar.bzl +++ b/java_src/tools/jar_jar.bzl
@@ -14,6 +14,8 @@ """starlark rules for jarjar. See https://github.com/pantsbuild/jarjar """ +load("@rules_java//java:defs.bzl", "JavaInfo") + def _jar_jar_impl(ctx): ctx.actions.run( inputs = [ctx.file.rules, ctx.file.input_jar],
diff --git a/java_src/tools/java_single_jar.bzl b/java_src/tools/java_single_jar.bzl index 9523c1c..647ea65 100644 --- a/java_src/tools/java_single_jar.bzl +++ b/java_src/tools/java_single_jar.bzl
@@ -14,6 +14,8 @@ """ Definition of java_single_jar. """ +load("@rules_java//java:defs.bzl", "JavaInfo") + def _check_non_empty(value, name): if not value: fail("%s must be non-empty" % name)
diff --git a/java_src/tools/javadoc.bzl b/java_src/tools/javadoc.bzl index 5e68c1f..ed8f0f2 100644 --- a/java_src/tools/javadoc.bzl +++ b/java_src/tools/javadoc.bzl
@@ -30,6 +30,8 @@ external_javadoc_links: a list of URLs that are passed to Javadoc's `-linkoffline` flag """ +load("@rules_java//java:defs.bzl", "JavaInfo", "java_common") + def _check_non_empty(value, name): if not value: fail("%s must be non-empty" % name)
diff --git a/proto/aes_cmac_prf.proto b/proto/aes_cmac_prf.proto index cfc4cf1..dd8dcae 100644 --- a/proto/aes_cmac_prf.proto +++ b/proto/aes_cmac_prf.proto
@@ -20,7 +20,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_cmac_prf_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_cmac_prf_go_proto"; // key_type: type.googleapis.com/google.crypto.tink.AesCmacPrfKey message AesCmacPrfKey {
diff --git a/proto/aes_ctr.proto b/proto/aes_ctr.proto index 23028c6..a873097 100644 --- a/proto/aes_ctr.proto +++ b/proto/aes_ctr.proto
@@ -20,7 +20,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_ctr_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_ctr_go_proto"; message AesCtrParams { uint32 iv_size = 1;
diff --git a/proto/aes_ctr_hmac_aead.proto b/proto/aes_ctr_hmac_aead.proto index a95a80d..7059346 100644 --- a/proto/aes_ctr_hmac_aead.proto +++ b/proto/aes_ctr_hmac_aead.proto
@@ -23,7 +23,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_ctr_hmac_aead_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_ctr_hmac_aead_go_proto"; message AesCtrHmacAeadKeyFormat { AesCtrKeyFormat aes_ctr_key_format = 1;
diff --git a/proto/aes_ctr_hmac_streaming.proto b/proto/aes_ctr_hmac_streaming.proto index 2d5678b..599e267 100644 --- a/proto/aes_ctr_hmac_streaming.proto +++ b/proto/aes_ctr_hmac_streaming.proto
@@ -23,7 +23,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_ctr_hmac_streaming_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_ctr_hmac_streaming_go_proto"; message AesCtrHmacStreamingParams { uint32 ciphertext_segment_size = 1;
diff --git a/proto/aes_eax.proto b/proto/aes_eax.proto index 82ff7ee..f086d49 100644 --- a/proto/aes_eax.proto +++ b/proto/aes_eax.proto
@@ -20,7 +20,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_eax_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_eax_go_proto"; // only allowing tag size in bytes = 16 message AesEaxParams {
diff --git a/proto/aes_gcm.proto b/proto/aes_gcm.proto index 459ec3d..1908c4a 100644 --- a/proto/aes_gcm.proto +++ b/proto/aes_gcm.proto
@@ -20,7 +20,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_gcm_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_gcm_go_proto"; option objc_class_prefix = "TINKPB"; message AesGcmKeyFormat {
diff --git a/proto/aes_gcm_hkdf_streaming.proto b/proto/aes_gcm_hkdf_streaming.proto index 2c445ae..a436abb 100644 --- a/proto/aes_gcm_hkdf_streaming.proto +++ b/proto/aes_gcm_hkdf_streaming.proto
@@ -24,7 +24,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_gcm_hkdf_streaming_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_gcm_hkdf_streaming_go_proto"; message AesGcmHkdfStreamingParams { uint32 ciphertext_segment_size = 1;
diff --git a/proto/aes_gcm_siv.proto b/proto/aes_gcm_siv.proto index 8cfea19..c663aee 100644 --- a/proto/aes_gcm_siv.proto +++ b/proto/aes_gcm_siv.proto
@@ -20,7 +20,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_gcm_siv_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_gcm_siv_go_proto"; // The only allowed IV size is 12 bytes and tag size is 16 bytes. // Thus, accept no params.
diff --git a/python/tink/jwt/_json_util_test.py b/python/tink/jwt/_json_util_test.py index 527a779..15462e9 100644 --- a/python/tink/jwt/_json_util_test.py +++ b/python/tink/jwt/_json_util_test.py
@@ -14,6 +14,7 @@ """Tests for tink.python.tink.jwt._json_util.""" from absl.testing import absltest + from tink.jwt import _json_util from tink.jwt import _jwt_error @@ -37,7 +38,10 @@ _json_util.json_loads('{"a":"a1", "a":"a2"}') def test_json_loads_recursion(self): - num_recursions = 1000 + # NOTE: Python 3.12 has raised the maximum C recursion limit to 1500 [1]. + # + # [1] https://github.com/python/cpython/pull/107618 + num_recursions = 2000 recursive_json = ('{"a":' * num_recursions) + '""' + ('}' * num_recursions) with self.assertRaises(_jwt_error.JwtInvalidError): _json_util.json_loads(recursive_json)
diff --git a/python/tink/proto/aes_cmac_prf.proto b/python/tink/proto/aes_cmac_prf.proto index cfc4cf1..dd8dcae 100644 --- a/python/tink/proto/aes_cmac_prf.proto +++ b/python/tink/proto/aes_cmac_prf.proto
@@ -20,7 +20,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_cmac_prf_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_cmac_prf_go_proto"; // key_type: type.googleapis.com/google.crypto.tink.AesCmacPrfKey message AesCmacPrfKey {
diff --git a/python/tink/proto/aes_ctr.proto b/python/tink/proto/aes_ctr.proto index 23028c6..a873097 100644 --- a/python/tink/proto/aes_ctr.proto +++ b/python/tink/proto/aes_ctr.proto
@@ -20,7 +20,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_ctr_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_ctr_go_proto"; message AesCtrParams { uint32 iv_size = 1;
diff --git a/python/tink/proto/aes_ctr_hmac_aead.proto b/python/tink/proto/aes_ctr_hmac_aead.proto index 6c8927a..fce983c 100644 --- a/python/tink/proto/aes_ctr_hmac_aead.proto +++ b/python/tink/proto/aes_ctr_hmac_aead.proto
@@ -23,7 +23,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_ctr_hmac_aead_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_ctr_hmac_aead_go_proto"; message AesCtrHmacAeadKeyFormat { AesCtrKeyFormat aes_ctr_key_format = 1;
diff --git a/python/tink/proto/aes_ctr_hmac_streaming.proto b/python/tink/proto/aes_ctr_hmac_streaming.proto index 7dcb44f..a20a6a9 100644 --- a/python/tink/proto/aes_ctr_hmac_streaming.proto +++ b/python/tink/proto/aes_ctr_hmac_streaming.proto
@@ -23,7 +23,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_ctr_hmac_streaming_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_ctr_hmac_streaming_go_proto"; message AesCtrHmacStreamingParams { uint32 ciphertext_segment_size = 1;
diff --git a/python/tink/proto/aes_eax.proto b/python/tink/proto/aes_eax.proto index 82ff7ee..f086d49 100644 --- a/python/tink/proto/aes_eax.proto +++ b/python/tink/proto/aes_eax.proto
@@ -20,7 +20,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_eax_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_eax_go_proto"; // only allowing tag size in bytes = 16 message AesEaxParams {
diff --git a/python/tink/proto/aes_gcm.proto b/python/tink/proto/aes_gcm.proto index 459ec3d..1908c4a 100644 --- a/python/tink/proto/aes_gcm.proto +++ b/python/tink/proto/aes_gcm.proto
@@ -20,7 +20,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_gcm_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_gcm_go_proto"; option objc_class_prefix = "TINKPB"; message AesGcmKeyFormat {
diff --git a/python/tink/proto/aes_gcm_hkdf_streaming.proto b/python/tink/proto/aes_gcm_hkdf_streaming.proto index 6800e3b..43862c4 100644 --- a/python/tink/proto/aes_gcm_hkdf_streaming.proto +++ b/python/tink/proto/aes_gcm_hkdf_streaming.proto
@@ -24,7 +24,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_gcm_hkdf_streaming_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_gcm_hkdf_streaming_go_proto"; message AesGcmHkdfStreamingParams { uint32 ciphertext_segment_size = 1;
diff --git a/python/tink/proto/aes_gcm_siv.proto b/python/tink/proto/aes_gcm_siv.proto index 8cfea19..c663aee 100644 --- a/python/tink/proto/aes_gcm_siv.proto +++ b/python/tink/proto/aes_gcm_siv.proto
@@ -20,7 +20,7 @@ option java_package = "com.google.crypto.tink.proto"; option java_multiple_files = true; -option go_package = "github.com/tink-crypto/tink-go/v2/aes_gcm_siv_go_proto"; +option go_package = "github.com/tink-crypto/tink-go/v2/proto/aes_gcm_siv_go_proto"; // The only allowed IV size is 12 bytes and tag size is 16 bytes. // Thus, accept no params.