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.security.spec.RSAKeyGenParameterSpec
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
|
import org.bouncycastle.asn1.x509.BasicConstraints
|
||||||
import org.bouncycastle.asn1.x509.Extension
|
import org.bouncycastle.asn1.x509.Extension
|
||||||
import org.bouncycastle.asn1.x509.KeyUsage
|
import org.bouncycastle.asn1.x509.KeyUsage
|
||||||
import org.bouncycastle.cert.X509CertificateHolder
|
import org.bouncycastle.cert.X509CertificateHolder
|
||||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||||
|
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils
|
||||||
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
|
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
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.interception.keystore.shim.KeyMintSecurityLevelInterceptor
|
||||||
import org.matrix.TEESimulator.logging.SystemLogger
|
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 {
|
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? {
|
fun generateSoftwareKeyPair(params: KeyMintAttestation): KeyPair? {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
val (algorithm, spec) =
|
val (algorithm, spec) =
|
||||||
@@ -64,17 +54,6 @@ object CertificateGenerator {
|
|||||||
.getOrNull()
|
.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(
|
fun generateCertificateChain(
|
||||||
uid: Int,
|
uid: Int,
|
||||||
subjectKeyPair: KeyPair,
|
subjectKeyPair: KeyPair,
|
||||||
@@ -91,22 +70,26 @@ object CertificateGenerator {
|
|||||||
return runCatching {
|
return runCatching {
|
||||||
val keybox = getKeyboxForAlgorithm(uid, params.algorithm)
|
val keybox = getKeyboxForAlgorithm(uid, params.algorithm)
|
||||||
|
|
||||||
// Determine the signing key and issuer. If an attestKey is provided, use it.
|
val (signingKey, issuer, issuerCert) =
|
||||||
// Otherwise, fall back to the root key from the keybox.
|
|
||||||
val (signingKey, issuer) =
|
|
||||||
if (attestKeyAlias != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (attestKeyAlias != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
getAttestationKeyInfo(uid, attestKeyAlias)?.let { it.first to it.second }
|
getAttestationKeyInfo(uid, attestKeyAlias)?.let {
|
||||||
?: (keybox.keyPair to getIssuerFromKeybox(keybox))
|
Triple(it.first, it.second, null as X509Certificate?)
|
||||||
|
} ?: Triple(
|
||||||
|
keybox.keyPair,
|
||||||
|
getIssuerFromKeybox(keybox),
|
||||||
|
keybox.certificates.firstOrNull() as? X509Certificate
|
||||||
|
)
|
||||||
} else {
|
} 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 =
|
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) {
|
if (attestKeyAlias != null) {
|
||||||
listOf(leafCert)
|
listOf(leafCert)
|
||||||
} else {
|
} else {
|
||||||
@@ -117,10 +100,6 @@ object CertificateGenerator {
|
|||||||
.getOrNull()
|
.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(
|
fun generateAttestedKeyPair(
|
||||||
uid: Int,
|
uid: Int,
|
||||||
alias: String,
|
alias: String,
|
||||||
@@ -166,11 +145,9 @@ object CertificateGenerator {
|
|||||||
?: throw Exception("Could not load keybox for UID $uid and algorithm $algorithmName")
|
?: 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>? {
|
private fun getAttestationKeyInfo(uid: Int, attestKeyAlias: String): Pair<KeyPair, X500Name>? {
|
||||||
SystemLogger.debug("Looking for attestation key: uid=$uid alias=$attestKeyAlias")
|
SystemLogger.debug("Looking for attestation key: uid=$uid alias=$attestKeyAlias")
|
||||||
val keyId = KeyIdentifier(uid, attestKeyAlias)
|
val keyId = KeyIdentifier(uid, attestKeyAlias)
|
||||||
// Access the public map of generated keys
|
|
||||||
val keyInfo = KeyMintSecurityLevelInterceptor.generatedKeys[keyId]
|
val keyInfo = KeyMintSecurityLevelInterceptor.generatedKeys[keyId]
|
||||||
return if (keyInfo != null) {
|
return if (keyInfo != null) {
|
||||||
val certChain = CertificateHelper.getCertificateChain(keyInfo.response)
|
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 {
|
private fun buildKeyUsageFromPurposes(purposes: List<Int>): Int {
|
||||||
var bits = 0
|
var bits = 0
|
||||||
for (purpose in purposes) {
|
for (purpose in purposes) {
|
||||||
@@ -204,19 +180,22 @@ object CertificateGenerator {
|
|||||||
return bits
|
return bits
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Constructs a new X.509 certificate with a simulated attestation extension. */
|
|
||||||
private fun buildCertificate(
|
private fun buildCertificate(
|
||||||
subjectKeyPair: KeyPair,
|
subjectKeyPair: KeyPair,
|
||||||
signingKeyPair: KeyPair,
|
signingKeyPair: KeyPair,
|
||||||
issuer: X500Name,
|
issuer: X500Name,
|
||||||
|
issuerCert: X509Certificate?,
|
||||||
params: KeyMintAttestation,
|
params: KeyMintAttestation,
|
||||||
uid: Int,
|
uid: Int,
|
||||||
securityLevel: Int,
|
securityLevel: Int,
|
||||||
): Certificate {
|
): Certificate {
|
||||||
val subject = params.certificateSubject ?: X500Name("CN=Android KeyStore Key")
|
val subject = params.certificateSubject ?: X500Name("CN=Android Keystore Key")
|
||||||
val leafNotAfter =
|
SystemLogger.debug("[CertGen] Subject: $subject")
|
||||||
(signingKeyPair.public as? X509Certificate)?.notAfter
|
// Real TEEs use 25-30 year validity periods (per AOSP Beanpod KeyMaster observation)
|
||||||
?: Date(System.currentTimeMillis() + 31536000000L)
|
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 =
|
val builder =
|
||||||
JcaX509v3CertificateBuilder(
|
JcaX509v3CertificateBuilder(
|
||||||
@@ -232,8 +211,33 @@ object CertificateGenerator {
|
|||||||
val keyUsageBits = buildKeyUsageFromPurposes(params.purpose)
|
val keyUsageBits = buildKeyUsageFromPurposes(params.purpose)
|
||||||
if (keyUsageBits != 0) {
|
if (keyUsageBits != 0) {
|
||||||
builder.addExtension(Extension.keyUsage, true, KeyUsage(keyUsageBits))
|
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(
|
builder.addExtension(
|
||||||
AttestationBuilder.buildAttestationExtension(params, uid, securityLevel)
|
AttestationBuilder.buildAttestationExtension(params, uid, securityLevel)
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user