Skip to content

Commit 6ce1f20

Browse files
agxpcopybara-github
authored andcommitted
Reject Android attestations chaining to the software root
Some older Android devices do not support Android Key Attestation with a hardware-backed key and fall back to using a software-based solution. These attestations chain up to a root certificate with CN = Android Keystore Software Attestation Root. These attestations are not considered trustworthy for use cases requiring hardware-backed keys. To prevent accidental misconfiguration, this change also adds a check to ensure the software root is never configured as the main trust anchor. PiperOrigin-RevId: 803547414
1 parent 534c1b0 commit 6ce1f20

File tree

3 files changed

+71
-0
lines changed

3 files changed

+71
-0
lines changed

src/main/kotlin/SoftwareRoot.kt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.android.keyattestation.verifier
18+
19+
import java.security.cert.X509Certificate
20+
21+
// The software root certificate used by the Android Key Attestation standard.
22+
// https://android.googlesource.com/platform/system/core/+/refs/heads/main/trusty/keymaster/set_attestation_key/keymaster_soft_attestation_keys.xml#97
23+
private const val GOOGLE_SOFTWARE_ROOT =
24+
"""-----BEGIN CERTIFICATE-----
25+
MIICizCCAjKgAwIBAgIJAKIFntEOQ1tXMAoGCCqGSM49BAMCMIGYMQswCQYDVQQG
26+
EwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmll
27+
dzEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMTMwMQYD
28+
VQQDDCpBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVzdGF0aW9uIFJvb3Qw
29+
HhcNMTYwMTExMDA0MzUwWhcNMzYwMTA2MDA0MzUwWjCBmDELMAkGA1UEBhMCVVMx
30+
EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTAT
31+
BgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDEzMDEGA1UEAwwq
32+
QW5kcm9pZCBLZXlzdG9yZSBTb2Z0d2FyZSBBdHRlc3RhdGlvbiBSb290MFkwEwYH
33+
KoZIzj0CAQYIKoZIzj0DAQcDQgAE7l1ex+HA220Dpn7mthvsTWpdamguD/9/SQ59
34+
dx9EIm29sa/6FsvHrcV30lacqrewLVQBXT5DKyqO107sSHVBpKNjMGEwHQYDVR0O
35+
BBYEFMit6XdMRcOjzw0WEOR5QzohWjDPMB8GA1UdIwQYMBaAFMit6XdMRcOjzw0W
36+
EOR5QzohWjDPMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgKEMAoGCCqG
37+
SM49BAMCA0cAMEQCIDUho++LNEYenNVg8x1YiSBq3KNlQfYNns6KGYxmSGB7AiBN
38+
C/NR2TB8fVvaNTQdqEcbY6WFZTytTySn502vQX3xvw==
39+
-----END CERTIFICATE-----"""
40+
41+
val SOFTWARE_ROOT: X509Certificate by lazy { GOOGLE_SOFTWARE_ROOT.asX509Certificate() }
42+
43+
internal fun X509Certificate.isSoftwareRoot() = this.publicKey == SOFTWARE_ROOT.publicKey

src/main/kotlin/Verifier.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ sealed interface VerificationResult {
6262

6363
data class ExtensionConstraintViolation(val cause: String, val reason: KeyAttestationReason) :
6464
VerificationResult
65+
66+
data object SoftwareAttestationUnsupported : VerificationResult
6567
}
6668

6769
/**
@@ -141,6 +143,13 @@ open class Verifier(
141143
) {
142144
init {
143145
Security.addProvider(KeyAttestationProvider())
146+
for (anchor in trustAnchorsSource()) {
147+
if (anchor.trustedCert?.isSoftwareRoot() == true) {
148+
throw IllegalArgumentException(
149+
"Software attestation root cannot be used as a trust anchor."
150+
)
151+
}
152+
}
144153
}
145154

146155
/**
@@ -244,6 +253,17 @@ open class Verifier(
244253
try {
245254
certPathValidator.validate(certPath, certPathParameters) as PKIXCertPathValidatorResult
246255
} catch (e: CertPathValidatorException) {
256+
if (
257+
e.message == "No matching trust anchor found" &&
258+
certPath.certificatesWithAnchor.last().isSoftwareRoot()
259+
) {
260+
return VerificationResult.PathValidationFailure(
261+
CertPathValidatorException(
262+
"Chain terminates in a software root and no matching trust anchor was found, so the chain was not validated.",
263+
e,
264+
)
265+
)
266+
}
247267
return VerificationResult.PathValidationFailure(e)
248268
}
249269

src/test/kotlin/VerifierTest.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import java.time.LocalDate
4343
import java.time.ZoneOffset
4444
import java.util.concurrent.Executors
4545
import java.util.concurrent.TimeUnit
46+
import kotlin.test.assertFailsWith
4647
import kotlin.test.assertIs
4748
import kotlinx.coroutines.guava.await
4849
import kotlinx.coroutines.runBlocking
@@ -237,4 +238,11 @@ class VerifierTest {
237238
verifier.verifyAsync(this, chain, delayedAlwaysTrueChecker).await()
238239
)
239240
}
241+
242+
@Test
243+
fun init_softwareRootAsTrustAnchor_fails() {
244+
assertFailsWith<IllegalArgumentException> {
245+
Verifier({ setOf(TrustAnchor(SOFTWARE_ROOT, null)) }, { setOf<String>() }, { Instant.now() })
246+
}
247+
}
240248
}

0 commit comments

Comments
 (0)