Skip to content

Commit

Permalink
Generalize binary downloader/verifier
Browse files Browse the repository at this point in the history
The new generic download task can download any file and can check its
SHA-256 checksum (optional). The signature verifier can be used for any
file now. This allows us to reuse the tasks to download the Tor binaries
later.

Other changes:
Currently, all Electrum binaries get zipped and moved to the app
resources. This okay until we decided to unpack the Electum.app file at
build time. To build the macOS version we require macOS as the host
operating system now.

Fixes #561
  • Loading branch information
alvasw committed Nov 29, 2022
1 parent 2562562 commit 70614c4
Show file tree
Hide file tree
Showing 12 changed files with 343 additions and 264 deletions.
Original file line number Diff line number Diff line change
@@ -1,83 +1,23 @@
package bisq.gradle.electrum

import bisq.gradle.electrum.tasks.DownloadElectrumBinariesTask
import bisq.gradle.electrum.tasks.ExtractElectrumAppFromDmgFile
import bisq.gradle.electrum.tasks.VerifyElectrumBinariesTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.file.Directory
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.Zip
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.register
import java.util.*


class BisqElectrumPlugin : Plugin<Project> {

companion object {
private const val DATA_DIR = "electrum_binaries"
private const val DOWNLOADS_DIR = "$DATA_DIR/downloads"
private const val BINARIES_DIR = "$DATA_DIR/binaries"
const val DATA_DIR = "electrum_binaries"
}

override fun apply(project: Project) {
val extension = project.extensions.create<BisqElectrumPluginExtension>("electrum")

val downloadTask: TaskProvider<DownloadElectrumBinariesTask> =
project.tasks.register<DownloadElectrumBinariesTask>("downloadElectrumBinaries") {
electrumVersion.set(extension.version)
val electrumBinaryDownloader = ElectrumBinaryDownloader(project, extension)
electrumBinaryDownloader.registerTasks()

binaryHashes.appImageHash.set(extension.appImageHash)
binaryHashes.dmgHash.set(extension.dmgHash)
binaryHashes.exeHash.set(extension.exeHash)

outputDir.set(project.layout.buildDirectory.dir(DOWNLOADS_DIR))
}

val verifyElectrumBinariesTask: TaskProvider<VerifyElectrumBinariesTask> =
project.tasks.register<VerifyElectrumBinariesTask>("verifyElectrumBinaries") {
electrumVersion.set(extension.version)

val downloadsDirectory: Provider<Directory> = downloadTask.flatMap { it.outputDir }
inputDirectory.set(downloadsDirectory)

outputDirectory.set(project.layout.buildDirectory.dir(BINARIES_DIR))
}

var extractElectrumAppFromDmgFileTask: TaskProvider<ExtractElectrumAppFromDmgFile>? = null
if (isMacOs()) {
extractElectrumAppFromDmgFileTask =
project.tasks.register<ExtractElectrumAppFromDmgFile>("extractElectrumAppFromDmgFile") {
electrumVersion.set(extension.version)
inputDirectory.set(verifyElectrumBinariesTask.flatMap { it.inputDirectory })
outputDirectory.set(project.layout.buildDirectory.dir(BINARIES_DIR))
}
}

val packageElectrumBinariesTask: TaskProvider<Zip> =
project.tasks.register<Zip>("packageElectrumBinaries") {

if (isMacOs()) {
dependsOn(extractElectrumAppFromDmgFileTask)
}

archiveFileName.set("electrum-binaries.zip")
destinationDirectory.set(project.layout.buildDirectory.dir("generated/src/main/resources"))

val binariesDir: Provider<Directory> = verifyElectrumBinariesTask.flatMap { it.outputDirectory }
from(binariesDir)
}

val processResourcesTask = project.tasks.named("processResources")
processResourcesTask.configure {
dependsOn(packageElectrumBinariesTask)
}
}

private fun isMacOs(): Boolean {
val osName: String = System.getProperty("os.name").toLowerCase(Locale.US)
return osName.contains("mac") || osName.contains("darwin")
val electrumBinaryPackager = ElectrumBinaryPackager(project, electrumBinaryDownloader)
electrumBinaryPackager.registerTasks()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package bisq.gradle.electrum

import bisq.gradle.electrum.tasks.DownloadTask
import bisq.gradle.electrum.tasks.FileVerificationTask
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.Directory
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider
import org.gradle.kotlin.dsl.register

class ElectrumBinaryDownloader(
private val project: Project,
private val pluginExtension: BisqElectrumPluginExtension
) {

companion object {
private const val ELECTRUM_WEBSITE_URL = "https://download.electrum.org"
private const val DOWNLOADS_DIR = "${BisqElectrumPlugin.DATA_DIR}/downloads"
}

private val taskDownloadDirectory: Provider<Directory> = project.layout.buildDirectory.dir(DOWNLOADS_DIR)

val binaryFile: RegularFile
get() = taskDownloadDirectory.get().file(getBinaryFileName())

lateinit var lastTask: TaskProvider<out Task>

fun registerTasks() {
val binaryDownloadTask: TaskProvider<DownloadTask> =
project.tasks.register<DownloadTask>("downloadElectrumBinary") {
downloadUrl.set(getBinaryDownloadUrl())
sha256hash.set(getBinaryHash())
downloadDirectory.set(taskDownloadDirectory)
}

val signatureDownloadTask: TaskProvider<DownloadTask> =
project.tasks.register<DownloadTask>("downloadElectrumSignature") {
downloadUrl.set(getSignatureDownloadUrl())
downloadDirectory.set(taskDownloadDirectory)
}

lastTask = project.tasks.register<FileVerificationTask>("verifyElectrumBinary") {
dependsOn(binaryDownloadTask, signatureDownloadTask)

fileToVerify.set(binaryDownloadTask.flatMap { it.outputFile })
detachedSignatureFile.set(signatureDownloadTask.flatMap { it.outputFile })
publicKeyUrls.set(getPublicKeyUrls())
publicKeyFingerprints.set(getPublicKeyFingerprints())
}
}

private fun getBinaryFileName(): String {
val electrumVersion: String = pluginExtension.version.get()
return when (getOS()) {
OS.LINUX -> "electrum-$electrumVersion-x86_64.AppImage"
OS.MAC_OS -> "electrum-$electrumVersion.dmg"
OS.WINDOWS -> "electrum-$electrumVersion.exe"
}
}

private fun getBinaryDownloadUrl(): String = "$ELECTRUM_WEBSITE_URL/${pluginExtension.version.get()}/" + getBinaryFileName()

private fun getSignatureDownloadUrl(): String = "${getBinaryDownloadUrl()}.asc"

private fun getBinaryHash(): Property<String> {
return when (getOS()) {
OS.LINUX -> pluginExtension.appImageHash
OS.MAC_OS -> pluginExtension.dmgHash
OS.WINDOWS -> pluginExtension.exeHash
}
}

private fun getPublicKeyUrls() =
setOf(
this::class.java.getResource("/ThomasV.asc")!!,

// Why failing signature check?
// this::class.java.getResource("/Emzy.asc")!!

this::class.java.getResource("/SomberNight.asc")!!
)

private fun getPublicKeyFingerprints(): Set<String> {
val fingerprints = setOf(
// ThomasV
"6694 D8DE 7BE8 EE56 31BE D950 2BD5 824B 7F94 70E6",
// Emzy
"9EDA FF80 E080 6596 04F4 A76B 2EBB 056F D847 F8A7",
// SomberNight
"0EED CFD5 CAFB 4590 6734 9B23 CA9E EEC4 3DF9 11DC"
)

return fingerprints.map { fingerprint ->
fingerprint.filterNot { it.isWhitespace() } // Remove all spaces
.toLowerCase()
}.toSet()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package bisq.gradle.electrum

import bisq.gradle.electrum.tasks.ExtractElectrumAppFromDmgFile
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.Directory
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.Zip
import org.gradle.kotlin.dsl.register

class ElectrumBinaryPackager(
private val project: Project,
private val binaryDownloader: ElectrumBinaryDownloader,
) {
companion object {
private const val BINARIES_DIR = "${BisqElectrumPlugin.DATA_DIR}/binaries"
}

private val binariesDir: Provider<Directory> = project.layout.buildDirectory.dir(BINARIES_DIR)

fun registerTasks() {
val extractDmgOrCopyTask: TaskProvider<out Task> =
if (isMacOS()) {
registerDmgExtractionTask()
} else {
registerVerifiedElectrumBinary()
}

extractDmgOrCopyTask.configure {
dependsOn(binaryDownloader.lastTask)
}

val packageElectrumBinariesTask: TaskProvider<Zip> =
project.tasks.register<Zip>("packageElectrumBinaries") {
dependsOn(extractDmgOrCopyTask)

archiveFileName.set("electrum-binaries.zip")
destinationDirectory.set(project.layout.buildDirectory.dir("generated/src/main/resources"))

from(binariesDir)
}

val processResourcesTask = project.tasks.named("processResources")
processResourcesTask.configure {
dependsOn(packageElectrumBinariesTask)
}
}

private fun isMacOS() = getOS() == OS.MAC_OS

private fun registerDmgExtractionTask(): TaskProvider<out Task> =
project.tasks.register<ExtractElectrumAppFromDmgFile>("extractElectrumAppFromDmgFile") {
dmgFile.set(binaryDownloader.binaryFile)
outputDirectory.set(binariesDir)
}

private fun registerVerifiedElectrumBinary(): TaskProvider<out Task> =
project.tasks.register<Copy>("copyVerifiedElectrumBinary") {
from(binaryDownloader.binaryFile.asFile.absolutePath)
into(binariesDir.get().asFile.absolutePath)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package bisq.gradle.electrum

import java.util.*

enum class OS {
LINUX, MAC_OS, WINDOWS
}

fun getOS(): OS {
val osName = System.getProperty("os.name").toLowerCase(Locale.US)
if (isLinux(osName)) {
return OS.LINUX
} else if (isMacOs(osName)) {
return OS.MAC_OS
} else if (isWindows(osName)) {
return OS.WINDOWS
}

throw IllegalStateException("Running on unsupported OS: $osName")
}

private fun isLinux(osName: String): Boolean {
return osName.contains("linux")
}

private fun isMacOs(osName: String): Boolean {
return osName.contains("mac") || osName.contains("darwin")
}

private fun isWindows(osName: String): Boolean {
return osName.contains("win")
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ class SignatureVerifier(
) {

fun verifySignature(
fileToVerify: File,
signatureFile: File,
fileToVerify: File
): Boolean {
Security.addProvider(BouncyCastleProvider())

Expand Down

This file was deleted.

Loading

0 comments on commit 70614c4

Please sign in to comment.