@@ -103,21 +103,33 @@ public extension ALTAppleAPI
103103 context. dsid = dsid
104104
105105 let authType = statusDictionary [ " au " ] as? String
106- if authType == " trustedDeviceSecondaryAuth "
106+ switch authType
107107 {
108+ case " trustedDeviceSecondaryAuth " :
108109 guard let verificationHandler = verificationHandler else { throw ALTAppleAPIError ( . requiresTwoFactorAuthentication) }
109110
110- self . requestTwoFactorCode ( dsid: dsid, idmsToken: idmsToken, anisetteData: anisetteData, verificationHandler: verificationHandler) { ( result) in
111+ self . requestTrustedDeviceTwoFactorCode ( dsid: dsid, idmsToken: idmsToken, anisetteData: anisetteData, verificationHandler: verificationHandler) { ( result) in
111112 switch result
112113 {
113114 case . failure( let error) : completionHandler ( nil , nil , error)
114115 case . success:
115116 self . authenticate ( appleID: unsanitizedAppleID, password: password, anisetteData: anisetteData, verificationHandler: verificationHandler, completionHandler: completionHandler)
116117 }
117118 }
118- }
119- else
120- {
119+
120+ case " secondaryAuth " :
121+ guard let verificationHandler = verificationHandler else { throw ALTAppleAPIError ( . requiresTwoFactorAuthentication) }
122+
123+ self . requestSMSTwoFactorCode ( dsid: dsid, idmsToken: idmsToken, anisetteData: anisetteData, verificationHandler: verificationHandler) { ( result) in
124+ switch result
125+ {
126+ case . failure( let error) : completionHandler ( nil , nil , error)
127+ case . success:
128+ self . authenticate ( appleID: unsanitizedAppleID, password: password, anisetteData: anisetteData, verificationHandler: verificationHandler, completionHandler: completionHandler)
129+ }
130+ }
131+
132+ default :
121133 guard let sessionKey = decryptedDictionary [ " sk " ] as? Data ,
122134 let c = decryptedDictionary [ " c " ] as? Data
123135 else { throw URLError ( . badServerResponse) }
@@ -204,40 +216,16 @@ private extension ALTAppleAPI
204216 }
205217 }
206218
207- func requestTwoFactorCode ( dsid: String ,
208- idmsToken: String ,
209- anisetteData: ALTAnisetteData ,
210- verificationHandler: @escaping ( @escaping ( String ? ) -> Void ) -> Void ,
211- completionHandler: @escaping ( Result < Void , Error > ) -> Void )
219+ func requestTrustedDeviceTwoFactorCode ( dsid: String ,
220+ idmsToken: String ,
221+ anisetteData: ALTAnisetteData ,
222+ verificationHandler: @escaping ( @escaping ( String ? ) -> Void ) -> Void ,
223+ completionHandler: @escaping ( Result < Void , Error > ) -> Void )
212224 {
213- let url = URL ( string: " https://gsa.apple.com/auth/verify/trusteddevice " ) !
225+ let requestURL = URL ( string: " https://gsa.apple.com/auth/verify/trusteddevice " ) !
226+ let verifyURL = URL ( string: " https://gsa.apple.com/grandslam/GsService2/validate " ) !
214227
215- let identityToken = dsid + " : " + idmsToken
216-
217- let identityTokenData = identityToken. data ( using: . utf8) !
218- let encodedIdentityToken = identityTokenData. base64EncodedString ( )
219-
220- let httpHeaders = [
221- " Accept " : " text/x-xml-plist " ,
222- " Accept-Language " : " en-us " ,
223- " Content-Type " : " text/x-xml-plist " ,
224- " User-Agent " : " Xcode " ,
225- " X-Apple-App-Info " : " com.apple.gs.xcode.auth " ,
226- " X-Xcode-Version " : " 11.2 (11B41) " ,
227- " X-Apple-Identity-Token " : encodedIdentityToken,
228- " X-Apple-I-MD-M " : anisetteData. machineID,
229- " X-Apple-I-MD " : anisetteData. oneTimePassword,
230- " X-Apple-I-MD-LU " : anisetteData. localUserID,
231- " X-Apple-I-MD-RINFO " : " \( anisetteData. routingInfo) " ,
232- " X-Mme-Device-Id " : anisetteData. deviceUniqueIdentifier,
233- " X-MMe-Client-Info " : anisetteData. deviceDescription,
234- " X-Apple-I-Client-Time " : self . dateFormatter. string ( from: anisetteData. date) ,
235- " X-Apple-Locale " : anisetteData. locale. identifier,
236- " X-Apple-I-TimeZone " : anisetteData. timeZone. abbreviation ( ) ?? " PST "
237- ]
238-
239- var request = URLRequest ( url: url)
240- httpHeaders. forEach { request. addValue ( $0. value, forHTTPHeaderField: $0. key) }
228+ let request = self . makeTwoFactorCodeRequest ( url: requestURL, dsid: dsid, idmsToken: idmsToken, anisetteData: anisetteData)
241229
242230 let requestCodeTask = self . session. dataTask ( with: request) { ( data, response, error) in
243231 do
@@ -249,14 +237,9 @@ private extension ALTAppleAPI
249237 do
250238 {
251239 guard let verificationCode = verificationCode else { throw ALTAppleAPIError ( . requiresTwoFactorAuthentication) }
252-
253- var headers = httpHeaders
254- headers [ " security-code " ] = verificationCode
255-
256- let url = URL ( string: " https://gsa.apple.com/grandslam/GsService2/validate " ) !
257-
258- var request = URLRequest ( url: url)
259- headers. forEach { request. addValue ( $0. value, forHTTPHeaderField: $0. key) }
240+
241+ var request = self . makeTwoFactorCodeRequest ( url: verifyURL, dsid: dsid, idmsToken: idmsToken, anisetteData: anisetteData)
242+ request. allHTTPHeaderFields ? [ " security-code " ] = verificationCode
260243
261244 let verifyCodeTask = self . session. dataTask ( with: request) { ( data, response, error) in
262245 do
@@ -305,6 +288,97 @@ private extension ALTAppleAPI
305288 requestCodeTask. resume ( )
306289 }
307290
291+ func requestSMSTwoFactorCode( dsid: String ,
292+ idmsToken: String ,
293+ anisetteData: ALTAnisetteData ,
294+ verificationHandler: @escaping ( @escaping ( String ? ) -> Void ) -> Void ,
295+ completionHandler: @escaping ( Result < Void , Error > ) -> Void )
296+ {
297+ let requestURL = URL ( string: " https://gsa.apple.com/auth/verify/phone/put?mode=sms " ) !
298+ let verifyURL = URL ( string: " https://gsa.apple.com/auth/verify/phone/securitycode?referrer=/auth/verify/phone/put " ) !
299+
300+ var request = self . makeTwoFactorCodeRequest ( url: requestURL, dsid: dsid, idmsToken: idmsToken, anisetteData: anisetteData)
301+ request. httpMethod = " POST "
302+
303+ do
304+ {
305+ let bodyXML = [
306+ " serverInfo " : [
307+ " phoneNumber.id " : " 1 "
308+ ]
309+ ] as [ String : Any ]
310+
311+ let bodyData = try PropertyListSerialization . data ( fromPropertyList: bodyXML, format: . xml, options: 0 )
312+ request. httpBody = bodyData
313+ }
314+ catch
315+ {
316+ completionHandler ( . failure( error) )
317+ return
318+ }
319+
320+ let requestCodeTask = self . session. dataTask ( with: request) { ( data, response, error) in
321+ do
322+ {
323+ guard error == nil else { throw error! }
324+
325+ func responseHandler( verificationCode: String ? )
326+ {
327+ do
328+ {
329+ guard let verificationCode = verificationCode else { throw ALTAppleAPIError ( . requiresTwoFactorAuthentication) }
330+
331+ var request = self . makeTwoFactorCodeRequest ( url: verifyURL, dsid: dsid, idmsToken: idmsToken, anisetteData: anisetteData)
332+ request. httpMethod = " POST "
333+
334+ let bodyXML = [
335+ " securityCode.code " : verificationCode,
336+ " serverInfo " : [
337+ " mode " : " sms " ,
338+ " phoneNumber.id " : " 1 "
339+ ]
340+ ] as [ String : Any ]
341+
342+ let bodyData = try PropertyListSerialization . data ( fromPropertyList: bodyXML, format: . xml, options: 0 )
343+ request. httpBody = bodyData
344+
345+ let verifyCodeTask = self . session. dataTask ( with: request) { ( data, response, error) in
346+ do
347+ {
348+ guard error == nil else { throw error! }
349+
350+ guard let httpResponse = response as? HTTPURLResponse ,
351+ httpResponse. statusCode == 200 ,
352+ httpResponse. allHeaderFields. keys. contains ( " X-Apple-PE-Token " ) // PE token is included in headers if we sent correct verification code.
353+ else { throw ALTAppleAPIError ( . incorrectVerificationCode) }
354+
355+ completionHandler ( . success( ( ) ) )
356+ }
357+ catch
358+ {
359+ completionHandler ( . failure( error) )
360+ }
361+ }
362+
363+ verifyCodeTask. resume ( )
364+ }
365+ catch
366+ {
367+ completionHandler ( . failure( error) )
368+ }
369+ }
370+
371+ verificationHandler ( responseHandler)
372+ }
373+ catch
374+ {
375+ completionHandler ( . failure( error) )
376+ }
377+ }
378+
379+ requestCodeTask. resume ( )
380+ }
381+
308382 func fetchAccount( session: ALTAppleAPISession , completionHandler: @escaping ( Result < ALTAccount , Error > ) -> Void )
309383 {
310384 let url = URL ( string: " viewDeveloper.action " , relativeTo: self . baseURL) !
@@ -330,7 +404,10 @@ private extension ALTAppleAPI
330404 }
331405 }
332406 }
333-
407+ }
408+
409+ private extension ALTAppleAPI
410+ {
334411 func sendAuthenticationRequest( parameters requestParameters: [ String : Any ] , anisetteData: ALTAnisetteData , completionHandler: @escaping ( Result < [ String : Any ] , Error > ) -> Void )
335412 {
336413 do
@@ -393,4 +470,39 @@ private extension ALTAppleAPI
393470 completionHandler ( . failure( error) )
394471 }
395472 }
473+
474+ func makeTwoFactorCodeRequest( url: URL ,
475+ dsid: String ,
476+ idmsToken: String ,
477+ anisetteData: ALTAnisetteData ) -> URLRequest
478+ {
479+ let identityToken = dsid + " : " + idmsToken
480+
481+ let identityTokenData = identityToken. data ( using: . utf8) !
482+ let encodedIdentityToken = identityTokenData. base64EncodedString ( )
483+
484+ let httpHeaders = [
485+ " Accept " : " application/x-buddyml " ,
486+ " Accept-Language " : " en-us " ,
487+ " Content-Type " : " application/x-plist " ,
488+ " User-Agent " : " Xcode " ,
489+ " X-Apple-App-Info " : " com.apple.gs.xcode.auth " ,
490+ " X-Xcode-Version " : " 11.2 (11B41) " ,
491+ " X-Apple-Identity-Token " : encodedIdentityToken,
492+ " X-Apple-I-MD-M " : anisetteData. machineID,
493+ " X-Apple-I-MD " : anisetteData. oneTimePassword,
494+ " X-Apple-I-MD-LU " : anisetteData. localUserID,
495+ " X-Apple-I-MD-RINFO " : " \( anisetteData. routingInfo) " ,
496+ " X-Mme-Device-Id " : anisetteData. deviceUniqueIdentifier,
497+ " X-MMe-Client-Info " : anisetteData. deviceDescription,
498+ " X-Apple-I-Client-Time " : self . dateFormatter. string ( from: anisetteData. date) ,
499+ " X-Apple-Locale " : anisetteData. locale. identifier,
500+ " X-Apple-I-TimeZone " : anisetteData. timeZone. abbreviation ( ) ?? " PST "
501+ ]
502+
503+ var request = URLRequest ( url: url)
504+ httpHeaders. forEach { request. addValue ( $0. value, forHTTPHeaderField: $0. key) }
505+
506+ return request
507+ }
396508}
0 commit comments