Skip to content

Commit

Permalink
feat: Add utility function around key certificate pairs
Browse files Browse the repository at this point in the history
  • Loading branch information
oSumAtrIX committed Mar 13, 2024
1 parent f2959b6 commit 2df3484
Show file tree
Hide file tree
Showing 3 changed files with 259 additions and 139 deletions.
23 changes: 21 additions & 2 deletions api/revanced-library.api
Original file line number Diff line number Diff line change
Expand Up @@ -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/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 readPrivateKeyCertificatePair (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
}

public final class app/revanced/library/ApkSigner$KeyStoreEntry {
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 getPassword ()Ljava/lang/String;
public final fun getPrivateKeyCertificatePair ()Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
Expand All @@ -35,8 +34,28 @@ public final class app/revanced/library/ApkSigner$Signer {
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 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;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 {
Expand Down
219 changes: 117 additions & 102 deletions src/main/kotlin/app/revanced/library/ApkSigner.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import java.security.*
import java.security.cert.X509Certificate
import java.util.*
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.
Expand All @@ -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 validUntil The date until the certificate is valid.
* @param validUntil The date until which the certificate is valid.
*
* @return The newly created private key and certificate pair.
*
* @return The created [PrivateKeyCertificatePair].
* @see PrivateKeyCertificatePair
*/
fun newPrivateKeyCertificatePair(
commonName: String = "ReVanced",
validUntil: Date = Date(System.currentTimeMillis() + (365.days * 8).inWholeMilliseconds * 24),
commonName: String,
validUntil: Date,
): PrivateKeyCertificatePair {
logger.fine("Creating certificate for $commonName")

Expand Down Expand Up @@ -80,8 +142,11 @@ object ApkSigner {
* @return The read [PrivateKeyCertificatePair].
*
* @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,
keyStoreEntryAlias: String,
keyStoreEntryPassword: String,
Expand All @@ -106,80 +171,6 @@ object ApkSigner {
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].
*
Expand All @@ -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].
*
Expand All @@ -216,13 +242,7 @@ object ApkSigner {
* @see PrivateKeyCertificatePair
* @see Signer
*/
@Suppress("DEPRECATION")
@Deprecated(
"This method will be removed in the future.",
ReplaceWith(
"newApkSigner(\"ReVanced\", privateKeyCertificatePair)",
),
)
@Deprecated("This method will be removed in the future.")
fun newApkSigner(privateKeyCertificatePair: PrivateKeyCertificatePair) =
Signer(
SigningExtension(
Expand All @@ -249,6 +269,7 @@ object ApkSigner {
* @see KeyStore
* @see Signer
*/
@Deprecated("This method will be removed in the future.")
fun newApkSigner(
signer: String,
keyStore: KeyStore,
Expand All @@ -268,13 +289,7 @@ object ApkSigner {
* @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",
),
)
@Deprecated("This method will be removed in the future.")
fun newApkSigner(
keyStore: KeyStore,
keyStoreEntryAlias: String,
Expand All @@ -293,7 +308,7 @@ object ApkSigner {
class KeyStoreEntry(
val alias: String,
val password: String,
val privateKeyCertificatePair: PrivateKeyCertificatePair = newPrivateKeyCertificatePair(),
val privateKeyCertificatePair: PrivateKeyCertificatePair,
)

/**
Expand All @@ -316,6 +331,12 @@ object ApkSigner {
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.")
internal constructor(signingExtension: SigningExtension) {
signerBuilder = null
Expand Down Expand Up @@ -344,11 +365,5 @@ object ApkSigner {

signingExtension?.register(apkZFile)
}

fun signApk(inputApkFile: File, outputApkFile: File) {
logger.info("Signing APK")

signerBuilder?.setInputApk(inputApkFile)?.setOutputApk(outputApkFile)?.build()?.sign()
}
}
}
Loading

0 comments on commit 2df3484

Please sign in to comment.