diff --git a/.gitignore b/.gitignore index ea8c4bf..eb5a316 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -/target +target diff --git a/Cargo.lock b/Cargo.lock index 077a69b..229528c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,23 +37,6 @@ 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" @@ -1017,32 +1000,6 @@ dependencies = [ "cc", ] -[[package]] -name = "icloud_auth" -version = "0.1.0" -dependencies = [ - "aes", - "base64 0.22.1", - "botan", - "cbc", - "hmac", - "num-bigint", - "omnisette", - "pbkdf2 0.11.0", - "pkcs7", - "plist", - "rand 0.9.2", - "reqwest", - "rustls 0.23.31", - "rustls-pemfile 2.2.0", - "serde", - "serde_json", - "sha2", - "srp", - "thiserror", - "tokio", -] - [[package]] name = "icu_collections" version = "2.0.0" @@ -1217,8 +1174,8 @@ version = "0.1.0" dependencies = [ "futures", "hex", - "icloud_auth", "idevice", + "nab138_icloud_auth", "openssl", "plist", "serde", @@ -1384,15 +1341,6 @@ 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" @@ -1408,15 +1356,6 @@ 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" @@ -1449,6 +1388,77 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "nab138_icloud_auth" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64bf4de13886dca6ab302234f3ccc7f5992c86bf0b66addd29b38aa83bd2e6a1" +dependencies = [ + "aes", + "base64 0.22.1", + "botan", + "cbc", + "hmac", + "nab138_omnisette", + "nab138_srp", + "num-bigint", + "pbkdf2 0.11.0", + "pkcs7", + "plist", + "rand", + "reqwest", + "rustls 0.23.31", + "rustls-pemfile 2.2.0", + "serde", + "serde_json", + "sha2", + "thiserror", + "tokio", +] + +[[package]] +name = "nab138_omnisette" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c93555aa1a34bb59460bb2a24ccb43ffc2b78080730d57801132525f810a1a" +dependencies = [ + "anyhow", + "async-trait", + "base64 0.22.1", + "chrono", + "dlopen2", + "futures-util", + "hex", + "libc", + "log", + "objc", + "objc-foundation", + "plist", + "rand", + "remove-async-await", + "reqwest", + "serde", + "serde_json", + "sha2", + "thiserror", + "tokio-tungstenite", + "uuid", +] + +[[package]] +name = "nab138_srp" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "587a7a2ae38ab9a818f42c12b02a7ad5d738006f78f3b53a9f28da91fe13411d" +dependencies = [ + "base64 0.22.1", + "digest", + "generic-array 1.2.0", + "lazy_static", + "num-bigint", + "subtle", +] + [[package]] name = "native-tls" version = "0.2.14" @@ -1548,34 +1558,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "omnisette" -version = "0.1.0" -dependencies = [ - "android-loader", - "anyhow", - "async-trait", - "base64 0.22.1", - "chrono", - "dlopen2", - "futures-util", - "hex", - "libc", - "log", - "objc", - "objc-foundation", - "plist", - "rand 0.9.2", - "remove-async-await", - "reqwest", - "serde", - "serde_json", - "sha2", - "thiserror", - "tokio-tungstenite", - "uuid", -] - [[package]] name = "once_cell" version = "1.21.3" @@ -1811,37 +1793,16 @@ 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_chacha", "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" @@ -1857,9 +1818,6 @@ 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" @@ -1899,18 +1857,6 @@ 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" @@ -2275,18 +2221,6 @@ dependencies = [ "der", ] -[[package]] -name = "srp" -version = "0.6.0" -dependencies = [ - "base64 0.22.1", - "digest", - "generic-array 1.2.0", - "lazy_static", - "num-bigint", - "subtle", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2359,11 +2293,6 @@ dependencies = [ "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" @@ -2556,7 +2485,7 @@ dependencies = [ "http 1.3.1", "httparse", "log", - "rand 0.9.2", + "rand", "rustls 0.23.31", "rustls-pki-types", "sha1", @@ -2619,7 +2548,7 @@ checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ "getrandom 0.3.3", "js-sys", - "rand 0.9.2", + "rand", "uuid-macro-internal", "wasm-bindgen", ] @@ -3127,15 +3056,6 @@ dependencies = [ "spki", ] -[[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" @@ -3160,12 +3080,6 @@ dependencies = [ "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" @@ -3307,13 +3221,14 @@ dependencies = [ [[package]] name = "zsign-rust" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9190123f81d18e157f6f3aab47f19b786538b158417f06ee9348c63163b23fca" +checksum = "7ce7a0eed461e2228b070bd8801e20be6308957204e8193dc87a1b7e51c1b5d3" dependencies = [ "bindgen 0.72.0", "cc", "openssl-sys", + "thiserror", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 324833f..181c1f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,14 +10,14 @@ vendored-botan = ["icloud_auth/vendored-botan"] [dependencies] serde = { version = "1", 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" +plist = { version = "1.7" } +icloud_auth = { version = "0.1.1", package = "nab138_icloud_auth"} +uuid = { version = "1.17.0", features = ["v4"] } +zip = "4.3" +hex = "0.4" sha1 = "0.10" idevice = { version = "0.1.37", features = ["afc", "usbmuxd", "installation_proxy"] } -openssl = "0.10.73" -futures = "0.3.31" +openssl = "0.10" +futures = "0.3" zsign-rust = "0.1" -thiserror = "2.0.12" +thiserror = "2" diff --git a/LICENSE b/LICENSE deleted file mode 100644 index ceaf669..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Nicholas Sharp - -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/README.md b/README.md index de24c63..6c536fa 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,6 @@ A Rust library for sideloading iOS applications. Designed for use in [YCode](htt ### Licensing -This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. +This project is licensed under the MPL-2.0 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. +The `apple-private-apis` package was used for authentication, but the original project was left unfinished. This repository contains a (more) complete implementation of the package. That part of the code is also licensed under the MPL-2.0 License. You can find the original code [here](https://github.com/SideStore/apple-private-apis). diff --git a/apple-private-apis/.gitignore b/apple-private-apis/.gitignore deleted file mode 100644 index 20a7378..0000000 --- a/apple-private-apis/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -/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 deleted file mode 100644 index 8611da1..0000000 --- a/apple-private-apis/Cargo.toml +++ /dev/null @@ -1,5 +0,0 @@ -[workspace] -members = [ - "omnisette", - "icloud-auth" -] diff --git a/apple-private-apis/LICENSE b/apple-private-apis/LICENSE deleted file mode 100644 index a612ad9..0000000 --- a/apple-private-apis/LICENSE +++ /dev/null @@ -1,373 +0,0 @@ -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 deleted file mode 100644 index 55f8980..0000000 --- a/apple-private-apis/icloud-auth/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/target -Cargo.lock -*.py diff --git a/apple-private-apis/icloud-auth/Cargo.toml b/apple-private-apis/icloud-auth/Cargo.toml deleted file mode 100644 index 1c78eb8..0000000 --- a/apple-private-apis/icloud-auth/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "icloud_auth" -version = "0.1.0" -edition = "2021" - -[features] -default = [] -vendored-botan = ["botan/vendored"] - -[dependencies] -serde = { version = "1", features = ["derive"] } -serde_json = { version = "1" } -base64 = "0.22" -srp = { version = "0.6.0", path = "./rustcrypto-srp" } -pbkdf2 = "0.11" -sha2 = "0.10" -rand = "0.9" -rustls = "0.23" -rustls-pemfile = "2.2" -plist = "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.4.1" -reqwest = { version = "0.11.14", features = ["blocking", "json", "default-tls"] } -omnisette = {path = "../omnisette", features = ["remote-anisette-v3"]} -thiserror = "2" -tokio = "1" -botan = "0.12.0" - -[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 deleted file mode 100644 index 8ab2aaa..0000000 --- a/apple-private-apis/icloud-auth/rustcrypto-srp/CHANGELOG.md +++ /dev/null @@ -1,41 +0,0 @@ -# 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 deleted file mode 100644 index 63f7808..0000000 --- a/apple-private-apis/icloud-auth/rustcrypto-srp/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[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 = "1" -digest = "0.10" -lazy_static = "1.2" -subtle = "2.4" -base64 = "0.22" - -[dev-dependencies] -hex-literal = "1" -num-traits = "0.2" -rand = "0.9" -sha1 = "0.10" -sha2 = "0.10" diff --git a/apple-private-apis/icloud-auth/rustcrypto-srp/LICENSE-APACHE b/apple-private-apis/icloud-auth/rustcrypto-srp/LICENSE-APACHE deleted file mode 100644 index 78173fa..0000000 --- a/apple-private-apis/icloud-auth/rustcrypto-srp/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - 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 deleted file mode 100644 index 8dcb85b..0000000 --- a/apple-private-apis/icloud-auth/rustcrypto-srp/LICENSE-MIT +++ /dev/null @@ -1,25 +0,0 @@ -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 deleted file mode 100644 index 3aa7583..0000000 --- a/apple-private-apis/icloud-auth/rustcrypto-srp/README.md +++ /dev/null @@ -1,73 +0,0 @@ -# [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 deleted file mode 100644 index 99eadc8..0000000 --- a/apple-private-apis/icloud-auth/rustcrypto-srp/src/client.rs +++ /dev/null @@ -1,248 +0,0 @@ -//! 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 deleted file mode 100644 index 86bc681..0000000 --- a/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! 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 deleted file mode 100644 index 7ce0aa3..0000000 --- a/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/1024.bin +++ /dev/null @@ -1,3 +0,0 @@ - -֜3 -`rau< 1L%evtt8;H֒PI\`]״aTֶΎi]IU){)ffWh<rl/nQ8vC[/ \ 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 deleted file mode 100644 index c3a5972..0000000 --- a/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/1536.bin +++ /dev/null @@ -1 +0,0 @@ -<9'z*{ۥLaKM_O_Un'QƩK`z)X;CU"|gЁ4ȹy` 㺶=GTűvN?KSݝ>+n94'/=$Ćew.C}lBsJ̷|&J㩾/鸵).Z^G碌$BI#Mv5 \ 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 deleted file mode 100644 index 23207c6..0000000 --- a/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/2048.bin +++ /dev/null @@ -1,2 +0,0 @@ -kA2Jf^X/re1=`Ps)˴큓uwg=#K1 HP9ig`:f)/ Uy^ t -tsYA>(Dkw;ʗ:#v zCldҹF[2wHTE#$}^z'u,/xa`'z毇NsS){*VÂq5؟z5#mR_Tuer֎Js \ 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 deleted file mode 100644 index 7e1a84d..0000000 Binary files a/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/3072.bin and /dev/null 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 deleted file mode 100644 index 82463c0..0000000 Binary files a/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/4096.bin and /dev/null 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 deleted file mode 100644 index 83a559a..0000000 Binary files a/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/6144.bin and /dev/null 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 deleted file mode 100644 index b1f32ac..0000000 Binary files a/apple-private-apis/icloud-auth/rustcrypto-srp/src/groups/8192.bin and /dev/null 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 deleted file mode 100644 index bec6c11..0000000 --- a/apple-private-apis/icloud-auth/rustcrypto-srp/src/lib.rs +++ /dev/null @@ -1,57 +0,0 @@ -#![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 deleted file mode 100644 index 2175571..0000000 --- a/apple-private-apis/icloud-auth/rustcrypto-srp/src/server.rs +++ /dev/null @@ -1,190 +0,0 @@ -//! 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 deleted file mode 100644 index 2310b8c..0000000 --- a/apple-private-apis/icloud-auth/rustcrypto-srp/src/types.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! 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 deleted file mode 100644 index bfdda50..0000000 --- a/apple-private-apis/icloud-auth/rustcrypto-srp/src/utils.rs +++ /dev/null @@ -1,70 +0,0 @@ -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 deleted file mode 100644 index d11601a..0000000 --- a/apple-private-apis/icloud-auth/src/anisette.rs +++ /dev/null @@ -1,104 +0,0 @@ -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(), "11.2 (11B41)".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 deleted file mode 100644 index 8a9ff24..0000000 Binary files a/apple-private-apis/icloud-auth/src/apple_root.der and /dev/null differ diff --git a/apple-private-apis/icloud-auth/src/client.rs b/apple-private-apis/icloud-auth/src/client.rs deleted file mode 100644 index a69141f..0000000 --- a/apple-private-apis/icloud-auth/src/client.rs +++ /dev/null @@ -1,838 +0,0 @@ -use crate::{anisette::AnisetteData, Error}; -use aes::cipher::block_padding::Pkcs7; -use base64::{engine::general_purpose, Engine}; -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()); - } - - 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]; - let ciphertext_and_tag = &encrypted_token[19..]; - - if sk.len() != 32 { - return Err(Error::Parse); - } - if iv.len() != 16 { - return Err(Error::Parse); - } - - 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( - /// || Ok(("test@waffle.me", "password")) - /// || Ok("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< - F: Fn() -> 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 = general_purpose::STANDARD.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 deleted file mode 100644 index 1ec3205..0000000 --- a/apple-private-apis/icloud-auth/src/lib.rs +++ /dev/null @@ -1,26 +0,0 @@ -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 deleted file mode 100644 index aeb3b1c..0000000 --- a/apple-private-apis/icloud-auth/tests/auth_debug.rs +++ /dev/null @@ -1,90 +0,0 @@ -use base64::engine::{general_purpose, Engine}; -use num_bigint::BigUint; -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 = general_purpose::STANDARD.decode("XChHXELsQ+ljxTFbvRMUsGJxiDIlOh9f8e+JzoegmVcOdAXXtPNzkHpAbAgSjyA+vXrTA93+BUu8EJ9+4xZu9g==").unwrap(); - let username = "apple3@f1sh.me"; - let password = "WaffleTest123"; - let salt = general_purpose::STANDARD - .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 = general_purpose::STANDARD - .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: {:?}", - general_purpose::STANDARD.encode(&a_pub_compute.to_bytes_be()) - ); - - let b_pub = general_purpose::STANDARD.decode("HlWxsRmNi/9DCGxYCoqCTfdSvpbx3mrgFLQfOsgf3Rojn7MQQN/g63PwlBghUcVVB4//yAaRRnz/VIByl8thA9AKuVZl8k52PAHKSh4e7TuXSeYCFr0+GYu8/hFdMDl42219uzSuOXuaKGVKq6hxEAf3n3uXXgQRkXWtLFJ5nn1wq/emf46hYAHzc/pYyvckAdh9WDCw95IXbzKD8LcPw/0ZQoydMuXgW2ZKZ52fiyEs94IZ7L5RLL7jY1nVdwtsp2fxeqiZ3DNmVZ2GdNrbJGT//160tyd2evtUtehr8ygXNzjWdjV0cc4+1F38ywSPFyieVzVTYzDywRllgo3A5A==").unwrap(); - println!( - "fixed b_pub: {:?}", - general_purpose::STANDARD.encode(&b_pub) - ); - println!(""); - - println!( - "salt: {:?} iterations: {:?}", - general_purpose::STANDARD.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 deleted file mode 100644 index ae4336d..0000000 --- a/apple-private-apis/icloud-auth/tests/gsa_auth.rs +++ /dev/null @@ -1,46 +0,0 @@ -#[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 deleted file mode 100644 index 8edb7a3..0000000 --- a/apple-private-apis/icloud-auth/tests/root_write.rs +++ /dev/null @@ -1,73 +0,0 @@ -#[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 deleted file mode 100644 index d0fe13f..0000000 --- a/apple-private-apis/omnisette/Cargo.toml +++ /dev/null @@ -1,40 +0,0 @@ -[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.22" -hex = "0.4" -plist = "1.4" -reqwest = { version = "0.11", default-features = false, features = ["blocking", "json", "rustls-tls", "gzip"] } -rand = "0.9" -sha2 = "0.10" -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.115", optional = true } -tokio-tungstenite = { version = "0.27.0", optional = true, features = ["rustls-tls-webpki-roots"] } -futures-util = { version = "0.3.28", optional = true } -chrono = { version = "0.4.37", optional = true } -thiserror = "2" -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 deleted file mode 100644 index ccaa3b9..0000000 --- a/apple-private-apis/omnisette/src/adi_proxy.rs +++ /dev/null @@ -1,383 +0,0 @@ -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::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 deleted file mode 100644 index 903203e..0000000 --- a/apple-private-apis/omnisette/src/anisette_headers_provider.rs +++ /dev/null @@ -1,31 +0,0 @@ - -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 deleted file mode 100644 index 5e53787..0000000 --- a/apple-private-apis/omnisette/src/aos_kit.rs +++ /dev/null @@ -1,124 +0,0 @@ -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 deleted file mode 100644 index bfd2681..0000000 --- a/apple-private-apis/omnisette/src/lib.rs +++ /dev/null @@ -1,226 +0,0 @@ -//! 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 std::io; -use std::path::PathBuf; -use adi_proxy::ADIError; -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")] - 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 deleted file mode 100644 index b061bd9..0000000 --- a/apple-private-apis/omnisette/src/remote_anisette.rs +++ /dev/null @@ -1,47 +0,0 @@ -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 deleted file mode 100644 index 5f30bfc..0000000 --- a/apple-private-apis/omnisette/src/remote_anisette_v3.rs +++ /dev/null @@ -1,543 +0,0 @@ -// 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::rng().random::<[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", "UTC") - .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)?.into())) - .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)?.into())) - .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)?.into(), - )) - .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 deleted file mode 100644 index b81d223..0000000 --- a/apple-private-apis/omnisette/src/store_services_core.rs +++ /dev/null @@ -1,450 +0,0 @@ -#[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::rng().random() -} - -#[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::AnisetteError; - use crate::{AnisetteConfiguration, AnisetteHeaders}; - use log::info; - use std::path::PathBuf; - - #[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 deleted file mode 100644 index 840a039..0000000 --- a/apple-private-apis/omnisette/src/store_services_core/posix_macos.rs +++ /dev/null @@ -1,102 +0,0 @@ -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 deleted file mode 100644 index 486e2b5..0000000 --- a/apple-private-apis/omnisette/src/store_services_core/posix_windows.rs +++ /dev/null @@ -1,268 +0,0 @@ -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 index ade2948..a3d5659 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,5 +1,6 @@ // This file was made using https://github.com/Dadoum/Sideloader as a reference. +use crate::Error; use crate::bundle::Bundle; use std::fs::File; use std::path::PathBuf; @@ -11,9 +12,11 @@ pub struct Application { } impl Application { - pub fn new(path: PathBuf) -> Self { + pub fn new(path: PathBuf) -> Result { if !path.exists() { - panic!("Application path does not exist: {}", path.display()); + return Err(Error::InvalidBundle( + "Application path does not exist".to_string(), + )); } let mut bundle_path = path.clone(); @@ -23,21 +26,24 @@ impl Application { 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::remove_dir_all(&temp_path).map_err(|e| Error::Filesystem(e))?; } - std::fs::create_dir_all(&temp_path).expect("Failed to create temporary directory"); + std::fs::create_dir_all(&temp_path).map_err(|e| Error::Filesystem(e))?; - 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 file = File::open(&path).map_err(|e| Error::Filesystem(e))?; + let mut archive = ZipArchive::new(file).map_err(|e| { + Error::Generic(format!("Failed to open application archive: {}", e)) + })?; + archive.extract(&temp_path).map_err(|e| { + Error::Generic(format!("Failed to extract application archive: {}", e)) + })?; 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") + .map_err(|e| { + Error::Generic(format!("Failed to read Payload directory: {}", e)) + })? .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")) @@ -45,18 +51,24 @@ impl Application { if app_dirs.len() == 1 { bundle_path = app_dirs[0].path(); } else if app_dirs.is_empty() { - panic!("No .app directory found in Payload"); + return Err(Error::InvalidBundle( + "No .app directory found in Payload".to_string(), + )); } else { - panic!("Multiple .app directories found in Payload"); + return Err(Error::InvalidBundle( + "Multiple .app directories found in Payload".to_string(), + )); } } else { - panic!("No Payload directory found in the application archive"); + return Err(Error::InvalidBundle( + "No Payload directory found in the application archive".to_string(), + )); } } - let bundle = Bundle::new(bundle_path).expect("Failed to create application bundle"); + let bundle = Bundle::new(bundle_path)?; - Application { + Ok(Application { bundle, /*temp_path*/ - } + }) } } diff --git a/src/certificate.rs b/src/certificate.rs index dd4b449..bf279d6 100644 --- a/src/certificate.rs +++ b/src/certificate.rs @@ -8,7 +8,10 @@ use openssl::{ x509::{X509, X509Name, X509ReqBuilder}, }; use sha1::{Digest, Sha1}; -use std::{fs, path::PathBuf}; +use std::{ + fs, + path::{Path, PathBuf}, +}; use crate::Error; use crate::developer_session::{DeveloperDeviceType, DeveloperSession, DeveloperTeam}; @@ -23,7 +26,7 @@ pub struct CertificateIdentity { impl CertificateIdentity { pub async fn new( - configuration_path: PathBuf, + configuration_path: &Path, dev_session: &DeveloperSession, apple_id: String, ) -> Result { @@ -31,8 +34,7 @@ impl CertificateIdentity { 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| Error::Filesystem(format!("Failed to create key directory: {}", e)))?; + fs::create_dir_all(&key_path).map_err(|e| Error::Filesystem(e))?; let key_file = key_path.join("key.pem"); let cert_file = key_path.join("cert.pem"); @@ -53,8 +55,7 @@ impl CertificateIdentity { let pem_data = key .private_key_to_pem_pkcs8() .map_err(|e| Error::Certificate(format!("Failed to encode private key: {}", e)))?; - fs::write(&key_file, pem_data) - .map_err(|e| Error::Filesystem(format!("Failed to save key file: {}", e)))?; + fs::write(&key_file, pem_data).map_err(|e| Error::Filesystem(e))?; key }; @@ -74,9 +75,7 @@ impl CertificateIdentity { let cert_pem = cert.to_pem().map_err(|e| { Error::Certificate(format!("Failed to encode certificate to PEM: {}", e)) })?; - fs::write(&cert_identity.cert_file, cert_pem).map_err(|e| { - Error::Filesystem(format!("Failed to save certificate file: {}", e)) - })?; + fs::write(&cert_identity.cert_file, cert_pem).map_err(|e| Error::Filesystem(e))?; return Ok(cert_identity); } @@ -199,19 +198,18 @@ impl CertificateIdentity { let cert_pem = certificate.to_pem().map_err(|e| { Error::Certificate(format!("Failed to encode certificate to PEM: {}", e)) })?; - fs::write(&self.cert_file, cert_pem) - .map_err(|e| Error::Filesystem(format!("Failed to save certificate file: {}", e)))?; + fs::write(&self.cert_file, cert_pem).map_err(|e| Error::Filesystem(e))?; self.certificate = Some(certificate); Ok(()) } - pub fn get_certificate_file_path(&self) -> &PathBuf { + pub fn get_certificate_file_path(&self) -> &Path { &self.cert_file } - pub fn get_private_key_file_path(&self) -> &PathBuf { + pub fn get_private_key_file_path(&self) -> &Path { &self.key_file } } diff --git a/src/device.rs b/src/device.rs index 923a102..68a055b 100644 --- a/src/device.rs +++ b/src/device.rs @@ -6,9 +6,10 @@ use idevice::{ usbmuxd::{UsbmuxdAddr, UsbmuxdConnection}, }; use serde::{Deserialize, Serialize}; -use std::future::Future; -use std::path::PathBuf; use std::pin::Pin; +use std::{future::Future, path::Path}; + +use crate::Error; #[derive(Deserialize, Serialize, Clone)] pub struct DeviceInfo { @@ -69,34 +70,32 @@ pub async fn list_devices() -> Result, String> { pub async fn install_app( device: &DeviceInfo, - app_path: &PathBuf, + app_path: &Path, callback: impl Fn(u64) -> (), -) -> Result<(), String> { +) -> Result<(), Error> { let mut usbmuxd = UsbmuxdConnection::default() .await - .map_err(|e| format!("Failed to connect to usbmuxd: {:?}", e))?; + .map_err(|e| Error::IdeviceError(e))?; let device = usbmuxd .get_device(&device.uuid) .await - .map_err(|e| format!("Failed to get device: {:?}", e))?; + .map_err(|e| Error::IdeviceError(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))?; + .map_err(|e| Error::IdeviceError(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))?; + afc_upload_dir(&mut afc_client, app_path, &dir).await?; let mut instproxy_client = InstallationProxyClient::connect(&provider) .await - .map_err(|e| format!("Failed to connect to installation proxy: {:?}", e))?; + .map_err(|e| Error::IdeviceError(e))?; let mut options = plist::Dictionary::new(); options.insert("PackageType".to_string(), "Developer".into()); @@ -110,25 +109,24 @@ pub async fn install_app( (), ) .await - .map_err(|e| format!("Failed to install app: {:?}", e))?; + .map_err(|e| Error::IdeviceError(e))?; Ok(()) } fn afc_upload_dir<'a>( afc_client: &'a mut AfcClient, - path: &'a PathBuf, + path: &'a Path, afc_path: &'a str, -) -> Pin> + Send + 'a>> { +) -> Pin> + Send + 'a>> { Box::pin(async move { - let entries = - std::fs::read_dir(path).map_err(|e| format!("Failed to read directory: {}", e))?; + let entries = std::fs::read_dir(path).map_err(|e| Error::Filesystem(e))?; afc_client .mk_dir(afc_path) .await - .map_err(|e| format!("Failed to create directory: {}", e))?; + .map_err(|e| Error::IdeviceError(e))?; for entry in entries { - let entry = entry.map_err(|e| format!("Failed to read directory entry: {}", e))?; + let entry = entry.map_err(|e| Error::Filesystem(e))?; let path = entry.path(); if path.is_dir() { let new_afc_path = format!( @@ -148,13 +146,12 @@ fn afc_upload_dir<'a>( 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))?; + .map_err(|e| Error::IdeviceError(e))?; + let bytes = std::fs::read(&path).map_err(|e| Error::Filesystem(e))?; file_handle .write(&bytes) .await - .map_err(|e| format!("Failed to write file: {}", e))?; + .map_err(|e| Error::IdeviceError(e))?; } } Ok(()) diff --git a/src/lib.rs b/src/lib.rs index 93c8176..7fcc8d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,14 +5,19 @@ pub mod developer_session; pub mod device; pub mod sideload; +use std::io::Error as IOError; + pub use developer_session::{ AppId, ApplicationGroup, DeveloperDevice, DeveloperDeviceType, DeveloperSession, DeveloperTeam, DevelopmentCertificate, ListAppIdsResponse, ProvisioningProfile, }; +pub use icloud_auth::{AnisetteConfiguration, AppleAccount}; +use idevice::IdeviceError; use thiserror::Error as ThisError; +use zsign_rust::ZSignError; -#[derive(Debug, Clone, ThisError)] +#[derive(Debug, ThisError)] pub enum Error { #[error("Authentication error {0}: {1}")] Auth(i64, String), @@ -26,23 +31,27 @@ pub enum Error { InvalidBundle(String), #[error("Certificate error: {0}")] Certificate(String), - #[error("Failed to use files: {0}")] - Filesystem(String), + #[error(transparent)] + Filesystem(#[from] IOError), + #[error(transparent)] + IdeviceError(#[from] IdeviceError), + #[error(transparent)] + ZSignError(#[from] ZSignError), } pub trait SideloadLogger { - async fn log(&self, message: &str); - async fn error(&self, error: &Error); + fn log(&self, message: &str); + fn error(&self, error: &Error); } pub struct DefaultLogger; impl SideloadLogger for DefaultLogger { - async fn log(&self, message: &str) { + fn log(&self, message: &str) { println!("{message}"); } - async fn error(&self, error: &Error) { + fn error(&self, error: &Error) { eprintln!("Error: {}", error); } } diff --git a/src/sideload.rs b/src/sideload.rs index 2a6af2c..64c9f50 100644 --- a/src/sideload.rs +++ b/src/sideload.rs @@ -3,7 +3,7 @@ use zsign_rust::ZSignOptions; use crate::application::Application; -use crate::{Error, SideloadLogger}; +use crate::{DeveloperTeam, Error, SideloadLogger}; use crate::{ certificate::CertificateIdentity, developer_session::{DeveloperDeviceType, DeveloperSession}, @@ -16,11 +16,20 @@ fn error_and_return(logger: &impl SideloadLogger, error: Error) -> Result<(), Er Err(error) } +/// Sideloads an `.ipa` or `.app` onto a device. +/// +/// # Arguments +/// - `logger` — Reports progress and errors. +/// - `dev_session` — Authenticated Apple developer session ([`crate::developer_session::DeveloperSession`]). +/// - `device` — Target device information ([`crate::device::DeviceInfo`]). +/// - `app_path` — Path to the `.ipa` file or `.app` bundle to sign and install +/// - `store_dir` — Directory used to store intermediate artifacts (profiles, certs, etc.). This directory will not be cleared at the end. pub async fn sideload_app( logger: impl SideloadLogger, dev_session: &DeveloperSession, device: &DeviceInfo, app_path: PathBuf, + store_dir: PathBuf, ) -> Result<(), Error> { if device.uuid.is_empty() { return error_and_return(&logger, Error::Generic("No device selected".to_string())); @@ -35,10 +44,15 @@ pub async fn sideload_app( logger.log("Successfully retrieved team"); - ensure_device_registered(&dev_session, window, &team, &device).await?; + ensure_device_registered(&logger, dev_session, &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 { + let cert = match CertificateIdentity::new( + &store_dir, + &dev_session, + dev_session.account.apple_id.clone(), + ) + .await + { Ok(c) => c, Err(e) => { return error_and_return(&logger, e); @@ -57,7 +71,7 @@ pub async fn sideload_app( } }; - let mut app = Application::new(app_path); + let mut app = 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(), @@ -287,20 +301,15 @@ pub async fn sideload_app( } }; - let profile_path = handle - .path() - .app_config_dir() - .map_err(|e| e.to_string())? - .join(format!("{}.mobileprovision", main_app_id_str)); + let profile_path = store_dir.join(format!("{}.mobileprovision", main_app_id_str)); if profile_path.exists() { - std::fs::remove_file(&profile_path).map_err(|e| e.to_string())?; + std::fs::remove_file(&profile_path).map_err(|e| Error::Filesystem(e))?; } - let mut file = - std::fs::File::create(&profile_path).map_err(|e| Error::Filesystem(e.to_string()))?; + let mut file = std::fs::File::create(&profile_path).map_err(|e| Error::Filesystem(e))?; file.write_all(&provisioning_profile.encoded_profile) - .map_err(|e| Error::Filesystem(e.to_string()))?; + .map_err(|e| Error::Filesystem(e))?; // Without this, zsign complains it can't find the provision file #[cfg(target_os = "windows")] @@ -310,7 +319,7 @@ pub async fn sideload_app( } // TODO: Recursive for sub-bundles? - app.bundle.write_info().map_err(|e| e.to_string())?; + app.bundle.write_info()?; match ZSignOptions::new(app.bundle.bundle_dir.to_str().unwrap()) .with_cert_file(cert.get_certificate_file_path().to_str().unwrap()) @@ -320,7 +329,7 @@ pub async fn sideload_app( { Ok(_) => {} Err(e) => { - return error_and_return(&logger, &format!("Failed to sign app: {:?}", e)); + return error_and_return(&logger, Error::ZSignError(e)); } }; @@ -329,12 +338,37 @@ pub async fn sideload_app( logger.log("Installing app (Transfer)... 0%"); let res = install_app(&device, &app.bundle.bundle_dir, |percentage| { - logger.log(format!("Installing app... {}%", percentage)); + logger.log(&format!("Installing app... {}%", percentage)); }) .await; if let Err(e) = res { - return error_and_return(&logger, &format!("Failed to install app: {:?}", e)); + return error_and_return(&logger, e); } Ok(()) } + +pub async fn ensure_device_registered( + logger: &impl SideloadLogger, + dev_session: &DeveloperSession, + team: &DeveloperTeam, + device: &DeviceInfo, +) -> Result<(), Error> { + let devices = dev_session + .list_devices(DeveloperDeviceType::Ios, team) + .await; + if let Err(e) = devices { + return error_and_return(logger, e); + } + let devices = devices.unwrap(); + if !devices.iter().any(|d| d.device_number == device.uuid) { + logger.log("Device not found in your account"); + // TODO: Actually test! + dev_session + .add_device(DeveloperDeviceType::Ios, team, &device.name, &device.uuid) + .await?; + logger.log("Successfully added device to your account"); + } + logger.log("Device is a development device"); + Ok(()) +}