handle signature and manifest file mixups, add file handler for .asc files

This commit is contained in:
Craig Raw 2024-03-04 15:07:56 +02:00
parent 803e43cb45
commit d109eaa654
7 changed files with 127 additions and 18 deletions

View file

@ -267,7 +267,7 @@ jlink {
appVersion = "${sparrowVersion}"
skipInstaller = os.macOsX || properties.skipInstallers
imageOptions = []
installerOptions = ['--file-associations', 'src/main/deploy/psbt.properties', '--file-associations', 'src/main/deploy/txn.properties', '--file-associations', 'src/main/deploy/bitcoin.properties', '--file-associations', 'src/main/deploy/auth47.properties', '--file-associations', 'src/main/deploy/lightning.properties', '--license-file', 'LICENSE']
installerOptions = ['--file-associations', 'src/main/deploy/psbt.properties', '--file-associations', 'src/main/deploy/txn.properties', '--file-associations', 'src/main/deploy/asc.properties', '--file-associations', 'src/main/deploy/bitcoin.properties', '--file-associations', 'src/main/deploy/auth47.properties', '--file-associations', 'src/main/deploy/lightning.properties', '--license-file', 'LICENSE']
if(os.windows) {
installerOptions += ['--win-per-user-install', '--win-dir-chooser', '--win-menu', '--win-menu-group', 'Sparrow', '--win-shortcut', '--resource-dir', 'src/main/deploy/package/windows/']
imageOptions += ['--icon', 'src/main/deploy/package/windows/sparrow.ico']

View file

@ -0,0 +1,3 @@
mime-type=application/pgp-signature
extension=asc
description=ASCII Armored Signature

View file

@ -94,6 +94,21 @@
<key>UTTypeIconFile</key>
<string>sparrow.icns</string>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>com.sparrowwallet.asc</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>asc</string>
</array>
</dict>
<key>UTTypeDescription</key>
<string>ASCII Armored Signature</string>
<key>UTTypeIconFile</key>
<string>sparrow.icns</string>
</dict>
</array>
<key>CFBundleDocumentTypes</key>
<array>

View file

@ -77,6 +77,7 @@ import java.util.*;
import java.util.stream.Collectors;
import static com.sparrowwallet.sparrow.AppServices.*;
import static com.sparrowwallet.sparrow.control.DownloadVerifierDialog.*;
public class AppController implements Initializable {
private static final Logger log = LoggerFactory.getLogger(AppController.class);
@ -992,6 +993,8 @@ public class AppController implements Initializable {
public void openFile(File file) {
if(isWalletFile(file)) {
openWalletFile(file, true);
} else if(isSignatureFile(file)) {
verifyDownload(new ActionEvent(file, rootStack));
} else {
openTransactionFile(file);
}
@ -1476,7 +1479,7 @@ public class AppController implements Initializable {
}
public void verifyDownload(ActionEvent event) {
DownloadVerifierDialog downloadVerifierDialog = new DownloadVerifierDialog();
DownloadVerifierDialog downloadVerifierDialog = new DownloadVerifierDialog(event.getSource() instanceof File file ? file : null);
downloadVerifierDialog.showAndWait();
}
@ -3041,6 +3044,13 @@ public class AppController implements Initializable {
}
}
@Subscribe
public void requestVerifyDownloadOpen(RequestVerifyDownloadEvent event) {
if(tabs.getScene().getWindow().equals(event.getWindow())) {
verifyDownload(new ActionEvent(event.getFile(), rootStack));
}
}
@Subscribe
public void functionAction(FunctionActionEvent event) {
selectTab(event.getWallet());

View file

@ -68,6 +68,8 @@ import java.util.*;
import java.util.List;
import java.util.stream.Collectors;
import static com.sparrowwallet.sparrow.control.DownloadVerifierDialog.*;
public class AppServices {
private static final Logger log = LoggerFactory.getLogger(AppServices.class);
@ -883,6 +885,8 @@ public class AppServices {
for(File file : openFiles) {
if(isWalletFile(file)) {
EventManager.get().post(new RequestWalletOpenEvent(openWindow, file));
} else if(isSignatureFile(file)) {
EventManager.get().post(new RequestVerifyDownloadEvent(openWindow, file));
} else {
EventManager.get().post(new RequestTransactionOpenEvent(openWindow, file));
}

View file

@ -42,6 +42,13 @@ public class DownloadVerifierDialog extends Dialog<ButtonBar.ButtonData> {
private static final DateFormat signatureDateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy z");
private static final long MAX_VALID_MANIFEST_SIZE = 100 * 1024;
private static final List<String> SIGNATURE_EXTENSIONS = List.of("asc", "sig", "gpg");
private static final List<String> MANIFEST_EXTENSIONS = List.of("txt");
private static final List<String> PUBLIC_KEY_EXTENSIONS = List.of("asc");
private static final List<String> MACOS_RELEASE_EXTENSIONS = List.of("dmg");
private static final List<String> WINDOWS_RELEASE_EXTENSIONS = List.of("exe", "zip");
private static final List<String> LINUX_RELEASE_EXTENSIONS = List.of("deb", "rpm", "tar.gz");
private final ObjectProperty<File> signature = new SimpleObjectProperty<>();
private final ObjectProperty<File> manifest = new SimpleObjectProperty<>();
private final ObjectProperty<File> publicKey = new SimpleObjectProperty<>();
@ -56,7 +63,7 @@ public class DownloadVerifierDialog extends Dialog<ButtonBar.ButtonData> {
private static File lastFileParent;
public DownloadVerifierDialog() {
public DownloadVerifierDialog(File initialSignatureFile) {
final DialogPane dialogPane = getDialogPane();
dialogPane.getStylesheets().add(AppServices.class.getResource("general.css").toExternalForm());
dialogPane.getStylesheets().add(AppServices.class.getResource("dialog.css").toExternalForm());
@ -74,9 +81,9 @@ public class DownloadVerifierDialog extends Dialog<ButtonBar.ButtonData> {
String version = VersionCheckService.getVersion() != null ? VersionCheckService.getVersion() : "x.x.x";
Field signatureField = setupField(signature, "Signature", List.of("asc", "sig", "gpg"), false, "sparrow-" + version + "-manifest.txt", null);
Field manifestField = setupField(manifest, "Manifest", List.of("txt"), false, "sparrow-" + version + "-manifest", null);
Field publicKeyField = setupField(publicKey, "Public Key", List.of("asc"), true, "pgp_keys", publicKeyDisabled);
Field signatureField = setupField(signature, "Signature", SIGNATURE_EXTENSIONS, false, "sparrow-" + version + "-manifest.txt", null);
Field manifestField = setupField(manifest, "Manifest", MANIFEST_EXTENSIONS, false, "sparrow-" + version + "-manifest", null);
Field publicKeyField = setupField(publicKey, "Public Key", PUBLIC_KEY_EXTENSIONS, true, "pgp_keys", publicKeyDisabled);
Field releaseFileField = setupField(release, "Release File", getReleaseFileExtensions(), false, getReleaseFileExample(version), null);
filesFieldset.getChildren().addAll(signatureField, manifestField, publicKeyField, releaseFileField);
@ -146,17 +153,18 @@ public class DownloadVerifierDialog extends Dialog<ButtonBar.ButtonData> {
signature.addListener((observable, oldValue, signatureFile) -> {
if(signatureFile != null) {
boolean verify = true;
if(PGPUtils.signatureContainsManifest(signatureFile)) {
File actualSignatureFile = findSignatureFile(signatureFile);
if(actualSignatureFile != null && !actualSignatureFile.equals(signature.get())) {
signature.set(actualSignatureFile);
verify = false;
} else if(PGPUtils.signatureContainsManifest(signatureFile)) {
manifest.set(signatureFile);
verify = false;
} else {
String signatureName = signatureFile.getName();
if(signatureName.length() > 4) {
File manifestFile = new File(signatureFile.getParent(), signatureName.substring(0, signatureName.length() - 4));
if(manifestFile.exists() && !manifestFile.equals(manifest.get())) {
manifest.set(manifestFile);
verify = false;
}
File manifestFile = findManifestFile(signatureFile);
if(manifestFile != null && !manifestFile.equals(manifest.get())) {
manifest.set(manifestFile);
verify = false;
}
}
@ -182,6 +190,16 @@ public class DownloadVerifierDialog extends Dialog<ButtonBar.ButtonData> {
}
}
}
if(release.get() == null) {
for(File file : manifestMap.keySet()) {
File releaseFile = new File(manifestFile.getParent(), file.getName());
if(releaseFile.exists() && !releaseFile.equals(release.get())) {
release.set(releaseFile);
verify = false;
break;
}
}
}
} catch(IOException e) {
log.debug("Error reading manifest file", e);
verify = false;
@ -203,6 +221,10 @@ public class DownloadVerifierDialog extends Dialog<ButtonBar.ButtonData> {
release.addListener((observable, oldValue, releaseFile) -> {
verify();
});
if(initialSignatureFile != null) {
javafx.application.Platform.runLater(() -> signature.set(initialSignatureFile));
}
}
public void verify() {
@ -382,17 +404,40 @@ public class DownloadVerifierDialog extends Dialog<ButtonBar.ButtonData> {
return null;
}
private File findSignatureFile(File providedFile) {
for(String extension : SIGNATURE_EXTENSIONS) {
File signatureFile = new File(providedFile.getParentFile(), providedFile.getName() + "." + extension);
if(signatureFile.exists()) {
return signatureFile;
}
}
return null;
}
private File findManifestFile(File providedFile) {
String signatureName = providedFile.getName();
if(signatureName.length() > 4 && SIGNATURE_EXTENSIONS.stream().anyMatch(ext -> signatureName.toLowerCase(Locale.ROOT).endsWith("." + ext))) {
File manifestFile = new File(providedFile.getParent(), signatureName.substring(0, signatureName.length() - 4));
if(manifestFile.exists()) {
return manifestFile;
}
}
return null;
}
private List<String> getReleaseFileExtensions() {
Platform platform = Platform.getCurrent();
switch(platform) {
case OSX -> {
return List.of("dmg");
return MACOS_RELEASE_EXTENSIONS;
}
case WINDOWS -> {
return List.of("exe", "zip");
return WINDOWS_RELEASE_EXTENSIONS;
}
default -> {
return List.of("deb", "rpm", "tar.gz");
default -> {
return LINUX_RELEASE_EXTENSIONS;
}
}
}
@ -432,6 +477,10 @@ public class DownloadVerifierDialog extends Dialog<ButtonBar.ButtonData> {
return message;
}
public static boolean isSignatureFile(File file) {
return file != null && file.getName().length() > 4 && SIGNATURE_EXTENSIONS.stream().anyMatch(ext -> file.getName().toLowerCase(Locale.ROOT).endsWith("." + ext));
}
private static class Header extends GridPane {
public Header() {
setMaxWidth(Double.MAX_VALUE);

View file

@ -0,0 +1,28 @@
package com.sparrowwallet.sparrow.event;
import javafx.stage.Window;
import java.io.File;
public class RequestVerifyDownloadEvent {
private final Window window;
private final File file;
public RequestVerifyDownloadEvent(Window window) {
this.window = window;
this.file = null;
}
public RequestVerifyDownloadEvent(Window window, File file) {
this.window = window;
this.file = file;
}
public Window getWindow() {
return window;
}
public File getFile() {
return file;
}
}