fix: Sign APKs using apksig

Previously, the signing extension from apkzlib was used incorrectly. The extension is meant to be added to a ZFile whereas on changes the extension would be used to sign. Instead the extension was added to a newly created ZFile, and without any changes, closed again, leading to no APK being signed. It turns out to be impractical to use the signing extension as we do not write an entire APK ZFile so that the signing extension can consider every file inside the ZFile, instead we just merge the patcher result to an existing ZFile. Instead use `apksig` after applying the patcher result to the ZFile which signs everything correctly.
This commit is contained in:
oSumAtrIX
2024-03-09 00:56:15 +01:00
parent c92be32607
commit f59ecbccd1
5 changed files with 141 additions and 20 deletions

View File

@@ -1,6 +1,8 @@
public final class app/revanced/library/ApkSigner { public final class app/revanced/library/ApkSigner {
public static final field INSTANCE Lapp/revanced/library/ApkSigner; public static final field INSTANCE Lapp/revanced/library/ApkSigner;
public final fun newApkSigner (Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)Lapp/revanced/library/ApkSigner$Signer; public final fun newApkSigner (Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)Lapp/revanced/library/ApkSigner$Signer;
public final fun newApkSigner (Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)Lapp/revanced/library/ApkSigner$Signer;
public final fun newApkSigner (Ljava/lang/String;Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$Signer;
public final fun newApkSigner (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$Signer; public final fun newApkSigner (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$Signer;
public final fun newKeyStore (Ljava/io/OutputStream;Ljava/lang/String;Ljava/util/Set;)V public final fun newKeyStore (Ljava/io/OutputStream;Ljava/lang/String;Ljava/util/Set;)V
public final fun newKeyStore (Ljava/util/Set;)Ljava/security/KeyStore; public final fun newKeyStore (Ljava/util/Set;)Ljava/security/KeyStore;
@@ -27,12 +29,14 @@ public final class app/revanced/library/ApkSigner$PrivateKeyCertificatePair {
public final class app/revanced/library/ApkSigner$Signer { public final class app/revanced/library/ApkSigner$Signer {
public final fun signApk (Lcom/android/tools/build/apkzlib/zip/ZFile;)V public final fun signApk (Lcom/android/tools/build/apkzlib/zip/ZFile;)V
public final fun signApk (Ljava/io/File;)V public final fun signApk (Ljava/io/File;)V
public final fun signApk (Ljava/io/File;Ljava/io/File;)V
} }
public final class app/revanced/library/ApkUtils { public final class app/revanced/library/ApkUtils {
public static final field INSTANCE Lapp/revanced/library/ApkUtils; public static final field INSTANCE Lapp/revanced/library/ApkUtils;
public final fun applyTo (Lapp/revanced/patcher/PatcherResult;Ljava/io/File;)V public final fun applyTo (Lapp/revanced/patcher/PatcherResult;Ljava/io/File;)V
public final fun sign (Ljava/io/File;Lapp/revanced/library/ApkUtils$SigningOptions;)V public final fun sign (Ljava/io/File;Lapp/revanced/library/ApkUtils$SigningOptions;)V
public final fun sign (Ljava/io/File;Ljava/io/File;Lapp/revanced/library/ApkUtils$SigningOptions;)V
} }
public final class app/revanced/library/ApkUtils$SigningOptions { public final class app/revanced/library/ApkUtils$SigningOptions {

View File

@@ -29,6 +29,7 @@ dependencies {
implementation(libs.jadb) // Fork with Shell v2 support. implementation(libs.jadb) // Fork with Shell v2 support.
implementation(libs.jackson.module.kotlin) implementation(libs.jackson.module.kotlin)
implementation(libs.apkzlib) implementation(libs.apkzlib)
implementation(libs.apksig)
implementation(libs.bcpkix.jdk15on) implementation(libs.bcpkix.jdk15on)
implementation(libs.guava) implementation(libs.guava)

View File

@@ -7,6 +7,7 @@ binary-compatibility-validator = "0.14.0"
apkzlib = "8.3.0" apkzlib = "8.3.0"
bcpkix-jdk15on = "1.70" bcpkix-jdk15on = "1.70"
guava = "33.0.0-jre" guava = "33.0.0-jre"
apksig = "8.3.0"
[libraries] [libraries]
jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson-module-kotlin" } jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson-module-kotlin" }
@@ -17,6 +18,7 @@ revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "re
apkzlib = { module = "com.android.tools.build:apkzlib", version.ref = "apkzlib" } apkzlib = { module = "com.android.tools.build:apkzlib", version.ref = "apkzlib" }
bcpkix-jdk15on = { module = "org.bouncycastle:bcpkix-jdk15on", version.ref = "bcpkix-jdk15on" } bcpkix-jdk15on = { module = "org.bouncycastle:bcpkix-jdk15on", version.ref = "bcpkix-jdk15on" }
guava = { module = "com.google.guava:guava", version.ref = "guava" } guava = { module = "com.google.guava:guava", version.ref = "guava" }
apksig = { module = "com.android.tools.build:apksig", version.ref = "apksig" }
[plugins] [plugins]
binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" } binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" }

View File

@@ -1,5 +1,6 @@
package app.revanced.library package app.revanced.library
import com.android.apksig.ApkSigner.SignerConfig
import com.android.tools.build.apkzlib.sign.SigningExtension import com.android.tools.build.apkzlib.sign.SigningExtension
import com.android.tools.build.apkzlib.sign.SigningOptions import com.android.tools.build.apkzlib.sign.SigningOptions
import com.android.tools.build.apkzlib.zip.ZFile import com.android.tools.build.apkzlib.zip.ZFile
@@ -182,6 +183,7 @@ object ApkSigner {
/** /**
* Create a new [Signer]. * Create a new [Signer].
* *
* @param signer The name of the signer.
* @param privateKeyCertificatePair The private key and certificate pair to use for signing. * @param privateKeyCertificatePair The private key and certificate pair to use for signing.
* *
* @return The new [Signer]. * @return The new [Signer].
@@ -189,6 +191,38 @@ object ApkSigner {
* @see PrivateKeyCertificatePair * @see PrivateKeyCertificatePair
* @see Signer * @see Signer
*/ */
fun newApkSigner(
signer: String,
privateKeyCertificatePair: PrivateKeyCertificatePair,
) = Signer(
com.android.apksig.ApkSigner.Builder(
listOf(
SignerConfig.Builder(
signer,
privateKeyCertificatePair.privateKey,
listOf(privateKeyCertificatePair.certificate),
).build(),
),
),
)
/**
* Create a new [Signer].
*
* @param privateKeyCertificatePair The private key and certificate pair to use for signing.
*
* @return The new [Signer].
*
* @see PrivateKeyCertificatePair
* @see Signer
*/
@Suppress("DEPRECATION")
@Deprecated(
"This method will be removed in the future.",
ReplaceWith(
"newApkSigner(\"ReVanced\", privateKeyCertificatePair)",
),
)
fun newApkSigner(privateKeyCertificatePair: PrivateKeyCertificatePair) = fun newApkSigner(privateKeyCertificatePair: PrivateKeyCertificatePair) =
Signer( Signer(
SigningExtension( SigningExtension(
@@ -205,6 +239,7 @@ object ApkSigner {
/** /**
* Create a new [Signer]. * Create a new [Signer].
* *
* @param signer The name of the signer.
* @param keyStore The keystore to use for signing. * @param keyStore The keystore to use for signing.
* @param keyStoreEntryAlias The alias of the key store entry to use for signing. * @param keyStoreEntryAlias The alias of the key store entry to use for signing.
* @param keyStoreEntryPassword The password for recovering the signing key. * @param keyStoreEntryPassword The password for recovering the signing key.
@@ -215,10 +250,36 @@ object ApkSigner {
* @see Signer * @see Signer
*/ */
fun newApkSigner( fun newApkSigner(
signer: String,
keyStore: KeyStore, keyStore: KeyStore,
keyStoreEntryAlias: String, keyStoreEntryAlias: String,
keyStoreEntryPassword: String, keyStoreEntryPassword: String,
) = newApkSigner(readKeyCertificatePair(keyStore, keyStoreEntryAlias, keyStoreEntryPassword)) ) = newApkSigner(signer, readKeyCertificatePair(keyStore, keyStoreEntryAlias, keyStoreEntryPassword))
/**
* Create a new [Signer].
*
* @param keyStore The keystore to use for signing.
* @param keyStoreEntryAlias The alias of the key store entry to use for signing.
* @param keyStoreEntryPassword The password for recovering the signing key.
*
* @return The new [Signer].
*
* @see KeyStore
* @see Signer
*/
@Deprecated(
"This method will be removed in the future.",
ReplaceWith(
"newApkSigner(\"ReVanced\", readKeyCertificatePair(keyStore, keyStoreEntryAlias, keyStoreEntryPassword))",
"app.revanced.library.ApkSigner.newApkSigner",
),
)
fun newApkSigner(
keyStore: KeyStore,
keyStoreEntryAlias: String,
keyStoreEntryPassword: String,
) = newApkSigner("ReVanced", readKeyCertificatePair(keyStore, keyStoreEntryAlias, keyStoreEntryPassword))
/** /**
* An entry in a keystore. * An entry in a keystore.
@@ -246,23 +307,48 @@ object ApkSigner {
val certificate: X509Certificate, val certificate: X509Certificate,
) )
class Signer internal constructor(private val signingExtension: SigningExtension) { class Signer {
private val signerBuilder: com.android.apksig.ApkSigner.Builder?
private val signingExtension: SigningExtension?
internal constructor(signerBuilder: com.android.apksig.ApkSigner.Builder) {
this.signerBuilder = signerBuilder
signingExtension = null
}
@Deprecated("This constructor will be removed in the future.")
internal constructor(signingExtension: SigningExtension) {
signerBuilder = null
this.signingExtension = signingExtension
}
/** /**
* Sign an APK file. * Sign an APK file.
* *
* @param apkFile The APK file to sign. * @param apkFile The APK file to sign.
*/ */
fun signApk(apkFile: File) = ZFile.openReadWrite(apkFile).use { signApk(it) } @Deprecated("This method will be removed in the future.")
fun signApk(apkFile: File) = ZFile.openReadWrite(apkFile).use {
@Suppress("DEPRECATION")
signApk(it)
}
/** /**
* Sign an APK file. * Sign an APK file.
* *
* @param apkZFile The APK [ZFile] to sign. * @param apkZFile The APK [ZFile] to sign.
*/ */
@Deprecated("This method will be removed in the future.")
fun signApk(apkZFile: ZFile) { fun signApk(apkZFile: ZFile) {
logger.info("Signing ${apkZFile.file.name}") logger.info("Signing ${apkZFile.file.name}")
signingExtension.register(apkZFile) signingExtension?.register(apkZFile)
}
fun signApk(inputApkFile: File, outputApkFile: File) {
logger.info("Signing APK")
signerBuilder?.setInputApk(inputApkFile)?.setOutputApk(outputApkFile)?.build()?.sign()
} }
} }
} }

View File

@@ -84,7 +84,7 @@ object ApkUtils {
} }
} }
logger.info("Aligning ${apkFile.name}") logger.info("Aligning APK")
targetApkZFile.realign() targetApkZFile.realign()
@@ -92,28 +92,38 @@ object ApkUtils {
} }
} }
/**
* Reads an existing or creates a new keystore.
*
* @param signingOptions The options to use for signing.
*/
private fun readOrNewKeyStore(signingOptions: SigningOptions) = if (signingOptions.keyStore.exists()) {
ApkSigner.readKeyStore(
signingOptions.keyStore.inputStream(),
signingOptions.keyStorePassword ?: "",
)
} else {
val entry = ApkSigner.KeyStoreEntry(signingOptions.alias, signingOptions.password)
// Create a new keystore with a new keypair and saves it.
ApkSigner.newKeyStore(setOf(entry)).apply {
store(
signingOptions.keyStore.outputStream(),
signingOptions.keyStorePassword?.toCharArray(),
)
}
}
/** /**
* Signs the apk file with the given options. * Signs the apk file with the given options.
* *
* @param signingOptions The options to use for signing. * @param signingOptions The options to use for signing.
*/ */
@Deprecated("Use sign(File, File, SigningOptions) instead.")
fun File.sign(signingOptions: SigningOptions) { fun File.sign(signingOptions: SigningOptions) {
// Get the keystore from the file or create a new one. val keyStore = readOrNewKeyStore(signingOptions)
val keyStore =
if (signingOptions.keyStore.exists()) {
ApkSigner.readKeyStore(signingOptions.keyStore.inputStream(), signingOptions.keyStorePassword ?: "")
} else {
val entries = setOf(ApkSigner.KeyStoreEntry(signingOptions.alias, signingOptions.password))
// Create a new keystore with a new keypair and saves it.
ApkSigner.newKeyStore(entries).apply {
store(
signingOptions.keyStore.outputStream(),
signingOptions.keyStorePassword?.toCharArray(),
)
}
}
@Suppress("DEPRECATION")
ApkSigner.newApkSigner( ApkSigner.newApkSigner(
keyStore, keyStore,
signingOptions.alias, signingOptions.alias,
@@ -121,6 +131,24 @@ object ApkUtils {
).signApk(this) ).signApk(this)
} }
/**
* Signs [inputApkFile] with the given options and saves the signed apk to [outputApkFile].
*
* @param inputApkFile The apk file to sign.
* @param outputApkFile The file to save the signed apk to.
* @param signingOptions The options to use for signing.
*/
fun sign(inputApkFile: File, outputApkFile: File, signingOptions: SigningOptions) {
val keyStore = readOrNewKeyStore(signingOptions)
ApkSigner.newApkSigner(
signingOptions.signer,
keyStore,
signingOptions.alias,
signingOptions.password,
).signApk(inputApkFile, outputApkFile)
}
/** /**
* Options for signing an apk. * Options for signing an apk.
* *