mirror of https://github.com/keeweb/keeweb
use private key from smart card
parent
a2cfaf8717
commit
9bd9785537
15
Gruntfile.js
15
Gruntfile.js
|
@ -175,14 +175,14 @@ module.exports = function(grunt) {
|
|||
},
|
||||
'desktop-app-content': {
|
||||
cwd: 'desktop/',
|
||||
src: '**',
|
||||
src: ['**', '!package-lock.json'],
|
||||
dest: 'tmp/desktop/app/',
|
||||
expand: true,
|
||||
nonull: true
|
||||
},
|
||||
'desktop-update': {
|
||||
cwd: 'tmp/desktop/app/',
|
||||
src: '**',
|
||||
src: ['**', '!package-lock.json'],
|
||||
dest: 'tmp/desktop/update/',
|
||||
expand: true,
|
||||
nonull: true
|
||||
|
@ -528,16 +528,14 @@ module.exports = function(grunt) {
|
|||
'desktop-update': {
|
||||
options: {
|
||||
file: 'dist/desktop/UpdateDesktop.zip',
|
||||
signature: zipCommentPlaceholder,
|
||||
privateKey: 'keys/private-key.pem'
|
||||
signature: zipCommentPlaceholder
|
||||
}
|
||||
}
|
||||
},
|
||||
'sign-desktop-files': {
|
||||
'desktop-update': {
|
||||
options: {
|
||||
path: 'tmp/desktop/update',
|
||||
privateKey: 'keys/private-key.pem'
|
||||
path: 'tmp/desktop/update'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -560,7 +558,7 @@ module.exports = function(grunt) {
|
|||
'app': {
|
||||
options: {
|
||||
file: 'dist/index.html',
|
||||
privateKey: 'keys/private-key.pem'
|
||||
skip: grunt.option('skip-sign')
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -636,8 +634,7 @@ module.exports = function(grunt) {
|
|||
'sign-dist': {
|
||||
'dist': {
|
||||
options: {
|
||||
sign: 'dist/desktop/Verify.sign.sha256',
|
||||
privateKey: 'keys/private-key.pem'
|
||||
sign: 'dist/desktop/Verify.sign.sha256'
|
||||
},
|
||||
files: {
|
||||
'dist/desktop/Verify.sha256': ['dist/desktop/KeeWeb-*', 'dist/desktop/UpdateDesktop.zip']
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "KeeWeb",
|
||||
"version": "1.5.4",
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"node-stream-zip": {
|
||||
"version": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.3.7.tgz",
|
||||
"integrity": "sha1-uImTxhXT9hNM9e0ckT/uvNvlAw4="
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,7 +4,10 @@
|
|||
"description": "Free cross-platform password manager compatible with KeePass",
|
||||
"main": "main.js",
|
||||
"homepage": "https://keeweb.info",
|
||||
"repository": { "type": "git", "url": "https://github.com/keeweb/keeweb" },
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/keeweb/keeweb"
|
||||
},
|
||||
"author": {
|
||||
"name": "Antelle",
|
||||
"email": "antelle.net@gmail.com",
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
const fs = require('fs');
|
||||
const signer = require('pkcs15-smartcard-sign');
|
||||
const keytar = require('keytar');
|
||||
|
||||
const verifyKey = fs.readFileSync('keys/public-key.pem');
|
||||
const key = '02';
|
||||
|
||||
function getPin() {
|
||||
if (getPin.pin) {
|
||||
return Promise.resolve(getPin.pin);
|
||||
}
|
||||
return keytar.getPassword('keeweb.pin', 'keeweb').then(pass => {
|
||||
if (pass) {
|
||||
getPin.pin = pass;
|
||||
return pass;
|
||||
} else {
|
||||
throw 'Cannot find PIN';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = function sign(grunt, data) {
|
||||
return getPin()
|
||||
.then(pin => signer.sign({ data, verifyKey, pin, key }))
|
||||
.catch(err => {
|
||||
if (grunt) {
|
||||
grunt.warn(`Error signing data: ${err}`);
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
};
|
|
@ -1,25 +1,26 @@
|
|||
module.exports = function (grunt) {
|
||||
grunt.registerMultiTask('sign-archive', 'Signs archive with a private key', function () {
|
||||
const done = this.async();
|
||||
const fs = require('fs');
|
||||
const crypto = require('crypto');
|
||||
const sign = require('../lib/sign');
|
||||
const file = fs.readFileSync(this.options().file);
|
||||
const ix = file.toString('binary').lastIndexOf(this.options().signature);
|
||||
if (ix < 0) {
|
||||
grunt.warn('Signature placeholder not found');
|
||||
return;
|
||||
}
|
||||
const sign = crypto.createSign('RSA-SHA256');
|
||||
sign.write(file.slice(0, ix));
|
||||
sign.end();
|
||||
const privateKey = fs.readFileSync(this.options().privateKey, 'binary');
|
||||
const 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 (let i = 0; i < signature.byteLength; i++) {
|
||||
file[ix + i] = signature[i];
|
||||
}
|
||||
fs.writeFileSync(this.options().file, file);
|
||||
const data = file.slice(0, ix);
|
||||
sign(grunt, data).then(signature => {
|
||||
signature = Buffer.from(signature.toString('hex'), 'binary');
|
||||
if (signature.byteLength !== Buffer.from(this.options().signature, 'binary').byteLength) {
|
||||
grunt.warn('Bad signature length');
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < signature.byteLength; i++) {
|
||||
file[ix + i] = signature[i];
|
||||
}
|
||||
fs.writeFileSync(this.options().file, file);
|
||||
done();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,42 +1,42 @@
|
|||
module.exports = function (grunt) {
|
||||
grunt.registerMultiTask('sign-desktop-files', 'Signs desktop files', function () {
|
||||
grunt.registerMultiTask('sign-desktop-files', 'Signs desktop files', async function () {
|
||||
const done = this.async();
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const sign = require('../lib/sign');
|
||||
const appPath = this.options().path;
|
||||
const privateKey = grunt.file.read(this.options().privateKey, { encoding: null });
|
||||
|
||||
const signatures = {};
|
||||
const signedFiles = [];
|
||||
walk(appPath);
|
||||
await walk(appPath);
|
||||
|
||||
const data = JSON.stringify(signatures);
|
||||
signatures.self = getSignature(Buffer.from(data));
|
||||
signatures.self = await getSignature(Buffer.from(data));
|
||||
grunt.file.write(path.join(appPath, 'signatures.json'), JSON.stringify(signatures));
|
||||
|
||||
grunt.log.writeln(`Signed ${signedFiles.length} files: ${signedFiles.join(', ')}`);
|
||||
grunt.log.writeln(`\nSigned ${signedFiles.length} files: ${signedFiles.join(', ')}`);
|
||||
done();
|
||||
|
||||
function walk(dir) {
|
||||
async function walk(dir) {
|
||||
const list = fs.readdirSync(dir);
|
||||
list.forEach(file => {
|
||||
file = dir + '/' + file;
|
||||
for (const fileName of list) {
|
||||
const file = dir + '/' + fileName;
|
||||
const stat = fs.statSync(file);
|
||||
if (stat && stat.isDirectory()) {
|
||||
walk(file);
|
||||
await walk(file);
|
||||
} else {
|
||||
const relFile = file.substr(appPath.length + 1);
|
||||
const fileData = grunt.file.read(file, { encoding: null });
|
||||
signatures[relFile] = getSignature(fileData);
|
||||
signatures[relFile] = await getSignature(fileData);
|
||||
signedFiles.push(relFile);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getSignature(data) {
|
||||
const sign = crypto.createSign('RSA-SHA256');
|
||||
sign.write(data);
|
||||
sign.end();
|
||||
return sign.sign(privateKey).toString('base64');
|
||||
async function getSignature(data) {
|
||||
const signature = await sign(grunt, data);
|
||||
grunt.log.write('.');
|
||||
return signature.toString('base64');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
module.exports = function (grunt) {
|
||||
grunt.registerMultiTask('sign-dist', 'Creates files signatures', function () {
|
||||
grunt.registerMultiTask('sign-dist', 'Creates files signatures', async function () {
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const sign = require('../lib/sign');
|
||||
|
||||
const done = this.async();
|
||||
const opt = this.options();
|
||||
|
||||
const privateKey = grunt.file.read(opt.privateKey, { encoding: null });
|
||||
|
||||
const results = [];
|
||||
|
||||
for (const file of this.files) {
|
||||
|
@ -20,10 +19,8 @@ module.exports = function (grunt) {
|
|||
hash.update(file);
|
||||
const digest = hash.digest('hex');
|
||||
|
||||
const sign = crypto.createSign('RSA-SHA256');
|
||||
sign.write(file);
|
||||
sign.end();
|
||||
const signature = sign.sign(privateKey).toString('hex');
|
||||
const rawSignature = await sign(grunt, file);
|
||||
const signature = rawSignature.toString('hex');
|
||||
|
||||
results.push({ basename, digest, signature });
|
||||
|
||||
|
|
|
@ -7,12 +7,15 @@ module.exports = function (grunt) {
|
|||
const keytar = require('keytar');
|
||||
const opt = this.options();
|
||||
const done = this.async();
|
||||
const password = keytar.getPassword(opt.keytarPasswordService, opt.keytarPasswordAccount);
|
||||
if (!password) {
|
||||
return grunt.warn('Code sign password not found');
|
||||
}
|
||||
const promises = Object.keys(opt.files).map(file => signFile(file, opt.files[file], opt, password));
|
||||
Promise.all(promises).then(done);
|
||||
keytar.getPassword(opt.keytarPasswordService, opt.keytarPasswordAccount).then(password => {
|
||||
if (!password) {
|
||||
return grunt.warn('Code sign password not found');
|
||||
}
|
||||
const promises = Object.keys(opt.files).map(file => signFile(file, opt.files[file], opt, password));
|
||||
Promise.all(promises).then(done);
|
||||
}).catch(e => {
|
||||
grunt.warn('Code sign error: ' + e);
|
||||
});
|
||||
});
|
||||
|
||||
function signFile(file, name, opt, password) {
|
||||
|
|
|
@ -1,24 +1,31 @@
|
|||
module.exports = function (grunt) {
|
||||
grunt.registerMultiTask('sign-html', 'Signs html page with a private key', function () {
|
||||
const fs = require('fs');
|
||||
const crypto = require('crypto');
|
||||
if (!fs.existsSync(this.options().privateKey)) {
|
||||
grunt.log.warn('Private key is missing, app html will not be signed.');
|
||||
if (this.options().skip) {
|
||||
grunt.log.writeln('Skipped app html signing');
|
||||
return;
|
||||
}
|
||||
let file = fs.readFileSync(this.options().file).toString('utf8');
|
||||
const done = this.async();
|
||||
const fs = require('fs');
|
||||
const sign = require('../lib/sign');
|
||||
const data = fs.readFileSync(this.options().file);
|
||||
let fileStr = data.toString();
|
||||
const marker = '<meta name="kw-signature" content="';
|
||||
const ix = file.indexOf(marker);
|
||||
const ix = fileStr.indexOf(marker);
|
||||
if (ix < 0) {
|
||||
grunt.warn('Signature placeholder not found');
|
||||
return;
|
||||
}
|
||||
const sign = crypto.createSign('RSA-SHA256');
|
||||
sign.write(file);
|
||||
sign.end();
|
||||
const privateKey = fs.readFileSync(this.options().privateKey, 'binary');
|
||||
const signature = sign.sign(privateKey).toString('base64');
|
||||
file = file.replace(marker, marker + signature);
|
||||
fs.writeFileSync(this.options().file, file, 'utf8');
|
||||
sign(null, data).then(signature => {
|
||||
signature = signature.toString('base64');
|
||||
fileStr = fileStr.replace(marker, marker + signature);
|
||||
fs.writeFileSync(this.options().file, fileStr, 'utf8');
|
||||
done();
|
||||
}).catch(e => {
|
||||
if (e === 'Cannot find PIN') {
|
||||
grunt.warn('Error signing app html. To build without sign, please launch grunt with --skip-sign.');
|
||||
} else {
|
||||
grunt.warn('Sign error: ' + e);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -26,7 +26,7 @@ module.exports = function (grunt) {
|
|||
const verify = crypto.createVerify('RSA-SHA256');
|
||||
verify.write(zipFileData.slice(0, zip.centralDirectory.headerOffset + 22));
|
||||
verify.end();
|
||||
const signature = new Buffer(zip.comment, 'hex');
|
||||
const signature = Buffer.from(zip.comment, 'hex');
|
||||
if (!verify.verify(publicKey, signature)) {
|
||||
grunt.warn('Invalid ZIP signature');
|
||||
return;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -16,7 +16,7 @@
|
|||
"babel-preset-es2015": "6.22.0",
|
||||
"base64-loader": "1.0.0",
|
||||
"cssnano": "3.10.0",
|
||||
"electron": "1.4.15",
|
||||
"electron": "^1.6.11",
|
||||
"eslint": "^3.14.1",
|
||||
"eslint-config-standard": "6.2.1",
|
||||
"eslint-plugin-promise": "3.4.0",
|
||||
|
@ -46,8 +46,9 @@
|
|||
"html-minifier": "3.3.0",
|
||||
"json-loader": "^0.5.4",
|
||||
"load-grunt-tasks": "3.5.2",
|
||||
"node-sass": "4.4.0",
|
||||
"node-sass": "4.5.3",
|
||||
"node-stream-zip": "1.3.7",
|
||||
"pkcs15-smartcard-sign": "^1.0.0",
|
||||
"raw-loader": "0.5.1",
|
||||
"stats-webpack-plugin": "0.4.3",
|
||||
"string-replace-webpack-plugin": "0.0.5",
|
||||
|
@ -59,7 +60,7 @@
|
|||
},
|
||||
"optionalDependencies": {
|
||||
"grunt-appdmg": "0.4.0",
|
||||
"keytar": "3.0.2"
|
||||
"keytar": "^4.0.3"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "grunt",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
Release notes
|
||||
-------------
|
||||
|
||||
##### v1.5.4 (2017-06-03)
|
||||
`-` fix #649: loading keyfiles with path
|
||||
`-` fix #645: layout issues while switching table view
|
||||
|
|
Loading…
Reference in New Issue