2020-01-08 12:41:02 -08:00
//
// F e t c h A n i s e t t e D a t a O p e r a t i o n . s w i f t
// A l t S t o r e
//
// C r e a t e d b y R i l e y T e s t u t o n 1 / 7 / 2 0 .
// C o p y r i g h t © 2 0 2 0 R i l e y T e s t u t . A l l r i g h t s r e s e r v e d .
//
import Foundation
2023-05-18 01:30:18 -07:00
import CommonCrypto
import Starscream
2020-01-08 12:41:02 -08:00
2020-09-03 16:39:08 -07:00
import AltStoreCore
2020-01-08 12:41:02 -08:00
import AltSign
import Roxas
2025-01-14 07:23:23 +05:30
class ANISETTE_VERBOSITY : Operation { } // d u m m y t a g i f a c e
2020-01-08 12:41:02 -08:00
@objc ( FetchAnisetteDataOperation )
2023-05-18 01:30:18 -07:00
final class FetchAnisetteDataOperation : ResultOperation < ALTAnisetteData > , WebSocketDelegate
2020-01-08 12:41:02 -08:00
{
2020-03-06 17:08:35 -08:00
let context : OperationContext
2023-05-18 01:30:18 -07:00
var socket : WebSocket !
var url : URL ?
var startProvisioningURL : URL ?
var endProvisioningURL : URL ?
var clientInfo : String ?
var userAgent : String ?
var mdLu : String ?
var deviceId : String ?
2020-01-08 12:41:02 -08:00
2020-03-06 17:08:35 -08:00
init ( context : OperationContext )
2020-01-08 12:41:02 -08:00
{
2020-03-06 17:08:35 -08:00
self . context = context
2020-01-08 12:41:02 -08:00
}
override func main ( )
{
super . main ( )
2020-03-06 17:08:35 -08:00
if let error = self . context . error
2020-01-08 12:41:02 -08:00
{
self . finish ( . failure ( error ) )
return
}
2024-11-28 21:57:42 +05:30
// TODO: P a s s i n p r o p e r v i e w c o n t e x t t o s h o w t h e T o a s t m e s s a g e s
let viewContext = context . presentingViewController
2022-11-16 17:51:13 -05:00
2024-11-28 21:57:42 +05:30
getAnisetteServerUrl ( viewContext ) { url , error in
2024-11-28 05:08:54 +05:30
guard let urlString = url else {
self . finish ( . failure ( error ! ) )
return
}
// s e t a s p r e f e r r e d
UserDefaults . standard . menuAnisetteURL = urlString
let url = URL ( string : urlString )
self . url = url
2025-01-14 07:23:23 +05:30
self . printOut ( " Anisette URL: \( self . url ! . absoluteString ) " )
2024-11-28 05:08:54 +05:30
if let identifier = Keychain . shared . identifier ,
let adiPb = Keychain . shared . adiPb {
self . fetchAnisetteV3 ( identifier , adiPb )
} else {
self . provision ( )
}
}
}
2024-11-28 21:57:42 +05:30
func getAnisetteServerUrl ( _ viewContext : UIViewController ? , completion : @ escaping ( String ? , Error ? ) -> Void ) {
2024-11-28 18:41:19 +05:30
var serverUrls = UserDefaults . standard . menuAnisetteServersList
let currentServer = UserDefaults . standard . menuAnisetteURL
// P r i o r i t i z e t h e c u r r e n t s e r v e r b y m o v i n g i t t o t h e t o p o f t h e l i s t
if let currentServerIndex = serverUrls . firstIndex ( of : currentServer ) {
serverUrls . remove ( at : currentServerIndex )
serverUrls . insert ( currentServer , at : 0 )
}
2024-11-28 21:57:42 +05:30
tryNextServer ( from : serverUrls , viewContext , currentIndex : 0 , completion : completion )
}
private func showToast ( viewContext : UIViewController ? , message : String ) {
if let viewContext = viewContext {
let error = OperationError . anisetteV1Error ( message : message )
let toastView = ToastView ( error : error )
// t o a s t V i e w . t e x t L a b e l . t e x t C o l o r = . a l t P r i m a r y
// t o a s t V i e w . d e t a i l T e x t L a b e l . t e x t C o l o r = . a l t P r i m a r y
DispatchQueue . main . async {
toastView . show ( in : viewContext )
}
}
2024-11-28 05:08:54 +05:30
}
2024-11-28 21:57:42 +05:30
private func tryNextServer ( from serverUrls : [ String ] , _ viewContext : UIViewController ? , currentIndex : Int , completion : @ escaping ( String ? , Error ? ) -> Void ) {
2024-11-28 05:08:54 +05:30
// C h e c k i f a l l U R L s h a v e b e e n e x h a u s t e d
guard currentIndex < serverUrls . count else {
let error = NSError ( domain : " AnisetteError " , code : 0 , userInfo : [ NSLocalizedDescriptionKey : " No valid server found. " ] )
completion ( nil , error )
return
}
let currentServerUrlString = serverUrls [ currentIndex ]
guard let url = URL ( string : currentServerUrlString ) else {
// I n v a l i d U R L , s k i p t o n e x t
2024-11-28 21:57:42 +05:30
let errmsg = " Skipping invalid URL: \( currentServerUrlString ) "
2025-01-14 07:23:23 +05:30
self . printOut ( errmsg )
2024-11-28 21:57:42 +05:30
showToast ( viewContext : viewContext , message : errmsg )
tryNextServer ( from : serverUrls , viewContext , currentIndex : currentIndex + 1 , completion : completion )
2024-11-28 05:08:54 +05:30
return
}
// A t t e m p t t o p i n g t h e c u r r e n t U R L
pingServer ( url ) { success , error in
if success {
// I f t h e s e r v e r i s r e a c h a b l e , r e t u r n t h e U R L
2024-11-28 21:57:42 +05:30
let okmsg = " Found working server: \( url . absoluteString ) "
2025-01-14 07:23:23 +05:30
self . printOut ( okmsg )
2024-11-28 21:57:42 +05:30
if ( currentIndex > 0 ) {
// n o t i f y u s e r i f a v a i l a b l e s e r v e r i s d i f f e r e n t t h e u s e r - s p e c i f i e d o n e
self . showToast ( viewContext : viewContext , message : okmsg )
}
2024-11-28 05:08:54 +05:30
completion ( url . absoluteString , nil )
} else {
// I f n o t , t r y t h e n e x t U R L
2024-11-28 21:57:42 +05:30
let errmsg = " Failed to reach server: \( url . absoluteString ) , trying next server. "
2025-01-14 07:23:23 +05:30
self . printOut ( errmsg )
2024-11-28 21:57:42 +05:30
self . showToast ( viewContext : viewContext , message : errmsg )
self . tryNextServer ( from : serverUrls , viewContext , currentIndex : currentIndex + 1 , completion : completion )
2024-11-28 05:08:54 +05:30
}
}
}
func pingServer ( _ url : URL , completion : @ escaping ( Bool , Error ? ) -> Void ) {
var request = URLRequest ( url : url )
request . timeoutInterval = 10 // T i m e o u t a f t e r 1 0 s e c o n d s
let task = URLSession . shared . dataTask ( with : request ) { ( data , response , error ) in
if let error = error {
completion ( false , error )
return
}
let httpResponse = response as ? HTTPURLResponse
let statusCode = httpResponse ? . statusCode
guard let statusCode = statusCode ,
( 200. . . 299 ) . contains ( statusCode ) else {
let serverError = OperationError . anisetteV3Error ( message : " Server unreachable or invalid response: \( String ( describing : statusCode ? ? nil ) ) " )
completion ( false , serverError )
return
}
completion ( true , nil )
2023-05-18 01:30:18 -07:00
}
2024-11-28 05:08:54 +05:30
task . resume ( )
2023-05-18 01:30:18 -07:00
}
2024-11-28 05:08:54 +05:30
2023-05-18 01:30:18 -07:00
// MARK: - C O M M O N
func extractAnisetteData ( _ data : Data , _ response : HTTPURLResponse ? , v3 : Bool ) throws {
// m a k e s u r e t h i s J S O N i s i n t h e f o r m a t w e e x p e c t
// c o n v e r t d a t a t o j s o n
if let json = try JSONSerialization . jsonObject ( with : data , options : [ ] ) as ? [ String : String ] {
if v3 {
if json [ " result " ] = = " GetHeadersError " {
let message = json [ " message " ]
2025-01-14 07:23:23 +05:30
self . printOut ( " Error getting V3 headers: \( message ? ? " no message " ) " )
2023-05-18 01:30:18 -07:00
if let message = message ,
message . contains ( " -45061 " ) {
2025-01-14 07:23:23 +05:30
self . printOut ( " Error message contains -45061 (not provisioned), resetting adi.pb and retrying " )
2023-05-18 01:30:18 -07:00
Keychain . shared . adiPb = nil
return provision ( )
} else { throw OperationError . anisetteV3Error ( message : message ? ? " Unknown error " ) }
}
}
2022-11-16 17:51:13 -05:00
2023-05-18 01:30:18 -07:00
// t r y t o r e a d o u t a d i c t i o n a r y
// f o r s o m e r e a s o n s e r i a l n u m b e r i s n ' t n e e d e d b u t i t d o e s n ' t w o r k u n l e s s i t h a s a v a l u e
var formattedJSON : [ String : String ] = [ " deviceSerialNumber " : " 0 " ]
if let machineID = json [ " X-Apple-I-MD-M " ] { formattedJSON [ " machineID " ] = machineID }
if let oneTimePassword = json [ " X-Apple-I-MD " ] { formattedJSON [ " oneTimePassword " ] = oneTimePassword }
if let routingInfo = json [ " X-Apple-I-MD-RINFO " ] { formattedJSON [ " routingInfo " ] = routingInfo }
if v3 {
formattedJSON [ " deviceDescription " ] = self . clientInfo !
formattedJSON [ " localUserID " ] = self . mdLu !
formattedJSON [ " deviceUniqueIdentifier " ] = self . deviceId !
// G e n e r a t e d a t e s t u f f o n c l i e n t
let formatter = DateFormatter ( )
formatter . locale = Locale ( identifier : " en_US_POSIX " )
formatter . calendar = Calendar ( identifier : . gregorian )
formatter . timeZone = TimeZone . current
formatter . dateFormat = " yyyy-MM-dd'T'HH:mm:ss'Z' "
let dateString = formatter . string ( from : Date ( ) )
formattedJSON [ " date " ] = dateString
formattedJSON [ " locale " ] = Locale . current . identifier
formattedJSON [ " timeZone " ] = TimeZone . current . abbreviation ( )
} else {
if let deviceDescription = json [ " X-MMe-Client-Info " ] { formattedJSON [ " deviceDescription " ] = deviceDescription }
if let localUserID = json [ " X-Apple-I-MD-LU " ] { formattedJSON [ " localUserID " ] = localUserID }
if let deviceUniqueIdentifier = json [ " X-Mme-Device-Id " ] { formattedJSON [ " deviceUniqueIdentifier " ] = deviceUniqueIdentifier }
if let date = json [ " X-Apple-I-Client-Time " ] { formattedJSON [ " date " ] = date }
if let locale = json [ " X-Apple-Locale " ] { formattedJSON [ " locale " ] = locale }
if let timeZone = json [ " X-Apple-I-TimeZone " ] { formattedJSON [ " timeZone " ] = timeZone }
}
if let response = response ,
let version = response . value ( forHTTPHeaderField : " Implementation-Version " ) {
2025-01-14 07:23:23 +05:30
self . printOut ( " Implementation-Version: \( version ) " )
} else { self . printOut ( " No Implementation-Version header " ) }
2023-05-18 01:30:18 -07:00
2025-01-14 07:23:23 +05:30
self . printOut ( " Anisette used: \( formattedJSON ) " )
self . printOut ( " Original JSON: \( json ) " )
2023-05-18 01:30:18 -07:00
if let anisette = ALTAnisetteData ( json : formattedJSON ) {
2025-01-14 07:23:23 +05:30
self . printOut ( " Anisette is valid! " )
2023-05-18 01:30:18 -07:00
self . finish ( . success ( anisette ) )
} else {
2025-01-14 07:23:23 +05:30
self . printOut ( " Anisette is invalid!!!! " )
2023-05-18 01:30:18 -07:00
if v3 {
throw OperationError . anisetteV3Error ( message : " Invalid anisette (the returned data may not have all the required fields) " )
} else {
throw OperationError . anisetteV1Error ( message : " Invalid anisette (the returned data may not have all the required fields) " )
2022-11-16 17:51:13 -05:00
}
2023-05-18 01:30:18 -07:00
}
} else {
if v3 {
throw OperationError . anisetteV3Error ( message : " Invalid anisette (the returned data may not be in JSON) " )
} else {
throw OperationError . anisetteV1Error ( message : " Invalid anisette (the returned data may not be in JSON) " )
}
}
}
// MARK: - V 1
func handleV1 ( ) {
2025-01-14 07:23:23 +05:30
self . printOut ( " Server is V1 " )
2023-05-18 01:30:18 -07:00
if UserDefaults . shared . trustedServerURL = = AnisetteManager . currentURLString {
2025-01-14 07:23:23 +05:30
self . printOut ( " Server has already been trusted, fetching anisette " )
2023-05-18 01:30:18 -07:00
return self . fetchAnisetteV1 ( )
}
2025-01-14 07:23:23 +05:30
self . printOut ( " Alerting user about outdated server " )
2023-05-18 01:30:18 -07:00
let alert = UIAlertController ( title : " WARNING: Outdated anisette server " , message : " We've detected you are using an older anisette server. Using this server has a higher likelihood of locking your account and causing other issues. Are you sure you want to continue? " , preferredStyle : UIAlertController . Style . alert )
alert . addAction ( UIAlertAction ( title : " Continue " , style : UIAlertAction . Style . destructive , handler : { action in
2025-01-14 07:23:23 +05:30
self . printOut ( " Fetching anisette via V1 " )
2023-05-18 01:30:18 -07:00
UserDefaults . shared . trustedServerURL = AnisetteManager . currentURLString
self . fetchAnisetteV1 ( )
} ) )
alert . addAction ( UIAlertAction ( title : " Cancel " , style : UIAlertAction . Style . cancel , handler : { action in
2025-01-14 07:23:23 +05:30
self . printOut ( " Cancelled anisette operation " )
2023-05-18 01:30:18 -07:00
self . finish ( . failure ( OperationError . cancelled ) )
} ) )
let keyWindow = UIApplication . shared . windows . filter { $0 . isKeyWindow } . first
DispatchQueue . main . async {
if let presentingController = keyWindow ? . rootViewController ? . presentedViewController {
presentingController . present ( alert , animated : true )
} else {
keyWindow ? . rootViewController ? . present ( alert , animated : true )
}
}
}
func fetchAnisetteV1 ( ) {
2025-01-14 07:23:23 +05:30
self . printOut ( " Fetching anisette V1 " )
2023-05-18 01:30:18 -07:00
URLSession . shared . dataTask ( with : self . url ! ) { data , response , error in
do {
guard let data = data , error = = nil else { throw OperationError . anisetteV1Error ( message : " Unable to fetch data \( error != nil ? " ( \( error ! . localizedDescription ) ) " : " " ) " ) }
try self . extractAnisetteData ( data , response as ? HTTPURLResponse , v3 : false )
2022-11-16 17:51:13 -05:00
} catch let error as NSError {
2025-01-14 07:23:23 +05:30
self . printOut ( " Failed to load: \( error . localizedDescription ) " )
2022-11-16 17:51:13 -05:00
self . finish ( . failure ( error ) )
}
2023-05-18 01:30:18 -07:00
} . resume ( )
}
// MARK: - V 3 : P R O V I S I O N I N G
func provision ( ) {
fetchClientInfo {
2025-01-14 07:23:23 +05:30
self . printOut ( " Getting provisioning URLs " )
2023-05-18 01:30:18 -07:00
var request = self . buildAppleRequest ( url : URL ( string : " https://gsa.apple.com/grandslam/GsService2/lookup " ) ! )
request . httpMethod = " GET "
URLSession . shared . dataTask ( with : request ) { data , response , error in
if let data = data ,
let plist = try ? PropertyListSerialization . propertyList ( from : data , format : nil ) as ? Dictionary < String , Dictionary < String , Any > > ,
let startProvisioningString = plist [ " urls " ] ? [ " midStartProvisioning " ] as ? String ,
let startProvisioningURL = URL ( string : startProvisioningString ) ,
let endProvisioningString = plist [ " urls " ] ? [ " midFinishProvisioning " ] as ? String ,
let endProvisioningURL = URL ( string : endProvisioningString ) {
self . startProvisioningURL = startProvisioningURL
self . endProvisioningURL = endProvisioningURL
2025-01-14 07:23:23 +05:30
self . printOut ( " startProvisioningURL: \( self . startProvisioningURL ! . absoluteString ) " )
self . printOut ( " endProvisioningURL: \( self . endProvisioningURL ! . absoluteString ) " )
self . printOut ( " Starting a provisioning session " )
2023-05-18 01:30:18 -07:00
self . startProvisioningSession ( )
} else {
2025-01-14 07:23:23 +05:30
self . printOut ( " Apple didn't give valid URLs! Got response: \( String ( data : data ? ? Data ( " nothing " . utf8 ) , encoding : . utf8 ) ? ? " not utf8 " ) " )
2023-05-18 01:30:18 -07:00
self . finish ( . failure ( OperationError . provisioningError ( result : " Apple didn't give valid URLs. Please try again later " , message : nil ) ) )
}
} . resume ( )
}
}
func startProvisioningSession ( ) {
let provisioningSessionURL = self . url ! . appendingPathComponent ( " v3 " ) . appendingPathComponent ( " provisioning_session " )
var wsRequest = URLRequest ( url : provisioningSessionURL )
wsRequest . timeoutInterval = 5
self . socket = WebSocket ( request : wsRequest )
self . socket . delegate = self
self . socket . connect ( )
}
2023-09-17 10:45:55 -07:00
func didReceive ( event : WebSocketEvent , client : WebSocketClient ) {
2023-05-18 01:30:18 -07:00
switch event {
case . text ( let string ) :
do {
if let json = try JSONSerialization . jsonObject ( with : string . data ( using : . utf8 ) ! , options : [ ] ) as ? [ String : Any ] {
guard let result = json [ " result " ] as ? String else {
2025-01-14 07:23:23 +05:30
self . printOut ( " The server didn't give us a result " )
2023-05-18 01:30:18 -07:00
client . disconnect ( closeCode : 0 )
self . finish ( . failure ( OperationError . provisioningError ( result : " The server didn't give us a result " , message : nil ) ) )
return
}
2025-01-14 07:23:23 +05:30
self . printOut ( " Received result: \( result ) " )
2023-05-18 01:30:18 -07:00
switch result {
case " GiveIdentifier " :
2025-01-14 07:23:23 +05:30
self . printOut ( " Giving identifier " )
2023-05-18 01:30:18 -07:00
client . json ( [ " identifier " : Keychain . shared . identifier ! ] )
case " GiveStartProvisioningData " :
2025-01-14 07:23:23 +05:30
self . printOut ( " Getting start provisioning data " )
2023-05-18 01:30:18 -07:00
let body = [
" Header " : [ String : Any ] ( ) ,
" Request " : [ String : Any ] ( ) ,
]
var request = self . buildAppleRequest ( url : self . startProvisioningURL ! )
request . httpMethod = " POST "
request . httpBody = try ! PropertyListSerialization . data ( fromPropertyList : body , format : . xml , options : 0 )
URLSession . shared . dataTask ( with : request ) { data , response , error in
if let data = data ,
let plist = try ? PropertyListSerialization . propertyList ( from : data , format : nil ) as ? Dictionary < String , Dictionary < String , Any > > ,
let spim = plist [ " Response " ] ? [ " spim " ] as ? String {
2025-01-14 07:23:23 +05:30
self . printOut ( " Giving start provisioning data " )
2023-05-18 01:30:18 -07:00
client . json ( [ " spim " : spim ] )
} else {
2025-01-14 07:23:23 +05:30
self . printOut ( " Apple didn't give valid start provisioning data! Got response: \( String ( data : data ? ? Data ( " nothing " . utf8 ) , encoding : . utf8 ) ? ? " not utf8 " ) " )
2023-05-18 01:30:18 -07:00
client . disconnect ( closeCode : 0 )
self . finish ( . failure ( OperationError . provisioningError ( result : " Apple didn't give valid start provisioning data. Please try again later " , message : nil ) ) )
}
} . resume ( )
case " GiveEndProvisioningData " :
2025-01-14 07:23:23 +05:30
self . printOut ( " Getting end provisioning data " )
2023-05-18 01:30:18 -07:00
guard let cpim = json [ " cpim " ] as ? String else {
2025-01-14 07:23:23 +05:30
self . printOut ( " The server didn't give us a cpim " )
2023-05-18 01:30:18 -07:00
client . disconnect ( closeCode : 0 )
self . finish ( . failure ( OperationError . provisioningError ( result : " The server didn't give us a cpim " , message : nil ) ) )
return
}
let body = [
" Header " : [ String : Any ] ( ) ,
" Request " : [
" cpim " : cpim ,
] ,
]
var request = self . buildAppleRequest ( url : self . endProvisioningURL ! )
request . httpMethod = " POST "
request . httpBody = try ! PropertyListSerialization . data ( fromPropertyList : body , format : . xml , options : 0 )
URLSession . shared . dataTask ( with : request ) { data , response , error in
if let data = data ,
let plist = try ? PropertyListSerialization . propertyList ( from : data , format : nil ) as ? Dictionary < String , Dictionary < String , Any > > ,
let ptm = plist [ " Response " ] ? [ " ptm " ] as ? String ,
let tk = plist [ " Response " ] ? [ " tk " ] as ? String {
2025-01-14 07:23:23 +05:30
self . printOut ( " Giving end provisioning data " )
2023-05-18 01:30:18 -07:00
client . json ( [ " ptm " : ptm , " tk " : tk ] )
} else {
2025-01-14 07:23:23 +05:30
self . printOut ( " Apple didn't give valid end provisioning data! Got response: \( String ( data : data ? ? Data ( " nothing " . utf8 ) , encoding : . utf8 ) ? ? " not utf8 " ) " )
2023-05-18 01:30:18 -07:00
client . disconnect ( closeCode : 0 )
self . finish ( . failure ( OperationError . provisioningError ( result : " Apple didn't give valid end provisioning data. Please try again later " , message : nil ) ) )
}
} . resume ( )
case " ProvisioningSuccess " :
2025-01-14 07:23:23 +05:30
self . printOut ( " Provisioning succeeded! " )
2023-05-18 01:30:18 -07:00
client . disconnect ( closeCode : 0 )
guard let adiPb = json [ " adi_pb " ] as ? String else {
2025-01-14 07:23:23 +05:30
self . printOut ( " The server didn't give us an adi.pb file " )
2023-05-18 01:30:18 -07:00
self . finish ( . failure ( OperationError . provisioningError ( result : " The server didn't give us an adi.pb file " , message : nil ) ) )
return
}
Keychain . shared . adiPb = adiPb
self . fetchAnisetteV3 ( Keychain . shared . identifier ! , Keychain . shared . adiPb ! )
default :
if result . contains ( " Error " ) || result . contains ( " Invalid " ) || result = = " ClosingPerRequest " || result = = " Timeout " || result = = " TextOnly " {
2025-01-14 07:23:23 +05:30
self . printOut ( " Failing because of \( result ) " )
2023-05-18 01:30:18 -07:00
self . finish ( . failure ( OperationError . provisioningError ( result : result , message : json [ " message " ] as ? String ) ) )
}
}
}
} catch let error as NSError {
2025-01-14 07:23:23 +05:30
self . printOut ( " Failed to handle text: \( error . localizedDescription ) " )
2023-05-18 01:30:18 -07:00
self . finish ( . failure ( OperationError . provisioningError ( result : error . localizedDescription , message : nil ) ) )
}
case . connected :
2025-01-14 07:23:23 +05:30
self . printOut ( " Connected " )
2023-05-18 01:30:18 -07:00
case . disconnected ( let string , let code ) :
2025-01-14 07:23:23 +05:30
self . printOut ( " Disconnected: \( code ) ; \( string ) " )
2023-05-18 01:30:18 -07:00
case . error ( let error ) :
2025-01-14 07:23:23 +05:30
self . printOut ( " Got error: \( String ( describing : error ) ) " )
2022-11-16 17:51:13 -05:00
2023-05-18 01:30:18 -07:00
default :
2025-01-14 07:23:23 +05:30
self . printOut ( " Unknown event: \( event ) " )
2023-05-18 01:30:18 -07:00
}
}
func buildAppleRequest ( url : URL ) -> URLRequest {
var request = URLRequest ( url : url )
request . setValue ( self . clientInfo ! , forHTTPHeaderField : " X-Mme-Client-Info " )
request . setValue ( self . userAgent ! , forHTTPHeaderField : " User-Agent " )
request . setValue ( " text/x-xml-plist " , forHTTPHeaderField : " Content-Type " )
request . setValue ( " */* " , forHTTPHeaderField : " Accept " )
request . setValue ( self . mdLu ! , forHTTPHeaderField : " X-Apple-I-MD-LU " )
request . setValue ( self . deviceId ! , forHTTPHeaderField : " X-Mme-Device-Id " )
let formatter = DateFormatter ( )
formatter . locale = Locale ( identifier : " en_US_POSIX " )
formatter . calendar = Calendar ( identifier : . gregorian )
formatter . timeZone = TimeZone ( identifier : " UTC " )
formatter . dateFormat = " yyyy-MM-dd'T'HH:mm:ss'Z' "
let dateString = formatter . string ( from : Date ( ) )
request . setValue ( dateString , forHTTPHeaderField : " X-Apple-I-Client-Time " )
request . setValue ( Locale . current . identifier , forHTTPHeaderField : " X-Apple-Locale " )
request . setValue ( TimeZone . current . abbreviation ( ) , forHTTPHeaderField : " X-Apple-I-TimeZone " )
return request
}
// MARK: - V 3 : F E T C H I N G
func fetchClientInfo ( _ callback : @ escaping ( ) -> Void ) {
if self . clientInfo != nil &&
self . userAgent != nil &&
self . mdLu != nil &&
self . deviceId != nil &&
Keychain . shared . identifier != nil {
2025-01-14 07:23:23 +05:30
self . printOut ( " Skipping client_info fetch since all the properties we need aren't nil " )
2023-05-18 01:30:18 -07:00
return callback ( )
}
2025-01-14 07:23:23 +05:30
self . printOut ( " Trying to get client_info " )
2023-05-18 01:30:18 -07:00
let clientInfoURL = self . url ! . appendingPathComponent ( " v3 " ) . appendingPathComponent ( " client_info " )
URLSession . shared . dataTask ( with : clientInfoURL ) { data , response , error in
do {
guard let data = data , error = = nil else {
return self . finish ( . failure ( OperationError . anisetteV3Error ( message : " Couldn't fetch client info. The server may be down \( error != nil ? " ( \( error ! . localizedDescription ) ) " : " " ) " ) ) )
}
if let json = try JSONSerialization . jsonObject ( with : data , options : [ ] ) as ? [ String : String ] {
if let clientInfo = json [ " client_info " ] {
2025-01-14 07:23:23 +05:30
self . printOut ( " Server is V3 " )
2023-05-18 01:30:18 -07:00
self . clientInfo = clientInfo
self . userAgent = json [ " user_agent " ] !
2025-01-14 07:23:23 +05:30
self . printOut ( " Client-Info: \( self . clientInfo ! ) " )
self . printOut ( " User-Agent: \( self . userAgent ! ) " )
2023-05-18 01:30:18 -07:00
if Keychain . shared . identifier = = nil {
2025-01-14 07:23:23 +05:30
self . printOut ( " Generating identifier " )
2023-05-18 01:30:18 -07:00
var bytes = [ Int8 ] ( repeating : 0 , count : 16 )
let status = SecRandomCopyBytes ( kSecRandomDefault , bytes . count , & bytes )
if status != errSecSuccess {
2025-01-14 07:23:23 +05:30
self . printOut ( " ERROR GENERATING IDENTIFIER!!! \( status ) " )
2023-05-18 01:30:18 -07:00
return self . finish ( . failure ( OperationError . provisioningError ( result : " Couldn't generate identifier " , message : nil ) ) )
}
Keychain . shared . identifier = Data ( bytes : & bytes , count : bytes . count ) . base64EncodedString ( )
}
let decoded = Data ( base64Encoded : Keychain . shared . identifier ! ) !
self . mdLu = decoded . sha256 ( ) . hexEncodedString ( )
2025-01-14 07:23:23 +05:30
self . printOut ( " X-Apple-I-MD-LU: \( self . mdLu ! ) " )
2023-05-18 01:30:18 -07:00
let uuid : UUID = decoded . object ( )
self . deviceId = uuid . uuidString . uppercased ( )
2025-01-14 07:23:23 +05:30
self . printOut ( " X-Mme-Device-Id: \( self . deviceId ! ) " )
2023-05-18 01:30:18 -07:00
callback ( )
} else { self . handleV1 ( ) }
} else { self . finish ( . failure ( OperationError . anisetteV3Error ( message : " Couldn't fetch client info. The returned data may not be in JSON " ) ) ) }
} catch let error as NSError {
2025-01-14 07:23:23 +05:30
self . printOut ( " Failed to load: \( error . localizedDescription ) " )
2023-05-18 01:30:18 -07:00
self . handleV1 ( )
}
} . resume ( )
}
func fetchAnisetteV3 ( _ identifier : String , _ adiPb : String ) {
fetchClientInfo {
2025-01-14 07:23:23 +05:30
self . printOut ( " Fetching anisette V3 " )
2024-08-06 10:43:52 +09:00
let url = UserDefaults . standard . menuAnisetteURL
2023-05-18 01:30:18 -07:00
var request = URLRequest ( url : self . url ! . appendingPathComponent ( " v3 " ) . appendingPathComponent ( " get_headers " ) )
request . httpMethod = " POST "
request . httpBody = try ! JSONSerialization . data ( withJSONObject : [
" identifier " : identifier ,
" adi_pb " : adiPb
] , options : [ ] )
request . setValue ( " application/json " , forHTTPHeaderField : " Content-Type " )
URLSession . shared . dataTask ( with : request ) { data , response , error in
do {
guard let data = data , error = = nil else { throw OperationError . anisetteV3Error ( message : " Couldn't fetch anisette " ) }
try self . extractAnisetteData ( data , response as ? HTTPURLResponse , v3 : true )
} catch let error as NSError {
2025-01-14 07:23:23 +05:30
self . printOut ( " Failed to load: \( error . localizedDescription ) " )
2023-05-18 01:30:18 -07:00
self . finish ( . failure ( error ) )
}
} . resume ( )
2022-11-16 17:51:13 -05:00
}
2020-01-08 12:41:02 -08:00
}
2025-01-14 07:23:23 +05:30
private func printOut ( _ text : String ? ) {
let isInternalLoggingEnabled = OperationsLoggingControl . getFromDatabase ( for : ANISETTE_VERBOSITY . self )
if ( isInternalLoggingEnabled ) {
// l o g g i n g e n a b l e d , s o l o g i t
text . map { _ in print ( text ! ) } ? ? print ( )
}
}
2020-01-08 12:41:02 -08:00
}
2023-05-18 01:30:18 -07:00
2023-09-17 10:45:55 -07:00
extension WebSocketClient {
2023-05-18 01:30:18 -07:00
func json ( _ dictionary : [ String : String ] ) {
let data = try ! JSONSerialization . data ( withJSONObject : dictionary , options : [ ] )
self . write ( string : String ( data : data , encoding : . utf8 ) ! )
}
}
extension Data {
// h t t p s : / / s t a c k o v e r f l o w . c o m / a / 2 5 3 9 1 0 2 0
func sha256 ( ) -> Data {
var hash = [ UInt8 ] ( repeating : 0 , count : Int ( CC_SHA256_DIGEST_LENGTH ) )
self . withUnsafeBytes {
_ = CC_SHA256 ( $0 . baseAddress , CC_LONG ( self . count ) , & hash )
}
return Data ( hash )
}
// h t t p s : / / s t a c k o v e r f l o w . c o m / a / 4 0 0 8 9 4 6 2
func hexEncodedString ( ) -> String {
return self . map { String ( format : " %02hhX " , $0 ) } . joined ( )
}
// h t t p s : / / s t a c k o v e r f l o w . c o m / a / 5 9 1 2 7 7 6 1
func object < T > ( ) -> T { self . withUnsafeBytes { $0 . load ( as : T . self ) } }
}