Commit 2e95d9f0 authored by vvb2060's avatar vvb2060 Committed by John Wu

Update to APK Signature Scheme v2

parent fe238839
......@@ -6,7 +6,6 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
......@@ -28,20 +27,20 @@ public class ZipSigner {
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(JarMap input, FileOutputStream 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 {
JarMap input, FileOutputStream output) throws Exception {
X509Certificate cert = CryptoUtils.readCertificate(certIs);
PrivateKey key = CryptoUtils.readPrivateKey(keyIs);
SignAPK.signAndAdjust(cert, key, input, output);
SignApk.sign(cert, key, input, output);
}
private static void sign(String keyStore, String keyStorePass, String alias, String keyPass,
JarMap in, OutputStream out) throws Exception {
JarMap in, FileOutputStream out) throws Exception {
KeyStore ks;
try {
ks = KeyStore.getInstance("JKS");
......@@ -56,7 +55,7 @@ public class ZipSigner {
}
X509Certificate cert = (X509Certificate) ks.getCertificate(alias);
PrivateKey key = (PrivateKey) ks.getKey(alias, keyPass.toCharArray());
SignAPK.signAndAdjust(cert, key, in, out);
SignApk.sign(cert, key, in, out);
}
public static void main(String[] args) throws Exception {
......@@ -66,7 +65,7 @@ public class ZipSigner {
Security.insertProviderAt(new BouncyCastleProvider(), 1);
try (JarMap in = JarMap.open(args[args.length - 2], false);
OutputStream out = new FileOutputStream(args[args.length - 1])) {
FileOutputStream out = new FileOutputStream(args[args.length - 1])) {
if (args.length == 2) {
sign(in, out);
} else if (args.length == 4) {
......
package com.topjohnwu.signing;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* Assorted ZIP format helpers.
*
* <p>NOTE: Most helper methods operating on {@code ByteBuffer} instances expect that the byte
* order of these buffers is little-endian.
*/
public abstract class ZipUtils {
private static final int ZIP_EOCD_REC_MIN_SIZE = 22;
private static final int ZIP_EOCD_REC_SIG = 0x06054b50;
private static final int ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET = 12;
private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET = 16;
private static final int ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET = 20;
private static final int ZIP64_EOCD_LOCATOR_SIZE = 20;
private static final int ZIP64_EOCD_LOCATOR_SIG = 0x07064b50;
private static final int UINT16_MAX_VALUE = 0xffff;
private ZipUtils() {
}
/**
* Returns the position at which ZIP End of Central Directory record starts in the provided
* buffer or {@code -1} if the record is not present.
*
* <p>NOTE: Byte order of {@code zipContents} must be little-endian.
*/
public static int findZipEndOfCentralDirectoryRecord(ByteBuffer zipContents) {
assertByteOrderLittleEndian(zipContents);
// ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive.
// The record can be identified by its 4-byte signature/magic which is located at the very
// beginning of the record. A complication is that the record is variable-length because of
// the comment field.
// The algorithm for locating the ZIP EOCD record is as follows. We search backwards from
// end of the buffer for the EOCD record signature. Whenever we find a signature, we check
// the candidate record's comment length is such that the remainder of the record takes up
// exactly the remaining bytes in the buffer. The search is bounded because the maximum
// size of the comment field is 65535 bytes because the field is an unsigned 16-bit number.
int archiveSize = zipContents.capacity();
if (archiveSize < ZIP_EOCD_REC_MIN_SIZE) {
return -1;
}
int maxCommentLength = Math.min(archiveSize - ZIP_EOCD_REC_MIN_SIZE, UINT16_MAX_VALUE);
int eocdWithEmptyCommentStartPosition = archiveSize - ZIP_EOCD_REC_MIN_SIZE;
for (int expectedCommentLength = 0; expectedCommentLength < maxCommentLength; expectedCommentLength++) {
int eocdStartPos = eocdWithEmptyCommentStartPosition - expectedCommentLength;
if (zipContents.getInt(eocdStartPos) == ZIP_EOCD_REC_SIG) {
int actualCommentLength = getUnsignedInt16(zipContents, eocdStartPos + ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET);
if (actualCommentLength == expectedCommentLength) {
return eocdStartPos;
}
}
}
return -1;
}
/**
* Returns {@code true} if the provided buffer contains a ZIP64 End of Central Directory
* Locator.
*
* <p>NOTE: Byte order of {@code zipContents} must be little-endian.
*/
public static boolean isZip64EndOfCentralDirectoryLocatorPresent(ByteBuffer zipContents, int zipEndOfCentralDirectoryPosition) {
assertByteOrderLittleEndian(zipContents);
// ZIP64 End of Central Directory Locator immediately precedes the ZIP End of Central
// Directory Record.
int locatorPosition = zipEndOfCentralDirectoryPosition - ZIP64_EOCD_LOCATOR_SIZE;
if (locatorPosition < 0) {
return false;
}
return zipContents.getInt(locatorPosition) == ZIP64_EOCD_LOCATOR_SIG;
}
/**
* Returns the offset of the start of the ZIP Central Directory in the archive.
*
* <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
*/
public static long getZipEocdCentralDirectoryOffset(ByteBuffer zipEndOfCentralDirectory) {
assertByteOrderLittleEndian(zipEndOfCentralDirectory);
return getUnsignedInt32(zipEndOfCentralDirectory, zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET);
}
/**
* Sets the offset of the start of the ZIP Central Directory in the archive.
*
* <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
*/
public static void setZipEocdCentralDirectoryOffset(ByteBuffer zipEndOfCentralDirectory, long offset) {
assertByteOrderLittleEndian(zipEndOfCentralDirectory);
setUnsignedInt32(zipEndOfCentralDirectory, zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET, offset);
}
/**
* Returns the size (in bytes) of the ZIP Central Directory.
*
* <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
*/
public static long getZipEocdCentralDirectorySizeBytes(ByteBuffer zipEndOfCentralDirectory) {
assertByteOrderLittleEndian(zipEndOfCentralDirectory);
return getUnsignedInt32(zipEndOfCentralDirectory, zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET);
}
private static void assertByteOrderLittleEndian(ByteBuffer buffer) {
if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
}
}
private static int getUnsignedInt16(ByteBuffer buffer, int offset) {
return buffer.getShort(offset) & 0xffff;
}
private static long getUnsignedInt32(ByteBuffer buffer, int offset) {
return buffer.getInt(offset) & 0xffffffffL;
}
private static void setUnsignedInt32(ByteBuffer buffer, int offset, long value) {
if ((value < 0) || (value > 0xffffffffL)) {
throw new IllegalArgumentException("uint32 value of out range: " + value);
}
buffer.putInt(buffer.position() + offset, (int) value);
}
}
......@@ -39,7 +39,7 @@ class Keygen(context: Context) : CertKeyProvider {
}
private val start = Calendar.getInstance().apply { add(Calendar.MONTH, -3) }
private val end = Calendar.getInstance().apply { add(Calendar.YEAR, 30) }
private val end = start.apply { add(Calendar.YEAR, 30) }
override val cert get() = provider.cert
override val key get() = provider.key
......
......@@ -13,7 +13,7 @@ import com.topjohnwu.magisk.data.network.GithubRawServices
import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.signing.JarMap
import com.topjohnwu.signing.SignAPK
import com.topjohnwu.signing.SignApk
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
......@@ -29,10 +29,8 @@ import java.security.SecureRandom
object PatchAPK {
private const val ALPHA = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
private const val DIGITS = "0123456789"
const val ALPHANUM = ALPHA + DIGITS
private const val ALPHANUMDOTS = "$ALPHANUM............"
private const val ALPHA = "abcdefghijklmnopqrstuvwxyz"
private const val ALPHADOTS = "$ALPHA....."
private const val APP_ID = "com.topjohnwu.magisk"
private const val APP_NAME = "Magisk Manager"
......@@ -48,7 +46,7 @@ object PatchAPK {
next = if (prev == '.' || i == len - 1) {
ALPHA[random.nextInt(ALPHA.length)]
} else {
ALPHANUMDOTS[random.nextInt(ALPHANUMDOTS.length)]
ALPHADOTS[random.nextInt(ALPHADOTS.length)]
}
builder.append(next)
prev = next
......@@ -96,7 +94,7 @@ object PatchAPK {
// Write apk changes
jar.getOutputStream(je).write(xml)
val keys = Keygen(get())
SignAPK.sign(keys.cert, keys.key, jar, FileOutputStream(out).buffered())
SignApk.sign(keys.cert, keys.key, jar, FileOutputStream(out))
} catch (e: Exception) {
Timber.e(e)
return false
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment