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
|
@@ -21,9 +21,8 @@ from socket import create_connection
|
|
|
21
21
|
from ssl import VerifyMode
|
|
22
22
|
from typing import Optional, TextIO, cast
|
|
23
23
|
|
|
24
|
-
from construct import Const, Container
|
|
24
|
+
from construct import Const, Container, GreedyBytes, GreedyRange, Int8ul, Int16ub, Int64ul, Prefixed, Struct
|
|
25
25
|
from construct import Enum as ConstructEnum
|
|
26
|
-
from construct import GreedyBytes, GreedyRange, Int8ul, Int16ub, Int64ul, Prefixed, Struct
|
|
27
26
|
from cryptography.hazmat.primitives import hashes
|
|
28
27
|
from cryptography.hazmat.primitives._serialization import Encoding, NoEncryption, PrivateFormat, PublicFormat
|
|
29
28
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
|
@@ -55,10 +54,20 @@ except ImportError:
|
|
|
55
54
|
|
|
56
55
|
from pymobiledevice3.bonjour import DEFAULT_BONJOUR_TIMEOUT, browse_remotepairing
|
|
57
56
|
from pymobiledevice3.ca import make_cert
|
|
58
|
-
from pymobiledevice3.exceptions import
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
from pymobiledevice3.exceptions import (
|
|
58
|
+
PairingError,
|
|
59
|
+
PyMobileDevice3Exception,
|
|
60
|
+
QuicProtocolNotSupportedError,
|
|
61
|
+
RemotePairingCompletedError,
|
|
62
|
+
UserDeniedPairingError,
|
|
63
|
+
)
|
|
64
|
+
from pymobiledevice3.pair_records import (
|
|
65
|
+
PAIRING_RECORD_EXT,
|
|
66
|
+
create_pairing_records_cache_folder,
|
|
67
|
+
generate_host_id,
|
|
68
|
+
get_remote_pairing_record_filename,
|
|
69
|
+
iter_remote_paired_identifiers,
|
|
70
|
+
)
|
|
62
71
|
from pymobiledevice3.remote.common import TunnelProtocol
|
|
63
72
|
from pymobiledevice3.remote.remote_service import RemoteService
|
|
64
73
|
from pymobiledevice3.remote.remote_service_discovery import RemoteServiceDiscoveryService
|
|
@@ -67,7 +76,7 @@ from pymobiledevice3.remote.xpc_message import XpcInt64Type, XpcUInt64Type
|
|
|
67
76
|
from pymobiledevice3.service_connection import ServiceConnection
|
|
68
77
|
from pymobiledevice3.utils import asyncio_print_traceback
|
|
69
78
|
|
|
70
|
-
DEFAULT_INTERFACE_NAME =
|
|
79
|
+
DEFAULT_INTERFACE_NAME = "pymobiledevice3-tunnel"
|
|
71
80
|
TIMEOUT = 1
|
|
72
81
|
|
|
73
82
|
OSUTIL = get_os_utils()
|
|
@@ -81,58 +90,60 @@ UDP_HEADER_SIZE = 8
|
|
|
81
90
|
IOS_DEVICE_MTU_SIZE = 1500
|
|
82
91
|
packet_builder.PACKET_MAX_SIZE = IOS_DEVICE_MTU_SIZE - IPV6_HEADER_SIZE - UDP_HEADER_SIZE
|
|
83
92
|
|
|
84
|
-
PairingDataComponentType = ConstructEnum(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
93
|
+
PairingDataComponentType = ConstructEnum(
|
|
94
|
+
Int8ul,
|
|
95
|
+
METHOD=0x00,
|
|
96
|
+
IDENTIFIER=0x01,
|
|
97
|
+
SALT=0x02,
|
|
98
|
+
PUBLIC_KEY=0x03,
|
|
99
|
+
PROOF=0x04,
|
|
100
|
+
ENCRYPTED_DATA=0x05,
|
|
101
|
+
STATE=0x06,
|
|
102
|
+
ERROR=0x07,
|
|
103
|
+
RETRY_DELAY=0x08,
|
|
104
|
+
CERTIFICATE=0x09,
|
|
105
|
+
SIGNATURE=0x0A,
|
|
106
|
+
PERMISSIONS=0x0B,
|
|
107
|
+
FRAGMENT_DATA=0x0C,
|
|
108
|
+
FRAGMENT_LAST=0x0D,
|
|
109
|
+
SESSION_ID=0x0E,
|
|
110
|
+
TTL=0x0F,
|
|
111
|
+
EXTRA_DATA=0x10,
|
|
112
|
+
INFO=0x11,
|
|
113
|
+
ACL=0x12,
|
|
114
|
+
FLAGS=0x13,
|
|
115
|
+
VALIDATION_DATA=0x14,
|
|
116
|
+
MFI_AUTH_TOKEN=0x15,
|
|
117
|
+
MFI_PRODUCT_TYPE=0x16,
|
|
118
|
+
SERIAL_NUMBER=0x17,
|
|
119
|
+
MFI_AUTH_TOKEN_UUID=0x18,
|
|
120
|
+
APP_FLAGS=0x19,
|
|
121
|
+
OWNERSHIP_PROOF=0x1A,
|
|
122
|
+
SETUP_CODE_TYPE=0x1B,
|
|
123
|
+
PRODUCTION_DATA=0x1C,
|
|
124
|
+
APP_INFO=0x1D,
|
|
125
|
+
SEPARATOR=0xFF,
|
|
126
|
+
)
|
|
116
127
|
|
|
117
128
|
PairingDataComponentTLV8 = Struct(
|
|
118
|
-
|
|
119
|
-
|
|
129
|
+
"type" / PairingDataComponentType,
|
|
130
|
+
"data" / Prefixed(Int8ul, GreedyBytes),
|
|
120
131
|
)
|
|
121
132
|
|
|
122
133
|
PairingDataComponentTLVBuf = GreedyRange(PairingDataComponentTLV8)
|
|
123
134
|
|
|
124
|
-
PairConsentResult = namedtuple(
|
|
135
|
+
PairConsentResult = namedtuple("PairConsentResult", "public_key salt pin")
|
|
125
136
|
|
|
126
137
|
CDTunnelPacket = Struct(
|
|
127
|
-
|
|
128
|
-
|
|
138
|
+
"magic" / Const(b"CDTunnel"),
|
|
139
|
+
"body" / Prefixed(Int16ub, GreedyBytes),
|
|
129
140
|
)
|
|
130
141
|
|
|
131
|
-
REPAIRING_PACKET_MAGIC = b
|
|
142
|
+
REPAIRING_PACKET_MAGIC = b"RPPairing"
|
|
132
143
|
|
|
133
144
|
RPPairingPacket = Struct(
|
|
134
|
-
|
|
135
|
-
|
|
145
|
+
"magic" / Const(REPAIRING_PACKET_MAGIC),
|
|
146
|
+
"body" / Prefixed(Int16ub, GreedyBytes),
|
|
136
147
|
)
|
|
137
148
|
|
|
138
149
|
|
|
@@ -140,7 +151,7 @@ class RemotePairingTunnel(ABC):
|
|
|
140
151
|
def __init__(self):
|
|
141
152
|
self._queue = asyncio.Queue()
|
|
142
153
|
self._tun_read_task = None
|
|
143
|
-
self._logger = logging.getLogger(f
|
|
154
|
+
self._logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
|
|
144
155
|
self.tun = None
|
|
145
156
|
|
|
146
157
|
@abstractmethod
|
|
@@ -159,11 +170,11 @@ class RemotePairingTunnel(ABC):
|
|
|
159
170
|
async def tun_read_task(self) -> None:
|
|
160
171
|
read_size = self.tun.mtu + len(LOOPBACK_HEADER)
|
|
161
172
|
try:
|
|
162
|
-
if sys.platform !=
|
|
173
|
+
if sys.platform != "win32":
|
|
163
174
|
while True:
|
|
164
175
|
packet = await asyncio.to_thread(self.tun.read, read_size)
|
|
165
176
|
assert packet.startswith(LOOPBACK_HEADER)
|
|
166
|
-
packet = packet[len(LOOPBACK_HEADER):]
|
|
177
|
+
packet = packet[len(LOOPBACK_HEADER) :]
|
|
167
178
|
await self.send_packet_to_device(packet)
|
|
168
179
|
else:
|
|
169
180
|
while True:
|
|
@@ -174,12 +185,12 @@ class RemotePairingTunnel(ABC):
|
|
|
174
185
|
continue
|
|
175
186
|
await self.send_packet_to_device(packet)
|
|
176
187
|
except ConnectionResetError:
|
|
177
|
-
self._logger.warning(f
|
|
188
|
+
self._logger.warning(f"got connection reset in {asyncio.current_task().get_name()}")
|
|
178
189
|
except OSError:
|
|
179
|
-
self._logger.warning(f
|
|
190
|
+
self._logger.warning(f"got oserror in {asyncio.current_task().get_name()}")
|
|
180
191
|
|
|
181
192
|
def start_tunnel(self, address: str, mtu: int, interface_name=DEFAULT_INTERFACE_NAME) -> None:
|
|
182
|
-
if
|
|
193
|
+
if sys.platform == "win32":
|
|
183
194
|
# Only win32 tunnel implementation supports interface name
|
|
184
195
|
self.tun = TunTapDevice(interface_name)
|
|
185
196
|
else:
|
|
@@ -187,10 +198,10 @@ class RemotePairingTunnel(ABC):
|
|
|
187
198
|
self.tun.addr = address
|
|
188
199
|
self.tun.mtu = mtu
|
|
189
200
|
self.tun.up()
|
|
190
|
-
self._tun_read_task = asyncio.create_task(self.tun_read_task(), name=f
|
|
201
|
+
self._tun_read_task = asyncio.create_task(self.tun_read_task(), name=f"tun-read-{address}")
|
|
191
202
|
|
|
192
203
|
async def stop_tunnel(self) -> None:
|
|
193
|
-
self._logger.debug(
|
|
204
|
+
self._logger.debug(f"[{asyncio.current_task().get_name()}] stopping tunnel")
|
|
194
205
|
self._tun_read_task.cancel()
|
|
195
206
|
with suppress(CancelledError):
|
|
196
207
|
await self._tun_read_task
|
|
@@ -200,7 +211,7 @@ class RemotePairingTunnel(ABC):
|
|
|
200
211
|
|
|
201
212
|
@staticmethod
|
|
202
213
|
def _encode_cdtunnel_packet(data: dict) -> bytes:
|
|
203
|
-
return CDTunnelPacket.build({
|
|
214
|
+
return CDTunnelPacket.build({"body": json.dumps(data).encode()})
|
|
204
215
|
|
|
205
216
|
|
|
206
217
|
class RemotePairingQuicTunnel(RemotePairingTunnel, QuicConnectionProtocol):
|
|
@@ -214,21 +225,23 @@ class RemotePairingQuicTunnel(RemotePairingTunnel, QuicConnectionProtocol):
|
|
|
214
225
|
self._keep_alive_task = None
|
|
215
226
|
|
|
216
227
|
async def wait_closed(self) -> None:
|
|
217
|
-
|
|
228
|
+
with suppress(asyncio.CancelledError):
|
|
218
229
|
await QuicConnectionProtocol.wait_closed(self)
|
|
219
|
-
except asyncio.CancelledError:
|
|
220
|
-
pass
|
|
221
230
|
|
|
222
231
|
async def send_packet_to_device(self, packet: bytes) -> None:
|
|
223
232
|
self._quic.send_datagram_frame(packet)
|
|
224
233
|
self.transmit()
|
|
225
234
|
|
|
235
|
+
# Allow other tasks to run
|
|
236
|
+
await asyncio.sleep(0)
|
|
237
|
+
|
|
226
238
|
async def request_tunnel_establish(self) -> dict:
|
|
227
239
|
stream_id = self._quic.get_next_available_stream_id()
|
|
228
240
|
# pad the data with random data to force the MTU size correctly
|
|
229
|
-
self._quic.send_datagram_frame(b
|
|
230
|
-
self._quic.send_stream_data(
|
|
231
|
-
{
|
|
241
|
+
self._quic.send_datagram_frame(b"x" * 1024)
|
|
242
|
+
self._quic.send_stream_data(
|
|
243
|
+
stream_id, self._encode_cdtunnel_packet({"type": "clientHandshakeRequest", "mtu": self.REQUESTED_MTU})
|
|
244
|
+
)
|
|
232
245
|
self.transmit()
|
|
233
246
|
return await self._queue.get()
|
|
234
247
|
|
|
@@ -258,7 +271,7 @@ class RemotePairingQuicTunnel(RemotePairingTunnel, QuicConnectionProtocol):
|
|
|
258
271
|
|
|
259
272
|
@staticmethod
|
|
260
273
|
def _encode_cdtunnel_packet(data: dict) -> bytes:
|
|
261
|
-
return CDTunnelPacket.build({
|
|
274
|
+
return CDTunnelPacket.build({"body": json.dumps(data).encode()})
|
|
262
275
|
|
|
263
276
|
|
|
264
277
|
class RemotePairingTcpTunnel(RemotePairingTunnel):
|
|
@@ -280,30 +293,27 @@ class RemotePairingTcpTunnel(RemotePairingTunnel):
|
|
|
280
293
|
while True:
|
|
281
294
|
try:
|
|
282
295
|
ipv6_header = await self._reader.readexactly(IPV6_HEADER_SIZE)
|
|
283
|
-
ipv6_length = struct.unpack(
|
|
296
|
+
ipv6_length = struct.unpack(">H", ipv6_header[4:6])[0]
|
|
284
297
|
ipv6_body = await self._reader.readexactly(ipv6_length)
|
|
285
298
|
self.tun.write(LOOPBACK_HEADER + ipv6_header + ipv6_body)
|
|
286
299
|
except asyncio.exceptions.IncompleteReadError:
|
|
287
300
|
await asyncio.sleep(1)
|
|
288
301
|
except OSError as e:
|
|
289
|
-
self._logger.warning(f
|
|
302
|
+
self._logger.warning(f"got {e.__class__.__name__} in {asyncio.current_task().get_name()}")
|
|
290
303
|
await self.wait_closed()
|
|
291
304
|
|
|
292
305
|
async def wait_closed(self) -> None:
|
|
293
|
-
|
|
306
|
+
with suppress(OSError):
|
|
294
307
|
await self._writer.wait_closed()
|
|
295
|
-
except OSError:
|
|
296
|
-
pass
|
|
297
308
|
|
|
298
309
|
async def request_tunnel_establish(self) -> dict:
|
|
299
|
-
self._writer.write(self._encode_cdtunnel_packet(
|
|
300
|
-
{'type': 'clientHandshakeRequest', 'mtu': self.REQUESTED_MTU}))
|
|
310
|
+
self._writer.write(self._encode_cdtunnel_packet({"type": "clientHandshakeRequest", "mtu": self.REQUESTED_MTU}))
|
|
301
311
|
await self._writer.drain()
|
|
302
312
|
return json.loads(CDTunnelPacket.parse(await self._reader.read(self.REQUESTED_MTU)).body)
|
|
303
313
|
|
|
304
314
|
def start_tunnel(self, address: str, mtu: int, interface_name=DEFAULT_INTERFACE_NAME) -> None:
|
|
305
315
|
super().start_tunnel(address, mtu, interface_name=interface_name)
|
|
306
|
-
self._sock_read_task = asyncio.create_task(self.sock_read_task(), name=f
|
|
316
|
+
self._sock_read_task = asyncio.create_task(self.sock_read_task(), name=f"sock-read-task-{address}")
|
|
307
317
|
|
|
308
318
|
async def stop_tunnel(self) -> None:
|
|
309
319
|
self._sock_read_task.cancel()
|
|
@@ -312,10 +322,8 @@ class RemotePairingTcpTunnel(RemotePairingTunnel):
|
|
|
312
322
|
await super().stop_tunnel()
|
|
313
323
|
if not self._writer.is_closing():
|
|
314
324
|
self._writer.close()
|
|
315
|
-
|
|
325
|
+
with suppress(OSError):
|
|
316
326
|
await self._writer.wait_closed()
|
|
317
|
-
except OSError:
|
|
318
|
-
pass
|
|
319
327
|
|
|
320
328
|
|
|
321
329
|
@dataclasses.dataclass
|
|
@@ -389,112 +397,143 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
389
397
|
raise RemotePairingCompletedError()
|
|
390
398
|
|
|
391
399
|
async def create_quic_listener(self, private_key: RSAPrivateKey) -> dict:
|
|
392
|
-
request = {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
400
|
+
request = {
|
|
401
|
+
"request": {
|
|
402
|
+
"_0": {
|
|
403
|
+
"createListener": {
|
|
404
|
+
"key": base64.b64encode(
|
|
405
|
+
private_key.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)
|
|
406
|
+
).decode(),
|
|
407
|
+
"peerConnectionsInfo": [{"owningPID": os.getpid(), "owningProcessName": "CoreDeviceService"}],
|
|
408
|
+
"transportProtocolType": "quic",
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
398
413
|
|
|
399
414
|
response = await self._send_receive_encrypted_request(request)
|
|
400
|
-
return response[
|
|
415
|
+
return response["createListener"]
|
|
401
416
|
|
|
402
417
|
async def create_tcp_listener(self) -> dict:
|
|
403
|
-
request = {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
418
|
+
request = {
|
|
419
|
+
"request": {
|
|
420
|
+
"_0": {
|
|
421
|
+
"createListener": {
|
|
422
|
+
"key": base64.b64encode(self.encryption_key).decode(),
|
|
423
|
+
"peerConnectionsInfo": [{"owningPID": os.getpid(), "owningProcessName": "CoreDeviceService"}],
|
|
424
|
+
"transportProtocolType": "tcp",
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
407
429
|
response = await self._send_receive_encrypted_request(request)
|
|
408
|
-
return response[
|
|
430
|
+
return response["createListener"]
|
|
409
431
|
|
|
410
432
|
@asynccontextmanager
|
|
411
433
|
async def start_quic_tunnel(
|
|
412
|
-
|
|
413
|
-
|
|
434
|
+
self,
|
|
435
|
+
secrets_log_file: Optional[TextIO] = None,
|
|
436
|
+
max_idle_timeout: float = RemotePairingQuicTunnel.MAX_IDLE_TIMEOUT,
|
|
437
|
+
) -> AsyncGenerator[TunnelResult, None]:
|
|
414
438
|
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
|
415
439
|
parameters = await self.create_quic_listener(private_key)
|
|
416
440
|
cert = make_cert(private_key, private_key.public_key())
|
|
417
441
|
configuration = QuicConfiguration(
|
|
418
|
-
alpn_protocols=[
|
|
442
|
+
alpn_protocols=["RemotePairingTunnelProtocol"],
|
|
419
443
|
is_client=True,
|
|
420
444
|
verify_mode=VerifyMode.CERT_NONE,
|
|
421
445
|
verify_hostname=False,
|
|
422
446
|
max_datagram_frame_size=RemotePairingQuicTunnel.MAX_QUIC_DATAGRAM,
|
|
423
|
-
idle_timeout=max_idle_timeout
|
|
447
|
+
idle_timeout=max_idle_timeout,
|
|
448
|
+
)
|
|
449
|
+
configuration.load_cert_chain(
|
|
450
|
+
cert.public_bytes(Encoding.PEM),
|
|
451
|
+
private_key.private_bytes(Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption()).decode(),
|
|
424
452
|
)
|
|
425
|
-
configuration.load_cert_chain(cert.public_bytes(Encoding.PEM),
|
|
426
|
-
private_key.private_bytes(Encoding.PEM, PrivateFormat.TraditionalOpenSSL,
|
|
427
|
-
NoEncryption()).decode())
|
|
428
453
|
configuration.secrets_log_file = secrets_log_file
|
|
429
454
|
|
|
430
455
|
host = self.hostname
|
|
431
|
-
port = parameters[
|
|
456
|
+
port = parameters["port"]
|
|
432
457
|
|
|
433
|
-
self.logger.debug(f
|
|
458
|
+
self.logger.debug(f"Connecting to {host}:{port}")
|
|
434
459
|
try:
|
|
435
460
|
async with aioquic_connect(
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
461
|
+
host,
|
|
462
|
+
port,
|
|
463
|
+
configuration=configuration,
|
|
464
|
+
create_protocol=RemotePairingQuicTunnel,
|
|
440
465
|
) as client:
|
|
441
|
-
self.logger.debug(
|
|
466
|
+
self.logger.debug("quic connected")
|
|
442
467
|
client = cast(RemotePairingQuicTunnel, client)
|
|
443
468
|
await client.wait_connected()
|
|
444
469
|
handshake_response = await client.request_tunnel_establish()
|
|
445
|
-
client.start_tunnel(
|
|
446
|
-
|
|
447
|
-
|
|
470
|
+
client.start_tunnel(
|
|
471
|
+
handshake_response["clientParameters"]["address"],
|
|
472
|
+
handshake_response["clientParameters"]["mtu"],
|
|
473
|
+
interface_name=f"{DEFAULT_INTERFACE_NAME}-{self.remote_identifier}",
|
|
474
|
+
)
|
|
448
475
|
try:
|
|
449
476
|
yield TunnelResult(
|
|
450
|
-
client.tun.name,
|
|
451
|
-
|
|
477
|
+
client.tun.name,
|
|
478
|
+
handshake_response["serverAddress"],
|
|
479
|
+
handshake_response["serverRSDPort"],
|
|
480
|
+
TunnelProtocol.QUIC,
|
|
481
|
+
client,
|
|
482
|
+
)
|
|
452
483
|
finally:
|
|
453
484
|
await client.stop_tunnel()
|
|
454
|
-
except ConnectionError:
|
|
485
|
+
except ConnectionError as e:
|
|
455
486
|
raise QuicProtocolNotSupportedError(
|
|
456
|
-
|
|
487
|
+
"iOS 18.2+ removed QUIC protocol support. Use TCP instead (requires python3.13+)"
|
|
488
|
+
) from e
|
|
457
489
|
|
|
458
490
|
@asynccontextmanager
|
|
459
491
|
async def start_tcp_tunnel(self) -> AsyncGenerator[TunnelResult, None]:
|
|
460
492
|
parameters = await self.create_tcp_listener()
|
|
461
493
|
host = self.hostname
|
|
462
|
-
port = parameters[
|
|
494
|
+
port = parameters["port"]
|
|
463
495
|
sock = create_connection((host, port))
|
|
464
496
|
OSUTIL.set_keepalive(sock)
|
|
465
497
|
if sys.version_info >= (3, 13):
|
|
466
498
|
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
|
|
467
499
|
ctx.check_hostname = False
|
|
468
500
|
ctx.verify_mode = ssl.CERT_NONE
|
|
469
|
-
ctx.set_ciphers(
|
|
501
|
+
ctx.set_ciphers("PSK")
|
|
470
502
|
ctx.set_psk_client_callback(lambda hint: (None, self.encryption_key))
|
|
471
503
|
else:
|
|
472
504
|
# TODO: remove this when python3.12 becomes deprecated
|
|
473
505
|
ctx = SSLPSKContext(ssl.PROTOCOL_TLSv1_2)
|
|
474
506
|
ctx.psk = self.encryption_key
|
|
475
|
-
ctx.set_ciphers(
|
|
476
|
-
reader, writer = await asyncio.open_connection(sock=sock, ssl=ctx, server_hostname=
|
|
507
|
+
ctx.set_ciphers("PSK")
|
|
508
|
+
reader, writer = await asyncio.open_connection(sock=sock, ssl=ctx, server_hostname="")
|
|
477
509
|
tunnel = RemotePairingTcpTunnel(reader, writer)
|
|
478
510
|
handshake_response = await tunnel.request_tunnel_establish()
|
|
479
511
|
|
|
480
|
-
tunnel.start_tunnel(
|
|
481
|
-
|
|
482
|
-
|
|
512
|
+
tunnel.start_tunnel(
|
|
513
|
+
handshake_response["clientParameters"]["address"],
|
|
514
|
+
handshake_response["clientParameters"]["mtu"],
|
|
515
|
+
interface_name=f"{DEFAULT_INTERFACE_NAME}-{self.remote_identifier}",
|
|
516
|
+
)
|
|
483
517
|
|
|
484
518
|
try:
|
|
485
519
|
yield TunnelResult(
|
|
486
|
-
tunnel.tun.name,
|
|
487
|
-
|
|
520
|
+
tunnel.tun.name,
|
|
521
|
+
handshake_response["serverAddress"],
|
|
522
|
+
handshake_response["serverRSDPort"],
|
|
523
|
+
TunnelProtocol.TCP,
|
|
524
|
+
tunnel,
|
|
525
|
+
)
|
|
488
526
|
finally:
|
|
489
527
|
await tunnel.stop_tunnel()
|
|
490
528
|
|
|
491
529
|
def save_pair_record(self) -> None:
|
|
492
530
|
self.pair_record_path.write_bytes(
|
|
493
531
|
plistlib.dumps({
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
})
|
|
532
|
+
"public_key": self.ed25519_private_key.public_key().public_bytes_raw(),
|
|
533
|
+
"private_key": self.ed25519_private_key.private_bytes_raw(),
|
|
534
|
+
"remote_unlock_host_key": self.remote_unlock_host_key,
|
|
535
|
+
})
|
|
536
|
+
)
|
|
498
537
|
OSUTIL.chown_to_non_sudo_if_needed(self.pair_record_path)
|
|
499
538
|
|
|
500
539
|
@property
|
|
@@ -505,17 +544,19 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
505
544
|
|
|
506
545
|
@property
|
|
507
546
|
def remote_identifier(self) -> str:
|
|
508
|
-
return self.handshake_info[
|
|
547
|
+
return self.handshake_info["peerDeviceInfo"]["identifier"]
|
|
509
548
|
|
|
510
549
|
@property
|
|
511
550
|
def remote_device_model(self) -> str:
|
|
512
|
-
return self.handshake_info[
|
|
551
|
+
return self.handshake_info["peerDeviceInfo"]["model"]
|
|
513
552
|
|
|
514
553
|
@property
|
|
515
554
|
def pair_record_path(self) -> Path:
|
|
516
555
|
pair_records_cache_directory = create_pairing_records_cache_folder()
|
|
517
|
-
return (
|
|
518
|
-
|
|
556
|
+
return (
|
|
557
|
+
pair_records_cache_directory
|
|
558
|
+
/ f"{get_remote_pairing_record_filename(self.remote_identifier)}.{PAIRING_RECORD_EXT}"
|
|
559
|
+
)
|
|
519
560
|
|
|
520
561
|
async def _pair(self) -> None:
|
|
521
562
|
pairing_consent_result = await self._request_pair_consent()
|
|
@@ -527,47 +568,49 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
527
568
|
self.save_pair_record()
|
|
528
569
|
|
|
529
570
|
async def _request_pair_consent(self) -> PairConsentResult:
|
|
530
|
-
"""
|
|
571
|
+
"""Display a Trust / Don't Trust dialog"""
|
|
531
572
|
|
|
532
573
|
tlv = PairingDataComponentTLVBuf.build([
|
|
533
|
-
{
|
|
534
|
-
{
|
|
574
|
+
{"type": PairingDataComponentType.METHOD, "data": b"\x00"},
|
|
575
|
+
{"type": PairingDataComponentType.STATE, "data": b"\x01"},
|
|
535
576
|
])
|
|
536
577
|
|
|
537
|
-
await self._send_pairing_data({
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
578
|
+
await self._send_pairing_data({
|
|
579
|
+
"data": tlv,
|
|
580
|
+
"kind": "setupManualPairing",
|
|
581
|
+
"sendingHost": platform.node(),
|
|
582
|
+
"startNewSession": True,
|
|
583
|
+
})
|
|
584
|
+
self.logger.info("Waiting user pairing consent")
|
|
542
585
|
response = await self._receive_plain_response()
|
|
543
|
-
response = response[
|
|
586
|
+
response = response["event"]["_0"]
|
|
544
587
|
|
|
545
588
|
pin = None
|
|
546
|
-
if
|
|
589
|
+
if "pairingRejectedWithError" in response:
|
|
547
590
|
raise PairingError(
|
|
548
|
-
response[
|
|
549
|
-
|
|
591
|
+
response["pairingRejectedWithError"]["wrappedError"]["userInfo"]["NSLocalizedDescription"]
|
|
592
|
+
)
|
|
593
|
+
elif "awaitingUserConsent" in response:
|
|
550
594
|
pairing_data = await self._receive_pairing_data()
|
|
551
595
|
else:
|
|
552
596
|
# On tvOS no consent is needed and pairing data is returned immediately.
|
|
553
|
-
pairing_data = self._decode_bytes_if_needed(response[
|
|
597
|
+
pairing_data = self._decode_bytes_if_needed(response["pairingData"]["_0"]["data"])
|
|
554
598
|
# On tvOS we need pin to setup pairing.
|
|
555
|
-
if
|
|
556
|
-
pin = input(
|
|
599
|
+
if "AppleTV" in self.remote_device_model:
|
|
600
|
+
pin = input("Enter PIN: ")
|
|
557
601
|
|
|
558
602
|
data = self.decode_tlv(PairingDataComponentTLVBuf.parse(pairing_data))
|
|
559
|
-
return PairConsentResult(
|
|
560
|
-
|
|
561
|
-
|
|
603
|
+
return PairConsentResult(
|
|
604
|
+
public_key=data[PairingDataComponentType.PUBLIC_KEY], salt=data[PairingDataComponentType.SALT], pin=pin
|
|
605
|
+
)
|
|
562
606
|
|
|
563
607
|
def _init_srp_context(self, pairing_consent_result: PairConsentResult) -> None:
|
|
564
608
|
# Receive server public and salt and process them.
|
|
565
|
-
pin = pairing_consent_result.pin or
|
|
609
|
+
pin = pairing_consent_result.pin or "000000"
|
|
566
610
|
client_session = SRPClientSession(
|
|
567
|
-
SRPContext(
|
|
568
|
-
|
|
569
|
-
client_session.process(pairing_consent_result.public_key.hex(),
|
|
570
|
-
pairing_consent_result.salt.hex())
|
|
611
|
+
SRPContext("Pair-Setup", password=pin, prime=PRIME_3072, generator=PRIME_3072_GEN, hash_func=hashlib.sha512)
|
|
612
|
+
)
|
|
613
|
+
client_session.process(pairing_consent_result.public_key.hex(), pairing_consent_result.salt.hex())
|
|
571
614
|
self.srp_context = client_session
|
|
572
615
|
self.encryption_key = binascii.unhexlify(self.srp_context.key)
|
|
573
616
|
|
|
@@ -576,17 +619,18 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
576
619
|
client_session_key_proof = binascii.unhexlify(self.srp_context.key_proof)
|
|
577
620
|
|
|
578
621
|
tlv = PairingDataComponentTLVBuf.build([
|
|
579
|
-
{
|
|
580
|
-
{
|
|
581
|
-
{
|
|
582
|
-
{
|
|
622
|
+
{"type": PairingDataComponentType.STATE, "data": b"\x03"},
|
|
623
|
+
{"type": PairingDataComponentType.PUBLIC_KEY, "data": client_public[:255]},
|
|
624
|
+
{"type": PairingDataComponentType.PUBLIC_KEY, "data": client_public[255:]},
|
|
625
|
+
{"type": PairingDataComponentType.PROOF, "data": client_session_key_proof},
|
|
583
626
|
])
|
|
584
627
|
|
|
585
628
|
response = await self._send_receive_pairing_data({
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
629
|
+
"data": tlv,
|
|
630
|
+
"kind": "setupManualPairing",
|
|
631
|
+
"sendingHost": platform.node(),
|
|
632
|
+
"startNewSession": False,
|
|
633
|
+
})
|
|
590
634
|
data = self.decode_tlv(PairingDataComponentTLVBuf.parse(response))
|
|
591
635
|
assert self.srp_context.verify_proof(data[PairingDataComponentType.PROOF].hex().encode())
|
|
592
636
|
|
|
@@ -596,8 +640,8 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
596
640
|
setup_encryption_key = HKDF(
|
|
597
641
|
algorithm=hashes.SHA512(),
|
|
598
642
|
length=32,
|
|
599
|
-
salt=b
|
|
600
|
-
info=b
|
|
643
|
+
salt=b"Pair-Setup-Encrypt-Salt",
|
|
644
|
+
info=b"Pair-Setup-Encrypt-Info",
|
|
601
645
|
).derive(self.encryption_key)
|
|
602
646
|
|
|
603
647
|
self.ed25519_private_key = Ed25519PrivateKey.generate()
|
|
@@ -607,8 +651,8 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
607
651
|
signbuf = HKDF(
|
|
608
652
|
algorithm=hashes.SHA512(),
|
|
609
653
|
length=32,
|
|
610
|
-
salt=b
|
|
611
|
-
info=b
|
|
654
|
+
salt=b"Pair-Setup-Controller-Sign-Salt",
|
|
655
|
+
info=b"Pair-Setup-Controller-Sign-Info",
|
|
612
656
|
).derive(self.encryption_key)
|
|
613
657
|
|
|
614
658
|
signbuf += self.identifier.encode()
|
|
@@ -617,41 +661,45 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
617
661
|
self.signature = self.ed25519_private_key.sign(signbuf)
|
|
618
662
|
|
|
619
663
|
device_info = dumps({
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
664
|
+
"altIRK": b"\xe9\xe8-\xc0jIykVoT\x00\x19\xb1\xc7{",
|
|
665
|
+
"btAddr": "11:22:33:44:55:66",
|
|
666
|
+
"mac": b"\x11\x22\x33\x44\x55\x66",
|
|
667
|
+
"remotepairing_serial_number": "AAAAAAAAAAAA",
|
|
668
|
+
"accountID": self.identifier,
|
|
669
|
+
"model": "computer-model",
|
|
670
|
+
"name": platform.node(),
|
|
627
671
|
})
|
|
628
672
|
|
|
629
673
|
tlv = PairingDataComponentTLVBuf.build([
|
|
630
|
-
{
|
|
631
|
-
{
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
674
|
+
{"type": PairingDataComponentType.IDENTIFIER, "data": self.identifier.encode()},
|
|
675
|
+
{
|
|
676
|
+
"type": PairingDataComponentType.PUBLIC_KEY,
|
|
677
|
+
"data": self.ed25519_private_key.public_key().public_bytes_raw(),
|
|
678
|
+
},
|
|
679
|
+
{"type": PairingDataComponentType.SIGNATURE, "data": self.signature},
|
|
680
|
+
{"type": PairingDataComponentType.INFO, "data": device_info},
|
|
635
681
|
])
|
|
636
682
|
|
|
637
683
|
cip = ChaCha20Poly1305(setup_encryption_key)
|
|
638
|
-
encrypted_data = cip.encrypt(b
|
|
684
|
+
encrypted_data = cip.encrypt(b"\x00\x00\x00\x00PS-Msg05", tlv, b"")
|
|
639
685
|
|
|
640
686
|
tlv = PairingDataComponentTLVBuf.build([
|
|
641
|
-
{
|
|
642
|
-
{
|
|
643
|
-
{
|
|
687
|
+
{"type": PairingDataComponentType.ENCRYPTED_DATA, "data": encrypted_data[:255]},
|
|
688
|
+
{"type": PairingDataComponentType.ENCRYPTED_DATA, "data": encrypted_data[255:]},
|
|
689
|
+
{"type": PairingDataComponentType.STATE, "data": b"\x05"},
|
|
644
690
|
])
|
|
645
691
|
|
|
646
692
|
response = await self._send_receive_pairing_data({
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
693
|
+
"data": tlv,
|
|
694
|
+
"kind": "setupManualPairing",
|
|
695
|
+
"sendingHost": platform.node(),
|
|
696
|
+
"startNewSession": False,
|
|
697
|
+
})
|
|
651
698
|
data = self.decode_tlv(PairingDataComponentTLVBuf.parse(response))
|
|
652
699
|
|
|
653
|
-
tlv = PairingDataComponentTLVBuf.parse(
|
|
654
|
-
b
|
|
700
|
+
tlv = PairingDataComponentTLVBuf.parse(
|
|
701
|
+
cip.decrypt(b"\x00\x00\x00\x00PS-Msg06", data[PairingDataComponentType.ENCRYPTED_DATA], b"")
|
|
702
|
+
)
|
|
655
703
|
|
|
656
704
|
return tlv
|
|
657
705
|
|
|
@@ -660,7 +708,7 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
660
708
|
algorithm=hashes.SHA512(),
|
|
661
709
|
length=32,
|
|
662
710
|
salt=None,
|
|
663
|
-
info=b
|
|
711
|
+
info=b"ClientEncrypt-main",
|
|
664
712
|
).derive(self.encryption_key)
|
|
665
713
|
self.client_cip = ChaCha20Poly1305(client_key)
|
|
666
714
|
|
|
@@ -668,22 +716,23 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
668
716
|
algorithm=hashes.SHA512(),
|
|
669
717
|
length=32,
|
|
670
718
|
salt=None,
|
|
671
|
-
info=b
|
|
719
|
+
info=b"ServerEncrypt-main",
|
|
672
720
|
).derive(self.encryption_key)
|
|
673
721
|
self.server_cip = ChaCha20Poly1305(server_key)
|
|
674
722
|
|
|
675
723
|
async def _create_remote_unlock(self) -> None:
|
|
676
724
|
try:
|
|
677
|
-
response = await self._send_receive_encrypted_request({
|
|
678
|
-
self.remote_unlock_host_key = response[
|
|
725
|
+
response = await self._send_receive_encrypted_request({"request": {"_0": {"createRemoteUnlockKey": {}}}})
|
|
726
|
+
self.remote_unlock_host_key = response["createRemoteUnlockKey"]["hostKey"]
|
|
679
727
|
except PyMobileDevice3Exception:
|
|
680
728
|
# tvOS does not support remote unlock.
|
|
681
|
-
self.remote_unlock_host_key =
|
|
729
|
+
self.remote_unlock_host_key = ""
|
|
682
730
|
|
|
683
731
|
async def _attempt_pair_verify(self) -> None:
|
|
684
732
|
self.handshake_info = await self._send_receive_handshake({
|
|
685
|
-
|
|
686
|
-
|
|
733
|
+
"hostOptions": {"attemptPairVerify": True},
|
|
734
|
+
"wireProtocolVersion": XpcInt64Type(self.WIRE_PROTOCOL_VERSION),
|
|
735
|
+
})
|
|
687
736
|
|
|
688
737
|
@staticmethod
|
|
689
738
|
def _decode_bytes_if_needed(data: bytes) -> bytes:
|
|
@@ -691,13 +740,17 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
691
740
|
|
|
692
741
|
async def _validate_pairing(self) -> bool:
|
|
693
742
|
pairing_data = PairingDataComponentTLVBuf.build([
|
|
694
|
-
{
|
|
695
|
-
{
|
|
696
|
-
|
|
743
|
+
{"type": PairingDataComponentType.STATE, "data": b"\x01"},
|
|
744
|
+
{
|
|
745
|
+
"type": PairingDataComponentType.PUBLIC_KEY,
|
|
746
|
+
"data": self.x25519_private_key.public_key().public_bytes_raw(),
|
|
747
|
+
},
|
|
697
748
|
])
|
|
698
|
-
response = await self._send_receive_pairing_data({
|
|
699
|
-
|
|
700
|
-
|
|
749
|
+
response = await self._send_receive_pairing_data({
|
|
750
|
+
"data": pairing_data,
|
|
751
|
+
"kind": "verifyManualPairing",
|
|
752
|
+
"startNewSession": True,
|
|
753
|
+
})
|
|
701
754
|
data = self.decode_tlv(PairingDataComponentTLVBuf.parse(response))
|
|
702
755
|
|
|
703
756
|
if PairingDataComponentType.ERROR in data:
|
|
@@ -710,8 +763,8 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
710
763
|
derived_key = HKDF(
|
|
711
764
|
algorithm=hashes.SHA512(),
|
|
712
765
|
length=32,
|
|
713
|
-
salt=b
|
|
714
|
-
info=b
|
|
766
|
+
salt=b"Pair-Verify-Encrypt-Salt",
|
|
767
|
+
info=b"Pair-Verify-Encrypt-Info",
|
|
715
768
|
).derive(self.encryption_key)
|
|
716
769
|
cip = ChaCha20Poly1305(derived_key)
|
|
717
770
|
|
|
@@ -720,31 +773,36 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
720
773
|
# do so. instead, we verify using the next stage
|
|
721
774
|
|
|
722
775
|
if self.pair_record is None:
|
|
723
|
-
private_key = Ed25519PrivateKey.from_private_bytes(b
|
|
776
|
+
private_key = Ed25519PrivateKey.from_private_bytes(b"\x00" * 0x20)
|
|
724
777
|
else:
|
|
725
|
-
private_key = Ed25519PrivateKey.from_private_bytes(self.pair_record[
|
|
778
|
+
private_key = Ed25519PrivateKey.from_private_bytes(self.pair_record["private_key"])
|
|
726
779
|
|
|
727
|
-
signbuf = b
|
|
780
|
+
signbuf = b""
|
|
728
781
|
signbuf += self.x25519_private_key.public_key().public_bytes_raw()
|
|
729
782
|
signbuf += self.identifier.encode()
|
|
730
783
|
signbuf += peer_public_key.public_bytes_raw()
|
|
731
784
|
|
|
732
785
|
signature = private_key.sign(signbuf)
|
|
733
786
|
|
|
734
|
-
encrypted_data = cip.encrypt(
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
787
|
+
encrypted_data = cip.encrypt(
|
|
788
|
+
b"\x00\x00\x00\x00PV-Msg03",
|
|
789
|
+
PairingDataComponentTLVBuf.build([
|
|
790
|
+
{"type": PairingDataComponentType.IDENTIFIER, "data": self.identifier.encode()},
|
|
791
|
+
{"type": PairingDataComponentType.SIGNATURE, "data": signature},
|
|
792
|
+
]),
|
|
793
|
+
b"",
|
|
794
|
+
)
|
|
738
795
|
|
|
739
796
|
pairing_data = PairingDataComponentTLVBuf.build([
|
|
740
|
-
{
|
|
741
|
-
{
|
|
797
|
+
{"type": PairingDataComponentType.STATE, "data": b"\x03"},
|
|
798
|
+
{"type": PairingDataComponentType.ENCRYPTED_DATA, "data": encrypted_data},
|
|
742
799
|
])
|
|
743
800
|
|
|
744
801
|
response = await self._send_receive_pairing_data({
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
802
|
+
"data": pairing_data,
|
|
803
|
+
"kind": "verifyManualPairing",
|
|
804
|
+
"startNewSession": False,
|
|
805
|
+
})
|
|
748
806
|
data = self.decode_tlv(PairingDataComponentTLVBuf.parse(response))
|
|
749
807
|
|
|
750
808
|
if PairingDataComponentType.ERROR in data:
|
|
@@ -754,66 +812,68 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
754
812
|
return True
|
|
755
813
|
|
|
756
814
|
async def _send_pair_verify_failed(self) -> None:
|
|
757
|
-
await self._send_plain_request({
|
|
815
|
+
await self._send_plain_request({"event": {"_0": {"pairVerifyFailed": {}}}})
|
|
758
816
|
|
|
759
817
|
async def _send_receive_encrypted_request(self, request: dict) -> dict:
|
|
760
|
-
nonce = Int64ul.build(self._encrypted_sequence_number) + b
|
|
761
|
-
encrypted_data = self.client_cip.encrypt(
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
'originatedBy': 'host',
|
|
769
|
-
'sequenceNumber': XpcUInt64Type(self._sequence_number)})
|
|
818
|
+
nonce = Int64ul.build(self._encrypted_sequence_number) + b"\x00" * 4
|
|
819
|
+
encrypted_data = self.client_cip.encrypt(nonce, json.dumps(request).encode(), b"")
|
|
820
|
+
|
|
821
|
+
response = await self.send_receive_request({
|
|
822
|
+
"message": {"streamEncrypted": {"_0": encrypted_data}},
|
|
823
|
+
"originatedBy": "host",
|
|
824
|
+
"sequenceNumber": XpcUInt64Type(self._sequence_number),
|
|
825
|
+
})
|
|
770
826
|
self._encrypted_sequence_number += 1
|
|
771
827
|
|
|
772
|
-
encrypted_data = self._decode_bytes_if_needed(response[
|
|
828
|
+
encrypted_data = self._decode_bytes_if_needed(response["message"]["streamEncrypted"]["_0"])
|
|
773
829
|
plaintext = self.server_cip.decrypt(nonce, encrypted_data, None)
|
|
774
|
-
response = json.loads(plaintext)[
|
|
830
|
+
response = json.loads(plaintext)["response"]["_1"]
|
|
775
831
|
|
|
776
|
-
if
|
|
777
|
-
raise PyMobileDevice3Exception(response[
|
|
832
|
+
if "errorExtended" in response:
|
|
833
|
+
raise PyMobileDevice3Exception(response["errorExtended"]["_0"]["userInfo"]["NSLocalizedDescription"])
|
|
778
834
|
|
|
779
835
|
return response
|
|
780
836
|
|
|
781
837
|
async def _send_receive_handshake(self, handshake_data: dict) -> dict:
|
|
782
|
-
response = await self._send_receive_plain_request({
|
|
783
|
-
return response[
|
|
838
|
+
response = await self._send_receive_plain_request({"request": {"_0": {"handshake": {"_0": handshake_data}}}})
|
|
839
|
+
return response["response"]["_1"]["handshake"]["_0"]
|
|
784
840
|
|
|
785
841
|
async def _send_receive_pairing_data(self, pairing_data: dict) -> bytes:
|
|
786
842
|
await self._send_pairing_data(pairing_data)
|
|
787
843
|
return await self._receive_pairing_data()
|
|
788
844
|
|
|
789
845
|
async def _send_pairing_data(self, pairing_data: dict) -> None:
|
|
790
|
-
await self._send_plain_request({
|
|
846
|
+
await self._send_plain_request({"event": {"_0": {"pairingData": {"_0": pairing_data}}}})
|
|
791
847
|
|
|
792
848
|
async def _receive_pairing_data(self) -> bytes:
|
|
793
849
|
response = await self._receive_plain_response()
|
|
794
|
-
response = response[
|
|
795
|
-
if
|
|
796
|
-
return self._decode_bytes_if_needed(response[
|
|
797
|
-
if
|
|
798
|
-
raise UserDeniedPairingError(
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
850
|
+
response = response["event"]["_0"]
|
|
851
|
+
if "pairingData" in response:
|
|
852
|
+
return self._decode_bytes_if_needed(response["pairingData"]["_0"]["data"])
|
|
853
|
+
if "pairingRejectedWithError" in response:
|
|
854
|
+
raise UserDeniedPairingError(
|
|
855
|
+
response["pairingRejectedWithError"]
|
|
856
|
+
.get("wrappedError", {})
|
|
857
|
+
.get("userInfo", {})
|
|
858
|
+
.get("NSLocalizedDescription")
|
|
859
|
+
)
|
|
860
|
+
raise PyMobileDevice3Exception(f"Got an unknown state message: {response}")
|
|
803
861
|
|
|
804
862
|
async def _send_receive_plain_request(self, plain_request: dict):
|
|
805
863
|
await self._send_plain_request(plain_request)
|
|
806
864
|
return await self._receive_plain_response()
|
|
807
865
|
|
|
808
866
|
async def _send_plain_request(self, plain_request: dict) -> None:
|
|
809
|
-
await self.send_request({
|
|
810
|
-
|
|
811
|
-
|
|
867
|
+
await self.send_request({
|
|
868
|
+
"message": {"plain": {"_0": plain_request}},
|
|
869
|
+
"originatedBy": "host",
|
|
870
|
+
"sequenceNumber": XpcUInt64Type(self._sequence_number),
|
|
871
|
+
})
|
|
812
872
|
self._sequence_number += 1
|
|
813
873
|
|
|
814
874
|
async def _receive_plain_response(self) -> dict:
|
|
815
875
|
response = await self.receive_response()
|
|
816
|
-
return response[
|
|
876
|
+
return response["message"]["plain"]["_0"]
|
|
817
877
|
|
|
818
878
|
@staticmethod
|
|
819
879
|
def decode_tlv(tlv_list: list[Container]) -> dict:
|
|
@@ -825,7 +885,7 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
825
885
|
result[tlv.type] = tlv.data
|
|
826
886
|
return result
|
|
827
887
|
|
|
828
|
-
async def __aenter__(self) ->
|
|
888
|
+
async def __aenter__(self) -> "CoreDeviceTunnelService":
|
|
829
889
|
return self
|
|
830
890
|
|
|
831
891
|
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
@@ -833,7 +893,7 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
833
893
|
|
|
834
894
|
|
|
835
895
|
class CoreDeviceTunnelService(RemotePairingProtocol, RemoteService):
|
|
836
|
-
SERVICE_NAME =
|
|
896
|
+
SERVICE_NAME = "com.apple.internal.dt.coredevice.untrusted.tunnelservice"
|
|
837
897
|
|
|
838
898
|
def __init__(self, rsd: RemoteServiceDiscoveryService):
|
|
839
899
|
RemoteService.__init__(self, rsd, self.SERVICE_NAME)
|
|
@@ -845,12 +905,12 @@ class CoreDeviceTunnelService(RemotePairingProtocol, RemoteService):
|
|
|
845
905
|
await RemoteService.connect(self)
|
|
846
906
|
try:
|
|
847
907
|
response = await self.service.receive_response()
|
|
848
|
-
self.version = response[
|
|
908
|
+
self.version = response["ServiceVersion"]
|
|
849
909
|
|
|
850
910
|
# Perform pairing if necessary and start a trusted RemoteXPC connection
|
|
851
911
|
await RemotePairingProtocol.connect(self, autopair=autopair)
|
|
852
912
|
self.hostname = self.service.address[0]
|
|
853
|
-
except Exception:
|
|
913
|
+
except Exception:
|
|
854
914
|
await self.service.close()
|
|
855
915
|
raise
|
|
856
916
|
|
|
@@ -860,11 +920,13 @@ class CoreDeviceTunnelService(RemotePairingProtocol, RemoteService):
|
|
|
860
920
|
|
|
861
921
|
async def receive_response(self) -> dict:
|
|
862
922
|
response = await self.service.receive_response()
|
|
863
|
-
return response[
|
|
923
|
+
return response["value"]
|
|
864
924
|
|
|
865
925
|
async def send_request(self, data: dict) -> None:
|
|
866
926
|
return await self.service.send_request({
|
|
867
|
-
|
|
927
|
+
"mangledTypeName": "RemotePairing.ControlChannelMessageEnvelope",
|
|
928
|
+
"value": data,
|
|
929
|
+
})
|
|
868
930
|
|
|
869
931
|
|
|
870
932
|
class RemotePairingTunnelService(RemotePairingProtocol):
|
|
@@ -889,7 +951,7 @@ class RemotePairingTunnelService(RemotePairingProtocol):
|
|
|
889
951
|
if not await self._validate_pairing():
|
|
890
952
|
raise ConnectionAbortedError()
|
|
891
953
|
self._init_client_server_main_encryption_keys()
|
|
892
|
-
except
|
|
954
|
+
except Exception:
|
|
893
955
|
await self.close()
|
|
894
956
|
raise
|
|
895
957
|
|
|
@@ -897,21 +959,20 @@ class RemotePairingTunnelService(RemotePairingProtocol):
|
|
|
897
959
|
if self._writer is None:
|
|
898
960
|
return
|
|
899
961
|
self._writer.close()
|
|
900
|
-
|
|
962
|
+
with suppress(ssl.SSLError):
|
|
901
963
|
await self._writer.wait_closed()
|
|
902
|
-
except ssl.SSLError:
|
|
903
|
-
pass
|
|
904
964
|
self._writer = None
|
|
905
965
|
self._reader = None
|
|
906
966
|
|
|
907
967
|
async def receive_response(self) -> dict:
|
|
908
968
|
await self._reader.readexactly(len(REPAIRING_PACKET_MAGIC))
|
|
909
|
-
size = struct.unpack(
|
|
969
|
+
size = struct.unpack(">H", await self._reader.readexactly(2))[0]
|
|
910
970
|
return json.loads(await self._reader.readexactly(size))
|
|
911
971
|
|
|
912
972
|
async def send_request(self, data: dict) -> None:
|
|
913
973
|
self._writer.write(
|
|
914
|
-
RPPairingPacket.build({
|
|
974
|
+
RPPairingPacket.build({"body": json.dumps(data, default=self._default_json_encoder).encode()})
|
|
975
|
+
)
|
|
915
976
|
await self._writer.drain()
|
|
916
977
|
|
|
917
978
|
@staticmethod
|
|
@@ -925,8 +986,9 @@ class RemotePairingTunnelService(RemotePairingProtocol):
|
|
|
925
986
|
return base64.b64decode(data)
|
|
926
987
|
|
|
927
988
|
def __repr__(self) -> str:
|
|
928
|
-
return (
|
|
929
|
-
|
|
989
|
+
return (
|
|
990
|
+
f"<{self.__class__.__name__} IDENTIFIER:{self.remote_identifier} HOSTNAME:{self.hostname} PORT:{self.port}>"
|
|
991
|
+
)
|
|
930
992
|
|
|
931
993
|
|
|
932
994
|
class RemotePairingManualPairingService(RemotePairingTunnelService):
|
|
@@ -937,28 +999,38 @@ class RemotePairingManualPairingService(RemotePairingTunnelService):
|
|
|
937
999
|
|
|
938
1000
|
|
|
939
1001
|
class CoreDeviceTunnelProxy(StartTcpTunnel):
|
|
940
|
-
SERVICE_NAME =
|
|
1002
|
+
SERVICE_NAME = "com.apple.internal.devicecompute.CoreDeviceProxy"
|
|
941
1003
|
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
1004
|
+
@classmethod
|
|
1005
|
+
async def create(cls, lockdown: LockdownServiceProvider) -> "CoreDeviceTunnelProxy":
|
|
1006
|
+
return cls(await lockdown.aio_start_lockdown_service(cls.SERVICE_NAME), lockdown.udid)
|
|
1007
|
+
|
|
1008
|
+
def __init__(self, service: ServiceConnection, remote_identifier: str) -> None:
|
|
1009
|
+
self._service: ServiceConnection = service
|
|
1010
|
+
self._remote_identifier: str = remote_identifier
|
|
945
1011
|
|
|
946
1012
|
@property
|
|
947
1013
|
def remote_identifier(self) -> str:
|
|
948
|
-
return self.
|
|
1014
|
+
return self._remote_identifier
|
|
949
1015
|
|
|
950
1016
|
@asynccontextmanager
|
|
951
|
-
async def start_tcp_tunnel(self) -> AsyncGenerator[
|
|
952
|
-
self._service
|
|
1017
|
+
async def start_tcp_tunnel(self) -> AsyncGenerator["TunnelResult", None]:
|
|
1018
|
+
assert self._service is not None, "service must be connected first"
|
|
953
1019
|
tunnel = RemotePairingTcpTunnel(self._service.reader, self._service.writer)
|
|
954
1020
|
handshake_response = await tunnel.request_tunnel_establish()
|
|
955
|
-
tunnel.start_tunnel(
|
|
956
|
-
|
|
957
|
-
|
|
1021
|
+
tunnel.start_tunnel(
|
|
1022
|
+
handshake_response["clientParameters"]["address"],
|
|
1023
|
+
handshake_response["clientParameters"]["mtu"],
|
|
1024
|
+
interface_name=f"{DEFAULT_INTERFACE_NAME}-{self.remote_identifier}",
|
|
1025
|
+
)
|
|
958
1026
|
try:
|
|
959
1027
|
yield TunnelResult(
|
|
960
|
-
tunnel.tun.name,
|
|
961
|
-
|
|
1028
|
+
tunnel.tun.name,
|
|
1029
|
+
handshake_response["serverAddress"],
|
|
1030
|
+
handshake_response["serverRSDPort"],
|
|
1031
|
+
TunnelProtocol.TCP,
|
|
1032
|
+
tunnel,
|
|
1033
|
+
)
|
|
962
1034
|
finally:
|
|
963
1035
|
await tunnel.stop_tunnel()
|
|
964
1036
|
|
|
@@ -968,7 +1040,8 @@ class CoreDeviceTunnelProxy(StartTcpTunnel):
|
|
|
968
1040
|
|
|
969
1041
|
|
|
970
1042
|
async def create_core_device_tunnel_service_using_rsd(
|
|
971
|
-
|
|
1043
|
+
rsd: RemoteServiceDiscoveryService, autopair: bool = True
|
|
1044
|
+
) -> CoreDeviceTunnelService:
|
|
972
1045
|
service = CoreDeviceTunnelService(rsd)
|
|
973
1046
|
try:
|
|
974
1047
|
await service.connect(autopair=autopair)
|
|
@@ -977,21 +1050,23 @@ async def create_core_device_tunnel_service_using_rsd(
|
|
|
977
1050
|
await service.close()
|
|
978
1051
|
service = CoreDeviceTunnelService(rsd)
|
|
979
1052
|
await service.connect(autopair=autopair)
|
|
980
|
-
except Exception:
|
|
1053
|
+
except Exception:
|
|
981
1054
|
await service.close()
|
|
982
1055
|
raise
|
|
983
1056
|
return service
|
|
984
1057
|
|
|
985
1058
|
|
|
986
1059
|
async def create_core_device_tunnel_service_using_remotepairing(
|
|
987
|
-
|
|
1060
|
+
remote_identifier: str, hostname: str, port: int, autopair: bool = True
|
|
1061
|
+
) -> RemotePairingTunnelService:
|
|
988
1062
|
service = RemotePairingTunnelService(remote_identifier, hostname, port)
|
|
989
1063
|
await service.connect(autopair=autopair)
|
|
990
1064
|
return service
|
|
991
1065
|
|
|
992
1066
|
|
|
993
1067
|
async def create_core_device_service_using_remotepairing_manual_pairing(
|
|
994
|
-
|
|
1068
|
+
remote_identifier: str, hostname: str, port: int, autopair: bool = True
|
|
1069
|
+
) -> RemotePairingTunnelService:
|
|
995
1070
|
service = RemotePairingManualPairingService(remote_identifier, hostname, port)
|
|
996
1071
|
await service.connect(autopair=autopair)
|
|
997
1072
|
return service
|
|
@@ -999,14 +1074,16 @@ async def create_core_device_service_using_remotepairing_manual_pairing(
|
|
|
999
1074
|
|
|
1000
1075
|
@asynccontextmanager
|
|
1001
1076
|
async def start_tunnel_over_remotepairing(
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1077
|
+
remote_pairing: RemotePairingTunnelService,
|
|
1078
|
+
secrets: Optional[TextIO] = None,
|
|
1079
|
+
max_idle_timeout: float = RemotePairingQuicTunnel.MAX_IDLE_TIMEOUT,
|
|
1080
|
+
protocol: TunnelProtocol = TunnelProtocol.QUIC,
|
|
1081
|
+
) -> AsyncGenerator[TunnelResult, None]:
|
|
1006
1082
|
async with remote_pairing:
|
|
1007
1083
|
if protocol == TunnelProtocol.QUIC:
|
|
1008
1084
|
async with remote_pairing.start_quic_tunnel(
|
|
1009
|
-
|
|
1085
|
+
secrets_log_file=secrets, max_idle_timeout=max_idle_timeout
|
|
1086
|
+
) as tunnel_result:
|
|
1010
1087
|
yield tunnel_result
|
|
1011
1088
|
elif protocol == TunnelProtocol.TCP:
|
|
1012
1089
|
async with remote_pairing.start_tcp_tunnel() as tunnel_result:
|
|
@@ -1015,15 +1092,17 @@ async def start_tunnel_over_remotepairing(
|
|
|
1015
1092
|
|
|
1016
1093
|
@asynccontextmanager
|
|
1017
1094
|
async def start_tunnel_over_core_device(
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1095
|
+
service_provider: CoreDeviceTunnelService,
|
|
1096
|
+
secrets: Optional[TextIO] = None,
|
|
1097
|
+
max_idle_timeout: float = RemotePairingQuicTunnel.MAX_IDLE_TIMEOUT,
|
|
1098
|
+
protocol: TunnelProtocol = TunnelProtocol.QUIC,
|
|
1099
|
+
) -> AsyncGenerator[TunnelResult, None]:
|
|
1022
1100
|
stop_remoted_if_required()
|
|
1023
1101
|
async with service_provider:
|
|
1024
1102
|
if protocol == TunnelProtocol.QUIC:
|
|
1025
1103
|
async with service_provider.start_quic_tunnel(
|
|
1026
|
-
|
|
1104
|
+
secrets_log_file=secrets, max_idle_timeout=max_idle_timeout
|
|
1105
|
+
) as tunnel_result:
|
|
1027
1106
|
resume_remoted_if_required()
|
|
1028
1107
|
yield tunnel_result
|
|
1029
1108
|
elif protocol == TunnelProtocol.TCP:
|
|
@@ -1034,57 +1113,64 @@ async def start_tunnel_over_core_device(
|
|
|
1034
1113
|
|
|
1035
1114
|
@asynccontextmanager
|
|
1036
1115
|
async def start_tunnel(
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1116
|
+
protocol_handler: RemotePairingProtocol,
|
|
1117
|
+
secrets: Optional[TextIO] = None,
|
|
1118
|
+
max_idle_timeout: float = RemotePairingQuicTunnel.MAX_IDLE_TIMEOUT,
|
|
1119
|
+
protocol: TunnelProtocol = TunnelProtocol.QUIC,
|
|
1120
|
+
) -> AsyncGenerator[TunnelResult, None]:
|
|
1040
1121
|
if isinstance(protocol_handler, CoreDeviceTunnelService):
|
|
1041
1122
|
async with start_tunnel_over_core_device(
|
|
1042
|
-
|
|
1123
|
+
protocol_handler, secrets=secrets, max_idle_timeout=max_idle_timeout, protocol=protocol
|
|
1124
|
+
) as service:
|
|
1043
1125
|
yield service
|
|
1044
1126
|
elif isinstance(protocol_handler, RemotePairingTunnelService):
|
|
1045
1127
|
async with start_tunnel_over_remotepairing(
|
|
1046
|
-
|
|
1128
|
+
protocol_handler, secrets=secrets, max_idle_timeout=max_idle_timeout, protocol=protocol
|
|
1129
|
+
) as service:
|
|
1047
1130
|
yield service
|
|
1048
1131
|
elif isinstance(protocol_handler, CoreDeviceTunnelProxy):
|
|
1049
1132
|
if protocol != TunnelProtocol.TCP:
|
|
1050
|
-
raise ValueError(
|
|
1133
|
+
raise ValueError("CoreDeviceTunnelProxy protocol can only be TCP")
|
|
1051
1134
|
async with protocol_handler.start_tcp_tunnel() as service:
|
|
1052
1135
|
yield service
|
|
1053
1136
|
else:
|
|
1054
|
-
raise
|
|
1137
|
+
raise TypeError(f"Bad value for protocol_handler: {protocol_handler}")
|
|
1055
1138
|
|
|
1056
1139
|
|
|
1057
1140
|
async def get_core_device_tunnel_services(
|
|
1058
|
-
|
|
1059
|
-
|
|
1141
|
+
bonjour_timeout: float = DEFAULT_BONJOUR_TIMEOUT, udid: Optional[str] = None
|
|
1142
|
+
) -> list[CoreDeviceTunnelService]:
|
|
1060
1143
|
result = []
|
|
1061
1144
|
for rsd in await get_rsds(bonjour_timeout=bonjour_timeout, udid=udid):
|
|
1062
|
-
if udid is None and (
|
|
1063
|
-
|
|
1064
|
-
|
|
1145
|
+
if udid is None and (
|
|
1146
|
+
(Version(rsd.product_version) < Version("17.0")) and not rsd.product_type.startswith("RealityDevice")
|
|
1147
|
+
):
|
|
1148
|
+
logger.debug(f"Skipping {rsd.udid}:, iOS {rsd.product_version} < 17.0")
|
|
1065
1149
|
await rsd.close()
|
|
1066
1150
|
continue
|
|
1067
1151
|
try:
|
|
1068
1152
|
result.append(await create_core_device_tunnel_service_using_rsd(rsd))
|
|
1069
|
-
except Exception
|
|
1070
|
-
logger.
|
|
1153
|
+
except Exception:
|
|
1154
|
+
logger.exception(f"Failed to start service: {rsd}")
|
|
1071
1155
|
await rsd.close()
|
|
1072
1156
|
raise
|
|
1073
1157
|
return result
|
|
1074
1158
|
|
|
1075
1159
|
|
|
1076
1160
|
async def get_remote_pairing_tunnel_services(
|
|
1077
|
-
|
|
1078
|
-
|
|
1161
|
+
bonjour_timeout: float = DEFAULT_BONJOUR_TIMEOUT, udid: Optional[str] = None
|
|
1162
|
+
) -> list[RemotePairingTunnelService]:
|
|
1079
1163
|
result = []
|
|
1080
1164
|
for answer in await browse_remotepairing(timeout=bonjour_timeout):
|
|
1081
|
-
for
|
|
1165
|
+
for address in answer.addresses:
|
|
1082
1166
|
for identifier in iter_remote_paired_identifiers():
|
|
1083
1167
|
if udid is not None and identifier != udid:
|
|
1084
1168
|
continue
|
|
1085
1169
|
conn = None
|
|
1086
1170
|
try:
|
|
1087
|
-
conn = await create_core_device_tunnel_service_using_remotepairing(
|
|
1171
|
+
conn = await create_core_device_tunnel_service_using_remotepairing(
|
|
1172
|
+
identifier, address.full_ip, answer.port
|
|
1173
|
+
)
|
|
1088
1174
|
result.append(conn)
|
|
1089
1175
|
break
|
|
1090
1176
|
except ConnectionAbortedError:
|