mirror of
https://github.com/Microsoft/vscode
synced 2024-10-05 19:02:54 +00:00
implement sign service for web (#182815)
* signing: implement signing service on the web * update distro
This commit is contained in:
parent
751fdcb904
commit
0c94abc4a5
|
@ -49,6 +49,5 @@ xterm-addon-webgl/out/**
|
|||
!@microsoft/applicationinsights-core-js/browser/applicationinsights-core-js.min.js
|
||||
!@microsoft/applicationinsights-shims/dist/umd/applicationinsights-shims.min.js
|
||||
|
||||
|
||||
|
||||
|
||||
vsda/**
|
||||
!vsda/rust/web/**
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -394,6 +394,13 @@ export function acquireWebNodePaths() {
|
|||
const root = path.join(__dirname, '..', '..');
|
||||
const webPackageJSON = path.join(root, '/remote/web', 'package.json');
|
||||
const webPackages = JSON.parse(fs.readFileSync(webPackageJSON, 'utf8')).dependencies;
|
||||
|
||||
const distroWebPackageJson = path.join(root, '.build/distro/npm/remote/web/package.json');
|
||||
if (fs.existsSync(distroWebPackageJson)) {
|
||||
const distroWebPackages = JSON.parse(fs.readFileSync(distroWebPackageJson, 'utf8')).dependencies;
|
||||
Object.assign(webPackages, distroWebPackages);
|
||||
}
|
||||
|
||||
const nodePaths: { [key: string]: string } = {};
|
||||
for (const key of Object.keys(webPackages)) {
|
||||
const packageJSON = path.join(root, 'node_modules', key, 'package.json');
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "code-oss-dev",
|
||||
"version": "1.79.0",
|
||||
"distro": "4e88da3231fbd0b33666c0d1b550b1ecdb739449",
|
||||
"distro": "10ec4d08d4a06a1c18addcded03e90c8a0e6ecad",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
},
|
||||
|
|
1
src/bootstrap-window.js
vendored
1
src/bootstrap-window.js
vendored
|
@ -114,6 +114,7 @@
|
|||
loaderConfig.paths = {
|
||||
'vscode-textmate': `${baseNodeModulesPath}/vscode-textmate/release/main.js`,
|
||||
'vscode-oniguruma': `${baseNodeModulesPath}/vscode-oniguruma/release/main.js`,
|
||||
'vsda': `${baseNodeModulesPath}/vsda/index.js`,
|
||||
'xterm': `${baseNodeModulesPath}/xterm/lib/xterm.js`,
|
||||
'xterm-addon-canvas': `${baseNodeModulesPath}/xterm-addon-canvas/lib/xterm-addon-canvas.js`,
|
||||
'xterm-addon-image': `${baseNodeModulesPath}/xterm-addon-image/lib/xterm-addon-image.js`,
|
||||
|
|
|
@ -3,24 +3,94 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IMessage, ISignService } from 'vs/platform/sign/common/sign';
|
||||
import { IntervalTimer } from 'vs/base/common/async';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { FileAccess } from 'vs/base/common/network';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { AbstractSignService, IVsdaValidator } from 'vs/platform/sign/common/abstractSignService';
|
||||
import { ISignService } from 'vs/platform/sign/common/sign';
|
||||
|
||||
export class SignService implements ISignService {
|
||||
declare module vsdaWeb {
|
||||
export function sign(salted_message: string): string;
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
constructor(
|
||||
private readonly _token: Promise<string> | string | undefined
|
||||
) { }
|
||||
|
||||
async createNewMessage(value: string): Promise<IMessage> {
|
||||
return { id: '', data: value };
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export class validator {
|
||||
free(): void;
|
||||
constructor();
|
||||
createNewMessage(original: string): string;
|
||||
validate(signed_message: string): 'ok' | 'error';
|
||||
}
|
||||
async validate(message: IMessage, value: string): Promise<boolean> {
|
||||
return true;
|
||||
|
||||
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
||||
export function init(module_or_path?: InitInput | Promise<InitInput>): Promise<unknown>;
|
||||
}
|
||||
|
||||
// Initialized if/when vsda is loaded
|
||||
declare const vsda_web: {
|
||||
default: typeof vsdaWeb.init;
|
||||
sign: typeof vsdaWeb.sign;
|
||||
validator: typeof vsdaWeb.validator;
|
||||
};
|
||||
|
||||
const KEY_SIZE = 32;
|
||||
const IV_SIZE = 16;
|
||||
const STEP_SIZE = KEY_SIZE + IV_SIZE;
|
||||
|
||||
export class SignService extends AbstractSignService implements ISignService {
|
||||
constructor(@IProductService private readonly productService: IProductService) {
|
||||
super();
|
||||
}
|
||||
async sign(value: string): Promise<string> {
|
||||
const token = await Promise.resolve(this._token);
|
||||
return token || '';
|
||||
protected override getValidator(): Promise<IVsdaValidator> {
|
||||
return this.vsda().then(vsda => {
|
||||
const v = new vsda.validator();
|
||||
return {
|
||||
createNewMessage: arg => v.createNewMessage(arg),
|
||||
validate: arg => v.validate(arg),
|
||||
dispose: () => v.free(),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
protected override signValue(arg: string): Promise<string> {
|
||||
return this.vsda().then(vsda => vsda.sign(arg));
|
||||
}
|
||||
|
||||
@memoize
|
||||
private async vsda(): Promise<typeof vsda_web> {
|
||||
const checkInterval = new IntervalTimer();
|
||||
let [wasm] = await Promise.all([
|
||||
this.getWasmBytes(),
|
||||
new Promise<void>((resolve, reject) => {
|
||||
require(['vsda'], resolve, reject);
|
||||
|
||||
// todo@connor4312: there seems to be a bug(?) in vscode-loader with
|
||||
// require() not resolving in web once the script loads, so check manually
|
||||
checkInterval.cancelAndSet(() => {
|
||||
if (typeof vsda_web !== 'undefined') {
|
||||
resolve();
|
||||
}
|
||||
}, 50);
|
||||
}).finally(() => checkInterval!.dispose()),
|
||||
]);
|
||||
|
||||
|
||||
const keyBytes = new TextEncoder().encode(this.productService.serverLicense?.join('\n') || '');
|
||||
for (let i = 0; i + STEP_SIZE < keyBytes.length; i += STEP_SIZE) {
|
||||
const key = await crypto.subtle.importKey('raw', keyBytes.slice(i + IV_SIZE, i + IV_SIZE + KEY_SIZE), { name: 'AES-CBC' }, false, ['decrypt']);
|
||||
wasm = await crypto.subtle.decrypt({ name: 'AES-CBC', iv: keyBytes.slice(i, i + IV_SIZE) }, key, wasm);
|
||||
}
|
||||
|
||||
await vsda_web.default(wasm);
|
||||
|
||||
return vsda_web;
|
||||
}
|
||||
|
||||
private async getWasmBytes(): Promise<ArrayBuffer> {
|
||||
const response = await fetch(FileAccess.asBrowserUri('vsda/../vsda_bg.wasm').toString(true));
|
||||
if (!response.ok) {
|
||||
throw new Error('error loading vsda');
|
||||
}
|
||||
|
||||
return response.arrayBuffer();
|
||||
}
|
||||
}
|
||||
|
|
72
src/vs/platform/sign/common/abstractSignService.ts
Normal file
72
src/vs/platform/sign/common/abstractSignService.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IMessage, ISignService } from 'vs/platform/sign/common/sign';
|
||||
|
||||
export interface IVsdaSigner {
|
||||
sign(arg: string): string;
|
||||
}
|
||||
|
||||
export interface IVsdaValidator {
|
||||
createNewMessage(arg: string): string;
|
||||
validate(arg: string): 'ok' | 'error';
|
||||
dispose?(): void;
|
||||
}
|
||||
|
||||
export abstract class AbstractSignService implements ISignService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private static _nextId = 1;
|
||||
private readonly validators = new Map<string, IVsdaValidator>();
|
||||
|
||||
protected abstract getValidator(): Promise<IVsdaValidator>;
|
||||
protected abstract signValue(arg: string): Promise<string>;
|
||||
|
||||
public async createNewMessage(value: string): Promise<IMessage> {
|
||||
try {
|
||||
const validator = await this.getValidator();
|
||||
if (validator) {
|
||||
const id = String(AbstractSignService._nextId++);
|
||||
this.validators.set(id, validator);
|
||||
return {
|
||||
id: id,
|
||||
data: validator.createNewMessage(value)
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore errors silently
|
||||
}
|
||||
return { id: '', data: value };
|
||||
}
|
||||
|
||||
async validate(message: IMessage, value: string): Promise<boolean> {
|
||||
if (!message.id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const validator = this.validators.get(message.id);
|
||||
if (!validator) {
|
||||
return false;
|
||||
}
|
||||
this.validators.delete(message.id);
|
||||
try {
|
||||
return (validator.validate(value) === 'ok');
|
||||
} catch (e) {
|
||||
// ignore errors silently
|
||||
return false;
|
||||
} finally {
|
||||
validator.dispose?.();
|
||||
}
|
||||
}
|
||||
|
||||
async sign(value: string): Promise<string> {
|
||||
try {
|
||||
return await this.signValue(value);
|
||||
} catch (e) {
|
||||
// ignore errors silently
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -3,7 +3,8 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IMessage, ISignService } from 'vs/platform/sign/common/sign';
|
||||
import { AbstractSignService, IVsdaValidator } from 'vs/platform/sign/common/abstractSignService';
|
||||
import { ISignService } from 'vs/platform/sign/common/sign';
|
||||
|
||||
declare module vsda {
|
||||
// the signer is a native module that for historical reasons uses a lower case class name
|
||||
|
@ -19,62 +20,15 @@ declare module vsda {
|
|||
}
|
||||
}
|
||||
|
||||
export class SignService implements ISignService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private static _nextId = 1;
|
||||
private readonly validators = new Map<string, vsda.validator>();
|
||||
export class SignService extends AbstractSignService implements ISignService {
|
||||
protected override getValidator(): Promise<IVsdaValidator> {
|
||||
return this.vsda().then(vsda => new vsda.validator());
|
||||
}
|
||||
protected override signValue(arg: string): Promise<string> {
|
||||
return this.vsda().then(vsda => new vsda.signer().sign(arg));
|
||||
}
|
||||
|
||||
private vsda(): Promise<typeof vsda> {
|
||||
return new Promise((resolve, reject) => require(['vsda'], resolve, reject));
|
||||
}
|
||||
|
||||
async createNewMessage(value: string): Promise<IMessage> {
|
||||
try {
|
||||
const vsda = await this.vsda();
|
||||
const validator = new vsda.validator();
|
||||
if (validator) {
|
||||
const id = String(SignService._nextId++);
|
||||
this.validators.set(id, validator);
|
||||
return {
|
||||
id: id,
|
||||
data: validator.createNewMessage(value)
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore errors silently
|
||||
}
|
||||
return { id: '', data: value };
|
||||
}
|
||||
|
||||
async validate(message: IMessage, value: string): Promise<boolean> {
|
||||
if (!message.id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const validator = this.validators.get(message.id);
|
||||
if (!validator) {
|
||||
return false;
|
||||
}
|
||||
this.validators.delete(message.id);
|
||||
try {
|
||||
return (validator.validate(value) === 'ok');
|
||||
} catch (e) {
|
||||
// ignore errors silently
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async sign(value: string): Promise<string> {
|
||||
try {
|
||||
const vsda = await this.vsda();
|
||||
const signer = new vsda.signer();
|
||||
if (signer) {
|
||||
return signer.sign(value);
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore errors silently
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -282,7 +282,7 @@ export class BrowserMain extends Disposable {
|
|||
serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService);
|
||||
|
||||
// Signing
|
||||
const signService = new SignService(connectionToken);
|
||||
const signService = new SignService(productService);
|
||||
serviceCollection.set(ISignService, signService);
|
||||
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ suite('WorkspaceContextService - Folder', () => {
|
|||
const uriIdentityService = new UriIdentityService(fileService);
|
||||
const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService);
|
||||
const userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService);
|
||||
testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, new RemoteAgentService(new RemoteSocketFactoryService(), userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(false, undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService()));
|
||||
testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, new RemoteAgentService(new RemoteSocketFactoryService(), userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(false, undefined, undefined, TestProductService, logService), new SignService(TestProductService), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService()));
|
||||
await (<WorkspaceService>testObject).initialize(convertToWorkspacePayload(folder));
|
||||
});
|
||||
|
||||
|
@ -133,7 +133,7 @@ suite('WorkspaceContextService - Folder', () => {
|
|||
const uriIdentityService = new UriIdentityService(fileService);
|
||||
const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService);
|
||||
const userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService);
|
||||
const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, new RemoteAgentService(new RemoteSocketFactoryService(), userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(false, undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService()));
|
||||
const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, new RemoteAgentService(new RemoteSocketFactoryService(), userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(false, undefined, undefined, TestProductService, logService), new SignService(TestProductService), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService()));
|
||||
await (<WorkspaceService>testObject).initialize(convertToWorkspacePayload(folder));
|
||||
|
||||
const actual = testObject.getWorkspaceFolder(joinPath(folder, 'a'));
|
||||
|
@ -156,7 +156,7 @@ suite('WorkspaceContextService - Folder', () => {
|
|||
const uriIdentityService = new UriIdentityService(fileService);
|
||||
const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService);
|
||||
const userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService);
|
||||
const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, new RemoteAgentService(new RemoteSocketFactoryService(), userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(false, undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService()));
|
||||
const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, new RemoteAgentService(new RemoteSocketFactoryService(), userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(false, undefined, undefined, TestProductService, logService), new SignService(TestProductService), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService()));
|
||||
await (<WorkspaceService>testObject).initialize(convertToWorkspacePayload(folder));
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue