mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 09:08:48 +01:00
Add Codesign validation for MacOS
This commit is contained in:
@@ -28,6 +28,7 @@ export function setup(context: TestContext) {
|
||||
if (context.platform === 'darwin-arm64') {
|
||||
it('cli-darwin-arm64', async () => {
|
||||
const dir = await context.downloadAndUnpack('cli-darwin-arm64');
|
||||
context.validateAllCodesignSignatures(dir);
|
||||
const entryPoint = context.getEntryPoint('cli', dir);
|
||||
await testCliApp(entryPoint);
|
||||
});
|
||||
@@ -36,6 +37,7 @@ export function setup(context: TestContext) {
|
||||
if (context.platform === 'darwin-x64') {
|
||||
it('cli-darwin-x64', async () => {
|
||||
const dir = await context.downloadAndUnpack('cli-darwin-x64');
|
||||
context.validateAllCodesignSignatures(dir);
|
||||
const entryPoint = context.getEntryPoint('cli', dir);
|
||||
await testCliApp(entryPoint);
|
||||
});
|
||||
@@ -68,7 +70,7 @@ export function setup(context: TestContext) {
|
||||
if (context.platform === 'win32-arm64') {
|
||||
it('cli-win32-arm64', async () => {
|
||||
const dir = await context.downloadAndUnpack('cli-win32-arm64');
|
||||
context.validateAllSignatures(dir);
|
||||
context.validateAllAuthenticodeSignatures(dir);
|
||||
const entryPoint = context.getEntryPoint('cli', dir);
|
||||
await testCliApp(entryPoint);
|
||||
});
|
||||
@@ -77,7 +79,7 @@ export function setup(context: TestContext) {
|
||||
if (context.platform === 'win32-x64') {
|
||||
it('cli-win32-x64', async () => {
|
||||
const dir = await context.downloadAndUnpack('cli-win32-x64');
|
||||
context.validateAllSignatures(dir);
|
||||
context.validateAllAuthenticodeSignatures(dir);
|
||||
const entryPoint = context.getEntryPoint('cli', dir);
|
||||
await testCliApp(entryPoint);
|
||||
});
|
||||
|
||||
@@ -192,7 +192,7 @@ export class TestContext {
|
||||
this.validateSha256Hash(filePath, sha256hash);
|
||||
|
||||
if (TestContext.authenticodeInclude.test(filePath) && os.platform() === 'win32') {
|
||||
this.validateSignature(filePath);
|
||||
this.validateAuthenticodeSignature(filePath);
|
||||
}
|
||||
|
||||
return filePath;
|
||||
@@ -218,7 +218,7 @@ export class TestContext {
|
||||
* Validates the Authenticode signature of a Windows executable.
|
||||
* @param filePath The path to the file to validate.
|
||||
*/
|
||||
public validateSignature(filePath: string) {
|
||||
public validateAuthenticodeSignature(filePath: string) {
|
||||
this.log(`Validating Authenticode signature for ${filePath}`);
|
||||
|
||||
const result = this.run('powershell', '-Command', `Get-AuthenticodeSignature "${filePath}" | Select-Object -ExpandProperty Status`);
|
||||
@@ -233,21 +233,87 @@ export class TestContext {
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates signatures for all executable files in the specified directory.
|
||||
* Validates Authenticode signatures for all executable files in the specified directory.
|
||||
* @param dir The directory to scan for executable files.
|
||||
*/
|
||||
public validateAllSignatures(dir: string) {
|
||||
public validateAllAuthenticodeSignatures(dir: string) {
|
||||
const files = fs.readdirSync(dir, { withFileTypes: true });
|
||||
for (const file of files) {
|
||||
const filePath = path.join(dir, file.name);
|
||||
if (file.isDirectory()) {
|
||||
this.validateAllSignatures(filePath);
|
||||
this.validateAllAuthenticodeSignatures(filePath);
|
||||
} else if (TestContext.authenticodeInclude.test(file.name)) {
|
||||
this.validateSignature(filePath);
|
||||
this.validateAuthenticodeSignature(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the codesign signature of a macOS binary or app bundle.
|
||||
* @param filePath The path to the file or app bundle to validate.
|
||||
*/
|
||||
public validateCodesignSignature(filePath: string) {
|
||||
this.log(`Validating codesign signature for ${filePath}`);
|
||||
|
||||
const result = this.run('codesign', '--verify', '--deep', '--strict', filePath);
|
||||
if (result.error !== undefined) {
|
||||
this.error(`Failed to run codesign: ${result.error.message}`);
|
||||
}
|
||||
|
||||
if (result.status !== 0) {
|
||||
this.error(`Codesign signature is not valid for ${filePath}: ${result.stderr}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates codesign signatures for all Mach-O binaries in the specified directory.
|
||||
* @param dir The directory to scan for Mach-O binaries.
|
||||
*/
|
||||
public validateAllCodesignSignatures(dir: string) {
|
||||
const files = fs.readdirSync(dir, { withFileTypes: true });
|
||||
for (const file of files) {
|
||||
const filePath = path.join(dir, file.name);
|
||||
if (file.isDirectory()) {
|
||||
// For .app bundles, validate the bundle itself, not its contents
|
||||
if (file.name.endsWith('.app') || file.name.endsWith('.framework')) {
|
||||
this.validateCodesignSignature(filePath);
|
||||
} else {
|
||||
this.validateAllCodesignSignatures(filePath);
|
||||
}
|
||||
} else if (this.isMachOBinary(filePath)) {
|
||||
this.validateCodesignSignature(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a file is a Mach-O binary by examining its magic number.
|
||||
* @param filePath The path to the file to check.
|
||||
* @returns True if the file is a Mach-O binary.
|
||||
*/
|
||||
private isMachOBinary(filePath: string): boolean {
|
||||
try {
|
||||
const fd = fs.openSync(filePath, 'r');
|
||||
const buffer = Buffer.alloc(4);
|
||||
fs.readSync(fd, buffer, 0, 4, 0);
|
||||
fs.closeSync(fd);
|
||||
|
||||
// Mach-O magic numbers:
|
||||
// MH_MAGIC: 0xFEEDFACE (32-bit)
|
||||
// MH_CIGAM: 0xCEFAEDFE (32-bit, byte-swapped)
|
||||
// MH_MAGIC_64: 0xFEEDFACF (64-bit)
|
||||
// MH_CIGAM_64: 0xCFFAEDFE (64-bit, byte-swapped)
|
||||
// FAT_MAGIC: 0xCAFEBABE (universal binary)
|
||||
// FAT_CIGAM: 0xBEBAFECA (universal binary, byte-swapped)
|
||||
const magic = buffer.readUInt32BE(0);
|
||||
return magic === 0xFEEDFACE || magic === 0xCEFAEDFE ||
|
||||
magic === 0xFEEDFACF || magic === 0xCFFAEDFE ||
|
||||
magic === 0xCAFEBABE || magic === 0xBEBAFECA;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads and unpacks the specified VS Code release target.
|
||||
* @param target The target platform (e.g., 'cli-linux-x64').
|
||||
|
||||
@@ -13,6 +13,7 @@ export function setup(context: TestContext) {
|
||||
if (context.platform === 'darwin-x64') {
|
||||
it('desktop-darwin-x64', async () => {
|
||||
const dir = await context.downloadAndUnpack('darwin');
|
||||
context.validateAllCodesignSignatures(dir);
|
||||
const entryPoint = context.installMacApp(dir);
|
||||
await testDesktopApp(entryPoint);
|
||||
});
|
||||
@@ -21,6 +22,7 @@ export function setup(context: TestContext) {
|
||||
if (context.platform === 'darwin-arm64') {
|
||||
it('desktop-darwin-arm64', async () => {
|
||||
const dir = await context.downloadAndUnpack('darwin-arm64');
|
||||
context.validateAllCodesignSignatures(dir);
|
||||
const entryPoint = context.installMacApp(dir);
|
||||
await testDesktopApp(entryPoint);
|
||||
});
|
||||
@@ -29,6 +31,7 @@ export function setup(context: TestContext) {
|
||||
if (context.platform === 'darwin-arm64' || context.platform === 'darwin-x64') {
|
||||
it('desktop-darwin-universal', async () => {
|
||||
const dir = await context.downloadAndUnpack('darwin-universal');
|
||||
context.validateAllCodesignSignatures(dir);
|
||||
const entryPoint = context.installMacApp(dir);
|
||||
await testDesktopApp(entryPoint, { universal: true });
|
||||
});
|
||||
@@ -117,9 +120,9 @@ export function setup(context: TestContext) {
|
||||
if (context.platform === 'win32-arm64') {
|
||||
it('desktop-win32-arm64', async () => {
|
||||
const packagePath = await context.downloadTarget('win32-arm64');
|
||||
context.validateSignature(packagePath);
|
||||
context.validateAuthenticodeSignature(packagePath);
|
||||
const entryPoint = context.installWindowsApp('system', packagePath);
|
||||
context.validateAllSignatures(path.dirname(entryPoint));
|
||||
context.validateAllAuthenticodeSignatures(path.dirname(entryPoint));
|
||||
await testDesktopApp(entryPoint);
|
||||
await context.uninstallWindowsApp('system');
|
||||
});
|
||||
@@ -128,7 +131,7 @@ export function setup(context: TestContext) {
|
||||
if (context.platform === 'win32-arm64') {
|
||||
it('desktop-win32-arm64-archive', async () => {
|
||||
const dir = await context.downloadAndUnpack('win32-arm64-archive');
|
||||
context.validateAllSignatures(dir);
|
||||
context.validateAllAuthenticodeSignatures(dir);
|
||||
const entryPoint = context.getEntryPoint('desktop', dir);
|
||||
await testDesktopApp(entryPoint);
|
||||
});
|
||||
@@ -137,9 +140,9 @@ export function setup(context: TestContext) {
|
||||
if (context.platform === 'win32-arm64') {
|
||||
it('desktop-win32-arm64-user', async () => {
|
||||
const packagePath = await context.downloadTarget('win32-arm64-user');
|
||||
context.validateSignature(packagePath);
|
||||
context.validateAuthenticodeSignature(packagePath);
|
||||
const entryPoint = context.installWindowsApp('user', packagePath);
|
||||
context.validateAllSignatures(path.dirname(entryPoint));
|
||||
context.validateAllAuthenticodeSignatures(path.dirname(entryPoint));
|
||||
await testDesktopApp(entryPoint);
|
||||
await context.uninstallWindowsApp('user');
|
||||
});
|
||||
@@ -148,9 +151,9 @@ export function setup(context: TestContext) {
|
||||
if (context.platform === 'win32-x64') {
|
||||
it('desktop-win32-x64', async () => {
|
||||
const packagePath = await context.downloadTarget('win32-x64');
|
||||
context.validateSignature(packagePath);
|
||||
context.validateAuthenticodeSignature(packagePath);
|
||||
const entryPoint = context.installWindowsApp('system', packagePath);
|
||||
context.validateAllSignatures(path.dirname(entryPoint));
|
||||
context.validateAllAuthenticodeSignatures(path.dirname(entryPoint));
|
||||
await testDesktopApp(entryPoint);
|
||||
await context.uninstallWindowsApp('system');
|
||||
});
|
||||
@@ -159,7 +162,7 @@ export function setup(context: TestContext) {
|
||||
if (context.platform === 'win32-x64') {
|
||||
it('desktop-win32-x64-archive', async () => {
|
||||
const dir = await context.downloadAndUnpack('win32-x64-archive');
|
||||
context.validateAllSignatures(dir);
|
||||
context.validateAllAuthenticodeSignatures(dir);
|
||||
const entryPoint = context.getEntryPoint('desktop', dir);
|
||||
await testDesktopApp(entryPoint);
|
||||
});
|
||||
@@ -168,9 +171,9 @@ export function setup(context: TestContext) {
|
||||
if (context.platform === 'win32-x64') {
|
||||
it('desktop-win32-x64-user', async () => {
|
||||
const packagePath = await context.downloadTarget('win32-x64-user');
|
||||
context.validateSignature(packagePath);
|
||||
context.validateAuthenticodeSignature(packagePath);
|
||||
const entryPoint = context.installWindowsApp('user', packagePath);
|
||||
context.validateAllSignatures(path.dirname(entryPoint));
|
||||
context.validateAllAuthenticodeSignatures(path.dirname(entryPoint));
|
||||
await testDesktopApp(entryPoint);
|
||||
await context.uninstallWindowsApp('user');
|
||||
});
|
||||
|
||||
@@ -29,6 +29,7 @@ export function setup(context: TestContext) {
|
||||
if (context.platform === 'darwin-arm64') {
|
||||
it('server-darwin-arm64', async () => {
|
||||
const dir = await context.downloadAndUnpack('server-darwin-arm64');
|
||||
context.validateAllCodesignSignatures(dir);
|
||||
const entryPoint = context.getServerEntryPoint(dir);
|
||||
await testServer(entryPoint);
|
||||
});
|
||||
@@ -37,6 +38,7 @@ export function setup(context: TestContext) {
|
||||
if (context.platform === 'darwin-x64') {
|
||||
it('server-darwin-x64', async () => {
|
||||
const dir = await context.downloadAndUnpack('server-darwin');
|
||||
context.validateAllCodesignSignatures(dir);
|
||||
const entryPoint = context.getServerEntryPoint(dir);
|
||||
await testServer(entryPoint);
|
||||
});
|
||||
@@ -69,7 +71,7 @@ export function setup(context: TestContext) {
|
||||
if (context.platform === 'win32-arm64') {
|
||||
it('server-win32-arm64', async () => {
|
||||
const dir = await context.downloadAndUnpack('server-win32-arm64');
|
||||
context.validateAllSignatures(dir);
|
||||
context.validateAllAuthenticodeSignatures(dir);
|
||||
const entryPoint = context.getServerEntryPoint(dir);
|
||||
await testServer(entryPoint);
|
||||
});
|
||||
@@ -78,7 +80,7 @@ export function setup(context: TestContext) {
|
||||
if (context.platform === 'win32-x64') {
|
||||
it('server-win32-x64', async () => {
|
||||
const dir = await context.downloadAndUnpack('server-win32-x64');
|
||||
context.validateAllSignatures(dir);
|
||||
context.validateAllAuthenticodeSignatures(dir);
|
||||
const entryPoint = context.getServerEntryPoint(dir);
|
||||
await testServer(entryPoint);
|
||||
});
|
||||
|
||||
@@ -29,6 +29,7 @@ export function setup(context: TestContext) {
|
||||
if (context.platform === 'darwin-arm64') {
|
||||
it('server-web-darwin-arm64', async () => {
|
||||
const dir = await context.downloadAndUnpack('server-darwin-arm64-web');
|
||||
context.validateAllCodesignSignatures(dir);
|
||||
const entryPoint = context.getServerEntryPoint(dir);
|
||||
await testServer(entryPoint);
|
||||
});
|
||||
@@ -37,6 +38,7 @@ export function setup(context: TestContext) {
|
||||
if (context.platform === 'darwin-x64') {
|
||||
it('server-web-darwin-x64', async () => {
|
||||
const dir = await context.downloadAndUnpack('server-darwin-web');
|
||||
context.validateAllCodesignSignatures(dir);
|
||||
const entryPoint = context.getServerEntryPoint(dir);
|
||||
await testServer(entryPoint);
|
||||
});
|
||||
@@ -69,7 +71,7 @@ export function setup(context: TestContext) {
|
||||
if (context.platform === 'win32-arm64') {
|
||||
it('server-web-win32-arm64', async () => {
|
||||
const dir = await context.downloadAndUnpack('server-win32-arm64-web');
|
||||
context.validateAllSignatures(dir);
|
||||
context.validateAllAuthenticodeSignatures(dir);
|
||||
const entryPoint = context.getServerEntryPoint(dir);
|
||||
await testServer(entryPoint);
|
||||
});
|
||||
@@ -78,7 +80,7 @@ export function setup(context: TestContext) {
|
||||
if (context.platform === 'win32-x64') {
|
||||
it('server-web-win32-x64', async () => {
|
||||
const dir = await context.downloadAndUnpack('server-win32-x64-web');
|
||||
context.validateAllSignatures(dir);
|
||||
context.validateAllAuthenticodeSignatures(dir);
|
||||
const entryPoint = context.getServerEntryPoint(dir);
|
||||
await testServer(entryPoint);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user