From c0b7312aae98d160c5d809afc230550c24a54bbb Mon Sep 17 00:00:00 2001 From: kieran Date: Fri, 14 Feb 2025 15:56:12 +0000 Subject: [PATCH] refactor: apk-parser crate --- .gitignore | 2 +- Cargo.lock | 20 +- Cargo.toml | 3 +- apk-parser/Cargo.lock | 878 ++++++++++++++++++++++++++++++++ apk-parser/Cargo.toml | 11 + apk-parser/src/lib.rs | 6 + apk-parser/src/manifest.rs | 99 ++++ apk-parser/src/signing_block.rs | 358 +++++++++++++ src/repo/mod.rs | 447 ++-------------- 9 files changed, 1409 insertions(+), 415 deletions(-) create mode 100644 apk-parser/Cargo.lock create mode 100644 apk-parser/Cargo.toml create mode 100644 apk-parser/src/lib.rs create mode 100644 apk-parser/src/manifest.rs create mode 100644 apk-parser/src/signing_block.rs diff --git a/.gitignore b/.gitignore index 6b39d31..573792f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -/target +**/target .idea/ \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 122e3d2..08fcebd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -124,6 +124,17 @@ dependencies = [ "zip", ] +[[package]] +name = "apk-parser" +version = "0.1.0" +dependencies = [ + "anyhow", + "apk", + "byteorder", + "hex", + "log", +] + [[package]] name = "arraydeque" version = "0.5.1" @@ -978,6 +989,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hex-conservative" version = "0.1.2" @@ -1463,9 +1480,8 @@ name = "nap" version = "0.1.0" dependencies = [ "anyhow", - "apk", + "apk-parser", "async-trait", - "byteorder", "clap", "config", "dialoguer", diff --git a/Cargo.toml b/Cargo.toml index 23bd278..ecfa29f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,10 +20,9 @@ reqwest = { version = "0.12.12", features = ["json", "stream"] } tokio = { version = "1.43.0", features = ["fs", "rt", "macros", "rt-multi-thread"] } serde = { version = "1.0.217", features = ["derive"] } async-trait = "0.1.86" -apk = "0.4.0" semver = "1.0.25" indicatif = "0.17.11" dialoguer = "0.11.0" env_logger = "0.11.6" sha2 = "0.10.8" -byteorder = "1.5.0" +apk-parser = { path = "./apk-parser" } diff --git a/apk-parser/Cargo.lock b/apk-parser/Cargo.lock new file mode 100644 index 0000000..a10eaff --- /dev/null +++ b/apk-parser/Cargo.lock @@ -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", +] diff --git a/apk-parser/Cargo.toml b/apk-parser/Cargo.toml new file mode 100644 index 0000000..dd48f6f --- /dev/null +++ b/apk-parser/Cargo.toml @@ -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" \ No newline at end of file diff --git a/apk-parser/src/lib.rs b/apk-parser/src/lib.rs new file mode 100644 index 0000000..6e66fc1 --- /dev/null +++ b/apk-parser/src/lib.rs @@ -0,0 +1,6 @@ +mod manifest; +mod signing_block; + +pub use apk::*; +pub use manifest::*; +pub use signing_block::*; diff --git a/apk-parser/src/manifest.rs b/apk-parser/src/manifest.rs new file mode 100644 index 0000000..ff54156 --- /dev/null +++ b/apk-parser/src/manifest.rs @@ -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 { + 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, + chunks: &Vec, + node: &str, + attr: &str, +) -> Option { + 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 + } + }) +} diff --git a/apk-parser/src/signing_block.rs b/apk-parser/src/signing_block.rs new file mode 100644 index 0000000..a312bc0 --- /dev/null +++ b/apk-parser/src/signing_block.rs @@ -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)>, +} + +impl ApkSigningBlock { + /// Load the signing block from and APK file + pub fn from_path(path: impl AsRef) -> Result { + let mut file = File::open(path)?; + ApkSigningBlock::from_reader(&mut file) + } + + /// Load the signing block from an APK file + pub fn from_reader(reader: &mut R) -> Result { + load_signing_block(reader) + } + + /// Parse signatures from signing block + pub fn get_signatures(&self) -> Result> { + 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 = 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 = 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) -> Vec { + 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 }, + + /// Android V2 Signature Block + /// + /// https://source.android.com/docs/security/features/apksigning/v2#apk-signature-scheme-v2-block-format + V2 { + signatures: Vec, + public_key: Vec, + certificates: Vec>, + attributes: HashMap>, + }, + + /// Android V3 Signature Block + /// + /// https://source.android.com/docs/security/features/apksigning/v3#format + V3 { + signatures: Vec, + certificates: Vec>, + public_key: Vec, + attributes: HashMap>, + 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, + pub digest: Vec, +} + +#[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 for ApkSignatureAlgo { + type Error = anyhow::Error; + + fn try_from(value: u32) -> std::result::Result { + 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(zip: &mut R) -> Result +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::()?; + ensure!(size1 <= flen, "Signing block is larger than entire file"); + + zip.seek(SeekFrom::Current(-(size1 as i64 - 8)))?; + let size2 = zip.read_u64::()?; + 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(file: &mut T) -> Result<(u32, Vec)> +where + T: Read + Seek, +{ + let kv_len = file.read_u64::()?; + let k = file.read_u32::()?; + 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> { + 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> { + 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> { + 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()) +} diff --git a/src/repo/mod.rs b/src/repo/mod.rs index f93bd6c..f30d6af 100644 --- a/src/repo/mod.rs +++ b/src/repo/mod.rs @@ -1,22 +1,19 @@ use crate::manifest::Manifest; use crate::repo::github::GithubRepo; use anyhow::{anyhow, bail, ensure, Result}; -use apk::res::Chunk; -use apk::zip::ZipArchive; -use apk::AndroidManifest; -use byteorder::LittleEndian; -use byteorder::ReadBytesExt; -use log::{debug, info, warn}; +use apk_parser::zip::ZipArchive; +use apk_parser::{parse_android_manifest, AndroidManifest, ApkSignatureBlock, ApkSigningBlock}; +use log::{info, warn}; use nostr_sdk::prelude::{hex, Coordinate, StreamExt}; use nostr_sdk::{Event, EventBuilder, Kind, NostrSigner, Tag}; use reqwest::Url; use semver::Version; use sha2::{Digest, Sha256}; -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; use std::env::temp_dir; use std::fmt::{Display, Formatter}; use std::fs::File; -use std::io::{Cursor, Read, Seek, SeekFrom}; +use std::io::{Read, Seek}; use std::path::{Path, PathBuf}; use tokio::io::AsyncWriteExt; @@ -66,7 +63,7 @@ impl TryInto for RepoArtifact { Tag::parse(["f", self.platform.to_string().as_str()])?, Tag::parse(["m", self.content_type.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 { b = b.tag(Tag::parse(["url", u.as_str()])?); @@ -74,26 +71,28 @@ impl TryInto for RepoArtifact { match self.metadata { ArtifactMetadata::APK { manifest, - signature, + signatures, } => { - match signature { - ApkSignatureBlock::None => { - 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), - ])?); + for signature in signatures { + match signature { + ApkSignatureBlock::Unknown { .. } => { + warn!("No signature found in metadata"); } - } - ApkSignatureBlock::V3 { signatures, .. } => { - for signature in signatures { - b = b.tag(Tag::parse([ - "apk_signature_hash", - &hex::encode(signature.digest), - ])?); + ApkSignatureBlock::V2 { signatures, .. } => { + for signature in signatures { + b = b.tag(Tag::parse([ + "apk_signature_hash", + &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 for RepoArtifact { target_sdk.to_string().as_str(), ])?); } - //TODO: apk sig } } Ok(b) @@ -126,117 +124,16 @@ impl TryInto for RepoArtifact { pub enum ArtifactMetadata { APK { manifest: AndroidManifest, - signature: ApkSignatureBlock, + signatures: Vec, }, } -#[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, - public_key: Vec, - certificates: Vec>, - attributes: HashMap>, - }, - V3 { - signatures: Vec, - public_key: Vec, - }, -} - -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, - pub digest: Vec, -} - -#[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 for ApkSignatureAlgo { - type Error = anyhow::Error; - - fn try_from(value: u32) -> std::result::Result { - 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 { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { ArtifactMetadata::APK { manifest, - signature, + signatures, } => { write!( f, @@ -244,7 +141,11 @@ impl Display for ArtifactMetadata { manifest.package.as_ref().unwrap_or(&"missing".to_string()), manifest.version_name.as_ref().unwrap_or(&String::new()), manifest.version_code.as_ref().unwrap_or(&0), - signature + signatures + .iter() + .map(|b| b.to_string()) + .collect::>() + .join(", ") ) } } @@ -441,7 +342,7 @@ async fn load_artifact_url(url: &str) -> Result { info!("Downloading artifact {}", url); let u = Url::parse(url)?; 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); tmp.set_extension( PathBuf::from(u.path()) @@ -480,7 +381,7 @@ fn load_artifact(path: &Path) -> Result { fn load_apk_artifact(path: &Path) -> Result { let file = File::open(path)?; 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 manifest = load_manifest(&mut zip)?; @@ -514,7 +415,7 @@ fn load_apk_artifact(path: &Path) -> Result { }, metadata: ArtifactMetadata::APK { manifest, - signature: sig_block.try_into()?, + signatures: sig_block.get_signatures()?, }, }) } @@ -545,180 +446,6 @@ where Ok(res) } -#[derive(Debug, Clone)] -struct ApkSigningBlock { - pub data: Vec<(u32, Vec)>, -} - -impl TryInto for ApkSigningBlock { - type Error = anyhow::Error; - - fn try_into(self) -> std::result::Result { - 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 = 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(zip: &mut R) -> Result -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::()?; - ensure!(size1 <= flen, "Signing block is larger than entire file"); - - zip.seek(SeekFrom::Current(-(size1 as i64 - 8)))?; - let size2 = zip.read_u64::()?; - 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(file: &mut T) -> Result<(u32, Vec)> -where - T: Read + Seek, -{ - let kv_len = file.read_u64::()?; - let k = file.read_u32::()?; - 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> { - 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> { - 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> { - 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(zip: &mut ZipArchive) -> Vec where T: Read + Seek, @@ -734,98 +461,6 @@ where .collect() } -fn parse_android_manifest(data: &[u8]) -> Result { - 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, - chunks: &Vec, - node: &str, - attr: &str, -) -> Option { - 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)] mod tests { use super::*; @@ -833,17 +468,9 @@ mod tests { #[ignore] #[test] 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))?; - 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); Ok(()) }