1c4ef02aeSEvan Bacon/** 2c4ef02aeSEvan Bacon * Copyright (c) 2021 Expo, Inc. 3c4ef02aeSEvan Bacon * Copyright (c) 2018 Drifty Co. 4c4ef02aeSEvan Bacon * 5c4ef02aeSEvan Bacon * This source code is licensed under the MIT license found in the 6c4ef02aeSEvan Bacon * LICENSE file in the root directory of this source tree. 7c4ef02aeSEvan Bacon */ 8c4ef02aeSEvan Baconimport Debug from 'debug'; 9c4ef02aeSEvan Baconimport { Socket } from 'net'; 10c4ef02aeSEvan Baconimport * as tls from 'tls'; 11c4ef02aeSEvan Bacon 12c4ef02aeSEvan Baconimport { ResponseError, ServiceClient } from './ServiceClient'; 13c4ef02aeSEvan Baconimport { UsbmuxdPairRecord } from './UsbmuxdClient'; 14*8a424bebSJames Ideimport { LockdownProtocolClient } from '../protocol/LockdownProtocol'; 15c4ef02aeSEvan Bacon 16c4ef02aeSEvan Baconconst debug = Debug('expo:apple-device:client:lockdownd'); 17c4ef02aeSEvan Bacon 18c4ef02aeSEvan Baconexport interface DeviceValues { 19c4ef02aeSEvan Bacon BasebandCertId: number; 20c4ef02aeSEvan Bacon BasebandKeyHashInformation: { 21c4ef02aeSEvan Bacon AKeyStatus: number; 22c4ef02aeSEvan Bacon SKeyHash: Buffer; 23c4ef02aeSEvan Bacon SKeyStatus: number; 24c4ef02aeSEvan Bacon }; 25c4ef02aeSEvan Bacon BasebandSerialNumber: Buffer; 26c4ef02aeSEvan Bacon BasebandVersion: string; 27c4ef02aeSEvan Bacon BoardId: number; 28c4ef02aeSEvan Bacon BuildVersion: string; 29c4ef02aeSEvan Bacon ChipID: number; 30bd710fa0SBartosz Kaszubowski ConnectionType: 'USB' | 'Network'; 31c4ef02aeSEvan Bacon DeviceClass: string; 32c4ef02aeSEvan Bacon DeviceColor: string; 33c4ef02aeSEvan Bacon DeviceName: string; 34c4ef02aeSEvan Bacon DieID: number; 35c4ef02aeSEvan Bacon HardwareModel: string; 36c4ef02aeSEvan Bacon HasSiDP: boolean; 37c4ef02aeSEvan Bacon PartitionType: string; 38c4ef02aeSEvan Bacon ProductName: string; 39c4ef02aeSEvan Bacon ProductType: string; 40c4ef02aeSEvan Bacon ProductVersion: string; 41c4ef02aeSEvan Bacon ProductionSOC: boolean; 42c4ef02aeSEvan Bacon ProtocolVersion: string; 43c4ef02aeSEvan Bacon TelephonyCapability: boolean; 44c4ef02aeSEvan Bacon UniqueChipID: number; 45c4ef02aeSEvan Bacon UniqueDeviceID: string; 46c4ef02aeSEvan Bacon WiFiAddress: string; 47c4ef02aeSEvan Bacon [key: string]: any; 48c4ef02aeSEvan Bacon} 49c4ef02aeSEvan Bacon 50c4ef02aeSEvan Baconinterface LockdowndServiceResponse { 51c4ef02aeSEvan Bacon Request: 'StartService'; 52c4ef02aeSEvan Bacon Service: string; 53c4ef02aeSEvan Bacon Port: number; 54c4ef02aeSEvan Bacon EnableServiceSSL?: boolean; // Only on iOS 13+ 55c4ef02aeSEvan Bacon} 56c4ef02aeSEvan Bacon 57c4ef02aeSEvan Baconinterface LockdowndSessionResponse { 58c4ef02aeSEvan Bacon Request: 'StartSession'; 59c4ef02aeSEvan Bacon EnableSessionSSL: boolean; 60c4ef02aeSEvan Bacon} 61c4ef02aeSEvan Bacon 62c4ef02aeSEvan Baconinterface LockdowndAllValuesResponse { 63c4ef02aeSEvan Bacon Request: 'GetValue'; 64c4ef02aeSEvan Bacon Value: DeviceValues; 65c4ef02aeSEvan Bacon} 66c4ef02aeSEvan Bacon 67c4ef02aeSEvan Baconinterface LockdowndValueResponse { 68c4ef02aeSEvan Bacon Request: 'GetValue'; 69c4ef02aeSEvan Bacon Key: string; 70c4ef02aeSEvan Bacon Value: string; 71c4ef02aeSEvan Bacon} 72c4ef02aeSEvan Bacon 73c4ef02aeSEvan Baconinterface LockdowndQueryTypeResponse { 74c4ef02aeSEvan Bacon Request: 'QueryType'; 75c4ef02aeSEvan Bacon Type: string; 76c4ef02aeSEvan Bacon} 77c4ef02aeSEvan Bacon 78c4ef02aeSEvan Baconfunction isLockdowndServiceResponse(resp: any): resp is LockdowndServiceResponse { 79c4ef02aeSEvan Bacon return resp.Request === 'StartService' && resp.Service !== undefined && resp.Port !== undefined; 80c4ef02aeSEvan Bacon} 81c4ef02aeSEvan Bacon 82c4ef02aeSEvan Baconfunction isLockdowndSessionResponse(resp: any): resp is LockdowndSessionResponse { 83c4ef02aeSEvan Bacon return resp.Request === 'StartSession'; 84c4ef02aeSEvan Bacon} 85c4ef02aeSEvan Bacon 86c4ef02aeSEvan Baconfunction isLockdowndAllValuesResponse(resp: any): resp is LockdowndAllValuesResponse { 87c4ef02aeSEvan Bacon return resp.Request === 'GetValue' && resp.Value !== undefined; 88c4ef02aeSEvan Bacon} 89c4ef02aeSEvan Bacon 90c4ef02aeSEvan Baconfunction isLockdowndValueResponse(resp: any): resp is LockdowndValueResponse { 91c4ef02aeSEvan Bacon return resp.Request === 'GetValue' && resp.Key !== undefined && typeof resp.Value === 'string'; 92c4ef02aeSEvan Bacon} 93c4ef02aeSEvan Bacon 94c4ef02aeSEvan Baconfunction isLockdowndQueryTypeResponse(resp: any): resp is LockdowndQueryTypeResponse { 95c4ef02aeSEvan Bacon return resp.Request === 'QueryType' && resp.Type !== undefined; 96c4ef02aeSEvan Bacon} 97c4ef02aeSEvan Bacon 98c4ef02aeSEvan Baconexport class LockdowndClient extends ServiceClient<LockdownProtocolClient> { 99c4ef02aeSEvan Bacon constructor(public socket: Socket) { 100c4ef02aeSEvan Bacon super(socket, new LockdownProtocolClient(socket)); 101c4ef02aeSEvan Bacon } 102c4ef02aeSEvan Bacon 103c4ef02aeSEvan Bacon async startService(name: string) { 104c4ef02aeSEvan Bacon debug(`startService: ${name}`); 105c4ef02aeSEvan Bacon 106c4ef02aeSEvan Bacon const resp = await this.protocolClient.sendMessage({ 107c4ef02aeSEvan Bacon Request: 'StartService', 108c4ef02aeSEvan Bacon Service: name, 109c4ef02aeSEvan Bacon }); 110c4ef02aeSEvan Bacon 111c4ef02aeSEvan Bacon if (isLockdowndServiceResponse(resp)) { 112c4ef02aeSEvan Bacon return { port: resp.Port, enableServiceSSL: !!resp.EnableServiceSSL }; 113c4ef02aeSEvan Bacon } else { 114c4ef02aeSEvan Bacon throw new ResponseError(`Error starting service ${name}`, resp); 115c4ef02aeSEvan Bacon } 116c4ef02aeSEvan Bacon } 117c4ef02aeSEvan Bacon 118c4ef02aeSEvan Bacon async startSession(pairRecord: UsbmuxdPairRecord) { 119c4ef02aeSEvan Bacon debug(`startSession: ${pairRecord}`); 120c4ef02aeSEvan Bacon 121c4ef02aeSEvan Bacon const resp = await this.protocolClient.sendMessage({ 122c4ef02aeSEvan Bacon Request: 'StartSession', 123c4ef02aeSEvan Bacon HostID: pairRecord.HostID, 124c4ef02aeSEvan Bacon SystemBUID: pairRecord.SystemBUID, 125c4ef02aeSEvan Bacon }); 126c4ef02aeSEvan Bacon 127c4ef02aeSEvan Bacon if (isLockdowndSessionResponse(resp)) { 128c4ef02aeSEvan Bacon if (resp.EnableSessionSSL) { 129c4ef02aeSEvan Bacon this.protocolClient.socket = new tls.TLSSocket(this.protocolClient.socket, { 130c4ef02aeSEvan Bacon secureContext: tls.createSecureContext({ 131eec3a87eSCedric van Putten // Avoid using `secureProtocol` fixing the socket to a single TLS version. 132eec3a87eSCedric van Putten // Newer Node versions might not support older TLS versions. 133eec3a87eSCedric van Putten // By using the default `minVersion` and `maxVersion` options, 134eec3a87eSCedric van Putten // The socket will automatically use the appropriate TLS version. 135eec3a87eSCedric van Putten // See: https://nodejs.org/api/tls.html#tlscreatesecurecontextoptions 136c4ef02aeSEvan Bacon cert: pairRecord.RootCertificate, 137c4ef02aeSEvan Bacon key: pairRecord.RootPrivateKey, 138c4ef02aeSEvan Bacon }), 139c4ef02aeSEvan Bacon }); 140c4ef02aeSEvan Bacon debug(`Socket upgraded to TLS connection`); 141c4ef02aeSEvan Bacon } 142c4ef02aeSEvan Bacon // TODO: save sessionID for StopSession? 143c4ef02aeSEvan Bacon } else { 144c4ef02aeSEvan Bacon throw new ResponseError('Error starting session', resp); 145c4ef02aeSEvan Bacon } 146c4ef02aeSEvan Bacon } 147c4ef02aeSEvan Bacon 148c4ef02aeSEvan Bacon async getAllValues() { 149c4ef02aeSEvan Bacon debug(`getAllValues`); 150c4ef02aeSEvan Bacon 151c4ef02aeSEvan Bacon const resp = await this.protocolClient.sendMessage({ Request: 'GetValue' }); 152c4ef02aeSEvan Bacon 153c4ef02aeSEvan Bacon if (isLockdowndAllValuesResponse(resp)) { 154c4ef02aeSEvan Bacon return resp.Value; 155c4ef02aeSEvan Bacon } else { 156c4ef02aeSEvan Bacon throw new ResponseError('Error getting lockdown value', resp); 157c4ef02aeSEvan Bacon } 158c4ef02aeSEvan Bacon } 159c4ef02aeSEvan Bacon 160c4ef02aeSEvan Bacon async getValue(val: string) { 161c4ef02aeSEvan Bacon debug(`getValue: ${val}`); 162c4ef02aeSEvan Bacon 163c4ef02aeSEvan Bacon const resp = await this.protocolClient.sendMessage({ 164c4ef02aeSEvan Bacon Request: 'GetValue', 165c4ef02aeSEvan Bacon Key: val, 166c4ef02aeSEvan Bacon }); 167c4ef02aeSEvan Bacon 168c4ef02aeSEvan Bacon if (isLockdowndValueResponse(resp)) { 169c4ef02aeSEvan Bacon return resp.Value; 170c4ef02aeSEvan Bacon } else { 171c4ef02aeSEvan Bacon throw new ResponseError('Error getting lockdown value', resp); 172c4ef02aeSEvan Bacon } 173c4ef02aeSEvan Bacon } 174c4ef02aeSEvan Bacon 175c4ef02aeSEvan Bacon async queryType() { 176c4ef02aeSEvan Bacon debug('queryType'); 177c4ef02aeSEvan Bacon 178c4ef02aeSEvan Bacon const resp = await this.protocolClient.sendMessage({ 179c4ef02aeSEvan Bacon Request: 'QueryType', 180c4ef02aeSEvan Bacon }); 181c4ef02aeSEvan Bacon 182c4ef02aeSEvan Bacon if (isLockdowndQueryTypeResponse(resp)) { 183c4ef02aeSEvan Bacon return resp.Type; 184c4ef02aeSEvan Bacon } else { 185c4ef02aeSEvan Bacon throw new ResponseError('Error getting lockdown query type', resp); 186c4ef02aeSEvan Bacon } 187c4ef02aeSEvan Bacon } 188c4ef02aeSEvan Bacon 189c4ef02aeSEvan Bacon async doHandshake(pairRecord: UsbmuxdPairRecord) { 190c4ef02aeSEvan Bacon debug('doHandshake'); 191c4ef02aeSEvan Bacon 192c4ef02aeSEvan Bacon // if (await this.lockdownQueryType() !== 'com.apple.mobile.lockdown') { 193c4ef02aeSEvan Bacon // throw new CommandError('Invalid type received from lockdown handshake'); 194c4ef02aeSEvan Bacon // } 195c4ef02aeSEvan Bacon // await this.getLockdownValue('ProductVersion'); 196c4ef02aeSEvan Bacon // TODO: validate pair and pair 197c4ef02aeSEvan Bacon await this.startSession(pairRecord); 198c4ef02aeSEvan Bacon } 199c4ef02aeSEvan Bacon} 200