1 /*
2  * Copyright (C) 2022 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.server.security;
18 
19 import static android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE;
20 import static android.security.attestationverification.AttestationVerificationManager.PARAM_PUBLIC_KEY;
21 import static android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE;
22 import static android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS;
23 import static android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE;
24 import static android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY;
25 import static android.security.attestationverification.AttestationVerificationManager.localBindingTypeToString;
26 
27 import static com.android.server.security.AndroidKeystoreAttestationVerificationAttributes.VerifiedBootState.VERIFIED;
28 import static com.android.server.security.AndroidKeystoreAttestationVerificationAttributes.fromCertificate;
29 
30 import static java.nio.charset.StandardCharsets.UTF_8;
31 
32 import android.annotation.NonNull;
33 import android.content.Context;
34 import android.os.Build;
35 import android.os.Bundle;
36 import android.security.attestationverification.AttestationVerificationManager.LocalBindingType;
37 import android.util.Log;
38 import android.util.Slog;
39 
40 import com.android.internal.R;
41 import com.android.internal.annotations.VisibleForTesting;
42 
43 import org.json.JSONObject;
44 
45 import java.io.ByteArrayInputStream;
46 import java.io.IOException;
47 import java.io.InputStream;
48 import java.net.URL;
49 import java.security.InvalidAlgorithmParameterException;
50 import java.security.cert.CertPath;
51 import java.security.cert.CertPathValidator;
52 import java.security.cert.CertPathValidatorException;
53 import java.security.cert.Certificate;
54 import java.security.cert.CertificateException;
55 import java.security.cert.CertificateFactory;
56 import java.security.cert.PKIXCertPathChecker;
57 import java.security.cert.PKIXParameters;
58 import java.security.cert.TrustAnchor;
59 import java.security.cert.X509Certificate;
60 import java.time.LocalDate;
61 import java.time.ZoneId;
62 import java.time.temporal.ChronoUnit;
63 import java.util.ArrayList;
64 import java.util.Arrays;
65 import java.util.Collection;
66 import java.util.Collections;
67 import java.util.HashSet;
68 import java.util.List;
69 import java.util.Objects;
70 import java.util.Set;
71 
72 /**
73  * Verifies Android key attestation according to the
74  * {@link android.security.attestationverification.AttestationVerificationManager#PROFILE_PEER_DEVICE PROFILE_PEER_DEVICE}
75  * profile.
76  *
77  * <p>
78  * The profile is satisfied by checking all the following:
79  * <ul>
80  * <li> TrustAnchor match
81  * <li> Certificate validity
82  * <li> Android OS 10 or higher
83  * <li> Hardware backed key store
84  * <li> Verified boot locked
85  * <li> Remote Patch level must be within 1 year of local patch if local patch is less than 1 year
86  * old.
87  * </ul>
88  *
89  * <p>
90  * Trust anchors are vendor-defined by populating
91  * {@link R.array#vendor_required_attestation_certificates} string array (defenined in
92  * {@code frameworks/base/core/res/res/values/vendor_required_attestation_certificates.xml}).
93  */
94 class AttestationVerificationPeerDeviceVerifier {
95     private static final String TAG = "AVF";
96     private static final boolean DEBUG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.VERBOSE);
97     private static final int MAX_PATCH_AGE_MONTHS = 12;
98 
99     /**
100      * Optional requirements bundle parameter key for {@code TYPE_PUBLIC_KEY} and
101      * {@code TYPE_CHALLENGE}.
102      *
103      * <p>
104      * This is NOT a part of the AVF API surface (neither public SDK nor internal to the
105      * system_server) and should really only be used by the CompanionDeviceManagerService (which
106      * duplicates the value rather than referencing it directly here).
107      */
108     private static final String PARAM_OWNED_BY_SYSTEM = "android.key_owned_by_system";
109 
110     private static final String ANDROID_SYSTEM_PACKAGE_NAME = "AndroidSystem";
111     private static final Set<String> ANDROID_SYSTEM_PACKAGE_NAME_SET =
112             Collections.singleton(ANDROID_SYSTEM_PACKAGE_NAME);
113 
114     private final Context mContext;
115     private final Set<TrustAnchor> mTrustAnchors;
116     private final boolean mRevocationEnabled;
117     private final LocalDate mTestSystemDate;
118     private final LocalDate mTestLocalPatchDate;
119     private final CertificateFactory mCertificateFactory;
120     private final CertPathValidator mCertPathValidator;
121 
AttestationVerificationPeerDeviceVerifier(@onNull Context context)122     AttestationVerificationPeerDeviceVerifier(@NonNull Context context) throws Exception {
123         mContext = Objects.requireNonNull(context);
124         mCertificateFactory = CertificateFactory.getInstance("X.509");
125         mCertPathValidator = CertPathValidator.getInstance("PKIX");
126         mTrustAnchors = getTrustAnchors();
127         mRevocationEnabled = true;
128         mTestSystemDate = null;
129         mTestLocalPatchDate = null;
130     }
131 
132     // Use ONLY for hermetic unit testing.
133     @VisibleForTesting
AttestationVerificationPeerDeviceVerifier(@onNull Context context, Set<TrustAnchor> trustAnchors, boolean revocationEnabled, LocalDate systemDate, LocalDate localPatchDate)134     AttestationVerificationPeerDeviceVerifier(@NonNull Context context,
135             Set<TrustAnchor> trustAnchors, boolean revocationEnabled,
136             LocalDate systemDate, LocalDate localPatchDate) throws Exception {
137         mContext = Objects.requireNonNull(context);
138         mCertificateFactory = CertificateFactory.getInstance("X.509");
139         mCertPathValidator = CertPathValidator.getInstance("PKIX");
140         mTrustAnchors = trustAnchors;
141         mRevocationEnabled = revocationEnabled;
142         mTestSystemDate = systemDate;
143         mTestLocalPatchDate = localPatchDate;
144     }
145 
146     /**
147      * Verifies attestation for public key or challenge local binding.
148      * <p>
149      * The attestations must be suitable for {@link java.security.cert.CertificateFactory}
150      * The certificates in the attestation provided must be DER-encoded and may be supplied in
151      * binary or printable (Base64) encoding. If the certificate is provided in Base64 encoding,
152      * it must be bounded at the beginning by {@code -----BEGIN CERTIFICATE-----}, and must be
153      * bounded at the end by {@code -----END CERTIFICATE-----}.
154      *
155      * @param localBindingType Only {@code TYPE_PUBLIC_KEY} and {@code TYPE_CHALLENGE} supported.
156      * @param requirements Only {@code PARAM_PUBLIC_KEY} and {@code PARAM_CHALLENGE} supported.
157      * @param attestation Certificates should be DER encoded with leaf certificate appended first.
158      */
verifyAttestation( @ocalBindingType int localBindingType, @NonNull Bundle requirements, @NonNull byte[] attestation)159     int verifyAttestation(
160             @LocalBindingType int localBindingType,
161             @NonNull Bundle requirements,
162             @NonNull byte[] attestation) {
163         if (mCertificateFactory == null) {
164             debugVerboseLog("Unable to access CertificateFactory");
165             return RESULT_FAILURE;
166         }
167 
168         if (mCertPathValidator == null) {
169             debugVerboseLog("Unable to access CertPathValidator");
170             return RESULT_FAILURE;
171         }
172 
173         // Check if the provided local binding type is supported and if the provided requirements
174         // "match" the binding type.
175         if (!validateAttestationParameters(localBindingType, requirements)) {
176             return RESULT_FAILURE;
177         }
178 
179         try {
180             // First: parse and validate the certificate chain.
181             final List<X509Certificate> certificateChain = getCertificates(attestation);
182             // (returns void, but throws CertificateException and other similar Exceptions)
183             validateCertificateChain(certificateChain);
184 
185             final var leafCertificate = certificateChain.get(0);
186             final var attestationExtension = fromCertificate(leafCertificate);
187 
188             // Second: verify if the attestation satisfies the "peer device" profile.
189             if (!checkAttestationForPeerDeviceProfile(attestationExtension)) {
190                 return RESULT_FAILURE;
191             }
192 
193             // Third: check if the attestation satisfies local binding requirements.
194             if (!checkLocalBindingRequirements(
195                     leafCertificate, attestationExtension, localBindingType, requirements)) {
196                 return RESULT_FAILURE;
197             }
198 
199             return RESULT_SUCCESS;
200         } catch (CertificateException | CertPathValidatorException
201                 | InvalidAlgorithmParameterException | IOException e) {
202             // Catch all non-RuntimeExpceptions (all of these are thrown by either getCertificates()
203             // or validateCertificateChain() or
204             // AndroidKeystoreAttestationVerificationAttributes.fromCertificate())
205             debugVerboseLog("Unable to parse/validate Android Attestation certificate(s)", e);
206             return RESULT_FAILURE;
207         } catch (RuntimeException e) {
208             // Catch everyting else (RuntimeExpcetions), since we don't want to throw any exceptions
209             // out of this class/method.
210             debugVerboseLog("Unexpected error", e);
211             return RESULT_FAILURE;
212         }
213     }
214 
215     @NonNull
getCertificates(byte[] attestation)216     private List<X509Certificate> getCertificates(byte[] attestation)
217             throws CertificateException {
218         List<X509Certificate> certificates = new ArrayList<>();
219         ByteArrayInputStream bis = new ByteArrayInputStream(attestation);
220         while (bis.available() > 0) {
221             certificates.add((X509Certificate) mCertificateFactory.generateCertificate(bis));
222         }
223 
224         return certificates;
225     }
226 
227     /**
228      * Check if the {@code localBindingType} is supported and if the {@code requirements} contains
229      * the required parameter for the given {@code @LocalBindingType}.
230      */
validateAttestationParameters( @ocalBindingType int localBindingType, @NonNull Bundle requirements)231     private boolean validateAttestationParameters(
232             @LocalBindingType int localBindingType, @NonNull Bundle requirements) {
233         if (localBindingType != TYPE_PUBLIC_KEY && localBindingType != TYPE_CHALLENGE) {
234             debugVerboseLog("Binding type is not supported: " + localBindingType);
235             return false;
236         }
237 
238         if (requirements.size() < 1) {
239             debugVerboseLog("At least 1 requirement is required.");
240             return false;
241         }
242 
243         if (localBindingType == TYPE_PUBLIC_KEY && !requirements.containsKey(PARAM_PUBLIC_KEY)) {
244             debugVerboseLog("Requirements does not contain key: " + PARAM_PUBLIC_KEY);
245             return false;
246         }
247 
248         if (localBindingType == TYPE_CHALLENGE && !requirements.containsKey(PARAM_CHALLENGE)) {
249             debugVerboseLog("Requirements does not contain key: " + PARAM_CHALLENGE);
250             return false;
251         }
252 
253         return true;
254     }
255 
validateCertificateChain(List<X509Certificate> certificates)256     private void validateCertificateChain(List<X509Certificate> certificates)
257             throws CertificateException, CertPathValidatorException,
258             InvalidAlgorithmParameterException  {
259         if (certificates.size() < 2) {
260             debugVerboseLog("Certificate chain less than 2 in size.");
261             throw new CertificateException("Certificate chain less than 2 in size.");
262         }
263 
264         CertPath certificatePath = mCertificateFactory.generateCertPath(certificates);
265         PKIXParameters validationParams = new PKIXParameters(mTrustAnchors);
266         if (mRevocationEnabled) {
267             // Checks Revocation Status List based on
268             // https://developer.android.com/training/articles/security-key-attestation#certificate_status
269             PKIXCertPathChecker checker = new AndroidRevocationStatusListChecker();
270             validationParams.addCertPathChecker(checker);
271         }
272         // Do not use built-in revocation status checker.
273         validationParams.setRevocationEnabled(false);
274         mCertPathValidator.validate(certificatePath, validationParams);
275     }
276 
getTrustAnchors()277     private Set<TrustAnchor> getTrustAnchors() throws CertPathValidatorException {
278         Set<TrustAnchor> modifiableSet = new HashSet<>();
279         try {
280             for (String certString: getTrustAnchorResources()) {
281                 modifiableSet.add(
282                         new TrustAnchor((X509Certificate) mCertificateFactory.generateCertificate(
283                                 new ByteArrayInputStream(getCertificateBytes(certString))), null));
284             }
285         } catch (CertificateException e) {
286             e.printStackTrace();
287             throw new CertPathValidatorException("Invalid trust anchor certificate.", e);
288         }
289         return Collections.unmodifiableSet(modifiableSet);
290     }
291 
getCertificateBytes(String certString)292     private byte[] getCertificateBytes(String certString) {
293         String formattedCertString = certString.replaceAll("\\s+", "\n");
294         formattedCertString = formattedCertString.replaceAll(
295                 "-BEGIN\\nCERTIFICATE-", "-BEGIN CERTIFICATE-");
296         formattedCertString = formattedCertString.replaceAll(
297                 "-END\\nCERTIFICATE-", "-END CERTIFICATE-");
298         return formattedCertString.getBytes(UTF_8);
299     }
300 
getTrustAnchorResources()301     private String[] getTrustAnchorResources() {
302         return mContext.getResources().getStringArray(
303                 R.array.vendor_required_attestation_certificates);
304     }
305 
checkLocalBindingRequirements( @onNull X509Certificate leafCertificate, @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes, @LocalBindingType int localBindingType, @NonNull Bundle requirements)306     private boolean checkLocalBindingRequirements(
307             @NonNull X509Certificate leafCertificate,
308             @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes,
309             @LocalBindingType int localBindingType,
310             @NonNull Bundle requirements) {
311         // First: check non-optional (for the given local binding type) requirements.
312         switch (localBindingType) {
313             case TYPE_PUBLIC_KEY:
314                 // Verify leaf public key matches provided public key.
315                 final boolean publicKeyMatches = checkPublicKey(
316                         leafCertificate, requirements.getByteArray(PARAM_PUBLIC_KEY));
317                 if (!publicKeyMatches) {
318                     debugVerboseLog(
319                             "Provided public key does not match leaf certificate public key.");
320                     return false;
321                 }
322                 break;
323 
324             case TYPE_CHALLENGE:
325                 // Verify challenge matches provided challenge.
326                 final boolean attestationChallengeMatches = checkAttestationChallenge(
327                         attestationAttributes, requirements.getByteArray(PARAM_CHALLENGE));
328                 if (!attestationChallengeMatches) {
329                     debugVerboseLog(
330                             "Provided challenge does not match leaf certificate challenge.");
331                     return false;
332                 }
333                 break;
334 
335             default:
336                 throw new IllegalArgumentException("Unsupported local binding type "
337                         + localBindingTypeToString(localBindingType));
338         }
339 
340         // Second: check specified optional requirements.
341         if (requirements.containsKey(PARAM_OWNED_BY_SYSTEM)) {
342             if (requirements.getBoolean(PARAM_OWNED_BY_SYSTEM)) {
343                 // Verify key is owned by the system.
344                 final boolean ownedBySystem = checkOwnedBySystem(
345                         leafCertificate, attestationAttributes);
346                 if (!ownedBySystem) {
347                     debugVerboseLog("Certificate public key is not owned by the AndroidSystem.");
348                     return false;
349                 }
350             } else {
351                 throw new IllegalArgumentException("The value of the requirement key "
352                         + PARAM_OWNED_BY_SYSTEM
353                         + " cannot be false. You can remove the key if you don't want to verify "
354                         + "it.");
355             }
356         }
357 
358         return true;
359     }
360 
checkAttestationForPeerDeviceProfile( @onNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes)361     private boolean checkAttestationForPeerDeviceProfile(
362             @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes) {
363         // Checks for support of Keymaster 4.
364         if (attestationAttributes.getAttestationVersion() < 3) {
365             debugVerboseLog("Attestation version is not at least 3 (Keymaster 4).");
366             return false;
367         }
368 
369         // Checks for support of Keymaster 4.
370         if (attestationAttributes.getKeymasterVersion() < 4) {
371             debugVerboseLog("Keymaster version is not at least 4.");
372             return false;
373         }
374 
375         // First two characters are Android OS version.
376         if (attestationAttributes.getKeyOsVersion() < 100000) {
377             debugVerboseLog("Android OS version is not 10+.");
378             return false;
379         }
380 
381         if (!attestationAttributes.isAttestationHardwareBacked()) {
382             debugVerboseLog("Key is not HW backed.");
383             return false;
384         }
385 
386         if (!attestationAttributes.isKeymasterHardwareBacked()) {
387             debugVerboseLog("Keymaster is not HW backed.");
388             return false;
389         }
390 
391         if (attestationAttributes.getVerifiedBootState() != VERIFIED) {
392             debugVerboseLog("Boot state not Verified.");
393             return false;
394         }
395 
396         try {
397             if (!attestationAttributes.isVerifiedBootLocked()) {
398                 debugVerboseLog("Verified boot state is not locked.");
399                 return false;
400             }
401         } catch (IllegalStateException e) {
402             debugVerboseLog("VerifiedBootLocked is not set.", e);
403             return false;
404         }
405 
406         // Patch level integer YYYYMM is expected to be within 1 year of today.
407         if (!isValidPatchLevel(attestationAttributes.getKeyOsPatchLevel())) {
408             debugVerboseLog("OS patch level is not within valid range.");
409             return false;
410         }
411 
412         // Patch level integer YYYYMMDD is expected to be within 1 year of today.
413         if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel())) {
414             debugVerboseLog("Boot patch level is not within valid range.");
415             return false;
416         }
417 
418         if (!isValidPatchLevel(attestationAttributes.getKeyVendorPatchLevel())) {
419             debugVerboseLog("Vendor patch level is not within valid range.");
420             return false;
421         }
422 
423         if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel())) {
424             debugVerboseLog("Boot patch level is not within valid range.");
425             return false;
426         }
427 
428         return true;
429     }
430 
checkPublicKey( @onNull Certificate certificate, @NonNull byte[] expectedPublicKey)431     private boolean checkPublicKey(
432             @NonNull Certificate certificate, @NonNull byte[] expectedPublicKey) {
433         final byte[] publicKey = certificate.getPublicKey().getEncoded();
434         return Arrays.equals(publicKey, expectedPublicKey);
435     }
436 
checkAttestationChallenge( @onNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes, @NonNull byte[] expectedChallenge)437     private boolean checkAttestationChallenge(
438             @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes,
439             @NonNull byte[] expectedChallenge) {
440         final byte[] challenge = attestationAttributes.getAttestationChallenge().toByteArray();
441         return Arrays.equals(challenge, expectedChallenge);
442     }
443 
checkOwnedBySystem(@onNull X509Certificate certificate, @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes)444     private boolean checkOwnedBySystem(@NonNull X509Certificate certificate,
445             @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes) {
446         final Set<String> ownerPackages =
447                 attestationAttributes.getApplicationPackageNameVersion().keySet();
448         if (!ANDROID_SYSTEM_PACKAGE_NAME_SET.equals(ownerPackages)) {
449             debugVerboseLog("Owner is not system, packages=" + ownerPackages);
450             return false;
451         }
452 
453         return true;
454     }
455 
456     /**
457      * Validates patchLevel passed is within range of the local device patch date if local patch is
458      * not over one year old. Since the time can be changed on device, just checking the patch date
459      * is not enough. Therefore, we also confirm the patch level for the remote and local device are
460      * similar.
461      */
isValidPatchLevel(int patchLevel)462     private boolean isValidPatchLevel(int patchLevel) {
463         LocalDate currentDate = mTestSystemDate != null
464                 ? mTestSystemDate : LocalDate.now(ZoneId.systemDefault());
465 
466         // Convert local patch date to LocalDate.
467         LocalDate localPatchDate;
468         try {
469             if (mTestLocalPatchDate != null) {
470                 localPatchDate = mTestLocalPatchDate;
471             } else {
472                 localPatchDate = LocalDate.parse(Build.VERSION.SECURITY_PATCH);
473             }
474         } catch (Throwable t) {
475             debugVerboseLog("Build.VERSION.SECURITY_PATCH: "
476                     + Build.VERSION.SECURITY_PATCH + " is not in format YYYY-MM-DD");
477             return false;
478         }
479 
480         // Check local patch date is not in last year of system clock.
481         if (ChronoUnit.MONTHS.between(localPatchDate, currentDate) > MAX_PATCH_AGE_MONTHS) {
482             return true;
483         }
484 
485         // Convert remote patch dates to LocalDate.
486         String remoteDeviceDateStr = String.valueOf(patchLevel);
487         if (remoteDeviceDateStr.length() != 6 && remoteDeviceDateStr.length() != 8) {
488             debugVerboseLog("Patch level is not in format YYYYMM or YYYYMMDD");
489             return false;
490         }
491 
492         int patchYear = Integer.parseInt(remoteDeviceDateStr.substring(0, 4));
493         int patchMonth = Integer.parseInt(remoteDeviceDateStr.substring(4, 6));
494         LocalDate remotePatchDate = LocalDate.of(patchYear, patchMonth, 1);
495 
496         // Check patch dates are within 1 year of each other
497         boolean IsRemotePatchWithinOneYearOfLocalPatch;
498         if (remotePatchDate.compareTo(localPatchDate) > 0) {
499             IsRemotePatchWithinOneYearOfLocalPatch = ChronoUnit.MONTHS.between(
500                     localPatchDate, remotePatchDate) <= MAX_PATCH_AGE_MONTHS;
501         } else if (remotePatchDate.compareTo(localPatchDate) < 0) {
502             IsRemotePatchWithinOneYearOfLocalPatch = ChronoUnit.MONTHS.between(
503                     remotePatchDate, localPatchDate) <= MAX_PATCH_AGE_MONTHS;
504         } else {
505             IsRemotePatchWithinOneYearOfLocalPatch = true;
506         }
507 
508         return IsRemotePatchWithinOneYearOfLocalPatch;
509     }
510 
511     /**
512      * Checks certificate revocation status.
513      *
514      * Queries status list from android.googleapis.com/attestation/status and checks for
515      * the existence of certificate's serial number. If serial number exists in map, then fail.
516      */
517     private final class AndroidRevocationStatusListChecker extends PKIXCertPathChecker {
518         private static final String TOP_LEVEL_JSON_PROPERTY_KEY = "entries";
519         private static final String STATUS_PROPERTY_KEY = "status";
520         private static final String REASON_PROPERTY_KEY = "reason";
521         private String mStatusUrl;
522         private JSONObject mJsonStatusMap;
523 
524         @Override
init(boolean forward)525         public void init(boolean forward) throws CertPathValidatorException {
526             mStatusUrl = getRevocationListUrl();
527             if (mStatusUrl == null || mStatusUrl.isEmpty()) {
528                 throw new CertPathValidatorException(
529                         "R.string.vendor_required_attestation_revocation_list_url is empty.");
530             }
531             // TODO(b/221067843): Update to only pull status map on non critical path and if
532             // out of date (24hrs).
533             mJsonStatusMap = getStatusMap(mStatusUrl);
534         }
535 
536         @Override
isForwardCheckingSupported()537         public boolean isForwardCheckingSupported() {
538             return false;
539         }
540 
541         @Override
getSupportedExtensions()542         public Set<String> getSupportedExtensions() {
543             return null;
544         }
545 
546         @Override
check(Certificate cert, Collection<String> unresolvedCritExts)547         public void check(Certificate cert, Collection<String> unresolvedCritExts)
548                 throws CertPathValidatorException {
549             X509Certificate x509Certificate = (X509Certificate) cert;
550             // The json key is the certificate's serial number converted to lowercase hex.
551             String serialNumber = x509Certificate.getSerialNumber().toString(16);
552 
553             if (serialNumber == null) {
554                 throw new CertPathValidatorException("Certificate serial number can not be null.");
555             }
556 
557             if (mJsonStatusMap.has(serialNumber)) {
558                 JSONObject revocationStatus;
559                 String status;
560                 String reason;
561                 try {
562                     revocationStatus = mJsonStatusMap.getJSONObject(serialNumber);
563                     status = revocationStatus.getString(STATUS_PROPERTY_KEY);
564                     reason = revocationStatus.getString(REASON_PROPERTY_KEY);
565                 } catch (Throwable t) {
566                     throw new CertPathValidatorException("Unable get properties for certificate "
567                             + "with serial number " + serialNumber);
568                 }
569                 throw new CertPathValidatorException(
570                         "Invalid certificate with serial number " + serialNumber
571                                 + " has status " + status
572                                 + " because reason " + reason);
573             }
574         }
575 
getStatusMap(String stringUrl)576         private JSONObject getStatusMap(String stringUrl) throws CertPathValidatorException {
577             URL url;
578             try {
579                 url = new URL(stringUrl);
580             } catch (Throwable t) {
581                 throw new CertPathValidatorException(
582                         "Unable to get revocation status from " + mStatusUrl, t);
583             }
584 
585             try (InputStream inputStream = url.openStream()) {
586                 JSONObject statusListJson = new JSONObject(
587                         new String(inputStream.readAllBytes(), UTF_8));
588                 return statusListJson.getJSONObject(TOP_LEVEL_JSON_PROPERTY_KEY);
589             } catch (Throwable t) {
590                 throw new CertPathValidatorException(
591                         "Unable to parse revocation status from " + mStatusUrl, t);
592             }
593         }
594 
getRevocationListUrl()595         private String getRevocationListUrl() {
596             return mContext.getResources().getString(
597                     R.string.vendor_required_attestation_revocation_list_url);
598         }
599     }
600 
debugVerboseLog(String str, Throwable t)601     private static void debugVerboseLog(String str, Throwable t) {
602         if (DEBUG) {
603             Slog.v(TAG, str, t);
604         }
605     }
606 
debugVerboseLog(String str)607     private static void debugVerboseLog(String str) {
608         if (DEBUG) {
609             Slog.v(TAG, str);
610         }
611     }
612 }
613