const fs = require('fs'); const path = require('path'); const os = require('os'); const axios = require('axios/index'); const exec = require('child_process').exec; const execFile = require('child_process').execFile; const spawn = require('child_process').spawn; const Constants = require('./constants'); const RandomString = require('randomstring'); let vcRuntimeExists; const _vcRuntimeExists = () => { return new Promise((resolve, reject) => { if (os.platform() !== 'win32') { reject('Windows OS is not being used'); } else { if (vcRuntimeExists) { resolve(true); } else { const cmd = path.join(process.env.windir, 'system32', 'reg.exe'); const args = [ 'QUERY', 'HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall' ]; _execProcessGetOutput(cmd, null, args) .then(lines => { const parseLine = index => { if (index < lines.length) { const line = lines[index]; if (line.startsWith('HKEY_LOCAL_MACHINE\\')) { let args2 = JSON.parse(JSON.stringify(args)); args2[1] = 'HKLM\\' + line.substr(19); args2.push('/v'); args2.push('DisplayName'); args2.push('/t'); args2.push('REG_SZ'); _execProcessGetOutput(cmd, null, args2) .then(lines => { const value = lines[2] .trim() .substr(args2[3].length) .trim() .substr(6) .trim(); if (value.includes( 'Microsoft Visual C++ 2015-2019 Redistributable (x64)')) { vcRuntimeExists = true; resolve(true); } else { parseLine(++index); } }) .catch(() => { parseLine(++index); }); } else { parseLine(++index); } } else { resolve(false); } }; parseLine(0); }) .catch(err => { reject(err); }); } } }); }; // https://stackoverflow.com/questions/19531453/transform-file-directory-structure-into-tree-in-javascript const _createTreeNodes = fileList => { let tree = {} const directorySort = (a, b) => { return !!a.directory === !!b.directory ? a.name.localeCompare(b.name) : a.directory ? -1 : 1; }; const addNode = obj => { let fullPath; const idx = obj.skylink.indexOf('/'); if (idx > -1) { fullPath = path.join(obj.directory, obj.skylink.substr(idx + 1)) .replace(/\\/g, '/'); } else { fullPath = path.join(obj.directory, obj.filename).replace(/\\/g, '/'); } const pathParts = fullPath.replace(/^\/|\/$/g, '').split('/'); let ptr = tree; for (let i = 0; i < pathParts.length; i++) { const node = { directory : true, name : pathParts[i], }; if (i === pathParts.length - 1) { node.directory = false; node.path = fullPath; } ptr[pathParts[i]] = ptr[pathParts[i]] || node; ptr[pathParts[i]].children = ptr[pathParts[i]].children || {}; ptr = ptr[pathParts[i]].children; } } const objectToArray = node => { Object.keys(node || {}).map((k) => { if (node[k].children) { objectToArray(node[k]) } }) if (node.children) { node.children = Object.values(node.children); node.children.forEach(objectToArray) node.children = node.children.sort(directorySort); } } fileList.map(addNode); objectToArray(tree); return Object.values(tree).sort(directorySort); }; const _exportAllSkylinks = version => { return new Promise((resolve, reject) => { const repertoryExec = _getRepertoryExec(version); const processOptions = { cwd : repertoryExec.working, detached : true, shell : false, windowsHide : true, }; const args = _getDefaultRepertoryArgs('Skynet'); args.push('-ea'); let result = ''; const process = new spawn(repertoryExec.cmd, args, processOptions); process.on('error', (err) => { reject(err); }); process.stdout.on('data', (d) => { result += d; }); process.stderr.on('data', (d) => { result += d; }); process.on('exit', code => { if (code === 0) { result = result.substr(result.indexOf('{')); resolve(JSON.parse(result)); } else { reject(new Error('Failed to import: ' + code + ':' + result)); } }); process.unref(); }); }; const _executeProcess = (command, working, args = []) => { return new Promise((resolve, reject) => { let processOptions = { detached : true, shell : false, }; if (working) { processOptions.cwd = working; } const process = new spawn(command, args, processOptions); const pid = process.pid; process.on('error', (err) => { reject(err, pid); }); process.on('exit', (code) => { resolve(code); }); process.unref(); }); }; const _execProcessGetOutput = (cmd, working, args) => { return new Promise((resolve, reject) => { let processOptions = { env : process.env, stdio : [ 'ignore', 'pipe', 'pipe' ] }; if (working) { processOptions.cwd = working; } const proc = spawn(cmd, args, processOptions); let output = ''; proc.stdout.on('data', data => { output += data.toString(); }); proc.on('error', (err) => { reject(err); }); proc.on('exit', () => { const lines = output.replace(/\r\n/g, '\n').split('\n'); resolve(lines); }); proc.unref(); }); }; const _getDataDirectory = () => { return _resolvePath(Constants.DATA_LOCATIONS[os.platform()]); }; const _getRepertoryDirectory = () => { return _resolvePath(Constants.REPERTORY_LOCATIONS[os.platform()]); }; const _getDefaultRepertoryArgs = (provider, remote, s3) => { const providerLower = provider.toLowerCase(); const args = []; if (s3) { args.push('-s3'); args.push('-na'); args.push(provider.substr(2)); } else if (remote) { args.push('-rm'); args.push(provider.substr(6)); } else if (Constants.PROVIDER_ARG[providerLower] && (Constants.PROVIDER_ARG[providerLower].length > 0)) { args.push(Constants.PROVIDER_ARG[providerLower]); } return args; }; const _getRepertoryExec = version => { return { cmd : (os.platform() === 'win32') ? 'repertory.exe' : './repertory', working : path.join(_getDataDirectory(), version), }; }; const _removeDirectoryRecursively = dir => { if (fs.existsSync(dir)) { fs.readdirSync(dir).forEach(file => { const curPath = path.join(dir, file); if (fs.lstatSync(curPath).isDirectory()) { module.exports.removeDirectoryRecursively(curPath); } else { fs.unlinkSync(curPath); } }); fs.rmdirSync(dir); } }; const _resolvePath = str => { if (os.platform() === 'win32') { return str.replace(/%([^%]+)%/g, (_, n) => { return process.env[n]; }); } else { return str.replace('~', os.homedir()); } }; const _tryParse = (j, def) => { try { return JSON.parse(j); } catch (e) { return def; } }; module.exports.checkDaemonVersion = (version, provider) => { return new Promise((resolve, reject) => { const repertoryExec = _getRepertoryExec(version); const processOptions = { cwd : repertoryExec.working, detached : true, shell : false, windowsHide : true, }; const args = _getDefaultRepertoryArgs(provider); args.push('-cv'); const process = new spawn(repertoryExec.cmd, args, processOptions); process.on('error', err => { reject(err); }); process.on('exit', code => { resolve(code); }); process.unref(); }); }; module.exports.cleanupOldReleases = versionList => { return new Promise((resolve, reject) => { try { if (versionList && versionList.length > 0) { const dataDir = _getDataDirectory(); const directoryList = fs.readdirSync(dataDir, {withFileTypes : true}) .filter(dirent => dirent.isDirectory()) .map(dirent => dirent); const removeList = directoryList.filter(dirent => !versionList.includes(dirent.name)) .map(dirent => dirent.name); for (const dir of removeList) { try { _removeDirectoryRecursively(path.join(dataDir, dir)); } catch (e) { console.log(e); } } resolve(); } } catch (e) { reject(e); console.log(e); } }); }; module.exports.createSignatureFiles = (signature, publicKey) => { const fileName1 = RandomString.generate({length : 12, charset : 'alphabetic'}); const fileName2 = RandomString.generate({length : 12, charset : 'alphabetic'}); const signatureFile = path.join(os.tmpdir(), fileName1 + '.sig'); const publicKeyFile = path.join(os.tmpdir(), fileName2 + '.pub'); const buffer = Buffer.from(signature, 'base64'); fs.writeFileSync(signatureFile, buffer); fs.writeFileSync(publicKeyFile, publicKey); return { PublicKeyFile : publicKeyFile, SignatureFile : signatureFile, }; }; module.exports.detectRepertoryMounts = (version, providerList) => { return new Promise((resolve, reject) => { let mountState = {}; const defaultData = {}; for (const provider of providerList) { defaultData[provider] = { Active : false, Location : '', PID : -1, }; } const grabStatus = index => { if (index >= providerList.length) { resolve(mountState); } else { const provider = providerList[index]; const repertoryExec = _getRepertoryExec(version); const processOptions = { cwd : repertoryExec.working, detached : true, shell : false, windowsHide : true, }; const args = _getDefaultRepertoryArgs( provider, !Constants.PROVIDER_LIST.includes(provider) && provider.toLowerCase().startsWith('remote'), !Constants.PROVIDER_LIST.includes(provider) && provider.toLowerCase().startsWith('s3')); args.push('-status'); const process = new spawn(repertoryExec.cmd, args, processOptions); let result = ''; process.on('error', (err) => { reject(err); }); process.stdout.on('data', (d) => { result += d; }); process.on('exit', () => { mountState[provider] = _tryParse(result, defaultData)[provider] || defaultData; if (mountState[provider].Active && ((mountState[provider].Location === 'elevating') || (mountState[provider].Location === ''))) { setTimeout(() => { grabStatus(index); }, 2000); } else { grabStatus(++index); } }); process.unref(); } }; grabStatus(0); }); }; module.exports.downloadFile = (url, destination, progressCallback, completeCallback) => { try { if (fs.existsSync(destination)) { fs.unlinkSync(destination); } } catch (e) { completeCallback(e); return; } axios .get(url, { responseType : 'stream', }) .then(response => { try { const total = parseInt(response.headers['content-length'], 10); if (total === 0) { completeCallback(new Error('No data available for download')); } else { const stream = fs.createWriteStream(destination); let downloaded = 0; response.data.on('data', (chunk) => { stream.write(Buffer.from(chunk)); downloaded += chunk.length; if (progressCallback) { progressCallback((downloaded / total * 100.0).toFixed(2)); } }); response.data.on('end', () => { stream.end(() => { if (downloaded === 0) { completeCallback(new Error('Received 0 bytes')); } else if (downloaded !== total) { completeCallback( new Error('Received incorrect number of bytes')); } else { completeCallback(); } }); }); response.data.on( 'error', error => { stream.end(() => { completeCallback(error); }); }); } } catch (error) { completeCallback(error); } }) .catch(error => { completeCallback(error); }); }; module.exports.executeAndWait = (command, ignoreResult) => { return new Promise((resolve, reject) => { const retryExecute = (count, lastError) => { if (++count <= 5) { exec(command, error => { if (error) { if (!ignoreResult && (error.code === 1)) { setTimeout(() => { retryExecute(count, error); }, 1000); } else { reject(error); } } else { resolve(); } }); } else { reject(lastError); } }; retryExecute(0); }); }; module.exports.executeAsync = (command, args = []) => { return new Promise((resolve, reject) => { const launchProcess = (count, timeout) => { let cmd = path.basename(command); const working = cmd.length === command.length ? null : command.substr(0, command.length - cmd.length); let processOptions = { detached : true, shell : false, }; if (working) { processOptions.cwd = working; if (os.platform() !== 'win32') { cmd = './' + cmd; } } const process = new spawn(cmd, args, processOptions); const pid = process.pid; process.on('error', err => { if (++count === 5) { reject(err, pid); } else { clearTimeout(timeout); setTimeout( () => launchProcess(count, setTimeout(() => resolve(), 3000)), 1000); } }); process.on('exit', (code) => { if (code !== 0) { if (++count === 5) { reject(code, pid); } else { clearTimeout(timeout); setTimeout( () => launchProcess(count, setTimeout(() => resolve(), 3000)), 1000); } } }); process.unref(); }; launchProcess(0, setTimeout(() => resolve(), 3000)); }); }; module.exports.executeScript = script => { return new Promise((resolve, reject) => { const processOptions = { detached : false, shell : true, windowsHide : true, }; const command = '/bin/sh'; const args = [ script ]; const process = new spawn(command, args, processOptions); let result = ''; process.on('error', (err) => { reject(err); }); process.stdout.on('data', (d) => { result += d; }); process.on('exit', () => { resolve(result); }); process.unref(); }); }; module.exports.executeMount = (version, provider, remote, s3, location, exitCallback) => { return new Promise((resolve) => { const repertoryExec = _getRepertoryExec(version); const processOptions = { cwd : repertoryExec.working, detached : false, shell : os.platform() !== 'darwin', stdio : 'ignore', }; const args = _getDefaultRepertoryArgs(provider, remote, s3); if ((os.platform() === 'linux') || (os.platform() === 'darwin')) { args.push('-o'); args.push('big_writes'); args.push('-f'); args.push('-nc'); } else if (os.platform() === 'win32') { args.push('-hidden'); } args.push(location); let process = new spawn(repertoryExec.cmd, args, processOptions); const pid = process.pid; const timeout = setTimeout(() => { resolve(pid); }, 3000); process.on('error', (err) => { clearTimeout(timeout); exitCallback(err, pid); }); process.on('exit', (code) => { clearTimeout(timeout); exitCallback(code, pid); }); }); }; module.exports.exportAllSkylinks = _exportAllSkylinks; module.exports.exportSkylinks = (version, paths) => { return new Promise((resolve, reject) => { const repertoryExec = _getRepertoryExec(version); const processOptions = { cwd : repertoryExec.working, detached : true, shell : false, windowsHide : true, }; const args = _getDefaultRepertoryArgs('Skynet'); args.push('-ex'); args.push(paths.join(',')); let result = ''; const process = new spawn(repertoryExec.cmd, args, processOptions); process.on('error', (err) => { reject(err); }); process.stdout.on('data', (d) => { result += d; }); process.stderr.on('data', (d) => { result += d; }); process.on('exit', code => { if (code === 0) { result = result.substr(result.indexOf('{')); resolve(JSON.parse(result)); } else { reject(new Error('Failed to import: ' + code + ':' + result)); } }); process.unref(); }); }; module.exports.getConfig = (version, provider, remote, s3) => { return new Promise((resolve, reject) => { const repertoryExec = _getRepertoryExec(version); const processOptions = { cwd : repertoryExec.working, detached : true, shell : false, windowsHide : true, }; const args = _getDefaultRepertoryArgs(provider, remote, s3); args.push('-dc'); const process = new spawn(repertoryExec.cmd, args, processOptions); let result = ''; process.on('error', (err) => { reject(err); }); process.stdout.on('data', (d) => { result += d; }); process.on('exit', () => { const lines = result.replace(/\r\n/g, '\n').split('\n'); const code = parseInt(lines[0], 10); if (code === 0) { lines.shift(); resolve({ Code : code, Data : JSON.parse(lines.join('\n')), }); } else { resolve(code); } }); process.unref(); }); }; module.exports.getConfigTemplate = (version, provider, remote, s3) => { return new Promise((resolve, reject) => { const repertoryExec = _getRepertoryExec(version); const processOptions = { cwd : repertoryExec.working, detached : true, shell : false, windowsHide : true, }; const args = _getDefaultRepertoryArgs(provider, remote, s3); args.push('-gt'); const process = new spawn(repertoryExec.cmd, args, processOptions); let result = ''; process.on('error', (err) => { reject(err); }); process.stdout.on('data', (d) => { result += d; }); process.on('exit', () => { resolve(JSON.parse(result)); }); process.unref(); }); }; module.exports.getDataDirectory = _getDataDirectory; module.exports.getRepertoryDirectory = _getRepertoryDirectory; module.exports.getMissingDependencies = dependencies => { return new Promise((resolve, reject) => { if (!dependencies) { reject(new Error('Dependency list is invalid')); } let missing = []; if (os.platform() === 'win32') { let count = 0; const resolveIfComplete = () => { if (++count === dependencies.length) { resolve(missing); } }; const Registry = require('winreg'); const checkRegistry = (dep, index) => { if (index >= dep.registry.length) { if (dep.display === 'VC Runtime 2015-2019') { _vcRuntimeExists() .then(exists => { if (!exists) { missing.push(dep); } resolveIfComplete(); }) .catch(() => { missing.push(dep); resolveIfComplete(); }) } else { missing.push(dep); resolveIfComplete(); } } else { let hive = null; const hiveName = dep.registry[index].split('\\')[0]; switch (hiveName) { case 'HKEY_CLASSES_ROOT': hive = Registry.HKCR; break; case 'HKEY_CURRENT_CONFIG': hive = Registry.HKCC; break; case 'HKEY_CURRENT_USER': hive = Registry.HKCU; break; case 'HKEY_LOCAL_MACHINE': hive = Registry.HKLM; break; case 'HKEY_USERS': hive = Registry.HKU; break; default: throw new Error('Invalid registry hive: ' + hiveName); } const key = dep.registry[index].substr(hiveName.length); const regKey = new Registry({hive : hive, key : key}); regKey.valueExists('DisplayName', (err, exists) => { if (err || !exists) { regKey.valueExists('ProductName', (err, exists) => { if (err || !exists) { checkRegistry(dep, ++index); } else { resolveIfComplete(); } }); } else { resolveIfComplete(); } }); } }; for (const dependency of dependencies) { checkRegistry(dependency, 0); } } else { for (const dep of dependencies) { try { if (!(fs.lstatSync(dep.file).isFile() || fs.lstatSync(dep.file).isSymbolicLink())) { missing.push(dep); } } catch (e) { missing.push(dep); } } resolve(missing); } }); }; module.exports.grabSkynetFileTree = version => { return new Promise((resolve, reject) => { _exportAllSkylinks(version) .then(results => { resolve([ { name : '/', directory : true, children : _createTreeNodes(results.success), } ]); }) .catch(e => { reject(e); }); }); }; module.exports.grabDirectoryItems = (path, version, provider, remote, s3) => { return new Promise((resolve, reject) => { const repertoryExec = _getRepertoryExec(version); const processOptions = { cwd : repertoryExec.working, detached : true, shell : false, windowsHide : true, }; const args = _getDefaultRepertoryArgs(provider, remote, s3); args.push('-gdi'); args.push(path); let result = ''; const process = new spawn(repertoryExec.cmd, args, processOptions); process.on('error', (err) => { reject(err); }); process.stdout.on('data', (d) => { result += d; }); process.stderr.on('data', (d) => { result += d; }); process.on('exit', code => { if (code === 0) { result = result.substr(result.indexOf('{')); resolve(JSON.parse(result)); } else { reject(new Error('Failed to import: ' + code + ':' + result)); } }); process.unref(); }); }; module.exports.setPinned = (path, pinned, version, provider, remote, s3) => { return new Promise((resolve, reject) => { const repertoryExec = _getRepertoryExec(version); const processOptions = { cwd : repertoryExec.working, detached : true, shell : false, windowsHide : true, }; const args = _getDefaultRepertoryArgs(provider, remote, s3); args.push(pinned ? '-pf' : '-uf'); args.push(path); let result = ''; const process = new spawn(repertoryExec.cmd, args, processOptions); process.on('error', (err) => { reject(err); }); process.stdout.on('data', (d) => { result += d; }); process.stderr.on('data', (d) => { result += d; }); process.on('exit', code => { if (code === 0) { resolve(JSON.parse(result).success); } else { reject(new Error('Failed to import: ' + code + ':' + result)); } }); process.unref(); }); }; module.exports.importSkylinks = (version, jsonArray) => { return new Promise((resolve, reject) => { const repertoryExec = _getRepertoryExec(version); const processOptions = { cwd : repertoryExec.working, detached : true, shell : false, windowsHide : true, }; const args = _getDefaultRepertoryArgs('Skynet'); args.push('-ij'); args.push(JSON.stringify(jsonArray)); let result = ''; const process = new spawn(repertoryExec.cmd, args, processOptions); process.on('error', (err) => { reject(err); }); process.stdout.on('data', (d) => { result += d; }); process.stderr.on('data', (d) => { result += d; }); process.on('exit', code => { if (code === 0) { result = result.substr(result.indexOf('{')); resolve(JSON.parse(result)); } else { reject(new Error('Failed to import: ' + code + ':' + result)); } }); process.unref(); }); }; // https://stackoverflow.com/questions/31645738/how-to-create-full-path-with-nodes-fs-mkdirsync module.exports.mkDirByPathSync = (targetDir, {isRelativeToScript = false} = {}) => { const sep = path.sep; const initDir = path.isAbsolute(targetDir) ? sep : ''; const baseDir = isRelativeToScript ? __dirname : '.'; return targetDir.split(sep).reduce((parentDir, childDir) => { const curDir = path.resolve(baseDir, parentDir, childDir); try { fs.mkdirSync(curDir); } catch (err) { if (err.code === 'EEXIST') { // curDir already exists! return curDir; } // To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on // Windows. if (err.code === 'ENOENT') { // Throw the original parentDir error on // curDir `ENOENT` failure. throw new Error(`EACCES: permission denied, mkdir '${parentDir}'`); } const caughtErr = [ 'EACCES', 'EPERM', 'EISDIR' ].indexOf(err.code) > -1; if (!caughtErr || (caughtErr && (targetDir === curDir))) { throw err; // Throw if it's just the last created dir. } } return curDir; }, initDir); }; module.exports.performWindowsUninstall = names => { return new Promise((resolve, reject) => { if (os.platform() !== 'win32') { reject('Windows OS is not being used'); } else { const cmd = path.join(process.env.windir, 'system32', 'reg.exe'); const args = [ 'QUERY', 'HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall' ]; _execProcessGetOutput(cmd, null, args) .then(lines => { const parseLine = index => { if (index < lines.length) { const line = lines[index]; if (line.startsWith('HKEY_LOCAL_MACHINE\\')) { let args2 = JSON.parse(JSON.stringify(args)); args2[1] = 'HKLM\\' + line.substr(19); args2.push('/v'); args2.push('DisplayName'); args2.push('/t'); args2.push('REG_SZ'); _execProcessGetOutput(cmd, null, args2) .then(lines => { const value = lines[2] .trim() .substr(args2[3].length) .trim() .substr(6) .trim(); if (names.includes(value)) { const items = line.split('\\'); const productCode = items[items.length - 1]; _executeProcess('msiexec.exe', null, [ '/x', productCode, '/norestart' ]) .then(code => { if ((code === 0) || (code === 3010) || (code === 1641)) { resolve(true); } else { reject('[' + value + '] uninstall failed: ' + code); } }) .catch(err => { reject(err); }); } else { parseLine(++index); } }) .catch(() => { parseLine(++index); }); } else { parseLine(++index); } } else { resolve(false); } }; parseLine(0); }) .catch(err => { reject(err); }); } }); }; module.exports.removeDirectoryRecursively = _removeDirectoryRecursively; module.exports.resolvePath = _resolvePath; module.exports.setConfigValue = (name, value, provider, remote, s3, version) => { return new Promise((resolve, reject) => { const repertoryExec = _getRepertoryExec(version); const processOptions = { cwd : repertoryExec.working, detached : true, shell : false, windowsHide : true, }; const args = _getDefaultRepertoryArgs(provider, remote, s3); args.push('-set'); args.push(name); args.push(value); const process = new spawn(repertoryExec.cmd, args, processOptions); process.on('error', (err) => { reject(err); }); process.on('exit', code => { if (code !== 0) { reject(new Error('Failed to set configuration value: ' + code)); } else { resolve(); } }); process.unref(); }); }; module.exports.stopMountProcess = (version, provider, remote, s3) => { return new Promise((resolve, reject) => { const repertoryExec = _getRepertoryExec(version); const processOptions = { cwd : repertoryExec.working, detached : os.platform() === 'darwin', shell : os.platform() !== 'darwin', windowsHide : true, }; const args = _getDefaultRepertoryArgs(provider, remote, s3); args.push('-unmount'); const process = new spawn(repertoryExec.cmd, args, processOptions); const pid = process.pid; process.on('error', (err) => { reject(err); }); process.on('exit', (code) => { resolve({ PID : pid, Code : code, }); }); if (os.platform() === 'darwin') { process.unref(); } }); }; module.exports.stopMountProcessSync = (version, provider, remote, s3) => { const repertoryExec = _getRepertoryExec(version); const processOptions = { cwd : repertoryExec.working, detached : true, shell : os.platform() !== 'darwin', windowsHide : true, }; const args = _getDefaultRepertoryArgs(provider, remote, s3); args.push('-unmount'); const process = new spawn(repertoryExec.cmd, args, processOptions); process.unref(); }; module.exports.testRepertoryBinary = version => { return new Promise((resolve, reject) => { const repertoryExec = _getRepertoryExec(version); _executeProcess(repertoryExec.cmd, repertoryExec.working, [ '-dc' ]) .then(code => { if (code === 0) { resolve(); } else { reject(new Error('Invalid exit code: ' + code)); } }) .catch(error => { reject(error); }); }); }; module.exports.verifyHash = (file, hash) => { return new Promise((resolve, reject) => { const platform = os.platform(); let command; let args; if (platform === 'darwin') { command = 'shasum'; args = [ '-b', '-a', '256', file ]; } else if (platform === 'linux') { command = 'sha256sum'; args = [ '-b', file, '-z' ]; } else { reject(new Error('Platform not supported: ' + os.platform())) } if (command) { execFile(command, args, (err, stdout) => { if (err) { reject(err); } else { const hash2 = stdout.split(' ')[0].trim().toLowerCase(); if (hash2 === hash.toLowerCase()) { resolve(hash2); } else { reject(new Error('Checksum failed for file')); } } }); } }); }; module.exports.verifySignature = (file, signatureFile, publicKeyFile) => { return new Promise((resolve, reject) => { const executeVerify = openssl => { execFile(openssl, [ 'dgst', '-sha256', '-verify', publicKeyFile, '-signature', signatureFile, file ], (err, stdout) => { if (err) { reject(err); } else { resolve(stdout); } }); }; if (os.platform() === 'win32') { const Registry = require('winreg'); const regKey = new Registry({ hive : Registry.HKLM, key : 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (64-bit)_is1' }); regKey.valueExists('InstallLocation', (err, exists) => { if (err) { reject(err); } else if (exists) { regKey.get('InstallLocation', (err, item) => { if (err) { reject(err); } else { executeVerify(path.join(item.value(), 'bin', 'openssl.exe')); } }); } else { reject(new Error('Failed to locate \'openssl.exe\'')); } }); } else if (os.platform() === 'linux') { executeVerify('openssl'); } else { reject(new Error('Platform not supported: ' + os.platform())) } }); };