feat(certgen): add enforcement tags to native DER encoder and teeResponses cache
Extend Rust native cert gen with software-enforced attestation tags (CALLER_NONCE, ACTIVE_DATETIME, ORIGINATION_EXPIRE_DATETIME, USAGE_EXPIRE_DATETIME, USAGE_COUNT_LIMIT, UNLOCKED_DEVICE_REQUIRED) and make NO_AUTH_REQUIRED conditional in teeEnforced. Fixes F5/F6 test failures where these tags were missing from NativeCertGen path. Add teeResponses cache so PATCH mode keys patched in onPostTransact return consistent attestation via getKeyEntry. Without this, getKeyEntry fell through to real keystore2, returning unpatched metadata. Remove dead Rust enums (KeyPurpose, SecurityLevel, VerifiedBootState) that were never referenced by the DER encoder.
This commit is contained in:
+19
-1
@@ -149,6 +149,10 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
metadata.authorizations =
|
metadata.authorizations =
|
||||||
InterceptorUtils.patchAuthorizations(metadata.authorizations, callingUid)
|
InterceptorUtils.patchAuthorizations(metadata.authorizations, callingUid)
|
||||||
patchedChains[keyId] = newChain
|
patchedChains[keyId] = newChain
|
||||||
|
teeResponses[keyId] = KeyEntryResponse().apply {
|
||||||
|
this.metadata = metadata
|
||||||
|
iSecurityLevel = original
|
||||||
|
}
|
||||||
SystemLogger.debug("Cached patched certificate chain for imported key $keyId.")
|
SystemLogger.debug("Cached patched certificate chain for imported key $keyId.")
|
||||||
return InterceptorUtils.createTypedObjectReply(metadata)
|
return InterceptorUtils.createTypedObjectReply(metadata)
|
||||||
}
|
}
|
||||||
@@ -212,6 +216,10 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
// We must clean up cached generated keys before storing the patched chain
|
// We must clean up cached generated keys before storing the patched chain
|
||||||
cleanupKeyData(keyId)
|
cleanupKeyData(keyId)
|
||||||
patchedChains[keyId] = newChain
|
patchedChains[keyId] = newChain
|
||||||
|
teeResponses[keyId] = KeyEntryResponse().apply {
|
||||||
|
this.metadata = metadata
|
||||||
|
iSecurityLevel = original
|
||||||
|
}
|
||||||
SystemLogger.debug(
|
SystemLogger.debug(
|
||||||
"Cached patched certificate chain for $keyId. (${key.alias} [${key.domain}, ${key.nspace}])"
|
"Cached patched certificate chain for $keyId. (${key.alias} [${key.domain}, ${key.nspace}])"
|
||||||
)
|
)
|
||||||
@@ -641,6 +649,13 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
idManufacturer = params.manufacturer,
|
idManufacturer = params.manufacturer,
|
||||||
idModel = params.model,
|
idModel = params.model,
|
||||||
idSecondImei = if (attestVersion >= 300) params.secondImei else null,
|
idSecondImei = if (attestVersion >= 300) params.secondImei else null,
|
||||||
|
activeDatetime = params.activeDateTime?.time ?: -1L,
|
||||||
|
originationExpireDatetime = params.originationExpireDateTime?.time ?: -1L,
|
||||||
|
usageExpireDatetime = params.usageExpireDateTime?.time ?: -1L,
|
||||||
|
usageCountLimit = params.usageCountLimit ?: -1,
|
||||||
|
callerNonce = params.callerNonce == true,
|
||||||
|
unlockedDeviceRequired = params.unlockedDeviceRequired == true,
|
||||||
|
noAuthRequired = params.noAuthRequired != false,
|
||||||
)
|
)
|
||||||
|
|
||||||
val resultBytes = NativeCertGen.generateAttestedKeyPair(config) ?: return null
|
val resultBytes = NativeCertGen.generateAttestedKeyPair(config) ?: return null
|
||||||
@@ -861,6 +876,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val generatedKeys = ConcurrentHashMap<KeyIdentifier, GeneratedKeyInfo>()
|
val generatedKeys = ConcurrentHashMap<KeyIdentifier, GeneratedKeyInfo>()
|
||||||
|
val teeResponses = ConcurrentHashMap<KeyIdentifier, KeyEntryResponse>()
|
||||||
val patchedChains = ConcurrentHashMap<KeyIdentifier, Array<Certificate>>()
|
val patchedChains = ConcurrentHashMap<KeyIdentifier, Array<Certificate>>()
|
||||||
val attestationKeys: MutableSet<KeyIdentifier> = ConcurrentHashMap.newKeySet()
|
val attestationKeys: MutableSet<KeyIdentifier> = ConcurrentHashMap.newKeySet()
|
||||||
val importedKeys: MutableSet<KeyIdentifier> = ConcurrentHashMap.newKeySet()
|
val importedKeys: MutableSet<KeyIdentifier> = ConcurrentHashMap.newKeySet()
|
||||||
@@ -868,7 +884,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
private val interceptedOperations = ConcurrentHashMap<IBinder, OperationInterceptor>()
|
private val interceptedOperations = ConcurrentHashMap<IBinder, OperationInterceptor>()
|
||||||
|
|
||||||
fun getGeneratedKeyResponse(keyId: KeyIdentifier): KeyEntryResponse? =
|
fun getGeneratedKeyResponse(keyId: KeyIdentifier): KeyEntryResponse? =
|
||||||
generatedKeys[keyId]?.response
|
generatedKeys[keyId]?.response ?: teeResponses[keyId]
|
||||||
|
|
||||||
fun findGeneratedKeyByKeyId(callingUid: Int, nspace: Long?): GeneratedKeyInfo? {
|
fun findGeneratedKeyByKeyId(callingUid: Int, nspace: Long?): GeneratedKeyInfo? {
|
||||||
if (nspace == null || nspace == 0L) return null
|
if (nspace == null || nspace == 0L) return null
|
||||||
@@ -887,6 +903,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
SystemLogger.debug("Remove generated key ${keyId}")
|
SystemLogger.debug("Remove generated key ${keyId}")
|
||||||
GeneratedKeyPersistence.delete(keyId)
|
GeneratedKeyPersistence.delete(keyId)
|
||||||
}
|
}
|
||||||
|
teeResponses.remove(keyId)
|
||||||
if (patchedChains.remove(keyId) != null) {
|
if (patchedChains.remove(keyId) != null) {
|
||||||
SystemLogger.debug("Remove patched chain for ${keyId}")
|
SystemLogger.debug("Remove patched chain for ${keyId}")
|
||||||
}
|
}
|
||||||
@@ -917,6 +934,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
val count = generatedKeys.size
|
val count = generatedKeys.size
|
||||||
val reasonMessage = reason?.let { " due to $it" } ?: ""
|
val reasonMessage = reason?.let { " due to $it" } ?: ""
|
||||||
generatedKeys.clear()
|
generatedKeys.clear()
|
||||||
|
teeResponses.clear()
|
||||||
patchedChains.clear()
|
patchedChains.clear()
|
||||||
attestationKeys.clear()
|
attestationKeys.clear()
|
||||||
importedKeys.clear()
|
importedKeys.clear()
|
||||||
|
|||||||
@@ -45,6 +45,13 @@ data class CertGenConfig(
|
|||||||
val idManufacturer: ByteArray?,
|
val idManufacturer: ByteArray?,
|
||||||
val idModel: ByteArray?,
|
val idModel: ByteArray?,
|
||||||
val idSecondImei: ByteArray?,
|
val idSecondImei: ByteArray?,
|
||||||
|
val activeDatetime: Long = -1L,
|
||||||
|
val originationExpireDatetime: Long = -1L,
|
||||||
|
val usageExpireDatetime: Long = -1L,
|
||||||
|
val usageCountLimit: Int = -1,
|
||||||
|
val callerNonce: Boolean = false,
|
||||||
|
val unlockedDeviceRequired: Boolean = false,
|
||||||
|
val noAuthRequired: Boolean = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
object NativeCertGen {
|
object NativeCertGen {
|
||||||
|
|||||||
@@ -33,6 +33,36 @@ pub fn build_attestation_extension(params: &CertGenParams) -> Result<Vec<u8>> {
|
|||||||
fn build_software_enforced(params: &CertGenParams) -> Result<Vec<u8>> {
|
fn build_software_enforced(params: &CertGenParams) -> Result<Vec<u8>> {
|
||||||
let mut fields: Vec<(u32, Vec<u8>)> = Vec::new();
|
let mut fields: Vec<(u32, Vec<u8>)> = Vec::new();
|
||||||
|
|
||||||
|
// Tag 303: CALLER_NONCE — NULL (presence = true)
|
||||||
|
if params.caller_nonce {
|
||||||
|
fields.push((303, enc_null()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tag 400: ACTIVE_DATETIME — INTEGER (milliseconds)
|
||||||
|
if params.active_datetime >= 0 {
|
||||||
|
fields.push((400, enc_integer(params.active_datetime)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tag 401: ORIGINATION_EXPIRE_DATETIME — INTEGER (milliseconds)
|
||||||
|
if params.origination_expire_datetime >= 0 {
|
||||||
|
fields.push((401, enc_integer(params.origination_expire_datetime)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tag 402: USAGE_EXPIRE_DATETIME — INTEGER (milliseconds)
|
||||||
|
if params.usage_expire_datetime >= 0 {
|
||||||
|
fields.push((402, enc_integer(params.usage_expire_datetime)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tag 405: USAGE_COUNT_LIMIT — INTEGER
|
||||||
|
if params.usage_count_limit >= 0 {
|
||||||
|
fields.push((405, enc_integer(params.usage_count_limit as i64)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tag 509: UNLOCKED_DEVICE_REQUIRED — NULL
|
||||||
|
if params.unlocked_device_required {
|
||||||
|
fields.push((509, enc_null()));
|
||||||
|
}
|
||||||
|
|
||||||
// Tag 701: CREATION_DATETIME — INTEGER (milliseconds)
|
// Tag 701: CREATION_DATETIME — INTEGER (milliseconds)
|
||||||
fields.push((701, enc_integer(params.creation_datetime)));
|
fields.push((701, enc_integer(params.creation_datetime)));
|
||||||
|
|
||||||
@@ -77,8 +107,10 @@ fn build_tee_enforced(params: &CertGenParams) -> Result<Vec<u8>> {
|
|||||||
fields.push((10, enc_integer(curve as i32 as i64)));
|
fields.push((10, enc_integer(curve as i32 as i64)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tag 503: NO_AUTH_REQUIRED — NULL (presence = true)
|
// Tag 503: NO_AUTH_REQUIRED — NULL (conditional)
|
||||||
fields.push((503, enc_null()));
|
if params.no_auth_required {
|
||||||
|
fields.push((503, enc_null()));
|
||||||
|
}
|
||||||
|
|
||||||
// Tag 702: ORIGIN — INTEGER 0 (GENERATED)
|
// Tag 702: ORIGIN — INTEGER 0 (GENERATED)
|
||||||
fields.push((702, enc_integer(0)));
|
fields.push((702, enc_integer(0)));
|
||||||
@@ -519,6 +551,44 @@ mod tests {
|
|||||||
assert_eq!(tags, sorted, "AuthorizationList fields must be sorted by tag number");
|
assert_eq!(tags, sorted, "AuthorizationList fields must be sorted by tag number");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_enforcement_tags_in_software_enforced() {
|
||||||
|
let mut params = make_test_params();
|
||||||
|
params.usage_count_limit = 3;
|
||||||
|
params.unlocked_device_required = true;
|
||||||
|
params.caller_nonce = true;
|
||||||
|
params.active_datetime = 1709913600000;
|
||||||
|
let sw = build_software_enforced(¶ms).unwrap();
|
||||||
|
let inner = skip_tlv_header(&sw);
|
||||||
|
let tags = extract_tag_numbers(inner);
|
||||||
|
assert!(tags.contains(&303), "CALLER_NONCE (303) must be in softwareEnforced");
|
||||||
|
assert!(tags.contains(&400), "ACTIVE_DATETIME (400) must be in softwareEnforced");
|
||||||
|
assert!(tags.contains(&405), "USAGE_COUNT_LIMIT (405) must be in softwareEnforced");
|
||||||
|
assert!(tags.contains(&509), "UNLOCKED_DEVICE_REQUIRED (509) must be in softwareEnforced");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_no_auth_required_conditional() {
|
||||||
|
let mut params = make_test_params();
|
||||||
|
params.no_auth_required = false;
|
||||||
|
let tee = build_tee_enforced(¶ms).unwrap();
|
||||||
|
let inner = skip_tlv_header(&tee);
|
||||||
|
let tags = extract_tag_numbers(inner);
|
||||||
|
assert!(!tags.contains(&503), "NO_AUTH_REQUIRED (503) must be absent when false");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_enforcement_tags_omitted_when_unset() {
|
||||||
|
let params = make_test_params();
|
||||||
|
let sw = build_software_enforced(¶ms).unwrap();
|
||||||
|
let inner = skip_tlv_header(&sw);
|
||||||
|
let tags = extract_tag_numbers(inner);
|
||||||
|
assert!(!tags.contains(&303), "CALLER_NONCE should be absent when false");
|
||||||
|
assert!(!tags.contains(&400), "ACTIVE_DATETIME should be absent when -1");
|
||||||
|
assert!(!tags.contains(&405), "USAGE_COUNT_LIMIT should be absent when -1");
|
||||||
|
assert!(!tags.contains(&509), "UNLOCKED_DEVICE_REQUIRED should be absent when false");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_full_extension_roundtrip() {
|
fn test_full_extension_roundtrip() {
|
||||||
let params = make_test_params();
|
let params = make_test_params();
|
||||||
@@ -568,6 +638,13 @@ mod tests {
|
|||||||
id_manufacturer: None,
|
id_manufacturer: None,
|
||||||
id_model: None,
|
id_model: None,
|
||||||
id_second_imei: None,
|
id_second_imei: None,
|
||||||
|
active_datetime: -1,
|
||||||
|
origination_expire_datetime: -1,
|
||||||
|
usage_expire_datetime: -1,
|
||||||
|
usage_count_limit: -1,
|
||||||
|
caller_nonce: false,
|
||||||
|
unlocked_device_required: false,
|
||||||
|
no_auth_required: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -199,6 +199,14 @@ fn extract_config(env: &mut JNIEnv, config: &JObject) -> Result<CertGenParams> {
|
|||||||
let id_model = get_nullable_byte_array(env, config, "idModel")?;
|
let id_model = get_nullable_byte_array(env, config, "idModel")?;
|
||||||
let id_second_imei = get_nullable_byte_array(env, config, "idSecondImei")?;
|
let id_second_imei = get_nullable_byte_array(env, config, "idSecondImei")?;
|
||||||
|
|
||||||
|
let active_datetime = get_long(env, config, "activeDatetime")?;
|
||||||
|
let origination_expire_datetime = get_long(env, config, "originationExpireDatetime")?;
|
||||||
|
let usage_expire_datetime = get_long(env, config, "usageExpireDatetime")?;
|
||||||
|
let usage_count_limit = get_int(env, config, "usageCountLimit")?;
|
||||||
|
let caller_nonce = get_boolean(env, config, "callerNonce")?;
|
||||||
|
let unlocked_device_required = get_boolean(env, config, "unlockedDeviceRequired")?;
|
||||||
|
let no_auth_required = get_boolean(env, config, "noAuthRequired")?;
|
||||||
|
|
||||||
Ok(CertGenParams {
|
Ok(CertGenParams {
|
||||||
algorithm: Algorithm::try_from(algorithm)?,
|
algorithm: Algorithm::try_from(algorithm)?,
|
||||||
key_size: key_size as u32,
|
key_size: key_size as u32,
|
||||||
@@ -238,6 +246,13 @@ fn extract_config(env: &mut JNIEnv, config: &JObject) -> Result<CertGenParams> {
|
|||||||
id_manufacturer,
|
id_manufacturer,
|
||||||
id_model,
|
id_model,
|
||||||
id_second_imei,
|
id_second_imei,
|
||||||
|
active_datetime,
|
||||||
|
origination_expire_datetime,
|
||||||
|
usage_expire_datetime,
|
||||||
|
usage_count_limit,
|
||||||
|
caller_nonce,
|
||||||
|
unlocked_device_required,
|
||||||
|
no_auth_required,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,6 +268,10 @@ fn get_long(env: &mut JNIEnv, obj: &JObject, name: &str) -> Result<i64> {
|
|||||||
Ok(env.get_field(obj, name, "J")?.j()?)
|
Ok(env.get_field(obj, name, "J")?.j()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_boolean(env: &mut JNIEnv, obj: &JObject, name: &str) -> Result<bool> {
|
||||||
|
Ok(env.get_field(obj, name, "Z")?.z()?)
|
||||||
|
}
|
||||||
|
|
||||||
fn get_byte_array(env: &mut JNIEnv, obj: &JObject, name: &'static str) -> Result<Vec<u8>> {
|
fn get_byte_array(env: &mut JNIEnv, obj: &JObject, name: &'static str) -> Result<Vec<u8>> {
|
||||||
let field = env.get_field(obj, name, "[B")?.l()?;
|
let field = env.get_field(obj, name, "[B")?.l()?;
|
||||||
if field.is_null() {
|
if field.is_null() {
|
||||||
|
|||||||
@@ -42,35 +42,6 @@ impl TryFrom<i32> for EcCurve {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
#[repr(i32)]
|
|
||||||
pub enum KeyPurpose {
|
|
||||||
Encrypt = 0,
|
|
||||||
Decrypt = 1,
|
|
||||||
Sign = 2,
|
|
||||||
Verify = 3,
|
|
||||||
WrapKey = 5,
|
|
||||||
AgreeKey = 6,
|
|
||||||
AttestKey = 7,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
#[repr(i32)]
|
|
||||||
pub enum SecurityLevel {
|
|
||||||
Software = 0,
|
|
||||||
TrustedEnvironment = 1,
|
|
||||||
StrongBox = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
#[repr(i32)]
|
|
||||||
pub enum VerifiedBootState {
|
|
||||||
Verified = 0,
|
|
||||||
SelfSigned = 1,
|
|
||||||
Unverified = 2,
|
|
||||||
Failed = 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CertGenParams {
|
pub struct CertGenParams {
|
||||||
pub algorithm: Algorithm,
|
pub algorithm: Algorithm,
|
||||||
pub key_size: u32,
|
pub key_size: u32,
|
||||||
@@ -114,6 +85,14 @@ pub struct CertGenParams {
|
|||||||
pub id_manufacturer: Option<Vec<u8>>,
|
pub id_manufacturer: Option<Vec<u8>>,
|
||||||
pub id_model: Option<Vec<u8>>,
|
pub id_model: Option<Vec<u8>>,
|
||||||
pub id_second_imei: Option<Vec<u8>>,
|
pub id_second_imei: Option<Vec<u8>>,
|
||||||
|
|
||||||
|
pub active_datetime: i64,
|
||||||
|
pub origination_expire_datetime: i64,
|
||||||
|
pub usage_expire_datetime: i64,
|
||||||
|
pub usage_count_limit: i32,
|
||||||
|
pub caller_nonce: bool,
|
||||||
|
pub unlocked_device_required: bool,
|
||||||
|
pub no_auth_required: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GeneratedKeyPair {
|
pub struct GeneratedKeyPair {
|
||||||
|
|||||||
Reference in New Issue
Block a user