Skip to content

Commit

Permalink
Change authentication to apples new SRP6a (#49)
Browse files Browse the repository at this point in the history
  • Loading branch information
PflaeginGmbh authored Dec 6, 2024
1 parent 0b31ddc commit 546c4b9
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 10 deletions.
72 changes: 63 additions & 9 deletions icloudpy/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
from re import match
from tempfile import gettempdir
from uuid import uuid1
import srp
import base64
import hashlib

from requests import Session
from six import PY2
Expand Down Expand Up @@ -319,13 +322,6 @@ def authenticate(self, force_refresh=False, service=None):
if not login_successful:
LOGGER.debug("Authenticating as %s", self.user["accountName"])

data = dict(self.user)

data["rememberMe"] = True
data["trustTokens"] = []
if self.session_data.get("trust_token"):
data["trustTokens"] = [self.session_data.get("trust_token")]

headers = self._get_auth_headers()

if self.session_data.get("scnt"):
Expand All @@ -334,9 +330,67 @@ def authenticate(self, force_refresh=False, service=None):
if self.session_data.get("session_id"):
headers["X-Apple-ID-Session-Id"] = self.session_data.get("session_id")

class SrpPassword():
def __init__(self, password: str):
self.password = password

def set_encrypt_info(self, salt: bytes, iterations: int, key_length: int):
self.salt = salt
self.iterations = iterations
self.key_length = key_length

def encode(self):
password_hash = hashlib.sha256(self.password.encode('utf-8')).digest()
return hashlib.pbkdf2_hmac('sha256', password_hash, salt, iterations, key_length)

srp_password = SrpPassword(self.user["password"])
srp.rfc5054_enable()
srp.no_username_in_x()
usr = srp.User(self.user["accountName"], srp_password, hash_alg=srp.SHA256, ng_type=srp.NG_2048)

uname, A = usr.start_authentication()

data = {
'a': base64.b64encode(A).decode(),
'accountName': uname,
'protocols': ['s2k', 's2k_fo']
}

try:
response = self.session.post(f"{self.auth_endpoint}/signin/init", data=json.dumps(data),
headers=headers)
response.raise_for_status()
except ICloudPyAPIResponseException as error:
msg = "Failed to initiate srp authentication."
raise ICloudPyFailedLoginException(msg, error) from error

body = response.json()

salt = base64.b64decode(body['salt'])
b = base64.b64decode(body['b'])
c = body['c']
iterations = body['iteration']
key_length = 32
srp_password.set_encrypt_info(salt, iterations, key_length)

m1 = usr.process_challenge(salt, b)
m2 = usr.H_AMK

data = {
"accountName": uname,
"c": c,
"m1": base64.b64encode(m1).decode(),
"m2": base64.b64encode(m2).decode(),
"rememberMe": True,
"trustTokens": [],
}

if self.session_data.get("trust_token"):
data["trustTokens"] = [self.session_data.get("trust_token")]

try:
self.session.post(
f"{self.auth_endpoint}/signin",
f"{self.auth_endpoint}/signin/complete",
params={"isRememberMeEnabled": "true"},
data=json.dumps(data),
headers=headers,
Expand Down Expand Up @@ -400,7 +454,7 @@ def _validate_token(self):

def _get_auth_headers(self, overrides=None):
headers = {
"Accept": "*/*",
"Accept": "application/json, text/javascript",
"Content-Type": "application/json",
"X-Apple-OAuth-Client-Id": "d39ba9916b7251055b22c7f910e2ea796ee65e98b2ddecea8f5dde8d9d1a815d",
"X-Apple-OAuth-Client-Type": "firstPartyAuth",
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ keyring==23.11.0
keyrings.alt==4.2.0
click==8.1.7
six==1.16.0
srp==1.0.21
tzlocal==5.2
pytz==2024.2
certifi==2024.8.30
Expand Down
5 changes: 4 additions & 1 deletion tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from .const_findmyiphone import FMI_FAMILY_WORKING
from .const_login import (
AUTH_OK,
SRP_INIT_OK,
LOGIN_2FA,
LOGIN_WORKING,
TRUSTED_DEVICE_1,
Expand Down Expand Up @@ -102,9 +103,11 @@ def request(self, method, url, **kwargs):
if "signin" in url and method == "POST":
if (
data.get("accountName") not in VALID_USERS
or data.get("password") != VALID_PASSWORD
# or data.get("password") != VALID_PASSWORD
):
self._raise_error(None, "Unknown reason")
if url.endswith('/init'):
return ResponseMock(SRP_INIT_OK)
if data.get("accountName") == REQUIRES_2FA_USER:
self.service.session_data["session_token"] = REQUIRES_2FA_TOKEN
return ResponseMock(AUTH_OK)
Expand Down
7 changes: 7 additions & 0 deletions tests/const_login.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@
# Data
AUTH_OK = {"authType": "hsa2"}

SRP_INIT_OK = {'iteration': 20433,
'salt': '0samK84bcBmkVsswOpZbZg==',
'protocol': 's2k',
'b': 'STVHcWTN9YOYn4IgtIJ6UPdPbvzvL+zza/l+6yUHUtdEyxwzpB78y8wqZ8QWSbVqjBcpl32iEA4T3nYp0LWZ5hD3r3yIJFloXvX0kpBJkr+Nh8EfHuW1V50A8riH6VWyuJ8m3JmOO7/xkNgP7je8GMpt/5f/7qE3AOj73e3JR0fzQ7IopdU0tlyVX0tD7T6wCyHS52GJWDdq1I2bgzurIK2/ZjR/Hwzd/67oFQPtKQgjrSRaKo5MJEfDP7C9wOlXsZqbb7igX6PeZRWrfl+iQFaA/FVeWSngB07ja3wOryY9GsYO06ELGOaQ+MpsT7mouqrGTfOJ0OMh9EgrkJEM6w==',
'c': 'e-1be-8746c235-b41c-11ef-bd17-c780acb4fe15:PRN'
}

LOGIN_WORKING = {
"dsInfo": {
"lastName": LAST_NAME,
Expand Down

0 comments on commit 546c4b9

Please sign in to comment.