feat: Add utility function around key certificate pairs

This commit is contained in:
oSumAtrIX
2024-03-13 23:04:35 +01:00
parent f2959b610a
commit 2df3484b68
3 changed files with 259 additions and 139 deletions

View File

@@ -7,14 +7,13 @@ public final class app/revanced/library/ApkSigner {
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;
public final fun newPrivateKeyCertificatePair (Ljava/lang/String;Ljava/util/Date;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair; 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 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 readKeyStore (Ljava/io/InputStream;Ljava/lang/String;)Ljava/security/KeyStore;
public final fun readPrivateKeyCertificatePair (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
} }
public final class app/revanced/library/ApkSigner$KeyStoreEntry { public final class app/revanced/library/ApkSigner$KeyStoreEntry {
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)V public fun <init> (Ljava/lang/String;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getAlias ()Ljava/lang/String; public final fun getAlias ()Ljava/lang/String;
public final fun getPassword ()Ljava/lang/String; public final fun getPassword ()Ljava/lang/String;
public final fun getPrivateKeyCertificatePair ()Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair; public final fun getPrivateKeyCertificatePair ()Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
@@ -35,8 +34,28 @@ public final class app/revanced/library/ApkSigner$Signer {
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 newPrivateKeyCertificatePair (Lapp/revanced/library/ApkUtils$PrivateKeyCertificatePairDetails;Lapp/revanced/library/ApkUtils$KeyStoreDetails;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
public final fun readPrivateKeyCertificatePairFromKeyStore (Lapp/revanced/library/ApkUtils$KeyStoreDetails;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
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 fun sign (Ljava/io/File;Ljava/io/File;Lapp/revanced/library/ApkUtils$SigningOptions;)V
public final fun sign (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)V
}
public final class app/revanced/library/ApkUtils$KeyStoreDetails {
public fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getAlias ()Ljava/lang/String;
public final fun getKeyStore ()Ljava/io/File;
public final fun getKeyStorePassword ()Ljava/lang/String;
public final fun getPassword ()Ljava/lang/String;
}
public final class app/revanced/library/ApkUtils$PrivateKeyCertificatePairDetails {
public fun <init> ()V
public fun <init> (Ljava/lang/String;Ljava/util/Date;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/util/Date;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getCommonName ()Ljava/lang/String;
public final fun getValidUntil ()Ljava/util/Date;
} }
public final class app/revanced/library/ApkUtils$SigningOptions { public final class app/revanced/library/ApkUtils$SigningOptions {

View File

@@ -19,7 +19,6 @@ import java.security.*
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import java.util.* import java.util.*
import java.util.logging.Logger import java.util.logging.Logger
import kotlin.time.Duration.Companion.days
/** /**
* Utility class for reading or writing 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.
@@ -34,17 +33,80 @@ object ApkSigner {
} }
} }
private fun newKeyStoreInstance() = KeyStore.getInstance("BKS", BouncyCastleProvider.PROVIDER_NAME)
/** /**
* Create a new [PrivateKeyCertificatePair]. * Create a new keystore with a new keypair.
*
* @param entries The entries to add to the keystore.
*
* @return The created keystore.
*
* @see KeyStoreEntry
* @see KeyStore
*/
fun newKeyStore(entries: Set<KeyStoreEntry>): KeyStore {
logger.fine("Creating keystore")
return newKeyStoreInstance().apply {
load(null)
entries.forEach { entry ->
// Add all entries to the keystore.
setKeyEntry(
entry.alias,
entry.privateKeyCertificatePair.privateKey,
entry.password.toCharArray(),
arrayOf(entry.privateKeyCertificatePair.certificate),
)
}
}
}
/**
* 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.
*
* @see KeyStore
*/
fun readKeyStore(
keyStoreInputStream: InputStream,
keyStorePassword: String?,
): KeyStore {
logger.fine("Reading keystore")
return newKeyStoreInstance().apply {
try {
load(keyStoreInputStream, keyStorePassword?.toCharArray())
} catch (exception: IOException) {
if (exception.cause is UnrecoverableKeyException) {
throw IllegalArgumentException("Invalid keystore password")
} else {
throw exception
}
}
}
}
/**
* Create a new private key and certificate pair.
* *
* @param commonName The common name of the certificate. * @param commonName The common name of the certificate.
* @param validUntil The date until the certificate is valid. * @param validUntil The date until which the certificate is valid.
* *
* @return The created [PrivateKeyCertificatePair]. * @return The newly created private key and certificate pair.
*
* @see PrivateKeyCertificatePair
*/ */
fun newPrivateKeyCertificatePair( fun newPrivateKeyCertificatePair(
commonName: String = "ReVanced", commonName: String,
validUntil: Date = Date(System.currentTimeMillis() + (365.days * 8).inWholeMilliseconds * 24), validUntil: Date,
): PrivateKeyCertificatePair { ): PrivateKeyCertificatePair {
logger.fine("Creating certificate for $commonName") logger.fine("Creating certificate for $commonName")
@@ -80,8 +142,11 @@ object ApkSigner {
* @return The read [PrivateKeyCertificatePair]. * @return The read [PrivateKeyCertificatePair].
* *
* @throws IllegalArgumentException If the keystore does not contain the given alias or the password is invalid. * @throws IllegalArgumentException If the keystore does not contain the given alias or the password is invalid.
*
* @see PrivateKeyCertificatePair
* @see KeyStore
*/ */
fun readKeyCertificatePair( fun readPrivateKeyCertificatePair(
keyStore: KeyStore, keyStore: KeyStore,
keyStoreEntryAlias: String, keyStoreEntryAlias: String,
keyStoreEntryPassword: String, keyStoreEntryPassword: String,
@@ -106,80 +171,6 @@ object ApkSigner {
return PrivateKeyCertificatePair(privateKey, certificate) return PrivateKeyCertificatePair(privateKey, certificate)
} }
/**
* 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: Set<KeyStoreEntry>): KeyStore {
logger.fine("Creating keystore")
return newKeyStoreInstance().apply {
load(null)
entries.forEach { entry ->
// Add all entries to the keystore.
setKeyEntry(
entry.alias,
entry.privateKeyCertificatePair.privateKey,
entry.password.toCharArray(),
arrayOf(entry.privateKeyCertificatePair.certificate),
)
}
}
}
private fun newKeyStoreInstance() = KeyStore.getInstance("BKS", BouncyCastleProvider.PROVIDER_NAME)
/**
* Create a new keystore with a new keypair and saves it to the given [keyStoreOutputStream].
*
* @param keyStoreOutputStream The stream to write the keystore to.
* @param keyStorePassword The password for the keystore.
* @param entries The entries to add to the keystore.
*/
fun newKeyStore(
keyStoreOutputStream: OutputStream,
keyStorePassword: String,
entries: Set<KeyStoreEntry>,
) = newKeyStore(entries).store(
keyStoreOutputStream,
keyStorePassword.toCharArray(),
)
/**
* 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(
keyStoreInputStream: InputStream,
keyStorePassword: String?,
): KeyStore {
logger.fine("Reading keystore")
return newKeyStoreInstance().apply {
try {
load(keyStoreInputStream, keyStorePassword?.toCharArray())
} catch (exception: IOException) {
if (exception.cause is UnrecoverableKeyException) {
throw IllegalArgumentException("Invalid keystore password")
} else {
throw exception
}
}
}
}
/** /**
* Create a new [Signer]. * Create a new [Signer].
* *
@@ -206,6 +197,41 @@ object ApkSigner {
), ),
) )
/**
* Read a [PrivateKeyCertificatePair] from a keystore entry.
*
* @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.
*/
@Deprecated("This method will be removed in the future.")
fun readKeyCertificatePair(
keyStore: KeyStore,
keyStoreEntryAlias: String,
keyStoreEntryPassword: String,
) = readPrivateKeyCertificatePair(keyStore, keyStoreEntryAlias, keyStoreEntryPassword)
/**
* Create a new keystore with a new keypair and saves it to the given [keyStoreOutputStream].
*
* @param keyStoreOutputStream The stream to write the keystore to.
* @param keyStorePassword The password for the keystore.
* @param entries The entries to add to the keystore.
*/
@Deprecated("This method will be removed in the future.")
fun newKeyStore(
keyStoreOutputStream: OutputStream,
keyStorePassword: String?,
entries: Set<KeyStoreEntry>,
) = newKeyStore(entries).store(
keyStoreOutputStream,
keyStorePassword?.toCharArray(),
)
/** /**
* Create a new [Signer]. * Create a new [Signer].
* *
@@ -216,13 +242,7 @@ object ApkSigner {
* @see PrivateKeyCertificatePair * @see PrivateKeyCertificatePair
* @see Signer * @see Signer
*/ */
@Suppress("DEPRECATION") @Deprecated("This method will be removed in the future.")
@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(
@@ -249,6 +269,7 @@ object ApkSigner {
* @see KeyStore * @see KeyStore
* @see Signer * @see Signer
*/ */
@Deprecated("This method will be removed in the future.")
fun newApkSigner( fun newApkSigner(
signer: String, signer: String,
keyStore: KeyStore, keyStore: KeyStore,
@@ -268,13 +289,7 @@ object ApkSigner {
* @see KeyStore * @see KeyStore
* @see Signer * @see Signer
*/ */
@Deprecated( @Deprecated("This method will be removed in the future.")
"This method will be removed in the future.",
ReplaceWith(
"newApkSigner(\"ReVanced\", readKeyCertificatePair(keyStore, keyStoreEntryAlias, keyStoreEntryPassword))",
"app.revanced.library.ApkSigner.newApkSigner",
),
)
fun newApkSigner( fun newApkSigner(
keyStore: KeyStore, keyStore: KeyStore,
keyStoreEntryAlias: String, keyStoreEntryAlias: String,
@@ -293,7 +308,7 @@ object ApkSigner {
class KeyStoreEntry( class KeyStoreEntry(
val alias: String, val alias: String,
val password: String, val password: String,
val privateKeyCertificatePair: PrivateKeyCertificatePair = newPrivateKeyCertificatePair(), val privateKeyCertificatePair: PrivateKeyCertificatePair,
) )
/** /**
@@ -316,6 +331,12 @@ object ApkSigner {
signingExtension = null signingExtension = null
} }
fun signApk(inputApkFile: File, outputApkFile: File) {
logger.info("Signing APK")
signerBuilder?.setInputApk(inputApkFile)?.setOutputApk(outputApkFile)?.build()?.sign()
}
@Deprecated("This constructor will be removed in the future.") @Deprecated("This constructor will be removed in the future.")
internal constructor(signingExtension: SigningExtension) { internal constructor(signingExtension: SigningExtension) {
signerBuilder = null signerBuilder = null
@@ -344,11 +365,5 @@ object ApkSigner {
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

@@ -1,12 +1,15 @@
package app.revanced.library package app.revanced.library
import app.revanced.library.ApkSigner.newPrivateKeyCertificatePair
import app.revanced.patcher.PatcherResult import app.revanced.patcher.PatcherResult
import com.android.tools.build.apkzlib.zip.AlignmentRules import com.android.tools.build.apkzlib.zip.AlignmentRules
import com.android.tools.build.apkzlib.zip.StoredEntry import com.android.tools.build.apkzlib.zip.StoredEntry
import com.android.tools.build.apkzlib.zip.ZFile import com.android.tools.build.apkzlib.zip.ZFile
import com.android.tools.build.apkzlib.zip.ZFileOptions import com.android.tools.build.apkzlib.zip.ZFileOptions
import java.io.File import java.io.File
import java.util.*
import java.util.logging.Logger import java.util.logging.Logger
import kotlin.time.Duration.Companion.days
/** /**
* Utility functions to work with APK files. * Utility functions to work with APK files.
@@ -93,24 +96,89 @@ object ApkUtils {
} }
/** /**
* Reads an existing or creates a new keystore. * Creates a new private key and certificate pair and saves it to the keystore in [keyStoreDetails].
* *
* @param signingOptions The options to use for signing. * @param privateKeyCertificatePairDetails The details for the private key and certificate pair.
* @param keyStoreDetails The details for the keystore.
*
* @return The newly created private key and certificate pair.
*/ */
private fun readOrNewKeyStore(signingOptions: SigningOptions) = if (signingOptions.keyStore.exists()) { fun newPrivateKeyCertificatePair(
ApkSigner.readKeyStore( privateKeyCertificatePairDetails: PrivateKeyCertificatePairDetails,
signingOptions.keyStore.inputStream(), keyStoreDetails: KeyStoreDetails,
signingOptions.keyStorePassword ?: "", ) = newPrivateKeyCertificatePair(
privateKeyCertificatePairDetails.commonName,
privateKeyCertificatePairDetails.validUntil,
).also { privateKeyCertificatePair ->
ApkSigner.newKeyStore(
setOf(
ApkSigner.KeyStoreEntry(
keyStoreDetails.alias,
keyStoreDetails.password,
privateKeyCertificatePair,
),
),
).store(
keyStoreDetails.keyStore.outputStream(),
keyStoreDetails.keyStorePassword?.toCharArray(),
) )
} 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 { * Reads the private key and certificate pair from an existing keystore.
store( *
signingOptions.keyStore.outputStream(), * @param keyStoreDetails The details for the keystore.
signingOptions.keyStorePassword?.toCharArray(), *
) * @return The private key and certificate pair.
*/
fun readPrivateKeyCertificatePairFromKeyStore(
keyStoreDetails: KeyStoreDetails,
) = ApkSigner.readKeyCertificatePair(
ApkSigner.readKeyStore(
keyStoreDetails.keyStore.inputStream(),
keyStoreDetails.keyStorePassword,
),
keyStoreDetails.alias,
keyStoreDetails.password,
)
/**
* 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 signer The name of the signer.
* @param privateKeyCertificatePair The private key and certificate pair to use for signing.
*/
fun sign(
inputApkFile: File,
outputApkFile: File,
signer: String,
privateKeyCertificatePair: ApkSigner.PrivateKeyCertificatePair,
) = ApkSigner.newApkSigner(
signer,
privateKeyCertificatePair,
).signApk(inputApkFile, outputApkFile)
@Deprecated("This method will be removed in the future.")
private fun readOrNewPrivateKeyCertificatePair(
signingOptions: SigningOptions,
): ApkSigner.PrivateKeyCertificatePair {
val privateKeyCertificatePairDetails = PrivateKeyCertificatePairDetails(
signingOptions.alias,
PrivateKeyCertificatePairDetails().validUntil,
)
val keyStoreDetails = KeyStoreDetails(
signingOptions.keyStore,
signingOptions.keyStorePassword,
signingOptions.alias,
signingOptions.password,
)
return if (keyStoreDetails.keyStore.exists()) {
readPrivateKeyCertificatePairFromKeyStore(keyStoreDetails)
} else {
newPrivateKeyCertificatePair(privateKeyCertificatePairDetails, keyStoreDetails)
} }
} }
@@ -119,17 +187,11 @@ object ApkUtils {
* *
* @param signingOptions The options to use for signing. * @param signingOptions The options to use for signing.
*/ */
@Deprecated("Use sign(File, File, SigningOptions) instead.") @Deprecated("This method will be removed in the future.")
fun File.sign(signingOptions: SigningOptions) { fun File.sign(signingOptions: SigningOptions) = ApkSigner.newApkSigner(
val keyStore = readOrNewKeyStore(signingOptions) signingOptions.signer,
readOrNewPrivateKeyCertificatePair(signingOptions),
@Suppress("DEPRECATION") ).signApk(this)
ApkSigner.newApkSigner(
keyStore,
signingOptions.alias,
signingOptions.password,
).signApk(this)
}
/** /**
* Signs [inputApkFile] with the given options and saves the signed apk to [outputApkFile]. * Signs [inputApkFile] with the given options and saves the signed apk to [outputApkFile].
@@ -138,16 +200,13 @@ object ApkUtils {
* @param outputApkFile The file to save the signed apk to. * @param outputApkFile The file to save the signed apk to.
* @param signingOptions The options to use for signing. * @param signingOptions The options to use for signing.
*/ */
fun sign(inputApkFile: File, outputApkFile: File, signingOptions: SigningOptions) { @Deprecated("This method will be removed in the future.")
val keyStore = readOrNewKeyStore(signingOptions) fun sign(inputApkFile: File, outputApkFile: File, signingOptions: SigningOptions) = sign(
inputApkFile,
ApkSigner.newApkSigner( outputApkFile,
signingOptions.signer, signingOptions.signer,
keyStore, readOrNewPrivateKeyCertificatePair(signingOptions),
signingOptions.alias, )
signingOptions.password,
).signApk(inputApkFile, outputApkFile)
}
/** /**
* Options for signing an apk. * Options for signing an apk.
@@ -158,6 +217,7 @@ object ApkUtils {
* @param password The password for recovering the signing key. * @param password The password for recovering the signing key.
* @param signer The name of the signer. * @param signer The name of the signer.
*/ */
@Deprecated("This class will be removed in the future.")
class SigningOptions( class SigningOptions(
val keyStore: File, val keyStore: File,
val keyStorePassword: String?, val keyStorePassword: String?,
@@ -165,4 +225,30 @@ object ApkUtils {
val password: String = "", val password: String = "",
val signer: String = "ReVanced", val signer: String = "ReVanced",
) )
/**
* Details for a keystore.
*
* @param keyStore The file to save the keystore to.
* @param keyStorePassword The password for the keystore.
* @param alias The alias of the key store entry to use for signing.
* @param password The password for recovering the signing key.
*/
class KeyStoreDetails(
val keyStore: File,
val keyStorePassword: String? = null,
val alias: String = "ReVanced Key",
val password: String = "",
)
/**
* Details for a private key and certificate pair.
*
* @param commonName The common name for the certificate saved in the keystore.
* @param validUntil The date until which the certificate is valid.
*/
class PrivateKeyCertificatePairDetails(
val commonName: String = "ReVanced",
val validUntil: Date = Date(System.currentTimeMillis() + (365.days * 8).inWholeMilliseconds * 24),
)
} }