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