mirror of https://github.com/keeweb/keeweb.git
sign desktop update archive
This commit is contained in:
parent
32eaa38b30
commit
141098b1d0
20
Gruntfile.js
20
Gruntfile.js
|
@ -18,6 +18,11 @@ module.exports = function(grunt) {
|
|||
var dt = new Date().toISOString().replace(/T.*/, '');
|
||||
var electronVersion = '0.35.6';
|
||||
var minElectronVersionForUpdate = '0.32.0';
|
||||
var zipCommentPlaceholder = 'zip_comment_placeholder_that_will_be_replaced_with_hash';
|
||||
|
||||
while (zipCommentPlaceholder.length < 512) {
|
||||
zipCommentPlaceholder += '.';
|
||||
}
|
||||
|
||||
function replaceFont(css) {
|
||||
css.walkAtRules('font-face', function (rule) {
|
||||
|
@ -371,15 +376,25 @@ module.exports = function(grunt) {
|
|||
files: [{ cwd: 'tmp/desktop/KeeWeb-linux-ia32', src: '**', expand: true }]
|
||||
},
|
||||
'desktop_update': {
|
||||
options: { archive: 'dist/desktop/UpdateDesktop.zip' },
|
||||
options: { archive: 'dist/desktop/UpdateDesktop.zip', comment: zipCommentPlaceholder },
|
||||
files: [{ cwd: 'tmp/desktop/app', src: '**', expand: true }]
|
||||
}
|
||||
},
|
||||
'sign-archive': {
|
||||
'desktop_update': {
|
||||
options: {
|
||||
file: 'dist/desktop/UpdateDesktop.zip',
|
||||
signature: zipCommentPlaceholder,
|
||||
privateKey: '../keys/keeweb-private.pem'
|
||||
}
|
||||
}
|
||||
},
|
||||
'validate-desktop-update': {
|
||||
desktop: {
|
||||
options: {
|
||||
file: 'dist/desktop/UpdateDesktop.zip',
|
||||
expected: ['main.js', 'app.js', 'index.html', 'package.json', 'node_modules/node-stream-zip/node_stream_zip.js']
|
||||
expected: ['main.js', 'app.js', 'index.html', 'package.json', 'node_modules/node-stream-zip/node_stream_zip.js'],
|
||||
publicKey: '../keys/keeweb-public.pem'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -412,6 +427,7 @@ module.exports = function(grunt) {
|
|||
'copy:desktop_app_content',
|
||||
'string-replace:desktop_html',
|
||||
'compress:desktop_update',
|
||||
'sign-archive:desktop_update',
|
||||
'validate-desktop-update',
|
||||
'electron',
|
||||
'electron_builder',
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0oZB2Kt7AzRFNqf8FuO3
|
||||
C3kepHPAIQYiDPYdQxHcsiaFCwyKVx6K1cE/3vBhb8/2rj+QIIWNfAAuu1Y+2VK9
|
||||
0ZBeq6HciukWzQRO/HWhfdy0c7JwDAslmyGI5olj0ZQkNLhkde1MiMxjDPpRhZtd
|
||||
JaryVO5cFJaJESpv3dV6m0qXsaQCluWYOSNfSjP9C8o2zRVjSi3ZQZnZIV5pnk9K
|
||||
2MtlZIPXrN9iJiM5zZ9DTSnqApI6dC9mX4R3LvGN+GTovm9C8Crl+qb106nGRR3L
|
||||
cweicDnPyMtZLa/E0DBpWYxUVLDp6WeLhxoUBr+6+t3Xp9IDnPoANDQXJXD0f1vQ
|
||||
xQIDAQAB
|
||||
-----END PUBLIC KEY-----
|
|
@ -7,7 +7,8 @@ var Backbone = require('backbone'),
|
|||
AppSettingsModel = require('../models/app-settings-model'),
|
||||
UpdateModel = require('../models/update-model'),
|
||||
Transport = require('../comp/transport'),
|
||||
Logger = require('../util/logger');
|
||||
Logger = require('../util/logger'),
|
||||
publicKey = require('raw!../../resources/public-key.pem');
|
||||
|
||||
var logger = new Logger('updater');
|
||||
|
||||
|
@ -210,6 +211,7 @@ var Updater = {
|
|||
var expectedFiles = this.UpdateCheckFiles;
|
||||
var appPath = Launcher.getUserDataPath();
|
||||
var StreamZip = Launcher.req('node-stream-zip');
|
||||
var that = this;
|
||||
var zip = new StreamZip({ file: updateFile, storeEntries: true });
|
||||
zip.on('error', cb);
|
||||
zip.on('ready', function() {
|
||||
|
@ -220,6 +222,10 @@ var Updater = {
|
|||
if (!containsAll) {
|
||||
return cb('Bad archive');
|
||||
}
|
||||
var validationError = that.validateArchiveSignature(updateFile, zip);
|
||||
if (validationError) {
|
||||
return cb('Invalid archive: ' + validationError);
|
||||
}
|
||||
zip.extract(null, appPath, function(err) {
|
||||
zip.close();
|
||||
if (err) {
|
||||
|
@ -231,6 +237,28 @@ var Updater = {
|
|||
});
|
||||
},
|
||||
|
||||
validateArchiveSignature: function(archivePath, zip) {
|
||||
if (!zip.comment) {
|
||||
return 'No comment in ZIP';
|
||||
}
|
||||
if (zip.comment.length !== 512) {
|
||||
return 'Bad comment length in ZIP: ' + zip.comment.length;
|
||||
}
|
||||
try {
|
||||
var zipFileData = Launcher.req('fs').readFileSync(archivePath);
|
||||
var verify = Launcher.req('crypto').createVerify('RSA-SHA256');
|
||||
verify.write(zipFileData.slice(0, zip.centralDirectory.headerOffset + 22));
|
||||
verify.end();
|
||||
var signature = new window.Buffer(zip.comment, 'hex');
|
||||
if (!verify.verify(publicKey, signature)) {
|
||||
return 'Invalid signature';
|
||||
}
|
||||
} catch (err) {
|
||||
return err.toString();
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
checkAppCacheUpdateReady: function() {
|
||||
if (window.applicationCache.status === window.applicationCache.UPDATEREADY) {
|
||||
try { window.applicationCache.swapCache(); } catch (e) { }
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = function (grunt) {
|
||||
grunt.registerMultiTask('sign-archive', 'Signs archive with a private key', function () {
|
||||
var fs = require('fs');
|
||||
var crypto = require('crypto');
|
||||
var file = fs.readFileSync(this.options().file);
|
||||
var ix = file.toString('binary').lastIndexOf(this.options().signature);
|
||||
if (ix < 0) {
|
||||
grunt.warn('Signature placeholder not found');
|
||||
return;
|
||||
}
|
||||
console.log('siz pos', ix)
|
||||
var sign = crypto.createSign('RSA-SHA256');
|
||||
sign.write(file.slice(0, ix));
|
||||
sign.end();
|
||||
var privateKey = fs.readFileSync(this.options().privateKey, 'binary');
|
||||
var signature = new Buffer(sign.sign(privateKey).toString('hex'), 'binary');
|
||||
if (signature.byteLength !== new Buffer(this.options().signature, 'binary').byteLength) {
|
||||
grunt.warn('Bad signature length');
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < signature.byteLength; i++) {
|
||||
file[ix + i] = signature[i];
|
||||
}
|
||||
fs.writeFileSync(this.options().file, file);
|
||||
});
|
||||
};
|
|
@ -3,15 +3,35 @@
|
|||
module.exports = function (grunt) {
|
||||
grunt.registerMultiTask('validate-desktop-update', 'Validates desktop update package', function () {
|
||||
var path = require('path');
|
||||
var crypto = require('crypto');
|
||||
var fs = require('fs');
|
||||
var done = this.async();
|
||||
var StreamZip = require(path.resolve(__dirname, '../../electron/node_modules/node-stream-zip'));
|
||||
var zip = new StreamZip({ file: this.options().file, storeEntries: true });
|
||||
var expFiles = this.options().expected;
|
||||
var publicKey = fs.readFileSync(this.options().publicKey, 'binary');
|
||||
var zipFileData = fs.readFileSync(this.options().file);
|
||||
zip.on('error', function(err) {
|
||||
grunt.warn(err);
|
||||
});
|
||||
zip.on('ready', function() {
|
||||
var valid = true;
|
||||
if (!zip.comment) {
|
||||
grunt.warn('No comment in ZIP');
|
||||
return;
|
||||
}
|
||||
if (zip.comment.length !== 512) {
|
||||
grunt.warn('Bad comment length in ZIP');
|
||||
return;
|
||||
}
|
||||
var verify = crypto.createVerify('RSA-SHA256');
|
||||
verify.write(zipFileData.slice(0, zip.centralDirectory.headerOffset + 22));
|
||||
verify.end();
|
||||
var signature = new Buffer(zip.comment, 'hex');
|
||||
if (!verify.verify(publicKey, signature)) {
|
||||
grunt.warn('Invalid ZIP signature');
|
||||
return;
|
||||
}
|
||||
expFiles.forEach(function(entry) {
|
||||
try {
|
||||
if (!zip.entryDataSync(entry)) {
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
"handlebars-loader": "^1.1.4",
|
||||
"html-minifier": "=1.1.1",
|
||||
"load-grunt-tasks": "^3.2.0",
|
||||
"raw-loader": "^0.5.1",
|
||||
"string-replace-webpack-plugin": "0.0.2",
|
||||
"strip-sourcemap-loader": "0.0.1",
|
||||
"time-grunt": "^1.2.1",
|
||||
|
|
Loading…
Reference in New Issue