diff --git a/src/main/kotlin/SoftwareRoot.kt b/src/main/kotlin/SoftwareRoot.kt new file mode 100644 index 0000000..f89e81b --- /dev/null +++ b/src/main/kotlin/SoftwareRoot.kt @@ -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 diff --git a/src/main/kotlin/Verifier.kt b/src/main/kotlin/Verifier.kt index b6db457..b7a81ba 100644 --- a/src/main/kotlin/Verifier.kt +++ b/src/main/kotlin/Verifier.kt @@ -62,6 +62,8 @@ sealed interface VerificationResult { data class ExtensionConstraintViolation(val cause: String, val reason: KeyAttestationReason) : VerificationResult + + data object SoftwareAttestationUnsupported : VerificationResult } /** @@ -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." + ) + } + } } /** @@ -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) } diff --git a/src/test/kotlin/VerifierTest.kt b/src/test/kotlin/VerifierTest.kt index e51910b..88bd475 100644 --- a/src/test/kotlin/VerifierTest.kt +++ b/src/test/kotlin/VerifierTest.kt @@ -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 @@ -237,4 +238,11 @@ class VerifierTest { verifier.verifyAsync(this, chain, delayedAlwaysTrueChecker).await() ) } + + @Test + fun init_softwareRootAsTrustAnchor_fails() { + assertFailsWith { + Verifier({ setOf(TrustAnchor(SOFTWARE_ROOT, null)) }, { setOf() }, { Instant.now() }) + } + } }