fix(certgen): omit attestation extension when no challenge provided

AOSP KeyMint only includes the attestation extension (OID
1.3.6.1.4.1.11129.2.1.17) when ATTESTATION_CHALLENGE is present.
Without a challenge, generateKey produces a plain self-signed cert.
Our code unconditionally added the extension, which behavioral
probes detect by generating a key without a challenge and checking
for the OID.

Fixes both the Rust native-certgen and BouncyCastle paths.
Also skips AAID computation when no challenge is provided,
matching keystore2 security_level.rs:457 behavior.
This commit is contained in:
Enginex0
2026-03-26 04:22:22 +01:00
parent ebb6336281
commit 47ab0225e1
4 changed files with 20 additions and 16 deletions
@@ -708,7 +708,8 @@ class KeyMintSecurityLevelInterceptor(
val attestVersion = AndroidDeviceUtils.getAttestVersion(securityLevel) val attestVersion = AndroidDeviceUtils.getAttestVersion(securityLevel)
val keymasterVersion = AndroidDeviceUtils.getKeymasterVersion(securityLevel) val keymasterVersion = AndroidDeviceUtils.getKeymasterVersion(securityLevel)
val appId = AttestationBuilder.createApplicationId(callingUid) val hasChallenge = params.attestationChallenge != null
val appId = if (hasChallenge) AttestationBuilder.createApplicationId(callingUid) else null
val config = CertGenConfig( val config = CertGenConfig(
algorithm = params.algorithm, algorithm = params.algorithm,
@@ -734,7 +735,7 @@ class KeyMintSecurityLevelInterceptor(
bootKey = AndroidDeviceUtils.bootKey, bootKey = AndroidDeviceUtils.bootKey,
bootHash = AndroidDeviceUtils.bootHash, bootHash = AndroidDeviceUtils.bootHash,
creationDatetime = System.currentTimeMillis(), creationDatetime = System.currentTimeMillis(),
attestationApplicationId = appId.octets, attestationApplicationId = appId?.octets ?: ByteArray(0),
moduleHash = if (attestVersion >= 400) AndroidDeviceUtils.moduleHash else null, moduleHash = if (attestVersion >= 400) AndroidDeviceUtils.moduleHash else null,
idBrand = params.brand, idBrand = params.brand,
idDevice = params.device, idDevice = params.device,
@@ -238,10 +238,11 @@ object CertificateGenerator {
if (keyUsageBits != 0) { if (keyUsageBits != 0) {
builder.addExtension(Extension.keyUsage, true, KeyUsage(keyUsageBits)) builder.addExtension(Extension.keyUsage, true, KeyUsage(keyUsageBits))
} }
// Add our custom, simulated attestation extension. if (params.attestationChallenge != null) {
builder.addExtension( builder.addExtension(
AttestationBuilder.buildAttestationExtension(params, uid, securityLevel) AttestationBuilder.buildAttestationExtension(params, uid, securityLevel)
) )
}
val signerAlgorithm = val signerAlgorithm =
when (signingKeyPair.private.algorithm) { when (signingKeyPair.private.algorithm) {
+7 -8
View File
@@ -16,7 +16,7 @@ const OID_KEY_USAGE: &[u64] = &[2, 5, 29, 15];
pub fn build_certificate_chain( pub fn build_certificate_chain(
key_pair: &GeneratedKeyPair, key_pair: &GeneratedKeyPair,
attestation_ext_der: &[u8], attestation_ext_der: Option<&[u8]>,
keybox: &ParsedKeybox, keybox: &ParsedKeybox,
params: &CertGenParams, params: &CertGenParams,
) -> Result<Vec<Vec<u8>>> { ) -> Result<Vec<Vec<u8>>> {
@@ -33,7 +33,7 @@ pub fn build_certificate_chain(
fn build_leaf_cert( fn build_leaf_cert(
key_pair: &GeneratedKeyPair, key_pair: &GeneratedKeyPair,
attestation_ext_der: &[u8], attestation_ext_der: Option<&[u8]>,
keybox: &ParsedKeybox, keybox: &ParsedKeybox,
params: &CertGenParams, params: &CertGenParams,
) -> Result<Vec<u8>> { ) -> Result<Vec<u8>> {
@@ -63,7 +63,6 @@ fn build_leaf_cert(
timestamp_to_datetime(params.cert_not_after)? timestamp_to_datetime(params.cert_not_after)?
}; };
// Extensions
let extensions_der = build_extensions(attestation_ext_der, &params.purposes)?; let extensions_der = build_extensions(attestation_ext_der, &params.purposes)?;
// TBS Certificate // TBS Certificate
@@ -256,19 +255,19 @@ fn extract_rsa_spki(pkcs8_der: &[u8]) -> Result<Vec<u8>> {
Ok(encode_der_sequence(&[&alg_id, &pub_key_bits])) Ok(encode_der_sequence(&[&alg_id, &pub_key_bits]))
} }
fn build_extensions(attestation_ext_der: &[u8], purposes: &[i32]) -> Result<Vec<u8>> { fn build_extensions(attestation_ext_der: Option<&[u8]>, purposes: &[i32]) -> Result<Vec<u8>> {
let mut extensions: Vec<Vec<u8>> = Vec::new(); let mut extensions: Vec<Vec<u8>> = Vec::new();
// KeyUsage extension (critical)
let ku_byte = map_key_usage_byte(purposes); let ku_byte = map_key_usage_byte(purposes);
if ku_byte != 0 { if ku_byte != 0 {
let ku_ext = build_key_usage_extension(ku_byte); let ku_ext = build_key_usage_extension(ku_byte);
extensions.push(ku_ext); extensions.push(ku_ext);
} }
// Attestation extension (non-critical) if let Some(attest_der) = attestation_ext_der {
let attest_ext = build_extension(&encode_der_oid(ATTESTATION_OID), false, attestation_ext_der); let attest_ext = build_extension(&encode_der_oid(ATTESTATION_OID), false, attest_der);
extensions.push(attest_ext); extensions.push(attest_ext);
}
Ok(encode_der_sequence_of(&extensions)) Ok(encode_der_sequence_of(&extensions))
} }
+5 -2
View File
@@ -62,11 +62,14 @@ fn generate_attested_inner(env: &mut JNIEnv, config: &JObject) -> Result<jbyteAr
let keybox = keybox::parse_keybox(&params.keybox_cert_chain, &params.keybox_private_key)?; let keybox = keybox::parse_keybox(&params.keybox_cert_chain, &params.keybox_private_key)?;
let attest_ext = attestation::build_attestation_extension(&params)?; let attest_ext = match params.attestation_challenge {
Some(_) => Some(attestation::build_attestation_extension(&params)?),
None => None,
};
let cert_chain = certbuilder::build_certificate_chain( let cert_chain = certbuilder::build_certificate_chain(
&key_pair, &key_pair,
&attest_ext, attest_ext.as_deref(),
&keybox, &keybox,
&params, &params,
)?; )?;