fix: use stricter options in SecStaticCodeCheckValidity (#33368)
* fix: use stricter options in SecStaticCodeCheckValidity * Update patches/squirrel.mac/fix_use_kseccschecknestedcode_kseccsstrictvalidate_in_the_sec.patch Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org> Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
This commit is contained in:
parent
0af2b8de73
commit
956406a193
3 changed files with 153 additions and 6 deletions
|
@ -1,2 +1,3 @@
|
||||||
build_add_gn_config.patch
|
build_add_gn_config.patch
|
||||||
fix_ensure_that_self_is_retained_until_the_racsignal_is_complete.patch
|
fix_ensure_that_self_is_retained_until_the_racsignal_is_complete.patch
|
||||||
|
fix_use_kseccschecknestedcode_kseccsstrictvalidate_in_the_sec.patch
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Samuel Attard <samuel.r.attard@gmail.com>
|
||||||
|
Date: Mon, 21 Mar 2022 15:14:19 -0700
|
||||||
|
Subject: fix: use kSecCSCheckNestedCode | kSecCSStrictValidate in the Sec
|
||||||
|
validate call
|
||||||
|
|
||||||
|
This ensures that Squirrel.Mac validates the nested bundles (nested Frameworks) including the incoming Squirrel.Mac framework.
|
||||||
|
|
||||||
|
diff --git a/Squirrel/SQRLCodeSignature.m b/Squirrel/SQRLCodeSignature.m
|
||||||
|
index f8754dbd6a1490d2b50f1014e2daa5c1f71b2103..2f5e27c1ae5c5bd514abe33d4cd42c4724656c07 100644
|
||||||
|
--- a/Squirrel/SQRLCodeSignature.m
|
||||||
|
+++ b/Squirrel/SQRLCodeSignature.m
|
||||||
|
@@ -124,7 +124,7 @@ - (RACSignal *)verifyBundleAtURL:(NSURL *)bundleURL {
|
||||||
|
}
|
||||||
|
|
||||||
|
CFErrorRef validityError = NULL;
|
||||||
|
- result = SecStaticCodeCheckValidityWithErrors(staticCode, kSecCSCheckAllArchitectures, (__bridge SecRequirementRef)self.requirement, &validityError);
|
||||||
|
+ result = SecStaticCodeCheckValidityWithErrors(staticCode, kSecCSCheckNestedCode | kSecCSStrictValidate | kSecCSCheckAllArchitectures, (__bridge SecRequirementRef)self.requirement, &validityError);
|
||||||
|
@onExit {
|
||||||
|
if (validityError != NULL) CFRelease(validityError);
|
||||||
|
};
|
|
@ -6,14 +6,14 @@ import * as fs from 'fs-extra';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { AddressInfo } from 'net';
|
import { AddressInfo } from 'net';
|
||||||
import { ifdescribe } from './spec-helpers';
|
import { ifdescribe, ifit } from './spec-helpers';
|
||||||
|
|
||||||
const features = process._linkedBinding('electron_common_features');
|
const features = process._linkedBinding('electron_common_features');
|
||||||
|
|
||||||
const fixturesPath = path.resolve(__dirname, 'fixtures');
|
const fixturesPath = path.resolve(__dirname, 'fixtures');
|
||||||
|
|
||||||
// We can only test the auto updater on darwin non-component builds
|
// We can only test the auto updater on darwin non-component builds
|
||||||
ifdescribe(process.platform === 'darwin' && process.arch !== 'arm64' && !process.mas && !features.isComponentBuild())('autoUpdater behavior', function () {
|
ifdescribe(process.platform === 'darwin' && !(process.env.CI && process.arch === 'arm64') && !process.mas && !features.isComponentBuild())('autoUpdater behavior', function () {
|
||||||
this.timeout(120000);
|
this.timeout(120000);
|
||||||
|
|
||||||
let identity = '';
|
let identity = '';
|
||||||
|
@ -115,8 +115,11 @@ ifdescribe(process.platform === 'darwin' && process.arch !== 'arm64' && !process
|
||||||
|
|
||||||
const cachedZips: Record<string, string> = {};
|
const cachedZips: Record<string, string> = {};
|
||||||
|
|
||||||
const getOrCreateUpdateZipPath = async (version: string, fixture: string) => {
|
const getOrCreateUpdateZipPath = async (version: string, fixture: string, mutateAppPostSign?: {
|
||||||
const key = `${version}-${fixture}`;
|
mutate: (appPath: string) => Promise<void>,
|
||||||
|
mutationKey: string,
|
||||||
|
}) => {
|
||||||
|
const key = `${version}-${fixture}-${mutateAppPostSign?.mutationKey || 'no-mutation'}`;
|
||||||
if (!cachedZips[key]) {
|
if (!cachedZips[key]) {
|
||||||
let updateZipPath: string;
|
let updateZipPath: string;
|
||||||
await withTempDirectory(async (dir) => {
|
await withTempDirectory(async (dir) => {
|
||||||
|
@ -127,6 +130,7 @@ ifdescribe(process.platform === 'darwin' && process.arch !== 'arm64' && !process
|
||||||
(await fs.readFile(appPJPath, 'utf8')).replace('1.0.0', version)
|
(await fs.readFile(appPJPath, 'utf8')).replace('1.0.0', version)
|
||||||
);
|
);
|
||||||
await signApp(secondAppPath);
|
await signApp(secondAppPath);
|
||||||
|
await mutateAppPostSign?.mutate(secondAppPath);
|
||||||
updateZipPath = path.resolve(dir, 'update.zip');
|
updateZipPath = path.resolve(dir, 'update.zip');
|
||||||
await spawn('zip', ['-r', '--symlinks', updateZipPath, './'], {
|
await spawn('zip', ['-r', '--symlinks', updateZipPath, './'], {
|
||||||
cwd: dir
|
cwd: dir
|
||||||
|
@ -143,10 +147,12 @@ ifdescribe(process.platform === 'darwin' && process.arch !== 'arm64' && !process
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail to set the feed URL when the app is not signed', async () => {
|
// On arm64 builds the built app is self-signed by default so the setFeedURL call always works
|
||||||
|
ifit(process.arch !== 'arm64')('should fail to set the feed URL when the app is not signed', async () => {
|
||||||
await withTempDirectory(async (dir) => {
|
await withTempDirectory(async (dir) => {
|
||||||
const appPath = await copyApp(dir);
|
const appPath = await copyApp(dir);
|
||||||
const launchResult = await launchApp(appPath, ['http://myupdate']);
|
const launchResult = await launchApp(appPath, ['http://myupdate']);
|
||||||
|
console.log(launchResult);
|
||||||
expect(launchResult.code).to.equal(1);
|
expect(launchResult.code).to.equal(1);
|
||||||
expect(launchResult.out).to.include('Could not get code signature for running application');
|
expect(launchResult.out).to.include('Could not get code signature for running application');
|
||||||
});
|
});
|
||||||
|
@ -259,12 +265,16 @@ ifdescribe(process.platform === 'darwin' && process.arch !== 'arm64' && !process
|
||||||
nextVersion: string;
|
nextVersion: string;
|
||||||
startFixture: string;
|
startFixture: string;
|
||||||
endFixture: string;
|
endFixture: string;
|
||||||
|
mutateAppPostSign?: {
|
||||||
|
mutate: (appPath: string) => Promise<void>,
|
||||||
|
mutationKey: string,
|
||||||
|
}
|
||||||
}, fn: (appPath: string, zipPath: string) => Promise<void>) => {
|
}, fn: (appPath: string, zipPath: string) => Promise<void>) => {
|
||||||
await withTempDirectory(async (dir) => {
|
await withTempDirectory(async (dir) => {
|
||||||
const appPath = await copyApp(dir, opts.startFixture);
|
const appPath = await copyApp(dir, opts.startFixture);
|
||||||
await signApp(appPath);
|
await signApp(appPath);
|
||||||
|
|
||||||
const updateZipPath = await getOrCreateUpdateZipPath(opts.nextVersion, opts.endFixture);
|
const updateZipPath = await getOrCreateUpdateZipPath(opts.nextVersion, opts.endFixture, opts.mutateAppPostSign);
|
||||||
|
|
||||||
await fn(appPath, updateZipPath);
|
await fn(appPath, updateZipPath);
|
||||||
});
|
});
|
||||||
|
@ -311,6 +321,121 @@ ifdescribe(process.platform === 'darwin' && process.arch !== 'arm64' && !process
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should hit the download endpoint when an update is available and fail when the zip signature is invalid', async () => {
|
||||||
|
await withUpdatableApp({
|
||||||
|
nextVersion: '2.0.0',
|
||||||
|
startFixture: 'update',
|
||||||
|
endFixture: 'update',
|
||||||
|
mutateAppPostSign: {
|
||||||
|
mutationKey: 'add-resource',
|
||||||
|
mutate: async (appPath) => {
|
||||||
|
const resourcesPath = path.resolve(appPath, 'Contents', 'Resources', 'app', 'injected.txt');
|
||||||
|
await fs.writeFile(resourcesPath, 'demo');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, async (appPath, updateZipPath) => {
|
||||||
|
server.get('/update-file', (req, res) => {
|
||||||
|
res.download(updateZipPath);
|
||||||
|
});
|
||||||
|
server.get('/update-check', (req, res) => {
|
||||||
|
res.json({
|
||||||
|
url: `http://localhost:${port}/update-file`,
|
||||||
|
name: 'My Release Name',
|
||||||
|
notes: 'Theses are some release notes innit',
|
||||||
|
pub_date: (new Date()).toString()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const launchResult = await launchApp(appPath, [`http://localhost:${port}/update-check`]);
|
||||||
|
logOnError(launchResult, () => {
|
||||||
|
expect(launchResult).to.have.property('code', 1);
|
||||||
|
expect(launchResult.out).to.include('Code signature at URL');
|
||||||
|
expect(launchResult.out).to.include('a sealed resource is missing or invalid');
|
||||||
|
expect(requests).to.have.lengthOf(2);
|
||||||
|
expect(requests[0]).to.have.property('url', '/update-check');
|
||||||
|
expect(requests[1]).to.have.property('url', '/update-file');
|
||||||
|
expect(requests[0].header('user-agent')).to.include('Electron/');
|
||||||
|
expect(requests[1].header('user-agent')).to.include('Electron/');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should hit the download endpoint when an update is available and fail when the ShipIt binary is a symlink', async () => {
|
||||||
|
await withUpdatableApp({
|
||||||
|
nextVersion: '2.0.0',
|
||||||
|
startFixture: 'update',
|
||||||
|
endFixture: 'update',
|
||||||
|
mutateAppPostSign: {
|
||||||
|
mutationKey: 'modify-shipit',
|
||||||
|
mutate: async (appPath) => {
|
||||||
|
const shipItPath = path.resolve(appPath, 'Contents', 'Frameworks', 'Squirrel.framework', 'Resources', 'ShipIt');
|
||||||
|
await fs.remove(shipItPath);
|
||||||
|
await fs.symlink('/tmp/ShipIt', shipItPath, 'file');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, async (appPath, updateZipPath) => {
|
||||||
|
server.get('/update-file', (req, res) => {
|
||||||
|
res.download(updateZipPath);
|
||||||
|
});
|
||||||
|
server.get('/update-check', (req, res) => {
|
||||||
|
res.json({
|
||||||
|
url: `http://localhost:${port}/update-file`,
|
||||||
|
name: 'My Release Name',
|
||||||
|
notes: 'Theses are some release notes innit',
|
||||||
|
pub_date: (new Date()).toString()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const launchResult = await launchApp(appPath, [`http://localhost:${port}/update-check`]);
|
||||||
|
logOnError(launchResult, () => {
|
||||||
|
expect(launchResult).to.have.property('code', 1);
|
||||||
|
expect(launchResult.out).to.include('Code signature at URL');
|
||||||
|
expect(launchResult.out).to.include('a sealed resource is missing or invalid');
|
||||||
|
expect(requests).to.have.lengthOf(2);
|
||||||
|
expect(requests[0]).to.have.property('url', '/update-check');
|
||||||
|
expect(requests[1]).to.have.property('url', '/update-file');
|
||||||
|
expect(requests[0].header('user-agent')).to.include('Electron/');
|
||||||
|
expect(requests[1].header('user-agent')).to.include('Electron/');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should hit the download endpoint when an update is available and fail when the Electron Framework is modified', async () => {
|
||||||
|
await withUpdatableApp({
|
||||||
|
nextVersion: '2.0.0',
|
||||||
|
startFixture: 'update',
|
||||||
|
endFixture: 'update',
|
||||||
|
mutateAppPostSign: {
|
||||||
|
mutationKey: 'modify-eframework',
|
||||||
|
mutate: async (appPath) => {
|
||||||
|
const shipItPath = path.resolve(appPath, 'Contents', 'Frameworks', 'Electron Framework.framework', 'Electron Framework');
|
||||||
|
await fs.appendFile(shipItPath, Buffer.from('123'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, async (appPath, updateZipPath) => {
|
||||||
|
server.get('/update-file', (req, res) => {
|
||||||
|
res.download(updateZipPath);
|
||||||
|
});
|
||||||
|
server.get('/update-check', (req, res) => {
|
||||||
|
res.json({
|
||||||
|
url: `http://localhost:${port}/update-file`,
|
||||||
|
name: 'My Release Name',
|
||||||
|
notes: 'Theses are some release notes innit',
|
||||||
|
pub_date: (new Date()).toString()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const launchResult = await launchApp(appPath, [`http://localhost:${port}/update-check`]);
|
||||||
|
logOnError(launchResult, () => {
|
||||||
|
expect(launchResult).to.have.property('code', 1);
|
||||||
|
expect(launchResult.out).to.include('Code signature at URL');
|
||||||
|
expect(launchResult.out).to.include(' main executable failed strict validation');
|
||||||
|
expect(requests).to.have.lengthOf(2);
|
||||||
|
expect(requests[0]).to.have.property('url', '/update-check');
|
||||||
|
expect(requests[1]).to.have.property('url', '/update-file');
|
||||||
|
expect(requests[0].header('user-agent')).to.include('Electron/');
|
||||||
|
expect(requests[1].header('user-agent')).to.include('Electron/');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should hit the download endpoint when an update is available and update successfully when the zip is provided with JSON update mode', async () => {
|
it('should hit the download endpoint when an update is available and update successfully when the zip is provided with JSON update mode', async () => {
|
||||||
await withUpdatableApp({
|
await withUpdatableApp({
|
||||||
nextVersion: '2.0.0',
|
nextVersion: '2.0.0',
|
||||||
|
|
Loading…
Reference in a new issue