From 6cfd692a74189107567ac1224b5d230fdda89eec Mon Sep 17 00:00:00 2001 From: Carmen Hanish Date: Fri, 17 Jul 2026 07:34:41 -0700 Subject: [PATCH] Support software root for testing. PiperOrigin-RevId: 949560520 --- src/main/kotlin/ConstraintConfig.kt | 7 ++++++ src/main/kotlin/SoftwareRoot.kt | 36 +++++++++++++++++++++++------ src/main/kotlin/Verifier.kt | 12 ++++++---- src/test/kotlin/VerifierTest.kt | 30 ++++++++++++++++-------- 4 files changed, 64 insertions(+), 21 deletions(-) diff --git a/src/main/kotlin/ConstraintConfig.kt b/src/main/kotlin/ConstraintConfig.kt index 8e4565f..7339e8d 100644 --- a/src/main/kotlin/ConstraintConfig.kt +++ b/src/main/kotlin/ConstraintConfig.kt @@ -46,6 +46,7 @@ sealed interface Constraint { */ @ThreadSafe class ConstraintConfig( + val allowSoftwareRoot: Boolean = false, val keyOrigin: Constraint? = null, val securityLevel: Constraint? = null, val rootOfTrust: Constraint? = null, @@ -65,6 +66,10 @@ class ConstraintConfig( ) .addAll(additionalConstraints) .build() + + companion object { + fun testDefault(): ConstraintConfig = ConstraintConfig(allowSoftwareRoot = true) + } } /** @@ -72,6 +77,7 @@ class ConstraintConfig( * Kotlin-idiomatic builder function is provided below. */ class ConstraintConfigBuilder() { + var allowSoftwareRoot: Boolean = false var keyOrigin: Constraint? = null var securityLevel: Constraint? = null var rootOfTrust: Constraint? = null @@ -95,6 +101,7 @@ class ConstraintConfigBuilder() { fun build(): ConstraintConfig = ConstraintConfig( + allowSoftwareRoot, keyOrigin, securityLevel, rootOfTrust, diff --git a/src/main/kotlin/SoftwareRoot.kt b/src/main/kotlin/SoftwareRoot.kt index f89e81b..5250d7c 100644 --- a/src/main/kotlin/SoftwareRoot.kt +++ b/src/main/kotlin/SoftwareRoot.kt @@ -18,10 +18,11 @@ package com.android.keyattestation.verifier import java.security.cert.X509Certificate -// The software root certificate used by the Android Key Attestation standard. -// https://android.googlesource.com/platform/system/core/+/refs/heads/main/trusty/keymaster/set_attestation_key/keymaster_soft_attestation_keys.xml#97 -private const val GOOGLE_SOFTWARE_ROOT = - """-----BEGIN CERTIFICATE----- +// The software root certificates used by the Android Key Attestation standard. +// https://android.googlesource.com/platform/system/core/+/refs/heads/main/trusty/keymaster/set_attestation_key/keymaster_soft_attestation_keys.xml +private val GOOGLE_SOFTWARE_ROOTS = + listOf( + """-----BEGIN CERTIFICATE----- MIICizCCAjKgAwIBAgIJAKIFntEOQ1tXMAoGCCqGSM49BAMCMIGYMQswCQYDVQQG EwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmll dzEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMTMwMQYD @@ -36,8 +37,29 @@ BBYEFMit6XdMRcOjzw0WEOR5QzohWjDPMB8GA1UdIwQYMBaAFMit6XdMRcOjzw0W EOR5QzohWjDPMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgKEMAoGCCqG SM49BAMCA0cAMEQCIDUho++LNEYenNVg8x1YiSBq3KNlQfYNns6KGYxmSGB7AiBN C/NR2TB8fVvaNTQdqEcbY6WFZTytTySn502vQX3xvw== ------END CERTIFICATE-----""" +-----END CERTIFICATE-----""", + """-----BEGIN CERTIFICATE----- +MIICpzCCAhCgAwIBAgIJAP+U2d2fB8gMMA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNV +BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBW +aWV3MRUwEwYDVQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJvaWQwHhcN +MTYwMTA0MTIzMTA4WhcNMzUxMjMwMTIzMTA4WjBjMQswCQYDVQQGEwJVUzETMBEG +A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEVMBMGA1UE +CgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQCia63rbi5EYe/VDoLmt5TRdSMfd5tjkWP/96r/C3JHTsAs +Q+wzfNes7UA+jCigZtX3hwszl94OuE4TQKuvpSe/lWmgMdsGUmX4RFlXYfC78hdL +t0GAZMAoDo9Sd47b0ke2RekZyOmLw9vCkT/X11DEHTVm+Vfkl5YLCazOkjWFmwID +AQABo2MwYTAdBgNVHQ4EFgQUKfrxrMxN0kyWQCd1trDpMuUH/i4wHwYDVR0jBBgw +FoAUKfrxrMxN0kyWQCd1trDpMuUH/i4wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B +Af8EBAMCAoQwDQYJKoZIhvcNAQELBQADgYEAT3LzNlmNDsG5dFsxWfbwjSVJMJ6j +HBwp0kUtILlNX2S06IDHeHqcOd6os/W/L3BfRxBcxebrTQaZYdKumgf/93y4q+uc +DyQHXrF/unlx/U1bnt8Uqf7f7XzAiF343ZtkMlbVNZriE/mPzsF83O+kqrJVw4Op +Lvtc9mL1J1IXvmM= +-----END CERTIFICATE-----""", + ) -val SOFTWARE_ROOT: X509Certificate by lazy { GOOGLE_SOFTWARE_ROOT.asX509Certificate() } +val SOFTWARE_ROOTS: List by lazy { + GOOGLE_SOFTWARE_ROOTS.map { it.asX509Certificate() } +} -internal fun X509Certificate.isSoftwareRoot() = this.publicKey == SOFTWARE_ROOT.publicKey +internal fun X509Certificate.isSoftwareRoot() = + SOFTWARE_ROOTS.map { it.publicKey }.contains(this.publicKey) diff --git a/src/main/kotlin/Verifier.kt b/src/main/kotlin/Verifier.kt index fc69182..5e583f5 100644 --- a/src/main/kotlin/Verifier.kt +++ b/src/main/kotlin/Verifier.kt @@ -155,11 +155,13 @@ constructor( ) { init { Security.addProvider(KeyAttestationProvider()) - for (anchor in trustAnchorsSource()) { - if (anchor.trustedCert?.isSoftwareRoot() == true) { - throw IllegalArgumentException( - "Software attestation root cannot be used as a trust anchor." - ) + if (!constraintConfig.allowSoftwareRoot) { + for (anchor in trustAnchorsSource()) { + if (anchor.trustedCert?.isSoftwareRoot() == true) { + throw IllegalArgumentException( + "Software attestation root cannot be used as a trust anchor." + ) + } } } } diff --git a/src/test/kotlin/VerifierTest.kt b/src/test/kotlin/VerifierTest.kt index 379c7ab..3185378 100644 --- a/src/test/kotlin/VerifierTest.kt +++ b/src/test/kotlin/VerifierTest.kt @@ -17,6 +17,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 @@ -87,25 +88,32 @@ class VerifierTest { 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() }, { timestamp }) + val verifier = + Verifier( + { prodAnchors + SOFTWARE_ROOTS.map { TrustAnchor(it, null) } }, + { setOf() }, + { timestamp }, + ConstraintConfig( + allowSoftwareRoot = true, + securityLevel = IgnoredConstraint, + rootOfTrust = IgnoredConstraint, + ), + ) val chain = readCertList("${subpath}.pem") + println(verifier.verify(chain)) val result = assertIs(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) + .isEqualTo( + json.hardwareEnforced.rootOfTrust?.verifiedBootState ?: VerifiedBootState.UNVERIFIED + ) assertThat(result.deviceLocked) .isEqualTo(json.hardwareEnforced.rootOfTrust?.deviceLocked ?: false) } @@ -369,7 +377,11 @@ class VerifierTest { @Test fun init_softwareRootAsTrustAnchor_fails() { assertFailsWith { - Verifier({ setOf(TrustAnchor(SOFTWARE_ROOT, null)) }, { setOf() }, { Instant.now() }) + Verifier( + { setOf(TrustAnchor(SOFTWARE_ROOTS.first(), null)) }, + { setOf() }, + { Instant.now() }, + ) } } }