diff --git a/package-lock.json b/package-lock.json index 35e00e984..5ad1e2d0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5777,7 +5777,7 @@ }, "bl": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz", "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "requires": { "readable-stream": "^2.3.5", @@ -8473,7 +8473,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -12078,7 +12078,7 @@ }, "http-proxy-middleware": { "version": "0.18.0", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", + "resolved": "http://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", "integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==", "dev": true, "requires": { @@ -15398,9 +15398,9 @@ } }, "node-forge": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz", - "integrity": "sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ==" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz", + "integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==" }, "node-gyp": { "version": "3.8.0", @@ -24011,11 +24011,11 @@ "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=" }, "selfsigned": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.4.tgz", - "integrity": "sha512-9AukTiDmHXGXWtWjembZ5NDmVvP2695EtpgbCsxCa68w3c88B+alqbmZ4O3hZ4VWGXeGWzEVdvqgAJD8DQPCDw==", + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz", + "integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==", "requires": { - "node-forge": "0.7.5" + "node-forge": "0.9.0" } }, "semver": { diff --git a/package.json b/package.json index a9c80a1bb..0a8eb2ec9 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "reselect": "^3.0.0", "restify": "^7.2.1", "restify-cors-middleware": "^1.1.1", - "selfsigned": "^1.10.3", + "selfsigned": "^1.10.7", "thread-loader": "^1.1.5", "uuid": "^3.3.2", "xmldom": "^0.1.27" diff --git a/src/main/MainApp.js b/src/main/MainApp.js index 23793b820..3910ac0ca 100644 --- a/src/main/MainApp.js +++ b/src/main/MainApp.js @@ -2,6 +2,7 @@ const { app, Menu } = require('electron'); const ProtocolManager = require('./data-managers/ProtocolManager'); const MainWindow = require('./components/mainWindow'); const { AdminService } = require('./server/AdminService'); +const { resetPemKeyPair } = require('./server/certificateManager'); const { isWindows } = require('./utils/environment'); const { createTray } = require('./components/tray'); @@ -30,10 +31,23 @@ const createApp = () => { const updater = Updater(); updater.checkForUpdates(true); + const regenerateCertificates = () => { + const responseNum = dialog.showMessageBox(mainWindow.window, { + message: 'Regenerate certificates?', + detail: 'Regenerating security certificates will require you to re-pair all of your devices. Do you want to continue?', + buttons: ['Regenerate Certificates', 'Cancel'], + cancelId: 1, + defaultId: 0, + }); + if (responseNum === 0) { + resetPemKeyPair().then(adminService.resetDevices()).then(reloadHomeScreen); + } + }; + const resetAppData = () => { const responseNum = dialog.showMessageBox(mainWindow.window, { message: 'Destroy all application files and data?', - detail: 'This includes all imported protocols and paired devices', + detail: 'This will delete ALL existing data, including interview data, imported protocols and paired devices. Do you want to continue?', buttons: ['Reset Data', 'Cancel'], cancelId: 1, defaultId: 0, @@ -91,6 +105,10 @@ const createApp = () => { click: showImportProtocolDialog, }, { type: 'separator' }, + { + label: 'Regenerate Certificates...', + click: regenerateCertificates, + }, { label: 'Reset Data...', click: resetAppData, diff --git a/src/main/data-managers/DeviceManager.js b/src/main/data-managers/DeviceManager.js index 6fb4345e2..75cac11e4 100644 --- a/src/main/data-managers/DeviceManager.js +++ b/src/main/data-managers/DeviceManager.js @@ -25,7 +25,6 @@ class DeviceManager { return this.db.all(); } - // TODO: Probably remove after alpha testing destroyAllDevices() { return this.db.destroyAll(); } diff --git a/src/main/server/AdminService.js b/src/main/server/AdminService.js index 8eb1fcf10..3888bbfea 100644 --- a/src/main/server/AdminService.js +++ b/src/main/server/AdminService.js @@ -7,6 +7,7 @@ const apiRequestLogger = require('./apiRequestLogger'); const DeviceManager = require('../data-managers/DeviceManager'); const ProtocolManager = require('../data-managers/ProtocolManager'); const ExportManager = require('../data-managers/ExportManager'); +const { resetPemKeyPair } = require('./certificateManager'); const { PairingRequestService } = require('./devices/PairingRequestService'); const { RequestError, ErrorMessages } = require('../errors/RequestError'); @@ -300,9 +301,13 @@ class AdminService { return api; } - // TODO: Probably remove after alpha testing + resetDevices() { + return this.deviceManager.destroyAllDevices(); + } + resetData() { return Promise.all([ + resetPemKeyPair(), this.deviceManager.destroyAllDevices(), this.protocolManager.destroyAllProtocols(), this.protocolManager.destroyAllSessions(), diff --git a/src/main/server/ServerFactory.js b/src/main/server/ServerFactory.js index 5cd0b8859..29b5f3bba 100644 --- a/src/main/server/ServerFactory.js +++ b/src/main/server/ServerFactory.js @@ -1,5 +1,5 @@ const Server = require('./Server'); -const ensurePemKeyPair = require('./ensurePemKeyPair'); +const { ensurePemKeyPair } = require('./certificateManager'); const { ready: cipherReady } = require('../utils/shared-api/cipher'); const { deviceServiceEvents } = require('./devices/DeviceService'); diff --git a/src/main/server/__tests__/ServerFactory-test.js b/src/main/server/__tests__/ServerFactory-test.js index 41d32ee0f..883001640 100644 --- a/src/main/server/__tests__/ServerFactory-test.js +++ b/src/main/server/__tests__/ServerFactory-test.js @@ -3,7 +3,7 @@ const path = require('path'); const { createServer } = require('../ServerFactory'); -jest.mock('../ensurePemKeyPair'); +jest.mock('../certificateManager'); const mockServerMethods = { close: jest.fn(), diff --git a/src/main/server/__tests__/ensurePemKeyPair-test.js b/src/main/server/__tests__/certificateManager-test.js similarity index 97% rename from src/main/server/__tests__/ensurePemKeyPair-test.js rename to src/main/server/__tests__/certificateManager-test.js index 11d2f8bf7..620550ee5 100644 --- a/src/main/server/__tests__/ensurePemKeyPair-test.js +++ b/src/main/server/__tests__/certificateManager-test.js @@ -1,7 +1,7 @@ /* eslint-env jest */ const selfsigned = require('selfsigned'); -const ensurePemKeyPair = require('../ensurePemKeyPair'); +const { ensurePemKeyPair } = require('../certificateManager'); const promisedFs = require('../../utils/promised-fs'); jest.mock('selfsigned'); diff --git a/src/main/server/ensurePemKeyPair.js b/src/main/server/certificateManager.js similarity index 86% rename from src/main/server/ensurePemKeyPair.js rename to src/main/server/certificateManager.js index 817ce0c64..28724069d 100644 --- a/src/main/server/ensurePemKeyPair.js +++ b/src/main/server/certificateManager.js @@ -37,6 +37,10 @@ const generatePemKeyPair = () => { keyEncipherment: true, dataEncipherment: true, }, + { + name: 'extKeyUsage', + serverAuth: true, + }, { name: 'subjectAltName', altNames: [ @@ -59,7 +63,7 @@ const generatePemKeyPair = () => { // TODO: Ed25519 and/or native implementation const pems = selfsigned.generate(attrs, { algorithm: 'sha256', - days: 365 * 10, + days: 365 * 2, keySize: 2048, extensions, }); @@ -105,4 +109,21 @@ const ensurePemKeyPair = () => ( }) ); -module.exports = ensurePemKeyPair; +const resetPemKeyPair = () => ( + Promise.all([ + promisedFs.tryUnlink(certPem), + promisedFs.tryUnlink(privatePem), + promisedFs.tryUnlink(publicPem), + promisedFs.tryUnlink(fingerprintFile), + ]) + .then(generatePemKeyPair) + .catch((err) => { + logger.error(err); + throw err; + }) +); + +module.exports = { + ensurePemKeyPair, + resetPemKeyPair, +}; diff --git a/src/main/utils/promised-fs.js b/src/main/utils/promised-fs.js index be2939a0e..76e47f7f5 100644 --- a/src/main/utils/promised-fs.js +++ b/src/main/utils/promised-fs.js @@ -68,6 +68,7 @@ const unlink = path => (new Promise((resolve, reject) => { } catch (err) { reject(err); } })); +// Ignore "file/directory doesn't exist" errors. const tryUnlink = path => unlink(path).catch((err) => { if (err.code !== 'ENOENT') { throw err; } });