diff --git a/native-certgen/src/lib.rs b/native-certgen/src/lib.rs index 147ae6e..f91365f 100644 --- a/native-certgen/src/lib.rs +++ b/native-certgen/src/lib.rs @@ -1,3 +1,5 @@ +#![deny(clippy::unwrap_used, clippy::expect_used)] + mod error; mod types; mod keygen; @@ -5,3 +7,320 @@ pub mod keybox; pub mod attestation; pub mod certbuilder; pub mod logging; + +use jni::objects::{JByteArray, JClass, JIntArray, JObject, JString}; +use jni::sys::{jboolean, jbyteArray, jstring}; +use jni::JNIEnv; + +use crate::error::{CertGenError, Result}; +use crate::types::{Algorithm, CertGenParams, EcCurve}; + +// --------------------------------------------------------------------------- +// JNI entry: generateAttestedKeyPair +// --------------------------------------------------------------------------- + +#[no_mangle] +pub extern "system" fn Java_org_matrix_TEESimulator_pki_NativeCertGen_generateAttestedKeyPair( + mut env: JNIEnv, + _class: JClass, + config: JObject, +) -> jbyteArray { + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + generate_attested_inner(&mut env, &config) + })); + + match result { + Ok(Ok(raw)) => raw, + Ok(Err(e)) => { + tracing::error!(%e, "generateAttestedKeyPair failed"); + let _ = env.throw_new( + "java/lang/RuntimeException", + format!("NativeCertGen: {e}"), + ); + std::ptr::null_mut() + } + Err(_) => { + tracing::error!("generateAttestedKeyPair panicked"); + let _ = env.throw_new( + "java/lang/RuntimeException", + "NativeCertGen: internal panic", + ); + std::ptr::null_mut() + } + } +} + +fn generate_attested_inner(env: &mut JNIEnv, config: &JObject) -> Result { + let params = extract_config(env, config)?; + + let key_pair = keygen::generate_key_pair( + params.algorithm, + params.key_size, + params.ec_curve, + params.rsa_public_exponent, + )?; + + let keybox = keybox::parse_keybox(¶ms.keybox_cert_chain, ¶ms.keybox_private_key)?; + + let attest_ext = attestation::build_attestation_extension(¶ms)?; + + let cert_chain = certbuilder::build_certificate_chain( + &key_pair, + &attest_ext, + &keybox, + ¶ms, + )?; + + let blob = assemble_result(&key_pair.private_key_pkcs8, &cert_chain); + + let out = env.byte_array_from_slice(&blob)?; + Ok(out.into_raw()) +} + +// --------------------------------------------------------------------------- +// JNI entry: initLogging +// --------------------------------------------------------------------------- + +#[no_mangle] +pub extern "system" fn Java_org_matrix_TEESimulator_pki_NativeCertGen_initLogging( + mut env: JNIEnv, + _class: JClass, + verbose: jboolean, + log_dir: JString, +) -> jboolean { + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + init_logging_inner(&mut env, verbose, &log_dir) + })); + + match result { + Ok(Ok(())) => 1, + Ok(Err(e)) => { + let _ = env.throw_new( + "java/lang/RuntimeException", + format!("NativeCertGen initLogging: {e}"), + ); + 0 + } + Err(_) => { + let _ = env.throw_new( + "java/lang/RuntimeException", + "NativeCertGen initLogging: internal panic", + ); + 0 + } + } +} + +fn init_logging_inner(env: &mut JNIEnv, verbose: jboolean, log_dir: &JString) -> Result<()> { + let dir: String = env.get_string(log_dir)?.into(); + logging::init(verbose != 0, &dir, 2, 3) + .map_err(|e| CertGenError::Jni(format!("logging init failed: {e}")))?; + Ok(()) +} + +// --------------------------------------------------------------------------- +// JNI entry: dumpLogs +// --------------------------------------------------------------------------- + +#[no_mangle] +pub extern "system" fn Java_org_matrix_TEESimulator_pki_NativeCertGen_dumpLogs( + mut env: JNIEnv, + _class: JClass, + _log_dir: JString, + _base_dir: JString, +) -> jstring { + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + dump_logs_inner(&mut env) + })); + + match result { + Ok(Ok(raw)) => raw, + Ok(Err(e)) => { + tracing::error!(%e, "dumpLogs failed"); + std::ptr::null_mut() + } + Err(_) => { + tracing::error!("dumpLogs panicked"); + std::ptr::null_mut() + } + } +} + +fn dump_logs_inner(env: &mut JNIEnv) -> Result { + logging::dump::execute_dump() + .map_err(|e| CertGenError::Jni(format!("dump failed: {e}")))?; + + // Read the dump path written by execute_dump + let path = std::fs::read_to_string("/data/adb/tricky_store/.dump_path") + .map_err(|e| CertGenError::Jni(format!("read dump path: {e}")))?; + + let jpath = env.new_string(&path)?; + Ok(jpath.into_raw()) +} + +// --------------------------------------------------------------------------- +// Config extraction from Java CertGenConfig object +// --------------------------------------------------------------------------- + +fn extract_config(env: &mut JNIEnv, config: &JObject) -> Result { + let algorithm = get_int(env, config, "algorithm")?; + let key_size = get_int(env, config, "keySize")?; + let ec_curve_raw = get_int(env, config, "ecCurve")?; + let rsa_pub_exp = get_long(env, config, "rsaPublicExponent")?; + + let cert_not_before = get_long(env, config, "certNotBefore")?; + let cert_not_after = get_long(env, config, "certNotAfter")?; + let security_level = get_int(env, config, "securityLevel")?; + let attest_version = get_int(env, config, "attestVersion")?; + let keymaster_version = get_int(env, config, "keymasterVersion")?; + let os_version = get_int(env, config, "osVersion")?; + let os_patch_level = get_int(env, config, "osPatchLevel")?; + let vendor_patch_level = get_int(env, config, "vendorPatchLevel")?; + let boot_patch_level = get_int(env, config, "bootPatchLevel")?; + let creation_datetime = get_long(env, config, "creationDatetime")?; + + let attestation_challenge = get_nullable_byte_array(env, config, "attestationChallenge")?; + let purposes = get_int_array(env, config, "purposes")?; + let digests = get_int_array(env, config, "digests")?; + let cert_serial = get_nullable_byte_array(env, config, "certSerial")?; + let cert_subject = get_nullable_byte_array(env, config, "certSubject")?; + let keybox_private_key = get_byte_array(env, config, "keyboxPrivateKey")?; + let keybox_cert_chain = get_byte_array(env, config, "keyboxCertChain")?; + let boot_key = get_byte_array(env, config, "bootKey")?; + let boot_hash = get_byte_array(env, config, "bootHash")?; + let attestation_app_id = get_byte_array(env, config, "attestationApplicationId")?; + let module_hash = get_nullable_byte_array(env, config, "moduleHash")?; + + let id_brand = get_nullable_byte_array(env, config, "idBrand")?; + let id_device = get_nullable_byte_array(env, config, "idDevice")?; + let id_product = get_nullable_byte_array(env, config, "idProduct")?; + let id_serial = get_nullable_byte_array(env, config, "idSerial")?; + let id_imei = get_nullable_byte_array(env, config, "idImei")?; + let id_meid = get_nullable_byte_array(env, config, "idMeid")?; + let id_manufacturer = get_nullable_byte_array(env, config, "idManufacturer")?; + let id_model = get_nullable_byte_array(env, config, "idModel")?; + let id_second_imei = get_nullable_byte_array(env, config, "idSecondImei")?; + + Ok(CertGenParams { + algorithm: Algorithm::try_from(algorithm)?, + key_size: key_size as u32, + ec_curve: if algorithm == 3 { + Some(EcCurve::try_from(ec_curve_raw)?) + } else { + None + }, + rsa_public_exponent: rsa_pub_exp as u64, + attestation_challenge, + purposes, + digests, + cert_serial, + cert_subject, + cert_not_before, + cert_not_after, + keybox_private_key, + keybox_cert_chain, + security_level, + attest_version, + keymaster_version, + os_version, + os_patch_level, + vendor_patch_level, + boot_patch_level, + boot_key, + boot_hash, + creation_datetime, + attestation_application_id: attestation_app_id, + module_hash, + id_brand, + id_device, + id_product, + id_serial, + id_imei, + id_meid, + id_manufacturer, + id_model, + id_second_imei, + }) +} + +// --------------------------------------------------------------------------- +// JNI field accessor helpers — called 35+ times, justifies the abstraction +// --------------------------------------------------------------------------- + +fn get_int(env: &mut JNIEnv, obj: &JObject, name: &str) -> Result { + Ok(env.get_field(obj, name, "I")?.i()?) +} + +fn get_long(env: &mut JNIEnv, obj: &JObject, name: &str) -> Result { + Ok(env.get_field(obj, name, "J")?.j()?) +} + +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() { + return Err(CertGenError::NullParam(name)); + } + let arr: JByteArray = field.into(); + let len = env.get_array_length(&arr)?; + let mut buf = vec![0i8; len as usize]; + env.get_byte_array_region(&arr, 0, &mut buf)?; + env.delete_local_ref(arr)?; + Ok(buf.into_iter().map(|b| b as u8).collect()) +} + +fn get_nullable_byte_array( + env: &mut JNIEnv, + obj: &JObject, + name: &str, +) -> Result>> { + let field = env.get_field(obj, name, "[B")?.l()?; + if field.is_null() { + return Ok(None); + } + let arr: JByteArray = field.into(); + let len = env.get_array_length(&arr)?; + let mut buf = vec![0i8; len as usize]; + env.get_byte_array_region(&arr, 0, &mut buf)?; + env.delete_local_ref(arr)?; + Ok(Some(buf.into_iter().map(|b| b as u8).collect())) +} + +fn get_int_array(env: &mut JNIEnv, obj: &JObject, name: &str) -> Result> { + let field = env.get_field(obj, name, "[I")?.l()?; + if field.is_null() { + return Ok(vec![]); + } + let arr: JIntArray = field.into(); + let len = env.get_array_length(&arr)?; + let mut buf = vec![0i32; len as usize]; + env.get_int_array_region(&arr, 0, &mut buf)?; + env.delete_local_ref(arr)?; + Ok(buf) +} + +// --------------------------------------------------------------------------- +// Binary result assembly (doc 09 section 4.1) +// --------------------------------------------------------------------------- + +fn assemble_result(private_key: &[u8], cert_chain: &[Vec]) -> Vec { + let total = 4 + private_key.len() + + 4 + + cert_chain.iter().map(|c| 4 + c.len()).sum::(); + + let mut buf = Vec::with_capacity(total); + + // Private key segment + buf.extend_from_slice(&(private_key.len() as u32).to_be_bytes()); + buf.extend_from_slice(private_key); + + // Cert count + buf.extend_from_slice(&(cert_chain.len() as u32).to_be_bytes()); + + // Each cert: length-prefixed DER + for cert in cert_chain { + buf.extend_from_slice(&(cert.len() as u32).to_be_bytes()); + buf.extend_from_slice(cert); + } + + buf +}