blob: 8d67f0910905d9cb311e221a4da74fd56d9202b0 [file] [edit]
// 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 "quiche/quic/masque/private_tokens.h"
#include <memory>
#include <string>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/escaping.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_replace.h"
#include "absl/strings/string_view.h"
#include "anonymous_tokens/cpp/crypto/crypto_utils.h"
#include "anonymous_tokens/cpp/crypto/rsa_blinder.h"
#include "anonymous_tokens/cpp/privacy_pass/rsa_bssa_client.h"
#include "anonymous_tokens/cpp/privacy_pass/token_encodings.h"
#include "openssl/base.h"
#include "openssl/bio.h"
#include "openssl/bn.h"
#include "openssl/digest.h"
#include "openssl/pem.h"
#include "openssl/rand.h"
#include "openssl/rsa.h"
#include "openssl/sha.h"
#include "quiche/common/quiche_status_utils.h"
namespace quic {
namespace AT = ::anonymous_tokens;
namespace {
absl::StatusOr<std::unique_ptr<AT::RsaBlinder>> CreateBlinder(
const RSA* public_key) {
const BIGNUM* rsa_modulus_bignum = RSA_get0_n(public_key);
if (rsa_modulus_bignum == nullptr) {
return absl::InternalError("Failed to get RSA modulus");
}
QUICHE_ASSIGN_OR_RETURN(std::string rsa_modulus,
AT::BignumToString(*rsa_modulus_bignum,
BN_num_bytes(rsa_modulus_bignum)));
const BIGNUM* rsa_public_exponent_bignum = RSA_get0_e(public_key);
if (rsa_public_exponent_bignum == nullptr) {
return absl::InternalError("Failed to get RSA public exponent");
}
QUICHE_ASSIGN_OR_RETURN(
std::string rsa_public_exponent,
AT::BignumToString(*rsa_public_exponent_bignum,
BN_num_bytes(rsa_public_exponent_bignum)));
return AT::RsaBlinder::New(rsa_modulus, rsa_public_exponent, EVP_sha384(),
EVP_sha384(), SHA384_DIGEST_LENGTH,
/*use_rsa_public_exponent=*/false);
}
absl::StatusOr<std::string> BlindSign(RSA* private_key,
absl::string_view blinded_message) {
if (blinded_message.size() != RSA_size(private_key)) {
return absl::InvalidArgumentError(
absl::StrCat("Blind message size ", blinded_message.size(),
" does not match RSA size ", RSA_size(private_key)));
}
std::string signature(blinded_message.size(), 0);
// Compute a raw RSA signature.
size_t out_len;
if (RSA_sign_raw(
/*rsa=*/private_key, /*out_len=*/&out_len,
/*out=*/reinterpret_cast<uint8_t*>(signature.data()),
/*max_out=*/signature.size(),
/*in=*/reinterpret_cast<const uint8_t*>(blinded_message.data()),
/*in_len=*/blinded_message.size(),
/*padding=*/RSA_NO_PADDING) != 1) {
return absl::InternalError(
"RSA_sign_raw failed when called from RsaBlindSigner::Sign");
}
if (out_len != signature.size()) {
return absl::InternalError(absl::StrCat("RSA_sign_raw set out_len to ",
out_len, " instead of ",
signature.size()));
}
return signature;
}
} // namespace
std::string Base64UrlEncodeWithPadding(absl::string_view input) {
// Private tokens require padded base64url so we need to use the non-URL-safe
// base64 encoding to get the padding then replace '+' and '/'.
std::string base64_encoded = absl::Base64Escape(input);
absl::StrReplaceAll({{"+", "-"}, {"/", "_"}}, &base64_encoded);
return base64_encoded;
}
absl::StatusOr<bssl::UniquePtr<RSA>> ParseRsaPrivateKeyFile(
absl::string_view file_path) {
BIO* bio = BIO_new_file(file_path.data(), "r");
if (!bio) {
return absl::InvalidArgumentError(
absl::StrCat("Failed to open file \"", file_path, "\""));
}
RSA* rsa_key = PEM_read_bio_RSAPrivateKey(bio, nullptr, nullptr, nullptr);
BIO_free(bio);
if (!rsa_key) {
return absl::InvalidArgumentError(absl::StrCat(
"Failed to read RSA private key from file \"", file_path, "\""));
}
return bssl::UniquePtr<RSA>(rsa_key);
}
absl::StatusOr<bssl::UniquePtr<RSA>> ParseRsaPrivateKeyData(
absl::string_view pem_data) {
BIO* bio = BIO_new_mem_buf(pem_data.data(), pem_data.size());
if (!bio) {
return absl::InvalidArgumentError("Failed to create BIO from PEM data");
}
RSA* rsa_key = PEM_read_bio_RSAPrivateKey(bio, nullptr, nullptr, nullptr);
BIO_free(bio);
if (!rsa_key) {
return absl::InvalidArgumentError(
"Failed to read RSA private key from PEM data");
}
return bssl::UniquePtr<RSA>(rsa_key);
}
absl::StatusOr<bssl::UniquePtr<RSA>> ParseRsaPublicKeyFile(
absl::string_view file_path) {
BIO* bio = BIO_new_file(file_path.data(), "r");
if (!bio) {
return absl::InvalidArgumentError(
absl::StrCat("Failed to open file \"", file_path, "\""));
}
RSA* rsa_key = PEM_read_bio_RSA_PUBKEY(bio, nullptr, nullptr, nullptr);
BIO_free(bio);
if (!rsa_key) {
return absl::InvalidArgumentError(absl::StrCat(
"Failed to read RSA public key from file \"", file_path, "\""));
}
return bssl::UniquePtr<RSA>(rsa_key);
}
absl::StatusOr<bssl::UniquePtr<RSA>> ParseRsaPublicKeyData(
absl::string_view pem_data) {
BIO* bio = BIO_new_mem_buf(pem_data.data(), pem_data.size());
if (!bio) {
return absl::InvalidArgumentError("Failed to create BIO from PEM data");
}
RSA* rsa_key = PEM_read_bio_RSA_PUBKEY(bio, nullptr, nullptr, nullptr);
BIO_free(bio);
if (!rsa_key) {
return absl::InvalidArgumentError(
"Failed to read RSA public key from PEM data");
}
return bssl::UniquePtr<RSA>(rsa_key);
}
absl::StatusOr<std::string> EncodePrivacyPassPublicKey(const RSA* public_key) {
QUICHE_ASSIGN_OR_RETURN(std::string der_encoding,
AT::RsaSsaPssPublicKeyToDerEncoding(public_key));
return Base64UrlEncodeWithPadding(der_encoding);
}
absl::StatusOr<std::string> CreateTokenLocally(RSA* private_key,
const RSA* public_key) {
QUICHE_ASSIGN_OR_RETURN(std::string public_key_der,
AT::RsaSsaPssPublicKeyToDerEncoding(public_key));
static constexpr size_t kNonceSize = 32;
static constexpr uint16_t kPrivacyPassBlindRsa2048TokenType = 0x0002;
static constexpr absl::string_view kChallenge = "";
AT::Token token;
token.token_type = kPrivacyPassBlindRsa2048TokenType;
token.token_key_id = std::string(SHA256_DIGEST_LENGTH, '\0');
if (SHA256(reinterpret_cast<const uint8_t*>(public_key_der.data()),
public_key_der.size(),
reinterpret_cast<uint8_t*>(token.token_key_id.data())) ==
nullptr) {
return absl::InternalError("Failed to compute token_key_id");
}
token.context = std::string(SHA256_DIGEST_LENGTH, '\0');
if (SHA256(reinterpret_cast<const uint8_t*>(kChallenge.data()),
kChallenge.size(),
reinterpret_cast<uint8_t*>(token.context.data())) == nullptr) {
return absl::InternalError("Failed to compute context/challenge_digest");
}
token.nonce = std::string(kNonceSize, '\0');
RAND_bytes(reinterpret_cast<uint8_t*>(token.nonce.data()),
token.nonce.size());
QUICHE_ASSIGN_OR_RETURN(std::string token_input,
AT::AuthenticatorInput(token));
QUICHE_ASSIGN_OR_RETURN(std::unique_ptr<AT::RsaBlinder> blinder,
CreateBlinder(public_key));
QUICHE_ASSIGN_OR_RETURN(std::string blinded_message,
blinder->Blind(token_input));
QUICHE_ASSIGN_OR_RETURN(std::string blinded_signature,
BlindSign(private_key, blinded_message));
QUICHE_ASSIGN_OR_RETURN(token.authenticator,
blinder->Unblind(blinded_signature));
QUICHE_RETURN_IF_ERROR(blinder->Verify(token.authenticator, token_input));
QUICHE_ASSIGN_OR_RETURN(std::string token_bytes, AT::MarshalToken(token));
return Base64UrlEncodeWithPadding(token_bytes);
}
absl::Status ValidateToken(absl::string_view base64_public_key,
absl::string_view base64_token) {
std::string der_public_key;
if (!absl::WebSafeBase64Unescape(base64_public_key, &der_public_key)) {
return absl::InvalidArgumentError("Failed to decode the base64 public key");
}
std::string token_key_id(SHA256_DIGEST_LENGTH, '\0');
if (SHA256(reinterpret_cast<const uint8_t*>(der_public_key.data()),
der_public_key.size(),
reinterpret_cast<uint8_t*>(token_key_id.data())) == nullptr) {
return absl::InternalError("Failed to compute token_key_id");
}
QUICHE_ASSIGN_OR_RETURN(
bssl::UniquePtr<RSA> public_key,
AT::RsaSsaPssPublicKeyFromDerEncoding(der_public_key));
std::string binary_token;
if (!absl::WebSafeBase64Unescape(base64_token, &binary_token)) {
return absl::InvalidArgumentError("Failed to decode the base64 token");
}
QUICHE_ASSIGN_OR_RETURN(AT::Token token, AT::UnmarshalToken(binary_token));
if (token.token_key_id != token_key_id) {
return absl::InvalidArgumentError(absl::StrCat(
"Token key ID ", absl::BytesToHexString(token.token_key_id),
" does not match the public key ID ",
absl::BytesToHexString(token_key_id)));
}
return AT::PrivacyPassRsaBssaClient::Verify(token, *public_key);
}
absl::Status TokenValidatesFromAtLeastOneKey(
const std::vector<std::string>& base64_public_keys,
absl::string_view base64_token) {
absl::Status last_error =
absl::InvalidArgumentError("No public keys provided");
for (const std::string& base64_public_key : base64_public_keys) {
absl::Status status = ValidateToken(base64_public_key, base64_token);
if (status.ok()) {
return absl::OkStatus();
}
last_error = status;
}
return last_error;
}
} // namespace quic