Skip to content

Commit 2477fb1

Browse files
committed
Fixes #83: Replace pkijs with forge
1 parent 74d9ef0 commit 2477fb1

File tree

7 files changed

+126
-231
lines changed

7 files changed

+126
-231
lines changed

docs/README.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ An ID of a known permission on Android.
4242

4343
#### Defined in
4444

45-
[android.ts:880](https://github.com/tweaselORG/appstraction/blob/main/src/android.ts#L880)
45+
[android.ts:877](https://github.com/tweaselORG/appstraction/blob/main/src/android.ts#L877)
4646

4747
___
4848

@@ -112,7 +112,7 @@ An ID of a known permission on iOS.
112112

113113
#### Defined in
114114

115-
[ios.ts:503](https://github.com/tweaselORG/appstraction/blob/main/src/ios.ts#L503)
115+
[ios.ts:508](https://github.com/tweaselORG/appstraction/blob/main/src/ios.ts#L508)
116116

117117
___
118118

@@ -320,7 +320,7 @@ The IDs of known permissions on Android.
320320

321321
#### Defined in
322322

323-
[android.ts:749](https://github.com/tweaselORG/appstraction/blob/main/src/android.ts#L749)
323+
[android.ts:746](https://github.com/tweaselORG/appstraction/blob/main/src/android.ts#L746)
324324

325325
___
326326

@@ -332,7 +332,7 @@ The IDs of known permissions on iOS.
332332

333333
#### Defined in
334334

335-
[ios.ts:486](https://github.com/tweaselORG/appstraction/blob/main/src/ios.ts#L486)
335+
[ios.ts:491](https://github.com/tweaselORG/appstraction/blob/main/src/ios.ts#L491)
336336

337337
## Functions
338338

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@
5555
"@napi-rs/lzma": "^1.1.2",
5656
"andromatic": "^1.0.0",
5757
"autopy": "1.0.0",
58-
"asn1js": "^3.0.5",
5958
"bplist-creator": "^0.1.1",
6059
"bplist-parser": "^0.3.2",
6160
"cross-fetch": "^3.1.5",
@@ -65,9 +64,9 @@
6564
"fs-extra": "^11.1.0",
6665
"global-cache-dir": "^5.0.0",
6766
"ipa-extract-info": "^1.2.6",
67+
"node-forge": "^1.3.1",
6868
"node-ssh": "^13.1.0",
6969
"p-retry": "^5.1.2",
70-
"pkijs": "^3.0.14",
7170
"semver": "^7.3.8",
7271
"tempy": "^3.0.0",
7372
"ts-node": "^10.9.1",
@@ -81,6 +80,7 @@
8180
"@parcel/transformer-typescript-types": "2.8.2",
8281
"@types/fs-extra": "^11.0.0",
8382
"@types/node": "^18.11.18",
83+
"@types/node-forge": "^1.3.2",
8484
"@types/plist": "^3.0.2",
8585
"@types/promise-timeout": "^1.3.0",
8686
"@types/semver": "^7.3.13",

src/android.ts

+6-9
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { createHash } from 'crypto';
66
import { fileTypeFromFile } from 'file-type';
77
import frida from 'frida';
88
import { open, rm, writeFile } from 'fs/promises';
9+
import forge from 'node-forge';
910
import pRetry from 'p-retry';
1011
import { basename } from 'path';
1112
import { major as semverMajor, minVersion as semverMinVersion } from 'semver';
@@ -21,15 +22,9 @@ import type {
2122
import { dependencies } from '../package.json';
2223
import { venvOptions } from '../scripts/common/python';
2324
import type { ParametersExceptFirst, XapkManifest } from './utils';
24-
import {
25-
asyncUnimplemented,
26-
getObjFromFridaScript,
27-
isRecord,
28-
parseAppMeta,
29-
retryCondition,
30-
} from './utils';
25+
import { asyncUnimplemented, getObjFromFridaScript, isRecord, parseAppMeta, retryCondition } from './utils';
26+
import { certSubjectToAsn1, parsePemCertificateFromFile } from './utils/crypto';
3127
import { forEachInZip, getFileFromZip, tmpFileFromZipEntry } from './utils/zip';
32-
import { parsePemCertificateFromFile} from './utils/crypto';
3328

3429
const adb = (...args: ParametersExceptFirst<typeof runAndroidDevTool>) => runAndroidDevTool('adb', args[0], args[1]);
3530
const venv = getVenv(venvOptions);
@@ -177,7 +172,9 @@ export const androidApi = <RunTarget extends SupportedRunTarget<'android'>>(
177172
getCertificateSubjectHashOld: async (path: string) => {
178173
const { cert } = await parsePemCertificateFromFile(path);
179174

180-
const hash = createHash('md5').update(Buffer.from(cert.subject.valueBeforeDecode)).digest();
175+
const hash = createHash('md5')
176+
.update(forge.asn1.toDer(certSubjectToAsn1(cert)).toHex(), 'hex')
177+
.digest();
181178
const truncated = hash.subarray(0, 4);
182179
const ulong = (truncated[0]! | (truncated[1]! << 8) | (truncated[2]! << 16) | (truncated[3]! << 24)) >>> 0;
183180

src/ios.ts

+34-29
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,21 @@ import { parseFile } from 'bplist-parser';
44
import { createHash } from 'crypto';
55
import { execa } from 'execa';
66
import frida from 'frida';
7-
import { exists,mkdirp } from 'fs-extra';
8-
import { readFile,writeFile } from 'fs/promises';
7+
import { exists, mkdirp } from 'fs-extra';
8+
import { readFile, writeFile } from 'fs/promises';
99
import globalCacheDir from 'global-cache-dir';
1010
import { NodeSSH } from 'node-ssh';
1111
import { join } from 'path';
12-
import type { PlatformApi,PlatformApiOptions,Proxy,SupportedCapability,SupportedRunTarget } from '.';
12+
import type { PlatformApi, PlatformApiOptions, Proxy, SupportedCapability, SupportedRunTarget } from '.';
1313
import { venvOptions } from '../scripts/common/python';
14-
import { asyncUnimplemented,getObjFromFridaScript,isRecord,retryCondition } from './utils';
14+
import { asyncUnimplemented, getObjFromFridaScript, isRecord, retryCondition } from './utils';
1515
import {
1616
arrayBufferToPem,
17+
asn1ValueToDer,
1718
certificateFingerprint,
1819
certificateHasExpired,
20+
certSubjectToAsn1,
21+
createPkcs12Container,
1922
generateCertificate,
2023
parsePemCertificateFromFile,
2124
pemToArrayBuffer,
@@ -170,53 +173,54 @@ export const iosApi = <RunTarget extends SupportedRunTarget<'ios'>>(
170173
if (!plist) throw new Error('Failed to ensure supervision mode: Invalid CloudConfiguration.');
171174

172175
let hostCert;
173-
let hostKey;
174176

175177
if (
176178
(await exists(join(cacheDir, 'ios', 'supervisorCert.pem'))) &&
177-
(await exists(join(cacheDir, 'ios', 'supervisorPrivateKey.pem')))
179+
(await exists(join(cacheDir, 'ios', 'supervisorKeyStore.p12')))
178180
) {
179-
hostCert = pemToArrayBuffer((await readFile(join(cacheDir, 'ios', 'supervisorCert.pem'))).toString());
180-
hostKey = pemToArrayBuffer(
181-
(await readFile(join(cacheDir, 'ios', 'supervisorPrivateKey.pem'))).toString()
182-
);
181+
hostCert = (await readFile(join(cacheDir, 'ios', 'supervisorCert.pem'))).toString();
183182

184183
if (!(await certificateHasExpired(hostCert))) {
185184
const hostCertFingerprint = await certificateFingerprint(hostCert);
186185

187-
// Test if the current host certificate is already controlling the device.
188-
if (
189-
plist.IsSupervised &&
190-
plist.SupervisorHostCertificates &&
191-
plist.SupervisorHostCertificates.length > 0 &&
192-
plist.SupervisorHostCertificates.some(
193-
async (cert) => (await certificateFingerprint(cert)) === hostCertFingerprint
186+
try {
187+
// Test if the current host certificate is already controlling the device.
188+
if (
189+
plist.IsSupervised &&
190+
plist.SupervisorHostCertificates &&
191+
plist.SupervisorHostCertificates.length > 0 &&
192+
plist.SupervisorHostCertificates.some(
193+
(cert) =>
194+
certificateFingerprint(arrayBufferToPem(cert, 'CERTIFICATE')) ===
195+
hostCertFingerprint
196+
)
194197
)
195-
)
196-
return;
198+
return;
199+
} catch (e) {
200+
// The certificate is invalid, so we need to generate a new one.
201+
hostCert = undefined;
202+
}
197203
} else {
198204
hostCert = undefined;
199-
hostKey = undefined;
200205
}
201206
}
202207

203-
if (!hostCert || !hostKey) {
208+
if (!hostCert) {
204209
// We have no exsiting keys, so let’s generate one.
205210
const generated = await generateCertificate(OrganizationName);
206211
hostCert = generated.certificate;
207-
hostKey = generated.privateKey;
212+
const hostKey = generated.privateKey;
213+
214+
const keyStore = createPkcs12Container(hostCert, hostKey, 'appstraction');
208215

209216
await mkdirp(join(cacheDir, 'ios'));
210-
await writeFile(join(cacheDir, 'ios', 'supervisorCert.pem'), arrayBufferToPem(hostCert, 'CERTIFICATE'));
211-
await writeFile(
212-
join(cacheDir, 'ios', 'supervisorPrivateKey.pem'),
213-
arrayBufferToPem(hostKey, 'PRIVATE KEY')
214-
);
217+
await writeFile(join(cacheDir, 'ios', 'supervisorCert.pem'), hostCert);
218+
await writeFile(join(cacheDir, 'ios', 'supervisorKeyStore.p12'), Buffer.from(keyStore.toHex(), 'hex'));
215219
}
216220

217221
const newPlist = {
218222
...plist,
219-
SupervisorHostCertificates: [Buffer.from(hostCert)],
223+
SupervisorHostCertificates: [Buffer.from(pemToArrayBuffer(hostCert))],
220224
IsSupervised: true,
221225
OrganizationName,
222226
AllowPairing: true,
@@ -426,7 +430,7 @@ export const iosApi = <RunTarget extends SupportedRunTarget<'ios'>>(
426430
const { cert, certDer } = await parsePemCertificateFromFile(path);
427431

428432
const sha256 = createHash('sha256').update(certDer).digest('hex');
429-
const subj = Buffer.from(cert.subject.toSchema().valueBlock.toBER()).toString('hex');
433+
const subj = asn1ValueToDer(certSubjectToAsn1(cert)).toHex();
430434
const tset = Buffer.from(
431435
`<?xml version="1.0" encoding="UTF-8"?>
432436
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -445,6 +449,7 @@ export const iosApi = <RunTarget extends SupportedRunTarget<'ios'>>(
445449
throw new Error('SSH is required for removing a certificate authority.');
446450

447451
const { certDer } = await parsePemCertificateFromFile(path);
452+
448453
const sha256 = createHash('sha256').update(certDer).digest('hex');
449454

450455
await this._internal.ssh(

src/types/forge.d.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type forge from 'node-forge';
2+
3+
declare module 'node-forge' {
4+
namespace pki {
5+
function distinguishedNameToAsn1(dn: forge.pki.Certificate['subject' | 'issuer']): forge.pki.Asn1;
6+
}
7+
}

0 commit comments

Comments
 (0)