| // Copyright (c) 2025 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/blind_sign_auth/blind_sign_auth_test_data.h" | |
| #include <memory> | |
| #include <string> | |
| #include <utility> | |
| #include "absl/status/status.h" | |
| #include "absl/status/statusor.h" | |
| #include "absl/strings/escaping.h" | |
| #include "absl/strings/string_view.h" | |
| #include "absl/time/clock.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/utils.h" | |
| #include "openssl/base.h" | |
| #include "openssl/digest.h" | |
| namespace quiche::test { | |
| using ::privacy::ppn::AttestAndSignResponse; | |
| using ::privacy::ppn::GetInitialDataResponse; | |
| using ::anonymous_tokens::AT_HASH_TYPE_SHA384; | |
| using ::anonymous_tokens::AT_MESSAGE_MASK_NO_MASK; | |
| using ::anonymous_tokens::AT_MGF_SHA384; | |
| using ::anonymous_tokens::ComputeHash; | |
| using ::anonymous_tokens::CreatePrivateKeyRSA; | |
| using ::anonymous_tokens::CreatePublicKeyRSA; | |
| using ::anonymous_tokens::DebugMode; | |
| using ::anonymous_tokens::EncodeExtensions; | |
| using ::anonymous_tokens::ExpirationTimestamp; | |
| using ::anonymous_tokens::Extension; | |
| using ::anonymous_tokens::Extensions; | |
| using ::anonymous_tokens::GeoHint; | |
| using ::anonymous_tokens::GetStrongTestRsaKeyPair2048; | |
| using ::anonymous_tokens::ProxyLayer; | |
| using ::anonymous_tokens::RSABlindSignaturePublicKey; | |
| using ::anonymous_tokens::RSAPublicKey; | |
| using ::anonymous_tokens::RsaSsaPssPublicKeyToDerEncoding; | |
| using ::anonymous_tokens::ServiceType; | |
| using ::anonymous_tokens::TestRsaPublicKey; | |
| namespace { | |
| // Helpers | |
| absl::StatusOr<Extensions> CreateExtensions(); | |
| RSABlindSignaturePublicKey CreatePublicKeyProto( | |
| TestRsaPublicKey test_rsa_public_key); | |
| } // namespace | |
| absl::StatusOr<std::unique_ptr<BlindSignAuthTestData>> | |
| BlindSignAuthTestData::Create() { | |
| // Create keypair and populate protos. | |
| auto [test_rsa_public_key, test_rsa_private_key] = | |
| GetStrongTestRsaKeyPair2048(); | |
| absl::StatusOr<bssl::UniquePtr<RSA>> rsa_public_key = | |
| CreatePublicKeyRSA(test_rsa_public_key.n, test_rsa_public_key.e); | |
| if (!rsa_public_key.ok()) { | |
| return rsa_public_key.status(); | |
| } | |
| absl::StatusOr<bssl::UniquePtr<RSA>> rsa_private_key = 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); | |
| if (!rsa_private_key.ok()) { | |
| return rsa_private_key.status(); | |
| } | |
| // token_key_id is derived from public key. | |
| absl::StatusOr<std::string> public_key_der = | |
| RsaSsaPssPublicKeyToDerEncoding(rsa_public_key->get()); | |
| if (!public_key_der.ok()) { | |
| return public_key_der.status(); | |
| } | |
| const EVP_MD *sha256 = EVP_sha256(); | |
| absl::StatusOr<std::string> token_key_id = | |
| ComputeHash(*public_key_der, *sha256); | |
| if (!token_key_id.ok()) { | |
| return token_key_id.status(); | |
| } | |
| absl::StatusOr<Extensions> extensions = CreateExtensions(); | |
| if (!extensions.ok()) { | |
| return extensions.status(); | |
| } | |
| absl::StatusOr<std::string> serialized_extensions = | |
| EncodeExtensions(*extensions); | |
| if (!serialized_extensions.ok()) { | |
| return serialized_extensions.status(); | |
| } | |
| GetInitialDataResponse::PrivacyPassData privacy_pass_data; | |
| privacy_pass_data.set_token_key_id(*token_key_id); | |
| privacy_pass_data.set_public_metadata_extensions(*serialized_extensions); | |
| return absl::WrapUnique(new BlindSignAuthTestData( | |
| std::move(*rsa_public_key), std::move(*rsa_private_key), | |
| CreatePublicKeyProto(test_rsa_public_key), privacy_pass_data)); | |
| } | |
| GetInitialDataResponse BlindSignAuthTestData::CreateGetInitialDataResponse() { | |
| GetInitialDataResponse response; | |
| *response.mutable_at_public_metadata_public_key() = public_key_proto_; | |
| *response.mutable_privacy_pass_data() = privacy_pass_data_; | |
| // Create fake GetInitialDataResponse for attestation flow. | |
| response.mutable_attestation()->set_attestation_nonce( | |
| "test_attestation_nonce"); | |
| return response; | |
| } | |
| absl::StatusOr<privacy::ppn::AttestAndSignResponse> | |
| BlindSignAuthTestData::CreateAttestAndSignResponse(absl::string_view body) { | |
| privacy::ppn::AttestAndSignRequest request; | |
| if (!request.ParseFromString(body)) { | |
| return absl::InvalidArgumentError("Failed to parse AttestAndSignRequest"); | |
| } | |
| privacy::ppn::AttestAndSignResponse response; | |
| for (const auto &request_token : request.blinded_tokens()) { | |
| std::string decoded_blinded_token; | |
| if (!absl::Base64Unescape(request_token, &decoded_blinded_token)) { | |
| return absl::InvalidArgumentError("Failed to decode blinded token"); | |
| } | |
| absl::StatusOr<std::string> signature = | |
| anonymous_tokens::TestSignWithPublicMetadata( | |
| decoded_blinded_token, | |
| privacy_pass_data_.public_metadata_extensions(), *rsa_private_key_, | |
| false); | |
| if (!signature.ok()) { | |
| return signature.status(); | |
| } | |
| response.add_blinded_token_signatures(absl::Base64Escape(*signature)); | |
| } | |
| return response; | |
| } | |
| namespace { | |
| absl::StatusOr<anonymous_tokens::Extensions> | |
| CreateExtensions() { | |
| // Create and serialize fake extensions. | |
| Extensions extensions; | |
| 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(); | |
| if (!expiration_extension.ok()) { | |
| return expiration_extension.status(); | |
| } | |
| extensions.extensions.push_back(*expiration_extension); | |
| GeoHint geo_hint; | |
| geo_hint.geo_hint = "US,US-AL,ALABASTER"; | |
| absl::StatusOr<Extension> geo_hint_extension = geo_hint.AsExtension(); | |
| if (!geo_hint_extension.ok()) { | |
| return geo_hint_extension.status(); | |
| } | |
| extensions.extensions.push_back(*geo_hint_extension); | |
| ServiceType service_type; | |
| service_type.service_type_id = ServiceType::kChromeIpBlinding; | |
| absl::StatusOr<Extension> service_type_extension = service_type.AsExtension(); | |
| if (!service_type_extension.ok()) { | |
| return service_type_extension.status(); | |
| } | |
| extensions.extensions.push_back(*service_type_extension); | |
| DebugMode debug_mode; | |
| debug_mode.mode = DebugMode::kDebug; | |
| absl::StatusOr<Extension> debug_mode_extension = debug_mode.AsExtension(); | |
| if (!debug_mode_extension.ok()) { | |
| return debug_mode_extension.status(); | |
| } | |
| extensions.extensions.push_back(*debug_mode_extension); | |
| ProxyLayer proxy_layer; | |
| proxy_layer.layer = ProxyLayer::kProxyA; | |
| absl::StatusOr<Extension> proxy_layer_extension = proxy_layer.AsExtension(); | |
| if (!proxy_layer_extension.ok()) { | |
| return proxy_layer_extension.status(); | |
| } | |
| extensions.extensions.push_back(*proxy_layer_extension); | |
| return extensions; | |
| } | |
| RSABlindSignaturePublicKey CreatePublicKeyProto( | |
| TestRsaPublicKey test_rsa_public_key) { | |
| RSAPublicKey public_key; | |
| public_key.set_n(test_rsa_public_key.n); | |
| public_key.set_e(test_rsa_public_key.e); | |
| RSABlindSignaturePublicKey public_key_proto; | |
| 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(AT_HASH_TYPE_SHA384); | |
| public_key_proto.set_mask_gen_function(AT_MGF_SHA384); | |
| public_key_proto.set_salt_length(48); | |
| public_key_proto.set_key_size(256); | |
| public_key_proto.set_message_mask_type(AT_MESSAGE_MASK_NO_MASK); | |
| public_key_proto.set_message_mask_size(0); | |
| return public_key_proto; | |
| } | |
| } // namespace | |
| } // namespace quiche::test |