Skip to content

Commit 5743471

Browse files
Generate and validate integrity of downloaded packages (#5978)
1 parent 61c22d7 commit 5743471

File tree

5 files changed

+174
-19
lines changed

5 files changed

+174
-19
lines changed

Extension/gulpfile.js

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ const vinyl = require('vinyl');
2121
const parse5 = require('parse5');
2222
const traverse = require('parse5-traverse');
2323
const jsonc = require('jsonc-parser'); // Used to allow comments in nativeStrings.json
24+
const crypto = require('crypto');
25+
const https = require('https');
2426

2527

2628
// Patterns to find HTML files
@@ -290,6 +292,108 @@ gulp.task("translations-import", (done) => {
290292
}));
291293
});
292294

295+
// ****************************
296+
// Command: generate-package-hashes
297+
// Generates a hash for each dependency package
298+
// ****************************
299+
300+
async function DownloadFile(urlString) {
301+
const buffers = [];
302+
return new Promise((resolve, reject) => {
303+
const req = https.request(urlString, (response) => {
304+
if (response.statusCode === 301 || response.statusCode === 302) {
305+
// Redirect - download from new location
306+
let redirectUrl;
307+
if (typeof response.headers.location === "string") {
308+
redirectUrl = response.headers.location;
309+
} else {
310+
if (!response.headers.location) {
311+
console.log(`Invalid download location received`);
312+
return reject();
313+
}
314+
redirectUrl = response.headers.location[0];
315+
}
316+
console.log(`Using redirectUrl: '${redirectUrl}'`);
317+
return resolve(DownloadFile(redirectUrl));
318+
} else if (response.statusCode !== 200) {
319+
if (response.statusCode === undefined || response.statusCode === null) {
320+
console.log("unknown error code.");
321+
return reject();
322+
}
323+
console.log(`failed with error code: '${response.statusCode}'`);
324+
return reject();
325+
}
326+
327+
response.on('data', (data) => {
328+
buffers.push(data);
329+
});
330+
331+
response.on('end', () => {
332+
if (buffers.length > 0) {
333+
return resolve(Buffer.concat(buffers));
334+
} else {
335+
return reject();
336+
}
337+
});
338+
339+
response.on('error', err => {
340+
console.log(`problem with request: '${err.message}'`);
341+
return reject();
342+
});
343+
});
344+
345+
req.on('error', err => {
346+
console.log(`problem with request: '${err.message}'`);
347+
return reject();
348+
});
349+
350+
// Execute the request
351+
req.end();
352+
});
353+
354+
}
355+
356+
async function generatePackageHashes(packageJson) {
357+
const downloadAndGetHash = async (url) => {
358+
console.log(url);
359+
try {
360+
const buf = await DownloadFile(url);
361+
if (buf) {
362+
const hash = crypto.createHash('sha256');
363+
hash.update(buf);
364+
const value = hash.digest('hex').toUpperCase();
365+
return value;
366+
}
367+
return undefined;
368+
} catch (err) {
369+
return undefined;
370+
}
371+
};
372+
373+
for (let dependency of packageJson.runtimeDependencies) {
374+
console.log(`-------- Downloading package: '${dependency.description}' --------`);
375+
const hash = await downloadAndGetHash(dependency.url);
376+
if (hash) {
377+
dependency.integrity = hash;
378+
console.log(`integrity: '${hash}'`);
379+
} else {
380+
console.log(`No hash generated for package '${dependency.description}`);
381+
}
382+
console.log(`\n`);
383+
}
384+
385+
let content = JSON.stringify(packageJson, null, 2);
386+
return content;
387+
}
388+
389+
gulp.task('generate-package-hashes', async (done) => {
390+
const packageJsonPath = './package.json';
391+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath).toString());
392+
const content = await generatePackageHashes(packageJson);
393+
fs.writeFileSync(packageJsonPath, content);
394+
done();
395+
});
396+
293397

294398
// ****************************
295399
// Command: translations-generate

Extension/jobs/build.windows.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ jobs:
1717
displayName: "Install Dependencies"
1818
workingDirectory: '$(Build.SourcesDirectory)\Extension'
1919

20+
- script: yarn run generatePackageHashes
21+
displayName: "Generate hashes for runtime dependency packages"
22+
workingDirectory: '$(Build.SourcesDirectory)/Extension'
23+
2024
- script: yarn run compile
2125
displayName: "Compile Sources"
2226
workingDirectory: '$(Build.SourcesDirectory)\Extension'

Extension/jobs/build.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ jobs:
1616
displayName: "Install Dependencies"
1717
workingDirectory: '$(Build.SourcesDirectory)/Extension'
1818

19+
- script: yarn run generatePackageHashes
20+
displayName: "Generate hashes for runtime dependency packages"
21+
workingDirectory: '$(Build.SourcesDirectory)/Extension'
22+
1923
- script: yarn run compile
2024
displayName: "Compile Sources"
2125
workingDirectory: '$(Build.SourcesDirectory)/Extension'

Extension/package.json

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2208,6 +2208,7 @@
22082208
"translations-import": "node ./tools/prepublish.js && gulp translations-import",
22092209
"prepublishjs": "node ./tools/prepublish.js",
22102210
"pretest": "tsc -p test.tsconfig.json",
2211+
"generatePackageHashes": "gulp generate-package-hashes",
22112212
"pr-check": "gulp pr-check",
22122213
"lint": "gulp lint",
22132214
"unitTests": "tsc -p test.tsconfig.json && node ./out/test/unitTests/runTest.js",
@@ -2299,7 +2300,8 @@
22992300
"binaries": [
23002301
"./bin/cpptools",
23012302
"./bin/cpptools-srv"
2302-
]
2303+
],
2304+
"integrity": "292D438B2573D17D18E30C4723702118BBCCB0049388C1B76879E9A15FE784FD"
23032305
},
23042306
{
23052307
"description": "C/C++ language components (Linux / armhf)",
@@ -2313,7 +2315,8 @@
23132315
"binaries": [
23142316
"./bin/cpptools",
23152317
"./bin/cpptools-srv"
2316-
]
2318+
],
2319+
"integrity": "61C2656ABD3856A657D6103D169838528F9454D39E7DA36A92B272AC2A052067"
23172320
},
23182321
{
23192322
"description": "C/C++ language components (Linux / aarch64)",
@@ -2327,7 +2330,8 @@
23272330
"binaries": [
23282331
"./bin/cpptools",
23292332
"./bin/cpptools-srv"
2330-
]
2333+
],
2334+
"integrity": "61C2656ABD3856A657D6103D169838528F9454D39E7DA36A92B272AC2A052067"
23312335
},
23322336
{
23332337
"description": "C/C++ language components (OS X)",
@@ -2338,7 +2342,8 @@
23382342
"binaries": [
23392343
"./bin/cpptools",
23402344
"./bin/cpptools-srv"
2341-
]
2345+
],
2346+
"integrity": "3CAAFD3A5375F4F3EA2D7EA4B432C1917FDC3B548CA81C5D032041A07821B121"
23422347
},
23432348
{
23442349
"description": "C/C++ language components (Windows)",
@@ -2349,7 +2354,8 @@
23492354
"binaries": [
23502355
"./bin/cpptools.exe",
23512356
"./bin/cpptools-srv.exe"
2352-
]
2357+
],
2358+
"integrity": "F98CB4D80013A119EB0043BA1A85E1E5966E6F160264A28E19D9D57ABD7FD015"
23532359
},
23542360
{
23552361
"description": "ClangFormat (Linux / x86_64)",
@@ -2362,7 +2368,8 @@
23622368
],
23632369
"binaries": [
23642370
"./LLVM/bin/clang-format"
2365-
]
2371+
],
2372+
"integrity": "B8381B87B83EE1AF9088AA6679F2DA013DC23A21D9EC5603C58DEFC323ED9617"
23662373
},
23672374
{
23682375
"description": "ClangFormat (Linux / armhf)",
@@ -2375,7 +2382,8 @@
23752382
],
23762383
"binaries": [
23772384
"./LLVM/bin/clang-format"
2378-
]
2385+
],
2386+
"integrity": "F06D3741192FFD7BB96BF9327D9DE1BBF6A9DB9753DA971C727D20D38835C1A9"
23792387
},
23802388
{
23812389
"description": "ClangFormat (Linux / aarch64)",
@@ -2388,7 +2396,8 @@
23882396
],
23892397
"binaries": [
23902398
"./LLVM/bin/clang-format"
2391-
]
2399+
],
2400+
"integrity": "F06D3741192FFD7BB96BF9327D9DE1BBF6A9DB9753DA971C727D20D38835C1A9"
23922401
},
23932402
{
23942403
"description": "ClangFormat (OS X)",
@@ -2398,7 +2407,8 @@
23982407
],
23992408
"binaries": [
24002409
"./LLVM/bin/clang-format.darwin"
2401-
]
2410+
],
2411+
"integrity": "416D3B814A76E52D943B3372D517D6AADE0AB0BD9FD9921F871C1FB89D7B17B1"
24022412
},
24032413
{
24042414
"description": "ClangFormat (Windows)",
@@ -2408,7 +2418,8 @@
24082418
],
24092419
"binaries": [
24102420
"./LLVM/bin/clang-format.exe"
2411-
]
2421+
],
2422+
"integrity": "E5A2543FA2D45B964B43AE770A6D50F8698B592485E7E883005FFF52BFAD0CCD"
24122423
},
24132424
{
24142425
"description": "Mono Framework Assemblies",
@@ -2417,7 +2428,8 @@
24172428
"linux",
24182429
"darwin"
24192430
],
2420-
"binaries": []
2431+
"binaries": [],
2432+
"integrity": "579295329C1825588B6251EEA56571D1B9555BD529AA1A319CDFE741BC22D786"
24212433
},
24222434
{
24232435
"description": "Mono Runtime (Linux / x86_64)",
@@ -2430,7 +2442,8 @@
24302442
],
24312443
"binaries": [
24322444
"./debugAdapters/mono.linux-x86_64"
2433-
]
2445+
],
2446+
"integrity": "927C5094E83CE64EDC3A267EE1F13267CFA09FC1E9A5ADC870A752C05AD26F17"
24342447
},
24352448
{
24362449
"description": "Mono Runtime (Linux / armhf)",
@@ -2443,7 +2456,8 @@
24432456
],
24442457
"binaries": [
24452458
"./debugAdapters/mono.linux-armhf"
2446-
]
2459+
],
2460+
"integrity": "D2D1FE7FB5CC5B2A60364F08829467509879D603FC951AC3805B530C8CEEF981"
24472461
},
24482462
{
24492463
"description": "Mono Runtime (Linux / arm64)",
@@ -2456,7 +2470,8 @@
24562470
],
24572471
"binaries": [
24582472
"./debugAdapters/mono.linux-arm64"
2459-
]
2473+
],
2474+
"integrity": "946C54C8C6BF5BF79AC05D4E152F8D8647700FA786C21505832B3C79988339B4"
24602475
},
24612476
{
24622477
"description": "Mono Runtime (OS X)",
@@ -2466,7 +2481,8 @@
24662481
],
24672482
"binaries": [
24682483
"./debugAdapters/mono.osx"
2469-
]
2484+
],
2485+
"integrity": "5D9E5AB3E921D138887BD12FF46B02BFC85B692B438EDFAAEB1E1E7837F1D40B"
24702486
},
24712487
{
24722488
"description": "LLDB-MI (macOS Mojave and higher)",
@@ -2478,7 +2494,8 @@
24782494
"matchVersion": false,
24792495
"binaries": [
24802496
"./debugAdapters/lldb-mi/bin/lldb-mi"
2481-
]
2497+
],
2498+
"integrity": "6C2CD2797380DE2F3EA2A991BA4DACE265920922C4E18A80FAC515B30C1D0B99"
24822499
},
24832500
{
24842501
"description": "LLDB 3.8.0 (macOS High Sierra and lower)",
@@ -2493,7 +2510,8 @@
24932510
"./debugAdapters/lldb/bin/lldb-mi",
24942511
"./debugAdapters/lldb/bin/lldb-argdumper",
24952512
"./debugAdapters/lldb/bin/lldb-launcher"
2496-
]
2513+
],
2514+
"integrity": "D4ACCD43F562E42CE30879AC15ADF5FB6AA50656795DCE8F3AD32FB108BB3B7E"
24972515
},
24982516
{
24992517
"description": "Visual Studio Windows Debugger",
@@ -2503,7 +2521,8 @@
25032521
],
25042522
"binaries": [
25052523
"./debugAdapters/vsdbg/bin/vsdbg.exe"
2506-
]
2524+
],
2525+
"integrity": "CF1A01AA75275F76800F6BC1D289F2066DCEBCD983376D344ABF6B03FDB8FEA0"
25072526
}
25082527
]
25092528
}

Extension/src/packageManager.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,22 @@ import { IncomingMessage, ClientRequest } from 'http';
2020
import { Logger } from './logger';
2121
import * as nls from 'vscode-nls';
2222
import { Readable } from 'stream';
23+
import * as crypto from 'crypto';
2324

2425
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
2526
const localize: nls.LocalizeFunc = nls.loadMessageBundle();
2627

28+
export function isValidPackage(buffer: Buffer, integrity: string): boolean {
29+
if (integrity && integrity.length > 0) {
30+
const hash: crypto.Hash = crypto.createHash('sha256');
31+
hash.update(buffer);
32+
const value: string = hash.digest('hex').toUpperCase();
33+
return (value === integrity.toUpperCase());
34+
}
35+
// No integrity has been specified
36+
return true;
37+
}
38+
2739
export interface IPackage {
2840
// Description of the package
2941
description: string;
@@ -49,6 +61,9 @@ export interface IPackage {
4961

5062
// Internal location to which the package was downloaded
5163
tmpFile: tmp.FileResult;
64+
65+
// sha256 hash of the package
66+
integrity: string;
5267
}
5368

5469
export class PackageManagerError extends Error {
@@ -240,6 +255,7 @@ export class PackageManager {
240255
rejectUnauthorized: proxyStrictSSL
241256
};
242257

258+
const buffers: Buffer[] = [];
243259
return new Promise<void>((resolve, reject) => {
244260
let secondsDelay: number = Math.pow(2, delay);
245261
if (secondsDelay === 1) {
@@ -292,6 +308,7 @@ export class PackageManager {
292308
this.AppendChannel(`(${Math.ceil(packageSize / 1024)} KB) `);
293309

294310
response.on('data', (data) => {
311+
buffers.push(data);
295312
// Update dots after package name in output console
296313
const newDots: number = Math.ceil(downloadPercentage / 5);
297314
if (newDots > dots) {
@@ -300,7 +317,14 @@ export class PackageManager {
300317
}
301318
});
302319

303-
response.on('end', resolve);
320+
response.on('end', () => {
321+
const packageBuffer: Buffer = Buffer.concat(buffers);
322+
if (isValidPackage(packageBuffer, pkg.integrity)) {
323+
resolve();
324+
} else {
325+
reject(new PackageManagerError('Invalid content received. Hash is incorrect.', localize("invalid.content.received", 'Invalid content received. Hash is incorrect.'), 'DownloadFile', pkg));
326+
}
327+
});
304328

305329
response.on('error', (error) =>
306330
reject(new PackageManagerWebResponseError(response.socket, 'HTTP/HTTPS Response Error', localize("web.response.error", 'HTTP/HTTPS Response Error'), 'DownloadFile', pkg, error.stack, error.name)));

0 commit comments

Comments
 (0)