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