Android Rust JNI系列教程(五) Rust 使用openssl 实现Android签名验证

前言

上一章我们介绍了rust通过调用 Android api 实现签名验证的方案.本章再介绍另一种方式,通过解压base.apk读取META-INF文件夹下的RSA文件,并用openssl进行验证.

代码

1. 添加依赖

Cargo.toml

openssl = {version="0.10.43", features = ["vendored"]}
zip = "0.6.3"

zip库是用来解压base.apk的.openssl库则为我们提供了本章的核心功能------读取RSA文件.在openssl中启用vendored feature可以免去我们自行用ndk编译openssl源码的步骤.

2. rust端代码

#[no_mangle]
pub extern "system" fn Java_com_jni_rust_RustNative_getSignatureOpenssl(env: JNIEnv, _: JClass) -> jstring {

    let activity_thread_clz = match env.find_class("android/app/ActivityThread") {
        Ok(activity_thread_clz) => activity_thread_clz,
        Err(_) => panic!()
    };
    let application_value = match env.call_static_method(activity_thread_clz, "currentApplication", "()Landroid/app/Application;", &[]) {
        Ok(application_value) => { application_value }
        Err(_) => { panic!() }
    };
    let application = match application_value.l() {
        Ok(application) => { application }
        Err(_) => { panic!() }
    };

    let package_code_path = match env.call_method(application, "getPackageCodePath", "()Ljava/lang/String;", &[]) {
        Ok(package_code_path) => { package_code_path }
        Err(_) => { panic!() }
    };

    let package_code_path = match package_code_path.l() {
        Ok(package_code_path) => { package_code_path }
        Err(_) => { panic!() }
    };
    let package_code_path = JString::from(package_code_path);

    let package_code_path = match env.get_string(package_code_path) {
        Ok(package_code_path) => { package_code_path }
        Err(_) => { panic!() }
    };
    let package_code_path: String = package_code_path.into();
    log::d("openssl".to_string(), package_code_path.to_string());
    let zip_file = match fs::File::open(package_code_path) {
        Ok(zip_file) => { zip_file }
        Err(_) => { panic!() }
    };
    let mut zip = match zip::ZipArchive::new(zip_file) {
        Ok(zip) => { zip }
        Err(_) => { panic!() }
    };

    for i in 0..zip.len() {
        let mut file = match zip.by_index(i) {
            Ok(file) => { file }
            Err(_) => {
                panic!()
            }
        };
        if file.is_file() {
            if file.name().contains("META-INF") && file.name().contains(".RSA") {
                log::d("openssl".to_string(), file.name().to_string());
                let mut file_bytes: Vec = vec![];
                let _ = match file.read_to_end(&mut file_bytes) {
                    Ok(_) => {}
                    Err(_) => { panic!() }
                };
                let pkcs7 = match Pkcs7::from_der(file_bytes.as_slice()) {
                    Ok(pkcs7) => { pkcs7 }
                    Err(_) => { panic!() }
                };

                let empty_stack: Stack = match Stack::new() {
                    Ok(empty_stack) => { empty_stack }
                    Err(_) => { panic!() }
                };
                let pkcs7_flags = Pkcs7Flags::STREAM;
                let stack = match pkcs7.signers(&empty_stack, pkcs7_flags) {
                    Ok(stack) => { stack }
                    Err(_) => { panic!() }
                };

                let x509_ref = match stack.get(0) {
                    None => { panic!() }
                    Some(x509_ref) => { x509_ref }
                };
                let digest_bytes = match x509_ref.digest(MessageDigest::md5()) {
                    Ok(digest_bytes) => { digest_bytes }
                    Err(_) => { panic!() }
                };
                let sign_bytes = digest_bytes.to_vec();
                let hex_sign: String = sign_bytes.iter()
                    .map(|b| format!("{:02x}", b).to_string())
                    .collect::>().join("");
                log::d("openssl".to_string(), hex_sign.to_string());
                let hex_sign = JNIString::from(hex_sign);
                let hex_sign = env.new_string(hex_sign).unwrap();
                return hex_sign.into_raw();
            }
        }
    }
    log::e("openssl".to_string(),"no signature found".to_string());
    let result = JNIString::from("no signature found");
    let result = env.new_string(result).unwrap();
    result.into_raw()
}

代码的整体流程是通过context.getPackageCodePath()先获取到base.apk的文件位置,然后通过zip解压,找到META-INF文件夹下的RSA文件,再调用opensslapi读取这个RSA文件.这段代码唯一的难点应该是大家可能对C语言版的openssl用法(api)比较熟悉,但是不太熟悉rust版的openssl把对应api给封装成什么名字了,所以调用起来会有不知道使用哪个api的情况.这种情况我大概说下相关思路.

  1. 首先当然是从网络上搜索信息,然后官方文档.
  2. 其次官方demo.
  3. 在前两个方案都没有解决的情况下,只能自己查看源码了.

我们来大体过一下自己查看源码这个思路.

前提,我们应该知道openssl的相关api. 在我们想要读取一个.RSA文件的时候,其方法为PKCS7 *p7 = d2i_PKCS7(NULL, &signature_msg, length);.

  1. 我们要在rust openssl中找一个名为d2i_PKCS7的方法.
  2. 既然d2i_PKCS7C的方法,rust调用C的话,调用格式如下:
extern "C" {
    pub fn d2i_PKCS7(...)
}

所以我们有关键字d2i_PKCS7,extern "C".
3. 源码中搜索,果然找到了一段代码:

extern "C" {
    pub fn d2i_PKCS7(a: *mut *mut PKCS7, pp: *mut *const c_uchar, length: c_long) -> *mut PKCS7;
}
  1. 代码有了,那说明rust openssl一定是在哪里封装了这个方法.所以查找调用的部分(我用的Clion,快捷键ctrl+B).
  2. 果然找到了,在Pkcs7结构体的from_der,代码如下:
from_der! {
    /// Deserializes a DER-encoded PKCS#7 signature
    #[corresponds(d2i_PKCS7)]
    from_der,
    Pkcs7,
    ffi::d2i_PKCS7
}

所以,就有了本段中的代码:

let pkcs7 = match Pkcs7::from_der(file_bytes.as_slice()) {
          Ok(pkcs7) => { pkcs7 }
          Err(_) => { panic!() }
};

成功读取到了Pkcs7.

按照这个思路,可以查找到大多数XXX bindings for Rust的具体api,如果没找到的话也没关系,自己extern "C" ... 加上就好了.

常见问题

  1. 编译不通过,有如下输出:
 error occurred: Failed to find tool. Is `aarch64-linux-android-clang` installed?

解决方案:
我们已经在~/.cargo/config文件中配置过linkerar了.但是如果编译的代码包含C语言代码的话还需要进一步配置.在我的linux环境下,配置CC环境变量

 export CC=/NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android30-clang
  1. 同理,如果编译失败有如下输出:
error occurred: Failed to find tool. Is `aarch64-linux-android-ar` installed?

则还需配置环境变量:

export AR=/NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android-ar

扩展知识

cargo-bloat

cargo-bloat可以帮助我们粗略的分析可执行文件中各个库的空间占用,github地址:https://github.com/RazrFalcon/cargo-bloat

  1. 安装
cargo install cargo-bloat
  1. 分析我们的so文件
  • 在使用cargo-bloat时,需要先把Cargo.toml中的strip = true配置去掉
  • 查看
    命令:
cargo bloat --release --crates  --target aarch64-linux-android

输出结果

 File  .text     Size Crate
11.0%  34.4% 437.4KiB openssl_sys
 8.8%  27.7% 352.0KiB [Unknown]
 4.5%  14.1% 179.4KiB std
 1.9%   6.0%  76.3KiB zstd_sys
 0.5%   1.6%  20.0KiB miniz_oxide
 0.3%   1.0%  12.9KiB bzip2_sys
 0.3%   1.0%  12.7KiB jni
 0.3%   0.8%  10.3KiB zip
 0.1%   0.4%   4.6KiB sha1
 0.1%   0.3%   3.9KiB combine
 0.0%   0.1%   1.1KiB cesu8
 0.0%   0.1%     976B openssl
 0.0%   0.1%     944B flate2
 0.0%   0.0%     624B bzip2
 0.0%   0.0%     612B byteorder
 0.0%   0.0%     436B crc32fast
 0.0%   0.0%     400B android_logger_lite
 0.0%   0.0%     400B bytes
 0.0%   0.0%     260B zstd
 0.0%   0.0%     232B adler
 0.0%   0.0%     204B And 5 more crates. Use -n N to show more.
31.8% 100.0%   1.2MiB .text section size, the file size is 3.9MiB

Note: numbers above are a result of guesswork. They are not 100% correct and never will be.
  • 同样也可查看优化std之后的结果
    命令:
 cargo bloat --release --crates  --target aarch64-linux-android -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort 

输出结果:

 File  .text     Size Crate
19.2%  40.3% 437.3KiB openssl_sys
14.8%  31.0% 336.1KiB [Unknown]
3.4%   7.0%  76.3KiB zstd_sys
0.6%   1.3%  13.8KiB core
0.6%   1.2%  12.8KiB bzip2_sys
0.5%   1.0%  10.7KiB jni
0.4%   0.9%   9.6KiB zip
0.4%   0.9%   9.4KiB std
0.3%   0.7%   7.7KiB miniz_oxide
0.2%   0.4%   4.5KiB sha1
0.2%   0.4%   4.0KiB alloc
0.2%   0.4%   3.9KiB combine
0.0%   0.1%   1.0KiB hashbrown
0.0%   0.1%     728B flate2
0.0%   0.1%     684B cesu8
0.0%   0.1%     568B openssl
0.0%   0.0%     416B crc32fast
0.0%   0.0%     416B bzip2
0.0%   0.0%     280B byteorder
0.0%   0.0%     188B android_logger_lite
0.0%   0.0%     372B And 6 more crates. Use -n N to show more.
47.7% 100.0%   1.1MiB .text section size, the file size is 2.2MiB

Note: numbers above are a result of guesswork. They are not 100% correct and never will be.

总结

本章我们了解了使用openssl进行android签名验证的方法,虽然本方法放在整个系列的最后来讲,但并不代表它就是个完全安全的方案,只是实现签名验证的另一种思路而已,依旧存在被破解的风险.本系列文章的目标是为了让大家更加了解rustandroid协同配合的方案,也是相比于以前只能用C/C++,现在多了一个选择------rust

Android项目地址:https://github.com/tangxuesong6/Android_Rust_JNI_Demo
rust项目地址:https://github.com/tangxuesong6/Rust_JNI_Demo

你可能感兴趣的:(android开发,rust,android,开发语言)