pymobiledevice3 5.0.1__py3-none-any.whl → 5.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pymobiledevice3 might be problematic. Click here for more details.
- misc/plist_sniffer.py +15 -15
- misc/remotexpc_sniffer.py +29 -28
- pymobiledevice3/__main__.py +128 -102
- pymobiledevice3/_version.py +2 -2
- pymobiledevice3/bonjour.py +26 -49
- pymobiledevice3/ca.py +32 -24
- pymobiledevice3/cli/activation.py +7 -7
- pymobiledevice3/cli/afc.py +19 -19
- pymobiledevice3/cli/amfi.py +4 -4
- pymobiledevice3/cli/apps.py +51 -39
- pymobiledevice3/cli/backup.py +58 -32
- pymobiledevice3/cli/bonjour.py +25 -18
- pymobiledevice3/cli/cli_common.py +112 -81
- pymobiledevice3/cli/companion_proxy.py +4 -4
- pymobiledevice3/cli/completions.py +10 -10
- pymobiledevice3/cli/crash.py +37 -31
- pymobiledevice3/cli/developer.py +602 -520
- pymobiledevice3/cli/diagnostics.py +38 -33
- pymobiledevice3/cli/lockdown.py +79 -74
- pymobiledevice3/cli/mounter.py +85 -68
- pymobiledevice3/cli/notification.py +10 -10
- pymobiledevice3/cli/pcap.py +19 -14
- pymobiledevice3/cli/power_assertion.py +12 -10
- pymobiledevice3/cli/processes.py +10 -10
- pymobiledevice3/cli/profile.py +88 -77
- pymobiledevice3/cli/provision.py +17 -17
- pymobiledevice3/cli/remote.py +186 -110
- pymobiledevice3/cli/restore.py +43 -40
- pymobiledevice3/cli/springboard.py +30 -28
- pymobiledevice3/cli/syslog.py +85 -58
- pymobiledevice3/cli/usbmux.py +21 -20
- pymobiledevice3/cli/version.py +3 -2
- pymobiledevice3/cli/webinspector.py +157 -79
- pymobiledevice3/common.py +1 -1
- pymobiledevice3/exceptions.py +154 -60
- pymobiledevice3/irecv.py +49 -53
- pymobiledevice3/irecv_devices.py +1489 -492
- pymobiledevice3/lockdown.py +394 -241
- pymobiledevice3/lockdown_service_provider.py +5 -7
- pymobiledevice3/osu/os_utils.py +18 -9
- pymobiledevice3/osu/posix_util.py +28 -15
- pymobiledevice3/osu/win_util.py +14 -8
- pymobiledevice3/pair_records.py +19 -19
- pymobiledevice3/remote/common.py +4 -4
- pymobiledevice3/remote/core_device/app_service.py +94 -67
- pymobiledevice3/remote/core_device/core_device_service.py +17 -14
- pymobiledevice3/remote/core_device/device_info.py +5 -5
- pymobiledevice3/remote/core_device/diagnostics_service.py +10 -8
- pymobiledevice3/remote/core_device/file_service.py +47 -33
- pymobiledevice3/remote/remote_service_discovery.py +53 -35
- pymobiledevice3/remote/remotexpc.py +62 -41
- pymobiledevice3/remote/tunnel_service.py +371 -293
- pymobiledevice3/remote/utils.py +12 -11
- pymobiledevice3/remote/xpc_message.py +145 -125
- pymobiledevice3/resources/dsc_uuid_map.py +19 -19
- pymobiledevice3/resources/firmware_notifications.py +16 -16
- pymobiledevice3/restore/asr.py +27 -27
- pymobiledevice3/restore/base_restore.py +90 -47
- pymobiledevice3/restore/consts.py +87 -66
- pymobiledevice3/restore/device.py +11 -11
- pymobiledevice3/restore/fdr.py +46 -46
- pymobiledevice3/restore/ftab.py +19 -19
- pymobiledevice3/restore/img4.py +130 -133
- pymobiledevice3/restore/mbn.py +35 -54
- pymobiledevice3/restore/recovery.py +125 -135
- pymobiledevice3/restore/restore.py +524 -523
- pymobiledevice3/restore/restore_options.py +122 -115
- pymobiledevice3/restore/restored_client.py +25 -22
- pymobiledevice3/restore/tss.py +378 -270
- pymobiledevice3/service_connection.py +50 -46
- pymobiledevice3/services/accessibilityaudit.py +136 -126
- pymobiledevice3/services/afc.py +350 -291
- pymobiledevice3/services/amfi.py +21 -18
- pymobiledevice3/services/companion.py +23 -19
- pymobiledevice3/services/crash_reports.py +60 -46
- pymobiledevice3/services/debugserver_applist.py +3 -3
- pymobiledevice3/services/device_arbitration.py +8 -8
- pymobiledevice3/services/device_link.py +55 -47
- pymobiledevice3/services/diagnostics.py +971 -968
- pymobiledevice3/services/dtfetchsymbols.py +8 -8
- pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
- pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
- pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
- pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
- pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
- pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +442 -421
- pymobiledevice3/services/dvt/instruments/device_info.py +11 -11
- pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
- pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
- pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
- pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
- pymobiledevice3/services/dvt/instruments/process_control.py +25 -10
- pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
- pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
- pymobiledevice3/services/dvt/testmanaged/xcuitest.py +40 -50
- pymobiledevice3/services/file_relay.py +10 -10
- pymobiledevice3/services/heartbeat.py +8 -7
- pymobiledevice3/services/house_arrest.py +12 -15
- pymobiledevice3/services/installation_proxy.py +119 -100
- pymobiledevice3/services/lockdown_service.py +12 -5
- pymobiledevice3/services/misagent.py +22 -19
- pymobiledevice3/services/mobile_activation.py +84 -72
- pymobiledevice3/services/mobile_config.py +330 -301
- pymobiledevice3/services/mobile_image_mounter.py +137 -116
- pymobiledevice3/services/mobilebackup2.py +188 -150
- pymobiledevice3/services/notification_proxy.py +11 -11
- pymobiledevice3/services/os_trace.py +69 -51
- pymobiledevice3/services/pcapd.py +306 -306
- pymobiledevice3/services/power_assertion.py +10 -9
- pymobiledevice3/services/preboard.py +4 -4
- pymobiledevice3/services/remote_fetch_symbols.py +16 -14
- pymobiledevice3/services/remote_server.py +176 -146
- pymobiledevice3/services/restore_service.py +16 -16
- pymobiledevice3/services/screenshot.py +13 -10
- pymobiledevice3/services/simulate_location.py +7 -7
- pymobiledevice3/services/springboard.py +15 -15
- pymobiledevice3/services/syslog.py +5 -5
- pymobiledevice3/services/web_protocol/alert.py +3 -3
- pymobiledevice3/services/web_protocol/automation_session.py +180 -176
- pymobiledevice3/services/web_protocol/cdp_screencast.py +44 -36
- pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
- pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
- pymobiledevice3/services/web_protocol/driver.py +47 -45
- pymobiledevice3/services/web_protocol/element.py +74 -63
- pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
- pymobiledevice3/services/web_protocol/selenium_api.py +2 -2
- pymobiledevice3/services/web_protocol/session_protocol.py +15 -10
- pymobiledevice3/services/web_protocol/switch_to.py +11 -12
- pymobiledevice3/services/webinspector.py +127 -116
- pymobiledevice3/tcp_forwarder.py +35 -22
- pymobiledevice3/tunneld/api.py +20 -15
- pymobiledevice3/tunneld/server.py +212 -133
- pymobiledevice3/usbmux.py +183 -138
- pymobiledevice3/utils.py +14 -11
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.2.dist-info}/METADATA +1 -1
- pymobiledevice3-5.0.2.dist-info/RECORD +173 -0
- pymobiledevice3-5.0.1.dist-info/RECORD +0 -173
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.2.dist-info}/WHEEL +0 -0
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.2.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.2.dist-info}/licenses/LICENSE +0 -0
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.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(f
|
|
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,10 +225,8 @@ 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)
|
|
@@ -229,9 +238,10 @@ class RemotePairingQuicTunnel(RemotePairingTunnel, QuicConnectionProtocol):
|
|
|
229
238
|
async def request_tunnel_establish(self) -> dict:
|
|
230
239
|
stream_id = self._quic.get_next_available_stream_id()
|
|
231
240
|
# pad the data with random data to force the MTU size correctly
|
|
232
|
-
self._quic.send_datagram_frame(b
|
|
233
|
-
self._quic.send_stream_data(
|
|
234
|
-
{
|
|
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
|
+
)
|
|
235
245
|
self.transmit()
|
|
236
246
|
return await self._queue.get()
|
|
237
247
|
|
|
@@ -261,7 +271,7 @@ class RemotePairingQuicTunnel(RemotePairingTunnel, QuicConnectionProtocol):
|
|
|
261
271
|
|
|
262
272
|
@staticmethod
|
|
263
273
|
def _encode_cdtunnel_packet(data: dict) -> bytes:
|
|
264
|
-
return CDTunnelPacket.build({
|
|
274
|
+
return CDTunnelPacket.build({"body": json.dumps(data).encode()})
|
|
265
275
|
|
|
266
276
|
|
|
267
277
|
class RemotePairingTcpTunnel(RemotePairingTunnel):
|
|
@@ -283,30 +293,27 @@ class RemotePairingTcpTunnel(RemotePairingTunnel):
|
|
|
283
293
|
while True:
|
|
284
294
|
try:
|
|
285
295
|
ipv6_header = await self._reader.readexactly(IPV6_HEADER_SIZE)
|
|
286
|
-
ipv6_length = struct.unpack(
|
|
296
|
+
ipv6_length = struct.unpack(">H", ipv6_header[4:6])[0]
|
|
287
297
|
ipv6_body = await self._reader.readexactly(ipv6_length)
|
|
288
298
|
self.tun.write(LOOPBACK_HEADER + ipv6_header + ipv6_body)
|
|
289
299
|
except asyncio.exceptions.IncompleteReadError:
|
|
290
300
|
await asyncio.sleep(1)
|
|
291
301
|
except OSError as e:
|
|
292
|
-
self._logger.warning(f
|
|
302
|
+
self._logger.warning(f"got {e.__class__.__name__} in {asyncio.current_task().get_name()}")
|
|
293
303
|
await self.wait_closed()
|
|
294
304
|
|
|
295
305
|
async def wait_closed(self) -> None:
|
|
296
|
-
|
|
306
|
+
with suppress(OSError):
|
|
297
307
|
await self._writer.wait_closed()
|
|
298
|
-
except OSError:
|
|
299
|
-
pass
|
|
300
308
|
|
|
301
309
|
async def request_tunnel_establish(self) -> dict:
|
|
302
|
-
self._writer.write(self._encode_cdtunnel_packet(
|
|
303
|
-
{'type': 'clientHandshakeRequest', 'mtu': self.REQUESTED_MTU}))
|
|
310
|
+
self._writer.write(self._encode_cdtunnel_packet({"type": "clientHandshakeRequest", "mtu": self.REQUESTED_MTU}))
|
|
304
311
|
await self._writer.drain()
|
|
305
312
|
return json.loads(CDTunnelPacket.parse(await self._reader.read(self.REQUESTED_MTU)).body)
|
|
306
313
|
|
|
307
314
|
def start_tunnel(self, address: str, mtu: int, interface_name=DEFAULT_INTERFACE_NAME) -> None:
|
|
308
315
|
super().start_tunnel(address, mtu, interface_name=interface_name)
|
|
309
|
-
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}")
|
|
310
317
|
|
|
311
318
|
async def stop_tunnel(self) -> None:
|
|
312
319
|
self._sock_read_task.cancel()
|
|
@@ -315,10 +322,8 @@ class RemotePairingTcpTunnel(RemotePairingTunnel):
|
|
|
315
322
|
await super().stop_tunnel()
|
|
316
323
|
if not self._writer.is_closing():
|
|
317
324
|
self._writer.close()
|
|
318
|
-
|
|
325
|
+
with suppress(OSError):
|
|
319
326
|
await self._writer.wait_closed()
|
|
320
|
-
except OSError:
|
|
321
|
-
pass
|
|
322
327
|
|
|
323
328
|
|
|
324
329
|
@dataclasses.dataclass
|
|
@@ -392,112 +397,143 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
392
397
|
raise RemotePairingCompletedError()
|
|
393
398
|
|
|
394
399
|
async def create_quic_listener(self, private_key: RSAPrivateKey) -> dict:
|
|
395
|
-
request = {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
+
}
|
|
401
413
|
|
|
402
414
|
response = await self._send_receive_encrypted_request(request)
|
|
403
|
-
return response[
|
|
415
|
+
return response["createListener"]
|
|
404
416
|
|
|
405
417
|
async def create_tcp_listener(self) -> dict:
|
|
406
|
-
request = {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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
|
+
}
|
|
410
429
|
response = await self._send_receive_encrypted_request(request)
|
|
411
|
-
return response[
|
|
430
|
+
return response["createListener"]
|
|
412
431
|
|
|
413
432
|
@asynccontextmanager
|
|
414
433
|
async def start_quic_tunnel(
|
|
415
|
-
|
|
416
|
-
|
|
434
|
+
self,
|
|
435
|
+
secrets_log_file: Optional[TextIO] = None,
|
|
436
|
+
max_idle_timeout: float = RemotePairingQuicTunnel.MAX_IDLE_TIMEOUT,
|
|
437
|
+
) -> AsyncGenerator[TunnelResult, None]:
|
|
417
438
|
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
|
418
439
|
parameters = await self.create_quic_listener(private_key)
|
|
419
440
|
cert = make_cert(private_key, private_key.public_key())
|
|
420
441
|
configuration = QuicConfiguration(
|
|
421
|
-
alpn_protocols=[
|
|
442
|
+
alpn_protocols=["RemotePairingTunnelProtocol"],
|
|
422
443
|
is_client=True,
|
|
423
444
|
verify_mode=VerifyMode.CERT_NONE,
|
|
424
445
|
verify_hostname=False,
|
|
425
446
|
max_datagram_frame_size=RemotePairingQuicTunnel.MAX_QUIC_DATAGRAM,
|
|
426
|
-
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(),
|
|
427
452
|
)
|
|
428
|
-
configuration.load_cert_chain(cert.public_bytes(Encoding.PEM),
|
|
429
|
-
private_key.private_bytes(Encoding.PEM, PrivateFormat.TraditionalOpenSSL,
|
|
430
|
-
NoEncryption()).decode())
|
|
431
453
|
configuration.secrets_log_file = secrets_log_file
|
|
432
454
|
|
|
433
455
|
host = self.hostname
|
|
434
|
-
port = parameters[
|
|
456
|
+
port = parameters["port"]
|
|
435
457
|
|
|
436
|
-
self.logger.debug(f
|
|
458
|
+
self.logger.debug(f"Connecting to {host}:{port}")
|
|
437
459
|
try:
|
|
438
460
|
async with aioquic_connect(
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
461
|
+
host,
|
|
462
|
+
port,
|
|
463
|
+
configuration=configuration,
|
|
464
|
+
create_protocol=RemotePairingQuicTunnel,
|
|
443
465
|
) as client:
|
|
444
|
-
self.logger.debug(
|
|
466
|
+
self.logger.debug("quic connected")
|
|
445
467
|
client = cast(RemotePairingQuicTunnel, client)
|
|
446
468
|
await client.wait_connected()
|
|
447
469
|
handshake_response = await client.request_tunnel_establish()
|
|
448
|
-
client.start_tunnel(
|
|
449
|
-
|
|
450
|
-
|
|
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
|
+
)
|
|
451
475
|
try:
|
|
452
476
|
yield TunnelResult(
|
|
453
|
-
client.tun.name,
|
|
454
|
-
|
|
477
|
+
client.tun.name,
|
|
478
|
+
handshake_response["serverAddress"],
|
|
479
|
+
handshake_response["serverRSDPort"],
|
|
480
|
+
TunnelProtocol.QUIC,
|
|
481
|
+
client,
|
|
482
|
+
)
|
|
455
483
|
finally:
|
|
456
484
|
await client.stop_tunnel()
|
|
457
|
-
except ConnectionError:
|
|
485
|
+
except ConnectionError as e:
|
|
458
486
|
raise QuicProtocolNotSupportedError(
|
|
459
|
-
|
|
487
|
+
"iOS 18.2+ removed QUIC protocol support. Use TCP instead (requires python3.13+)"
|
|
488
|
+
) from e
|
|
460
489
|
|
|
461
490
|
@asynccontextmanager
|
|
462
491
|
async def start_tcp_tunnel(self) -> AsyncGenerator[TunnelResult, None]:
|
|
463
492
|
parameters = await self.create_tcp_listener()
|
|
464
493
|
host = self.hostname
|
|
465
|
-
port = parameters[
|
|
494
|
+
port = parameters["port"]
|
|
466
495
|
sock = create_connection((host, port))
|
|
467
496
|
OSUTIL.set_keepalive(sock)
|
|
468
497
|
if sys.version_info >= (3, 13):
|
|
469
498
|
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
|
|
470
499
|
ctx.check_hostname = False
|
|
471
500
|
ctx.verify_mode = ssl.CERT_NONE
|
|
472
|
-
ctx.set_ciphers(
|
|
501
|
+
ctx.set_ciphers("PSK")
|
|
473
502
|
ctx.set_psk_client_callback(lambda hint: (None, self.encryption_key))
|
|
474
503
|
else:
|
|
475
504
|
# TODO: remove this when python3.12 becomes deprecated
|
|
476
505
|
ctx = SSLPSKContext(ssl.PROTOCOL_TLSv1_2)
|
|
477
506
|
ctx.psk = self.encryption_key
|
|
478
|
-
ctx.set_ciphers(
|
|
479
|
-
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="")
|
|
480
509
|
tunnel = RemotePairingTcpTunnel(reader, writer)
|
|
481
510
|
handshake_response = await tunnel.request_tunnel_establish()
|
|
482
511
|
|
|
483
|
-
tunnel.start_tunnel(
|
|
484
|
-
|
|
485
|
-
|
|
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
|
+
)
|
|
486
517
|
|
|
487
518
|
try:
|
|
488
519
|
yield TunnelResult(
|
|
489
|
-
tunnel.tun.name,
|
|
490
|
-
|
|
520
|
+
tunnel.tun.name,
|
|
521
|
+
handshake_response["serverAddress"],
|
|
522
|
+
handshake_response["serverRSDPort"],
|
|
523
|
+
TunnelProtocol.TCP,
|
|
524
|
+
tunnel,
|
|
525
|
+
)
|
|
491
526
|
finally:
|
|
492
527
|
await tunnel.stop_tunnel()
|
|
493
528
|
|
|
494
529
|
def save_pair_record(self) -> None:
|
|
495
530
|
self.pair_record_path.write_bytes(
|
|
496
531
|
plistlib.dumps({
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
})
|
|
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
|
+
)
|
|
501
537
|
OSUTIL.chown_to_non_sudo_if_needed(self.pair_record_path)
|
|
502
538
|
|
|
503
539
|
@property
|
|
@@ -508,17 +544,19 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
508
544
|
|
|
509
545
|
@property
|
|
510
546
|
def remote_identifier(self) -> str:
|
|
511
|
-
return self.handshake_info[
|
|
547
|
+
return self.handshake_info["peerDeviceInfo"]["identifier"]
|
|
512
548
|
|
|
513
549
|
@property
|
|
514
550
|
def remote_device_model(self) -> str:
|
|
515
|
-
return self.handshake_info[
|
|
551
|
+
return self.handshake_info["peerDeviceInfo"]["model"]
|
|
516
552
|
|
|
517
553
|
@property
|
|
518
554
|
def pair_record_path(self) -> Path:
|
|
519
555
|
pair_records_cache_directory = create_pairing_records_cache_folder()
|
|
520
|
-
return (
|
|
521
|
-
|
|
556
|
+
return (
|
|
557
|
+
pair_records_cache_directory
|
|
558
|
+
/ f"{get_remote_pairing_record_filename(self.remote_identifier)}.{PAIRING_RECORD_EXT}"
|
|
559
|
+
)
|
|
522
560
|
|
|
523
561
|
async def _pair(self) -> None:
|
|
524
562
|
pairing_consent_result = await self._request_pair_consent()
|
|
@@ -530,47 +568,49 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
530
568
|
self.save_pair_record()
|
|
531
569
|
|
|
532
570
|
async def _request_pair_consent(self) -> PairConsentResult:
|
|
533
|
-
"""
|
|
571
|
+
"""Display a Trust / Don't Trust dialog"""
|
|
534
572
|
|
|
535
573
|
tlv = PairingDataComponentTLVBuf.build([
|
|
536
|
-
{
|
|
537
|
-
{
|
|
574
|
+
{"type": PairingDataComponentType.METHOD, "data": b"\x00"},
|
|
575
|
+
{"type": PairingDataComponentType.STATE, "data": b"\x01"},
|
|
538
576
|
])
|
|
539
577
|
|
|
540
|
-
await self._send_pairing_data({
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
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")
|
|
545
585
|
response = await self._receive_plain_response()
|
|
546
|
-
response = response[
|
|
586
|
+
response = response["event"]["_0"]
|
|
547
587
|
|
|
548
588
|
pin = None
|
|
549
|
-
if
|
|
589
|
+
if "pairingRejectedWithError" in response:
|
|
550
590
|
raise PairingError(
|
|
551
|
-
response[
|
|
552
|
-
|
|
591
|
+
response["pairingRejectedWithError"]["wrappedError"]["userInfo"]["NSLocalizedDescription"]
|
|
592
|
+
)
|
|
593
|
+
elif "awaitingUserConsent" in response:
|
|
553
594
|
pairing_data = await self._receive_pairing_data()
|
|
554
595
|
else:
|
|
555
596
|
# On tvOS no consent is needed and pairing data is returned immediately.
|
|
556
|
-
pairing_data = self._decode_bytes_if_needed(response[
|
|
597
|
+
pairing_data = self._decode_bytes_if_needed(response["pairingData"]["_0"]["data"])
|
|
557
598
|
# On tvOS we need pin to setup pairing.
|
|
558
|
-
if
|
|
559
|
-
pin = input(
|
|
599
|
+
if "AppleTV" in self.remote_device_model:
|
|
600
|
+
pin = input("Enter PIN: ")
|
|
560
601
|
|
|
561
602
|
data = self.decode_tlv(PairingDataComponentTLVBuf.parse(pairing_data))
|
|
562
|
-
return PairConsentResult(
|
|
563
|
-
|
|
564
|
-
|
|
603
|
+
return PairConsentResult(
|
|
604
|
+
public_key=data[PairingDataComponentType.PUBLIC_KEY], salt=data[PairingDataComponentType.SALT], pin=pin
|
|
605
|
+
)
|
|
565
606
|
|
|
566
607
|
def _init_srp_context(self, pairing_consent_result: PairConsentResult) -> None:
|
|
567
608
|
# Receive server public and salt and process them.
|
|
568
|
-
pin = pairing_consent_result.pin or
|
|
609
|
+
pin = pairing_consent_result.pin or "000000"
|
|
569
610
|
client_session = SRPClientSession(
|
|
570
|
-
SRPContext(
|
|
571
|
-
|
|
572
|
-
client_session.process(pairing_consent_result.public_key.hex(),
|
|
573
|
-
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())
|
|
574
614
|
self.srp_context = client_session
|
|
575
615
|
self.encryption_key = binascii.unhexlify(self.srp_context.key)
|
|
576
616
|
|
|
@@ -579,17 +619,18 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
579
619
|
client_session_key_proof = binascii.unhexlify(self.srp_context.key_proof)
|
|
580
620
|
|
|
581
621
|
tlv = PairingDataComponentTLVBuf.build([
|
|
582
|
-
{
|
|
583
|
-
{
|
|
584
|
-
{
|
|
585
|
-
{
|
|
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},
|
|
586
626
|
])
|
|
587
627
|
|
|
588
628
|
response = await self._send_receive_pairing_data({
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
629
|
+
"data": tlv,
|
|
630
|
+
"kind": "setupManualPairing",
|
|
631
|
+
"sendingHost": platform.node(),
|
|
632
|
+
"startNewSession": False,
|
|
633
|
+
})
|
|
593
634
|
data = self.decode_tlv(PairingDataComponentTLVBuf.parse(response))
|
|
594
635
|
assert self.srp_context.verify_proof(data[PairingDataComponentType.PROOF].hex().encode())
|
|
595
636
|
|
|
@@ -599,8 +640,8 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
599
640
|
setup_encryption_key = HKDF(
|
|
600
641
|
algorithm=hashes.SHA512(),
|
|
601
642
|
length=32,
|
|
602
|
-
salt=b
|
|
603
|
-
info=b
|
|
643
|
+
salt=b"Pair-Setup-Encrypt-Salt",
|
|
644
|
+
info=b"Pair-Setup-Encrypt-Info",
|
|
604
645
|
).derive(self.encryption_key)
|
|
605
646
|
|
|
606
647
|
self.ed25519_private_key = Ed25519PrivateKey.generate()
|
|
@@ -610,8 +651,8 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
610
651
|
signbuf = HKDF(
|
|
611
652
|
algorithm=hashes.SHA512(),
|
|
612
653
|
length=32,
|
|
613
|
-
salt=b
|
|
614
|
-
info=b
|
|
654
|
+
salt=b"Pair-Setup-Controller-Sign-Salt",
|
|
655
|
+
info=b"Pair-Setup-Controller-Sign-Info",
|
|
615
656
|
).derive(self.encryption_key)
|
|
616
657
|
|
|
617
658
|
signbuf += self.identifier.encode()
|
|
@@ -620,41 +661,45 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
620
661
|
self.signature = self.ed25519_private_key.sign(signbuf)
|
|
621
662
|
|
|
622
663
|
device_info = dumps({
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
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(),
|
|
630
671
|
})
|
|
631
672
|
|
|
632
673
|
tlv = PairingDataComponentTLVBuf.build([
|
|
633
|
-
{
|
|
634
|
-
{
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
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},
|
|
638
681
|
])
|
|
639
682
|
|
|
640
683
|
cip = ChaCha20Poly1305(setup_encryption_key)
|
|
641
|
-
encrypted_data = cip.encrypt(b
|
|
684
|
+
encrypted_data = cip.encrypt(b"\x00\x00\x00\x00PS-Msg05", tlv, b"")
|
|
642
685
|
|
|
643
686
|
tlv = PairingDataComponentTLVBuf.build([
|
|
644
|
-
{
|
|
645
|
-
{
|
|
646
|
-
{
|
|
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"},
|
|
647
690
|
])
|
|
648
691
|
|
|
649
692
|
response = await self._send_receive_pairing_data({
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
693
|
+
"data": tlv,
|
|
694
|
+
"kind": "setupManualPairing",
|
|
695
|
+
"sendingHost": platform.node(),
|
|
696
|
+
"startNewSession": False,
|
|
697
|
+
})
|
|
654
698
|
data = self.decode_tlv(PairingDataComponentTLVBuf.parse(response))
|
|
655
699
|
|
|
656
|
-
tlv = PairingDataComponentTLVBuf.parse(
|
|
657
|
-
b
|
|
700
|
+
tlv = PairingDataComponentTLVBuf.parse(
|
|
701
|
+
cip.decrypt(b"\x00\x00\x00\x00PS-Msg06", data[PairingDataComponentType.ENCRYPTED_DATA], b"")
|
|
702
|
+
)
|
|
658
703
|
|
|
659
704
|
return tlv
|
|
660
705
|
|
|
@@ -663,7 +708,7 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
663
708
|
algorithm=hashes.SHA512(),
|
|
664
709
|
length=32,
|
|
665
710
|
salt=None,
|
|
666
|
-
info=b
|
|
711
|
+
info=b"ClientEncrypt-main",
|
|
667
712
|
).derive(self.encryption_key)
|
|
668
713
|
self.client_cip = ChaCha20Poly1305(client_key)
|
|
669
714
|
|
|
@@ -671,22 +716,23 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
671
716
|
algorithm=hashes.SHA512(),
|
|
672
717
|
length=32,
|
|
673
718
|
salt=None,
|
|
674
|
-
info=b
|
|
719
|
+
info=b"ServerEncrypt-main",
|
|
675
720
|
).derive(self.encryption_key)
|
|
676
721
|
self.server_cip = ChaCha20Poly1305(server_key)
|
|
677
722
|
|
|
678
723
|
async def _create_remote_unlock(self) -> None:
|
|
679
724
|
try:
|
|
680
|
-
response = await self._send_receive_encrypted_request({
|
|
681
|
-
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"]
|
|
682
727
|
except PyMobileDevice3Exception:
|
|
683
728
|
# tvOS does not support remote unlock.
|
|
684
|
-
self.remote_unlock_host_key =
|
|
729
|
+
self.remote_unlock_host_key = ""
|
|
685
730
|
|
|
686
731
|
async def _attempt_pair_verify(self) -> None:
|
|
687
732
|
self.handshake_info = await self._send_receive_handshake({
|
|
688
|
-
|
|
689
|
-
|
|
733
|
+
"hostOptions": {"attemptPairVerify": True},
|
|
734
|
+
"wireProtocolVersion": XpcInt64Type(self.WIRE_PROTOCOL_VERSION),
|
|
735
|
+
})
|
|
690
736
|
|
|
691
737
|
@staticmethod
|
|
692
738
|
def _decode_bytes_if_needed(data: bytes) -> bytes:
|
|
@@ -694,13 +740,17 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
694
740
|
|
|
695
741
|
async def _validate_pairing(self) -> bool:
|
|
696
742
|
pairing_data = PairingDataComponentTLVBuf.build([
|
|
697
|
-
{
|
|
698
|
-
{
|
|
699
|
-
|
|
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
|
+
},
|
|
700
748
|
])
|
|
701
|
-
response = await self._send_receive_pairing_data({
|
|
702
|
-
|
|
703
|
-
|
|
749
|
+
response = await self._send_receive_pairing_data({
|
|
750
|
+
"data": pairing_data,
|
|
751
|
+
"kind": "verifyManualPairing",
|
|
752
|
+
"startNewSession": True,
|
|
753
|
+
})
|
|
704
754
|
data = self.decode_tlv(PairingDataComponentTLVBuf.parse(response))
|
|
705
755
|
|
|
706
756
|
if PairingDataComponentType.ERROR in data:
|
|
@@ -713,8 +763,8 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
713
763
|
derived_key = HKDF(
|
|
714
764
|
algorithm=hashes.SHA512(),
|
|
715
765
|
length=32,
|
|
716
|
-
salt=b
|
|
717
|
-
info=b
|
|
766
|
+
salt=b"Pair-Verify-Encrypt-Salt",
|
|
767
|
+
info=b"Pair-Verify-Encrypt-Info",
|
|
718
768
|
).derive(self.encryption_key)
|
|
719
769
|
cip = ChaCha20Poly1305(derived_key)
|
|
720
770
|
|
|
@@ -723,31 +773,36 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
723
773
|
# do so. instead, we verify using the next stage
|
|
724
774
|
|
|
725
775
|
if self.pair_record is None:
|
|
726
|
-
private_key = Ed25519PrivateKey.from_private_bytes(b
|
|
776
|
+
private_key = Ed25519PrivateKey.from_private_bytes(b"\x00" * 0x20)
|
|
727
777
|
else:
|
|
728
|
-
private_key = Ed25519PrivateKey.from_private_bytes(self.pair_record[
|
|
778
|
+
private_key = Ed25519PrivateKey.from_private_bytes(self.pair_record["private_key"])
|
|
729
779
|
|
|
730
|
-
signbuf = b
|
|
780
|
+
signbuf = b""
|
|
731
781
|
signbuf += self.x25519_private_key.public_key().public_bytes_raw()
|
|
732
782
|
signbuf += self.identifier.encode()
|
|
733
783
|
signbuf += peer_public_key.public_bytes_raw()
|
|
734
784
|
|
|
735
785
|
signature = private_key.sign(signbuf)
|
|
736
786
|
|
|
737
|
-
encrypted_data = cip.encrypt(
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
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
|
+
)
|
|
741
795
|
|
|
742
796
|
pairing_data = PairingDataComponentTLVBuf.build([
|
|
743
|
-
{
|
|
744
|
-
{
|
|
797
|
+
{"type": PairingDataComponentType.STATE, "data": b"\x03"},
|
|
798
|
+
{"type": PairingDataComponentType.ENCRYPTED_DATA, "data": encrypted_data},
|
|
745
799
|
])
|
|
746
800
|
|
|
747
801
|
response = await self._send_receive_pairing_data({
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
802
|
+
"data": pairing_data,
|
|
803
|
+
"kind": "verifyManualPairing",
|
|
804
|
+
"startNewSession": False,
|
|
805
|
+
})
|
|
751
806
|
data = self.decode_tlv(PairingDataComponentTLVBuf.parse(response))
|
|
752
807
|
|
|
753
808
|
if PairingDataComponentType.ERROR in data:
|
|
@@ -757,66 +812,68 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
757
812
|
return True
|
|
758
813
|
|
|
759
814
|
async def _send_pair_verify_failed(self) -> None:
|
|
760
|
-
await self._send_plain_request({
|
|
815
|
+
await self._send_plain_request({"event": {"_0": {"pairVerifyFailed": {}}}})
|
|
761
816
|
|
|
762
817
|
async def _send_receive_encrypted_request(self, request: dict) -> dict:
|
|
763
|
-
nonce = Int64ul.build(self._encrypted_sequence_number) + b
|
|
764
|
-
encrypted_data = self.client_cip.encrypt(
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
'originatedBy': 'host',
|
|
772
|
-
'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
|
+
})
|
|
773
826
|
self._encrypted_sequence_number += 1
|
|
774
827
|
|
|
775
|
-
encrypted_data = self._decode_bytes_if_needed(response[
|
|
828
|
+
encrypted_data = self._decode_bytes_if_needed(response["message"]["streamEncrypted"]["_0"])
|
|
776
829
|
plaintext = self.server_cip.decrypt(nonce, encrypted_data, None)
|
|
777
|
-
response = json.loads(plaintext)[
|
|
830
|
+
response = json.loads(plaintext)["response"]["_1"]
|
|
778
831
|
|
|
779
|
-
if
|
|
780
|
-
raise PyMobileDevice3Exception(response[
|
|
832
|
+
if "errorExtended" in response:
|
|
833
|
+
raise PyMobileDevice3Exception(response["errorExtended"]["_0"]["userInfo"]["NSLocalizedDescription"])
|
|
781
834
|
|
|
782
835
|
return response
|
|
783
836
|
|
|
784
837
|
async def _send_receive_handshake(self, handshake_data: dict) -> dict:
|
|
785
|
-
response = await self._send_receive_plain_request({
|
|
786
|
-
return response[
|
|
838
|
+
response = await self._send_receive_plain_request({"request": {"_0": {"handshake": {"_0": handshake_data}}}})
|
|
839
|
+
return response["response"]["_1"]["handshake"]["_0"]
|
|
787
840
|
|
|
788
841
|
async def _send_receive_pairing_data(self, pairing_data: dict) -> bytes:
|
|
789
842
|
await self._send_pairing_data(pairing_data)
|
|
790
843
|
return await self._receive_pairing_data()
|
|
791
844
|
|
|
792
845
|
async def _send_pairing_data(self, pairing_data: dict) -> None:
|
|
793
|
-
await self._send_plain_request({
|
|
846
|
+
await self._send_plain_request({"event": {"_0": {"pairingData": {"_0": pairing_data}}}})
|
|
794
847
|
|
|
795
848
|
async def _receive_pairing_data(self) -> bytes:
|
|
796
849
|
response = await self._receive_plain_response()
|
|
797
|
-
response = response[
|
|
798
|
-
if
|
|
799
|
-
return self._decode_bytes_if_needed(response[
|
|
800
|
-
if
|
|
801
|
-
raise UserDeniedPairingError(
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
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}")
|
|
806
861
|
|
|
807
862
|
async def _send_receive_plain_request(self, plain_request: dict):
|
|
808
863
|
await self._send_plain_request(plain_request)
|
|
809
864
|
return await self._receive_plain_response()
|
|
810
865
|
|
|
811
866
|
async def _send_plain_request(self, plain_request: dict) -> None:
|
|
812
|
-
await self.send_request({
|
|
813
|
-
|
|
814
|
-
|
|
867
|
+
await self.send_request({
|
|
868
|
+
"message": {"plain": {"_0": plain_request}},
|
|
869
|
+
"originatedBy": "host",
|
|
870
|
+
"sequenceNumber": XpcUInt64Type(self._sequence_number),
|
|
871
|
+
})
|
|
815
872
|
self._sequence_number += 1
|
|
816
873
|
|
|
817
874
|
async def _receive_plain_response(self) -> dict:
|
|
818
875
|
response = await self.receive_response()
|
|
819
|
-
return response[
|
|
876
|
+
return response["message"]["plain"]["_0"]
|
|
820
877
|
|
|
821
878
|
@staticmethod
|
|
822
879
|
def decode_tlv(tlv_list: list[Container]) -> dict:
|
|
@@ -828,7 +885,7 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
828
885
|
result[tlv.type] = tlv.data
|
|
829
886
|
return result
|
|
830
887
|
|
|
831
|
-
async def __aenter__(self) ->
|
|
888
|
+
async def __aenter__(self) -> "CoreDeviceTunnelService":
|
|
832
889
|
return self
|
|
833
890
|
|
|
834
891
|
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
@@ -836,7 +893,7 @@ class RemotePairingProtocol(StartTcpTunnel):
|
|
|
836
893
|
|
|
837
894
|
|
|
838
895
|
class CoreDeviceTunnelService(RemotePairingProtocol, RemoteService):
|
|
839
|
-
SERVICE_NAME =
|
|
896
|
+
SERVICE_NAME = "com.apple.internal.dt.coredevice.untrusted.tunnelservice"
|
|
840
897
|
|
|
841
898
|
def __init__(self, rsd: RemoteServiceDiscoveryService):
|
|
842
899
|
RemoteService.__init__(self, rsd, self.SERVICE_NAME)
|
|
@@ -848,12 +905,12 @@ class CoreDeviceTunnelService(RemotePairingProtocol, RemoteService):
|
|
|
848
905
|
await RemoteService.connect(self)
|
|
849
906
|
try:
|
|
850
907
|
response = await self.service.receive_response()
|
|
851
|
-
self.version = response[
|
|
908
|
+
self.version = response["ServiceVersion"]
|
|
852
909
|
|
|
853
910
|
# Perform pairing if necessary and start a trusted RemoteXPC connection
|
|
854
911
|
await RemotePairingProtocol.connect(self, autopair=autopair)
|
|
855
912
|
self.hostname = self.service.address[0]
|
|
856
|
-
except Exception:
|
|
913
|
+
except Exception:
|
|
857
914
|
await self.service.close()
|
|
858
915
|
raise
|
|
859
916
|
|
|
@@ -863,11 +920,13 @@ class CoreDeviceTunnelService(RemotePairingProtocol, RemoteService):
|
|
|
863
920
|
|
|
864
921
|
async def receive_response(self) -> dict:
|
|
865
922
|
response = await self.service.receive_response()
|
|
866
|
-
return response[
|
|
923
|
+
return response["value"]
|
|
867
924
|
|
|
868
925
|
async def send_request(self, data: dict) -> None:
|
|
869
926
|
return await self.service.send_request({
|
|
870
|
-
|
|
927
|
+
"mangledTypeName": "RemotePairing.ControlChannelMessageEnvelope",
|
|
928
|
+
"value": data,
|
|
929
|
+
})
|
|
871
930
|
|
|
872
931
|
|
|
873
932
|
class RemotePairingTunnelService(RemotePairingProtocol):
|
|
@@ -892,7 +951,7 @@ class RemotePairingTunnelService(RemotePairingProtocol):
|
|
|
892
951
|
if not await self._validate_pairing():
|
|
893
952
|
raise ConnectionAbortedError()
|
|
894
953
|
self._init_client_server_main_encryption_keys()
|
|
895
|
-
except
|
|
954
|
+
except Exception:
|
|
896
955
|
await self.close()
|
|
897
956
|
raise
|
|
898
957
|
|
|
@@ -900,21 +959,20 @@ class RemotePairingTunnelService(RemotePairingProtocol):
|
|
|
900
959
|
if self._writer is None:
|
|
901
960
|
return
|
|
902
961
|
self._writer.close()
|
|
903
|
-
|
|
962
|
+
with suppress(ssl.SSLError):
|
|
904
963
|
await self._writer.wait_closed()
|
|
905
|
-
except ssl.SSLError:
|
|
906
|
-
pass
|
|
907
964
|
self._writer = None
|
|
908
965
|
self._reader = None
|
|
909
966
|
|
|
910
967
|
async def receive_response(self) -> dict:
|
|
911
968
|
await self._reader.readexactly(len(REPAIRING_PACKET_MAGIC))
|
|
912
|
-
size = struct.unpack(
|
|
969
|
+
size = struct.unpack(">H", await self._reader.readexactly(2))[0]
|
|
913
970
|
return json.loads(await self._reader.readexactly(size))
|
|
914
971
|
|
|
915
972
|
async def send_request(self, data: dict) -> None:
|
|
916
973
|
self._writer.write(
|
|
917
|
-
RPPairingPacket.build({
|
|
974
|
+
RPPairingPacket.build({"body": json.dumps(data, default=self._default_json_encoder).encode()})
|
|
975
|
+
)
|
|
918
976
|
await self._writer.drain()
|
|
919
977
|
|
|
920
978
|
@staticmethod
|
|
@@ -928,8 +986,9 @@ class RemotePairingTunnelService(RemotePairingProtocol):
|
|
|
928
986
|
return base64.b64decode(data)
|
|
929
987
|
|
|
930
988
|
def __repr__(self) -> str:
|
|
931
|
-
return (
|
|
932
|
-
|
|
989
|
+
return (
|
|
990
|
+
f"<{self.__class__.__name__} IDENTIFIER:{self.remote_identifier} HOSTNAME:{self.hostname} PORT:{self.port}>"
|
|
991
|
+
)
|
|
933
992
|
|
|
934
993
|
|
|
935
994
|
class RemotePairingManualPairingService(RemotePairingTunnelService):
|
|
@@ -940,10 +999,10 @@ class RemotePairingManualPairingService(RemotePairingTunnelService):
|
|
|
940
999
|
|
|
941
1000
|
|
|
942
1001
|
class CoreDeviceTunnelProxy(StartTcpTunnel):
|
|
943
|
-
SERVICE_NAME =
|
|
1002
|
+
SERVICE_NAME = "com.apple.internal.devicecompute.CoreDeviceProxy"
|
|
944
1003
|
|
|
945
1004
|
@classmethod
|
|
946
|
-
async def create(cls, lockdown: LockdownServiceProvider) ->
|
|
1005
|
+
async def create(cls, lockdown: LockdownServiceProvider) -> "CoreDeviceTunnelProxy":
|
|
947
1006
|
return cls(await lockdown.aio_start_lockdown_service(cls.SERVICE_NAME), lockdown.udid)
|
|
948
1007
|
|
|
949
1008
|
def __init__(self, service: ServiceConnection, remote_identifier: str) -> None:
|
|
@@ -955,17 +1014,23 @@ class CoreDeviceTunnelProxy(StartTcpTunnel):
|
|
|
955
1014
|
return self._remote_identifier
|
|
956
1015
|
|
|
957
1016
|
@asynccontextmanager
|
|
958
|
-
async def start_tcp_tunnel(self) -> AsyncGenerator[
|
|
959
|
-
assert self._service is not None,
|
|
1017
|
+
async def start_tcp_tunnel(self) -> AsyncGenerator["TunnelResult", None]:
|
|
1018
|
+
assert self._service is not None, "service must be connected first"
|
|
960
1019
|
tunnel = RemotePairingTcpTunnel(self._service.reader, self._service.writer)
|
|
961
1020
|
handshake_response = await tunnel.request_tunnel_establish()
|
|
962
|
-
tunnel.start_tunnel(
|
|
963
|
-
|
|
964
|
-
|
|
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
|
+
)
|
|
965
1026
|
try:
|
|
966
1027
|
yield TunnelResult(
|
|
967
|
-
tunnel.tun.name,
|
|
968
|
-
|
|
1028
|
+
tunnel.tun.name,
|
|
1029
|
+
handshake_response["serverAddress"],
|
|
1030
|
+
handshake_response["serverRSDPort"],
|
|
1031
|
+
TunnelProtocol.TCP,
|
|
1032
|
+
tunnel,
|
|
1033
|
+
)
|
|
969
1034
|
finally:
|
|
970
1035
|
await tunnel.stop_tunnel()
|
|
971
1036
|
|
|
@@ -975,7 +1040,8 @@ class CoreDeviceTunnelProxy(StartTcpTunnel):
|
|
|
975
1040
|
|
|
976
1041
|
|
|
977
1042
|
async def create_core_device_tunnel_service_using_rsd(
|
|
978
|
-
|
|
1043
|
+
rsd: RemoteServiceDiscoveryService, autopair: bool = True
|
|
1044
|
+
) -> CoreDeviceTunnelService:
|
|
979
1045
|
service = CoreDeviceTunnelService(rsd)
|
|
980
1046
|
try:
|
|
981
1047
|
await service.connect(autopair=autopair)
|
|
@@ -984,21 +1050,23 @@ async def create_core_device_tunnel_service_using_rsd(
|
|
|
984
1050
|
await service.close()
|
|
985
1051
|
service = CoreDeviceTunnelService(rsd)
|
|
986
1052
|
await service.connect(autopair=autopair)
|
|
987
|
-
except Exception:
|
|
1053
|
+
except Exception:
|
|
988
1054
|
await service.close()
|
|
989
1055
|
raise
|
|
990
1056
|
return service
|
|
991
1057
|
|
|
992
1058
|
|
|
993
1059
|
async def create_core_device_tunnel_service_using_remotepairing(
|
|
994
|
-
|
|
1060
|
+
remote_identifier: str, hostname: str, port: int, autopair: bool = True
|
|
1061
|
+
) -> RemotePairingTunnelService:
|
|
995
1062
|
service = RemotePairingTunnelService(remote_identifier, hostname, port)
|
|
996
1063
|
await service.connect(autopair=autopair)
|
|
997
1064
|
return service
|
|
998
1065
|
|
|
999
1066
|
|
|
1000
1067
|
async def create_core_device_service_using_remotepairing_manual_pairing(
|
|
1001
|
-
|
|
1068
|
+
remote_identifier: str, hostname: str, port: int, autopair: bool = True
|
|
1069
|
+
) -> RemotePairingTunnelService:
|
|
1002
1070
|
service = RemotePairingManualPairingService(remote_identifier, hostname, port)
|
|
1003
1071
|
await service.connect(autopair=autopair)
|
|
1004
1072
|
return service
|
|
@@ -1006,14 +1074,16 @@ async def create_core_device_service_using_remotepairing_manual_pairing(
|
|
|
1006
1074
|
|
|
1007
1075
|
@asynccontextmanager
|
|
1008
1076
|
async def start_tunnel_over_remotepairing(
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
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]:
|
|
1013
1082
|
async with remote_pairing:
|
|
1014
1083
|
if protocol == TunnelProtocol.QUIC:
|
|
1015
1084
|
async with remote_pairing.start_quic_tunnel(
|
|
1016
|
-
|
|
1085
|
+
secrets_log_file=secrets, max_idle_timeout=max_idle_timeout
|
|
1086
|
+
) as tunnel_result:
|
|
1017
1087
|
yield tunnel_result
|
|
1018
1088
|
elif protocol == TunnelProtocol.TCP:
|
|
1019
1089
|
async with remote_pairing.start_tcp_tunnel() as tunnel_result:
|
|
@@ -1022,15 +1092,17 @@ async def start_tunnel_over_remotepairing(
|
|
|
1022
1092
|
|
|
1023
1093
|
@asynccontextmanager
|
|
1024
1094
|
async def start_tunnel_over_core_device(
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
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]:
|
|
1029
1100
|
stop_remoted_if_required()
|
|
1030
1101
|
async with service_provider:
|
|
1031
1102
|
if protocol == TunnelProtocol.QUIC:
|
|
1032
1103
|
async with service_provider.start_quic_tunnel(
|
|
1033
|
-
|
|
1104
|
+
secrets_log_file=secrets, max_idle_timeout=max_idle_timeout
|
|
1105
|
+
) as tunnel_result:
|
|
1034
1106
|
resume_remoted_if_required()
|
|
1035
1107
|
yield tunnel_result
|
|
1036
1108
|
elif protocol == TunnelProtocol.TCP:
|
|
@@ -1041,48 +1113,53 @@ async def start_tunnel_over_core_device(
|
|
|
1041
1113
|
|
|
1042
1114
|
@asynccontextmanager
|
|
1043
1115
|
async def start_tunnel(
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
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]:
|
|
1047
1121
|
if isinstance(protocol_handler, CoreDeviceTunnelService):
|
|
1048
1122
|
async with start_tunnel_over_core_device(
|
|
1049
|
-
|
|
1123
|
+
protocol_handler, secrets=secrets, max_idle_timeout=max_idle_timeout, protocol=protocol
|
|
1124
|
+
) as service:
|
|
1050
1125
|
yield service
|
|
1051
1126
|
elif isinstance(protocol_handler, RemotePairingTunnelService):
|
|
1052
1127
|
async with start_tunnel_over_remotepairing(
|
|
1053
|
-
|
|
1128
|
+
protocol_handler, secrets=secrets, max_idle_timeout=max_idle_timeout, protocol=protocol
|
|
1129
|
+
) as service:
|
|
1054
1130
|
yield service
|
|
1055
1131
|
elif isinstance(protocol_handler, CoreDeviceTunnelProxy):
|
|
1056
1132
|
if protocol != TunnelProtocol.TCP:
|
|
1057
|
-
raise ValueError(
|
|
1133
|
+
raise ValueError("CoreDeviceTunnelProxy protocol can only be TCP")
|
|
1058
1134
|
async with protocol_handler.start_tcp_tunnel() as service:
|
|
1059
1135
|
yield service
|
|
1060
1136
|
else:
|
|
1061
|
-
raise
|
|
1137
|
+
raise TypeError(f"Bad value for protocol_handler: {protocol_handler}")
|
|
1062
1138
|
|
|
1063
1139
|
|
|
1064
1140
|
async def get_core_device_tunnel_services(
|
|
1065
|
-
|
|
1066
|
-
|
|
1141
|
+
bonjour_timeout: float = DEFAULT_BONJOUR_TIMEOUT, udid: Optional[str] = None
|
|
1142
|
+
) -> list[CoreDeviceTunnelService]:
|
|
1067
1143
|
result = []
|
|
1068
1144
|
for rsd in await get_rsds(bonjour_timeout=bonjour_timeout, udid=udid):
|
|
1069
|
-
if udid is None and (
|
|
1070
|
-
|
|
1071
|
-
|
|
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")
|
|
1072
1149
|
await rsd.close()
|
|
1073
1150
|
continue
|
|
1074
1151
|
try:
|
|
1075
1152
|
result.append(await create_core_device_tunnel_service_using_rsd(rsd))
|
|
1076
|
-
except Exception
|
|
1077
|
-
logger.
|
|
1153
|
+
except Exception:
|
|
1154
|
+
logger.exception(f"Failed to start service: {rsd}")
|
|
1078
1155
|
await rsd.close()
|
|
1079
1156
|
raise
|
|
1080
1157
|
return result
|
|
1081
1158
|
|
|
1082
1159
|
|
|
1083
1160
|
async def get_remote_pairing_tunnel_services(
|
|
1084
|
-
|
|
1085
|
-
|
|
1161
|
+
bonjour_timeout: float = DEFAULT_BONJOUR_TIMEOUT, udid: Optional[str] = None
|
|
1162
|
+
) -> list[RemotePairingTunnelService]:
|
|
1086
1163
|
result = []
|
|
1087
1164
|
for answer in await browse_remotepairing(timeout=bonjour_timeout):
|
|
1088
1165
|
for address in answer.addresses:
|
|
@@ -1092,7 +1169,8 @@ async def get_remote_pairing_tunnel_services(
|
|
|
1092
1169
|
conn = None
|
|
1093
1170
|
try:
|
|
1094
1171
|
conn = await create_core_device_tunnel_service_using_remotepairing(
|
|
1095
|
-
identifier, address.full_ip, answer.port
|
|
1172
|
+
identifier, address.full_ip, answer.port
|
|
1173
|
+
)
|
|
1096
1174
|
result.append(conn)
|
|
1097
1175
|
break
|
|
1098
1176
|
except ConnectionAbortedError:
|