// Copyright 2021 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/internal/jwt_mac_wrapper.h" #include #include #include #include #include "gtest/gtest.h" #include "absl/strings/str_split.h" #include "tink/cleartext_keyset_handle.h" #include "tink/jwt/internal/json_util.h" #include "tink/jwt/internal/jwt_format.h" #include "tink/jwt/internal/jwt_hmac_key_manager.h" #include "tink/keyset_manager.h" #include "tink/primitive_set.h" #include "tink/util/status.h" #include "tink/util/test_matchers.h" #include "tink/util/test_util.h" #include "proto/jwt_hmac.pb.h" #include "proto/tink.pb.h" using google::crypto::tink::JwtHmacAlgorithm; using google::crypto::tink::JwtHmacKeyFormat; using google::crypto::tink::KeyTemplate; using google::crypto::tink::OutputPrefixType; namespace crypto { namespace tink { namespace jwt_internal { namespace { using ::crypto::tink::CleartextKeysetHandle; using ::crypto::tink::test::IsOk; using ::crypto::tink::test::IsOkAndHolds; using ::google::crypto::tink::Keyset; using ::testing::Eq; using ::testing::Not; KeyTemplate createTemplate(OutputPrefixType output_prefix) { KeyTemplate key_template; key_template.set_type_url( "type.googleapis.com/google.crypto.tink.JwtHmacKey"); key_template.set_output_prefix_type(output_prefix); JwtHmacKeyFormat key_format; key_format.set_key_size(32); key_format.set_algorithm(JwtHmacAlgorithm::HS256); key_format.SerializeToString(key_template.mutable_value()); return key_template; } std::unique_ptr KeysetHandleWithNewKeyId( const KeysetHandle& keyset_handle) { Keyset keyset(CleartextKeysetHandle::GetKeyset(keyset_handle)); uint32_t new_key_id = keyset.mutable_key(0)->key_id() ^ 0xdeadbeef; keyset.mutable_key(0)->set_key_id(new_key_id); keyset.set_primary_key_id(new_key_id); return CleartextKeysetHandle::GetKeysetHandle(keyset); } std::unique_ptr KeysetHandleWithTinkPrefix( const KeysetHandle& keyset_handle) { Keyset keyset(CleartextKeysetHandle::GetKeyset(keyset_handle)); keyset.mutable_key(0)->set_output_prefix_type(OutputPrefixType::TINK); return CleartextKeysetHandle::GetKeysetHandle(keyset); } class JwtMacWrapperTest : public ::testing::Test { protected: void SetUp() override { ASSERT_THAT( Registry::RegisterPrimitiveWrapper(absl::make_unique()), IsOk()); ASSERT_THAT(Registry::RegisterKeyTypeManager( absl::make_unique(), true), IsOk()); } }; TEST_F(JwtMacWrapperTest, WrapNullptr) { util::StatusOr> mac_result = JwtMacWrapper().Wrap(nullptr); EXPECT_FALSE(mac_result.ok()); } TEST_F(JwtMacWrapperTest, WrapEmpty) { auto jwt_mac_set = absl::make_unique>(); util::StatusOr> jwt_mac_result = JwtMacWrapper().Wrap(std::move(jwt_mac_set)); EXPECT_FALSE(jwt_mac_result.ok()); } TEST_F(JwtMacWrapperTest, CannotWrapPrimitivesFromNonRawOrTinkKeys) { KeyTemplate tink_key_template = createTemplate(OutputPrefixType::LEGACY); util::StatusOr> keyset_handle = KeysetHandle::GenerateNew(tink_key_template); EXPECT_THAT(keyset_handle, IsOk()); EXPECT_FALSE((*keyset_handle)->GetPrimitive().status().ok()); } TEST_F(JwtMacWrapperTest, GenerateRawComputeVerifySuccess) { KeyTemplate key_template = createTemplate(OutputPrefixType::RAW); util::StatusOr> keyset_handle = KeysetHandle::GenerateNew(key_template); EXPECT_THAT(keyset_handle, IsOk()); util::StatusOr> jwt_mac = (*keyset_handle)->GetPrimitive(); EXPECT_THAT(jwt_mac, IsOk()); util::StatusOr raw_jwt = RawJwtBuilder().SetIssuer("issuer").WithoutExpiration().Build(); ASSERT_THAT(raw_jwt, IsOk()); util::StatusOr compact = (*jwt_mac)->ComputeMacAndEncode(*raw_jwt); ASSERT_THAT(compact, IsOk()); util::StatusOr validator = JwtValidatorBuilder() .ExpectIssuer("issuer") .AllowMissingExpiration() .Build(); ASSERT_THAT(validator, IsOk()); util::StatusOr verified_jwt = (*jwt_mac)->VerifyMacAndDecode(*compact, *validator); ASSERT_THAT(verified_jwt, IsOk()); EXPECT_THAT(verified_jwt->GetIssuer(), IsOkAndHolds("issuer")); util::StatusOr validator2 = JwtValidatorBuilder() .ExpectIssuer("unknown") .AllowMissingExpiration() .Build(); ASSERT_THAT(validator2, IsOk()); util::StatusOr verified_jwt2 = (*jwt_mac)->VerifyMacAndDecode(*compact, *validator2); EXPECT_FALSE(verified_jwt2.ok()); // Make sure the error message is interesting EXPECT_THAT(verified_jwt2.status().message(), Eq("wrong issuer")); // Raw primitives don't add a kid header, Tink primitives require a kid // header to be set. Thefore, changing the output prefix to TINK makes the // validation fail. std::unique_ptr tink_keyset_handle = KeysetHandleWithTinkPrefix(**keyset_handle); util::StatusOr> tink_jwt_mac = tink_keyset_handle->GetPrimitive(); ASSERT_THAT(tink_jwt_mac, IsOk()); EXPECT_THAT( (*tink_jwt_mac)->VerifyMacAndDecode(*compact, *validator).status(), Not(IsOk())); } TEST_F(JwtMacWrapperTest, GenerateTinkComputeVerifySuccess) { KeyTemplate key_template = createTemplate(OutputPrefixType::TINK); util::StatusOr> keyset_handle = KeysetHandle::GenerateNew(key_template); EXPECT_THAT(keyset_handle, IsOk()); util::StatusOr> jwt_mac = (*keyset_handle)->GetPrimitive(); EXPECT_THAT(jwt_mac, IsOk()); util::StatusOr raw_jwt = RawJwtBuilder().SetIssuer("issuer").WithoutExpiration().Build(); ASSERT_THAT(raw_jwt, IsOk()); util::StatusOr compact = (*jwt_mac)->ComputeMacAndEncode(*raw_jwt); ASSERT_THAT(compact, IsOk()); util::StatusOr validator = JwtValidatorBuilder() .ExpectIssuer("issuer") .AllowMissingExpiration() .Build(); ASSERT_THAT(validator, IsOk()); util::StatusOr verified_jwt = (*jwt_mac)->VerifyMacAndDecode(*compact, *validator); ASSERT_THAT(verified_jwt, IsOk()); EXPECT_THAT(verified_jwt->GetIssuer(), test::IsOkAndHolds("issuer")); // Parse header to make sure that key ID is correctly encoded. google::crypto::tink::KeysetInfo keyset_info = (*keyset_handle)->GetKeysetInfo(); uint32_t key_id = keyset_info.key_info(0).key_id(); std::vector parts = absl::StrSplit(*compact, '.'); ASSERT_THAT(parts.size(), Eq(3)); std::string json_header; ASSERT_TRUE(DecodeHeader(parts[0], &json_header)); util::StatusOr header = JsonStringToProtoStruct(json_header); ASSERT_THAT(header, IsOk()); EXPECT_THAT(GetKeyId((*header).fields().find("kid")->second.string_value()), key_id); // For Tink primitives, the kid must be correctly set and is verified. // Therefore, changing the key_id makes the validation fail. std::unique_ptr keyset_handle_with_new_key_id = KeysetHandleWithNewKeyId(**keyset_handle); util::StatusOr> jwt_mac_with_new_key_id = keyset_handle_with_new_key_id->GetPrimitive(); ASSERT_THAT(jwt_mac_with_new_key_id, IsOk()); util::StatusOr verified_jwt_2 = (*jwt_mac_with_new_key_id)->VerifyMacAndDecode(*compact, *validator); EXPECT_FALSE(verified_jwt_2.ok()); } TEST_F(JwtMacWrapperTest, KeyRotation) { std::vector prefixes = {OutputPrefixType::RAW, OutputPrefixType::TINK}; for (OutputPrefixType prefix : prefixes) { SCOPED_TRACE(absl::StrCat("Testing with prefix ", prefix)); KeyTemplate key_template = createTemplate(prefix); KeysetManager manager; util::StatusOr old_id = manager.Add(key_template); ASSERT_THAT(old_id, IsOk()); ASSERT_THAT(manager.SetPrimary(*old_id), IsOk()); std::unique_ptr handle1 = manager.GetKeysetHandle(); util::StatusOr> jwt_mac1 = handle1->GetPrimitive(); ASSERT_THAT(jwt_mac1, IsOk()); util::StatusOr new_id = manager.Add(key_template); ASSERT_THAT(new_id, IsOk()); std::unique_ptr handle2 = manager.GetKeysetHandle(); util::StatusOr> jwt_mac2 = handle2->GetPrimitive(); ASSERT_THAT(jwt_mac2, IsOk()); ASSERT_THAT(manager.SetPrimary(*new_id), IsOk()); std::unique_ptr handle3 = manager.GetKeysetHandle(); util::StatusOr> jwt_mac3 = handle3->GetPrimitive(); ASSERT_THAT(jwt_mac3, IsOk()); ASSERT_THAT(manager.Disable(*old_id), IsOk()); std::unique_ptr handle4 = manager.GetKeysetHandle(); util::StatusOr> jwt_mac4 = handle4->GetPrimitive(); ASSERT_THAT(jwt_mac4, IsOk()); util::StatusOr raw_jwt = RawJwtBuilder().SetIssuer("issuer").WithoutExpiration().Build(); ASSERT_THAT(raw_jwt, IsOk()); util::StatusOr validator = JwtValidatorBuilder() .ExpectIssuer("issuer") .AllowMissingExpiration() .Build(); ASSERT_THAT(validator, IsOk()); util::StatusOr compact1 = (*jwt_mac1)->ComputeMacAndEncode(*raw_jwt); ASSERT_THAT(compact1, IsOk()); util::StatusOr compact2 = (*jwt_mac2)->ComputeMacAndEncode(*raw_jwt); ASSERT_THAT(compact2, IsOk()); util::StatusOr compact3 = (*jwt_mac3)->ComputeMacAndEncode(*raw_jwt); ASSERT_THAT(compact3, IsOk()); util::StatusOr compact4 = (*jwt_mac4)->ComputeMacAndEncode(*raw_jwt); ASSERT_THAT(compact4, IsOk()); EXPECT_THAT((*jwt_mac1)->VerifyMacAndDecode(*compact1, *validator).status(), IsOk()); EXPECT_THAT((*jwt_mac2)->VerifyMacAndDecode(*compact1, *validator).status(), IsOk()); EXPECT_THAT((*jwt_mac3)->VerifyMacAndDecode(*compact1, *validator).status(), IsOk()); EXPECT_FALSE((*jwt_mac4)->VerifyMacAndDecode(*compact1, *validator).ok()); EXPECT_THAT((*jwt_mac1)->VerifyMacAndDecode(*compact2, *validator).status(), IsOk()); EXPECT_THAT((*jwt_mac2)->VerifyMacAndDecode(*compact2, *validator).status(), IsOk()); EXPECT_THAT((*jwt_mac3)->VerifyMacAndDecode(*compact2, *validator).status(), IsOk()); EXPECT_FALSE((*jwt_mac4)->VerifyMacAndDecode(*compact2, *validator).ok()); EXPECT_FALSE((*jwt_mac1)->VerifyMacAndDecode(*compact3, *validator).ok()); EXPECT_THAT((*jwt_mac2)->VerifyMacAndDecode(*compact3, *validator).status(), IsOk()); EXPECT_THAT((*jwt_mac3)->VerifyMacAndDecode(*compact3, *validator).status(), IsOk()); EXPECT_THAT((*jwt_mac4)->VerifyMacAndDecode(*compact3, *validator).status(), IsOk()); EXPECT_FALSE((*jwt_mac1)->VerifyMacAndDecode(*compact4, *validator).ok()); EXPECT_THAT((*jwt_mac2)->VerifyMacAndDecode(*compact4, *validator).status(), IsOk()); EXPECT_THAT((*jwt_mac3)->VerifyMacAndDecode(*compact4, *validator).status(), IsOk()); EXPECT_THAT((*jwt_mac4)->VerifyMacAndDecode(*compact4, *validator).status(), IsOk()); } } } // namespace } // namespace jwt_internal } // namespace tink } // namespace crypto