Add Privacy Pass token generation to BlindSignAuth and test clients.

PiperOrigin-RevId: 572689274
diff --git a/quiche/blind_sign_auth/blind_sign_auth.cc b/quiche/blind_sign_auth/blind_sign_auth.cc
index 0c8ea53..fbfd429 100644
--- a/quiche/blind_sign_auth/blind_sign_auth.cc
+++ b/quiche/blind_sign_auth/blind_sign_auth.cc
@@ -4,7 +4,10 @@
 
 #include "quiche/blind_sign_auth/blind_sign_auth.h"
 
+#include <algorithm>
 #include <cstddef>
+#include <cstdint>
+#include <cstring>
 #include <memory>
 #include <string>
 #include <utility>
@@ -15,7 +18,12 @@
 #include "absl/status/statusor.h"
 #include "absl/strings/escaping.h"
 #include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
 #include "absl/types/span.h"
+#include "anonymous_tokens/cpp/client/anonymous_tokens_rsa_bssa_client.h"
+#include "anonymous_tokens/cpp/crypto/crypto_utils.h"
+#include "anonymous_tokens/cpp/privacy_pass/rsa_bssa_public_metadata_client.h"
+#include "anonymous_tokens/cpp/privacy_pass/token_encodings.h"
 #include "anonymous_tokens/cpp/shared/proto_utils.h"
 #include "quiche/blind_sign_auth/blind_sign_auth_protos.h"
 #include "quiche/blind_sign_auth/blind_sign_http_response.h"
@@ -31,16 +39,13 @@
   return value == 0 ? "" : absl::StrCat(value);
 }
 
+constexpr absl::string_view kIssuerHostname =
+    "https://ipprotection-ppissuer.googleapis.com";
+
 }  // namespace
 
 void BlindSignAuth::GetTokens(std::string oauth_token, int num_tokens,
                               SignedTokenCallback callback) {
-  // Check whether Privacy Pass crypto is enabled.
-  if (auth_options_.enable_privacy_pass()) {
-    std::move(callback)(
-        absl::UnimplementedError("Privacy Pass is not supported."));
-    return;
-  }
   // Create GetInitialData RPC.
   privacy::ppn::GetInitialDataRequest request;
   request.set_use_attestation(false);
@@ -66,7 +71,7 @@
     std::move(callback)(response.status());
     return;
   }
-  absl::StatusCode code = HttpCodeToStatusCode(response.value().status_code());
+  absl::StatusCode code = HttpCodeToStatusCode(response->status_code());
   if (code != absl::StatusCode::kOk) {
     std::string message =
         absl::StrCat("GetInitialDataRequest failed with code: ", code);
@@ -76,7 +81,7 @@
   }
   // Parse GetInitialDataResponse.
   privacy::ppn::GetInitialDataResponse initial_data_response;
-  if (!initial_data_response.ParseFromString(response.value().body())) {
+  if (!initial_data_response.ParseFromString(response->body())) {
     QUICHE_LOG(WARNING) << "Failed to parse GetInitialDataResponse";
     std::move(callback)(
         absl::InternalError("Failed to parse GetInitialDataResponse"));
@@ -93,7 +98,148 @@
     return;
   }
 
-  // Create RSA BSSA client.
+  // Create token signing requests.
+  bool use_privacy_pass_client =
+      initial_data_response.has_privacy_pass_data() &&
+      auth_options_.enable_privacy_pass();
+
+  if (use_privacy_pass_client) {
+    QUICHE_DVLOG(1) << "Using Privacy Pass client";
+    GeneratePrivacyPassTokens(
+        initial_data_response, *public_metadata_expiry_time,
+        std::move(oauth_token), num_tokens, std::move(callback));
+  } else {
+    QUICHE_DVLOG(1) << "Using public metadata client";
+    GenerateRsaBssaTokens(initial_data_response, *public_metadata_expiry_time,
+                          std::move(oauth_token), num_tokens,
+                          std::move(callback));
+  }
+}
+
+void BlindSignAuth::GeneratePrivacyPassTokens(
+    privacy::ppn::GetInitialDataResponse initial_data_response,
+    absl::Time public_metadata_expiry_time, std::string oauth_token,
+    int num_tokens, SignedTokenCallback callback) {
+  // Set up values used in the token generation loop.
+  anonymous_tokens::RSAPublicKey public_key_proto;
+  if (!public_key_proto.ParseFromString(
+          initial_data_response.at_public_metadata_public_key()
+              .serialized_public_key())) {
+    std::move(callback)(
+        absl::InvalidArgumentError("Failed to parse Privacy Pass public key"));
+    return;
+  }
+  absl::StatusOr<bssl::UniquePtr<RSA>> bssl_rsa_key =
+      anonymous_tokens::CreatePublicKeyRSA(
+          public_key_proto.n(), public_key_proto.e());
+  if (!bssl_rsa_key.ok()) {
+    std::move(callback)(bssl_rsa_key.status());
+    return;
+  }
+  absl::StatusOr<anonymous_tokens::Extensions> extensions =
+      anonymous_tokens::DecodeExtensions(
+          initial_data_response.privacy_pass_data()
+              .public_metadata_extensions());
+  if (!extensions.ok()) {
+    QUICHE_LOG(WARNING) << "Failed to decode extensions: "
+                        << extensions.status();
+    std::move(callback)(extensions.status());
+    return;
+  }
+
+  // Create token challenge.
+  anonymous_tokens::TokenChallenge challenge;
+  challenge.issuer_name = kIssuerHostname;
+  absl::StatusOr<std::string> token_challenge =
+      anonymous_tokens::MarshalTokenChallenge(challenge);
+  if (!token_challenge.ok()) {
+    QUICHE_LOG(WARNING) << "Failed to marshal token challenge: "
+                        << token_challenge.status();
+    std::move(callback)(token_challenge.status());
+    return;
+  }
+
+  QuicheRandom* random = QuicheRandom::GetInstance();
+  // Create vector of Privacy Pass clients, one for each token.
+  std::vector<anonymous_tokens::ExtendedTokenRequest>
+      extended_token_requests;
+  std::vector<std::unique_ptr<anonymous_tokens::
+                                  PrivacyPassRsaBssaPublicMetadataClient>>
+      privacy_pass_clients;
+  std::vector<std::string> privacy_pass_blinded_tokens;
+
+  for (int i = 0; i < num_tokens; i++) {
+    // Create client.
+    auto client = anonymous_tokens::
+        PrivacyPassRsaBssaPublicMetadataClient::Create(*bssl_rsa_key.value());
+    if (!client.ok()) {
+      QUICHE_LOG(WARNING) << "Failed to create Privacy Pass client: "
+                          << client.status();
+      std::move(callback)(client.status());
+      return;
+    }
+
+    // Create nonce.
+    std::string nonce_rand(32, '\0');
+    random->RandBytes(nonce_rand.data(), nonce_rand.size());
+
+    // Create token request.
+    absl::StatusOr<anonymous_tokens::ExtendedTokenRequest>
+        extended_token_request = client.value()->CreateTokenRequest(
+            *token_challenge, nonce_rand,
+            initial_data_response.privacy_pass_data().token_key_id(),
+            *extensions);
+    if (!extended_token_request.ok()) {
+      QUICHE_LOG(WARNING) << "Failed to create ExtendedTokenRequest: "
+                          << extended_token_request.status();
+      std::move(callback)(extended_token_request.status());
+      return;
+    }
+    privacy_pass_clients.push_back(*std::move(client));
+    extended_token_requests.push_back(*extended_token_request);
+    privacy_pass_blinded_tokens.push_back(absl::Base64Escape(
+        extended_token_request->request.blinded_token_request));
+  }
+
+  privacy::ppn::AuthAndSignRequest sign_request;
+  sign_request.set_oauth_token(oauth_token);
+  sign_request.set_service_type("chromeipblinding");
+  sign_request.set_key_type(privacy::ppn::AT_PUBLIC_METADATA_KEY_TYPE);
+  sign_request.set_key_version(
+      initial_data_response.at_public_metadata_public_key().key_version());
+  sign_request.mutable_blinded_token()->Assign(
+      privacy_pass_blinded_tokens.begin(), privacy_pass_blinded_tokens.end());
+  sign_request.mutable_public_metadata_extensions()->assign(
+      initial_data_response.privacy_pass_data().public_metadata_extensions());
+  // TODO(b/295924807): deprecate this option after AT server defaults to it
+  sign_request.set_do_not_use_rsa_public_exponent(true);
+
+  absl::StatusOr<anonymous_tokens::AnonymousTokensUseCase>
+      use_case = anonymous_tokens::ParseUseCase(
+          initial_data_response.at_public_metadata_public_key().use_case());
+  if (!use_case.ok()) {
+    QUICHE_LOG(WARNING) << "Failed to parse use case: " << use_case.status();
+    std::move(callback)(use_case.status());
+    return;
+  }
+
+  BlindSignHttpCallback auth_and_sign_callback =
+      absl::bind_front(&BlindSignAuth::PrivacyPassAuthAndSignCallback, this,
+                       std::move(initial_data_response.privacy_pass_data()
+                                     .public_metadata_extensions()),
+                       public_metadata_expiry_time, *use_case,
+                       std::move(privacy_pass_clients), std::move(callback));
+  // TODO(b/304811277): remove other usages of string.data()
+  http_fetcher_->DoRequest(BlindSignHttpRequestType::kAuthAndSign, oauth_token,
+                           sign_request.SerializeAsString(),
+                           std::move(auth_and_sign_callback));
+}
+
+void BlindSignAuth::GenerateRsaBssaTokens(
+    privacy::ppn::GetInitialDataResponse initial_data_response,
+    absl::Time public_metadata_expiry_time, std::string oauth_token,
+    int num_tokens, SignedTokenCallback callback) {
+  // Create public metadata client.
   auto bssa_client =
       anonymous_tokens::AnonymousTokensRsaBssaClient::
           Create(initial_data_response.at_public_metadata_public_key());
@@ -106,10 +252,10 @@
 
   // Create plaintext tokens.
   // Client blinds plaintext tokens (random 32-byte strings) in CreateRequest.
+  QuicheRandom* random = QuicheRandom::GetInstance();
   std::vector<
       anonymous_tokens::PlaintextMessageWithPublicMetadata>
       plaintext_tokens;
-  QuicheRandom* random = QuicheRandom::GetInstance();
   for (int i = 0; i < num_tokens; i++) {
     // Create random 32-byte string prefixed with "blind:".
     anonymous_tokens::PlaintextMessageWithPublicMetadata
@@ -134,10 +280,9 @@
     plaintext_message.set_public_metadata(key);
     plaintext_tokens.push_back(plaintext_message);
   }
-
   absl::StatusOr<
       anonymous_tokens::AnonymousTokensSignRequest>
-      at_sign_request = bssa_client.value()->CreateRequest(plaintext_tokens);
+      at_sign_request = *bssa_client.value()->CreateRequest(plaintext_tokens);
   if (!at_sign_request.ok()) {
     QUICHE_LOG(WARNING) << "Failed to create AT Sign Request: "
                         << at_sign_request.status();
@@ -165,8 +310,8 @@
       initial_data_response.public_metadata_info();
   BlindSignHttpCallback auth_and_sign_callback = absl::bind_front(
       &BlindSignAuth::AuthAndSignCallback, this, public_metadata_info,
-      public_metadata_expiry_time.value(), *at_sign_request,
-      *std::move(bssa_client), std::move(callback));
+      public_metadata_expiry_time, *at_sign_request, *std::move(bssa_client),
+      std::move(callback));
   http_fetcher_->DoRequest(BlindSignHttpRequestType::kAuthAndSign,
                            oauth_token.data(), sign_request.SerializeAsString(),
                            std::move(auth_and_sign_callback));
@@ -188,23 +333,21 @@
     std::move(callback)(response.status());
     return;
   }
-  absl::StatusCode code = HttpCodeToStatusCode(response.value().status_code());
+  absl::StatusCode code = HttpCodeToStatusCode(response->status_code());
   if (code != absl::StatusCode::kOk) {
     std::string message = absl::StrCat("AuthAndSign failed with code: ", code);
     QUICHE_LOG(WARNING) << message;
     std::move(callback)(absl::Status(code, message));
     return;
   }
-
   // Decode AuthAndSignResponse.
   privacy::ppn::AuthAndSignResponse sign_response;
-  if (!sign_response.ParseFromString(response.value().body())) {
+  if (!sign_response.ParseFromString(response->body())) {
     QUICHE_LOG(WARNING) << "Failed to parse AuthAndSignResponse";
     std::move(callback)(
         absl::InternalError("Failed to parse AuthAndSignResponse"));
     return;
   }
-
   // Create vector of unblinded anonymous tokens.
   anonymous_tokens::AnonymousTokensSignResponse
       at_sign_response;
@@ -288,6 +431,88 @@
   std::move(callback)(absl::Span<BlindSignToken>(tokens_vec));
 }
 
+void BlindSignAuth::PrivacyPassAuthAndSignCallback(
+    std::string encoded_extensions, absl::Time public_key_expiry_time,
+    anonymous_tokens::AnonymousTokensUseCase use_case,
+    std::vector<std::unique_ptr<anonymous_tokens::
+                                    PrivacyPassRsaBssaPublicMetadataClient>>
+        privacy_pass_clients,
+    SignedTokenCallback callback,
+    absl::StatusOr<BlindSignHttpResponse> response) {
+  // Validate response.
+  if (!response.ok()) {
+    QUICHE_LOG(WARNING) << "AuthAndSign failed: " << response.status();
+    std::move(callback)(response.status());
+    return;
+  }
+  absl::StatusCode code = HttpCodeToStatusCode(response->status_code());
+  if (code != absl::StatusCode::kOk) {
+    std::string message = absl::StrCat("AuthAndSign failed with code: ", code);
+    QUICHE_LOG(WARNING) << message;
+    std::move(callback)(absl::Status(code, message));
+    return;
+  }
+
+  // Decode AuthAndSignResponse.
+  privacy::ppn::AuthAndSignResponse sign_response;
+  if (!sign_response.ParseFromString(response->body())) {
+    QUICHE_LOG(WARNING) << "Failed to parse AuthAndSignResponse";
+    std::move(callback)(
+        absl::InternalError("Failed to parse AuthAndSignResponse"));
+    return;
+  }
+  if (static_cast<size_t>(sign_response.blinded_token_signature_size()) !=
+      privacy_pass_clients.size()) {
+    QUICHE_LOG(WARNING) << "Number of signatures does not equal number of "
+                           "Privacy Pass tokens sent";
+    std::move(callback)(
+        absl::InternalError("Number of signatures does not equal number of "
+                            "Privacy Pass tokens sent"));
+    return;
+  }
+
+  // Create tokens using blinded signatures.
+  std::vector<BlindSignToken> tokens_vec;
+  for (int i = 0; i < sign_response.blinded_token_signature_size(); i++) {
+    std::string unescaped_blinded_sig;
+    if (!absl::Base64Unescape(sign_response.blinded_token_signature()[i],
+                              &unescaped_blinded_sig)) {
+      QUICHE_LOG(WARNING) << "Failed to unescape blinded signature";
+      std::move(callback)(
+          absl::InternalError("Failed to unescape blinded signature"));
+      return;
+    }
+
+    absl::StatusOr<anonymous_tokens::Token> token =
+        privacy_pass_clients[i]->FinalizeToken(unescaped_blinded_sig);
+    if (!token.ok()) {
+      QUICHE_LOG(WARNING) << "Failed to finalize token: " << token.status();
+      std::move(callback)(token.status());
+      return;
+    }
+
+    absl::StatusOr<std::string> marshaled_token =
+        anonymous_tokens::MarshalToken(*token);
+    if (!marshaled_token.ok()) {
+      QUICHE_LOG(WARNING) << "Failed to marshal token: "
+                          << marshaled_token.status();
+      std::move(callback)(marshaled_token.status());
+      return;
+    }
+
+    privacy::ppn::PrivacyPassTokenData privacy_pass_token_data;
+    privacy_pass_token_data.mutable_token()->assign(
+        absl::Base64Escape(*marshaled_token));
+    privacy_pass_token_data.mutable_encoded_extensions()->assign(
+        absl::Base64Escape(encoded_extensions));
+    privacy_pass_token_data.set_use_case_override(use_case);
+    tokens_vec.push_back(BlindSignToken{
+        privacy_pass_token_data.SerializeAsString(), public_key_expiry_time});
+  }
+
+  std::move(callback)(absl::Span<BlindSignToken>(tokens_vec));
+}
+
 absl::Status BlindSignAuth::FingerprintPublicMetadata(
     const privacy::ppn::PublicMetadata& metadata, uint64_t* fingerprint) {
   const EVP_MD* hasher = EVP_sha256();
diff --git a/quiche/blind_sign_auth/blind_sign_auth.h b/quiche/blind_sign_auth/blind_sign_auth.h
index d678afd..43fd604 100644
--- a/quiche/blind_sign_auth/blind_sign_auth.h
+++ b/quiche/blind_sign_auth/blind_sign_auth.h
@@ -12,6 +12,7 @@
 #include "absl/status/statusor.h"
 #include "absl/time/time.h"
 #include "anonymous_tokens/cpp/client/anonymous_tokens_rsa_bssa_client.h"
+#include "anonymous_tokens/cpp/privacy_pass/rsa_bssa_public_metadata_client.h"
 #include "quiche/blind_sign_auth/blind_sign_auth_interface.h"
 #include "quiche/blind_sign_auth/blind_sign_auth_protos.h"
 #include "quiche/blind_sign_auth/blind_sign_http_interface.h"
@@ -41,6 +42,14 @@
   void GetInitialDataCallback(std::string oauth_token, int num_tokens,
                               SignedTokenCallback callback,
                               absl::StatusOr<BlindSignHttpResponse> response);
+  void GeneratePrivacyPassTokens(
+      privacy::ppn::GetInitialDataResponse initial_data_response,
+      absl::Time public_metadata_expiry_time, std::string oauth_token,
+      int num_tokens, SignedTokenCallback callback);
+  void GenerateRsaBssaTokens(
+      privacy::ppn::GetInitialDataResponse initial_data_response,
+      absl::Time public_metadata_expiry_time, std::string oauth_token,
+      int num_tokens, SignedTokenCallback callback);
   void AuthAndSignCallback(
       privacy::ppn::PublicMetadataInfo public_metadata_info,
       absl::Time public_key_expiry_time,
@@ -51,6 +60,14 @@
           bssa_client,
       SignedTokenCallback callback,
       absl::StatusOr<BlindSignHttpResponse> response);
+  void PrivacyPassAuthAndSignCallback(
+      std::string encoded_extensions, absl::Time public_key_expiry_time,
+      anonymous_tokens::AnonymousTokensUseCase use_case,
+      std::vector<std::unique_ptr<anonymous_tokens::
+                                      PrivacyPassRsaBssaPublicMetadataClient>>
+          privacy_pass_clients,
+      SignedTokenCallback callback,
+      absl::StatusOr<BlindSignHttpResponse> response);
   absl::Status FingerprintPublicMetadata(
       const privacy::ppn::PublicMetadata& metadata, uint64_t* fingerprint);
   absl::StatusCode HttpCodeToStatusCode(int http_code);
diff --git a/quiche/blind_sign_auth/blind_sign_auth_test.cc b/quiche/blind_sign_auth/blind_sign_auth_test.cc
index c2e33cd..9f61ee4 100644
--- a/quiche/blind_sign_auth/blind_sign_auth_test.cc
+++ b/quiche/blind_sign_auth/blind_sign_auth_test.cc
@@ -4,6 +4,7 @@
 
 #include "quiche/blind_sign_auth/blind_sign_auth.h"
 
+#include <cstdint>
 #include <memory>
 #include <string>
 #include <utility>
@@ -13,9 +14,12 @@
 #include "absl/strings/escaping.h"
 #include "absl/strings/string_view.h"
 #include "absl/time/time.h"
+#include "anonymous_tokens/cpp/crypto/crypto_utils.h"
+#include "anonymous_tokens/cpp/privacy_pass/token_encodings.h"
 #include "anonymous_tokens/cpp/testing/proto_utils.h"
 #include "anonymous_tokens/cpp/testing/utils.h"
 #include "openssl/base.h"
+#include "openssl/digest.h"
 #include "quiche/blind_sign_auth/blind_sign_auth_protos.h"
 #include "quiche/blind_sign_auth/blind_sign_http_interface.h"
 #include "quiche/blind_sign_auth/blind_sign_http_response.h"
@@ -38,28 +42,48 @@
 class BlindSignAuthTest : public QuicheTest {
  protected:
   void SetUp() override {
-    // Create public key.
-    auto keypair = anonymous_tokens::CreateTestKey();
-    if (!keypair.ok()) {
-      return;
-    }
-    keypair_ = *std::move(keypair);
-    keypair_.second.set_key_version(1);
-    keypair_.second.set_use_case("TEST_USE_CASE");
+    // Create keypair and populate protos.
+    auto [test_rsa_public_key, test_rsa_private_key] =
+        anonymous_tokens::GetStrongTestRsaKeyPair2048();
+    ANON_TOKENS_ASSERT_OK_AND_ASSIGN(
+        rsa_public_key_,
+        anonymous_tokens::CreatePublicKeyRSA(
+            test_rsa_public_key.n, test_rsa_public_key.e));
+    ANON_TOKENS_ASSERT_OK_AND_ASSIGN(
+        rsa_private_key_,
+        anonymous_tokens::CreatePrivateKeyRSA(
+            test_rsa_private_key.n, test_rsa_private_key.e,
+            test_rsa_private_key.d, test_rsa_private_key.p,
+            test_rsa_private_key.q, test_rsa_private_key.dp,
+            test_rsa_private_key.dq, test_rsa_private_key.crt));
 
-    // Create fake GetInitialDataRequest.
+    anonymous_tokens::RSAPublicKey public_key;
+    public_key.set_n(test_rsa_public_key.n);
+    public_key.set_e(test_rsa_public_key.e);
+
+    public_key_proto_.set_key_version(1);
+    public_key_proto_.set_use_case("TEST_USE_CASE");
+    public_key_proto_.set_serialized_public_key(public_key.SerializeAsString());
+    public_key_proto_.set_sig_hash_type(
+        anonymous_tokens::AT_HASH_TYPE_SHA384);
+    public_key_proto_.set_mask_gen_function(
+        anonymous_tokens::AT_MGF_SHA384);
+    public_key_proto_.set_salt_length(48);
+    public_key_proto_.set_key_size(256);
+    public_key_proto_.set_message_mask_type(
+        anonymous_tokens::AT_MESSAGE_MASK_CONCAT);
+    public_key_proto_.set_message_mask_size(32);
+
+    // Create expected GetInitialDataRequest.
     expected_get_initial_data_request_.set_use_attestation(false);
     expected_get_initial_data_request_.set_service_type("chromeipblinding");
     expected_get_initial_data_request_.set_location_granularity(
         privacy::ppn::GetInitialDataRequest_LocationGranularity_CITY_GEOS);
 
-    // Create fake public key response.
+    // Create fake GetInitialDataResponse.
     privacy::ppn::GetInitialDataResponse fake_get_initial_data_response;
-    anonymous_tokens::RSABlindSignaturePublicKey public_key;
-    ASSERT_TRUE(
-        public_key.ParseFromString(keypair_.second.SerializeAsString()));
     *fake_get_initial_data_response.mutable_at_public_metadata_public_key() =
-        public_key;
+        public_key_proto_;
 
     // Create public metadata info.
     privacy::ppn::PublicMetadata::Location location;
@@ -76,6 +100,57 @@
         public_metadata_info_;
     fake_get_initial_data_response_ = fake_get_initial_data_response;
 
+    // Create PrivacyPassData.
+    privacy::ppn::GetInitialDataResponse::PrivacyPassData privacy_pass_data;
+    // token_key_id is derived from public key.
+    ANON_TOKENS_ASSERT_OK_AND_ASSIGN(
+        std::string public_key_der,
+        anonymous_tokens::RsaSsaPssPublicKeyToDerEncoding(
+            rsa_public_key_.get()));
+    const EVP_MD* sha256 = EVP_sha256();
+    ANON_TOKENS_ASSERT_OK_AND_ASSIGN(
+        token_key_id_, anonymous_tokens::ComputeHash(
+                           public_key_der, *sha256));
+
+    // Create and serialize fake extensions.
+    anonymous_tokens::ExpirationTimestamp
+        expiration_timestamp;
+    int64_t one_hour_away = absl::ToUnixSeconds(absl::Now() + absl::Hours(1));
+    expiration_timestamp.timestamp = one_hour_away - (one_hour_away % 900);
+    expiration_timestamp.timestamp_precision = 900;
+    absl::StatusOr<anonymous_tokens::Extension>
+        expiration_extension = expiration_timestamp.AsExtension();
+    QUICHE_EXPECT_OK(expiration_extension);
+    extensions_.extensions.push_back(*expiration_extension);
+
+    anonymous_tokens::GeoHint geo_hint;
+    geo_hint.country_code = "US";
+    absl::StatusOr<anonymous_tokens::Extension>
+        geo_hint_extension = geo_hint.AsExtension();
+    QUICHE_EXPECT_OK(geo_hint_extension);
+    extensions_.extensions.push_back(*geo_hint_extension);
+
+    anonymous_tokens::ServiceType service_type;
+    service_type.service_type_id =
+        anonymous_tokens::ServiceType::kChromeIpBlinding;
+    absl::StatusOr<anonymous_tokens::Extension>
+        service_type_extension = service_type.AsExtension();
+    QUICHE_EXPECT_OK(service_type_extension);
+    extensions_.extensions.push_back(*service_type_extension);
+
+    absl::StatusOr<std::string> serialized_extensions =
+        anonymous_tokens::EncodeExtensions(extensions_);
+    QUICHE_EXPECT_OK(serialized_extensions);
+
+    privacy_pass_data.set_token_key_id(token_key_id_);
+    privacy_pass_data.set_public_metadata_extensions(*serialized_extensions);
+
+    *fake_get_initial_data_response.mutable_public_metadata_info() =
+        public_metadata_info_;
+    *fake_get_initial_data_response.mutable_privacy_pass_data() =
+        privacy_pass_data;
+    fake_get_initial_data_response_ = fake_get_initial_data_response;
+
     // Create BlindSignAuthOptions.
     privacy::ppn::BlindSignAuthOptions options;
     options.set_enable_privacy_pass(false);
@@ -86,12 +161,10 @@
 
   void TearDown() override {
     blind_sign_auth_.reset(nullptr);
-    keypair_.first.reset(nullptr);
-    keypair_.second.Clear();
   }
 
  public:
-  void CreateSignResponse(const std::string& body) {
+  void CreateSignResponse(const std::string& body, bool use_privacy_pass) {
     privacy::ppn::AuthAndSignRequest request;
     ASSERT_TRUE(request.ParseFromString(body));
 
@@ -102,27 +175,45 @@
     // privacy::ppn::AT_PUBLIC_METADATA_KEY_TYPE.
     EXPECT_EQ(request.key_type(), privacy::ppn::AT_PUBLIC_METADATA_KEY_TYPE);
     EXPECT_EQ(request.public_key_hash(), "");
-    EXPECT_EQ(request.public_metadata_info().SerializeAsString(),
-              public_metadata_info_.SerializeAsString());
-    EXPECT_EQ(request.key_version(), keypair_.second.key_version());
+    EXPECT_EQ(request.key_version(), public_key_proto_.key_version());
     EXPECT_EQ(request.do_not_use_rsa_public_exponent(), true);
+    EXPECT_NE(request.blinded_token().size(), 0);
+
+    if (use_privacy_pass) {
+      EXPECT_EQ(request.public_metadata_extensions(),
+                fake_get_initial_data_response_.privacy_pass_data()
+                    .public_metadata_extensions());
+    } else {
+      EXPECT_EQ(request.public_metadata_info().SerializeAsString(),
+                public_metadata_info_.SerializeAsString());
+    }
 
     // Construct AuthAndSignResponse.
     privacy::ppn::AuthAndSignResponse response;
     for (const auto& request_token : request.blinded_token()) {
       std::string decoded_blinded_token;
       ASSERT_TRUE(absl::Base64Unescape(request_token, &decoded_blinded_token));
-      absl::StatusOr<std::string> serialized_token =
-          anonymous_tokens::TestSign(decoded_blinded_token,
-                                                         keypair_.first.get());
-      QUICHE_EXPECT_OK(serialized_token);
-      response.add_blinded_token_signature(
-          absl::Base64Escape(*serialized_token));
+      if (use_privacy_pass) {
+        absl::StatusOr<std::string> signature =
+            anonymous_tokens::TestSignWithPublicMetadata(
+                decoded_blinded_token, request.public_metadata_extensions(),
+                *rsa_private_key_, false);
+        QUICHE_EXPECT_OK(signature);
+        response.add_blinded_token_signature(absl::Base64Escape(*signature));
+      } else {
+        absl::StatusOr<std::string> serialized_token =
+            anonymous_tokens::TestSign(
+                decoded_blinded_token, rsa_private_key_.get());
+        // TestSignWithPublicMetadata for privacy pass
+        QUICHE_EXPECT_OK(serialized_token);
+        response.add_blinded_token_signature(
+            absl::Base64Escape(*serialized_token));
+      }
     }
     sign_response_ = response;
   }
 
-  void ValidateGetTokensOutput(const absl::Span<BlindSignToken>& tokens) {
+  void ValidateGetTokensOutput(absl::Span<BlindSignToken> tokens) {
     for (const auto& token : tokens) {
       privacy::ppn::SpendTokenData spend_token_data;
       ASSERT_TRUE(spend_token_data.ParseFromString(token.token));
@@ -133,7 +224,7 @@
       EXPECT_GE(spend_token_data.unblinded_token_signature().size(),
                 spend_token_data.unblinded_token().size());
       EXPECT_EQ(spend_token_data.signing_key_version(),
-                keypair_.second.key_version());
+                public_key_proto_.key_version());
       EXPECT_NE(spend_token_data.use_case(),
                 anonymous_tokens::AnonymousTokensUseCase::
                     ANONYMOUS_TOKENS_USE_CASE_UNDEFINED);
@@ -141,11 +232,28 @@
     }
   }
 
+  void ValidatePrivacyPassTokensOutput(absl::Span<BlindSignToken> tokens) {
+    for (const auto& token : tokens) {
+      privacy::ppn::PrivacyPassTokenData privacy_pass_token_data;
+      ASSERT_TRUE(privacy_pass_token_data.ParseFromString(token.token));
+      // Validate token structure.
+      std::string decoded_token;
+      ASSERT_TRUE(absl::Base64Unescape(privacy_pass_token_data.token(),
+                                       &decoded_token));
+      std::string decoded_extensions;
+      ASSERT_TRUE(absl::Base64Unescape(
+          privacy_pass_token_data.encoded_extensions(), &decoded_extensions));
+    }
+  }
+
   MockBlindSignHttpInterface mock_http_interface_;
   std::unique_ptr<BlindSignAuth> blind_sign_auth_;
-  std::pair<bssl::UniquePtr<RSA>,
-            anonymous_tokens::RSABlindSignaturePublicKey>
-      keypair_;
+  anonymous_tokens::RSABlindSignaturePublicKey
+      public_key_proto_;
+  bssl::UniquePtr<RSA> rsa_public_key_;
+  bssl::UniquePtr<RSA> rsa_private_key_;
+  std::string token_key_id_;
+  anonymous_tokens::Extensions extensions_;
   privacy::ppn::PublicMetadataInfo public_metadata_info_;
   privacy::ppn::AuthAndSignResponse sign_response_;
   privacy::ppn::GetInitialDataResponse fake_get_initial_data_response_;
@@ -176,7 +284,7 @@
         .Times(1)
         .WillOnce(Invoke([this](Unused, Unused, const std::string& body,
                                 BlindSignHttpCallback callback) {
-          CreateSignResponse(body);
+          CreateSignResponse(body, false);
           BlindSignHttpResponse http_response(
               200, sign_response_.SerializeAsString());
           std::move(callback)(http_response);
@@ -275,7 +383,7 @@
         .Times(1)
         .WillOnce(Invoke([this](Unused, Unused, const std::string& body,
                                 BlindSignHttpCallback callback) {
-          CreateSignResponse(body);
+          CreateSignResponse(body, false);
           // Add an invalid signature that can't be Base64 decoded.
           sign_response_.add_blinded_token_signature("invalid_signature%");
           BlindSignHttpResponse http_response(
@@ -295,17 +403,87 @@
   done.WaitForNotification();
 }
 
-TEST_F(BlindSignAuthTest, TestGetTokensFailedPrivacyPass) {
+TEST_F(BlindSignAuthTest, TestPrivacyPassGetTokensSucceeds) {
   privacy::ppn::BlindSignAuthOptions options;
   options.set_enable_privacy_pass(true);
   blind_sign_auth_ =
       std::make_unique<BlindSignAuth>(&mock_http_interface_, options);
 
+  public_key_proto_.set_message_mask_type(
+      anonymous_tokens::AT_MESSAGE_MASK_NO_MASK);
+  public_key_proto_.set_message_mask_size(0);
+  *fake_get_initial_data_response_.mutable_at_public_metadata_public_key() =
+      public_key_proto_;
+  BlindSignHttpResponse fake_public_key_response(
+      200, fake_get_initial_data_response_.SerializeAsString());
+  {
+    InSequence seq;
+
+    EXPECT_CALL(
+        mock_http_interface_,
+        DoRequest(
+            Eq(BlindSignHttpRequestType::kGetInitialData), Eq(oauth_token_),
+            Eq(expected_get_initial_data_request_.SerializeAsString()), _))
+        .Times(1)
+        .WillOnce([=](auto&&, auto&&, auto&&, auto get_initial_data_cb) {
+          std::move(get_initial_data_cb)(fake_public_key_response);
+        });
+
+    EXPECT_CALL(mock_http_interface_,
+                DoRequest(Eq(BlindSignHttpRequestType::kAuthAndSign),
+                          Eq(oauth_token_), _, _))
+        .Times(1)
+        .WillOnce(Invoke([this](Unused, Unused, const std::string& body,
+                                BlindSignHttpCallback callback) {
+          CreateSignResponse(body, /*use_privacy_pass=*/true);
+          BlindSignHttpResponse http_response(
+              200, sign_response_.SerializeAsString());
+          std::move(callback)(http_response);
+        }));
+  }
+
+  int num_tokens = 1;
+  QuicheNotification done;
+  SignedTokenCallback callback =
+      [this, &done](absl::StatusOr<absl::Span<BlindSignToken>> tokens) {
+        QUICHE_EXPECT_OK(tokens);
+        ValidatePrivacyPassTokensOutput(*tokens);
+        done.Notify();
+      };
+  blind_sign_auth_->GetTokens(oauth_token_, num_tokens, std::move(callback));
+  done.WaitForNotification();
+}
+
+TEST_F(BlindSignAuthTest, TestPrivacyPassGetTokensFailsWithBadExtensions) {
+  privacy::ppn::BlindSignAuthOptions options;
+  options.set_enable_privacy_pass(true);
+  blind_sign_auth_ =
+      std::make_unique<BlindSignAuth>(&mock_http_interface_, options);
+
+  public_key_proto_.set_message_mask_type(
+      anonymous_tokens::AT_MESSAGE_MASK_NO_MASK);
+  public_key_proto_.set_message_mask_size(0);
+  *fake_get_initial_data_response_.mutable_at_public_metadata_public_key() =
+      public_key_proto_;
+  fake_get_initial_data_response_.mutable_privacy_pass_data()
+      ->set_public_metadata_extensions("spam");
+  BlindSignHttpResponse fake_public_key_response(
+      200, fake_get_initial_data_response_.SerializeAsString());
+
+  EXPECT_CALL(
+      mock_http_interface_,
+      DoRequest(Eq(BlindSignHttpRequestType::kGetInitialData), Eq(oauth_token_),
+                Eq(expected_get_initial_data_request_.SerializeAsString()), _))
+      .Times(1)
+      .WillOnce([=](auto&&, auto&&, auto&&, auto get_initial_data_cb) {
+        std::move(get_initial_data_cb)(fake_public_key_response);
+      });
+
   int num_tokens = 1;
   QuicheNotification done;
   SignedTokenCallback callback =
       [&done](absl::StatusOr<absl::Span<BlindSignToken>> tokens) {
-        EXPECT_THAT(tokens.status().code(), absl::StatusCode::kUnimplemented);
+        EXPECT_THAT(tokens.status().code(), absl::StatusCode::kInvalidArgument);
         done.Notify();
       };
   blind_sign_auth_->GetTokens(oauth_token_, num_tokens, std::move(callback));
diff --git a/quiche/blind_sign_auth/proto/spend_token_data.proto b/quiche/blind_sign_auth/proto/spend_token_data.proto
index 90b490d..694f189 100644
--- a/quiche/blind_sign_auth/proto/spend_token_data.proto
+++ b/quiche/blind_sign_auth/proto/spend_token_data.proto
@@ -39,3 +39,13 @@
   // or public_metadata.
   bytes binary_public_metadata = 7;
 }
+
+message PrivacyPassTokenData {
+  // The base64 encoded Privacy Pass token.
+  string token = 1;
+  // The base64 encoded, Privacy Pass compliant binary public metadata.
+  string encoded_extensions = 2;
+  // Populate only for testing, this will affect which public key is used.
+  anonymous_tokens.AnonymousTokensUseCase use_case_override =
+      3;
+}