[#21: Add signature validation during installations [partial]] [Updated packages] [Removed Hyperspace] [Updated README]
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
# Changelog #
|
||||
## 1.0.3 ##
|
||||
* Linux distribution support
|
||||
* Bodhi 5.0.0
|
||||
* Debian 9
|
||||
* Elementary OS 5.0
|
||||
* Linux Mint 19
|
||||
* Linux Mint 19.1
|
||||
* Solus
|
||||
@@ -9,6 +11,8 @@
|
||||
* Ubuntu 18.10
|
||||
* Ubuntu 19.04
|
||||
* Removed `react-css-modules` dependency
|
||||
* Removed Hyperspace (no active development/insufficient host network)
|
||||
* Added signature or SHA-256 validation to downloads
|
||||
|
||||
## 1.0.2 ##
|
||||
* Option to launch application hidden (notification icon only)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Repertory UI
|
||||

|
||||
### GUI for [Repertory](https://bitbucket.org/blockstorage/repertory) ###
|
||||
Repertory allows you to mount Sia, SiaPrime and/or Hyperspace blockchain storage solutions via FUSE on Linux/OS X or via WinFSP on Windows.
|
||||
Repertory allows you to mount Sia and/or SiaPrime blockchain storage solutions via FUSE on Linux/OS X or via WinFSP on Windows.
|
||||
# Downloads #
|
||||
* [Repertory UI v1.0.3 Linux 64-bit]()
|
||||
* [Repertory UI v1.0.3 OS X 64-bit]()
|
||||
@@ -10,7 +10,9 @@ Repertory allows you to mount Sia, SiaPrime and/or Hyperspace blockchain storage
|
||||
* OS X 64-bit
|
||||
* Windows 64-bit
|
||||
* Linux 64-bit Distributions:
|
||||
* Bodhi 5.0.0
|
||||
* Debian 9
|
||||
* Elementary OS 5.0
|
||||
* Linux Mint 19
|
||||
* Linux Mint 19.1
|
||||
* Solus
|
||||
|
||||
19
package.json
19
package.json
@@ -5,28 +5,29 @@
|
||||
"author": "scott.e.graves@gmail.com",
|
||||
"description": "GUI for Repertory - Repertory allows you to mount Hyperspace, Sia and/or SiaPrime blockchain storage solutions via FUSE on Linux/OS X or via WinFSP on Windows.",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.10",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.6.1",
|
||||
"@fortawesome/react-fontawesome": "^0.1.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.17",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.8.1",
|
||||
"@fortawesome/react-fontawesome": "^0.1.4",
|
||||
"auto-launch": "^5.0.5",
|
||||
"axios": "^0.18.0",
|
||||
"electron-debug": "^2.2.0",
|
||||
"font-awesome": "^4.7.0",
|
||||
"node-schedule": "^1.3.1",
|
||||
"randomstring": "^1.1.5",
|
||||
"node-schedule": "^1.3.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-loader-spinner": "^2.3.0",
|
||||
"react-scripts": "2.1.8",
|
||||
"react-tooltip": "^3.9.0",
|
||||
"unzipper": "^0.9.6",
|
||||
"react-tooltip": "^3.10.0",
|
||||
"unzipper": "^0.9.11",
|
||||
"winreg": "^1.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^5.2.0",
|
||||
"electron": "^4.1.0",
|
||||
"electron-builder": "^20.38.5",
|
||||
"electron": "^4.1.4",
|
||||
"electron-builder": "^20.40.2",
|
||||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"typescript": "^3.4.2",
|
||||
"typescript": "^3.4.3",
|
||||
"webpack-browser-plugin": "^1.0.20"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -11,10 +11,37 @@ const helpers = require('../src/helpers');
|
||||
const fs = require('fs');
|
||||
const unzip = require('unzipper');
|
||||
const AutoLaunch = require('auto-launch');
|
||||
|
||||
require.extensions['.sh'] = function (module, filename) {
|
||||
module.exports = fs.readFileSync(filename, 'utf8');
|
||||
};
|
||||
const detectScript = require('./detect_linux.sh');
|
||||
const publicKey =
|
||||
'-----BEGIN PUBLIC KEY-----\n' +
|
||||
'MIIEIjANBgkqhkiG9w0BAQEFAAOCBA8AMIIECgKCBAEKfZmq5mMAtD4kSt2Gc/5J\n' +
|
||||
'H+HHTYtUZE6YYvsvz8TNG/bNL67ZtNRyaoMyhLTfIN4rPBNLUfD+owNS+u5Yk+lS\n' +
|
||||
'ZLYyOuhoCZIFefayYqKLr42G8EeuRbx0IMzXmJtN0a4rqxlWhkYufJubpdQ+V4DF\n' +
|
||||
'oeupcPdIATaadCKVeZC7A0G0uaSwoiAVMG5dZqjQW7F2LoQm3PhNkPvAybIJ6vBy\n' +
|
||||
'LqdBegS1JrDn43x/pvQHzLO+l+FIG23D1F7iF+yZm3DkzBdcmi/mOMYs/rXZpBym\n' +
|
||||
'2/kTuSGh5buuJCeyOwR8N3WdvXw6+KHMU/wWU8qTCTT87mYbzH4YR8HgkjkLHxAO\n' +
|
||||
'5waHK6vMu0TxugCdJmVV6BSbiarJsh66VRosn7+6hlq6AdgksxqCeNELZBS+LBki\n' +
|
||||
'tb5hKyL+jNZnaHiR0U7USWtmnqZG6FVVRzlCnxP7tZo5O5Ex9AAFGz5JzOzsFNbv\n' +
|
||||
'xwQ0zqaTQOze+MJbkda7JfRoC6TncD0+3hoXsiaF4mCn8PqUCn0DwhglcRucZlST\n' +
|
||||
'ZvDNDo1WAtxPJebb3aS6uymNhBIquQbVAWxVO4eTrOYEgutxwkHE3yO3is+ogp8d\n' +
|
||||
'xot7f/+vzlbsbIDyuZBDe0fFkbTIMTU48QuUUVZpRKmKZTHQloz4EHqminbfX1sh\n' +
|
||||
'M7wvDkpJEtqbc0VnG/BukUzP6e7Skvgc7eF1sI3+8jH8du2rivZeZAl7Q2f+L9JA\n' +
|
||||
'BY9pjaxttxsud7V5jeFi4tKuDHi21/XhSjlJK2c2C4AiUEK5/WhtGbQ5JjmcOjRq\n' +
|
||||
'yXFRqLlerzOcop2kbtU3Ar230wOx3Dj23Wg8++lV3LU4U9vMR/t0qnSbCSGJys7m\n' +
|
||||
'ax2JpFlTwj/0wYuTlVFoNQHZJ1cdfyRiRBY4Ou7XO0W5hcBBKiYsC+neEeMMHdCe\n' +
|
||||
'iTDIW/ojcVTdFovl+sq3n1u4SBknE90JC/3H+TPE1s2iB+fwORVg0KPosQSNDS0A\n' +
|
||||
'7iK6AZCDC3YooFo+OzHkYMt9uLkXiXMSLx70az+qlIwOzVHKxCo7W/QpeKCXUCRZ\n' +
|
||||
'MMdlYEUs1PC8x2qIRUEVHuJ0XMTKNyOHmzVLuLK93wUWbToh+rdDxnbhX+emuESn\n' +
|
||||
'XH6aKiUwX4olEVKSylRUQw8nVckZGVWXzLDlgpzDrLHC8J8qHzFt7eCqOdiqsxhZ\n' +
|
||||
'x1U5LtugxwSWncTZ7vlKl0DuC/AWB7SuDi7bGRMSVp2n+MnD1VLKlsCclHXjIciE\n' +
|
||||
'W29n3G3lJ/sOta2sxqLd0j1XBQddrFXl5b609sIY81ocHqu8P2hRu5CpqJ/sGZC5\n' +
|
||||
'mMH3segHBkRj0xJcfOxceRLj1a+ULIIR3xL/3f8s5Id25TDo/nqBoCvu5PeCpo6L\n' +
|
||||
'9wIDAQAB\n' +
|
||||
'-----END PUBLIC KEY-----';
|
||||
|
||||
// Keep a global reference of the window object, if you don't, the window will
|
||||
// be closed automatically when the JavaScript object is garbage collected.
|
||||
@@ -582,9 +609,10 @@ ipcMain.on(Constants.IPC_Get_Config_Template, (event, data) => {
|
||||
ipcMain.on(Constants.IPC_Get_Platform, (event) => {
|
||||
let platform = os.platform();
|
||||
if (platform === 'linux') {
|
||||
fs.writeFileSync('/tmp/repertory_detect_linux.sh', detectScript);
|
||||
const scriptFile = path.join(os.tmpdir(), 'repertory_detect_linux.sh');
|
||||
fs.writeFileSync(scriptFile, detectScript);
|
||||
helpers
|
||||
.executeScript('/tmp/repertory_detect_linux.sh')
|
||||
.executeScript(scriptFile)
|
||||
.then(data => {
|
||||
platform = data.replace(/(\r\n|\n|\r)/gm,"");
|
||||
event.sender.send(Constants.IPC_Get_Platform_Reply, {
|
||||
@@ -661,29 +689,101 @@ ipcMain.on(Constants.IPC_Install_Dependency, (event, data) => {
|
||||
});
|
||||
|
||||
ipcMain.on(Constants.IPC_Install_Upgrade, (event, data) => {
|
||||
let tempSig;
|
||||
let tempPub;
|
||||
const hasSignature = data.Signature && (data.Signature.length > 0);
|
||||
const hasHash = data.Sha256 && (data.Sha256.length > 0);
|
||||
if (hasSignature) {
|
||||
try {
|
||||
const files = helpers.createSignatureFiles(data.Signature, publicKey);
|
||||
tempPub = files.PublicKeyFile;
|
||||
tempSig = files.SignatureFile;
|
||||
} catch (e) {
|
||||
standardIPCReply(event, Constants.IPC_Install_Upgrade_Reply, {
|
||||
Source: data.Source,
|
||||
}, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const cleanupFiles = () => {
|
||||
try {
|
||||
if (tempSig) {
|
||||
fs.unlinkSync(tempSig);
|
||||
}
|
||||
if (tempPub) {
|
||||
fs.unlinkSync(tempPub);
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
};
|
||||
|
||||
if (os.platform() === 'win32') {
|
||||
helpers
|
||||
const executeInstall = () => {
|
||||
helpers
|
||||
.executeAsync(data.Source)
|
||||
.then(() => {
|
||||
cleanupFiles();
|
||||
closeApplication();
|
||||
})
|
||||
.catch(error => {
|
||||
cleanupFiles();
|
||||
standardIPCReply(event, Constants.IPC_Install_Upgrade_Reply, {
|
||||
Source: data.Source,
|
||||
}, error);
|
||||
});
|
||||
};
|
||||
if (hasSignature) {
|
||||
helpers
|
||||
.verifySignature(data.Source, tempSig, tempPub)
|
||||
.then(() => {
|
||||
executeInstall();
|
||||
})
|
||||
.catch(() => {
|
||||
cleanupFiles();
|
||||
standardIPCReply(event, Constants.IPC_Install_Upgrade_Reply, {
|
||||
Source: data.Source,
|
||||
}, 'Failed to verify installation package signature');
|
||||
});
|
||||
} else { // TODO Check Sha256
|
||||
executeInstall();
|
||||
}
|
||||
} else if (data.Source.toLocaleLowerCase().endsWith('.dmg')) {
|
||||
helpers
|
||||
const executeInstall = () => {
|
||||
helpers
|
||||
.executeAsync('open', ['-a', 'Finder', data.Source])
|
||||
.then(() => {
|
||||
cleanupFiles();
|
||||
closeApplication();
|
||||
})
|
||||
.catch(error => {
|
||||
cleanupFiles();
|
||||
standardIPCReply(event, Constants.IPC_Install_Upgrade_Reply, {
|
||||
Source: data.Source,
|
||||
}, error);
|
||||
});
|
||||
};
|
||||
|
||||
if (hasHash) {
|
||||
helpers
|
||||
.verifyHash(data.Source, data.Sha256)
|
||||
.then(()=> {
|
||||
executeInstall();
|
||||
})
|
||||
.catch(() => {
|
||||
cleanupFiles();
|
||||
standardIPCReply(event, Constants.IPC_Install_Upgrade_Reply, {
|
||||
Source: data.Source,
|
||||
}, 'Failed to verify installation package hash');
|
||||
});
|
||||
} else {
|
||||
executeInstall();
|
||||
}
|
||||
} else if (data.Source.toLocaleLowerCase().endsWith('.appimage')) {
|
||||
cleanupFiles();
|
||||
standardIPCReply(event, Constants.IPC_Install_Upgrade_Reply, {
|
||||
Source: data.Source,
|
||||
}, Error('Not implemented upgrade: ' + data.Source));
|
||||
// TODO Generate and execute script with delay
|
||||
/*helpers
|
||||
.executeAsync(data.Source)
|
||||
@@ -696,6 +796,7 @@ ipcMain.on(Constants.IPC_Install_Upgrade, (event, data) => {
|
||||
}, error);
|
||||
});*/
|
||||
} else {
|
||||
cleanupFiles();
|
||||
standardIPCReply(event, Constants.IPC_Install_Upgrade_Reply, {
|
||||
Source: data.Source,
|
||||
}, Error('Unsupported upgrade: ' + data.Source));
|
||||
|
||||
@@ -2,18 +2,21 @@
|
||||
"Locations": {
|
||||
"win32": {
|
||||
"1.0.3": {
|
||||
"sha256": "",
|
||||
"sig": "",
|
||||
"urls": []
|
||||
}
|
||||
},
|
||||
"darwin": {
|
||||
"1.0.3": {
|
||||
"sha256": "",
|
||||
"sig": "",
|
||||
"urls": []
|
||||
}
|
||||
},
|
||||
"solus": {
|
||||
"1.0.3": {
|
||||
"sha256": "",
|
||||
"sig": "",
|
||||
"urls": []
|
||||
}
|
||||
|
||||
@@ -295,7 +295,11 @@ class App extends IPCContainer {
|
||||
|
||||
installUpgrade = data => {
|
||||
if (data.Success) {
|
||||
const sha256 = this.state.LocationsLookup[this.props.platform][this.state.VersionLookup[this.props.platform][0]].sha256;
|
||||
const signature = this.state.LocationsLookup[this.props.platform][this.state.VersionLookup[this.props.platform][0]].sig;
|
||||
this.sendRequest(Constants.IPC_Install_Upgrade, {
|
||||
Sha256: sha256,
|
||||
Signature: signature,
|
||||
Source: data.Destination,
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -15,13 +15,13 @@ exports.DATA_LOCATIONS = {
|
||||
exports.UI_RELEASES_URL = 'https://bitbucket.org/blockstorage/repertory-ui/raw/1.0.3_branch/releases.json';
|
||||
|
||||
exports.PROVIDER_LIST = [
|
||||
'Hyperspace',
|
||||
//'Hyperspace',
|
||||
'Sia',
|
||||
'SiaPrime'
|
||||
];
|
||||
|
||||
exports.PROVIDER_ARG = {
|
||||
hyperspace: '-hs',
|
||||
//hyperspace: '-hs',
|
||||
sia: '',
|
||||
siaprime: '-sp'
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ const axios = require('axios/index');
|
||||
const exec = require('child_process').exec;
|
||||
const spawn = require('child_process').spawn;
|
||||
const Constants = require('./constants');
|
||||
const RandomString = require('randomstring');
|
||||
|
||||
const tryParse = (j, def) => {
|
||||
try {
|
||||
@@ -14,6 +15,29 @@ const tryParse = (j, def) => {
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.createSignatureFiles = (signature, publicKey) => {
|
||||
const fileName1 = RandomString.generate({
|
||||
length: 12,
|
||||
charset: 'alphabetic'
|
||||
});
|
||||
const fileName2 = RandomString.generate({
|
||||
length: 12,
|
||||
charset: 'alphabetic'
|
||||
});
|
||||
|
||||
const signatureFile = path.join(os.tmpdir(), fileName1 + '.sig');
|
||||
const publicKeyFile = path.join(os.tmpdir(), fileName2 + '.pub');
|
||||
|
||||
const buffer = new Buffer(signature, 'base64');
|
||||
fs.writeFileSync(signatureFile, buffer);
|
||||
fs.writeFileSync(publicKeyFile, publicKey);
|
||||
|
||||
return {
|
||||
PublicKeyFile: publicKeyFile,
|
||||
SignatureFile: signatureFile,
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.detectRepertoryMounts = (directory, version) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const processOptions = {
|
||||
@@ -93,13 +117,13 @@ module.exports.downloadFile = (url, destination, progressCallback, completeCallb
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.executeAndWait = command => {
|
||||
module.exports.executeAndWait = (command, ignoreResult) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const retryExecute = (count, lastError) => {
|
||||
if (++count <= 5) {
|
||||
exec(command, (error) => {
|
||||
exec(command, error => {
|
||||
if (error) {
|
||||
if (error.code === 1) {
|
||||
if (!ignoreResult && (error.code === 1)) {
|
||||
setTimeout(() => {
|
||||
retryExecute(count, error);
|
||||
}, 1000);
|
||||
@@ -518,3 +542,58 @@ module.exports.stopMountProcessSync = (directory, version, storageType) => {
|
||||
const process = new spawn(command, args, processOptions);
|
||||
process.unref();
|
||||
};
|
||||
|
||||
module.exports.verifySignature = (file, signatureFile, publicKeyFile) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const executeVerify = openssl => {
|
||||
//openssl dgst -sha256 -verify $pubkeyfile -signature signature.sig file
|
||||
const command = '"' + openssl + '" dgst -sha256 -verify "' + publicKeyFile + '" -signature "' + signatureFile + '"';
|
||||
exec(command, res => {
|
||||
if (res.code !== 0) {
|
||||
reject(res);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (os.platform() === 'win32') {
|
||||
const Registry = require('winreg');
|
||||
const regKey = new Registry({
|
||||
hive: Registry.HKLM,
|
||||
key: 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (64-bit)_is1'
|
||||
});
|
||||
regKey.valueExists('InstallLocation', (err, exists) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else if (exists) {
|
||||
regKey.get('InstallLocation', (err, item) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
const openssl = path.join(item.value(), 'bin', 'openssl.exe');
|
||||
executeVerify(openssl);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
reject('Failed to locate \'openssl.exe\'');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
reject('Platform not supported: ' + os.platform())
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.verifyHash = (file, hash) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (os.platform() === 'darwin') {
|
||||
reject('Not implemented');
|
||||
} else if (os.platform() === 'linux') {
|
||||
reject('Not implemented');
|
||||
}
|
||||
else {
|
||||
reject('Platform not supported: ' + os.platform())
|
||||
}
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user