Skip to content

Commit 845f524

Browse files
authored
Merge pull request microsoft#162285 from dtivel/dtivel/verify-packages
Add extension signature verification service
2 parents 6873816 + 310ff3e commit 845f524

22 files changed

+461
-78
lines changed

src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { SharedProcessEnvironmentService } from 'vs/platform/sharedProcess/node/
3232
import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService';
3333
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService';
3434
import { IExtensionGalleryService, IExtensionManagementService, IExtensionTipsService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
35+
import { ExtensionSignatureVerificationService, IExtensionSignatureVerificationService } from 'vs/platform/extensionManagement/node/extensionSignatureVerificationService';
3536
import { ExtensionManagementChannel, ExtensionTipsChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
3637
import { ExtensionTipsService } from 'vs/platform/extensionManagement/electron-sandbox/extensionTipsService';
3738
import { ExtensionManagementService, INativeServerExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
@@ -331,6 +332,7 @@ class SharedProcessMain extends Disposable {
331332
// Extension Management
332333
services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService, undefined, true));
333334
services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService, undefined, true));
335+
services.set(IExtensionSignatureVerificationService, new SyncDescriptor(ExtensionSignatureVerificationService, undefined, true));
334336
services.set(INativeServerExtensionManagementService, new SyncDescriptor(ExtensionManagementService, undefined, true));
335337

336338
// Extension Gallery

src/vs/code/node/cliProcessMain.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { INativeEnvironmentService } from 'vs/platform/environment/common/enviro
2424
import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
2525
import { ExtensionGalleryServiceWithNoStorageService } from 'vs/platform/extensionManagement/common/extensionGalleryService';
2626
import { IExtensionGalleryService, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
27+
import { ExtensionSignatureVerificationService, IExtensionSignatureVerificationService } from 'vs/platform/extensionManagement/node/extensionSignatureVerificationService';
2728
import { ExtensionManagementCLI } from 'vs/platform/extensionManagement/common/extensionManagementCLI';
2829
import { ExtensionsProfileScannerService, IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
2930
import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService';
@@ -181,6 +182,7 @@ class CliMain extends Disposable {
181182
// Extensions
182183
services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService, undefined, true));
183184
services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService, undefined, true));
185+
services.set(IExtensionSignatureVerificationService, new SyncDescriptor(ExtensionSignatureVerificationService, undefined, true));
184186
services.set(INativeServerExtensionManagementService, new SyncDescriptor(ExtensionManagementService, undefined, true));
185187
services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryServiceWithNoStorageService, undefined, true));
186188

src/vs/platform/environment/common/environment.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ export interface INativeEnvironmentService extends IEnvironmentService {
142142

143143
// --- extensions
144144
extensionsPath: string;
145-
extensionsDownloadPath: string;
145+
extensionsDownloadLocation: URI;
146146
builtinExtensionsPath: string;
147147

148148
// --- use keytar for credentials

src/vs/platform/environment/common/environmentService.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,13 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron
129129
return normalize(join(FileAccess.asFileUri('', require).fsPath, '..', 'extensions'));
130130
}
131131

132-
get extensionsDownloadPath(): string {
132+
get extensionsDownloadLocation(): URI {
133133
const cliExtensionsDownloadDir = this.args['extensions-download-dir'];
134134
if (cliExtensionsDownloadDir) {
135-
return resolve(cliExtensionsDownloadDir);
135+
return URI.file(resolve(cliExtensionsDownloadDir));
136136
}
137137

138-
return join(this.userDataPath, 'CachedExtensionVSIXs');
138+
return URI.file(join(this.userDataPath, 'CachedExtensionVSIXs'));
139139
}
140140

141141
@memoize

src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export interface IInstallExtensionTask {
2828
readonly identifier: IExtensionIdentifier;
2929
readonly source: IGalleryExtension | URI;
3030
readonly operation: InstallOperation;
31+
readonly wasVerified?: boolean;
3132
run(): Promise<{ local: ILocalExtension; metadata: Metadata }>;
3233
waitUntilTaskIsFinished(): Promise<{ local: ILocalExtension; metadata: Metadata }>;
3334
cancel(): void;
@@ -243,6 +244,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
243244
const durationSinceUpdate = isUpdate ? undefined : (new Date().getTime() - task.source.lastUpdated) / 1000;
244245
reportTelemetry(this.telemetryService, isUpdate ? 'extensionGallery:update' : 'extensionGallery:install', {
245246
extensionData: getGalleryExtensionTelemetryData(task.source),
247+
wasVerified: task.wasVerified,
246248
duration: new Date().getTime() - startTime,
247249
durationSinceUpdate
248250
});
@@ -256,7 +258,12 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
256258
installResults.push({ local, identifier: task.identifier, operation: task.operation, source: task.source, context: options.context, profileLocation: options.profileLocation, applicationScoped: local.isApplicationScoped });
257259
} catch (error) {
258260
if (!URI.isUri(task.source)) {
259-
reportTelemetry(this.telemetryService, task.operation === InstallOperation.Update ? 'extensionGallery:update' : 'extensionGallery:install', { extensionData: getGalleryExtensionTelemetryData(task.source), duration: new Date().getTime() - startTime, error });
261+
reportTelemetry(this.telemetryService, task.operation === InstallOperation.Update ? 'extensionGallery:update' : 'extensionGallery:install', {
262+
extensionData: getGalleryExtensionTelemetryData(task.source),
263+
wasVerified: task.wasVerified,
264+
duration: new Date().getTime() - startTime,
265+
error
266+
});
260267
}
261268
this.logService.error('Error while installing the extension:', task.identifier.id);
262269
throw error;
@@ -665,6 +672,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
665672
abstract getManifest(vsix: URI): Promise<IExtensionManifest>;
666673
abstract install(vsix: URI, options?: InstallVSIXOptions): Promise<ILocalExtension>;
667674
abstract getInstalled(type?: ExtensionType, profileLocation?: URI): Promise<ILocalExtension[]>;
675+
abstract download(extension: IGalleryExtension, operation: InstallOperation): Promise<URI>;
668676

669677
abstract getMetadata(extension: ILocalExtension): Promise<Metadata | undefined>;
670678
abstract updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise<ILocalExtension>;
@@ -693,16 +701,32 @@ function toExtensionManagementError(error: Error): ExtensionManagementError {
693701
return e;
694702
}
695703

696-
export function reportTelemetry(telemetryService: ITelemetryService, eventName: string, { extensionData, duration, error, durationSinceUpdate }: { extensionData: any; duration?: number; durationSinceUpdate?: number; error?: Error }): void {
697-
const errorcode = error ? error instanceof ExtensionManagementError ? error.code : ExtensionManagementErrorCode.Internal : undefined;
704+
export function reportTelemetry(telemetryService: ITelemetryService, eventName: string, { extensionData, wasVerified, duration, error, durationSinceUpdate }: { extensionData: any; wasVerified?: boolean; duration?: number; durationSinceUpdate?: number; error?: Error }): void {
705+
let errorcode: ExtensionManagementErrorCode | undefined;
706+
let errorcodeDetail: string | undefined;
707+
708+
if (error) {
709+
if (error instanceof ExtensionManagementError) {
710+
errorcode = error.code;
711+
712+
if (error.code === ExtensionManagementErrorCode.Signature) {
713+
errorcodeDetail = error.message;
714+
}
715+
} else {
716+
errorcode = ExtensionManagementErrorCode.Internal;
717+
}
718+
}
719+
698720
/* __GDPR__
699721
"extensionGallery:install" : {
700722
"owner": "sandy081",
701723
"success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
702724
"duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
703725
"durationSinceUpdate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
704726
"errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
727+
"errorcodeDetail": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
705728
"recommendationReason": { "retiredFromVersion": "1.23.0", "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
729+
"wasVerified" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
706730
"${include}": [
707731
"${GalleryExtensionTelemetryData}"
708732
]
@@ -725,12 +749,14 @@ export function reportTelemetry(telemetryService: ITelemetryService, eventName:
725749
"success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
726750
"duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
727751
"errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
752+
"errorcodeDetail": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
753+
"wasVerified" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
728754
"${include}": [
729755
"${GalleryExtensionTelemetryData}"
730756
]
731757
}
732758
*/
733-
telemetryService.publicLog(eventName, { ...extensionData, success: !error, duration, errorcode, durationSinceUpdate });
759+
telemetryService.publicLog(eventName, { ...extensionData, wasVerified, success: !error, duration, errorcode, errorcodeDetail, durationSinceUpdate });
734760
}
735761

736762
export abstract class AbstractExtensionTask<T> {

src/vs/platform/extensionManagement/common/extensionGalleryService.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,8 @@ const AssetType = {
193193
Manifest: 'Microsoft.VisualStudio.Code.Manifest',
194194
VSIX: 'Microsoft.VisualStudio.Services.VSIXPackage',
195195
License: 'Microsoft.VisualStudio.Services.Content.License',
196-
Repository: 'Microsoft.VisualStudio.Services.Links.Source'
196+
Repository: 'Microsoft.VisualStudio.Services.Links.Source',
197+
Signature: 'Microsoft.VisualStudio.Services.VsixSignature'
197198
};
198199

199200
const PropertyType = {
@@ -505,6 +506,7 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller
505506
repository: getRepositoryAsset(version),
506507
download: getDownloadAsset(version),
507508
icon: getVersionAsset(version, AssetType.Icon),
509+
signature: getVersionAsset(version, AssetType.Signature),
508510
coreTranslations: getCoreTranslationAssets(version)
509511
};
510512

@@ -542,6 +544,7 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller
542544
hasPreReleaseVersion: isPreReleaseVersion(latestVersion),
543545
hasReleaseVersion: true,
544546
preview: getIsPreview(galleryExtension.flags),
547+
isSigned: !!assets.signature
545548
};
546549
}
547550

@@ -1027,6 +1030,17 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi
10271030
log(new Date().getTime() - startTime);
10281031
}
10291032

1033+
async downloadSignatureArchive(extension: IGalleryExtension, location: URI): Promise<void> {
1034+
if (!extension.assets.signature) {
1035+
throw new Error('No signature asset found');
1036+
}
1037+
1038+
this.logService.trace('ExtensionGalleryService#downloadSignatureArchive', extension.identifier.id);
1039+
1040+
const context = await this.getAsset(extension.assets.signature);
1041+
await this.fileService.writeFile(location, context.stream);
1042+
}
1043+
10301044
async getReadme(extension: IGalleryExtension, token: CancellationToken): Promise<string> {
10311045
if (extension.assets.readme) {
10321046
const context = await this.getAsset(extension.assets.readme, {}, token);

src/vs/platform/extensionManagement/common/extensionManagement.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ export interface IGalleryExtensionAssets {
178178
repository: IGalleryExtensionAsset | null;
179179
download: IGalleryExtensionAsset;
180180
icon: IGalleryExtensionAsset | null;
181+
signature: IGalleryExtensionAsset | null;
181182
coreTranslations: [string, IGalleryExtensionAsset][];
182183
}
183184

@@ -224,6 +225,7 @@ export interface IGalleryExtension {
224225
preview: boolean;
225226
hasPreReleaseVersion: boolean;
226227
hasReleaseVersion: boolean;
228+
isSigned: boolean;
227229
allTargetPlatforms: TargetPlatform[];
228230
assets: IGalleryExtensionAssets;
229231
properties: IGalleryExtensionProperties;
@@ -335,6 +337,7 @@ export interface IExtensionGalleryService {
335337
getCompatibleExtension(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise<IGalleryExtension | null>;
336338
getAllCompatibleVersions(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise<IGalleryExtensionVersion[]>;
337339
download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise<void>;
340+
downloadSignatureArchive(extension: IGalleryExtension, location: URI): Promise<void>;
338341
reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise<void>;
339342
getReadme(extension: IGalleryExtension, token: CancellationToken): Promise<string>;
340343
getManifest(extension: IGalleryExtension, token: CancellationToken): Promise<IExtensionManifest | null>;
@@ -390,6 +393,7 @@ export enum ExtensionManagementErrorCode {
390393
CorruptZip = 'CorruptZip',
391394
IncompleteZip = 'IncompleteZip',
392395
Internal = 'Internal',
396+
Signature = 'Signature'
393397
}
394398

395399
export class ExtensionManagementError extends Error {
@@ -440,6 +444,7 @@ export interface IExtensionManagementService {
440444
getInstalled(type?: ExtensionType, profileLocation?: URI): Promise<ILocalExtension[]>;
441445
getExtensionsControlManifest(): Promise<IExtensionsControlManifest>;
442446

447+
download(extension: IGalleryExtension, operation: InstallOperation): Promise<URI>;
443448
getMetadata(extension: ILocalExtension): Promise<Metadata | undefined>;
444449
updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise<ILocalExtension>;
445450
updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise<ILocalExtension>;

src/vs/platform/extensionManagement/common/extensionManagementIpc.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { cloneAndChange } from 'vs/base/common/objects';
1010
import { URI, UriComponents } from 'vs/base/common/uri';
1111
import { DefaultURITransformer, IURITransformer, transformAndReviveIncomingURIs } from 'vs/base/common/uriIpc';
1212
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
13-
import { IExtensionIdentifier, IExtensionTipsService, IGalleryExtension, IGalleryMetadata, ILocalExtension, IExtensionsControlManifest, isTargetPlatformCompatible, InstallOptions, InstallVSIXOptions, UninstallOptions, Metadata, IExtensionManagementService, DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement';
13+
import { IExtensionIdentifier, IExtensionTipsService, IGalleryExtension, IGalleryMetadata, ILocalExtension, IExtensionsControlManifest, isTargetPlatformCompatible, InstallOptions, InstallVSIXOptions, UninstallOptions, Metadata, IExtensionManagementService, DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
1414
import { ExtensionType, IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions';
1515

1616
function transformIncomingURI(uri: UriComponents, transformer: IURITransformer | null): URI {
@@ -75,6 +75,7 @@ export class ExtensionManagementChannel implements IServerChannel {
7575
case 'updateMetadata': return this.service.updateMetadata(transformIncomingExtension(args[0], uriTransformer), args[1]).then(e => transformOutgoingExtension(e, uriTransformer));
7676
case 'updateExtensionScope': return this.service.updateExtensionScope(transformIncomingExtension(args[0], uriTransformer), args[1]).then(e => transformOutgoingExtension(e, uriTransformer));
7777
case 'getExtensionsControlManifest': return this.service.getExtensionsControlManifest();
78+
case 'download': return this.service.download(args[0], args[1]);
7879
}
7980

8081
throw new Error('Invalid call');
@@ -177,6 +178,11 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt
177178
return Promise.resolve(this.channel.call<IExtensionsControlManifest>('getExtensionsControlManifest'));
178179
}
179180

181+
async download(extension: IGalleryExtension, operation: InstallOperation): Promise<URI> {
182+
const result = await this.channel.call<UriComponents>('download', [extension, operation]);
183+
return URI.revive(result);
184+
}
185+
180186
registerParticipant() { throw new Error('Not Supported'); }
181187
}
182188

src/vs/platform/extensionManagement/common/extensionManagementUtil.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ export function getLocalExtensionTelemetryData(extension: ILocalExtension): any
125125
"publisherDisplayName": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
126126
"isPreReleaseVersion": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
127127
"dependencies": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
128+
"isSigned": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
128129
"${include}": [
129130
"${GalleryExtensionTelemetryData2}"
130131
]
@@ -140,6 +141,7 @@ export function getGalleryExtensionTelemetryData(extension: IGalleryExtension):
140141
publisherDisplayName: extension.publisherDisplayName,
141142
isPreReleaseVersion: extension.properties.isPreReleaseVersion,
142143
dependencies: !!(extension.properties.dependencies && extension.properties.dependencies.length > 0),
144+
isSigned: extension.isSigned,
143145
...extension.telemetryData
144146
};
145147
}

0 commit comments

Comments
 (0)