Skip to content

Commit 557d373

Browse files
committed
Supports SMS-based 2FA
Fixes new Apple IDs that have 2FA enabled but do not have any trusted devices.
1 parent 1ed9432 commit 557d373

1 file changed

Lines changed: 158 additions & 46 deletions

File tree

AltSign/Sources/ALTAppleAPI+Authentication.swift

Lines changed: 158 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)