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.