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
43 changes: 43 additions & 0 deletions src/main/kotlin/SoftwareRoot.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

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-----
MIICizCCAjKgAwIBAgIJAKIFntEOQ1tXMAoGCCqGSM49BAMCMIGYMQswCQYDVQQG
EwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmll
dzEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMTMwMQYD
VQQDDCpBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVzdGF0aW9uIFJvb3Qw
HhcNMTYwMTExMDA0MzUwWhcNMzYwMTA2MDA0MzUwWjCBmDELMAkGA1UEBhMCVVMx
EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTAT
BgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDEzMDEGA1UEAwwq
QW5kcm9pZCBLZXlzdG9yZSBTb2Z0d2FyZSBBdHRlc3RhdGlvbiBSb290MFkwEwYH
KoZIzj0CAQYIKoZIzj0DAQcDQgAE7l1ex+HA220Dpn7mthvsTWpdamguD/9/SQ59
dx9EIm29sa/6FsvHrcV30lacqrewLVQBXT5DKyqO107sSHVBpKNjMGEwHQYDVR0O
BBYEFMit6XdMRcOjzw0WEOR5QzohWjDPMB8GA1UdIwQYMBaAFMit6XdMRcOjzw0W
EOR5QzohWjDPMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgKEMAoGCCqG
SM49BAMCA0cAMEQCIDUho++LNEYenNVg8x1YiSBq3KNlQfYNns6KGYxmSGB7AiBN
C/NR2TB8fVvaNTQdqEcbY6WFZTytTySn502vQX3xvw==
-----END CERTIFICATE-----"""

val SOFTWARE_ROOT: X509Certificate by lazy { GOOGLE_SOFTWARE_ROOT.asX509Certificate() }

internal fun X509Certificate.isSoftwareRoot() = this.publicKey == SOFTWARE_ROOT.publicKey
20 changes: 20 additions & 0 deletions src/main/kotlin/Verifier.kt
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ sealed interface VerificationResult {

data class ExtensionConstraintViolation(val cause: String, val reason: KeyAttestationReason) :
VerificationResult

data object SoftwareAttestationUnsupported : VerificationResult
}

/**
Expand Down Expand Up @@ -141,6 +143,13 @@ open class Verifier(
) {
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."
)
}
}
}

/**
Expand Down Expand Up @@ -244,6 +253,17 @@ open class Verifier(
try {
certPathValidator.validate(certPath, certPathParameters) as PKIXCertPathValidatorResult
} catch (e: CertPathValidatorException) {
if (
e.message == "No matching trust anchor found" &&
certPath.certificatesWithAnchor.last().isSoftwareRoot()
) {
return VerificationResult.PathValidationFailure(
CertPathValidatorException(
"Chain terminates in a software root and no matching trust anchor was found, so the chain was not validated.",
e,
)
)
}
return VerificationResult.PathValidationFailure(e)
}

Expand Down
8 changes: 8 additions & 0 deletions src/test/kotlin/VerifierTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import java.time.LocalDate
import java.time.ZoneOffset
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import kotlin.test.assertFailsWith
import kotlin.test.assertIs
import kotlinx.coroutines.guava.await
import kotlinx.coroutines.runBlocking
Expand Down Expand Up @@ -237,4 +238,11 @@ class VerifierTest {
verifier.verifyAsync(this, chain, delayedAlwaysTrueChecker).await()
)
}

@Test
fun init_softwareRootAsTrustAnchor_fails() {
assertFailsWith<IllegalArgumentException> {
Verifier({ setOf(TrustAnchor(SOFTWARE_ROOT, null)) }, { setOf<String>() }, { Instant.now() })
}
}
}