Skip to content

Commit

Permalink
Fixes #83: Replace pkijs with forge
Browse files Browse the repository at this point in the history
  • Loading branch information
zner0L committed Jun 1, 2023
1 parent 74d9ef0 commit 2477fb1
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 231 deletions.
8 changes: 4 additions & 4 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ An ID of a known permission on Android.

#### Defined in

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

___

Expand Down Expand Up @@ -112,7 +112,7 @@ An ID of a known permission on iOS.

#### Defined in

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

___

Expand Down Expand Up @@ -320,7 +320,7 @@ The IDs of known permissions on Android.

#### Defined in

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

___

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

#### Defined in

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

## Functions

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@
"@napi-rs/lzma": "^1.1.2",
"andromatic": "^1.0.0",
"autopy": "1.0.0",
"asn1js": "^3.0.5",
"bplist-creator": "^0.1.1",
"bplist-parser": "^0.3.2",
"cross-fetch": "^3.1.5",
Expand All @@ -65,9 +64,9 @@
"fs-extra": "^11.1.0",
"global-cache-dir": "^5.0.0",
"ipa-extract-info": "^1.2.6",
"node-forge": "^1.3.1",
"node-ssh": "^13.1.0",
"p-retry": "^5.1.2",
"pkijs": "^3.0.14",
"semver": "^7.3.8",
"tempy": "^3.0.0",
"ts-node": "^10.9.1",
Expand All @@ -81,6 +80,7 @@
"@parcel/transformer-typescript-types": "2.8.2",
"@types/fs-extra": "^11.0.0",
"@types/node": "^18.11.18",
"@types/node-forge": "^1.3.2",
"@types/plist": "^3.0.2",
"@types/promise-timeout": "^1.3.0",
"@types/semver": "^7.3.13",
Expand Down
15 changes: 6 additions & 9 deletions src/android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { createHash } from 'crypto';
import { fileTypeFromFile } from 'file-type';
import frida from 'frida';
import { open, rm, writeFile } from 'fs/promises';
import forge from 'node-forge';
import pRetry from 'p-retry';
import { basename } from 'path';
import { major as semverMajor, minVersion as semverMinVersion } from 'semver';
Expand All @@ -21,15 +22,9 @@ import type {
import { dependencies } from '../package.json';
import { venvOptions } from '../scripts/common/python';
import type { ParametersExceptFirst, XapkManifest } from './utils';
import {
asyncUnimplemented,
getObjFromFridaScript,
isRecord,
parseAppMeta,
retryCondition,
} from './utils';
import { asyncUnimplemented, getObjFromFridaScript, isRecord, parseAppMeta, retryCondition } from './utils';
import { certSubjectToAsn1, parsePemCertificateFromFile } from './utils/crypto';
import { forEachInZip, getFileFromZip, tmpFileFromZipEntry } from './utils/zip';
import { parsePemCertificateFromFile} from './utils/crypto';

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

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

Expand Down
63 changes: 34 additions & 29 deletions src/ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@ import { parseFile } from 'bplist-parser';
import { createHash } from 'crypto';
import { execa } from 'execa';
import frida from 'frida';
import { exists,mkdirp } from 'fs-extra';
import { readFile,writeFile } from 'fs/promises';
import { exists, mkdirp } from 'fs-extra';
import { readFile, writeFile } from 'fs/promises';
import globalCacheDir from 'global-cache-dir';
import { NodeSSH } from 'node-ssh';
import { join } from 'path';
import type { PlatformApi,PlatformApiOptions,Proxy,SupportedCapability,SupportedRunTarget } from '.';
import type { PlatformApi, PlatformApiOptions, Proxy, SupportedCapability, SupportedRunTarget } from '.';
import { venvOptions } from '../scripts/common/python';
import { asyncUnimplemented,getObjFromFridaScript,isRecord,retryCondition } from './utils';
import { asyncUnimplemented, getObjFromFridaScript, isRecord, retryCondition } from './utils';
import {
arrayBufferToPem,
asn1ValueToDer,
certificateFingerprint,
certificateHasExpired,
certSubjectToAsn1,
createPkcs12Container,
generateCertificate,
parsePemCertificateFromFile,
pemToArrayBuffer,
Expand Down Expand Up @@ -170,53 +173,54 @@ export const iosApi = <RunTarget extends SupportedRunTarget<'ios'>>(
if (!plist) throw new Error('Failed to ensure supervision mode: Invalid CloudConfiguration.');

let hostCert;
let hostKey;

if (
(await exists(join(cacheDir, 'ios', 'supervisorCert.pem'))) &&
(await exists(join(cacheDir, 'ios', 'supervisorPrivateKey.pem')))
(await exists(join(cacheDir, 'ios', 'supervisorKeyStore.p12')))
) {
hostCert = pemToArrayBuffer((await readFile(join(cacheDir, 'ios', 'supervisorCert.pem'))).toString());
hostKey = pemToArrayBuffer(
(await readFile(join(cacheDir, 'ios', 'supervisorPrivateKey.pem'))).toString()
);
hostCert = (await readFile(join(cacheDir, 'ios', 'supervisorCert.pem'))).toString();

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

// Test if the current host certificate is already controlling the device.
if (
plist.IsSupervised &&
plist.SupervisorHostCertificates &&
plist.SupervisorHostCertificates.length > 0 &&
plist.SupervisorHostCertificates.some(
async (cert) => (await certificateFingerprint(cert)) === hostCertFingerprint
try {
// Test if the current host certificate is already controlling the device.
if (
plist.IsSupervised &&
plist.SupervisorHostCertificates &&
plist.SupervisorHostCertificates.length > 0 &&
plist.SupervisorHostCertificates.some(
(cert) =>
certificateFingerprint(arrayBufferToPem(cert, 'CERTIFICATE')) ===
hostCertFingerprint
)
)
)
return;
return;
} catch (e) {
// The certificate is invalid, so we need to generate a new one.
hostCert = undefined;
}
} else {
hostCert = undefined;
hostKey = undefined;
}
}

if (!hostCert || !hostKey) {
if (!hostCert) {
// We have no exsiting keys, so let’s generate one.
const generated = await generateCertificate(OrganizationName);
hostCert = generated.certificate;
hostKey = generated.privateKey;
const hostKey = generated.privateKey;

const keyStore = createPkcs12Container(hostCert, hostKey, 'appstraction');

await mkdirp(join(cacheDir, 'ios'));
await writeFile(join(cacheDir, 'ios', 'supervisorCert.pem'), arrayBufferToPem(hostCert, 'CERTIFICATE'));
await writeFile(
join(cacheDir, 'ios', 'supervisorPrivateKey.pem'),
arrayBufferToPem(hostKey, 'PRIVATE KEY')
);
await writeFile(join(cacheDir, 'ios', 'supervisorCert.pem'), hostCert);
await writeFile(join(cacheDir, 'ios', 'supervisorKeyStore.p12'), Buffer.from(keyStore.toHex(), 'hex'));
}

const newPlist = {
...plist,
SupervisorHostCertificates: [Buffer.from(hostCert)],
SupervisorHostCertificates: [Buffer.from(pemToArrayBuffer(hostCert))],
IsSupervised: true,
OrganizationName,
AllowPairing: true,
Expand Down Expand Up @@ -426,7 +430,7 @@ export const iosApi = <RunTarget extends SupportedRunTarget<'ios'>>(
const { cert, certDer } = await parsePemCertificateFromFile(path);

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

const { certDer } = await parsePemCertificateFromFile(path);

const sha256 = createHash('sha256').update(certDer).digest('hex');

await this._internal.ssh(
Expand Down
7 changes: 7 additions & 0 deletions src/types/forge.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type forge from 'node-forge';

declare module 'node-forge' {
namespace pki {
function distinguishedNameToAsn1(dn: forge.pki.Certificate['subject' | 'issuer']): forge.pki.Asn1;
}
}
Loading

0 comments on commit 2477fb1

Please sign in to comment.