commit 9baf77f00e273fe9a6ea3e8c3e1b017123132b95 Author: nab138 Date: Wed Aug 6 22:01:36 2025 -0400 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..5928ded --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3258 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-loader" +version = "0.2.0" +source = "git+https://github.com/Dadoum/android-loader?branch=bigger_pages#dfa86501afca7caa23d5ce15322ac7260d857485" +dependencies = [ + "anyhow", + "lazy_static", + "libc", + "log", + "memmap2", + "rand 0.8.5", + "region", + "sysv64", + "xmas-elf", + "zero", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.60.2", +] + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "async-compression" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8" +dependencies = [ + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags 2.9.1", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.104", + "which", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[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 = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "botan" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d4c7647d67c53194fa0740404c6c508880aef2bfe99a9868dbb4b86f090377" +dependencies = [ + "botan-sys", +] + +[[package]] +name = "botan-src" +version = "0.30701.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c4e1c7910f3b4712aed10e4259eca77e79ed84c1b023098c8eac596b993fc44" + +[[package]] +name = "botan-sys" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04285fa0c094cc9961fe435b1b279183db9394844ad82ce483aa6196c0e6da38" +dependencies = [ + "botan-src", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "bzip2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bea8dcd42434048e4f7a304411d9273a411f647446c1234a65ce0554923f4cff" +dependencies = [ + "libbz2-rs-sys", +] + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "const-oid" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[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.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[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 = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "deflate64" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" + +[[package]] +name = "der" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +dependencies = [ + "const-oid", +] + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "dlopen2" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b121caccfc363e4d9a4589528f3bef7c71b83c6ed01c8dc68cbeeb7fd29ec698" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a09ac8bb8c16a282264c379dffba707b9c998afc7506009137f3c6136888078" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "libz-rs-sys", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[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.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http", + "hyper", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icloud_auth" +version = "0.1.0" +dependencies = [ + "aes", + "base64 0.13.1", + "botan", + "cbc", + "chrono", + "hmac", + "num-bigint", + "omnisette", + "pbkdf2 0.11.0", + "pkcs7", + "plist", + "rand 0.8.5", + "reqwest", + "rustls 0.20.9", + "rustls-pemfile", + "serde", + "serde_json", + "sha2", + "srp", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idevice" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b9d40e2753062fba4cac48cb459987dd9f66f6b777d90c546548f053bdd03c0" +dependencies = [ + "base64 0.22.1", + "env_logger", + "log", + "plist", + "rustls 0.23.31", + "serde", + "thiserror 2.0.12", + "tokio", + "tokio-rustls 0.26.2", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "io-uring" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "libc", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "isideload" +version = "0.1.0" +dependencies = [ + "hex", + "icloud_auth", + "idevice", + "plist", + "serde", + "sha1", + "uuid", + "zip", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jiff" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libbz2-rs-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets 0.53.3", +] + +[[package]] +name = "liblzma" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0791ab7e08ccc8e0ce893f6906eb2703ed8739d8e89b57c0714e71bad09024c8" +dependencies = [ + "liblzma-sys", +] + +[[package]] +name = "liblzma-sys" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01b9596486f6d60c3bbe644c0e1be1aa6ccc472ad630fe8927b456973d7cb736" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "libz-rs-sys" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "172a788537a2221661b480fee8dc5f96c580eb34fa88764d3205dc356c7e4221" +dependencies = [ + "zlib-rs", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[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-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[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-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "omnisette" +version = "0.1.0" +dependencies = [ + "android-loader", + "anyhow", + "async-trait", + "base64 0.21.7", + "chrono", + "dlopen2", + "futures-util", + "hex", + "libc", + "log", + "objc", + "objc-foundation", + "plist", + "rand 0.8.5", + "remove-async-await", + "reqwest", + "serde", + "serde_json", + "sha2", + "thiserror 1.0.69", + "tokio-tungstenite", + "uuid", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs7" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f7364e6d0e236473de91e042395d71e0e64715f99a60620b014a4a4c7d1619b" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plist" +version = "1.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1" +dependencies = [ + "base64 0.22.1", + "indexmap", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppmd-rust" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c834641d8ad1b348c9ee86dec3b9840d805acd5f24daa5f90c788951a52ff59b" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" +dependencies = [ + "proc-macro2", + "syn 2.0.104", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.38.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9845d9dccf565065824e69f9f235fafba1587031eda353c1f1561cd6a6be78f4" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[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 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "region" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b6ebd13bc009aef9cd476c1310d49ac354d36e240cf1bd753290f3dc7199a7" +dependencies = [ + "bitflags 1.3.2", + "libc", + "mach2", + "windows-sys 0.52.0", +] + +[[package]] +name = "remove-async-await" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0993102a683d0fb29c6053ad44d7ed7555f69b2fa5fe0fa3bba959a9aa4cd1" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "async-compression", + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.21.12", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-rustls 0.24.1", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.60.2", +] + +[[package]] +name = "rustls" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" +dependencies = [ + "log", + "ring 0.16.20", + "sct", + "webpki", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring 0.17.14", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.23.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "rustls-pki-types", + "rustls-webpki 0.103.4", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring 0.17.14", + "untrusted 0.9.0", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +dependencies = [ + "aws-lc-rs", + "ring 0.17.14", + "rustls-pki-types", + "untrusted 0.9.0", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring 0.17.14", + "untrusted 0.9.0", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.1", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "serde_json" +version = "1.0.142" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spki" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" +dependencies = [ + "der", +] + +[[package]] +name = "srp" +version = "0.6.0" +dependencies = [ + "base64 0.21.7", + "digest", + "generic-array", + "lazy_static", + "num-bigint", + "subtle", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[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.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "sysv64" +version = "0.1.0" +source = "git+https://github.com/Dadoum/android-loader?branch=bigger_pages#dfa86501afca7caa23d5ce15322ac7260d857485" + +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix 1.0.8", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.47.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "pin-project-lite", + "slab", + "socket2 0.6.0", + "windows-sys 0.59.0", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls 0.23.31", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" +dependencies = [ + "futures-util", + "log", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", + "tungstenite", + "webpki-roots", +] + +[[package]] +name = "tokio-util" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.8.5", + "rustls 0.21.12", + "sha1", + "thiserror 1.0.69", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +dependencies = [ + "getrandom 0.3.3", + "js-sys", + "rand 0.9.2", + "uuid-macro-internal", + "wasm-bindgen", +] + +[[package]] +name = "uuid-macro-internal" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b682e8c381995ea03130e381928e0e005b7c9eb483c6c8682f50e07b33c2b7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.104", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" +dependencies = [ + "ring 0.17.14", + "untrusted 0.9.0", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "xmas-elf" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42c49817e78342f7f30a181573d82ff55b88a35f86ccaf07fc64b3008f56d1c6" +dependencies = [ + "zero", +] + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "synstructure", +] + +[[package]] +name = "zero" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fe21bcc34ca7fe6dd56cc2cb1261ea59d6b93620215aefb5ea6032265527784" + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "zip" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aed4ac33e8eb078c89e6cbb1d5c4c7703ec6d299fc3e7c3695af8f8b423468b" +dependencies = [ + "aes", + "arbitrary", + "bzip2", + "constant_time_eq", + "crc32fast", + "deflate64", + "flate2", + "getrandom 0.3.3", + "hmac", + "indexmap", + "liblzma", + "memchr", + "pbkdf2 0.12.2", + "ppmd-rust", + "sha1", + "time", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zlib-rs" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626bd9fa9734751fc50d6060752170984d7053f5a39061f524cda68023d4db8a" + +[[package]] +name = "zopfli" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.15+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..109dbe7 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "isideload" +version = "0.1.0" +edition = "2024" + +[dependencies] +serde = { version = "1.0.219", features = ["derive"] } +plist = { version = "1.7.2" } +icloud_auth = {path = "./apple-private-apis/icloud-auth" } +uuid = "1.17.0" +zip = "4.3.0" +hex = "0.4.3" +sha1 = "0.10.6" +idevice = "0.1.37" diff --git a/README.md b/README.md new file mode 100644 index 0000000..de24c63 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# isideload + +A Rust library for sideloading iOS applications. Designed for use in [YCode](https://github.com/nab138/YCode). + +### Licensing + +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. + +A lot of the authentication code came from https://github.com/SideStore/apple-private-apis/, but the original project was left unfinished. This repository contains a (more) complete implementation of the package. That part of the code has been kept under its original license, MPL-2.0. diff --git a/apple-private-apis/.gitignore b/apple-private-apis/.gitignore new file mode 100644 index 0000000..20a7378 --- /dev/null +++ b/apple-private-apis/.gitignore @@ -0,0 +1,10 @@ +/target +*/target +*/Cargo.lock +Cargo.lock +ignore_this_test.js + +# IDE generated files +.idea + +anisette_test/ diff --git a/apple-private-apis/Cargo.toml b/apple-private-apis/Cargo.toml new file mode 100644 index 0000000..8611da1 --- /dev/null +++ b/apple-private-apis/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +members = [ + "omnisette", + "icloud-auth" +] diff --git a/apple-private-apis/LICENSE b/apple-private-apis/LICENSE new file mode 100644 index 0000000..a612ad9 --- /dev/null +++ b/apple-private-apis/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/apple-private-apis/icloud-auth/.gitignore b/apple-private-apis/icloud-auth/.gitignore new file mode 100644 index 0000000..55f8980 --- /dev/null +++ b/apple-private-apis/icloud-auth/.gitignore @@ -0,0 +1,3 @@ +/target +Cargo.lock +*.py diff --git a/apple-private-apis/icloud-auth/Cargo.toml b/apple-private-apis/icloud-auth/Cargo.toml new file mode 100644 index 0000000..60d783b --- /dev/null +++ b/apple-private-apis/icloud-auth/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "icloud_auth" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = { version = "1.0.219", features = ["derive"] } +serde_json = { version = "1.0.142" } +base64 = "0.13.1" +srp = { version = "0.6.0", path = "./rustcrypto-srp" } +pbkdf2 = { version = "0.11.0" } +sha2 = { version = "0.10.6" } +rand = { version = "0.8.5" } +rustls = { version = "0.20.7" } +rustls-pemfile = { version = "1.0.1" } +plist = { version = "1.7.2" } +hmac = "0.12.1" +num-bigint = "0.4.3" +cbc = { version = "0.1.2", features = ["std"] } +aes = "0.8.2" +pkcs7 = "0.3.0" +reqwest = { version = "0.11.14", features = ["blocking", "json", "default-tls"] } +omnisette = {path = "../omnisette", features = ["remote-anisette-v3"]} +thiserror = "1.0.58" +tokio = "1" +botan = { version = "0.11.1", features = ["vendored"] } +chrono = { version = "0.4", features = ["serde"] } + +[dev-dependencies] +tokio = { version = "1", features = ["rt", "macros"] } diff --git a/apple-private-apis/icloud-auth/rustcrypto-srp/CHANGELOG.md b/apple-private-apis/icloud-auth/rustcrypto-srp/CHANGELOG.md new file mode 100644 index 0000000..8ab2aaa --- /dev/null +++ b/apple-private-apis/icloud-auth/rustcrypto-srp/CHANGELOG.md @@ -0,0 +1,41 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 0.6.0 (2022-01-22) +### Changed +- Use `modpow` for constant time modular exponentiation ([#78]) +- Rebuild library ([#79]) + +[#78]: https://github.com/RustCrypto/PAKEs/pull/78 +[#79]: https://github.com/RustCrypto/PAKEs/pull/79 + +## 0.5.0 (2020-10-07) + +## 0.4.3 (2019-11-07) + +## 0.4.2 (2019-11-06) + +## 0.4.1 (2019-11-07) + +## 0.4.0 (2018-12-20) + +## 0.3.0 (2018-10-22) + +## 0.2.5 (2018-04-14) + +## 0.2.4 (2017-11-01) + +## 0.2.3 (2017-08-17) + +## 0.2.2 (2017-08-14) + +## 0.2.1 (2017-08-14) + +## 0.2.0 (2017-08-14) + +## 0.1.1 (2017-08-13) + +## 0.1.0 (2017-08-13) diff --git a/apple-private-apis/icloud-auth/rustcrypto-srp/Cargo.toml b/apple-private-apis/icloud-auth/rustcrypto-srp/Cargo.toml new file mode 100644 index 0000000..aeae6fc --- /dev/null +++ b/apple-private-apis/icloud-auth/rustcrypto-srp/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "srp" +version = "0.6.0" +authors = ["RustCrypto Developers"] +license = "MIT OR Apache-2.0" +description = "Secure Remote Password (SRP) protocol implementation" +documentation = "https://docs.rs/srp" +repository = "https://github.com/RustCrypto/PAKEs" +keywords = ["crypto", "pake", "authentication"] +categories = ["cryptography", "authentication"] +readme = "README.md" +edition = "2021" +rust-version = "1.56" + +[dependencies] +num-bigint = "0.4" +generic-array = "0.14" +digest = "0.10" +lazy_static = "1.2" +subtle = "2.4" +base64 = "0.21.0" + +[dev-dependencies] +hex-literal = "0.3" +num-traits = "0.2" +rand = "0.8" +sha1 = "0.10.6" +sha2 = "0.10.8" diff --git a/apple-private-apis/icloud-auth/rustcrypto-srp/LICENSE-APACHE b/apple-private-apis/icloud-auth/rustcrypto-srp/LICENSE-APACHE new file mode 100644 index 0000000..78173fa --- /dev/null +++ b/apple-private-apis/icloud-auth/rustcrypto-srp/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/apple-private-apis/icloud-auth/rustcrypto-srp/LICENSE-MIT b/apple-private-apis/icloud-auth/rustcrypto-srp/LICENSE-MIT new file mode 100644 index 0000000..8dcb85b --- /dev/null +++ b/apple-private-apis/icloud-auth/rustcrypto-srp/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2017 Artyom Pavlov + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/apple-private-apis/icloud-auth/rustcrypto-srp/README.md b/apple-private-apis/icloud-auth/rustcrypto-srp/README.md new file mode 100644 index 0000000..3aa7583 --- /dev/null +++ b/apple-private-apis/icloud-auth/rustcrypto-srp/README.md @@ -0,0 +1,73 @@ +# [RustCrypto]: SRP + +[![crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +![Apache2/MIT licensed][license-image] +![Rust Version][rustc-image] +[![Project Chat][chat-image]][chat-link] +[![Build Status][build-image]][build-link] + +Pure Rust implementation of the [Secure Remote Password] password-authenticated +key-exchange algorithm. + +[Documentation][docs-link] + +## About + +This implementation is generic over hash functions using the [`Digest`] trait, +so you will need to choose a hash function, e.g. `Sha256` from [`sha2`] crate. + +Additionally this crate allows to use a specialized password hashing +algorithm for private key computation instead of method described in the +SRP literature. + +Compatibility with other implementations has not yet been tested. + +## âš ï¸ Security Warning + +This crate has never received an independent third party audit for security and +correctness. + +USE AT YOUR OWN RISK! + +## Minimum Supported Rust Version + +Rust **1.56** or higher. + +Minimum supported Rust version can be changed in the future, but it will be +done with a minor version bump. + +## License + +Licensed under either of: + + * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + * [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/srp.svg +[crate-link]: https://crates.io/crates/srp +[docs-image]: https://docs.rs/srp/badge.svg +[docs-link]: https://docs.rs/srp/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.56+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260045-PAKEs +[build-image]: https://github.com/RustCrypto/PAKEs/actions/workflows/srp.yml/badge.svg +[build-link]: https://github.com/RustCrypto/PAKEs/actions/workflows/srp.yml + +[//]: # (general links) + +[RustCrypto]: https://github.com/RustCrypto +[Secure Remote Password]: https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol +[`Digest`]: https://docs.rs/digest +[`sha2`]: https://crates.io/crates/sha2 diff --git a/apple-private-apis/icloud-auth/rustcrypto-srp/src/client.rs b/apple-private-apis/icloud-auth/rustcrypto-srp/src/client.rs new file mode 100644 index 0000000..99eadc8 --- /dev/null +++ b/apple-private-apis/icloud-auth/rustcrypto-srp/src/client.rs @@ -0,0 +1,248 @@ +//! SRP client implementation. +//! +//! # Usage +//! First create SRP client struct by passing to it SRP parameters (shared +//! between client and server). +//! +//! You can use SHA1 from SRP-6a, but it's highly recommended to use specialized +//! password hashing algorithm instead (e.g. PBKDF2, argon2 or scrypt). +//! +//! ```rust +//! use crate::srp::groups::G_2048; +//! use sha2::Sha256; // Note: You should probably use a proper password KDF +//! # use crate::srp::client::SrpClient; +//! +//! let client = SrpClient::::new(&G_2048); +//! ``` +//! +//! Next send handshake data (username and `a_pub`) to the server and receive +//! `salt` and `b_pub`: +//! +//! ```rust +//! # let client = crate::srp::client::SrpClient::::new(&crate::srp::groups::G_2048); +//! # fn server_response()-> (Vec, Vec) { (vec![], vec![]) } +//! +//! let mut a = [0u8; 64]; +//! // rng.fill_bytes(&mut a); +//! let a_pub = client.compute_public_ephemeral(&a); +//! let (salt, b_pub) = server_response(); +//! ``` +//! +//! Process the server response and create verifier instance. +//! process_reply can return error in case of malicious `b_pub`. +//! +//! ```rust +//! # let client = crate::srp::client::SrpClient::::new(&crate::srp::groups::G_2048); +//! # let a = [0u8; 64]; +//! # let username = b"username"; +//! # let password = b"password"; +//! # let salt = b"salt"; +//! # let b_pub = b"b_pub"; +//! +//! let private_key = (username, password, salt); +//! let verifier = client.process_reply(&a, username, password, salt, b_pub); +//! ``` +//! +//! Finally verify the server: first generate user proof, +//! send it to the server and verify server proof in the reply. Note that +//! `verify_server` method will return error in case of incorrect server reply. +//! +//! ```rust +//! # let client = crate::srp::client::SrpClient::::new(&crate::srp::groups::G_2048); +//! # let verifier = client.process_reply(b"", b"", b"", b"", b"1").unwrap(); +//! # fn send_proof(_: &[u8]) -> Vec { vec![173, 202, 13, 26, 207, 73, 0, 46, 121, 238, 48, 170, 96, 146, 60, 49, 88, 76, 12, 184, 152, 76, 207, 220, 140, 205, 190, 189, 117, 6, 131, 63] } +//! +//! let client_proof = verifier.proof(); +//! let server_proof = send_proof(client_proof); +//! verifier.verify_server(&server_proof).unwrap(); +//! ``` +//! +//! `key` contains shared secret key between user and the server. You can extract shared secret +//! key using `key()` method. +//! ```rust +//! # let client = crate::srp::client::SrpClient::::new(&crate::srp::groups::G_2048); +//! # let verifier = client.process_reply(b"", b"", b"", b"", b"1").unwrap(); +//! +//! verifier.key(); +//!``` +//! +//! +//! For user registration on the server first generate salt (e.g. 32 bytes long) +//! and get password verifier which depends on private key. Send username, salt +//! and password verifier over protected channel to protect against +//! Man-in-the-middle (MITM) attack for registration. +//! +//! ```rust +//! # let client = crate::srp::client::SrpClient::::new(&crate::srp::groups::G_2048); +//! # let username = b"username"; +//! # let password = b"password"; +//! # let salt = b"salt"; +//! # fn send_registration_data(_: &[u8], _: &[u8], _: &[u8]) {} +//! +//! let pwd_verifier = client.compute_verifier(username, password, salt); +//! send_registration_data(username, salt, &pwd_verifier); +//! ``` + +use digest::{Digest, Output}; +use num_bigint::BigUint; +use std::marker::PhantomData; +use subtle::ConstantTimeEq; + +use crate::types::{SrpAuthError, SrpGroup}; +use crate::utils::{compute_k, compute_m1, compute_m2, compute_u}; + +/// SRP client state before handshake with the server. +pub struct SrpClient<'a, D: Digest> { + params: &'a SrpGroup, + d: PhantomData, +} + +/// SRP client state after handshake with the server. +pub struct SrpClientVerifier { + m1: Output, + m2: Output, + key: Vec, +} + +impl<'a, D: Digest> SrpClient<'a, D> { + /// Create new SRP client instance. + pub fn new(params: &'a SrpGroup) -> Self { + Self { + params, + d: Default::default(), + } + } + + pub fn compute_a_pub(&self, a: &BigUint) -> BigUint { + self.params.g.modpow(a, &self.params.n) + } + + // H( | ":" | ) + pub fn compute_identity_hash(username: &[u8], password: &[u8]) -> Output { + let mut d = D::new(); + d.update(username); + d.update(b":"); + d.update(password); + d.finalize() + } + + // x = H( | H( | ":" | )) + pub fn compute_x(identity_hash: &[u8], salt: &[u8]) -> BigUint { + let mut x = D::new(); + x.update(salt); + x.update(identity_hash); + BigUint::from_bytes_be(&x.finalize()) + } + + // (B - (k * g^x)) ^ (a + (u * x)) % N + pub fn compute_premaster_secret( + &self, + b_pub: &BigUint, + k: &BigUint, + x: &BigUint, + a: &BigUint, + u: &BigUint, + ) -> BigUint { + // (k * g^x) + let base = (k * (self.params.g.modpow(x, &self.params.n))) % &self.params.n; + // Because we do operation in modulo N we can get: b_pub > base. That's not good. So we add N to b_pub to make sure. + // B - kg^x + let base = ((&self.params.n + b_pub) - &base) % &self.params.n; + let exp = (u * x) + a; + // S = (B - kg^x) ^ (a + ux) + // or + // S = base ^ exp + base.modpow(&exp, &self.params.n) + } + + // v = g^x % N + pub fn compute_v(&self, x: &BigUint) -> BigUint { + self.params.g.modpow(x, &self.params.n) + } + + /// Get password verifier (v in RFC5054) for user registration on the server. + pub fn compute_verifier(&self, username: &[u8], password: &[u8], salt: &[u8]) -> Vec { + let identity_hash = Self::compute_identity_hash(username, password); + let x = Self::compute_x(identity_hash.as_slice(), salt); + self.compute_v(&x).to_bytes_be() + } + + /// Get public ephemeral value for handshaking with the server. + /// g^a % N + pub fn compute_public_ephemeral(&self, a: &[u8]) -> Vec { + self.compute_a_pub(&BigUint::from_bytes_be(a)).to_bytes_be() + } + + /// Process server reply to the handshake. + /// a is a random value, + /// username, password is supplied by the user + /// salt and b_pub come from the server + pub fn process_reply( + &self, + a: &[u8], + username: &[u8], + password: &[u8], + salt: &[u8], + b_pub: &[u8], + ) -> Result, SrpAuthError> { + let a = BigUint::from_bytes_be(a); + // let a_pub = BigUint::from_bytes_be(&a_pub_bytes); + let a_pub = Self::compute_a_pub(&self, &a); + + let b_pub = BigUint::from_bytes_be(b_pub); + + // Safeguard against malicious B + if &b_pub % &self.params.n == BigUint::default() { + return Err(SrpAuthError::IllegalParameter("b_pub".to_owned())); + } + + let u = compute_u::(&a_pub.to_bytes_be(), &b_pub.to_bytes_be()); + let k = compute_k::(self.params); + let identity_hash = Self::compute_identity_hash(&[], password); + let x = Self::compute_x(identity_hash.as_slice(), salt); + + let key = self.compute_premaster_secret(&b_pub, &k, &x, &a, &u); + let key = D::digest(key.to_bytes_be()); + + let m1 = compute_m1::( + &a_pub.to_bytes_be(), + &b_pub.to_bytes_be(), + &key, + username, + salt, + self.params, + ); + + let m2 = compute_m2::(&a_pub.to_bytes_be(), &m1, &key); + + Ok(SrpClientVerifier { + m1, + m2, + key: key.to_vec(), + }) + } +} + +impl SrpClientVerifier { + /// Get shared secret key without authenticating server, e.g. for using with + /// authenticated encryption modes. DO NOT USE this method without + /// some kind of secure authentication + pub fn key(&self) -> &[u8] { + &self.key + } + + /// Verification data for sending to the server. + pub fn proof(&self) -> &[u8] { + self.m1.as_slice() + } + + /// Verify server reply to verification data. + pub fn verify_server(&self, reply: &[u8]) -> Result<(), SrpAuthError> { + if self.m2.ct_eq(reply).unwrap_u8() != 1 { + // aka == 0 + Err(SrpAuthError::BadRecordMac("server".to_owned())) + } else { + Ok(()) + } + } +} diff --git a/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups.rs b/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups.rs new file mode 100644 index 0000000..86bc681 --- /dev/null +++ b/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups.rs @@ -0,0 +1,57 @@ +//! Groups from [RFC 5054](https://tools.ietf.org/html/rfc5054) +//! +//! It is strongly recommended to use them instead of custom generated +//! groups. Additionally it is not recommended to use `G_1024` and `G_1536`, +//! they are provided only for compatibility with the legacy software. +use crate::types::SrpGroup; +use lazy_static::lazy_static; +use num_bigint::BigUint; + +lazy_static! { + pub static ref G_1024: SrpGroup = SrpGroup { + n: BigUint::from_bytes_be(include_bytes!("groups/1024.bin")), + g: BigUint::from_bytes_be(&[2]), + }; +} + +lazy_static! { + pub static ref G_1536: SrpGroup = SrpGroup { + n: BigUint::from_bytes_be(include_bytes!("groups/1536.bin")), + g: BigUint::from_bytes_be(&[2]), + }; +} + +lazy_static! { + pub static ref G_2048: SrpGroup = SrpGroup { + n: BigUint::from_bytes_be(include_bytes!("groups/2048.bin")), + g: BigUint::from_bytes_be(&[2]), + }; +} + +lazy_static! { + pub static ref G_3072: SrpGroup = SrpGroup { + n: BigUint::from_bytes_be(include_bytes!("groups/3072.bin")), + g: BigUint::from_bytes_be(&[5]), + }; +} + +lazy_static! { + pub static ref G_4096: SrpGroup = SrpGroup { + n: BigUint::from_bytes_be(include_bytes!("groups/4096.bin")), + g: BigUint::from_bytes_be(&[5]), + }; +} + +lazy_static! { + pub static ref G_6144: SrpGroup = SrpGroup { + n: BigUint::from_bytes_be(include_bytes!("groups/6144.bin")), + g: BigUint::from_bytes_be(&[5]), + }; +} + +lazy_static! { + pub static ref G_8192: SrpGroup = SrpGroup { + n: BigUint::from_bytes_be(include_bytes!("groups/8192.bin")), + g: BigUint::from_bytes_be(&[19]), + }; +} diff --git a/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/1024.bin b/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/1024.bin new file mode 100644 index 0000000..7ce0aa3 --- /dev/null +++ b/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/1024.bin @@ -0,0 +1,3 @@ +î¯ +¹­³Öœ3ø +úÅè`ra‡uÿ< ž¢1Lœ%evÖtßt–êÓ8;HÖ’ÆààÕØâP¹‹äŽI\`‰ÚÑ]Ç×´aTÖ¶ÎŽô­i±]I‚U›){Ï…Å)õffWìhí¼<rlÀ/ÔËô—nªšýQ8þƒvC[ŸÆ/Àëã \ No newline at end of file diff --git a/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/1536.bin b/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/1536.bin new file mode 100644 index 0000000..c3a5972 --- /dev/null +++ b/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/1536.bin @@ -0,0 +1 @@ +ï<¯¹9'z±ñ*†¤{»Û¥ô™¬L€¾î©aKÌM_O_Un'ËÞQÆ©Kä`z)X; ÐøC€¶U»š"èÜߊ|ìgðÐ4±È¹y‰›`ž 㺶=GTƒÛűüvN?KSÝ¡‹ý>+œŒõnß•94–'Û/Õ=$·Ä†ew.C}lŒäBsJ÷Ì·®ƒ|&J㩾¸Š/鸵).Zÿ^‘GžŒç¢Œ$BÆó“Iš#MÏvãþÑ5ù» \ No newline at end of file diff --git a/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/2048.bin b/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/2048.bin new file mode 100644 index 0000000..23207c6 --- /dev/null +++ b/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/2048.bin @@ -0,0 +1,2 @@ +¬kÛA2Jš›ñfÞ^‰X/¯r¶e‡îü1’”=µ`P£s)Ë´ ™í“àuwg¡=Õ#«K1 ÍH©ÚýPè9ií·g°Ï`•š:³fûÕúªè)©–/ “¸Uùy“ì—^ê¨ t +ÛôÿtsYÐAÕÃ>§(Dkw;Ê—´:#û€v½ zCldñÒ¹‡F[2æˆøwHTE#µ$°Õ}^§z'uÒìú,ûÛõ/³xa`'åz毇NsÎS)œÌ{ÃØ*V˜ó¨ÐÂq®5øéÛû¶”µÈØŸzä5Þ#mR_Tu›eãrüÖŽò§žJÿs \ No newline at end of file diff --git a/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/3072.bin b/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/3072.bin new file mode 100644 index 0000000..7e1a84d Binary files /dev/null and b/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/3072.bin differ diff --git a/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/4096.bin b/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/4096.bin new file mode 100644 index 0000000..82463c0 Binary files /dev/null and b/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/4096.bin differ diff --git a/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/6144.bin b/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/6144.bin new file mode 100644 index 0000000..83a559a Binary files /dev/null and b/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/6144.bin differ diff --git a/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/8192.bin b/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/8192.bin new file mode 100644 index 0000000..b1f32ac Binary files /dev/null and b/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/8192.bin differ diff --git a/apple-private-apis/icloud-auth/rustcrypto-srp/src/lib.rs b/apple-private-apis/icloud-auth/rustcrypto-srp/src/lib.rs new file mode 100644 index 0000000..bec6c11 --- /dev/null +++ b/apple-private-apis/icloud-auth/rustcrypto-srp/src/lib.rs @@ -0,0 +1,57 @@ +#![allow(clippy::many_single_char_names)] +#![doc(html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo_small.png")] +#![doc = include_str!("../README.md")] + +//! # Usage +//! Add `srp` dependency to your `Cargo.toml`: +//! +//! ```toml +//! [dependencies] +//! srp = "0.6" +//! ``` +//! +//! Next read documentation for [`client`](client/index.html) and +//! [`server`](server/index.html) modules. +//! +//! # Algorithm description +//! Here we briefly describe implemented algorithm. For additional information +//! refer to SRP literature. All arithmetic is done modulo `N`, where `N` is a +//! large safe prime (`N = 2q+1`, where `q` is prime). Additionally `g` MUST be +//! a generator modulo `N`. It's STRONGLY recommended to use SRP parameters +//! provided by this crate in the [`groups`](groups/index.html) module. +//! +//! | Client | Data transfer | Server | +//! |------------------------|-------------------|---------------------------------| +//! |`a_pub = g^a` | — `a_pub`, `I` —> | (lookup `s`, `v` for given `I`) | +//! |`x = PH(P, s)` | <— `b_pub`, `s` — | `b_pub = k*v + g^b` | +//! |`u = H(a_pub ‖ b_pub)` | | `u = H(a_pub ‖ b_pub)` | +//! |`s = (b_pub - k*g^x)^(a+u*x)` | | `S = (b_pub - k*g^x)^(a+u*x)` | +//! |`K = H(s)` | | `K = H(s)` | +//! |`M1 = H(A ‖ B ‖ K)` | — `M1` —> | (verify `M1`) | +//! |(verify `M2`) | <— `M2` — | `M2 = H(A ‖ M1 ‖ K)` | +//! +//! Variables and notations have the following meaning: +//! +//! - `I` — user identity (username) +//! - `P` — user password +//! - `H` — one-way hash function +//! - `PH` — password hashing algroithm, in the RFC 5054 described as +//! `H(s ‖ H(I ‖ ":" ‖ P))` +//! - `^` — (modular) exponentiation +//! - `‖` — concatenation +//! - `x` — user private key +//! - `s` — salt generated by user and stored on the server +//! - `v` — password verifier equal to `g^x` and stored on the server +//! - `a`, `b` — secret ephemeral values (at least 256 bits in length) +//! - `A`, `B` — Public ephemeral values +//! - `u` — scrambling parameter +//! - `k` — multiplier parameter (`k = H(N || g)` in SRP-6a) +//! +//! [1]: https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol +//! [2]: https://tools.ietf.org/html/rfc5054 + +pub mod client; +pub mod groups; +pub mod server; +pub mod types; +pub mod utils; diff --git a/apple-private-apis/icloud-auth/rustcrypto-srp/src/server.rs b/apple-private-apis/icloud-auth/rustcrypto-srp/src/server.rs new file mode 100644 index 0000000..2175571 --- /dev/null +++ b/apple-private-apis/icloud-auth/rustcrypto-srp/src/server.rs @@ -0,0 +1,190 @@ +//! SRP server implementation +//! +//! # Usage +//! First receive user's username and public value `a_pub`, retrieve from a +//! database the salt and verifier for a given username. Generate `b` and public value `b_pub`. +//! +//! +//! ```rust +//! use crate::srp::groups::G_2048; +//! use sha2::Sha256; // Note: You should probably use a proper password KDF +//! # use crate::srp::server::SrpServer; +//! # fn get_client_request()-> (Vec, Vec) { (vec![], vec![])} +//! # fn get_user(_: &[u8])-> (Vec, Vec) { (vec![], vec![])} +//! +//! let server = SrpServer::::new(&G_2048); +//! let (username, a_pub) = get_client_request(); +//! let (salt, v) = get_user(&username); +//! let mut b = [0u8; 64]; +//! // rng.fill_bytes(&mut b); +//! let b_pub = server.compute_public_ephemeral(&b, &v); +//! ``` +//! +//! Next send to user `b_pub` and `salt` from user record +//! +//! Next process the user response: +//! +//! ```rust +//! # let server = crate::srp::server::SrpServer::::new(&crate::srp::groups::G_2048); +//! # fn get_client_response() -> Vec { vec![1] } +//! # let b = [0u8; 64]; +//! # let v = b""; +//! +//! let a_pub = get_client_response(); +//! let verifier = server.process_reply(&b, v, &a_pub).unwrap(); +//! ``` +//! +//! +//! And finally receive user proof, verify it and send server proof in the +//! reply: +//! +//! ```rust +//! # let server = crate::srp::server::SrpServer::::new(&crate::srp::groups::G_2048); +//! # let verifier = server.process_reply(b"", b"", b"1").unwrap(); +//! # fn get_client_proof()-> Vec { vec![26, 80, 8, 243, 111, 162, 238, 171, 208, 237, 207, 46, 46, 137, 44, 213, 105, 208, 84, 224, 244, 216, 103, 145, 14, 103, 182, 56, 242, 4, 179, 57] }; +//! # fn send_proof(_: &[u8]) { }; +//! +//! let client_proof = get_client_proof(); +//! verifier.verify_client(&client_proof).unwrap(); +//! send_proof(verifier.proof()); +//! ``` +//! +//! +//! `key` contains shared secret key between user and the server. You can extract shared secret +//! key using `key()` method. +//! ```rust +//! # let server = crate::srp::server::SrpServer::::new(&crate::srp::groups::G_2048); +//! # let verifier = server.process_reply(b"", b"", b"1").unwrap(); +//! +//! verifier.key(); +//!``` +//! +use std::marker::PhantomData; + +use digest::{Digest, Output}; +use num_bigint::BigUint; +use subtle::ConstantTimeEq; + +use crate::types::{SrpAuthError, SrpGroup}; +use crate::utils::{compute_k, compute_m1, compute_m2, compute_u}; + +/// SRP server state +pub struct SrpServer<'a, D: Digest> { + params: &'a SrpGroup, + d: PhantomData, +} + +/// SRP server state after handshake with the client. +pub struct SrpServerVerifier { + m1: Output, + m2: Output, + key: Vec, +} + +impl<'a, D: Digest> SrpServer<'a, D> { + /// Create new server state. + pub fn new(params: &'a SrpGroup) -> Self { + Self { + params, + d: Default::default(), + } + } + + // k*v + g^b % N + pub fn compute_b_pub(&self, b: &BigUint, k: &BigUint, v: &BigUint) -> BigUint { + let inter = (k * v) % &self.params.n; + (inter + self.params.g.modpow(b, &self.params.n)) % &self.params.n + } + + // = (A * v^u) ^ b % N + pub fn compute_premaster_secret( + &self, + a_pub: &BigUint, + v: &BigUint, + u: &BigUint, + b: &BigUint, + ) -> BigUint { + // (A * v^u) + let base = (a_pub * v.modpow(u, &self.params.n)) % &self.params.n; + base.modpow(b, &self.params.n) + } + + /// Get public ephemeral value for sending to the client. + pub fn compute_public_ephemeral(&self, b: &[u8], v: &[u8]) -> Vec { + self.compute_b_pub( + &BigUint::from_bytes_be(b), + &compute_k::(self.params), + &BigUint::from_bytes_be(v), + ) + .to_bytes_be() + } + + /// Process client reply to the handshake. + /// b is a random value, + /// v is the provided during initial user registration + pub fn process_reply( + &self, + b: &[u8], + v: &[u8], + a_pub: &[u8], + username: &[u8], + salt: &[u8], + ) -> Result, SrpAuthError> { + let b = BigUint::from_bytes_be(b); + let v = BigUint::from_bytes_be(v); + let a_pub = BigUint::from_bytes_be(a_pub); + + let k = compute_k::(self.params); + let b_pub = self.compute_b_pub(&b, &k, &v); + + // Safeguard against malicious A + if &a_pub % &self.params.n == BigUint::default() { + return Err(SrpAuthError::IllegalParameter("a_pub".to_owned())); + } + + let u = compute_u::(&a_pub.to_bytes_be(), &b_pub.to_bytes_be()); + + let key = self.compute_premaster_secret(&a_pub, &v, &u, &b); + + let m1 = compute_m1::( + &a_pub.to_bytes_be(), + &b_pub.to_bytes_be(), + &key.to_bytes_be(), + username, + salt, + self.params, + ); + + let m2 = compute_m2::(&a_pub.to_bytes_be(), &m1, &key.to_bytes_be()); + + Ok(SrpServerVerifier { + m1, + m2, + key: key.to_bytes_be(), + }) + } +} + +impl SrpServerVerifier { + /// Get shared secret between user and the server. (do not forget to verify + /// that keys are the same!) + pub fn key(&self) -> &[u8] { + &self.key + } + + /// Verification data for sending to the client. + pub fn proof(&self) -> &[u8] { + // TODO not Output + self.m2.as_slice() + } + + /// Process user proof of having the same shared secret. + pub fn verify_client(&self, reply: &[u8]) -> Result<(), SrpAuthError> { + if self.m1.ct_eq(reply).unwrap_u8() != 1 { + // aka == 0 + Err(SrpAuthError::BadRecordMac("client".to_owned())) + } else { + Ok(()) + } + } +} diff --git a/apple-private-apis/icloud-auth/rustcrypto-srp/src/types.rs b/apple-private-apis/icloud-auth/rustcrypto-srp/src/types.rs new file mode 100644 index 0000000..2310b8c --- /dev/null +++ b/apple-private-apis/icloud-auth/rustcrypto-srp/src/types.rs @@ -0,0 +1,45 @@ +//! Additional SRP types. +use num_bigint::BigUint; +use std::fmt; + +/// SRP authentication error. +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum SrpAuthError { + IllegalParameter(String), + BadRecordMac(String), +} + +impl fmt::Display for SrpAuthError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + SrpAuthError::IllegalParameter(param) => { + write!(f, "illegal_parameter: bad '{}' value", param) + } + SrpAuthError::BadRecordMac(param) => { + write!(f, "bad_record_mac: incorrect '{}' proof", param) + } + } + } +} + +/// Group used for SRP computations +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct SrpGroup { + /// A large safe prime (N = 2q+1, where q is prime) + pub n: BigUint, + /// A generator modulo N + pub g: BigUint, +} + +// #[cfg(test)] +// mod tests { +// use crate::groups::G_1024; +// use crate::utils::compute_k; +// use sha1::Sha1; + +// #[test] +// fn test_k_1024_sha1() { +// let k = compute_k::(&G_1024).to_bytes_be(); +// assert_eq!(&k, include_bytes!("test/k_sha1_1024.bin")); +// } +// } diff --git a/apple-private-apis/icloud-auth/rustcrypto-srp/src/utils.rs b/apple-private-apis/icloud-auth/rustcrypto-srp/src/utils.rs new file mode 100644 index 0000000..bfdda50 --- /dev/null +++ b/apple-private-apis/icloud-auth/rustcrypto-srp/src/utils.rs @@ -0,0 +1,70 @@ +use digest::{Digest, Output}; +use num_bigint::BigUint; + +use crate::types::SrpGroup; + +// u = H(PAD(A) | PAD(B)) +pub fn compute_u(a_pub: &[u8], b_pub: &[u8]) -> BigUint { + let mut u = D::new(); + u.update(a_pub); + u.update(b_pub); + BigUint::from_bytes_be(&u.finalize()) +} + +// k = H(N | PAD(g)) +pub fn compute_k(params: &SrpGroup) -> BigUint { + let n = params.n.to_bytes_be(); + let g_bytes = params.g.to_bytes_be(); + let mut buf = vec![0u8; n.len()]; + let l = n.len() - g_bytes.len(); + buf[l..].copy_from_slice(&g_bytes); + + let mut d = D::new(); + d.update(&n); + d.update(&buf); + BigUint::from_bytes_be(d.finalize().as_slice()) +} + +// M1 = H(A, B, K) this doesn't follow the spec but apparently no one does for M1 +// M1 should equal = H(H(N) XOR H(g) | H(U) | s | A | B | K) according to the spec +pub fn compute_m1( + a_pub: &[u8], + b_pub: &[u8], + key: &[u8], + username: &[u8], + salt: &[u8], + params: &SrpGroup, +) -> Output { + let n = params.n.to_bytes_be(); + let g_bytes = params.g.to_bytes_be(); + //pad g and n to the same length + let mut g = vec![0; n.len() - g_bytes.len()]; + g.extend_from_slice(&g_bytes); + + // Compute the hash of n and g + let mut g_hash = D::digest(&g); + let n_hash = D::digest(&n); + + // XOR the hashes + for i in 0..g_hash.len() { + g_hash[i] ^= n_hash[i]; + } + + let mut d = D::new(); + d.update(&g_hash); + d.update(D::digest(username)); + d.update(salt); + d.update(a_pub); + d.update(b_pub); + d.update(key); + d.finalize() +} + +// M2 = H(A, M1, K) +pub fn compute_m2(a_pub: &[u8], m1: &Output, key: &[u8]) -> Output { + let mut d = D::new(); + d.update(&a_pub); + d.update(&m1); + d.update(&key); + d.finalize() +} diff --git a/apple-private-apis/icloud-auth/src/anisette.rs b/apple-private-apis/icloud-auth/src/anisette.rs new file mode 100644 index 0000000..3349f91 --- /dev/null +++ b/apple-private-apis/icloud-auth/src/anisette.rs @@ -0,0 +1,108 @@ +use crate::Error; +use omnisette::{AnisetteConfiguration, AnisetteHeaders}; +use std::{collections::HashMap, time::SystemTime}; + +#[derive(Debug, Clone)] +pub struct AnisetteData { + pub base_headers: HashMap, + pub generated_at: SystemTime, + pub config: AnisetteConfiguration, +} + +impl AnisetteData { + /// Fetches the data at an anisette server + pub async fn new(config: AnisetteConfiguration) -> Result { + let mut b = AnisetteHeaders::get_anisette_headers_provider(config.clone())?; + let base_headers = b.provider.get_authentication_headers().await?; + + Ok(AnisetteData { + base_headers, + generated_at: SystemTime::now(), + config, + }) + } + + pub fn needs_refresh(&self) -> bool { + let elapsed = self.generated_at.elapsed().unwrap(); + elapsed.as_secs() > 60 + } + + pub fn is_valid(&self) -> bool { + let elapsed = self.generated_at.elapsed().unwrap(); + elapsed.as_secs() < 90 + } + + pub async fn refresh(&self) -> Result { + Self::new(self.config.clone()).await + } + + pub fn generate_headers( + &self, + cpd: bool, + client_info: bool, + app_info: bool, + ) -> HashMap { + if !self.is_valid() { + panic!("Invalid data!") + } + let mut headers = self.base_headers.clone(); + let old_client_info = headers.remove("X-Mme-Client-Info"); + if client_info { + let client_info = match old_client_info { + Some(v) => { + let temp = v.as_str(); + + temp.replace( + temp.split('<').nth(3).unwrap().split('>').nth(0).unwrap(), + "com.apple.AuthKit/1 (com.apple.dt.Xcode/3594.4.19)", + ) + } + None => { + return headers; + } + }; + headers.insert("X-Mme-Client-Info".to_owned(), client_info.to_owned()); + } + + if app_info { + headers.insert( + "X-Apple-App-Info".to_owned(), + "com.apple.gs.xcode.auth".to_owned(), + ); + headers.insert("X-Xcode-Version".to_owned(), "14.2 (14C18)".to_owned()); + } + + if cpd { + headers.insert("bootstrap".to_owned(), "true".to_owned()); + headers.insert("icscrec".to_owned(), "true".to_owned()); + headers.insert("loc".to_owned(), "en_GB".to_owned()); + headers.insert("pbe".to_owned(), "false".to_owned()); + headers.insert("prkgen".to_owned(), "true".to_owned()); + headers.insert("svct".to_owned(), "iCloud".to_owned()); + } + + headers + } + + pub fn to_plist(&self, cpd: bool, client_info: bool, app_info: bool) -> plist::Dictionary { + let mut plist = plist::Dictionary::new(); + for (key, value) in self.generate_headers(cpd, client_info, app_info).iter() { + plist.insert(key.to_owned(), plist::Value::String(value.to_owned())); + } + + plist + } + + pub fn get_header(&self, header: &str) -> Result { + let headers = self + .generate_headers(true, true, true) + .iter() + .map(|(k, v)| (k.to_lowercase(), v.to_lowercase())) + .collect::>(); + + match headers.get(&header.to_lowercase()) { + Some(v) => Ok(v.to_string()), + None => Err(Error::Parse), + } + } +} diff --git a/apple-private-apis/icloud-auth/src/apple_root.der b/apple-private-apis/icloud-auth/src/apple_root.der new file mode 100644 index 0000000..8a9ff24 Binary files /dev/null and b/apple-private-apis/icloud-auth/src/apple_root.der differ diff --git a/apple-private-apis/icloud-auth/src/client.rs b/apple-private-apis/icloud-auth/src/client.rs new file mode 100644 index 0000000..08c88e0 --- /dev/null +++ b/apple-private-apis/icloud-auth/src/client.rs @@ -0,0 +1,827 @@ +use crate::{anisette::AnisetteData, Error}; +use aes::cipher::block_padding::Pkcs7; +use botan::Cipher; +use cbc::cipher::{BlockDecryptMut, KeyIvInit}; +use hmac::{Hmac, Mac}; +use omnisette::AnisetteConfiguration; +use reqwest::{ + header::{HeaderMap, HeaderName, HeaderValue}, + Certificate, Client, ClientBuilder, Response, +}; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use srp::{ + client::{SrpClient, SrpClientVerifier}, + groups::G_2048, +}; +use std::str::FromStr; +use tokio::sync::Mutex; + +const GSA_ENDPOINT: &str = "https://gsa.apple.com/grandslam/GsService2"; +const APPLE_ROOT: &[u8] = include_bytes!("./apple_root.der"); + +#[derive(Debug, Serialize, Deserialize)] +pub struct InitRequestBody { + #[serde(rename = "A2k")] + a_pub: plist::Value, + cpd: plist::Dictionary, + #[serde(rename = "o")] + operation: String, + ps: Vec, + #[serde(rename = "u")] + username: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct RequestHeader { + #[serde(rename = "Version")] + version: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct InitRequest { + #[serde(rename = "Header")] + header: RequestHeader, + #[serde(rename = "Request")] + request: InitRequestBody, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ChallengeRequestBody { + #[serde(rename = "M1")] + m: plist::Value, + cpd: plist::Dictionary, + c: String, + #[serde(rename = "o")] + operation: String, + #[serde(rename = "u")] + username: String, +} +#[derive(Debug, Serialize, Deserialize)] +pub struct ChallengeRequest { + #[serde(rename = "Header")] + header: RequestHeader, + #[serde(rename = "Request")] + request: ChallengeRequestBody, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct AuthTokenRequestBody { + app: Vec, + c: plist::Value, + cpd: plist::Dictionary, + #[serde(rename = "o")] + operation: String, + t: String, + u: String, + checksum: plist::Value, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct AuthTokenRequest { + #[serde(rename = "Header")] + header: RequestHeader, + #[serde(rename = "Request")] + request: AuthTokenRequestBody, +} + +pub struct AppleAccount { + //TODO: move this to omnisette + pub anisette: Mutex, + // pub spd: Option, + //mutable spd + pub spd: Option, + client: Client, +} + +#[derive(Clone, Debug)] +pub struct AppToken { + pub app_tokens: plist::Dictionary, + pub auth_token: String, + pub app: String, +} +//Just make it return a custom enum, with LoggedIn(account: AppleAccount) or Needs2FA(FinishLoginDel: fn(i32) -> TFAResponse) +#[repr(C)] +#[derive(Debug)] +pub enum LoginState { + LoggedIn, + // NeedsSMS2FASent(Send2FAToDevices), + NeedsDevice2FA, + Needs2FAVerification, + NeedsSMS2FA, + NeedsSMS2FAVerification(VerifyBody), + NeedsExtraStep(String), + NeedsLogin, +} + +#[derive(Serialize, Debug, Clone)] +struct VerifyCode { + code: String, +} + +#[derive(Serialize, Debug, Clone)] +struct PhoneNumber { + id: u32, +} + +#[derive(Serialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct VerifyBody { + phone_number: PhoneNumber, + mode: String, + security_code: Option, +} + +#[repr(C)] +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TrustedPhoneNumber { + pub number_with_dial_code: String, + pub last_two_digits: String, + pub push_mode: String, + pub id: u32, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AuthenticationExtras { + pub trusted_phone_numbers: Vec, + pub recovery_url: Option, + pub cant_use_phone_number_url: Option, + pub dont_have_access_url: Option, + pub recovery_web_url: Option, + pub repair_phone_number_url: Option, + pub repair_phone_number_web_url: Option, + #[serde(skip)] + pub new_state: Option, +} + +async fn parse_response( + res: Result, +) -> Result { + let res = res?.text().await?; + let res: plist::Dictionary = plist::from_bytes(res.as_bytes())?; + let res: plist::Value = res.get("Response").unwrap().to_owned(); + match res { + plist::Value::Dictionary(dict) => Ok(dict), + _ => Err(crate::Error::Parse), + } +} + +impl AppleAccount { + pub async fn new(config: AnisetteConfiguration) -> Result { + let anisette = AnisetteData::new(config).await?; + Ok(Self::new_with_anisette(anisette)?) + } + + pub fn new_with_anisette(anisette: AnisetteData) -> Result { + let client = ClientBuilder::new() + .add_root_certificate(Certificate::from_der(APPLE_ROOT)?) + // uncomment when debugging w/ charles proxy + // .danger_accept_invalid_certs(true) + .http1_title_case_headers() + .connection_verbose(true) + .build()?; + + Ok(AppleAccount { + client, + anisette: Mutex::new(anisette), + spd: None, + }) + } + + pub async fn login( + appleid_closure: impl Fn() -> Result<(String, String), String>, + tfa_closure: impl Fn() -> Result, + config: AnisetteConfiguration, + ) -> Result { + let anisette = AnisetteData::new(config).await?; + AppleAccount::login_with_anisette(appleid_closure, tfa_closure, anisette).await + } + + pub async fn get_anisette(&self) -> AnisetteData { + let mut locked = self.anisette.lock().await; + if locked.needs_refresh() { + *locked = locked.refresh().await.unwrap(); + } + locked.clone() + } + + pub async fn get_app_token(&self, app_name: &str) -> Result { + let spd = self.spd.as_ref().unwrap(); + let dsid = spd.get("adsid").unwrap().as_string().unwrap(); + let auth_token = spd.get("GsIdmsToken").unwrap().as_string().unwrap(); + + let valid_anisette = self.get_anisette().await; + + let sk = spd.get("sk").unwrap().as_data().unwrap(); + let c = spd.get("c").unwrap().as_data().unwrap(); + + let checksum = Self::create_checksum(&sk.to_vec(), dsid, app_name); + + let mut gsa_headers = HeaderMap::new(); + gsa_headers.insert( + "Content-Type", + HeaderValue::from_str("text/x-xml-plist").unwrap(), + ); + gsa_headers.insert("Accept", HeaderValue::from_str("*/*").unwrap()); + gsa_headers.insert( + "User-Agent", + HeaderValue::from_str("akd/1.0 CFNetwork/978.0.7 Darwin/18.7.0").unwrap(), + ); + gsa_headers.insert( + "X-MMe-Client-Info", + HeaderValue::from_str(&valid_anisette.get_header("x-mme-client-info")?).unwrap(), + ); + + let header = RequestHeader { + version: "1.0.1".to_string(), + }; + let body = AuthTokenRequestBody { + cpd: valid_anisette.to_plist(true, false, false), + app: vec![app_name.to_string()], + c: plist::Value::Data(c.to_vec()), + operation: "apptokens".to_owned(), + t: auth_token.to_string(), + u: dsid.to_string(), + checksum: plist::Value::Data(checksum), + }; + + let packet = AuthTokenRequest { + header: header.clone(), + request: body, + }; + + let mut buffer = Vec::new(); + plist::to_writer_xml(&mut buffer, &packet)?; + let buffer = String::from_utf8(buffer).unwrap(); + + let res = self + .client + .post(GSA_ENDPOINT) + .headers(gsa_headers.clone()) + .body(buffer) + .send() + .await; + let res = parse_response(res).await?; + let err_check = Self::check_error(&res); + if err_check.is_err() { + return Err(err_check.err().unwrap()); + } + + // --- D code logic starts here --- + let encrypted_token = res + .get("et") + .ok_or(Error::Parse)? + .as_data() + .ok_or(Error::Parse)?; + + if encrypted_token.len() < 3 + 16 + 16 { + return Err(Error::Parse); + } + let header = &encrypted_token[0..3]; + if header != b"XYZ" { + return Err(Error::AuthSrpWithMessage( + 0, + "Encrypted token is in an unknown format.".to_string(), + )); + } + let iv = &encrypted_token[3..19]; // 16 bytes + let ciphertext_and_tag = &encrypted_token[19..]; + + if sk.len() != 32 { + return Err(Error::Parse); + } + if iv.len() != 16 { + return Err(Error::Parse); + } + + // Botan AES-256/GCM decryption with 16-byte IV and 3-byte AAD + // true = encrypt, false = decrypt + let mut cipher = Cipher::new("AES-256/GCM", botan::CipherDirection::Decrypt) + .map_err(|_| Error::Parse)?; + cipher.set_key(sk).map_err(|_| Error::Parse)?; + cipher + .set_associated_data(header) + .map_err(|_| Error::Parse)?; + cipher.start(iv).map_err(|_| Error::Parse)?; + + let mut buf = ciphertext_and_tag.to_vec(); + buf = cipher.finish(&mut buf).map_err(|_| { + Error::AuthSrpWithMessage( + 0, + "Failed to decrypt app token (Botan AES-256/GCM).".to_string(), + ) + })?; + + let decrypted_token: plist::Dictionary = + plist::from_bytes(&buf).map_err(|_| Error::Parse)?; + + let t_val = decrypted_token.get("t").ok_or(Error::Parse)?; + let app_tokens = t_val.as_dictionary().ok_or(Error::Parse)?; + let app_token_dict = app_tokens.get(app_name).ok_or(Error::Parse)?; + let app_token = app_token_dict.as_dictionary().ok_or(Error::Parse)?; + let token = app_token + .get("token") + .and_then(|v| v.as_string()) + .ok_or(Error::Parse)?; + + Ok(AppToken { + app_tokens: app_tokens.clone(), + auth_token: token.to_string(), + app: app_name.to_string(), + }) + } + + fn create_checksum(session_key: &Vec, dsid: &str, app_name: &str) -> Vec { + Hmac::::new_from_slice(&session_key) + .unwrap() + .chain_update("apptokens".as_bytes()) + .chain_update(dsid.as_bytes()) + .chain_update(app_name.as_bytes()) + .finalize() + .into_bytes() + .to_vec() + } + + /// # Arguments + /// + /// * `appleid_closure` - A closure that takes no arguments and returns a tuple of the Apple ID and password + /// * `tfa_closure` - A closure that takes no arguments and returns the 2FA code + /// * `anisette` - AnisetteData + /// # Examples + /// + /// ``` + /// use icloud_auth::AppleAccount; + /// use omnisette::AnisetteData; + /// + /// let anisette = AnisetteData::new(); + /// let account = AppleAccount::login( + /// || ("test@waffle.me", "password") + /// || "123123", + /// anisette + /// ); + /// ``` + /// Note: You would not provide the 2FA code like this, you would have to actually ask input for it. + //TODO: add login_with_anisette and login, where login autodetcts anisette + pub async fn login_with_anisette Result<(String, String), String>, G: Fn() -> Result>( + appleid_closure: F, + tfa_closure: G, + anisette: AnisetteData, + ) -> Result { + let mut _self = AppleAccount::new_with_anisette(anisette)?; + let (username, password) = appleid_closure().map_err(|e| { + Error::AuthSrpWithMessage(0, format!("Failed to get Apple ID credentials: {}", e)) + })?; + let mut response = _self.login_email_pass(&username, &password).await?; + loop { + match response { + LoginState::NeedsDevice2FA => response = _self.send_2fa_to_devices().await?, + LoginState::Needs2FAVerification => { + response = _self.verify_2fa(tfa_closure().map_err(|e| { + Error::AuthSrpWithMessage(0, format!("Failed to get 2FA code: {}", e)) + })?).await? + } + LoginState::NeedsSMS2FA => response = _self.send_sms_2fa_to_devices(1).await?, + LoginState::NeedsSMS2FAVerification(body) => { + response = _self.verify_sms_2fa(tfa_closure().map_err(|e| { + Error::AuthSrpWithMessage(0, format!("Failed to get SMS 2FA code: {}", e)) + })?, body).await? + } + LoginState::NeedsLogin => { + response = _self.login_email_pass(&username, &password).await? + } + LoginState::LoggedIn => return Ok(_self), + LoginState::NeedsExtraStep(step) => { + if _self.get_pet().is_some() { + return Ok(_self); + } else { + return Err(Error::ExtraStep(step)); + } + } + } + } + } + + pub fn get_pet(&self) -> Option { + let Some(token) = self.spd.as_ref().unwrap().get("t") else { + return None; + }; + Some( + token + .as_dictionary() + .unwrap() + .get("com.apple.gs.idms.pet") + .unwrap() + .as_dictionary() + .unwrap() + .get("token") + .unwrap() + .as_string() + .unwrap() + .to_string(), + ) + } + + pub fn get_name(&self) -> (String, String) { + ( + self.spd + .as_ref() + .unwrap() + .get("fn") + .unwrap() + .as_string() + .unwrap() + .to_string(), + self.spd + .as_ref() + .unwrap() + .get("ln") + .unwrap() + .as_string() + .unwrap() + .to_string(), + ) + } + + pub async fn login_email_pass( + &mut self, + username: &str, + password: &str, + ) -> Result { + let srp_client = SrpClient::::new(&G_2048); + let a: Vec = (0..32).map(|_| rand::random::()).collect(); + let a_pub = srp_client.compute_public_ephemeral(&a); + + let valid_anisette = self.get_anisette().await; + + let mut gsa_headers = HeaderMap::new(); + gsa_headers.insert( + "Content-Type", + HeaderValue::from_str("text/x-xml-plist").unwrap(), + ); + gsa_headers.insert("Accept", HeaderValue::from_str("*/*").unwrap()); + gsa_headers.insert( + "User-Agent", + HeaderValue::from_str("akd/1.0 CFNetwork/978.0.7 Darwin/18.7.0").unwrap(), + ); + gsa_headers.insert( + "X-MMe-Client-Info", + HeaderValue::from_str(&valid_anisette.get_header("x-mme-client-info")?).unwrap(), + ); + + let header = RequestHeader { + version: "1.0.1".to_string(), + }; + let body = InitRequestBody { + a_pub: plist::Value::Data(a_pub), + cpd: valid_anisette.to_plist(true, false, false), + operation: "init".to_string(), + ps: vec!["s2k".to_string(), "s2k_fo".to_string()], + username: username.to_string(), + }; + + let packet = InitRequest { + header: header.clone(), + request: body, + }; + + let mut buffer = Vec::new(); + plist::to_writer_xml(&mut buffer, &packet)?; + let buffer = String::from_utf8(buffer).unwrap(); + + // println!("{:?}", gsa_headers.clone()); + // println!("{:?}", buffer); + + let res = self + .client + .post(GSA_ENDPOINT) + .headers(gsa_headers.clone()) + .body(buffer) + .send() + .await; + + let res = parse_response(res).await?; + let err_check = Self::check_error(&res); + if err_check.is_err() { + return Err(err_check.err().unwrap()); + } + // println!("{:?}", res); + let salt = res.get("s").unwrap().as_data().unwrap(); + let b_pub = res.get("B").unwrap().as_data().unwrap(); + let iters = res.get("i").unwrap().as_signed_integer().unwrap(); + let c = res.get("c").unwrap().as_string().unwrap(); + + let hashed_password = Sha256::digest(password.as_bytes()); + + let mut password_buf = [0u8; 32]; + pbkdf2::pbkdf2::>( + &hashed_password, + salt, + iters as u32, + &mut password_buf, + ); + + let verifier: SrpClientVerifier = srp_client + .process_reply(&a, &username.as_bytes(), &password_buf, salt, b_pub) + .unwrap(); + + let m = verifier.proof(); + + let body = ChallengeRequestBody { + m: plist::Value::Data(m.to_vec()), + c: c.to_string(), + cpd: valid_anisette.to_plist(true, false, false), + operation: "complete".to_string(), + username: username.to_string(), + }; + + let packet = ChallengeRequest { + header, + request: body, + }; + + let mut buffer = Vec::new(); + plist::to_writer_xml(&mut buffer, &packet)?; + let buffer = String::from_utf8(buffer).unwrap(); + + let res = self + .client + .post(GSA_ENDPOINT) + .headers(gsa_headers.clone()) + .body(buffer) + .send() + .await; + + let res = parse_response(res).await?; + let err_check = Self::check_error(&res); + if err_check.is_err() { + return Err(err_check.err().unwrap()); + } + // println!("{:?}", res); + let m2 = res.get("M2").unwrap().as_data().unwrap(); + verifier.verify_server(&m2).unwrap(); + + let spd = res.get("spd").unwrap().as_data().unwrap(); + let decrypted_spd = Self::decrypt_cbc(&verifier, spd); + let decoded_spd: plist::Dictionary = plist::from_bytes(&decrypted_spd).unwrap(); + + let status = res.get("Status").unwrap().as_dictionary().unwrap(); + + self.spd = Some(decoded_spd); + + if let Some(plist::Value::String(s)) = status.get("au") { + return match s.as_str() { + "trustedDeviceSecondaryAuth" => Ok(LoginState::NeedsDevice2FA), + "secondaryAuth" => Ok(LoginState::NeedsSMS2FA), + _unk => Ok(LoginState::NeedsExtraStep(_unk.to_string())), + }; + } + + Ok(LoginState::LoggedIn) + } + + fn create_session_key(usr: &SrpClientVerifier, name: &str) -> Vec { + Hmac::::new_from_slice(&usr.key()) + .unwrap() + .chain_update(name.as_bytes()) + .finalize() + .into_bytes() + .to_vec() + } + + fn decrypt_cbc(usr: &SrpClientVerifier, data: &[u8]) -> Vec { + let extra_data_key = Self::create_session_key(usr, "extra data key:"); + let extra_data_iv = Self::create_session_key(usr, "extra data iv:"); + let extra_data_iv = &extra_data_iv[..16]; + + cbc::Decryptor::::new_from_slices(&extra_data_key, extra_data_iv) + .unwrap() + .decrypt_padded_vec_mut::(&data) + .unwrap() + } + + pub async fn send_2fa_to_devices(&self) -> Result { + let headers = self.build_2fa_headers(false); + + let res = self + .client + .get("https://gsa.apple.com/auth/verify/trusteddevice") + .headers(headers.await) + .send() + .await?; + + if !res.status().is_success() { + return Err(Error::AuthSrp); + } + + return Ok(LoginState::Needs2FAVerification); + } + + pub async fn send_sms_2fa_to_devices(&self, phone_id: u32) -> Result { + let headers = self.build_2fa_headers(true); + + let body = VerifyBody { + phone_number: PhoneNumber { id: phone_id }, + mode: "sms".to_string(), + security_code: None, + }; + + let res = self + .client + .put("https://gsa.apple.com/auth/verify/phone/") + .headers(headers.await) + .json(&body) + .send() + .await?; + + if !res.status().is_success() { + return Err(Error::AuthSrp); + } + + return Ok(LoginState::NeedsSMS2FAVerification(body)); + } + + pub async fn get_auth_extras(&self) -> Result { + let headers = self.build_2fa_headers(true); + + let req = self + .client + .get("https://gsa.apple.com/auth") + .headers(headers.await) + .header("Accept", "application/json") + .send() + .await?; + let status = req.status().as_u16(); + let mut new_state = req.json::().await?; + if status == 201 { + new_state.new_state = Some(LoginState::NeedsSMS2FAVerification(VerifyBody { + phone_number: PhoneNumber { + id: new_state.trusted_phone_numbers.first().unwrap().id, + }, + mode: "sms".to_string(), + security_code: None, + })); + } + + Ok(new_state) + } + + pub async fn verify_2fa(&self, code: String) -> Result { + let headers = self.build_2fa_headers(false); + // println!("Recieved code: {}", code); + let res = self + .client + .get("https://gsa.apple.com/grandslam/GsService2/validate") + .headers(headers.await) + .header( + HeaderName::from_str("security-code").unwrap(), + HeaderValue::from_str(&code).unwrap(), + ) + .send() + .await?; + + let res: plist::Dictionary = plist::from_bytes(res.text().await?.as_bytes())?; + + Self::check_error(&res)?; + + Ok(LoginState::NeedsLogin) + } + + pub async fn verify_sms_2fa( + &self, + code: String, + mut body: VerifyBody, + ) -> Result { + let headers = self.build_2fa_headers(true).await; + // println!("Recieved code: {}", code); + + body.security_code = Some(VerifyCode { code }); + + let res = self + .client + .post("https://gsa.apple.com/auth/verify/phone/securitycode") + .headers(headers) + .header("accept", "application/json") + .json(&body) + .send() + .await?; + + if res.status() != 200 { + return Err(Error::Bad2faCode); + } + + Ok(LoginState::NeedsLogin) + } + + fn check_error(res: &plist::Dictionary) -> Result<(), Error> { + let res = match res.get("Status") { + Some(plist::Value::Dictionary(d)) => d, + _ => &res, + }; + + if res.get("ec").unwrap().as_signed_integer().unwrap() != 0 { + return Err(Error::AuthSrpWithMessage( + res.get("ec").unwrap().as_signed_integer().unwrap(), + res.get("em").unwrap().as_string().unwrap().to_owned(), + )); + } + + Ok(()) + } + + pub async fn build_2fa_headers(&self, sms: bool) -> HeaderMap { + let spd = self.spd.as_ref().unwrap(); + let dsid = spd.get("adsid").unwrap().as_string().unwrap(); + let token = spd.get("GsIdmsToken").unwrap().as_string().unwrap(); + + let identity_token = base64::encode(format!("{}:{}", dsid, token)); + + let valid_anisette = self.get_anisette().await; + + let mut headers = HeaderMap::new(); + valid_anisette + .generate_headers(false, true, true) + .iter() + .for_each(|(k, v)| { + headers.append( + HeaderName::from_bytes(k.as_bytes()).unwrap(), + HeaderValue::from_str(v).unwrap(), + ); + }); + + if !sms { + headers.insert( + "Content-Type", + HeaderValue::from_str("text/x-xml-plist").unwrap(), + ); + headers.insert("Accept", HeaderValue::from_str("text/x-xml-plist").unwrap()); + } + headers.insert("User-Agent", HeaderValue::from_str("Xcode").unwrap()); + headers.insert("Accept-Language", HeaderValue::from_str("en-us").unwrap()); + headers.append( + "X-Apple-Identity-Token", + HeaderValue::from_str(&identity_token).unwrap(), + ); + + headers.insert( + "Loc", + HeaderValue::from_str(&valid_anisette.get_header("x-apple-locale").unwrap()).unwrap(), + ); + + headers + } + + pub async fn send_request( + &self, + url: &str, + body: Option, + ) -> Result { + let spd = self.spd.as_ref().unwrap(); + let app_token = self.get_app_token("com.apple.gs.xcode.auth").await?; + let valid_anisette = self.get_anisette().await; + + let mut headers = HeaderMap::new(); + headers.insert("Content-Type", HeaderValue::from_static("text/x-xml-plist")); + headers.insert("Accept", HeaderValue::from_static("text/x-xml-plist")); + headers.insert("Accept-Language", HeaderValue::from_static("en-us")); + headers.insert("User-Agent", HeaderValue::from_static("Xcode")); + headers.insert( + "X-Apple-I-Identity-Id", + HeaderValue::from_str(spd.get("adsid").unwrap().as_string().unwrap()).unwrap(), + ); + headers.insert( + "X-Apple-GS-Token", + HeaderValue::from_str(&app_token.auth_token).unwrap(), + ); + + for (k, v) in valid_anisette.generate_headers(false, true, true) { + headers.insert( + HeaderName::from_bytes(k.as_bytes()).unwrap(), + HeaderValue::from_str(&v).unwrap(), + ); + } + + if let Ok(locale) = valid_anisette.get_header("x-apple-locale") { + headers.insert("X-Apple-Locale", HeaderValue::from_str(&locale).unwrap()); + } + + let response = if let Some(body) = body { + let mut buf = Vec::new(); + plist::to_writer_xml(&mut buf, &body)?; + self.client + .post(url) + .headers(headers) + .body(buf) + .send() + .await? + } else { + self.client.get(url).headers(headers).send().await? + }; + + let response = response.text().await?; + + let response: plist::Dictionary = plist::from_bytes(response.as_bytes())?; + Ok(response) + } +} diff --git a/apple-private-apis/icloud-auth/src/lib.rs b/apple-private-apis/icloud-auth/src/lib.rs new file mode 100644 index 0000000..1ec3205 --- /dev/null +++ b/apple-private-apis/icloud-auth/src/lib.rs @@ -0,0 +1,26 @@ +pub mod anisette; +mod client; + +pub use client::{AppleAccount, AuthenticationExtras, LoginState, TrustedPhoneNumber, VerifyBody}; +pub use omnisette::AnisetteConfiguration; + +use thiserror::Error; +#[derive(Debug, Error)] +pub enum Error { + #[error("Failed to parse the response")] + Parse, + #[error("Failed to authenticate.")] + AuthSrp, + #[error("Bad 2fa code.")] + Bad2faCode, + #[error("{1} ({0})")] + AuthSrpWithMessage(i64, String), + #[error("Please login to appleid.apple.com to fix this account")] + ExtraStep(String), + #[error("Failed to parse a plist {0}")] + PlistError(#[from] plist::Error), + #[error("Request failed {0}")] + ReqwestError(#[from] reqwest::Error), + #[error("Failed getting anisette data {0}")] + ErrorGettingAnisette(#[from] omnisette::AnisetteError), +} diff --git a/apple-private-apis/icloud-auth/tests/auth_debug.rs b/apple-private-apis/icloud-auth/tests/auth_debug.rs new file mode 100644 index 0000000..c68c392 --- /dev/null +++ b/apple-private-apis/icloud-auth/tests/auth_debug.rs @@ -0,0 +1,81 @@ +// use icloud_auth::ani +use std::sync::Arc; + +use num_bigint::BigUint; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use srp::{ + client::{SrpClient, SrpClientVerifier}, + groups::G_2048, +}; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn auth_debug() { + // not a real account + let bytes_a = base64::decode("XChHXELsQ+ljxTFbvRMUsGJxiDIlOh9f8e+JzoegmVcOdAXXtPNzkHpAbAgSjyA+vXrTA93+BUu8EJ9+4xZu9g==").unwrap(); + let username = "apple3@f1sh.me"; + let password = "WaffleTest123"; + let salt = base64::decode("6fK6ailLUcp2kJswJVrKjQ==").unwrap(); + let iters = 20832; + + let mut password_hasher = sha2::Sha256::new(); + password_hasher.update(&password.as_bytes()); + let hashed_password = password_hasher.finalize(); + // println!("Hashed password: {:?}", base64::encode(&hashed_password)); + + let mut password_buf = [0u8; 32]; + pbkdf2::pbkdf2::>( + &hashed_password, + &salt, + iters as u32, + &mut password_buf, + ); + // println!("PBKDF2 Encrypted password: {:?}",base64::encode(&password_buf)); + + let identity_hash = SrpClient::::compute_identity_hash(&[], &password_buf); + let x = SrpClient::::compute_x(identity_hash.as_slice(), &salt); + + // apub: N2XHuh/4P1urPoBvDocF0RCRIl2pliZYqg9p6wGH0nnJdckJPn3M00jEqoM4teqH03HjG1murdcZiNHb5YayufW//+asW01XB7nYIIVvGiUFLRypYITEKYWBQ6h2q02GaZspYJKy98V8Fwcvr0ri+al7zJo1X1aoRKINyjV5TywhhwmTleI1qJkf+JBRYKKqO1XFtOTpQsysWD3ZJdK3K78kSgT3q0kXE3oDRMiHPAO77GFJZErYTuvI6QPRbOgcrn+RKV6AsjR5tUQAoSGRdtibdZTAQijJg788qVg+OFVCNZoY9GYVxa+Ze1bPGdkkgCYicTE8iNFG9KlJ+QpKgQ== + + let a_random = base64::decode("ywN1O32vmBogb5Fyt9M7Tn8bbzLtDDbcYgPFpSy8n9E=").unwrap(); + let client = SrpClient::::new(&G_2048); + + let a_pub_compute = + SrpClient::::compute_a_pub(&client, &BigUint::from_bytes_be(&a_random)); + // expect it to be same to a_pub + println!( + "compute a_pub: {:?}", + base64::encode(&a_pub_compute.to_bytes_be()) + ); + + let b_pub = base64::decode("HlWxsRmNi/9DCGxYCoqCTfdSvpbx3mrgFLQfOsgf3Rojn7MQQN/g63PwlBghUcVVB4//yAaRRnz/VIByl8thA9AKuVZl8k52PAHKSh4e7TuXSeYCFr0+GYu8/hFdMDl42219uzSuOXuaKGVKq6hxEAf3n3uXXgQRkXWtLFJ5nn1wq/emf46hYAHzc/pYyvckAdh9WDCw95IXbzKD8LcPw/0ZQoydMuXgW2ZKZ52fiyEs94IZ7L5RLL7jY1nVdwtsp2fxeqiZ3DNmVZ2GdNrbJGT//160tyd2evtUtehr8ygXNzjWdjV0cc4+1F38ywSPFyieVzVTYzDywRllgo3A5A==").unwrap(); + println!("fixed b_pub: {:?}", base64::encode(&b_pub)); + println!(""); + + println!("salt: {:?} iterations: {:?}", base64::encode(&salt), iters); + + let verifier: SrpClientVerifier = SrpClient::::process_reply( + &client, + &a_random, + // &a_pub, + username.as_bytes(), + &password_buf, + &salt, + &b_pub, + ) + .unwrap(); + + let m = verifier.proof(); + } + + #[test] + fn print_n_g() { + // println!("Print N/G test: "); + // println!("g2048 g: {:?}", &G_2048.g); + // println!("g2048 n: {:?}", &G_2048.n); + } +} diff --git a/apple-private-apis/icloud-auth/tests/gsa_auth.rs b/apple-private-apis/icloud-auth/tests/gsa_auth.rs new file mode 100644 index 0000000..ae4336d --- /dev/null +++ b/apple-private-apis/icloud-auth/tests/gsa_auth.rs @@ -0,0 +1,46 @@ +#[cfg(test)] +mod tests { + use std::{path::PathBuf, str::FromStr}; + + use icloud_auth::*; + use omnisette::AnisetteConfiguration; + + #[tokio::test] + async fn gsa_auth() { + println!("gsa auth test"); + let email = std::env::var("apple_email").unwrap_or_else(|_| { + println!("Enter Apple email: "); + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + input.trim().to_string() + }); + + let password = std::env::var("apple_password").unwrap_or_else(|_| { + println!("Enter Apple password: "); + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + input.trim().to_string() + }); + + let appleid_closure = move || Ok((email.clone(), password.clone())); + // ask console for 2fa code, make sure it is only 6 digits, no extra characters + let tfa_closure = || { + println!("Enter 2FA code: "); + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + Ok(input.trim().to_string()) + }; + let acc = AppleAccount::login( + appleid_closure, + tfa_closure, + AnisetteConfiguration::new() + .set_configuration_path(PathBuf::from_str("anisette_test").unwrap()), + ) + .await; + + let account = acc.unwrap(); + println!("data {:?}", account.get_name()); + println!("PET: {}", account.get_pet().unwrap()); + return; + } +} diff --git a/apple-private-apis/icloud-auth/tests/root_write.rs b/apple-private-apis/icloud-auth/tests/root_write.rs new file mode 100644 index 0000000..8edb7a3 --- /dev/null +++ b/apple-private-apis/icloud-auth/tests/root_write.rs @@ -0,0 +1,73 @@ +#[cfg(test)] +mod tests { + use std::{fs::File, io::Write}; + + const APPLE_ROOT: &[u8] = &[ + 48, 130, 4, 187, 48, 130, 3, 163, 160, 3, 2, 1, 2, 2, 1, 2, 48, 13, 6, 9, 42, 134, 72, 134, + 247, 13, 1, 1, 5, 5, 0, 48, 98, 49, 11, 48, 9, 6, 3, 85, 4, 6, 19, 2, 85, 83, 49, 19, 48, + 17, 6, 3, 85, 4, 10, 19, 10, 65, 112, 112, 108, 101, 32, 73, 110, 99, 46, 49, 38, 48, 36, + 6, 3, 85, 4, 11, 19, 29, 65, 112, 112, 108, 101, 32, 67, 101, 114, 116, 105, 102, 105, 99, + 97, 116, 105, 111, 110, 32, 65, 117, 116, 104, 111, 114, 105, 116, 121, 49, 22, 48, 20, 6, + 3, 85, 4, 3, 19, 13, 65, 112, 112, 108, 101, 32, 82, 111, 111, 116, 32, 67, 65, 48, 30, 23, + 13, 48, 54, 48, 52, 50, 53, 50, 49, 52, 48, 51, 54, 90, 23, 13, 51, 53, 48, 50, 48, 57, 50, + 49, 52, 48, 51, 54, 90, 48, 98, 49, 11, 48, 9, 6, 3, 85, 4, 6, 19, 2, 85, 83, 49, 19, 48, + 17, 6, 3, 85, 4, 10, 19, 10, 65, 112, 112, 108, 101, 32, 73, 110, 99, 46, 49, 38, 48, 36, + 6, 3, 85, 4, 11, 19, 29, 65, 112, 112, 108, 101, 32, 67, 101, 114, 116, 105, 102, 105, 99, + 97, 116, 105, 111, 110, 32, 65, 117, 116, 104, 111, 114, 105, 116, 121, 49, 22, 48, 20, 6, + 3, 85, 4, 3, 19, 13, 65, 112, 112, 108, 101, 32, 82, 111, 111, 116, 32, 67, 65, 48, 130, 1, + 34, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 1, 15, 0, 48, 130, 1, + 10, 2, 130, 1, 1, 0, 228, 145, 169, 9, 31, 145, 219, 30, 71, 80, 235, 5, 237, 94, 121, 132, + 45, 235, 54, 162, 87, 76, 85, 236, 139, 25, 137, 222, 249, 75, 108, 245, 7, 171, 34, 48, 2, + 232, 24, 62, 248, 80, 9, 211, 127, 65, 168, 152, 249, 209, 202, 102, 156, 36, 107, 17, 208, + 163, 187, 228, 27, 42, 195, 31, 149, 158, 122, 12, 164, 71, 139, 91, 212, 22, 55, 51, 203, + 196, 15, 77, 206, 20, 105, 209, 201, 25, 114, 245, 93, 14, 213, 127, 95, 155, 242, 37, 3, + 186, 85, 143, 77, 93, 13, 241, 100, 53, 35, 21, 75, 21, 89, 29, 179, 148, 247, 246, 156, + 158, 207, 80, 186, 193, 88, 80, 103, 143, 8, 180, 32, 247, 203, 172, 44, 32, 111, 112, 182, + 63, 1, 48, 140, 183, 67, 207, 15, 157, 61, 243, 43, 73, 40, 26, 200, 254, 206, 181, 185, + 14, 217, 94, 28, 214, 203, 61, 181, 58, 173, 244, 15, 14, 0, 146, 11, 177, 33, 22, 46, 116, + 213, 60, 13, 219, 98, 22, 171, 163, 113, 146, 71, 83, 85, 193, 175, 47, 65, 179, 248, 251, + 227, 112, 205, 230, 163, 76, 69, 126, 31, 76, 107, 80, 150, 65, 137, 196, 116, 98, 11, 16, + 131, 65, 135, 51, 138, 129, 177, 48, 88, 236, 90, 4, 50, 140, 104, 179, 143, 29, 222, 101, + 115, 255, 103, 94, 101, 188, 73, 216, 118, 159, 51, 20, 101, 161, 119, 148, 201, 45, 2, 3, + 1, 0, 1, 163, 130, 1, 122, 48, 130, 1, 118, 48, 14, 6, 3, 85, 29, 15, 1, 1, 255, 4, 4, 3, + 2, 1, 6, 48, 15, 6, 3, 85, 29, 19, 1, 1, 255, 4, 5, 48, 3, 1, 1, 255, 48, 29, 6, 3, 85, 29, + 14, 4, 22, 4, 20, 43, 208, 105, 71, 148, 118, 9, 254, 244, 107, 141, 46, 64, 166, 247, 71, + 77, 127, 8, 94, 48, 31, 6, 3, 85, 29, 35, 4, 24, 48, 22, 128, 20, 43, 208, 105, 71, 148, + 118, 9, 254, 244, 107, 141, 46, 64, 166, 247, 71, 77, 127, 8, 94, 48, 130, 1, 17, 6, 3, 85, + 29, 32, 4, 130, 1, 8, 48, 130, 1, 4, 48, 130, 1, 0, 6, 9, 42, 134, 72, 134, 247, 99, 100, + 5, 1, 48, 129, 242, 48, 42, 6, 8, 43, 6, 1, 5, 5, 7, 2, 1, 22, 30, 104, 116, 116, 112, 115, + 58, 47, 47, 119, 119, 119, 46, 97, 112, 112, 108, 101, 46, 99, 111, 109, 47, 97, 112, 112, + 108, 101, 99, 97, 47, 48, 129, 195, 6, 8, 43, 6, 1, 5, 5, 7, 2, 2, 48, 129, 182, 26, 129, + 179, 82, 101, 108, 105, 97, 110, 99, 101, 32, 111, 110, 32, 116, 104, 105, 115, 32, 99, + 101, 114, 116, 105, 102, 105, 99, 97, 116, 101, 32, 98, 121, 32, 97, 110, 121, 32, 112, 97, + 114, 116, 121, 32, 97, 115, 115, 117, 109, 101, 115, 32, 97, 99, 99, 101, 112, 116, 97, + 110, 99, 101, 32, 111, 102, 32, 116, 104, 101, 32, 116, 104, 101, 110, 32, 97, 112, 112, + 108, 105, 99, 97, 98, 108, 101, 32, 115, 116, 97, 110, 100, 97, 114, 100, 32, 116, 101, + 114, 109, 115, 32, 97, 110, 100, 32, 99, 111, 110, 100, 105, 116, 105, 111, 110, 115, 32, + 111, 102, 32, 117, 115, 101, 44, 32, 99, 101, 114, 116, 105, 102, 105, 99, 97, 116, 101, + 32, 112, 111, 108, 105, 99, 121, 32, 97, 110, 100, 32, 99, 101, 114, 116, 105, 102, 105, + 99, 97, 116, 105, 111, 110, 32, 112, 114, 97, 99, 116, 105, 99, 101, 32, 115, 116, 97, 116, + 101, 109, 101, 110, 116, 115, 46, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 5, 5, 0, + 3, 130, 1, 1, 0, 92, 54, 153, 76, 45, 120, 183, 237, 140, 155, 220, 243, 119, 155, 242, + 118, 210, 119, 48, 79, 193, 31, 133, 131, 133, 27, 153, 61, 71, 55, 242, 169, 155, 64, 142, + 44, 212, 177, 144, 18, 216, 190, 244, 115, 155, 238, 210, 100, 15, 203, 121, 79, 52, 216, + 162, 62, 249, 120, 255, 107, 200, 7, 236, 125, 57, 131, 139, 83, 32, 211, 56, 196, 177, + 191, 154, 79, 10, 107, 255, 43, 252, 89, 167, 5, 9, 124, 23, 64, 86, 17, 30, 116, 211, 183, + 139, 35, 59, 71, 163, 213, 111, 36, 226, 235, 209, 183, 112, 223, 15, 69, 225, 39, 202, + 241, 109, 120, 237, 231, 181, 23, 23, 168, 220, 126, 34, 53, 202, 37, 213, 217, 15, 214, + 107, 212, 162, 36, 35, 17, 247, 161, 172, 143, 115, 129, 96, 198, 27, 91, 9, 47, 146, 178, + 248, 68, 72, 240, 96, 56, 158, 21, 245, 61, 38, 103, 32, 138, 51, 106, 247, 13, 130, 207, + 222, 235, 163, 47, 249, 83, 106, 91, 100, 192, 99, 51, 119, 247, 58, 7, 44, 86, 235, 218, + 15, 33, 14, 218, 186, 115, 25, 79, 181, 217, 54, 127, 193, 135, 85, 217, 167, 153, 185, 50, + 66, 251, 216, 213, 113, 158, 126, 161, 82, 183, 27, 189, 147, 66, 36, 18, 42, 199, 15, 29, + 182, 77, 156, 94, 99, 200, 75, 128, 23, 80, 170, 138, 213, 218, 228, 252, 208, 9, 7, 55, + 176, 117, 117, 33, + ]; + #[test] + + fn root_write() { + // write to file src/root.der + let mut file = File::create("src/apple_root.der").unwrap(); + file.write_all(&APPLE_ROOT).unwrap(); + } +} diff --git a/apple-private-apis/omnisette/Cargo.toml b/apple-private-apis/omnisette/Cargo.toml new file mode 100644 index 0000000..1c6dc12 --- /dev/null +++ b/apple-private-apis/omnisette/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "omnisette" +version = "0.1.0" +edition = "2021" + +[features] +remote-anisette = [] +async = ["dep:async-trait"] +default = ["remote-anisette", "dep:remove-async-await"] +remote-anisette-v3 = ["async", "dep:serde", "dep:serde_json", "dep:tokio-tungstenite", "dep:futures-util", "dep:chrono"] + +[dependencies] +base64 = "0.21" +hex = "0.4.3" +plist = "1.4" +reqwest = { version = "0.11", default-features = false, features = ["blocking", "json", "rustls-tls", "gzip"] } +rand = "0.8" +sha2 = "0.10.8" +uuid = { version = "1.3", features = [ "v4", "fast-rng", "macro-diagnostics" ] } +android-loader = { git = "https://github.com/Dadoum/android-loader", branch = "bigger_pages" } +libc = "0.2" +log = "0.4" +async-trait = { version = "0.1", optional = true } +remove-async-await = { version = "1.0", optional = true } +serde = { version = "1.0", features = ["derive"], optional = true } +serde_json = { version = "1.0.142", optional = true } +tokio-tungstenite = { version = "0.20.1", optional = true, features = ["rustls-tls-webpki-roots"] } +futures-util = { version = "0.3.28", optional = true } +chrono = { version = "0.4.37", optional = true } +thiserror = "1.0.58" +anyhow = "1.0.81" + +[target.'cfg(target_os = "macos")'.dependencies] +dlopen2 = "0.4" +objc = "0.2" +objc-foundation = "0.1" + +[dev-dependencies] +tokio = { version = "1", features = ["rt", "macros"] } +simplelog = "0.12" diff --git a/apple-private-apis/omnisette/src/adi_proxy.rs b/apple-private-apis/omnisette/src/adi_proxy.rs new file mode 100644 index 0000000..617af58 --- /dev/null +++ b/apple-private-apis/omnisette/src/adi_proxy.rs @@ -0,0 +1,381 @@ +use crate::adi_proxy::ProvisioningError::InvalidResponse; +use crate::anisette_headers_provider::AnisetteHeadersProvider; +use crate::AnisetteError; +use base64::engine::general_purpose::STANDARD as base64_engine; +use base64::Engine; +use log::debug; +use plist::{Dictionary, Value}; +use rand::RngCore; +#[cfg(not(feature = "async"))] +use reqwest::blocking::{Client, ClientBuilder, Response}; +use reqwest::header::{HeaderMap, HeaderValue, InvalidHeaderValue}; +#[cfg(feature = "async")] +use reqwest::{Client, ClientBuilder, Response}; +use sha2::{Digest, Sha256}; +use std::collections::HashMap; +use std::fmt::{Display, Formatter}; +use std::io::{self, Read, Write}; +use std::path::PathBuf; +use thiserror::Error; + +#[derive(Debug)] +pub struct ServerError { + pub code: i64, + pub description: String, +} + +#[derive(Debug)] +pub enum ProvisioningError { + InvalidResponse, + ServerError(ServerError), +} + +impl std::fmt::Display for ProvisioningError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } +} + +impl std::error::Error for ProvisioningError {} + +#[derive(Debug, Error)] +pub enum ADIError { + Unknown(i32), + ProvisioningError(#[from] ProvisioningError), + PlistError(#[from] plist::Error), + ReqwestError(#[from] reqwest::Error), + Base64Error(#[from] base64::DecodeError), + InvalidHeaderValue(#[from] InvalidHeaderValue), + IOError(#[from] io::Error) +} + +impl ADIError { + pub fn resolve(error_number: i32) -> ADIError { + ADIError::Unknown(error_number) + } +} + +#[cfg_attr(feature = "async", async_trait::async_trait)] +trait ToPlist { + #[cfg_attr(not(feature = "async"), remove_async_await::remove_async_await)] + async fn plist(self) -> Result; +} + +#[cfg_attr(feature = "async", async_trait::async_trait)] +impl ToPlist for Response { + #[cfg_attr(not(feature = "async"), remove_async_await::remove_async_await)] + async fn plist(self) -> Result { + if let Ok(property_list) = Value::from_reader_xml(&*self.bytes().await?) { + Ok(property_list.as_dictionary().unwrap().to_owned()) + } else { + Err(ProvisioningError::InvalidResponse.into()) + } + } +} + +impl Display for ADIError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } +} + +pub struct SynchronizeData { + pub mid: Vec, + pub srm: Vec, +} + +pub struct StartProvisioningData { + pub cpim: Vec, + pub session: u32, +} + +pub struct RequestOTPData { + pub otp: Vec, + pub mid: Vec, +} + +#[cfg_attr(feature = "async", async_trait::async_trait(?Send))] +pub trait ADIProxy: Send + Sync { + fn erase_provisioning(&mut self, ds_id: i64) -> Result<(), ADIError>; + fn synchronize(&mut self, ds_id: i64, sim: &[u8]) -> Result; + fn destroy_provisioning_session(&mut self, session: u32) -> Result<(), ADIError>; + fn end_provisioning(&mut self, session: u32, ptm: &[u8], tk: &[u8]) -> Result<(), ADIError>; + fn start_provisioning( + &mut self, + ds_id: i64, + spim: &[u8], + ) -> Result; + fn is_machine_provisioned(&self, ds_id: i64) -> bool; + fn request_otp(&self, ds_id: i64) -> Result; + + fn set_local_user_uuid(&mut self, local_user_uuid: String); + fn set_device_identifier(&mut self, device_identifier: String) -> Result<(), ADIError>; + + fn get_local_user_uuid(&self) -> String; + fn get_device_identifier(&self) -> String; + fn get_serial_number(&self) -> String; +} + +pub trait ConfigurableADIProxy: ADIProxy { + fn set_identifier(&mut self, identifier: &str) -> Result<(), ADIError>; + fn set_provisioning_path(&mut self, path: &str) -> Result<(), ADIError>; +} + +pub const AKD_USER_AGENT: &str = "akd/1.0 CFNetwork/808.1.4"; +pub const CLIENT_INFO_HEADER: &str = + " "; +pub const DS_ID: i64 = -2; +pub const IDENTIFIER_LENGTH: usize = 16; +pub type Identifier = [u8; IDENTIFIER_LENGTH]; + +trait AppleRequestResult { + fn check_status(&self) -> Result<(), ADIError>; + fn get_response(&self) -> Result<&Dictionary, ADIError>; +} + +impl AppleRequestResult for Dictionary { + fn check_status(&self) -> Result<(), ADIError> { + let status = self + .get("Status") + .ok_or(InvalidResponse)? + .as_dictionary() + .unwrap(); + let code = status.get("ec").unwrap().as_signed_integer().unwrap(); + if code != 0 { + let description = status.get("em").unwrap().as_string().unwrap().to_string(); + Err(ProvisioningError::ServerError(ServerError { code, description }).into()) + } else { + Ok(()) + } + } + + fn get_response(&self) -> Result<&Dictionary, ADIError> { + if let Some(response) = self.get("Response") { + let response = response.as_dictionary().unwrap(); + response.check_status()?; + Ok(response) + } else { + Err(InvalidResponse.into()) + } + } +} + +impl dyn ADIProxy { + fn make_http_client(&mut self) -> Result { + let mut headers = HeaderMap::new(); + headers.insert("Content-Type", HeaderValue::from_str("text/x-xml-plist")?); + + headers.insert( + "X-Mme-Client-Info", + HeaderValue::from_str(CLIENT_INFO_HEADER)?, + ); + headers.insert( + "X-Mme-Device-Id", + HeaderValue::from_str(self.get_device_identifier().as_str())?, + ); + headers.insert( + "X-Apple-I-MD-LU", + HeaderValue::from_str(self.get_local_user_uuid().as_str())?, + ); + headers.insert( + "X-Apple-I-SRL-NO", + HeaderValue::from_str(self.get_serial_number().as_str())?, + ); + + debug!("Headers sent: {headers:?}"); + + let http_client = ClientBuilder::new() + .http1_title_case_headers() + .danger_accept_invalid_certs(true) // TODO: pin the apple certificate + .user_agent(AKD_USER_AGENT) + .default_headers(headers) + .build()?; + + Ok(http_client) + } + + #[cfg_attr(not(feature = "async"), remove_async_await::remove_async_await)] + async fn provision_device(&mut self) -> Result<(), ADIError> { + let client = self.make_http_client()?; + + let url_bag_res = client + .get("https://gsa.apple.com/grandslam/GsService2/lookup") + .send() + .await? + .plist() + .await?; + + let urls = url_bag_res.get("urls").unwrap().as_dictionary().unwrap(); + + let start_provisioning_url = urls + .get("midStartProvisioning") + .unwrap() + .as_string() + .unwrap(); + let finish_provisioning_url = urls + .get("midFinishProvisioning") + .unwrap() + .as_string() + .unwrap(); + + let mut body = plist::Dictionary::new(); + body.insert( + "Header".to_string(), + plist::Value::Dictionary(plist::Dictionary::new()), + ); + body.insert( + "Request".to_string(), + plist::Value::Dictionary(plist::Dictionary::new()), + ); + + let mut sp_request = Vec::new(); + plist::Value::Dictionary(body).to_writer_xml(&mut sp_request)?; + + debug!("First provisioning request..."); + let response = client + .post(start_provisioning_url) + .body(sp_request) + .send() + .await? + .plist() + .await?; + + let response = response.get_response()?; + + let spim = response + .get("spim") + .unwrap() + .as_string() + .unwrap() + .to_owned(); + + let spim = base64_engine.decode(spim)?; + let first_step = self.start_provisioning(DS_ID, spim.as_slice())?; + + let mut body = Dictionary::new(); + let mut request = Dictionary::new(); + request.insert( + "cpim".to_owned(), + Value::String(base64_engine.encode(first_step.cpim)), + ); + body.insert("Header".to_owned(), Value::Dictionary(Dictionary::new())); + body.insert("Request".to_owned(), Value::Dictionary(request)); + + let mut fp_request = Vec::new(); + Value::Dictionary(body).to_writer_xml(&mut fp_request)?; + + debug!("Second provisioning request..."); + let response = client + .post(finish_provisioning_url) + .body(fp_request) + .send() + .await? + .plist() + .await?; + + let response = response.get_response()?; + + let ptm = base64_engine.decode(response.get("ptm").unwrap().as_string().unwrap())?; + let tk = base64_engine.decode(response.get("tk").unwrap().as_string().unwrap())?; + + self.end_provisioning(first_step.session, ptm.as_slice(), tk.as_slice())?; + debug!("Done."); + + Ok(()) + } +} + +pub struct ADIProxyAnisetteProvider { + adi_proxy: ProxyType, +} + +impl ADIProxyAnisetteProvider { + /// If you use this method, you are expected to set the identifier yourself. + pub fn without_identifier(adi_proxy: ProxyType) -> Result, ADIError> { + Ok(ADIProxyAnisetteProvider { adi_proxy }) + } + + pub fn new( + mut adi_proxy: ProxyType, + configuration_path: PathBuf, + ) -> Result, ADIError> { + let identifier_file_path = configuration_path.join("identifier"); + let mut identifier_file = std::fs::OpenOptions::new() + .create(true) + .read(true) + .write(true) + .open(identifier_file_path)?; + let mut identifier = [0u8; IDENTIFIER_LENGTH]; + if identifier_file.metadata()?.len() == IDENTIFIER_LENGTH as u64 { + identifier_file.read_exact(&mut identifier)?; + } else { + rand::thread_rng().fill_bytes(&mut identifier); + identifier_file.write_all(&identifier)?; + } + + let mut local_user_uuid_hasher = Sha256::new(); + local_user_uuid_hasher.update(identifier); + + adi_proxy.set_device_identifier( + uuid::Uuid::from_bytes(identifier) + .to_string() + .to_uppercase(), + )?; // UUID, uppercase + adi_proxy + .set_local_user_uuid(hex::encode(local_user_uuid_hasher.finalize()).to_uppercase()); // 64 uppercase character hex + + Ok(ADIProxyAnisetteProvider { adi_proxy }) + } + + pub fn adi_proxy(&mut self) -> &mut ProxyType { + &mut self.adi_proxy + } +} + +#[cfg_attr(feature = "async", async_trait::async_trait)] +impl AnisetteHeadersProvider + for ADIProxyAnisetteProvider +{ + #[cfg_attr(not(feature = "async"), remove_async_await::remove_async_await)] + async fn get_anisette_headers( + &mut self, + skip_provisioning: bool, + ) -> Result, AnisetteError> { + let adi_proxy = &mut self.adi_proxy as &mut dyn ADIProxy; + + if !adi_proxy.is_machine_provisioned(DS_ID) && !skip_provisioning { + adi_proxy.provision_device().await?; + } + + let machine_data = adi_proxy.request_otp(DS_ID)?; + + let mut headers = HashMap::new(); + headers.insert( + "X-Apple-I-MD".to_string(), + base64_engine.encode(machine_data.otp), + ); + headers.insert( + "X-Apple-I-MD-M".to_string(), + base64_engine.encode(machine_data.mid), + ); + headers.insert("X-Apple-I-MD-RINFO".to_string(), "17106176".to_string()); + headers.insert( + "X-Apple-I-MD-LU".to_string(), + adi_proxy.get_local_user_uuid(), + ); + headers.insert( + "X-Apple-I-SRL-NO".to_string(), + adi_proxy.get_serial_number(), + ); + headers.insert( + "X-Mme-Client-Info".to_string(), + CLIENT_INFO_HEADER.to_string(), + ); + headers.insert( + "X-Mme-Device-Id".to_string(), + adi_proxy.get_device_identifier(), + ); + + Ok(headers) + } +} diff --git a/apple-private-apis/omnisette/src/anisette_headers_provider.rs b/apple-private-apis/omnisette/src/anisette_headers_provider.rs new file mode 100644 index 0000000..903203e --- /dev/null +++ b/apple-private-apis/omnisette/src/anisette_headers_provider.rs @@ -0,0 +1,31 @@ + +use std::collections::HashMap; + +use crate::AnisetteError; + +#[cfg_attr(feature = "async", async_trait::async_trait)] +pub trait AnisetteHeadersProvider: Send + Sync { + #[cfg_attr(not(feature = "async"), remove_async_await::remove_async_await)] + async fn get_anisette_headers( + &mut self, + skip_provisioning: bool, + ) -> Result, AnisetteError>; + + #[cfg_attr(not(feature = "async"), remove_async_await::remove_async_await)] + async fn get_authentication_headers(&mut self) -> Result, AnisetteError> { + let headers = self.get_anisette_headers(false).await?; + Ok(self.normalize_headers(headers)) + } + + /// Normalizes headers to ensure that all the required headers are given. + fn normalize_headers( + &mut self, + mut headers: HashMap, + ) -> HashMap { + if let Some(client_info) = headers.remove("X-MMe-Client-Info") { + headers.insert("X-Mme-Client-Info".to_string(), client_info); + } + + headers + } +} diff --git a/apple-private-apis/omnisette/src/aos_kit.rs b/apple-private-apis/omnisette/src/aos_kit.rs new file mode 100644 index 0000000..5e53787 --- /dev/null +++ b/apple-private-apis/omnisette/src/aos_kit.rs @@ -0,0 +1,124 @@ +use crate::anisette_headers_provider::AnisetteHeadersProvider; +use anyhow::Result; + +use dlopen2::symbor::Library; +use objc::{msg_send, runtime::Class, sel, sel_impl}; +use objc_foundation::{INSString, NSObject, NSString}; +use std::collections::HashMap; +use std::error::Error; +use std::fmt::{Display, Formatter}; +pub struct AOSKitAnisetteProvider<'lt> { + aos_utilities: &'lt Class, + ak_device: &'lt Class, +} + +impl<'lt> AOSKitAnisetteProvider<'lt> { + pub fn new() -> Result> { + Library::open("/System/Library/PrivateFrameworks/AOSKit.framework/AOSKit")?; + Library::open("/System/Library/PrivateFrameworks/AuthKit.framework/AuthKit")?; + Ok(AOSKitAnisetteProvider { + aos_utilities: Class::get("AOSUtilities").ok_or(AOSKitError::ClassLoadFailed)?, + ak_device: Class::get("AKDevice").ok_or(AOSKitError::ClassLoadFailed)?, + }) + } +} + +#[cfg_attr(feature = "async", async_trait::async_trait(?Send))] +impl<'lt> AnisetteHeadersProvider for AOSKitAnisetteProvider<'lt> { + #[cfg_attr(not(feature = "async"), remove_async_await::remove_async_await)] + async fn get_anisette_headers( + &mut self, + _skip_provisioning: bool, + ) -> Result> { + let mut headers_map = HashMap::new(); + + let headers: *const NSObject = unsafe { + msg_send![self.aos_utilities, retrieveOTPHeadersForDSID: NSString::from_str("-2")] + }; + + let otp: *const NSString = + unsafe { msg_send![headers, valueForKey: NSString::from_str("X-Apple-MD")] }; + headers_map.insert( + "X-Apple-I-MD".to_string(), + unsafe { (*otp).as_str() }.to_string(), + ); + + let mid: *const NSString = + unsafe { msg_send![headers, valueForKey: NSString::from_str("X-Apple-MD-M")] }; + headers_map.insert( + "X-Apple-I-MD-M".to_string(), + unsafe { (*mid).as_str() }.to_string(), + ); + + let machine_serial_number: *const NSString = + unsafe { msg_send![self.aos_utilities, machineSerialNumber] }; + headers_map.insert( + "X-Apple-SRL-NO".to_string(), + unsafe { (*machine_serial_number).as_str() }.to_string(), + ); + + let current_device: *const NSObject = unsafe { msg_send![self.ak_device, currentDevice] }; + + let local_user_uuid: *const NSString = unsafe { msg_send![current_device, localUserUUID] }; + headers_map.insert( + "X-Apple-I-MD-LU".to_string(), + unsafe { (*local_user_uuid).as_str() }.to_string(), + ); + + let locale: *const NSObject = unsafe { msg_send![current_device, locale] }; + let locale: *const NSString = unsafe { msg_send![locale, localeIdentifier] }; + headers_map.insert( + "X-Apple-Locale".to_string(), + unsafe { (*locale).as_str() }.to_string(), + ); // FIXME maybe not the right header name + + let server_friendly_description: *const NSString = + unsafe { msg_send![current_device, serverFriendlyDescription] }; + headers_map.insert( + "X-Mme-Client-Info".to_string(), + unsafe { (*server_friendly_description).as_str() }.to_string(), + ); + + let unique_device_identifier: *const NSString = + unsafe { msg_send![current_device, uniqueDeviceIdentifier] }; + headers_map.insert( + "X-Mme-Device-Id".to_string(), + unsafe { (*unique_device_identifier).as_str() }.to_string(), + ); + + Ok(headers_map) + } +} + +#[derive(Debug)] +enum AOSKitError { + ClassLoadFailed, +} + +impl Display for AOSKitError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } +} + +impl Error for AOSKitError {} + +#[cfg(all(test, not(feature = "async")))] +mod tests { + use crate::anisette_headers_provider::AnisetteHeadersProvider; + use crate::aos_kit::AOSKitAnisetteProvider; + use anyhow::Result; + use log::info; + + #[test] + fn fetch_anisette_aoskit() -> Result<()> { + crate::tests::init_logger(); + + let mut provider = AOSKitAnisetteProvider::new()?; + info!( + "AOSKit headers: {:?}", + (&mut provider as &mut dyn AnisetteHeadersProvider).get_authentication_headers()? + ); + Ok(()) + } +} diff --git a/apple-private-apis/omnisette/src/lib.rs b/apple-private-apis/omnisette/src/lib.rs new file mode 100644 index 0000000..af770c2 --- /dev/null +++ b/apple-private-apis/omnisette/src/lib.rs @@ -0,0 +1,231 @@ +//! A library to generate "anisette" data. Docs are coming soon. +//! +//! If you want an async API, enable the `async` feature. +//! +//! If you want remote anisette, make sure the `remote-anisette` feature is enabled. (it's currently on by default) + +use crate::adi_proxy::{ADIProxyAnisetteProvider, ConfigurableADIProxy}; +use crate::anisette_headers_provider::AnisetteHeadersProvider; +use adi_proxy::ADIError; +use std::io; +use std::path::PathBuf; +use thiserror::Error; + +pub mod adi_proxy; +pub mod anisette_headers_provider; +pub mod store_services_core; + +#[cfg(feature = "remote-anisette-v3")] +pub mod remote_anisette_v3; + +#[cfg(target_os = "macos")] +pub mod aos_kit; + +#[cfg(feature = "remote-anisette")] +pub mod remote_anisette; + +#[allow(dead_code)] +pub struct AnisetteHeaders; + +#[allow(dead_code)] +#[derive(Debug, Error)] +pub enum AnisetteError { + #[allow(dead_code)] + #[error("Unsupported device")] + UnsupportedDevice, + #[error("Invalid argument {0}")] + InvalidArgument(String), + #[error("Anisette not provisioned!")] + AnisetteNotProvisioned, + #[error("Plist serialization error {0}")] + PlistError(#[from] plist::Error), + #[error("Request Error {0}")] + ReqwestError(#[from] reqwest::Error), + #[cfg(feature = "remote-anisette-v3")] + #[error("Provisioning socket error {0}")] + WsError(#[from] tokio_tungstenite::tungstenite::error::Error), + #[cfg(feature = "remote-anisette-v3")] + #[error("JSON error {0}")] + SerdeError(#[from] serde_json::Error), + #[error("IO error {0}")] + IOError(#[from] io::Error), + #[error("ADI error {0}")] + ADIError(#[from] ADIError), + #[error("Invalid library format")] + InvalidLibraryFormat, + #[error("Misc")] + Misc, + #[error("Missing Libraries")] + MissingLibraries, + #[error("{0}")] + Anyhow(#[from] anyhow::Error), +} + +pub const DEFAULT_ANISETTE_URL: &str = "https://ani.f1sh.me/"; + +pub const DEFAULT_ANISETTE_URL_V3: &str = "https://ani.sidestore.io"; + +#[derive(Clone, Debug)] +pub struct AnisetteConfiguration { + anisette_url: String, + anisette_url_v3: String, + configuration_path: PathBuf, + macos_serial: String, +} + +impl Default for AnisetteConfiguration { + fn default() -> Self { + AnisetteConfiguration::new() + } +} + +impl AnisetteConfiguration { + pub fn new() -> AnisetteConfiguration { + AnisetteConfiguration { + anisette_url: DEFAULT_ANISETTE_URL.to_string(), + anisette_url_v3: DEFAULT_ANISETTE_URL_V3.to_string(), + configuration_path: PathBuf::new(), + macos_serial: "0".to_string(), + } + } + + pub fn anisette_url(&self) -> &String { + &self.anisette_url + } + + pub fn configuration_path(&self) -> &PathBuf { + &self.configuration_path + } + + pub fn set_anisette_url(mut self, anisette_url: String) -> AnisetteConfiguration { + self.anisette_url = anisette_url; + self + } + + pub fn set_macos_serial(mut self, macos_serial: String) -> AnisetteConfiguration { + self.macos_serial = macos_serial; + self + } + + pub fn set_configuration_path(mut self, configuration_path: PathBuf) -> AnisetteConfiguration { + self.configuration_path = configuration_path; + self + } +} + +pub enum AnisetteHeadersProviderType { + Local, + Remote, +} + +pub struct AnisetteHeadersProviderRes { + pub provider: Box, + pub provider_type: AnisetteHeadersProviderType, +} + +impl AnisetteHeadersProviderRes { + pub fn local(provider: Box) -> AnisetteHeadersProviderRes { + AnisetteHeadersProviderRes { + provider, + provider_type: AnisetteHeadersProviderType::Local, + } + } + + pub fn remote(provider: Box) -> AnisetteHeadersProviderRes { + AnisetteHeadersProviderRes { + provider, + provider_type: AnisetteHeadersProviderType::Remote, + } + } +} + +impl AnisetteHeaders { + pub fn get_anisette_headers_provider( + configuration: AnisetteConfiguration, + ) -> Result { + #[cfg(target_os = "macos")] + if let Ok(prov) = aos_kit::AOSKitAnisetteProvider::new() { + return Ok(AnisetteHeadersProviderRes::local(Box::new(prov))); + } + + // TODO: handle Err because it will just go to remote anisette and not tell the user anything + if let Ok(ssc_anisette_headers_provider) = + AnisetteHeaders::get_ssc_anisette_headers_provider(configuration.clone()) + { + return Ok(ssc_anisette_headers_provider); + } + + #[cfg(feature = "remote-anisette-v3")] + return Ok(AnisetteHeadersProviderRes::remote(Box::new( + remote_anisette_v3::RemoteAnisetteProviderV3::new( + configuration.anisette_url_v3, + configuration.configuration_path.clone(), + configuration.macos_serial.clone(), + ), + ))); + + #[cfg(feature = "remote-anisette")] + #[allow(unreachable_code)] + return Ok(AnisetteHeadersProviderRes::remote(Box::new( + remote_anisette::RemoteAnisetteProvider::new(configuration.anisette_url), + ))); + + #[cfg(not(feature = "remote-anisette"))] + bail!(AnisetteMetaError::UnsupportedDevice) + } + + pub fn get_ssc_anisette_headers_provider( + configuration: AnisetteConfiguration, + ) -> Result { + let mut ssc_adi_proxy = store_services_core::StoreServicesCoreADIProxy::new( + configuration.configuration_path(), + )?; + let config_path = configuration.configuration_path(); + ssc_adi_proxy.set_provisioning_path(config_path.to_str().ok_or( + AnisetteError::InvalidArgument("configuration.configuration_path".to_string()), + )?)?; + Ok(AnisetteHeadersProviderRes::local(Box::new( + ADIProxyAnisetteProvider::new(ssc_adi_proxy, config_path.to_path_buf())?, + ))) + } +} + +#[cfg(test)] +mod tests { + use log::LevelFilter; + use simplelog::{ColorChoice, ConfigBuilder, TermLogger, TerminalMode}; + + pub fn init_logger() { + if TermLogger::init( + LevelFilter::Trace, + ConfigBuilder::new() + .set_target_level(LevelFilter::Error) + .add_filter_allow_str("omnisette") + .build(), + TerminalMode::Mixed, + ColorChoice::Auto, + ) + .is_ok() + {} + } + + #[cfg(not(feature = "async"))] + #[test] + fn fetch_anisette_auto() -> Result<()> { + use crate::{AnisetteConfiguration, AnisetteHeaders}; + use log::info; + use std::path::PathBuf; + + crate::tests::init_logger(); + + let mut provider = AnisetteHeaders::get_anisette_headers_provider( + AnisetteConfiguration::new() + .set_configuration_path(PathBuf::new().join("anisette_test")), + )?; + info!( + "Headers: {:?}", + provider.provider.get_authentication_headers()? + ); + Ok(()) + } +} diff --git a/apple-private-apis/omnisette/src/remote_anisette.rs b/apple-private-apis/omnisette/src/remote_anisette.rs new file mode 100644 index 0000000..b061bd9 --- /dev/null +++ b/apple-private-apis/omnisette/src/remote_anisette.rs @@ -0,0 +1,47 @@ +use crate::{anisette_headers_provider::AnisetteHeadersProvider, AnisetteError}; +#[cfg(not(feature = "async"))] +use reqwest::blocking::get; +#[cfg(feature = "async")] +use reqwest::get; +use std::collections::HashMap; + +pub struct RemoteAnisetteProvider { + url: String, +} + +impl RemoteAnisetteProvider { + pub fn new(url: String) -> RemoteAnisetteProvider { + RemoteAnisetteProvider { url } + } +} + +#[cfg_attr(feature = "async", async_trait::async_trait)] +impl AnisetteHeadersProvider for RemoteAnisetteProvider { + #[cfg_attr(not(feature = "async"), remove_async_await::remove_async_await)] + async fn get_anisette_headers( + &mut self, + _skip_provisioning: bool, + ) -> Result, AnisetteError> { + Ok(get(&self.url).await?.json().await?) + } +} + +#[cfg(all(test, not(feature = "async")))] +mod tests { + use crate::anisette_headers_provider::AnisetteHeadersProvider; + use crate::remote_anisette::RemoteAnisetteProvider; + use crate::DEFAULT_ANISETTE_URL; + use log::info; + + #[test] + fn fetch_anisette_remote() -> Result<(), AnisetteError> { + crate::tests::init_logger(); + + let mut provider = RemoteAnisetteProvider::new(DEFAULT_ANISETTE_URL.to_string()); + info!( + "Remote headers: {:?}", + (&mut provider as &mut dyn AnisetteHeadersProvider).get_authentication_headers()? + ); + Ok(()) + } +} diff --git a/apple-private-apis/omnisette/src/remote_anisette_v3.rs b/apple-private-apis/omnisette/src/remote_anisette_v3.rs new file mode 100644 index 0000000..e41573c --- /dev/null +++ b/apple-private-apis/omnisette/src/remote_anisette_v3.rs @@ -0,0 +1,541 @@ +// Implementing the SideStore Anisette v3 protocol + +use std::{collections::HashMap, fs, io::Cursor, path::PathBuf}; + +use async_trait::async_trait; +use base64::engine::general_purpose; +use base64::Engine; +use chrono::{DateTime, SubsecRound, Utc}; +use futures_util::{stream::StreamExt, SinkExt}; +use log::debug; +use plist::{Data, Dictionary}; +use rand::Rng; +use reqwest::{Client, ClientBuilder, RequestBuilder}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use sha2::{Digest, Sha256}; +use std::fmt::Write; +use tokio_tungstenite::{connect_async, tungstenite::Message}; +use uuid::Uuid; + +use crate::{anisette_headers_provider::AnisetteHeadersProvider, AnisetteError}; + +fn plist_to_string(value: &T) -> Result { + plist_to_buf(value).map(|val| String::from_utf8(val).unwrap()) +} + +fn plist_to_buf(value: &T) -> Result, plist::Error> { + let mut buf: Vec = Vec::new(); + let writer = Cursor::new(&mut buf); + plist::to_writer_xml(writer, &value)?; + Ok(buf) +} + +fn bin_serialize(x: &[u8], s: S) -> Result +where + S: Serializer, +{ + s.serialize_bytes(x) +} + +fn bin_serialize_opt(x: &Option>, s: S) -> Result +where + S: Serializer, +{ + x.clone().map(|i| Data::new(i)).serialize(s) +} + +fn bin_deserialize_opt<'de, D>(d: D) -> Result>, D::Error> +where + D: Deserializer<'de>, +{ + let s: Option = Deserialize::deserialize(d)?; + Ok(s.map(|i| i.into())) +} + +fn bin_deserialize_16<'de, D>(d: D) -> Result<[u8; 16], D::Error> +where + D: Deserializer<'de>, +{ + let s: Data = Deserialize::deserialize(d)?; + let s: Vec = s.into(); + Ok(s.try_into().unwrap()) +} + +fn encode_hex(bytes: &[u8]) -> String { + let mut s = String::with_capacity(bytes.len() * 2); + for &b in bytes { + write!(&mut s, "{:02x}", b).unwrap(); + } + s +} +fn base64_encode(data: &[u8]) -> String { + general_purpose::STANDARD.encode(data) +} + +fn base64_decode(data: &str) -> Vec { + general_purpose::STANDARD.decode(data.trim()).unwrap() +} + +#[derive(Deserialize)] +struct AnisetteClientInfo { + client_info: String, + user_agent: String, +} + +#[derive(Serialize, Deserialize)] +pub struct AnisetteState { + #[serde( + serialize_with = "bin_serialize", + deserialize_with = "bin_deserialize_16" + )] + keychain_identifier: [u8; 16], + #[serde( + serialize_with = "bin_serialize_opt", + deserialize_with = "bin_deserialize_opt" + )] + adi_pb: Option>, +} + +impl Default for AnisetteState { + fn default() -> Self { + AnisetteState { + keychain_identifier: rand::thread_rng().gen::<[u8; 16]>(), + adi_pb: None, + } + } +} + +impl AnisetteState { + pub fn new() -> AnisetteState { + AnisetteState::default() + } + + pub fn is_provisioned(&self) -> bool { + self.adi_pb.is_some() + } + + fn md_lu(&self) -> [u8; 32] { + let mut hasher = Sha256::new(); + hasher.update(&self.keychain_identifier); + hasher.finalize().into() + } + + fn device_id(&self) -> String { + Uuid::from_bytes(self.keychain_identifier).to_string() + } +} +pub struct AnisetteClient { + client_info: AnisetteClientInfo, + url: String, +} + +#[derive(Serialize)] +#[serde(rename_all = "PascalCase")] +struct ProvisionBodyData { + header: Dictionary, + request: Dictionary, +} + +#[derive(Debug)] +pub struct AnisetteData { + machine_id: String, + one_time_password: String, + routing_info: String, + device_description: String, + local_user_id: String, + device_unique_identifier: String, +} + +impl AnisetteData { + pub fn get_headers(&self, serial: String) -> HashMap { + let dt: DateTime = Utc::now().round_subsecs(0); + + HashMap::from_iter( + [ + ( + "X-Apple-I-Client-Time".to_string(), + dt.format("%+").to_string().replace("+00:00", "Z"), + ), + ("X-Apple-I-SRL-NO".to_string(), serial), + ("X-Apple-I-TimeZone".to_string(), "UTC".to_string()), + ("X-Apple-Locale".to_string(), "en_US".to_string()), + ("X-Apple-I-MD-RINFO".to_string(), self.routing_info.clone()), + ("X-Apple-I-MD-LU".to_string(), self.local_user_id.clone()), + ( + "X-Mme-Device-Id".to_string(), + self.device_unique_identifier.clone(), + ), + ("X-Apple-I-MD".to_string(), self.one_time_password.clone()), + ("X-Apple-I-MD-M".to_string(), self.machine_id.clone()), + ( + "X-Mme-Client-Info".to_string(), + self.device_description.clone(), + ), + ] + .into_iter(), + ) + } +} + +fn make_reqwest() -> Result { + Ok(ClientBuilder::new() + .http1_title_case_headers() + .danger_accept_invalid_certs(true) // TODO: pin the apple certificate + .build()?) +} + +impl AnisetteClient { + pub async fn new(url: String) -> Result { + let path = format!("{}/v3/client_info", url); + let http_client = make_reqwest()?; + let client_info = http_client + .get(path) + .send() + .await? + .json::() + .await?; + Ok(AnisetteClient { client_info, url }) + } + + fn build_apple_request( + &self, + state: &AnisetteState, + builder: RequestBuilder, + ) -> RequestBuilder { + let dt: DateTime = Utc::now().round_subsecs(0); + + builder + .header("X-Mme-Client-Info", &self.client_info.client_info) + .header("User-Agent", &self.client_info.user_agent) + .header("Content-Type", "text/x-xml-plist") + .header("X-Apple-I-MD-LU", encode_hex(&state.md_lu())) + .header("X-Mme-Device-Id", state.device_id()) + .header("X-Apple-I-Client-Time", dt.format("%+").to_string()) + .header("X-Apple-I-TimeZone", "EDT") + .header("X-Apple-Locale", "en_US") + } + + pub async fn get_headers(&self, state: &AnisetteState) -> Result { + let path = format!("{}/v3/get_headers", self.url); + let http_client = make_reqwest()?; + + #[derive(Serialize)] + struct GetHeadersBody { + identifier: String, + adi_pb: String, + } + let body = GetHeadersBody { + identifier: base64_encode(&state.keychain_identifier), + adi_pb: base64_encode( + state + .adi_pb + .as_ref() + .ok_or(AnisetteError::AnisetteNotProvisioned)?, + ), + }; + + #[derive(Deserialize)] + #[serde(tag = "result")] + enum AnisetteHeaders { + GetHeadersError { + message: String, + }, + Headers { + #[serde(rename = "X-Apple-I-MD-M")] + machine_id: String, + #[serde(rename = "X-Apple-I-MD")] + one_time_password: String, + #[serde(rename = "X-Apple-I-MD-RINFO")] + routing_info: String, + }, + } + + let headers = http_client + .post(path) + .json(&body) + .send() + .await? + .json::() + .await?; + match headers { + AnisetteHeaders::GetHeadersError { message } => { + if message.contains("-45061") { + Err(AnisetteError::AnisetteNotProvisioned) + } else { + panic!("Unknown error {}", message) + } + } + AnisetteHeaders::Headers { + machine_id, + one_time_password, + routing_info, + } => Ok(AnisetteData { + machine_id, + one_time_password, + routing_info, + device_description: self.client_info.client_info.clone(), + local_user_id: encode_hex(&state.md_lu()), + device_unique_identifier: state.device_id(), + }), + } + } + + pub async fn provision(&self, state: &mut AnisetteState) -> Result<(), AnisetteError> { + debug!("Provisioning Anisette"); + let http_client = make_reqwest()?; + let resp = self + .build_apple_request( + &state, + http_client.get("https://gsa.apple.com/grandslam/GsService2/lookup"), + ) + .send() + .await?; + let text = resp.text().await?; + + let protocol_val = plist::Value::from_reader(Cursor::new(text.as_str()))?; + let urls = protocol_val + .as_dictionary() + .unwrap() + .get("urls") + .unwrap() + .as_dictionary() + .unwrap(); + + let start_provisioning_url = urls + .get("midStartProvisioning") + .unwrap() + .as_string() + .unwrap(); + let end_provisioning_url = urls + .get("midFinishProvisioning") + .unwrap() + .as_string() + .unwrap(); + debug!( + "Got provisioning urls: {} and {}", + start_provisioning_url, end_provisioning_url + ); + + let provision_ws_url = + format!("{}/v3/provisioning_session", self.url).replace("https://", "wss://"); + let (mut connection, _) = connect_async(&provision_ws_url).await?; + + #[derive(Deserialize)] + #[serde(tag = "result")] + enum ProvisionInput { + GiveIdentifier, + GiveStartProvisioningData, + GiveEndProvisioningData { + #[allow(dead_code)] // it's not even dead, rust just has problems + cpim: String, + }, + ProvisioningSuccess { + #[allow(dead_code)] // it's not even dead, rust just has problems + adi_pb: String, + }, + } + + loop { + let Some(Ok(data)) = connection.next().await else { + continue; + }; + if data.is_text() { + let txt = data.to_text().unwrap(); + let msg: ProvisionInput = serde_json::from_str(txt)?; + match msg { + ProvisionInput::GiveIdentifier => { + #[derive(Serialize)] + struct Identifier { + identifier: String, // base64 + } + let identifier = Identifier { + identifier: base64_encode(&state.keychain_identifier), + }; + connection + .send(Message::Text(serde_json::to_string(&identifier)?)) + .await?; + } + ProvisionInput::GiveStartProvisioningData => { + let http_client = make_reqwest()?; + let body_data = ProvisionBodyData { + header: Dictionary::new(), + request: Dictionary::new(), + }; + let resp = self + .build_apple_request(state, http_client.post(start_provisioning_url)) + .body(plist_to_string(&body_data)?) + .send() + .await?; + let text = resp.text().await?; + + let protocol_val = plist::Value::from_reader(Cursor::new(text.as_str()))?; + let spim = protocol_val + .as_dictionary() + .unwrap() + .get("Response") + .unwrap() + .as_dictionary() + .unwrap() + .get("spim") + .unwrap() + .as_string() + .unwrap(); + + debug!("GiveStartProvisioningData"); + #[derive(Serialize)] + struct Spim { + spim: String, // base64 + } + let spim = Spim { + spim: spim.to_string(), + }; + connection + .send(Message::Text(serde_json::to_string(&spim)?)) + .await?; + } + ProvisionInput::GiveEndProvisioningData { cpim } => { + let http_client = make_reqwest()?; + let body_data = ProvisionBodyData { + header: Dictionary::new(), + request: Dictionary::from_iter([("cpim", cpim)].into_iter()), + }; + let resp = self + .build_apple_request(state, http_client.post(end_provisioning_url)) + .body(plist_to_string(&body_data)?) + .send() + .await?; + let text = resp.text().await?; + + let protocol_val = plist::Value::from_reader(Cursor::new(text.as_str()))?; + let response = protocol_val + .as_dictionary() + .unwrap() + .get("Response") + .unwrap() + .as_dictionary() + .unwrap(); + + debug!("GiveEndProvisioningData"); + + #[derive(Serialize)] + struct EndProvisioning<'t> { + ptm: &'t str, + tk: &'t str, + } + let end_provisioning = EndProvisioning { + ptm: response.get("ptm").unwrap().as_string().unwrap(), + tk: response.get("tk").unwrap().as_string().unwrap(), + }; + connection + .send(Message::Text(serde_json::to_string(&end_provisioning)?)) + .await?; + } + ProvisionInput::ProvisioningSuccess { adi_pb } => { + debug!("ProvisioningSuccess"); + state.adi_pb = Some(base64_decode(&adi_pb)); + connection.close(None).await?; + break; + } + } + } else if data.is_close() { + break; + } + } + + Ok(()) + } +} + +pub struct RemoteAnisetteProviderV3 { + client_url: String, + client: Option, + pub state: Option, + configuration_path: PathBuf, + serial: String, +} + +impl RemoteAnisetteProviderV3 { + pub fn new( + url: String, + configuration_path: PathBuf, + serial: String, + ) -> RemoteAnisetteProviderV3 { + RemoteAnisetteProviderV3 { + client_url: url, + client: None, + state: None, + configuration_path, + serial, + } + } +} + +#[async_trait] +impl AnisetteHeadersProvider for RemoteAnisetteProviderV3 { + async fn get_anisette_headers( + &mut self, + _skip_provisioning: bool, + ) -> Result, AnisetteError> { + if self.client.is_none() { + self.client = Some(AnisetteClient::new(self.client_url.clone()).await?); + } + let client = self.client.as_ref().unwrap(); + + fs::create_dir_all(&self.configuration_path)?; + + let config_path = self.configuration_path.join("state.plist"); + if self.state.is_none() { + self.state = Some(if let Ok(text) = plist::from_file(&config_path) { + text + } else { + AnisetteState::new() + }); + } + + let state = self.state.as_mut().unwrap(); + if !state.is_provisioned() { + client.provision(state).await?; + plist::to_file_xml(&config_path, state)?; + } + let data = match client.get_headers(&state).await { + Ok(data) => data, + Err(err) => { + if matches!(err, AnisetteError::AnisetteNotProvisioned) { + state.adi_pb = None; + client.provision(state).await?; + plist::to_file_xml(config_path, state)?; + client.get_headers(&state).await? + } else { + panic!() + } + } + }; + Ok(data.get_headers(self.serial.clone())) + } +} + +#[cfg(test)] +mod tests { + use crate::anisette_headers_provider::AnisetteHeadersProvider; + use crate::remote_anisette_v3::RemoteAnisetteProviderV3; + use crate::{AnisetteError, DEFAULT_ANISETTE_URL_V3}; + use log::info; + + #[tokio::test] + async fn fetch_anisette_remote_v3() -> Result<(), AnisetteError> { + crate::tests::init_logger(); + + let mut provider = RemoteAnisetteProviderV3::new( + DEFAULT_ANISETTE_URL_V3.to_string(), + "anisette_test".into(), + "0".to_string(), + ); + info!( + "Remote headers: {:?}", + (&mut provider as &mut dyn AnisetteHeadersProvider) + .get_authentication_headers() + .await? + ); + Ok(()) + } +} diff --git a/apple-private-apis/omnisette/src/store_services_core.rs b/apple-private-apis/omnisette/src/store_services_core.rs new file mode 100644 index 0000000..5782c03 --- /dev/null +++ b/apple-private-apis/omnisette/src/store_services_core.rs @@ -0,0 +1,452 @@ +#[cfg(target_os = "macos")] +mod posix_macos; +#[cfg(target_family = "windows")] +mod posix_windows; + +use crate::adi_proxy::{ + ADIError, ADIProxy, ConfigurableADIProxy, RequestOTPData, StartProvisioningData, + SynchronizeData, +}; +use crate::AnisetteError; + +use android_loader::android_library::AndroidLibrary; +use android_loader::sysv64_type; +use android_loader::{hook_manager, sysv64}; +use std::collections::HashMap; +use std::ffi::{c_char, CString}; +use std::path::PathBuf; + +pub struct StoreServicesCoreADIProxy<'lt> { + #[allow(dead_code)] + store_services_core: AndroidLibrary<'lt>, + + local_user_uuid: String, + device_identifier: String, + + adi_set_android_id: sysv64_type!(fn(id: *const u8, length: u32) -> i32), + adi_set_provisioning_path: sysv64_type!(fn(path: *const u8) -> i32), + + adi_provisioning_erase: sysv64_type!(fn(ds_id: i64) -> i32), + adi_synchronize: sysv64_type!( + fn( + ds_id: i64, + sim: *const u8, + sim_length: u32, + out_mid: *mut *const u8, + out_mid_length: *mut u32, + out_srm: *mut *const u8, + out_srm_length: *mut u32, + ) -> i32 + ), + adi_provisioning_destroy: sysv64_type!(fn(session: u32) -> i32), + adi_provisioning_end: sysv64_type!( + fn(session: u32, ptm: *const u8, ptm_length: u32, tk: *const u8, tk_length: u32) -> i32 + ), + adi_provisioning_start: sysv64_type!( + fn( + ds_id: i64, + spim: *const u8, + spim_length: u32, + out_cpim: *mut *const u8, + out_cpim_length: *mut u32, + out_session: *mut u32, + ) -> i32 + ), + adi_get_login_code: sysv64_type!(fn(ds_id: i64) -> i32), + adi_dispose: sysv64_type!(fn(ptr: *const u8) -> i32), + adi_otp_request: sysv64_type!( + fn( + ds_id: i64, + out_mid: *mut *const u8, + out_mid_size: *mut u32, + out_otp: *mut *const u8, + out_otp_size: *mut u32, + ) -> i32 + ), +} + +impl StoreServicesCoreADIProxy<'_> { + pub fn new<'lt>(library_path: &PathBuf) -> Result, AnisetteError> { + Self::with_custom_provisioning_path(library_path, library_path) + } + + pub fn with_custom_provisioning_path<'lt>(library_path: &PathBuf, provisioning_path: &PathBuf) -> Result, AnisetteError> { + // Should be safe if the library is correct. + unsafe { + LoaderHelpers::setup_hooks(); + + if !library_path.exists() { + std::fs::create_dir(library_path)?; + return Err(AnisetteError::MissingLibraries.into()); + } + + let library_path = library_path.canonicalize()?; + + #[cfg(target_arch = "x86_64")] + const ARCH: &str = "x86_64"; + #[cfg(target_arch = "x86")] + const ARCH: &str = "x86"; + #[cfg(target_arch = "arm")] + const ARCH: &str = "armeabi-v7a"; + #[cfg(target_arch = "aarch64")] + const ARCH: &str = "arm64-v8a"; + + let native_library_path = library_path.join("lib").join(ARCH); + + let path = native_library_path.join("libstoreservicescore.so"); + let path = path.to_str().ok_or(AnisetteError::Misc)?; + let store_services_core = AndroidLibrary::load(path)?; + + let adi_load_library_with_path: sysv64_type!(fn(path: *const u8) -> i32) = + std::mem::transmute( + store_services_core + .get_symbol("kq56gsgHG6") + .ok_or(AnisetteError::InvalidLibraryFormat)?, + ); + + let path = CString::new( + native_library_path + .to_str() + .ok_or(AnisetteError::Misc)?, + ) + .unwrap(); + assert_eq!((adi_load_library_with_path)(path.as_ptr() as *const u8), 0); + + let adi_set_android_id = store_services_core + .get_symbol("Sph98paBcz") + .ok_or(AnisetteError::InvalidLibraryFormat)?; + let adi_set_provisioning_path = store_services_core + .get_symbol("nf92ngaK92") + .ok_or(AnisetteError::InvalidLibraryFormat)?; + + let adi_provisioning_erase = store_services_core + .get_symbol("p435tmhbla") + .ok_or(AnisetteError::InvalidLibraryFormat)?; + let adi_synchronize = store_services_core + .get_symbol("tn46gtiuhw") + .ok_or(AnisetteError::InvalidLibraryFormat)?; + let adi_provisioning_destroy = store_services_core + .get_symbol("fy34trz2st") + .ok_or(AnisetteError::InvalidLibraryFormat)?; + let adi_provisioning_end = store_services_core + .get_symbol("uv5t6nhkui") + .ok_or(AnisetteError::InvalidLibraryFormat)?; + let adi_provisioning_start = store_services_core + .get_symbol("rsegvyrt87") + .ok_or(AnisetteError::InvalidLibraryFormat)?; + let adi_get_login_code = store_services_core + .get_symbol("aslgmuibau") + .ok_or(AnisetteError::InvalidLibraryFormat)?; + let adi_dispose = store_services_core + .get_symbol("jk24uiwqrg") + .ok_or(AnisetteError::InvalidLibraryFormat)?; + let adi_otp_request = store_services_core + .get_symbol("qi864985u0") + .ok_or(AnisetteError::InvalidLibraryFormat)?; + + let mut proxy = StoreServicesCoreADIProxy { + store_services_core, + + local_user_uuid: String::new(), + device_identifier: String::new(), + + adi_set_android_id: std::mem::transmute(adi_set_android_id), + adi_set_provisioning_path: std::mem::transmute(adi_set_provisioning_path), + + adi_provisioning_erase: std::mem::transmute(adi_provisioning_erase), + adi_synchronize: std::mem::transmute(adi_synchronize), + adi_provisioning_destroy: std::mem::transmute(adi_provisioning_destroy), + adi_provisioning_end: std::mem::transmute(adi_provisioning_end), + adi_provisioning_start: std::mem::transmute(adi_provisioning_start), + adi_get_login_code: std::mem::transmute(adi_get_login_code), + adi_dispose: std::mem::transmute(adi_dispose), + adi_otp_request: std::mem::transmute(adi_otp_request), + }; + + proxy.set_provisioning_path( + provisioning_path.to_str().ok_or(AnisetteError::Misc)?, + )?; + + Ok(proxy) + } + } +} + +impl ADIProxy for StoreServicesCoreADIProxy<'_> { + fn erase_provisioning(&mut self, ds_id: i64) -> Result<(), ADIError> { + match (self.adi_provisioning_erase)(ds_id) { + 0 => Ok(()), + err => Err(ADIError::resolve(err)), + } + } + + fn synchronize(&mut self, ds_id: i64, sim: &[u8]) -> Result { + unsafe { + let sim_size = sim.len() as u32; + let sim_ptr = sim.as_ptr(); + + let mut mid_size: u32 = 0; + let mut mid_ptr: *const u8 = std::ptr::null(); + let mut srm_size: u32 = 0; + let mut srm_ptr: *const u8 = std::ptr::null(); + + match (self.adi_synchronize)( + ds_id, + sim_ptr, + sim_size, + &mut mid_ptr, + &mut mid_size, + &mut srm_ptr, + &mut srm_size, + ) { + 0 => { + let mut mid = vec![0; mid_size as usize]; + let mut srm = vec![0; srm_size as usize]; + + mid.copy_from_slice(std::slice::from_raw_parts(mid_ptr, mid_size as usize)); + srm.copy_from_slice(std::slice::from_raw_parts(srm_ptr, srm_size as usize)); + + (self.adi_dispose)(mid_ptr); + (self.adi_dispose)(srm_ptr); + + Ok(SynchronizeData { mid, srm }) + } + err => Err(ADIError::resolve(err)), + } + } + } + + fn destroy_provisioning_session(&mut self, session: u32) -> Result<(), ADIError> { + match (self.adi_provisioning_destroy)(session) { + 0 => Ok(()), + err => Err(ADIError::resolve(err)), + } + } + + fn end_provisioning(&mut self, session: u32, ptm: &[u8], tk: &[u8]) -> Result<(), ADIError> { + let ptm_size = ptm.len() as u32; + let ptm_ptr = ptm.as_ptr(); + + let tk_size = tk.len() as u32; + let tk_ptr = tk.as_ptr(); + + match (self.adi_provisioning_end)(session, ptm_ptr, ptm_size, tk_ptr, tk_size) { + 0 => Ok(()), + err => Err(ADIError::resolve(err)), + } + } + + fn start_provisioning( + &mut self, + ds_id: i64, + spim: &[u8], + ) -> Result { + unsafe { + let spim_size = spim.len() as u32; + let spim_ptr = spim.as_ptr(); + + let mut cpim_size: u32 = 0; + let mut cpim_ptr: *const u8 = std::ptr::null(); + + let mut session: u32 = 0; + + match (self.adi_provisioning_start)( + ds_id, + spim_ptr, + spim_size, + &mut cpim_ptr, + &mut cpim_size, + &mut session, + ) { + 0 => { + let mut cpim = vec![0; cpim_size as usize]; + + cpim.copy_from_slice(std::slice::from_raw_parts(cpim_ptr, cpim_size as usize)); + + (self.adi_dispose)(cpim_ptr); + + Ok(StartProvisioningData { cpim, session }) + } + err => Err(ADIError::resolve(err)), + } + } + } + + fn is_machine_provisioned(&self, ds_id: i64) -> bool { + (self.adi_get_login_code)(ds_id) == 0 + } + + fn request_otp(&self, ds_id: i64) -> Result { + unsafe { + let mut mid_size: u32 = 0; + let mut mid_ptr: *const u8 = std::ptr::null(); + let mut otp_size: u32 = 0; + let mut otp_ptr: *const u8 = std::ptr::null(); + + match (self.adi_otp_request)( + ds_id, + &mut mid_ptr, + &mut mid_size, + &mut otp_ptr, + &mut otp_size, + ) { + 0 => { + let mut mid = vec![0; mid_size as usize]; + let mut otp = vec![0; otp_size as usize]; + + mid.copy_from_slice(std::slice::from_raw_parts(mid_ptr, mid_size as usize)); + otp.copy_from_slice(std::slice::from_raw_parts(otp_ptr, otp_size as usize)); + + (self.adi_dispose)(mid_ptr); + (self.adi_dispose)(otp_ptr); + + Ok(RequestOTPData { mid, otp }) + } + err => Err(ADIError::resolve(err)), + } + } + } + + fn set_local_user_uuid(&mut self, local_user_uuid: String) { + self.local_user_uuid = local_user_uuid; + } + + fn set_device_identifier(&mut self, device_identifier: String) -> Result<(), ADIError> { + self.set_identifier(&device_identifier[0..16])?; + self.device_identifier = device_identifier; + Ok(()) + } + + fn get_local_user_uuid(&self) -> String { + self.local_user_uuid.clone() + } + + fn get_device_identifier(&self) -> String { + self.device_identifier.clone() + } + + fn get_serial_number(&self) -> String { + "0".to_string() + } +} + +impl ConfigurableADIProxy for StoreServicesCoreADIProxy<'_> { + fn set_identifier(&mut self, identifier: &str) -> Result<(), ADIError> { + match (self.adi_set_android_id)(identifier.as_ptr(), identifier.len() as u32) { + 0 => Ok(()), + err => Err(ADIError::resolve(err)), + } + } + + fn set_provisioning_path(&mut self, path: &str) -> Result<(), ADIError> { + let path = CString::new(path).unwrap(); + match (self.adi_set_provisioning_path)(path.as_ptr() as *const u8) { + 0 => Ok(()), + err => Err(ADIError::resolve(err)), + } + } +} + +struct LoaderHelpers; + +use rand::Rng; + +#[cfg(all(target_family = "unix", not(target_os = "macos")))] +use libc::{ + chmod, close, free, fstat, ftruncate, gettimeofday, lstat, malloc, mkdir, open, read, strncpy, + umask, write, +}; +#[cfg(target_os = "macos")] +use posix_macos::*; + +static mut ERRNO: i32 = 0; + +#[allow(unreachable_code)] +#[sysv64] +unsafe fn __errno_location() -> *mut i32 { + ERRNO = std::io::Error::last_os_error().raw_os_error().unwrap_or(0); + &mut ERRNO +} + +#[sysv64] +fn arc4random() -> u32 { + rand::thread_rng().gen() +} + +#[sysv64] +unsafe fn __system_property_get(_name: *const c_char, value: *mut c_char) -> i32 { + *value = '0' as c_char; + return 1; +} + +#[cfg(target_family = "windows")] +use posix_windows::*; + +impl LoaderHelpers { + pub fn setup_hooks() { + let mut hooks = HashMap::new(); + hooks.insert("arc4random".to_owned(), arc4random as usize); + hooks.insert("chmod".to_owned(), chmod as usize); + hooks.insert( + "__system_property_get".to_owned(), + __system_property_get as usize, + ); + hooks.insert("__errno".to_owned(), __errno_location as usize); + hooks.insert("close".to_owned(), close as usize); + hooks.insert("free".to_owned(), free as usize); + hooks.insert("fstat".to_owned(), fstat as usize); + hooks.insert("ftruncate".to_owned(), ftruncate as usize); + hooks.insert("gettimeofday".to_owned(), gettimeofday as usize); + hooks.insert("lstat".to_owned(), lstat as usize); + hooks.insert("malloc".to_owned(), malloc as usize); + hooks.insert("mkdir".to_owned(), mkdir as usize); + hooks.insert("open".to_owned(), open as usize); + hooks.insert("read".to_owned(), read as usize); + hooks.insert("strncpy".to_owned(), strncpy as usize); + hooks.insert("umask".to_owned(), umask as usize); + hooks.insert("write".to_owned(), write as usize); + + hook_manager::add_hooks(hooks); + } +} + +#[cfg(test)] +mod tests { + use crate::{AnisetteConfiguration, AnisetteHeaders}; + use log::info; + use std::path::PathBuf; + use crate::AnisetteError; + + #[cfg(not(feature = "async"))] + #[test] + fn fetch_anisette_ssc() -> Result<(), AnisetteError> { + crate::tests::init_logger(); + + let mut provider = AnisetteHeaders::get_ssc_anisette_headers_provider( + AnisetteConfiguration::new() + .set_configuration_path(PathBuf::new().join("anisette_test")), + )?; + info!( + "Headers: {:?}", + provider.provider.get_authentication_headers()? + ); + Ok(()) + } + + #[cfg(feature = "async")] + #[tokio::test] + async fn fetch_anisette_ssc_async() -> Result<(), AnisetteError> { + + crate::tests::init_logger(); + + let mut provider = AnisetteHeaders::get_ssc_anisette_headers_provider( + AnisetteConfiguration::new() + .set_configuration_path(PathBuf::new().join("anisette_test")), + )?; + info!( + "Headers: {:?}", + provider.provider.get_authentication_headers().await? + ); + Ok(()) + } +} diff --git a/apple-private-apis/omnisette/src/store_services_core/posix_macos.rs b/apple-private-apis/omnisette/src/store_services_core/posix_macos.rs new file mode 100644 index 0000000..840a039 --- /dev/null +++ b/apple-private-apis/omnisette/src/store_services_core/posix_macos.rs @@ -0,0 +1,102 @@ +pub use libc::{chmod, close, free, ftruncate, gettimeofday, malloc, mkdir, read, strncpy, umask, write}; + +use libc::{lstat as lstat_macos, fstat as fstat_macos, stat as stat_macos, open as open_macos, O_CREAT, O_WRONLY, O_RDWR, O_RDONLY}; + +use android_loader::sysv64; + +#[repr(C)] +pub struct StatLinux { + pub st_dev: u64, + pub st_ino: u64, + pub st_nlink: u64, + pub st_mode: u32, + pub st_uid: u32, + pub st_gid: u32, + __pad0: libc::c_int, + pub st_rdev: u64, + pub st_size: i64, + pub st_blksize: i64, + pub st_blocks: i64, + pub st_atime: i64, + pub st_atime_nsec: i64, + pub st_mtime: i64, + pub st_mtime_nsec: i64, + pub st_ctime: i64, + pub st_ctime_nsec: i64, + __unused: [i64; 3], +} + +#[sysv64] +pub unsafe fn lstat(path: *const libc::c_char, buf: *mut StatLinux) -> libc::c_int { + let mut st: stat_macos = std::mem::zeroed(); + lstat_macos(path, &mut st); + *buf = StatLinux { + st_dev: st.st_dev as _, + st_ino: st.st_ino as _, + st_nlink: st.st_nlink as _, + st_mode: st.st_mode as _, + st_uid: st.st_uid as _, + st_gid: st.st_gid as _, + __pad0: 0 as _, + st_rdev: st.st_rdev as _, + st_size: st.st_size as _, + st_blksize: st.st_blksize as _, + st_blocks: st.st_blocks as _, + st_atime: st.st_atime as _, + st_atime_nsec: st.st_atime_nsec as _, + st_mtime: st.st_mtime as _, + st_mtime_nsec: st.st_mtime_nsec as _, + st_ctime: st.st_ctime as _, + st_ctime_nsec: st.st_ctime_nsec as _, + __unused: [0, 0, 0], + }; + 0 +} + +#[sysv64] +pub unsafe fn fstat(fildes: libc::c_int, buf: *mut StatLinux) -> libc::c_int { + let mut st: stat_macos = std::mem::zeroed(); + fstat_macos(fildes, &mut st); + *buf = StatLinux { + st_dev: st.st_dev as _, + st_ino: st.st_ino as _, + st_nlink: st.st_nlink as _, + st_mode: st.st_mode as _, + st_uid: st.st_uid as _, + st_gid: st.st_gid as _, + __pad0: 0 as _, + st_rdev: st.st_rdev as _, + st_size: st.st_size as _, + st_blksize: st.st_blksize as _, + st_blocks: st.st_blocks as _, + st_atime: st.st_atime as _, + st_atime_nsec: st.st_atime_nsec as _, + st_mtime: st.st_mtime as _, + st_mtime_nsec: st.st_mtime_nsec as _, + st_ctime: st.st_ctime as _, + st_ctime_nsec: st.st_ctime_nsec as _, + __unused: [0, 0, 0], + }; + 0 +} + +#[sysv64] +pub unsafe fn open(path: *const libc::c_char, oflag: libc::c_int) -> libc::c_int { + let mut win_flag = 0; // binary mode + + if oflag & 0o100 != 0 { + win_flag |= O_CREAT; + } + + if oflag & 0o1 == 1 { + win_flag |= O_WRONLY; + } else if oflag & 0o2 != 0 { + win_flag |= O_RDWR; + } else { + win_flag |= O_RDONLY; + } + + let val = open_macos(path, win_flag); + + val +} diff --git a/apple-private-apis/omnisette/src/store_services_core/posix_windows.rs b/apple-private-apis/omnisette/src/store_services_core/posix_windows.rs new file mode 100644 index 0000000..486e2b5 --- /dev/null +++ b/apple-private-apis/omnisette/src/store_services_core/posix_windows.rs @@ -0,0 +1,268 @@ +use android_loader::sysv64; +use libc::{O_CREAT, O_RDONLY, O_RDWR, O_WRONLY}; +use log::debug; +use std::ffi::{CStr, CString}; +use std::mem::MaybeUninit; + +#[link(name = "ucrt")] +extern "C" { + fn _errno() -> *mut libc::c_int; + fn _timespec64_get(__ts: *mut libc::timespec, __base: libc::c_int) -> libc::c_int; + fn _chsize(handle: i64, length: u64) -> usize; +} + +// took from cosmopolitan libc +#[sysv64] +pub unsafe fn umask(mask: usize) -> usize { + debug!("umask: Windows specific implementation called!"); + mask +} + +#[sysv64] +pub unsafe fn ftruncate(handle: i64, length: u64) -> usize { + debug!( + "ftruncate: Windows translate-call. handle: {}, length: {}", + handle, length + ); + let ftr = _chsize(handle, length); + + ftr +} + +#[repr(C)] +pub struct PosixTimeval { + tv_sec: u64, + tv_usec: u64, /* microseconds */ +} + +#[repr(C)] +pub struct PosixTimespec { + tv_sec: i64, + tv_nsec: i64, /* microseconds */ +} + +#[repr(C)] +pub struct PosixTimezone { + tz_minuteswest: u32, + tz_dsttime: u32, /* microseconds */ +} + +static HECTONANOSECONDS: u64 = 10000000; + +impl PosixTimespec { + pub fn from_windows_time(time: u64) -> PosixTimespec { + PosixTimespec { + tv_sec: (time / HECTONANOSECONDS) as i64, + tv_nsec: (time % HECTONANOSECONDS) as i64 * 100, + } + } +} + +#[sysv64] +pub unsafe fn gettimeofday(timeval: *mut PosixTimeval, _tz: *mut PosixTimezone) -> isize { + debug!("gettimeofday: Windows specific implementation called!"); + let mut ts = MaybeUninit::::zeroed(); + + let ret = _timespec64_get(ts.as_mut_ptr(), 1); + let ts = ts.assume_init(); + + *timeval = PosixTimeval { + tv_sec: ts.tv_sec as _, + tv_usec: (ts.tv_nsec / 1000) as _, + }; + + ret as _ +} + +#[repr(C)] +pub struct StatLinux { + pub st_dev: u64, + pub st_ino: u64, + pub st_nlink: u64, + pub st_mode: u32, + pub st_uid: u32, + pub st_gid: u32, + __pad0: libc::c_int, + pub st_rdev: u64, + pub st_size: i64, + pub st_blksize: i64, + pub st_blocks: i64, + pub st_atime: i64, + pub st_atime_nsec: i64, + pub st_mtime: i64, + pub st_mtime_nsec: i64, + pub st_ctime: i64, + pub st_ctime_nsec: i64, + __unused: [i64; 3], +} + +trait ToWindows { + unsafe fn to_windows(&self) -> T; +} + +impl ToWindows for CStr { + unsafe fn to_windows(&self) -> CString { + let path = self + .to_str() + .unwrap() + .to_string() + .chars() + .map(|x| match x { + '/' => '\\', + c => c, + }) + .collect::(); + + let path = path.trim_start_matches("\\\\?\\").to_string(); + + CString::new(path).unwrap() + } +} + +#[sysv64] +pub unsafe fn lstat(path: *const libc::c_char, buf: *mut StatLinux) -> libc::c_int { + debug!( + "lstat: Windows translate-call, path: {:?}", + CStr::from_ptr(path) + ); + let mut stat_win = MaybeUninit::::zeroed(); + let path = CStr::from_ptr(path).to_windows(); + + let ret = libc::stat(path.as_ptr(), stat_win.as_mut_ptr()); + let stat_win = stat_win.assume_init(); + + *buf = stat_win.to_windows(); + + ret +} + +impl ToWindows for libc::stat { + unsafe fn to_windows(&self) -> StatLinux { + let atime = PosixTimespec::from_windows_time(self.st_atime as u64); + let mtime = PosixTimespec::from_windows_time(self.st_mtime as u64); + let ctime = PosixTimespec::from_windows_time(self.st_ctime as u64); + + let mut mode = 0o555; + let win_mode = self.st_mode; + + if win_mode & 0b11 != 0 { + mode |= 0o200; + } + + if win_mode & 0x4000 != 0 { + mode |= 0o40000; + } + + StatLinux { + st_dev: self.st_dev as _, + st_ino: self.st_ino as _, + st_nlink: self.st_nlink as _, + st_mode: mode as _, + st_uid: self.st_uid as _, + st_gid: self.st_gid as _, + __pad0: 0, + st_rdev: self.st_rdev as _, + st_size: self.st_size as _, + st_blksize: 0, + st_blocks: 0, + st_atime: atime.tv_sec, + st_atime_nsec: 0, + st_mtime: mtime.tv_sec, + st_mtime_nsec: 0, + st_ctime: ctime.tv_sec, + st_ctime_nsec: 0, + __unused: [0, 0, 0], + } + } +} + +#[sysv64] +pub unsafe fn fstat(fildes: libc::c_int, buf: *mut StatLinux) -> libc::c_int { + debug!("fstat: Windows translate-call"); + let mut stat_win = MaybeUninit::::zeroed(); + let ret = libc::fstat(fildes, stat_win.as_mut_ptr()); + let stat_win = stat_win.assume_init(); + + *buf = stat_win.to_windows(); + + ret +} + +#[sysv64] +pub unsafe fn malloc(size: libc::size_t) -> *mut libc::c_void { + // debug!("malloc: Windows translate-call"); + libc::malloc(size) +} + +#[sysv64] +pub unsafe fn free(p: *mut libc::c_void) { + // debug!("free: Windows translate-call"); + libc::free(p) +} + +#[sysv64] +pub unsafe fn strncpy( + dst: *mut libc::c_char, + src: *const libc::c_char, + n: libc::size_t, +) -> *mut libc::c_char { + debug!("strncpy: Windows translate-call"); + libc::strncpy(dst, src, n) +} + +#[sysv64] +pub unsafe fn chmod(path: *const libc::c_char, mode: libc::c_int) -> libc::c_int { + debug!("chmod: Windows translate-call"); + libc::chmod(path, mode) +} + +#[sysv64] +pub unsafe fn mkdir(path: *const libc::c_char) -> libc::c_int { + debug!("mkdir: Windows translate-call"); + libc::mkdir(path) +} + +#[sysv64] +pub unsafe fn open(path: *const libc::c_char, oflag: libc::c_int) -> libc::c_int { + debug!("open: Windows translate-call oflag 0o{:o}", oflag); + + let path = CStr::from_ptr(path).to_windows(); + + let mut win_flag = 0x8000; // binary mode + + if oflag & 0o100 != 0 { + win_flag |= O_CREAT; + } + + if oflag & 0o1 == 1 { + win_flag |= O_WRONLY; + } else if oflag & 0o2 != 0 { + win_flag |= O_RDWR; + } else { + win_flag |= O_RDONLY; + } + + let val = libc::open(path.as_ptr(), win_flag); + + val +} + +#[sysv64] +pub unsafe fn close(fd: libc::c_int) -> libc::c_int { + debug!("close: Windows translate-call"); + libc::close(fd) +} + +#[sysv64] +pub unsafe fn read(fd: libc::c_int, buf: *mut libc::c_void, count: libc::c_uint) -> libc::c_int { + debug!("read: Windows translate-call"); + + let r = libc::read(fd, buf, count); + r +} + +#[sysv64] +pub unsafe fn write(fd: libc::c_int, buf: *const libc::c_void, count: libc::c_uint) -> libc::c_int { + debug!("write: Windows translate-call"); + libc::write(fd, buf, count) +} diff --git a/src/application.rs b/src/application.rs new file mode 100644 index 0000000..ade2948 --- /dev/null +++ b/src/application.rs @@ -0,0 +1,62 @@ +// This file was made using https://github.com/Dadoum/Sideloader as a reference. + +use crate::bundle::Bundle; +use std::fs::File; +use std::path::PathBuf; +use zip::ZipArchive; + +pub struct Application { + pub bundle: Bundle, + //pub temp_path: PathBuf, +} + +impl Application { + pub fn new(path: PathBuf) -> Self { + if !path.exists() { + panic!("Application path does not exist: {}", path.display()); + } + + let mut bundle_path = path.clone(); + //let mut temp_path = PathBuf::new(); + + if path.is_file() { + let temp_dir = std::env::temp_dir(); + let temp_path = temp_dir.join(path.file_name().unwrap()); + if temp_path.exists() { + std::fs::remove_dir_all(&temp_path) + .expect("Failed to remove existing temporary files"); + } + std::fs::create_dir_all(&temp_path).expect("Failed to create temporary directory"); + + let file = File::open(&path).expect("Failed to open application file"); + let mut archive = ZipArchive::new(file).expect("Failed to read application archive"); + archive + .extract(&temp_path) + .expect("Failed to extract application archive"); + + let payload_folder = temp_path.join("Payload"); + if payload_folder.exists() && payload_folder.is_dir() { + let app_dirs: Vec<_> = std::fs::read_dir(&payload_folder) + .expect("Failed to read Payload directory") + .filter_map(Result::ok) + .filter(|entry| entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false)) + .filter(|entry| entry.path().extension().map_or(false, |ext| ext == "app")) + .collect(); + if app_dirs.len() == 1 { + bundle_path = app_dirs[0].path(); + } else if app_dirs.is_empty() { + panic!("No .app directory found in Payload"); + } else { + panic!("Multiple .app directories found in Payload"); + } + } else { + panic!("No Payload directory found in the application archive"); + } + } + let bundle = Bundle::new(bundle_path).expect("Failed to create application bundle"); + + Application { + bundle, /*temp_path*/ + } + } +} diff --git a/src/bundle.rs b/src/bundle.rs new file mode 100644 index 0000000..2bf7714 --- /dev/null +++ b/src/bundle.rs @@ -0,0 +1,180 @@ +// This file was made using https://github.com/Dadoum/Sideloader as a reference. + +use crate::Error; +use plist::{Dictionary, Value}; +use std::{ + fs, + path::{Path, PathBuf}, +}; + +pub struct Bundle { + pub app_info: Dictionary, + pub bundle_dir: PathBuf, + + app_extensions: Vec, + _frameworks: Vec, + _libraries: Vec, +} + +impl Bundle { + pub fn new(bundle_dir: PathBuf) -> Result { + let mut bundle_path = bundle_dir; + // Remove trailing slash/backslash + if let Some(path_str) = bundle_path.to_str() { + if path_str.ends_with('/') || path_str.ends_with('\\') { + bundle_path = PathBuf::from(&path_str[..path_str.len() - 1]); + } + } + + let info_plist_path = bundle_path.join("Info.plist"); + assert_bundle( + info_plist_path.exists(), + &format!("No Info.plist here: {}", info_plist_path.display()), + )?; + + let plist_data = fs::read(&info_plist_path) + .map_err(|e| Error::InvalidBundle(format!("Failed to read Info.plist: {}", e)))?; + + let app_info = plist::from_bytes(&plist_data) + .map_err(|e| Error::InvalidBundle(format!("Failed to parse Info.plist: {}", e)))?; + + // Load app extensions from PlugIns directory + let plug_ins_dir = bundle_path.join("PlugIns"); + let app_extensions = if plug_ins_dir.exists() { + fs::read_dir(&plug_ins_dir) + .map_err(|e| { + Error::InvalidBundle(format!("Failed to read PlugIns directory: {}", e)) + })? + .filter_map(|entry| entry.ok()) + .filter(|entry| { + entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false) + && entry.path().join("Info.plist").exists() + }) + .filter_map(|entry| Bundle::new(entry.path()).ok()) + .collect() + } else { + Vec::new() + }; + + // Load frameworks from Frameworks directory + let frameworks_dir = bundle_path.join("Frameworks"); + let frameworks = if frameworks_dir.exists() { + fs::read_dir(&frameworks_dir) + .map_err(|e| { + Error::InvalidBundle(format!("Failed to read Frameworks directory: {}", e)) + })? + .filter_map(|entry| entry.ok()) + .filter(|entry| { + entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false) + && entry.path().join("Info.plist").exists() + }) + .filter_map(|entry| Bundle::new(entry.path()).ok()) + .collect() + } else { + Vec::new() + }; + + // Find all .dylib files in the bundle directory (recursive) + let libraries = find_dylibs(&bundle_path, &bundle_path)?; + + Ok(Bundle { + app_info, + bundle_dir: bundle_path, + app_extensions, + _frameworks: frameworks, + _libraries: libraries, + }) + } + + pub fn set_bundle_identifier(&mut self, id: &str) { + self.app_info.insert( + "CFBundleIdentifier".to_string(), + Value::String(id.to_string()), + ); + } + + pub fn bundle_identifier(&self) -> Option<&str> { + self.app_info + .get("CFBundleIdentifier") + .and_then(|v| v.as_string()) + } + + pub fn bundle_name(&self) -> Option<&str> { + self.app_info + .get("CFBundleName") + .and_then(|v| v.as_string()) + } + + pub fn app_extensions(&self) -> &[Bundle] { + &self.app_extensions + } + + pub fn app_extensions_mut(&mut self) -> &mut [Bundle] { + &mut self.app_extensions + } + + pub fn write_info(&self) -> Result<(), Error> { + let info_plist_path = self.bundle_dir.join("Info.plist"); + let result = plist::to_file_binary(&info_plist_path, &self.app_info); + + if result.is_err() { + return Err(Error::InvalidBundle(format!( + "Failed to write Info.plist: {}", + result.unwrap_err() + ))); + } + Ok(()) + } +} + +fn assert_bundle(condition: bool, msg: &str) -> Result<(), Error> { + if !condition { + Err(Error::InvalidBundle(msg.to_string())) + } else { + Ok(()) + } +} + +fn find_dylibs(dir: &Path, bundle_root: &Path) -> Result, Error> { + let mut libraries = Vec::new(); + + fn collect_dylibs( + dir: &Path, + bundle_root: &Path, + libraries: &mut Vec, + ) -> Result<(), Error> { + let entries = fs::read_dir(dir).map_err(|e| { + Error::InvalidBundle(format!("Failed to read directory {}: {}", dir.display(), e)) + })?; + + for entry in entries { + let entry = entry.map_err(|e| { + Error::InvalidBundle(format!("Failed to read directory entry: {}", e)) + })?; + + let path = entry.path(); + let file_type = entry + .file_type() + .map_err(|e| Error::InvalidBundle(format!("Failed to get file type: {}", e)))?; + + if file_type.is_file() { + if let Some(name) = path.file_name().and_then(|n| n.to_str()) { + if name.ends_with(".dylib") { + // Get relative path from bundle root + if let Ok(relative_path) = path.strip_prefix(bundle_root) { + if let Some(relative_str) = relative_path.to_str() { + libraries.push(relative_str.to_string()); + } + } + } + } + } else if file_type.is_dir() { + collect_dylibs(&path, bundle_root, libraries)?; + } + } + Ok(()) + } + + collect_dylibs(dir, bundle_root, &mut libraries)?; + Ok(libraries) +} diff --git a/src/certificate.rs b/src/certificate.rs new file mode 100644 index 0000000..2a906fa --- /dev/null +++ b/src/certificate.rs @@ -0,0 +1,214 @@ +// This file was made using https://github.com/Dadoum/Sideloader as a reference. + +use hex; +use openssl::{ + hash::MessageDigest, + pkey::{PKey, Private}, + rsa::Rsa, + x509::{X509, X509Name, X509ReqBuilder}, +}; +use sha1::{Digest, Sha1}; +use std::{fs, path::PathBuf}; + +use crate::Error; +use crate::developer_session::{DeveloperDeviceType, DeveloperSession, DeveloperTeam}; + +#[derive(Debug, Clone)] +pub struct CertificateIdentity { + pub certificate: Option, + pub private_key: PKey, + pub key_file: PathBuf, + pub cert_file: PathBuf, +} + +impl CertificateIdentity { + pub async fn new( + configuration_path: PathBuf, + dev_session: &DeveloperSession, + apple_id: String, + ) -> Result { + let mut hasher = Sha1::new(); + hasher.update(apple_id.as_bytes()); + let hash_string = hex::encode(hasher.finalize()).to_lowercase(); + let key_path = configuration_path.join("keys").join(hash_string); + fs::create_dir_all(&key_path) + .map_err(|e| format!("Failed to create key directory: {}", e))?; + + let key_file = key_path.join("key.pem"); + let cert_file = key_path.join("cert.pem"); + let teams = dev_session + .list_teams() + .await + .map_err(|e| format!("Failed to list teams: {:?}", e))?; + let team = teams.first().ok_or("No teams found")?; + let private_key = if key_file.exists() { + let key_data = fs::read_to_string(&key_file) + .map_err(|e| format!("Failed to read key file: {}", e))?; + PKey::private_key_from_pem(key_data.as_bytes()) + .map_err(|e| format!("Failed to load private key: {}", e))? + } else { + let rsa = + Rsa::generate(2048).map_err(|e| format!("Failed to generate RSA key: {}", e))?; + let key = + PKey::from_rsa(rsa).map_err(|e| format!("Failed to create private key: {}", e))?; + let pem_data = key + .private_key_to_pem_pkcs8() + .map_err(|e| format!("Failed to encode private key: {}", e))?; + fs::write(&key_file, pem_data) + .map_err(|e| format!("Failed to save key file: {}", e))?; + key + }; + + let mut cert_identity = CertificateIdentity { + certificate: None, + private_key, + key_file, + cert_file, + }; + + if let Ok(cert) = cert_identity + .find_matching_certificate(dev_session, team) + .await + { + cert_identity.certificate = Some(cert.clone()); + + let cert_pem = cert + .to_pem() + .map_err(|e| format!("Failed to encode certificate to PEM: {}", e))?; + fs::write(&cert_identity.cert_file, cert_pem) + .map_err(|e| format!("Failed to save certificate file: {}", e))?; + + return Ok(cert_identity); + } + + cert_identity + .request_new_certificate(dev_session, team) + .await?; + Ok(cert_identity) + } + + async fn find_matching_certificate( + &self, + dev_session: &DeveloperSession, + team: &DeveloperTeam, + ) -> Result { + let certificates = dev_session + .list_all_development_certs(DeveloperDeviceType::Ios, team) + .await + .map_err(|e| Error::Certificate(format!("Failed to list certificates: {:?}", e)))?; + + let our_public_key = self + .private_key + .public_key_to_der() + .map_err(|e| Error::Certificate(format!("Failed to get public key: {}", e)))?; + + for cert in certificates + .iter() + .filter(|c| c.machine_name == "YCode".to_string()) + { + if let Ok(x509_cert) = X509::from_der(&cert.cert_content) { + if let Ok(cert_public_key) = x509_cert.public_key() { + if let Ok(cert_public_key_der) = cert_public_key.public_key_to_der() { + if cert_public_key_der == our_public_key { + return Ok(x509_cert); + } + } + } + } + } + Error::Certificate("No matching certificate found".to_string()) + } + + async fn request_new_certificate( + &mut self, + dev_session: &DeveloperSession, + team: &DeveloperTeam, + ) -> Result<(), Error> { + let mut req_builder = X509ReqBuilder::new() + .map_err(|e| format!("Failed to create request builder: {}", e))?; + let mut name_builder = + X509Name::builder().map_err(|e| format!("Failed to create name builder: {}", e))?; + + name_builder + .append_entry_by_text("C", "US") + .map_err(|e| format!("Failed to set country: {}", e))?; + name_builder + .append_entry_by_text("ST", "STATE") + .map_err(|e| format!("Failed to set state: {}", e))?; + name_builder + .append_entry_by_text("L", "LOCAL") + .map_err(|e| format!("Failed to set locality: {}", e))?; + name_builder + .append_entry_by_text("O", "ORGNIZATION") + .map_err(|e| format!("Failed to set organization: {}", e))?; + name_builder + .append_entry_by_text("CN", "CN") + .map_err(|e| format!("Failed to set common name: {}", e))?; + + req_builder + .set_subject_name(&name_builder.build()) + .map_err(|e| format!("Failed to set subject name: {}", e))?; + req_builder + .set_pubkey(&self.private_key) + .map_err(|e| format!("Failed to set public key: {}", e))?; + req_builder + .sign(&self.private_key, MessageDigest::sha256()) + .map_err(|e| format!("Failed to sign request: {}", e))?; + + let csr_pem = req_builder + .build() + .to_pem() + .map_err(|e| format!("Failed to encode CSR: {}", e))?; + + let certificate_id = dev_session + .submit_development_csr( + DeveloperDeviceType::Ios, + team, + String::from_utf8_lossy(&csr_pem).to_string(), + ) + .await + .map_err(|e| { + let is_7460 = match &e { + Error::DeveloperSession(code, _) => *code == 7460, + _ => false, + }; + if is_7460 { + Error::Certificate("You have too many certificates!".to_string()) + } else { + Error::Certificate(format!("Failed to submit CSR: {:?}", e)) + } + })?; + + let certificates = dev_session + .list_all_development_certs(DeveloperDeviceType::Ios, team) + .await + .map_err(|e| format!("Failed to list certificates: {:?}", e))?; + + let apple_cert = certificates + .iter() + .find(|cert| cert.certificate_id == certificate_id) + .ok_or("Certificate not found after submission")?; + + let certificate = X509::from_der(&apple_cert.cert_content) + .map_err(|e| format!("Failed to parse certificate: {}", e))?; + + // Write certificate to disk + let cert_pem = certificate + .to_pem() + .map_err(|e| format!("Failed to encode certificate to PEM: {}", e))?; + fs::write(&self.cert_file, cert_pem) + .map_err(|e| format!("Failed to save certificate file: {}", e))?; + + self.certificate = Some(certificate); + + Ok(()) + } + + pub fn get_certificate_file_path(&self) -> &PathBuf { + &self.cert_file + } + + pub fn get_private_key_file_path(&self) -> &PathBuf { + &self.key_file + } +} diff --git a/src/developer_session.rs b/src/developer_session.rs new file mode 100644 index 0000000..3d3fb49 --- /dev/null +++ b/src/developer_session.rs @@ -0,0 +1,698 @@ +// This file was made using https://github.com/Dadoum/Sideloader as a reference for the apple private endpoints + +use crate::Error; +use icloud_auth::{AppleAccount, Error as ICloudError}; +use plist::{Date, Dictionary, Value}; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use uuid::Uuid; + +pub struct DeveloperSession { + pub account: Arc, + team: Option, +} + +impl DeveloperSession { + pub fn new(account: Arc) -> Self { + DeveloperSession { + account, + team: None, + } + } + + pub async fn send_developer_request( + &self, + url: &str, + body: Option, + ) -> Result { + let mut request = Dictionary::new(); + request.insert( + "clientId".to_string(), + Value::String("XABBG36SBA".to_string()), + ); + request.insert( + "protocolVersion".to_string(), + Value::String("QH65B2".to_string()), + ); + request.insert( + "requestId".to_string(), + Value::String(Uuid::new_v4().to_string().to_uppercase()), + ); + request.insert( + "userLocale".to_string(), + Value::Array(vec![Value::String("en_US".to_string())]), + ); + if let Some(body) = body { + for (key, value) in body { + request.insert(key, value); + } + } + + let response = self + .account + .send_request(url, Some(request)) + .await + .map_err(|e| { + if let ICloudError::AuthSrpWithMessage(code, message) = e { + Error::DeveloperSession(code, format!("Developer request failed: {}", message)) + } else { + Error::Generic + } + })?; + + let status_code = response + .get("resultCode") + .and_then(|v| v.as_unsigned_integer()) + .unwrap_or(0); + if status_code != 0 { + let description = response + .get("userString") + .and_then(|v| v.as_string()) + .or_else(|| response.get("resultString").and_then(|v| v.as_string())) + .unwrap_or("(null)"); + return Err(Error::DeveloperSession( + status_code as i64, + description.to_string(), + )); + } + Ok(response) + } + + pub async fn list_teams(&self) -> Result, Error> { + let url = "https://developerservices2.apple.com/services/QH65B2/listTeams.action?clientId=XABBG36SBA"; + let response = self.send_developer_request(url, None).await?; + + let teams = response + .get("teams") + .and_then(|v| v.as_array()) + .ok_or(Error::Parse)?; + + let mut result = Vec::new(); + for team in teams { + let dict = team.as_dictionary().ok_or(Error::Parse)?; + let name = dict + .get("name") + .and_then(|v| v.as_string()) + .ok_or(Error::Parse)? + .to_string(); + let team_id = dict + .get("teamId") + .and_then(|v| v.as_string()) + .ok_or(Error::Parse)? + .to_string(); + result.push(DeveloperTeam { + _name: name, + team_id, + }); + } + Ok(result) + } + + pub async fn get_team(&self) -> Result { + if let Some(team) = &self.team { + return Ok(team.clone()); + } + let teams = self.list_teams().await?; + if teams.is_empty() { + return Err(Error::DeveloperSession( + -1, + "No developer teams found".to_string(), + )); + } + // TODO: Handle multiple teams + Ok(teams[0].clone()) + } + + pub fn set_team(&mut self, team: DeveloperTeam) { + self.team = Some(team); + } + + pub async fn list_devices( + &self, + device_type: DeveloperDeviceType, + team: &DeveloperTeam, + ) -> Result, Error> { + let url = dev_url(device_type, "listDevices"); + let mut body = Dictionary::new(); + body.insert("teamId".to_string(), Value::String(team.team_id.clone())); + let response = self.send_developer_request(&url, Some(body)).await?; + + let devices = response + .get("devices") + .and_then(|v| v.as_array()) + .ok_or(Error::Parse)?; + + let mut result = Vec::new(); + for device in devices { + let dict = device.as_dictionary().ok_or(Error::Parse)?; + let device_id = dict + .get("deviceId") + .and_then(|v| v.as_string()) + .ok_or(Error::Parse)? + .to_string(); + let name = dict + .get("name") + .and_then(|v| v.as_string()) + .ok_or(Error::Parse)? + .to_string(); + let device_number = dict + .get("deviceNumber") + .and_then(|v| v.as_string()) + .ok_or(Error::Parse)? + .to_string(); + result.push(DeveloperDevice { + _device_id: device_id, + _name: name, + device_number, + }); + } + Ok(result) + } + + pub async fn add_device( + &self, + device_type: DeveloperDeviceType, + team: &DeveloperTeam, + device_name: &str, + udid: &str, + ) -> Result { + let url = dev_url(device_type, "addDevice"); + let mut body = Dictionary::new(); + body.insert("teamId".to_string(), Value::String(team.team_id.clone())); + body.insert("name".to_string(), Value::String(device_name.to_string())); + body.insert("deviceNumber".to_string(), Value::String(udid.to_string())); + + let response = self.send_developer_request(&url, Some(body)).await?; + + let device_dict = response + .get("device") + .and_then(|v| v.as_dictionary()) + .ok_or(Error::Parse)?; + + let device_id = device_dict + .get("deviceId") + .and_then(|v| v.as_string()) + .ok_or(Error::Parse)? + .to_string(); + let name = device_dict + .get("name") + .and_then(|v| v.as_string()) + .ok_or(Error::Parse)? + .to_string(); + let device_number = device_dict + .get("deviceNumber") + .and_then(|v| v.as_string()) + .ok_or(Error::Parse)? + .to_string(); + + Ok(DeveloperDevice { + _device_id: device_id, + _name: name, + device_number, + }) + } + + pub async fn list_all_development_certs( + &self, + device_type: DeveloperDeviceType, + team: &DeveloperTeam, + ) -> Result, Error> { + let url = dev_url(device_type, "listAllDevelopmentCerts"); + let mut body = Dictionary::new(); + body.insert("teamId".to_string(), Value::String(team.team_id.clone())); + + let response = self.send_developer_request(&url, Some(body)).await?; + + let certs = response + .get("certificates") + .and_then(|v| v.as_array()) + .ok_or(Error::Parse)?; + + let mut result = Vec::new(); + for cert in certs { + let dict = cert.as_dictionary().ok_or(Error::Parse)?; + let name = dict + .get("name") + .and_then(|v| v.as_string()) + .ok_or(Error::Parse)? + .to_string(); + let certificate_id = dict + .get("certificateId") + .and_then(|v| v.as_string()) + .ok_or(Error::Parse)? + .to_string(); + let serial_number = dict + .get("serialNumber") + .and_then(|v| v.as_string()) + .ok_or(Error::Parse)? + .to_string(); + let machine_name = dict + .get("machineName") + .and_then(|v| v.as_string()) + .unwrap_or("") + .to_string(); + let cert_content = dict + .get("certContent") + .and_then(|v| v.as_data()) + .ok_or(Error::Parse)? + .to_vec(); + + result.push(DevelopmentCertificate { + name: name, + certificate_id, + serial_number: serial_number, + machine_name: machine_name, + cert_content, + }); + } + Ok(result) + } + + pub async fn revoke_development_cert( + &self, + device_type: DeveloperDeviceType, + team: &DeveloperTeam, + serial_number: &str, + ) -> Result<(), Error> { + let url = dev_url(device_type, "revokeDevelopmentCert"); + let mut body = Dictionary::new(); + body.insert("teamId".to_string(), Value::String(team.team_id.clone())); + body.insert( + "serialNumber".to_string(), + Value::String(serial_number.to_string()), + ); + + self.send_developer_request(&url, Some(body)).await?; + Ok(()) + } + + pub async fn submit_development_csr( + &self, + device_type: DeveloperDeviceType, + team: &DeveloperTeam, + csr_content: String, + ) -> Result { + let url = dev_url(device_type, "submitDevelopmentCSR"); + let mut body = Dictionary::new(); + body.insert("teamId".to_string(), Value::String(team.team_id.clone())); + body.insert("csrContent".to_string(), Value::String(csr_content)); + body.insert( + "machineId".to_string(), + Value::String(uuid::Uuid::new_v4().to_string().to_uppercase()), + ); + body.insert( + "machineName".to_string(), + Value::String("YCode".to_string()), + ); + + let response = self.send_developer_request(&url, Some(body)).await?; + let cert_dict = response + .get("certRequest") + .and_then(|v| v.as_dictionary()) + .ok_or(Error::Parse)?; + let id = cert_dict + .get("certRequestId") + .and_then(|v| v.as_string()) + .ok_or(Error::Parse)? + .to_string(); + + Ok(id) + } + + pub async fn list_app_ids( + &self, + device_type: DeveloperDeviceType, + team: &DeveloperTeam, + ) -> Result { + let url = dev_url(device_type, "listAppIds"); + let mut body = Dictionary::new(); + body.insert("teamId".to_string(), Value::String(team.team_id.clone())); + + let response = self.send_developer_request(&url, Some(body)).await?; + + let app_ids = response + .get("appIds") + .and_then(|v| v.as_array()) + .ok_or(Error::Parse)?; + + let mut result = Vec::new(); + for app_id in app_ids { + let dict = app_id.as_dictionary().ok_or(Error::Parse)?; + let name = dict + .get("name") + .and_then(|v| v.as_string()) + .ok_or(Error::Parse)? + .to_string(); + let app_id_id = dict + .get("appIdId") + .and_then(|v| v.as_string()) + .ok_or(Error::Parse)? + .to_string(); + let identifier = dict + .get("identifier") + .and_then(|v| v.as_string()) + .ok_or(Error::Parse)? + .to_string(); + let features = dict + .get("features") + .and_then(|v| v.as_dictionary()) + .ok_or(Error::Parse)?; + let expiration_date = if dict.contains_key("expirationDate") { + Some( + dict.get("expirationDate") + .and_then(|v| v.as_date()) + .ok_or(Error::Parse)?, + ) + } else { + None + }; + + result.push(AppId { + name, + app_id_id, + identifier, + features: features.clone(), + expiration_date, + }); + } + + let max_quantity = response + .get("maxQuantity") + .and_then(|v| v.as_unsigned_integer()) + .ok_or(Error::Parse)?; + let available_quantity = response + .get("availableQuantity") + .and_then(|v| v.as_unsigned_integer()) + .ok_or(Error::Parse)?; + + Ok(ListAppIdsResponse { + app_ids: result, + max_quantity, + available_quantity, + }) + } + + pub async fn add_app_id( + &self, + device_type: DeveloperDeviceType, + team: &DeveloperTeam, + name: &str, + identifier: &str, + ) -> Result<(), Error> { + let url = dev_url(device_type, "addAppId"); + let mut body = Dictionary::new(); + body.insert("teamId".to_string(), Value::String(team.team_id.clone())); + body.insert("name".to_string(), Value::String(name.to_string())); + body.insert( + "identifier".to_string(), + Value::String(identifier.to_string()), + ); + + self.send_developer_request(&url, Some(body)).await?; + + Ok(()) + } + + pub async fn update_app_id( + &self, + device_type: DeveloperDeviceType, + team: &DeveloperTeam, + app_id: &AppId, + features: &Dictionary, + ) -> Result { + let url = dev_url(device_type, "updateAppId"); + let mut body = Dictionary::new(); + body.insert( + "appIdId".to_string(), + Value::String(app_id.app_id_id.clone()), + ); + body.insert("teamId".to_string(), Value::String(team.team_id.clone())); + + for (key, value) in features { + body.insert(key.clone(), value.clone()); + } + + let response = self.send_developer_request(&url, Some(body)).await?; + let cert_dict = response + .get("appId") + .and_then(|v| v.as_dictionary()) + .ok_or(Error::Parse)?; + let feats = cert_dict + .get("features") + .and_then(|v| v.as_dictionary()) + .ok_or(Error::Parse)?; + + Ok(feats.clone()) + } + + pub async fn delete_app_id( + &self, + device_type: DeveloperDeviceType, + team: &DeveloperTeam, + app_id_id: String, + ) -> Result<(), Error> { + let url = dev_url(device_type, "deleteAppId"); + let mut body = Dictionary::new(); + body.insert("teamId".to_string(), Value::String(team.team_id.clone())); + body.insert("appIdId".to_string(), Value::String(app_id_id.clone())); + + self.send_developer_request(&url, Some(body)).await?; + + Ok(()) + } + + pub async fn list_application_groups( + &self, + device_type: DeveloperDeviceType, + team: &DeveloperTeam, + ) -> Result, Error> { + let url = dev_url(device_type, "listApplicationGroups"); + let mut body = Dictionary::new(); + body.insert("teamId".to_string(), Value::String(team.team_id.clone())); + + let response = self.send_developer_request(&url, Some(body)).await?; + + let app_groups = response + .get("applicationGroupList") + .and_then(|v| v.as_array()) + .ok_or(Error::Parse)?; + + let mut result = Vec::new(); + for app_group in app_groups { + let dict = app_group.as_dictionary().ok_or(Error::Parse)?; + let application_group = dict + .get("applicationGroup") + .and_then(|v| v.as_string()) + .ok_or(Error::Parse)? + .to_string(); + let name = dict + .get("name") + .and_then(|v| v.as_string()) + .ok_or(Error::Parse)? + .to_string(); + let identifier = dict + .get("identifier") + .and_then(|v| v.as_string()) + .ok_or(Error::Parse)? + .to_string(); + + result.push(ApplicationGroup { + application_group, + _name: name, + identifier, + }); + } + + Ok(result) + } + + pub async fn add_application_group( + &self, + device_type: DeveloperDeviceType, + team: &DeveloperTeam, + group_identifier: &str, + name: &str, + ) -> Result { + let url = dev_url(device_type, "addApplicationGroup"); + let mut body = Dictionary::new(); + body.insert("teamId".to_string(), Value::String(team.team_id.clone())); + body.insert("name".to_string(), Value::String(name.to_string())); + body.insert( + "identifier".to_string(), + Value::String(group_identifier.to_string()), + ); + + let response = self.send_developer_request(&url, Some(body)).await?; + let app_group_dict = response + .get("applicationGroup") + .and_then(|v| v.as_dictionary()) + .ok_or(Error::Parse)?; + let application_group = app_group_dict + .get("applicationGroup") + .and_then(|v| v.as_string()) + .ok_or(Error::Parse)? + .to_string(); + let name = app_group_dict + .get("name") + .and_then(|v| v.as_string()) + .ok_or(Error::Parse)? + .to_string(); + let identifier = app_group_dict + .get("identifier") + .and_then(|v| v.as_string()) + .ok_or(Error::Parse)? + .to_string(); + + Ok(ApplicationGroup { + application_group, + _name: name, + identifier, + }) + } + + pub async fn assign_application_group_to_app_id( + &self, + device_type: DeveloperDeviceType, + team: &DeveloperTeam, + app_id: &AppId, + app_group: &ApplicationGroup, + ) -> Result<(), Error> { + let url = dev_url(device_type, "assignApplicationGroupToAppId"); + let mut body = Dictionary::new(); + body.insert("teamId".to_string(), Value::String(team.team_id.clone())); + body.insert( + "appIdId".to_string(), + Value::String(app_id.app_id_id.clone()), + ); + body.insert( + "applicationGroups".to_string(), + Value::String(app_group.application_group.clone()), + ); + + self.send_developer_request(&url, Some(body)).await?; + + Ok(()) + } + + pub async fn download_team_provisioning_profile( + &self, + device_type: DeveloperDeviceType, + team: &DeveloperTeam, + app_id: &AppId, + ) -> Result { + let url = dev_url(device_type, "downloadTeamProvisioningProfile"); + let mut body = Dictionary::new(); + body.insert("teamId".to_string(), Value::String(team.team_id.clone())); + body.insert( + "appIdId".to_string(), + Value::String(app_id.app_id_id.clone()), + ); + + let response = self.send_developer_request(&url, Some(body)).await?; + + let profile = response + .get("provisioningProfile") + .and_then(|v| v.as_dictionary()) + .ok_or(Error::Parse)?; + let name = profile + .get("name") + .and_then(|v| v.as_string()) + .ok_or(Error::Parse)? + .to_string(); + let provisioning_profile_id = profile + .get("provisioningProfileId") + .and_then(|v| v.as_string()) + .ok_or(Error::Parse)? + .to_string(); + let encoded_profile = profile + .get("encodedProfile") + .and_then(|v| v.as_data()) + .ok_or(Error::Parse)? + .to_vec(); + + Ok(ProvisioningProfile { + _name: name, + _provisioning_profile_id: provisioning_profile_id, + encoded_profile, + }) + } +} + +#[derive(Debug, Clone)] +pub enum DeveloperDeviceType { + Any, + Ios, + Tvos, + Watchos, +} + +impl DeveloperDeviceType { + pub fn url_segment(&self) -> &'static str { + match self { + DeveloperDeviceType::Any => "", + DeveloperDeviceType::Ios => "ios/", + DeveloperDeviceType::Tvos => "tvos/", + DeveloperDeviceType::Watchos => "watchos/", + } + } +} + +fn dev_url(device_type: DeveloperDeviceType, endpoint: &str) -> String { + format!( + "https://developerservices2.apple.com/services/QH65B2/{}{}.action?clientId=XABBG36SBA", + device_type.url_segment(), + endpoint + ) +} + +#[derive(Debug, Clone)] +pub struct DeveloperDevice { + pub _device_id: String, + pub _name: String, + pub device_number: String, +} + +#[derive(Debug, Clone)] +pub struct DeveloperTeam { + pub _name: String, + pub team_id: String, +} + +#[derive(Debug, Clone)] +pub struct DevelopmentCertificate { + pub name: String, + pub certificate_id: String, + pub serial_number: String, + pub machine_name: String, + pub cert_content: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AppId { + pub app_id_id: String, + pub identifier: String, + pub name: String, + pub features: Dictionary, + pub expiration_date: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ListAppIdsResponse { + pub app_ids: Vec, + pub max_quantity: u64, + pub available_quantity: u64, +} + +#[derive(Debug, Clone)] +pub struct ApplicationGroup { + pub application_group: String, + pub _name: String, + pub identifier: String, +} + +#[derive(Debug, Clone)] +pub struct ProvisioningProfile { + pub _provisioning_profile_id: String, + pub _name: String, + pub encoded_profile: Vec, +} diff --git a/src/device.rs b/src/device.rs new file mode 100644 index 0000000..2dee3cc --- /dev/null +++ b/src/device.rs @@ -0,0 +1,179 @@ +use idevice::{ + IdeviceService, + afc::AfcClient, + installation_proxy::InstallationProxyClient, + lockdown::LockdownClient, + usbmuxd::{UsbmuxdAddr, UsbmuxdConnection}, +}; +use serde::{Deserialize, Serialize}; +use std::future::Future; +use std::path::PathBuf; +use std::pin::Pin; + +#[derive(Deserialize, Serialize, Clone)] +pub struct DeviceInfo { + pub name: String, + pub id: u32, + pub uuid: String, +} + +pub async fn list_devices() -> Result, String> { + let usbmuxd = UsbmuxdConnection::default().await; + if usbmuxd.is_err() { + eprintln!("Failed to connect to usbmuxd: {:?}", usbmuxd.err()); + return Err("Failed to connect to usbmuxd".to_string()); + } + let mut usbmuxd = usbmuxd.unwrap(); + + let devs = usbmuxd.get_devices().await.unwrap(); + if devs.is_empty() { + return Ok(vec![]); + } + + let device_info_futures: Vec<_> = devs + .iter() + .map(|d| async move { + let provider = d.to_provider(UsbmuxdAddr::from_env_var().unwrap(), "y-code"); + let device_uid = d.device_id; + + let mut lockdown_client = match LockdownClient::connect(&provider).await { + Ok(l) => l, + Err(e) => { + eprintln!("Unable to connect to lockdown: {e:?}"); + return DeviceInfo { + name: String::from("Unknown Device"), + id: device_uid, + uuid: d.udid.clone(), + }; + } + }; + + let device_name = lockdown_client + .get_value("DeviceName", None) + .await + .expect("Failed to get device name") + .as_string() + .expect("Failed to convert device name to string") + .to_string(); + + DeviceInfo { + name: device_name, + id: device_uid, + uuid: d.udid.clone(), + } + }) + .collect(); + + Ok(futures::future::join_all(device_info_futures).await) +} + +pub async fn install_app( + device: &DeviceInfo, + app_path: &PathBuf, + callback: impl Fn(u64) -> (), +) -> Result<(), String> { + let mut usbmuxd = UsbmuxdConnection::default() + .await + .map_err(|e| format!("Failed to connect to usbmuxd: {:?}", e))?; + let device = usbmuxd + .get_device(&device.uuid) + .await + .map_err(|e| format!("Failed to get device: {:?}", e))?; + + let provider = device.to_provider(UsbmuxdAddr::from_env_var().unwrap(), "y-code"); + + let mut afc_client = AfcClient::connect(&provider) + .await + .map_err(|e| format!("Failed to connect to AFC: {:?}", e))?; + + let dir = format!( + "PublicStaging/{}", + app_path.file_name().unwrap().to_string_lossy() + ); + afc_upload_dir(&mut afc_client, app_path, &dir) + .await + .map_err(|e| format!("Failed to upload directory: {:?}", e))?; + + let mut instproxy_client = InstallationProxyClient::connect(&provider) + .await + .map_err(|e| format!("Failed to connect to installation proxy: {:?}", e))?; + + let mut options = plist::Dictionary::new(); + options.insert("PackageType".to_string(), "Developer".into()); + instproxy_client + .install_with_callback( + dir, + Some(plist::Value::Dictionary(options)), + async |(percentage, _)| { + callback(percentage); + }, + (), + ) + .await + .map_err(|e| format!("Failed to install app: {:?}", e))?; + + Ok(()) +} + +fn afc_upload_dir<'a>( + afc_client: &'a mut AfcClient, + path: &'a PathBuf, + afc_path: &'a str, +) -> Pin> + Send + 'a>> { + Box::pin(async move { + let entries = + std::fs::read_dir(path).map_err(|e| format!("Failed to read directory: {}", e))?; + afc_client + .mk_dir(afc_path) + .await + .map_err(|e| format!("Failed to create directory: {}", e))?; + for entry in entries { + let entry = entry.map_err(|e| format!("Failed to read directory entry: {}", e))?; + let path = entry.path(); + if path.is_dir() { + let new_afc_path = format!( + "{}/{}", + afc_path, + path.file_name().unwrap().to_string_lossy() + ); + afc_upload_dir(afc_client, &path, &new_afc_path).await?; + } else { + let mut file_handle = afc_client + .open( + format!( + "{}/{}", + afc_path, + path.file_name().unwrap().to_string_lossy() + ), + idevice::afc::opcode::AfcFopenMode::WrOnly, + ) + .await + .map_err(|e| format!("Failed to open file: {}", e))?; + let bytes = + std::fs::read(&path).map_err(|e| format!("Failed to read file: {}", e))?; + file_handle + .write(&bytes) + .await + .map_err(|e| format!("Failed to write file: {}", e))?; + } + } + Ok(()) + }) +} + +#[tauri::command] +pub async fn refresh_idevice(window: tauri::Window) { + match list_devices().await { + Ok(devices) => { + window + .emit("idevices", devices) + .expect("Failed to send devices"); + } + Err(e) => { + window + .emit("idevices", Vec::::new()) + .expect("Failed to send error"); + eprintln!("Failed to list devices: {}", e); + } + }; +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..13a5335 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,21 @@ +pub mod application; +pub mod bundle; +pub mod certificate; +pub mod developer_session; +pub mod device; +pub mod sideload; + +pub use developer_session::{ + AppId, ApplicationGroup, DeveloperDevice, DeveloperDeviceType, DeveloperSession, DeveloperTeam, + DevelopmentCertificate, ListAppIdsResponse, ProvisioningProfile, +}; + +#[derive(Debug)] +pub enum Error { + Auth(i64, String), + DeveloperSession(i64, String), + Generic, + Parse, + InvalidBundle(String), + Certificate(String), +} diff --git a/src/sideload.rs b/src/sideload.rs new file mode 100644 index 0000000..a96427c --- /dev/null +++ b/src/sideload.rs @@ -0,0 +1,400 @@ +// This file was made using https://github.com/Dadoum/Sideloader as a reference. + +use crate::Error; +use crate::{ + device::{DeviceInfo, install_app}, + sideloader::{ + certificate::CertificateIdentity, developer_session::DeveloperDeviceType, + }, +}; +use std::{io::Write, path::PathBuf}; + +pub async fn sideload_app( + handle: &tauri::AppHandle, + window: &tauri::Window, + anisette_server: String, + device: DeviceInfo, + app_path: PathBuf, +) -> Result<(), Error> { + if device.uuid.is_empty() { + return emit_error_and_return(window, "No device selected"); + } + let dev_session = match crate::sideloader::apple::get_developer_session( + &handle, + window, + anisette_server.clone(), + ) + .await + { + Ok(acc) => acc, + Err(e) => { + return emit_error_and_return( + window, + &format!("Failed to login to Apple account: {:?}", e), + ); + } + }; + let team = match dev_session.get_team().await { + Ok(t) => t, + Err(e) => { + return emit_error_and_return(window, &format!("Failed to get team: {:?}", e)); + } + }; + window + .emit("build-output", "Successfully retrieved team".to_string()) + .ok(); + ensure_device_registered(&dev_session, window, &team, &device).await?; + + let config_dir = handle.path().app_config_dir().map_err(|e| e.to_string())?; + let cert = match CertificateIdentity::new(config_dir, &dev_session, get_apple_email()).await { + Ok(c) => c, + Err(e) => { + return emit_error_and_return(window, &format!("Failed to get certificate: {:?}", e)); + } + }; + window + .emit( + "build-output", + "Certificate acquired succesfully".to_string(), + ) + .ok(); + let mut list_app_id_response = match dev_session + .list_app_ids(DeveloperDeviceType::Ios, &team) + .await + { + Ok(ids) => ids, + Err(e) => { + return emit_error_and_return(window, &format!("Failed to list app IDs: {:?}", e)); + } + }; + + let mut app = crate::sideloader::application::Application::new(app_path); + let is_sidestore = app.bundle.bundle_identifier().unwrap_or("") == "com.SideStore.SideStore"; + let main_app_bundle_id = match app.bundle.bundle_identifier() { + Some(id) => id.to_string(), + None => { + return emit_error_and_return(window, "No bundle identifier found in IPA"); + } + }; + let main_app_id_str = format!("{}.{}", main_app_bundle_id, team.team_id); + let main_app_name = match app.bundle.bundle_name() { + Some(name) => name.to_string(), + None => { + return emit_error_and_return(window, "No bundle name found in IPA"); + } + }; + + let extensions = app.bundle.app_extensions_mut(); + // for each extension, ensure it has a unique bundle identifier that starts with the main app's bundle identifier + for ext in extensions.iter_mut() { + if let Some(id) = ext.bundle_identifier() { + if !(id.starts_with(&main_app_bundle_id) && id.len() > main_app_bundle_id.len()) { + return emit_error_and_return( + window, + &format!( + "Extension {} is not part of the main app bundle identifier: {}", + ext.bundle_name().unwrap_or("Unknown"), + id + ), + ); + } else { + ext.set_bundle_identifier(&format!( + "{}{}", + main_app_id_str, + &id[main_app_bundle_id.len()..] + )); + } + } + } + app.bundle.set_bundle_identifier(&main_app_id_str); + + let extension_refs: Vec<_> = app.bundle.app_extensions().into_iter().collect(); + let mut bundles_with_app_id = vec![&app.bundle]; + bundles_with_app_id.extend(extension_refs); + + let app_ids_to_register = bundles_with_app_id + .iter() + .filter(|bundle| { + let bundle_id = bundle.bundle_identifier().unwrap_or(""); + !list_app_id_response + .app_ids + .iter() + .any(|app_id| app_id.identifier == bundle_id) + }) + .collect::>(); + + if app_ids_to_register.len() > list_app_id_response.available_quantity.try_into().unwrap() { + return emit_error_and_return( + window, + &format!( + "This app requires {} app ids, but you only have {} available", + app_ids_to_register.len(), + list_app_id_response.available_quantity + ), + ); + } + + for bundle in app_ids_to_register { + let id = bundle.bundle_identifier().unwrap_or(""); + let name = bundle.bundle_name().unwrap_or(""); + if let Err(e) = dev_session + .add_app_id(DeveloperDeviceType::Ios, &team, &name, &id) + .await + { + return emit_error_and_return(window, &format!("Failed to register app ID: {:?}", e)); + } + } + list_app_id_response = match dev_session + .list_app_ids(DeveloperDeviceType::Ios, &team) + .await + { + Ok(ids) => ids, + Err(e) => { + return emit_error_and_return(window, &format!("Failed to list app IDs: {:?}", e)); + } + }; + + let mut app_ids: Vec<_> = list_app_id_response + .app_ids + .into_iter() + .filter(|app_id| { + bundles_with_app_id + .iter() + .any(|bundle| app_id.identifier == bundle.bundle_identifier().unwrap_or("")) + }) + .collect(); + let main_app_id = match app_ids + .iter() + .find(|app_id| app_id.identifier == main_app_id_str) + .cloned() + { + Some(id) => id, + None => { + return emit_error_and_return( + window, + &format!( + "Main app ID {} not found in registered app IDs", + main_app_id_str + ), + ); + } + }; + + window + .emit("build-output", "Registered app IDs".to_string()) + .ok(); + + for app_id in app_ids.iter_mut() { + let app_group_feature_enabled = app_id + .features + .get( + "APG3427HIY", /* Gotta love apple and their magic strings! */ + ) + .and_then(|v| v.as_boolean()) + .ok_or("App group feature not found in app id")?; + if !app_group_feature_enabled { + let mut body = plist::Dictionary::new(); + body.insert("APG3427HIY".to_string(), plist::Value::Boolean(true)); + let new_features = match dev_session + .update_app_id(DeveloperDeviceType::Ios, &team, &app_id, &body) + .await + { + Ok(new_feats) => new_feats, + Err(e) => { + return emit_error_and_return( + window, + &format!("Failed to update app ID features: {:?}", e), + ); + } + }; + app_id.features = new_features; + } + } + + let group_identifier = format!("group.{}", main_app_id_str); + + if is_sidestore { + app.bundle.app_info.insert( + "ALTAppGroups".to_string(), + plist::Value::Array(vec![plist::Value::String(group_identifier.clone())]), + ); + } + + let app_groups = match dev_session + .list_application_groups(DeveloperDeviceType::Ios, &team) + .await + { + Ok(groups) => groups, + Err(e) => { + return emit_error_and_return(window, &format!("Failed to list app groups: {:?}", e)); + } + }; + + let matching_app_groups = app_groups + .iter() + .filter(|group| group.identifier == group_identifier.clone()) + .collect::>(); + + let app_group = if matching_app_groups.is_empty() { + match dev_session + .add_application_group( + DeveloperDeviceType::Ios, + &team, + &group_identifier, + &main_app_name, + ) + .await + { + Ok(group) => group, + Err(e) => { + return emit_error_and_return( + window, + &format!("Failed to register app group: {:?}", e), + ); + } + } + } else { + matching_app_groups[0].clone() + }; + + //let mut provisioning_profiles: HashMap = HashMap::new(); + for app_id in app_ids { + let assign_res = dev_session + .assign_application_group_to_app_id( + DeveloperDeviceType::Ios, + &team, + &app_id, + &app_group, + ) + .await; + if assign_res.is_err() { + return emit_error_and_return( + window, + &format!( + "Failed to assign app group to app ID: {:?}", + assign_res.err() + ), + ); + } + // let provisioning_profile = match account + // // This doesn't seem right to me, but it's what Sideloader does... Shouldn't it be downloading the provisioning profile for this app ID, not the main? + // .download_team_provisioning_profile(DeveloperDeviceType::Ios, &team, &main_app_id) + // .await + // { + // Ok(pp /* tee hee */) => pp, + // Err(e) => { + // return emit_error_and_return( + // &window, + // &format!("Failed to download provisioning profile: {:?}", e), + // ); + // } + // }; + // provisioning_profiles.insert(app_id.identifier.clone(), provisioning_profile); + } + + window + .emit("build-output", "Registered app groups".to_string()) + .ok(); + + let provisioning_profile = match dev_session + .download_team_provisioning_profile(DeveloperDeviceType::Ios, &team, &main_app_id) + .await + { + Ok(pp /* tee hee */) => pp, + Err(e) => { + return emit_error_and_return( + window, + &format!("Failed to download provisioning profile: {:?}", e), + ); + } + }; + + let profile_path = handle + .path() + .app_config_dir() + .map_err(|e| e.to_string())? + .join(format!("{}.mobileprovision", main_app_id_str)); + + if profile_path.exists() { + std::fs::remove_file(&profile_path).map_err(|e| e.to_string())?; + } + + let mut file = std::fs::File::create(&profile_path).map_err(|e| e.to_string())?; + file.write_all(&provisioning_profile.encoded_profile) + .map_err(|e| e.to_string())?; + + // Without this, zsign complains it can't find the provision file + #[cfg(target_os = "windows")] + { + file.sync_all().map_err(|e| e.to_string())?; + drop(file); + } + + // TODO: Recursive for sub-bundles? + app.bundle.write_info().map_err(|e| e.to_string())?; + + window + .emit("build-output", "Signining app...".to_string()) + .ok(); + + let zsign_command = handle.shell().sidecar("zsign").unwrap().args([ + "-k", + cert.get_private_key_file_path().to_str().unwrap(), + "-c", + cert.get_certificate_file_path().to_str().unwrap(), + "-m", + profile_path.to_str().unwrap(), + app.bundle.bundle_dir.to_str().unwrap(), + ]); + let (mut rx, mut _child) = zsign_command.spawn().expect("Failed to spawn zsign"); + + let mut signing_failed = false; + while let Some(event) = rx.recv().await { + match event { + CommandEvent::Stdout(line_bytes) | CommandEvent::Stderr(line_bytes) => { + let line = String::from_utf8_lossy(&line_bytes); + window + .emit("build-output", Some(line)) + .expect("failed to emit event"); + } + CommandEvent::Terminated(result) => { + if result.code != Some(0) { + window + .emit("build-output", "App signing failed!".to_string()) + .ok(); + signing_failed = true; + break; + } + window.emit("build-output", "App signed!").ok(); + + window + .emit( + "build-output", + "Installing app (Transfer)... 0%".to_string(), + ) + .ok(); + + let res = install_app(&device, &app.bundle.bundle_dir, |percentage| { + window + .emit("build-output", format!("Installing app... {}%", percentage)) + .expect("failed to emit event"); + }) + .await; + if let Err(e) = res { + window + .emit("build-output", format!("Failed to install app: {:?}", e)) + .ok(); + signing_failed = true; + } + break; + } + _ => {} + } + } + + if signing_failed { + return Err("Signing or installation failed".to_string()); + } + + Ok(()) +}