mirror of
https://github.com/morluto/rea.git
synced 2026-11-04 05:46:17 +00:00
Replace fixed Linux Hopper release pins with current vendor feed metadata, complete detached-PGP package verification under the stable publisher primary key, and authenticated package evidence for offline admission. Share parsed publisher facts across approved setup, doctor and the pre-execution guard. Preserve existing ownership, cancellation, private-display and cleanup rules. Fixes #3577 Normalize empty XDG settings, preserve backups during approved cache repair, and retain publisher cleanup through cancellation and shutdown. Exercise cold offline verification without reusing process-local proof state. Serialize approved cache publication across processes, restore old evidence on failure, and share the existing lease owner from process without changing its identities. Validation: 438 focused tests (six platform-related skips), full static/documentation checks, real Ubuntu 24.04 official Hopper 6.6.0 setup/CLI/MCP, offline ordinary-user admission, launcher mutation rejection, and cold/warm verification measurements. Official RPM metadata mismatch is tracked separately in #3578 and fails closed. BREAKING CHANGE: Older Linux Hopper builds admitted by the fixed allowlist now require retained publisher-signed package evidence or an approved update to the current public demo. Update explicit HOPPER_LAUNCHER_PATH values when migrating; the live feed exposes only its current release. Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com> Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
102 lines
3.0 KiB
TypeScript
102 lines
3.0 KiB
TypeScript
import { err, ok, type Result } from "../domain/result.js";
|
|
|
|
export class DetachedPgpSignatureError extends Error {
|
|
readonly _tag = "DetachedPgpSignatureError";
|
|
constructor(
|
|
readonly reason: "invalid_signature" | "wrong_publisher" | "cancelled",
|
|
message: string,
|
|
options?: ErrorOptions,
|
|
) {
|
|
super(message, options);
|
|
this.name = this._tag;
|
|
}
|
|
}
|
|
|
|
/** Verify the complete binary payload and its signing subkey's primary identity. */
|
|
export const verifyDetachedPgpSignature = async (
|
|
bytes: Uint8Array,
|
|
detachedSignature: Uint8Array,
|
|
armoredPublicKey: string,
|
|
primaryFingerprint: string,
|
|
signal?: AbortSignal,
|
|
): Promise<
|
|
Result<
|
|
{
|
|
readonly primaryFingerprint: string;
|
|
readonly signingFingerprint: string;
|
|
},
|
|
DetachedPgpSignatureError
|
|
>
|
|
> => {
|
|
const cancelled = () =>
|
|
err(
|
|
new DetachedPgpSignatureError(
|
|
"cancelled",
|
|
"Package verification was cancelled.",
|
|
),
|
|
);
|
|
const isAborted = () => signal?.aborted === true;
|
|
if (isAborted()) return cancelled();
|
|
try {
|
|
// Keep the cryptographic implementation out of unrelated CLI cold starts.
|
|
const pgp = await import("openpgp");
|
|
const key = await pgp.readKey({ armoredKey: armoredPublicKey });
|
|
if (isAborted()) return cancelled();
|
|
const fingerprint = key.getFingerprint().toUpperCase();
|
|
if (fingerprint !== primaryFingerprint)
|
|
return err(
|
|
new DetachedPgpSignatureError(
|
|
"wrong_publisher",
|
|
"The package signing key does not belong to the pinned publisher.",
|
|
),
|
|
);
|
|
const signature = await pgp.readSignature({
|
|
binarySignature: detachedSignature,
|
|
});
|
|
if (isAborted()) return cancelled();
|
|
if (
|
|
signature.packets.length !== 1 ||
|
|
signature.packets[0]?.signatureType !== pgp.enums.signature.binary
|
|
)
|
|
return err(
|
|
new DetachedPgpSignatureError(
|
|
"invalid_signature",
|
|
"Expected one detached binary package signature.",
|
|
),
|
|
);
|
|
const verified = await pgp.verify({
|
|
message: await pgp.createMessage({ binary: bytes }),
|
|
signature,
|
|
verificationKeys: key,
|
|
format: "binary",
|
|
});
|
|
const signed = verified.signatures[0];
|
|
if (signed === undefined || verified.signatures.length !== 1)
|
|
return err(
|
|
new DetachedPgpSignatureError(
|
|
"invalid_signature",
|
|
"The package has no unique verified signature.",
|
|
),
|
|
);
|
|
await signed.verified;
|
|
const signingKey = await key.getSigningKey(
|
|
signed.keyID,
|
|
signature.packets[0].created,
|
|
);
|
|
if (isAborted()) return cancelled();
|
|
return ok({
|
|
primaryFingerprint: fingerprint,
|
|
signingFingerprint: signingKey.getFingerprint().toUpperCase(),
|
|
});
|
|
} catch (cause: unknown) {
|
|
if (isAborted()) return cancelled();
|
|
return err(
|
|
new DetachedPgpSignatureError(
|
|
"invalid_signature",
|
|
"The package signature could not be verified.",
|
|
{ cause },
|
|
),
|
|
);
|
|
}
|
|
};
|