blob: c6991454fc8dbe8609555720063b62337af00558 [file]
// Copyright 2026 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <algorithm>
#include <string>
#include <vector>
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
#include "openssl/base.h"
#include "openssl/rsa.h"
#include "quiche/quic/masque/private_tokens.h"
#include "quiche/common/platform/api/quiche_command_line_flags.h"
#include "quiche/common/platform/api/quiche_logging.h"
#include "quiche/common/quiche_status_utils.h"
// This tool exists to help test out private tokens as defined in RFCs 9577 and
// 9578.
// To generate a config based on existing keys in PEM files:
// blaze run //quiche/quic/masque:private_tokens -- --alsologtostderr
// --private_key_file=/path/to/private_key.pem
// --public_key_file==/path/to/public_key.pem
// To test a token against a given public key in base64 format:
// blaze run //quiche/quic/masque:private_tokens -- --alsologtostderr
// --encoded_public_key="$PUBLIC_KEY" --token="$TOKEN"
// To test out whether a token matches an issuer URL:
// blaze run //quiche/quic/masque:private_tokens -- --alsologtostderr
// --token="$TOKEN" --encoded_public_key="$(
// curl --silent -H "Accept: application/private-token-issuer-directory"
// "$ISSUER_URL" | jq -r '.["token-keys"] | map(.["token-key"]) | join(",")')"
DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, private_key_file, "",
"Path to the PEM-encoded RSA private key.");
DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, public_key_file, "",
"Path to the PEM-encoded RSA public key.");
DEFINE_QUICHE_COMMAND_LINE_FLAG(
std::string, encoded_public_key, "",
"Base64-encoded public key to use for token validation. Multiple entries "
"may be passed in by separating them with commas.");
DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, token, "", "Token to validate.");
namespace quic {
namespace {
absl::Status RunPrivateTokens(int argc, char* argv[]) {
const char* usage = "Usage: private_tokens";
std::vector<std::string> params =
quiche::QuicheParseCommandLineFlags(usage, argc, argv);
const std::string private_key_file =
quiche::GetQuicheCommandLineFlag(FLAGS_private_key_file);
const std::string public_key_file =
quiche::GetQuicheCommandLineFlag(FLAGS_public_key_file);
const std::string encoded_public_key_from_flags =
quiche::GetQuicheCommandLineFlag(FLAGS_encoded_public_key);
std::vector<std::string> encoded_public_keys = absl::StrSplit(
encoded_public_key_from_flags, ',', absl::SkipWhitespace());
const std::string token_from_flags =
quiche::GetQuicheCommandLineFlag(FLAGS_token);
bssl::UniquePtr<RSA> public_key;
std::string encoded_public_key;
if (!public_key_file.empty()) {
QUICHE_ASSIGN_OR_RETURN(public_key, ParseRsaPublicKey(public_key_file));
QUICHE_ASSIGN_OR_RETURN(encoded_public_key,
EncodePrivacyPassPublicKey(public_key.get()));
if (!encoded_public_keys.empty()) {
if (std::find(encoded_public_keys.begin(), encoded_public_keys.end(),
encoded_public_key) == encoded_public_keys.end()) {
return absl::InvalidArgumentError(
"Public key from --public_key_file does not match "
"--encoded_public_key");
}
} else {
encoded_public_keys.push_back(encoded_public_key);
std::string issuer_config = absl::StrCat(
"{\n \"issuer-request-uri\": "
"\"https://issuer.example.net/request\",\n",
" \"token-keys\": [\n {\n \"token-type\": 2,\n",
" \"token-key\": \"", encoded_public_key, "\",\n }\n ]\n}");
QUICHE_LOG(INFO) << "The issuer config could look like:\n"
<< issuer_config;
}
}
if (!token_from_flags.empty()) {
QUICHE_RETURN_IF_ERROR(
TokenValidatesFromAtLeastOneKey(encoded_public_keys, token_from_flags));
QUICHE_LOG(INFO) << "Validated token from --token";
}
if (!private_key_file.empty()) {
QUICHE_ASSIGN_OR_RETURN(bssl::UniquePtr<RSA> private_key,
ParseRsaPrivateKey(private_key_file));
if (public_key == nullptr) {
return absl::InvalidArgumentError(
"--public_key_file is required when --private_key_file is set.");
}
QUICHE_ASSIGN_OR_RETURN(
std::string generated_token,
CreateTokenLocally(private_key.get(), public_key.get()));
std::string auth_header = absl::StrCat(
"Authorization: PrivateToken token=\"", generated_token, "\"");
QUICHE_LOG(INFO) << "The generated auth header would look like:\n"
<< auth_header;
QUICHE_RETURN_IF_ERROR(
TokenValidatesFromAtLeastOneKey(encoded_public_keys, generated_token));
QUICHE_LOG(INFO) << "Validated locally-generated token";
}
return absl::OkStatus();
}
} // namespace
} // namespace quic
int main(int argc, char* argv[]) {
absl::Status status = quic::RunPrivateTokens(argc, argv);
if (!status.ok()) {
QUICHE_LOG(ERROR) << status.message();
return 1;
}
return 0;
}