Skip to content

Commit 91652f7

Browse files
authored
Verify signatures of real signed vsixs (#8556)
2 parents 25c07c1 + d85fe6f commit 91652f7

File tree

5 files changed

+75
-7
lines changed

5 files changed

+75
-7
lines changed
File renamed without changes.

azure-pipelines/build-vsix.yml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,11 @@ stages:
114114
- output: pipelineArtifact
115115
path: $(Build.SourcesDirectory)/Packages
116116
artifact: Packages
117+
condition: always()
117118
- output: pipelineArtifact
118119
path: $(Build.SourcesDirectory)/out/logs
119120
artifact: SigningLogs
121+
condition: always()
120122
steps:
121123
- checkout: self
122124
clean: true
@@ -134,9 +136,9 @@ stages:
134136
versionSpec: 3.11
135137

136138
# Non-windows signing requires .NET 8 installed on the machine.
137-
- task: UseDotNet@2
138-
displayName: Use .NET Core sdk 8.0.x
139-
inputs:
139+
- task: UseDotNet@2
140+
displayName: Use .NET Core sdk 8.0.x
141+
inputs:
140142
version: 8.0.x
141143

142144
# If we're in an official build, install the signing plugin
@@ -181,6 +183,12 @@ stages:
181183
SourceFolder: '$(Build.SourcesDirectory)/vsix'
182184
TargetFolder: '$(Build.SourcesDirectory)/Packages/VSIX_$(channel)'
183185

186+
- ${{ if eq(parameters.isOfficial, true) }}:
187+
- script: gulp verifyVsix
188+
condition: and( succeeded(), eq('$(SignType)', 'Real'))
189+
displayName: 🔑 Verify VSIX Signature Files
190+
workingDirectory: '$(Build.SourcesDirectory)/Packages/VSIX_$(channel)'
191+
184192
- ${{ if ne(parameters.isOfficial, true) }}:
185193
- task: PublishPipelineArtifact@1
186194
condition: succeeded()

tasks/signingTasks.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as fs from 'fs';
88
import * as gulp from 'gulp';
99
import { rootPath } from './projectPaths';
1010
import path from 'path';
11+
import { verifySignature } from './vsceTasks';
1112
// There are no typings for this library.
1213
// eslint-disable-next-line @typescript-eslint/no-var-requires
1314
//const argv = require('yargs').argv;
@@ -20,6 +21,10 @@ gulp.task('signVsix', async () => {
2021
await signVsix();
2122
});
2223

24+
gulp.task('verifyVsix', async () => {
25+
await verifyVsix();
26+
});
27+
2328
// Development task to install the signing plugin locally.
2429
// Required to run test sigining tasks locally.
2530
gulp.task('installSignPlugin', async () => {
@@ -43,7 +48,7 @@ async function signJs(): Promise<void> {
4348
const logPath = getLogPath();
4449
const signType = process.env.SignType;
4550
if (!signType) {
46-
console.warn('SignType environment variable is not set, skipping JS signing');
51+
console.warn('SignType environment variable is not set, skipping JS signing.');
4752
return;
4853
}
4954

@@ -64,7 +69,7 @@ async function signVsix(): Promise<void> {
6469
const logPath = getLogPath();
6570
const signType = process.env.SignType;
6671
if (!signType) {
67-
console.warn('SignType environment variable is not set, skipping VSIX signing');
72+
console.warn('SignType environment variable is not set, skipping VSIX signing.');
6873
return;
6974
}
7075

@@ -80,6 +85,25 @@ async function signVsix(): Promise<void> {
8085
]);
8186
}
8287

88+
async function verifyVsix(): Promise<void> {
89+
const signType = process.env.SignType;
90+
if (!signType) {
91+
console.warn('SignType environment variable is not set, skipping VSIX verification.');
92+
return;
93+
}
94+
95+
if (signType.toLowerCase() !== 'real') {
96+
console.log('Signing verification is only supported for real signing. Skipping VSIX verification.');
97+
return;
98+
}
99+
100+
const vsixs = fs.readdirSync('.').filter((file) => path.extname(file) === '.vsix');
101+
for (const vsixFile in vsixs) {
102+
console.log(`Verifying signature of ${vsixFile}`);
103+
await verifySignature(vsixFile);
104+
}
105+
}
106+
83107
function getLogPath(): string {
84108
const logPath = path.join(rootPath, 'out', 'logs');
85109
if (!fs.existsSync(logPath)) {

tasks/spawnNode.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export default async function spawnNode(args?: string[], options?: SpawnSyncOpti
1010
if (!options) {
1111
options = {
1212
env: {},
13+
stdio: 'inherit',
1314
};
1415
}
1516

@@ -20,12 +21,12 @@ export default async function spawnNode(args?: string[], options?: SpawnSyncOpti
2021
...process.env,
2122
...options.env,
2223
},
23-
stdio: 'inherit',
24+
stdio: options.stdio ?? 'inherit',
2425
};
2526

2627
console.log(`starting ${nodePath} ${args ? args.join(' ') : ''}`);
2728

2829
const buffer = spawnSync(nodePath, args, optionsWithFullEnvironment);
2930

30-
return { code: buffer.status, signal: buffer.signal };
31+
return { code: buffer.status, signal: buffer.signal, stdout: buffer.stdout };
3132
}

tasks/vsceTasks.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,38 @@ export async function generateVsixManifest(vsixPath: string) {
8282
throw new Error(`'${vsceArgs.join(' ')}' failed with code ${spawnResult.code}.`);
8383
}
8484
}
85+
86+
export async function verifySignature(vsixPath: string) {
87+
const vsceArgs = [];
88+
if (!(await util.fileExists(vscePath))) {
89+
throw new Error(`vsce does not exist at expected location: '${vscePath}'`);
90+
}
91+
92+
vsceArgs.push(vscePath);
93+
vsceArgs.push('verify-signature');
94+
vsceArgs.push('--packagePath');
95+
vsceArgs.push(vsixPath);
96+
97+
const parsed = path.parse(vsixPath);
98+
const outputFolder = parsed.dir;
99+
const vsixNameWithoutExtension = parsed.name;
100+
101+
vsceArgs.push('--manifestPath');
102+
vsceArgs.push(path.join(outputFolder, `${vsixNameWithoutExtension}.manifest`));
103+
104+
vsceArgs.push('--signaturePath');
105+
vsceArgs.push(path.join(outputFolder, `${vsixNameWithoutExtension}.signature.p7s`));
106+
107+
const spawnResult = await spawnNode(vsceArgs, { stdio: 'pipe' });
108+
if (spawnResult.code != 0) {
109+
throw new Error(`'${vsceArgs.join(' ')}' failed with code ${spawnResult.code}.`);
110+
} else if (spawnResult.stdout != 'Signature verification result: Success') {
111+
// This is a brittle check but the command does not return a non-zero exit code for failed validation.
112+
// Opened https://github.com/microsoft/vscode-vsce/issues/1192 to track this.
113+
114+
console.log(spawnResult.stdout);
115+
throw new Error(`Signature verification failed - '${vsceArgs.join(' ')}'.`);
116+
}
117+
118+
console.log(`Signature verification succeeded for ${vsixPath}`);
119+
}

0 commit comments

Comments
 (0)