diff --git a/src/main/kotlin/provider/KeyAttestationCertPathValidator.kt b/src/main/kotlin/provider/KeyAttestationCertPathValidator.kt index e9a0857..bdba1f4 100644 --- a/src/main/kotlin/provider/KeyAttestationCertPathValidator.kt +++ b/src/main/kotlin/provider/KeyAttestationCertPathValidator.kt @@ -303,6 +303,10 @@ private class BasicChecker( BasicReason.NOT_YET_VALID, ) } catch (e: CertificateExpiredException) { + // Ignore validity on factory-provisioned certificate chains because it is not possible to + // safely rotate the keys. + if (certPath.provisioningMethod() == ProvisioningMethod.FACTORY_PROVISIONED) return + throw CertPathValidatorException( "Validity check failed", e, diff --git a/src/main/kotlin/testing/Certs.kt b/src/main/kotlin/testing/Certs.kt index 71f1de7..3f10567 100644 --- a/src/main/kotlin/testing/Certs.kt +++ b/src/main/kotlin/testing/Certs.kt @@ -481,9 +481,9 @@ object Chains { ) } - /* A chain where the attestation certificate has expired. */ + /* A factory-provisioned chain where the attestation certificate has expired. */ @JvmStatic - val expired by lazy { + val expiredFactoryProvisioned by lazy { KeyAttestationCertPath( certFactory.generateLeafCert(), certFactory.generateAttestationCert( @@ -495,6 +495,24 @@ object Chains { ) } + /* A remotely-provisioned chain where the attestation certificate has expired. */ + @JvmStatic + val expiredRemotelyProvisioned by lazy { + val rkpAttestationCert = + certFactory.generateRkpAttestationCert( + serialNumber = BigInteger.valueOf(0x1234567890), + notBefore = fakeCalendar.lastWeek(), + notAfter = fakeCalendar.lastWeek(), + ) + KeyAttestationCertPath( + certFactory.generateLeafCert(issuer = rkpAttestationCert.subject), + rkpAttestationCert, + certFactory.rkpIntermediate, + Certs.remoteIntermediate, + certFactory.root, + ) + } + /* A chain where the leaf certificate has expired. This will pass. */ val expiredLeaf by lazy { KeyAttestationCertPath( diff --git a/src/main/kotlin/testing/KeyAttestationCertFactory.kt b/src/main/kotlin/testing/KeyAttestationCertFactory.kt index 4529deb..d741c16 100644 --- a/src/main/kotlin/testing/KeyAttestationCertFactory.kt +++ b/src/main/kotlin/testing/KeyAttestationCertFactory.kt @@ -117,12 +117,16 @@ internal class KeyAttestationCertFactory(val fakeCalendar: FakeCalendar = FakeCa internal fun generateRkpAttestationCert( securityLevel: SecurityLevel = SecurityLevel.TRUSTED_ENVIRONMENT, serialNumber: BigInteger, + notBefore: Date = fakeCalendar.lastWeek(), + notAfter: Date = fakeCalendar.nextWeek(), ) = generateAttestationCert( signingKey = rkpKey.private, subject = rkpAttestationName(securityLevel, serialNumber), issuer = rkpIntermediate.subject, serialNumber, + notBefore, + notAfter, extraExtension = Extension( ProvisioningInfoMap.OID, diff --git a/src/main/kotlin/testing/X509CertificateExt.kt b/src/main/kotlin/testing/X509CertificateExt.kt index 97a7abe..087df80 100644 --- a/src/main/kotlin/testing/X509CertificateExt.kt +++ b/src/main/kotlin/testing/X509CertificateExt.kt @@ -23,5 +23,5 @@ import org.bouncycastle.cert.jcajce.JcaX500NameUtil private fun X500Principal.asX500Name() = JcaX500NameUtil.getX500Name(this) -internal val X509Certificate.subject: X500Name +val X509Certificate.subject: X500Name get() = subjectX500Principal.asX500Name() diff --git a/src/test/kotlin/provider/KeyAttestationCertPathValidatorTest.kt b/src/test/kotlin/provider/KeyAttestationCertPathValidatorTest.kt index afb9f56..f683a68 100644 --- a/src/test/kotlin/provider/KeyAttestationCertPathValidatorTest.kt +++ b/src/test/kotlin/provider/KeyAttestationCertPathValidatorTest.kt @@ -22,6 +22,7 @@ import com.android.keyattestation.verifier.testing.Certs.rootAnchor as testAncho import com.android.keyattestation.verifier.testing.Chains import com.android.keyattestation.verifier.testing.FakeCalendar import com.android.keyattestation.verifier.testing.TestUtils.prodAnchors +import com.android.keyattestation.verifier.testing.TestUtils.readCertPath import com.google.common.truth.Truth.assertThat import java.security.InvalidAlgorithmParameterException import java.security.Security @@ -34,6 +35,8 @@ import java.security.cert.PKIXCertPathChecker import java.security.cert.PKIXCertPathValidatorResult import java.security.cert.PKIXParameters import java.security.cert.PKIXReason +import java.security.cert.TrustAnchor +import java.time.LocalDate import kotlin.test.assertFailsWith import org.junit.BeforeClass import org.junit.Test @@ -84,14 +87,14 @@ class KeyAttestationCertPathValidatorTest { val exception = assertFailsWith { certPathValidator.validate( - Chains.validFactoryProvisioned, + Chains.validRemotelyProvisioned, PKIXParameters(setOf(testAnchor)), ) } val pkixException = assertFailsWith { pkixCertPathValidator.validate( - Chains.validFactoryProvisioned, + Chains.validRemotelyProvisioned, PKIXParameters(setOf(testAnchor)), ) } @@ -205,8 +208,13 @@ class KeyAttestationCertPathValidatorTest { } @Test - fun expired_throwsCertPathValidatorException() { - val certPath = Chains.expired + fun expiredFactory_succeeds() { + certPathValidator.validate(Chains.expiredFactoryProvisioned, testParams) + } + + @Test + fun expiredRkp_throwsCertPathValidatorException() { + val certPath = Chains.expiredRemotelyProvisioned val exception = assertFailsWith { certPathValidator.validate(certPath, testParams) @@ -214,6 +222,30 @@ class KeyAttestationCertPathValidatorTest { assertThat(exception.reason).isEqualTo(BasicReason.EXPIRED) } + @Test + fun bluelineSdk28_factoryProvisioned_expiryIgnored() { + val certPath = readCertPath("blueline/sdk28/TEE_EC_NONE.pem") + val root = certPath.certificatesWithAnchor.last() + val params = + PKIXParameters(setOf(TrustAnchor(root, null))).apply { + date = FakeCalendar(LocalDate.of(2030, 1, 1)).today() + } + certPathValidator.validate(certPath, params) + } + + @Test + fun caimanSdk36_remoteProvisioned_expiryHonored() { + val certPath = readCertPath("caiman/sdk36/TEE_EC_RKP.pem") + val root = certPath.certificatesWithAnchor.last() + val params = + PKIXParameters(setOf(TrustAnchor(root, null))).apply { + date = FakeCalendar(LocalDate.of(2030, 1, 1)).today() + } + val exception = + assertFailsWith { certPathValidator.validate(certPath, params) } + assertThat(exception.reason).isEqualTo(BasicReason.EXPIRED) + } + @Test fun forgedKeybox_throwsCertPathValidatorException() { val certPath = Chains.forgedKeybox