Skip to content

Commit a1105af

Browse files
suzannajiwanicopybara-github
authored andcommitted
No public description
PiperOrigin-RevId: 791371467
1 parent aca13e5 commit a1105af

6 files changed

Lines changed: 142 additions & 15 deletions

File tree

README.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ val verifier = Verifier(
1212
{ Instant.now() } // Time source
1313
)
1414

15-
// Verify an attestation certificate chain with challenge
16-
val result = verifier.verify(certificateChain, challenge)
15+
// Verify an attestation certificate chain
16+
val result = verifier.verify(certificateChain)
1717

1818
// Handle the verification result
1919
when (result) {
@@ -32,6 +32,22 @@ when (result) {
3232
}
3333
```
3434

35+
If there is additional verification you'd like to perform on the challenge
36+
associated with the attestation certificate chain, pass in a `ChallengeChecker`
37+
when verifying. For example, if you expect the challenge to be equal to
38+
"challenge123", then usage would look like
39+
40+
```kotlin
41+
// Create a ChallengeChecker
42+
val challengeChecker = ChallengeMatcher("challenge123")
43+
44+
// Verify an attestation certificate chain with the checker
45+
val result = verifier.verify(certificateChain, challengeChecker)
46+
```
47+
48+
If the implementations in challengecheckers/ don't fit your needs, simply extend
49+
the `ChallengeChecker` interface.
50+
3551
## Building
3652

3753
```bash
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.android.keyattestation.verifier
2+
3+
import com.google.protobuf.ByteString
4+
5+
/** An interface to handle checking validity of challenges. */
6+
interface ChallengeChecker {
7+
/**
8+
* Checks the given challenge for validity.
9+
*
10+
* @param challenge The challenge being check.
11+
* @return True if the challenge is valid, else false.
12+
*/
13+
fun checkChallenge(challenge: ByteString): Boolean
14+
}

src/main/kotlin/Verifier.kt

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import com.android.keyattestation.verifier.provider.ProvisioningMethod
2222
import com.android.keyattestation.verifier.provider.RevocationChecker
2323
import com.google.errorprone.annotations.ThreadSafe
2424
import com.google.protobuf.ByteString
25-
import java.nio.ByteBuffer
2625
import java.security.PublicKey
2726
import java.security.Security
2827
import java.security.cert.CertPathValidator
@@ -73,26 +72,42 @@ open class Verifier(
7372
Security.addProvider(KeyAttestationProvider())
7473
}
7574

76-
fun verify(chain: List<X509Certificate>, challenge: ByteArray? = null): VerificationResult {
75+
/**
76+
* Verifies an Android Key Attestation certificate chain.
77+
*
78+
* @param chain The attestation certificate chain to verify.
79+
* @param challengeChecker The challenge checker to use for additional challenge validation.
80+
* @return [VerificationResult]
81+
*/
82+
@JvmOverloads
83+
fun verify(
84+
chain: List<X509Certificate>,
85+
challengeChecker: ChallengeChecker? = null,
86+
): VerificationResult {
7787
val certPath =
7888
try {
7989
KeyAttestationCertPath(chain)
8090
} catch (e: Exception) {
8191
return VerificationResult.ChainParsingFailure
8292
}
83-
return verify(certPath, challenge)
93+
return verify(certPath, challengeChecker)
8494
}
8595

8696
/**
8797
* Verifies an Android Key Attestation certificate chain.
8898
*
8999
* @param chain The attestation certificate chain to verify.
100+
* @param challengeChecker The challenge checker to use for additional validation of the challenge
101+
* in the attestation chain.
90102
* @return [VerificationResult]
91103
*
92104
* TODO: b/366058500 - Make the challenge required after Apparat's changes are rollback safe.
93105
*/
94106
@JvmOverloads
95-
fun verify(certPath: KeyAttestationCertPath, challenge: ByteArray? = null): VerificationResult {
107+
fun verify(
108+
certPath: KeyAttestationCertPath,
109+
challengeChecker: ChallengeChecker? = null,
110+
): VerificationResult {
96111
val certPathValidator = CertPathValidator.getInstance("KeyAttestation")
97112
val certPathParameters =
98113
PKIXParameters(trustAnchorsSource()).apply {
@@ -114,8 +129,8 @@ open class Verifier(
114129
}
115130

116131
if (
117-
challenge != null &&
118-
keyDescription.attestationChallenge.asReadOnlyByteBuffer() != ByteBuffer.wrap(challenge)
132+
challengeChecker != null &&
133+
!challengeChecker.checkChallenge(keyDescription.attestationChallenge)
119134
) {
120135
return VerificationResult.ChallengeMismatch
121136
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.android.keyattestation.verifier.challengecheckers
2+
3+
import com.android.keyattestation.verifier.ChallengeChecker
4+
import com.google.protobuf.ByteString
5+
6+
/**
7+
* A basic implementation of [ChallengeChecker] that checks if the challenge in the attestation
8+
* certificate is equal to the expected challenge.
9+
*/
10+
class ChallengeMatcher(private val expectedChallenge: ByteString) : ChallengeChecker {
11+
12+
constructor(expectedChallenge: ByteArray) : this(ByteString.copyFrom(expectedChallenge))
13+
14+
override fun checkChallenge(challenge: ByteString): Boolean = challenge.equals(expectedChallenge)
15+
}

src/test/kotlin/VerifierTest.kt

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.android.keyattestation.verifier
1818

19+
import com.android.keyattestation.verifier.challengecheckers.ChallengeMatcher
1920
import com.android.keyattestation.verifier.testing.CertLists
2021
import com.android.keyattestation.verifier.testing.TestUtils.prodAnchors
2122
import com.android.keyattestation.verifier.testing.TestUtils.readCertPath
@@ -35,8 +36,7 @@ class VerifierTest {
3536
@Test
3637
fun verify_validChain_returnsSuccess() {
3738
val chain = readCertPath("blueline/sdk28/TEE_EC_NONE.pem")
38-
val result =
39-
assertIs<VerificationResult.Success>(verifier.verify(chain, "challenge".toByteArray()))
39+
val result = assertIs<VerificationResult.Success>(verifier.verify(chain))
4040
assertThat(result.publicKey).isEqualTo(chain.leafCert().publicKey)
4141
assertThat(result.challenge).isEqualTo(ByteString.copyFromUtf8("challenge"))
4242
assertThat(result.securityLevel).isEqualTo(SecurityLevel.TRUSTED_ENVIRONMENT)
@@ -46,8 +46,7 @@ class VerifierTest {
4646
@Test
4747
fun verify_validChain_returnsDeviceIdentity() {
4848
val chain = readCertPath("blueline/sdk28/TEE_RSA_BASE+IMEI.pem")
49-
val result =
50-
assertIs<VerificationResult.Success>(verifier.verify(chain, "challenge".toByteArray()))
49+
val result = assertIs<VerificationResult.Success>(verifier.verify(chain))
5150
assertThat(result.attestedDeviceIds)
5251
.isEqualTo(
5352
DeviceIdentity(
@@ -64,15 +63,34 @@ class VerifierTest {
6463
}
6564

6665
@Test
67-
fun verify_unexpectedChallenge_returnsChallengeMismatch() {
66+
fun verify_challengeCheckerReturnsTrue_returnsSuccess() {
67+
val challengeChecker: ChallengeChecker =
68+
object : ChallengeChecker {
69+
override fun checkChallenge(challenge: ByteString) = true
70+
}
71+
6872
val chain = readCertPath("blueline/sdk28/TEE_EC_NONE.pem")
69-
assertIs<VerificationResult.ChallengeMismatch>(verifier.verify(chain, "foo".toByteArray()))
73+
assertIs<VerificationResult.Success>(verifier.verify(chain, challengeChecker))
74+
}
75+
76+
@Test
77+
fun verify_challengeCheckerReturnsFalse_returnsChallengeMismatch() {
78+
val challengeChecker: ChallengeChecker =
79+
object : ChallengeChecker {
80+
override fun checkChallenge(challenge: ByteString) = false
81+
}
82+
83+
val chain = readCertPath("blueline/sdk28/TEE_EC_NONE.pem")
84+
assertIs<VerificationResult.ChallengeMismatch>(verifier.verify(chain, challengeChecker))
7085
}
7186

7287
@Test
7388
fun verify_unexpectedRootKey_returnsPathValidationFailure() {
7489
assertIs<VerificationResult.PathValidationFailure>(
75-
verifier.verify(CertLists.wrongTrustAnchor, "challenge".toByteArray())
90+
verifier.verify(
91+
CertLists.wrongTrustAnchor,
92+
ChallengeMatcher(ByteString.copyFromUtf8("challenge")),
93+
)
7694
)
7795
}
7896
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.android.keyattestation.verifier.challengecheckers
2+
3+
import com.android.keyattestation.verifier.VerificationResult
4+
import com.android.keyattestation.verifier.Verifier
5+
import com.android.keyattestation.verifier.testing.TestUtils.prodAnchors
6+
import com.android.keyattestation.verifier.testing.TestUtils.readCertPath
7+
import com.google.common.truth.Truth.assertThat
8+
import com.google.protobuf.ByteString
9+
import java.time.Instant
10+
import kotlin.test.assertIs
11+
import org.junit.Test
12+
import org.junit.runner.RunWith
13+
import org.junit.runners.JUnit4
14+
15+
@RunWith(JUnit4::class)
16+
class ChallengeMatcherTest {
17+
18+
companion object {
19+
private val testChallenge = ByteString.copyFromUtf8("challenge")
20+
}
21+
22+
@Test
23+
fun checkChallenge_matchingChallenge_returnsTrue() {
24+
val challengeChecker = ChallengeMatcher(testChallenge)
25+
assertThat(challengeChecker.checkChallenge(testChallenge)).isTrue()
26+
}
27+
28+
@Test
29+
fun checkChallenge_mismatchedChallenge_returnsFalse() {
30+
val challengeChecker = ChallengeMatcher(testChallenge)
31+
assertThat(challengeChecker.checkChallenge(ByteString.copyFromUtf8("foo"))).isFalse()
32+
}
33+
34+
@Test
35+
fun verify_expectedChallenge_returnsSuccess() {
36+
val verifier = Verifier({ prodAnchors }, { setOf<String>() }, { Instant.now() })
37+
val chain = readCertPath("blueline/sdk28/TEE_EC_NONE.pem")
38+
assertIs<VerificationResult.Success>(verifier.verify(chain, ChallengeMatcher(testChallenge)))
39+
}
40+
41+
@Test
42+
fun verify_unexpectedChallenge_returnsChallengeMismatch() {
43+
val verifier = Verifier({ prodAnchors }, { setOf<String>() }, { Instant.now() })
44+
val chain = readCertPath("blueline/sdk28/TEE_EC_NONE.pem")
45+
assertIs<VerificationResult.ChallengeMismatch>(
46+
verifier.verify(chain, ChallengeMatcher(ByteString.copyFromUtf8("foo")))
47+
)
48+
}
49+
}

0 commit comments

Comments
 (0)