diff --git a/.rebase/CHANGELOG.md b/.rebase/CHANGELOG.md index e1f0055e971..4ed99ebbdb2 100644 --- a/.rebase/CHANGELOG.md +++ b/.rebase/CHANGELOG.md @@ -2,6 +2,12 @@ The file to keep a list of changed files which will potentionaly help to resolve rebase conflicts. +#### @vitaliy-guliy +https://github.com/che-incubator/che-code/pull/340 + +- code/src/vs/code/browser/workbench/workbench.ts +--- + #### @vitaliy-guliy https://github.com/che-incubator/che-code/pull/339 diff --git a/.rebase/replace/code/src/vs/code/browser/workbench/workbench.ts.json b/.rebase/replace/code/src/vs/code/browser/workbench/workbench.ts.json index 1705cae816f..38172b36b49 100644 --- a/.rebase/replace/code/src/vs/code/browser/workbench/workbench.ts.json +++ b/.rebase/replace/code/src/vs/code/browser/workbench/workbench.ts.json @@ -2,5 +2,13 @@ { "from": "return URI.parse(mainWindow.location.href).with({ path: this._callbackRoute, query: queryParams.join('&') });", "by": "const windowURI = URI.parse(mainWindow.location.href);\\\n\\\t\\\tconst fullPath = windowURI.path.replace(/\\\\/$/, '') + this._callbackRoute;\\\n\\\t\\\treturn windowURI.with({ path: fullPath, query: queryParams.join('\\&') });" + }, + { + "from": "private _serverKey: Uint8Array \\| undefined;", + "by": "private _serverKey: Uint8Array \\| undefined = new TextEncoder().encode('{{LOCAL-STORAGE}}/{{SECURE-KEY}}');" + }, + { + "from": "const secretStorageKeyPath = readCookie('vscode-secret-key-path');", + "by": "const secretStorageKeyPath = readCookie('vscode-secret-key-path') \\|\\| '/';" } ] diff --git a/code/src/vs/code/browser/workbench/workbench.ts b/code/src/vs/code/browser/workbench/workbench.ts index 0014e000162..2c218c04771 100644 --- a/code/src/vs/code/browser/workbench/workbench.ts +++ b/code/src/vs/code/browser/workbench/workbench.ts @@ -45,7 +45,7 @@ const enum AESConstants { } class ServerKeyedAESCrypto implements ISecretStorageCrypto { - private _serverKey: Uint8Array | undefined; + private _serverKey: Uint8Array | undefined = new TextEncoder().encode('{{LOCAL-STORAGE}}/{{SECURE-KEY}}'); /** Gets whether the algorithm is supported; requires a secure context */ public static supported() { @@ -575,20 +575,10 @@ function readCookie(name: string): string | undefined { const cheConfig = getCheConfig(); const config: IWorkbenchConstructionOptions & { folderUri?: UriComponents; workspaceUri?: UriComponents; callbackRoute: string } = JSON.parse(configElementAttribute); - const secretStorageKeyPath = readCookie('vscode-secret-key-path'); + const secretStorageKeyPath = readCookie('vscode-secret-key-path') || '/'; const secretStorageCrypto = secretStorageKeyPath && ServerKeyedAESCrypto.supported() ? new ServerKeyedAESCrypto(secretStorageKeyPath) : new TransparentCrypto(); - console.log('Creating workbench with config ', JSON.stringify({ - ...config, - ...cheConfig, - settingsSyncOptions: config.settingsSyncOptions ? { - enabled: config.settingsSyncOptions.enabled, - } : undefined, - workspaceProvider: WorkspaceProvider.create(config), - urlCallbackProvider: new LocalStorageURLCallbackProvider(config.callbackRoute), - credentialsProvider: config.remoteAuthority ? undefined : new LocalStorageSecretStorageProvider(secretStorageCrypto) // with a remote, we don't use a local secret storage provider - }, undefined, 2)); - + // Create workbench create(mainWindow.document.body, { ...config, diff --git a/launcher/src/files.ts b/launcher/src/files.ts index e21324d3279..a4eaeeda684 100644 --- a/launcher/src/files.ts +++ b/launcher/src/files.ts @@ -10,4 +10,6 @@ export const FILE_WORKBENCH_WEB_MAIN = 'out/vs/workbench/workbench.web.main.js'; +export const FILE_WORKBENCH = 'out/vs/code/browser/workbench/workbench.js'; + export const FILE_EXTENSION_HOST_PROCESS = 'out/vs/workbench/api/node/extensionHostProcess.js'; diff --git a/launcher/src/local-storage-key-provider.ts b/launcher/src/local-storage-key-provider.ts new file mode 100644 index 00000000000..f0533a8c2bf --- /dev/null +++ b/launcher/src/local-storage-key-provider.ts @@ -0,0 +1,81 @@ +/********************************************************************** + * Copyright (c) 2024 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + ***********************************************************************/ + +import { FILE_WORKBENCH } from './files'; +import * as fs from './fs-extra'; + +const SERVER_KEY_MASK = '{{LOCAL-STORAGE}}/{{SECURE-KEY}}'; + +const CERTS_DIR = '/etc/ssh'; + +/** + * Finds a public key in `/etc/ssh` and initializes VS Code with 32 bytes (every fourth character) of the key. + * The key is used to encrypt/decrypt the extension secrets stored in browser local storage. + */ +export class LocalStorageKeyProvider { + async configure(): Promise { + console.log('# Injecting server public key to che-code...'); + + try { + const publicKeyFile = await this.findPublicKeyFile(); + console.log(` > found key file ${publicKeyFile}`); + + const secret = await this.getPartOfPublicKey(publicKeyFile); + await this.update(FILE_WORKBENCH, SERVER_KEY_MASK, secret); + } catch (err) { + console.error(err.message); + } + } + + async findPublicKeyFile(): Promise { + // Check for public certificates in /public-certs + if (await fs.pathExists(CERTS_DIR)) { + const dir = await fs.readdir(CERTS_DIR); + + for (const item of dir) { + const file = `${CERTS_DIR}/${item}`; + + if (await fs.isFile(file)) { + // check for it's public part + const publicKey = file + '.pub'; + if ((await fs.pathExists(publicKey)) && (await fs.isFile(publicKey))) { + return publicKey; + } + } + } + } + + throw new Error(`Public key file is not found in ${CERTS_DIR}`); + } + + async getPartOfPublicKey(file: string): Promise { + let content = await fs.readFile(file); + content = content.substring(content.indexOf(' ') + 1); + + let secret = ''; + for (let i = 0; i < 32; i++) { + secret += content.charAt(i * 4); + } + + return secret; + } + + async update(file: string, text: string, newText: string): Promise { + const content = await fs.readFile(file); + const newContent = content.replace(text, newText); + + if (content === newContent) { + console.log(` > ${file} is not updated`); + } else { + await fs.writeFile(file, newContent); + console.log(` > ${file} has been updated`); + } + } +} diff --git a/launcher/src/main.ts b/launcher/src/main.ts index 90b33d4972b..38f234b270e 100644 --- a/launcher/src/main.ts +++ b/launcher/src/main.ts @@ -12,6 +12,7 @@ import { CodeWorkspace } from './code-workspace'; import { DevWorkspaceId } from './devworkspace-id'; import { NodeExtraCertificate } from './node-extra-certificate'; import { OpenVSIXRegistry } from './openvsix-registry'; +import { LocalStorageKeyProvider } from './local-storage-key-provider'; import { TrustedExtensions } from './trusted-extensions'; import { VSCodeLauncher } from './vscode-launcher'; import { WebviewResources } from './webview-resources'; @@ -28,6 +29,7 @@ export class Main { await new OpenVSIXRegistry().configure(); await new WebviewResources().configure(); await new NodeExtraCertificate().configure(); + await new LocalStorageKeyProvider().configure(); await new TrustedExtensions().configure(); const workspaceFile = await new CodeWorkspace().generate(); diff --git a/launcher/tests/local-storage-key-provider.spec.ts b/launcher/tests/local-storage-key-provider.spec.ts new file mode 100644 index 00000000000..f191f5301a8 --- /dev/null +++ b/launcher/tests/local-storage-key-provider.spec.ts @@ -0,0 +1,73 @@ +/********************************************************************** + * Copyright (c) 2024 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + ***********************************************************************/ + +import * as fs from '../src/fs-extra'; +import { LocalStorageKeyProvider } from '../src/local-storage-key-provider'; + +const ORIGIN_WORKBENCH_FILE = ` +some code, some code, a mask to be replaced {{LOCAL-STORAGE}}/{{SECURE-KEY}}, some code +`; + +const NEW_WORKBENCH_FILE = ` +some code, some code, a mask to be replaced 1234567890ABCDEFGHIJKLMNOPQRSTUV, some code +`; + +describe('Test setting of Local Storage public key to VS Code', () => { + beforeEach(() => { + Object.assign(fs, { + pathExists: jest.fn(), + isFile: jest.fn(), + readdir: jest.fn(), + readFile: jest.fn(), + writeFile: jest.fn(), + }); + }); + + test('should return if env.DEVWORKSPACE_ID is not set', async () => { + const pathExistsMock = jest.fn(); + const readdirMock = jest.fn(); + const isFileMock = jest.fn(); + const readFileMock = jest.fn(); + const writeFileMock = jest.fn(); + Object.assign(fs, { + pathExists: pathExistsMock, + readdir: readdirMock, + isFile: isFileMock, + readFile: readFileMock, + writeFile: writeFileMock, + }); + + pathExistsMock.mockImplementation(async (path: string) => { + return '/etc/ssh' === path || '/etc/ssh/first-key.pub' === path; + }); + + readdirMock.mockImplementation(async (path: string) => { + return ['some-file', 'first-key', 'second-key', 'first-key.pub', 'second-key.pub']; + }); + + isFileMock.mockImplementation(async (path: string) => { + return '/etc/ssh/first-key' === path || '/etc/ssh/first-key.pub' === path; + }); + + readFileMock.mockImplementation(async (file: string) => { + switch (file) { + case '/etc/ssh/first-key.pub': + return 'ssh-rsa 1111222233334444555566667777888899990000AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ'; + case 'out/vs/code/browser/workbench/workbench.js': + return ORIGIN_WORKBENCH_FILE; + } + }); + + const localStorageKeyProvider = new LocalStorageKeyProvider(); + await localStorageKeyProvider.configure(); + + expect(writeFileMock).toBeCalledWith('out/vs/code/browser/workbench/workbench.js', NEW_WORKBENCH_FILE); + }); +}); diff --git a/launcher/tests/main.spec.ts b/launcher/tests/main.spec.ts index 42307c02b99..b9baefe25b0 100644 --- a/launcher/tests/main.spec.ts +++ b/launcher/tests/main.spec.ts @@ -38,6 +38,13 @@ jest.mock('../src/node-extra-certificate', () => ({ }, })); +const configureLocalStorageKeyProvider = jest.fn(); +jest.mock('../src/local-storage-key-provider', () => ({ + LocalStorageKeyProvider: function () { + return { configure: configureLocalStorageKeyProvider }; + }, +})); + const configureTustedExtensions = jest.fn(); jest.mock('../src/trusted-extensions', () => ({ TrustedExtensions: function () { @@ -67,6 +74,7 @@ describe('Test main flow:', () => { expect(configureOpenVSIXRegistryMock).toBeCalled(); expect(configureWebviewResourcesMock).toBeCalled(); expect(configureNodeExtraCertificate).toBeCalled(); + expect(configureLocalStorageKeyProvider).toBeCalled(); expect(configureTustedExtensions).toBeCalled(); expect(generateCodeWorkspace).toBeCalled();