pymobiledevice3 4.14.6__py3-none-any.whl → 7.0.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- misc/plist_sniffer.py +15 -15
- misc/remotexpc_sniffer.py +29 -28
- misc/understanding_idevice_protocol_layers.md +15 -10
- pymobiledevice3/__main__.py +317 -127
- pymobiledevice3/_version.py +22 -4
- pymobiledevice3/bonjour.py +358 -113
- pymobiledevice3/ca.py +253 -16
- pymobiledevice3/cli/activation.py +31 -23
- pymobiledevice3/cli/afc.py +49 -40
- pymobiledevice3/cli/amfi.py +16 -21
- pymobiledevice3/cli/apps.py +87 -42
- pymobiledevice3/cli/backup.py +160 -90
- pymobiledevice3/cli/bonjour.py +44 -40
- pymobiledevice3/cli/cli_common.py +204 -198
- pymobiledevice3/cli/companion_proxy.py +14 -14
- pymobiledevice3/cli/crash.py +105 -56
- pymobiledevice3/cli/developer/__init__.py +62 -0
- pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
- pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
- pymobiledevice3/cli/developer/arbitration.py +50 -0
- pymobiledevice3/cli/developer/condition.py +33 -0
- pymobiledevice3/cli/developer/core_device.py +294 -0
- pymobiledevice3/cli/developer/debugserver.py +244 -0
- pymobiledevice3/cli/developer/dvt/__init__.py +438 -0
- pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
- pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
- pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
- pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
- pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
- pymobiledevice3/cli/developer/simulate_location.py +51 -0
- pymobiledevice3/cli/diagnostics/__init__.py +75 -0
- pymobiledevice3/cli/diagnostics/battery.py +47 -0
- pymobiledevice3/cli/idam.py +42 -0
- pymobiledevice3/cli/lockdown.py +108 -103
- pymobiledevice3/cli/mounter.py +158 -99
- pymobiledevice3/cli/notification.py +38 -26
- pymobiledevice3/cli/pcap.py +45 -24
- pymobiledevice3/cli/power_assertion.py +18 -17
- pymobiledevice3/cli/processes.py +17 -23
- pymobiledevice3/cli/profile.py +165 -109
- pymobiledevice3/cli/provision.py +35 -34
- pymobiledevice3/cli/remote.py +217 -129
- pymobiledevice3/cli/restore.py +159 -143
- pymobiledevice3/cli/springboard.py +63 -53
- pymobiledevice3/cli/syslog.py +193 -86
- pymobiledevice3/cli/usbmux.py +73 -33
- pymobiledevice3/cli/version.py +5 -7
- pymobiledevice3/cli/webinspector.py +376 -214
- pymobiledevice3/common.py +3 -1
- pymobiledevice3/exceptions.py +182 -58
- pymobiledevice3/irecv.py +52 -53
- pymobiledevice3/irecv_devices.py +1489 -464
- pymobiledevice3/lockdown.py +473 -275
- pymobiledevice3/lockdown_service_provider.py +15 -8
- pymobiledevice3/osu/os_utils.py +27 -9
- pymobiledevice3/osu/posix_util.py +34 -15
- pymobiledevice3/osu/win_util.py +14 -8
- pymobiledevice3/pair_records.py +102 -21
- pymobiledevice3/remote/common.py +8 -4
- pymobiledevice3/remote/core_device/app_service.py +94 -67
- pymobiledevice3/remote/core_device/core_device_service.py +17 -14
- pymobiledevice3/remote/core_device/device_info.py +5 -5
- pymobiledevice3/remote/core_device/diagnostics_service.py +19 -4
- pymobiledevice3/remote/core_device/file_service.py +53 -23
- pymobiledevice3/remote/remote_service_discovery.py +79 -45
- pymobiledevice3/remote/remotexpc.py +73 -44
- pymobiledevice3/remote/tunnel_service.py +442 -317
- pymobiledevice3/remote/utils.py +14 -13
- pymobiledevice3/remote/xpc_message.py +145 -125
- pymobiledevice3/resources/dsc_uuid_map.py +19 -19
- pymobiledevice3/resources/firmware_notifications.py +20 -16
- pymobiledevice3/resources/notifications.txt +144 -0
- pymobiledevice3/restore/asr.py +27 -27
- pymobiledevice3/restore/base_restore.py +110 -21
- pymobiledevice3/restore/consts.py +87 -66
- pymobiledevice3/restore/device.py +59 -12
- pymobiledevice3/restore/fdr.py +46 -48
- pymobiledevice3/restore/ftab.py +19 -19
- pymobiledevice3/restore/img4.py +163 -0
- pymobiledevice3/restore/mbn.py +587 -0
- pymobiledevice3/restore/recovery.py +151 -151
- pymobiledevice3/restore/restore.py +562 -544
- pymobiledevice3/restore/restore_options.py +131 -110
- pymobiledevice3/restore/restored_client.py +51 -31
- pymobiledevice3/restore/tss.py +385 -267
- pymobiledevice3/service_connection.py +252 -59
- pymobiledevice3/services/accessibilityaudit.py +202 -120
- pymobiledevice3/services/afc.py +962 -365
- pymobiledevice3/services/amfi.py +24 -30
- pymobiledevice3/services/companion.py +23 -19
- pymobiledevice3/services/crash_reports.py +71 -47
- pymobiledevice3/services/debugserver_applist.py +3 -3
- pymobiledevice3/services/device_arbitration.py +8 -8
- pymobiledevice3/services/device_link.py +101 -79
- pymobiledevice3/services/diagnostics.py +973 -967
- pymobiledevice3/services/dtfetchsymbols.py +8 -8
- pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
- pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
- pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
- pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
- pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
- pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +466 -384
- pymobiledevice3/services/dvt/instruments/device_info.py +20 -11
- pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
- pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
- pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
- pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
- pymobiledevice3/services/dvt/instruments/process_control.py +35 -10
- pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
- pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
- pymobiledevice3/services/dvt/testmanaged/xcuitest.py +42 -52
- pymobiledevice3/services/file_relay.py +10 -10
- pymobiledevice3/services/heartbeat.py +9 -8
- pymobiledevice3/services/house_arrest.py +16 -15
- pymobiledevice3/services/idam.py +20 -0
- pymobiledevice3/services/installation_proxy.py +173 -81
- pymobiledevice3/services/lockdown_service.py +20 -10
- pymobiledevice3/services/misagent.py +22 -19
- pymobiledevice3/services/mobile_activation.py +147 -64
- pymobiledevice3/services/mobile_config.py +331 -294
- pymobiledevice3/services/mobile_image_mounter.py +141 -113
- pymobiledevice3/services/mobilebackup2.py +203 -145
- pymobiledevice3/services/notification_proxy.py +11 -11
- pymobiledevice3/services/os_trace.py +134 -74
- pymobiledevice3/services/pcapd.py +314 -302
- pymobiledevice3/services/power_assertion.py +10 -9
- pymobiledevice3/services/preboard.py +4 -4
- pymobiledevice3/services/remote_fetch_symbols.py +21 -14
- pymobiledevice3/services/remote_server.py +176 -146
- pymobiledevice3/services/restore_service.py +16 -16
- pymobiledevice3/services/screenshot.py +15 -12
- pymobiledevice3/services/simulate_location.py +7 -7
- pymobiledevice3/services/springboard.py +15 -15
- pymobiledevice3/services/syslog.py +5 -5
- pymobiledevice3/services/web_protocol/alert.py +11 -11
- pymobiledevice3/services/web_protocol/automation_session.py +251 -239
- pymobiledevice3/services/web_protocol/cdp_screencast.py +46 -37
- pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
- pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
- pymobiledevice3/services/web_protocol/driver.py +114 -111
- pymobiledevice3/services/web_protocol/element.py +124 -111
- pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
- pymobiledevice3/services/web_protocol/selenium_api.py +49 -49
- pymobiledevice3/services/web_protocol/session_protocol.py +18 -12
- pymobiledevice3/services/web_protocol/switch_to.py +30 -27
- pymobiledevice3/services/webinspector.py +189 -155
- pymobiledevice3/tcp_forwarder.py +87 -69
- pymobiledevice3/tunneld/__init__.py +0 -0
- pymobiledevice3/tunneld/api.py +63 -0
- pymobiledevice3/tunneld/server.py +603 -0
- pymobiledevice3/usbmux.py +198 -147
- pymobiledevice3/utils.py +14 -11
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +55 -28
- pymobiledevice3-7.0.6.dist-info/RECORD +188 -0
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +1 -1
- pymobiledevice3/cli/developer.py +0 -1215
- pymobiledevice3/cli/diagnostics.py +0 -99
- pymobiledevice3/tunneld.py +0 -524
- pymobiledevice3-4.14.6.dist-info/RECORD +0 -168
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info/licenses}/LICENSE +0 -0
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/top_level.txt +0 -0
pymobiledevice3/lockdown.py
CHANGED
|
@@ -7,49 +7,74 @@ import sys
|
|
|
7
7
|
import tempfile
|
|
8
8
|
import time
|
|
9
9
|
from abc import ABC, abstractmethod
|
|
10
|
-
from collections.abc import
|
|
10
|
+
from collections.abc import AsyncIterable
|
|
11
11
|
from contextlib import contextmanager, suppress
|
|
12
12
|
from enum import Enum
|
|
13
13
|
from functools import wraps
|
|
14
14
|
from pathlib import Path
|
|
15
|
-
from ssl import SSLZeroReturnError
|
|
15
|
+
from ssl import SSLError, SSLZeroReturnError, TLSVersion
|
|
16
16
|
from typing import Optional
|
|
17
17
|
|
|
18
|
+
import construct
|
|
18
19
|
from cryptography import x509
|
|
19
20
|
from cryptography.hazmat.primitives import hashes, serialization
|
|
20
21
|
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
|
|
21
22
|
from cryptography.hazmat.primitives.serialization import Encoding
|
|
22
|
-
from cryptography.hazmat.primitives.serialization.pkcs7 import PKCS7SignatureBuilder
|
|
23
|
+
from cryptography.hazmat.primitives.serialization.pkcs7 import PKCS7Options, PKCS7SignatureBuilder
|
|
23
24
|
from packaging.version import Version
|
|
24
25
|
|
|
25
26
|
from pymobiledevice3 import usbmux
|
|
26
27
|
from pymobiledevice3.bonjour import DEFAULT_BONJOUR_TIMEOUT, browse_mobdev2
|
|
27
|
-
from pymobiledevice3.ca import
|
|
28
|
+
from pymobiledevice3.ca import generate_pairing_cert_chain
|
|
28
29
|
from pymobiledevice3.common import get_home_folder
|
|
29
|
-
from pymobiledevice3.exceptions import
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
from pymobiledevice3.exceptions import (
|
|
31
|
+
BadDevError,
|
|
32
|
+
CannotStopSessionError,
|
|
33
|
+
ConnectionFailedError,
|
|
34
|
+
ConnectionTerminatedError,
|
|
35
|
+
DeviceNotFoundError,
|
|
36
|
+
FatalPairingError,
|
|
37
|
+
GetProhibitedError,
|
|
38
|
+
IncorrectModeError,
|
|
39
|
+
InvalidConnectionError,
|
|
40
|
+
InvalidHostIDError,
|
|
41
|
+
InvalidServiceError,
|
|
42
|
+
LockdownError,
|
|
43
|
+
MissingValueError,
|
|
44
|
+
NoDeviceConnectedError,
|
|
45
|
+
NotPairedError,
|
|
46
|
+
PairingDialogResponsePendingError,
|
|
47
|
+
PairingError,
|
|
48
|
+
PasswordRequiredError,
|
|
49
|
+
PyMobileDevice3Exception,
|
|
50
|
+
SetProhibitedError,
|
|
51
|
+
StartServiceError,
|
|
52
|
+
UserDeniedPairingError,
|
|
53
|
+
)
|
|
33
54
|
from pymobiledevice3.irecv_devices import IRECV_DEVICES
|
|
34
55
|
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
35
|
-
from pymobiledevice3.pair_records import
|
|
36
|
-
|
|
56
|
+
from pymobiledevice3.pair_records import (
|
|
57
|
+
create_pairing_records_cache_folder,
|
|
58
|
+
generate_host_id,
|
|
59
|
+
get_preferred_pair_record,
|
|
60
|
+
)
|
|
37
61
|
from pymobiledevice3.service_connection import ServiceConnection
|
|
38
62
|
from pymobiledevice3.usbmux import PlistMuxConnection
|
|
39
63
|
|
|
40
|
-
SYSTEM_BUID =
|
|
64
|
+
SYSTEM_BUID = "30142955-444094379208051516"
|
|
65
|
+
RESTORED_SERVICE_TYPE = "com.apple.mobile.restored"
|
|
41
66
|
|
|
42
|
-
DEFAULT_LABEL =
|
|
67
|
+
DEFAULT_LABEL = "pymobiledevice3"
|
|
43
68
|
SERVICE_PORT = 62078
|
|
44
69
|
|
|
45
70
|
|
|
46
71
|
class DeviceClass(Enum):
|
|
47
|
-
IPHONE =
|
|
48
|
-
IPAD =
|
|
49
|
-
IPOD =
|
|
50
|
-
WATCH =
|
|
51
|
-
APPLE_TV =
|
|
52
|
-
UNKNOWN =
|
|
72
|
+
IPHONE = "iPhone"
|
|
73
|
+
IPAD = "iPad"
|
|
74
|
+
IPOD = "iPod"
|
|
75
|
+
WATCH = "Watch"
|
|
76
|
+
APPLE_TV = "AppleTV"
|
|
77
|
+
UNKNOWN = "Unknown"
|
|
53
78
|
|
|
54
79
|
|
|
55
80
|
def _reconnect_on_remote_close(f):
|
|
@@ -58,7 +83,7 @@ def _reconnect_on_remote_close(f):
|
|
|
58
83
|
transmitted). When this happens, we'll attempt to reconnect.
|
|
59
84
|
"""
|
|
60
85
|
|
|
61
|
-
def _reconnect(self:
|
|
86
|
+
def _reconnect(self: "LockdownClient"):
|
|
62
87
|
self._reestablish_connection()
|
|
63
88
|
self.validate_pairing()
|
|
64
89
|
|
|
@@ -66,11 +91,11 @@ def _reconnect_on_remote_close(f):
|
|
|
66
91
|
def _inner_reconnect_on_remote_close(*args, **kwargs):
|
|
67
92
|
try:
|
|
68
93
|
return f(*args, **kwargs)
|
|
69
|
-
except (BrokenPipeError, ConnectionTerminatedError):
|
|
94
|
+
except (BrokenPipeError, ConnectionTerminatedError, SSLError):
|
|
70
95
|
_reconnect(args[0])
|
|
71
96
|
return f(*args, **kwargs)
|
|
72
97
|
except ConnectionAbortedError:
|
|
73
|
-
if sys.platform !=
|
|
98
|
+
if sys.platform != "win32":
|
|
74
99
|
raise
|
|
75
100
|
_reconnect(args[0])
|
|
76
101
|
return f(*args, **kwargs)
|
|
@@ -79,9 +104,17 @@ def _reconnect_on_remote_close(f):
|
|
|
79
104
|
|
|
80
105
|
|
|
81
106
|
class LockdownClient(ABC, LockdownServiceProvider):
|
|
82
|
-
def __init__(
|
|
83
|
-
|
|
84
|
-
|
|
107
|
+
def __init__(
|
|
108
|
+
self,
|
|
109
|
+
service: ServiceConnection,
|
|
110
|
+
host_id: str,
|
|
111
|
+
identifier: Optional[str] = None,
|
|
112
|
+
label: str = DEFAULT_LABEL,
|
|
113
|
+
system_buid: str = SYSTEM_BUID,
|
|
114
|
+
pair_record: Optional[dict] = None,
|
|
115
|
+
pairing_records_cache_folder: Optional[Path] = None,
|
|
116
|
+
port: int = SERVICE_PORT,
|
|
117
|
+
):
|
|
85
118
|
"""
|
|
86
119
|
Create a LockdownClient instance
|
|
87
120
|
|
|
@@ -107,21 +140,31 @@ class LockdownClient(ABC, LockdownServiceProvider):
|
|
|
107
140
|
self.pairing_records_cache_folder = pairing_records_cache_folder
|
|
108
141
|
self.port = port
|
|
109
142
|
|
|
110
|
-
if self.query_type() !=
|
|
143
|
+
if self.query_type() != "com.apple.mobile.lockdown":
|
|
111
144
|
raise IncorrectModeError()
|
|
112
145
|
|
|
113
146
|
self.all_values = self.get_value()
|
|
114
|
-
self.udid = self.all_values.get(
|
|
115
|
-
self.unique_chip_id = self.all_values.get(
|
|
116
|
-
self.device_public_key = self.all_values.get(
|
|
117
|
-
self.product_type = self.all_values.get(
|
|
147
|
+
self.udid = self.all_values.get("UniqueDeviceID")
|
|
148
|
+
self.unique_chip_id = self.all_values.get("UniqueChipID")
|
|
149
|
+
self.device_public_key = self.all_values.get("DevicePublicKey")
|
|
150
|
+
self.product_type = self.all_values.get("ProductType")
|
|
118
151
|
|
|
119
152
|
@classmethod
|
|
120
|
-
def create(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
153
|
+
def create(
|
|
154
|
+
cls,
|
|
155
|
+
service: ServiceConnection,
|
|
156
|
+
identifier: Optional[str] = None,
|
|
157
|
+
system_buid: str = SYSTEM_BUID,
|
|
158
|
+
label: str = DEFAULT_LABEL,
|
|
159
|
+
autopair: bool = True,
|
|
160
|
+
pair_timeout: Optional[float] = None,
|
|
161
|
+
local_hostname: Optional[str] = None,
|
|
162
|
+
pair_record: Optional[dict] = None,
|
|
163
|
+
pairing_records_cache_folder: Optional[Path] = None,
|
|
164
|
+
port: int = SERVICE_PORT,
|
|
165
|
+
private_key: Optional[RSAPrivateKey] = None,
|
|
166
|
+
**cls_specific_args,
|
|
167
|
+
):
|
|
125
168
|
"""
|
|
126
169
|
Create a LockdownClient instance
|
|
127
170
|
|
|
@@ -143,23 +186,32 @@ class LockdownClient(ABC, LockdownServiceProvider):
|
|
|
143
186
|
pairing_records_cache_folder = create_pairing_records_cache_folder(pairing_records_cache_folder)
|
|
144
187
|
|
|
145
188
|
lockdown_client = cls(
|
|
146
|
-
service,
|
|
147
|
-
|
|
148
|
-
|
|
189
|
+
service,
|
|
190
|
+
host_id=host_id,
|
|
191
|
+
identifier=identifier,
|
|
192
|
+
label=label,
|
|
193
|
+
system_buid=system_buid,
|
|
194
|
+
pair_record=pair_record,
|
|
195
|
+
pairing_records_cache_folder=pairing_records_cache_folder,
|
|
196
|
+
port=port,
|
|
197
|
+
**cls_specific_args,
|
|
198
|
+
)
|
|
149
199
|
lockdown_client._handle_autopair(autopair, pair_timeout, private_key=private_key)
|
|
150
200
|
return lockdown_client
|
|
151
201
|
|
|
152
202
|
def __repr__(self) -> str:
|
|
153
|
-
return
|
|
154
|
-
|
|
203
|
+
return (
|
|
204
|
+
f"<{self.__class__.__name__} ID:{self.identifier} VERSION:{self.product_version} "
|
|
205
|
+
f"TYPE:{self.product_type} PAIRED:{self.paired}>"
|
|
206
|
+
)
|
|
155
207
|
|
|
156
|
-
def __enter__(self) ->
|
|
208
|
+
def __enter__(self) -> "LockdownClient":
|
|
157
209
|
return self
|
|
158
210
|
|
|
159
211
|
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
160
212
|
self.close()
|
|
161
213
|
|
|
162
|
-
async def __aenter__(self) ->
|
|
214
|
+
async def __aenter__(self) -> "LockdownClient":
|
|
163
215
|
return self
|
|
164
216
|
|
|
165
217
|
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
@@ -167,24 +219,28 @@ class LockdownClient(ABC, LockdownServiceProvider):
|
|
|
167
219
|
|
|
168
220
|
@property
|
|
169
221
|
def product_version(self) -> str:
|
|
170
|
-
return self.all_values.get(
|
|
222
|
+
return self.all_values.get("ProductVersion") or "1.0"
|
|
223
|
+
|
|
224
|
+
@property
|
|
225
|
+
def product_build_version(self) -> str:
|
|
226
|
+
return self.all_values.get("BuildVersion")
|
|
171
227
|
|
|
172
228
|
@property
|
|
173
229
|
def device_class(self) -> DeviceClass:
|
|
174
230
|
try:
|
|
175
|
-
return DeviceClass(self.all_values.get(
|
|
231
|
+
return DeviceClass(self.all_values.get("DeviceClass"))
|
|
176
232
|
except ValueError:
|
|
177
|
-
return DeviceClass(
|
|
233
|
+
return DeviceClass("Unknown")
|
|
178
234
|
|
|
179
235
|
@property
|
|
180
236
|
def wifi_mac_address(self) -> str:
|
|
181
|
-
return self.all_values.get(
|
|
237
|
+
return self.all_values.get("WiFiAddress")
|
|
182
238
|
|
|
183
239
|
@property
|
|
184
240
|
def short_info(self) -> dict:
|
|
185
|
-
keys_to_copy = [
|
|
241
|
+
keys_to_copy = ["DeviceClass", "DeviceName", "BuildVersion", "ProductVersion", "ProductType", "UniqueDeviceID"]
|
|
186
242
|
result = {
|
|
187
|
-
|
|
243
|
+
"Identifier": self.identifier,
|
|
188
244
|
}
|
|
189
245
|
for key in keys_to_copy:
|
|
190
246
|
result[key] = self.all_values.get(key)
|
|
@@ -192,61 +248,65 @@ class LockdownClient(ABC, LockdownServiceProvider):
|
|
|
192
248
|
|
|
193
249
|
@property
|
|
194
250
|
def share_iphone_analytics_enabled(self) -> bool:
|
|
195
|
-
return self.get_value(
|
|
251
|
+
return self.get_value("com.apple.MobileDeviceCrashCopy", "ShouldSubmit")
|
|
196
252
|
|
|
197
253
|
@property
|
|
198
254
|
def assistive_touch(self) -> bool:
|
|
199
255
|
"""AssistiveTouch (the on-screen software home button)"""
|
|
200
|
-
return bool(self.get_value(
|
|
256
|
+
return bool(self.get_value("com.apple.Accessibility").get("AssistiveTouchEnabledByiTunes", 0))
|
|
201
257
|
|
|
202
258
|
@assistive_touch.setter
|
|
203
259
|
def assistive_touch(self, value: bool) -> None:
|
|
204
260
|
"""AssistiveTouch (the on-screen software home button)"""
|
|
205
|
-
self.set_value(int(value),
|
|
261
|
+
self.set_value(int(value), "com.apple.Accessibility", "AssistiveTouchEnabledByiTunes")
|
|
206
262
|
|
|
207
263
|
@property
|
|
208
264
|
def voice_over(self) -> bool:
|
|
209
|
-
return bool(self.get_value(
|
|
265
|
+
return bool(self.get_value("com.apple.Accessibility").get("VoiceOverTouchEnabledByiTunes", 0))
|
|
210
266
|
|
|
211
267
|
@voice_over.setter
|
|
212
268
|
def voice_over(self, value: bool) -> None:
|
|
213
|
-
self.set_value(int(value),
|
|
269
|
+
self.set_value(int(value), "com.apple.Accessibility", "VoiceOverTouchEnabledByiTunes")
|
|
214
270
|
|
|
215
271
|
@property
|
|
216
272
|
def invert_display(self) -> bool:
|
|
217
|
-
return bool(self.get_value(
|
|
273
|
+
return bool(self.get_value("com.apple.Accessibility").get("InvertDisplayEnabledByiTunes", 0))
|
|
218
274
|
|
|
219
275
|
@invert_display.setter
|
|
220
276
|
def invert_display(self, value: bool) -> None:
|
|
221
|
-
self.set_value(int(value),
|
|
277
|
+
self.set_value(int(value), "com.apple.Accessibility", "InvertDisplayEnabledByiTunes")
|
|
222
278
|
|
|
223
279
|
@property
|
|
224
280
|
def enable_wifi_connections(self) -> bool:
|
|
225
|
-
return self.get_value(
|
|
281
|
+
return self.get_value("com.apple.mobile.wireless_lockdown").get("EnableWifiConnections", False)
|
|
226
282
|
|
|
227
283
|
@enable_wifi_connections.setter
|
|
228
284
|
def enable_wifi_connections(self, value: bool) -> None:
|
|
229
|
-
self.set_value(value,
|
|
285
|
+
self.set_value(value, "com.apple.mobile.wireless_lockdown", "EnableWifiConnections")
|
|
230
286
|
|
|
231
287
|
@property
|
|
232
288
|
def ecid(self) -> int:
|
|
233
|
-
return self.all_values[
|
|
289
|
+
return self.all_values["UniqueChipID"]
|
|
234
290
|
|
|
235
291
|
@property
|
|
236
292
|
def date(self) -> datetime.datetime:
|
|
237
|
-
return datetime.datetime.fromtimestamp(self.get_value(key=
|
|
293
|
+
return datetime.datetime.fromtimestamp(self.get_value(key="TimeIntervalSince1970"))
|
|
238
294
|
|
|
239
295
|
@property
|
|
240
296
|
def language(self) -> str:
|
|
241
|
-
return self.get_value(key=
|
|
297
|
+
return self.get_value(key="Language", domain="com.apple.international")
|
|
242
298
|
|
|
243
299
|
@property
|
|
244
300
|
def locale(self) -> str:
|
|
245
|
-
return self.get_value(key=
|
|
301
|
+
return self.get_value(key="Locale", domain="com.apple.international")
|
|
246
302
|
|
|
247
303
|
@property
|
|
248
304
|
def preflight_info(self) -> dict:
|
|
249
|
-
return self.get_value(key=
|
|
305
|
+
return self.get_value(key="PreflightInfo")
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def firmware_preflight_info(self) -> dict:
|
|
309
|
+
return self.get_value(key="FirmwarePreflightInfo")
|
|
250
310
|
|
|
251
311
|
@property
|
|
252
312
|
def display_name(self) -> str:
|
|
@@ -274,34 +334,35 @@ class LockdownClient(ABC, LockdownServiceProvider):
|
|
|
274
334
|
|
|
275
335
|
@property
|
|
276
336
|
def developer_mode_status(self) -> bool:
|
|
277
|
-
return self.get_value(
|
|
337
|
+
return self.get_value("com.apple.security.mac.amfi", "DeveloperModeStatus")
|
|
278
338
|
|
|
279
339
|
def query_type(self) -> str:
|
|
280
|
-
return self._request(
|
|
340
|
+
return self._request("QueryType").get("Type")
|
|
281
341
|
|
|
282
342
|
def set_language(self, language: str) -> None:
|
|
283
|
-
self.set_value(language, key=
|
|
343
|
+
self.set_value(language, key="Language", domain="com.apple.international")
|
|
284
344
|
|
|
285
345
|
def set_locale(self, locale: str) -> None:
|
|
286
|
-
self.set_value(locale, key=
|
|
346
|
+
self.set_value(locale, key="Locale", domain="com.apple.international")
|
|
287
347
|
|
|
288
348
|
def set_timezone(self, timezone: str) -> None:
|
|
289
|
-
self.set_value(timezone, key=
|
|
349
|
+
self.set_value(timezone, key="TimeZone")
|
|
290
350
|
|
|
291
351
|
def set_uses24hClock(self, value: bool) -> None:
|
|
292
|
-
self.set_value(value, key=
|
|
352
|
+
self.set_value(value, key="Uses24HourClock")
|
|
293
353
|
|
|
294
354
|
@_reconnect_on_remote_close
|
|
295
355
|
def enter_recovery(self):
|
|
296
|
-
return self._request(
|
|
356
|
+
return self._request("EnterRecovery")
|
|
297
357
|
|
|
298
358
|
def stop_session(self) -> dict:
|
|
299
359
|
if self.session_id and self.service:
|
|
300
|
-
response = self._request(
|
|
360
|
+
response = self._request("StopSession", {"SessionID": self.session_id})
|
|
301
361
|
self.session_id = None
|
|
302
|
-
if not response or response.get(
|
|
362
|
+
if not response or response.get("Result") != "Success":
|
|
303
363
|
raise CannotStopSessionError()
|
|
304
364
|
return response
|
|
365
|
+
raise PyMobileDevice3Exception("No active session")
|
|
305
366
|
|
|
306
367
|
def validate_pairing(self) -> bool:
|
|
307
368
|
if self.pair_record is None:
|
|
@@ -310,23 +371,28 @@ class LockdownClient(ABC, LockdownServiceProvider):
|
|
|
310
371
|
if self.pair_record is None:
|
|
311
372
|
return False
|
|
312
373
|
|
|
313
|
-
if (Version(self.product_version) < Version(
|
|
374
|
+
if (Version(self.product_version) < Version("7.0")) and (self.device_class != DeviceClass.WATCH):
|
|
314
375
|
try:
|
|
315
|
-
self._request(
|
|
376
|
+
self._request("ValidatePair", {"PairRecord": self.pair_record})
|
|
316
377
|
except PairingError:
|
|
317
378
|
return False
|
|
318
379
|
|
|
319
|
-
self.host_id = self.pair_record.get(
|
|
320
|
-
self.system_buid = self.pair_record.get(
|
|
380
|
+
self.host_id = self.pair_record.get("HostID", self.host_id)
|
|
381
|
+
self.system_buid = self.pair_record.get("SystemBUID", self.system_buid)
|
|
321
382
|
|
|
322
383
|
try:
|
|
323
|
-
start_session = self._request(
|
|
384
|
+
start_session = self._request("StartSession", {"HostID": self.host_id, "SystemBUID": self.system_buid})
|
|
324
385
|
except (InvalidHostIDError, InvalidConnectionError):
|
|
325
386
|
# no host id means there is no such pairing record
|
|
326
387
|
return False
|
|
327
388
|
|
|
328
|
-
self.session_id = start_session.get(
|
|
329
|
-
if start_session.get(
|
|
389
|
+
self.session_id = start_session.get("SessionID")
|
|
390
|
+
if start_session.get("EnableSessionSSL"):
|
|
391
|
+
if (Version(self.product_version) < Version("5.0")) and (self.device_class != DeviceClass.WATCH):
|
|
392
|
+
# TLS v1 is the protocol required for versions prior to iOS 5
|
|
393
|
+
self.service.min_ssl_proto = TLSVersion.SSLv3
|
|
394
|
+
self.service.max_ssl_proto = TLSVersion.TLSv1
|
|
395
|
+
|
|
330
396
|
with self.ssl_file() as f:
|
|
331
397
|
try:
|
|
332
398
|
self.service.ssl_start(f)
|
|
@@ -339,41 +405,48 @@ class LockdownClient(ABC, LockdownServiceProvider):
|
|
|
339
405
|
|
|
340
406
|
# reload data after pairing
|
|
341
407
|
self.all_values = self.get_value()
|
|
342
|
-
self.udid = self.all_values.get(
|
|
408
|
+
self.udid = self.all_values.get("UniqueDeviceID")
|
|
343
409
|
|
|
344
410
|
return True
|
|
345
411
|
|
|
346
412
|
@_reconnect_on_remote_close
|
|
347
|
-
def pair(self, timeout: float = None, private_key: Optional[RSAPrivateKey] = None) -> None:
|
|
348
|
-
self.device_public_key = self.get_value(
|
|
413
|
+
def pair(self, timeout: Optional[float] = None, private_key: Optional[RSAPrivateKey] = None) -> None:
|
|
414
|
+
self.device_public_key = self.get_value("", "DevicePublicKey")
|
|
349
415
|
if not self.device_public_key:
|
|
350
|
-
self.logger.error(
|
|
416
|
+
self.logger.error("Unable to retrieve DevicePublicKey")
|
|
351
417
|
self.service.close()
|
|
352
418
|
raise PairingError()
|
|
353
419
|
|
|
354
|
-
self.logger.info(
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
420
|
+
self.logger.info("Creating host key & certificate")
|
|
421
|
+
host_cert_pem, host_key_pem, device_cert_pem, root_cert_pem, root_key_pem = generate_pairing_cert_chain(
|
|
422
|
+
self.device_public_key,
|
|
423
|
+
private_key=private_key,
|
|
424
|
+
# TODO: consider parsing product_version to support iOS < 4
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
pair_record = {
|
|
428
|
+
"DeviceCertificate": device_cert_pem,
|
|
429
|
+
"HostCertificate": host_cert_pem,
|
|
430
|
+
"HostID": self.host_id,
|
|
431
|
+
"RootCertificate": root_cert_pem,
|
|
432
|
+
"RootPrivateKey": root_key_pem,
|
|
433
|
+
"WiFiMACAddress": self.wifi_mac_address,
|
|
434
|
+
"SystemBUID": self.system_buid,
|
|
435
|
+
}
|
|
366
436
|
|
|
367
|
-
pair_options = {
|
|
368
|
-
|
|
437
|
+
pair_options = {
|
|
438
|
+
"PairRecord": pair_record,
|
|
439
|
+
"ProtocolVersion": "2",
|
|
440
|
+
"PairingOptions": {"ExtendedPairingErrors": True},
|
|
441
|
+
}
|
|
369
442
|
|
|
370
443
|
pair = self._request_pair(pair_options, timeout=timeout)
|
|
371
444
|
|
|
372
|
-
pair_record[
|
|
373
|
-
escrow_bag = pair.get(
|
|
445
|
+
pair_record["HostPrivateKey"] = host_key_pem
|
|
446
|
+
escrow_bag = pair.get("EscrowBag")
|
|
374
447
|
|
|
375
448
|
if escrow_bag is not None:
|
|
376
|
-
pair_record[
|
|
449
|
+
pair_record["EscrowBag"] = pair.get("EscrowBag")
|
|
377
450
|
|
|
378
451
|
self.pair_record = pair_record
|
|
379
452
|
self.save_pair_record()
|
|
@@ -381,138 +454,150 @@ class LockdownClient(ABC, LockdownServiceProvider):
|
|
|
381
454
|
|
|
382
455
|
@_reconnect_on_remote_close
|
|
383
456
|
def pair_supervised(self, keybag_file: Path, timeout: Optional[float] = None) -> None:
|
|
384
|
-
with open(keybag_file,
|
|
457
|
+
with open(keybag_file, "rb") as keybag_file:
|
|
385
458
|
keybag_file = keybag_file.read()
|
|
386
459
|
private_key = serialization.load_pem_private_key(keybag_file, password=None)
|
|
387
460
|
cer = x509.load_pem_x509_certificate(keybag_file)
|
|
388
461
|
public_key = cer.public_bytes(Encoding.DER)
|
|
389
462
|
|
|
390
|
-
self.device_public_key = self.get_value(
|
|
463
|
+
self.device_public_key = self.get_value("", "DevicePublicKey")
|
|
391
464
|
if not self.device_public_key:
|
|
392
|
-
self.logger.error(
|
|
465
|
+
self.logger.error("Unable to retrieve DevicePublicKey")
|
|
393
466
|
self.service.close()
|
|
394
467
|
raise PairingError()
|
|
395
468
|
|
|
396
|
-
self.logger.info(
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
469
|
+
self.logger.info("Creating host key & certificate")
|
|
470
|
+
host_cert_pem, host_key_pem, device_cert_pem, root_cert_pem, root_key_pem = generate_pairing_cert_chain(
|
|
471
|
+
self.device_public_key
|
|
472
|
+
# TODO: consider parsing product_version to support iOS < 4
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
pair_record = {
|
|
476
|
+
"DeviceCertificate": device_cert_pem,
|
|
477
|
+
"HostCertificate": host_cert_pem,
|
|
478
|
+
"HostID": self.host_id,
|
|
479
|
+
"RootCertificate": root_cert_pem,
|
|
480
|
+
"RootPrivateKey": root_key_pem,
|
|
481
|
+
"WiFiMACAddress": self.wifi_mac_address,
|
|
482
|
+
"SystemBUID": self.system_buid,
|
|
483
|
+
}
|
|
407
484
|
|
|
408
|
-
pair_options = {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
485
|
+
pair_options = {
|
|
486
|
+
"PairRecord": pair_record,
|
|
487
|
+
"ProtocolVersion": "2",
|
|
488
|
+
"PairingOptions": {"SupervisorCertificate": public_key, "ExtendedPairingErrors": True},
|
|
489
|
+
}
|
|
412
490
|
|
|
413
491
|
# first pair with SupervisorCertificate as PairingOptions to get PairingChallenge
|
|
414
492
|
pair = self._request_pair(pair_options, timeout=timeout)
|
|
415
|
-
if pair.get(
|
|
416
|
-
extended_response = pair.get(
|
|
493
|
+
if pair.get("Error") == "MCChallengeRequired":
|
|
494
|
+
extended_response = pair.get("ExtendedResponse")
|
|
417
495
|
if extended_response is not None:
|
|
418
|
-
pairing_challenge = extended_response.get(
|
|
419
|
-
signed_response =
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
496
|
+
pairing_challenge = extended_response.get("PairingChallenge")
|
|
497
|
+
signed_response = (
|
|
498
|
+
PKCS7SignatureBuilder()
|
|
499
|
+
.set_data(pairing_challenge)
|
|
500
|
+
.add_signer(cer, private_key, hashes.SHA256())
|
|
501
|
+
.sign(Encoding.DER, [PKCS7Options.Binary])
|
|
502
|
+
)
|
|
503
|
+
pair_options = {
|
|
504
|
+
"PairRecord": pair_record,
|
|
505
|
+
"ProtocolVersion": "2",
|
|
506
|
+
"PairingOptions": {"ChallengeResponse": signed_response, "ExtendedPairingErrors": True},
|
|
507
|
+
}
|
|
423
508
|
# second pair with Response to Challenge
|
|
424
509
|
pair = self._request_pair(pair_options, timeout=timeout)
|
|
425
510
|
|
|
426
|
-
pair_record[
|
|
427
|
-
escrow_bag = pair.get(
|
|
511
|
+
pair_record["HostPrivateKey"] = host_key_pem
|
|
512
|
+
escrow_bag = pair.get("EscrowBag")
|
|
428
513
|
|
|
429
514
|
if escrow_bag is not None:
|
|
430
|
-
pair_record[
|
|
515
|
+
pair_record["EscrowBag"] = pair.get("EscrowBag")
|
|
431
516
|
|
|
432
517
|
self.pair_record = pair_record
|
|
433
518
|
self.save_pair_record()
|
|
434
519
|
self.paired = True
|
|
435
520
|
|
|
436
521
|
@_reconnect_on_remote_close
|
|
437
|
-
def unpair(self, host_id: str = None) -> None:
|
|
438
|
-
pair_record = self.pair_record if host_id is None else {
|
|
439
|
-
self._request(
|
|
522
|
+
def unpair(self, host_id: Optional[str] = None) -> None:
|
|
523
|
+
pair_record = self.pair_record if host_id is None else {"HostID": host_id}
|
|
524
|
+
self._request("Unpair", {"PairRecord": pair_record, "ProtocolVersion": "2"}, verify_request=False)
|
|
440
525
|
|
|
441
526
|
@_reconnect_on_remote_close
|
|
442
527
|
def reset_pairing(self):
|
|
443
|
-
return self._request(
|
|
528
|
+
return self._request("ResetPairing", {"FullReset": True})
|
|
444
529
|
|
|
445
530
|
@_reconnect_on_remote_close
|
|
446
|
-
def get_value(self, domain: str = None, key: str = None):
|
|
531
|
+
def get_value(self, domain: Optional[str] = None, key: Optional[str] = None):
|
|
447
532
|
options = {}
|
|
448
533
|
|
|
449
534
|
if domain:
|
|
450
|
-
options[
|
|
535
|
+
options["Domain"] = domain
|
|
451
536
|
if key:
|
|
452
|
-
options[
|
|
537
|
+
options["Key"] = key
|
|
453
538
|
|
|
454
|
-
res = self._request(
|
|
539
|
+
res = self._request("GetValue", options)
|
|
455
540
|
if res:
|
|
456
|
-
r = res.get(
|
|
457
|
-
if hasattr(r,
|
|
541
|
+
r = res.get("Value")
|
|
542
|
+
if hasattr(r, "data"):
|
|
458
543
|
return r.data
|
|
459
544
|
return r
|
|
460
545
|
|
|
461
546
|
@_reconnect_on_remote_close
|
|
462
|
-
def remove_value(self, domain: str = None, key: str = None) -> dict:
|
|
547
|
+
def remove_value(self, domain: Optional[str] = None, key: Optional[str] = None) -> dict:
|
|
463
548
|
options = {}
|
|
464
549
|
|
|
465
550
|
if domain:
|
|
466
|
-
options[
|
|
551
|
+
options["Domain"] = domain
|
|
467
552
|
if key:
|
|
468
|
-
options[
|
|
553
|
+
options["Key"] = key
|
|
469
554
|
|
|
470
|
-
return self._request(
|
|
555
|
+
return self._request("RemoveValue", options)
|
|
471
556
|
|
|
472
557
|
@_reconnect_on_remote_close
|
|
473
|
-
def set_value(self, value, domain: str = None, key: str = None) -> dict:
|
|
558
|
+
def set_value(self, value, domain: Optional[str] = None, key: Optional[str] = None) -> dict:
|
|
474
559
|
options = {}
|
|
475
560
|
|
|
476
561
|
if domain:
|
|
477
|
-
options[
|
|
562
|
+
options["Domain"] = domain
|
|
478
563
|
if key:
|
|
479
|
-
options[
|
|
564
|
+
options["Key"] = key
|
|
480
565
|
|
|
481
|
-
options[
|
|
482
|
-
return self._request(
|
|
566
|
+
options["Value"] = value
|
|
567
|
+
return self._request("SetValue", options)
|
|
483
568
|
|
|
484
569
|
def get_service_connection_attributes(self, name: str, include_escrow_bag: bool = False) -> dict:
|
|
485
570
|
if not self.paired:
|
|
486
571
|
raise NotPairedError()
|
|
487
572
|
|
|
488
|
-
options = {
|
|
573
|
+
options = {"Service": name}
|
|
489
574
|
if include_escrow_bag:
|
|
490
|
-
options[
|
|
575
|
+
options["EscrowBag"] = self.pair_record["EscrowBag"]
|
|
491
576
|
|
|
492
|
-
response = self._request(
|
|
493
|
-
if not response or response.get(
|
|
494
|
-
if response.get(
|
|
577
|
+
response = self._request("StartService", options)
|
|
578
|
+
if not response or response.get("Error"):
|
|
579
|
+
if response.get("Error", "") == "PasswordProtected":
|
|
495
580
|
raise PasswordRequiredError(
|
|
496
|
-
|
|
497
|
-
|
|
581
|
+
"your device is protected with password, please enter password in device and try again"
|
|
582
|
+
)
|
|
583
|
+
raise StartServiceError(name, response.get("Error"))
|
|
498
584
|
return response
|
|
499
585
|
|
|
500
586
|
@_reconnect_on_remote_close
|
|
501
587
|
def start_lockdown_service(self, name: str, include_escrow_bag: bool = False) -> ServiceConnection:
|
|
502
588
|
attr = self.get_service_connection_attributes(name, include_escrow_bag=include_escrow_bag)
|
|
503
|
-
service_connection = self._create_service_connection(attr[
|
|
589
|
+
service_connection = self._create_service_connection(attr["Port"])
|
|
504
590
|
|
|
505
|
-
if attr.get(
|
|
591
|
+
if attr.get("EnableServiceSSL", False):
|
|
506
592
|
with self.ssl_file() as f:
|
|
507
593
|
service_connection.ssl_start(f)
|
|
508
594
|
return service_connection
|
|
509
595
|
|
|
510
|
-
async def aio_start_lockdown_service(
|
|
511
|
-
self, name: str, include_escrow_bag: bool = False) -> ServiceConnection:
|
|
596
|
+
async def aio_start_lockdown_service(self, name: str, include_escrow_bag: bool = False) -> ServiceConnection:
|
|
512
597
|
attr = self.get_service_connection_attributes(name, include_escrow_bag=include_escrow_bag)
|
|
513
|
-
service_connection = self._create_service_connection(attr[
|
|
598
|
+
service_connection = self._create_service_connection(attr["Port"])
|
|
514
599
|
|
|
515
|
-
if attr.get(
|
|
600
|
+
if attr.get("EnableServiceSSL", False):
|
|
516
601
|
with self.ssl_file() as f:
|
|
517
602
|
await service_connection.aio_ssl_start(f)
|
|
518
603
|
return service_connection
|
|
@@ -522,13 +607,13 @@ class LockdownClient(ABC, LockdownServiceProvider):
|
|
|
522
607
|
|
|
523
608
|
@contextmanager
|
|
524
609
|
def ssl_file(self) -> str:
|
|
525
|
-
cert_pem = self.pair_record[
|
|
526
|
-
private_key_pem = self.pair_record[
|
|
610
|
+
cert_pem = self.pair_record["HostCertificate"]
|
|
611
|
+
private_key_pem = self.pair_record["HostPrivateKey"]
|
|
527
612
|
|
|
528
613
|
# use delete=False and manage the deletion ourselves because Windows
|
|
529
614
|
# cannot use in-use files
|
|
530
|
-
with tempfile.NamedTemporaryFile(
|
|
531
|
-
f.write(cert_pem + b
|
|
615
|
+
with tempfile.NamedTemporaryFile("w+b", delete=False) as f:
|
|
616
|
+
f.write(cert_pem + b"\n" + private_key_pem)
|
|
532
617
|
filename = f.name
|
|
533
618
|
|
|
534
619
|
try:
|
|
@@ -551,52 +636,56 @@ class LockdownClient(ABC, LockdownServiceProvider):
|
|
|
551
636
|
|
|
552
637
|
@abstractmethod
|
|
553
638
|
def _create_service_connection(self, port: int) -> ServiceConnection:
|
|
554
|
-
"""
|
|
639
|
+
"""Used to establish a new ServiceConnection to a given port"""
|
|
555
640
|
pass
|
|
556
641
|
|
|
557
642
|
def _request(self, request: str, options: Optional[dict] = None, verify_request: bool = True) -> dict:
|
|
558
|
-
message = {
|
|
643
|
+
message = {"Label": self.label, "Request": request}
|
|
559
644
|
if options:
|
|
560
645
|
message.update(options)
|
|
561
646
|
response = self.service.send_recv_plist(message)
|
|
562
647
|
|
|
563
|
-
if verify_request and response
|
|
564
|
-
|
|
648
|
+
if verify_request and response.get("Request") != request:
|
|
649
|
+
if response.get("Type") == RESTORED_SERVICE_TYPE:
|
|
650
|
+
raise IncorrectModeError(f"Incorrect mode returned. Got: {response}")
|
|
651
|
+
raise LockdownError(f"Incorrect response returned. Got: {response}")
|
|
565
652
|
|
|
566
|
-
error = response.get(
|
|
653
|
+
error = response.get("Error")
|
|
567
654
|
if error is not None:
|
|
568
655
|
# return response if supervisor cert challenge is required, to work with pair_supervisor
|
|
569
|
-
if error ==
|
|
656
|
+
if error == "MCChallengeRequired":
|
|
570
657
|
return response
|
|
571
|
-
exception_errors = {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
658
|
+
exception_errors = {
|
|
659
|
+
"PasswordProtected": PasswordRequiredError,
|
|
660
|
+
"PairingDialogResponsePending": PairingDialogResponsePendingError,
|
|
661
|
+
"UserDeniedPairing": UserDeniedPairingError,
|
|
662
|
+
"InvalidHostID": InvalidHostIDError,
|
|
663
|
+
"GetProhibited": GetProhibitedError,
|
|
664
|
+
"SetProhibited": SetProhibitedError,
|
|
665
|
+
"MissingValue": MissingValueError,
|
|
666
|
+
"InvalidService": InvalidServiceError,
|
|
667
|
+
"InvalidConnection": InvalidConnectionError,
|
|
668
|
+
}
|
|
580
669
|
raise exception_errors.get(error, LockdownError)(error, self.identifier)
|
|
581
670
|
|
|
582
671
|
# iOS < 5: 'Error' is not present, so we need to check the 'Result' instead
|
|
583
|
-
if response.get(
|
|
584
|
-
raise LockdownError(
|
|
672
|
+
if response.get("Result") == "Failure":
|
|
673
|
+
raise LockdownError("", self.identifier)
|
|
585
674
|
|
|
586
675
|
return response
|
|
587
676
|
|
|
588
677
|
def _request_pair(self, pair_options: dict, timeout: Optional[float] = None) -> dict:
|
|
589
678
|
try:
|
|
590
|
-
return self._request(
|
|
679
|
+
return self._request("Pair", pair_options)
|
|
591
680
|
except PairingDialogResponsePendingError:
|
|
592
681
|
if timeout == 0:
|
|
593
682
|
raise
|
|
594
683
|
|
|
595
|
-
self.logger.info(
|
|
684
|
+
self.logger.info("waiting user pairing dialog...")
|
|
596
685
|
start = time.time()
|
|
597
686
|
while timeout is None or time.time() <= start + timeout:
|
|
598
687
|
with suppress(PairingDialogResponsePendingError):
|
|
599
|
-
return self._request(
|
|
688
|
+
return self._request("Pair", pair_options)
|
|
600
689
|
time.sleep(1)
|
|
601
690
|
raise PairingDialogResponsePendingError()
|
|
602
691
|
|
|
@@ -605,7 +694,7 @@ class LockdownClient(ABC, LockdownServiceProvider):
|
|
|
605
694
|
self.pair_record = get_preferred_pair_record(self.identifier, self.pairing_records_cache_folder)
|
|
606
695
|
|
|
607
696
|
def save_pair_record(self) -> None:
|
|
608
|
-
pair_record_file = self.pairing_records_cache_folder / f
|
|
697
|
+
pair_record_file = self.pairing_records_cache_folder / f"{self.identifier}.plist"
|
|
609
698
|
pair_record_file.write_bytes(plistlib.dumps(self.pair_record))
|
|
610
699
|
|
|
611
700
|
def _reestablish_connection(self) -> None:
|
|
@@ -614,29 +703,39 @@ class LockdownClient(ABC, LockdownServiceProvider):
|
|
|
614
703
|
|
|
615
704
|
|
|
616
705
|
class UsbmuxLockdownClient(LockdownClient):
|
|
617
|
-
def __init__(
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
706
|
+
def __init__(
|
|
707
|
+
self,
|
|
708
|
+
service: ServiceConnection,
|
|
709
|
+
host_id: str,
|
|
710
|
+
identifier: Optional[str] = None,
|
|
711
|
+
label: str = DEFAULT_LABEL,
|
|
712
|
+
system_buid: str = SYSTEM_BUID,
|
|
713
|
+
pair_record: Optional[dict] = None,
|
|
714
|
+
pairing_records_cache_folder: Optional[Path] = None,
|
|
715
|
+
port: int = SERVICE_PORT,
|
|
716
|
+
usbmux_address: Optional[str] = None,
|
|
717
|
+
):
|
|
623
718
|
self.usbmux_address = usbmux_address
|
|
719
|
+
super().__init__(
|
|
720
|
+
service, host_id, identifier, label, system_buid, pair_record, pairing_records_cache_folder, port
|
|
721
|
+
)
|
|
624
722
|
|
|
625
723
|
@property
|
|
626
724
|
def short_info(self) -> dict:
|
|
627
725
|
short_info = super().short_info
|
|
628
|
-
short_info[
|
|
726
|
+
short_info["ConnectionType"] = self.service.mux_device.connection_type
|
|
629
727
|
return short_info
|
|
630
728
|
|
|
631
729
|
def fetch_pair_record(self) -> None:
|
|
632
730
|
if self.identifier is not None:
|
|
633
|
-
self.pair_record = get_preferred_pair_record(
|
|
634
|
-
|
|
731
|
+
self.pair_record = get_preferred_pair_record(
|
|
732
|
+
self.identifier, self.pairing_records_cache_folder, usbmux_address=self.usbmux_address
|
|
733
|
+
)
|
|
635
734
|
|
|
636
735
|
def _create_service_connection(self, port: int) -> ServiceConnection:
|
|
637
|
-
return ServiceConnection.create_using_usbmux(
|
|
638
|
-
|
|
639
|
-
|
|
736
|
+
return ServiceConnection.create_using_usbmux(
|
|
737
|
+
self.identifier, port, self.service.mux_device.connection_type, usbmux_address=self.usbmux_address
|
|
738
|
+
)
|
|
640
739
|
|
|
641
740
|
|
|
642
741
|
class PlistUsbmuxLockdownClient(UsbmuxLockdownClient):
|
|
@@ -648,9 +747,19 @@ class PlistUsbmuxLockdownClient(UsbmuxLockdownClient):
|
|
|
648
747
|
|
|
649
748
|
|
|
650
749
|
class TcpLockdownClient(LockdownClient):
|
|
651
|
-
def __init__(
|
|
652
|
-
|
|
653
|
-
|
|
750
|
+
def __init__(
|
|
751
|
+
self,
|
|
752
|
+
service: ServiceConnection,
|
|
753
|
+
host_id: str,
|
|
754
|
+
hostname: str,
|
|
755
|
+
identifier: Optional[str] = None,
|
|
756
|
+
label: str = DEFAULT_LABEL,
|
|
757
|
+
system_buid: str = SYSTEM_BUID,
|
|
758
|
+
pair_record: Optional[dict] = None,
|
|
759
|
+
pairing_records_cache_folder: Optional[Path] = None,
|
|
760
|
+
port: int = SERVICE_PORT,
|
|
761
|
+
keep_alive: bool = True,
|
|
762
|
+
):
|
|
654
763
|
"""
|
|
655
764
|
Create a LockdownClient instance
|
|
656
765
|
|
|
@@ -665,10 +774,12 @@ class TcpLockdownClient(LockdownClient):
|
|
|
665
774
|
:param port: lockdownd service port
|
|
666
775
|
:param keep_alive: use keep-alive to get notified when the connection is lost
|
|
667
776
|
"""
|
|
668
|
-
super().__init__(
|
|
669
|
-
|
|
777
|
+
super().__init__(
|
|
778
|
+
service, host_id, identifier, label, system_buid, pair_record, pairing_records_cache_folder, port
|
|
779
|
+
)
|
|
670
780
|
self._keep_alive = keep_alive
|
|
671
781
|
self.hostname = hostname
|
|
782
|
+
self.identifier = hostname
|
|
672
783
|
|
|
673
784
|
def _create_service_connection(self, port: int) -> ServiceConnection:
|
|
674
785
|
return ServiceConnection.create_using_tcp(self.hostname, port, keep_alive=self._keep_alive)
|
|
@@ -677,21 +788,30 @@ class TcpLockdownClient(LockdownClient):
|
|
|
677
788
|
class RemoteLockdownClient(LockdownClient):
|
|
678
789
|
def _create_service_connection(self, port: int) -> ServiceConnection:
|
|
679
790
|
raise NotImplementedError(
|
|
680
|
-
|
|
791
|
+
"RemoteXPC service connections should only be created using RemoteServiceDiscoveryService"
|
|
792
|
+
)
|
|
681
793
|
|
|
682
794
|
def _handle_autopair(self, *args, **kwargs):
|
|
683
795
|
# The RemoteXPC version of lockdown doesn't support pairing operations
|
|
684
796
|
return None
|
|
685
797
|
|
|
686
798
|
def pair(self, *args, **kwargs) -> None:
|
|
687
|
-
raise NotImplementedError(
|
|
688
|
-
|
|
689
|
-
def unpair(self, timeout: float = None) -> None:
|
|
690
|
-
raise NotImplementedError(
|
|
691
|
-
|
|
692
|
-
def __init__(
|
|
693
|
-
|
|
694
|
-
|
|
799
|
+
raise NotImplementedError("RemoteXPC lockdown version does not support pairing operations")
|
|
800
|
+
|
|
801
|
+
def unpair(self, timeout: Optional[float] = None) -> None:
|
|
802
|
+
raise NotImplementedError("RemoteXPC lockdown version does not support pairing operations")
|
|
803
|
+
|
|
804
|
+
def __init__(
|
|
805
|
+
self,
|
|
806
|
+
service: ServiceConnection,
|
|
807
|
+
host_id: str,
|
|
808
|
+
identifier: Optional[str] = None,
|
|
809
|
+
label: str = DEFAULT_LABEL,
|
|
810
|
+
system_buid: str = SYSTEM_BUID,
|
|
811
|
+
pair_record: Optional[dict] = None,
|
|
812
|
+
pairing_records_cache_folder: Optional[Path] = None,
|
|
813
|
+
port: int = SERVICE_PORT,
|
|
814
|
+
):
|
|
695
815
|
"""
|
|
696
816
|
Create a LockdownClient instance
|
|
697
817
|
|
|
@@ -704,14 +824,24 @@ class RemoteLockdownClient(LockdownClient):
|
|
|
704
824
|
:param pairing_records_cache_folder: Use the following location to search and save pair records
|
|
705
825
|
:param port: lockdownd service port
|
|
706
826
|
"""
|
|
707
|
-
super().__init__(
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
827
|
+
super().__init__(
|
|
828
|
+
service, host_id, identifier, label, system_buid, pair_record, pairing_records_cache_folder, port
|
|
829
|
+
)
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
def create_using_usbmux(
|
|
833
|
+
serial: Optional[str] = None,
|
|
834
|
+
identifier: Optional[str] = None,
|
|
835
|
+
label: str = DEFAULT_LABEL,
|
|
836
|
+
autopair: bool = True,
|
|
837
|
+
connection_type: Optional[str] = None,
|
|
838
|
+
pair_timeout: Optional[float] = None,
|
|
839
|
+
local_hostname: Optional[str] = None,
|
|
840
|
+
pair_record: Optional[dict] = None,
|
|
841
|
+
pairing_records_cache_folder: Optional[Path] = None,
|
|
842
|
+
port: int = SERVICE_PORT,
|
|
843
|
+
usbmux_address: Optional[str] = None,
|
|
844
|
+
) -> UsbmuxLockdownClient:
|
|
715
845
|
"""
|
|
716
846
|
Create a UsbmuxLockdownClient instance
|
|
717
847
|
|
|
@@ -728,29 +858,73 @@ def create_using_usbmux(serial: str = None, identifier: str = None, label: str =
|
|
|
728
858
|
:param usbmux_address: usbmuxd address
|
|
729
859
|
:return: UsbmuxLockdownClient instance
|
|
730
860
|
"""
|
|
731
|
-
service = ServiceConnection.create_using_usbmux(
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
identifier
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
861
|
+
service = ServiceConnection.create_using_usbmux(
|
|
862
|
+
serial, port, connection_type=connection_type, usbmux_address=usbmux_address
|
|
863
|
+
)
|
|
864
|
+
try:
|
|
865
|
+
cls = UsbmuxLockdownClient
|
|
866
|
+
with usbmux.create_mux(usbmux_address=usbmux_address) as client:
|
|
867
|
+
if isinstance(client, PlistMuxConnection):
|
|
868
|
+
# Only the Plist version of usbmuxd supports this message type
|
|
869
|
+
system_buid = client.get_buid()
|
|
870
|
+
cls = PlistUsbmuxLockdownClient
|
|
871
|
+
|
|
872
|
+
if identifier is None:
|
|
873
|
+
# attempt get identifier from mux device serial
|
|
874
|
+
identifier = service.mux_device.serial
|
|
875
|
+
|
|
876
|
+
return cls.create(
|
|
877
|
+
service,
|
|
878
|
+
identifier=identifier,
|
|
879
|
+
label=label,
|
|
880
|
+
system_buid=system_buid,
|
|
881
|
+
local_hostname=local_hostname,
|
|
882
|
+
pair_record=pair_record,
|
|
883
|
+
pairing_records_cache_folder=pairing_records_cache_folder,
|
|
884
|
+
pair_timeout=pair_timeout,
|
|
885
|
+
autopair=autopair,
|
|
886
|
+
usbmux_address=usbmux_address,
|
|
887
|
+
)
|
|
888
|
+
except Exception:
|
|
889
|
+
service.close()
|
|
890
|
+
raise
|
|
891
|
+
|
|
892
|
+
|
|
893
|
+
def retry_create_using_usbmux(retry_timeout: Optional[float] = None, **kwargs) -> UsbmuxLockdownClient:
|
|
894
|
+
"""
|
|
895
|
+
Repeatedly retry to create a UsbmuxLockdownClient instance while dismissing different errors that might occur
|
|
896
|
+
while device is rebooting
|
|
897
|
+
|
|
898
|
+
:param retry_timeout: Retry timeout in seconds or None for no timeout
|
|
899
|
+
:return: UsbmuxLockdownClient instance
|
|
900
|
+
"""
|
|
901
|
+
start = time.time()
|
|
902
|
+
while (retry_timeout is None) or (time.time() - start < retry_timeout):
|
|
903
|
+
try:
|
|
904
|
+
return create_using_usbmux(**kwargs)
|
|
905
|
+
except (
|
|
906
|
+
NoDeviceConnectedError,
|
|
907
|
+
ConnectionFailedError,
|
|
908
|
+
BadDevError,
|
|
909
|
+
OSError,
|
|
910
|
+
construct.core.StreamError,
|
|
911
|
+
DeviceNotFoundError,
|
|
912
|
+
):
|
|
913
|
+
pass
|
|
914
|
+
|
|
915
|
+
|
|
916
|
+
def create_using_tcp(
|
|
917
|
+
hostname: str,
|
|
918
|
+
identifier: Optional[str] = None,
|
|
919
|
+
label: str = DEFAULT_LABEL,
|
|
920
|
+
autopair: bool = True,
|
|
921
|
+
pair_timeout: Optional[float] = None,
|
|
922
|
+
local_hostname: Optional[str] = None,
|
|
923
|
+
pair_record: Optional[dict] = None,
|
|
924
|
+
pairing_records_cache_folder: Optional[Path] = None,
|
|
925
|
+
port: int = SERVICE_PORT,
|
|
926
|
+
keep_alive: bool = False,
|
|
927
|
+
) -> TcpLockdownClient:
|
|
754
928
|
"""
|
|
755
929
|
Create a TcpLockdownClient instance
|
|
756
930
|
|
|
@@ -767,17 +941,36 @@ def create_using_tcp(hostname: str, identifier: str = None, label: str = DEFAULT
|
|
|
767
941
|
:return: TcpLockdownClient instance
|
|
768
942
|
"""
|
|
769
943
|
service = ServiceConnection.create_using_tcp(hostname, port, keep_alive=keep_alive)
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
944
|
+
try:
|
|
945
|
+
return TcpLockdownClient.create(
|
|
946
|
+
service,
|
|
947
|
+
identifier=identifier,
|
|
948
|
+
label=label,
|
|
949
|
+
local_hostname=local_hostname,
|
|
950
|
+
pair_record=pair_record,
|
|
951
|
+
pairing_records_cache_folder=pairing_records_cache_folder,
|
|
952
|
+
pair_timeout=pair_timeout,
|
|
953
|
+
autopair=autopair,
|
|
954
|
+
port=port,
|
|
955
|
+
hostname=hostname,
|
|
956
|
+
keep_alive=keep_alive,
|
|
957
|
+
)
|
|
958
|
+
except Exception:
|
|
959
|
+
service.close()
|
|
960
|
+
raise
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
def create_using_remote(
|
|
964
|
+
service: ServiceConnection,
|
|
965
|
+
identifier: Optional[str] = None,
|
|
966
|
+
label: str = DEFAULT_LABEL,
|
|
967
|
+
autopair: bool = True,
|
|
968
|
+
pair_timeout: Optional[float] = None,
|
|
969
|
+
local_hostname: Optional[str] = None,
|
|
970
|
+
pair_record: Optional[dict] = None,
|
|
971
|
+
pairing_records_cache_folder: Optional[Path] = None,
|
|
972
|
+
port: int = SERVICE_PORT,
|
|
973
|
+
) -> RemoteLockdownClient:
|
|
781
974
|
"""
|
|
782
975
|
Create a TcpLockdownClient instance over RSD
|
|
783
976
|
|
|
@@ -792,52 +985,57 @@ def create_using_remote(service: ServiceConnection, identifier: str = None, labe
|
|
|
792
985
|
:param port: lockdownd service port
|
|
793
986
|
:return: TcpLockdownClient instance
|
|
794
987
|
"""
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
988
|
+
try:
|
|
989
|
+
return RemoteLockdownClient.create(
|
|
990
|
+
service,
|
|
991
|
+
identifier=identifier,
|
|
992
|
+
label=label,
|
|
993
|
+
local_hostname=local_hostname,
|
|
994
|
+
pair_record=pair_record,
|
|
995
|
+
pairing_records_cache_folder=pairing_records_cache_folder,
|
|
996
|
+
pair_timeout=pair_timeout,
|
|
997
|
+
autopair=autopair,
|
|
998
|
+
port=port,
|
|
999
|
+
)
|
|
1000
|
+
except Exception:
|
|
1001
|
+
service.close()
|
|
1002
|
+
raise
|
|
800
1003
|
|
|
801
1004
|
|
|
802
1005
|
async def get_mobdev2_lockdowns(
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
1006
|
+
udid: Optional[str] = None,
|
|
1007
|
+
pair_records: Optional[Path] = None,
|
|
1008
|
+
only_paired: bool = False,
|
|
1009
|
+
timeout: float = DEFAULT_BONJOUR_TIMEOUT,
|
|
1010
|
+
) -> AsyncIterable[tuple[str, TcpLockdownClient]]:
|
|
806
1011
|
records = {}
|
|
807
1012
|
if pair_records is None:
|
|
808
1013
|
pair_records = get_home_folder()
|
|
809
|
-
for file in pair_records.glob(
|
|
810
|
-
if file.name.startswith(
|
|
1014
|
+
for file in pair_records.glob("*.plist"):
|
|
1015
|
+
if file.name.startswith("remote_"):
|
|
811
1016
|
# skip RemotePairing records
|
|
812
1017
|
continue
|
|
813
|
-
record_udid = file.parts[-1].strip(
|
|
1018
|
+
record_udid = file.parts[-1].strip(".plist")
|
|
814
1019
|
if udid is not None and record_udid != udid:
|
|
815
1020
|
continue
|
|
816
1021
|
record = plistlib.loads(file.read_bytes())
|
|
817
|
-
records[record[
|
|
1022
|
+
records[record["WiFiMACAddress"]] = record
|
|
818
1023
|
|
|
819
|
-
iterated_ips = set()
|
|
820
1024
|
for answer in await browse_mobdev2(timeout=timeout):
|
|
821
|
-
if
|
|
1025
|
+
if "@" not in answer.instance:
|
|
822
1026
|
continue
|
|
823
|
-
wifi_mac_address = answer.
|
|
1027
|
+
wifi_mac_address = answer.instance.split("@", 1)[0]
|
|
824
1028
|
record = records.get(wifi_mac_address)
|
|
825
1029
|
|
|
826
1030
|
if only_paired and record is None:
|
|
827
1031
|
continue
|
|
828
1032
|
|
|
829
|
-
for
|
|
830
|
-
if ip in iterated_ips:
|
|
831
|
-
# skip ips we already iterated over, possibly from previous queries
|
|
832
|
-
continue
|
|
833
|
-
iterated_ips.add(ip)
|
|
1033
|
+
for address in answer.addresses:
|
|
834
1034
|
try:
|
|
835
|
-
lockdown = create_using_tcp(hostname=
|
|
1035
|
+
lockdown = create_using_tcp(hostname=address.full_ip, autopair=False, pair_record=record)
|
|
836
1036
|
except Exception:
|
|
837
1037
|
continue
|
|
838
|
-
if lockdown is None:
|
|
839
|
-
continue
|
|
840
1038
|
if only_paired and not lockdown.paired:
|
|
841
1039
|
lockdown.close()
|
|
842
1040
|
continue
|
|
843
|
-
yield
|
|
1041
|
+
yield address.full_ip, lockdown
|