diff --git a/packages/core/src/main/start-main-application/lens-window/application-window/__test__/session-certificate-verifier.test.ts b/packages/core/src/main/start-main-application/lens-window/application-window/__test__/session-certificate-verifier.test.ts new file mode 100644 index 0000000000..a8db1947a3 --- /dev/null +++ b/packages/core/src/main/start-main-application/lens-window/application-window/__test__/session-certificate-verifier.test.ts @@ -0,0 +1,78 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { DiContainer } from "@ogre-tools/injectable"; +import setupLensProxyCertificateInjectable from "../../../../start-main-application/runnables/setup-lens-proxy-certificate.injectable"; +import lensProxyCertificateInjectable from "../../../../../common/certificate/lens-proxy-certificate.injectable"; +import { getDiForUnitTesting } from "../../../../getDiForUnitTesting"; +import sessionCertificateVerifierInjectable, { ChromiumNetError } from "../session-certificate-verifier.injectable"; + +const externalCertificate = `-----BEGIN CERTIFICATE----- +MIIFzzCCBLegAwIBAgIQByL1wEn7yGRLqHZvmBzvpTANBgkqhkiG9w0BAQsFADA8 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRwwGgYDVQQDExNBbWF6b24g +UlNBIDIwNDggTTAyMB4XDTIzMDIwOTAwMDAwMFoXDTIzMTAxNDIzNTk1OVowFjEU +MBIGA1UEAxMLazhzbGVucy5kZXYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQDNdPm5tKztUpgHgDHjktelNvRsaj4QHTzShUP5p2uGu+lNHhPByp3+fp8p +v6V4PhRyH006RcyvUkQlEOiprP0fF/L16Jlrlo13N7hspVS4drlxE0v4JcLxBKm8 +pwsv7bfeZ7g6SWKA/0wbSTk8AyL0rCgcpMUWyPloq3gInO1x7kazgCAgrB34CSdj +JyD1Y8Od8eH8C9qdRlTcV0rG8y2np8YbK1lF77CXjD2feGjiUAMUAtArGKCZOc33 +erdhvXgJQ1/SgWcEbbhEZ7j8cfH6y7hPPmU43epyePvY0SZ7x1PBt870W1LjG6lq +pfzqxVVxmT6Txiktnd/6cHCzfxjbAgMBAAGjggLxMIIC7TAfBgNVHSMEGDAWgBTA +MVLNWlDDgnx0cc7L6Zz5euuC4jAdBgNVHQ4EFgQUcC3Qdy61LUiE9hOvDJGYC/yt +fu0wJQYDVR0RBB4wHIILazhzbGVucy5kZXaCDSouazhzbGVucy5kZXYwDgYDVR0P +AQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjA7BgNVHR8E +NDAyMDCgLqAshipodHRwOi8vY3JsLnIybTAyLmFtYXpvbnRydXN0LmNvbS9yMm0w +Mi5jcmwwEwYDVR0gBAwwCjAIBgZngQwBAgEwdQYIKwYBBQUHAQEEaTBnMC0GCCsG +AQUFBzABhiFodHRwOi8vb2NzcC5yMm0wMi5hbWF6b250cnVzdC5jb20wNgYIKwYB +BQUHMAKGKmh0dHA6Ly9jcnQucjJtMDIuYW1hem9udHJ1c3QuY29tL3IybTAyLmNl +cjAMBgNVHRMBAf8EAjAAMIIBfAYKKwYBBAHWeQIEAgSCAWwEggFoAWYAdQDoPtDa +PvUGNTLnVyi8iWvJA9PL0RFr7Otp4Xd9bQa9bgAAAYY4etOIAAAEAwBGMEQCIGT/ +/BWgTcOFQdzEX2qKlArMTvMwXggEY+m4ervIFLFnAiAyuX0I9jbGBI1XBiQ2mjXT +FIGw3TMF5b4rrCwhkRBG/gB1ALNzdwfhhFD4Y4bWBancEQlKeS2xZwwLh9zwAw55 +NqWaAAABhjh6084AAAQDAEYwRAIgewezL8S3+qwozF4fNt+0FiV95luazD1yKb35 +ZeOqudACIC7eFoZsaySOOivbqIp+nr9PB3qD08C1VKoi/LmnDp+3AHYAtz77JN+c +Tbp18jnFulj0bF38Qs96nzXEnh0JgSXttJkAAAGGOHrTlgAABAMARzBFAiAmZyNU +1H54FbGdwwXVXPxNYVE3MUlHswkR56WvWkvJ0wIhAJELvOBDIsCJ5uxTam2Xaxe0 +nZ+YTVzXDoQAfHplV1N6MA0GCSqGSIb3DQEBCwUAA4IBAQAghl2vkfW4Gph6Ez/v +EA/INeDXSErm/o3zBv4uTS7kuINPAtTlDtVJW/usw++F5fmgjmyNVc94y35hFG9Q +8LTDgJWvxekmiJJ+FCAxbpkhqXjHhugXwoUvAKktpyFnw+1cliYeA01EevOhnN+n +ux6vjEyhhEZm/JV/TXWaNSmVprXRXwc1m5dQzEEqkXgIhhhSK7E/63L+Zm548cjp +LAp+pJnaHfg0a83QnPWyZeyob+GklQjEdx64i+7wAhhpUp1Ge2TnFfs6zQGv2Y7/ +mgyzhHkKlUwQb5pi0rgR4oqKhnItyXjWqN3Y3wefTJblIs2sxEtYEzBUwlQZ3YM/ +ycM4 +-----END CERTIFICATE-----`; + +describe("sessionCertificateVerifier", () => { + let di: DiContainer; + + beforeEach(() => { + di = getDiForUnitTesting(); + di.unoverride(lensProxyCertificateInjectable); + di.inject(setupLensProxyCertificateInjectable).run(); + }); + + it("marks lens proxy certificate as trusted", () => { + const sessionCertificateVerifier = di.inject(sessionCertificateVerifierInjectable); + const lensProxyCertificate = di.inject(lensProxyCertificateInjectable).get(); + const callback = jest.fn(); + + sessionCertificateVerifier({ + certificate: { data: lensProxyCertificate.cert }, + } as any, callback); + + expect(callback).toHaveBeenCalledWith(ChromiumNetError.SUCCESS); + }); + + it("passes verification to chromium on non lens proxy certificate", () => { + const sessionCertificateVerifier = di.inject(sessionCertificateVerifierInjectable); + const callback = jest.fn(); + + sessionCertificateVerifier({ + certificate: { data: externalCertificate }, + } as any, callback); + + expect(callback).toHaveBeenCalledWith(ChromiumNetError.RESULT_FROM_CHROMIUM); + }); +}); diff --git a/packages/core/src/main/start-main-application/lens-window/application-window/create-electron-window.injectable.ts b/packages/core/src/main/start-main-application/lens-window/application-window/create-electron-window.injectable.ts index b53f58c838..0693c71643 100644 --- a/packages/core/src/main/start-main-application/lens-window/application-window/create-electron-window.injectable.ts +++ b/packages/core/src/main/start-main-application/lens-window/application-window/create-electron-window.injectable.ts @@ -3,7 +3,6 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { timingSafeEqual, X509Certificate } from "crypto"; import loggerInjectable from "../../../../common/logger.injectable"; import applicationWindowStateInjectable from "./application-window-state.injectable"; import { BrowserWindow } from "electron"; @@ -14,8 +13,8 @@ import getAbsolutePathInjectable from "../../../../common/path/get-absolute-path import lensResourcesDirInjectable from "../../../../common/vars/lens-resources-dir.injectable"; import isLinuxInjectable from "../../../../common/vars/is-linux.injectable"; import pathExistsSyncInjectable from "../../../../common/fs/path-exists-sync.injectable"; -import lensProxyCertificateInjectable from "../../../../common/certificate/lens-proxy-certificate.injectable"; import { applicationInformationToken } from "@k8slens/application"; +import sessionCertificateVerifierInjectable from "./session-certificate-verifier.injectable"; export type ElectronWindowTitleBarStyle = "hiddenInset" | "hidden" | "default" | "customButtonsOnHover"; @@ -27,13 +26,6 @@ export interface UrlSource { } export type ContentSource = RequireExactlyOne; -// see https://www.electronjs.org/docs/latest/api/session#sessetcertificateverifyprocproc -enum ChromiumNetError { - SUCCESS = 0, - FAILURE = -2, - RESULT_FROM_CHROMIUM = -3, -} - export interface ElectronWindowConfiguration { id: string; title: string; @@ -64,8 +56,7 @@ const createElectronWindowInjectable = getInjectable({ const isLinux = di.inject(isLinuxInjectable); const applicationInformation = di.inject(applicationInformationToken); const pathExistsSync = di.inject(pathExistsSyncInjectable); - const lensProxyCertificate = di.inject(lensProxyCertificateInjectable).get(); - const lensProxyX509Cert = new X509Certificate(lensProxyCertificate.cert); + const sessionCertificateVerifier = di.inject(sessionCertificateVerifierInjectable); return (configuration) => { const applicationWindowState = di.inject( @@ -119,14 +110,7 @@ const createElectronWindowInjectable = getInjectable({ applicationWindowState.manage(browserWindow); - browserWindow.webContents.session.setCertificateVerifyProc((request, shouldBeTrusted) => { - const { certificate } = request; - const cert = new X509Certificate(certificate.data); - const shouldTrustCert = cert.raw.length === lensProxyX509Cert.raw.length - && timingSafeEqual(cert.raw, lensProxyX509Cert.raw); - - shouldBeTrusted(shouldTrustCert ? ChromiumNetError.SUCCESS : ChromiumNetError.RESULT_FROM_CHROMIUM); - }); + browserWindow.webContents.session.setCertificateVerifyProc(sessionCertificateVerifier); browserWindow .on("focus", () => { diff --git a/packages/core/src/main/start-main-application/lens-window/application-window/session-certificate-verifier.injectable.ts b/packages/core/src/main/start-main-application/lens-window/application-window/session-certificate-verifier.injectable.ts new file mode 100644 index 0000000000..33ec0d1f09 --- /dev/null +++ b/packages/core/src/main/start-main-application/lens-window/application-window/session-certificate-verifier.injectable.ts @@ -0,0 +1,36 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { timingSafeEqual, X509Certificate } from "crypto"; +import type { Request } from "electron"; +import lensProxyCertificateInjectable from "../../../../common/certificate/lens-proxy-certificate.injectable"; + +// see https://www.electronjs.org/docs/latest/api/session#sessetcertificateverifyprocproc +export enum ChromiumNetError { + SUCCESS = 0, + FAILURE = -2, + RESULT_FROM_CHROMIUM = -3, +} + +export type CertificateVerificationCallback = (error: ChromiumNetError) => void; + +const sessionCertificateVerifierInjectable = getInjectable({ + id: "session-certificate-verifier", + instantiate: (di) => { + const lensProxyCertificate = di.inject(lensProxyCertificateInjectable).get(); + const lensProxyX509Cert = new X509Certificate(lensProxyCertificate.cert); + + return (request: Request, shouldBeTrusted: CertificateVerificationCallback) => { + const { certificate } = request; + const cert = new X509Certificate(certificate.data); + const shouldTrustCert = cert.raw.length === lensProxyX509Cert.raw.length + && timingSafeEqual(cert.raw, lensProxyX509Cert.raw); + + shouldBeTrusted(shouldTrustCert ? ChromiumNetError.SUCCESS : ChromiumNetError.RESULT_FROM_CHROMIUM); + }; + }, +}); + +export default sessionCertificateVerifierInjectable;