1 package android.security.attestationverification 2 3 import android.os.Bundle 4 import android.app.Activity 5 import androidx.test.ext.junit.rules.ActivityScenarioRule 6 import androidx.test.ext.junit.runners.AndroidJUnit4 7 import androidx.test.filters.SmallTest 8 import org.junit.Assert.assertThrows 9 import org.junit.Before 10 import org.junit.Rule 11 import org.junit.Test 12 import org.junit.runner.RunWith 13 import com.google.common.truth.Truth.assertThat 14 import android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE 15 import android.security.attestationverification.AttestationVerificationManager.PROFILE_SELF_TRUSTED 16 import android.security.attestationverification.AttestationVerificationManager.PROFILE_UNKNOWN 17 import android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE 18 import android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS 19 import android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN 20 import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY 21 import android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE 22 import android.security.keystore.KeyGenParameterSpec 23 import android.security.keystore.KeyProperties 24 import java.lang.IllegalArgumentException 25 import java.io.ByteArrayOutputStream 26 import java.security.KeyPairGenerator 27 import java.security.KeyStore 28 import java.time.Duration 29 import java.util.concurrent.CompletableFuture 30 import java.util.concurrent.TimeUnit 31 32 /** Test for system-defined attestation verifiers. */ 33 @SmallTest 34 @RunWith(AndroidJUnit4::class) 35 class SystemAttestationVerificationTest { 36 @get:Rule 37 val rule = ActivityScenarioRule(TestActivity::class.java) 38 39 private lateinit var activity: Activity 40 private lateinit var avm: AttestationVerificationManager 41 private lateinit var androidKeystore: KeyStore 42 43 @Before 44 fun setup() { 45 rule.getScenario().onActivity { 46 avm = it.getSystemService(AttestationVerificationManager::class.java) 47 activity = it 48 androidKeystore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) } 49 } 50 } 51 52 @Test 53 fun verifyAttestation_returnsUnknown() { 54 val future = CompletableFuture<Int>() 55 val profile = AttestationProfile(PROFILE_UNKNOWN) 56 avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0), 57 activity.mainExecutor) { result, _ -> 58 future.complete(result) 59 } 60 61 assertThat(future.getSoon()).isEqualTo(RESULT_UNKNOWN) 62 } 63 64 @Test 65 fun verifyAttestation_returnsFailureWithEmptyAttestation() { 66 val future = CompletableFuture<Int>() 67 val profile = AttestationProfile(PROFILE_SELF_TRUSTED) 68 avm.verifyAttestation(profile, TYPE_CHALLENGE, Bundle(), ByteArray(0), 69 activity.mainExecutor) { result, _ -> 70 future.complete(result) 71 } 72 73 assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) 74 } 75 76 @Test 77 fun verifyAttestation_returnsFailureWithEmptyRequirements() { 78 val future = CompletableFuture<Int>() 79 val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr") 80 avm.verifyAttestation(selfTrusted.profile, selfTrusted.localBindingType, 81 Bundle(), selfTrusted.attestation, activity.mainExecutor) { result, _ -> 82 future.complete(result) 83 } 84 assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) 85 } 86 87 @Test 88 fun verifyAttestation_returnsFailureWithWrongBindingType() { 89 val future = CompletableFuture<Int>() 90 val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr") 91 avm.verifyAttestation(selfTrusted.profile, TYPE_PUBLIC_KEY, 92 selfTrusted.requirements, selfTrusted.attestation, activity.mainExecutor) { result, _ -> 93 future.complete(result) 94 } 95 assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) 96 } 97 98 @Test 99 fun verifyAttestation_returnsFailureWithWrongRequirements() { 100 val future = CompletableFuture<Int>() 101 val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr") 102 val wrongKeyRequirements = Bundle() 103 wrongKeyRequirements.putByteArray( 104 "wrongBindingKey", "challengeStr".encodeToByteArray()) 105 avm.verifyAttestation(selfTrusted.profile, selfTrusted.localBindingType, 106 wrongKeyRequirements, selfTrusted.attestation, activity.mainExecutor) { result, _ -> 107 future.complete(result) 108 } 109 assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) 110 } 111 112 @Test 113 fun verifyAttestation_returnsFailureWithWrongChallenge() { 114 val future = CompletableFuture<Int>() 115 val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr") 116 val wrongChallengeRequirements = Bundle() 117 wrongChallengeRequirements.putByteArray(PARAM_CHALLENGE, "wrong".encodeToByteArray()) 118 avm.verifyAttestation(selfTrusted.profile, selfTrusted.localBindingType, 119 wrongChallengeRequirements, selfTrusted.attestation, activity.mainExecutor) { 120 result, _ -> future.complete(result) 121 } 122 assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) 123 } 124 125 // TODO(b/216144791): Add more failure tests for PROFILE_SELF_TRUSTED. 126 @Test 127 fun verifyAttestation_returnsSuccess() { 128 val future = CompletableFuture<Int>() 129 val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr") 130 avm.verifyAttestation(selfTrusted.profile, selfTrusted.localBindingType, 131 selfTrusted.requirements, selfTrusted.attestation, activity.mainExecutor) { result, _ -> 132 future.complete(result) 133 } 134 assertThat(future.getSoon()).isEqualTo(RESULT_SUCCESS) 135 } 136 137 @Test 138 fun verifyToken_returnsUnknown() { 139 val future = CompletableFuture<Int>() 140 val profile = AttestationProfile(PROFILE_SELF_TRUSTED) 141 avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0), 142 activity.mainExecutor) { _, token -> 143 val result = avm.verifyToken(profile, TYPE_PUBLIC_KEY, Bundle(), token, null) 144 future.complete(result) 145 } 146 147 assertThat(future.getSoon()).isEqualTo(RESULT_UNKNOWN) 148 } 149 150 @Test 151 fun verifyToken_tooBigMaxAgeThrows() { 152 val future = CompletableFuture<VerificationToken>() 153 val profile = AttestationProfile(PROFILE_SELF_TRUSTED) 154 avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0), 155 activity.mainExecutor) { _, token -> 156 future.complete(token) 157 } 158 159 assertThrows(IllegalArgumentException::class.java) { 160 avm.verifyToken(profile, TYPE_PUBLIC_KEY, Bundle(), future.getSoon(), 161 Duration.ofSeconds(3601)) 162 } 163 } 164 165 private fun <T> CompletableFuture<T>.getSoon(): T { 166 return this.get(1, TimeUnit.SECONDS) 167 } 168 169 class TestActivity : Activity() { 170 override fun onCreate(savedInstanceState: Bundle?) { 171 super.onCreate(savedInstanceState) 172 } 173 } 174 175 inner class TestSelfTrustedAttestation(val alias: String, val challenge: String) { 176 val profile = AttestationProfile(PROFILE_SELF_TRUSTED) 177 val localBindingType = TYPE_CHALLENGE 178 val requirements: Bundle 179 val attestation: ByteArray 180 181 init { 182 val challengeByteArray = challenge.encodeToByteArray() 183 generateAndStoreKey(alias, challengeByteArray) 184 attestation = generateCertificatesByteArray(alias) 185 requirements = Bundle() 186 requirements.putByteArray(PARAM_CHALLENGE, challengeByteArray) 187 } 188 189 private fun generateAndStoreKey(alias: String, challenge: ByteArray) { 190 val kpg: KeyPairGenerator = KeyPairGenerator.getInstance( 191 KeyProperties.KEY_ALGORITHM_EC, 192 ANDROID_KEYSTORE 193 ) 194 val parameterSpec: KeyGenParameterSpec = KeyGenParameterSpec.Builder( 195 alias, 196 KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY 197 ).run { 198 // a challenge results in a generated attestation 199 setAttestationChallenge(challenge) 200 setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512) 201 build() 202 } 203 kpg.initialize(parameterSpec) 204 kpg.generateKeyPair() 205 } 206 207 private fun generateCertificatesByteArray(alias: String): ByteArray { 208 val pkEntry = androidKeystore.getEntry(alias, null) as KeyStore.PrivateKeyEntry 209 val certs = pkEntry.certificateChain 210 val bos = ByteArrayOutputStream() 211 certs.forEach { 212 bos.write(it.encoded) 213 } 214 return bos.toByteArray() 215 } 216 } 217 218 companion object { 219 private const val TAG = "AVFTEST" 220 private const val ANDROID_KEYSTORE = "AndroidKeyStore" 221 } 222 } 223