diff --git a/src/vs/platform/integrity/common/integrity.ts b/src/vs/platform/integrity/common/integrity.ts new file mode 100644 index 00000000000..046b836bc4c --- /dev/null +++ b/src/vs/platform/integrity/common/integrity.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import {TPromise} from 'vs/base/common/winjs.base'; +import {createDecorator} from 'vs/platform/instantiation/common/instantiation'; +import URI from 'vs/base/common/uri'; + +export const IIntegrityService = createDecorator('integrityService'); + +export interface ChecksumPair { + uri: URI; + actual: string; + expected: string; + isPure: boolean; +} + +export interface IntegrityTestResult { + isPure: boolean; + proof: ChecksumPair[]; +} + +export interface IIntegrityService { + _serviceBrand: any; + + isPure(): TPromise; +} diff --git a/src/vs/platform/integrity/node/integrityServiceImpl.ts b/src/vs/platform/integrity/node/integrityServiceImpl.ts new file mode 100644 index 00000000000..66b844c66e3 --- /dev/null +++ b/src/vs/platform/integrity/node/integrityServiceImpl.ts @@ -0,0 +1,101 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import {TPromise} from 'vs/base/common/winjs.base'; +import {IIntegrityService, IntegrityTestResult, ChecksumPair} from 'vs/platform/integrity/common/integrity'; +import product from 'vs/platform/product'; +import URI from 'vs/base/common/uri'; +import * as fs from 'fs'; +const crypto = require('crypto'); + +interface ILoaderChecksums { + [scriptSrc:string]: string; +} + +export class IntegrityServiceImpl implements IIntegrityService { + + public _serviceBrand: any; + private _loaderChecksums: ILoaderChecksums; + + constructor() { + // Fetch checksums from loader + let loaderChecksums = (require).getChecksums(); + + // Transform loader checksums to be uri => checksum + this._loaderChecksums = Object.create(null); + Object.keys(loaderChecksums).forEach((scriptSrc) => { + let scriptUri = URI.file(scriptSrc).toString(); + this._loaderChecksums[scriptUri.toString()] = loaderChecksums[scriptSrc]; + }); + } + + public isPure(): TPromise { + const expectedChecksums = product.checksums || {}; + let syncResults: ChecksumPair[] = []; + let asyncResults: TPromise[] = []; + Object.keys(expectedChecksums).forEach((filename) => { + let r = this._resolve(filename, expectedChecksums[filename]); + if (TPromise.is(r)) { + asyncResults.push(r); + } else { + syncResults.push(r); + } + }); + + return TPromise.join(asyncResults).then((asyncResults) => { + let allResults = syncResults.concat(asyncResults); + let isPure = true; + for (let i = 0, len = allResults.length; isPure && i < len; i++) { + if (!allResults[i].isPure) { + isPure = false; + } + } + + return { + isPure: isPure, + proof: allResults + }; + }); + } + + private _resolve(filename:string, expected:string): ChecksumPair | TPromise { + let fileUri = URI.parse(require.toUrl(filename)); + let loaderChecksum = this._loaderChecksums[fileUri.toString()]; + if (loaderChecksum) { + return IntegrityServiceImpl._createChecksumPair(fileUri, loaderChecksum, expected); + } + if (/\.js$/.test(filename)) { + console.warn(`Did not find checksum for ${filename} in loader checksums.`); + } + return new TPromise((c, e, p) => { + fs.readFile(fileUri.fsPath, (err, buff) => { + if (err) { + return e(err); + } + c(IntegrityServiceImpl._createChecksumPair(fileUri, this._computeChecksum(buff), expected)); + }); + }); + } + + private _computeChecksum(buff:Buffer): string { + let hash = crypto + .createHash('md5') + .update(buff) + .digest('base64') + .replace(/=+$/, ''); + + return hash; + } + + private static _createChecksumPair(uri:URI, actual:string, expected:string): ChecksumPair { + return { + uri: uri, + actual: actual, + expected: expected, + isPure: (actual === expected) + }; + } +} diff --git a/src/vs/platform/product.ts b/src/vs/platform/product.ts index 720faaaad87..180b58c8f21 100644 --- a/src/vs/platform/product.ts +++ b/src/vs/platform/product.ts @@ -45,6 +45,7 @@ export interface IProductConfiguration { licenseUrl: string; privacyStatementUrl: string; npsSurveyUrl: string; + checksums: {[path:string]:string;}; } const rootPath = path.dirname(uri.parse(require.toUrl('')).fsPath); diff --git a/src/vs/workbench/electron-browser/bootstrap/index.js b/src/vs/workbench/electron-browser/bootstrap/index.js index 1fa80755ba4..b91dcdac11e 100644 --- a/src/vs/workbench/electron-browser/bootstrap/index.js +++ b/src/vs/workbench/electron-browser/bootstrap/index.js @@ -141,7 +141,8 @@ function main() { recordStats: !!configuration.performance, ignoreDuplicateModules: [ 'vs/workbench/parts/search/common/searchQuery' - ] + ], + checksum: true }); if (nlsConfig.pseudo) { diff --git a/src/vs/workbench/electron-browser/shell.ts b/src/vs/workbench/electron-browser/shell.ts index 5aa5f5dad68..125819187f5 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -46,6 +46,8 @@ import {ICompatWorkerService} from 'vs/editor/common/services/compatWorkerServic import {MainThreadCompatWorkerService} from 'vs/editor/common/services/compatWorkerServiceMain'; import {CodeEditorServiceImpl} from 'vs/editor/browser/services/codeEditorServiceImpl'; import {ICodeEditorService} from 'vs/editor/common/services/codeEditorService'; +import {IntegrityServiceImpl} from 'vs/platform/integrity/node/integrityServiceImpl'; +import {IIntegrityService} from 'vs/platform/integrity/common/integrity'; import {EditorWorkerServiceImpl} from 'vs/editor/common/services/editorWorkerServiceImpl'; import {IEditorWorkerService} from 'vs/editor/common/services/editorWorkerService'; import {MainProcessExtensionService} from 'vs/workbench/api/node/mainThreadExtensionService'; @@ -327,6 +329,9 @@ export class WorkbenchShell { const codeEditorService = instantiationService.createInstance(CodeEditorServiceImpl); serviceCollection.set(ICodeEditorService, codeEditorService); + const integrityService = instantiationService.createInstance(IntegrityServiceImpl); + serviceCollection.set(IIntegrityService, integrityService); + const extensionManagementChannel = getDelayedChannel(sharedProcess.then(c => c.getChannel('extensions'))); const extensionManagementChannelClient = new ExtensionManagementChannelClient(extensionManagementChannel); serviceCollection.set(IExtensionManagementService, extensionManagementChannelClient);