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