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