[AVF] Make patch level diff flexible by a requirement
Bug: 359629878
Ignore-AOSP-First: This changes the security model for glasses and
hasn't been tested e2e yet.
Test: Unit Tests
Flag: EXEMPT bugfix
Change-Id: Iabcf12d68ee7c7abe7d53f27236260dd96b9dc4c
diff --git a/core/java/android/security/attestationverification/AttestationVerificationManager.java b/core/java/android/security/attestationverification/AttestationVerificationManager.java
index 2e61db1..acf3382 100644
--- a/core/java/android/security/attestationverification/AttestationVerificationManager.java
+++ b/core/java/android/security/attestationverification/AttestationVerificationManager.java
@@ -322,6 +322,10 @@
/** 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 80a1f44..15b9ce4 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 1559a3f..df3071e 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 @@
*
* @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 945a340..41e3d00 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 @@
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 @@
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 @@
}
private boolean checkAttestationForPeerDeviceProfile(
+ @NonNull Bundle requirements,
@NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes,
MyDumpData dumpData) {
boolean result = true;
@@ -461,30 +464,37 @@
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 @@
* 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 @@
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 @@
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 afb3593..4712d6b 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.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 @@
}
@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),