// 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 "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 "anonymous_tokens/cpp/crypto/anonymous_tokens_pb_openssl_converters.h"
#include "anonymous_tokens/cpp/crypto/crypto_utils.h"
#include "anonymous_tokens/cpp/shared/proto_utils.h"
#include "anonymous_tokens/cpp/shared/status_utils.h"
#include "anonymous_tokens/proto/anonymous_tokens.pb.h"

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.");
  }

  switch (public_key.message_mask_type()) {
    case AT_MESSAGE_MASK_CONCAT:
      if (public_key.message_mask_size() < 32) {
        return absl::InvalidArgumentError(
            "Message mask concat type must have a size of at least 32 bytes.");
      }
      break;
    case AT_MESSAGE_MASK_NO_MASK:
      if (public_key.message_mask_size() != 0) {
        return absl::InvalidArgumentError(
            "Message mask no mask type must be set to size 0 bytes.");
      }
      break;
    default:
      return absl::InvalidArgumentError(
          "Message mask type must be defined and supported.");
  }

  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();
}

}  // 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));
}

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.");
  }

  RSAPublicKey rsa_public_key_proto;
  if (!rsa_public_key_proto.ParseFromString(
          public_key_.serialized_public_key())) {
    return absl::InvalidArgumentError("Public key is malformed.");
  }

  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();
    }
    const bool use_rsa_public_exponent = false;
    // Owned by BoringSSL.
    ANON_TOKENS_ASSIGN_OR_RETURN(
        const EVP_MD* sig_hash,
        ProtoHashTypeToEVPDigest(public_key_.sig_hash_type()));
    // Owned by BoringSSL.
    ANON_TOKENS_ASSIGN_OR_RETURN(
        const EVP_MD* mgf1_hash,
        ProtoMaskGenFunctionToEVPDigest(public_key_.mask_gen_function()));
    // Generate RSA blinder.
    ANON_TOKENS_ASSIGN_OR_RETURN(
        auto rsa_bssa_blinder,
        RsaBlinder::New(rsa_public_key_proto.n(), rsa_public_key_proto.e(),
                        sig_hash, mgf1_hash, public_key_.salt_length(),
                        use_rsa_public_exponent, 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());
    blinded_token->set_do_not_use_rsa_public_exponent(!use_rsa_public_exponent);
    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.");
    } else if (public_key_.public_metadata_support() &&
               !anonymous_token.do_not_use_rsa_public_exponent()) {
      // Bool do_not_use_rsa_public_exponent does not matter for the non-public
      // metadata version.
      return absl::InvalidArgumentError(
          "Setting do_not_use_rsa_public_exponent to false is no longer "
          "supported.");
    }

    // 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
