Add X.509 certificate extensions for RFC 5280 compliance
- BasicConstraints: CA=false (critical) - SubjectKeyIdentifier via SHA-1 hash - AuthorityKeyIdentifier linked to issuer cert
This commit is contained in:
@@ -13,10 +13,12 @@ import java.security.spec.ECGenParameterSpec
|
||||
import java.security.spec.RSAKeyGenParameterSpec
|
||||
import java.util.Date
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x509.BasicConstraints
|
||||
import org.bouncycastle.asn1.x509.Extension
|
||||
import org.bouncycastle.asn1.x509.KeyUsage
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils
|
||||
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
||||
@@ -28,20 +30,8 @@ import org.matrix.TEESimulator.interception.keystore.KeyIdentifier
|
||||
import org.matrix.TEESimulator.interception.keystore.shim.KeyMintSecurityLevelInterceptor
|
||||
import org.matrix.TEESimulator.logging.SystemLogger
|
||||
|
||||
/**
|
||||
* Responsible for generating new cryptographic key pairs and X.509 certificate chains.
|
||||
*
|
||||
* This object simulates the behavior of the Android KeyMint/Keymaster HAL by creating certificates
|
||||
* that include a fully-featured, simulated attestation extension.
|
||||
*/
|
||||
object CertificateGenerator {
|
||||
|
||||
/**
|
||||
* Generates a software-based cryptographic key pair.
|
||||
*
|
||||
* @param params The parameters specifying the key's algorithm, size, and other properties.
|
||||
* @return A new [KeyPair], or `null` on failure.
|
||||
*/
|
||||
fun generateSoftwareKeyPair(params: KeyMintAttestation): KeyPair? {
|
||||
return runCatching {
|
||||
val (algorithm, spec) =
|
||||
@@ -64,17 +54,6 @@ object CertificateGenerator {
|
||||
.getOrNull()
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a certificate chain for a given key pair. This is the primary function for creating
|
||||
* attested certificates.
|
||||
*
|
||||
* @param uid The UID of the application requesting the key.
|
||||
* @param subjectKeyPair The key pair for which the certificate will be generated.
|
||||
* @param attestKeyAlias Optional alias of a key to use for attestation signing.
|
||||
* @param params The parameters for the new key and its attestation.
|
||||
* @param securityLevel The security level to embed in the attestation.
|
||||
* @return A [List] of [Certificate] forming the new chain, or `null` on failure.
|
||||
*/
|
||||
fun generateCertificateChain(
|
||||
uid: Int,
|
||||
subjectKeyPair: KeyPair,
|
||||
@@ -91,22 +70,26 @@ object CertificateGenerator {
|
||||
return runCatching {
|
||||
val keybox = getKeyboxForAlgorithm(uid, params.algorithm)
|
||||
|
||||
// Determine the signing key and issuer. If an attestKey is provided, use it.
|
||||
// Otherwise, fall back to the root key from the keybox.
|
||||
val (signingKey, issuer) =
|
||||
val (signingKey, issuer, issuerCert) =
|
||||
if (attestKeyAlias != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
getAttestationKeyInfo(uid, attestKeyAlias)?.let { it.first to it.second }
|
||||
?: (keybox.keyPair to getIssuerFromKeybox(keybox))
|
||||
getAttestationKeyInfo(uid, attestKeyAlias)?.let {
|
||||
Triple(it.first, it.second, null as X509Certificate?)
|
||||
} ?: Triple(
|
||||
keybox.keyPair,
|
||||
getIssuerFromKeybox(keybox),
|
||||
keybox.certificates.firstOrNull() as? X509Certificate
|
||||
)
|
||||
} else {
|
||||
keybox.keyPair to getIssuerFromKeybox(keybox)
|
||||
Triple(
|
||||
keybox.keyPair,
|
||||
getIssuerFromKeybox(keybox),
|
||||
keybox.certificates.firstOrNull() as? X509Certificate
|
||||
)
|
||||
}
|
||||
|
||||
// Build the new leaf certificate with the simulated attestation.
|
||||
val leafCert =
|
||||
buildCertificate(subjectKeyPair, signingKey, issuer, params, uid, securityLevel)
|
||||
buildCertificate(subjectKeyPair, signingKey, issuer, issuerCert, params, uid, securityLevel)
|
||||
|
||||
// If not self-attesting, the chain is just the leaf. Otherwise, append the keybox
|
||||
// chain.
|
||||
if (attestKeyAlias != null) {
|
||||
listOf(leafCert)
|
||||
} else {
|
||||
@@ -117,10 +100,6 @@ object CertificateGenerator {
|
||||
.getOrNull()
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience function that combines key pair generation and certificate chain generation.
|
||||
* Primarily used by the modern Keystore2 interceptor where generation is a single step.
|
||||
*/
|
||||
fun generateAttestedKeyPair(
|
||||
uid: Int,
|
||||
alias: String,
|
||||
@@ -166,11 +145,9 @@ object CertificateGenerator {
|
||||
?: throw Exception("Could not load keybox for UID $uid and algorithm $algorithmName")
|
||||
}
|
||||
|
||||
/** Retrieves the key pair and issuer name for a given attestation key alias. */
|
||||
private fun getAttestationKeyInfo(uid: Int, attestKeyAlias: String): Pair<KeyPair, X500Name>? {
|
||||
SystemLogger.debug("Looking for attestation key: uid=$uid alias=$attestKeyAlias")
|
||||
val keyId = KeyIdentifier(uid, attestKeyAlias)
|
||||
// Access the public map of generated keys
|
||||
val keyInfo = KeyMintSecurityLevelInterceptor.generatedKeys[keyId]
|
||||
return if (keyInfo != null) {
|
||||
val certChain = CertificateHelper.getCertificateChain(keyInfo.response)
|
||||
@@ -188,7 +165,6 @@ object CertificateGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
/** Maps KeyPurpose values to X.509 KeyUsage bits per KeyCreationResult.aidl spec */
|
||||
private fun buildKeyUsageFromPurposes(purposes: List<Int>): Int {
|
||||
var bits = 0
|
||||
for (purpose in purposes) {
|
||||
@@ -204,19 +180,22 @@ object CertificateGenerator {
|
||||
return bits
|
||||
}
|
||||
|
||||
/** Constructs a new X.509 certificate with a simulated attestation extension. */
|
||||
private fun buildCertificate(
|
||||
subjectKeyPair: KeyPair,
|
||||
signingKeyPair: KeyPair,
|
||||
issuer: X500Name,
|
||||
issuerCert: X509Certificate?,
|
||||
params: KeyMintAttestation,
|
||||
uid: Int,
|
||||
securityLevel: Int,
|
||||
): Certificate {
|
||||
val subject = params.certificateSubject ?: X500Name("CN=Android KeyStore Key")
|
||||
val leafNotAfter =
|
||||
(signingKeyPair.public as? X509Certificate)?.notAfter
|
||||
?: Date(System.currentTimeMillis() + 31536000000L)
|
||||
val subject = params.certificateSubject ?: X500Name("CN=Android Keystore Key")
|
||||
SystemLogger.debug("[CertGen] Subject: $subject")
|
||||
// Real TEEs use 25-30 year validity periods (per AOSP Beanpod KeyMaster observation)
|
||||
val THIRTY_YEARS_MS = 30L * 365 * 24 * 60 * 60 * 1000
|
||||
val leafNotAfter = issuerCert?.notAfter
|
||||
?: Date(System.currentTimeMillis() + THIRTY_YEARS_MS)
|
||||
SystemLogger.debug("[CertGen] Validity: ${params.certificateNotBefore ?: Date()} to $leafNotAfter")
|
||||
|
||||
val builder =
|
||||
JcaX509v3CertificateBuilder(
|
||||
@@ -232,8 +211,33 @@ object CertificateGenerator {
|
||||
val keyUsageBits = buildKeyUsageFromPurposes(params.purpose)
|
||||
if (keyUsageBits != 0) {
|
||||
builder.addExtension(Extension.keyUsage, true, KeyUsage(keyUsageBits))
|
||||
SystemLogger.debug("[CertGen] Added KeyUsage extension: bits=0x${keyUsageBits.toString(16)}")
|
||||
}
|
||||
// Add our custom, simulated attestation extension.
|
||||
|
||||
// RFC 5280 Section 4.2.1.9: end-entity cert must not act as CA
|
||||
builder.addExtension(Extension.basicConstraints, true, BasicConstraints(false))
|
||||
SystemLogger.debug("[CertGen] Added BasicConstraints: CA=false")
|
||||
|
||||
val extUtils = JcaX509ExtensionUtils()
|
||||
|
||||
// RFC 5280 Section 4.2.1.2: SHA-1 hash of subject public key
|
||||
builder.addExtension(
|
||||
Extension.subjectKeyIdentifier,
|
||||
false,
|
||||
extUtils.createSubjectKeyIdentifier(subjectKeyPair.public)
|
||||
)
|
||||
SystemLogger.debug("[CertGen] Added SubjectKeyIdentifier (SKI)")
|
||||
|
||||
// RFC 5280 Section 4.2.1.1: links certificate to issuer's signing key
|
||||
if (issuerCert != null) {
|
||||
builder.addExtension(
|
||||
Extension.authorityKeyIdentifier,
|
||||
false,
|
||||
extUtils.createAuthorityKeyIdentifier(issuerCert)
|
||||
)
|
||||
SystemLogger.debug("[CertGen] Added AuthorityKeyIdentifier (AKI) from issuer")
|
||||
}
|
||||
|
||||
builder.addExtension(
|
||||
AttestationBuilder.buildAttestationExtension(params, uid, securityLevel)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user