From 97f4476bdda308cfd340f8346294b78616d1f7dc Mon Sep 17 00:00:00 2001 From: Kabir Oberai Date: Thu, 22 Oct 2026 04:15:22 -0400 Subject: [PATCH] gsa: use macOS Omnisette --- Package.resolved | 11 +- Package.swift | 2 - .../GrandSlam/Anisette/ADIDataProvider.swift | 10 +- .../Anisette/OmnisetteADIProvider.swift | 3 +- .../GrandSlam/Anisette/XADIProvider.swift | 198 ------------------ .../Requests/GrandSlamTwoFactorRequest.swift | 2 +- Sources/XToolSupport/AuthCommand.swift | 6 +- 7 files changed, 9 insertions(+), 223 deletions(-) delete mode 100644 Sources/XKit/GrandSlam/Anisette/XADIProvider.swift diff --git a/Package.resolved b/Package.resolved index 4cc2f508..8c4b3ec6 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "7ae7f57a6038e121dec04557563bc2a3412c9e46e473d9c84f3e3ac82be608f9", + "originHash" : "cacf44740030c30db6fcb586874b2f3bfbe8823feefae664855c2953ac612a37", "pins" : [ { "identity" : "aexml", @@ -406,15 +406,6 @@ "version" : "2.16.2" } }, - { - "identity" : "xadi", - "kind" : "remoteSourceControl", - "location" : "https://github.com/xtool-org/xadi", - "state" : { - "revision" : "3db108d3cafe719a4b2e9d13a9c8c52770e20aa7", - "version" : "0.4.2" - } - }, { "identity" : "xcode-project-format", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index 199a142c..1b2f4910 100644 --- a/Package.swift +++ b/Package.swift @@ -51,7 +51,6 @@ let package = Package( .package(url: "https://github.com/xtool-org/xtool-core", .upToNextMinor(from: "1.5.0")), .package(url: "https://github.com/xtool-org/SwiftyMobileDevice", .upToNextMinor(from: "1.7.0")), .package(url: "https://github.com/xtool-org/zsign", .upToNextMinor(from: "1.9.0")), - .package(url: "https://github.com/xtool-org/xadi", .upToNextMinor(from: "0.4.2")), .package(url: "https://github.com/apple/swift-system", from: "1.4.0"), .package(url: "https://github.com/apple/swift-http-types", from: "1.3.1"), @@ -126,7 +125,6 @@ let package = Package( "DeveloperAPI", "CXKit", "XUtils", - .product(name: "XADI", package: "xadi", condition: .when(platforms: [.linux, .macOS])), .product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"), .product(name: "Dependencies", package: "swift-dependencies"), .product(name: "SwiftyMobileDevice", package: "SwiftyMobileDevice"), diff --git a/Sources/XKit/GrandSlam/Anisette/ADIDataProvider.swift b/Sources/XKit/GrandSlam/Anisette/ADIDataProvider.swift index d4104767..d259a461 100644 --- a/Sources/XKit/GrandSlam/Anisette/ADIDataProvider.swift +++ b/Sources/XKit/GrandSlam/Anisette/ADIDataProvider.swift @@ -44,13 +44,7 @@ private struct UnimplementedRawADIProvider: RawADIProvider { private enum RawADIProviderDependencyKey: DependencyKey { static let testValue: RawADIProvider = UnimplementedRawADIProvider() - static let liveValue: RawADIProvider = { - #if canImport(XADI) - return XADIProvider() - #else - return OmnisetteADIProvider() - #endif - }() + static let liveValue: RawADIProvider = OmnisetteADIProvider() } extension DependencyValues { @@ -242,7 +236,7 @@ public struct ADIDataProvider: AnisetteDataProvider { } private static var providerID: String? { - "2" + "mac-omnisette" } public var providerID: String? { Self.providerID } diff --git a/Sources/XKit/GrandSlam/Anisette/OmnisetteADIProvider.swift b/Sources/XKit/GrandSlam/Anisette/OmnisetteADIProvider.swift index 63eaefba..2bd146c3 100644 --- a/Sources/XKit/GrandSlam/Anisette/OmnisetteADIProvider.swift +++ b/Sources/XKit/GrandSlam/Anisette/OmnisetteADIProvider.swift @@ -6,10 +6,9 @@ struct OmnisetteADIProvider: RawADIProvider { // should implement v3 of https://github.com/SideStore/omnisette-server // list: https://servers.sidestore.io/servers.json - // e.g. https://ani.sidestore.io private let url: URL init( - url: URL = URL(string: "https://ani.sidestore.io")! // URL(string: "http://localhost:6969")! + url: URL = URL(string: "https://mac-ani.jkcoxson.com")! ) { self.url = url } diff --git a/Sources/XKit/GrandSlam/Anisette/XADIProvider.swift b/Sources/XKit/GrandSlam/Anisette/XADIProvider.swift deleted file mode 100644 index 03189d53..00000000 --- a/Sources/XKit/GrandSlam/Anisette/XADIProvider.swift +++ /dev/null @@ -1,198 +0,0 @@ -#if canImport(XADI) - -import Foundation -import XADI -import Dependencies -import Subprocess - -public actor XADIProvider: RawADIProvider { - @MainActor private static var loadTask: Task? - @Dependency(\.httpClient) private var httpClient - @Dependency(\.persistentDirectory) private var persistentDirectory - - private var directory: URL { persistentDirectory.appendingPathComponent("Anisette") } - - public init() {} - - private func _loadADI(id: UUID) async throws { - let dir = self.directory - let libDir = dir.appending(path: "libs") - if !libDir.dirExists { - let tmp = dir.appending(path: "tmp") - try? FileManager.default.createDirectory(at: tmp, withIntermediateDirectories: true) - - let applemusic = tmp.appending(path: "applemusic.apk") - if !applemusic.exists { - print("[Downloading libraries] ...", terminator: "") - fflush(stdoutSafe) - let url = URL(string: "https://apps.mzstatic.com/content/android-apple-music-apk/applemusic.apk")! - defer { print("") } - let data = try await httpClient.makeRequest(HTTPRequest(url: url)) { progress in - guard let progress else { return } - let progString = "\(Int(progress * 100))%".padding(toLength: 4, leading: true) - print("\r[Downloading libraries] \(progString)", terminator: "") - fflush(stdoutSafe) - }.body - try data.write(to: applemusic) - } - - #if arch(arm64) - let arch = "arm64-v8a" - #elseif arch(x86_64) - let arch = "x86_64" - #else - #error("Unsupported architecture") - #endif - let archDir = "lib/\(arch)" - - // TODO: Use ZIPFoundation - try await Subprocess.run( - .name("unzip"), - arguments: [ - "-q", - applemusic.path, - "\(archDir)/libCoreADI.so", - "\(archDir)/libstoreservicescore.so", - "-d", tmp.path - ], - output: .discarded - ).checkSuccess() - try FileManager.default.moveItem( - at: tmp.appending(path: archDir), - to: libDir - ) - try? FileManager.default.removeItem(at: tmp) - } - - xadi_Load(strdup(libDir.path())) - - let rawID = id.uuidString.replacingOccurrences(of: "-", with: "").prefix(16).lowercased() - try check(xadi_SetAndroidID(rawID, UInt32(rawID.utf8.count))) - - let adi = dir.appending(path: "adi") - try? FileManager.default.createDirectory(at: adi, withIntermediateDirectories: true) - try check(xadi_SetProvisioningPath(adi.path())) - } - - @MainActor private func loadADI(id: UUID) async throws { - if let loadTask = Self.loadTask { - try await loadTask.value - return - } - let loadTask = Task { try await _loadADI(id: id) } - Self.loadTask = loadTask - try await loadTask.value - } - - /// XADI's Anisette data comes from Android ADI, so every GSA request (including - /// provisioning) has to identify as an Android device or GSA rejects it (-80045). - public func clientInfo() async throws -> String { - """ - - """ - } - - public func startProvisioning(spim: Data, userID: UUID) async throws -> (any RawADIProvisioningSession, Data) { - try await loadADI(id: userID) - - var sessionID: UInt32 = 0 - var cpimBytes: UnsafeMutableRawPointer? - var cpimLen: UInt32 = 0 - try check(spim.withUnsafeBytes { spimBytes in - xadi_ProvisioningStart( - UInt64(bitPattern: -2), - spimBytes.baseAddress!, UInt32(spimBytes.count), - &cpimBytes, &cpimLen, - &sessionID - ) - }) - let data = Data(UnsafeRawBufferPointer(start: cpimBytes, count: Int(cpimLen))) - _ = cpimBytes.map { xadi_Dispose($0) } - return ( - ADISession( - sessionID: sessionID, - adiDirectory: directory.appending(path: "adi") - ), - data - ) - } - - private struct ADISession: RawADIProvisioningSession { - let sessionID: UInt32 - let adiDirectory: URL - - func endProvisioning(routingInfo: UInt64, ptm: Data, tk: Data) async throws -> Data { - try check(ptm.withUnsafeBytes { ptmBuf in - tk.withUnsafeBytes { tkBuf in - xadi_ProvisioningEnd( - sessionID, - ptmBuf.baseAddress!, UInt32(ptmBuf.count), - tkBuf.baseAddress!, UInt32(tkBuf.count) - ) - } - }) - let file = adiDirectory.appending(path: "adi.pb") - // adi saves the file as 0o034 (group wx, public r) by default - // which is a... strange choice that prevents us from reading - // or writing by default. Since we're the owner, we can easily - // change this though. - try FileManager.default.setAttributes( - // rw for owner - [.posixPermissions: 0o600], - ofItemAtPath: file.path - ) - return try Data(contentsOf: file) - } - } - - public func requestOTP( - userID: UUID, - routingInfo: inout UInt64, - provisioningInfo: Data - ) async throws -> (machineID: Data, otp: Data) { - try await loadADI(id: userID) - - var midBytes: UnsafeMutableRawPointer? - var midLen: UInt32 = 0 - var otpBytes: UnsafeMutableRawPointer? - var otpLen: UInt32 = 0 - - let adiDirectory = directory.appending(path: "adi") - try? FileManager.default.createDirectory(at: adiDirectory, withIntermediateDirectories: true) - let adiFile = adiDirectory.appending(path: "adi.pb") - try provisioningInfo.write(to: adiFile) - - try check(xadi_OTPRequest( - UInt64(bitPattern: -2), - &midBytes, &midLen, - &otpBytes, &otpLen - )) - - let mid = Data(UnsafeRawBufferPointer(start: midBytes, count: Int(midLen))) - let otp = Data(UnsafeRawBufferPointer(start: otpBytes, count: Int(otpLen))) - - _ = midBytes.map { xadi_Dispose($0) } - _ = otpBytes.map { xadi_Dispose($0) } - - return (machineID: mid, otp: otp) - } -} - -private func check(_ adiStatus: Int32) throws { - guard adiStatus == 0 else { - throw ADIError(code: Int(adiStatus)) - } -} - -extension StringProtocol { - fileprivate func padding( - toLength minLength: Int, - withPad pad: Character = " ", - leading: Bool = false - ) -> String { - let justPadding = String(repeating: pad, count: Swift.max(minLength - count, 0)) - return leading ? justPadding + self : self + justPadding - } -} - -#endif diff --git a/Sources/XKit/GrandSlam/Requests/GrandSlamTwoFactorRequest.swift b/Sources/XKit/GrandSlam/Requests/GrandSlamTwoFactorRequest.swift index 67e02598..03291136 100644 --- a/Sources/XKit/GrandSlam/Requests/GrandSlamTwoFactorRequest.swift +++ b/Sources/XKit/GrandSlam/Requests/GrandSlamTwoFactorRequest.swift @@ -20,7 +20,7 @@ extension GrandSlamTwoFactorRequest { func configure(request: inout HTTPRequest, anisetteData: AnisetteData) { request.headerFields[.accept] = "application/x-buddyml" request.headerFields[.contentType] = "application/x-plist" - request.headerFields[.userAgent] = "" + request.headerFields[.userAgent] = DeviceInfo.akdUserAgent request.headerFields[.init("X-Apple-App-Info")!] = "com.apple.gs.xcode.auth" request.headerFields[.init(DeviceInfo.xcodeVersionKey)!] = DeviceInfo.xcodeVersion request.headerFields[.init("X-Apple-Identity-Token")!] = loginData.identityToken diff --git a/Sources/XToolSupport/AuthCommand.swift b/Sources/XToolSupport/AuthCommand.swift index e10aac3a..4a88b91d 100644 --- a/Sources/XToolSupport/AuthCommand.swift +++ b/Sources/XToolSupport/AuthCommand.swift @@ -78,8 +78,10 @@ struct AuthOperation { private func logInWithPassword() async throws -> AuthToken { print(""" Heads up: - This works by emulating a Pixel phone/Windows PC to interact with Apple services; - consider using a secondary Apple ID. Credentials are only ever sent to Apple. + This works by emulating a virtual Mac to interact with Apple services. + We use a third-party service to create a randomized device ID token + (Anisette Data) but your credentials are only ever sent to Apple. + Still, consider using a secondary Apple ID since this is a private API. """) let username = try await Console.promptRequired("Apple ID: ", existing: username)