diff --git a/src/main/kotlin/app/revanced/cli/command/MainCommand.kt b/src/main/kotlin/app/revanced/cli/command/MainCommand.kt index 19ebef6..9700b09 100644 --- a/src/main/kotlin/app/revanced/cli/command/MainCommand.kt +++ b/src/main/kotlin/app/revanced/cli/command/MainCommand.kt @@ -2,6 +2,7 @@ package app.revanced.cli.command import app.revanced.cli.patcher.Patcher import app.revanced.cli.signing.Signing +import app.revanced.cli.signing.SigningOptions import app.revanced.patcher.PatcherOptions import app.revanced.patcher.extensions.PatchExtensions.description import app.revanced.patcher.extensions.PatchExtensions.patchName @@ -11,6 +12,8 @@ import picocli.CommandLine.* import java.io.File import java.nio.file.Files import java.util.logging.Logger +import kotlin.io.path.Path +import kotlin.io.path.name @Command( name = "ReVanced-CLI", version = ["1.0.0"], mixinStandardHelpOptions = true @@ -62,6 +65,9 @@ internal object MainCommand : Runnable { @Option(names = ["--cn"], description = ["Overwrite the default CN for the signed file"]) var cn = "ReVanced" + @Option(names = ["--keystore"], description = ["File path to your keystore"]) + var keystorePath: String? = null + @Option(names = ["-p", "--password"], description = ["Overwrite the default password for the signed file"]) var password = "ReVanced" @@ -111,10 +117,12 @@ internal object MainCommand : Runnable { if (!args.mount) { Signing.start( - patchedFile, - outputFile, - args.cn, - args.password, + patchedFile, outputFile, SigningOptions( + args.cn, + args.password, + args.keystorePath + ?: Path(outputFile.parent).resolve("${outputFile.nameWithoutExtension}.keystore").name + ) ) } diff --git a/src/main/kotlin/app/revanced/cli/signing/Signing.kt b/src/main/kotlin/app/revanced/cli/signing/Signing.kt index 4a6d1fd..f22ecc2 100644 --- a/src/main/kotlin/app/revanced/cli/signing/Signing.kt +++ b/src/main/kotlin/app/revanced/cli/signing/Signing.kt @@ -7,7 +7,7 @@ import app.revanced.utils.signing.align.ZipAligner import java.io.File object Signing { - fun start(inputFile: File, outputFile: File, cn: String, password: String) { + fun start(inputFile: File, outputFile: File, signingOptions: SigningOptions) { val cacheDirectory = File(args.pArgs!!.cacheDirectory) val alignedOutput = cacheDirectory.resolve("${outputFile.nameWithoutExtension}_aligned.apk") val signedOutput = cacheDirectory.resolve("${outputFile.nameWithoutExtension}_signed.apk") @@ -22,7 +22,7 @@ object Signing { // sign the alignedOutput and write to signedOutput // the reason is, in case the signer fails // it does not damage the output file - val keyStore = Signer(cn, password).signApk(alignedOutput, signedOutput) + val keyStore = Signer(signingOptions).signApk(alignedOutput, signedOutput) // afterwards copy over the file and the keystore to the output signedOutput.copyTo(outputFile, true) diff --git a/src/main/kotlin/app/revanced/cli/signing/SigningOptions.kt b/src/main/kotlin/app/revanced/cli/signing/SigningOptions.kt new file mode 100644 index 0000000..252ef65 --- /dev/null +++ b/src/main/kotlin/app/revanced/cli/signing/SigningOptions.kt @@ -0,0 +1,7 @@ +package app.revanced.cli.signing + +data class SigningOptions( + val cn: String, + val password: String, + val keyStoreFilePath: String +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/utils/signing/Signer.kt b/src/main/kotlin/app/revanced/utils/signing/Signer.kt index 25c29cd..1bb4bd1 100644 --- a/src/main/kotlin/app/revanced/utils/signing/Signer.kt +++ b/src/main/kotlin/app/revanced/utils/signing/Signer.kt @@ -1,5 +1,7 @@ package app.revanced.utils.signing +import app.revanced.cli.command.MainCommand.logger +import app.revanced.cli.signing.SigningOptions import com.android.apksig.ApkSigner import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo @@ -17,9 +19,9 @@ import java.security.cert.X509Certificate import java.util.* internal class Signer( - private val cn: String, password: String + private val signingOptions: SigningOptions ) { - private val passwordCharArray = password.toCharArray() + private val passwordCharArray = signingOptions.password.toCharArray() private fun newKeystore(out: File) { val (publicKey, privateKey) = createKey() val privateKS = KeyStore.getInstance("BKS", "BC") @@ -34,7 +36,7 @@ internal class Signer( val pair = gen.generateKeyPair() var serialNumber: BigInteger do serialNumber = BigInteger.valueOf(SecureRandom().nextLong()) while (serialNumber < BigInteger.ZERO) - val x500Name = X500Name("CN=$cn") + val x500Name = X500Name("CN=${signingOptions.cn}") val builder = X509v3CertificateBuilder( x500Name, serialNumber, @@ -52,21 +54,21 @@ internal class Signer( Security.addProvider(BouncyCastleProvider()) // TODO: keystore should be saved securely - val ks = File(input.parent, "${output.nameWithoutExtension}.keystore") - if (!ks.exists()) newKeystore(ks) + val ks = File(signingOptions.keyStoreFilePath) + if (!ks.exists()) newKeystore(ks) else logger.info("Found existing keystore ${ks.nameWithoutExtension}") val keyStore = KeyStore.getInstance("BKS", "BC") FileInputStream(ks).use { fis -> keyStore.load(fis, null) } val alias = keyStore.aliases().nextElement() val config = ApkSigner.SignerConfig.Builder( - cn, + signingOptions.cn, keyStore.getKey(alias, passwordCharArray) as PrivateKey, listOf(keyStore.getCertificate(alias) as X509Certificate) ).build() val signer = ApkSigner.Builder(listOf(config)) - signer.setCreatedBy(cn) + signer.setCreatedBy(signingOptions.cn) signer.setInputApk(input) signer.setOutputApk(output)