Skip to content

Commit

Permalink
feat: Use apkzlib instead of own implementations and bump ReVanced …
Browse files Browse the repository at this point in the history
…Patcher

BREAKING CHANGE: This commit removes deprecated APIs and bumps ReVanced Patcher. Because of it's changes, `apkzlib` is now used instead of own implementations of `ZipFile`
  • Loading branch information
oSumAtrIX committed Feb 14, 2024
1 parent c664a6e commit 3aa6dc2
Show file tree
Hide file tree
Showing 12 changed files with 173 additions and 719 deletions.
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[*.{kt,kts}]
ktlint_code_style = intellij_idea
ktlint_standard_no-wildcard-imports = disabled
39 changes: 12 additions & 27 deletions api/revanced-library.api
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
public final class app/revanced/library/ApkSigner {
public static final field INSTANCE Lapp/revanced/library/ApkSigner;
public final fun newApkSignerBuilder (Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;Ljava/lang/String;Ljava/lang/String;)Lcom/android/apksig/ApkSigner$Builder;
public final fun newApkSignerBuilder (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/android/apksig/ApkSigner$Builder;
public final fun newKeyStore (Ljava/io/OutputStream;Ljava/lang/String;Ljava/util/List;)V
public final fun newKeyStore (Ljava/util/List;)Ljava/security/KeyStore;
public final fun newApkSigner (Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)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;
public final fun newPrivateKeyCertificatePair (Ljava/lang/String;Ljava/util/Date;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
public static synthetic fun newPrivateKeyCertificatePair$default (Lapp/revanced/library/ApkSigner;Ljava/lang/String;Ljava/util/Date;ILjava/lang/Object;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
public final fun readKeyCertificatePair (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
public final fun readKeyStore (Ljava/io/InputStream;Ljava/lang/String;)Ljava/security/KeyStore;
public final fun signApk (Lcom/android/apksig/ApkSigner$Builder;Ljava/io/File;Ljava/io/File;)V
}

public final class app/revanced/library/ApkSigner$KeyStoreEntry {
Expand All @@ -25,10 +24,16 @@ public final class app/revanced/library/ApkSigner$PrivateKeyCertificatePair {
public final fun getPrivateKey ()Ljava/security/PrivateKey;
}

public final class app/revanced/library/ApkSigner$Signer {
public final fun getSigningExtension ()Lcom/android/tools/build/apkzlib/sign/SigningExtension;
public final fun signApk (Lcom/android/tools/build/apkzlib/zip/ZFile;)V
public final fun signApk (Ljava/io/File;)V
}

public final class app/revanced/library/ApkUtils {
public static final field INSTANCE Lapp/revanced/library/ApkUtils;
public final fun copyAligned (Ljava/io/File;Ljava/io/File;Lapp/revanced/patcher/PatcherResult;)V
public final fun sign (Ljava/io/File;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 writePatcherResult (Ljava/io/File;Lapp/revanced/patcher/PatcherResult;)V
}

public final class app/revanced/library/ApkUtils$SigningOptions {
Expand Down Expand Up @@ -162,23 +167,3 @@ public final class app/revanced/library/logging/Logger {
public static synthetic fun setFormat$default (Lapp/revanced/library/logging/Logger;Ljava/lang/String;ILjava/lang/Object;)V
}

public final class app/revanced/library/zip/ZipFile : java/io/Closeable {
public static final field ApkZipFile Lapp/revanced/library/zip/ZipFile$ApkZipFile;
public fun <init> (Ljava/io/File;)V
public final fun addEntryCompressData (Lapp/revanced/library/zip/structures/ZipEntry;[B)V
public fun close ()V
public final fun copyEntriesFromFileAligned (Lapp/revanced/library/zip/ZipFile;Lkotlin/jvm/functions/Function1;)V
}

public final class app/revanced/library/zip/ZipFile$ApkZipFile {
public final fun getApkZipEntryAlignment ()Lkotlin/jvm/functions/Function1;
}

public final class app/revanced/library/zip/structures/ZipEntry {
public static final field Companion Lapp/revanced/library/zip/structures/ZipEntry$Companion;
public fun <init> (Ljava/lang/String;)V
}

public final class app/revanced/library/zip/structures/ZipEntry$Companion {
}

7 changes: 4 additions & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ repositories {
dependencies {
implementation(libs.revanced.patcher)
implementation(libs.kotlin.reflect)
implementation(libs.jadb) // Updated fork
implementation(libs.apksig)
implementation(libs.bcpkix.jdk18on)
implementation(libs.jadb) // Fork with Shell v2 support.
implementation(libs.jackson.module.kotlin)
implementation(libs.apkzlib)
implementation(libs.bcpkix.jdk15on)
implementation(libs.guava)

testImplementation(libs.revanced.patcher)
testImplementation(libs.kotlin.test)
Expand Down
20 changes: 11 additions & 9 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
[versions]
apksig = "8.1.4"
bcpkix-jdk18on = "1.76"
jackson-module-kotlin = "2.14.3"
jackson-module-kotlin = "2.15.0"
jadb = "1.2.1"
kotlin-reflect = "1.9.20"
kotlin-test = "1.9.20"
revanced-patcher = "19.2.0"
binary-compatibility-validator = "0.13.2"
kotlin-reflect = "1.9.22"
kotlin-test = "1.9.22"
revanced-patcher = "19.3.1"
binary-compatibility-validator = "0.14.0"
apkzlib = "8.2.2"
bcpkix-jdk15on = "1.70"
guava = "33.0.0-jre"

[libraries]
apksig = { module = "com.android.tools.build:apksig", version.ref = "apksig" }
bcpkix-jdk18on = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "bcpkix-jdk18on" }
jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson-module-kotlin" }
jadb = { module = "app.revanced:jadb", version.ref = "jadb" }
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin-reflect" }
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin-test" }
revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" }
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" }

[plugins]
binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" }
133 changes: 63 additions & 70 deletions src/main/kotlin/app/revanced/library/ApkSigner.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package app.revanced.library

import com.android.apksig.ApkSigner
import com.android.tools.build.apkzlib.sign.SigningExtension
import com.android.tools.build.apkzlib.sign.SigningOptions
import com.android.tools.build.apkzlib.zip.ZFile
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
import org.bouncycastle.cert.X509v3CertificateBuilder
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
import java.io.File
import java.io.IOException
Expand All @@ -19,23 +20,18 @@ import java.util.logging.Logger
import kotlin.time.Duration.Companion.days

/**
* Utility class for writing or reading keystore files and entries as well as signing APK files.
* Utility class for reading or writing keystore files and entries as well as signing APK files.
*/
@Suppress("MemberVisibilityCanBePrivate", "unused")
object ApkSigner {
private val logger = Logger.getLogger(app.revanced.library.ApkSigner::class.java.name)

init {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(BouncyCastleProvider())
}
}
private val logger = Logger.getLogger(Signer::class.java.name)

/**
* Create a new [PrivateKeyCertificatePair].
*
* @param commonName The common name of the certificate.
* @param validUntil The date until the certificate is valid.
*
* @return The created [PrivateKeyCertificatePair].
*/
fun newPrivateKeyCertificatePair(
Expand Down Expand Up @@ -79,7 +75,9 @@ object ApkSigner {
* @param keyStore The keystore to read the entry from.
* @param keyStoreEntryAlias The alias of the key store entry to read.
* @param keyStoreEntryPassword The password for recovering the signing key.
*
* @return The read [PrivateKeyCertificatePair].
*
* @throws IllegalArgumentException If the keystore does not contain the given alias or the password is invalid.
*/
fun readKeyCertificatePair(
Expand Down Expand Up @@ -111,13 +109,15 @@ object ApkSigner {
* Create a new keystore with a new keypair.
*
* @param entries The entries to add to the keystore.
*
* @return The created keystore.
*
* @see KeyStoreEntry
*/
fun newKeyStore(entries: List<KeyStoreEntry>): KeyStore {
fun newKeyStore(entries: Set<KeyStoreEntry>): KeyStore {
logger.fine("Creating keystore")

return KeyStore.getInstance("BKS", BouncyCastleProvider.PROVIDER_NAME).apply {
return KeyStore.getInstance(KeyStore.getDefaultType()).apply {
load(null)

entries.forEach { entry ->
Expand All @@ -142,18 +142,20 @@ object ApkSigner {
fun newKeyStore(
keyStoreOutputStream: OutputStream,
keyStorePassword: String,
entries: List<KeyStoreEntry>,
entries: Set<KeyStoreEntry>,
) = newKeyStore(entries).store(
keyStoreOutputStream,
keyStorePassword.toCharArray(),
) // Save the keystore.
)

/**
* Read a keystore from the given [keyStoreInputStream].
*
* @param keyStoreInputStream The stream to read the keystore from.
* @param keyStorePassword The password for the keystore.
*
* @return The keystore.
*
* @throws IllegalArgumentException If the keystore password is invalid.
*/
fun readKeyStore(
Expand All @@ -162,7 +164,7 @@ object ApkSigner {
): KeyStore {
logger.fine("Reading keystore")

return KeyStore.getInstance("BKS", BouncyCastleProvider.PROVIDER_NAME).apply {
return KeyStore.getInstance(KeyStore.getDefaultType()).apply {
try {
load(keyStoreInputStream, keyStorePassword?.toCharArray())
} catch (exception: IOException) {
Expand All @@ -176,82 +178,53 @@ object ApkSigner {
}

/**
* Create a new [ApkSigner.Builder].
* Create a new [Signer].
*
* @param privateKeyCertificatePair The private key and certificate pair to use for signing.
* @param signer The name of the signer.
* @param createdBy The value for the `Created-By` attribute in the APK's manifest.
* @return The created [ApkSigner.Builder] instance.
*
* @return The new [Signer].
*
* @see PrivateKeyCertificatePair
* @see Signer
*/
fun newApkSignerBuilder(
privateKeyCertificatePair: PrivateKeyCertificatePair,
signer: String,
createdBy: String,
): ApkSigner.Builder {
logger.fine(
"Creating new ApkSigner " +
"with $signer as signer and " +
"$createdBy as Created-By attribute in the APK's manifest",
fun newApkSigner(privateKeyCertificatePair: PrivateKeyCertificatePair) =
Signer(
SigningExtension(
SigningOptions.builder()
.setMinSdkVersion(21) // TODO: Extracting from the target APK would be ideal.
.setV1SigningEnabled(true)
.setV2SigningEnabled(true)
.setCertificates(privateKeyCertificatePair.certificate)
.setKey(privateKeyCertificatePair.privateKey)
.build(),
),
)

// Create the signer config.
val signerConfig =
ApkSigner.SignerConfig.Builder(
signer,
privateKeyCertificatePair.privateKey,
listOf(privateKeyCertificatePair.certificate),
).build()

// Create the signer.
return ApkSigner.Builder(listOf(signerConfig)).apply {
setCreatedBy(createdBy)
}
}

/**
* Create a new [ApkSigner.Builder].
* 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.
* @param signer The name of the signer.
* @param createdBy The value for the `Created-By` attribute in the APK's manifest.
* @return The created [ApkSigner.Builder] instance.
* @see KeyStoreEntry
* @see PrivateKeyCertificatePair
* @see ApkSigner.Builder.setCreatedBy
* @see ApkSigner.Builder.signApk
*
* @return The new [Signer].
*
* @see KeyStore
* @see Signer
*/
fun newApkSignerBuilder(
fun newApkSigner(
keyStore: KeyStore,
keyStoreEntryAlias: String,
keyStoreEntryPassword: String,
signer: String,
createdBy: String,
) = newApkSignerBuilder(
readKeyCertificatePair(keyStore, keyStoreEntryAlias, keyStoreEntryPassword),
signer,
createdBy,
)

fun ApkSigner.Builder.signApk(
input: File,
output: File,
) {
logger.info("Signing ${input.name}")

setInputApk(input)
setOutputApk(output)

build().sign()
}
) = newApkSigner(readKeyCertificatePair(keyStore, keyStoreEntryAlias, keyStoreEntryPassword))

/**
* An entry in a keystore.
*
* @param alias The alias of the entry.
* @param password The password for recovering the signing key.
* @param privateKeyCertificatePair The private key and certificate pair.
*
* @see PrivateKeyCertificatePair
*/
class KeyStoreEntry(
Expand All @@ -270,4 +243,24 @@ object ApkSigner {
val privateKey: PrivateKey,
val certificate: X509Certificate,
)

class Signer internal constructor(val signingExtension: SigningExtension) {
/**
* Sign an APK file.
*
* @param apkFile The APK file to sign.
*/
fun signApk(apkFile: File) = ZFile.openReadWrite(apkFile).use { signApk(it) }

/**
* Sign an APK file.
*
* @param apkZFile The APK [ZFile] to sign.
*/
fun signApk(apkZFile: ZFile) {
logger.info("Signing ${apkZFile.file.name}")

signingExtension.register(apkZFile)
}
}
}
Loading

0 comments on commit 3aa6dc2

Please sign in to comment.