Generate keys for signing hidden Magisk Manager

This commit is contained in:
topjohnwu 2019-10-20 06:56:33 -04:00
parent a02493fbaa
commit 325d9a0b86
6 changed files with 196 additions and 42 deletions

View File

@ -32,8 +32,9 @@ object Config : PreferenceModel, DBConfig {
const val ROOT_ACCESS = "root_access" const val ROOT_ACCESS = "root_access"
const val SU_MULTIUSER_MODE = "multiuser_mode" const val SU_MULTIUSER_MODE = "multiuser_mode"
const val SU_MNT_NS = "mnt_ns" const val SU_MNT_NS = "mnt_ns"
const val SU_MANAGER = "requester"
const val SU_FINGERPRINT = "su_fingerprint" const val SU_FINGERPRINT = "su_fingerprint"
const val SU_MANAGER = "requester"
const val KEYSTORE = "keystore"
// prefs // prefs
const val SU_REQUEST_TIMEOUT = "su_request_timeout" const val SU_REQUEST_TIMEOUT = "su_request_timeout"
@ -123,6 +124,7 @@ object Config : PreferenceModel, DBConfig {
var suMultiuserMode by dbSettings(Key.SU_MULTIUSER_MODE, Value.MULTIUSER_MODE_OWNER_ONLY) var suMultiuserMode by dbSettings(Key.SU_MULTIUSER_MODE, Value.MULTIUSER_MODE_OWNER_ONLY)
var suFingerprint by dbSettings(Key.SU_FINGERPRINT, false) var suFingerprint by dbSettings(Key.SU_FINGERPRINT, false)
var suManager by dbStrings(Key.SU_MANAGER, "", true) var suManager by dbStrings(Key.SU_MANAGER, "", true)
var keyStoreRaw by dbStrings(Key.KEYSTORE, "", true)
// Always return a path in external storage where we can write // Always return a path in external storage where we can write
val downloadDirectory get() = val downloadDirectory get() =

View File

@ -0,0 +1,133 @@
package com.topjohnwu.magisk.utils
import android.content.pm.PackageManager
import android.util.Base64
import android.util.Base64OutputStream
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.di.koinModules
import com.topjohnwu.signing.CryptoUtils.readCertificate
import com.topjohnwu.signing.CryptoUtils.readPrivateKey
import com.topjohnwu.superuser.internal.InternalUtils
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
import org.koin.core.context.GlobalContext
import org.koin.core.context.startKoin
import timber.log.Timber
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.math.BigInteger
import java.security.KeyPairGenerator
import java.security.KeyStore
import java.security.MessageDigest
import java.security.PrivateKey
import java.security.cert.X509Certificate
import java.util.*
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
private interface CertKeyProvider {
val cert: X509Certificate
val key: PrivateKey
}
@Suppress("DEPRECATION")
object Keygen: CertKeyProvider {
private const val ALIAS = "magisk"
private val PASSWORD = "magisk".toCharArray()
private const val TESTKEY_CERT = "61ed377e85d386a8dfee6b864bd85b0bfaa5af81"
private val start get() = Calendar.getInstance()
private val end get() = Calendar.getInstance().apply {
add(Calendar.YEAR, 20)
}
override val cert get() = provider.cert
override val key get() = provider.key
private val provider: CertKeyProvider
class KeyStoreProvider : CertKeyProvider {
private val ks by lazy { init() }
override val cert by lazy { ks.getCertificate(ALIAS) as X509Certificate }
override val key by lazy { ks.getKey(ALIAS, PASSWORD) as PrivateKey }
}
class TestProvider : CertKeyProvider {
override val cert by lazy {
readCertificate(javaClass.getResourceAsStream("/keys/testkey.x509.pem"))
}
override val key by lazy {
readPrivateKey(javaClass.getResourceAsStream("/keys/testkey.pk8"))
}
}
init {
// This object could possibly be accessed from an external app
// Get context from reflection into Android's framework
val context = InternalUtils.getContext()
val pm = context.packageManager
val info = pm.getPackageInfo(context.packageName, PackageManager.GET_SIGNATURES)
val sig = info.signatures[0]
val digest = MessageDigest.getInstance("SHA1")
val chksum = digest.digest(sig.toByteArray())
val sb = StringBuilder()
for (b in chksum) {
sb.append("%02x".format(0xFF and b.toInt()))
}
provider = if (sb.toString() == TESTKEY_CERT) {
// The app was signed by the test key, continue to use it (legacy mode)
TestProvider()
} else {
KeyStoreProvider()
}
}
private fun init(): KeyStore {
GlobalContext.getOrNull() ?: {
// Invoked externally, do some basic initialization
startKoin {
modules(koinModules)
}
Timber.plant(Timber.DebugTree())
}()
val raw = Config.keyStoreRaw
val ks = KeyStore.getInstance("PKCS12")
if (raw.isEmpty()) {
ks.load(null)
} else {
GZIPInputStream(ByteArrayInputStream(
Base64.decode(raw, Base64.NO_PADDING or Base64.NO_WRAP)
)).use {
ks.load(it, PASSWORD)
}
}
// Keys already exist
if (ks.containsAlias(ALIAS))
return ks
// Generate new private key and certificate
val kp = KeyPairGenerator.getInstance("RSA").apply { initialize(2048) }.genKeyPair()
val dn = X500Name("CN=Magisk")
val builder = JcaX509v3CertificateBuilder(dn,
BigInteger.valueOf(start.timeInMillis), start.time, end.time, dn, kp.public)
val signer = JcaContentSignerBuilder("SHA256WithRSA").build(kp.private)
val cert = JcaX509CertificateConverter().getCertificate(builder.build(signer))
// Store them into keystore
ks.setKeyEntry(ALIAS, kp.private, PASSWORD, arrayOf(cert))
val bytes = ByteArrayOutputStream()
GZIPOutputStream(Base64OutputStream(bytes, Base64.NO_PADDING or Base64.NO_WRAP)).use {
ks.store(it, PASSWORD)
}
Config.keyStoreRaw = bytes.toString()
return ks
}
}

View File

@ -107,7 +107,7 @@ object PatchAPK {
// Write apk changes // Write apk changes
jar.getOutputStream(je).write(xml) jar.getOutputStream(je).write(xml)
SignAPK.sign(jar, FileOutputStream(out).buffered()) SignAPK.sign(Keygen.cert, Keygen.key, jar, FileOutputStream(out).buffered())
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e) Timber.e(e)
return false return false

View File

@ -24,7 +24,7 @@ import java.security.spec.PKCS8EncodedKeySpec;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
class CryptoUtils { public class CryptoUtils {
static final Map<String, String> ID_TO_ALG; static final Map<String, String> ID_TO_ALG;
static final Map<String, String> ALG_TO_ID; static final Map<String, String> ALG_TO_ID;
@ -81,7 +81,7 @@ class CryptoUtils {
return new AlgorithmIdentifier(new ASN1ObjectIdentifier(id)); return new AlgorithmIdentifier(new ASN1ObjectIdentifier(id));
} }
static X509Certificate readCertificate(InputStream input) public static X509Certificate readCertificate(InputStream input)
throws IOException, GeneralSecurityException { throws IOException, GeneralSecurityException {
try { try {
CertificateFactory cf = CertificateFactory.getInstance("X.509"); CertificateFactory cf = CertificateFactory.getInstance("X.509");
@ -92,7 +92,7 @@ class CryptoUtils {
} }
/** Read a PKCS#8 format private key. */ /** Read a PKCS#8 format private key. */
static PrivateKey readPrivateKey(InputStream input) public static PrivateKey readPrivateKey(InputStream input)
throws IOException, GeneralSecurityException { throws IOException, GeneralSecurityException {
try { try {
ByteArrayStream buf = new ByteArrayStream(); ByteArrayStream buf = new ByteArrayStream();

View File

@ -30,7 +30,6 @@ import java.io.PrintStream;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import java.security.DigestOutputStream; import java.security.DigestOutputStream;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateEncodingException;
@ -61,29 +60,8 @@ public class SignAPK {
private static final int USE_SHA1 = 1; private static final int USE_SHA1 = 1;
private static final int USE_SHA256 = 2; private static final int USE_SHA256 = 2;
public static void sign(JarMap input, OutputStream output) throws Exception { public static void signAndAdjust(X509Certificate cert, PrivateKey key,
sign(SignAPK.class.getResourceAsStream("/keys/testkey.x509.pem"), JarMap input, OutputStream output) throws Exception {
SignAPK.class.getResourceAsStream("/keys/testkey.pk8"), input, output);
}
public static void sign(InputStream certIs, InputStream keyIs,
JarMap input, OutputStream output) throws Exception {
X509Certificate cert = CryptoUtils.readCertificate(certIs);
PrivateKey key = CryptoUtils.readPrivateKey(keyIs);
sign(cert, key, input, output);
}
public static void sign(InputStream jks, String keyStorePass, String alias, String keyPass,
JarMap input, OutputStream output) throws Exception {
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(jks, keyStorePass.toCharArray());
X509Certificate cert = (X509Certificate) ks.getCertificate(alias);
PrivateKey key = (PrivateKey) ks.getKey(alias, keyPass.toCharArray());
sign(cert, key, input, output);
}
private static void sign(X509Certificate cert, PrivateKey key,
JarMap input, OutputStream output) throws Exception {
File temp1 = File.createTempFile("signAPK", null); File temp1 = File.createTempFile("signAPK", null);
File temp2 = File.createTempFile("signAPK", null); File temp2 = File.createTempFile("signAPK", null);
@ -103,6 +81,11 @@ public class SignAPK {
} }
} }
public static void sign(X509Certificate cert, PrivateKey key,
JarMap input, OutputStream output) throws Exception {
sign(cert, key, input, output, false);
}
private static void sign(X509Certificate cert, PrivateKey key, private static void sign(X509Certificate cert, PrivateKey key,
JarMap input, OutputStream output, boolean minSign) throws Exception { JarMap input, OutputStream output, boolean minSign) throws Exception {
int hashes = 0; int hashes = 0;
@ -498,11 +481,11 @@ public class SignAPK {
outputStream.close(); outputStream.close();
} }
private static void signFile(Manifest manifest, JarMap inputJar, private static void signFile(Manifest manifest, JarMap inputJar,
X509Certificate publicKey, PrivateKey privateKey, X509Certificate cert, PrivateKey privateKey,
JarOutputStream outputJar) JarOutputStream outputJar)
throws Exception { throws Exception {
// Assume the certificate is valid for at least an hour. // Assume the certificate is valid for at least an hour.
long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000; long timestamp = cert.getNotBefore().getTime() + 3600L * 1000;
// MANIFEST.MF // MANIFEST.MF
JarEntry je = new JarEntry(JarFile.MANIFEST_NAME); JarEntry je = new JarEntry(JarFile.MANIFEST_NAME);
je.setTime(timestamp); je.setTime(timestamp);
@ -512,15 +495,15 @@ public class SignAPK {
je.setTime(timestamp); je.setTime(timestamp);
outputJar.putNextEntry(je); outputJar.putNextEntry(je);
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
writeSignatureFile(manifest, baos, getDigestAlgorithm(publicKey)); writeSignatureFile(manifest, baos, getDigestAlgorithm(cert));
byte[] signedData = baos.toByteArray(); byte[] signedData = baos.toByteArray();
outputJar.write(signedData); outputJar.write(signedData);
// CERT.{EC,RSA} / CERT#.{EC,RSA} // CERT.{EC,RSA} / CERT#.{EC,RSA}
final String keyType = publicKey.getPublicKey().getAlgorithm(); final String keyType = cert.getPublicKey().getAlgorithm();
je = new JarEntry(String.format(CERT_SIG_NAME, keyType)); je = new JarEntry(String.format(CERT_SIG_NAME, keyType));
je.setTime(timestamp); je.setTime(timestamp);
outputJar.putNextEntry(je); outputJar.putNextEntry(je);
writeSignatureBlock(new CMSProcessableByteArray(signedData), writeSignatureBlock(new CMSProcessableByteArray(signedData),
publicKey, privateKey, outputJar); cert, privateKey, outputJar);
} }
} }

View File

@ -4,23 +4,61 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Security; import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
public class ZipSigner { public class ZipSigner {
public static void usage() { private static void usage() {
System.err.println("ZipSigner usage:"); System.err.println("ZipSigner usage:");
System.err.println(" zipsigner.jar input.jar output.jar"); System.err.println(" zipsigner.jar input.jar output.jar");
System.err.println(" sign jar with AOSP test keys"); System.err.println(" sign jar with AOSP test keys");
System.err.println(" zipsigner.jar x509.pem pk8 input.jar output.jar"); System.err.println(" zipsigner.jar x509.pem pk8 input.jar output.jar");
System.err.println(" sign jar with certificate / private key pair"); System.err.println(" sign jar with certificate / private key pair");
System.err.println(" zipsigner.jar jks keyStorePass keyAlias keyPass input.jar output.jar"); System.err.println(" zipsigner.jar keyStore keyStorePass alias keyPass input.jar output.jar");
System.err.println(" sign jar with Java KeyStore"); System.err.println(" sign jar with Java KeyStore");
System.exit(2); System.exit(2);
} }
private static void sign(JarMap input, OutputStream output) throws Exception {
sign(SignAPK.class.getResourceAsStream("/keys/testkey.x509.pem"),
SignAPK.class.getResourceAsStream("/keys/testkey.pk8"), input, output);
}
private static void sign(InputStream certIs, InputStream keyIs,
JarMap input, OutputStream output) throws Exception {
X509Certificate cert = CryptoUtils.readCertificate(certIs);
PrivateKey key = CryptoUtils.readPrivateKey(keyIs);
SignAPK.signAndAdjust(cert, key, input, output);
}
private static void sign(String keyStore, String keyStorePass, String alias, String keyPass,
JarMap in, OutputStream out) throws Exception {
KeyStore ks;
try {
ks = KeyStore.getInstance("JKS");
try (InputStream is = new FileInputStream(keyStore)) {
ks.load(is, keyStorePass.toCharArray());
}
} catch (KeyStoreException|IOException|CertificateException|NoSuchAlgorithmException e) {
ks = KeyStore.getInstance("PKCS12");
try (InputStream is = new FileInputStream(keyStore)) {
ks.load(is, keyStorePass.toCharArray());
}
}
X509Certificate cert = (X509Certificate) ks.getCertificate(alias);
PrivateKey key = (PrivateKey) ks.getKey(alias, keyPass.toCharArray());
SignAPK.signAndAdjust(cert, key, in, out);
}
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
if (args.length != 2 && args.length != 4 && args.length != 6) if (args.length != 2 && args.length != 4 && args.length != 6)
usage(); usage();
@ -30,16 +68,14 @@ public class ZipSigner {
try (JarMap in = new JarMap(args[args.length - 2], false); try (JarMap in = new JarMap(args[args.length - 2], false);
OutputStream out = new FileOutputStream(args[args.length - 1])) { OutputStream out = new FileOutputStream(args[args.length - 1])) {
if (args.length == 2) { if (args.length == 2) {
SignAPK.sign(in, out); sign(in, out);
} else if (args.length == 4) { } else if (args.length == 4) {
try (InputStream cert = new FileInputStream(args[0]); try (InputStream cert = new FileInputStream(args[0]);
InputStream key = new FileInputStream(args[1])) { InputStream key = new FileInputStream(args[1])) {
SignAPK.sign(cert, key, in, out); sign(cert, key, in, out);
} }
} else if (args.length == 6) { } else if (args.length == 6) {
try (InputStream jks = new FileInputStream(args[0])) { sign(args[0], args[1], args[2], args[3], in, out);
SignAPK.sign(jks, args[1], args[2], args[3], in, out);
}
} }
} }
} }