appium-adb
Version:
Android Debug Bridge interface
163 lines (150 loc) • 5.62 kB
JavaScript
import { exec } from 'teen_process';
import path from 'path';
import log from '../logger.js';
import { tempDir, system, mkdirp, fs } from 'appium-support';
import AdmZip from 'adm-zip';
import { getJavaForOs, getJavaHome } from '../helpers.js';
let apkSigningMethods = {};
apkSigningMethods.signWithDefaultCert = async function (apk) {
const java = getJavaForOs();
let signPath = path.resolve(this.helperJarPath, 'sign.jar');
log.debug("Resigning apk.");
try {
if (!(await fs.exists(apk))) {
throw new Error(`${apk} file doesn't exist.`);
}
await exec(java, ['-jar', signPath, apk, '--override']);
} catch (e) {
log.errorAndThrow(`Could not sign with default ceritficate. Original error ${e.message}`);
}
};
apkSigningMethods.signWithCustomCert = async function (apk) {
log.debug(`Signing '${apk}' with custom cert`);
const java = getJavaForOs();
let javaHome = getJavaHome();
let jarsigner = path.resolve(javaHome, 'bin', 'jarsigner');
if (system.isWindows()) {
jarsigner = jarsigner + '.exe';
}
if (!(await fs.exists(this.keystorePath))) {
throw new Error(`Keystore: ${this.keystorePath} doesn't exist.`);
}
if (!(await fs.exists(apk))) {
throw new Error(`${apk} file doesn't exist.`);
}
try {
log.debug("Unsigning apk.");
await exec(java, ['-jar', path.resolve(this.helperJarPath, 'unsign.jar'), apk]);
log.debug("Signing apk.");
await exec(jarsigner, ['-sigalg', 'MD5withRSA', '-digestalg', 'SHA1',
'-keystore', this.keystorePath, '-storepass', this.keystorePassword,
'-keypass', this.keyPassword, apk, this.keyAlias]);
} catch (e) {
log.errorAndThrow(`Could not sign with custom ceritficate. Original error ${e.message}`);
}
};
apkSigningMethods.sign = async function (apk) {
if (this.useKeystore) {
await this.signWithCustomCert(apk);
} else {
await this.signWithDefaultCert(apk);
}
await this.zipAlignApk(apk);
};
apkSigningMethods.zipAlignApk = async function (apk) {
log.debug(`Zip-aligning '${apk}'`);
await this.initZipAlign();
let alignedApk = await tempDir.path({prefix: 'appium', suffix: '.tmp'});
await mkdirp(path.dirname(alignedApk));
log.debug("Zip-aligning apk.");
try {
await exec(this.binaries.zipalign, ['-f', '4', apk, alignedApk]);
await fs.mv(alignedApk, apk, { mkdirp: true });
} catch (e) {
log.errorAndThrow(`zipAlignApk failed. Original error: ${e.message}`);
}
};
// returns true when already signed, false otherwise.
apkSigningMethods.checkApkCert = async function (apk, pkg) {
const java = getJavaForOs();
if (!(await fs.exists(apk))) {
log.debug(`APK doesn't exist. ${apk}`);
return false;
}
if (this.useKeystore) {
return await this.checkCustomApkCert(apk, pkg);
}
log.debug(`Checking app cert for ${apk}.`);
try {
await exec(java, ['-jar', path.resolve(this.helperJarPath, 'verify.jar'), apk]);
log.debug("App already signed.");
await this.zipAlignApk(apk);
return true;
} catch (e) {
log.debug("App not signed with debug cert.");
return false;
}
};
apkSigningMethods.checkCustomApkCert = async function (apk, pkg) {
log.debug(`Checking custom app cert for ${apk}`);
let h = "a-fA-F0-9";
let md5Str = [`.*MD5.*((?:[${h}]{2}:){15}[${h}]{2})`];
let md5 = new RegExp(md5Str, 'mi');
let javaHome = getJavaHome();
let keytool = path.resolve(javaHome, 'bin', 'keytool');
keytool = system.isWindows() ? `${keytool}.exe` : keytool;
let keystoreHash = await this.getKeystoreMd5(keytool, md5);
return await this.checkApkKeystoreMatch(keytool, md5, keystoreHash, pkg, apk);
};
apkSigningMethods.getKeystoreMd5 = async function (keytool, md5re) {
let keystoreHash;
log.debug("Printing keystore md5.");
try {
let {stdout} = await exec(keytool, ['-v', '-list', '-alias', this.keyAlias,
'-keystore', this.keystorePath, '-storepass',
this.keystorePassword]);
keystoreHash = md5re.exec(stdout);
keystoreHash = keystoreHash ? keystoreHash[1] : null;
log.debug(`Keystore MD5: ${keystoreHash}`);
return keystoreHash;
} catch (e) {
log.errorAndThrow(`getKeystoreMd5 failed. Original error: ${e.message}`);
}
};
apkSigningMethods.checkApkKeystoreMatch = async function (keytool, md5re, keystoreHash,
pkg, apk) {
let entryHash = null;
let zip = new AdmZip(apk);
let rsa = /^META-INF\/.*\.[rR][sS][aA]$/;
let entries = zip.getEntries();
for (let entry of entries) {
entry = entry.entryName;
if (!rsa.test(entry)) {
continue;
}
log.debug(`Entry: ${entry}`);
let entryPath = path.join(this.tmpDir, pkg, 'cert');
log.debug(`entryPath: ${entryPath}`);
let entryFile = path.join(entryPath, entry);
log.debug(`entryFile: ${entryFile}`);
// ensure /tmp/pkg/cert/ doesn't exist or extract will fail.
await fs.rimraf(entryPath);
// META-INF/CERT.RSA
zip.extractEntryTo(entry, entryPath, true); // overwrite = true
log.debug("extracted!");
// check for match
log.debug("Printing apk md5.");
let {stdout} = await exec(keytool, ['-v', '-printcert', '-file', entryFile]);
entryHash = md5re.exec(stdout);
entryHash = entryHash ? entryHash[1] : null;
log.debug(`entryHash MD5: ${entryHash}`);
log.debug(`keystore MD5: ${keystoreHash}`);
let matchesKeystore = entryHash && entryHash === keystoreHash;
log.debug(`Matches keystore? ${matchesKeystore}`);
if (matchesKeystore) {
return true;
}
}
return false;
};
export default apkSigningMethods;