Skip to content

Commit

Permalink
Added a lot more output on registering device
Browse files Browse the repository at this point in the history
  • Loading branch information
dchristl committed Mar 31, 2024
1 parent 085cb9d commit 94cd8ba
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 67 deletions.
3 changes: 2 additions & 1 deletion endpoint/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def getLogLevel():
logLevel = config.get('Settings', 'loglevel', fallback='INFO')
return logging.getLevelName(logLevel)


logging.basicConfig(level=getLogLevel(),
format='%(asctime)s - %(levelname)s - %(message)s')
# Suppress http-log
logging.getLogger('urllib3').setLevel(logging.INFO)
7 changes: 4 additions & 3 deletions endpoint/register/apple_cryptography.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,14 @@ def decode_tag(data):
return {'lat': latitude, 'lon': longitude, 'conf': confidence, 'status': status}


def getAuth(regenerate=False, second_factor='sms'):
def getAuth(regenerate=False):
if os.path.exists(config.getConfigFile()) and not regenerate:
with open(config.getConfigFile(), "r") as f:
j = json.load(f)
else:
logger.info('Trying to login')
mobileme = icloud_login_mobileme(
username=config.getUser(), password=config.getPass(), second_factor=second_factor)
username=config.getUser(), password=config.getPass())

logger.debug('Answer from icloud login')
logger.debug(mobileme)
Expand All @@ -73,4 +74,4 @@ def getAuth(regenerate=False, second_factor='sms'):
def registerDevice():

logger.info(f'Trying to register new device.')
getAuth(regenerate=True, second_factor='trusted_device' 'sms')
getAuth(regenerate=True)
95 changes: 32 additions & 63 deletions endpoint/register/pypush_gsa_icloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@
logger = logging.getLogger()


def icloud_login_mobileme(username='', password='', second_factor='sms'):
def icloud_login_mobileme(username='', password=''):
if not username:
username = input('Apple ID: ')
if not password:
password = getpass('Password: ')
g = gsa_authenticate(username, password, second_factor)
g = gsa_authenticate(username, password)
pet = g["t"]["com.apple.gs.idms.pet"]["token"]
adsid = g["adsid"]

Expand All @@ -55,21 +55,24 @@ def icloud_login_mobileme(username='', password='', second_factor='sms'):
}
headers.update(generate_anisette_headers())

r = requests.post(
logger.info("Registering device after login")
resp = requests.post(
"https://setup.icloud.com/setup/iosbuddy/loginDelegates",
auth=(username, pet),
data=data,
headers=headers,
verify=False,
)
return plist.loads(r.content)
response = f"HTTP-Code: {resp.status_code}\n{resp.text}"
logger.debug(response)
return plist.loads(resp.content)


def gsa_authenticate(username, password, second_factor='sms'):
def gsa_authenticate(username, password):
# Password is None as we'll provide it later
usr = srp.User(username, bytes(), hash_alg=srp.SHA256, ng_type=srp.NG_2048)
_, A = usr.start_authentication()

logger.info("Authentication request initialization")
r = gsa_authenticated_request(
{"A2k": A, "ps": ["s2k", "s2k_fo"], "u": username, "o": "init"})

Expand All @@ -87,36 +90,38 @@ def gsa_authenticate(username, password, second_factor='sms'):
if M is None:
logger.error("Failed to process challenge")
return

r = gsa_authenticated_request(
logger.info("Authentication request completion")
resp = gsa_authenticated_request(
{"c": r["c"], "M1": M, "u": username, "o": "complete"})

# Make sure that the server's session key matches our session key (and thus that they are not an imposter)
usr.verify_session(r["M2"])
if "M2" not in resp:
logger.error("Error on authentication")
logger.error(resp)
return
usr.verify_session(resp["M2"])
if not usr.authenticated():
logger.error("Failed to verify session")
return

spd = decrypt_cbc(usr, r["spd"])
spd = decrypt_cbc(usr, resp["spd"])
# For some reason plistlib doesn't accept it without the header...
PLISTHEADER = b"""\
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE plist PUBLIC '-//Apple//DTD PLIST 1.0//EN' 'http://www.apple.com/DTDs/PropertyList-1.0.dtd'>
"""
spd = plist.loads(PLISTHEADER + spd)

if "au" in r["Status"] and r["Status"]["au"] in ["trustedDeviceSecondaryAuth", "secondaryAuth"]:
if "au" in resp["Status"] and resp["Status"]["au"] in ["trustedDeviceSecondaryAuth", "secondaryAuth"]:
logger.info("2FA required, requesting code")
# Replace bytes with strings
for k, v in spd.items():
if isinstance(v, bytes):
spd[k] = base64.b64encode(v).decode()
if second_factor == 'sms':
sms_second_factor(spd["adsid"], spd["GsIdmsToken"])
elif second_factor == 'trusted_device':
trusted_second_factor(spd["adsid"], spd["GsIdmsToken"])
sms_second_factor(spd["adsid"], spd["GsIdmsToken"])

return gsa_authenticate(username, password)
elif "au" in r["Status"]:
elif "au" in resp["Status"]:
logger.error(f"Unknown auth value {r['Status']['au']}")
return
else:
Expand Down Expand Up @@ -144,6 +149,8 @@ def gsa_authenticated_request(parameters):
verify=False,
timeout=5,
)
response = f"HTTP-Code: {resp.status_code}\n{resp.text}"
logger.debug(response)

return plist.loads(resp.content)["Response"]

Expand All @@ -163,10 +170,6 @@ def generate_cpd():


def generate_anisette_headers():


logger.debug(
f'Querying {config.getAnisetteServer()} for an anisette server')
h = json.loads(requests.get(config.getAnisetteServer(), timeout=5).text)
a = {"X-Apple-I-MD": h["X-Apple-I-MD"],
"X-Apple-I-MD-M": h["X-Apple-I-MD-M"]}
Expand Down Expand Up @@ -214,48 +217,6 @@ def decrypt_cbc(usr, data):
return padder.update(data) + padder.finalize()


def trusted_second_factor(dsid, idms_token):
identity_token = base64.b64encode(
(dsid + ":" + idms_token).encode()).decode()

headers = {
"Content-Type": "text/x-xml-plist",
"User-Agent": "Xcode",
"Accept": "text/x-xml-plist",
"Accept-Language": "en-us",
"X-Apple-Identity-Token": identity_token,
"X-Apple-App-Info": "com.apple.gs.xcode.auth",
"X-Xcode-Version": "11.2 (11B41)",
"X-Mme-Client-Info": '<MacBookPro18,3> <Mac OS X;13.4.1;22F8> <com.apple.AOSKit/282 (com.apple.dt.Xcode/3594.4.19)>'
}

headers.update(generate_anisette_headers())

# This will trigger the 2FA prompt on trusted devices
# We don't care about the response, it's just some HTML with a form for entering the code
# Easier to just use a text prompt
requests.get(
"https://gsa.apple.com/auth/verify/trusteddevice",
headers=headers,
verify=False,
timeout=10,
)

# Prompt for the 2FA code. It's just a string like '123456', no dashes or spaces
code = getpass("Enter 2FA code: ")
headers["security-code"] = code

# Send the 2FA code to Apple
resp = requests.get(
"https://gsa.apple.com/grandslam/GsService2/validate",
headers=headers,
verify=False,
timeout=10,
)
if resp.ok:
logger.info("2FA successful")


def sms_second_factor(dsid, idms_token):
identity_token = base64.b64encode(
(dsid + ":" + idms_token).encode()).decode()
Expand Down Expand Up @@ -302,5 +263,13 @@ def sms_second_factor(dsid, idms_token):
verify=False,
timeout=5,
)
if resp.ok:
header_string = "Header der Antwort:\n"
for header, value in response.headers.items():
header_string += f"{header}: {value}\n"
response = f"HTTP-Code: {resp.status_code} with {header_string} bytes"
logger.debug(response)
# If the answer was too long, the output is the
if resp.ok and len(resp.text) < 100:
logger.info("2FA successful")
else:
raise Exception("2FA unsuccessful. Maybe wrong code or wrong number. Check your account details.")
52 changes: 52 additions & 0 deletions macless_haystack/test/accessory/accessory_registry_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,56 @@ void main() {
expect(locationHistory.elementAt(2).start, DateTime(2024, 1, 1, 12, 0, 0));
expect(locationHistory.elementAt(2).end, DateTime(2024, 1, 1, 12, 0, 0));
});

test('Add same location entries twice should not change latest anything',
() async {
List<FindMyLocationReport> reports = [];

// 8 o'clock 1st location
reports.add(FindMyLocationReport.withHash(
1,
2,
DateTime(2024, 1, 1, 8, 0, 0),
DateTime.now().microsecondsSinceEpoch.toString()));
//10 o'clock second location
reports.add(FindMyLocationReport.withHash(
2,
2,
DateTime(2024, 1, 1, 10, 0, 0),
DateTime.now().microsecondsSinceEpoch.toString()));
// 9 o'clock first location
reports.add(FindMyLocationReport.withHash(
1,
2,
DateTime(2024, 1, 1, 9, 0, 0),
DateTime.now().microsecondsSinceEpoch.toString()));
//12 o'clock 1st location
reports.add(FindMyLocationReport.withHash(
1,
2,
DateTime(2024, 1, 1, 12, 0, 0),
DateTime.now().microsecondsSinceEpoch.toString()));
await registry.fillLocationHistory(reports, accessory);
reports.shuffle();
await registry.fillLocationHistory(reports, accessory);
var locationHistory = accessory.locationHistory;
expect(3, locationHistory.length);

var latest = accessory.datePublished;
var lastLocation = accessory.lastLocation;
var endOfFirstEntry = accessory.latestHistoryEntry();

expect(endOfFirstEntry, DateTime(2024, 1, 1, 9, 0, 0));
expect(latest, DateTime(2024, 1, 1, 12, 0, 0));
expect(lastLocation, const LatLng(1, 2));

expect(locationHistory.elementAt(0).start, DateTime(2024, 1, 1, 8, 0, 0));
expect(locationHistory.elementAt(0).end, DateTime(2024, 1, 1, 9, 0, 0));

expect(locationHistory.elementAt(1).start, DateTime(2024, 1, 1, 10, 0, 0));
expect(locationHistory.elementAt(1).end, DateTime(2024, 1, 1, 10, 0, 0));

expect(locationHistory.elementAt(2).start, DateTime(2024, 1, 1, 12, 0, 0));
expect(locationHistory.elementAt(2).end, DateTime(2024, 1, 1, 12, 0, 0));
});
}

0 comments on commit 94cd8ba

Please sign in to comment.