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.PARAM_CHALLENGE
7 import android.security.attestationverification.AttestationVerificationManager.PARAM_PUBLIC_KEY
8 import android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE
9 import android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS
10 import android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE
11 import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY
12 import androidx.test.ext.junit.runners.AndroidJUnit4
13 import androidx.test.filters.SmallTest
14 import androidx.test.platform.app.InstrumentationRegistry
15 import com.google.common.truth.Truth.assertThat
16 import org.junit.Before
17 import org.junit.Test
18 import org.junit.runner.RunWith
19 import org.mockito.Mock
20 import org.mockito.MockitoAnnotations
21 import java.io.ByteArrayOutputStream
22 import java.security.cert.Certificate
23 import java.security.cert.CertificateFactory
24 import java.security.cert.TrustAnchor
25 import java.security.cert.X509Certificate
26 import java.time.LocalDate
27 
28 /** Test for Peer Device attestation verifier. */
29 @SmallTest
30 @RunWith(AndroidJUnit4::class)
31 class AttestationVerificationPeerDeviceVerifierTest {
32     private val certificateFactory = CertificateFactory.getInstance("X.509")
33     @Mock private lateinit var context: Context
34     private lateinit var trustAnchors: HashSet<TrustAnchor>
35 
36     @Before
37     fun setup() {
38         MockitoAnnotations.initMocks(this)
39 
40         val rootCerts = TEST_ROOT_CERT_FILENAME.fromPEMFileToCerts()
41         trustAnchors = HashSet<TrustAnchor>()
42         rootCerts.forEach {
43             trustAnchors.add(TrustAnchor(it as X509Certificate, null))
44         }
45     }
46 
47     @Test
48     fun verifyAttestation_returnsSuccessTypeChallenge() {
49         val verifier = AttestationVerificationPeerDeviceVerifier(
50             context, trustAnchors, false, LocalDate.of(2022, 2, 1),
51             LocalDate.of(2021, 8, 1))
52         val challengeRequirements = Bundle()
53         challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
54 
55         val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
56             TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
57         assertThat(result).isEqualTo(RESULT_SUCCESS)
58     }
59 
60     @Test
61     fun verifyAttestation_returnsSuccessLocalPatchOlderThanOneYear() {
62         val verifier = AttestationVerificationPeerDeviceVerifier(
63             context, trustAnchors, false, LocalDate.of(2022, 2, 1),
64             LocalDate.of(2021, 1, 1))
65         val challengeRequirements = Bundle()
66         challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
67 
68         val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
69             TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
70         assertThat(result).isEqualTo(RESULT_SUCCESS)
71     }
72 
73     @Test
74     fun verifyAttestation_returnsSuccessTypePublicKey() {
75         val verifier = AttestationVerificationPeerDeviceVerifier(
76             context, trustAnchors, false, LocalDate.of(2022, 2, 1),
77             LocalDate.of(2021, 8, 1))
78 
79         val leafCert =
80             (TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToCerts() as List)[0]
81                     as X509Certificate
82         val pkRequirements = Bundle()
83         pkRequirements.putByteArray(PARAM_PUBLIC_KEY, leafCert.publicKey.encoded)
84 
85         val result = verifier.verifyAttestation(
86             TYPE_PUBLIC_KEY, pkRequirements,
87             TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
88         assertThat(result).isEqualTo(RESULT_SUCCESS)
89     }
90 
91     @Test
92     fun verifyAttestation_returnsSuccessOwnedBySystem() {
93         val verifier = AttestationVerificationPeerDeviceVerifier(
94                 context, trustAnchors, false, LocalDate.of(2022, 2, 1),
95                 LocalDate.of(2021, 1, 1))
96         val challengeRequirements = Bundle()
97         challengeRequirements.putByteArray(PARAM_CHALLENGE, "activeUnlockValid".encodeToByteArray())
98         challengeRequirements.putBoolean("android.key_owned_by_system", true)
99 
100         val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
101                 TEST_OWNED_BY_SYSTEM_FILENAME.fromPEMFileToByteArray())
102         assertThat(result).isEqualTo(RESULT_SUCCESS)
103     }
104 
105     @Test
106     fun verifyAttestation_returnsFailureOwnedBySystem() {
107         val verifier = AttestationVerificationPeerDeviceVerifier(
108                 context, trustAnchors, false, LocalDate.of(2022, 2, 1),
109                 LocalDate.of(2021, 1, 1))
110         val challengeRequirements = Bundle()
111         challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
112         challengeRequirements.putBoolean("android.key_owned_by_system", true)
113 
114         val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
115                 TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
116         assertThat(result).isEqualTo(RESULT_FAILURE)
117     }
118 
119     @Test
120     fun verifyAttestation_returnsFailurePatchDateNotWithinOneYearLocalPatch() {
121         val verifier = AttestationVerificationPeerDeviceVerifier(
122             context, trustAnchors, false, LocalDate.of(2023, 3, 1),
123             LocalDate.of(2023, 2, 1))
124         val challengeRequirements = Bundle()
125         challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
126 
127         val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
128             TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
129         assertThat(result).isEqualTo(RESULT_FAILURE)
130     }
131 
132     @Test
133     fun verifyAttestation_returnsFailureTrustedAnchorEmpty() {
134         val verifier = AttestationVerificationPeerDeviceVerifier(
135             context, HashSet(), false, LocalDate.of(2022, 1, 1),
136             LocalDate.of(2022, 1, 1))
137         val challengeRequirements = Bundle()
138         challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
139 
140         val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
141             TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
142         assertThat(result).isEqualTo(RESULT_FAILURE)
143     }
144 
145     @Test
146     fun verifyAttestation_returnsFailureTrustedAnchorMismatch() {
147         val badTrustAnchorsCerts = TEST_ATTESTATION_CERT_FILENAME.fromPEMFileToCerts()
148         val badTrustAnchors = HashSet<TrustAnchor>()
149         badTrustAnchorsCerts.forEach {
150             badTrustAnchors.add(TrustAnchor(it as X509Certificate, null))
151         }
152 
153         val verifier = AttestationVerificationPeerDeviceVerifier(
154             context, badTrustAnchors, false, LocalDate.of(2022, 1, 1),
155             LocalDate.of(2022, 1, 1))
156         val challengeRequirements = Bundle()
157         challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
158 
159         val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
160             TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
161         assertThat(result).isEqualTo(RESULT_FAILURE)
162     }
163 
164     fun verifyAttestation_returnsFailureChallenge() {
165         val verifier = AttestationVerificationPeerDeviceVerifier(
166             context, trustAnchors, false, LocalDate.of(2022, 1, 1),
167             LocalDate.of(2022, 1, 1))
168         val challengeRequirements = Bundle()
169         challengeRequirements.putByteArray(PARAM_CHALLENGE, "wrong".encodeToByteArray())
170 
171         val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
172             TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
173         assertThat(result).isEqualTo(RESULT_FAILURE)
174     }
175 
176     private fun String.fromPEMFileToCerts(): Collection<Certificate> {
177         return certificateFactory.generateCertificates(
178             InstrumentationRegistry.getInstrumentation().getContext().getResources().getAssets()
179                 .open(this))
180     }
181 
182     private fun String.fromPEMFileToByteArray(): ByteArray {
183         val certs = this.fromPEMFileToCerts()
184         val bos = ByteArrayOutputStream()
185         certs.forEach {
186             bos.write(it.encoded)
187         }
188         return bos.toByteArray()
189     }
190 
191     class TestActivity : Activity() {
192         override fun onCreate(savedInstanceState: Bundle?) {
193             super.onCreate(savedInstanceState)
194         }
195     }
196 
197     companion object {
198         private const val TEST_ROOT_CERT_FILENAME = "test_root_certs.pem"
199         private const val TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME =
200             "test_attestation_with_root_certs.pem"
201         private const val TEST_ATTESTATION_CERT_FILENAME = "test_attestation_wrong_root_certs.pem"
202         private const val TEST_OWNED_BY_SYSTEM_FILENAME = "test_owned_by_system_certs.pem"
203     }
204 }
205