Repository: microsoft/vscode (TypeScript)
Date: 2023-03-21 10:56:33+00:00
Task type: Bug Fix

Issue:
Continue installing the extension when signature verification fails
Continue installing the extension when signature verification fails - 
Report the error in telemetry

CC @isidorn @dtivel 




Issue discussion:
To verify:

- Install `tyriar.luna-paint` extension and make sure the extension is installed successfully.
- Also make sure you see following in the Shared log
```
2023-03-21 11:40:33.358 [info] Getting Manifest... tyriar.luna-paint
2023-03-21 11:40:33.369 [info] Installing extension: tyriar.luna-paint
2023-03-21 11:40:34.054 [warning] Extension signature verification failed with error 'PackageIntegrityCheckFailed': tyriar.luna-paint
2023-03-21 11:40:34.263 [info] Extracted extension to /Users/sandy081/.vscode-oss-dev/extensions/.79f5051b-3123-4206-bb8c-e8ccaf10c7d0: tyriar.luna-paint
2023-03-21 11:40:34.356 [info] Renamed to /Users/sandy081/.vscode-oss-dev/extensions/tyriar.luna-paint-0.16.0
2023-03-21 11:40:34.357 [info] Extracting extension completed. tyriar.luna-paint
2023-03-21 11:40:34.366 [info] Extension installed successfully: tyriar.luna-paint
```

Solution patch:
diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts
--- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts
+++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts
@@ -10,6 +10,7 @@ import { CancellationError, getErrorMessage } from 'vs/base/common/errors';
 import { Emitter, Event } from 'vs/base/common/event';
 import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
 import { isWeb } from 'vs/base/common/platform';
+import { isDefined } from 'vs/base/common/types';
 import { URI } from 'vs/base/common/uri';
 import * as nls from 'vs/nls';
 import {
@@ -24,11 +25,7 @@ import { IProductService } from 'vs/platform/product/common/productService';
 import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
 import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
 
-export const enum ExtensionVerificationStatus {
-	'Verified' = 'Verified',
-	'Unverified' = 'Unverified',
-	'UnknownError' = 'UnknownError',
-}
+export type ExtensionVerificationStatus = boolean | string;
 
 export type InstallExtensionTaskOptions = InstallOptions & InstallVSIXOptions & { readonly profileLocation: URI };
 export interface IInstallExtensionTask {
@@ -694,6 +691,18 @@ function reportTelemetry(telemetryService: ITelemetryService, eventName: string,
 	let errorcode: ExtensionManagementErrorCode | undefined;
 	let errorcodeDetail: string | undefined;
 
+	if (isDefined(verificationStatus)) {
+		if (verificationStatus === true) {
+			verificationStatus = 'Verified';
+		} else if (verificationStatus === false) {
+			verificationStatus = 'Unverified';
+		} else {
+			errorcode = ExtensionManagementErrorCode.Signature;
+			errorcodeDetail = verificationStatus;
+			verificationStatus = 'Unverified';
+		}
+	}
+
 	if (error) {
 		if (error instanceof ExtensionManagementError) {
 			errorcode = error.code;
diff --git a/src/vs/platform/extensionManagement/node/extensionDownloader.ts b/src/vs/platform/extensionManagement/node/extensionDownloader.ts
--- a/src/vs/platform/extensionManagement/node/extensionDownloader.ts
+++ b/src/vs/platform/extensionManagement/node/extensionDownloader.ts
@@ -56,34 +56,20 @@ export class ExtensionsDownloader extends Disposable {
 			throw new ExtensionManagementError(error.message, ExtensionManagementErrorCode.Download);
 		}
 
-		let verificationStatus: ExtensionVerificationStatus = ExtensionVerificationStatus.Unverified;
+		let verificationStatus: ExtensionVerificationStatus = false;
 
 		if (verifySignature && this.shouldVerifySignature(extension)) {
 			const signatureArchiveLocation = await this.downloadSignatureArchive(extension);
 			try {
-				const verified = await this.extensionSignatureVerificationService.verify(location.fsPath, signatureArchiveLocation.fsPath, this.logService.getLevel() === LogLevel.Trace);
-				if (verified) {
-					verificationStatus = ExtensionVerificationStatus.Verified;
-				}
-				this.logService.info(`Extension signature verification: ${extension.identifier.id}. Verification status: ${verificationStatus}.`);
+				verificationStatus = await this.extensionSignatureVerificationService.verify(location.fsPath, signatureArchiveLocation.fsPath, this.logService.getLevel() === LogLevel.Trace);
 			} catch (error) {
 				const sigError = error as ExtensionSignatureVerificationError;
-				const code: string = sigError.code;
+				verificationStatus = sigError.code;
 				if (sigError.output) {
 					this.logService.trace(`Extension signature verification details for ${extension.identifier.id} ${extension.version}:\n${sigError.output}`);
 				}
-
-				if (code === ExtensionSignaturetErrorCode.UnknownError) {
-					verificationStatus = ExtensionVerificationStatus.UnknownError;
-					this.logService.warn(`Extension signature verification: ${extension.identifier.id}. Verification status: ${verificationStatus}.`);
-				} else if (code === ExtensionSignaturetErrorCode.PackageIsInvalidZip || code === ExtensionSignaturetErrorCode.SignatureArchiveIsInvalidZip) {
+				if (verificationStatus === ExtensionSignaturetErrorCode.PackageIsInvalidZip || verificationStatus === ExtensionSignaturetErrorCode.SignatureArchiveIsInvalidZip) {
 					throw new ExtensionManagementError(CorruptZipMessage, ExtensionManagementErrorCode.CorruptZip);
-				} else if (!sigError.didExecute) {
-					this.logService.warn(`Extension signature verification: ${extension.identifier.id}. Verification status: ${verificationStatus} (${code})`);
-				} else {
-					await this.delete(location);
-
-					throw new ExtensionManagementError(code, ExtensionManagementErrorCode.Signature);
 				}
 			} finally {
 				try {
@@ -95,6 +81,14 @@ export class ExtensionsDownloader extends Disposable {
 			}
 		}
 
+		if (verificationStatus === true) {
+			this.logService.info(`Extension signature is verified: ${extension.identifier.id}`);
+		} else if (verificationStatus === false) {
+			this.logService.info(`Extension signature verification is not done: ${extension.identifier.id}`);
+		} else {
+			this.logService.warn(`Extension signature verification failed with error '${verificationStatus}': ${extension.identifier.id}`);
+		}
+
 		return { location, verificationStatus };
 	}
 
diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts
--- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts
+++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts
@@ -738,7 +738,7 @@ abstract class InstallExtensionTask extends AbstractExtensionTask<ILocalExtensio
 	private _profileLocation = this.options.profileLocation;
 	get profileLocation() { return this._profileLocation; }
 
-	protected _verificationStatus = ExtensionVerificationStatus.Unverified;
+	protected _verificationStatus: ExtensionVerificationStatus = false;
 	get verificationStatus() { return this._verificationStatus; }
 
 	protected _operation = InstallOperation.Install;


Test patch:
diff --git a/src/vs/platform/extensionManagement/test/node/installGalleryExtensionTask.test.ts b/src/vs/platform/extensionManagement/test/node/installGalleryExtensionTask.test.ts
--- a/src/vs/platform/extensionManagement/test/node/installGalleryExtensionTask.test.ts
+++ b/src/vs/platform/extensionManagement/test/node/installGalleryExtensionTask.test.ts
@@ -17,8 +17,7 @@ import { mock } from 'vs/base/test/common/mock';
 import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
 import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
 import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
-import { ExtensionVerificationStatus } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService';
-import { ExtensionManagementError, ExtensionManagementErrorCode, getTargetPlatform, IExtensionGalleryService, IGalleryExtension, IGalleryExtensionAssets, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
+import { getTargetPlatform, IExtensionGalleryService, IGalleryExtension, IGalleryExtensionAssets, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
 import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
 import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
 import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService';
@@ -136,7 +135,7 @@ suite('InstallGalleryExtensionTask Tests', () => {
 
 		await testObject.run();
 
-		assert.strictEqual(testObject.verificationStatus, ExtensionVerificationStatus.Verified);
+		assert.strictEqual(testObject.verificationStatus, true);
 		assert.strictEqual(testObject.installed, true);
 	});
 
@@ -145,7 +144,7 @@ suite('InstallGalleryExtensionTask Tests', () => {
 
 		await testObject.run();
 
-		assert.strictEqual(testObject.verificationStatus, ExtensionVerificationStatus.Verified);
+		assert.strictEqual(testObject.verificationStatus, true);
 		assert.strictEqual(testObject.installed, true);
 	});
 
@@ -154,7 +153,7 @@ suite('InstallGalleryExtensionTask Tests', () => {
 
 		await testObject.run();
 
-		assert.strictEqual(testObject.verificationStatus, ExtensionVerificationStatus.Unverified);
+		assert.strictEqual(testObject.verificationStatus, false);
 		assert.strictEqual(testObject.installed, true);
 	});
 
@@ -163,7 +162,7 @@ suite('InstallGalleryExtensionTask Tests', () => {
 
 		await testObject.run();
 
-		assert.strictEqual(testObject.verificationStatus, ExtensionVerificationStatus.Unverified);
+		assert.strictEqual(testObject.verificationStatus, false);
 		assert.strictEqual(testObject.installed, true);
 	});
 
@@ -173,27 +172,19 @@ suite('InstallGalleryExtensionTask Tests', () => {
 
 		await testObject.run();
 
-		assert.strictEqual(testObject.verificationStatus, ExtensionVerificationStatus.Unverified);
+		assert.strictEqual(testObject.verificationStatus, errorCode);
 		assert.strictEqual(testObject.installed, true);
 	});
 
-	test('if verification fails, the task throws', async () => {
+	test('if verification fails', async () => {
 		const errorCode = 'IntegrityCheckFailed';
 
 		const testObject = new TestInstallGalleryExtensionTask(aGalleryExtension('a', { isSigned: true }), anExtensionsDownloader({ isSignatureVerificationEnabled: true, verificationResult: errorCode, didExecute: true }));
 
-		try {
-			await testObject.run();
-		} catch (e) {
-			assert.ok(e instanceof ExtensionManagementError);
-			assert.strictEqual(e.code, ExtensionManagementErrorCode.Signature);
-			assert.strictEqual(e.message, errorCode);
-			assert.strictEqual(testObject.verificationStatus, ExtensionVerificationStatus.Unverified);
-			assert.strictEqual(testObject.installed, false);
-			return;
-		}
+		await testObject.run();
 
-		assert.fail('It should have thrown.');
+		assert.strictEqual(testObject.verificationStatus, errorCode);
+		assert.strictEqual(testObject.installed, true);
 	});
 
 	test('if verification succeeds, the task completes', async () => {
@@ -201,7 +192,7 @@ suite('InstallGalleryExtensionTask Tests', () => {
 
 		await testObject.run();
 
-		assert.strictEqual(testObject.verificationStatus, ExtensionVerificationStatus.Verified);
+		assert.strictEqual(testObject.verificationStatus, true);
 		assert.strictEqual(testObject.installed, true);
 	});
 
@@ -210,7 +201,7 @@ suite('InstallGalleryExtensionTask Tests', () => {
 
 		await testObject.run();
 
-		assert.strictEqual(testObject.verificationStatus, ExtensionVerificationStatus.Unverified);
+		assert.strictEqual(testObject.verificationStatus, false);
 		assert.strictEqual(testObject.installed, true);
 	});
 
@@ -219,7 +210,7 @@ suite('InstallGalleryExtensionTask Tests', () => {
 
 		await testObject.run();
 
-		assert.strictEqual(testObject.verificationStatus, ExtensionVerificationStatus.Unverified);
+		assert.strictEqual(testObject.verificationStatus, false);
 		assert.strictEqual(testObject.installed, true);
 	});