diff --git a/api/revanced-library.api b/api/revanced-library.api index c6ff752..91e2553 100644 --- a/api/revanced-library.api +++ b/api/revanced-library.api @@ -1,6 +1,8 @@ public final class app/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 (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 newKeyStore (Ljava/io/OutputStream;Ljava/lang/String;Ljava/util/Set;)V 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 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;Ljava/io/File;)V } public final class app/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 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 { diff --git a/build.gradle.kts b/build.gradle.kts index ffcec6d..25048b2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -29,6 +29,7 @@ dependencies { implementation(libs.jadb) // Fork with Shell v2 support. implementation(libs.jackson.module.kotlin) implementation(libs.apkzlib) + implementation(libs.apksig) implementation(libs.bcpkix.jdk15on) implementation(libs.guava) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e0fc88e..04a06fc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,6 +7,7 @@ binary-compatibility-validator = "0.14.0" apkzlib = "8.3.0" bcpkix-jdk15on = "1.70" guava = "33.0.0-jre" +apksig = "8.3.0" [libraries] 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" } bcpkix-jdk15on = { module = "org.bouncycastle:bcpkix-jdk15on", version.ref = "bcpkix-jdk15on" } guava = { module = "com.google.guava:guava", version.ref = "guava" } +apksig = { module = "com.android.tools.build:apksig", version.ref = "apksig" } [plugins] binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" } diff --git a/src/main/kotlin/app/revanced/library/ApkSigner.kt b/src/main/kotlin/app/revanced/library/ApkSigner.kt index 9fe0aea..dce445e 100644 --- a/src/main/kotlin/app/revanced/library/ApkSigner.kt +++ b/src/main/kotlin/app/revanced/library/ApkSigner.kt @@ -1,5 +1,6 @@ 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.SigningOptions import com.android.tools.build.apkzlib.zip.ZFile @@ -182,6 +183,7 @@ object ApkSigner { /** * Create a new [Signer]. * + * @param signer The name of the signer. * @param privateKeyCertificatePair The private key and certificate pair to use for signing. * * @return The new [Signer]. @@ -189,6 +191,38 @@ object ApkSigner { * @see PrivateKeyCertificatePair * @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) = Signer( SigningExtension( @@ -205,6 +239,7 @@ object ApkSigner { /** * Create a new [Signer]. * + * @param signer The name of the 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. @@ -215,10 +250,36 @@ object ApkSigner { * @see Signer */ fun newApkSigner( + signer: String, keyStore: KeyStore, keyStoreEntryAlias: 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. @@ -246,23 +307,48 @@ object ApkSigner { 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. * * @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. * * @param apkZFile The APK [ZFile] to sign. */ + @Deprecated("This method will be removed in the future.") fun signApk(apkZFile: ZFile) { 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() } } } diff --git a/src/main/kotlin/app/revanced/library/ApkUtils.kt b/src/main/kotlin/app/revanced/library/ApkUtils.kt index 34bc8f9..ab73bc3 100644 --- a/src/main/kotlin/app/revanced/library/ApkUtils.kt +++ b/src/main/kotlin/app/revanced/library/ApkUtils.kt @@ -84,7 +84,7 @@ object ApkUtils { } } - logger.info("Aligning ${apkFile.name}") + logger.info("Aligning APK") 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. * * @param signingOptions The options to use for signing. */ + @Deprecated("Use sign(File, File, SigningOptions) instead.") fun File.sign(signingOptions: SigningOptions) { - // Get the keystore from the file or create a new one. - 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(), - ) - } - } + val keyStore = readOrNewKeyStore(signingOptions) + @Suppress("DEPRECATION") ApkSigner.newApkSigner( keyStore, signingOptions.alias, @@ -121,6 +131,24 @@ object ApkUtils { ).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. *