diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt index 63e3f92..0930af1 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt @@ -149,6 +149,10 @@ class KeyMintSecurityLevelInterceptor( metadata.authorizations = InterceptorUtils.patchAuthorizations(metadata.authorizations, callingUid) patchedChains[keyId] = newChain + teeResponses[keyId] = KeyEntryResponse().apply { + this.metadata = metadata + iSecurityLevel = original + } SystemLogger.debug("Cached patched certificate chain for imported key $keyId.") return InterceptorUtils.createTypedObjectReply(metadata) } @@ -212,6 +216,10 @@ class KeyMintSecurityLevelInterceptor( // We must clean up cached generated keys before storing the patched chain cleanupKeyData(keyId) patchedChains[keyId] = newChain + teeResponses[keyId] = KeyEntryResponse().apply { + this.metadata = metadata + iSecurityLevel = original + } SystemLogger.debug( "Cached patched certificate chain for $keyId. (${key.alias} [${key.domain}, ${key.nspace}])" ) @@ -641,6 +649,13 @@ class KeyMintSecurityLevelInterceptor( idManufacturer = params.manufacturer, idModel = params.model, 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 @@ -861,6 +876,7 @@ class KeyMintSecurityLevelInterceptor( } val generatedKeys = ConcurrentHashMap() + val teeResponses = ConcurrentHashMap() val patchedChains = ConcurrentHashMap>() val attestationKeys: MutableSet = ConcurrentHashMap.newKeySet() val importedKeys: MutableSet = ConcurrentHashMap.newKeySet() @@ -868,7 +884,7 @@ class KeyMintSecurityLevelInterceptor( private val interceptedOperations = ConcurrentHashMap() fun getGeneratedKeyResponse(keyId: KeyIdentifier): KeyEntryResponse? = - generatedKeys[keyId]?.response + generatedKeys[keyId]?.response ?: teeResponses[keyId] fun findGeneratedKeyByKeyId(callingUid: Int, nspace: Long?): GeneratedKeyInfo? { if (nspace == null || nspace == 0L) return null @@ -887,6 +903,7 @@ class KeyMintSecurityLevelInterceptor( SystemLogger.debug("Remove generated key ${keyId}") GeneratedKeyPersistence.delete(keyId) } + teeResponses.remove(keyId) if (patchedChains.remove(keyId) != null) { SystemLogger.debug("Remove patched chain for ${keyId}") } @@ -917,6 +934,7 @@ class KeyMintSecurityLevelInterceptor( val count = generatedKeys.size val reasonMessage = reason?.let { " due to $it" } ?: "" generatedKeys.clear() + teeResponses.clear() patchedChains.clear() attestationKeys.clear() importedKeys.clear() diff --git a/app/src/main/java/org/matrix/TEESimulator/pki/NativeCertGen.kt b/app/src/main/java/org/matrix/TEESimulator/pki/NativeCertGen.kt index 0865021..649ce64 100644 --- a/app/src/main/java/org/matrix/TEESimulator/pki/NativeCertGen.kt +++ b/app/src/main/java/org/matrix/TEESimulator/pki/NativeCertGen.kt @@ -45,6 +45,13 @@ data class CertGenConfig( val idManufacturer: ByteArray?, val idModel: 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 { diff --git a/native-certgen/src/attestation.rs b/native-certgen/src/attestation.rs index d379d89..82a964e 100644 --- a/native-certgen/src/attestation.rs +++ b/native-certgen/src/attestation.rs @@ -33,6 +33,36 @@ pub fn build_attestation_extension(params: &CertGenParams) -> Result> { fn build_software_enforced(params: &CertGenParams) -> Result> { let mut fields: Vec<(u32, Vec)> = 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) fields.push((701, enc_integer(params.creation_datetime))); @@ -77,8 +107,10 @@ fn build_tee_enforced(params: &CertGenParams) -> Result> { fields.push((10, enc_integer(curve as i32 as i64))); } - // Tag 503: NO_AUTH_REQUIRED — NULL (presence = true) - fields.push((503, enc_null())); + // Tag 503: NO_AUTH_REQUIRED — NULL (conditional) + if params.no_auth_required { + fields.push((503, enc_null())); + } // Tag 702: ORIGIN — INTEGER 0 (GENERATED) fields.push((702, enc_integer(0))); @@ -519,6 +551,44 @@ mod tests { 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] fn test_full_extension_roundtrip() { let params = make_test_params(); @@ -568,6 +638,13 @@ mod tests { id_manufacturer: None, id_model: 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, } } diff --git a/native-certgen/src/lib.rs b/native-certgen/src/lib.rs index 00c93c4..0e1abc3 100644 --- a/native-certgen/src/lib.rs +++ b/native-certgen/src/lib.rs @@ -199,6 +199,14 @@ fn extract_config(env: &mut JNIEnv, config: &JObject) -> Result { let id_model = get_nullable_byte_array(env, config, "idModel")?; 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 { algorithm: Algorithm::try_from(algorithm)?, key_size: key_size as u32, @@ -238,6 +246,13 @@ fn extract_config(env: &mut JNIEnv, config: &JObject) -> Result { id_manufacturer, id_model, 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 { Ok(env.get_field(obj, name, "J")?.j()?) } +fn get_boolean(env: &mut JNIEnv, obj: &JObject, name: &str) -> Result { + Ok(env.get_field(obj, name, "Z")?.z()?) +} + fn get_byte_array(env: &mut JNIEnv, obj: &JObject, name: &'static str) -> Result> { let field = env.get_field(obj, name, "[B")?.l()?; if field.is_null() { diff --git a/native-certgen/src/types.rs b/native-certgen/src/types.rs index ec77f33..806c1ed 100644 --- a/native-certgen/src/types.rs +++ b/native-certgen/src/types.rs @@ -42,35 +42,6 @@ impl TryFrom 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 algorithm: Algorithm, pub key_size: u32, @@ -114,6 +85,14 @@ pub struct CertGenParams { pub id_manufacturer: Option>, pub id_model: Option>, pub id_second_imei: Option>, + + 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 {