Loading core/java/android/security/attestationverification/AttestationVerificationManager.java +4 −0 Original line number Diff line number Diff line Loading @@ -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; Loading core/java/android/security/attestationverification/OWNERS +3 −0 Original line number Diff line number Diff line Loading @@ -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 services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java +1 −2 Original line number Diff line number Diff line Loading @@ -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 Loading services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java +26 −24 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; } Loading Loading @@ -400,6 +402,7 @@ class AttestationVerificationPeerDeviceVerifier { } private boolean checkAttestationForPeerDeviceProfile( @NonNull Bundle requirements, @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes, MyDumpData dumpData) { boolean result = true; Loading Loading @@ -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 { Loading Loading @@ -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()); Loading @@ -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; } Loading @@ -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; } /** Loading tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt +36 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -161,6 +162,41 @@ class AttestationVerificationPeerDeviceVerifierTest { assertThat(result).isEqualTo(RESULT_FAILURE) } @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( Loading Loading
core/java/android/security/attestationverification/AttestationVerificationManager.java +4 −0 Original line number Diff line number Diff line Loading @@ -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; Loading
core/java/android/security/attestationverification/OWNERS +3 −0 Original line number Diff line number Diff line Loading @@ -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
services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java +1 −2 Original line number Diff line number Diff line Loading @@ -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 Loading
services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java +26 −24 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; } Loading Loading @@ -400,6 +402,7 @@ class AttestationVerificationPeerDeviceVerifier { } private boolean checkAttestationForPeerDeviceProfile( @NonNull Bundle requirements, @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes, MyDumpData dumpData) { boolean result = true; Loading Loading @@ -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 { Loading Loading @@ -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()); Loading @@ -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; } Loading @@ -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; } /** Loading
tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt +36 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -161,6 +162,41 @@ class AttestationVerificationPeerDeviceVerifierTest { assertThat(result).isEqualTo(RESULT_FAILURE) } @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( Loading