sign desktop update archive

This commit is contained in:
Antelle 2016-03-05 14:16:12 +03:00
parent 32eaa38b30
commit 141098b1d0
6 changed files with 105 additions and 3 deletions

View File

@ -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',

View File

@ -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-----

View File

@ -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) { }

View File

@ -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);
});
};

View 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)) {

View File

@ -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",