1 package com.android.server.security 2 3 import android.app.Activity 4 import android.content.Context 5 import android.os.Bundle 6 import android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_CERTS 7 import android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS 8 import android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_PATCH_LEVEL_DIFF 9 import android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE 10 import android.security.attestationverification.AttestationVerificationManager.PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS 11 import android.security.attestationverification.AttestationVerificationManager.PARAM_PUBLIC_KEY 12 import android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE 13 import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY 14 import android.util.IndentingPrintWriter 15 import android.util.Log 16 import androidx.test.ext.junit.runners.AndroidJUnit4 17 import androidx.test.filters.SmallTest 18 import androidx.test.platform.app.InstrumentationRegistry 19 import com.android.server.security.AttestationVerificationManagerService.DumpLogger 20 import com.google.common.truth.Truth.assertThat 21 import java.io.ByteArrayOutputStream 22 import java.io.PrintWriter 23 import java.io.StringWriter 24 import java.security.cert.Certificate 25 import java.security.cert.CertificateFactory 26 import java.security.cert.TrustAnchor 27 import java.security.cert.X509Certificate 28 import java.time.LocalDate 29 import org.junit.After 30 import org.junit.Before 31 import org.junit.Test 32 import org.junit.runner.RunWith 33 import org.mockito.Mock 34 import org.mockito.MockitoAnnotations 35 36 37 /** Test for Peer Device attestation verifier. */ 38 @SmallTest 39 @RunWith(AndroidJUnit4::class) 40 class AttestationVerificationPeerDeviceVerifierTest { 41 private val certificateFactory = CertificateFactory.getInstance("X.509") 42 @Mock private lateinit var context: Context 43 private val dumpLogger = DumpLogger() 44 private lateinit var trustAnchors: HashSet<TrustAnchor> 45 46 @Before setupnull47 fun setup() { 48 MockitoAnnotations.initMocks(this) 49 50 val rootCerts = TEST_ROOT_CERT_FILENAME.fromPEMFileToCerts() 51 trustAnchors = HashSet<TrustAnchor>() 52 rootCerts.forEach { 53 trustAnchors.add(TrustAnchor(it as X509Certificate, null)) 54 } 55 } 56 57 @After dumpAndLognull58 fun dumpAndLog() { 59 val dump = dumpLogger.getDump() 60 Log.d(TAG, "$dump") 61 } 62 63 @Test verifyAttestation_returnsSuccessTypeChallengenull64 fun verifyAttestation_returnsSuccessTypeChallenge() { 65 val verifier = AttestationVerificationPeerDeviceVerifier( 66 context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1), 67 LocalDate.of(2021, 8, 1) 68 ) 69 val challengeRequirements = Bundle() 70 challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) 71 72 val result = verifier.verifyAttestation( 73 TYPE_CHALLENGE, challengeRequirements, 74 TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() 75 ) 76 assertThat(result).isEqualTo(0) 77 } 78 79 @Test verifyAttestation_returnsSuccessLocalPatchOlderThanOneYearnull80 fun verifyAttestation_returnsSuccessLocalPatchOlderThanOneYear() { 81 val verifier = AttestationVerificationPeerDeviceVerifier( 82 context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1), 83 LocalDate.of(2021, 1, 1) 84 ) 85 val challengeRequirements = Bundle() 86 challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) 87 88 val result = verifier.verifyAttestation( 89 TYPE_CHALLENGE, challengeRequirements, 90 TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() 91 ) 92 assertThat(result).isEqualTo(0) 93 } 94 95 @Test verifyAttestation_returnsSuccessTypePublicKeynull96 fun verifyAttestation_returnsSuccessTypePublicKey() { 97 val verifier = AttestationVerificationPeerDeviceVerifier( 98 context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1), 99 LocalDate.of(2021, 8, 1) 100 ) 101 102 val leafCert = 103 (TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToCerts() as List)[0] 104 as X509Certificate 105 val pkRequirements = Bundle() 106 pkRequirements.putByteArray(PARAM_PUBLIC_KEY, leafCert.publicKey.encoded) 107 108 val result = verifier.verifyAttestation( 109 TYPE_PUBLIC_KEY, pkRequirements, 110 TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() 111 ) 112 assertThat(result).isEqualTo(0) 113 } 114 115 @Test verifyAttestation_returnsSuccessOwnedBySystemnull116 fun verifyAttestation_returnsSuccessOwnedBySystem() { 117 val verifier = AttestationVerificationPeerDeviceVerifier( 118 context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1), 119 LocalDate.of(2021, 1, 1) 120 ) 121 val challengeRequirements = Bundle() 122 challengeRequirements.putByteArray(PARAM_CHALLENGE, "activeUnlockValid".encodeToByteArray()) 123 challengeRequirements.putBoolean("android.key_owned_by_system", true) 124 125 val result = verifier.verifyAttestation( 126 TYPE_CHALLENGE, challengeRequirements, 127 TEST_OWNED_BY_SYSTEM_FILENAME.fromPEMFileToByteArray() 128 ) 129 130 assertThat(result).isEqualTo(0) 131 } 132 133 @Test verifyAttestation_returnsFailureOwnedBySystemnull134 fun verifyAttestation_returnsFailureOwnedBySystem() { 135 val verifier = AttestationVerificationPeerDeviceVerifier( 136 context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1), 137 LocalDate.of(2021, 1, 1) 138 ) 139 val challengeRequirements = Bundle() 140 challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) 141 challengeRequirements.putBoolean("android.key_owned_by_system", true) 142 143 val result = verifier.verifyAttestation( 144 TYPE_CHALLENGE, challengeRequirements, 145 TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() 146 ) 147 assertThat(result).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS) 148 } 149 150 @Test verifyAttestation_returnsFailurePatchDateNotWithinOneYearLocalPatchnull151 fun verifyAttestation_returnsFailurePatchDateNotWithinOneYearLocalPatch() { 152 val verifier = AttestationVerificationPeerDeviceVerifier( 153 context, dumpLogger, trustAnchors, false, LocalDate.of(2023, 3, 1), 154 LocalDate.of(2023, 2, 1) 155 ) 156 val challengeRequirements = Bundle() 157 challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) 158 159 val result = verifier.verifyAttestation( 160 TYPE_CHALLENGE, challengeRequirements, 161 TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() 162 ) 163 assertThat(result).isEqualTo(FLAG_FAILURE_PATCH_LEVEL_DIFF) 164 } 165 166 @Test verifyAttestation_returnsSuccessPatchDataWithinMaxPatchDiffnull167 fun verifyAttestation_returnsSuccessPatchDataWithinMaxPatchDiff() { 168 val verifier = AttestationVerificationPeerDeviceVerifier( 169 context, dumpLogger, trustAnchors, false, LocalDate.of(2023, 3, 1), 170 LocalDate.of(2023, 2, 1) 171 ) 172 val challengeRequirements = Bundle() 173 challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) 174 challengeRequirements.putInt(PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS, 24) 175 176 val result = verifier.verifyAttestation( 177 TYPE_CHALLENGE, challengeRequirements, 178 TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() 179 ) 180 assertThat(result).isEqualTo(0) 181 } 182 183 @Test verifyAttestation_returnsFailurePatchDataNotWithinMaxPatchDiffnull184 fun verifyAttestation_returnsFailurePatchDataNotWithinMaxPatchDiff() { 185 val verifier = AttestationVerificationPeerDeviceVerifier( 186 context, dumpLogger, trustAnchors, false, LocalDate.of(2024, 10, 1), 187 LocalDate.of(2024, 9, 1) 188 ) 189 val challengeRequirements = Bundle() 190 challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) 191 challengeRequirements.putInt(PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS, 24) 192 193 val result = verifier.verifyAttestation( 194 TYPE_CHALLENGE, challengeRequirements, 195 TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() 196 ) 197 assertThat(result).isEqualTo(FLAG_FAILURE_PATCH_LEVEL_DIFF) 198 } 199 200 @Test verifyAttestation_returnsFailureOwnedBySystemAndPatchDataNotWithinMaxPatchDiffnull201 fun verifyAttestation_returnsFailureOwnedBySystemAndPatchDataNotWithinMaxPatchDiff() { 202 val verifier = AttestationVerificationPeerDeviceVerifier( 203 context, dumpLogger, trustAnchors, false, LocalDate.of(2024, 10, 1), 204 LocalDate.of(2024, 9, 1) 205 ) 206 val challengeRequirements = Bundle() 207 challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) 208 challengeRequirements.putBoolean("android.key_owned_by_system", true) 209 challengeRequirements.putInt(PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS, 24) 210 211 val result = verifier.verifyAttestation( 212 TYPE_CHALLENGE, challengeRequirements, 213 TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() 214 ) 215 // Both "owned by system" and "patch level diff" checks should fail. 216 assertThat(result).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS or FLAG_FAILURE_PATCH_LEVEL_DIFF) 217 } 218 219 @Test verifyAttestation_returnsFailureTrustedAnchorEmptynull220 fun verifyAttestation_returnsFailureTrustedAnchorEmpty() { 221 val verifier = AttestationVerificationPeerDeviceVerifier( 222 context, dumpLogger, HashSet(), false, LocalDate.of(2022, 1, 1), 223 LocalDate.of(2022, 1, 1) 224 ) 225 val challengeRequirements = Bundle() 226 challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) 227 228 val result = verifier.verifyAttestation( 229 TYPE_CHALLENGE, challengeRequirements, 230 TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() 231 ) 232 assertThat(result).isEqualTo(FLAG_FAILURE_CERTS) 233 } 234 235 @Test verifyAttestation_returnsFailureTrustedAnchorMismatchnull236 fun verifyAttestation_returnsFailureTrustedAnchorMismatch() { 237 val badTrustAnchorsCerts = TEST_ATTESTATION_CERT_FILENAME.fromPEMFileToCerts() 238 val badTrustAnchors = HashSet<TrustAnchor>() 239 badTrustAnchorsCerts.forEach { 240 badTrustAnchors.add(TrustAnchor(it as X509Certificate, null)) 241 } 242 243 val verifier = AttestationVerificationPeerDeviceVerifier( 244 context, dumpLogger, badTrustAnchors, false, LocalDate.of(2022, 1, 1), 245 LocalDate.of(2022, 1, 1) 246 ) 247 val challengeRequirements = Bundle() 248 challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) 249 250 val result = verifier.verifyAttestation( 251 TYPE_CHALLENGE, challengeRequirements, 252 TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() 253 ) 254 assertThat(result).isEqualTo(FLAG_FAILURE_CERTS) 255 } 256 verifyAttestation_returnsFailureChallengenull257 fun verifyAttestation_returnsFailureChallenge() { 258 val verifier = AttestationVerificationPeerDeviceVerifier( 259 context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 1, 1), 260 LocalDate.of(2022, 1, 1) 261 ) 262 val challengeRequirements = Bundle() 263 challengeRequirements.putByteArray(PARAM_CHALLENGE, "wrong".encodeToByteArray()) 264 265 val result = verifier.verifyAttestation( 266 TYPE_CHALLENGE, challengeRequirements, 267 TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() 268 ) 269 assertThat(result).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS) 270 } 271 Stringnull272 private fun String.fromPEMFileToCerts(): Collection<Certificate> { 273 return certificateFactory.generateCertificates( 274 InstrumentationRegistry.getInstrumentation().getContext().getResources().getAssets() 275 .open(this) 276 ) 277 } 278 fromPEMFileToByteArraynull279 private fun String.fromPEMFileToByteArray(): ByteArray { 280 val certs = this.fromPEMFileToCerts() 281 val bos = ByteArrayOutputStream() 282 certs.forEach { 283 bos.write(it.encoded) 284 } 285 return bos.toByteArray() 286 } 287 getDumpnull288 private fun DumpLogger.getDump(): String { 289 val sw = StringWriter() 290 this.dumpTo(IndentingPrintWriter(PrintWriter(sw), " ")) 291 return sw.toString() 292 } 293 294 class TestActivity : Activity() { onCreatenull295 override fun onCreate(savedInstanceState: Bundle?) { 296 super.onCreate(savedInstanceState) 297 } 298 } 299 300 companion object { 301 private const val TAG = "AVFTest" 302 private const val TEST_ROOT_CERT_FILENAME = "test_root_certs.pem" 303 // Local patch date is 20220105 304 private const val TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME = 305 "test_attestation_with_root_certs.pem" 306 private const val TEST_ATTESTATION_CERT_FILENAME = "test_attestation_wrong_root_certs.pem" 307 private const val TEST_OWNED_BY_SYSTEM_FILENAME = "test_owned_by_system_certs.pem" 308 } 309 } 310