1 /* 2 * Copyright (C) 2025 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.adservices.service.adselection; 18 19 import android.adservices.adselection.AuctionEncryptionKeyFixture; 20 import android.adservices.adselection.GetAdSelectionDataResponse; 21 22 import com.android.adservices.ohttp.ObliviousHttpGateway; 23 import com.android.adservices.ohttp.OhttpGatewayPrivateKey; 24 import com.android.adservices.ohttp.algorithms.UnsupportedHpkeAlgorithmException; 25 import com.android.adservices.service.common.httpclient.AdServicesHttpClientResponse; 26 import com.android.adservices.service.proto.bidding_auction_servers.BiddingAuctionServers; 27 import com.android.adservices.service.stats.AdServicesLogger; 28 29 import com.google.common.collect.ImmutableList; 30 import com.google.common.io.BaseEncoding; 31 import com.google.protobuf.ByteString; 32 33 import org.json.JSONException; 34 35 import java.io.IOException; 36 import java.util.HashMap; 37 import java.util.Map; 38 import java.util.Objects; 39 40 /** 41 * Helper class for testing server auction. Primarily does server side decryption of 42 * getAdSelectionData response, server side encryption of AuctionResult which can then be decrypted 43 * by the client via persistAdSelectionResult and a few other utility methods. 44 */ 45 public class ServerAuctionTestHelper { 46 47 private static final String DEFAULT_PRIVATE_KEY_HEX = 48 "e7b292f49df28b8065992cdeadbc9d032a0e09e8476cb6d8d507212e7be3b9b4"; 49 private static final String DEFAULT_PUBLIC_KEY_HEX = 50 "87ey8XZPXAd+/+ytKv2GFUWW5j9zdepSJ2G4gebDwyM="; 51 private static final String DEFAULT_KEY_ID_HEX = "400bed24-c62f-46e0-a1ad-211361ad771a"; 52 private static final int DEFAULT_COMPRESSION_ALGORITHM_VERSION = 2; 53 private static final int DEFAULT_PAYLOAD_FORMAT_VERSION = 0; 54 private static final ImmutableList<Integer> DEFAULT_PAYLOAD_BUCKET_SIZES = 55 ImmutableList.of(0, 1024, 2048, 4096, 8192, 16384, 32768, 65536); 56 57 private final OhttpGatewayPrivateKey mPrivateKey; 58 public final AuctionEncryptionKeyFixture.AuctionKey mAuctionKey; 59 private final AuctionServerDataCompressor mAuctionServerDataCompressor; 60 private final AuctionServerPayloadFormatter mAuctionServerPayloadFormatter; 61 private final AuctionServerPayloadExtractor mAuctionServerPayloadExtractor; 62 63 /** 64 * Gets an instance of the helper with default public/private key pair and default compressor 65 * and payload formatter versions 66 */ getDefaultInstance(AdServicesLogger adServicesLogger)67 public static ServerAuctionTestHelper getDefaultInstance(AdServicesLogger adServicesLogger) { 68 return new ServerAuctionTestHelper( 69 DEFAULT_KEY_ID_HEX, 70 DEFAULT_PRIVATE_KEY_HEX, 71 DEFAULT_PUBLIC_KEY_HEX, 72 DEFAULT_PAYLOAD_BUCKET_SIZES, 73 DEFAULT_PAYLOAD_FORMAT_VERSION, 74 DEFAULT_COMPRESSION_ALGORITHM_VERSION, 75 adServicesLogger); 76 } 77 ServerAuctionTestHelper( String idHex, String privateKeyHex, String publicKeyHex, ImmutableList<Integer> payloadBucketSizes, int payloadFormatterVersion, int compressorVersion, AdServicesLogger adServicesLogger)78 public ServerAuctionTestHelper( 79 String idHex, 80 String privateKeyHex, 81 String publicKeyHex, 82 ImmutableList<Integer> payloadBucketSizes, 83 int payloadFormatterVersion, 84 int compressorVersion, 85 AdServicesLogger adServicesLogger) { 86 mPrivateKey = 87 OhttpGatewayPrivateKey.create( 88 BaseEncoding.base16().lowerCase().decode(privateKeyHex)); 89 mAuctionKey = 90 AuctionEncryptionKeyFixture.AuctionKey.builder() 91 .setKeyId(idHex) 92 .setPublicKey(publicKeyHex) 93 .build(); 94 mAuctionServerDataCompressor = 95 AuctionServerDataCompressorFactory.getDataCompressor(compressorVersion); 96 mAuctionServerPayloadFormatter = 97 AuctionServerPayloadFormatterFactory.createPayloadFormatter( 98 payloadFormatterVersion, 99 payloadBucketSizes, 100 /* sellerConfiguration= */ null); 101 mAuctionServerPayloadExtractor = 102 AuctionServerPayloadFormatterFactory.createPayloadExtractor( 103 payloadFormatterVersion, adServicesLogger); 104 } 105 106 /** Get an HTTP response with the public auction key */ getPublicAuctionKeyHttpResponse()107 public AdServicesHttpClientResponse getPublicAuctionKeyHttpResponse() throws JSONException { 108 return AuctionEncryptionKeyFixture.mockAuctionKeyFetchResponseWithGivenKey(mAuctionKey); 109 } 110 111 /** Get a map of decompressed buyer inputs from a protected auction input */ getDecompressedBuyerInputs( BiddingAuctionServers.ProtectedAuctionInput protectedAuctionInput)112 public Map<String, BiddingAuctionServers.BuyerInput> getDecompressedBuyerInputs( 113 BiddingAuctionServers.ProtectedAuctionInput protectedAuctionInput) throws Exception { 114 Map<String, BiddingAuctionServers.BuyerInput> decompressedBuyerInputs = new HashMap<>(); 115 for (Map.Entry<String, ByteString> entry : 116 protectedAuctionInput.getBuyerInputMap().entrySet()) { 117 byte[] buyerInputBytes = entry.getValue().toByteArray(); 118 byte[] decompressed = 119 mAuctionServerDataCompressor 120 .decompress( 121 AuctionServerDataCompressor.CompressedData.create( 122 buyerInputBytes)) 123 .getData(); 124 decompressedBuyerInputs.put( 125 entry.getKey(), BiddingAuctionServers.BuyerInput.parseFrom(decompressed)); 126 } 127 return decompressedBuyerInputs; 128 } 129 130 /** Get a protected auction input from the encrypted ad selection data */ decryptAdSelectionData( byte[] adSelectionData)131 public BiddingAuctionServers.ProtectedAuctionInput decryptAdSelectionData( 132 byte[] adSelectionData) throws Exception { 133 byte[] decrypted = 134 ObliviousHttpGateway.decrypt(mPrivateKey, Objects.requireNonNull(adSelectionData)); 135 AuctionServerPayloadUnformattedData unformatted = 136 mAuctionServerPayloadExtractor.extract( 137 AuctionServerPayloadFormattedData.create(decrypted)); 138 return BiddingAuctionServers.ProtectedAuctionInput.parseFrom(unformatted.getData()); 139 } 140 141 /** 142 * Returns an encrypted server response based on the output of getAdSelectionData() and 143 * unencrypted auction result bytes 144 */ encryptServerAuctionResult( GetAdSelectionDataResponse adSelectionData, BiddingAuctionServers.AuctionResult auctionResult)145 public byte[] encryptServerAuctionResult( 146 GetAdSelectionDataResponse adSelectionData, 147 BiddingAuctionServers.AuctionResult auctionResult) 148 throws UnsupportedHpkeAlgorithmException, IOException { 149 return ObliviousHttpGateway.encrypt( 150 mPrivateKey, 151 Objects.requireNonNull(adSelectionData.getAdSelectionData()), 152 prepareAuctionResultBytes(auctionResult)); 153 } 154 prepareAuctionResultBytes(BiddingAuctionServers.AuctionResult auctionResult)155 private byte[] prepareAuctionResultBytes(BiddingAuctionServers.AuctionResult auctionResult) { 156 byte[] auctionResultBytes = auctionResult.toByteArray(); 157 AuctionServerDataCompressor.CompressedData compressedData = 158 mAuctionServerDataCompressor.compress( 159 AuctionServerDataCompressor.UncompressedData.create(auctionResultBytes)); 160 AuctionServerPayloadFormattedData formattedData = 161 mAuctionServerPayloadFormatter.apply( 162 AuctionServerPayloadUnformattedData.create(compressedData.getData()), 163 AuctionServerDataCompressorGzip.VERSION); 164 return formattedData.getData(); 165 } 166 } 167