This commit is contained in:
Oleg Maslov
2026-09-02 10:14:22 +02:00
parent 0c3e2ead3b
commit b20b138fe4
771 changed files with 161561 additions and 9027 deletions

View File

@@ -16,12 +16,10 @@ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
// The pure helpers below mirror app/scripts/signing-config.ts so this CLI has
// zero TS-loader dependency at runtime. The .ts version is the canonical
// implementation tested by signing-config.test.ts (19 cases covering parse,
// merge, idempotency, immutability). Keep the two implementations in lockstep:
// any change to parseThumbprintString or addWindowsSigningToOverride below
// MUST be mirrored in signing-config.ts and vice versa.
// The pure helpers below mirror the certificate-store helpers in
// app/scripts/signing-config.ts so this pilot CLI has zero TS-loader dependency
// at runtime. Keep parseThumbprintString and addWindowsSigningToOverride in
// lockstep with the canonical TypeScript implementation.
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
const APP_DIR = resolve(SCRIPT_DIR, '..');
@@ -33,11 +31,12 @@ const OVERRIDE_PATH = resolve(
'tauri.build-override.conf.json',
);
const THUMBPRINT_PATH = resolve(APP_DIR, 'src-tauri', '.thumbprint.txt');
const DEFAULT_DIGEST_ALGORITHM = 'sha256';
const DEFAULT_TIMESTAMP_URL = 'http://timestamp.digicert.com';
const THUMBPRINT_LENGTH = 40;
const HEX_PATTERN = /^[0-9A-F]+$/;
const WINDOWS_SIGNING_MODE =
process.env.WAGGLE_WINDOWS_SIGNING_MODE ?? 'certificate-store';
function parseThumbprintString(raw) {
if (!raw || raw.trim().length === 0) {
@@ -59,13 +58,15 @@ function addWindowsSigningToOverride(config, thumbprint, options = {}) {
const existingBundle = config.bundle ?? {};
const existingWindows = existingBundle.windows ?? {};
const nonCustomCommandWindows = { ...existingWindows };
delete nonCustomCommandWindows.signCommand;
return {
...config,
bundle: {
...existingBundle,
windows: {
...existingWindows,
...nonCustomCommandWindows,
certificateThumbprint: normalisedThumbprint,
digestAlgorithm,
timestampUrl,
@@ -77,6 +78,20 @@ function addWindowsSigningToOverride(config, thumbprint, options = {}) {
// ─── Main ───────────────────────────────────────────────────────────────────
function main() {
if (!['certificate-store', 'artifact-signing'].includes(WINDOWS_SIGNING_MODE)) {
console.error(
`[apply-signing-config] unsupported WAGGLE_WINDOWS_SIGNING_MODE: ${WINDOWS_SIGNING_MODE}`,
);
process.exit(1);
}
if (WINDOWS_SIGNING_MODE === 'artifact-signing') {
console.error(
'[apply-signing-config] Azure Artifact Signing is hosted-only. '
+ 'Run the protected GitHub-hosted release workflow; this local helper cannot issue '
+ 'the immutable build receipt, protected OIDC identity, session manifest, or callback ledger.',
);
process.exit(1);
}
if (!existsSync(THUMBPRINT_PATH)) {
console.error(
`[apply-signing-config] thumbprint file missing: ${THUMBPRINT_PATH}`,
@@ -93,7 +108,6 @@ function main() {
process.exit(1);
}
const rawThumbprint = readFileSync(THUMBPRINT_PATH, 'utf8');
const overrideRaw = readFileSync(OVERRIDE_PATH, 'utf8');
let override;
@@ -108,6 +122,7 @@ function main() {
let updated;
try {
const rawThumbprint = readFileSync(THUMBPRINT_PATH, 'utf8');
updated = addWindowsSigningToOverride(override, rawThumbprint);
} catch (err) {
console.error(
@@ -122,9 +137,9 @@ function main() {
writeFileSync(OVERRIDE_PATH, serialised, 'utf8');
const relativePath = OVERRIDE_PATH.replace(REPO_ROOT, '').replace(/^\\/, '');
console.log(
`[apply-signing-config] wrote thumbprint ${updated.bundle.windows.certificateThumbprint.slice(0, 8)}... to ${relativePath}`,
);
const signingDescription =
`thumbprint ${updated.bundle.windows.certificateThumbprint.slice(0, 8)}...`;
console.log(`[apply-signing-config] wrote ${signingDescription} to ${relativePath}`);
}
main();

File diff suppressed because it is too large Load Diff

View File

@@ -7,11 +7,10 @@
# Usage:
# ./sign-macos-adhoc.sh <path-to-Waggle.app>
#
# Tauri's bundle config (tauri.build-override.conf.json) already passes
# `signingIdentity: "-"` to codesign at build time, so the produced .app is
# already ad-hoc-signed. This script:
# Ordinary `npm run tauri:build:mac` does not load the optional build override.
# Treat the input as unsigned until this script signs and verifies it. This script:
#
# 1. Re-signs the bundle with --force --deep to catch any nested helpers
# 1. Signs or re-signs the bundle with --force --deep to catch nested helpers
# (sidecar binary, native deps) that Tauri's pass missed.
# 2. Verifies the signature with --verify --deep --strict.
#

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -155,9 +155,8 @@ if ($Mode -eq 'Setup') {
Write-Host "[setup] thumbprint -> $ThumbprintFile" -ForegroundColor Green
Write-Host ''
Write-Host 'Next:' -ForegroundColor Cyan
Write-Host ' 1. cd app && npm run tauri:sign:pilot:win:apply'
Write-Host ' 2. npm run tauri:build:win'
Write-Host ' 3. .\scripts\sign-windows-pilot.ps1 -Mode Sign -ArtifactPath <path-to-msi>'
Write-Host ' 1. npm run tauri:build:win:pilot-signed'
Write-Host ' 2. .\scripts\sign-windows-pilot.ps1 -Mode Sign -ArtifactPath <path-to-msi> # optional'
return
}

View File

@@ -1,6 +1,10 @@
import { describe, it, expect } from 'vitest';
import { spawnSync } from 'node:child_process';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import {
parseThumbprintString,
addWindowsArtifactSigningToOverride,
addWindowsSigningToOverride,
addMacosAdhocToOverride,
type TauriOverrideConfig,
@@ -86,6 +90,23 @@ describe('addWindowsSigningToOverride', () => {
expect(out.bundle?.windows?.certificateThumbprint).toBe(VALID_THUMBPRINT);
});
it('removes an Azure signCommand when returning to certificate-store signing', () => {
const azure: TauriOverrideConfig = addWindowsArtifactSigningToOverride(
{
bundle: {
windows: { nsis: { installMode: 'currentUser' } },
},
},
String.raw`D:\a\waggle-os\app\scripts\sign-windows-artifact.ps1`,
);
const out = addWindowsSigningToOverride(azure, VALID_THUMBPRINT);
expect(out.bundle?.windows?.signCommand).toBeUndefined();
expect(out.bundle?.windows?.certificateThumbprint).toBe(VALID_THUMBPRINT);
expect(out.bundle?.windows?.nsis).toEqual({ installMode: 'currentUser' });
});
it('overrides custom digestAlgorithm and timestampUrl when options provided', () => {
const out = addWindowsSigningToOverride({}, VALID_THUMBPRINT, {
digestAlgorithm: 'sha384',
@@ -127,6 +148,154 @@ describe('addWindowsSigningToOverride', () => {
});
});
// ─── addWindowsArtifactSigningToOverride ───────────────────────────────────
describe('addWindowsArtifactSigningToOverride', () => {
const WRAPPER_PATH = String.raw`D:\a\waggle-os\app\scripts\sign-windows-artifact.ps1`;
const SYSTEM_POWERSHELL_PATH =
String.raw`C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe`;
it('configures an object-form Tauri signCommand with one artifact placeholder', () => {
const out = addWindowsArtifactSigningToOverride(
{},
WRAPPER_PATH,
);
expect(out.bundle?.windows?.signCommand).toEqual({
cmd: SYSTEM_POWERSHELL_PATH,
args: [
'-NoLogo',
'-NoProfile',
'-NonInteractive',
'-ExecutionPolicy',
'Bypass',
'-File',
WRAPPER_PATH,
'-ArtifactPath',
'%1',
],
});
});
it('removes mutually exclusive certificate-store signing fields', () => {
const input: TauriOverrideConfig = {
bundle: {
windows: {
certificateThumbprint: 'AB'.repeat(20),
digestAlgorithm: 'sha256',
timestampUrl: 'http://timestamp.digicert.com',
tsp: true,
nsis: { installMode: 'currentUser' },
},
},
};
const out = addWindowsArtifactSigningToOverride(
input,
WRAPPER_PATH,
);
expect(out.bundle?.windows?.certificateThumbprint).toBeUndefined();
expect(out.bundle?.windows?.digestAlgorithm).toBeUndefined();
expect(out.bundle?.windows?.timestampUrl).toBeUndefined();
expect(out.bundle?.windows?.tsp).toBeUndefined();
expect(out.bundle?.windows?.nsis).toEqual({ installMode: 'currentUser' });
});
it('is immutable and idempotent', () => {
const input: TauriOverrideConfig = {
build: {
beforeBuildCommand: 'npm run build',
beforeBundleCommand: 'node mutate-bundle.mjs',
},
bundle: {
active: false,
targets: ['msi'],
windows: { nsis: { installMode: 'currentUser' } },
},
};
const snapshot = JSON.parse(JSON.stringify(input));
const once = addWindowsArtifactSigningToOverride(
input,
WRAPPER_PATH,
);
const twice = addWindowsArtifactSigningToOverride(
once,
WRAPPER_PATH,
);
expect(input).toEqual(snapshot);
expect(twice).toEqual(once);
expect(once.build).toEqual({
beforeBuildCommand: '',
beforeBundleCommand: '',
});
expect(once.bundle?.active).toBe(true);
expect(once.bundle?.targets).toEqual(['nsis']);
expect(once.bundle?.windows?.nsis).toEqual({ installMode: 'currentUser' });
});
it('rejects non-absolute, placeholder-bearing, or control-character wrapper paths', () => {
expect(() =>
addWindowsArtifactSigningToOverride(
{},
'scripts/sign.ps1',
),
).toThrow(/absolute Windows path/i);
expect(() =>
addWindowsArtifactSigningToOverride(
{},
String.raw`D:\a\%1\sign-windows-artifact.ps1`,
),
).toThrow(/placeholder/i);
expect(() =>
addWindowsArtifactSigningToOverride(
{},
'D:\\safe\nmalicious.ps1',
),
).toThrow(/control characters/i);
expect(() =>
addWindowsArtifactSigningToOverride(
{},
String.raw`D:\safe\..\malicious.ps1`,
),
).toThrow(/canonical local Windows/i);
expect(() =>
addWindowsArtifactSigningToOverride(
{},
String.raw`D:\safe\sign.ps1:payload`,
),
).toThrow(/canonical local Windows/i);
});
it('contains exactly one artifact placeholder across the complete command', () => {
const out = addWindowsArtifactSigningToOverride({}, WRAPPER_PATH);
const command = out.bundle?.windows?.signCommand;
const placeholderCount = [command?.cmd, ...(command?.args ?? [])]
.flatMap((part) => part?.match(/%1/g) ?? [])
.length;
expect(placeholderCount).toBe(1);
});
});
describe('apply-signing-config Artifact Signing boundary', () => {
it('fails closed toward the protected hosted release workflow, never local Build mode', () => {
const scriptDir = dirname(fileURLToPath(import.meta.url));
const result = spawnSync(
process.execPath,
[resolve(scriptDir, 'apply-signing-config.mjs')],
{
cwd: resolve(scriptDir, '..'),
env: { ...process.env, WAGGLE_WINDOWS_SIGNING_MODE: 'artifact-signing' },
encoding: 'utf8',
},
);
const output = `${result.stdout}\n${result.stderr}`;
expect(result.status).not.toBe(0);
expect(output).toMatch(/protected GitHub-hosted release workflow/i);
expect(output).not.toMatch(/-Mode Build/i);
});
});
// ─── addMacosAdhocToOverride ────────────────────────────────────────────────
describe('addMacosAdhocToOverride', () => {

View File

@@ -18,10 +18,17 @@
export interface TauriBundleWindows {
certificateThumbprint?: string;
digestAlgorithm?: string;
signCommand?: TauriSignCommand;
timestampUrl?: string;
tsp?: boolean;
[key: string]: unknown;
}
export interface TauriSignCommand {
cmd: string;
args: string[];
}
export interface TauriBundleMacOS {
signingIdentity?: string;
[key: string]: unknown;
@@ -34,6 +41,7 @@ export interface TauriBundle {
}
export interface TauriOverrideConfig {
build?: Record<string, unknown>;
bundle?: TauriBundle;
[key: string]: unknown;
}
@@ -50,6 +58,39 @@ const DEFAULT_TIMESTAMP_URL = 'http://timestamp.digicert.com';
const MACOS_ADHOC_IDENTITY = '-';
const THUMBPRINT_LENGTH = 40;
const HEX_PATTERN = /^[0-9A-F]+$/;
const WINDOWS_ABSOLUTE_PATH_PATTERN = /^[A-Za-z]:[\\/]/;
const WINDOWS_POWERSHELL_PATH =
String.raw`C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe`;
function containsControlCharacter(value: string): boolean {
return [...value].some((character) => {
const codePoint = character.codePointAt(0) ?? 0;
return codePoint <= 31 || codePoint === 127;
});
}
function assertCanonicalWindowsFilePath(value: string): void {
if (containsControlCharacter(value)) {
throw new Error('Artifact Signing wrapper path contains control characters.');
}
if (!WINDOWS_ABSOLUTE_PATH_PATTERN.test(value)) {
throw new Error('Artifact Signing wrapper must use an absolute Windows path.');
}
const pathTail = value.slice(3);
const segments = pathTail.split(/[\\/]/);
if (
pathTail.length === 0
|| value.slice(2).includes(':')
|| segments.some(
(segment) => segment.length === 0
|| segment === '.'
|| segment === '..'
|| /[. ]$/.test(segment),
)
) {
throw new Error('Artifact Signing wrapper must use a canonical local Windows file path.');
}
}
// ─── parseThumbprintString ──────────────────────────────────────────────────
@@ -82,7 +123,7 @@ export function parseThumbprintString(raw: string): string {
* Return a new override config with Windows code-signing fields applied.
*
* Preserves all existing top-level and bundle fields; replaces only the
* three signing-specific keys under `bundle.windows`. Idempotent — calling
* signing-specific keys under `bundle.windows`. Idempotent — calling
* twice with the same thumbprint yields an equal result.
*/
export function addWindowsSigningToOverride<T extends TauriOverrideConfig>(
@@ -96,9 +137,11 @@ export function addWindowsSigningToOverride<T extends TauriOverrideConfig>(
const existingBundle: TauriBundle = config.bundle ?? {};
const existingWindows: TauriBundleWindows = existingBundle.windows ?? {};
const nonCustomCommandWindows: TauriBundleWindows = { ...existingWindows };
delete nonCustomCommandWindows.signCommand;
const nextWindows: TauriBundleWindows = {
...existingWindows,
...nonCustomCommandWindows,
certificateThumbprint: normalisedThumbprint,
digestAlgorithm,
timestampUrl,
@@ -115,6 +158,73 @@ export function addWindowsSigningToOverride<T extends TauriOverrideConfig>(
};
}
// ─── addWindowsArtifactSigningToOverride ───────────────────────────────────
/**
* Return a new override config that delegates every Tauri Windows signing
* target to the fail-closed Azure Artifact Signing wrapper.
*
* Tauri replaces `%1` with each binary path. Object form keeps the absolute
* wrapper path intact when the checkout contains spaces. Certificate-store
* fields are removed because Tauri must not combine them with `signCommand`.
*/
export function addWindowsArtifactSigningToOverride<
T extends TauriOverrideConfig,
>(config: Readonly<T>, wrapperPath: string): T {
assertCanonicalWindowsFilePath(wrapperPath);
if (wrapperPath.includes('%1')) {
throw new Error('Artifact Signing wrapper path cannot contain the %1 placeholder.');
}
const existingBuild = config.build ?? {};
const existingBundle: TauriBundle = config.bundle ?? {};
const existingWindows: TauriBundleWindows = existingBundle.windows ?? {};
const nonSigningWindows: TauriBundleWindows = { ...existingWindows };
delete nonSigningWindows.certificateThumbprint;
delete nonSigningWindows.digestAlgorithm;
delete nonSigningWindows.timestampUrl;
delete nonSigningWindows.tsp;
const nextWindows: TauriBundleWindows = {
...nonSigningWindows,
signCommand: {
cmd: WINDOWS_POWERSHELL_PATH,
args: [
'-NoLogo',
'-NoProfile',
'-NonInteractive',
'-ExecutionPolicy',
'Bypass',
'-File',
wrapperPath,
'-ArtifactPath',
'%1',
],
},
};
const placeholderCount = [
nextWindows.signCommand?.cmd,
...(nextWindows.signCommand?.args ?? []),
].flatMap((part) => part?.match(/%1/g) ?? []).length;
if (placeholderCount !== 1) {
throw new Error('Artifact Signing command must contain exactly one %1 placeholder.');
}
return {
...config,
build: {
...existingBuild,
beforeBuildCommand: '',
beforeBundleCommand: '',
},
bundle: {
...existingBundle,
active: true,
targets: ['nsis'],
windows: nextWindows,
},
};
}
// ─── addMacosAdhocToOverride ────────────────────────────────────────────────
/**

View File

@@ -0,0 +1,45 @@
import { readFileSync } from 'node:fs';
import { describe, expect, it } from 'vitest';
const playbook = readFileSync(
new URL('../../docs/code-signing-pilot-and-launch.md', import.meta.url),
'utf8',
);
const pilotScript = readFileSync(
new URL('./sign-windows-pilot.ps1', import.meta.url),
'utf8',
);
const macPilotScript = readFileSync(
new URL('./sign-macos-adhoc.sh', import.meta.url),
'utf8',
);
describe('internal pilot signing guidance', () => {
it('routes Windows builds through the explicit pilot-signing override', () => {
expect(playbook).toContain('npm run tauri:build:win:pilot-signed');
expect(playbook).not.toMatch(/^npm run tauri:build:win$/m);
expect(pilotScript).toContain(
"Write-Host ' 1. npm run tauri:build:win:pilot-signed'",
);
expect(pilotScript).not.toMatch(
/Write-Host '[ ]{2}1\. npm run tauri:build:win'\s*$/m,
);
expect(pilotScript).not.toMatch(/Write-Host '[ ]+1\. cd app/);
});
it('does not claim an ordinary macOS build loads the signing override', () => {
expect(playbook).toContain('macOS is deferred');
expect(playbook).toContain('npm run tauri:sign:pilot:mac:adhoc');
expect(playbook).not.toContain(
'so every `npm run tauri:build:mac` produces an ad-hoc-signed `.app` automatically',
);
expect(playbook).not.toContain(
'ships the macOS ad-hoc identity in the build-override config by default',
);
expect(macPilotScript).toContain(
'Treat the input as unsigned until this script signs and verifies it.',
);
expect(macPilotScript).not.toContain('already passes');
});
});