refactor: apk-parser crate

This commit is contained in:
2025-02-14 15:56:12 +00:00
parent 3df55189ad
commit c0b7312aae
9 changed files with 1409 additions and 415 deletions

2
.gitignore vendored
View File

@ -1,2 +1,2 @@
/target **/target
.idea/ .idea/

20
Cargo.lock generated
View File

@ -124,6 +124,17 @@ dependencies = [
"zip", "zip",
] ]
[[package]]
name = "apk-parser"
version = "0.1.0"
dependencies = [
"anyhow",
"apk",
"byteorder",
"hex",
"log",
]
[[package]] [[package]]
name = "arraydeque" name = "arraydeque"
version = "0.5.1" version = "0.5.1"
@ -978,6 +989,12 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]] [[package]]
name = "hex-conservative" name = "hex-conservative"
version = "0.1.2" version = "0.1.2"
@ -1463,9 +1480,8 @@ name = "nap"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"apk", "apk-parser",
"async-trait", "async-trait",
"byteorder",
"clap", "clap",
"config", "config",
"dialoguer", "dialoguer",

View File

@ -20,10 +20,9 @@ reqwest = { version = "0.12.12", features = ["json", "stream"] }
tokio = { version = "1.43.0", features = ["fs", "rt", "macros", "rt-multi-thread"] } tokio = { version = "1.43.0", features = ["fs", "rt", "macros", "rt-multi-thread"] }
serde = { version = "1.0.217", features = ["derive"] } serde = { version = "1.0.217", features = ["derive"] }
async-trait = "0.1.86" async-trait = "0.1.86"
apk = "0.4.0"
semver = "1.0.25" semver = "1.0.25"
indicatif = "0.17.11" indicatif = "0.17.11"
dialoguer = "0.11.0" dialoguer = "0.11.0"
env_logger = "0.11.6" env_logger = "0.11.6"
sha2 = "0.10.8" sha2 = "0.10.8"
byteorder = "1.5.0" apk-parser = { path = "./apk-parser" }

878
apk-parser/Cargo.lock generated Normal file
View File

@ -0,0 +1,878 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "anyhow"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
[[package]]
name = "apk"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cb37b529daddddf129612580831db21a538d1aa2798b367039e9316762c81a7"
dependencies = [
"anyhow",
"byteorder",
"quick-xml",
"rasn",
"rasn-pkix",
"roxmltree",
"rsa",
"serde",
"sha2",
"tracing",
"xcommon",
"zip",
]
[[package]]
name = "apk-parser"
version = "0.1.0"
dependencies = [
"anyhow",
"apk",
"byteorder",
"hex",
"log",
]
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "base64ct"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitvec"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
dependencies = [
"funty",
"radium",
"tap",
"wyz",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "bytemuck"
version = "1.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
dependencies = [
"num-traits",
]
[[package]]
name = "color_quant"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "const-oid"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crc32fast"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "der"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de"
dependencies = [
"const-oid",
"pem-rfc7468",
"zeroize",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"const-oid",
"crypto-common",
]
[[package]]
name = "doc-comment"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "dunce"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "fdeflate"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
dependencies = [
"simd-adler32",
]
[[package]]
name = "flate2"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "funty"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "image"
version = "0.24.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d"
dependencies = [
"bytemuck",
"byteorder",
"color_quant",
"num-traits",
"png",
]
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "konst"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "330f0e13e6483b8c34885f7e6c9f19b1a7bd449c673fbb948a51c99d66ef74f4"
dependencies = [
"konst_macro_rules",
]
[[package]]
name = "konst_macro_rules"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37"
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
dependencies = [
"spin",
]
[[package]]
name = "libc"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "libm"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
[[package]]
name = "log"
version = "0.4.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b"
dependencies = [
"adler2",
"simd-adler32",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "num-bigint"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-bigint-dig"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
dependencies = [
"byteorder",
"lazy_static",
"libm",
"num-integer",
"num-iter",
"num-traits",
"rand",
"smallvec",
"zeroize",
]
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
"libm",
]
[[package]]
name = "once_cell"
version = "1.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
[[package]]
name = "pem"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8"
dependencies = [
"base64",
]
[[package]]
name = "pem-rfc7468"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d159833a9105500e0398934e205e0773f0b27529557134ecfc51c27646adac"
dependencies = [
"base64ct",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pkcs1"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eff33bdbdfc54cc98a2eca766ebdec3e1b8fb7387523d5c9c9a2891da856f719"
dependencies = [
"der",
"pkcs8",
"spki",
"zeroize",
]
[[package]]
name = "pkcs8"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba"
dependencies = [
"der",
"spki",
]
[[package]]
name = "png"
version = "0.17.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
dependencies = [
"bitflags",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide",
]
[[package]]
name = "ppv-lite86"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro2"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quick-xml"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd"
dependencies = [
"memchr",
"serde",
]
[[package]]
name = "quote"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "rasn"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "417c93c4470c2637fd27307f046653c70a957f374caae89cb5c9fb35a5fdf147"
dependencies = [
"bitvec",
"bytes",
"chrono",
"konst",
"nom",
"num-bigint",
"num-traits",
"rasn-derive",
"snafu",
]
[[package]]
name = "rasn-derive"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e1c39006ee96ffe7c83f656a5d20269366098a3feb35ad4a7c022e3784643cb"
dependencies = [
"itertools",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "rasn-pkix"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88a7f32e03743ae0f691408246c7e1ce9065a345b6589e05df3b6c57138d4655"
dependencies = [
"rasn",
]
[[package]]
name = "roxmltree"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb6d47b59770b0ae88c7f270c68502832ec14d8c7ab5f7a584f204bb76dbfd8e"
dependencies = [
"xmlparser",
]
[[package]]
name = "rsa"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "094052d5470cbcef561cb848a7209968c9f12dfa6d668f4bca048ac5de51099c"
dependencies = [
"byteorder",
"digest",
"num-bigint-dig",
"num-integer",
"num-iter",
"num-traits",
"pkcs1",
"pkcs8",
"rand_core",
"signature",
"smallvec",
"subtle",
"zeroize",
]
[[package]]
name = "serde"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
]
[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "signature"
version = "1.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c"
dependencies = [
"digest",
"rand_core",
]
[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "snafu"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6"
dependencies = [
"doc-comment",
"snafu-derive",
]
[[package]]
name = "snafu-derive"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "spki"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b"
dependencies = [
"base64ct",
"der",
]
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tracing"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
]
[[package]]
name = "tracing-core"
version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
dependencies = [
"once_cell",
]
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicode-ident"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wyz"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
dependencies = [
"tap",
]
[[package]]
name = "xcommon"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cd8830f4fbeb328f04d29d48af5f2b968395542d6046c9bf55f5b59ebbc342f"
dependencies = [
"anyhow",
"byteorder",
"dunce",
"image",
"pem",
"rasn",
"rasn-pkix",
"rsa",
"sha2",
"zip",
]
[[package]]
name = "xmlparser"
version = "0.13.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4"
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
]
[[package]]
name = "zeroize"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
[[package]]
name = "zip"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
dependencies = [
"byteorder",
"crc32fast",
"crossbeam-utils",
"flate2",
]

11
apk-parser/Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "apk-parser"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0.95"
log = "0.4.25"
hex = "0.4.3"
byteorder = "1.5.0"
apk = "0.4.0"

6
apk-parser/src/lib.rs Normal file
View File

@ -0,0 +1,6 @@
mod manifest;
mod signing_block;
pub use apk::*;
pub use manifest::*;
pub use signing_block::*;

View File

@ -0,0 +1,99 @@
use anyhow::{bail, Result};
use apk::res::Chunk;
use apk::AndroidManifest;
use log::debug;
use std::collections::HashMap;
use std::io::Cursor;
/// Parse android manifest from AndroidManifest.xml file data
pub fn parse_android_manifest(data: &[u8]) -> Result<AndroidManifest> {
let chunks = if let Chunk::Xml(chunks) = Chunk::parse(&mut Cursor::new(data))? {
chunks
} else {
bail!("Invalid AndroidManifest file");
};
let strings = if let Chunk::StringPool(strings, _) = &chunks[0] {
HashMap::from_iter(
strings
.iter()
.enumerate()
.map(|(i, s)| (s.to_string(), i as i32)),
)
} else {
bail!("invalid manifest 1");
};
let mut res = AndroidManifest::default();
res.package = find_value_in(&strings, &chunks, "manifest", "package");
res.version_code =
find_value_in(&strings, &chunks, "manifest", "versionCode").and_then(|v| v.parse().ok());
res.version_name = find_value_in(&strings, &chunks, "manifest", "versionName");
res.compile_sdk_version = find_value_in(&strings, &chunks, "manifest", "compileSdkVersion")
.and_then(|v| v.parse().ok());
res.compile_sdk_version_codename =
find_value_in(&strings, &chunks, "manifest", "compileSdkVersionCodename")
.and_then(|v| v.parse().ok());
res.platform_build_version_code =
find_value_in(&strings, &chunks, "manifest", "platformBuildVersionCode")
.and_then(|v| v.parse().ok());
res.platform_build_version_name =
find_value_in(&strings, &chunks, "manifest", "platformBuildVersionName")
.and_then(|v| v.parse().ok());
res.sdk.min_sdk_version =
find_value_in(&strings, &chunks, "uses-sdk", "minSdkVersion").and_then(|v| v.parse().ok());
res.sdk.target_sdk_version = find_value_in(&strings, &chunks, "uses-sdk", "targetSdkVersion")
.and_then(|v| v.parse().ok());
res.sdk.max_sdk_version =
find_value_in(&strings, &chunks, "uses-sdk", "maxSdkVersion").and_then(|v| v.parse().ok());
res.application.theme = find_value_in(&strings, &chunks, "application", "theme");
res.application.label = find_value_in(&strings, &chunks, "application", "label");
res.application.icon = find_value_in(&strings, &chunks, "application", "icon");
Ok(res)
}
fn find_value_in(
strings: &HashMap<String, i32>,
chunks: &Vec<Chunk>,
node: &str,
attr: &str,
) -> Option<String> {
let idx_node = if let Some(i) = strings.get(node) {
*i
} else {
return None;
};
let idx_attr = if let Some(i) = strings.get(attr) {
*i
} else {
return None;
};
chunks.iter().find_map(|chunk| {
if let Chunk::XmlStartElement(_, el, attrs) = chunk {
match el.name {
x if x == idx_node => attrs.iter().find(|e| e.name == idx_attr).and_then(|e| {
debug!("{}, {}, {:?}", node, attr, e);
match e.typed_value.data_type {
3 => strings
.iter()
.find(|(_, v)| **v == e.raw_value)
.map(|(k, _)| k.clone()),
16 => Some(e.typed_value.data.to_string()),
_ => {
debug!("unknown data type {},{},{:?}", node, attr, e);
None
}
}
}),
_ => None,
}
} else {
None
}
})
}

View File

@ -0,0 +1,358 @@
use anyhow::{bail, ensure, Result};
use byteorder::{LittleEndian, ReadBytesExt};
use log::{debug, warn};
use std::collections::HashMap;
use std::convert::TryInto;
use std::fmt::{Display, Formatter};
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::path::Path;
/// APK Signing block storage type
#[derive(Debug, Clone)]
pub struct ApkSigningBlock {
pub data: Vec<(u32, Vec<u8>)>,
}
impl ApkSigningBlock {
/// Load the signing block from and APK file
pub fn from_path(path: impl AsRef<Path>) -> Result<Self> {
let mut file = File::open(path)?;
ApkSigningBlock::from_reader(&mut file)
}
/// Load the signing block from an APK file
pub fn from_reader<R: Read + Seek>(reader: &mut R) -> Result<Self> {
load_signing_block(reader)
}
/// Parse signatures from signing block
pub fn get_signatures(&self) -> Result<Vec<ApkSignatureBlock>> {
const V2_SIG_BLOCK_ID: u32 = 0x7109871a;
const V3_SIG_BLOCK_ID: u32 = 0xf05368c0;
let mut sigs = vec![];
for (k, v) in &self.data {
match *k {
V2_SIG_BLOCK_ID => {
let v2 = get_length_prefixed_u32_sequence(v)?;
ensure!(
v2.len() == 1,
"Expected 1 element in signing block got {}",
v2.len()
);
let v2 = get_length_prefixed_u32_sequence(v2[0])?;
let signed_data = get_sequence(v2[0])?;
let digests = get_sequence_kv(signed_data[0])?;
let certificates = get_sequence(signed_data[1])?;
let attributes = get_sequence_kv(signed_data[2])?;
let signatures = get_sequence_kv(v2[1])?;
let public_key = v2[2];
let digests: HashMap<u32, &[u8]> = HashMap::from_iter(digests);
sigs.push(ApkSignatureBlock::V2 {
attributes: HashMap::from_iter(
attributes.into_iter().map(|(k, v)| (k, v.to_vec())),
),
certificates: certificates.into_iter().map(|v| v.to_vec()).collect(),
signatures: parse_sigs(&signatures, &digests),
public_key: public_key.to_vec(),
});
}
V3_SIG_BLOCK_ID => {
let v3 = get_length_prefixed_u32_sequence(v)?;
ensure!(
v3.len() == 1,
"Expected 1 element in signing block got {}",
v3.len()
);
let v3 = get_length_prefixed_u32_sequence(v3[0])?;
let signed_data = get_sequence(v3[0])?;
let digests = get_sequence_kv(signed_data[0])?;
let certificates = get_sequence(signed_data[1])?;
let min_sdk_signed = u32::from_le_bytes(signed_data[2].try_into()?);
let max_sdk_signed = u32::from_le_bytes(signed_data[3].try_into()?);
let attributes = get_sequence_kv(signed_data[4])?;
let min_sdk = u32::from_le_bytes(v3[1].try_into()?);
let max_sdk = u32::from_le_bytes(v3[2].try_into()?);
ensure!(
min_sdk_signed == min_sdk,
"Invalid min_sdk in signing block V3 {} != {}",
min_sdk_signed,
min_sdk
);
ensure!(
max_sdk_signed == max_sdk,
"Invalid max_sdk in signing block V3 {} != {}",
max_sdk_signed,
max_sdk
);
let signatures = get_sequence_kv(v3[3])?;
let public_key = v3[4];
let digests: HashMap<u32, &[u8]> = HashMap::from_iter(digests);
sigs.push(ApkSignatureBlock::V3 {
min_sdk,
max_sdk,
attributes: HashMap::from_iter(
attributes.into_iter().map(|(k, v)| (k, v.to_vec())),
),
certificates: certificates.into_iter().map(|v| v.to_vec()).collect(),
signatures: parse_sigs(&signatures, &digests),
public_key: public_key.to_vec(),
});
}
v => debug!("Unknown block id {}", v),
}
}
Ok(sigs)
}
}
fn parse_sigs(signatures: &Vec<(u32, &[u8])>, digests: &HashMap<u32, &[u8]>) -> Vec<ApkSignature> {
signatures
.into_iter()
.filter_map(|(k, v)| {
let sig_len = u32::from_le_bytes(v[..4].try_into().ok()?) as usize;
if sig_len > v.len() - 4 {
warn!("Invalid signature length: {} > {}", sig_len, v.len());
return None;
}
if let Ok(a) = ApkSignatureAlgo::try_from(*k) {
Some(ApkSignature {
algo: a,
digest: digests.get(&k).map(|v| v[4..].to_vec())?,
signature: v[4..sig_len + 4].to_vec(),
})
} else {
None
}
})
.collect()
}
#[derive(Debug, Clone)]
pub enum ApkSignatureBlock {
/// Unknown block
Unknown { data: Vec<u8> },
/// Android V2 Signature Block
///
/// https://source.android.com/docs/security/features/apksigning/v2#apk-signature-scheme-v2-block-format
V2 {
signatures: Vec<ApkSignature>,
public_key: Vec<u8>,
certificates: Vec<Vec<u8>>,
attributes: HashMap<u32, Vec<u8>>,
},
/// Android V3 Signature Block
///
/// https://source.android.com/docs/security/features/apksigning/v3#format
V3 {
signatures: Vec<ApkSignature>,
certificates: Vec<Vec<u8>>,
public_key: Vec<u8>,
attributes: HashMap<u32, Vec<u8>>,
min_sdk: u32,
max_sdk: u32,
},
}
impl Display for ApkSignatureBlock {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ApkSignatureBlock::Unknown { .. } => write!(f, "unknown"),
ApkSignatureBlock::V2 { signatures, .. } => {
write!(f, "v2: ")?;
for sig in signatures {
write!(
f,
"algo={}, digest={}, sig={}",
sig.algo,
hex::encode(&sig.digest),
hex::encode(&sig.signature)
)?;
}
Ok(())
}
ApkSignatureBlock::V3 { signatures, .. } => {
write!(f, "V3: ")?;
for sig in signatures {
write!(
f,
"algo={}, digest={}, sig={}",
sig.algo,
hex::encode(&sig.digest),
hex::encode(&sig.signature)
)?;
}
Ok(())
}
}
}
}
#[derive(Debug, Clone)]
pub struct ApkSignature {
pub algo: ApkSignatureAlgo,
pub signature: Vec<u8>,
pub digest: Vec<u8>,
}
#[derive(Debug, Clone)]
pub enum ApkSignatureAlgo {
RsaSsaPssSha256,
RsaSsaPssSha512,
RsaSsaPkcs1Sha256,
RsaSsaPkcs1Sha512,
EcdsaSha256,
EcdsaSha512,
DsaSha256,
}
impl Display for ApkSignatureAlgo {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ApkSignatureAlgo::RsaSsaPssSha256 => write!(f, "RSASSA-PSS-SHA256"),
ApkSignatureAlgo::RsaSsaPssSha512 => write!(f, "RSASSA-PSS-SHA512"),
ApkSignatureAlgo::RsaSsaPkcs1Sha256 => write!(f, "RSASSA-PKCS1-SHA256"),
ApkSignatureAlgo::RsaSsaPkcs1Sha512 => write!(f, "RSASSA-PKCS1-SHA512"),
ApkSignatureAlgo::EcdsaSha256 => write!(f, "ECDSA-SHA256"),
ApkSignatureAlgo::EcdsaSha512 => write!(f, "ECDSA-SHA512"),
ApkSignatureAlgo::DsaSha256 => write!(f, "DSA-SHA256"),
}
}
}
impl TryFrom<u32> for ApkSignatureAlgo {
type Error = anyhow::Error;
fn try_from(value: u32) -> std::result::Result<Self, Self::Error> {
match value {
0x0101 => Ok(ApkSignatureAlgo::RsaSsaPssSha256),
0x0102 => Ok(ApkSignatureAlgo::RsaSsaPssSha512),
0x0103 => Ok(ApkSignatureAlgo::RsaSsaPkcs1Sha256),
0x0104 => Ok(ApkSignatureAlgo::RsaSsaPkcs1Sha512),
0x0201 => Ok(ApkSignatureAlgo::EcdsaSha256),
0x0202 => Ok(ApkSignatureAlgo::EcdsaSha512),
0x0301 => Ok(ApkSignatureAlgo::DsaSha256),
_ => bail!("Unknown signature algo"),
}
}
}
fn load_signing_block<R>(zip: &mut R) -> Result<ApkSigningBlock>
where
R: Read + Seek,
{
const SIG_BLOCK_MAGIC: &[u8] = b"APK Sig Block 42";
// scan backwards until we find the singing block
let flen = zip.seek(SeekFrom::End(0))?;
let mut magic_buf = [0u8; 16];
loop {
let magic_pos = zip.seek(SeekFrom::Current(-17))?;
if magic_pos <= 4 {
bail!("Failed to find signing block");
}
zip.read_exact(&mut magic_buf)?;
if magic_buf == SIG_BLOCK_MAGIC {
zip.seek(SeekFrom::Current(-(16 + 8)))?;
let size1 = zip.read_u64::<LittleEndian>()?;
ensure!(size1 <= flen, "Signing block is larger than entire file");
zip.seek(SeekFrom::Current(-(size1 as i64 - 8)))?;
let size2 = zip.read_u64::<LittleEndian>()?;
ensure!(
size2 == size1,
"Invalid block sizes, {} != {}",
size1,
size2
);
let mut data_bytes = size1 - 8 - 16;
let mut sigs = Vec::new();
loop {
let (k, v) = read_u64_length_prefixed_kv(zip)?;
data_bytes -= (v.len() + 4 + 8) as u64;
sigs.push((k, v));
if data_bytes == 0 {
break;
}
}
zip.seek(SeekFrom::Start(0))?;
return Ok(ApkSigningBlock { data: sigs });
}
}
}
#[inline]
fn read_u64_length_prefixed_kv<T>(file: &mut T) -> Result<(u32, Vec<u8>)>
where
T: Read + Seek,
{
let kv_len = file.read_u64::<LittleEndian>()?;
let k = file.read_u32::<LittleEndian>()?;
let v_len = kv_len as usize - 4;
let mut v = vec![0; v_len];
file.read_exact(v.as_mut_slice())?;
Ok((k, v))
}
#[inline]
fn get_u64_length_prefixed_kv(slice: &[u8]) -> Result<(u32, &[u8])> {
let kv_len = u64::from_le_bytes(slice[..8].try_into()?);
let k = u32::from_le_bytes(slice[8..12].try_into()?);
Ok((k, &slice[12..(kv_len as usize - 12)]))
}
#[inline]
fn get_u32_length_prefixed_kv(slice: &[u8]) -> Result<(u32, &[u8])> {
let kv_len = u32::from_le_bytes(slice[..4].try_into()?);
let k = u32::from_le_bytes(slice[4..8].try_into()?);
Ok((k, &slice[8..(kv_len as usize - 8)]))
}
#[inline]
fn get_length_prefixed_u32(slice: &[u8]) -> Result<&[u8]> {
let len = u32::from_le_bytes(slice[..4].try_into()?);
Ok(&slice[4..4 + len as usize])
}
#[inline]
fn get_length_prefixed_u32_sequence(slice: &[u8]) -> Result<Vec<&[u8]>> {
let sequence_len = u32::from_le_bytes(slice[..4].try_into()?);
get_sequence(&slice[4..4 + sequence_len as usize])
}
#[inline]
fn get_sequence(mut slice: &[u8]) -> Result<Vec<&[u8]>> {
let mut ret = Vec::new();
while slice.len() >= 4 {
let data = get_length_prefixed_u32(slice)?;
let r_len = data.len() + 4;
slice = &slice[r_len..];
ret.push(data);
}
Ok(ret)
}
#[inline]
fn get_sequence_kv(slice: &[u8]) -> Result<Vec<(u32, &[u8])>> {
let seq = get_sequence(slice)?;
Ok(seq
.into_iter()
.map(|s| {
let k = u32::from_le_bytes(s[..4].try_into().unwrap());
let v = &s[4..];
(k, v)
})
.collect())
}

View File

@ -1,22 +1,19 @@
use crate::manifest::Manifest; use crate::manifest::Manifest;
use crate::repo::github::GithubRepo; use crate::repo::github::GithubRepo;
use anyhow::{anyhow, bail, ensure, Result}; use anyhow::{anyhow, bail, ensure, Result};
use apk::res::Chunk; use apk_parser::zip::ZipArchive;
use apk::zip::ZipArchive; use apk_parser::{parse_android_manifest, AndroidManifest, ApkSignatureBlock, ApkSigningBlock};
use apk::AndroidManifest; use log::{info, warn};
use byteorder::LittleEndian;
use byteorder::ReadBytesExt;
use log::{debug, info, warn};
use nostr_sdk::prelude::{hex, Coordinate, StreamExt}; use nostr_sdk::prelude::{hex, Coordinate, StreamExt};
use nostr_sdk::{Event, EventBuilder, Kind, NostrSigner, Tag}; use nostr_sdk::{Event, EventBuilder, Kind, NostrSigner, Tag};
use reqwest::Url; use reqwest::Url;
use semver::Version; use semver::Version;
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use std::collections::{HashMap, HashSet}; use std::collections::HashSet;
use std::env::temp_dir; use std::env::temp_dir;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::fs::File; use std::fs::File;
use std::io::{Cursor, Read, Seek, SeekFrom}; use std::io::{Read, Seek};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
@ -66,7 +63,7 @@ impl TryInto<EventBuilder> for RepoArtifact {
Tag::parse(["f", self.platform.to_string().as_str()])?, Tag::parse(["f", self.platform.to_string().as_str()])?,
Tag::parse(["m", self.content_type.as_str()])?, Tag::parse(["m", self.content_type.as_str()])?,
Tag::parse(["size", self.size.to_string().as_str()])?, Tag::parse(["size", self.size.to_string().as_str()])?,
Tag::parse(["x", &hex::encode(self.hash)])? Tag::parse(["x", &hex::encode(self.hash)])?,
]); ]);
if let RepoResource::Remote(u) = self.location { if let RepoResource::Remote(u) = self.location {
b = b.tag(Tag::parse(["url", u.as_str()])?); b = b.tag(Tag::parse(["url", u.as_str()])?);
@ -74,26 +71,28 @@ impl TryInto<EventBuilder> for RepoArtifact {
match self.metadata { match self.metadata {
ArtifactMetadata::APK { ArtifactMetadata::APK {
manifest, manifest,
signature, signatures,
} => { } => {
match signature { for signature in signatures {
ApkSignatureBlock::None => { match signature {
warn!("No signature found in metadata"); ApkSignatureBlock::Unknown { .. } => {
} warn!("No signature found in metadata");
ApkSignatureBlock::V2 { signatures, .. } => {
for signature in signatures {
b = b.tag(Tag::parse([
"apk_signature_hash",
&hex::encode(signature.digest),
])?);
} }
} ApkSignatureBlock::V2 { signatures, .. } => {
ApkSignatureBlock::V3 { signatures, .. } => { for signature in signatures {
for signature in signatures { b = b.tag(Tag::parse([
b = b.tag(Tag::parse([ "apk_signature_hash",
"apk_signature_hash", &hex::encode(signature.digest),
&hex::encode(signature.digest), ])?);
])?); }
}
ApkSignatureBlock::V3 { signatures, .. } => {
for signature in signatures {
b = b.tag(Tag::parse([
"apk_signature_hash",
&hex::encode(signature.digest),
])?);
}
} }
} }
} }
@ -115,7 +114,6 @@ impl TryInto<EventBuilder> for RepoArtifact {
target_sdk.to_string().as_str(), target_sdk.to_string().as_str(),
])?); ])?);
} }
//TODO: apk sig
} }
} }
Ok(b) Ok(b)
@ -126,117 +124,16 @@ impl TryInto<EventBuilder> for RepoArtifact {
pub enum ArtifactMetadata { pub enum ArtifactMetadata {
APK { APK {
manifest: AndroidManifest, manifest: AndroidManifest,
signature: ApkSignatureBlock, signatures: Vec<ApkSignatureBlock>,
}, },
} }
#[derive(Debug, Clone)]
pub enum ApkSignatureBlock {
None,
/// Android V2 Signature Block
///
/// https://source.android.com/docs/security/features/apksigning/v2#apk-signature-scheme-v2-block-format
V2 {
signatures: Vec<ApkSignature>,
public_key: Vec<u8>,
certificates: Vec<Vec<u8>>,
attributes: HashMap<u32, Vec<u8>>,
},
V3 {
signatures: Vec<ApkSignature>,
public_key: Vec<u8>,
},
}
impl Display for ApkSignatureBlock {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ApkSignatureBlock::None => write!(f, "none"),
ApkSignatureBlock::V2 { signatures, .. } => {
write!(f, "v2: ")?;
for sig in signatures {
write!(
f,
"algo={}, digest={}, sig={}",
sig.algo,
hex::encode(&sig.digest),
hex::encode(&sig.signature)
)?;
}
Ok(())
}
ApkSignatureBlock::V3 { signatures, .. } => {
write!(f, "V3: ")?;
for sig in signatures {
write!(
f,
"algo={}, digest={}, sig={}",
sig.algo,
hex::encode(&sig.digest),
hex::encode(&sig.signature)
)?;
}
Ok(())
}
}
}
}
#[derive(Debug, Clone)]
pub struct ApkSignature {
pub algo: ApkSignatureAlgo,
pub signature: Vec<u8>,
pub digest: Vec<u8>,
}
#[derive(Debug, Clone)]
pub enum ApkSignatureAlgo {
RsaSsaPssSha256,
RsaSsaPssSha512,
RsaSsaPkcs1Sha256,
RsaSsaPkcs1Sha512,
EcdsaSha256,
EcdsaSha512,
DsaSha256,
}
impl Display for ApkSignatureAlgo {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ApkSignatureAlgo::RsaSsaPssSha256 => write!(f, "RSASSA-PSS-SHA256"),
ApkSignatureAlgo::RsaSsaPssSha512 => write!(f, "RSASSA-PSS-SHA512"),
ApkSignatureAlgo::RsaSsaPkcs1Sha256 => write!(f, "RSASSA-PKCS1-SHA256"),
ApkSignatureAlgo::RsaSsaPkcs1Sha512 => write!(f, "RSASSA-PKCS1-SHA512"),
ApkSignatureAlgo::EcdsaSha256 => write!(f, "ECDSA-SHA256"),
ApkSignatureAlgo::EcdsaSha512 => write!(f, "ECDSA-SHA512"),
ApkSignatureAlgo::DsaSha256 => write!(f, "DSA-SHA256"),
}
}
}
impl TryFrom<u32> for ApkSignatureAlgo {
type Error = anyhow::Error;
fn try_from(value: u32) -> std::result::Result<Self, Self::Error> {
match value {
0x0101 => Ok(ApkSignatureAlgo::RsaSsaPssSha256),
0x0102 => Ok(ApkSignatureAlgo::RsaSsaPssSha512),
0x0103 => Ok(ApkSignatureAlgo::RsaSsaPkcs1Sha256),
0x0104 => Ok(ApkSignatureAlgo::RsaSsaPkcs1Sha512),
0x0201 => Ok(ApkSignatureAlgo::EcdsaSha256),
0x0202 => Ok(ApkSignatureAlgo::EcdsaSha512),
0x0301 => Ok(ApkSignatureAlgo::DsaSha256),
_ => bail!("Unknown signature algo"),
}
}
}
impl Display for ArtifactMetadata { impl Display for ArtifactMetadata {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self { match self {
ArtifactMetadata::APK { ArtifactMetadata::APK {
manifest, manifest,
signature, signatures,
} => { } => {
write!( write!(
f, f,
@ -244,7 +141,11 @@ impl Display for ArtifactMetadata {
manifest.package.as_ref().unwrap_or(&"missing".to_string()), manifest.package.as_ref().unwrap_or(&"missing".to_string()),
manifest.version_name.as_ref().unwrap_or(&String::new()), manifest.version_name.as_ref().unwrap_or(&String::new()),
manifest.version_code.as_ref().unwrap_or(&0), manifest.version_code.as_ref().unwrap_or(&0),
signature signatures
.iter()
.map(|b| b.to_string())
.collect::<Vec<String>>()
.join(", ")
) )
} }
} }
@ -441,7 +342,7 @@ async fn load_artifact_url(url: &str) -> Result<RepoArtifact> {
info!("Downloading artifact {}", url); info!("Downloading artifact {}", url);
let u = Url::parse(url)?; let u = Url::parse(url)?;
let rsp = reqwest::get(u.clone()).await?; let rsp = reqwest::get(u.clone()).await?;
let id = hex::encode(sha2::Sha256::digest(url.as_bytes())); let id = hex::encode(Sha256::digest(url.as_bytes()));
let mut tmp = temp_dir().join(id); let mut tmp = temp_dir().join(id);
tmp.set_extension( tmp.set_extension(
PathBuf::from(u.path()) PathBuf::from(u.path())
@ -480,7 +381,7 @@ fn load_artifact(path: &Path) -> Result<RepoArtifact> {
fn load_apk_artifact(path: &Path) -> Result<RepoArtifact> { fn load_apk_artifact(path: &Path) -> Result<RepoArtifact> {
let file = File::open(path)?; let file = File::open(path)?;
let mut file = std::io::BufReader::new(file); let mut file = std::io::BufReader::new(file);
let sig_block = load_signing_block(&mut file)?; let sig_block = ApkSigningBlock::from_reader(&mut file)?;
let mut zip = ZipArchive::new(file)?; let mut zip = ZipArchive::new(file)?;
let manifest = load_manifest(&mut zip)?; let manifest = load_manifest(&mut zip)?;
@ -514,7 +415,7 @@ fn load_apk_artifact(path: &Path) -> Result<RepoArtifact> {
}, },
metadata: ArtifactMetadata::APK { metadata: ArtifactMetadata::APK {
manifest, manifest,
signature: sig_block.try_into()?, signatures: sig_block.get_signatures()?,
}, },
}) })
} }
@ -545,180 +446,6 @@ where
Ok(res) Ok(res)
} }
#[derive(Debug, Clone)]
struct ApkSigningBlock {
pub data: Vec<(u32, Vec<u8>)>,
}
impl TryInto<ApkSignatureBlock> for ApkSigningBlock {
type Error = anyhow::Error;
fn try_into(self) -> std::result::Result<ApkSignatureBlock, Self::Error> {
const V2_SIG_BLOCK_ID: u32 = 0x7109871a;
const V3_SIG_BLOCK_ID: u32 = 0xf05368c0;
if let Some(v3) =
self.data
.iter()
.find_map(|(k, v)| if *k == V3_SIG_BLOCK_ID { Some(v) } else { None })
{
todo!("Not done yet")
}
if let Some(v2) =
self.data
.iter()
.find_map(|(k, v)| if *k == V2_SIG_BLOCK_ID { Some(v) } else { None })
{
let v2 = get_length_prefixed_u32_sequence(&v2[4..])?;
let signed_data = get_sequence(v2[0])?;
let digests = get_sequence_kv(signed_data[0])?;
let certificates = get_sequence(signed_data[1])?;
let attributes = get_sequence_kv(signed_data[2])?;
let signatures = get_sequence_kv(v2[1])?;
let public_key = v2[2];
let digests: HashMap<u32, &[u8]> = HashMap::from_iter(digests);
return Ok(ApkSignatureBlock::V2 {
attributes: HashMap::from_iter(
attributes.into_iter().map(|(k, v)| (k, v.to_vec())),
),
certificates: certificates.into_iter().map(|v| v.to_vec()).collect(),
signatures: signatures
.into_iter()
.filter_map(|(k, v)| {
let sig_len = u32::from_le_bytes(v[..4].try_into().ok()?) as usize;
if sig_len > v.len() - 4 {
warn!("Invalid signature length: {} > {}", sig_len, v.len());
return None;
}
if let Ok(a) = ApkSignatureAlgo::try_from(k) {
Some(ApkSignature {
algo: a,
digest: digests.get(&k).map(|v| v[4..].to_vec())?,
signature: v[4..sig_len + 4].to_vec(),
})
} else {
None
}
})
.collect(),
public_key: public_key.to_vec(),
});
}
Ok(ApkSignatureBlock::None)
}
}
fn load_signing_block<R>(zip: &mut R) -> Result<ApkSigningBlock>
where
R: Read + Seek,
{
const SIG_BLOCK_MAGIC: &[u8] = b"APK Sig Block 42";
// scan backwards until we find the singing block
let flen = zip.seek(SeekFrom::End(0))?;
let mut magic_buf = [0u8; 16];
loop {
let magic_pos = zip.seek(SeekFrom::Current(-17))?;
if magic_pos <= 4 {
bail!("Failed to find signing block");
}
zip.read_exact(&mut magic_buf)?;
if magic_buf == SIG_BLOCK_MAGIC {
zip.seek(SeekFrom::Current(-(16 + 8)))?;
let size1 = zip.read_u64::<LittleEndian>()?;
ensure!(size1 <= flen, "Signing block is larger than entire file");
zip.seek(SeekFrom::Current(-(size1 as i64 - 8)))?;
let size2 = zip.read_u64::<LittleEndian>()?;
ensure!(
size2 == size1,
"Invalid block sizes, {} != {}",
size1,
size2
);
let mut data_bytes = size1 - 8 - 16;
let mut sigs = Vec::new();
loop {
let (k, v) = read_u64_length_prefixed_kv(zip)?;
data_bytes -= (v.len() + 4 + 8) as u64;
sigs.push((k, v));
if data_bytes == 0 {
break;
}
}
zip.seek(SeekFrom::Start(0))?;
return Ok(ApkSigningBlock { data: sigs });
}
}
}
#[inline]
fn read_u64_length_prefixed_kv<T>(file: &mut T) -> Result<(u32, Vec<u8>)>
where
T: Read + Seek,
{
let kv_len = file.read_u64::<LittleEndian>()?;
let k = file.read_u32::<LittleEndian>()?;
let v_len = kv_len as usize - 4;
let mut v = vec![0; v_len];
file.read_exact(v.as_mut_slice())?;
Ok((k, v))
}
#[inline]
fn get_u64_length_prefixed_kv(slice: &[u8]) -> Result<(u32, &[u8])> {
let kv_len = u64::from_le_bytes(slice[..8].try_into()?);
let k = u32::from_le_bytes(slice[8..12].try_into()?);
Ok((k, &slice[12..(kv_len as usize - 12)]))
}
#[inline]
fn get_u32_length_prefixed_kv(slice: &[u8]) -> Result<(u32, &[u8])> {
let kv_len = u32::from_le_bytes(slice[..4].try_into()?);
let k = u32::from_le_bytes(slice[4..8].try_into()?);
Ok((k, &slice[8..(kv_len as usize - 8)]))
}
#[inline]
fn get_length_prefixed_u32(slice: &[u8]) -> Result<&[u8]> {
let len = u32::from_le_bytes(slice[..4].try_into()?);
Ok(&slice[4..4 + len as usize])
}
#[inline]
fn get_length_prefixed_u32_sequence(slice: &[u8]) -> Result<Vec<&[u8]>> {
let sequence_len = u32::from_le_bytes(slice[..4].try_into()?);
get_sequence(&slice[4..4 + sequence_len as usize])
}
#[inline]
fn get_sequence(mut slice: &[u8]) -> Result<Vec<&[u8]>> {
let mut ret = Vec::new();
while slice.len() >= 4 {
let data = get_length_prefixed_u32(slice)?;
let r_len = data.len() + 4;
slice = &slice[r_len..];
ret.push(data);
}
Ok(ret)
}
#[inline]
fn get_sequence_kv(slice: &[u8]) -> Result<Vec<(u32, &[u8])>> {
let seq = get_sequence(slice)?;
Ok(seq
.into_iter()
.map(|s| {
let k = u32::from_le_bytes(s[..4].try_into().unwrap());
let v = &s[4..];
(k, v)
})
.collect())
}
fn list_libs<T>(zip: &mut ZipArchive<T>) -> Vec<String> fn list_libs<T>(zip: &mut ZipArchive<T>) -> Vec<String>
where where
T: Read + Seek, T: Read + Seek,
@ -734,98 +461,6 @@ where
.collect() .collect()
} }
fn parse_android_manifest(data: &[u8]) -> Result<AndroidManifest> {
let chunks = if let Chunk::Xml(chunks) = Chunk::parse(&mut Cursor::new(data))? {
chunks
} else {
bail!("Invalid AndroidManifest file");
};
let strings = if let Chunk::StringPool(strings, _) = &chunks[0] {
HashMap::from_iter(
strings
.iter()
.enumerate()
.map(|(i, s)| (s.to_string(), i as i32)),
)
} else {
bail!("invalid manifest 1");
};
let mut res = AndroidManifest::default();
res.package = find_value_in(&strings, &chunks, "manifest", "package");
res.version_code =
find_value_in(&strings, &chunks, "manifest", "versionCode").and_then(|v| v.parse().ok());
res.version_name = find_value_in(&strings, &chunks, "manifest", "versionName");
res.compile_sdk_version = find_value_in(&strings, &chunks, "manifest", "compileSdkVersion")
.and_then(|v| v.parse().ok());
res.compile_sdk_version_codename =
find_value_in(&strings, &chunks, "manifest", "compileSdkVersionCodename")
.and_then(|v| v.parse().ok());
res.platform_build_version_code =
find_value_in(&strings, &chunks, "manifest", "platformBuildVersionCode")
.and_then(|v| v.parse().ok());
res.platform_build_version_name =
find_value_in(&strings, &chunks, "manifest", "platformBuildVersionName")
.and_then(|v| v.parse().ok());
res.sdk.min_sdk_version =
find_value_in(&strings, &chunks, "uses-sdk", "minSdkVersion").and_then(|v| v.parse().ok());
res.sdk.target_sdk_version = find_value_in(&strings, &chunks, "uses-sdk", "targetSdkVersion")
.and_then(|v| v.parse().ok());
res.sdk.max_sdk_version =
find_value_in(&strings, &chunks, "uses-sdk", "maxSdkVersion").and_then(|v| v.parse().ok());
res.application.theme = find_value_in(&strings, &chunks, "application", "theme");
res.application.label = find_value_in(&strings, &chunks, "application", "label");
res.application.icon = find_value_in(&strings, &chunks, "application", "icon");
Ok(res)
}
fn find_value_in(
strings: &HashMap<String, i32>,
chunks: &Vec<Chunk>,
node: &str,
attr: &str,
) -> Option<String> {
let idx_node = if let Some(i) = strings.get(node) {
*i
} else {
return None;
};
let idx_attr = if let Some(i) = strings.get(attr) {
*i
} else {
return None;
};
chunks.iter().find_map(|chunk| {
if let Chunk::XmlStartElement(_, el, attrs) = chunk {
match el.name {
x if x == idx_node => attrs.iter().find(|e| e.name == idx_attr).and_then(|e| {
debug!("{}, {}, {:?}", node, attr, e);
match e.typed_value.data_type {
3 => strings
.iter()
.find(|(_, v)| **v == e.raw_value)
.map(|(k, _)| k.clone()),
16 => Some(e.typed_value.data.to_string()),
_ => {
debug!("unknown data type {},{},{:?}", node, attr, e);
None
}
}
}),
_ => None,
}
} else {
None
}
})
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -833,17 +468,9 @@ mod tests {
#[ignore] #[ignore]
#[test] #[test]
fn read_apk() -> Result<()> { fn read_apk() -> Result<()> {
let path = "/home/kieran/Downloads/app-arm64-v8a-release.apk"; let path = "/home/kieran/Downloads/snort-arm64-v8a-v0.3.0.apk";
let apk = load_apk_artifact(&PathBuf::from(path))?; let apk = load_apk_artifact(&PathBuf::from(path))?;
assert!(
matches!(&apk.platform, Platform::Android { arch } if matches!(arch, Architecture::ARM64 { .. }))
);
assert!(matches!(&apk.metadata,
ArtifactMetadata::APK { signature, .. } if matches!(signature,
ApkSignatureBlock::V2 { signatures, .. } if signatures.len() == 1 &&
matches!(signatures[0].algo, ApkSignatureAlgo::RsaSsaPkcs1Sha256))));
eprint!("{}", apk); eprint!("{}", apk);
Ok(()) Ok(())
} }