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