feat(coding): add marketplace package trust primitives

This commit is contained in:
2026-08-28 12:57:50 +08:00
parent 4d8b1fcec0
commit 898e2b7bdd
7 changed files with 1386 additions and 16 deletions

View File

@@ -0,0 +1,155 @@
import { Buffer } from 'node:buffer';
import { createHash, createPublicKey, verify } from 'node:crypto';
import type { KeyObject } from 'node:crypto';
import {
isMakeLoreVersionCompatible,
parsePluginReleaseDescriptor,
serializePluginReleaseDescriptor,
type PluginReleaseDescriptor,
} from './release-descriptor';
import {
createCodeOwnedPluginTrustStore,
type PluginSigningKeyStore,
} from './trusted-keys';
export type PluginSignatureFailureCode =
| 'plugin_signature_invalid'
| 'plugin_artifact_invalid'
| 'plugin_incompatible_client';
export type PluginSignatureVerificationResult =
| { readonly ok: true }
| { readonly ok: false; readonly code: PluginSignatureFailureCode; readonly message: string };
export interface PluginSignatureVerifierOptions {
readonly keyStore?: PluginSigningKeyStore | ReadonlyMap<string, Uint8Array | string>;
readonly clientVersion: string;
}
export interface PluginSignatureVerifier {
verify(input: {
readonly keyId: string;
readonly signature: string | Uint8Array;
readonly descriptor: PluginReleaseDescriptor;
readonly artifact: Uint8Array;
}): PluginSignatureVerificationResult;
}
function failure(code: PluginSignatureFailureCode, message: string): PluginSignatureVerificationResult {
return { ok: false, code, message };
}
function keyFromStore(
keyStore: PluginSigningKeyStore | ReadonlyMap<string, Uint8Array | string>,
keyId: string,
): Uint8Array | string | null {
const key = keyStore.get(keyId);
return key === undefined ? null : key;
}
function publicKey(value: Uint8Array | string): KeyObject | null {
try {
if (typeof value === 'string' && !value.includes('PUBLIC KEY')) return null;
const key = typeof value === 'string'
? createPublicKey(value)
: createPublicKey({ key: Buffer.from(value), format: 'der', type: 'spki' });
return key.asymmetricKeyType === 'ed25519' ? key : null;
} catch {
return null;
}
}
function signatureBytes(value: string | Uint8Array): Buffer | null {
try {
if (typeof value === 'string'
&& (value.length !== 86 || !/^[A-Za-z0-9_-]+$/u.test(value))) return null;
const bytes = typeof value === 'string' ? Buffer.from(value, 'base64url') : Buffer.from(value);
return bytes.length === 64 ? bytes : null;
} catch {
return null;
}
}
function artifactDigest(artifact: Uint8Array): string {
return createHash('sha256').update(artifact).digest('hex');
}
export function createPluginSignatureVerifier(
options: PluginSignatureVerifierOptions,
): PluginSignatureVerifier {
const keyStore = options.keyStore ?? createCodeOwnedPluginTrustStore();
return {
verify(input) {
if (!(input.artifact instanceof Uint8Array)) {
return failure('plugin_artifact_invalid', 'artifact bytes are invalid');
}
let descriptorBytes: Uint8Array;
try {
descriptorBytes = serializePluginReleaseDescriptor(input.descriptor);
} catch {
return failure('plugin_signature_invalid', 'descriptor is invalid');
}
if (input.artifact.byteLength !== input.descriptor.artifact.sizeBytes
|| artifactDigest(input.artifact) !== input.descriptor.artifact.sha256) {
return failure('plugin_artifact_invalid', 'artifact bytes do not match the descriptor');
}
if (!isCompatibleClient(options.clientVersion, input.descriptor)) {
return failure('plugin_incompatible_client', 'client version is outside the Release range');
}
const encodedKey = keyFromStore(keyStore, input.keyId);
if (encodedKey === null) return failure('plugin_signature_invalid', 'signing key is not trusted');
const key = publicKey(encodedKey);
const signature = signatureBytes(input.signature);
if (!key || !signature || !verify(null, descriptorBytes, key, signature)) {
return failure('plugin_signature_invalid', 'descriptor signature is invalid');
}
return { ok: true };
},
};
}
function isCompatibleClient(clientVersion: string, descriptor: PluginReleaseDescriptor): boolean {
// Kept local to avoid making the verifier infer a range from untrusted fields.
return isMakeLoreVersionCompatible(
clientVersion,
descriptor.minMakeloreVersion,
descriptor.maxMakeloreVersion,
);
}
export interface VerifyPluginReleaseSignatureInput {
readonly verifier?: PluginSignatureVerifier;
readonly keyId: string;
readonly signature: string | Uint8Array;
readonly descriptor: PluginReleaseDescriptor | Uint8Array | string;
readonly artifact: Uint8Array;
readonly clientVersion?: string;
readonly keyStore?: PluginSigningKeyStore | ReadonlyMap<string, Uint8Array | string>;
}
export function verifyPluginReleaseSignature(
input: VerifyPluginReleaseSignatureInput,
): PluginSignatureVerificationResult {
const descriptor = input.descriptor instanceof Uint8Array || typeof input.descriptor === 'string'
? (() => {
try {
return parsePluginReleaseDescriptor(input.descriptor as Uint8Array | string);
} catch {
return null;
}
})()
: input.descriptor;
if (!descriptor) return failure('plugin_signature_invalid', 'descriptor is invalid');
const verifier = input.verifier ?? createPluginSignatureVerifier({
keyStore: input.keyStore,
clientVersion: input.clientVersion ?? '',
});
return verifier.verify({
keyId: input.keyId,
signature: input.signature,
descriptor,
artifact: input.artifact,
});
}
export const verifyReleaseSignature = verifyPluginReleaseSignature;