blob: 77e2e9ed569f14cce4e4428489fb0066bbce6f1e [file] [log] [blame]
// 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 <cstddef>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_set.h"
#include "absl/status/status.h"
#include "absl/time/time.h"
#include "quiche/blind_sign_auth/anonymous_tokens/cpp/crypto/crypto_utils.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/proto/anonymous_tokens.pb.h"
namespace private_membership {
namespace anonymous_tokens {
namespace {
absl::Status ValidityChecksForClientCreation(
const RSABlindSignaturePublicKey& public_key) {
// Basic validity checks.
if (!ParseUseCase(public_key.use_case()).ok()) {
return absl::InvalidArgumentError("Invalid use case for public key.");
} else if (public_key.key_version() <= 0) {
return absl::InvalidArgumentError(
"Key version cannot be zero or negative.");
} else if (public_key.key_size() < 256) {
return absl::InvalidArgumentError(
"Key modulus size cannot be less than 256 bytes.");
} else if (public_key.mask_gen_function() == AT_TEST_MGF ||
public_key.mask_gen_function() == AT_MGF_UNDEFINED) {
return absl::InvalidArgumentError("Unknown or unacceptable mgf1 hash.");
} else if (public_key.sig_hash_type() == AT_TEST_HASH_TYPE ||
public_key.sig_hash_type() == AT_HASH_TYPE_UNDEFINED) {
return absl::InvalidArgumentError(
"Unknown or unacceptable signature hash.");
} else if (public_key.salt_length() <= 0) {
return absl::InvalidArgumentError(
"Non-positive salt length is not allowed.");
} else if (public_key.mask_gen_function() == AT_TEST_MGF ||
public_key.mask_gen_function() == AT_MGF_UNDEFINED) {
return absl::InvalidArgumentError("Message mask type must be defined.");
} else if (public_key.message_mask_size() <= 0) {
return absl::InvalidArgumentError("Message mask size must be positive.");
}
RSAPublicKey rsa_public_key;
if (!rsa_public_key.ParseFromString(public_key.serialized_public_key())) {
return absl::InvalidArgumentError("Public key is malformed.");
}
if (rsa_public_key.n().size() != static_cast<size_t>(public_key.key_size())) {
return absl::InvalidArgumentError(
"Public key size does not match key size.");
}
return absl::OkStatus();
}
absl::Status CheckPublicKeyValidity(
const RSABlindSignaturePublicKey& public_key) {
absl::Time time_now = absl::Now();
ANON_TOKENS_ASSIGN_OR_RETURN(
absl::Time start_time,
TimeFromProto(public_key.key_validity_start_time()));
if (start_time > time_now) {
return absl::FailedPreconditionError("Key is not valid yet.");
}
if (public_key.has_expiration_time()) {
ANON_TOKENS_ASSIGN_OR_RETURN(absl::Time expiration_time,
TimeFromProto(public_key.expiration_time()));
if (expiration_time <= time_now) {
return absl::FailedPreconditionError("Key is already expired.");
}
}
return absl::OkStatus();
}
} // namespace
AnonymousTokensRsaBssaClient::AnonymousTokensRsaBssaClient(
const RSABlindSignaturePublicKey& public_key)
: public_key_(public_key) {}
absl::StatusOr<std::unique_ptr<AnonymousTokensRsaBssaClient>>
AnonymousTokensRsaBssaClient::Create(
const RSABlindSignaturePublicKey& public_key) {
ANON_TOKENS_RETURN_IF_ERROR(ValidityChecksForClientCreation(public_key));
return absl::WrapUnique(new AnonymousTokensRsaBssaClient(public_key));
}
// TODO(b/261866075): Offer an API to simply return bytes of blinded requests.
absl::StatusOr<AnonymousTokensSignRequest>
AnonymousTokensRsaBssaClient::CreateRequest(
const std::vector<PlaintextMessageWithPublicMetadata>& inputs) {
if (inputs.empty()) {
return absl::InvalidArgumentError("Cannot create an empty request.");
} else if (!blinding_info_map_.empty()) {
return absl::FailedPreconditionError(
"Blind signature request already created.");
}
ANON_TOKENS_RETURN_IF_ERROR(CheckPublicKeyValidity(public_key_));
AnonymousTokensSignRequest request;
for (const PlaintextMessageWithPublicMetadata& input : inputs) {
// Generate nonce and masked message. For more details, see
// https://datatracker.ietf.org/doc/draft-irtf-cfrg-rsa-blind-signatures/
ANON_TOKENS_ASSIGN_OR_RETURN(std::string mask, GenerateMask(public_key_));
std::string masked_message =
MaskMessageConcat(mask, input.plaintext_message());
std::optional<std::string> public_metadata = std::nullopt;
if (public_key_.public_metadata_support()) {
// Empty public metadata is a valid value.
public_metadata = input.public_metadata();
}
// Generate RSA blinder.
ANON_TOKENS_ASSIGN_OR_RETURN(auto rsa_bssa_blinder,
RsaBlinder::New(public_key_, public_metadata));
ANON_TOKENS_ASSIGN_OR_RETURN(const std::string blinded_message,
rsa_bssa_blinder->Blind(masked_message));
// Store randomness needed to unblind.
BlindingInfo blinding_info = {
input,
mask,
std::move(rsa_bssa_blinder),
};
// Create the blinded token.
AnonymousTokensSignRequest_BlindedToken* blinded_token =
request.add_blinded_tokens();
blinded_token->set_use_case(public_key_.use_case());
blinded_token->set_key_version(public_key_.key_version());
blinded_token->set_serialized_token(blinded_message);
blinded_token->set_public_metadata(input.public_metadata());
blinding_info_map_[blinded_message] = std::move(blinding_info);
}
return request;
}
absl::StatusOr<std::vector<RSABlindSignatureTokenWithInput>>
AnonymousTokensRsaBssaClient::ProcessResponse(
const AnonymousTokensSignResponse& response) {
if (blinding_info_map_.empty()) {
return absl::FailedPreconditionError(
"A valid Blind signature request was not created before calling "
"RetrieveAnonymousTokensFromSignResponse.");
} else if (response.anonymous_tokens().empty()) {
return absl::InvalidArgumentError("Cannot process an empty response.");
} else if (static_cast<size_t>(response.anonymous_tokens().size()) !=
blinding_info_map_.size()) {
return absl::InvalidArgumentError(
"Response is missing some requested tokens.");
}
// Vector to accumulate output tokens.
std::vector<RSABlindSignatureTokenWithInput> tokens;
// Temporary set structure to check for duplicate responses.
absl::flat_hash_set<absl::string_view> blinded_messages;
// Loop over all the anonymous tokens in the response.
for (const AnonymousTokensSignResponse_AnonymousToken& anonymous_token :
response.anonymous_tokens()) {
// Basic validity checks on the response.
if (anonymous_token.use_case() != public_key_.use_case()) {
return absl::InvalidArgumentError("Use case does not match public key.");
} else if (anonymous_token.key_version() != public_key_.key_version()) {
return absl::InvalidArgumentError(
"Key version does not match public key.");
} else if (anonymous_token.serialized_blinded_message().empty()) {
return absl::InvalidArgumentError(
"Blinded message that was sent in request cannot be empty in "
"response.");
} else if (anonymous_token.serialized_token().empty()) {
return absl::InvalidArgumentError(
"Blinded anonymous token (serialized_token) in response cannot be "
"empty.");
}
// Check for duplicate in responses.
if (!blinded_messages.insert(anonymous_token.serialized_blinded_message())
.second) {
return absl::InvalidArgumentError(
"Blinded message was repeated in the response.");
}
// Retrieve blinding info associated with blind response.
auto it =
blinding_info_map_.find(anonymous_token.serialized_blinded_message());
if (it == blinding_info_map_.end()) {
return absl::InvalidArgumentError(
"Response has some tokens for some blinded messages that were not "
"requested.");
}
const BlindingInfo& blinding_info = it->second;
if (blinding_info.input.public_metadata() !=
anonymous_token.public_metadata()) {
return absl::InvalidArgumentError(
"Response public metadata does not match input.");
}
// Unblind the blinded anonymous token to obtain the final anonymous token
// (signature).
ANON_TOKENS_ASSIGN_OR_RETURN(
const std::string final_anonymous_token,
blinding_info.rsa_blinder->Unblind(anonymous_token.serialized_token()));
// Verify the signature for correctness.
ANON_TOKENS_RETURN_IF_ERROR(blinding_info.rsa_blinder->Verify(
final_anonymous_token,
MaskMessageConcat(blinding_info.mask,
blinding_info.input.plaintext_message())));
// Construct the final signature proto.
RSABlindSignatureTokenWithInput final_token_proto;
*final_token_proto.mutable_token()->mutable_token() = final_anonymous_token;
*final_token_proto.mutable_token()->mutable_message_mask() =
blinding_info.mask;
*final_token_proto.mutable_input() = blinding_info.input;
tokens.push_back(final_token_proto);
}
return tokens;
}
absl::Status AnonymousTokensRsaBssaClient::Verify(
const RSABlindSignaturePublicKey& /*public_key*/,
const RSABlindSignatureToken& /*token*/,
const PlaintextMessageWithPublicMetadata& /*input*/) {
return absl::UnimplementedError("Verify not implemented yet.");
}
} // namespace anonymous_tokens
} // namespace private_membership