diff --git a/.github/workflows/cmake-cross-compile.yml b/.github/workflows/cmake-cross-compile.yml index f737e96..39b1eb2 100644 --- a/.github/workflows/cmake-cross-compile.yml +++ b/.github/workflows/cmake-cross-compile.yml @@ -1,10 +1,6 @@ name: ARM builds -on: - push: - branches: [ "main", "develop" ] - pull_request: - branches: [ "main", "develop" ] +on: push env: BUILD_TYPE: Release diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 2f2a52e..1865628 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -1,10 +1,6 @@ name: x86_64 builds -on: - push: - branches: [ "main", "develop" ] - pull_request: - branches: [ "main", "develop" ] +on: push env: BUILD_TYPE: Release diff --git a/.gitignore b/.gitignore index e660fd9..e4e9e26 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ bin/ +.dub/ diff --git a/CMakeLists.txt b/CMakeLists.txt index efc32f9..0f825f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,8 @@ set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake-d/cmake-d) project(Provision D) option(build_anisetteserver "Build Anisette server" ON) +option(build_mkcassette "Build mkcassette" OFF) +option(use_native_plist "Want dlang plist" OFF) option(link_libplist_dynamic "Load libplist at runtime" OFF) include(cmake/dependencies.cmake) @@ -28,16 +30,14 @@ else() endif() endif() - if (plist_FOUND) + if (plist_FOUND AND NOT use_native_plist) message("Using libplist. ") add_library(plist ALIAS PkgConfig::plist) target_compile_versions(provision PUBLIC LibPlist) else() - if (build_sideloadipa) - message(FATAL_ERROR "Sideload IPA requires libplist development package. ") - endif() message(WARNING "Using fallback Property list parser. ") + include(UseDub) FetchContent_Declare( plist_proj GIT_REPOSITORY https://github.com/hatf0/plist @@ -65,3 +65,13 @@ if(build_anisetteserver) target_link_libraries(anisette_server provision handy-httpd) endif() + +if(build_mkcassette) + set(MKCASSETTE_SOURCE_DIR "mkcassette/") + file(GLOB_RECURSE MKCASSETTE_D_SOURCES "${MKCASSETTE_SOURCE_DIR}*.d") + + add_executable(mkcassette ${MKCASSETTE_D_SOURCES}) + target_include_directories(mkcassette PUBLIC ${MKCASSETTE_SOURCE_DIR}) + + target_link_libraries(mkcassette provision) +endif() diff --git a/LISEZMOI.md b/LISEZMOI.md index ed6d342..efb8e3a 100644 --- a/LISEZMOI.md +++ b/LISEZMOI.md @@ -19,7 +19,7 @@ les en-têtes HTTP à utiliser pour identifier l'appareil. Plus précisément, libprovision enregistre l'appareil auprès d'apple et récupère les données ADI pour celui-ci. Une fois connecté avec cette machine, les serveurs d'Apple se rappeleront de votre appareil comme sûre, -donc assurez vous de ne pas vous connecter n'importe où, et à conserver précieusement les données ADI à `~/.adi/adi.pb`. +donc assurez-vous de ne pas vous connecter n'importe où, et à conserver précieusement les données ADI à `~/.adi/adi.pb`. Il y avait *sideload-ipa* aussi précédemment. Le code a été retiré, car il ne fonctionnait de toute façon pas et que j'aide au développement de [SideServer]() (pas de lien officiel pour le moment), qui fonctionnera AltServer là @@ -42,7 +42,7 @@ libplist. ## Compilation -Clonez le projet et compilez le avec DUB: +Clonez le projet et compilez le avec DUB : ```bash git clone https://github.com/Dadoum/Provision --recursive @@ -61,6 +61,55 @@ cmake -G Ninja .. -DCMAKE_BUILD_TYPE=Release ninja ``` +## Utilisation de libprovision + +L'interface essaie tant bien que mal de rester proche de celle d'AuthKit, même si Provision est écrit en D. + +```d +import std.digest: toHexString; +import file = std.file; +import std.path: expandTilde, buildPath; +import std.random: rndGen; +import std.range: take, array; +import std.stdio: stderr, write, writeln; +import std.uni: toUpper; +import std.uuid: randomUUID; + +import provision.adi; + +void main() { + string configuration_folder = expandTilde("~/.config/Provision/"); + if (!file.exists(configuration_folder)) { + file.mkdir(configuration_folder); + } + + ADI adi = new ADI("lib/" ~ architectureIdentifier); + adi.provisioningPath = configuration_folder; + Device device = new Device(configuration_folder.buildPath("device.json")); + + if (!device.initialized) { + stderr.write("Creating machine... "); + device.serverFriendlyDescription = " "; + device.uniqueDeviceIdentifier = randomUUID().toString().toUpper(); + device.adiIdentifier = (cast(ubyte[]) rndGen.take(2).array()).toHexString().toLower(); + device.localUserUUID = (cast(ubyte[]) rndGen.take(8).array()).toHexString().toUpper(); + + stderr.writeln("done !"); + } + + adi.identifier = device.adiIdentifier; + if (!adi.isMachineProvisioned(-2)) { + stderr.write("Machine requires provisioning... "); + + ProvisioningSession provisioningSession = new ProvisioningSession(adi, device); + provisioningSession.provision(-2); + stderr.writeln("done !"); + } + + // Faites ce que vous voulez avec adi ! +} +``` + ## Soutien Vous pouvez me soutenir en faisant un don avec GitHub Sponsor. diff --git a/README.md b/README.md index 64e6fe9..674a8dd 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,55 @@ cmake -G Ninja .. -DCMAKE_BUILD_TYPE=Release ninja ``` +## libprovision usage + +The Provision API tries to be stay close to the AuthKit API, but written in D. + +```d +import std.digest: toHexString; +import file = std.file; +import std.path: expandTilde, buildPath; +import std.random: rndGen; +import std.range: take, array; +import std.stdio: stderr, write, writeln; +import std.uni: toUpper; +import std.uuid: randomUUID; + +import provision.adi; + +void main() { + string configuration_folder = expandTilde("~/.config/Provision/"); + if (!file.exists(configuration_folder)) { + file.mkdir(configuration_folder); + } + + ADI adi = new ADI("lib/" ~ architectureIdentifier); + adi.provisioningPath = configuration_folder; + Device device = new Device(configuration_folder.buildPath("device.json")); + + if (!device.initialized) { + stderr.write("Creating machine... "); + device.serverFriendlyDescription = " "; + device.uniqueDeviceIdentifier = randomUUID().toString().toUpper(); + device.adiIdentifier = (cast(ubyte[]) rndGen.take(2).array()).toHexString().toLower(); + device.localUserUUID = (cast(ubyte[]) rndGen.take(8).array()).toHexString().toUpper(); + + stderr.writeln("done !"); + } + + adi.identifier = device.adiIdentifier; + if (!adi.isMachineProvisioned(-2)) { + stderr.write("Machine requires provisioning... "); + + ProvisioningSession provisioningSession = new ProvisioningSession(adi, device); + provisioningSession.provision(-2); + stderr.writeln("done !"); + } + + // Do stuff with adi. +} +``` + ## Support Donations are welcome with GitHub Sponsor. diff --git a/anisette_server/app.d b/anisette_server/app.d index 733e861..e564bc9 100644 --- a/anisette_server/app.d +++ b/anisette_server/app.d @@ -1,4 +1,5 @@ import handy_httpd; +import handy_httpd.components.request; import std.algorithm.searching; import std.array; import std.base64; @@ -7,6 +8,7 @@ import std.format; import std.getopt; import std.math; import std.net.curl; +import std.parallelism; import std.path; import std.stdio; import std.zip; @@ -15,33 +17,54 @@ import provision; import constants; -static __gshared ADI* adi; -static __gshared ulong rinfo; +version (X86_64) { + enum string architectureIdentifier = "x86_64"; +} else version (X86) { + enum string architectureIdentifier = "x86"; +} else version (AArch64) { + enum string architectureIdentifier = "arm64-v8a"; +} else version (ARM) { + enum string architectureIdentifier = "armeabi-v7a"; +} else { + static assert(false, "Architecture not supported :("); +} + +__gshared bool allowRemoteProvisioning = false; +__gshared ADI adi; +__gshared Device device; void main(string[] args) { + writeln(anisetteServerBranding, " v", provisionVersion); auto serverConfig = ServerConfig.defaultValues; serverConfig.hostname = "0.0.0.0"; serverConfig.port = 6969; - bool rememberMachine = false; - string path = "~/.adi"; + bool rememberMachine = true; + string configurationPath = expandTilde("~/.config/Provision"); bool onlyInit = false; bool apkDownloadAllowed = true; auto helpInformation = getopt( - args, - "n|host", format!"The hostname to bind to (default: %s)"(serverConfig.hostname), &serverConfig.hostname, - "p|port", format!"The port to bind to (default: %s)"(serverConfig.hostname), &serverConfig.port, - "r|remember-machine", format!"Whether this machine should be remembered (default: %s)"(rememberMachine), &rememberMachine, - "a|adi-path", format!"Where the provisioning information should be stored on the computer (default: %s)"(path), &path, - "init-only", format!"Download libraries and exit (default: %s)"(onlyInit), &onlyInit, - "can-download", format!"If turned on, may download the dependencies automatically (default: %s)"(apkDownloadAllowed), &apkDownloadAllowed, + args, + "n|host", format!"The hostname to bind to (default: %s)"(serverConfig.hostname), &serverConfig.hostname, + "p|port", format!"The port to bind to (default: %s)"(serverConfig.hostname), &serverConfig.port, + "r|remember-machine", format!"Whether this machine should be remembered (default: %s)"(rememberMachine), &rememberMachine, + "a|adi-path", format!"Where the provisioning information should be stored on the computer (default: %s)"(configurationPath), &configurationPath, + "init-only", format!"Download libraries and exit (default: %s)"(onlyInit), &onlyInit, + "can-download", format!"If turned on, may download the dependencies automatically (default: %s)"(apkDownloadAllowed), &apkDownloadAllowed, + "allow-remote-reprovisioning", format!"If turned on, the server may reprovision the server on client demand (default: %s)"(allowRemoteProvisioning), &allowRemoteProvisioning, ); if (helpInformation.helpWanted) { defaultGetoptPrinter("This program allows you to host anisette through libprovision!", helpInformation.options); - return; + return; } + if (!file.exists(configurationPath)) { + file.mkdirRecurse(configurationPath); + } + + string libraryPath = configurationPath.buildPath("lib/" ~ architectureIdentifier); + auto coreADIPath = libraryPath.buildPath("libCoreADI.so"); auto SSCPath = libraryPath.buildPath("libstoreservicescore.so"); @@ -71,101 +94,113 @@ void main(string[] args) { auto apk = new ZipArchive(apkData); auto dir = apk.directory(); - if (!file.exists("lib/")) { - file.mkdir("lib/"); - } if (!file.exists(libraryPath)) { - file.mkdir(libraryPath); + file.mkdirRecurse(libraryPath); } - file.write(coreADIPath, apk.expand(dir[coreADIPath])); - file.write(SSCPath, apk.expand(dir[SSCPath])); + file.write(coreADIPath, apk.expand(dir["lib/" ~ architectureIdentifier ~ "/libCoreADI.so"])); + file.write(SSCPath, apk.expand(dir["lib/" ~ architectureIdentifier ~ "/libstoreservicescore.so"])); } if (onlyInit) { return; } - if (rememberMachine) { - adi = new ADI(expandTilde(path)); - } else { - import std.digest: toHexString; + // Initializing ADI and machine if it has not already been made. + device = new Device(rememberMachine ? configurationPath.buildPath("device.json") : "/dev/null"); + adi = new ADI(libraryPath); + adi.provisioningPath = configurationPath; + + if (!device.initialized) { + stderr.write("Creating machine... "); + + import std.digest; import std.random; import std.range; import std.uni; - ubyte[] id = cast(ubyte[]) rndGen.take(2).array; - adi = new ADI(expandTilde(path), cast(char[]) id.toHexString().toLower()); + import std.uuid; + device.serverFriendlyDescription = " "; + device.uniqueDeviceIdentifier = randomUUID().toString().toUpper(); + device.adiIdentifier = (cast(ubyte[]) rndGen.take(2).array()).toHexString().toLower(); + device.localUserUUID = (cast(ubyte[]) rndGen.take(8).array()).toHexString().toUpper(); + + stderr.writeln("done !"); } - if (!adi.isMachineProvisioned()) { - write("Machine requires provisioning... "); - adi.provisionDevice(rinfo); - writeln("done !"); - } else { - adi.getRoutingInformation(rinfo); + enum dsId = -2; + + adi.identifier = device.adiIdentifier; + if (!adi.isMachineProvisioned(dsId)) { + stderr.write("Machine requires provisioning... "); + + ProvisioningSession provisioningSession = new ProvisioningSession(adi, device); + provisioningSession.provision(dsId); + stderr.writeln("done !"); } auto s = new HttpServer((ref ctx) { - ctx.response.addHeader("Implementation-Version", anisetteServerBranding ~ " " ~ anisetteServerVersion); - auto req = ctx.request; - auto res = ctx.response; - if (req.url == "/version") { - writeln("[<<] GET /version"); - res.writeBodyString(anisetteServerVersion); - writeln("[>>] 200 OK"); - res.setStatus(200); - } else if (req.url == "/reprovision") { - writeln("[<<] GET /reprovision"); - adi.provisionDevice(rinfo); - writeln("[>>] 200 OK"); - res.setStatus(200); - } else { - try { - import std.datetime.systime; - import std.datetime.timezone; - import core.time; - auto time = Clock.currTime(); - - writefln("[<<] GET /"); - - ubyte[] mid; - ubyte[] otp; - try { - adi.getOneTimePassword(mid, otp); - } catch (Throwable) { - writeln("Reprovision needed."); - adi.provisionDevice(rinfo); - adi.getOneTimePassword(mid, otp); - } + ctx.response.addHeader("Implementation-Version", anisetteServerBranding ~ " " ~ provisionVersion); - import std.conv; - import std.json; + writeln("[<<] ", req.method, " ", req.url); + if (req.method != "GET") { + writefln("[>>] 405 Method Not Allowed"); + ctx.response.setStatus(405).setStatusText("Method Not Allowed"); + return; + } + + if (req.url == "/reprovision") { + if (allowRemoteProvisioning) { + ProvisioningSession provisioningSession = new ProvisioningSession(adi, device); + provisioningSession.provision(dsId); + writeln("[>>] 200 OK"); + ctx.response.setStatus(200); + } else { + writeln("[>>] 403 Forbidden"); + ctx.response.setStatus(403).setStatusText("Forbidden"); + } + return; + } + + if (req.url != "") { + writeln("[>>] 404 Not Found"); + ctx.response.setStatus(404).setStatusText("Not Found"); + return; + } - JSONValue response = [ + try { + import std.datetime.systime; + import std.datetime.timezone; + import core.time; + auto time = Clock.currTime(); + + auto otp = adi.requestOTP(dsId); + + import std.conv; + import std.json; + + JSONValue response = [ "X-Apple-I-Client-Time": time.toISOExtString.split('.')[0] ~ "Z", - "X-Apple-I-MD": Base64.encode(otp), - "X-Apple-I-MD-M": Base64.encode(mid), - "X-Apple-I-MD-RINFO": to!string(rinfo), - "X-Apple-I-MD-LU": adi.localUserUUID, - "X-Apple-I-SRL-NO": adi.serialNo, - "X-MMe-Client-Info": adi.clientInfo, + "X-Apple-I-MD": Base64.encode(otp.oneTimePassword), + "X-Apple-I-MD-M": Base64.encode(otp.machineIdentifier), + "X-Apple-I-MD-RINFO": to!string(17106176), + "X-Apple-I-MD-LU": device.localUserUUID, + "X-Apple-I-SRL-NO": "0", + "X-MMe-Client-Info": device.serverFriendlyDescription, "X-Apple-I-TimeZone": time.timezone.dstName, "X-Apple-Locale": "en_US", - "X-Mme-Device-Id": adi.deviceId, - ]; - - writefln!"[>>] 200 OK %s"(response); - - res.setStatus(200); - res.writeBodyString(response.toString(JSONOptions.doNotEscapeSlashes), "application/json"); - } catch(Throwable t) { - res.setStatus(500); - res.writeBodyString(t.toString()); - } + "X-Mme-Device-Id": device.uniqueDeviceIdentifier, + ]; + ctx.response.writeBodyString(response.toString(JSONOptions.doNotEscapeSlashes), "application/json"); + writefln!"[>>] 200 OK %s"(response); + } catch(Throwable t) { + string exception = t.toString(); + writeln("Encountered an error: ", exception); + writeln("[>>] 500 Internal Server Error"); + ctx.response.writeBodyString(exception); + ctx.response.setStatus(500).setStatusText("Internal Server Error"); } }, serverConfig); writeln("Ready! Serving data."); s.start(); } - diff --git a/anisette_server/constants.d b/anisette_server/constants.d index 903af06..2fae19c 100644 --- a/anisette_server/constants.d +++ b/anisette_server/constants.d @@ -1,5 +1,4 @@ module constants; enum anisetteServerBranding = "anisette-server-provision"; -enum anisetteServerVersion = "1.2.2"; enum nativesUrl = "https://apps.mzstatic.com/content/android-apple-music-apk/applemusic.apk"; diff --git a/dub.sdl b/dub.sdl index efbd1fb..b7bee49 100644 --- a/dub.sdl +++ b/dub.sdl @@ -29,6 +29,18 @@ subPackage { dependency "provision" version="*" } +subPackage { + name "mkcassette" + targetType "executable" + targetPath "bin" + + workingDirectory "bin" + + sourcePaths "mkcassette" + + dependency "provision" version="*" +} + subPackage { name "anisette-server" targetType "executable" diff --git a/lib/provision/adi.d b/lib/provision/adi.d index 8f88527..a8020ea 100644 --- a/lib/provision/adi.d +++ b/lib/provision/adi.d @@ -1,13 +1,14 @@ module provision.adi; -import provision.android.id; import provision.androidlibrary; import std.base64; import std.conv; import std.digest.sha; -import std.file; +import file = std.file; import std.format; +import std.json; import std.net.curl; +import std.path; import std.stdio; import std.string; @@ -21,9 +22,8 @@ version (LibPlist) { alias ADILoadLibraryWithPath_t = extern(C) int function(const char*); alias ADISetAndroidID_t = extern(C) int function(const char*, uint); alias ADISetProvisioningPath_t = extern(C) int function(const char*); - alias ADIProvisioningErase_t = extern(C) int function(ulong); -alias ADISynchronize_t = extern(C) int function(uint, ubyte*, uint, ubyte**, uint*, ubyte**, uint*); +alias ADISynchronize_t = extern(C) int function(ulong, ubyte*, uint, ubyte**, uint*, ubyte**, uint*); alias ADIProvisioningDestroy_t = extern(C) int function(uint); alias ADIProvisioningEnd_t = extern(C) int function(uint, ubyte*, uint, ubyte*, uint); alias ADIProvisioningStart_t = extern(C) int function(ulong, ubyte*, uint, ubyte**, uint*, uint*); @@ -31,463 +31,558 @@ alias ADIGetLoginCode_t = extern(C) int function(ulong); alias ADIDispose_t = extern(C) int function(void*); alias ADIOTPRequest_t = extern(C) int function(ulong, ubyte**, uint*, ubyte**, uint*); -version (X86_64) { - enum string architectureIdentifier = "x86_64"; -} else version (X86) { - enum string architectureIdentifier = "x86"; -} else version (AArch64) { - enum string architectureIdentifier = "arm64-v8a"; -} else version (ARM) { - enum string architectureIdentifier = "armeabi-v7a"; -} else { - static assert(false, "Architecture not supported :("); -} -enum string libraryPath = "lib/" ~ architectureIdentifier ~ "/"; - -@nogc public struct ADI { - private string path; - private ulong dsId; - - private string __identifier; - private string[string] urlBag; - - AndroidLibrary* libstoreservicescore; - - ADILoadLibraryWithPath_t pADILoadLibraryWithPath; - ADISetAndroidID_t pADISetAndroidID; - ADISetProvisioningPath_t pADISetProvisioningPath; - - ADIProvisioningErase_t pADIProvisioningErase; - ADISynchronize_t pADISynchronize; - ADIProvisioningDestroy_t pADIProvisioningDestroy; - ADIProvisioningEnd_t pADIProvisioningEnd; - ADIProvisioningStart_t pADIProvisioningStart; - ADIGetLoginCode_t pADIGetLoginCode; - ADIDispose_t pADIDispose; - ADIOTPRequest_t pADIOTPRequest; - - string __clientInfo = " "; - public @property string clientInfo() { - return __clientInfo; +public class ADI { + private ADILoadLibraryWithPath_t pADILoadLibraryWithPath; + private ADISetAndroidID_t pADISetAndroidID; + private ADISetProvisioningPath_t pADISetProvisioningPath; + + private ADIProvisioningErase_t pADIProvisioningErase; + private ADISynchronize_t pADISynchronize; + private ADIProvisioningDestroy_t pADIProvisioningDestroy; + private ADIProvisioningEnd_t pADIProvisioningEnd; + private ADIProvisioningStart_t pADIProvisioningStart; + private ADIGetLoginCode_t pADIGetLoginCode; + private ADIDispose_t pADIDispose; + private ADIOTPRequest_t pADIOTPRequest; + + private AndroidLibrary storeServicesCore; + + private string __provisioningPath; + public string provisioningPath() { + return __provisioningPath; } - public @property void clientInfo(string value) { - __clientInfo = value; - } - - string __serialNo = "0"; - public @property string serialNo() { - return __serialNo; - } - - public @property void serialNo(string value) { - __serialNo = value; - } - - public @property string provisionPath() { - return this.path; - } - - public void identifier(string value) { - pADISetAndroidID(/+identifierStr+/ value.toStringz, /+length+/ cast(uint) value.length); - __identifier = value; + public void provisioningPath(string path) { + __provisioningPath = path; + pADISetProvisioningPath(path.toStringz).unwrapADIError(); } + private string __identifier; public string identifier() { return __identifier; } - public @property string deviceId() { - return sha1Of(this.identifier).toHexString().toUpper().dup(); + public void identifier(string identifier) { + __identifier = identifier; + pADISetAndroidID(identifier.ptr, cast(uint) identifier.length).unwrapADIError(); } - public @property string localUserUUID() { - return sha256Of(this.identifier).toHexString().toUpper().dup(); + public this(string libraryPath) { + string storeServicesCorePath = libraryPath.buildPath("libstoreservicescore.so"); + if (!file.exists(storeServicesCorePath)) { + throw new file.FileException(storeServicesCorePath); + } + + this(libraryPath, new AndroidLibrary(storeServicesCorePath)); } - @disable this(); + public this(string libraryPath, AndroidLibrary storeServicesCore) { + this.storeServicesCore = storeServicesCore; - public this(string provisioningPath, char[] identifier = null) { - if (!exists(libraryPath ~ "libstoreservicescore.so")) { - throw new FileException(libraryPath ~ "libstoreservicescore.so", "Apple libraries are not installed correctly. Refer to README for instructions. "); - } - - this.libstoreservicescore = new AndroidLibrary(libraryPath ~ "libstoreservicescore.so"); + // We are loading the symbols from the ELF library from their name. + // Those has been obfuscated but they keep a consistent obfuscated name, like a hash function would. debug { stderr.writeln("Loading Android-specific symbols..."); } - this.pADILoadLibraryWithPath = cast(ADILoadLibraryWithPath_t) libstoreservicescore.load("kq56gsgHG6"); - this.pADISetAndroidID = cast(ADISetAndroidID_t) libstoreservicescore.load("Sph98paBcz"); - this.pADISetProvisioningPath = cast(ADISetProvisioningPath_t) libstoreservicescore.load("nf92ngaK92"); + pADILoadLibraryWithPath = cast(ADILoadLibraryWithPath_t) storeServicesCore.load("kq56gsgHG6"); + pADISetAndroidID = cast(ADISetAndroidID_t) storeServicesCore.load("Sph98paBcz"); + pADISetProvisioningPath = cast(ADISetProvisioningPath_t) storeServicesCore.load("nf92ngaK92"); debug { stderr.writeln("Loading ADI symbols..."); } - this.pADIProvisioningErase = cast(ADIProvisioningErase_t) libstoreservicescore.load("p435tmhbla"); - this.pADISynchronize = cast(ADISynchronize_t) libstoreservicescore.load("tn46gtiuhw"); - this.pADIProvisioningDestroy = cast(ADIProvisioningDestroy_t) libstoreservicescore.load("fy34trz2st"); - this.pADIProvisioningEnd = cast(ADIProvisioningEnd_t) libstoreservicescore.load("uv5t6nhkui"); - this.pADIProvisioningStart = cast(ADIProvisioningStart_t) libstoreservicescore.load("rsegvyrt87"); - this.pADIGetLoginCode = cast(ADIGetLoginCode_t) libstoreservicescore.load("aslgmuibau"); - this.pADIDispose = cast(ADIDispose_t) libstoreservicescore.load("jk24uiwqrg"); - this.pADIOTPRequest = cast(ADIOTPRequest_t) libstoreservicescore.load("qi864985u0"); + pADIProvisioningErase = cast(ADIProvisioningErase_t) storeServicesCore.load("p435tmhbla"); + pADISynchronize = cast(ADISynchronize_t) storeServicesCore.load("tn46gtiuhw"); + pADIProvisioningDestroy = cast(ADIProvisioningDestroy_t) storeServicesCore.load("fy34trz2st"); + pADIProvisioningEnd = cast(ADIProvisioningEnd_t) storeServicesCore.load("uv5t6nhkui"); + pADIProvisioningStart = cast(ADIProvisioningStart_t) storeServicesCore.load("rsegvyrt87"); + pADIGetLoginCode = cast(ADIGetLoginCode_t) storeServicesCore.load("aslgmuibau"); + pADIDispose = cast(ADIDispose_t) storeServicesCore.load("jk24uiwqrg"); + pADIOTPRequest = cast(ADIOTPRequest_t) storeServicesCore.load("qi864985u0"); debug { stderr.writeln("First calls..."); } - pADILoadLibraryWithPath(/+path+/ libraryPath.toStringz); - - this.path = provisioningPath; - pADISetProvisioningPath(/+path+/ path.toStringz); + loadLibrary(libraryPath); - if (identifier == null) - this.identifier = cast(string) genAndroidId(); - else - this.identifier = cast(string) identifier; + // We are setting those to be sure to have the same value in the class (used in getter) and the real one in ADI. + provisioningPath = "/"; + identifier = "0000000000000000"; + } - debug { - stderr.writeln("Setting fields..."); + ~this() { + if (storeServicesCore) { + destroy(storeServicesCore); } + } - dsId = -2; + public void loadLibrary(string libraryPath) { + pADILoadLibraryWithPath(libraryPath.toStringz).unwrapADIError(); + } - debug { - stderr.writeln("Ctor done !"); - } + public void eraseProvisioning(ulong dsId) { + pADIProvisioningErase(dsId).unwrapADIError(); } - private HTTP makeHttpClient() { - auto client = HTTP(); + struct SynchronizationResumeMetadata { + public ubyte[] synchronizationResumeMetadata; + public ubyte[] machineIdentifier; + private ADI adi; - client.setUserAgent("iCloud.exe (unknown version) CFNetwork/520.44.6"); - client.handle.set(CurlOption.ssl_verifypeer, 0); + @disable this(); + @disable this(this); - // debug { - // client.handle.set(CurlOption.verbose, 1); - // } - client.addRequestHeader("Accept", "*/*"); - client.addRequestHeader("Content-Type", "text/x-xml-plist"); - client.addRequestHeader("Accept-Language", "en"); - client.addRequestHeader("Accept-Encoding", "gzip, deflate"); - client.addRequestHeader("Connection", "keep-alive"); - client.addRequestHeader("Proxy-Connection", "keep-alive"); + this(ADI adiInstance, ubyte* srm, uint srmLength, ubyte* mid, uint midLength) { + adi = adiInstance; + synchronizationResumeMetadata = srm[0..srmLength]; + machineIdentifier = mid[0..midLength]; + } - return client; + ~this() { + adi.dispose(synchronizationResumeMetadata.ptr); + adi.dispose(machineIdentifier.ptr); + } } - private void populateUrlBag(HTTP client) { - auto content = cast(string) std.net.curl.get("https://gsa.apple.com/grandslam/GsService2/lookup", client); - - version (LibPlist) { - PlistDict plist = cast(PlistDict) Plist.fromXml(content); - auto response = cast(PlistDict) plist["urls"]; - auto responseIter = response.iter(); - Plist val; - string key; - while (responseIter.next(val, key)) { - urlBag[key] = cast(string) cast(PlistString) val; - } - } else { - Plist plist = new Plist(); - plist.read(cast(string) content); - auto response = (cast(PlistElementDict) (cast(PlistElementDict) (plist[0]))["urls"]); + public SynchronizationResumeMetadata synchronize(ulong dsId, ubyte[] serverIntermediateMetadata) { + ubyte* srm; + uint srmLength; + ubyte* mid; + uint midLength; + + pADISynchronize( + dsId, + serverIntermediateMetadata.ptr, + cast(uint) serverIntermediateMetadata.length, + &mid, + &midLength, + &srm, + &srmLength + ).unwrapADIError(); + + return SynchronizationResumeMetadata(this, srm, srmLength, mid, midLength); + } - foreach (key; response.keys()) { - urlBag[key] = (cast(PlistElementString) response[key]).value; - } - } + public void destroyProvisioning(uint session) { + pADIProvisioningDestroy(session).unwrapADIError(); } - private ubyte[] downloadSPIM(HTTP client) { - import std.datetime.systime; - auto time = Clock.currTime(); + public void endProvisioning(uint session, ubyte[] persistentTokenMetadata, ubyte[] trustKey) { + pADIProvisioningEnd( + session, + persistentTokenMetadata.ptr, + cast(uint) persistentTokenMetadata.length, + trustKey.ptr, + cast(uint) trustKey.length + ).unwrapADIError(); + } - client.addRequestHeader("X-Apple-I-Client-Time", time.toISOExtString()); - client.addRequestHeader("X-Apple-I-TimeZone", time.timezone().dstName); + struct ClientProvisioningIntermediateMetadata { + public ubyte[] clientProvisioningIntermediateMetadata; + public uint session; + private ADI adi; - string content = cast(string) post(urlBag["midStartProvisioning"], - " - - - -\tHeader -\t -\tRequest -\t - - -", client); + @disable this(); + @disable this(this); - string spimStr; - version (LibPlist) { - auto spimPlist = cast(PlistDict) Plist.fromXml(content); - auto spimResponse = cast(PlistDict) spimPlist["Response"]; - spimStr = cast(string) cast(PlistString) spimResponse["spim"]; - } else { - Plist spimPlist = new Plist(); - spimPlist.read(content); - PlistElementDict spimResponse = cast(PlistElementDict) (cast(PlistElementDict) (spimPlist[0]))["Response"]; - spimStr = (cast(PlistElementString) spimResponse["spim"]).value; + this(ADI adiInstance, ubyte* cpim, uint cpimLength, uint session) { + adi = adiInstance; + clientProvisioningIntermediateMetadata = cpim[0..cpimLength]; + this.session = session; } - return Base64.decode(spimStr); + ~this() { + adi.dispose(clientProvisioningIntermediateMetadata.ptr); + } } - auto sendCPIM(HTTP client, ubyte[] cpim) { - string body_ = format!" - - - -\tHeader -\t -\tRequest -\t -\t\tcpim -\t\t%s -\t - - -"(Base64.encode(cpim)); + public ClientProvisioningIntermediateMetadata startProvisioning(ulong dsId, ubyte[] serverProvisioningIntermediateMetadata) { + ubyte* cpim; + uint cpimLength; + uint session; - import std.datetime.systime; - auto time = Clock.currTime(); + pADIProvisioningStart( + dsId, + serverProvisioningIntermediateMetadata.ptr, + cast(uint) serverProvisioningIntermediateMetadata.length, + &cpim, + &cpimLength, + &session + ).unwrapADIError(); - client.addRequestHeader("X-Apple-I-Client-Time", time.toISOExtString()); - client.addRequestHeader("X-Apple-I-TimeZone", time.timezone().dstName); + return ClientProvisioningIntermediateMetadata(this, cpim, cpimLength, session); + } - string content = cast(string) post(urlBag["midFinishProvisioning"], - body_, client); + public bool isMachineProvisioned(ulong dsId) { + int errorCode = pADIGetLoginCode(dsId); - struct SecondStepAnswers { - string rinfo; - ubyte[] tk; - ubyte[] ptm; + if (errorCode == 0) { + return true; + } else if (errorCode == -45061) { + return false; } - SecondStepAnswers secondStepAnswers = SecondStepAnswers(); - - version (LibPlist) { - PlistDict plist = cast(PlistDict) Plist.fromXml(content); - PlistDict spimResponse = cast(PlistDict) plist["Response"]; - secondStepAnswers.rinfo = cast(string) cast(PlistString) spimResponse["X-Apple-I-MD-RINFO"]; - secondStepAnswers.tk = Base64.decode(cast(string) cast(PlistString) spimResponse["tk"]); - secondStepAnswers.ptm = Base64.decode(cast(string) cast(PlistString) spimResponse["ptm"]); - } else { - Plist plist = new Plist(); - plist.read(content); - PlistElementDict spimResponse = cast(PlistElementDict) (cast(PlistElementDict) (plist[0]))["Response"]; - - secondStepAnswers.rinfo = (cast(PlistElementString) spimResponse["X-Apple-I-MD-RINFO"]).value; - secondStepAnswers.tk = Base64.decode((cast(PlistElementString) spimResponse["tk"]).value); - secondStepAnswers.ptm = Base64.decode((cast(PlistElementString) spimResponse["ptm"]).value); - } + throw new ADIException(errorCode); + } - return secondStepAnswers; + public void dispose(void* ptr) { + pADIDispose(ptr).unwrapADIError(); } - public bool isMachineProvisioned() { - debug { - stderr.writeln("isMachineProvisioned called !"); - } + struct OneTimePassword { + public ubyte[] oneTimePassword; + public ubyte[] machineIdentifier; + private ADI adi; - int loginCode = pADIGetLoginCode(dsId); + @disable this(); + @disable this(this); - debug { - stderr.writefln("isMachineProvisioned -> %d", loginCode); + this(ADI adiInstance, ubyte* otp, uint otpLength, ubyte* mid, uint midLength) { + adi = adiInstance; + oneTimePassword = otp[0..otpLength]; + machineIdentifier = mid[0..midLength]; } - if (loginCode == 0) { - return true; - } else if (loginCode == -45061) { - return false; + ~this() { + adi.dispose(oneTimePassword.ptr); + adi.dispose(machineIdentifier.ptr); } - throw new AnisetteException(loginCode); } - public void provisionDevice(out ulong routingInformation) { - debug { - stderr.writeln("provisionDevice called !"); - } - auto client = makeHttpClient(); - - client.addRequestHeader("X-Mme-Client-Info", clientInfo); - client.addRequestHeader("X-Mme-Device-Id", deviceId); - client.addRequestHeader("X-Apple-I-MD-LU", localUserUUID); - client.addRequestHeader("X-Apple-I-SRL-NO", serialNo); + public OneTimePassword requestOTP(ulong dsId) { + ubyte* otp; + uint otpLength; + ubyte* mid; + uint midLength; + + pADIOTPRequest( + dsId, + &mid, + &midLength, + &otp, + &otpLength + ).unwrapADIError(); + + return OneTimePassword(this, otp, otpLength, mid, midLength); + } +} - debug { - stderr.writeln("First request... (urlBag)"); +public class Device { + JSONValue deviceData; + + enum uniqueDeviceIdentifierJson = "UUID"; + enum serverFriendlyDescriptionJson = "clientInfo"; + enum adiIdentifierJson = "identifier"; + enum localUserUUIDJson = "localUUID"; + + string uniqueDeviceIdentifier() { return deviceData[uniqueDeviceIdentifierJson].str(); } + string serverFriendlyDescription() { return deviceData[serverFriendlyDescriptionJson].str(); } + string adiIdentifier() { return deviceData[adiIdentifierJson].str(); } + // It is a value computed by AuthKit. On Windows, it takes the path to the home folder and hash every component in. + // We could do the same thing but anyway we can also just hash the ADI identifier, that's good too. + string localUserUUID() { return deviceData[localUserUUIDJson].str(); } + + void uniqueDeviceIdentifier(string value) { deviceData[uniqueDeviceIdentifierJson] = value; write(); } + void serverFriendlyDescription(string value) { deviceData[serverFriendlyDescriptionJson] = value; write(); } + void adiIdentifier(string value) { deviceData[adiIdentifierJson] = value; write(); } + void localUserUUID(string value) { deviceData[localUserUUIDJson] = value; write(); } + + // We could generate that, but we servers don't care so anyway + // string logicBoardSerialNumber; + // string romAddress; + // string machineSerialNumber; + + bool initialized = false; + string path; + + this(string filePath) { + path = filePath; + if (file.exists(path)) { + try { + JSONValue deviceFile = parseJSON(cast(char[]) file.read(filePath)); + uniqueDeviceIdentifier = deviceFile[uniqueDeviceIdentifierJson].str(); + serverFriendlyDescription = deviceFile[serverFriendlyDescriptionJson].str(); + adiIdentifier = deviceFile[adiIdentifierJson].str(); + localUserUUID = deviceFile[localUserUUIDJson].str(); + initialized = true; + } catch (Throwable) { /+ do nothing +/ } } + } - populateUrlBag(client); + public void write(string path) { + this.path = path; + write(); + } - debug { - stderr.writeln("Erasing provisioning..."); + public void write() { + if (path) { + file.write(path, deviceData.toString()); + initialized = true; } + } +} - pADIProvisioningErase(dsId); +public class ProvisioningSession { + private HTTP httpClient; + private string[string] urlBag; - debug { - stderr.writeln("Second request... (spim)"); - } + private ADI adi; + private Device device; - ubyte[] spim = downloadSPIM(client); + public this(ADI adi, Device device) { + this.adi = adi; + this.device = device; - ubyte* cpimPtr; - uint l; + httpClient = HTTP(); - uint session; + httpClient.setUserAgent("akd/1.0 CFNetwork/1404.0.5 Darwin/22.3.0"); + httpClient.handle.set(CurlOption.ssl_verifypeer, 0); - debug { - stderr.writeln("Start provisioning..."); - } + // they are somehow not using the plist content-type in AuthKit + httpClient.addRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + httpClient.addRequestHeader("Connection", "keep-alive"); - int ret = pADIProvisioningStart( - /+dsId+/ dsId, - /+spim ptr+/ spim.ptr, - /+spim length+/ cast(uint) spim.length, - /+(out) cpim ptr+/ &cpimPtr, - /+(out) cpim length+/ &l, - /+(out) session+/ &session - ); + httpClient.addRequestHeader("X-Mme-Device-Id", device.uniqueDeviceIdentifier); + // on macOS, MMe for the Client-Info header is written with 2 caps, while on Windows it is Mme... + // and HTTP headers are supposed to be case-insensitive in the HTTP spec... + httpClient.addRequestHeader("X-MMe-Client-Info", device.serverFriendlyDescription); + httpClient.addRequestHeader("X-Apple-I-MD-LU", device.localUserUUID); - if (ret) - throw new AnisetteException(ret); + // httpClient.addRequestHeader("X-Apple-I-MLB", device.logicBoardSerialNumber); // 17 letters, uppercase in Apple's base 34 + // httpClient.addRequestHeader("X-Apple-I-ROM", device.romAddress); // 6 bytes, lowercase hexadecimal + // httpClient.addRequestHeader("X-Apple-I-SRL-NO", device.machineSerialNumber); // 12 letters, uppercase - ubyte[] cpim = cpimPtr[0..l]; - - debug { - stderr.writeln("Third request... (ptm & tk)"); - } + // different apps can be used, I already saw fmfd and Setup here + // and Reprovision uses Xcode in some requests, so maybe it is possible here too. + httpClient.addRequestHeader("X-Apple-Client-App-Name", "Setup"); + } - auto secondStep = sendCPIM(client, cpim); - routingInformation = to!ulong(secondStep.rinfo); + public void loadURLBag() { + string content = cast(string) std.net.curl.get("https://gsa.apple.com/grandslam/GsService2/lookup", httpClient); - debug { - stderr.writeln("End provisioning..."); - } - - ret = pADIProvisioningEnd( - session, - secondStep.ptm.ptr, - cast(uint) secondStep.ptm.length, - secondStep.tk.ptr, - cast(uint) secondStep.tk.length - ); + version (LibPlist) { + PlistDict plist = cast(PlistDict) Plist.fromXml(content); + auto response = cast(PlistDict) plist["urls"]; + auto responseIter = response.iter(); - if (ret) - throw new AnisetteException(ret); + Plist val; + string key; + while (responseIter.next(val, key)) { + urlBag[key] = cast(string) cast(PlistString) val; + } + } else { + Plist plist = new Plist(); + plist.read(cast(string) content); + auto response = (cast(PlistElementDict) (cast(PlistElementDict) (plist[0]))["urls"]); - debug { - stderr.writeln("Cleanup..."); + foreach (key; response.keys()) { + urlBag[key] = (cast(PlistElementString) response[key]).value; + } } - - ret = pADIDispose(cpimPtr); - - if (ret) - throw new AnisetteException(ret); } - public void getOneTimePassword(bool writeMachineId = true)(out ubyte[] machineId, out ubyte[] oneTimePassword) { - debug { - stderr.writeln("getOneTimePassword called !"); + public void provision(ulong dsId) { + if (urlBag.length == 0) { + loadURLBag(); } - ubyte* midPtr; - uint midLen; - ubyte* otpPtr; - uint otpLen; - - auto ret = pADIOTPRequest( - /+accountID+/ dsId, - /+(out) machineID+/ &midPtr, // X-Apple-I-MD-M - /+(out) machineID length+/ &midLen, - /+(out) oneTimePW+/ &otpPtr, // X-Apple-I-MD - /+(out) oneTimePW length+/ &otpLen, - ); + import std.datetime.systime; - debug { - stderr.writefln("getOneTimePassword -> %d", ret); + httpClient.addRequestHeader("X-Apple-I-Client-Time", Clock.currTime().toISOExtString()); + string startProvisioningPlist = cast(string) post(urlBag["midStartProvisioning"], + " + + + +\tHeader +\t +\tRequest +\t + +", httpClient); + + scope string spimStr; + { + version (LibPlist) { + scope auto spimPlist = cast(PlistDict) Plist.fromXml(startProvisioningPlist); + scope auto spimResponse = cast(PlistDict) spimPlist["Response"]; + spimStr = cast(string) cast(PlistString) spimResponse["spim"]; + } else { + Plist spimPlist = new Plist(); + spimPlist.read(startProvisioningPlist); + PlistElementDict spimResponse = cast(PlistElementDict) (cast(PlistElementDict) (spimPlist[0]))["Response"]; + spimStr = (cast(PlistElementString) spimResponse["spim"]).value; + } } - if (ret) - throw new AnisetteException(ret); + scope ubyte[] spim = Base64.decode(spimStr); - static if (writeMachineId) { - machineId = midPtr[0..midLen].dup; - } - oneTimePassword = otpPtr[0..otpLen].dup; + scope auto cpim = adi.startProvisioning(dsId, spim); + scope (failure) adi.destroyProvisioning(cpim.session); - debug { - stderr.writeln("Cleaning up..."); + httpClient.addRequestHeader("X-Apple-I-Client-Time", Clock.currTime().toISOExtString()); + string endProvisioningPlist = cast(string) post(urlBag["midFinishProvisioning"], format!" + + + +\tHeader +\t +\tRequest +\t +\t\tcpim +\t\t%s +\t + +"(Base64.encode(cpim.clientProvisioningIntermediateMetadata)), httpClient); + + scope ulong routingInformation; + scope ubyte[] persistentTokenMetadata; + scope ubyte[] trustKey; + + { + version (LibPlist) { + scope PlistDict plist = cast(PlistDict) Plist.fromXml(endProvisioningPlist); + scope PlistDict spimResponse = cast(PlistDict) plist["Response"]; + routingInformation = to!ulong(cast(string) cast(PlistString) spimResponse["X-Apple-I-MD-RINFO"]); + persistentTokenMetadata = Base64.decode(cast(string) cast(PlistString) spimResponse["ptm"]); + trustKey = Base64.decode(cast(string) cast(PlistString) spimResponse["tk"]); + } else { + scope Plist plist = new Plist(); + plist.read(endProvisioningPlist); + scope PlistElementDict spimResponse = cast(PlistElementDict) (cast(PlistElementDict) (plist[0]))["Response"]; + + routingInformation = to!ulong((cast(PlistElementString) spimResponse["X-Apple-I-MD-RINFO"]).value); + persistentTokenMetadata = Base64.decode((cast(PlistElementString) spimResponse["ptm"]).value); + trustKey = Base64.decode((cast(PlistElementString) spimResponse["tk"]).value); + } } - ret = pADIDispose(midPtr); - - if (ret) - throw new AnisetteException(ret); - - ret = pADIDispose(otpPtr); + adi.endProvisioning(cpim.session, persistentTokenMetadata, trustKey); + } +} - if (ret) - throw new AnisetteException(ret); +void unwrapADIError(int error, string file = __FILE__, size_t line = __LINE__) { + if (error) { + throw new ADIException(error, file, line); } +} - public void getRoutingInformation(out ulong routingInfo) { - debug { - stderr.writeln("getRoutingInformation ignored"); - } +enum ADIError: int { + invalidParams = -45001, + invalidParams2 = -45002, + invalidTrustKey = -45003, + ptmTkNotMatchingState = -45006, + invalidInputDataParamHeader = -45018, + unknownAdiFunction = -45019, + invalidInputDataParamBody = -45020, + unknownSession = -45025, + emptySession = -45026, + invalidDataHeader = -45031, + dataTooShort = -45032, + invalidDataBody = -45033, + unknownADICallFlags = -45034, + timeError = -45036, + emptyHardwareIds = -45046, + filesystemError = -45054, + notProvisioned = -45061, + noProvisioningToErase = -45062, + pendingSession = -45063, + sessionAlreadyDone = -45066, + libraryLoadingFailed = -45075, +} - routingInfo = 17106176; +string toString(ADIError error) { + string formatString; + switch (cast(int) error) { + case -45001: + formatString = "invalid parameters (%d)"; + break; + case -45002: + formatString = "invalid parameters (for decipher) (%d)"; + break; + case -45003: + formatString = "invalid Trust Key (%d)"; + break; + case -45006: + formatString = "ptm and tk are not matching the transmitted cpim (%d)"; + break; + // -45017: exists (observed: iOS), unknown meaning + case -45018: + formatString = "invalid input data header (first uint) (pointer is correct tho) (%d)"; + break; + case -45019: + formatString = "vdfut768ig doesn't know the asked function (%d)"; + break; + case -45020: + formatString = "invalid input data (not the first uint) (%d)"; + break; + case -45025: + formatString = "unknown session (%d)"; + break; + case -45026: + formatString = "empty session (%d)"; + break; + case -45031: + formatString = "invalid data (header) (%d)"; + break; + case -45032: + formatString = "data too short (%d)"; + break; + case -45033: + formatString = "invalid data (body) (%d)"; + break; + case -45034: + formatString = "unknown ADI call flags (%d)"; + break; + case -45036: + formatString = "time error (%d)"; + break; + // -45044: exists (observed: macOS iTunes, from Google), unknown meaning + // -45045: probably a typo of -45054 + case -45046: + formatString = "identifier generation failure: empty hardware ids (%d)"; + break; + // -45048: exists (observed: windows iTunes, from Google), unknown meaning, resolved by adi file suppression + case -45054: + formatString = "generic libc/file manipulation error (%d)"; + break; + case -45061: + formatString = "not provisioned (%d)"; + break; + case -45062: + formatString = "cannot erase provisioning: not provisioned (%d)"; + break; + case -45063: + formatString = "provisioning first step is already pending (%d)"; + break; + case -45066: + formatString = "2nd step fail: session already consumed (%d)"; + break; + case -45075: + formatString = "library loading error (%d)"; + break; + // -45076: exists (observed: macOS iTunes, from Google), unknown meaning, seems related to backward compatibility between 12.6.x and 12.7 + default: + formatString = "unknown ADI error (%d)"; + break; } + return format(formatString, error); } -public class AnisetteException: Exception { +public class ADIException: Exception { + private ADIError errorCode; + this(int error, string file = __FILE__, size_t line = __LINE__) { - super(format!"ADI error: %s."(translateADIErrorCode(error)), file, line); + this.errorCode = cast(ADIError) error; + super(errorCode.toString(), file, line); } -} -enum knownErrorCodes = [ - -45001: "invalid parameters", - -45002: "invalid parameters (for decipher)", - -45003: "invalid Trust Key", - -45006: "ptm and tk are not matching the transmitted cpim", - // -45017: exists (observed: iOS), unknown meaning - -45018: "invalid input data header (first uint) (pointer is correct tho)", - -45019: "vdfut768ig doesn't know the asked function", - -45020: "invalid input data (not the first uint)", - -45025: "unknown session", - -45026: "empty session", - -45031: "invalid data (header)", - -45032: "data too short", - -45033: "invalid data (body)", - -45034: "unknown ADI call flags", - -45036: "time error", - // -45044: exists (observed: macOS iTunes, from Google), unknown meaning - // -45045: probably a typo of -45054 - -45046: "identifier generation failure: empty hardware ids", - // -45048: exists (observed: windows iTunes, from Google), unknown meaning, resolved by adi file suppression - -45054: "generic libc/file manipulation error", - -45061: "not provisioned", - -45062: "cannot erase provisioning: not provisioned", - -45063: "provisioning first step is already pending", - -45066: "2nd step fail: session already consumed", - -45075: "library loading error", - // -45076: exists (observed: macOS iTunes, from Google), unknown meaning, seems related to backward compatibility between 12.6.x and 12.7 -]; - -string translateADIErrorCode(int errorCode) { - foreach (knownErrorCode; knownErrorCodes.byKeyValue) { - if (errorCode == knownErrorCode.key) { - return format!"%s (%d)"(knownErrorCode.value, errorCode); - } + ADIError adiError() { + return errorCode; } - - return format!"%d"(errorCode); } diff --git a/lib/provision/android/id.d b/lib/provision/android/id.d deleted file mode 100644 index 7b95ee2..0000000 --- a/lib/provision/android/id.d +++ /dev/null @@ -1,30 +0,0 @@ -module provision.android.id; - -import std.digest: toHexString; -import File = std.file; -import std.format; -import std.random; -import std.range; -import std.stdio; -import std.uni; - -char[] genAndroidId() { - ubyte[] identifier; - try { - identifier = cast(ubyte[]) File.read("/etc/machine-id", 8); - - static foreach (index, appIdentifierBit; [ - 0x8b, 0x06, 0x7f, 0xdd, - 0x3c, 0xbf, 0x40, 0x8c, - 0x90, 0x64, 0xc7, 0x5a, - 0x9a, 0xc4, 0xc7, 0x8b - ]) { - identifier[index % 8] ^= appIdentifierBit; - } - } catch (File.FileException) { - stderr.writeln("WARN: Generation of unique identifier failed, using a random one instead. "); - identifier = cast(ubyte[]) rndGen.take(2).array; - } - - return identifier.toHexString().toLower().dup; -} diff --git a/lib/provision/androidlibrary.d b/lib/provision/androidlibrary.d index 0774803..a242c52 100644 --- a/lib/provision/androidlibrary.d +++ b/lib/provision/androidlibrary.d @@ -13,6 +13,7 @@ import std.conv; import std.experimental.allocator; import std.experimental.allocator.mallocator; import std.experimental.allocator.mmap_allocator; +import std.functional; import std.mmfile; import std.path; import std.random; @@ -21,7 +22,7 @@ import std.stdio; import std.string; import std.traits; -public struct AndroidLibrary { +public class AndroidLibrary { package MmFile elfFile; package void[] allocation; @@ -29,9 +30,15 @@ public struct AndroidLibrary { package char[] dynamicStringTable; package ElfW!"Sym"[] dynamicSymbolTable; package SymbolHashTable hashTable; + package AndroidLibrary[] loadedLibraries; - public this(string libraryName) { + public void*[string] hooks; + + private ElfW!"Shdr"[] relocationSections; + + public this(string libraryName, void*[string] hooks = null) { elfFile = new MmFile(libraryName); + if (hooks) this.hooks = hooks; auto elfHeader = elfFile.identify!(ElfW!"Ehdr")(0); auto programHeaders = elfFile.identifyArray!(ElfW!"Phdr")(elfHeader.e_phoff, elfHeader.e_phnum); @@ -71,6 +78,10 @@ public struct AndroidLibrary { if (mmapped_alloc == MAP_FAILED) { throw new LoaderException("Cannot allocate the memory: " ~ to!string(errno)); } + memoryTable[MemoryBlock(cast(size_t) mmapped_alloc, cast(size_t) mmapped_alloc + allocSize)] = this; + debug { + stderr.writefln("Allocating %x - %x for %s", cast(size_t) mmapped_alloc, cast(size_t) mmapped_alloc + allocSize, libraryName); + } allocation = mmapped_alloc[0..allocSize]; size_t fileStart; @@ -116,9 +127,11 @@ public struct AndroidLibrary { break; case SHT_REL: this.relocate!(ElfW!"Rel")(sectionHeader); + relocationSections ~= sectionHeader; break; case SHT_RELA: this.relocate!(ElfW!"Rela")(sectionHeader); + relocationSections ~= sectionHeader; break; default: break; @@ -127,12 +140,29 @@ public struct AndroidLibrary { } ~this() { + foreach (library; loadedLibraries) { + destroy(library); + } + if (allocation) { munmap(allocation.ptr, allocation.length); } } - @disable this(this); + public void relocate() { + foreach (relocationSection; relocationSections) { + switch (relocationSection.sh_type) { + case SHT_REL: + this.relocate!(ElfW!"Rel")(relocationSection); + break; + case SHT_RELA: + this.relocate!(ElfW!"Rela")(relocationSection); + break; + default: + break; + } + } + } private void relocate(RelocationType)(ref ElfW!"Shdr" shdr) { auto relocations = this.elfFile.identifyArray!(RelocationType)(shdr.sh_offset, shdr.sh_size / RelocationType.sizeof); @@ -184,10 +214,20 @@ public struct AndroidLibrary { return cast(string) fromStringz(§ionNamesTable[section.sh_name]); } + void* getSymbolImplementation(string symbolName) { + void** hook = symbolName in hooks; + if (hook) { + return *hook; + } + + import provision.symbols; + return lookupSymbol(symbolName); + } + void* load(string symbolName) { ElfW!"Sym" sym; if (hashTable) { - sym = hashTable.lookup(symbolName, &this); + sym = hashTable.lookup(symbolName, this); } else { foreach (symbol; dynamicSymbolTable) { if (getSymbolName(symbol) == symbolName) { @@ -200,8 +240,32 @@ public struct AndroidLibrary { } } +private struct MemoryBlock { + size_t start; + size_t end; +} + +private __gshared AndroidLibrary[MemoryBlock] memoryTable; +AndroidLibrary memoryOwner(size_t address) { + foreach(memoryBlock; memoryTable.keys()) { + if (address > memoryBlock.start && address < memoryBlock.end) { + return memoryTable[memoryBlock]; + } + } + + return null; +} + +import core.sys.linux.execinfo; +pragma(inline, true) AndroidLibrary rootLibrary() { + enum MAXFRAMES = 4; + void*[MAXFRAMES] callstack; + auto numframes = backtrace(callstack.ptr, MAXFRAMES); + return memoryOwner(cast(size_t) callstack[numframes - 1]); +} + interface SymbolHashTable { - ElfW!"Sym" lookup(string symbolName, AndroidLibrary* library); + ElfW!"Sym" lookup(string symbolName, AndroidLibrary library); } package class ElfHashTable: SymbolHashTable { @@ -227,17 +291,14 @@ package class ElfHashTable: SymbolHashTable { uint h = 0, g; foreach (c; name) { - h = (h << 4) + c; - if ((g = h & 0xf0000000) != 0) { - h ^= g >> 24; - } - h &= ~g; + h = 16 * h + c; + h ^= h >> 24 & 0xf0; } - return h; + return h & 0xfffffff; } - ElfW!"Sym" lookup(string symbolName, AndroidLibrary* library) { + ElfW!"Sym" lookup(string symbolName, AndroidLibrary library) { auto targetHash = hash(symbolName); scope ElfW!"Sym" symbol; @@ -285,7 +346,7 @@ package class GnuHashTable: SymbolHashTable { return h; } - ElfW!"Sym" lookup(string symbolName, AndroidLibrary* library) { + ElfW!"Sym" lookup(string symbolName, AndroidLibrary library) { auto targetHash = hash(symbolName); auto bucket = buckets[targetHash % table.nbuckets]; @@ -378,11 +439,6 @@ RetType reinterpret(RetType, FromType)(FromType[] obj) { return (cast(RetType[]) obj)[0]; } -private static void* getSymbolImplementation(string symbolName) { - import provision.symbols; - return lookupSymbol(symbolName); -} - class LoaderException: Exception { this(string message, string file = __FILE__, size_t line = __LINE__) { super("Cannot load library: " ~ message, file, line); diff --git a/lib/provision/c.d b/lib/provision/c.d deleted file mode 100644 index daebee3..0000000 --- a/lib/provision/c.d +++ /dev/null @@ -1,83 +0,0 @@ -module provision.c; - -import core.runtime; -import core.stdc.string; -import provision.adi; -import std.experimental.allocator; -import std.experimental.allocator.mallocator; -import std.string; - -extern(C) __gshared { - void provision_init() { - Runtime.initialize(); - } - - void provision_dispose() { - Runtime.terminate(); - } - - ADI* provision_adi_create(immutable char* path) { - return Mallocator.instance.make!ADI(path.fromStringz); - } - - ADI* provision_adi_create_with_identifier(immutable char* path, char* identifier) { - return Mallocator.instance.make!ADI(path.fromStringz, identifier.fromStringz); - } - - void provision_adi_dispose(ADI* handle) { - Mallocator.instance.dispose(handle); - } - - immutable(char)* provision_adi_get_client_info(ADI* handle) { - return handle.clientInfo.toStringz; - } - - void provision_adi_set_client_info(ADI* handle, immutable(char)* value) { - handle.clientInfo = value.fromStringz; - } - - immutable(char)* provision_adi_get_serial_no(ADI* handle) { - return handle.serialNo.toStringz; - } - - void provision_adi_set_serial_no(ADI* handle, immutable(char)* value) { - handle.serialNo = value.fromStringz; - } - - immutable(char)* provision_adi_get_provision_path(ADI* handle) { - return handle.provisionPath.toStringz; - } - - immutable(char)* provision_adi_get_device_id(ADI* handle) { - return handle.deviceId.toStringz; - } - - immutable(char)* provision_adi_get_local_user_uuid(ADI* handle) { - return handle.localUserUUID.toStringz; - } - - ulong provision_adi_provision_device(ADI* handle, ulong* routingInfo) { - try { - handle.provisionDevice(*routingInfo); - } catch (Throwable t) { - return t.toHash(); - } - return 0; - } - - ulong provision_adi_get_one_time_password(ADI* handle, ubyte** mid_arr, ulong* mid_length, ubyte** otp_arr, ulong* otp_length) { - try { - ubyte[] mid; - ubyte[] otp; - handle.getOneTimePassword(mid, otp); - *mid_arr = mid.ptr; - *mid_length = mid.length; - *otp_arr = otp.ptr; - *otp_length = otp.length; - } catch (Throwable t) { - return t.toHash(); - } - return 0; - } -} - diff --git a/lib/provision/package.d b/lib/provision/package.d index d152838..4307c05 100644 --- a/lib/provision/package.d +++ b/lib/provision/package.d @@ -1,3 +1,4 @@ module provision; +enum provisionVersion = "2.0.0"; public import provision.adi; diff --git a/lib/provision/symbols.d b/lib/provision/symbols.d index b165384..adc07b2 100644 --- a/lib/provision/symbols.d +++ b/lib/provision/symbols.d @@ -1,5 +1,6 @@ module provision.symbols; +import core.memory; import core.stdc.errno; import core.stdc.stdlib; import core.stdc.string; @@ -8,6 +9,7 @@ import core.sys.posix.sys.stat; import core.sys.posix.sys.time; import core.sys.posix.unistd; import provision.androidlibrary; +import std.algorithm.mutation; import std.experimental.allocator; import std.experimental.allocator.mallocator; import std.random; @@ -38,34 +40,40 @@ private extern (C) noreturn undefinedSymbol() { throw new UndefinedSymbolException(); } -private extern (C) AndroidLibrary* dlopenWrapper(const char* name) { - stderr.writeln("Attempting to load ", name.fromStringz()); +private extern (C) AndroidLibrary dlopenWrapper(const char* name) { + debug { + stderr.writeln("Attempting to load ", name.fromStringz()); + } try { - return Mallocator.instance.make!AndroidLibrary(cast(string) name.fromStringz()); + auto caller = rootLibrary(); + auto lib = new AndroidLibrary(cast(string) name.fromStringz(), caller.hooks); + caller.loadedLibraries ~= lib; + return lib; } catch (Throwable) { return null; } } -private extern (C) void* dlsymWrapper(AndroidLibrary* library, const char* symbolName) { - stderr.writeln("Attempting to load ", symbolName.fromStringz()); +private extern (C) void* dlsymWrapper(AndroidLibrary library, const char* symbolName) { + debug { + stderr.writeln("Attempting to load symbol ", symbolName.fromStringz()); + } return library.load(cast(string) symbolName.fromStringz()); } -private extern (C) void dlcloseWrapper(AndroidLibrary* library) { - return Mallocator.instance.dispose(library); +private extern (C) void dlcloseWrapper(AndroidLibrary library) { + if (library) { + rootLibrary().loadedLibraries.remove!((lib) => lib == library); + destroy(library); + } } -public bool doTimeTravel = false; -public timeval targetTime; - -private extern (C) ReturnType!gettimeofday gettimeofday_timeTravel(timeval* timeval, void* ptr) { - auto ret = gettimeofday(timeval, ptr); - if (doTimeTravel) { - *timeval = targetTime; - } +private extern (C) void* malloc_GC_replacement(size_t sz) { + return GC.malloc(sz); +} - return ret; +private extern (C) void free_GC_replacement(void* ptr) { + return GC.free(ptr); } // gperf generated code: @@ -140,15 +148,15 @@ package void* lookupSymbol(string str) { {"strncpy", &strncpy}, {"pthread_mutex_lock", &emptyStub}, {"ftruncate", &ftruncate}, {"write", &write}, {"pthread_rwlock_unlock", &emptyStub}, - {"pthread_rwlock_destroy", &emptyStub}, {""}, {"free", &free}, + {"pthread_rwlock_destroy", &emptyStub}, {""}, {"free", &free_GC_replacement}, {"fstat", &fstat}, {"pthread_rwlock_wrlock", &emptyStub}, {"__errno", &errno}, {""}, {"pthread_rwlock_init", &emptyStub}, {"pthread_mutex_unlock", &emptyStub}, {"pthread_rwlock_rdlock", &emptyStub}, { "gettimeofday", - &gettimeofday_timeTravel + &gettimeofday }, {""}, {"read", &read}, - {"mkdir", &mkdir}, {"malloc", &malloc}, {""}, {""}, {""}, {""}, + {"mkdir", &mkdir}, {"malloc", &malloc_GC_replacement}, {""}, {""}, {""}, {""}, {"__system_property_get", &__system_property_get_impl}, {""}, {""}, {""}, {"arc4random", &arc4random_impl}, ]; diff --git a/mkcassette/app.d b/mkcassette/app.d new file mode 100644 index 0000000..da18e2e --- /dev/null +++ b/mkcassette/app.d @@ -0,0 +1,223 @@ +module app; + +import core.sys.posix.sys.time; +import std.algorithm; +import std.array; +import std.base64; +import std.datetime.stopwatch: StopWatch; +import file = std.file; +import std.format; +import std.getopt; +import std.math; +import std.mmfile; +import std.net.curl; +import std.parallelism; +import std.path; +import std.range; +import std.stdio; +import std.zip; + +import provision; +import provision.androidlibrary; +import provision.symbols; + +import constants; + +version (X86_64) { + enum string architectureIdentifier = "x86_64"; +} else version (X86) { + enum string architectureIdentifier = "x86"; +} else version (AArch64) { + enum string architectureIdentifier = "arm64-v8a"; +} else version (ARM) { + enum string architectureIdentifier = "armeabi-v7a"; +} else { + static assert(false, "Architecture not supported :("); +} + +struct AnisetteCassetteHeader { + align(1): + ubyte[7] magicHeader = [0x69, 'C', 'A', 'S', 'S', 'T', 'E']; + ubyte formatVersion = 0; + ulong baseTime; + + ubyte[64] machineId; +} + +static assert(AnisetteCassetteHeader.sizeof % 16 == 0); + +__gshared ulong origTime; +int main(string[] args) { + writeln(mkcassetteBranding, " v", provisionVersion); + + char[] identifier = cast(char[]) "ba10defe42ea69ff"; + string configurationPath = expandTilde("~/.config/Provision"); + string outputFile = "./otp-file.acs"; + ulong days = 90; + bool onlyInit = false; + bool apkDownloadAllowed = true; + + // Parse command-line arguments + auto helpInformation = getopt( + args, + "i|identifier", format!"The identifier used for the cassette (default: %s)"(identifier), &identifier, + "a|adi-path", format!"Where the provisioning information should be stored on the computer (default: %s)"(configurationPath), &configurationPath, + "d|days", format!"Number of days in the cassette (default: %s)"(days), &days, + "o|output", format!"Output location (default: %s)"(outputFile), &outputFile, + "init-only", format!"Download libraries and exit (default: %s)"(onlyInit), &onlyInit, + "can-download", format!"If turned on, may download the dependencies automatically (default: %s)"(apkDownloadAllowed), &apkDownloadAllowed, + ); + + if (helpInformation.helpWanted) { + defaultGetoptPrinter("This program allows you to host anisette through libprovision!", helpInformation.options); + return 0; + } + + if (!file.exists(configurationPath)) { + file.mkdirRecurse(configurationPath); + } + + string libraryPath = configurationPath.buildPath("lib/" ~ architectureIdentifier); + + auto coreADIPath = libraryPath.buildPath("libCoreADI.so"); + auto SSCPath = libraryPath.buildPath("libstoreservicescore.so"); + + // Download APK if needed + if (!(file.exists(coreADIPath) && file.exists(SSCPath)) && apkDownloadAllowed) { + auto http = HTTP(); + http.onProgress = (size_t dlTotal, size_t dlNow, size_t ulTotal, size_t ulNow) { + write("Downloading libraries from Apple servers... "); + if (dlTotal != 0) { + write((dlNow * 100)/dlTotal, "% \r"); + } else { + // Convert dlNow (in bytes) to a human readable string + float downloadedSize = dlNow; + + enum units = ["B", "kB", "MB", "GB", "TB"]; + int i = 0; + while (downloadedSize > 1000 && i < units.length - 1) { + downloadedSize = floor(downloadedSize) / 1000; + ++i; + } + + write(downloadedSize, units[i], " \r"); + } + return 0; + }; + auto apkData = get!(HTTP, ubyte)(nativesUrl, http); + writeln("Downloading libraries from Apple servers... done! \r"); + auto apk = new ZipArchive(apkData); + auto dir = apk.directory(); + + if (!file.exists(libraryPath)) { + file.mkdirRecurse(libraryPath); + } + file.write(coreADIPath, apk.expand(dir["lib/" ~ architectureIdentifier ~ "/libCoreADI.so"])); + file.write(SSCPath, apk.expand(dir["lib/" ~ architectureIdentifier ~ "/libstoreservicescore.so"])); + } + + if (onlyInit) { + return 0; + } + + // 1 per minute + auto numberOfOTP = days*24*60; + + ubyte[] mid; + ubyte[] nothing; + + // We store the real time in a shared variable, and create a thread-local time variable. + __gshared timeval origTimeVal; + gettimeofday(&origTimeVal, null); + origTime = origTimeVal.tv_sec; + targetTime = taskPool.workerLocalStorage(origTimeVal); + + // Initializing ADI and machine if it has not already been made. + Device device = new Device(configurationPath.buildPath("/dev/null")); + { + ADI adi = new ADI("lib/" ~ architectureIdentifier); + adi.provisioningPath = configurationPath; + + if (!device.initialized) { + stderr.write("Creating machine... "); + + import std.digest; + import std.digest.sha; + import std.random; + import std.range; + import std.uni; + import std.uuid; + device.serverFriendlyDescription = " "; + device.uniqueDeviceIdentifier = randomUUID().toString().toUpper(); + device.adiIdentifier = sha1Of(device.uniqueDeviceIdentifier).toHexString()[0..16].toLower(); + device.localUserUUID = sha256Of(device.uniqueDeviceIdentifier).toHexString().toUpper(); + + stderr.writeln("done !"); + } + + adi.identifier = device.adiIdentifier; + + ProvisioningSession provisioningSession = new ProvisioningSession(adi, device); + provisioningSession.provision(-2); + + mid = adi.requestOTP(-2).machineIdentifier; + } + + auto adi = taskPool().workerLocalStorage!ADI({ + // We hook the gettimeofday function in the library to change the date. + AndroidLibrary storeServicesCore = new AndroidLibrary(SSCPath, [ + "gettimeofday": cast(void*) &gettimeofday_timeTravel + ]); + + ADI adi = new ADI(libraryPath, storeServicesCore); + + adi.provisioningPath = configurationPath; + adi.identifier = device.adiIdentifier; + + return adi; + }()); + + StopWatch sw; + writeln("Starting generation of ", numberOfOTP, " otps (", days, " days) with ", totalCPUs, " threads."); + sw.start(); + + auto anisetteCassetteHeader = AnisetteCassetteHeader(); + anisetteCassetteHeader.baseTime = origTime; + anisetteCassetteHeader.machineId[0..mid.length] = mid; + + auto anisetteCassetteHeaderBytes = (cast(ubyte*) &anisetteCassetteHeader)[0..AnisetteCassetteHeader.sizeof]; + + // The file consists of 1 header and then all the 16-bytes long OTPs, so we make a memory-mapped file of the correct size. + scope otpFile = new MmFile(outputFile, MmFile.Mode.readWriteNew, AnisetteCassetteHeader.sizeof + 16 * numberOfOTP * ubyte.sizeof, null); + scope acs = cast(ubyte[]) otpFile[0..$]; + acs[0..AnisetteCassetteHeader.sizeof] = anisetteCassetteHeaderBytes; + + // we take every 16 bytes chunk of the OTP part of the file, and iterate concurrently through it. + foreach (idx, otp; parallel(std.range.chunks(cast(ubyte[]) acs[AnisetteCassetteHeader.sizeof..$], 16))) { + scope localAdi = adi.get(); + scope time = targetTime.get(); + + time.tv_sec = origTime + idx * 30; + targetTime.get() = time; + + otp[] = localAdi.requestOTP(-2).oneTimePassword[8..24]; + + assert(targetTime.get().tv_sec == origTime + idx * 30); + } + + sw.stop(); + + writeln("Success. File written at ", outputFile, ", duration: ", sw.peek()); + + return 0; +} + +import core.sys.posix.sys.time; +import std.parallelism; + +public __gshared TaskPool.WorkerLocalStorage!timeval targetTime; + +private extern (C) int gettimeofday_timeTravel(timeval* timeval, void* ptr) { + *timeval = targetTime.get(); + return 0; +} diff --git a/mkcassette/constants.d b/mkcassette/constants.d new file mode 100644 index 0000000..d475851 --- /dev/null +++ b/mkcassette/constants.d @@ -0,0 +1,4 @@ +module constants; + +enum mkcassetteBranding = "mkcassette"; +enum nativesUrl = "https://apps.mzstatic.com/content/android-apple-music-apk/applemusic.apk"; diff --git a/retrieve_headers/app.d b/retrieve_headers/app.d index c86cbc3..a17f0b4 100644 --- a/retrieve_headers/app.d +++ b/retrieve_headers/app.d @@ -2,26 +2,61 @@ module app; import std.array; import std.base64; +import std.conv: to; import std.format; import std.path; +import file = std.file; import std.stdio; import provision; +version (X86_64) { + enum string architectureIdentifier = "x86_64"; +} else version (X86) { + enum string architectureIdentifier = "x86"; +} else version (AArch64) { + enum string architectureIdentifier = "arm64-v8a"; +} else version (ARM) { + enum string architectureIdentifier = "armeabi-v7a"; +} else { + static assert(false, "Architecture not supported :("); +} + int main(string[] args) { - ADI* adi = new ADI(expandTilde("~/.adi")); + string configurationPath = expandTilde("~/.config/Provision/"); + if (!file.exists(configurationPath)) { + file.mkdirRecurse(configurationPath); + } + + ADI adi = new ADI("lib/" ~ architectureIdentifier); + adi.provisioningPath = configurationPath; + Device device = new Device(configurationPath.buildPath("device.json")); + + if (!device.initialized) { + stderr.write("Creating machine... "); - ulong rinfo; - if (!adi.isMachineProvisioned()) { + import std.digest; + import std.random; + import std.range; + import std.uni; + import std.uuid; + device.serverFriendlyDescription = " "; + device.uniqueDeviceIdentifier = randomUUID().toString().toUpper(); + device.adiIdentifier = (cast(ubyte[]) rndGen.take(2).array()).toHexString().toLower(); + device.localUserUUID = (cast(ubyte[]) rndGen.take(8).array()).toHexString().toUpper(); + + stderr.writeln("done !"); + } + + adi.identifier = device.adiIdentifier; + if (!adi.isMachineProvisioned(-2)) { stderr.write("Machine requires provisioning... "); - adi.provisionDevice(rinfo); + + ProvisioningSession provisioningSession = new ProvisioningSession(adi, device); + provisioningSession.provision(-2); stderr.writeln("done !"); - } else { - adi.getRoutingInformation(rinfo); } - ubyte[] mid; - ubyte[] otp; - adi.getOneTimePassword(mid, otp); + auto otp = adi.requestOTP(-2); import std.datetime.systime; auto time = Clock.currTime(); @@ -39,15 +74,15 @@ int main(string[] args) { "X-Apple-Locale": "en_US", "X-Mme-Device-Id": "%s" }`( - Base64.encode(otp), - Base64.encode(mid), - rinfo, - adi.localUserUUID, - adi.serialNo, - adi.clientInfo, + Base64.encode(otp.oneTimePassword), + Base64.encode(otp.machineIdentifier), + 17106176, + device.localUserUUID, + "0", + device.serverFriendlyDescription, time.toISOExtString.split('.')[0] ~ "Z", time.timezone.dstName, - adi.deviceId + device.uniqueDeviceIdentifier ) );