• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.signature;
18 
19 import android.adservices.adselection.SignedContextualAds;
20 import android.adservices.common.AdTechIdentifier;
21 import android.annotation.NonNull;
22 
23 import com.android.adservices.LoggerFactory;
24 import com.android.adservices.data.encryptionkey.EncryptionKeyDao;
25 import com.android.adservices.data.enrollment.EnrollmentDao;
26 import com.android.adservices.service.encryptionkey.EncryptionKey;
27 import com.android.adservices.service.enrollment.EnrollmentData;
28 import com.android.adservices.service.stats.SignatureVerificationLogger;
29 import com.android.adservices.service.stats.SignatureVerificationStats;
30 
31 import com.google.common.annotations.VisibleForTesting;
32 
33 import java.util.Base64;
34 import java.util.Collections;
35 import java.util.Comparator;
36 import java.util.List;
37 import java.util.Objects;
38 import java.util.stream.Collectors;
39 
40 /**
41  * Manages signature verification for Protected Audience Contextual Ads
42  *
43  * <p>See {@link android.adservices.adselection.SignedContextualAds} for more details.
44  */
45 public class ProtectedAudienceSignatureManager {
46     private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger();
47     @VisibleForTesting public static final String EMPTY_STRING_FOR_MISSING_ENROLLMENT_ID = "";
48 
49     /**
50      * This P-256 ECDSA key is used to verify signatures if {@link
51      * com.android.adservices.service.FlagsConstants #KEY_DISABLE_FLEDGE_ENROLLMENT_CHECK} is set to
52      * true.
53      *
54      * <p>This enables CTS and integration testing.
55      *
56      * <p>To test with this key, {@link SignedContextualAds} should be signed with {@link
57      * ProtectedAudienceSignatureManager#PRIVATE_TEST_KEY_STRING}.
58      */
59     public static final String PUBLIC_TEST_KEY_STRING =
60             "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+Eyo0TOllW8as2pTTzxawQ57pXJiH16VERgHqcV1/YpADt3iq6"
61                     + "9vbhwW8Ksi3M0GrxacOuge/AwiM7Uh6+V3PA==";
62 
63     /**
64      * Private key pair of the {@link ProtectedAudienceSignatureManager#PUBLIC_TEST_KEY_STRING}
65      *
66      * <p>See {@link ProtectedAudienceSignatureManager#PUBLIC_TEST_KEY_STRING}
67      */
68     public static final String PRIVATE_TEST_KEY_STRING =
69             "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgECetqRr9eE9DKKjILR+hP66Y1niEw/bqPD/MNx"
70                     + "PTMvmhRANCAAT4TKjRM6WVbxqzalNPPFrBDnulcmIfXpURGAepxXX9ikAO3eKrr29uHBbwqyLczQ"
71                     + "avFpw66B78DCIztSHr5Xc8";
72 
73     @NonNull private final EnrollmentDao mEnrollmentDao;
74     @NonNull private final EncryptionKeyDao mEncryptionKeyDao;
75     @NonNull private final SignatureVerificationLogger mSignatureVerificationLogger;
76     private final boolean mIsEnrollmentCheckEnabled;
77     private final SignatureVerifier mSignatureVerifier;
78 
ProtectedAudienceSignatureManager( @onNull EnrollmentDao enrollmentDao, @NonNull EncryptionKeyDao encryptionKeyDao, @NonNull SignatureVerificationLogger signatureVerificationLogger, boolean isEnrollmentCheckEnabled)79     public ProtectedAudienceSignatureManager(
80             @NonNull EnrollmentDao enrollmentDao,
81             @NonNull EncryptionKeyDao encryptionKeyDao,
82             @NonNull SignatureVerificationLogger signatureVerificationLogger,
83             boolean isEnrollmentCheckEnabled) {
84         Objects.requireNonNull(enrollmentDao);
85         Objects.requireNonNull(encryptionKeyDao);
86         Objects.requireNonNull(signatureVerificationLogger);
87 
88         mEnrollmentDao = enrollmentDao;
89         mEncryptionKeyDao = encryptionKeyDao;
90         mSignatureVerificationLogger = signatureVerificationLogger;
91         mIsEnrollmentCheckEnabled = isEnrollmentCheckEnabled;
92 
93         mSignatureVerifier = new ECDSASignatureVerifier(mSignatureVerificationLogger);
94     }
95 
96     /**
97      * Returns whether is the given {@link SignedContextualAds} object is valid or not
98      *
99      * @param buyer Ad tech's identifier to resolve their public key
100      * @param signedContextualAds contextual ads object to verify
101      * @return true if the object is valid else false
102      */
isVerified( @onNull AdTechIdentifier buyer, @NonNull AdTechIdentifier seller, @NonNull String callerPackageName, @NonNull SignedContextualAds signedContextualAds)103     public boolean isVerified(
104             @NonNull AdTechIdentifier buyer,
105             @NonNull AdTechIdentifier seller,
106             @NonNull String callerPackageName,
107             @NonNull SignedContextualAds signedContextualAds) {
108         Objects.requireNonNull(buyer);
109         Objects.requireNonNull(seller);
110         Objects.requireNonNull(callerPackageName);
111         Objects.requireNonNull(signedContextualAds);
112 
113         try {
114             List<byte[]> publicKeys = fetchPublicKeyForAdTech(buyer);
115             sLogger.v("Received %s keys", publicKeys.size());
116 
117             byte[] serialized = serializeSignedContextualAds(signedContextualAds);
118             sLogger.v("Serialized contextual ads object");
119 
120             logStartSignatureVerification();
121             for (byte[] publicKey : publicKeys) {
122                 if (mSignatureVerifier.verify(
123                         publicKey, serialized, signedContextualAds.getSignature())) {
124                     sLogger.v(
125                             "Signature is verified with key: '%s'.",
126                             Base64.getEncoder().encodeToString(publicKey));
127                     endSuccessfulSigningProcess();
128                     return true;
129                 } else {
130                     logKeyFailedSignatureVerification();
131                 }
132                 sLogger.v(
133                         "Key '%s' didn't verify the signature. Trying the next key...",
134                         Base64.getEncoder().encodeToString(publicKey));
135             }
136             sLogger.v("All keys are exhausted and signature is not verified!");
137             endFailedSigningProcess(buyer, seller, callerPackageName);
138             return false;
139         } catch (Exception e) {
140             sLogger.v("Unknown error during signature verification: %s", e);
141             boolean isUnknownError = true;
142             endFailedSigningProcess(buyer, seller, callerPackageName, isUnknownError);
143             return false;
144         }
145     }
146 
147     @VisibleForTesting
fetchPublicKeyForAdTech(AdTechIdentifier adTech)148     List<byte[]> fetchPublicKeyForAdTech(AdTechIdentifier adTech) {
149         logStartKeyFetch();
150         Base64.Decoder decoder = Base64.getDecoder();
151         if (!mIsEnrollmentCheckEnabled) {
152             sLogger.v("Enrollment check is disabled, returning the default key");
153             List<byte[]> toReturn =
154                     Collections.singletonList(decoder.decode(PUBLIC_TEST_KEY_STRING));
155             logSuccessfulKeyFetch(toReturn.size());
156             return toReturn;
157         }
158 
159         try {
160             sLogger.v("Fetching EnrollmentData for %s", adTech);
161             EnrollmentData enrollmentData =
162                     mEnrollmentDao.getEnrollmentDataForFledgeByAdTechIdentifier(adTech);
163 
164             if (enrollmentData == null || enrollmentData.getEnrollmentId() == null) {
165                 sLogger.v("Enrollment data or id is not found for ad tech: %s", adTech);
166                 logNoEnrollmentDataForBuyer();
167                 logFailedKeyFetch();
168                 return Collections.emptyList();
169             }
170 
171             sLogger.v("Fetching signature keys for %s", enrollmentData.getEnrollmentId());
172             List<EncryptionKey> encryptionKeys =
173                     mEncryptionKeyDao.getEncryptionKeyFromEnrollmentIdAndKeyType(
174                             enrollmentData.getEnrollmentId(), EncryptionKey.KeyType.SIGNING);
175 
176             sLogger.v("Received %s signing key(s)", encryptionKeys.size());
177             List<byte[]> publicKeys =
178                     encryptionKeys.stream()
179                             .sorted(Comparator.comparingLong(EncryptionKey::getExpiration))
180                             .map(key -> decoder.decode(key.getBody()))
181                             .collect(Collectors.toList());
182             logSuccessfulKeyFetch(publicKeys.size());
183             return publicKeys;
184         } catch (Exception e) {
185             logFailedKeyFetch();
186             sLogger.e(e, "Unknown error during key fetch for signature verification");
187             throw e;
188         }
189     }
190 
serializeSignedContextualAds(SignedContextualAds signedContextualAds)191     private byte[] serializeSignedContextualAds(SignedContextualAds signedContextualAds) {
192         logStartSerialization();
193         byte[] toReturn = new SignedContextualAdsHashUtil().serialize(signedContextualAds);
194         logEndSerialization();
195         return toReturn;
196     }
197 
logStartKeyFetch()198     private void logStartKeyFetch() {
199         mSignatureVerificationLogger.startKeyFetchForSignatureVerification();
200     }
201 
logFailedKeyFetch()202     private void logFailedKeyFetch() {
203         mSignatureVerificationLogger.endKeyFetchForSignatureVerification();
204         mSignatureVerificationLogger.setNumOfKeysFetched(0);
205     }
206 
logStartSerialization()207     private void logStartSerialization() {
208         mSignatureVerificationLogger.startSerializationForSignatureVerification();
209     }
210 
logEndSerialization()211     private void logEndSerialization() {
212         mSignatureVerificationLogger.endSerializationForSignatureVerification();
213     }
214 
logSuccessfulKeyFetch(int numOfKeysFetched)215     private void logSuccessfulKeyFetch(int numOfKeysFetched) {
216         mSignatureVerificationLogger.endKeyFetchForSignatureVerification();
217         mSignatureVerificationLogger.setNumOfKeysFetched(numOfKeysFetched);
218         if (numOfKeysFetched == 0) {
219             mSignatureVerificationLogger.setFailureDetailNoKeysFetchedForBuyer();
220         }
221     }
222 
logStartSignatureVerification()223     private void logStartSignatureVerification() {
224         mSignatureVerificationLogger.startSignatureVerification();
225     }
226 
logKeyFailedSignatureVerification()227     private void logKeyFailedSignatureVerification() {
228         mSignatureVerificationLogger.addFailureDetailCountOfKeysFailedToVerifySignature();
229     }
230 
logNoEnrollmentDataForBuyer()231     private void logNoEnrollmentDataForBuyer() {
232         mSignatureVerificationLogger.setFailureDetailNoEnrollmentDataForBuyer();
233     }
234 
endFailedSigningProcess( AdTechIdentifier buyer, AdTechIdentifier seller, String callerPackageName)235     private void endFailedSigningProcess(
236             AdTechIdentifier buyer, AdTechIdentifier seller, String callerPackageName) {
237         boolean isKnownError = false;
238         endFailedSigningProcess(buyer, seller, callerPackageName, isKnownError);
239     }
240 
endFailedSigningProcess( AdTechIdentifier buyer, AdTechIdentifier seller, String callerPackageName, boolean isUnknownError)241     private void endFailedSigningProcess(
242             AdTechIdentifier buyer,
243             AdTechIdentifier seller,
244             String callerPackageName,
245             boolean isUnknownError) {
246         mSignatureVerificationLogger.endSignatureVerification();
247         mSignatureVerificationLogger.setFailedSignatureBuyerEnrollmentId(
248                 resolveEnrollmentIdFromAdTechIdentifier(buyer));
249         mSignatureVerificationLogger.setFailedSignatureSellerEnrollmentId(
250                 resolveEnrollmentIdFromAdTechIdentifier(seller));
251         mSignatureVerificationLogger.setFailedSignatureCallerPackageName(callerPackageName);
252         if (isUnknownError) {
253             mSignatureVerificationLogger.setFailureDetailUnknownError();
254         }
255         mSignatureVerificationLogger.close(
256                 SignatureVerificationStats.VerificationStatus.VERIFICATION_FAILED.getValue());
257     }
258 
endSuccessfulSigningProcess()259     private void endSuccessfulSigningProcess() {
260         mSignatureVerificationLogger.endSignatureVerification();
261         mSignatureVerificationLogger.close(
262                 SignatureVerificationStats.VerificationStatus.VERIFIED.getValue());
263     }
264 
resolveEnrollmentIdFromAdTechIdentifier(AdTechIdentifier adTechIdentifier)265     private String resolveEnrollmentIdFromAdTechIdentifier(AdTechIdentifier adTechIdentifier) {
266         EnrollmentData enrollmentData =
267                 mEnrollmentDao.getEnrollmentDataForFledgeByAdTechIdentifier(adTechIdentifier);
268         if (Objects.isNull(enrollmentData) || Objects.isNull(enrollmentData.getEnrollmentId())) {
269             return EMPTY_STRING_FOR_MISSING_ENROLLMENT_ID;
270         }
271         return enrollmentData.getEnrollmentId();
272     }
273 }
274