| // Copyright 2023 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://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 "quiche/blind_sign_auth/anonymous_tokens/cpp/client/anonymous_tokens_rsa_bssa_client.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "quiche/common/platform/api/quiche_test.h" |
| #include "quiche/common/test_tools/quiche_test_utils.h" |
| #include "absl/time/time.h" |
| #include "quiche/blind_sign_auth/anonymous_tokens/cpp/shared/proto_utils.h" |
| #include "quiche/blind_sign_auth/anonymous_tokens/cpp/shared/status_utils.h" |
| #include "quiche/blind_sign_auth/anonymous_tokens/cpp/testing/utils.h" |
| #include "quiche/blind_sign_auth/anonymous_tokens/proto/anonymous_tokens.pb.h" |
| #include "openssl/base.h" |
| #include "openssl/rsa.h" |
| |
| namespace private_membership { |
| namespace anonymous_tokens { |
| namespace { |
| |
| using quiche::test::StatusIs; |
| |
| absl::StatusOr<std::pair<bssl::UniquePtr<RSA>, RSABlindSignaturePublicKey>> |
| CreateClientTestKey(absl::string_view use_case = "TEST_USE_CASE", |
| int key_version = 1, |
| MessageMaskType mask_type = AT_MESSAGE_MASK_CONCAT, |
| int message_mask_size = 32) { |
| ANON_TOKENS_ASSIGN_OR_RETURN(auto key, CreateTestKey()); |
| key.second.set_use_case(std::string(use_case)); |
| key.second.set_key_version(key_version); |
| key.second.set_message_mask_type(mask_type); |
| key.second.set_message_mask_size(message_mask_size); |
| absl::Time start_time = absl::Now() - absl::Minutes(100); |
| ANON_TOKENS_ASSIGN_OR_RETURN(*key.second.mutable_key_validity_start_time(), |
| TimeToProto(start_time)); |
| return key; |
| } |
| |
| TEST(CreateAnonymousTokensRsaBssaClientTest, Success) { |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN(auto rsa_key, CreateClientTestKey()); |
| QUICHE_EXPECT_OK(AnonymousTokensRsaBssaClient::Create(rsa_key.second)); |
| } |
| |
| TEST(CreateAnonymousTokensRsaBssaClientTest, InvalidUseCase) { |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN(auto rsa_key, |
| CreateClientTestKey("INVALID_USE_CASE")); |
| EXPECT_THAT(AnonymousTokensRsaBssaClient::Create(rsa_key.second), |
| StatusIs(absl::StatusCode::kInvalidArgument)); |
| } |
| |
| TEST(CreateAnonymousTokensRsaBssaClientTest, NotAUseCase) { |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN(auto rsa_key, |
| CreateClientTestKey("NOT_A_USE_CASE")); |
| EXPECT_THAT(AnonymousTokensRsaBssaClient::Create(rsa_key.second), |
| StatusIs(absl::StatusCode::kInvalidArgument)); |
| } |
| |
| TEST(CreateAnonymousTokensRsaBssaClientTest, InvalidKeyVersion) { |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN(auto rsa_key, |
| CreateClientTestKey("TEST_USE_CASE", 0)); |
| EXPECT_THAT(AnonymousTokensRsaBssaClient::Create(rsa_key.second), |
| StatusIs(absl::StatusCode::kInvalidArgument)); |
| } |
| |
| TEST(CreateAnonymousTokensRsaBssaClientTest, InvalidMessageMaskType) { |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN( |
| auto rsa_key, |
| CreateClientTestKey("TEST_USE_CASE", 0, AT_MESSAGE_MASK_TYPE_UNDEFINED)); |
| EXPECT_THAT(AnonymousTokensRsaBssaClient::Create(rsa_key.second), |
| StatusIs(absl::StatusCode::kInvalidArgument)); |
| } |
| |
| TEST(CreateAnonymousTokensRsaBssaClientTest, InvalidMessageMaskSize) { |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN( |
| auto rsa_key, |
| CreateClientTestKey("TEST_USE_CASE", 0, AT_MESSAGE_MASK_CONCAT, 0)); |
| EXPECT_THAT(AnonymousTokensRsaBssaClient::Create(rsa_key.second), |
| StatusIs(absl::StatusCode::kInvalidArgument)); |
| } |
| |
| class AnonymousTokensRsaBssaClientTest : public testing::Test { |
| protected: |
| void SetUp() override { |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN(auto key, CreateClientTestKey()); |
| rsa_key_ = std::move(key.first); |
| public_key_ = std::move(key.second); |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN( |
| client_, AnonymousTokensRsaBssaClient::Create(public_key_)); |
| } |
| |
| absl::StatusOr<AnonymousTokensSignResponse> CreateResponse( |
| const AnonymousTokensSignRequest& request) { |
| AnonymousTokensSignResponse response; |
| for (const auto& request_token : request.blinded_tokens()) { |
| auto* response_token = response.add_anonymous_tokens(); |
| response_token->set_use_case(request_token.use_case()); |
| response_token->set_key_version(request_token.key_version()); |
| response_token->set_public_metadata(request_token.public_metadata()); |
| response_token->set_serialized_blinded_message( |
| request_token.serialized_token()); |
| ANON_TOKENS_ASSIGN_OR_RETURN( |
| *response_token->mutable_serialized_token(), |
| TestSign(request_token.serialized_token(), rsa_key_.get())); |
| } |
| return response; |
| } |
| |
| std::vector<PlaintextMessageWithPublicMetadata> CreateInput( |
| const std::vector<std::string>& messages) { |
| std::vector<PlaintextMessageWithPublicMetadata> output; |
| output.reserve(messages.size()); |
| for (const std::string& message : messages) { |
| PlaintextMessageWithPublicMetadata proto; |
| proto.set_plaintext_message(message); |
| output.push_back(proto); |
| } |
| return output; |
| } |
| |
| bssl::UniquePtr<RSA> rsa_key_; |
| RSABlindSignaturePublicKey public_key_; |
| std::unique_ptr<AnonymousTokensRsaBssaClient> client_; |
| }; |
| |
| TEST_F(AnonymousTokensRsaBssaClientTest, SuccessOneMessage) { |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN( |
| AnonymousTokensSignRequest request, |
| client_->CreateRequest(CreateInput({"message"}))); |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN(AnonymousTokensSignResponse response, |
| CreateResponse(request)); |
| QUICHE_EXPECT_OK(client_->ProcessResponse(response)); |
| EXPECT_EQ(response.anonymous_tokens_size(), 1); |
| } |
| |
| TEST_F(AnonymousTokensRsaBssaClientTest, SuccessMultipleMessages) { |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN( |
| AnonymousTokensSignRequest request, |
| client_->CreateRequest(CreateInput( |
| {"message1", "msg2", "anotherMessage", "one_more_message"}))); |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN(AnonymousTokensSignResponse response, |
| CreateResponse(request)); |
| EXPECT_EQ(response.anonymous_tokens_size(), 4); |
| QUICHE_EXPECT_OK(client_->ProcessResponse(response)); |
| } |
| |
| TEST_F(AnonymousTokensRsaBssaClientTest, EnsureRandomTokens) { |
| std::string message = "test_same_message"; |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN( |
| AnonymousTokensSignRequest request, |
| client_->CreateRequest(CreateInput({message, message}))); |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN(AnonymousTokensSignResponse response, |
| CreateResponse(request)); |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN( |
| std::vector<RSABlindSignatureTokenWithInput> tokens, |
| client_->ProcessResponse(response)); |
| ASSERT_EQ(tokens.size(), 2); |
| for (const RSABlindSignatureTokenWithInput& token : tokens) { |
| EXPECT_EQ(token.input().plaintext_message(), message); |
| } |
| EXPECT_NE(tokens[0].token().message_mask(), tokens[1].token().message_mask()); |
| EXPECT_NE(tokens[0].token().token(), tokens[1].token().token()); |
| } |
| |
| TEST_F(AnonymousTokensRsaBssaClientTest, EmptyInput) { |
| EXPECT_THAT(client_->CreateRequest(CreateInput({})), |
| StatusIs(absl::StatusCode::kInvalidArgument)); |
| } |
| |
| TEST_F(AnonymousTokensRsaBssaClientTest, NotYetValidKey) { |
| RSABlindSignaturePublicKey not_valid_key = public_key_; |
| absl::Time start_time = absl::Now() + absl::Minutes(100); |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN( |
| *not_valid_key.mutable_key_validity_start_time(), |
| TimeToProto(start_time)); |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN( |
| std::unique_ptr<AnonymousTokensRsaBssaClient> client, |
| AnonymousTokensRsaBssaClient::Create(not_valid_key)); |
| EXPECT_THAT(client->CreateRequest(CreateInput({"message"})), |
| StatusIs(absl::StatusCode::kFailedPrecondition)); |
| } |
| |
| TEST_F(AnonymousTokensRsaBssaClientTest, ExpiredKey) { |
| RSABlindSignaturePublicKey expired_key = public_key_; |
| absl::Time end_time = absl::Now() - absl::Seconds(1); |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN(*expired_key.mutable_expiration_time(), |
| TimeToProto(end_time)); |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN( |
| std::unique_ptr<AnonymousTokensRsaBssaClient> client, |
| AnonymousTokensRsaBssaClient::Create(expired_key)); |
| EXPECT_THAT(client->CreateRequest(CreateInput({"message"})), |
| StatusIs(absl::StatusCode::kFailedPrecondition)); |
| } |
| |
| TEST_F(AnonymousTokensRsaBssaClientTest, CreateRequestTwice) { |
| QUICHE_EXPECT_OK(client_->CreateRequest(CreateInput({"once"}))); |
| EXPECT_THAT(client_->CreateRequest(CreateInput({"twice"})), |
| StatusIs(absl::StatusCode::kFailedPrecondition)); |
| } |
| |
| TEST_F(AnonymousTokensRsaBssaClientTest, ProcessResponseWithoutCreateRequest) { |
| AnonymousTokensSignResponse response; |
| EXPECT_THAT(client_->ProcessResponse(response), |
| StatusIs(absl::StatusCode::kFailedPrecondition)); |
| } |
| |
| TEST_F(AnonymousTokensRsaBssaClientTest, ProcessEmptyResponse) { |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN( |
| AnonymousTokensSignRequest request, |
| client_->CreateRequest(CreateInput({"message"}))); |
| AnonymousTokensSignResponse response; |
| EXPECT_THAT(client_->ProcessResponse(response), |
| StatusIs(absl::StatusCode::kInvalidArgument)); |
| } |
| |
| TEST_F(AnonymousTokensRsaBssaClientTest, ProcessResponseWithBadUseCase) { |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN( |
| AnonymousTokensSignRequest request, |
| client_->CreateRequest(CreateInput({"message"}))); |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN(AnonymousTokensSignResponse response, |
| CreateResponse(request)); |
| response.mutable_anonymous_tokens(0)->set_use_case("TEST_USE_CASE_2"); |
| EXPECT_THAT(client_->ProcessResponse(response), |
| StatusIs(absl::StatusCode::kInvalidArgument)); |
| } |
| |
| TEST_F(AnonymousTokensRsaBssaClientTest, ProcessResponseWithBadKeyVersion) { |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN( |
| AnonymousTokensSignRequest request, |
| client_->CreateRequest(CreateInput({"message"}))); |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN(AnonymousTokensSignResponse response, |
| CreateResponse(request)); |
| response.mutable_anonymous_tokens(0)->set_key_version(2); |
| EXPECT_THAT(client_->ProcessResponse(response), |
| StatusIs(absl::StatusCode::kInvalidArgument)); |
| } |
| |
| TEST_F(AnonymousTokensRsaBssaClientTest, ProcessResponseFromDifferentClient) { |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN( |
| std::unique_ptr<AnonymousTokensRsaBssaClient> client2, |
| AnonymousTokensRsaBssaClient::Create(public_key_)); |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN( |
| AnonymousTokensSignRequest request1, |
| client_->CreateRequest(CreateInput({"message"}))); |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN( |
| AnonymousTokensSignRequest request2, |
| client2->CreateRequest(CreateInput({"message"}))); |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN(AnonymousTokensSignResponse response1, |
| CreateResponse(request1)); |
| ANON_TOKENS_ASSERT_OK_AND_ASSIGN(AnonymousTokensSignResponse response2, |
| CreateResponse(request2)); |
| EXPECT_THAT(client_->ProcessResponse(response2), |
| StatusIs(absl::StatusCode::kInvalidArgument)); |
| EXPECT_THAT(client2->ProcessResponse(response1), |
| StatusIs(absl::StatusCode::kInvalidArgument)); |
| } |
| |
| } // namespace |
| } // namespace anonymous_tokens |
| } // namespace private_membership |