139 lines
6 KiB
JavaScript
139 lines
6 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.ClientManager = void 0;
|
|
const stream_1 = require("stream");
|
|
const tls = require("tls");
|
|
const afc_1 = require("./client/afc");
|
|
const debugserver_1 = require("./client/debugserver");
|
|
const installation_proxy_1 = require("./client/installation_proxy");
|
|
const lockdownd_1 = require("./client/lockdownd");
|
|
const mobile_image_mounter_1 = require("./client/mobile_image_mounter");
|
|
const usbmuxd_1 = require("./client/usbmuxd");
|
|
class ClientManager {
|
|
constructor(pairRecord, device, lockdowndClient) {
|
|
this.pairRecord = pairRecord;
|
|
this.device = device;
|
|
this.lockdowndClient = lockdowndClient;
|
|
this.connections = [lockdowndClient.socket];
|
|
}
|
|
static async create(udid) {
|
|
const usbmuxClient = new usbmuxd_1.UsbmuxdClient(usbmuxd_1.UsbmuxdClient.connectUsbmuxdSocket());
|
|
const device = await usbmuxClient.getDevice(udid);
|
|
const pairRecord = await usbmuxClient.readPairRecord(device.Properties.SerialNumber);
|
|
const lockdownSocket = await usbmuxClient.connect(device, 62078);
|
|
const lockdownClient = new lockdownd_1.LockdowndClient(lockdownSocket);
|
|
await lockdownClient.doHandshake(pairRecord);
|
|
return new ClientManager(pairRecord, device, lockdownClient);
|
|
}
|
|
async getUsbmuxdClient() {
|
|
const usbmuxClient = new usbmuxd_1.UsbmuxdClient(usbmuxd_1.UsbmuxdClient.connectUsbmuxdSocket());
|
|
this.connections.push(usbmuxClient.socket);
|
|
return usbmuxClient;
|
|
}
|
|
async getLockdowndClient() {
|
|
const usbmuxClient = new usbmuxd_1.UsbmuxdClient(usbmuxd_1.UsbmuxdClient.connectUsbmuxdSocket());
|
|
const lockdownSocket = await usbmuxClient.connect(this.device, 62078);
|
|
const lockdownClient = new lockdownd_1.LockdowndClient(lockdownSocket);
|
|
this.connections.push(lockdownClient.socket);
|
|
return lockdownClient;
|
|
}
|
|
async getLockdowndClientWithHandshake() {
|
|
const lockdownClient = await this.getLockdowndClient();
|
|
await lockdownClient.doHandshake(this.pairRecord);
|
|
return lockdownClient;
|
|
}
|
|
async getAFCClient() {
|
|
return this.getServiceClient('com.apple.afc', afc_1.AFCClient);
|
|
}
|
|
async getInstallationProxyClient() {
|
|
return this.getServiceClient('com.apple.mobile.installation_proxy', installation_proxy_1.InstallationProxyClient);
|
|
}
|
|
async getMobileImageMounterClient() {
|
|
return this.getServiceClient('com.apple.mobile.mobile_image_mounter', mobile_image_mounter_1.MobileImageMounterClient);
|
|
}
|
|
async getDebugserverClient() {
|
|
try {
|
|
// iOS 14 added support for a secure debug service so try to connect to that first
|
|
return await this.getServiceClient('com.apple.debugserver.DVTSecureSocketProxy', debugserver_1.DebugserverClient);
|
|
}
|
|
catch {
|
|
// otherwise, fall back to the previous implementation
|
|
return this.getServiceClient('com.apple.debugserver', debugserver_1.DebugserverClient, true);
|
|
}
|
|
}
|
|
async getServiceClient(name, ServiceType, disableSSL = false) {
|
|
const { port: servicePort, enableServiceSSL } = await this.lockdowndClient.startService(name);
|
|
const usbmuxClient = new usbmuxd_1.UsbmuxdClient(usbmuxd_1.UsbmuxdClient.connectUsbmuxdSocket());
|
|
let usbmuxdSocket = await usbmuxClient.connect(this.device, servicePort);
|
|
if (enableServiceSSL) {
|
|
const tlsOptions = {
|
|
rejectUnauthorized: false,
|
|
secureContext: tls.createSecureContext({
|
|
secureProtocol: 'TLSv1_2_method',
|
|
cert: this.pairRecord.RootCertificate,
|
|
key: this.pairRecord.RootPrivateKey,
|
|
}),
|
|
};
|
|
// Some services seem to not support TLS/SSL after the initial handshake
|
|
// More info: https://github.com/libimobiledevice/libimobiledevice/issues/793
|
|
if (disableSSL) {
|
|
// According to https://nodejs.org/api/tls.html#tls_tls_connect_options_callback we can
|
|
// pass any Duplex in to tls.connect instead of a Socket. So we'll use our proxy to keep
|
|
// the TLS wrapper and underlying usbmuxd socket separate.
|
|
const proxy = new UsbmuxdProxy(usbmuxdSocket);
|
|
tlsOptions.socket = proxy;
|
|
await new Promise((res, rej) => {
|
|
const timeoutId = setTimeout(() => {
|
|
rej('The TLS handshake failed to complete after 5s.');
|
|
}, 5000);
|
|
tls.connect(tlsOptions, function () {
|
|
clearTimeout(timeoutId);
|
|
// After the handshake, we don't need TLS or the proxy anymore,
|
|
// since we'll just pass in the naked usbmuxd socket to the service client
|
|
this.destroy();
|
|
res();
|
|
});
|
|
});
|
|
}
|
|
else {
|
|
tlsOptions.socket = usbmuxdSocket;
|
|
usbmuxdSocket = tls.connect(tlsOptions);
|
|
}
|
|
}
|
|
const client = new ServiceType(usbmuxdSocket);
|
|
this.connections.push(client.socket);
|
|
return client;
|
|
}
|
|
end() {
|
|
for (const socket of this.connections) {
|
|
// may already be closed
|
|
try {
|
|
socket.end();
|
|
}
|
|
catch (err) {
|
|
// ignore
|
|
}
|
|
}
|
|
}
|
|
}
|
|
exports.ClientManager = ClientManager;
|
|
class UsbmuxdProxy extends stream_1.Duplex {
|
|
constructor(usbmuxdSock) {
|
|
super();
|
|
this.usbmuxdSock = usbmuxdSock;
|
|
this.usbmuxdSock.on('data', (data) => {
|
|
this.push(data);
|
|
});
|
|
}
|
|
_write(chunk, encoding, callback) {
|
|
this.usbmuxdSock.write(chunk);
|
|
callback();
|
|
}
|
|
_read(size) {
|
|
// Stub so we don't error, since we push everything we get from usbmuxd as it comes in.
|
|
// TODO: better way to do this?
|
|
}
|
|
_destroy() {
|
|
this.usbmuxdSock.removeAllListeners();
|
|
}
|
|
}
|