From 1184eb13f0bbe4768bf3dbd6cd31adf10c6c8dfe Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Fri, 22 Apr 2022 00:53:48 +0200 Subject: feat: Certificate viewer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Show certificates with an interface modeled after firefox's certificate viewer so that they can be inspected before trusting. The current implementation assumes that each certificate has a unique fingerprint (collisions are astronomically unlikely). Signed-off-by: Kristóf Marussy --- packages/main/src/stores/MainEnv.ts | 2 ++ packages/main/src/stores/Service.ts | 41 ++++++++++++++++++++++++++++++++++--- 2 files changed, 40 insertions(+), 3 deletions(-) (limited to 'packages/main/src/stores') diff --git a/packages/main/src/stores/MainEnv.ts b/packages/main/src/stores/MainEnv.ts index 8923322..f9dc209 100644 --- a/packages/main/src/stores/MainEnv.ts +++ b/packages/main/src/stores/MainEnv.ts @@ -28,4 +28,6 @@ export default interface MainEnv { openURLInExternalBrowser(url: string): void; openAboutDialog(): void; + + saveTextFile(filename: string, value: string): Promise; } diff --git a/packages/main/src/stores/Service.ts b/packages/main/src/stores/Service.ts index d8f3166..1d46dc9 100644 --- a/packages/main/src/stores/Service.ts +++ b/packages/main/src/stores/Service.ts @@ -20,12 +20,13 @@ import type { UnreadCount } from '@sophie/service-shared'; import { - CertificateSnapshotIn, + type Certificate, + type CertificateSnapshotIn, defineServiceModel, ServiceAction, - ServiceStateSnapshotIn, + type ServiceStateSnapshotIn, } from '@sophie/shared'; -import { Instance, getSnapshot, cast } from 'mobx-state-tree'; +import { type Instance, getSnapshot, cast, flow } from 'mobx-state-tree'; import type { ServiceView } from '../infrastructure/electron/types'; import { getLogger } from '../utils/log'; @@ -129,6 +130,35 @@ const Service = defineServiceModel(ServiceSettings) toggleDeveloperTools(): void { self.serviceView?.toggleDeveloperTools(); }, + downloadCertificate: flow(function* downloadCertificate( + fingerprint: string, + ) { + const { state } = self; + if (state.type !== 'certificateError') { + log.warn( + 'Tried to save certificate', + fingerprint, + 'when there is no certificate error', + ); + return; + } + let { certificate } = state; + while ( + certificate !== undefined && + certificate.fingerprint !== fingerprint + ) { + certificate = certificate.issuerCert as Certificate; + } + if (certificate === undefined) { + log.warn( + 'Tried to save certificate', + fingerprint, + 'which is not part of the current certificate chain', + ); + return; + } + yield getEnv(self).saveTextFile('certificate.pem', certificate.data); + }), })) .actions((self) => { function setState(state: ServiceStateSnapshotIn): void { @@ -272,6 +302,11 @@ const Service = defineServiceModel(ServiceSettings) case 'dismiss-all-popups': self.dismissAllPopups(); break; + case 'download-certificate': + self.downloadCertificate(action.fingerprint).catch((error) => { + log.error('Error while saving certificate', error); + }); + break; default: log.error('Unknown action to dispatch', action); break; -- cgit v1.2.3-70-g09d2