Sig verification for multiple keys

but download somehow does not work anymore :s
TODO: Open download folder, give qualified report about signatures
This commit is contained in:
SimonTuberlin 2017-05-04 16:48:00 +02:00 committed by Mike Rosseel
parent 5a8235008a
commit 3e895f45e4
5 changed files with 337 additions and 58 deletions

View File

@ -0,0 +1,8 @@
package io.bitsquare.common.util;
public enum DownloadType {
INST,
KEY,
SIG,
MISC
}

View File

@ -8,53 +8,73 @@ package io.bitsquare.common.util;
import javafx.concurrent.Task;
import javafx.scene.control.ProgressIndicator;
import org.bouncycastle.openpgp.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.Security;
import java.security.SignatureException;
import java.util.Iterator;
public class DownloadUtil extends Task<File> {
/* public static void main(String [] args) {
try {
downloadFile("https://github.com/bitsquare/bitsquare/releases/download/v0.4.9.9/Bitsquare-64bit-0.4.9.9.exe", "/home/bob/test");
} catch (IOException e) {e.printStackTrace();}
}
*/
private static final int BUFFER_SIZE = 4096;
private String fileName = null;
private final String fileURL;
private final String saveDir;
private DownloadType downloadType;
private byte index; // For numbering tasks to diferent pubkeys/signatures
/**
* Downloads a file from a URL
* Prepares a task to download a file from {@code fileURL} to {@code saveDir}.
* @param fileURL HTTP URL of the file to be downloaded
* @param saveDir path of the directory to save the file
*/
public DownloadUtil (final String fileURL, final String saveDir) {
public DownloadUtil(final String fileURL, final String saveDir, DownloadType downloadType, byte index) {
super();
this.fileURL = fileURL;
this.saveDir = saveDir;
this.downloadType = downloadType;
this.index = index;
}
public DownloadUtil(final String fileURL, final String saveDir) {
this(fileURL, saveDir, DownloadType.MISC, (byte) -1);
}
/**
* Downloads a file from a URL
* Prepares a task to download a file from {@code fileURL} to the sysyem's temp dir specified in java.io.tmpdir.
* @param fileURL HTTP URL of the file to be downloaded
*/
public DownloadUtil (final String fileURL) {
this.fileURL = fileURL;
this.saveDir = System.getProperty("java.io.tmpdir");
public DownloadUtil(final String fileURL, DownloadType downloadType, byte index) {
this(fileURL, "/home/bob/Downloads/", downloadType, index);
//TODO final String saveDir = System.getProperty("java.io.tmpdir");
// final String saveDir = "/home/bob/Downloads/";
System.out.println("Auto-selected temp dir " + this.saveDir);
}
@Override protected File call() throws Exception{
public DownloadUtil(final String fileURL) {
this(fileURL, DownloadType.MISC, (byte) -1);
}
/**
* Starts the task and therefore the actual download.
* @return A reference to the created file or {@code null} if no file could be found at the provided URL
* @throws IOException Forwarded exceotions from HttpURLConnection and file handling methods
*/
@Override protected File call() throws IOException {
System.out.println("Task started....");
URL url = new URL(fileURL);
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
int responseCode = httpConn.getResponseCode();
// always check HTTP response code first
if (responseCode == HttpURLConnection.HTTP_OK) {
/* if (responseCode == HttpURLConnection.HTTP_OK) {
String fileName = "";
String disposition = httpConn.getHeaderField("Content-Disposition");
String contentType = httpConn.getContentType();
@ -66,25 +86,21 @@ public class DownloadUtil extends Task<File> {
// extracts file name from header field
int index = disposition.indexOf("filename=");
if (index > 0) {
fileName = disposition.substring(index + 9, disposition.length());
this.fileName = disposition.substring(index + 9, disposition.length());
}
} else {
// extracts file name from URL
fileName = fileURL.substring(fileURL.lastIndexOf("/") + 1, fileURL.length());
}
*/ this.fileName = fileURL.substring(fileURL.lastIndexOf("/") + 1, fileURL.length());
/* }
/* System.out.println("Content-Type = " + contentType);
System.out.println("Content-Disposition = " + disposition);
System.out.println("Content-Length = " + contentLength);
System.out.println("fileName = " + fileName);
*/
// opens input stream from the HTTP connection
InputStream inputStream = httpConn.getInputStream();
String saveFilePath = saveDir + File.separator + fileName;
*/ String saveFilePath = saveDir + (saveDir.endsWith(File.separator) ? "" : File.separator) + fileName;
// opens an output stream to save into file
File outputFile = new File(saveFilePath);
FileOutputStream outputStream = new FileOutputStream(outputFile);
/* FileOutputStream outputStream = new FileOutputStream(outputFile);
int bytesRead;
int totalRead = 0;
@ -103,13 +119,102 @@ public class DownloadUtil extends Task<File> {
} finally {
inputStream.close();
}
System.out.println("File downloaded");
return outputFile.getParentFile();
} else {
*/
return outputFile;
/* } else {
System.out.println("No file to download. Server replied HTTP code: " + responseCode);
}
httpConn.disconnect();
return null;
*/ }
/**
* Verifies detached PGP signatures against GPG/openPGP RSA public keys. Does currently not work with openssl or JCA/JCE keys.
* @param pubKeyFilename Path to file providing the public key to use
* @param sigFilename Path to detached signature file
* @param dataFilename Path to signed data file
* @return {@code true} if signature is valid, {@code false} if signature is not valid
* @throws Exception throws various exceptions in case something went wrong. Main reason should be that key or
* signature could be extracted from the provided files due to a "bad" format.<br>
* <code>FileNotFoundException, IOException, SignatureException, PGPException</code>
*/
public static boolean verifySignature(File pubKeyFile, File sigFile, File dataFile) throws Exception {
// private final String pubkeyFilename = "/home/bob/Downloads/F379A1C6.asc.gpg";
// private final String sigFilename = "/home/bob/Downloads/sig.asc";
// private final String dataFilename = "/home/bob/Downloads/Bitsquare-64bit-0.4.9.9.1.deb";
Security.addProvider(new BouncyCastleProvider());
InputStream is;
int bytesRead;
PGPPublicKey publicKey;
PGPSignature pgpSignature;
boolean result;
// Read keys from file
is = PGPUtil.getDecoderStream(new FileInputStream(pubKeyFile));
PGPPublicKeyRingCollection publicKeyRingCollection = new PGPPublicKeyRingCollection(is);
is.close();
Iterator<PGPPublicKeyRing> rIt = publicKeyRingCollection.getKeyRings();
PGPPublicKeyRing pgpPublicKeyRing;
if (rIt.hasNext()) {
pgpPublicKeyRing = rIt.next();
} else {
throw new PGPException("Could not find public keyring in provided key file");
}
// Would be the solution for multiple keys in one file
// Iterator<PGPPublicKey> kIt;
// kIt = pgpPublicKeyRing.getPublicKeys();
// publicKey = pgpPublicKeyRing.getPublicKey(0xF5B84436F379A1C6L);
// Read signature from file
is = PGPUtil.getDecoderStream(new FileInputStream(sigFile));
PGPObjectFactory pgpObjectFactory = new PGPObjectFactory(is);
Object o = pgpObjectFactory.nextObject();
if (o instanceof PGPSignatureList) {
PGPSignatureList signatureList = (PGPSignatureList) o;
pgpSignature = signatureList.get(0);
} else if (o instanceof PGPSignature) {
pgpSignature = (PGPSignature) o;
} else {
throw new SignatureException("Could not find signature in provided signature file");
}
is.close();
System.out.format("KeyID used in signature: %X\n", pgpSignature.getKeyID());
publicKey = pgpPublicKeyRing.getPublicKey(pgpSignature.getKeyID());
System.out.format("The ID of the selected key is %X\n", publicKey.getKeyID());
pgpSignature.initVerify(publicKey, "BC");
// Read file to verify
try {
byte[] data = new byte[1024];
is = new DataInputStream(new BufferedInputStream(new FileInputStream(dataFile)));
while (true) {
bytesRead = is.read(data, 0, 1024);
if (bytesRead == -1)
break;
pgpSignature.update(data, 0, bytesRead);
}
is.close();
// Verify the signature
result = pgpSignature.verify();
System.out.println("The signature turned out to be " + (result ? "good." : "bad."));
return result;
} catch (SignatureException sigExc) {
try {
is.close();
} catch (IOException ioExc) {}
throw sigExc;
}
}
}
public String getFileName() {return this.fileName;}
public DownloadType getDownloadType() {return downloadType;}
public byte getIndex() {return index;}
}

View File

@ -33,6 +33,7 @@ import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import javax.crypto.Cipher;
import java.awt.*;
import java.io.*;
@ -207,12 +208,22 @@ public class Utilities {
}
}
public static Task<File> downloadFile(String fileURL, String saveDir, ProgressIndicator indicator) throws IOException {
/**
* Creates and starts a Task for background downloading
* @param fileURL URL of file to be downloaded
* @param saveDir Directory to save file to
* @param indicator Progress indicator, can be {@code null}
* @param downloadType enum to identify downloaded files after completion, options are {INST, KEY, SIG, MISC}
* @param index For coordination between key and sig files
* @return The task handling the download
* @throws IOException
*/
public static DownloadUtil downloadFile(String fileURL, String saveDir, @Nullable ProgressIndicator indicator, DownloadType downloadType, byte index) throws IOException {
DownloadUtil task;
if (saveDir != null)
task = new DownloadUtil(fileURL, saveDir);
task = new DownloadUtil(fileURL, saveDir, downloadType, index);
else
task = new DownloadUtil(fileURL); // Tries to use system temp directory
task = new DownloadUtil(fileURL, downloadType, index); // Tries to use system temp directory
if (indicator != null) {
indicator.progressProperty().unbind();
indicator.progressProperty().bind(task.progressProperty());

View File

@ -18,14 +18,19 @@
package io.bitsquare.gui.main.overlays.windows;
import io.bitsquare.alert.Alert;
import io.bitsquare.common.util.DownloadType;
import io.bitsquare.common.util.DownloadUtil;
import io.bitsquare.common.util.Utilities;
import io.bitsquare.gui.main.overlays.Overlay;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.GridPane;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -37,10 +42,18 @@ import static io.bitsquare.gui.util.FormBuilder.addButton;
import static io.bitsquare.gui.util.FormBuilder.addLabelHyperlinkWithIcon;
import static io.bitsquare.gui.util.FormBuilder.addMultilineLabel;
// TODO: For future use sigFile and key download calls have to loop through keyIDs and verifySignature needs to be called for all sigs/keys
public class DisplayUpdateDownloadWindow extends Overlay<DisplayUpdateDownloadWindow> {
private static final String [] keyIDs = {"F379A1C6"};
private static final Logger log = LoggerFactory.getLogger(DisplayUpdateDownloadWindow.class);
private Alert alert;
private Task<File> task;
private DownloadUtil installerTask, keyTasks[], sigTasks[];
private boolean downloadSuccessInstaller, downloadSuccessKey[], downloadSuccessSignature[];
private File installerFile, pubKeyFile[], sigFile[];
private Label messageLabel;
private ProgressIndicator indicator;
private final String DOWNLOAD_FAILED = "Download failed. Please download and verify the correct file yourself from https://bitsquare.io/downloads/";
///////////////////////////////////////////////////////////////////////////////////////////
@ -74,13 +87,13 @@ public class DisplayUpdateDownloadWindow extends Overlay<DisplayUpdateDownloadWi
private void addContent() {
checkNotNull(alert, "alertMessage must not be null");
Label messageLabel = addMultilineLabel(gridPane, ++rowIndex, alert.message, 10);
messageLabel = addMultilineLabel(gridPane, ++rowIndex, alert.message, 10);
headLine = "Important update information!";
headLineLabel.setStyle("-fx-text-fill: -fx-accent; -fx-font-weight: bold; -fx-font-size: 22;");
ProgressIndicator indicator = new ProgressIndicator(0L);
indicator = new ProgressIndicator(0L);
indicator.setVisible(false);
GridPane.setRowIndex(indicator, ++rowIndex);
GridPane.setColumnIndex(indicator, 1);
@ -88,7 +101,7 @@ public class DisplayUpdateDownloadWindow extends Overlay<DisplayUpdateDownloadWi
Button downloadButton = addButton(gridPane, ++rowIndex, "Download now");
// TODO How do we get the right URL for the download?
// TODO How do we get the right URL for the download? (check for other platforms)
String url = "https://github.com/bitsquare/bitsquare/releases/download/" + "v" + alert.version + "/" ;
String fileName;
if (Utilities.isOSX())
@ -105,36 +118,96 @@ public class DisplayUpdateDownloadWindow extends Overlay<DisplayUpdateDownloadWi
}
downloadButton.setOnAction(e -> {
String source = url.concat(fileName);
indicator.setVisible(true);
downloadButton.setDisable(true);
// download installer
downloadSuccessInstaller = false;
try {
System.out.println("Button: " + source);
task = Utilities.downloadFile(source, null, indicator);
task.setOnSucceeded(evt -> {
if (task.getValue() == null) {
messageLabel.setText("Download failed. Please download and verify the correct file yourself from https://bitsquare.io/downloads/");
return;
}
try {
Utilities.openDirectory(task.getValue());
messageLabel.setText("Successfully downloaded file " + fileName);
} catch (IOException exc) {
messageLabel.setText("Unable to open download directory " + task.getValue() + " in file browser.");
exc.printStackTrace();
}
});
installerTask = Utilities.downloadFile(url.concat(fileName), null, indicator, DownloadType.INST, (byte) 0);
} catch (IOException exception) {
messageLabel.setText("Unable to download files.\n" +
"Please manually download and verify the file from https://bitsquare.io/downloads");
messageLabel.setText(DOWNLOAD_FAILED);
return;
}
installerTask.setOnSucceeded(evt -> {
/* installerFile = installerTask.getValue();
if (installerFile == null) {
messageLabel.setText(DOWNLOAD_FAILED);
} else {
downloadSuccessInstaller = true;
verifySignature();
}
*/
System.out.println("Completed installer");
updateStatusAndFileReferences(installerTask);
verifySignature();
});
// download key file
keyTasks = new DownloadUtil[keyIDs.length];
pubKeyFile = new File[keyIDs.length];
downloadSuccessKey = new boolean[keyIDs.length];
byte i = 0;
try {
for (String keyID : keyIDs) {
keyTasks[i] = Utilities.downloadFile(url.concat(keyID + ".asc"), null, null, DownloadType.KEY, i);
i++;
}
} catch (IOException exception) {
messageLabel.setText(DOWNLOAD_FAILED);
return;
}
for (DownloadUtil task : keyTasks) {
// keyTasks[i].setOnSucceeded(new TaskSucceededHandler(keyTasks[i], pubKeyFile[i], downloadSuccessKey[i]));
task.setOnSucceeded(evt -> {
/* file = task.getValue();
if (file == null) {
messageLabel.setText(DOWNLOAD_FAILED);
} else {
downloadSuccessKey[j] = true;
verifySignature();
}
*/
System.out.println("Completed key");
updateStatusAndFileReferences(task);
verifySignature();
});
}
//TODO
// download signature file
sigTasks = new DownloadUtil[keyIDs.length];
sigFile = new File[keyIDs.length];
downloadSuccessSignature = new boolean[] {false};
try {
sigTasks[0] = Utilities.downloadFile(url.concat(fileName /*+ "-" + keyIDs[0]*/ + ".asc"), null, null, DownloadType.SIG, (byte) 0);
} catch (IOException exception) {
messageLabel.setText(DOWNLOAD_FAILED);
return;
}
sigTasks[0].setOnSucceeded(evt -> {
System.out.println("Completed sig");
sigFile[0] = sigTasks[0].getValue();
if (sigFile == null) {
messageLabel.setText(DOWNLOAD_FAILED);
} else {
downloadSuccessSignature[0] = true;
verifySignature();
}
});
});
//TODO
closeButton = new Button("Close");
closeButton.setOnAction(e -> {
hide();
if (task != null && task.isRunning())
task.cancel();
if (installerTask != null && installerTask.isRunning())
installerTask.cancel();
if (keyTasks[0] != null && keyTasks[0].isRunning())
keyTasks[0].cancel();
if (sigTasks[0] != null && sigTasks[0].isRunning())
sigTasks[0].cancel();
closeHandlerOptional.ifPresent(closeHandler -> closeHandler.run());
});
@ -144,5 +217,87 @@ public class DisplayUpdateDownloadWindow extends Overlay<DisplayUpdateDownloadWi
GridPane.setMargin(closeButton, new Insets(10, 0, 0, 0));
}
private void updateStatusAndFileReferences(DownloadUtil sourceTask) {
switch (sourceTask.getDownloadType()) {
case INST:
installerFile = sourceTask.getValue();
System.out.println(installerFile.getName());
if (installerFile != null)
downloadSuccessInstaller = true;
break;
case KEY:
pubKeyFile[sourceTask.getIndex()] = sourceTask.getValue();
System.out.println(sourceTask.getIndex() + "\t" + pubKeyFile[sourceTask.getIndex()].getName());
if (pubKeyFile != null)
downloadSuccessKey[sourceTask.getIndex()] = true;
break;
case SIG:
sigFile[sourceTask.getIndex()] = sourceTask.getValue();
System.out.println(sourceTask.getIndex() + "\t" + sigFile[sourceTask.getIndex()].getName());
if (sigFile[sourceTask.getIndex()] != null)
downloadSuccessSignature[sourceTask.getIndex()] = true;
break;
}
}
private void verifySignature () {
System.out.print("Inst.: " + downloadSuccessInstaller);
System.out.print("\tKey: " + downloadSuccessKey[0]);
System.out.println("\tSig.: " + downloadSuccessSignature[0]);
if (downloadSuccessInstaller && arrayAnd(downloadSuccessKey) && arrayAnd(downloadSuccessSignature)) {
VerificationResult [] results = new VerificationResult[keyIDs.length];
for (int i=0; i<keyIDs.length; i++) {
try {
boolean verified = DownloadUtil.verifySignature(pubKeyFile[i], sigFile[i], installerFile);
results[i] = verified ? VerificationResult.GOOD : VerificationResult.BAD;
/* if (verified) {
messageLabel.setText("The signature turned out to be GOOD. Please install " + installerFile.getName() + " from " + installerFile.getParent());
Utilities.openDirectory(installerFile.getParentFile());
} else {
indicator.setVisible(false);
messageLabel.setText("The downloaded installer file showed a BAD signature. The file may have been manipulated.");
}
*/ } catch (Exception exc) {
results[i] = VerificationResult.EXCEPTION;
indicator.setVisible(false);
messageLabel.setText("Signature check caused an exception. This does not yet mean a bad signature. Please try to verify yourself.");
exc.printStackTrace();
}
}
StringBuilder sb = new StringBuilder();
for (int i=0; i<keyIDs.length; i++)
sb.append(keyIDs[i] + ":\t"+ results[i] + "\n");
System.out.print(sb.toString());
indicator.setVisible(false);
messageLabel.setText(sb.toString());
}
}
private boolean arrayAnd(boolean [] array) {
boolean and = true;
for (boolean b : array)
and &= b;
return and;
}
}
class TaskSucceededHandler implements EventHandler<WorkerStateEvent> {
Task<File> task;
File file;
Boolean success;
TaskSucceededHandler(@NotNull Task<File> t, File f, Boolean s) {
task = t;
file = f;
}
public void handle(WorkerStateEvent evt) {
file = task.getValue();
if (file != null)
success = true;
}
}
enum VerificationResult {
GOOD,
BAD,
EXCEPTION
}

View File

@ -127,7 +127,7 @@
<version>4.1.0</version>
</dependency>
<!--crypto-->
<!--crypto
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>