Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/main/kotlin/testing/TestUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ import java.security.cert.TrustAnchor
import java.security.cert.X509Certificate
import java.util.Base64
import kotlin.io.path.Path
import kotlin.io.path.isDirectory
import kotlin.io.path.listDirectoryEntries
import kotlin.io.path.name
import kotlin.io.path.reader
import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
Expand Down Expand Up @@ -98,6 +101,31 @@ object TestUtils {

private fun readFile(path: Path) = path.reader()
private fun readFile(path: String) = readFile(Path(path))

data class TestCaseInfo(val model: String, val sdk: Int)

fun getTestCases(): List<TestCaseInfo> {
val root = Path(TESTDATA_PATH)
val testCases =
root
.listDirectoryEntries()
.filter { it.isDirectory() }
.flatMap { modelDir ->
modelDir
.listDirectoryEntries("sdk*")
.filter { it.isDirectory() }
.mapNotNull { sdkDir ->
val sdkVersion = sdkDir.name.removePrefix("sdk").toIntOrNull()
if (sdkVersion == null) {
null
} else {
TestCaseInfo(modelDir.name, sdkVersion)
}
}
}
check(testCases.isNotEmpty()) { "No test cases found in $root" }
return testCases
}
}

object Base64ByteStringAdapter : JsonDeserializer<ByteString>, JsonSerializer<ByteString> {
Expand Down
38 changes: 10 additions & 28 deletions src/test/kotlin/ExtensionTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.android.keyattestation.verifier
import com.android.keyattestation.verifier.testing.Chains
import com.android.keyattestation.verifier.testing.FakeLogHook
import com.android.keyattestation.verifier.testing.TestUtils.TESTDATA_PATH
import com.android.keyattestation.verifier.testing.TestUtils.getTestCases
import com.android.keyattestation.verifier.testing.TestUtils.readCertPath
import com.android.keyattestation.verifier.testing.V3Extensions
import com.android.keyattestation.verifier.testing.toKeyDescription
Expand All @@ -29,12 +30,11 @@ import com.google.testing.junit.testparameterinjector.TestParameterInjector
import com.google.testing.junit.testparameterinjector.TestParameters
import com.google.testing.junit.testparameterinjector.TestParameters.TestParametersValues
import com.google.testing.junit.testparameterinjector.TestParametersValuesProvider
import com.google.testing.junit.testparameterinjector.TestParametersValuesProvider.Context
import java.time.YearMonth
import kotlin.io.path.Path
import kotlin.io.path.inputStream
import kotlin.io.path.isDirectory
import kotlin.io.path.listDirectoryEntries
import kotlin.io.path.name
import kotlin.io.path.nameWithoutExtension
import kotlin.io.path.readText
import kotlin.io.path.reader
Expand Down Expand Up @@ -72,32 +72,14 @@ class ExtensionTest {
}

class TestCaseProvider : TestParametersValuesProvider() {
override fun provideValues(context: Context): List<TestParametersValues> {
val root = Path("testdata")
val parameters =
root
.listDirectoryEntries()
.filter { it.isDirectory() }
.flatMap { modelDir ->
modelDir
.listDirectoryEntries("sdk*")
.filter { it.isDirectory() }
.mapNotNull { sdkDir ->
val sdkVersion = sdkDir.name.removePrefix("sdk").toIntOrNull()
if (sdkVersion == null) {
null
} else {
TestParametersValues.builder()
.name("${modelDir.name}_sdk$sdkVersion")
.addParameter("model", modelDir.name)
.addParameter("sdk", sdkVersion)
.build()
}
}
}
assertThat(parameters).isNotEmpty()
return parameters
}
override fun provideValues(context: Context): List<TestParametersValues> =
getTestCases().map { testCase ->
TestParametersValues.builder()
.name("${testCase.model}_sdk${testCase.sdk}")
.addParameter("model", testCase.model)
.addParameter("sdk", testCase.sdk)
.build()
}
}

@Test
Expand Down
87 changes: 51 additions & 36 deletions src/test/kotlin/VerifierTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.android.keyattestation.verifier

import kotlin.io.path.Path
import com.android.keyattestation.verifier.VerificationResult.ConstraintViolation
import com.android.keyattestation.verifier.VerificationResult.ExtensionParsingFailure
import com.android.keyattestation.verifier.VerificationResult.PathValidationFailure
Expand All @@ -24,6 +25,7 @@ import com.android.keyattestation.verifier.testing.CertLists
import com.android.keyattestation.verifier.testing.Certs
import com.android.keyattestation.verifier.testing.FakeCalendar
import com.android.keyattestation.verifier.testing.FakeLogHook
import com.android.keyattestation.verifier.testing.TestUtils.TESTDATA_PATH
import com.android.keyattestation.verifier.testing.TestUtils.falseChecker
import com.android.keyattestation.verifier.testing.TestUtils.prodAnchors
import com.android.keyattestation.verifier.testing.TestUtils.readCertList
Expand All @@ -32,17 +34,21 @@ import com.android.keyattestation.verifier.testing.TestUtils.trueChecker
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture

import com.google.protobuf.ByteString
import com.google.protobuf.kotlin.toByteString
import com.google.testing.junit.testparameterinjector.TestParameter
import com.google.testing.junit.testparameterinjector.TestParameterInjector
import com.google.testing.junit.testparameterinjector.TestParameters
import com.google.testing.junit.testparameterinjector.TestParameters.TestParametersValues
import com.google.testing.junit.testparameterinjector.TestParametersValuesProvider
import com.google.testing.junit.testparameterinjector.TestParametersValuesProvider.Context
import java.security.cert.PKIXReason
import java.security.cert.TrustAnchor
import java.time.Instant
import java.time.LocalDate
import java.time.ZoneOffset
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import kotlin.io.path.listDirectoryEntries
import kotlin.io.path.nameWithoutExtension
import kotlin.test.assertFailsWith
import kotlin.test.assertIs
import kotlinx.coroutines.guava.await
Expand All @@ -53,6 +59,8 @@ import org.junit.runner.RunWith
/** Unit tests for [Verifier]. */
@RunWith(TestParameterInjector::class)
class VerifierTest {
private val testData = Path("testdata")

private val verifier =
Verifier(
{ prodAnchors + TrustAnchor(Certs.root, null) },
Expand All @@ -72,41 +80,48 @@ class VerifierTest {
}

@Test
fun verify_validChain_returnsSuccess(@TestParameter testCase: TestCase) {
val verifier = Verifier({ prodAnchors }, { setOf<String>() }, { testCase.timestamp })
val chain = readCertList("${testCase.path}.pem")
val json = readJson("${testCase.path}.json")
val result = assertIs<VerificationResult.Success>(verifier.verify(chain))
assertThat(result.publicKey).isEqualTo(chain[0].publicKey)
assertThat(result.challenge).isEqualTo(json.attestationChallenge)
assertThat(result.securityLevel).isEqualTo(json.attestationSecurityLevel)
assertThat(result.verifiedBootState)
.isEqualTo(json.hardwareEnforced.rootOfTrust?.verifiedBootState)
assertThat(result.deviceLocked)
.isEqualTo(json.hardwareEnforced.rootOfTrust?.deviceLocked ?: false)
@TestParameters(valuesProvider = TestCaseProvider::class)
fun verify_validChain_returnsSuccess(model: String, sdk: Int) {
val path = testData.resolve("${model}/sdk${sdk}")
val pemFiles = path.listDirectoryEntries("*.pem")
for (pemPath in pemFiles) {
val subpath = "${model}/sdk${sdk}/${pemPath.nameWithoutExtension}"
val json = readJson("${subpath}.json")

// TODO(google-internal bug): update here once sw root is supported.
if (json.attestationSecurityLevel == SecurityLevel.SOFTWARE) {
continue
}

val creationDateTime =
json.softwareEnforced.creationDateTime ?: json.hardwareEnforced.creationDateTime
assertThat(creationDateTime).isNotNull()
val timestamp = Instant.ofEpochMilli(creationDateTime!!.toLong())

val verifier = Verifier({ prodAnchors }, { setOf<String>() }, { timestamp })
val chain = readCertList("${subpath}.pem")
val result = assertIs<VerificationResult.Success>(verifier.verify(chain))
assertThat(result.publicKey).isEqualTo(chain[0].publicKey)
assertThat(result.challenge).isEqualTo(json.attestationChallenge)
assertThat(result.securityLevel).isEqualTo(json.attestationSecurityLevel)
assertThat(result.verifiedBootState)
.isEqualTo(json.hardwareEnforced.rootOfTrust?.verifiedBootState)
assertThat(result.deviceLocked)
.isEqualTo(json.hardwareEnforced.rootOfTrust?.deviceLocked ?: false)
}
}

enum class TestCase(val path: String, val timestamp: Instant) {
PIXEL_3_SDK28(
"blueline/sdk28/TEE_EC_NONE",
LocalDate.of(2024, 10, 1).atStartOfDay(ZoneOffset.UTC).toInstant(),
),
PIXEL_8A_SDK34(
"akita/sdk34/TEE_EC_NONE",
LocalDate.of(2024, 10, 1).atStartOfDay(ZoneOffset.UTC).toInstant(),
),
PIXEL_9PRO_SDK36(
"caiman/sdk36/TEE_EC_RKP",
LocalDate.of(2025, 9, 30).atStartOfDay(ZoneOffset.UTC).toInstant(),
),
PIXEL_9A_SDK36(
"tegu/sdk36/TEE_EC_2026_ROOT",
LocalDate.of(2026, 2, 23).atStartOfDay(ZoneOffset.UTC).toInstant(),
),
PIXEL_9A_SDK36_STRONGBOX(
"tegu/sdk36/SB_EC_2026_ROOT",
LocalDate.of(2026, 2, 24).atStartOfDay(ZoneOffset.UTC).toInstant(),
),
class TestCaseProvider : TestParametersValuesProvider() {
override fun provideValues(context: Context): List<TestParametersValues> {
val testCases = com.android.keyattestation.verifier.testing.TestUtils.getTestCases()
return testCases.map { testCase ->
TestParametersValues.builder()
.name("${testCase.model}_sdk${testCase.sdk}")
.addParameter("model", testCase.model)
.addParameter("sdk", testCase.sdk)
.build()
}
}
}

@Test
Expand Down
Loading