pymobiledevice3 4.25.1__py3-none-any.whl → 4.26.1__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.
- pymobiledevice3/_version.py +2 -2
- pymobiledevice3/bonjour.py +4 -2
- pymobiledevice3/ca.py +244 -15
- pymobiledevice3/cli/bonjour.py +4 -9
- pymobiledevice3/cli/cli_common.py +29 -2
- pymobiledevice3/lockdown.py +26 -20
- {pymobiledevice3-4.25.1.dist-info → pymobiledevice3-4.26.1.dist-info}/METADATA +1 -1
- {pymobiledevice3-4.25.1.dist-info → pymobiledevice3-4.26.1.dist-info}/RECORD +12 -12
- {pymobiledevice3-4.25.1.dist-info → pymobiledevice3-4.26.1.dist-info}/WHEEL +0 -0
- {pymobiledevice3-4.25.1.dist-info → pymobiledevice3-4.26.1.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-4.25.1.dist-info → pymobiledevice3-4.26.1.dist-info}/licenses/LICENSE +0 -0
- {pymobiledevice3-4.25.1.dist-info → pymobiledevice3-4.26.1.dist-info}/top_level.txt +0 -0
pymobiledevice3/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '4.
|
|
32
|
-
__version_tuple__ = version_tuple = (4,
|
|
31
|
+
__version__ = version = '4.26.1'
|
|
32
|
+
__version_tuple__ = version_tuple = (4, 26, 1)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
pymobiledevice3/bonjour.py
CHANGED
|
@@ -131,8 +131,10 @@ async def browse_remoted(timeout: float = DEFAULT_BONJOUR_TIMEOUT) -> list[Bonjo
|
|
|
131
131
|
return await browse_ipv6(REMOTED_SERVICE_NAMES, timeout=timeout)
|
|
132
132
|
|
|
133
133
|
|
|
134
|
-
async def browse_mobdev2(timeout: float = DEFAULT_BONJOUR_TIMEOUT) -> list[BonjourAnswer]:
|
|
135
|
-
|
|
134
|
+
async def browse_mobdev2(timeout: float = DEFAULT_BONJOUR_TIMEOUT, ips: Optional[list[str]] = None) -> list[BonjourAnswer]:
|
|
135
|
+
if ips is None:
|
|
136
|
+
ips = get_ipv4_addresses() + OSUTILS.get_ipv6_ips()
|
|
137
|
+
return await browse(MOBDEV2_SERVICE_NAMES, ips, timeout=timeout)
|
|
136
138
|
|
|
137
139
|
|
|
138
140
|
async def browse_remotepairing(timeout: float = DEFAULT_BONJOUR_TIMEOUT) -> list[BonjourAnswer]:
|
pymobiledevice3/ca.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
from datetime import datetime, timedelta
|
|
1
|
+
from datetime import datetime, timedelta, timezone
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
from typing import Optional
|
|
3
|
+
from typing import Optional, Union
|
|
4
4
|
|
|
5
5
|
from cryptography import x509
|
|
6
6
|
from cryptography.hazmat.primitives import hashes, serialization
|
|
@@ -10,8 +10,235 @@ from cryptography.hazmat.primitives.serialization import Encoding, NoEncryption,
|
|
|
10
10
|
from cryptography.x509 import Certificate
|
|
11
11
|
from cryptography.x509.oid import NameOID
|
|
12
12
|
|
|
13
|
+
_SERIAL = 1
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def select_hash_algorithm(device_version: Union[tuple[int, int, int], str, None]) -> hashes.HashAlgorithm:
|
|
17
|
+
"""
|
|
18
|
+
Choose hash algorithm to match libimobiledevice (idevicepair) logic.
|
|
19
|
+
|
|
20
|
+
:param device_version: Device version tuple (major, minor, patch) or "a.b.c" string.
|
|
21
|
+
If None, defaults to SHA-256 (modern).
|
|
22
|
+
:returns: SHA-1 if version < 4.0.0, else SHA-256.
|
|
23
|
+
"""
|
|
24
|
+
if device_version is None:
|
|
25
|
+
return hashes.SHA256()
|
|
26
|
+
if isinstance(device_version, str):
|
|
27
|
+
parts = tuple(int(x) for x in device_version.split("."))
|
|
28
|
+
else:
|
|
29
|
+
parts = device_version
|
|
30
|
+
return hashes.SHA1() if parts < (4, 0, 0) else hashes.SHA256()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_validity_bounds(years: int = 10) -> tuple[datetime, datetime]:
|
|
34
|
+
"""
|
|
35
|
+
Compute notBefore / notAfter validity window.
|
|
36
|
+
|
|
37
|
+
:param years: Number of years for certificate validity.
|
|
38
|
+
:returns: (not_before, not_after) in UTC.
|
|
39
|
+
"""
|
|
40
|
+
now = datetime.now(timezone.utc)
|
|
41
|
+
return now - timedelta(minutes=1), now + timedelta(days=365 * years)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def serialize_cert_pem(cert: Certificate) -> bytes:
|
|
45
|
+
"""
|
|
46
|
+
Serialize an X.509 certificate in PEM format.
|
|
47
|
+
|
|
48
|
+
:param cert: Certificate object.
|
|
49
|
+
:returns: PEM-encoded certificate bytes.
|
|
50
|
+
"""
|
|
51
|
+
return cert.public_bytes(Encoding.PEM)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def serialize_private_key_pkcs8_pem(key: RSAPrivateKey) -> bytes:
|
|
55
|
+
"""
|
|
56
|
+
Serialize a private key in PKCS#8 PEM format (like OpenSSL's PEM_write_bio_PrivateKey).
|
|
57
|
+
|
|
58
|
+
:param key: RSA private key.
|
|
59
|
+
:returns: PEM-encoded PKCS#8 key bytes (unencrypted).
|
|
60
|
+
"""
|
|
61
|
+
return key.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption())
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# =======================================
|
|
65
|
+
# Certificate builders (empty DN, v3, KU)
|
|
66
|
+
# =======================================
|
|
67
|
+
|
|
68
|
+
def build_root_certificate(root_key: RSAPrivateKey, alg: hashes.HashAlgorithm) -> Certificate:
|
|
69
|
+
"""
|
|
70
|
+
Build a self-signed root (CA) certificate:
|
|
71
|
+
- Empty subject/issuer (x509.Name([]))
|
|
72
|
+
- Serial = 1
|
|
73
|
+
- X.509 v3 with BasicConstraints CA:TRUE (critical)
|
|
74
|
+
- Signed with root_key using the chosen hash
|
|
75
|
+
|
|
76
|
+
:param root_key: RSA private key for the root CA.
|
|
77
|
+
:param alg: Hash algorithm (SHA-1 or SHA-256).
|
|
78
|
+
:returns: Root CA certificate.
|
|
79
|
+
"""
|
|
80
|
+
not_before, not_after = get_validity_bounds()
|
|
81
|
+
empty = x509.Name([])
|
|
82
|
+
builder = (
|
|
83
|
+
x509.CertificateBuilder()
|
|
84
|
+
.subject_name(empty)
|
|
85
|
+
.issuer_name(empty)
|
|
86
|
+
.public_key(root_key.public_key())
|
|
87
|
+
.serial_number(_SERIAL)
|
|
88
|
+
.not_valid_before(not_before)
|
|
89
|
+
.not_valid_after(not_after)
|
|
90
|
+
.add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True)
|
|
91
|
+
)
|
|
92
|
+
return builder.sign(root_key, alg)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def build_host_certificate(
|
|
96
|
+
host_key: RSAPrivateKey,
|
|
97
|
+
root_cert: Certificate,
|
|
98
|
+
root_key: RSAPrivateKey,
|
|
99
|
+
alg: hashes.HashAlgorithm,
|
|
100
|
+
) -> Certificate:
|
|
101
|
+
"""
|
|
102
|
+
Build the host (leaf) certificate signed by the root:
|
|
103
|
+
- Empty subject
|
|
104
|
+
- Issuer = root's (empty) subject
|
|
105
|
+
- Serial = 1
|
|
106
|
+
- BasicConstraints CA:FALSE (critical)
|
|
107
|
+
- KeyUsage: digitalSignature, keyEncipherment (critical)
|
|
108
|
+
- Signed with root_key
|
|
109
|
+
|
|
110
|
+
:param host_key: Host RSA private key (leaf).
|
|
111
|
+
:param root_cert: Root CA certificate.
|
|
112
|
+
:param root_key: Root RSA private key.
|
|
113
|
+
:param alg: Hash algorithm (SHA-1 or SHA-256).
|
|
114
|
+
:returns: Host certificate (leaf).
|
|
115
|
+
"""
|
|
116
|
+
not_before, not_after = get_validity_bounds()
|
|
117
|
+
builder = (
|
|
118
|
+
x509.CertificateBuilder()
|
|
119
|
+
.subject_name(x509.Name([]))
|
|
120
|
+
.issuer_name(root_cert.subject) # empty
|
|
121
|
+
.public_key(host_key.public_key())
|
|
122
|
+
.serial_number(_SERIAL)
|
|
123
|
+
.not_valid_before(not_before)
|
|
124
|
+
.not_valid_after(not_after)
|
|
125
|
+
.add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=True)
|
|
126
|
+
.add_extension(
|
|
127
|
+
x509.KeyUsage(
|
|
128
|
+
digital_signature=True,
|
|
129
|
+
key_encipherment=True,
|
|
130
|
+
key_cert_sign=False, crl_sign=False,
|
|
131
|
+
content_commitment=False, data_encipherment=False,
|
|
132
|
+
key_agreement=False, encipher_only=False, decipher_only=False,
|
|
133
|
+
),
|
|
134
|
+
critical=True,
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
return builder.sign(root_key, alg)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def build_device_certificate(
|
|
141
|
+
device_public_key: RSAPublicKey,
|
|
142
|
+
root_cert: Certificate,
|
|
143
|
+
root_key: RSAPrivateKey,
|
|
144
|
+
alg: hashes.HashAlgorithm,
|
|
145
|
+
) -> Certificate:
|
|
146
|
+
"""
|
|
147
|
+
Build the device certificate (leaf) signed by the root:
|
|
148
|
+
- Empty subject
|
|
149
|
+
- Issuer = root's (empty) subject
|
|
150
|
+
- Serial = 1
|
|
151
|
+
- BasicConstraints CA:FALSE (critical)
|
|
152
|
+
- KeyUsage: digitalSignature, keyEncipherment (critical)
|
|
153
|
+
- SubjectKeyIdentifier = hash
|
|
154
|
+
- Signed with root_key
|
|
155
|
+
|
|
156
|
+
:param device_public_key: Device's RSA public key (as advertised by lockdown).
|
|
157
|
+
:param root_cert: Root CA certificate.
|
|
158
|
+
:param root_key: Root RSA private key.
|
|
159
|
+
:param alg: Hash algorithm (SHA-1 or SHA-256).
|
|
160
|
+
:returns: Device certificate (leaf).
|
|
161
|
+
"""
|
|
162
|
+
not_before, not_after = get_validity_bounds()
|
|
163
|
+
builder = (
|
|
164
|
+
x509.CertificateBuilder()
|
|
165
|
+
.subject_name(x509.Name([]))
|
|
166
|
+
.issuer_name(root_cert.subject) # empty
|
|
167
|
+
.public_key(device_public_key)
|
|
168
|
+
.serial_number(_SERIAL)
|
|
169
|
+
.not_valid_before(not_before)
|
|
170
|
+
.not_valid_after(not_after)
|
|
171
|
+
.add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=True)
|
|
172
|
+
.add_extension(
|
|
173
|
+
x509.KeyUsage(
|
|
174
|
+
digital_signature=True,
|
|
175
|
+
key_encipherment=True,
|
|
176
|
+
key_cert_sign=False, crl_sign=False,
|
|
177
|
+
content_commitment=False, data_encipherment=False,
|
|
178
|
+
key_agreement=False, encipher_only=False, decipher_only=False,
|
|
179
|
+
),
|
|
180
|
+
critical=True,
|
|
181
|
+
)
|
|
182
|
+
.add_extension(x509.SubjectKeyIdentifier.from_public_key(device_public_key), critical=False)
|
|
183
|
+
)
|
|
184
|
+
return builder.sign(root_key, alg)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# ==========================================
|
|
188
|
+
# Public API for your pairing flow (renamed)
|
|
189
|
+
# ==========================================
|
|
190
|
+
|
|
191
|
+
def generate_pairing_cert_chain(
|
|
192
|
+
device_public_key_pem: bytes,
|
|
193
|
+
private_key: Optional[RSAPrivateKey] = None,
|
|
194
|
+
device_version: Union[tuple[int, int, int], str, None] = (4, 0, 0),
|
|
195
|
+
) -> tuple[bytes, bytes, bytes, bytes, bytes]:
|
|
196
|
+
"""
|
|
197
|
+
Generate a root→host certificate chain and a device certificate that mirror the
|
|
198
|
+
libimobiledevice C behavior (empty DN, serial=1, BC/KU/SKI, SHA1 flip for < 4.0).
|
|
199
|
+
|
|
200
|
+
:param device_public_key_pem: Device RSA public key in PEM ("RSA PUBLIC KEY") format.
|
|
201
|
+
:param private_key: Optional host RSA private key to reuse; if None, a new one is generated.
|
|
202
|
+
:param device_version: Version to select hash (tuple or "a.b.c"). < 4.0.0 => SHA-1; else SHA-256.
|
|
203
|
+
:returns: (host_cert_pem, host_key_pem, device_cert_pem, root_cert_pem, root_key_pem)
|
|
204
|
+
"""
|
|
205
|
+
alg = select_hash_algorithm(device_version)
|
|
206
|
+
|
|
207
|
+
# Root CA (self-signed)
|
|
208
|
+
root_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
|
209
|
+
root_cert = build_root_certificate(root_key, alg)
|
|
210
|
+
|
|
211
|
+
# Host leaf (reuse provided key if given)
|
|
212
|
+
host_key = private_key or rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
|
213
|
+
host_cert = build_host_certificate(host_key, root_cert, root_key, alg)
|
|
214
|
+
|
|
215
|
+
# Device leaf (public key provided by the device)
|
|
216
|
+
dev_pub = load_pem_public_key(device_public_key_pem)
|
|
217
|
+
if not isinstance(dev_pub, RSAPublicKey):
|
|
218
|
+
raise ValueError("device_public_key_pem must be an RSA PUBLIC KEY in PEM format")
|
|
219
|
+
device_cert = build_device_certificate(dev_pub, root_cert, root_key, alg)
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
serialize_cert_pem(host_cert),
|
|
223
|
+
serialize_private_key_pkcs8_pem(host_key),
|
|
224
|
+
serialize_cert_pem(device_cert),
|
|
225
|
+
serialize_cert_pem(root_cert),
|
|
226
|
+
serialize_private_key_pkcs8_pem(root_key),
|
|
227
|
+
)
|
|
228
|
+
|
|
13
229
|
|
|
14
230
|
def make_cert(key: RSAPrivateKey, public_key: RSAPublicKey, common_name: Optional[str] = None) -> Certificate:
|
|
231
|
+
"""
|
|
232
|
+
Create a simple self-signed certificate for the provided key.
|
|
233
|
+
|
|
234
|
+
NOTE: This is not suitable for pairing (it sets subject/issuer and lacks C-style fields).
|
|
235
|
+
It is preserved as-is for your keybag usage.
|
|
236
|
+
|
|
237
|
+
:param key: RSA private key for signing.
|
|
238
|
+
:param public_key: RSA public key to embed in the certificate.
|
|
239
|
+
:param common_name: Optional CN to include in subject/issuer.
|
|
240
|
+
:returns: Self-signed certificate.
|
|
241
|
+
"""
|
|
15
242
|
attributes = [x509.NameAttribute(NameOID.COMMON_NAME, common_name)] if common_name else []
|
|
16
243
|
subject = issuer = x509.Name(attributes)
|
|
17
244
|
cert = x509.CertificateBuilder()
|
|
@@ -27,22 +254,23 @@ def make_cert(key: RSAPrivateKey, public_key: RSAPublicKey, common_name: Optiona
|
|
|
27
254
|
return cert
|
|
28
255
|
|
|
29
256
|
|
|
30
|
-
def dump_cert(cert):
|
|
31
|
-
|
|
257
|
+
def dump_cert(cert: Certificate) -> bytes:
|
|
258
|
+
"""
|
|
259
|
+
Serialize a certificate in PEM format.
|
|
32
260
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
cert = make_cert(private_key, private_key.public_key())
|
|
39
|
-
dev_key = load_pem_public_key(device_public_key)
|
|
40
|
-
dev_cert = make_cert(private_key, dev_key, 'Device')
|
|
41
|
-
return dump_cert(cert), private_key.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption()), dump_cert(
|
|
42
|
-
dev_cert)
|
|
261
|
+
:param cert: Certificate object.
|
|
262
|
+
:returns: PEM-encoded certificate bytes.
|
|
263
|
+
"""
|
|
264
|
+
return cert.public_bytes(Encoding.PEM)
|
|
43
265
|
|
|
44
266
|
|
|
45
267
|
def create_keybag_file(file: Path, common_name: str) -> None:
|
|
268
|
+
"""
|
|
269
|
+
Write a private key and a simple self-signed certificate to a file (PEM concatenated).
|
|
270
|
+
|
|
271
|
+
:param file: Destination file path.
|
|
272
|
+
:param common_name: Common Name to embed in the self-signed certificate.
|
|
273
|
+
"""
|
|
46
274
|
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
|
47
275
|
cer = make_cert(private_key, private_key.public_key(), common_name)
|
|
48
276
|
file.write_bytes(
|
|
@@ -50,4 +278,5 @@ def create_keybag_file(file: Path, common_name: str) -> None:
|
|
|
50
278
|
encoding=serialization.Encoding.PEM,
|
|
51
279
|
format=PrivateFormat.TraditionalOpenSSL,
|
|
52
280
|
encryption_algorithm=serialization.NoEncryption()
|
|
53
|
-
) + cer.public_bytes(encoding=serialization.Encoding.PEM)
|
|
281
|
+
) + cer.public_bytes(encoding=serialization.Encoding.PEM)
|
|
282
|
+
)
|
pymobiledevice3/cli/bonjour.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import
|
|
3
|
-
from pathlib import Path
|
|
2
|
+
from typing import Optional
|
|
4
3
|
|
|
5
4
|
import click
|
|
6
5
|
|
|
@@ -21,13 +20,9 @@ def bonjour_cli() -> None:
|
|
|
21
20
|
pass
|
|
22
21
|
|
|
23
22
|
|
|
24
|
-
async def cli_mobdev2_task(timeout: float, pair_records: str) -> None:
|
|
25
|
-
records = []
|
|
26
|
-
if pair_records is not None:
|
|
27
|
-
for record in Path(pair_records).glob('*.plist'):
|
|
28
|
-
records.append(plistlib.loads(record.read_bytes()))
|
|
23
|
+
async def cli_mobdev2_task(timeout: float, pair_records: Optional[str]) -> None:
|
|
29
24
|
output = []
|
|
30
|
-
async for ip, lockdown in get_mobdev2_lockdowns(timeout=timeout):
|
|
25
|
+
async for ip, lockdown in get_mobdev2_lockdowns(timeout=timeout, pair_records=pair_records):
|
|
31
26
|
short_info = lockdown.short_info
|
|
32
27
|
short_info['ip'] = ip
|
|
33
28
|
output.append(short_info)
|
|
@@ -38,7 +33,7 @@ async def cli_mobdev2_task(timeout: float, pair_records: str) -> None:
|
|
|
38
33
|
@click.option('--timeout', default=DEFAULT_BONJOUR_TIMEOUT, type=click.INT)
|
|
39
34
|
@click.option('--pair-records', type=click.Path(dir_okay=True, file_okay=False, exists=True),
|
|
40
35
|
help='pair records to attempt validation with')
|
|
41
|
-
def cli_mobdev2(timeout: float, pair_records: str) -> None:
|
|
36
|
+
def cli_mobdev2(timeout: float, pair_records: Optional[str]) -> None:
|
|
42
37
|
""" browse for mobdev2 devices over bonjour """
|
|
43
38
|
asyncio.run(cli_mobdev2_task(timeout, pair_records))
|
|
44
39
|
|
|
@@ -17,7 +17,7 @@ from inquirer3.themes import GreenPassion
|
|
|
17
17
|
from pygments import formatters, highlight, lexers
|
|
18
18
|
|
|
19
19
|
from pymobiledevice3.exceptions import AccessDeniedError, DeviceNotFoundError, NoDeviceConnectedError
|
|
20
|
-
from pymobiledevice3.lockdown import LockdownClient, create_using_usbmux
|
|
20
|
+
from pymobiledevice3.lockdown import LockdownClient, TcpLockdownClient, create_using_usbmux, get_mobdev2_lockdowns
|
|
21
21
|
from pymobiledevice3.osu.os_utils import get_os_utils
|
|
22
22
|
from pymobiledevice3.remote.remote_service_discovery import RemoteServiceDiscoveryService
|
|
23
23
|
from pymobiledevice3.tunneld.api import TUNNELD_DEFAULT_ADDRESS, async_get_tunneld_devices
|
|
@@ -189,7 +189,11 @@ class LockdownCommand(BaseServiceProviderCommand):
|
|
|
189
189
|
def __init__(self, *args, **kwargs):
|
|
190
190
|
super().__init__(*args, **kwargs)
|
|
191
191
|
self.usbmux_address = None
|
|
192
|
+
self.mobdev2_option = None
|
|
192
193
|
self.params[:0] = [
|
|
194
|
+
click.Option(('mobdev2', '--mobdev2'), callback=self.mobdev2, expose_value=False, default=None,
|
|
195
|
+
help='Use bonjour browse for mobdev2 devices. Expected value IP address of the interface to '
|
|
196
|
+
'use. Leave empty to browse through all interfaces'),
|
|
193
197
|
click.Option(('usbmux', '--usbmux'), callback=self.usbmux, expose_value=False,
|
|
194
198
|
envvar=USBMUX_ENV_VAR, help=USBMUX_OPTION_HELP),
|
|
195
199
|
click.Option(('lockdown_service_provider', '--udid'), envvar=UDID_ENV_VAR, callback=self.udid,
|
|
@@ -197,12 +201,22 @@ class LockdownCommand(BaseServiceProviderCommand):
|
|
|
197
201
|
f' option as well'),
|
|
198
202
|
]
|
|
199
203
|
|
|
204
|
+
async def get_mobdev2_devices(self, udid: Optional[str] = None, ips: Optional[list[str]] = None) \
|
|
205
|
+
-> list[TcpLockdownClient]:
|
|
206
|
+
result = []
|
|
207
|
+
async for ip, lockdown in get_mobdev2_lockdowns(udid=udid, ips=ips):
|
|
208
|
+
result.append(lockdown)
|
|
209
|
+
return result
|
|
210
|
+
|
|
211
|
+
def mobdev2(self, ctx, param: str, value: Optional[str] = None) -> None:
|
|
212
|
+
self.mobdev2_option = value
|
|
213
|
+
|
|
200
214
|
def usbmux(self, ctx, param: str, value: Optional[str] = None) -> None:
|
|
201
215
|
if value is None:
|
|
202
216
|
return
|
|
203
217
|
self.usbmux_address = value
|
|
204
218
|
|
|
205
|
-
def udid(self, ctx, param: str, value: str) -> Optional[LockdownClient]:
|
|
219
|
+
def udid(self, ctx, param: str, value: Optional[str]) -> Optional[LockdownClient]:
|
|
206
220
|
if is_invoked_for_completion():
|
|
207
221
|
# prevent lockdown connection establishment when in autocomplete mode
|
|
208
222
|
return
|
|
@@ -210,6 +224,19 @@ class LockdownCommand(BaseServiceProviderCommand):
|
|
|
210
224
|
if self.service_provider is not None:
|
|
211
225
|
return self.service_provider
|
|
212
226
|
|
|
227
|
+
if self.mobdev2_option is not None:
|
|
228
|
+
devices = asyncio.run(self.get_mobdev2_devices(
|
|
229
|
+
udid=value if value else None, ips=[self.mobdev2_option] if self.mobdev2_option else None))
|
|
230
|
+
if not devices:
|
|
231
|
+
raise NoDeviceConnectedError()
|
|
232
|
+
|
|
233
|
+
if len(devices) == 1:
|
|
234
|
+
self.service_provider = devices[0]
|
|
235
|
+
return self.service_provider
|
|
236
|
+
|
|
237
|
+
self.service_provider = prompt_device_list(devices)
|
|
238
|
+
return self.service_provider
|
|
239
|
+
|
|
213
240
|
if value is not None:
|
|
214
241
|
return create_using_usbmux(serial=value)
|
|
215
242
|
|
pymobiledevice3/lockdown.py
CHANGED
|
@@ -24,13 +24,13 @@ from packaging.version import Version
|
|
|
24
24
|
|
|
25
25
|
from pymobiledevice3 import usbmux
|
|
26
26
|
from pymobiledevice3.bonjour import DEFAULT_BONJOUR_TIMEOUT, browse_mobdev2
|
|
27
|
-
from pymobiledevice3.ca import
|
|
27
|
+
from pymobiledevice3.ca import generate_pairing_cert_chain
|
|
28
28
|
from pymobiledevice3.common import get_home_folder
|
|
29
29
|
from pymobiledevice3.exceptions import BadDevError, CannotStopSessionError, ConnectionFailedError, \
|
|
30
30
|
ConnectionTerminatedError, DeviceNotFoundError, FatalPairingError, GetProhibitedError, IncorrectModeError, \
|
|
31
31
|
InvalidConnectionError, InvalidHostIDError, InvalidServiceError, LockdownError, MissingValueError, \
|
|
32
32
|
NoDeviceConnectedError, NotPairedError, PairingDialogResponsePendingError, PairingError, PasswordRequiredError, \
|
|
33
|
-
SetProhibitedError, StartServiceError, UserDeniedPairingError
|
|
33
|
+
PyMobileDevice3Exception, SetProhibitedError, StartServiceError, UserDeniedPairingError
|
|
34
34
|
from pymobiledevice3.irecv_devices import IRECV_DEVICES
|
|
35
35
|
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
36
36
|
from pymobiledevice3.pair_records import create_pairing_records_cache_folder, generate_host_id, \
|
|
@@ -308,6 +308,7 @@ class LockdownClient(ABC, LockdownServiceProvider):
|
|
|
308
308
|
if not response or response.get('Result') != 'Success':
|
|
309
309
|
raise CannotStopSessionError()
|
|
310
310
|
return response
|
|
311
|
+
raise PyMobileDevice3Exception('No active session')
|
|
311
312
|
|
|
312
313
|
def validate_pairing(self) -> bool:
|
|
313
314
|
if self.pair_record is None:
|
|
@@ -363,15 +364,17 @@ class LockdownClient(ABC, LockdownServiceProvider):
|
|
|
363
364
|
raise PairingError()
|
|
364
365
|
|
|
365
366
|
self.logger.info('Creating host key & certificate')
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
367
|
+
host_cert_pem, host_key_pem, device_cert_pem, root_cert_pem, root_key_pem = generate_pairing_cert_chain(
|
|
368
|
+
self.device_public_key,
|
|
369
|
+
private_key=private_key
|
|
370
|
+
# TODO: consider parsing product_version to support iOS < 4
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
pair_record = {'DeviceCertificate': device_cert_pem,
|
|
374
|
+
'HostCertificate': host_cert_pem,
|
|
372
375
|
'HostID': self.host_id,
|
|
373
|
-
'RootCertificate':
|
|
374
|
-
'RootPrivateKey':
|
|
376
|
+
'RootCertificate': root_cert_pem,
|
|
377
|
+
'RootPrivateKey': root_key_pem,
|
|
375
378
|
'WiFiMACAddress': self.wifi_mac_address,
|
|
376
379
|
'SystemBUID': self.system_buid}
|
|
377
380
|
|
|
@@ -380,7 +383,7 @@ class LockdownClient(ABC, LockdownServiceProvider):
|
|
|
380
383
|
|
|
381
384
|
pair = self._request_pair(pair_options, timeout=timeout)
|
|
382
385
|
|
|
383
|
-
pair_record['HostPrivateKey'] =
|
|
386
|
+
pair_record['HostPrivateKey'] = host_key_pem
|
|
384
387
|
escrow_bag = pair.get('EscrowBag')
|
|
385
388
|
|
|
386
389
|
if escrow_bag is not None:
|
|
@@ -405,14 +408,16 @@ class LockdownClient(ABC, LockdownServiceProvider):
|
|
|
405
408
|
raise PairingError()
|
|
406
409
|
|
|
407
410
|
self.logger.info('Creating host key & certificate')
|
|
408
|
-
|
|
411
|
+
host_cert_pem, host_key_pem, device_cert_pem, root_cert_pem, root_key_pem = generate_pairing_cert_chain(
|
|
412
|
+
self.device_public_key
|
|
413
|
+
# TODO: consider parsing product_version to support iOS < 4
|
|
414
|
+
)
|
|
409
415
|
|
|
410
|
-
pair_record = {'
|
|
411
|
-
'
|
|
412
|
-
'HostCertificate': cert_pem,
|
|
416
|
+
pair_record = {'DeviceCertificate': device_cert_pem,
|
|
417
|
+
'HostCertificate': host_cert_pem,
|
|
413
418
|
'HostID': self.host_id,
|
|
414
|
-
'RootCertificate':
|
|
415
|
-
'RootPrivateKey':
|
|
419
|
+
'RootCertificate': root_cert_pem,
|
|
420
|
+
'RootPrivateKey': root_key_pem,
|
|
416
421
|
'WiFiMACAddress': self.wifi_mac_address,
|
|
417
422
|
'SystemBUID': self.system_buid}
|
|
418
423
|
|
|
@@ -434,7 +439,7 @@ class LockdownClient(ABC, LockdownServiceProvider):
|
|
|
434
439
|
# second pair with Response to Challenge
|
|
435
440
|
pair = self._request_pair(pair_options, timeout=timeout)
|
|
436
441
|
|
|
437
|
-
pair_record['HostPrivateKey'] =
|
|
442
|
+
pair_record['HostPrivateKey'] = host_key_pem
|
|
438
443
|
escrow_bag = pair.get('EscrowBag')
|
|
439
444
|
|
|
440
445
|
if escrow_bag is not None:
|
|
@@ -682,6 +687,7 @@ class TcpLockdownClient(LockdownClient):
|
|
|
682
687
|
port)
|
|
683
688
|
self._keep_alive = keep_alive
|
|
684
689
|
self.hostname = hostname
|
|
690
|
+
self.identifier = hostname
|
|
685
691
|
|
|
686
692
|
def _create_service_connection(self, port: int) -> ServiceConnection:
|
|
687
693
|
return ServiceConnection.create_using_tcp(self.hostname, port, keep_alive=self._keep_alive)
|
|
@@ -842,7 +848,7 @@ def create_using_remote(service: ServiceConnection, identifier: str = None, labe
|
|
|
842
848
|
|
|
843
849
|
async def get_mobdev2_lockdowns(
|
|
844
850
|
udid: Optional[str] = None, pair_records: Optional[Path] = None, only_paired: bool = False,
|
|
845
|
-
timeout: float = DEFAULT_BONJOUR_TIMEOUT) \
|
|
851
|
+
timeout: float = DEFAULT_BONJOUR_TIMEOUT, ips: Optional[list[str]] = None) \
|
|
846
852
|
-> AsyncIterable[tuple[str, TcpLockdownClient]]:
|
|
847
853
|
records = {}
|
|
848
854
|
if pair_records is None:
|
|
@@ -858,7 +864,7 @@ async def get_mobdev2_lockdowns(
|
|
|
858
864
|
records[record['WiFiMACAddress']] = record
|
|
859
865
|
|
|
860
866
|
iterated_ips = set()
|
|
861
|
-
for answer in await browse_mobdev2(timeout=timeout):
|
|
867
|
+
for answer in await browse_mobdev2(timeout=timeout, ips=ips):
|
|
862
868
|
if '@' not in answer.name:
|
|
863
869
|
continue
|
|
864
870
|
wifi_mac_address = answer.name.split('@', 1)[0]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pymobiledevice3
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.26.1
|
|
4
4
|
Summary: Pure python3 implementation for working with iDevices (iPhone, etc...)
|
|
5
5
|
Author-email: doronz88 <doron88@gmail.com>, matan <matan1008@gmail.com>
|
|
6
6
|
Maintainer-email: doronz88 <doron88@gmail.com>, matan <matan1008@gmail.com>
|
|
@@ -8,14 +8,14 @@ misc/understanding_idevice_protocol_layers.md,sha256=8tEqRXWOUPoxOJLZVh7C7H9JGCh
|
|
|
8
8
|
misc/usbmux_sniff.sh,sha256=iWtbucOEQ9_UEFXk9x-2VNt48Jg5zrPsnUbZ_LfZxwA,212
|
|
9
9
|
pymobiledevice3/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
10
|
pymobiledevice3/__main__.py,sha256=tBERBLftc5ENGB4NQfNJRIecok0LQfNvkqXlsi1feFw,11572
|
|
11
|
-
pymobiledevice3/_version.py,sha256=
|
|
12
|
-
pymobiledevice3/bonjour.py,sha256
|
|
13
|
-
pymobiledevice3/ca.py,sha256=
|
|
11
|
+
pymobiledevice3/_version.py,sha256=qneizFYIunJ9wWwPPVKHWiU6zVellqgeXXUE9ezfGGg,706
|
|
12
|
+
pymobiledevice3/bonjour.py,sha256=-Q_TLBGJ6qW3CX_DgBcz-CXfWSwxWVQ2L64hk6PxnDY,5631
|
|
13
|
+
pymobiledevice3/ca.py,sha256=mTvWdSjTZw6Eb-22-IZ323GyA1G6CXYmdPedImTjm3A,10542
|
|
14
14
|
pymobiledevice3/common.py,sha256=-PG6oaUkNFlB3jb7E0finMrX8wqhkS-cuTAfmLvZUmc,329
|
|
15
15
|
pymobiledevice3/exceptions.py,sha256=VqWB6WWoMrXt8GDdKqRHeJ1otP-eZIThoHERswXWqpw,10347
|
|
16
16
|
pymobiledevice3/irecv.py,sha256=FoEln1_zHkAiNcEctB5bStfhKNgniOSg7lg9xcX1U2Q,10596
|
|
17
17
|
pymobiledevice3/irecv_devices.py,sha256=BG30ecXSChxdyYCCGIrIO0sVWT31hbKymB78nZWVfWc,38506
|
|
18
|
-
pymobiledevice3/lockdown.py,sha256=
|
|
18
|
+
pymobiledevice3/lockdown.py,sha256=xejqmSLhJsvM-F4rs4InxtVVtSYYSN3VJXnxd-ijspI,38814
|
|
19
19
|
pymobiledevice3/lockdown_service_provider.py,sha256=l5N72tiuI-2uowk8wu6B7qkjY2UmqQsnhdJqvJy3I8A,1744
|
|
20
20
|
pymobiledevice3/pair_records.py,sha256=Tr28mlBWPXvOF7vdKBDOuw1rCRwm6RViDTGbikfP77I,6034
|
|
21
21
|
pymobiledevice3/service_connection.py,sha256=_-PTLFr3krtwEBNHEKXCd_2eOGwMpbsfPbB8AX2uN-g,14861
|
|
@@ -28,8 +28,8 @@ pymobiledevice3/cli/afc.py,sha256=z-qnBVUPA4uOnXADkYVyRJxeibRDFF3j5LejHt_6UW4,21
|
|
|
28
28
|
pymobiledevice3/cli/amfi.py,sha256=6hlqKrKOFj0secUnLQ8grDDnnh3fRsO6x_vo40oy22w,963
|
|
29
29
|
pymobiledevice3/cli/apps.py,sha256=LH75A1gDRGP0nWO4QFcOUDg0EdphkGuYnWJgIQHrIBg,3859
|
|
30
30
|
pymobiledevice3/cli/backup.py,sha256=SyHojiRRguxdkAPMz_Rp_9-zJNeuOtmpa0imdPN12-4,6691
|
|
31
|
-
pymobiledevice3/cli/bonjour.py,sha256=
|
|
32
|
-
pymobiledevice3/cli/cli_common.py,sha256=
|
|
31
|
+
pymobiledevice3/cli/bonjour.py,sha256=qWFH05BZ-FlnSilVP0PMzPUidfBs5LPdepMhPyIliFE,2806
|
|
32
|
+
pymobiledevice3/cli/cli_common.py,sha256=i51C8Y4ST_BqUMBvwuoLanGDJZ-rZS6GsxX6OYqsPd0,12908
|
|
33
33
|
pymobiledevice3/cli/companion_proxy.py,sha256=ey0X3moJ49zVJoNCpRMMHmf9fBZfdqimhz2VCA35oII,581
|
|
34
34
|
pymobiledevice3/cli/completions.py,sha256=t8oryezQTcWDno_E2Cch7o1f-qURVL9M1Z4o6uLA_kM,1722
|
|
35
35
|
pymobiledevice3/cli/crash.py,sha256=m1vs0_KUy4cxu8vHYjn7olay8oPQGTFZqMCHspnGpVs,3181
|
|
@@ -164,9 +164,9 @@ pymobiledevice3/services/web_protocol/switch_to.py,sha256=hDddJUEePbRN-8xlllOeGh
|
|
|
164
164
|
pymobiledevice3/tunneld/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
165
165
|
pymobiledevice3/tunneld/api.py,sha256=EfGKXEWhsMSB__menPmRmL9R6dpazVJDUy7B3pn05MM,2357
|
|
166
166
|
pymobiledevice3/tunneld/server.py,sha256=SvC57AV_R8YQhA0fCwGNUdhfy8TKMFWwL_fp_FmXrBI,22715
|
|
167
|
-
pymobiledevice3-4.
|
|
168
|
-
pymobiledevice3-4.
|
|
169
|
-
pymobiledevice3-4.
|
|
170
|
-
pymobiledevice3-4.
|
|
171
|
-
pymobiledevice3-4.
|
|
172
|
-
pymobiledevice3-4.
|
|
167
|
+
pymobiledevice3-4.26.1.dist-info/licenses/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
|
|
168
|
+
pymobiledevice3-4.26.1.dist-info/METADATA,sha256=xkmBpgcU9uIklTKsi-zlXIUmy03ERVPu_YcRmHhnlaU,17463
|
|
169
|
+
pymobiledevice3-4.26.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
170
|
+
pymobiledevice3-4.26.1.dist-info/entry_points.txt,sha256=jJMlOanHlVwUxcY__JwvKeWPrvBJr_wJyEq4oHIZNKE,66
|
|
171
|
+
pymobiledevice3-4.26.1.dist-info/top_level.txt,sha256=MjZoRqcWPOh5banG-BbDOnKEfsS3kCxqV9cv-nzyg2Q,21
|
|
172
|
+
pymobiledevice3-4.26.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|