1+ import time
2+
13import urllib3
24from getpass import getpass
35import plistlib as plist
1618from cryptography .hazmat .primitives import padding
1719from cryptography .hazmat .primitives .ciphers import Cipher , algorithms , modes
1820from Crypto .Hash import SHA256
19- import config
21+
22+ from endpoint import mh_config
2023
2124# Created here so that it is consistent
2225USER_ID = uuid .uuid4 ()
@@ -37,6 +40,7 @@ def icloud_login_mobileme(username='', password=''):
3740 username = input ('Apple ID: ' )
3841 if not password :
3942 password = getpass ('Password: ' )
43+
4044 g = gsa_authenticate (username , password )
4145 pet = g ["t" ]["com.apple.gs.idms.pet" ]["token" ]
4246 adsid = g ["adsid" ]
@@ -71,10 +75,10 @@ def icloud_login_mobileme(username='', password=''):
7175def gsa_authenticate (username , password ):
7276 # Password is None as we'll provide it later
7377 usr = srp .User (username , bytes (), hash_alg = srp .SHA256 , ng_type = srp .NG_2048 )
74- _ , A = usr .start_authentication ()
78+ _ , a = usr .start_authentication ()
7579 logger .info ("Authentication request initialization" )
7680 r = gsa_authenticated_request (
77- {"A2k" : A , "ps" : ["s2k" , "s2k_fo" ], "u" : username , "o" : "init" })
81+ {"A2k" : a , "ps" : ["s2k" , "s2k_fo" ], "u" : username , "o" : "init" })
7882
7983 if r ["sp" ] not in ["s2k" , "s2k_fo" ]:
8084 logger .warning (f"This implementation only supports s2k and sk2_fo. Server returned { r ['sp' ]} " )
@@ -169,7 +173,7 @@ def generate_cpd():
169173
170174
171175def generate_anisette_headers ():
172- h = json .loads (requests .get (config .getAnisetteServer (), timeout = 5 ).text )
176+ h = json .loads (requests .get (mh_config .getAnisetteServer (), timeout = 5 ).text )
173177 a = {"X-Apple-I-MD" : h ["X-Apple-I-MD" ],
174178 "X-Apple-I-MD-M" : h ["X-Apple-I-MD-M" ]}
175179 a .update (generate_meta_headers (user_id = USER_ID , device_id = DEVICE_ID ))
@@ -219,15 +223,13 @@ def decrypt_cbc(usr, data):
219223 return padder .update (data ) + padder .finalize ()
220224
221225
226+ WAITING_TIME = 60
227+
228+
222229def sms_second_factor (dsid , idms_token ):
223230 identity_token = base64 .b64encode (
224231 (dsid + ":" + idms_token ).encode ()).decode ()
225232
226- # TODO: Actually do this request to get user prompt data
227- # a = requests.get("https://gsa.apple.com/auth", verify=False)
228- # This request isn't strictly necessary though,
229- # and most accounts should have their id 1 SMS, if not contribute ;)
230-
231233 headers = {
232234 "User-Agent" : "Xcode" ,
233235 "Accept-Language" : "en-us" ,
@@ -238,32 +240,46 @@ def sms_second_factor(dsid, idms_token):
238240 }
239241
240242 headers .update (generate_anisette_headers ())
241-
243+
242244 # Extract the "boot_args" from the auth page to get the id of the trusted phone number
243245 pattern = r'<script.*class="boot_args">\s*(.*?)\s*</script>'
244246 auth = requests .get ("https://gsa.apple.com/auth" , headers = headers , verify = False )
245247 sms_id = 1
246248 match = re .search (pattern , auth .text , re .DOTALL )
247249 if match :
248250 boot_args = json .loads (match .group (1 ).strip ())
249- try :
251+ try :
250252 sms_id = boot_args ["direct" ]["phoneNumberVerification" ]["trustedPhoneNumber" ]["id" ]
251- except KeyError as e :
252- logger .debug (match .group (1 ).strip ())
253- logger .error ("Key for sms id not found. Using the first phone number" )
253+ except KeyError as e :
254+ logger .debug (match .group (1 ).strip ())
255+ logger .error ("Key for sms id not found. Using the first phone number" )
254256 else :
255- logger .debug (auth .text )
256- logger .error ("Script for sms id not found. Using the first phone number" )
257-
258- logger .info (f"Using phone with id { sms_id } for SMS2FA" )
259- body = {"phoneNumber" : {"id" : sms_id }, "mode" : "sms" }
260-
261- # This will send the 2FA code to the user's phone over SMS
262- # We don't care about the response, it's just some HTML with a form for entering the code
263- # Easier to just use a text prompt
257+ logger .debug (auth .text )
258+ logger .error ("Script for sms id not found. Using the first phone number" )
264259
260+ logger .info (f"Using phone with id { sms_id } for SMS2FA" )
261+ body = {"phoneNumber" : {"id" : sms_id }, "mode" : "sms" }
262+ for handler in logger .handlers :
263+ handler .flush ()
265264 # Prompt for the 2FA code. It's just a string like '123456', no dashes or spaces
266- code = input ("Enter SMS 2FA code: " )
265+ start_time = time .perf_counter ()
266+ code = input (
267+ f"Enter SMS 2FA code (If you do not receive a code, wait { WAITING_TIME } s and press Enter. An attempt will be made to request the SMS in another way.): " )
268+ end_time = time .perf_counter ()
269+
270+ if code == "" :
271+ elapsed_time = int (end_time - start_time )
272+ if elapsed_time < WAITING_TIME :
273+ waiting_time = WAITING_TIME - elapsed_time
274+ logger .info (
275+ f"You only waited { elapsed_time } seconds. The next request will be started in { waiting_time } seconds" )
276+ time .sleep (waiting_time )
277+ code = input (f"Enter SMS 2FA code if you have received it in the meantime, otherwise press Enter: " )
278+
279+ if code == "" :
280+ code = request_code (headers )
281+ else :
282+ code = request_code (headers )
267283
268284 body ['securityCode' ] = {'code' : code }
269285
@@ -287,3 +303,19 @@ def sms_second_factor(dsid, idms_token):
287303 else :
288304 raise Exception (
289305 "2FA unsuccessful. Maybe wrong code or wrong number. Check your account details." )
306+
307+
308+ def request_code (headers ):
309+ # This will send the 2FA code to the user's phone over SMS
310+ # We don't care about the response, it's just some HTML with a form for entering the code
311+ # Easier to just use a text prompt
312+ body = {"phoneNumber" : {"id" : 1 }, "mode" : "sms" }
313+ requests .put (
314+ "https://gsa.apple.com/auth/verify/phone/" ,
315+ json = body ,
316+ headers = headers ,
317+ verify = False ,
318+ timeout = 5
319+ )
320+ code = input (f"Enter SMS 2FA code:" )
321+ return code
0 commit comments