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