当前位置:   article > 正文

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

android rust crates

前言

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

代码

1. 添加依赖

Cargo.toml

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

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<u8> = 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<X509> = 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::<Vec<String>>().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()
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96

代码的整体流程是通过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(...)
}
  • 1
  • 2
  • 3

所以我们有关键字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
  • 2
  • 3
  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
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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

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

成功读取到了Pkcs7.

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

常见问题

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

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

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

则还需配置环境变量:

export AR=<path>/NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android-ar
  • 1

扩展知识

cargo-bloat

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

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

输出结果

 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.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 同样也可查看优化std之后的结果
    命令:
 cargo bloat --release --crates  --target aarch64-linux-android -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort 
  • 1

输出结果:

 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.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

总结

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/582006
推荐阅读
相关标签
  

闽ICP备14008679号