diff --git a/desktop/app/src/server/comms/ServerAdapter.tsx b/desktop/app/src/server/comms/ServerAdapter.tsx index eb41680ff..22821f939 100644 --- a/desktop/app/src/server/comms/ServerAdapter.tsx +++ b/desktop/app/src/server/comms/ServerAdapter.tsx @@ -138,10 +138,11 @@ abstract class ServerAdapter { async _onHandleUntrustedMessage( clientQuery: ClientQuery, rawData: any, - ): Promise { + ): Promise { // OSS's older Client SDK might not send medium information. // This is not an issue for internal FB users, as Flipper release // is insync with client SDK through launcher. + const message: { method: 'signCertificate'; csr: string; @@ -149,24 +150,37 @@ abstract class ServerAdapter { medium: number | undefined; } = rawData; + console.log( + `[conn] Connection attempt: ${clientQuery.app} on ${clientQuery.device}, medium: ${message.medium}, cert: ${message.destination}`, + clientQuery, + rawData, + ); + if (message.method === 'signCertificate') { console.debug('CSR received from device', 'server'); const {csr, destination, medium} = message; + console.log( + `[conn] Starting certificate exchange: ${clientQuery.app} on ${clientQuery.device}`, + ); const result = await this.listener.onProcessCSR( csr, clientQuery, destination, transformCertificateExchangeMediumToType(medium), ); + + console.log( + `[conn] Exchanged certificate: ${clientQuery.app} on ${clientQuery.device_id}`, + ); const response = JSON.stringify({ deviceId: result.deviceId, }); return response; } - return Promise.resolve(); + return undefined; } } diff --git a/desktop/app/src/server/comms/ServerController.tsx b/desktop/app/src/server/comms/ServerController.tsx index b8181ead3..36a94064e 100644 --- a/desktop/app/src/server/comms/ServerController.tsx +++ b/desktop/app/src/server/comms/ServerController.tsx @@ -151,6 +151,9 @@ class ServerController extends EventEmitter implements ServerEventsListener { const {app, os, device, device_id, sdk_version, csr, csr_path, medium} = clientQuery; const transformedMedium = transformCertificateExchangeMediumToType(medium); + console.log( + `[conn] Connection established: ${app} on ${device_id}. Medium ${medium}. CSR: ${csr_path}`, + ); return this.addConnection( clientConnection, { @@ -293,6 +296,9 @@ class ServerController extends EventEmitter implements ServerEventsListener { csr_path, csr, ); + console.log( + `[conn] Detected ${app_name} on ${query.device_id} in certificate`, + ); } query.app = appNameWithUpdateHint(query); @@ -303,8 +309,9 @@ class ServerController extends EventEmitter implements ServerEventsListener { device: query.device, device_id: query.device_id, }); - console.debug(`Device connected: ${id}`, 'server'); - + console.log( + `[conn] Matching device for ${query.app} on ${query.device_id}...`, + ); const device = getDeviceBySerial(this.store.getState(), query.device_id) ?? (await findDeviceForConnection(this.store, query.app, query.device_id)); @@ -324,6 +331,10 @@ class ServerController extends EventEmitter implements ServerEventsListener { connection: connection, }; + console.log( + `[conn] Initializing client ${query.app} on ${query.device_id}...`, + ); + await client.init(); connection.subscribeToEvents((status: ConnectionStatus) => { @@ -336,7 +347,7 @@ class ServerController extends EventEmitter implements ServerEventsListener { }); console.debug( - `Device client initialized: ${id}. Supported plugins: ${Array.from( + `[conn] Device client initialized: ${id}. Supported plugins: ${Array.from( client.plugins, ).join(', ')}`, 'server', @@ -382,6 +393,9 @@ class ServerController extends EventEmitter implements ServerEventsListener { removeConnection(id: string) { const info = this.connections.get(id); if (info) { + console.log( + `[conn] Disconnected: ${info.client.query.app} on ${info.client.query.device_id}.`, + ); info.client.disconnect(); this.connections.delete(id); this.emit('clients-change'); @@ -412,7 +426,7 @@ class ConnectionTracker { this.connectionAttempts.set(key, entry); if (entry.length >= this.connectionProblemThreshold) { console.error( - `Connection loop detected with ${key}. Connected ${ + `[conn] Connection loop detected with ${key}. Connected ${ this.connectionProblemThreshold } times within ${this.timeWindowMillis / 1000}s.`, 'server', @@ -444,7 +458,10 @@ async function findDeviceForConnection( const timeout = setTimeout(() => { unsubscribe(); const error = `Timed out waiting for device ${serial} for client ${clientId}`; - console.error('Unable to find device for connection. Error:', error); + console.error( + '[conn] Unable to find device for connection. Error:', + error, + ); reject(error); }, 15000); unsubscribe = sideEffect( @@ -460,6 +477,7 @@ async function findDeviceForConnection( (device) => device.serial === serial, ); if (matchingDevice) { + console.log(`[conn] Found device for: ${clientId} on ${serial}.`); clearTimeout(timeout); resolve(matchingDevice); unsubscribe(); diff --git a/desktop/app/src/server/comms/ServerRSocket.tsx b/desktop/app/src/server/comms/ServerRSocket.tsx index 959109464..812c90978 100644 --- a/desktop/app/src/server/comms/ServerRSocket.tsx +++ b/desktop/app/src/server/comms/ServerRSocket.tsx @@ -109,6 +109,9 @@ class ServerRSocket extends ServerAdapter { const clientQuery: SecureClientQuery = JSON.parse(payload.data); this.listener.onSecureConnectionAttempt(clientQuery); + console.log( + `[conn] Secure rsocket connection attempt: ${clientQuery.app} on ${clientQuery.device_id}. Medium ${clientQuery.medium}. CSR: ${clientQuery.csr_path}`, + ); const clientConnection: ClientConnection = { subscribeToEvents(subscriber: ConnectionStatusChange): void { @@ -168,10 +171,13 @@ class ServerRSocket extends ServerAdapter { ); client .then((client) => { + console.log( + `[conn] Client created: ${clientQuery.app} on ${clientQuery.device_id}. Medium ${clientQuery.medium}. CSR: ${clientQuery.csr_path}`, + ); resolvedClient = client; }) .catch((e) => { - console.error('Failed to resolve new client', e); + console.error('[conn] Failed to resolve new client', e); }); return { @@ -184,7 +190,9 @@ class ServerRSocket extends ServerAdapter { .then((client) => { client.onMessage(payload.data); }) - .catch((_) => {}); + .catch((e) => { + console.error('Could not deliver message: ', e); + }); } }, }; @@ -221,7 +229,7 @@ class ServerRSocket extends ServerAdapter { rawData = JSON.parse(payload.data); } catch (err) { console.error( - `Invalid JSON: ${payload.data}`, + `[conn] Invalid JSON: ${payload.data}`, 'clientMessage', 'server', ); @@ -263,7 +271,10 @@ class ServerRSocket extends ServerAdapter { this._onHandleUntrustedMessage(clientQuery, rawData) .then((_) => {}) .catch((err) => { - console.error('Unable to process CSR, failed with error.', err); + console.error( + '[conn] Unable to process CSR, failed with error.', + err, + ); }); } }, diff --git a/desktop/app/src/server/comms/ServerWebSocket.tsx b/desktop/app/src/server/comms/ServerWebSocket.tsx index 1bd1bfa99..b363e8fde 100644 --- a/desktop/app/src/server/comms/ServerWebSocket.tsx +++ b/desktop/app/src/server/comms/ServerWebSocket.tsx @@ -51,11 +51,16 @@ class ServerWebSocket extends ServerWebSocketBase { const query = querystring.decode(message.url.split('?')[1]); const clientQuery = this._parseClientQuery(query); if (!clientQuery) { - console.warn('Unable to extract the client query from the request URL.'); + console.warn( + '[conn] Unable to extract the client query from the request URL.', + ); ws.close(); return; } + console.log( + `[conn] Insecure websocket connection attempt: ${clientQuery.app} on ${clientQuery.device_id}.`, + ); this.listener.onConnectionAttempt(clientQuery); ws.on('message', async (message: any) => { @@ -78,10 +83,15 @@ class ServerWebSocket extends ServerWebSocketBase { const query = querystring.decode(message.url.split('?')[1]); const clientQuery = this._parseSecureClientQuery(query); if (!clientQuery) { - console.warn('Unable to extract the client query from the request URL.'); + console.warn( + '[conn] Unable to extract the client query from the request URL.', + ); ws.close(); return; } + console.log( + `[conn] Secure websocket connection attempt: ${clientQuery.app} on ${clientQuery.device_id}. Medium ${clientQuery.medium}. CSR: ${clientQuery.csr_path}`, + ); this.listener.onSecureConnectionAttempt(clientQuery); const pendingRequests: Map< @@ -116,7 +126,14 @@ class ServerWebSocket extends ServerWebSocketBase { clientQuery, clientConnection, ); - client.then((client) => (resolvedClient = client)).catch((_) => {}); + client + .then((client) => (resolvedClient = client)) + .catch((e) => { + console.error( + `[conn] Failed to resolve client ${clientQuery.app} on ${clientQuery.device_id} medium ${clientQuery.medium}`, + e, + ); + }); ws.on('message', (message: any) => { let json: any | undefined; diff --git a/desktop/app/src/server/comms/ServerWebSocketBase.tsx b/desktop/app/src/server/comms/ServerWebSocketBase.tsx index ef3aff5f5..5d42d6cf6 100644 --- a/desktop/app/src/server/comms/ServerWebSocketBase.tsx +++ b/desktop/app/src/server/comms/ServerWebSocketBase.tsx @@ -72,7 +72,7 @@ abstract class ServerWebSocketBase extends ServerAdapter { handleRequest.apply(self, [ws, message]); }); rawServer.on('error', (_ws: WebSocket, error: any) => { - console.warn('Server found connection error: ' + error); + console.warn('[conn] Server found connection error: ' + error); reject(error); }); diff --git a/desktop/app/src/server/comms/ServerWebSocketBrowser.tsx b/desktop/app/src/server/comms/ServerWebSocketBrowser.tsx index 55b005ebd..85d7edc3e 100644 --- a/desktop/app/src/server/comms/ServerWebSocketBrowser.tsx +++ b/desktop/app/src/server/comms/ServerWebSocketBrowser.tsx @@ -27,9 +27,15 @@ class ServerWebSocketBrowser extends ServerWebSocketBase { verifyClient(): ws.VerifyClientCallbackSync { return (info: {origin: string; req: IncomingMessage; secure: boolean}) => { - return constants.VALID_WEB_SOCKET_REQUEST_ORIGIN_PREFIXES.some( + const ok = constants.VALID_WEB_SOCKET_REQUEST_ORIGIN_PREFIXES.some( (validPrefix) => info.origin.startsWith(validPrefix), ); + if (!ok) { + console.warn( + `[conn] Refused webSocket connection from ${info.origin} (secure: ${info.secure})`, + ); + } + return ok; }; } @@ -60,6 +66,9 @@ class ServerWebSocketBrowser extends ServerWebSocketBase { os: 'MacOS', // TODO: not hardcoded! Use host device? }; + console.log( + `[conn] Local websocket connection attempt: ${clientQuery.app} on ${clientQuery.device_id}.`, + ); this.listener.onConnectionAttempt(clientQuery); const cleanup = () => { @@ -104,6 +113,10 @@ class ServerWebSocketBrowser extends ServerWebSocketBase { const extendedClientQuery = {...clientQuery, medium: 1}; extendedClientQuery.sdk_version = plugins == null ? 4 : 1; + console.log( + `[conn] Local websocket connection established: ${clientQuery.app} on ${clientQuery.device_id}.`, + ); + let resolvedClient: Client | null = null; const client: Promise = this.listener.onConnectionCreated( extendedClientQuery, @@ -111,10 +124,16 @@ class ServerWebSocketBrowser extends ServerWebSocketBase { ); client .then((client) => { + console.log( + `[conn] Client created: ${clientQuery.app} on ${clientQuery.device_id}.`, + ); resolvedClient = client; }) .catch((e) => { - console.error('Failed to connect client over webSocket', e); + console.error( + '[conn] Failed to connect client over webSocket', + e, + ); }); clients[app] = client; @@ -125,7 +144,7 @@ class ServerWebSocketBrowser extends ServerWebSocketBase { parsed = JSON.parse(m.toString()); } catch (error) { // Throws a SyntaxError exception if the string to parse is not valid JSON. - console.log('Received message is not valid.', error); + console.log('[conn] Received message is not valid.', error); return; } // non-null payload id means response to prev request, it's handled in connection @@ -159,7 +178,7 @@ class ServerWebSocketBrowser extends ServerWebSocketBase { }); /** Error event from the existing client connection. */ ws.on('error', (error) => { - console.warn('Server found connection error: ' + error); + console.warn('[conn] Server found connection error: ' + error); cleanup(); }); } diff --git a/desktop/app/src/server/utils/CertificateProvider.tsx b/desktop/app/src/server/utils/CertificateProvider.tsx index c2b802faf..ed7e8dbce 100644 --- a/desktop/app/src/server/utils/CertificateProvider.tsx +++ b/desktop/app/src/server/utils/CertificateProvider.tsx @@ -110,6 +110,10 @@ export default class CertificateProvider { this.ensureServerCertExists(), 'ensureServerCertExists', ); + // make sure initialization failure is already logged + this.certificateSetup.catch((e) => { + console.error('Failed to find or generate certificates', e); + }); } this.config = config; this.server = server; @@ -128,10 +132,10 @@ export default class CertificateProvider { certificate_zip: file, device_id: deviceID, }), - 'Timed out uploading Flipper export.', + 'Timed out uploading Flipper certificates to WWW.', ), 'uploadCertificates', - ).catch((e) => console.error(`Failed to upload certificates due to ${e}`)); + ); }; async processCertificateSigningRequest( @@ -148,71 +152,60 @@ export default class CertificateProvider { const rootFolder = await promisify(tmp.dir)(); const certFolder = rootFolder + '/FlipperCerts/'; const certsZipPath = rootFolder + '/certs.zip'; - return this.certificateSetup - .then((_) => this.getCACertificate()) - .then((caCert) => - this.deployOrStageFileForMobileApp( - appDirectory, - deviceCAcertFile, - caCert, - csr, - os, - medium, - certFolder, - ), - ) - .then((_) => this.generateClientCertificate(csr)) - .then((clientCert) => - this.deployOrStageFileForMobileApp( - appDirectory, - deviceClientCertFile, - clientCert, - csr, - os, - medium, - certFolder, - ), - ) - .then((_) => { - return this.extractAppNameFromCSR(csr); - }) - .then((appName) => { - if (medium === 'FS_ACCESS') { - return this.getTargetDeviceId(os, appName, appDirectory, csr); - } else { - return uuid(); - } - }) - .then(async (deviceId) => { - if (medium === 'WWW') { - const zipPromise = new Promise((resolve, reject) => { - const output = fs.createWriteStream(certsZipPath); - const archive = archiver('zip', { - zlib: {level: 9}, // Sets the compression level. - }); - archive.directory(certFolder, false); - output.on('close', function () { - resolve(certsZipPath); - }); - archive.on('warning', reject); - archive.on('error', reject); - archive.pipe(output); - archive.finalize(); - }); - - await reportPlatformFailures( - zipPromise, - 'www-certs-exchange-zipping-certs', - ); - await reportPlatformFailures( - this.uploadFiles(certsZipPath, deviceId), - 'www-certs-exchange-uploading-certs', - ); - } - return { - deviceId, - }; + await this.certificateSetup; + const caCert = await this.getCACertificate(); + await this.deployOrStageFileForMobileApp( + appDirectory, + deviceCAcertFile, + caCert, + csr, + os, + medium, + certFolder, + ); + const clientCert = await this.generateClientCertificate(csr); + await this.deployOrStageFileForMobileApp( + appDirectory, + deviceClientCertFile, + clientCert, + csr, + os, + medium, + certFolder, + ); + const appName = await this.extractAppNameFromCSR(csr); + const deviceId = + medium === 'FS_ACCESS' + ? await this.getTargetDeviceId(os, appName, appDirectory, csr) + : uuid(); + if (medium === 'WWW') { + const zipPromise = new Promise((resolve, reject) => { + const output = fs.createWriteStream(certsZipPath); + const archive = archiver('zip', { + zlib: {level: 9}, // Sets the compression level. + }); + archive.directory(certFolder, false); + output.on('close', function () { + resolve(certsZipPath); + }); + archive.on('warning', reject); + archive.on('error', reject); + archive.pipe(output); + archive.finalize(); }); + + await reportPlatformFailures( + zipPromise, + 'www-certs-exchange-zipping-certs', + ); + await reportPlatformFailures( + this.uploadFiles(certsZipPath, deviceId), + 'www-certs-exchange-uploading-certs', + ); + } + return { + deviceId, + }; } getTargetDeviceId( @@ -276,64 +269,56 @@ export default class CertificateProvider { medium: CertificateExchangeMedium, certFolder: string, ): Promise { - const appNamePromise = this.extractAppNameFromCSR(csr); - if (medium === 'WWW') { const certPathExists = await fs.pathExists(certFolder); if (!certPathExists) { await fs.mkdir(certFolder); } - return fs.writeFile(certFolder + filename, contents).catch((e) => { + try { + await fs.writeFile(certFolder + filename, contents); + return; + } catch (e) { throw new Error( `Failed to write ${filename} to temporary folder. Error: ${e}`, ); - }); + } } + const appName = await this.extractAppNameFromCSR(csr); + if (os === 'Android') { - const deviceIdPromise = appNamePromise.then((app) => - this.getTargetAndroidDeviceId(app, destination, csr), + const deviceId = await this.getTargetAndroidDeviceId( + appName, + destination, + csr, ); - return Promise.all([deviceIdPromise, appNamePromise, this.adb]).then( - ([deviceId, appName, adbClient]) => - androidUtil.push( - adbClient, - deviceId, - appName, - destination + filename, - contents, - ), + const adbClient = await this.adb; + await androidUtil.push( + adbClient, + deviceId, + appName, + destination + filename, + contents, ); + } else if (os === 'iOS') { + try { + await fs.writeFile(destination + filename, contents); + } catch (err) { + // Writing directly to FS failed. It's probably a physical device. + const relativePathInsideApp = + this.getRelativePathInAppContainer(destination); + const udid = await this.getTargetiOSDeviceId(appName, destination, csr); + await this.pushFileToiOSDevice( + udid, + appName, + relativePathInsideApp, + filename, + contents, + ); + } + } else { + throw new Error(`Unsupported device OS for Certificate Exchange: ${os}`); } - if (os === 'iOS' || os === 'windows' || os == 'MacOS') { - return fs - .writeFile(destination + filename, contents) - .catch(async (err) => { - if (os === 'iOS') { - // Writing directly to FS failed. It's probably a physical device. - const relativePathInsideApp = - this.getRelativePathInAppContainer(destination); - const appName = await appNamePromise; - const udid = await this.getTargetiOSDeviceId( - appName, - destination, - csr, - ); - return await this.pushFileToiOSDevice( - udid, - appName, - relativePathInsideApp, - filename, - contents, - ); - } - throw new Error( - `Invalid appDirectory recieved from ${os} device: ${destination}: ` + - err.toString(), - ); - }); - } - return Promise.reject(new Error(`Unsupported device os: ${os}`)); } private async pushFileToiOSDevice( @@ -346,7 +331,7 @@ export default class CertificateProvider { const dir = await tmpDir({unsafeCleanup: true}); const filePath = path.resolve(dir, filename); await fs.writeFile(filePath, contents); - return await iosUtil.push( + await iosUtil.push( udid, filePath, bundleId, @@ -360,11 +345,11 @@ export default class CertificateProvider { deviceCsrFilePath: string, csr: string, ): Promise { - const devices = await this.adb.then((client) => client.listDevices()); - if (devices.length === 0) { + const devicesInAdb = await this.adb.then((client) => client.listDevices()); + if (devicesInAdb.length === 0) { throw new Error('No Android devices found'); } - const deviceMatchList = devices.map(async (device) => { + const deviceMatchList = devicesInAdb.map(async (device) => { try { const result = await this.androidDeviceHasMatchingCSR( deviceCsrFilePath, @@ -381,33 +366,32 @@ export default class CertificateProvider { return {id: device.id, isMatch: false, foundCsr: null, error: e}; } }); - return Promise.all(deviceMatchList).then((devices) => { - const matchingIds = devices.filter((m) => m.isMatch).map((m) => m.id); - if (matchingIds.length == 0) { - const erroredDevice = devices.find((d) => d.error); - if (erroredDevice) { - throw erroredDevice.error; - } - const foundCsrs = devices - .filter((d) => d.foundCsr !== null) - .map((d) => (d.foundCsr ? encodeURI(d.foundCsr) : 'null')); - console.warn(`Looking for CSR (url encoded): + const devices = await Promise.all(deviceMatchList); + const matchingIds = devices.filter((m) => m.isMatch).map((m) => m.id); + if (matchingIds.length == 0) { + const erroredDevice = devices.find((d) => d.error); + if (erroredDevice) { + throw erroredDevice.error; + } + const foundCsrs = devices + .filter((d) => d.foundCsr !== null) + .map((d) => (d.foundCsr ? encodeURI(d.foundCsr) : 'null')); + console.warn(`Looking for CSR (url encoded): ${encodeURI(this.santitizeString(csr))} Found these: ${foundCsrs.join('\n\n')}`); - throw new Error(`No matching device found for app: ${appName}`); - } - if (matchingIds.length > 1) { - console.warn( - new Error('More than one matching device found for CSR'), - csr, - ); - } - return matchingIds[0]; - }); + throw new Error(`No matching device found for app: ${appName}`); + } + if (matchingIds.length > 1) { + console.warn( + new Error('[conn] More than one matching device found for CSR'), + csr, + ); + } + return matchingIds[0]; } private async getTargetiOSDeviceId( @@ -418,7 +402,7 @@ export default class CertificateProvider { const matches = /\/Devices\/([^/]+)\//.exec(deviceCsrFilePath); if (matches && matches.length == 2) { // It's a simulator, the deviceId is in the filepath. - return Promise.resolve(matches[1]); + return matches[1]; } const targets = await iosUtil.targets( this.config.idbPath, @@ -436,40 +420,38 @@ export default class CertificateProvider { ); return {id: target.udid, isMatch}; }); - return Promise.all(deviceMatchList).then((devices) => { - const matchingIds = devices.filter((m) => m.isMatch).map((m) => m.id); - if (matchingIds.length == 0) { - throw new Error(`No matching device found for app: ${appName}`); - } - return matchingIds[0]; - }); + const devices = await Promise.all(deviceMatchList); + const matchingIds = devices.filter((m) => m.isMatch).map((m) => m.id); + if (matchingIds.length == 0) { + throw new Error(`No matching device found for app: ${appName}`); + } + if (matchingIds.length > 1) { + console.warn(`Multiple devices found for app: ${appName}`); + } + return matchingIds[0]; } - private androidDeviceHasMatchingCSR( + private async androidDeviceHasMatchingCSR( directory: string, deviceId: string, processName: string, csr: string, ): Promise<{isMatch: boolean; foundCsr: string}> { - return this.adb - .then((adbClient) => - androidUtil.pull( - adbClient, - deviceId, - processName, - directory + csrFileName, - ), - ) - .then((deviceCsr) => { - // Santitize both of the string before comparation - // The csr string extraction on client side return string in both way - const [sanitizedDeviceCsr, sanitizedClientCsr] = [ - deviceCsr.toString(), - csr, - ].map((s) => this.santitizeString(s)); - const isMatch = sanitizedDeviceCsr === sanitizedClientCsr; - return {isMatch: isMatch, foundCsr: sanitizedDeviceCsr}; - }); + const adbClient = await this.adb; + const deviceCsr = await androidUtil.pull( + adbClient, + deviceId, + processName, + directory + csrFileName, + ); + // Santitize both of the string before comparation + // The csr string extraction on client side return string in both way + const [sanitizedDeviceCsr, sanitizedClientCsr] = [ + deviceCsr.toString(), + csr, + ].map((s) => this.santitizeString(s)); + const isMatch = sanitizedDeviceCsr === sanitizedClientCsr; + return {isMatch: isMatch, foundCsr: sanitizedDeviceCsr}; } private async iOSDeviceHasMatchingCSR( @@ -553,59 +535,57 @@ export default class CertificateProvider { private async checkCertIsValid(filename: string): Promise { if (!(await fs.pathExists(filename))) { - return Promise.reject(new Error(`${filename} does not exist`)); + throw new Error(`${filename} does not exist`); } // openssl checkend is a nice feature but it only checks for certificates // expiring in the future, not those that have already expired. // So we need a separate check for certificates that have already expired // but since this involves parsing date outputs from openssl, which is less // reliable, keeping both checks for safety. - return openssl('x509', { - checkend: minCertExpiryWindowSeconds, - in: filename, - }) - .then(() => undefined) - .catch((e) => { - console.warn(`Certificate will expire soon: ${filename}`, logTag); - throw e; - }) - .then((_) => - openssl('x509', { - enddate: true, - in: filename, - noout: true, - }), - ) - .then((endDateOutput) => { - const dateString = endDateOutput.trim().split('=')[1].trim(); - const expiryDate = Date.parse(dateString); - if (isNaN(expiryDate)) { - console.error( - 'Unable to parse certificate expiry date: ' + endDateOutput, - ); - throw new Error( - 'Cannot parse certificate expiry date. Assuming it has expired.', - ); - } - if (expiryDate <= Date.now() + minCertExpiryWindowSeconds * 1000) { - throw new Error('Certificate has expired or will expire soon.'); - } + try { + await openssl('x509', { + checkend: minCertExpiryWindowSeconds, + in: filename, }); + } catch (e) { + console.warn( + `Checking if certificate expire soon: ${filename}`, + logTag, + e, + ); + const endDateOutput = await openssl('x509', { + enddate: true, + in: filename, + noout: true, + }); + const dateString = endDateOutput.trim().split('=')[1].trim(); + const expiryDate = Date.parse(dateString); + if (isNaN(expiryDate)) { + console.error( + 'Unable to parse certificate expiry date: ' + endDateOutput, + ); + throw new Error( + 'Cannot parse certificate expiry date. Assuming it has expired.', + ); + } + if (expiryDate <= Date.now() + minCertExpiryWindowSeconds * 1000) { + throw new Error('Certificate has expired or will expire soon.'); + } + } } - private verifyServerCertWasIssuedByCA() { + private async verifyServerCertWasIssuedByCA() { const options: { [key: string]: any; } = {CAfile: caCert}; options[serverCert] = false; - return openssl('verify', options).then((output) => { - const verified = output.match(/[^:]+: OK/); - if (!verified) { - // This should never happen, but if it does, we need to notice so we can - // generate a valid one, or no clients will trust our server. - throw new Error('Current server cert was not issued by current CA'); - } - }); + const output = await openssl('verify', options); + const verified = output.match(/[^:]+: OK/); + if (!verified) { + // This should never happen, but if it does, we need to notice so we can + // generate a valid one, or no clients will trust our server. + throw new Error('Current server cert was not issued by current CA'); + } } private async generateCertificateAuthority(): Promise { @@ -613,21 +593,18 @@ export default class CertificateProvider { await fs.mkdir(getFilePath('')); } console.log('Generating new CA', logTag); - return openssl('genrsa', {out: caKey, '2048': false}) - .then((_) => - openssl('req', { - new: true, - x509: true, - subj: caSubject, - key: caKey, - out: caCert, - }), - ) - .then((_) => undefined); + await openssl('genrsa', {out: caKey, '2048': false}); + await openssl('req', { + new: true, + x509: true, + subj: caSubject, + key: caKey, + out: caCert, + }); } private async ensureServerCertExists(): Promise { - const allExist = Promise.all([ + const allExist = await Promise.all([ fs.pathExists(serverKey), fs.pathExists(serverCert), fs.pathExists(caCert), @@ -636,37 +613,34 @@ export default class CertificateProvider { return this.generateServerCertificate(); } - return this.checkCertIsValid(serverCert) - .then(() => this.verifyServerCertWasIssuedByCA()) - .catch(() => this.generateServerCertificate()); + try { + await this.checkCertIsValid(serverCert); + await this.verifyServerCertWasIssuedByCA(); + } catch (e) { + console.warn('Not all certs are valid, generating new ones', e); + await this.generateServerCertificate(); + } } - private generateServerCertificate(): Promise { - return this.ensureCertificateAuthorityExists() - .then((_) => { - console.warn('Creating new server cert', logTag); - }) - .then((_) => openssl('genrsa', {out: serverKey, '2048': false})) - .then((_) => - openssl('req', { - new: true, - key: serverKey, - out: serverCsr, - subj: serverSubject, - }), - ) - .then((_) => - openssl('x509', { - req: true, - in: serverCsr, - CA: caCert, - CAkey: caKey, - CAcreateserial: true, - CAserial: serverSrl, - out: serverCert, - }), - ) - .then((_) => undefined); + private async generateServerCertificate(): Promise { + await this.ensureCertificateAuthorityExists(); + console.warn('Creating new server cert', logTag); + await openssl('genrsa', {out: serverKey, '2048': false}); + await openssl('req', { + new: true, + key: serverKey, + out: serverCsr, + subj: serverSubject, + }); + await openssl('x509', { + req: true, + in: serverCsr, + CA: caCert, + CAkey: caKey, + CAcreateserial: true, + CAserial: serverSrl, + out: serverCert, + }); } private async writeToTempFile(content: string): Promise { diff --git a/desktop/plugins/public/crash_reporter/index.tsx b/desktop/plugins/public/crash_reporter/index.tsx index 48ea1f501..774e35ee8 100644 --- a/desktop/plugins/public/crash_reporter/index.tsx +++ b/desktop/plugins/public/crash_reporter/index.tsx @@ -30,7 +30,7 @@ export type CrashLog = { export function devicePlugin(client: DevicePluginClient) { let notificationID = -1; - let watcher: Promise; + let watcher: Promise | undefined = undefined; const crashes = createState([], {persist: 'crashes'}); const selectedCrash = createState(); @@ -71,7 +71,7 @@ export function devicePlugin(client: DevicePluginClient) { client.onDestroy(() => { watcher - .then((watcher) => watcher?.close()) + ?.then((watcher) => watcher?.close()) .catch((e) => console.error( '[crash_reporter] FSWatcher failed resoving on destroy:',