diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bf5cf5..b1142ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# [2.2.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.1.0...v2.2.0-dev.1) (2024-03-09) + + +### Bug Fixes + +* Make property private ([51109c4](https://github.com/ReVanced/revanced-library/commit/51109c476837828535dcd395a5222d2fcf7fc22c)) +* Sign APKs using `apksig` ([f59ecbc](https://github.com/ReVanced/revanced-library/commit/f59ecbccd14a08d87d4f18c3c0cc47a884088b99)) + + +### Features + +* Increase default expiration date of certificate ([f2bd3f5](https://github.com/ReVanced/revanced-library/commit/f2bd3f5eeee14ca32094be0d41c32b231a16bcc3)) + # [2.1.0](https://github.com/ReVanced/revanced-library/compare/v2.0.0...v2.1.0) (2024-03-04) diff --git a/api/revanced-library.api b/api/revanced-library.api index 5164c17..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; @@ -25,15 +27,16 @@ public final class app/revanced/library/ApkSigner$PrivateKeyCertificatePair { } 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 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 4ea1f71..25048b2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + plugins { alias(libs.plugins.kotlin) alias(libs.plugins.binary.compatibility.validator) @@ -27,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) @@ -43,10 +46,14 @@ tasks { } } -kotlin { jvmToolchain(11) } +kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_11) + } +} java { - withSourcesJar() + targetCompatibility = JavaVersion.VERSION_11 } publishing { diff --git a/gradle.properties b/gradle.properties index cabe972..995c9cc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true kotlin.code.style = official -version = 2.1.0 +version = 2.2.0-dev.1 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 e7c71d9..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 @@ -43,35 +44,28 @@ object ApkSigner { */ fun newPrivateKeyCertificatePair( commonName: String = "ReVanced", - validUntil: Date = Date(System.currentTimeMillis() + 356.days.inWholeMilliseconds * 24), + validUntil: Date = Date(System.currentTimeMillis() + (365.days * 8).inWholeMilliseconds * 24), ): PrivateKeyCertificatePair { logger.fine("Creating certificate for $commonName") // Generate a new key pair. - val keyPair = - KeyPairGenerator.getInstance("RSA").apply { - initialize(4096) - }.generateKeyPair() + val keyPair = KeyPairGenerator.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME).apply { + initialize(4096) + }.generateKeyPair() - var serialNumber: BigInteger - do serialNumber = BigInteger.valueOf(SecureRandom().nextLong()) - while (serialNumber < BigInteger.ZERO) + val contentSigner = JcaContentSignerBuilder("SHA256withRSA").build(keyPair.private) val name = X500Name("CN=$commonName") - - // Create a new certificate. - val certificate = - JcaX509CertificateConverter().getCertificate( - X509v3CertificateBuilder( - name, - serialNumber, - Date(System.currentTimeMillis()), - validUntil, - Locale.ENGLISH, - name, - SubjectPublicKeyInfo.getInstance(keyPair.public.encoded), - ).build(JcaContentSignerBuilder("SHA256withRSA").build(keyPair.private)), - ) + val certificateHolder = X509v3CertificateBuilder( + name, + BigInteger.valueOf(SecureRandom().nextLong()), + Date(System.currentTimeMillis()), + validUntil, + Locale.ENGLISH, + name, + SubjectPublicKeyInfo.getInstance(keyPair.public.encoded), + ).build(contentSigner) + val certificate = JcaX509CertificateConverter().getCertificate(certificateHolder) return PrivateKeyCertificatePair(keyPair.private, certificate) } @@ -189,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]. @@ -196,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( @@ -212,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. @@ -222,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. @@ -253,23 +307,48 @@ object ApkSigner { val certificate: X509Certificate, ) - class Signer internal constructor(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. *