Added missing files

This commit is contained in:
antelle 2019-01-07 18:33:21 +01:00
parent af9d668d2a
commit d9c85d16c8
11 changed files with 418 additions and 1 deletions

1
.gitignore vendored
View File

@ -5,7 +5,6 @@ node_modules/
*.log
dist/
tmp/
build/
coverage/
keys
*.user

View File

@ -0,0 +1,31 @@
module.exports = function (grunt) {
grunt.registerMultiTask('codesign', 'Launches Apple codesign', function () {
const done = this.async();
const opt = this.options();
const config = require('../../keys/codesign.json');
for (const file of this.files) {
const args = [
'-s',
config.identities[opt.identity]
];
if (opt.deep) {
args.push('--deep');
}
args.push(file.src);
grunt.log.writeln('codesign:', args.join(' '));
grunt.util.spawn({
cmd: 'codesign',
args: args,
opts: {stdio: 'inherit'}
}, (error, result, code) => {
if (error) {
return grunt.warn('codesign: ' + error);
}
if (code) {
return grunt.warn('codesign exit code ' + code);
}
done();
});
}
});
};

38
build/tasks/grunt-nsis.js Normal file
View File

@ -0,0 +1,38 @@
module.exports = function (grunt) {
grunt.registerMultiTask('nsis', 'Launches NSIS installer', function () {
const done = this.async();
const opt = this.options();
const args = [];
const win = process.platform === 'win32';
const prefix = win ? '/' : '-';
Object.keys(opt.vars).forEach(key => {
let value = opt.vars[key];
if (typeof value === 'function') {
value = value();
}
if (value) {
args.push(`${prefix}D${key}=${value}`);
}
});
args.push(`${prefix}Darch=${opt.arch}`);
args.push(`${prefix}Doutput=${opt.output}`);
args.push(`${prefix}NOCD`);
args.push(`${prefix}V2`);
args.push(opt.installScript);
const executable = win ? 'C:\\Program Files (x86)\\NSIS\\makensis.exe' : 'makensis';
grunt.log.writeln('Running NSIS:', args.join(' '));
grunt.util.spawn({
cmd: executable,
args: args,
opts: {stdio: 'inherit'}
}, (error, result, code) => {
if (error) {
return grunt.warn('NSIS error: ' + error);
}
if (code) {
return grunt.warn('NSIS exit code ' + code);
}
done();
});
});
};

View File

@ -0,0 +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 sign = require('../util/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 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();
});
});
};

View File

@ -0,0 +1,42 @@
module.exports = function (grunt) {
grunt.registerMultiTask('sign-desktop-files', 'Signs desktop files', async function () {
const done = this.async();
const fs = require('fs');
const path = require('path');
const sign = require('../util/sign');
const appPath = this.options().path;
const signatures = {};
const signedFiles = [];
await walk(appPath);
const data = JSON.stringify(signatures);
signatures.kwResSelf = await getSignature(Buffer.from(data));
grunt.file.write(path.join(appPath, 'signatures.json'), JSON.stringify(signatures));
grunt.log.writeln(`\nSigned ${signedFiles.length} files: ${signedFiles.join(', ')}`);
done();
async function walk(dir) {
const list = fs.readdirSync(dir);
for (const fileName of list) {
const file = dir + '/' + fileName;
const stat = fs.statSync(file);
if (stat && stat.isDirectory()) {
await walk(file);
} else {
const relFile = file.substr(appPath.length + 1);
const fileData = grunt.file.read(file, { encoding: null });
signatures[relFile] = await getSignature(fileData);
signedFiles.push(relFile);
}
}
}
async function getSignature(data) {
const signature = await sign(grunt, data);
grunt.log.write('.');
return signature.toString('base64');
}
});
};

View File

@ -0,0 +1,36 @@
module.exports = function (grunt) {
grunt.registerMultiTask('sign-dist', 'Creates files signatures', async function () {
const path = require('path');
const crypto = require('crypto');
const sign = require('../util/sign');
const done = this.async();
const opt = this.options();
const results = [];
for (const file of this.files) {
grunt.log.writeln(`Calculating sha256 for ${file.src.length} files...`);
for (const src of file.src) {
const basename = path.basename(src);
const file = grunt.file.read(src, { encoding: null });
const hash = crypto.createHash('sha256');
hash.update(file);
const digest = hash.digest('hex');
const rawSignature = await sign(grunt, file);
const signature = rawSignature.toString('hex');
results.push({ basename, digest, signature });
grunt.log.writeln(basename);
}
grunt.file.write(file.dest, results.map(line => `${line.digest} *${line.basename}`).join('\n'));
grunt.file.write(opt.sign, results.map(line => `${line.signature} *${line.basename}`).join('\n'));
}
done();
});
};

View File

@ -0,0 +1,101 @@
/**
* This will require the latest (unreleased) version of `osslsigncode` with pkcs11 patch
* Build it like this:
*
* curl -L http://sourceforge.net/projects/osslsigncode/files/osslsigncode/osslsigncode-1.7.1.tar.gz/download -o osslsigncode.tar.gz
* tar -zxvf osslsigncode.tar.gz
* git clone https://git.code.sf.net/p/osslsigncode/osslsigncode osslsigncode-master
* cp osslsigncode-master/osslsigncode.c osslsigncode-1.7.1/osslsigncode.c
* rm osslsigncode.tar.gz
* rm -rf osslsigncode-master
* cd osslsigncode-1.7.1/
* export PKG_CONFIG_PATH=/usr/local/opt/openssl/lib/pkgconfig
* ./configure
* make
* sudo cp osslsigncode /usr/local/bin/osslsigncode
*
* Install this:
* brew install opensc
* brew install engine_pkcs11
*
* https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Build_Instructions/Signing_an_executable_with_Authenticode
*/
const fs = require('fs');
module.exports = function (grunt) {
grunt.registerMultiTask('sign-exe', 'Signs exe file with authenticode certificate', async function () {
const opt = this.options();
const done = this.async();
if (opt.pvk) {
const keytar = require('keytar');
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);
});
} else {
const sign = require('../util/sign');
const pin = await sign.getPin();
for (const file of Object.keys(opt.files)) {
await signFile(file, opt.files[file], opt, pin);
}
done();
}
});
function signFile(file, name, opt, password) {
const signedFile = file + '.sign';
return new Promise((resolve, reject) => {
const pkcsArgs = opt.pvk ? [] : [
'-pkcs11engine', '/usr/local/lib/engines/engine_pkcs11.so',
'-pkcs11module', '/usr/local/lib/opensc-pkcs11.so'
];
const args = [
'-spc', opt.spc,
'-key', opt.pvk ? require('path').resolve(opt.pvk) : opt.key,
'-pass', password,
'-h', opt.algo,
'-n', name,
'-i', opt.url,
'-t', 'http://timestamp.verisign.com/scripts/timstamp.dll',
...pkcsArgs,
'-in', file,
'-out', signedFile
];
const spawned = grunt.util.spawn({
cmd: 'osslsigncode',
args: args
}, (error, result, code) => {
if (error || code) {
spawned.kill();
grunt.warn(`Cannot sign file ${file}, signtool error ${code}: ${error}`);
return reject();
}
grunt.util.spawn({
cmd: 'osslsigncode',
args: ['verify', signedFile]
}, (ex, result, code) => {
if (code) {
grunt.warn(`Verify error ${file}: \n${result.stdout.toString()}`);
return;
}
if (fs.existsSync(file)) {
fs.renameSync(signedFile, file);
}
grunt.log.writeln(`Signed ${file}: ${name}`);
resolve();
});
});
// spawned.stdout.pipe(process.stdout);
spawned.stderr.pipe(process.stderr);
// spawned.stdin.setEncoding('utf-8');
// spawned.stdin.write(password);
// spawned.stdin.write('\n');
});
}
};

View File

@ -0,0 +1,32 @@
module.exports = function (grunt) {
grunt.registerMultiTask('sign-html', 'Signs html page with a private key', function () {
if (this.options().skip) {
grunt.log.writeln('Skipped app html signing');
return;
}
const done = this.async();
const fs = require('fs');
const sign = require('../util/sign');
const data = fs.readFileSync(this.options().file);
let fileStr = data.toString();
const marker = '<meta name="kw-signature" content="';
const ix = fileStr.indexOf(marker);
if (ix < 0) {
grunt.warn('Signature placeholder not found');
return;
}
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);
}
done(false);
});
});
};

View File

@ -0,0 +1,54 @@
module.exports = function (grunt) {
grunt.registerMultiTask('validate-desktop-update', 'Validates desktop update package', function () {
const path = require('path');
const crypto = require('crypto');
const fs = require('fs');
const done = this.async();
const StreamZip = require(path.resolve(__dirname, '../../desktop/node_modules/node-stream-zip'));
const zip = new StreamZip({ file: this.options().file, storeEntries: true });
const expFiles = this.options().expected;
const expFilesCount = this.options().expectedCount;
const publicKey = fs.readFileSync(this.options().publicKey, 'binary');
const zipFileData = fs.readFileSync(this.options().file);
zip.on('error', err => {
grunt.warn(err);
});
zip.on('ready', () => {
let 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;
}
const verify = crypto.createVerify('RSA-SHA256');
verify.write(zipFileData.slice(0, zip.centralDirectory.headerOffset + 22));
verify.end();
const signature = Buffer.from(zip.comment, 'hex');
if (!verify.verify(publicKey, signature)) {
grunt.warn('Invalid ZIP signature');
return;
}
if (zip.entriesCount !== expFilesCount) {
grunt.warn(`ZIP contains ${zip.entriesCount} entries, expected ${expFilesCount}`);
valid = false;
}
expFiles.forEach(entry => {
try {
if (!zip.entryDataSync(entry)) {
grunt.warn('Corrupted entry in desktop update archive: ' + entry);
valid = false;
}
} catch (e) {
grunt.warn('Entry not found in desktop update archive: ' + entry);
valid = false;
}
});
if (valid) {
done();
}
});
});
};

View File

@ -0,0 +1,25 @@
const fs = require('fs');
function replaceFont(css) {
css.walkAtRules('font-face', rule => {
const fontFamily = rule.nodes.filter(n => n.prop === 'font-family')[0];
if (!fontFamily) {
throw 'Bad font rule: ' + rule.toString();
}
const value = fontFamily.value.replace(/["']/g, '');
const fontFiles = {
FontAwesome: 'fontawesome-webfont.woff'
};
const fontFile = fontFiles[value];
if (!fontFile) {
throw 'Unsupported font ' + value + ': ' + rule.toString();
}
const data = fs.readFileSync('tmp/fonts/' + fontFile, 'base64');
const src = 'url(data:application/font-woff;charset=utf-8;base64,{data}) format(\'woff\')'
.replace('{data}', data);
rule.nodes = rule.nodes.filter(n => n.prop !== 'src');
rule.append({ prop: 'src', value: src });
});
}
module.exports = replaceFont;

33
build/util/sign.js Normal file
View File

@ -0,0 +1,33 @@
const fs = require('fs');
const signer = require('pkcs15-smartcard-sign');
const keytar = require('keytar');
const verifyKey = fs.readFileSync('app/resources/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;
});
};
module.exports.getPin = getPin;