diff options
5 files changed, 70 insertions, 26 deletions
diff --git a/core/java/android/security/attestationverification/AttestationVerificationManager.java b/core/java/android/security/attestationverification/AttestationVerificationManager.java index 2e61db1b932a..acf33822b3c7 100644 --- a/core/java/android/security/attestationverification/AttestationVerificationManager.java +++ b/core/java/android/security/attestationverification/AttestationVerificationManager.java @@ -322,6 +322,10 @@ public class AttestationVerificationManager { /** Requirements bundle parameter for a challenge. */ public static final String PARAM_CHALLENGE = "localbinding.challenge"; + /** Requirements bundle parameter for max patch level diff (int) for a peer device. **/ + public static final String PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS = + "param_max_patch_level_diff_months"; + /** @hide */ public static String localBindingTypeToString(@LocalBindingType int localBindingType) { final String text; diff --git a/core/java/android/security/attestationverification/OWNERS b/core/java/android/security/attestationverification/OWNERS index 80a1f44b8427..15b9ce4c7696 100644 --- a/core/java/android/security/attestationverification/OWNERS +++ b/core/java/android/security/attestationverification/OWNERS @@ -2,3 +2,6 @@ dlm@google.com dkrahn@google.com +guojing@google.com +raphk@google.com +yukl@google.com
\ No newline at end of file diff --git a/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java b/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java index 1559a3f8fdf8..df3071e08a03 100644 --- a/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java +++ b/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java @@ -53,9 +53,8 @@ public class AttestationVerifier { * * @param remoteAttestation the full certificate chain containing attestation extension. * @param attestationChallenge attestation challenge for authentication. - * @return true if attestation is successfully verified; false otherwise. + * @return 1 if attestation is successfully verified; 0 otherwise. */ - @NonNull public int verifyAttestation( @NonNull byte[] remoteAttestation, @NonNull byte[] attestationChallenge diff --git a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java index 945a3400d971..41e3d00f3924 100644 --- a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java +++ b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java @@ -17,6 +17,7 @@ package com.android.server.security; import static android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE; +import static android.security.attestationverification.AttestationVerificationManager.PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS; import static android.security.attestationverification.AttestationVerificationManager.PARAM_PUBLIC_KEY; import static android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE; import static android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS; @@ -174,8 +175,8 @@ class AttestationVerificationPeerDeviceVerifier { MyDumpData dumpData = new MyDumpData(); - int result = - verifyAttestationInternal(localBindingType, requirements, attestation, dumpData); + int result = verifyAttestationInternal(localBindingType, requirements, attestation, + dumpData); dumpData.mResult = result; mDumpLogger.logAttempt(dumpData); return result; @@ -222,7 +223,8 @@ class AttestationVerificationPeerDeviceVerifier { final var attestationExtension = fromCertificate(leafCertificate); // Second: verify if the attestation satisfies the "peer device" profile. - if (!checkAttestationForPeerDeviceProfile(attestationExtension, dumpData)) { + if (!checkAttestationForPeerDeviceProfile(requirements, attestationExtension, + dumpData)) { failed = true; } @@ -400,6 +402,7 @@ class AttestationVerificationPeerDeviceVerifier { } private boolean checkAttestationForPeerDeviceProfile( + @NonNull Bundle requirements, @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes, MyDumpData dumpData) { boolean result = true; @@ -461,30 +464,37 @@ class AttestationVerificationPeerDeviceVerifier { result = false; } - // Patch level integer YYYYMM is expected to be within 1 year of today. - if (!isValidPatchLevel(attestationAttributes.getKeyOsPatchLevel())) { + int maxPatchLevelDiffMonths = requirements.getInt(PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS, + MAX_PATCH_AGE_MONTHS); + + // Patch level integer YYYYMM is expected to be within maxPatchLevelDiffMonths of today. + if (!isValidPatchLevel(attestationAttributes.getKeyOsPatchLevel(), + maxPatchLevelDiffMonths)) { debugVerboseLog("OS patch level is not within valid range."); result = false; } else { dumpData.mOsPatchLevelInRange = true; } - // Patch level integer YYYYMMDD is expected to be within 1 year of today. - if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel())) { + // Patch level integer YYYYMMDD is expected to be within maxPatchLevelDiffMonths of today. + if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel(), + maxPatchLevelDiffMonths)) { debugVerboseLog("Boot patch level is not within valid range."); result = false; } else { dumpData.mKeyBootPatchLevelInRange = true; } - if (!isValidPatchLevel(attestationAttributes.getKeyVendorPatchLevel())) { + if (!isValidPatchLevel(attestationAttributes.getKeyVendorPatchLevel(), + maxPatchLevelDiffMonths)) { debugVerboseLog("Vendor patch level is not within valid range."); result = false; } else { dumpData.mKeyVendorPatchLevelInRange = true; } - if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel())) { + if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel(), + maxPatchLevelDiffMonths)) { debugVerboseLog("Boot patch level is not within valid range."); result = false; } else { @@ -525,7 +535,7 @@ class AttestationVerificationPeerDeviceVerifier { * is not enough. Therefore, we also confirm the patch level for the remote and local device are * similar. */ - private boolean isValidPatchLevel(int patchLevel) { + private boolean isValidPatchLevel(int patchLevel, int maxPatchLevelDiffMonths) { LocalDate currentDate = mTestSystemDate != null ? mTestSystemDate : LocalDate.now(ZoneId.systemDefault()); @@ -543,7 +553,9 @@ class AttestationVerificationPeerDeviceVerifier { return false; } - // Check local patch date is not in last year of system clock. + // Check local patch date is not in last year of system clock. If the local patch already + // has a year's worth of bugs and vulnerabilities, it has no security meanings to check the + // remote patch level. if (ChronoUnit.MONTHS.between(localPatchDate, currentDate) > MAX_PATCH_AGE_MONTHS) { return true; } @@ -559,19 +571,9 @@ class AttestationVerificationPeerDeviceVerifier { int patchMonth = Integer.parseInt(remoteDeviceDateStr.substring(4, 6)); LocalDate remotePatchDate = LocalDate.of(patchYear, patchMonth, 1); - // Check patch dates are within 1 year of each other - boolean IsRemotePatchWithinOneYearOfLocalPatch; - if (remotePatchDate.compareTo(localPatchDate) > 0) { - IsRemotePatchWithinOneYearOfLocalPatch = ChronoUnit.MONTHS.between( - localPatchDate, remotePatchDate) <= MAX_PATCH_AGE_MONTHS; - } else if (remotePatchDate.compareTo(localPatchDate) < 0) { - IsRemotePatchWithinOneYearOfLocalPatch = ChronoUnit.MONTHS.between( - remotePatchDate, localPatchDate) <= MAX_PATCH_AGE_MONTHS; - } else { - IsRemotePatchWithinOneYearOfLocalPatch = true; - } - - return IsRemotePatchWithinOneYearOfLocalPatch; + // Check patch dates are within the max patch level diff of each other + return Math.abs(ChronoUnit.MONTHS.between(localPatchDate, remotePatchDate)) + <= maxPatchLevelDiffMonths; } /** diff --git a/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt index afb3593e3e98..4712d6b51bd5 100644 --- a/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt +++ b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.content.Context import android.os.Bundle import android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE +import android.security.attestationverification.AttestationVerificationManager.PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS import android.security.attestationverification.AttestationVerificationManager.PARAM_PUBLIC_KEY import android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE import android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS @@ -162,6 +163,41 @@ class AttestationVerificationPeerDeviceVerifierTest { } @Test + fun verifyAttestation_returnsSuccessPatchDataWithinMaxPatchDiff() { + val verifier = AttestationVerificationPeerDeviceVerifier( + context, dumpLogger, trustAnchors, false, LocalDate.of(2023, 3, 1), + LocalDate.of(2023, 2, 1) + ) + val challengeRequirements = Bundle() + challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) + challengeRequirements.putInt(PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS, 24) + + val result = verifier.verifyAttestation( + TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() + ) + assertThat(result).isEqualTo(RESULT_SUCCESS) + } + + @Test + fun verifyAttestation_returnsFailurePatchDataNotWithinMaxPatchDiff() { + val verifier = AttestationVerificationPeerDeviceVerifier( + context, dumpLogger, trustAnchors, false, LocalDate.of(2024, 10, 1), + LocalDate.of(2024, 9, 1) + ) + val challengeRequirements = Bundle() + challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) + challengeRequirements.putInt(PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS, 24) + + val result = verifier.verifyAttestation( + TYPE_CHALLENGE, challengeRequirements, + // The patch date of this file is early 2022 + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() + ) + assertThat(result).isEqualTo(RESULT_FAILURE) + } + + @Test fun verifyAttestation_returnsFailureTrustedAnchorEmpty() { val verifier = AttestationVerificationPeerDeviceVerifier( context, dumpLogger, HashSet(), false, LocalDate.of(2022, 1, 1), |