use std::fs::File; use std::io; use std::io::{Cursor, Read, Seek, SeekFrom}; use std::mem::size_of_val; use std::os::fd::{FromRawFd, RawFd}; use base::*; const EOCD_MAGIC: u32 = 0x06054B50; const APK_SIGNING_BLOCK_MAGIC: [u8; 16] = *b"APK Sig Block 42"; const SIGNATURE_SCHEME_V2_MAGIC: u32 = 0x7109871A; macro_rules! bad_apk { ($msg:literal) => { io::Error::new(io::ErrorKind::InvalidData, concat!("cert: ", $msg)) }; } /* * A v2/v3 signed APK has the format as following * * +---------------+ * | zip content | * +---------------+ * | signing block | * +---------------+ * | central dir | * +---------------+ * | EOCD | * +---------------+ * * Scan from end of file to find EOCD, and figure our way back to the * offset of the signing block. Next, directly extract the certificate * from the v2 signature block. * * All structures above are mostly just for documentation purpose. * * This method extracts the first certificate of the first signer * within the APK v2 signature block. */ pub fn read_certificate(fd: RawFd, version: i32) -> Vec { fn inner(apk: &mut File, version: i32) -> io::Result> { let mut u32_val = 0u32; let mut u64_val = 0u64; // Find EOCD for i in 0u16.. { let mut comment_sz = 0u16; apk.seek(SeekFrom::End(-(size_of_val(&comment_sz) as i64) - i as i64))?; apk.read_pod(&mut comment_sz)?; if comment_sz == i { apk.seek(SeekFrom::Current(-22))?; let mut magic = 0u32; apk.read_pod(&mut magic)?; if magic == EOCD_MAGIC { break; } } if i == 0xffff { return Err(bad_apk!("invalid APK format")); } } // We are now at EOCD + sizeof(magic) // Seek and read central_dir_off to find the start of the central directory let mut central_dir_off = 0u32; apk.seek(SeekFrom::Current(12))?; apk.read_pod(&mut central_dir_off)?; // Code for parse APK comment to get version code if version >= 0 { let mut comment_sz = 0u16; apk.read_pod(&mut comment_sz)?; let mut comment = vec![0u8; comment_sz as usize]; apk.read_exact(&mut comment)?; let mut comment = Cursor::new(&comment); let mut apk_ver = 0; comment.foreach_props(|k, v| { if k == "versionCode" { apk_ver = v.parse::().unwrap_or(0); false } else { true } }); if version > apk_ver { return Err(bad_apk!("APK version too low")); } } // Next, find the start of the APK signing block apk.seek(SeekFrom::Start((central_dir_off - 24) as u64))?; apk.read_pod(&mut u64_val)?; // u64_value = block_sz_ let mut magic = [0u8; 16]; apk.read_exact(&mut magic)?; if magic != APK_SIGNING_BLOCK_MAGIC { return Err(bad_apk!("invalid signing block magic")); } let mut signing_blk_sz = 0u64; apk.seek(SeekFrom::Current( -(u64_val as i64) - (size_of_val(&signing_blk_sz) as i64), ))?; apk.read_pod(&mut signing_blk_sz)?; if signing_blk_sz != u64_val { return Err(bad_apk!("invalid signing block size")); } // Finally, we are now at the beginning of the id-value pair sequence loop { apk.read_pod(&mut u64_val)?; // id-value pair length if u64_val == signing_blk_sz { break; } let mut id = 0u32; apk.read_pod(&mut id)?; if id == SIGNATURE_SCHEME_V2_MAGIC { // Skip [signer sequence length] + [1st signer length] + [signed data length] apk.seek(SeekFrom::Current((size_of_val(&u32_val) * 3) as i64))?; apk.read_pod(&mut u32_val)?; // digest sequence length apk.seek(SeekFrom::Current(u32_val as i64))?; // skip all digests apk.seek(SeekFrom::Current(size_of_val(&u32_val) as i64))?; // cert sequence length apk.read_pod(&mut u32_val)?; // 1st cert length let mut cert = vec![0; u32_val as usize]; apk.read_exact(cert.as_mut())?; return Ok(cert); } else { // Skip this id-value pair apk.seek(SeekFrom::Current( u64_val as i64 - (size_of_val(&id) as i64), ))?; } } Err(bad_apk!("cannot find certificate")) } if fd == -1 { return vec![]; } let mut file = unsafe { File::from_raw_fd(fd) }; let r = inner(&mut file, version).log().unwrap_or(vec![]); std::mem::forget(file); r }