Skip to content
Open
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
7 changes: 7 additions & 0 deletions src/main/kotlin/ConstraintConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -65,13 +66,18 @@ class ConstraintConfig(
)
.addAll(additionalConstraints)
.build()

companion object {
fun testDefault(): ConstraintConfig = ConstraintConfig(allowSoftwareRoot = true)
}
}

/**
* We need a builder to support creating a [ConstraintConfig], as it's a thread-safe object. A
* 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
Expand All @@ -95,6 +101,7 @@ class ConstraintConfigBuilder() {

fun build(): ConstraintConfig =
ConstraintConfig(
allowSoftwareRoot,
keyOrigin,
securityLevel,
rootOfTrust,
Expand Down
36 changes: 29 additions & 7 deletions src/main/kotlin/SoftwareRoot.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<X509Certificate> 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)
12 changes: 7 additions & 5 deletions src/main/kotlin/Verifier.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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."
)
}
}
}
}
Expand Down
30 changes: 21 additions & 9 deletions src/test/kotlin/VerifierTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<String>() }, { timestamp })
val verifier =
Verifier(
{ prodAnchors + SOFTWARE_ROOTS.map { TrustAnchor(it, null) } },
{ setOf<String>() },
{ timestamp },
ConstraintConfig(
allowSoftwareRoot = true,
securityLevel = IgnoredConstraint,
rootOfTrust = IgnoredConstraint,
),
)
val chain = readCertList("${subpath}.pem")
println(verifier.verify(chain))
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)
.isEqualTo(
json.hardwareEnforced.rootOfTrust?.verifiedBootState ?: VerifiedBootState.UNVERIFIED
)
assertThat(result.deviceLocked)
.isEqualTo(json.hardwareEnforced.rootOfTrust?.deviceLocked ?: false)
}
Expand Down Expand Up @@ -369,7 +377,11 @@ class VerifierTest {
@Test
fun init_softwareRootAsTrustAnchor_fails() {
assertFailsWith<IllegalArgumentException> {
Verifier({ setOf(TrustAnchor(SOFTWARE_ROOT, null)) }, { setOf<String>() }, { Instant.now() })
Verifier(
{ setOf(TrustAnchor(SOFTWARE_ROOTS.first(), null)) },
{ setOf<String>() },
{ Instant.now() },
)
}
}
}
Loading