BlindSignAuth: Switch to WebSafeBase64EscapeWithPadding for token and extensions in PrivacyPassTokenData, per version 02 of draft-wood-privacypass-auth-scheme-extensions.

Tested using `google3/gfe/privacy_proxy/util/testing/client/privacy_proxy_client_bin.cc` in two-hop mode against both ProxyBs, padded tokens + extensions were successfully spent.

PiperOrigin-RevId: 649149482
diff --git a/quiche/blind_sign_auth/blind_sign_auth.cc b/quiche/blind_sign_auth/blind_sign_auth.cc
index f61862f..2f6c4e1 100644
--- a/quiche/blind_sign_auth/blind_sign_auth.cc
+++ b/quiche/blind_sign_auth/blind_sign_auth.cc
@@ -13,6 +13,7 @@
 #include <utility>
 #include <vector>
 
+#include "absl/algorithm/container.h"
 #include "absl/functional/bind_front.h"
 #include "absl/status/status.h"
 #include "absl/status/statusor.h"
@@ -336,9 +337,9 @@
 
     privacy::ppn::PrivacyPassTokenData privacy_pass_token_data;
     privacy_pass_token_data.mutable_token()->assign(
-        absl::WebSafeBase64Escape(*marshaled_token));
+        ConvertBase64ToWebSafeBase64(absl::Base64Escape(*marshaled_token)));
     privacy_pass_token_data.mutable_encoded_extensions()->assign(
-        absl::WebSafeBase64Escape(encoded_extensions));
+        ConvertBase64ToWebSafeBase64(absl::Base64Escape(encoded_extensions)));
     privacy_pass_token_data.set_use_case_override(use_case);
     tokens_vec.push_back(
         BlindSignToken{privacy_pass_token_data.SerializeAsString(),
@@ -360,6 +361,13 @@
   }
 }
 
+std::string BlindSignAuth::ConvertBase64ToWebSafeBase64(
+    std::string base64_string) {
+  absl::c_replace(base64_string, /*old_value=*/'+', /*new_value=*/'-');
+  absl::c_replace(base64_string, /*old_value=*/'/', /*new_value=*/'_');
+  return base64_string;
+}
+
 std::string BlindSignAuthServiceTypeToString(
     quiche::BlindSignAuthServiceType service_type) {
   switch (service_type) {
diff --git a/quiche/blind_sign_auth/blind_sign_auth.h b/quiche/blind_sign_auth/blind_sign_auth.h
index 39d8fa6..37acf27 100644
--- a/quiche/blind_sign_auth/blind_sign_auth.h
+++ b/quiche/blind_sign_auth/blind_sign_auth.h
@@ -60,6 +60,8 @@
       absl::StatusOr<BlindSignMessageResponse> response);
   privacy::ppn::ProxyLayer QuicheProxyLayerToPpnProxyLayer(
       quiche::ProxyLayer proxy_layer);
+  // Replaces '+' and '/' with '-' and '_' in a Base64 string.
+  std::string ConvertBase64ToWebSafeBase64(std::string base64_string);
 
   BlindSignMessageInterface* fetcher_ = nullptr;
   privacy::ppn::BlindSignAuthOptions auth_options_;
diff --git a/quiche/blind_sign_auth/blind_sign_auth_test.cc b/quiche/blind_sign_auth/blind_sign_auth_test.cc
index f293bca..c9ee00d 100644
--- a/quiche/blind_sign_auth/blind_sign_auth_test.cc
+++ b/quiche/blind_sign_auth/blind_sign_auth_test.cc
@@ -215,25 +215,6 @@
     sign_response_ = response;
   }
 
-  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));
-      // Validate token structure.
-      EXPECT_EQ(spend_token_data.public_metadata().SerializeAsString(),
-                public_metadata_info_.public_metadata().SerializeAsString());
-      EXPECT_THAT(spend_token_data.unblinded_token(), StartsWith("blind:"));
-      EXPECT_GE(spend_token_data.unblinded_token_signature().size(),
-                spend_token_data.unblinded_token().size());
-      EXPECT_EQ(spend_token_data.signing_key_version(),
-                public_key_proto_.key_version());
-      EXPECT_NE(spend_token_data.use_case(),
-                anonymous_tokens::AnonymousTokensUseCase::
-                    ANONYMOUS_TOKENS_USE_CASE_UNDEFINED);
-      EXPECT_NE(spend_token_data.message_mask(), "");
-    }
-  }
-
   void ValidatePrivacyPassTokensOutput(absl::Span<BlindSignToken> tokens) {
     for (const auto& token : tokens) {
       privacy::ppn::PrivacyPassTokenData privacy_pass_token_data;
@@ -242,6 +223,8 @@
       std::string decoded_token;
       ASSERT_TRUE(absl::WebSafeBase64Unescape(privacy_pass_token_data.token(),
                                               &decoded_token));
+      // Extensions should be padded and web-safe.
+      EXPECT_EQ(privacy_pass_token_data.encoded_extensions().back(), '=');
       std::string decoded_extensions;
       ASSERT_TRUE(absl::WebSafeBase64Unescape(
           privacy_pass_token_data.encoded_extensions(), &decoded_extensions));