feat(KAN-65): add signed macOS updates and DMG distribution
CI/CD / preconditions (pull_request) Successful in 2s
Native JavaScript checks / Native launcher and release tests (pull_request) Successful in 1m0s
CI/CD / test_and_check_app (pull_request) Successful in 7m2s
CI/CD / Publish immutable image (pull_request) Skipped
macOS checks / Native Rust tests and unsigned build (pull_request) Successful in 1m35s
CI/CD / preconditions (pull_request) Successful in 2s
Native JavaScript checks / Native launcher and release tests (pull_request) Successful in 1m0s
CI/CD / test_and_check_app (pull_request) Successful in 7m2s
CI/CD / Publish immutable image (pull_request) Skipped
macOS checks / Native Rust tests and unsigned build (pull_request) Successful in 1m35s
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
import { Buffer } from 'node:buffer';
|
||||
import { createHash, createPublicKey, verify } from 'node:crypto';
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
export const updateSource = JSON.parse(
|
||||
readFileSync(new URL('./update-source.json', import.meta.url), 'utf8'),
|
||||
);
|
||||
export function compareVersions(left, right) {
|
||||
const pattern = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
||||
if (!pattern.test(left) || !pattern.test(right)) {
|
||||
throw new Error(
|
||||
'Update versions must be stable MAJOR.MINOR.PATCH versions.',
|
||||
);
|
||||
}
|
||||
const a = left.split('.').map(BigInt);
|
||||
const b = right.split('.').map(BigInt);
|
||||
for (let i = 0; i < 3; i++) {
|
||||
if (a[i] !== b[i]) {
|
||||
return a[i] > b[i] ? 1 : -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
export function verifyUpdateSignature(
|
||||
bytes,
|
||||
signature,
|
||||
publicKey = updateSource.publicKey,
|
||||
) {
|
||||
try {
|
||||
const key = Buffer.from(
|
||||
Buffer.from(publicKey, 'base64')
|
||||
.toString('utf8')
|
||||
.trim()
|
||||
.split('\n')[1],
|
||||
'base64',
|
||||
);
|
||||
const lines = Buffer.from(signature.trim(), 'base64')
|
||||
.toString('utf8')
|
||||
.trim()
|
||||
.split('\n');
|
||||
const signed = Buffer.from(lines[1], 'base64');
|
||||
if (
|
||||
key.length !== 42 ||
|
||||
signed.length !== 74 ||
|
||||
!key.subarray(2, 10).equals(signed.subarray(2, 10)) ||
|
||||
!lines[2].startsWith('trusted comment: ')
|
||||
) {
|
||||
throw new Error('Invalid signature structure');
|
||||
}
|
||||
const algorithm = signed.subarray(0, 2).toString();
|
||||
if (!['Ed', 'ED'].includes(algorithm)) {
|
||||
throw new Error('Unsupported signature');
|
||||
}
|
||||
const cryptoKey = createPublicKey({
|
||||
key: Buffer.concat([
|
||||
Buffer.from('302a300506032b6570032100', 'hex'),
|
||||
key.subarray(10),
|
||||
]),
|
||||
format: 'der',
|
||||
type: 'spki',
|
||||
});
|
||||
const contents =
|
||||
algorithm === 'ED'
|
||||
? createHash('blake2b512').update(bytes).digest()
|
||||
: bytes;
|
||||
if (
|
||||
!verify(null, contents, cryptoKey, signed.subarray(10)) ||
|
||||
!verify(
|
||||
null,
|
||||
Buffer.concat([
|
||||
signed.subarray(10),
|
||||
Buffer.from(lines[2].slice('trusted comment: '.length)),
|
||||
]),
|
||||
cryptoKey,
|
||||
Buffer.from(lines[3], 'base64'),
|
||||
)
|
||||
) {
|
||||
throw new Error('Signature mismatch');
|
||||
}
|
||||
} catch {
|
||||
throw new Error(
|
||||
'Updater archive signature does not match the permanent public key.',
|
||||
);
|
||||
}
|
||||
}
|
||||
export function updateManifest(metadata, publicPrefix, signature, notes) {
|
||||
compareVersions(metadata.version, '0.0.0');
|
||||
const platform = {
|
||||
'aarch64-apple-darwin': 'darwin-aarch64',
|
||||
'x86_64-apple-darwin': 'darwin-x86_64',
|
||||
}[metadata.target];
|
||||
if (!platform || !signature.trim()) {
|
||||
throw new Error('Invalid updater platform or missing signature.');
|
||||
}
|
||||
return {
|
||||
version: metadata.version,
|
||||
notes: notes?.trim() || `KanbanGit ${metadata.version}`,
|
||||
platforms: {
|
||||
[platform]: {
|
||||
url: `${publicPrefix}/${metadata.updaterFilename}`,
|
||||
signature: signature.trim(),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user