Skip to content

Commit

Permalink
fix: Limit trust anchor scope to a single host
Browse files Browse the repository at this point in the history
  • Loading branch information
Ben Schwartz committed Mar 17, 2022
1 parent 4409154 commit 7dec0bd
Show file tree
Hide file tree
Showing 8 changed files with 42 additions and 13 deletions.
15 changes: 11 additions & 4 deletions src/server_manager/electron_app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import * as path from 'path';
import {URL, URLSearchParams} from 'url';

import * as menu from './menu';
import {HostAnchor} from './util';

const app = electron.app;
const ipcMain = electron.ipcMain;
Expand Down Expand Up @@ -239,14 +240,20 @@ function main() {
});

// Handle request to trust the certificate from the renderer process.
const trustedFingerprints = new Set<string>();
ipcMain.on('trust-certificate', (event: IpcEvent, fingerprint: string) => {
trustedFingerprints.add(`sha256/${fingerprint}`);
const trustedFingerprints = new Map<string, string>();
ipcMain.on('trust-certificate', (event: IpcEvent, anchor: HostAnchor) => {
trustedFingerprints.set(anchor.host, `sha256/${anchor.fingerprint}`);
event.returnValue = true;
});
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
event.preventDefault();
callback(trustedFingerprints.has(certificate.fingerprint));
try {
const parsed = new URL(url);
callback(trustedFingerprints.get(parsed.host) === certificate.fingerprint);
} catch (e) {
console.error(e);
callback(false);
}
});

// Restores the mainWindow if minimized and brings it into focus.
Expand Down
6 changes: 3 additions & 3 deletions src/server_manager/electron_app/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {URL} from 'url';

import * as digitalocean_oauth from './digitalocean_oauth';
import * as gcp_oauth from './gcp_oauth';
import {redactManagerUrl} from './util';
import {HostAnchor, redactManagerUrl} from './util';

// This file is run in the renderer process *before* nodeIntegration is disabled.
//
Expand Down Expand Up @@ -47,8 +47,8 @@ if (sentryDsn) {
});
}

contextBridge.exposeInMainWorld('trustCertificate', (fingerprint: string) => {
return ipcRenderer.sendSync('trust-certificate', fingerprint);
contextBridge.exposeInMainWorld('trustCertificate', (anchor: HostAnchor) => {
return ipcRenderer.sendSync('trust-certificate', anchor);
});

contextBridge.exposeInMainWorld('openImage', (basename: string) => {
Expand Down
6 changes: 6 additions & 0 deletions src/server_manager/electron_app/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,9 @@ import {URL} from 'url';
export function redactManagerUrl(s: string) {
return new URL(s).pathname.split('/').slice(2).join('/');
}

/** Represents an additional trust anchor for a single host. */
export interface HostAnchor {
host: string;
fingerprint: string;
}
7 changes: 6 additions & 1 deletion src/server_manager/types/preload.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@

// Functions made available to the renderer process via preload.ts.

declare function trustCertificate(fingerprint: string): boolean;
interface HostAnchor {
host: string;
fingerprint: string;
}

declare function trustCertificate(anchor: HostAnchor): boolean;
declare function openImage(basename: string): void;
declare function onUpdateDownloaded(callback: () => void): void;

Expand Down
5 changes: 3 additions & 2 deletions src/server_manager/web_app/browser_main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
// limitations under the License.

// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).trustCertificate = (fingerprint: string) => {
console.log(`Requested to trust certificate with fingerprint ${fingerprint}`);
(window as any).trustCertificate = (anchor: HostAnchor) => {
console.log(`Requested to trust certificate: ${anchor}`);
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -59,4 +59,5 @@
console.info(`Requested bringToFront`);
};

import {HostAnchor} from '../electron_app/util';
import './main';
3 changes: 2 additions & 1 deletion src/server_manager/web_app/digitalocean_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,9 @@ export class DigitalOceanServer extends ShadowboxServer implements server.Manage
// these methods throw exceptions if the fields are unavailable.
const certificateFingerprint = this.getCertificateFingerprint();
const apiAddress = this.getManagementApiAddress();
const parsed = new URL(apiAddress);
// Loaded both the cert and url without exceptions, they can be set.
trustCertificate(certificateFingerprint);
trustCertificate({host: parsed.host, fingerprint: certificateFingerprint});
this.setManagementApiUrl(apiAddress);
return true;
} catch (e) {
Expand Down
9 changes: 8 additions & 1 deletion src/server_manager/web_app/gcp_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,14 @@ export class GcpServer extends ShadowboxServer implements server.ManagedServer {
if (outlineGuestAttributes.has('apiUrl') && outlineGuestAttributes.has('certSha256')) {
const certSha256 = outlineGuestAttributes.get('certSha256');
const apiUrl = outlineGuestAttributes.get('apiUrl');
trustCertificate(certSha256);
try {
const parsed = new URL(apiUrl);
trustCertificate({host: parsed.host, fingerprint: certSha256});
} catch (e) {
console.error(e);
this.setInstallState(InstallState.FAILED);
break;
}
this.setManagementApiUrl(apiUrl);
this.setInstallState(InstallState.COMPLETED);
break;
Expand Down
4 changes: 3 additions & 1 deletion src/server_manager/web_app/manual_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ class ManualServer extends ShadowboxServer implements server.ManualServer {
// Electron requires that this be decoded from hex (to unprintable binary),
// then encoded as base64.
try {
trustCertificate(btoa(hexToString(manualServerConfig.certSha256)));
const parsed = new URL(manualServerConfig.apiUrl);
const fingerprint = btoa(hexToString(manualServerConfig.certSha256));
trustCertificate({host: parsed.host, fingerprint});
} catch (e) {
// Error trusting certificate, may be due to bad user input.
console.error('Error trusting certificate');
Expand Down

0 comments on commit 7dec0bd

Please sign in to comment.