mirror of
https://github.com/nab138/isideload.git
synced 2026-03-02 14:36:16 +01:00
first commit
This commit is contained in:
3
apple-private-apis/icloud-auth/.gitignore
vendored
Normal file
3
apple-private-apis/icloud-auth/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
*.py
|
||||
32
apple-private-apis/icloud-auth/Cargo.toml
Normal file
32
apple-private-apis/icloud-auth/Cargo.toml
Normal file
@@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "icloud_auth"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = { version = "1.0.142" }
|
||||
base64 = "0.13.1"
|
||||
srp = { version = "0.6.0", path = "./rustcrypto-srp" }
|
||||
pbkdf2 = { version = "0.11.0" }
|
||||
sha2 = { version = "0.10.6" }
|
||||
rand = { version = "0.8.5" }
|
||||
rustls = { version = "0.20.7" }
|
||||
rustls-pemfile = { version = "1.0.1" }
|
||||
plist = { version = "1.7.2" }
|
||||
hmac = "0.12.1"
|
||||
num-bigint = "0.4.3"
|
||||
cbc = { version = "0.1.2", features = ["std"] }
|
||||
aes = "0.8.2"
|
||||
pkcs7 = "0.3.0"
|
||||
reqwest = { version = "0.11.14", features = ["blocking", "json", "default-tls"] }
|
||||
omnisette = {path = "../omnisette", features = ["remote-anisette-v3"]}
|
||||
thiserror = "1.0.58"
|
||||
tokio = "1"
|
||||
botan = { version = "0.11.1", features = ["vendored"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1", features = ["rt", "macros"] }
|
||||
41
apple-private-apis/icloud-auth/rustcrypto-srp/CHANGELOG.md
Normal file
41
apple-private-apis/icloud-auth/rustcrypto-srp/CHANGELOG.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## 0.6.0 (2022-01-22)
|
||||
### Changed
|
||||
- Use `modpow` for constant time modular exponentiation ([#78])
|
||||
- Rebuild library ([#79])
|
||||
|
||||
[#78]: https://github.com/RustCrypto/PAKEs/pull/78
|
||||
[#79]: https://github.com/RustCrypto/PAKEs/pull/79
|
||||
|
||||
## 0.5.0 (2020-10-07)
|
||||
|
||||
## 0.4.3 (2019-11-07)
|
||||
|
||||
## 0.4.2 (2019-11-06)
|
||||
|
||||
## 0.4.1 (2019-11-07)
|
||||
|
||||
## 0.4.0 (2018-12-20)
|
||||
|
||||
## 0.3.0 (2018-10-22)
|
||||
|
||||
## 0.2.5 (2018-04-14)
|
||||
|
||||
## 0.2.4 (2017-11-01)
|
||||
|
||||
## 0.2.3 (2017-08-17)
|
||||
|
||||
## 0.2.2 (2017-08-14)
|
||||
|
||||
## 0.2.1 (2017-08-14)
|
||||
|
||||
## 0.2.0 (2017-08-14)
|
||||
|
||||
## 0.1.1 (2017-08-13)
|
||||
|
||||
## 0.1.0 (2017-08-13)
|
||||
28
apple-private-apis/icloud-auth/rustcrypto-srp/Cargo.toml
Normal file
28
apple-private-apis/icloud-auth/rustcrypto-srp/Cargo.toml
Normal file
@@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "srp"
|
||||
version = "0.6.0"
|
||||
authors = ["RustCrypto Developers"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Secure Remote Password (SRP) protocol implementation"
|
||||
documentation = "https://docs.rs/srp"
|
||||
repository = "https://github.com/RustCrypto/PAKEs"
|
||||
keywords = ["crypto", "pake", "authentication"]
|
||||
categories = ["cryptography", "authentication"]
|
||||
readme = "README.md"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
|
||||
[dependencies]
|
||||
num-bigint = "0.4"
|
||||
generic-array = "0.14"
|
||||
digest = "0.10"
|
||||
lazy_static = "1.2"
|
||||
subtle = "2.4"
|
||||
base64 = "0.21.0"
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = "0.3"
|
||||
num-traits = "0.2"
|
||||
rand = "0.8"
|
||||
sha1 = "0.10.6"
|
||||
sha2 = "0.10.8"
|
||||
201
apple-private-apis/icloud-auth/rustcrypto-srp/LICENSE-APACHE
Normal file
201
apple-private-apis/icloud-auth/rustcrypto-srp/LICENSE-APACHE
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
25
apple-private-apis/icloud-auth/rustcrypto-srp/LICENSE-MIT
Normal file
25
apple-private-apis/icloud-auth/rustcrypto-srp/LICENSE-MIT
Normal file
@@ -0,0 +1,25 @@
|
||||
Copyright (c) 2017 Artyom Pavlov
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
73
apple-private-apis/icloud-auth/rustcrypto-srp/README.md
Normal file
73
apple-private-apis/icloud-auth/rustcrypto-srp/README.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# [RustCrypto]: SRP
|
||||
|
||||
[![crate][crate-image]][crate-link]
|
||||
[![Docs][docs-image]][docs-link]
|
||||
![Apache2/MIT licensed][license-image]
|
||||
![Rust Version][rustc-image]
|
||||
[![Project Chat][chat-image]][chat-link]
|
||||
[![Build Status][build-image]][build-link]
|
||||
|
||||
Pure Rust implementation of the [Secure Remote Password] password-authenticated
|
||||
key-exchange algorithm.
|
||||
|
||||
[Documentation][docs-link]
|
||||
|
||||
## About
|
||||
|
||||
This implementation is generic over hash functions using the [`Digest`] trait,
|
||||
so you will need to choose a hash function, e.g. `Sha256` from [`sha2`] crate.
|
||||
|
||||
Additionally this crate allows to use a specialized password hashing
|
||||
algorithm for private key computation instead of method described in the
|
||||
SRP literature.
|
||||
|
||||
Compatibility with other implementations has not yet been tested.
|
||||
|
||||
## ⚠️ Security Warning
|
||||
|
||||
This crate has never received an independent third party audit for security and
|
||||
correctness.
|
||||
|
||||
USE AT YOUR OWN RISK!
|
||||
|
||||
## Minimum Supported Rust Version
|
||||
|
||||
Rust **1.56** or higher.
|
||||
|
||||
Minimum supported Rust version can be changed in the future, but it will be
|
||||
done with a minor version bump.
|
||||
|
||||
## License
|
||||
|
||||
Licensed under either of:
|
||||
|
||||
* [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
* [MIT license](http://opensource.org/licenses/MIT)
|
||||
|
||||
at your option.
|
||||
|
||||
### Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
|
||||
dual licensed as above, without any additional terms or conditions.
|
||||
|
||||
[//]: # (badges)
|
||||
|
||||
[crate-image]: https://img.shields.io/crates/v/srp.svg
|
||||
[crate-link]: https://crates.io/crates/srp
|
||||
[docs-image]: https://docs.rs/srp/badge.svg
|
||||
[docs-link]: https://docs.rs/srp/
|
||||
[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg
|
||||
[rustc-image]: https://img.shields.io/badge/rustc-1.56+-blue.svg
|
||||
[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg
|
||||
[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260045-PAKEs
|
||||
[build-image]: https://github.com/RustCrypto/PAKEs/actions/workflows/srp.yml/badge.svg
|
||||
[build-link]: https://github.com/RustCrypto/PAKEs/actions/workflows/srp.yml
|
||||
|
||||
[//]: # (general links)
|
||||
|
||||
[RustCrypto]: https://github.com/RustCrypto
|
||||
[Secure Remote Password]: https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol
|
||||
[`Digest`]: https://docs.rs/digest
|
||||
[`sha2`]: https://crates.io/crates/sha2
|
||||
248
apple-private-apis/icloud-auth/rustcrypto-srp/src/client.rs
Normal file
248
apple-private-apis/icloud-auth/rustcrypto-srp/src/client.rs
Normal file
@@ -0,0 +1,248 @@
|
||||
//! SRP client implementation.
|
||||
//!
|
||||
//! # Usage
|
||||
//! First create SRP client struct by passing to it SRP parameters (shared
|
||||
//! between client and server).
|
||||
//!
|
||||
//! You can use SHA1 from SRP-6a, but it's highly recommended to use specialized
|
||||
//! password hashing algorithm instead (e.g. PBKDF2, argon2 or scrypt).
|
||||
//!
|
||||
//! ```rust
|
||||
//! use crate::srp::groups::G_2048;
|
||||
//! use sha2::Sha256; // Note: You should probably use a proper password KDF
|
||||
//! # use crate::srp::client::SrpClient;
|
||||
//!
|
||||
//! let client = SrpClient::<Sha256>::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::<sha2::Sha256>::new(&crate::srp::groups::G_2048);
|
||||
//! # fn server_response()-> (Vec<u8>, Vec<u8>) { (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::<sha2::Sha256>::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::<sha2::Sha256>::new(&crate::srp::groups::G_2048);
|
||||
//! # let verifier = client.process_reply(b"", b"", b"", b"", b"1").unwrap();
|
||||
//! # fn send_proof(_: &[u8]) -> Vec<u8> { 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::<sha2::Sha256>::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::<sha2::Sha256>::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<D>,
|
||||
}
|
||||
|
||||
/// SRP client state after handshake with the server.
|
||||
pub struct SrpClientVerifier<D: Digest> {
|
||||
m1: Output<D>,
|
||||
m2: Output<D>,
|
||||
key: Vec<u8>,
|
||||
}
|
||||
|
||||
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(<username> | ":" | <raw password>)
|
||||
pub fn compute_identity_hash(username: &[u8], password: &[u8]) -> Output<D> {
|
||||
let mut d = D::new();
|
||||
d.update(username);
|
||||
d.update(b":");
|
||||
d.update(password);
|
||||
d.finalize()
|
||||
}
|
||||
|
||||
// x = H(<salt> | H(<username> | ":" | <raw password>))
|
||||
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<u8> {
|
||||
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<u8> {
|
||||
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<SrpClientVerifier<D>, 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::<D>(&a_pub.to_bytes_be(), &b_pub.to_bytes_be());
|
||||
let k = compute_k::<D>(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::<D>(
|
||||
&a_pub.to_bytes_be(),
|
||||
&b_pub.to_bytes_be(),
|
||||
&key,
|
||||
username,
|
||||
salt,
|
||||
self.params,
|
||||
);
|
||||
|
||||
let m2 = compute_m2::<D>(&a_pub.to_bytes_be(), &m1, &key);
|
||||
|
||||
Ok(SrpClientVerifier {
|
||||
m1,
|
||||
m2,
|
||||
key: key.to_vec(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Digest> SrpClientVerifier<D> {
|
||||
/// 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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
57
apple-private-apis/icloud-auth/rustcrypto-srp/src/groups.rs
Normal file
57
apple-private-apis/icloud-auth/rustcrypto-srp/src/groups.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
//! Groups from [RFC 5054](https://tools.ietf.org/html/rfc5054)
|
||||
//!
|
||||
//! It is strongly recommended to use them instead of custom generated
|
||||
//! groups. Additionally it is not recommended to use `G_1024` and `G_1536`,
|
||||
//! they are provided only for compatibility with the legacy software.
|
||||
use crate::types::SrpGroup;
|
||||
use lazy_static::lazy_static;
|
||||
use num_bigint::BigUint;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref G_1024: SrpGroup = SrpGroup {
|
||||
n: BigUint::from_bytes_be(include_bytes!("groups/1024.bin")),
|
||||
g: BigUint::from_bytes_be(&[2]),
|
||||
};
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref G_1536: SrpGroup = SrpGroup {
|
||||
n: BigUint::from_bytes_be(include_bytes!("groups/1536.bin")),
|
||||
g: BigUint::from_bytes_be(&[2]),
|
||||
};
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref G_2048: SrpGroup = SrpGroup {
|
||||
n: BigUint::from_bytes_be(include_bytes!("groups/2048.bin")),
|
||||
g: BigUint::from_bytes_be(&[2]),
|
||||
};
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref G_3072: SrpGroup = SrpGroup {
|
||||
n: BigUint::from_bytes_be(include_bytes!("groups/3072.bin")),
|
||||
g: BigUint::from_bytes_be(&[5]),
|
||||
};
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref G_4096: SrpGroup = SrpGroup {
|
||||
n: BigUint::from_bytes_be(include_bytes!("groups/4096.bin")),
|
||||
g: BigUint::from_bytes_be(&[5]),
|
||||
};
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref G_6144: SrpGroup = SrpGroup {
|
||||
n: BigUint::from_bytes_be(include_bytes!("groups/6144.bin")),
|
||||
g: BigUint::from_bytes_be(&[5]),
|
||||
};
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref G_8192: SrpGroup = SrpGroup {
|
||||
n: BigUint::from_bytes_be(include_bytes!("groups/8192.bin")),
|
||||
g: BigUint::from_bytes_be(&[19]),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
î¯
|
||||
¹³<EFBFBD>Öœ3ø
|
||||
ú<EFBFBD>Åè`ra‡uÿ<ž¢1Lœ%evÖtßt–ê<E28093>Ó8;HÖ’ÆààÕØâP¹‹äŽI\`‰ÚÑ]Ç×´aTÖ¶ÎŽôi±]I‚U›){Ï…Å)õffWìhí¼<rlÀ/ÔËô—nªšýQ8þƒvC[ŸÆ/Àëã
|
||||
@@ -0,0 +1 @@
|
||||
ťď<Żą9'z±ń*†¤{»ŰĄô™¬L€ľî©aKĚM_O_Un'ËŢQĆ©Kä`z)X<>; ĐřC€¶U»š"čÜߊ|ěgđĐ<C491>4±Čąy‰›`žăş¶=GT<47><54>ŰűüvN?KSÝťˇ‹ý>+śŚőnß•94–'Ű/Ő=$·Ä†ew.C}lŚäBsJ÷Ě·®<C2B7>|&J㩾¸Š/鸵).Z˙^‘GžŚç˘Ś$BĆó“Iš#MĎvăţŃ5ů»
|
||||
@@ -0,0 +1,2 @@
|
||||
¬kÛA2Jš›ñfÞ^‰X/¯r¶e‡îü1’”=µ`P£s)Ë´ ™í<E284A2>“àuwg¡=Õ#«K1
|
||||
ÍH©ÚýPè9ií·g°Ï`•š:³fûÕúªè)©–/“¸Uùy“ì—^ê¨
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
57
apple-private-apis/icloud-auth/rustcrypto-srp/src/lib.rs
Normal file
57
apple-private-apis/icloud-auth/rustcrypto-srp/src/lib.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
#![allow(clippy::many_single_char_names)]
|
||||
#![doc(html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo_small.png")]
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
//! # Usage
|
||||
//! Add `srp` dependency to your `Cargo.toml`:
|
||||
//!
|
||||
//! ```toml
|
||||
//! [dependencies]
|
||||
//! srp = "0.6"
|
||||
//! ```
|
||||
//!
|
||||
//! Next read documentation for [`client`](client/index.html) and
|
||||
//! [`server`](server/index.html) modules.
|
||||
//!
|
||||
//! # Algorithm description
|
||||
//! Here we briefly describe implemented algorithm. For additional information
|
||||
//! refer to SRP literature. All arithmetic is done modulo `N`, where `N` is a
|
||||
//! large safe prime (`N = 2q+1`, where `q` is prime). Additionally `g` MUST be
|
||||
//! a generator modulo `N`. It's STRONGLY recommended to use SRP parameters
|
||||
//! provided by this crate in the [`groups`](groups/index.html) module.
|
||||
//!
|
||||
//! | Client | Data transfer | Server |
|
||||
//! |------------------------|-------------------|---------------------------------|
|
||||
//! |`a_pub = g^a` | — `a_pub`, `I` —> | (lookup `s`, `v` for given `I`) |
|
||||
//! |`x = PH(P, s)` | <— `b_pub`, `s` — | `b_pub = k*v + g^b` |
|
||||
//! |`u = H(a_pub ‖ b_pub)` | | `u = H(a_pub ‖ b_pub)` |
|
||||
//! |`s = (b_pub - k*g^x)^(a+u*x)` | | `S = (b_pub - k*g^x)^(a+u*x)` |
|
||||
//! |`K = H(s)` | | `K = H(s)` |
|
||||
//! |`M1 = H(A ‖ B ‖ K)` | — `M1` —> | (verify `M1`) |
|
||||
//! |(verify `M2`) | <— `M2` — | `M2 = H(A ‖ M1 ‖ K)` |
|
||||
//!
|
||||
//! Variables and notations have the following meaning:
|
||||
//!
|
||||
//! - `I` — user identity (username)
|
||||
//! - `P` — user password
|
||||
//! - `H` — one-way hash function
|
||||
//! - `PH` — password hashing algroithm, in the RFC 5054 described as
|
||||
//! `H(s ‖ H(I ‖ ":" ‖ P))`
|
||||
//! - `^` — (modular) exponentiation
|
||||
//! - `‖` — concatenation
|
||||
//! - `x` — user private key
|
||||
//! - `s` — salt generated by user and stored on the server
|
||||
//! - `v` — password verifier equal to `g^x` and stored on the server
|
||||
//! - `a`, `b` — secret ephemeral values (at least 256 bits in length)
|
||||
//! - `A`, `B` — Public ephemeral values
|
||||
//! - `u` — scrambling parameter
|
||||
//! - `k` — multiplier parameter (`k = H(N || g)` in SRP-6a)
|
||||
//!
|
||||
//! [1]: https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol
|
||||
//! [2]: https://tools.ietf.org/html/rfc5054
|
||||
|
||||
pub mod client;
|
||||
pub mod groups;
|
||||
pub mod server;
|
||||
pub mod types;
|
||||
pub mod utils;
|
||||
190
apple-private-apis/icloud-auth/rustcrypto-srp/src/server.rs
Normal file
190
apple-private-apis/icloud-auth/rustcrypto-srp/src/server.rs
Normal file
@@ -0,0 +1,190 @@
|
||||
//! SRP server implementation
|
||||
//!
|
||||
//! # Usage
|
||||
//! First receive user's username and public value `a_pub`, retrieve from a
|
||||
//! database the salt and verifier for a given username. Generate `b` and public value `b_pub`.
|
||||
//!
|
||||
//!
|
||||
//! ```rust
|
||||
//! use crate::srp::groups::G_2048;
|
||||
//! use sha2::Sha256; // Note: You should probably use a proper password KDF
|
||||
//! # use crate::srp::server::SrpServer;
|
||||
//! # fn get_client_request()-> (Vec<u8>, Vec<u8>) { (vec![], vec![])}
|
||||
//! # fn get_user(_: &[u8])-> (Vec<u8>, Vec<u8>) { (vec![], vec![])}
|
||||
//!
|
||||
//! let server = SrpServer::<Sha256>::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::<sha2::Sha256>::new(&crate::srp::groups::G_2048);
|
||||
//! # fn get_client_response() -> Vec<u8> { 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::<sha2::Sha256>::new(&crate::srp::groups::G_2048);
|
||||
//! # let verifier = server.process_reply(b"", b"", b"1").unwrap();
|
||||
//! # fn get_client_proof()-> Vec<u8> { 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::<sha2::Sha256>::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<D>,
|
||||
}
|
||||
|
||||
/// SRP server state after handshake with the client.
|
||||
pub struct SrpServerVerifier<D: Digest> {
|
||||
m1: Output<D>,
|
||||
m2: Output<D>,
|
||||
key: Vec<u8>,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// <premaster secret> = (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<u8> {
|
||||
self.compute_b_pub(
|
||||
&BigUint::from_bytes_be(b),
|
||||
&compute_k::<D>(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<SrpServerVerifier<D>, 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::<D>(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::<D>(&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::<D>(
|
||||
&a_pub.to_bytes_be(),
|
||||
&b_pub.to_bytes_be(),
|
||||
&key.to_bytes_be(),
|
||||
username,
|
||||
salt,
|
||||
self.params,
|
||||
);
|
||||
|
||||
let m2 = compute_m2::<D>(&a_pub.to_bytes_be(), &m1, &key.to_bytes_be());
|
||||
|
||||
Ok(SrpServerVerifier {
|
||||
m1,
|
||||
m2,
|
||||
key: key.to_bytes_be(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Digest> SrpServerVerifier<D> {
|
||||
/// 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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
45
apple-private-apis/icloud-auth/rustcrypto-srp/src/types.rs
Normal file
45
apple-private-apis/icloud-auth/rustcrypto-srp/src/types.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
//! Additional SRP types.
|
||||
use num_bigint::BigUint;
|
||||
use std::fmt;
|
||||
|
||||
/// SRP authentication error.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum SrpAuthError {
|
||||
IllegalParameter(String),
|
||||
BadRecordMac(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for SrpAuthError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
SrpAuthError::IllegalParameter(param) => {
|
||||
write!(f, "illegal_parameter: bad '{}' value", param)
|
||||
}
|
||||
SrpAuthError::BadRecordMac(param) => {
|
||||
write!(f, "bad_record_mac: incorrect '{}' proof", param)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Group used for SRP computations
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct SrpGroup {
|
||||
/// A large safe prime (N = 2q+1, where q is prime)
|
||||
pub n: BigUint,
|
||||
/// A generator modulo N
|
||||
pub g: BigUint,
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use crate::groups::G_1024;
|
||||
// use crate::utils::compute_k;
|
||||
// use sha1::Sha1;
|
||||
|
||||
// #[test]
|
||||
// fn test_k_1024_sha1() {
|
||||
// let k = compute_k::<Sha1>(&G_1024).to_bytes_be();
|
||||
// assert_eq!(&k, include_bytes!("test/k_sha1_1024.bin"));
|
||||
// }
|
||||
// }
|
||||
70
apple-private-apis/icloud-auth/rustcrypto-srp/src/utils.rs
Normal file
70
apple-private-apis/icloud-auth/rustcrypto-srp/src/utils.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
use digest::{Digest, Output};
|
||||
use num_bigint::BigUint;
|
||||
|
||||
use crate::types::SrpGroup;
|
||||
|
||||
// u = H(PAD(A) | PAD(B))
|
||||
pub fn compute_u<D: Digest>(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<D: Digest>(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<D: Digest>(
|
||||
a_pub: &[u8],
|
||||
b_pub: &[u8],
|
||||
key: &[u8],
|
||||
username: &[u8],
|
||||
salt: &[u8],
|
||||
params: &SrpGroup,
|
||||
) -> Output<D> {
|
||||
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<D: Digest>(a_pub: &[u8], m1: &Output<D>, key: &[u8]) -> Output<D> {
|
||||
let mut d = D::new();
|
||||
d.update(&a_pub);
|
||||
d.update(&m1);
|
||||
d.update(&key);
|
||||
d.finalize()
|
||||
}
|
||||
108
apple-private-apis/icloud-auth/src/anisette.rs
Normal file
108
apple-private-apis/icloud-auth/src/anisette.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
use crate::Error;
|
||||
use omnisette::{AnisetteConfiguration, AnisetteHeaders};
|
||||
use std::{collections::HashMap, time::SystemTime};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AnisetteData {
|
||||
pub base_headers: HashMap<String, String>,
|
||||
pub generated_at: SystemTime,
|
||||
pub config: AnisetteConfiguration,
|
||||
}
|
||||
|
||||
impl AnisetteData {
|
||||
/// Fetches the data at an anisette server
|
||||
pub async fn new(config: AnisetteConfiguration) -> Result<Self, crate::Error> {
|
||||
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, crate::Error> {
|
||||
Self::new(self.config.clone()).await
|
||||
}
|
||||
|
||||
pub fn generate_headers(
|
||||
&self,
|
||||
cpd: bool,
|
||||
client_info: bool,
|
||||
app_info: bool,
|
||||
) -> HashMap<String, String> {
|
||||
if !self.is_valid() {
|
||||
panic!("Invalid data!")
|
||||
}
|
||||
let mut headers = self.base_headers.clone();
|
||||
let old_client_info = headers.remove("X-Mme-Client-Info");
|
||||
if client_info {
|
||||
let client_info = match old_client_info {
|
||||
Some(v) => {
|
||||
let temp = v.as_str();
|
||||
|
||||
temp.replace(
|
||||
temp.split('<').nth(3).unwrap().split('>').nth(0).unwrap(),
|
||||
"com.apple.AuthKit/1 (com.apple.dt.Xcode/3594.4.19)",
|
||||
)
|
||||
}
|
||||
None => {
|
||||
return headers;
|
||||
}
|
||||
};
|
||||
headers.insert("X-Mme-Client-Info".to_owned(), client_info.to_owned());
|
||||
}
|
||||
|
||||
if app_info {
|
||||
headers.insert(
|
||||
"X-Apple-App-Info".to_owned(),
|
||||
"com.apple.gs.xcode.auth".to_owned(),
|
||||
);
|
||||
headers.insert("X-Xcode-Version".to_owned(), "14.2 (14C18)".to_owned());
|
||||
}
|
||||
|
||||
if cpd {
|
||||
headers.insert("bootstrap".to_owned(), "true".to_owned());
|
||||
headers.insert("icscrec".to_owned(), "true".to_owned());
|
||||
headers.insert("loc".to_owned(), "en_GB".to_owned());
|
||||
headers.insert("pbe".to_owned(), "false".to_owned());
|
||||
headers.insert("prkgen".to_owned(), "true".to_owned());
|
||||
headers.insert("svct".to_owned(), "iCloud".to_owned());
|
||||
}
|
||||
|
||||
headers
|
||||
}
|
||||
|
||||
pub fn to_plist(&self, cpd: bool, client_info: bool, app_info: bool) -> plist::Dictionary {
|
||||
let mut plist = plist::Dictionary::new();
|
||||
for (key, value) in self.generate_headers(cpd, client_info, app_info).iter() {
|
||||
plist.insert(key.to_owned(), plist::Value::String(value.to_owned()));
|
||||
}
|
||||
|
||||
plist
|
||||
}
|
||||
|
||||
pub fn get_header(&self, header: &str) -> Result<String, Error> {
|
||||
let headers = self
|
||||
.generate_headers(true, true, true)
|
||||
.iter()
|
||||
.map(|(k, v)| (k.to_lowercase(), v.to_lowercase()))
|
||||
.collect::<HashMap<String, String>>();
|
||||
|
||||
match headers.get(&header.to_lowercase()) {
|
||||
Some(v) => Ok(v.to_string()),
|
||||
None => Err(Error::Parse),
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
apple-private-apis/icloud-auth/src/apple_root.der
Normal file
BIN
apple-private-apis/icloud-auth/src/apple_root.der
Normal file
Binary file not shown.
827
apple-private-apis/icloud-auth/src/client.rs
Normal file
827
apple-private-apis/icloud-auth/src/client.rs
Normal file
@@ -0,0 +1,827 @@
|
||||
use crate::{anisette::AnisetteData, Error};
|
||||
use aes::cipher::block_padding::Pkcs7;
|
||||
use botan::Cipher;
|
||||
use cbc::cipher::{BlockDecryptMut, KeyIvInit};
|
||||
use hmac::{Hmac, Mac};
|
||||
use omnisette::AnisetteConfiguration;
|
||||
use reqwest::{
|
||||
header::{HeaderMap, HeaderName, HeaderValue},
|
||||
Certificate, Client, ClientBuilder, Response,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
use srp::{
|
||||
client::{SrpClient, SrpClientVerifier},
|
||||
groups::G_2048,
|
||||
};
|
||||
use std::str::FromStr;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
const GSA_ENDPOINT: &str = "https://gsa.apple.com/grandslam/GsService2";
|
||||
const APPLE_ROOT: &[u8] = include_bytes!("./apple_root.der");
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct InitRequestBody {
|
||||
#[serde(rename = "A2k")]
|
||||
a_pub: plist::Value,
|
||||
cpd: plist::Dictionary,
|
||||
#[serde(rename = "o")]
|
||||
operation: String,
|
||||
ps: Vec<String>,
|
||||
#[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<String>,
|
||||
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<AnisetteData>,
|
||||
// pub spd: Option<plist::Dictionary>,
|
||||
//mutable spd
|
||||
pub spd: Option<plist::Dictionary>,
|
||||
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<VerifyCode>,
|
||||
}
|
||||
|
||||
#[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<TrustedPhoneNumber>,
|
||||
pub recovery_url: Option<String>,
|
||||
pub cant_use_phone_number_url: Option<String>,
|
||||
pub dont_have_access_url: Option<String>,
|
||||
pub recovery_web_url: Option<String>,
|
||||
pub repair_phone_number_url: Option<String>,
|
||||
pub repair_phone_number_web_url: Option<String>,
|
||||
#[serde(skip)]
|
||||
pub new_state: Option<LoginState>,
|
||||
}
|
||||
|
||||
async fn parse_response(
|
||||
res: Result<Response, reqwest::Error>,
|
||||
) -> Result<plist::Dictionary, crate::Error> {
|
||||
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<Self, crate::Error> {
|
||||
let anisette = AnisetteData::new(config).await?;
|
||||
Ok(Self::new_with_anisette(anisette)?)
|
||||
}
|
||||
|
||||
pub fn new_with_anisette(anisette: AnisetteData) -> Result<Self, crate::Error> {
|
||||
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<String, String>,
|
||||
config: AnisetteConfiguration,
|
||||
) -> Result<AppleAccount, Error> {
|
||||
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<AppToken, Error> {
|
||||
let spd = self.spd.as_ref().unwrap();
|
||||
let dsid = spd.get("adsid").unwrap().as_string().unwrap();
|
||||
let auth_token = spd.get("GsIdmsToken").unwrap().as_string().unwrap();
|
||||
|
||||
let valid_anisette = self.get_anisette().await;
|
||||
|
||||
let sk = spd.get("sk").unwrap().as_data().unwrap();
|
||||
let c = spd.get("c").unwrap().as_data().unwrap();
|
||||
|
||||
let checksum = Self::create_checksum(&sk.to_vec(), dsid, app_name);
|
||||
|
||||
let mut gsa_headers = HeaderMap::new();
|
||||
gsa_headers.insert(
|
||||
"Content-Type",
|
||||
HeaderValue::from_str("text/x-xml-plist").unwrap(),
|
||||
);
|
||||
gsa_headers.insert("Accept", HeaderValue::from_str("*/*").unwrap());
|
||||
gsa_headers.insert(
|
||||
"User-Agent",
|
||||
HeaderValue::from_str("akd/1.0 CFNetwork/978.0.7 Darwin/18.7.0").unwrap(),
|
||||
);
|
||||
gsa_headers.insert(
|
||||
"X-MMe-Client-Info",
|
||||
HeaderValue::from_str(&valid_anisette.get_header("x-mme-client-info")?).unwrap(),
|
||||
);
|
||||
|
||||
let header = RequestHeader {
|
||||
version: "1.0.1".to_string(),
|
||||
};
|
||||
let body = AuthTokenRequestBody {
|
||||
cpd: valid_anisette.to_plist(true, false, false),
|
||||
app: vec![app_name.to_string()],
|
||||
c: plist::Value::Data(c.to_vec()),
|
||||
operation: "apptokens".to_owned(),
|
||||
t: auth_token.to_string(),
|
||||
u: dsid.to_string(),
|
||||
checksum: plist::Value::Data(checksum),
|
||||
};
|
||||
|
||||
let packet = AuthTokenRequest {
|
||||
header: header.clone(),
|
||||
request: body,
|
||||
};
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
plist::to_writer_xml(&mut buffer, &packet)?;
|
||||
let buffer = String::from_utf8(buffer).unwrap();
|
||||
|
||||
let res = self
|
||||
.client
|
||||
.post(GSA_ENDPOINT)
|
||||
.headers(gsa_headers.clone())
|
||||
.body(buffer)
|
||||
.send()
|
||||
.await;
|
||||
let res = parse_response(res).await?;
|
||||
let err_check = Self::check_error(&res);
|
||||
if err_check.is_err() {
|
||||
return Err(err_check.err().unwrap());
|
||||
}
|
||||
|
||||
// --- D code logic starts here ---
|
||||
let encrypted_token = res
|
||||
.get("et")
|
||||
.ok_or(Error::Parse)?
|
||||
.as_data()
|
||||
.ok_or(Error::Parse)?;
|
||||
|
||||
if encrypted_token.len() < 3 + 16 + 16 {
|
||||
return Err(Error::Parse);
|
||||
}
|
||||
let header = &encrypted_token[0..3];
|
||||
if header != b"XYZ" {
|
||||
return Err(Error::AuthSrpWithMessage(
|
||||
0,
|
||||
"Encrypted token is in an unknown format.".to_string(),
|
||||
));
|
||||
}
|
||||
let iv = &encrypted_token[3..19]; // 16 bytes
|
||||
let ciphertext_and_tag = &encrypted_token[19..];
|
||||
|
||||
if sk.len() != 32 {
|
||||
return Err(Error::Parse);
|
||||
}
|
||||
if iv.len() != 16 {
|
||||
return Err(Error::Parse);
|
||||
}
|
||||
|
||||
// Botan AES-256/GCM decryption with 16-byte IV and 3-byte AAD
|
||||
// true = encrypt, false = decrypt
|
||||
let mut cipher = Cipher::new("AES-256/GCM", botan::CipherDirection::Decrypt)
|
||||
.map_err(|_| Error::Parse)?;
|
||||
cipher.set_key(sk).map_err(|_| Error::Parse)?;
|
||||
cipher
|
||||
.set_associated_data(header)
|
||||
.map_err(|_| Error::Parse)?;
|
||||
cipher.start(iv).map_err(|_| Error::Parse)?;
|
||||
|
||||
let mut buf = ciphertext_and_tag.to_vec();
|
||||
buf = cipher.finish(&mut buf).map_err(|_| {
|
||||
Error::AuthSrpWithMessage(
|
||||
0,
|
||||
"Failed to decrypt app token (Botan AES-256/GCM).".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let decrypted_token: plist::Dictionary =
|
||||
plist::from_bytes(&buf).map_err(|_| Error::Parse)?;
|
||||
|
||||
let t_val = decrypted_token.get("t").ok_or(Error::Parse)?;
|
||||
let app_tokens = t_val.as_dictionary().ok_or(Error::Parse)?;
|
||||
let app_token_dict = app_tokens.get(app_name).ok_or(Error::Parse)?;
|
||||
let app_token = app_token_dict.as_dictionary().ok_or(Error::Parse)?;
|
||||
let token = app_token
|
||||
.get("token")
|
||||
.and_then(|v| v.as_string())
|
||||
.ok_or(Error::Parse)?;
|
||||
|
||||
Ok(AppToken {
|
||||
app_tokens: app_tokens.clone(),
|
||||
auth_token: token.to_string(),
|
||||
app: app_name.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
fn create_checksum(session_key: &Vec<u8>, dsid: &str, app_name: &str) -> Vec<u8> {
|
||||
Hmac::<Sha256>::new_from_slice(&session_key)
|
||||
.unwrap()
|
||||
.chain_update("apptokens".as_bytes())
|
||||
.chain_update(dsid.as_bytes())
|
||||
.chain_update(app_name.as_bytes())
|
||||
.finalize()
|
||||
.into_bytes()
|
||||
.to_vec()
|
||||
}
|
||||
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `appleid_closure` - A closure that takes no arguments and returns a tuple of the Apple ID and password
|
||||
/// * `tfa_closure` - A closure that takes no arguments and returns the 2FA code
|
||||
/// * `anisette` - AnisetteData
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use icloud_auth::AppleAccount;
|
||||
/// use omnisette::AnisetteData;
|
||||
///
|
||||
/// let anisette = AnisetteData::new();
|
||||
/// let account = AppleAccount::login(
|
||||
/// || ("test@waffle.me", "password")
|
||||
/// || "123123",
|
||||
/// anisette
|
||||
/// );
|
||||
/// ```
|
||||
/// Note: You would not provide the 2FA code like this, you would have to actually ask input for it.
|
||||
//TODO: add login_with_anisette and login, where login autodetcts anisette
|
||||
pub async fn login_with_anisette<F: Fn() -> Result<(String, String), String>, G: Fn() -> Result<String, String>>(
|
||||
appleid_closure: F,
|
||||
tfa_closure: G,
|
||||
anisette: AnisetteData,
|
||||
) -> Result<AppleAccount, Error> {
|
||||
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<String> {
|
||||
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<LoginState, Error> {
|
||||
let srp_client = SrpClient::<Sha256>::new(&G_2048);
|
||||
let a: Vec<u8> = (0..32).map(|_| rand::random::<u8>()).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::<hmac::Hmac<Sha256>>(
|
||||
&hashed_password,
|
||||
salt,
|
||||
iters as u32,
|
||||
&mut password_buf,
|
||||
);
|
||||
|
||||
let verifier: SrpClientVerifier<Sha256> = 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<Sha256>, name: &str) -> Vec<u8> {
|
||||
Hmac::<Sha256>::new_from_slice(&usr.key())
|
||||
.unwrap()
|
||||
.chain_update(name.as_bytes())
|
||||
.finalize()
|
||||
.into_bytes()
|
||||
.to_vec()
|
||||
}
|
||||
|
||||
fn decrypt_cbc(usr: &SrpClientVerifier<Sha256>, data: &[u8]) -> Vec<u8> {
|
||||
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::<aes::Aes256>::new_from_slices(&extra_data_key, extra_data_iv)
|
||||
.unwrap()
|
||||
.decrypt_padded_vec_mut::<Pkcs7>(&data)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn send_2fa_to_devices(&self) -> Result<LoginState, crate::Error> {
|
||||
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<LoginState, crate::Error> {
|
||||
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<AuthenticationExtras, Error> {
|
||||
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::<AuthenticationExtras>().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<LoginState, Error> {
|
||||
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<LoginState, Error> {
|
||||
let headers = self.build_2fa_headers(true).await;
|
||||
// println!("Recieved code: {}", code);
|
||||
|
||||
body.security_code = Some(VerifyCode { code });
|
||||
|
||||
let res = self
|
||||
.client
|
||||
.post("https://gsa.apple.com/auth/verify/phone/securitycode")
|
||||
.headers(headers)
|
||||
.header("accept", "application/json")
|
||||
.json(&body)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if res.status() != 200 {
|
||||
return Err(Error::Bad2faCode);
|
||||
}
|
||||
|
||||
Ok(LoginState::NeedsLogin)
|
||||
}
|
||||
|
||||
fn check_error(res: &plist::Dictionary) -> Result<(), Error> {
|
||||
let res = match res.get("Status") {
|
||||
Some(plist::Value::Dictionary(d)) => d,
|
||||
_ => &res,
|
||||
};
|
||||
|
||||
if res.get("ec").unwrap().as_signed_integer().unwrap() != 0 {
|
||||
return Err(Error::AuthSrpWithMessage(
|
||||
res.get("ec").unwrap().as_signed_integer().unwrap(),
|
||||
res.get("em").unwrap().as_string().unwrap().to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn build_2fa_headers(&self, sms: bool) -> HeaderMap {
|
||||
let spd = self.spd.as_ref().unwrap();
|
||||
let dsid = spd.get("adsid").unwrap().as_string().unwrap();
|
||||
let token = spd.get("GsIdmsToken").unwrap().as_string().unwrap();
|
||||
|
||||
let identity_token = base64::encode(format!("{}:{}", dsid, token));
|
||||
|
||||
let valid_anisette = self.get_anisette().await;
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
valid_anisette
|
||||
.generate_headers(false, true, true)
|
||||
.iter()
|
||||
.for_each(|(k, v)| {
|
||||
headers.append(
|
||||
HeaderName::from_bytes(k.as_bytes()).unwrap(),
|
||||
HeaderValue::from_str(v).unwrap(),
|
||||
);
|
||||
});
|
||||
|
||||
if !sms {
|
||||
headers.insert(
|
||||
"Content-Type",
|
||||
HeaderValue::from_str("text/x-xml-plist").unwrap(),
|
||||
);
|
||||
headers.insert("Accept", HeaderValue::from_str("text/x-xml-plist").unwrap());
|
||||
}
|
||||
headers.insert("User-Agent", HeaderValue::from_str("Xcode").unwrap());
|
||||
headers.insert("Accept-Language", HeaderValue::from_str("en-us").unwrap());
|
||||
headers.append(
|
||||
"X-Apple-Identity-Token",
|
||||
HeaderValue::from_str(&identity_token).unwrap(),
|
||||
);
|
||||
|
||||
headers.insert(
|
||||
"Loc",
|
||||
HeaderValue::from_str(&valid_anisette.get_header("x-apple-locale").unwrap()).unwrap(),
|
||||
);
|
||||
|
||||
headers
|
||||
}
|
||||
|
||||
pub async fn send_request(
|
||||
&self,
|
||||
url: &str,
|
||||
body: Option<plist::Dictionary>,
|
||||
) -> Result<plist::Dictionary, Error> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
26
apple-private-apis/icloud-auth/src/lib.rs
Normal file
26
apple-private-apis/icloud-auth/src/lib.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
pub mod anisette;
|
||||
mod client;
|
||||
|
||||
pub use client::{AppleAccount, AuthenticationExtras, LoginState, TrustedPhoneNumber, VerifyBody};
|
||||
pub use omnisette::AnisetteConfiguration;
|
||||
|
||||
use thiserror::Error;
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("Failed to parse the response")]
|
||||
Parse,
|
||||
#[error("Failed to authenticate.")]
|
||||
AuthSrp,
|
||||
#[error("Bad 2fa code.")]
|
||||
Bad2faCode,
|
||||
#[error("{1} ({0})")]
|
||||
AuthSrpWithMessage(i64, String),
|
||||
#[error("Please login to appleid.apple.com to fix this account")]
|
||||
ExtraStep(String),
|
||||
#[error("Failed to parse a plist {0}")]
|
||||
PlistError(#[from] plist::Error),
|
||||
#[error("Request failed {0}")]
|
||||
ReqwestError(#[from] reqwest::Error),
|
||||
#[error("Failed getting anisette data {0}")]
|
||||
ErrorGettingAnisette(#[from] omnisette::AnisetteError),
|
||||
}
|
||||
81
apple-private-apis/icloud-auth/tests/auth_debug.rs
Normal file
81
apple-private-apis/icloud-auth/tests/auth_debug.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
// use icloud_auth::ani
|
||||
use std::sync::Arc;
|
||||
|
||||
use num_bigint::BigUint;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
use srp::{
|
||||
client::{SrpClient, SrpClientVerifier},
|
||||
groups::G_2048,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn auth_debug() {
|
||||
// not a real account
|
||||
let bytes_a = base64::decode("XChHXELsQ+ljxTFbvRMUsGJxiDIlOh9f8e+JzoegmVcOdAXXtPNzkHpAbAgSjyA+vXrTA93+BUu8EJ9+4xZu9g==").unwrap();
|
||||
let username = "apple3@f1sh.me";
|
||||
let password = "WaffleTest123";
|
||||
let salt = base64::decode("6fK6ailLUcp2kJswJVrKjQ==").unwrap();
|
||||
let iters = 20832;
|
||||
|
||||
let mut password_hasher = sha2::Sha256::new();
|
||||
password_hasher.update(&password.as_bytes());
|
||||
let hashed_password = password_hasher.finalize();
|
||||
// println!("Hashed password: {:?}", base64::encode(&hashed_password));
|
||||
|
||||
let mut password_buf = [0u8; 32];
|
||||
pbkdf2::pbkdf2::<hmac::Hmac<Sha256>>(
|
||||
&hashed_password,
|
||||
&salt,
|
||||
iters as u32,
|
||||
&mut password_buf,
|
||||
);
|
||||
// println!("PBKDF2 Encrypted password: {:?}",base64::encode(&password_buf));
|
||||
|
||||
let identity_hash = SrpClient::<Sha256>::compute_identity_hash(&[], &password_buf);
|
||||
let x = SrpClient::<Sha256>::compute_x(identity_hash.as_slice(), &salt);
|
||||
|
||||
// apub: N2XHuh/4P1urPoBvDocF0RCRIl2pliZYqg9p6wGH0nnJdckJPn3M00jEqoM4teqH03HjG1murdcZiNHb5YayufW//+asW01XB7nYIIVvGiUFLRypYITEKYWBQ6h2q02GaZspYJKy98V8Fwcvr0ri+al7zJo1X1aoRKINyjV5TywhhwmTleI1qJkf+JBRYKKqO1XFtOTpQsysWD3ZJdK3K78kSgT3q0kXE3oDRMiHPAO77GFJZErYTuvI6QPRbOgcrn+RKV6AsjR5tUQAoSGRdtibdZTAQijJg788qVg+OFVCNZoY9GYVxa+Ze1bPGdkkgCYicTE8iNFG9KlJ+QpKgQ==
|
||||
|
||||
let a_random = base64::decode("ywN1O32vmBogb5Fyt9M7Tn8bbzLtDDbcYgPFpSy8n9E=").unwrap();
|
||||
let client = SrpClient::<Sha256>::new(&G_2048);
|
||||
|
||||
let a_pub_compute =
|
||||
SrpClient::<Sha256>::compute_a_pub(&client, &BigUint::from_bytes_be(&a_random));
|
||||
// expect it to be same to a_pub
|
||||
println!(
|
||||
"compute a_pub: {:?}",
|
||||
base64::encode(&a_pub_compute.to_bytes_be())
|
||||
);
|
||||
|
||||
let b_pub = base64::decode("HlWxsRmNi/9DCGxYCoqCTfdSvpbx3mrgFLQfOsgf3Rojn7MQQN/g63PwlBghUcVVB4//yAaRRnz/VIByl8thA9AKuVZl8k52PAHKSh4e7TuXSeYCFr0+GYu8/hFdMDl42219uzSuOXuaKGVKq6hxEAf3n3uXXgQRkXWtLFJ5nn1wq/emf46hYAHzc/pYyvckAdh9WDCw95IXbzKD8LcPw/0ZQoydMuXgW2ZKZ52fiyEs94IZ7L5RLL7jY1nVdwtsp2fxeqiZ3DNmVZ2GdNrbJGT//160tyd2evtUtehr8ygXNzjWdjV0cc4+1F38ywSPFyieVzVTYzDywRllgo3A5A==").unwrap();
|
||||
println!("fixed b_pub: {:?}", base64::encode(&b_pub));
|
||||
println!("");
|
||||
|
||||
println!("salt: {:?} iterations: {:?}", base64::encode(&salt), iters);
|
||||
|
||||
let verifier: SrpClientVerifier<Sha256> = SrpClient::<Sha256>::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);
|
||||
}
|
||||
}
|
||||
46
apple-private-apis/icloud-auth/tests/gsa_auth.rs
Normal file
46
apple-private-apis/icloud-auth/tests/gsa_auth.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
|
||||
use icloud_auth::*;
|
||||
use omnisette::AnisetteConfiguration;
|
||||
|
||||
#[tokio::test]
|
||||
async fn gsa_auth() {
|
||||
println!("gsa auth test");
|
||||
let email = std::env::var("apple_email").unwrap_or_else(|_| {
|
||||
println!("Enter Apple email: ");
|
||||
let mut input = String::new();
|
||||
std::io::stdin().read_line(&mut input).unwrap();
|
||||
input.trim().to_string()
|
||||
});
|
||||
|
||||
let password = std::env::var("apple_password").unwrap_or_else(|_| {
|
||||
println!("Enter Apple password: ");
|
||||
let mut input = String::new();
|
||||
std::io::stdin().read_line(&mut input).unwrap();
|
||||
input.trim().to_string()
|
||||
});
|
||||
|
||||
let appleid_closure = move || Ok((email.clone(), password.clone()));
|
||||
// ask console for 2fa code, make sure it is only 6 digits, no extra characters
|
||||
let tfa_closure = || {
|
||||
println!("Enter 2FA code: ");
|
||||
let mut input = String::new();
|
||||
std::io::stdin().read_line(&mut input).unwrap();
|
||||
Ok(input.trim().to_string())
|
||||
};
|
||||
let acc = AppleAccount::login(
|
||||
appleid_closure,
|
||||
tfa_closure,
|
||||
AnisetteConfiguration::new()
|
||||
.set_configuration_path(PathBuf::from_str("anisette_test").unwrap()),
|
||||
)
|
||||
.await;
|
||||
|
||||
let account = acc.unwrap();
|
||||
println!("data {:?}", account.get_name());
|
||||
println!("PET: {}", account.get_pet().unwrap());
|
||||
return;
|
||||
}
|
||||
}
|
||||
73
apple-private-apis/icloud-auth/tests/root_write.rs
Normal file
73
apple-private-apis/icloud-auth/tests/root_write.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{fs::File, io::Write};
|
||||
|
||||
const APPLE_ROOT: &[u8] = &[
|
||||
48, 130, 4, 187, 48, 130, 3, 163, 160, 3, 2, 1, 2, 2, 1, 2, 48, 13, 6, 9, 42, 134, 72, 134,
|
||||
247, 13, 1, 1, 5, 5, 0, 48, 98, 49, 11, 48, 9, 6, 3, 85, 4, 6, 19, 2, 85, 83, 49, 19, 48,
|
||||
17, 6, 3, 85, 4, 10, 19, 10, 65, 112, 112, 108, 101, 32, 73, 110, 99, 46, 49, 38, 48, 36,
|
||||
6, 3, 85, 4, 11, 19, 29, 65, 112, 112, 108, 101, 32, 67, 101, 114, 116, 105, 102, 105, 99,
|
||||
97, 116, 105, 111, 110, 32, 65, 117, 116, 104, 111, 114, 105, 116, 121, 49, 22, 48, 20, 6,
|
||||
3, 85, 4, 3, 19, 13, 65, 112, 112, 108, 101, 32, 82, 111, 111, 116, 32, 67, 65, 48, 30, 23,
|
||||
13, 48, 54, 48, 52, 50, 53, 50, 49, 52, 48, 51, 54, 90, 23, 13, 51, 53, 48, 50, 48, 57, 50,
|
||||
49, 52, 48, 51, 54, 90, 48, 98, 49, 11, 48, 9, 6, 3, 85, 4, 6, 19, 2, 85, 83, 49, 19, 48,
|
||||
17, 6, 3, 85, 4, 10, 19, 10, 65, 112, 112, 108, 101, 32, 73, 110, 99, 46, 49, 38, 48, 36,
|
||||
6, 3, 85, 4, 11, 19, 29, 65, 112, 112, 108, 101, 32, 67, 101, 114, 116, 105, 102, 105, 99,
|
||||
97, 116, 105, 111, 110, 32, 65, 117, 116, 104, 111, 114, 105, 116, 121, 49, 22, 48, 20, 6,
|
||||
3, 85, 4, 3, 19, 13, 65, 112, 112, 108, 101, 32, 82, 111, 111, 116, 32, 67, 65, 48, 130, 1,
|
||||
34, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 1, 15, 0, 48, 130, 1,
|
||||
10, 2, 130, 1, 1, 0, 228, 145, 169, 9, 31, 145, 219, 30, 71, 80, 235, 5, 237, 94, 121, 132,
|
||||
45, 235, 54, 162, 87, 76, 85, 236, 139, 25, 137, 222, 249, 75, 108, 245, 7, 171, 34, 48, 2,
|
||||
232, 24, 62, 248, 80, 9, 211, 127, 65, 168, 152, 249, 209, 202, 102, 156, 36, 107, 17, 208,
|
||||
163, 187, 228, 27, 42, 195, 31, 149, 158, 122, 12, 164, 71, 139, 91, 212, 22, 55, 51, 203,
|
||||
196, 15, 77, 206, 20, 105, 209, 201, 25, 114, 245, 93, 14, 213, 127, 95, 155, 242, 37, 3,
|
||||
186, 85, 143, 77, 93, 13, 241, 100, 53, 35, 21, 75, 21, 89, 29, 179, 148, 247, 246, 156,
|
||||
158, 207, 80, 186, 193, 88, 80, 103, 143, 8, 180, 32, 247, 203, 172, 44, 32, 111, 112, 182,
|
||||
63, 1, 48, 140, 183, 67, 207, 15, 157, 61, 243, 43, 73, 40, 26, 200, 254, 206, 181, 185,
|
||||
14, 217, 94, 28, 214, 203, 61, 181, 58, 173, 244, 15, 14, 0, 146, 11, 177, 33, 22, 46, 116,
|
||||
213, 60, 13, 219, 98, 22, 171, 163, 113, 146, 71, 83, 85, 193, 175, 47, 65, 179, 248, 251,
|
||||
227, 112, 205, 230, 163, 76, 69, 126, 31, 76, 107, 80, 150, 65, 137, 196, 116, 98, 11, 16,
|
||||
131, 65, 135, 51, 138, 129, 177, 48, 88, 236, 90, 4, 50, 140, 104, 179, 143, 29, 222, 101,
|
||||
115, 255, 103, 94, 101, 188, 73, 216, 118, 159, 51, 20, 101, 161, 119, 148, 201, 45, 2, 3,
|
||||
1, 0, 1, 163, 130, 1, 122, 48, 130, 1, 118, 48, 14, 6, 3, 85, 29, 15, 1, 1, 255, 4, 4, 3,
|
||||
2, 1, 6, 48, 15, 6, 3, 85, 29, 19, 1, 1, 255, 4, 5, 48, 3, 1, 1, 255, 48, 29, 6, 3, 85, 29,
|
||||
14, 4, 22, 4, 20, 43, 208, 105, 71, 148, 118, 9, 254, 244, 107, 141, 46, 64, 166, 247, 71,
|
||||
77, 127, 8, 94, 48, 31, 6, 3, 85, 29, 35, 4, 24, 48, 22, 128, 20, 43, 208, 105, 71, 148,
|
||||
118, 9, 254, 244, 107, 141, 46, 64, 166, 247, 71, 77, 127, 8, 94, 48, 130, 1, 17, 6, 3, 85,
|
||||
29, 32, 4, 130, 1, 8, 48, 130, 1, 4, 48, 130, 1, 0, 6, 9, 42, 134, 72, 134, 247, 99, 100,
|
||||
5, 1, 48, 129, 242, 48, 42, 6, 8, 43, 6, 1, 5, 5, 7, 2, 1, 22, 30, 104, 116, 116, 112, 115,
|
||||
58, 47, 47, 119, 119, 119, 46, 97, 112, 112, 108, 101, 46, 99, 111, 109, 47, 97, 112, 112,
|
||||
108, 101, 99, 97, 47, 48, 129, 195, 6, 8, 43, 6, 1, 5, 5, 7, 2, 2, 48, 129, 182, 26, 129,
|
||||
179, 82, 101, 108, 105, 97, 110, 99, 101, 32, 111, 110, 32, 116, 104, 105, 115, 32, 99,
|
||||
101, 114, 116, 105, 102, 105, 99, 97, 116, 101, 32, 98, 121, 32, 97, 110, 121, 32, 112, 97,
|
||||
114, 116, 121, 32, 97, 115, 115, 117, 109, 101, 115, 32, 97, 99, 99, 101, 112, 116, 97,
|
||||
110, 99, 101, 32, 111, 102, 32, 116, 104, 101, 32, 116, 104, 101, 110, 32, 97, 112, 112,
|
||||
108, 105, 99, 97, 98, 108, 101, 32, 115, 116, 97, 110, 100, 97, 114, 100, 32, 116, 101,
|
||||
114, 109, 115, 32, 97, 110, 100, 32, 99, 111, 110, 100, 105, 116, 105, 111, 110, 115, 32,
|
||||
111, 102, 32, 117, 115, 101, 44, 32, 99, 101, 114, 116, 105, 102, 105, 99, 97, 116, 101,
|
||||
32, 112, 111, 108, 105, 99, 121, 32, 97, 110, 100, 32, 99, 101, 114, 116, 105, 102, 105,
|
||||
99, 97, 116, 105, 111, 110, 32, 112, 114, 97, 99, 116, 105, 99, 101, 32, 115, 116, 97, 116,
|
||||
101, 109, 101, 110, 116, 115, 46, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 5, 5, 0,
|
||||
3, 130, 1, 1, 0, 92, 54, 153, 76, 45, 120, 183, 237, 140, 155, 220, 243, 119, 155, 242,
|
||||
118, 210, 119, 48, 79, 193, 31, 133, 131, 133, 27, 153, 61, 71, 55, 242, 169, 155, 64, 142,
|
||||
44, 212, 177, 144, 18, 216, 190, 244, 115, 155, 238, 210, 100, 15, 203, 121, 79, 52, 216,
|
||||
162, 62, 249, 120, 255, 107, 200, 7, 236, 125, 57, 131, 139, 83, 32, 211, 56, 196, 177,
|
||||
191, 154, 79, 10, 107, 255, 43, 252, 89, 167, 5, 9, 124, 23, 64, 86, 17, 30, 116, 211, 183,
|
||||
139, 35, 59, 71, 163, 213, 111, 36, 226, 235, 209, 183, 112, 223, 15, 69, 225, 39, 202,
|
||||
241, 109, 120, 237, 231, 181, 23, 23, 168, 220, 126, 34, 53, 202, 37, 213, 217, 15, 214,
|
||||
107, 212, 162, 36, 35, 17, 247, 161, 172, 143, 115, 129, 96, 198, 27, 91, 9, 47, 146, 178,
|
||||
248, 68, 72, 240, 96, 56, 158, 21, 245, 61, 38, 103, 32, 138, 51, 106, 247, 13, 130, 207,
|
||||
222, 235, 163, 47, 249, 83, 106, 91, 100, 192, 99, 51, 119, 247, 58, 7, 44, 86, 235, 218,
|
||||
15, 33, 14, 218, 186, 115, 25, 79, 181, 217, 54, 127, 193, 135, 85, 217, 167, 153, 185, 50,
|
||||
66, 251, 216, 213, 113, 158, 126, 161, 82, 183, 27, 189, 147, 66, 36, 18, 42, 199, 15, 29,
|
||||
182, 77, 156, 94, 99, 200, 75, 128, 23, 80, 170, 138, 213, 218, 228, 252, 208, 9, 7, 55,
|
||||
176, 117, 117, 33,
|
||||
];
|
||||
#[test]
|
||||
|
||||
fn root_write() {
|
||||
// write to file src/root.der
|
||||
let mut file = File::create("src/apple_root.der").unwrap();
|
||||
file.write_all(&APPLE_ROOT).unwrap();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user