Vydané zdroje
Stáhnout zdroje ZIP
CH-J Server Manager
Procházejte adresáře a soubory konkrétního vydání aplikace.
Zdroje jsou zveřejněny pod CH-J Proprietary Software License 1.14. Jejich dostupnost nemění licenční podmínky ani neposkytuje další oprávnění.
1
"use strict";3
const fs = require("node:fs");4
const { Readable } = require("node:stream");5
const openpgp = require("openpgp");7
const TRUSTED_PRIMARY_FINGERPRINT = "0D92778AD8ECF85C80E3924848F2433AD9CDF453";8
const MAX_PUBLIC_KEY_BYTES = 128 * 1024;9
const MAX_SIGNATURE_BYTES = 128 * 1024;10
const DEFAULT_VERIFICATION_TIMEOUT_MS = 2 * 60 * 1000;12
class SignatureVerificationError extends Error {13
constructor(message, code = "UPDATE_SIGNATURE_INVALID", options = {}) {14
super(message, options);15
this.name = "SignatureVerificationError";16
this.code = code;17
}18
}20
function normalizeFingerprint(value) {21
return String(value || "").replace(/[^0-9a-f]/gi, "").toUpperCase();22
}24
function assertRegularFile(filePath, label, maxBytes) {25
let stat;26
try {27
stat = fs.lstatSync(filePath);28
} catch (error) {29
throw new SignatureVerificationError(`${label} is missing.`, "UPDATE_SIGNATURE_FILE_MISSING", { cause: error });30
}31
if (stat.isSymbolicLink() || !stat.isFile()) {32
throw new SignatureVerificationError(`${label} must be a regular file.`, "UPDATE_SIGNATURE_UNSAFE_FILE");33
}34
if (stat.size <= 0) {35
throw new SignatureVerificationError(`${label} is empty.`, "UPDATE_SIGNATURE_EMPTY");36
}37
if (maxBytes && stat.size > maxBytes) {38
throw new SignatureVerificationError(`${label} is too large.`, "UPDATE_SIGNATURE_TOO_LARGE");39
}40
return stat;41
}43
function withTimeout(operation, timeoutMs, onTimeout) {44
let timer;45
const timeout = new Promise((_, reject) => {46
timer = setTimeout(() => {47
try { onTimeout?.(); } catch {}48
reject(new SignatureVerificationError(49
"OpenPGP verification timed out.",50
"UPDATE_SIGNATURE_TIMEOUT"51
));52
}, timeoutMs);53
timer.unref?.();54
});55
return Promise.race([Promise.resolve(operation), timeout]).finally(() => clearTimeout(timer));56
}58
function packetFromVerificationResult(signatureResult) {59
return Promise.resolve(signatureResult.signature).then((signature) => signature?.packets?.[0] || null);60
}62
class OpenPgpVerifier {63
constructor(options = {}) {64
this.publicKeyPath = options.publicKeyPath;65
this.trustedPrimaryFingerprint = normalizeFingerprint(66
options.trustedPrimaryFingerprint || TRUSTED_PRIMARY_FINGERPRINT67
);68
this.logger = options.logger;69
this.verificationTimeoutMs = Number(options.verificationTimeoutMs || DEFAULT_VERIFICATION_TIMEOUT_MS);70
this.openpgp = options.openpgpImpl || openpgp;72
if (!this.publicKeyPath) throw new Error("A bundled OpenPGP public key path is required.");73
if (!/^[0-9A-F]{40}$/.test(this.trustedPrimaryFingerprint)) {74
throw new Error("The trusted primary OpenPGP fingerprint is invalid.");75
}76
if (!Number.isFinite(this.verificationTimeoutMs) || this.verificationTimeoutMs <= 0) {77
throw new Error("The OpenPGP verification timeout is invalid.");78
}79
}81
async verifyFile(artifactPath, signaturePath) {82
this.logger?.info("OpenPGP update verification started.", {83
artifact: String(artifactPath || "").split(/[\\/]/).pop() || null84
});85
let source;86
try {87
assertRegularFile(artifactPath, "Update artifact");88
assertRegularFile(signaturePath, "Detached OpenPGP signature", MAX_SIGNATURE_BYTES);89
assertRegularFile(this.publicKeyPath, "Bundled OpenPGP public key", MAX_PUBLIC_KEY_BYTES);91
const operation = (async () => {92
const armoredKey = fs.readFileSync(this.publicKeyPath, "utf8");93
let publicKey;94
try {95
publicKey = await this.openpgp.readKey({ armoredKey });96
} catch (error) {97
throw new SignatureVerificationError(98
"Bundled OpenPGP public key is malformed.",99
"UPDATE_PUBLIC_KEY_INVALID",100
{ cause: error }101
);102
}103
if (publicKey.isPrivate?.()) {104
throw new SignatureVerificationError("Bundled OpenPGP key contains private key material.");105
}107
const actualPrimaryFingerprint = normalizeFingerprint(publicKey.getFingerprint());108
this.logger?.info("Bundled OpenPGP public key loaded.", {109
primaryFingerprint: actualPrimaryFingerprint,110
trustedPrimaryFingerprint: this.trustedPrimaryFingerprint111
});112
if (actualPrimaryFingerprint !== this.trustedPrimaryFingerprint) {113
throw new SignatureVerificationError(114
"Bundled OpenPGP primary fingerprint does not match the trust anchor.",115
"UPDATE_PUBLIC_KEY_FINGERPRINT_MISMATCH"116
);117
}119
const verificationDate = new Date();120
try {121
await publicKey.verifyPrimaryKey(verificationDate);122
} catch (error) {123
throw new SignatureVerificationError(124
"Bundled OpenPGP primary key is revoked, expired, or invalid.",125
"UPDATE_PUBLIC_KEY_INVALID",126
{ cause: error }127
);128
}129
this.logger?.info("Trusted OpenPGP primary fingerprint verified.", {130
trustedPrimaryFingerprint: this.trustedPrimaryFingerprint131
});133
const armoredSignature = fs.readFileSync(signaturePath, "utf8");134
let signature;135
try {136
signature = await this.openpgp.readSignature({ armoredSignature });137
} catch (error) {138
throw new SignatureVerificationError(139
"Detached OpenPGP signature is malformed.",140
"UPDATE_SIGNATURE_MALFORMED",141
{ cause: error }142
);143
}145
source = fs.createReadStream(artifactPath);146
const message = await this.openpgp.createMessage({ binary: Readable.toWeb(source) });147
let verification;148
try {149
verification = await this.openpgp.verify({150
message,151
signature,152
verificationKeys: publicKey,153
expectSigned: true,154
format: "binary",155
date: verificationDate156
});157
} catch (error) {158
throw new SignatureVerificationError(159
"Detached OpenPGP signature verification failed.",160
"UPDATE_SIGNATURE_INVALID",161
{ cause: error }162
);163
}165
if (!Array.isArray(verification.signatures) || verification.signatures.length === 0) {166
throw new SignatureVerificationError("Detached OpenPGP signature has no signer.");167
}169
// OpenPGP.js verifies streaming messages while their data is consumed.170
try {171
for await (const _chunk of verification.data) {}172
} catch (error) {173
throw new SignatureVerificationError(174
"Detached OpenPGP signature is not cryptographically valid.",175
"UPDATE_SIGNATURE_INVALID",176
{ cause: error }177
);178
}180
const signers = [];181
for (const result of verification.signatures) {182
try {183
await result.verified;184
} catch (error) {185
throw new SignatureVerificationError(186
"Detached OpenPGP signature is not cryptographically valid.",187
"UPDATE_SIGNATURE_INVALID",188
{ cause: error }189
);190
}192
let signingKey;193
try {194
signingKey = await publicKey.getSigningKey(result.keyID, verificationDate);195
} catch (error) {196
throw new SignatureVerificationError(197
"The signature was not made by a valid signing subkey of the trusted primary key.",198
"UPDATE_SIGNATURE_UNTRUSTED_SIGNER",199
{ cause: error }200
);201
}203
const signingFingerprint = normalizeFingerprint(signingKey.getFingerprint());204
if (!signingFingerprint || signingFingerprint === actualPrimaryFingerprint) {205
throw new SignatureVerificationError(206
"Updates must be signed by a valid signing subkey of the trusted primary key.",207
"UPDATE_SIGNATURE_NOT_SIGNING_SUBKEY"208
);209
}211
const matchingSubkeys = publicKey.getSubkeys(result.keyID);212
const signingSubkey = matchingSubkeys.find(213
(subkey) => normalizeFingerprint(subkey.getFingerprint()) === signingFingerprint214
);215
if (!signingSubkey) {216
throw new SignatureVerificationError(217
"The signing subkey is not bound to the trusted primary key.",218
"UPDATE_SIGNATURE_UNTRUSTED_SIGNER"219
);220
}222
let bindingSignature;223
try {224
bindingSignature = await signingSubkey.verify(verificationDate);225
} catch (error) {226
throw new SignatureVerificationError(227
"The signing subkey is revoked, expired, or has an invalid binding.",228
"UPDATE_SIGNING_SUBKEY_INVALID",229
{ cause: error }230
);231
}232
const keyFlags = bindingSignature?.keyFlags || [];233
const hasSigningCapability = [...keyFlags].some(234
(flags) => (flags & this.openpgp.enums.keyFlags.signData) !== 0235
);236
if (!hasSigningCapability) {237
throw new SignatureVerificationError(238
"The OpenPGP subkey does not have signing capability.",239
"UPDATE_SIGNING_SUBKEY_CAPABILITY_INVALID"240
);241
}243
const signaturePacket = await packetFromVerificationResult(result);244
signers.push({245
fingerprint: signingFingerprint,246
createdAt: signaturePacket?.created instanceof Date247
? signaturePacket.created.toISOString()248
: null249
});250
}252
this.logger?.info("OpenPGP update signature verified.", {253
result: "valid",254
trustedPrimaryFingerprint: this.trustedPrimaryFingerprint,255
signingFingerprints: signers.map((signer) => signer.fingerprint)256
});257
return {258
valid: true,259
primaryFingerprint: actualPrimaryFingerprint,260
signingFingerprints: signers.map((signer) => signer.fingerprint),261
signatureCreatedAt: signers[0]?.createdAt || null262
};263
})();265
return await withTimeout(operation, this.verificationTimeoutMs, () => source?.destroy());266
} catch (error) {267
const failure = error instanceof SignatureVerificationError268
? error269
: new SignatureVerificationError(270
"OpenPGP verification failed unexpectedly.",271
"UPDATE_SIGNATURE_ERROR",272
{ cause: error }273
);274
this.logger?.warn("OpenPGP update verification failed.", {275
result: "invalid",276
code: failure.code,277
reason: failure.message,278
technicalReason: failure.cause?.message || null,279
trustedPrimaryFingerprint: this.trustedPrimaryFingerprint280
});281
throw failure;282
} finally {283
source?.destroy();284
}285
}286
}288
module.exports = {289
DEFAULT_VERIFICATION_TIMEOUT_MS,290
MAX_PUBLIC_KEY_BYTES,291
MAX_SIGNATURE_BYTES,292
OpenPgpVerifier,293
SignatureVerificationError,294
TRUSTED_PRIMARY_FINGERPRINT,295
normalizeFingerprint296
};SHA-256: 145a1fb7fabb0ffbfda72fd7503c6583c1e061dd9e0080a1c4c24961d2fce299
SHA-256 archivu: 5ac91caf4fa32a6fdb114f2430deed486fbe7489d5eea343d1f034169fafb5e0