chore: initialize from frontend template 4dc033c
This commit is contained in:
@@ -0,0 +1,396 @@
|
||||
import { timingSafeEqual } from "node:crypto";
|
||||
|
||||
export type ProviderGuardianKind = "vulnerability" | "provenance";
|
||||
|
||||
export const MAX_PROVIDER_GUARDIAN_FRAME_PAYLOAD_BYTES = 4_092;
|
||||
export const MAX_PROVIDER_GUARDIAN_LEASE_MS = 40 * 60 * 1_000;
|
||||
export const MAX_PROVIDER_SEALED_BYTES = 8_388_608;
|
||||
const V2_GUARD_KEYS = ["type", "version", "kind", "nonce", "deadlineEpochMs"] as const;
|
||||
const READY_KEYS = [
|
||||
"type", "version", "nonce", "rawDev", "rawIno",
|
||||
"sealedTempLeaf", "sealedDev", "sealedIno",
|
||||
] as const;
|
||||
const PUBLISH_KEYS = [
|
||||
"type", "version", "nonce", "sealedDev", "sealedIno", "size", "sha256",
|
||||
] as const;
|
||||
const PUBLISHED_KEYS = ["type", "version", "nonce", "sealedDev", "sealedIno"] as const;
|
||||
const COMMIT_KEYS = ["type", "version", "nonce"] as const;
|
||||
|
||||
export type ProviderGuardianGuard = Readonly<{
|
||||
kind: ProviderGuardianKind;
|
||||
nonce: Buffer;
|
||||
deadlineEpochMs: number;
|
||||
}>;
|
||||
|
||||
export type ProviderGuardianReady = Readonly<{
|
||||
nonce: Buffer;
|
||||
rawDev: number;
|
||||
rawIno: number;
|
||||
sealedTempLeaf: string;
|
||||
sealedDev: number;
|
||||
sealedIno: number;
|
||||
}>;
|
||||
|
||||
export type ProviderGuardianPublish = Readonly<{
|
||||
nonce: Buffer;
|
||||
sealedDev: number;
|
||||
sealedIno: number;
|
||||
size: number;
|
||||
sha256: string;
|
||||
}>;
|
||||
|
||||
export type ProviderGuardianPublished = Readonly<{
|
||||
nonce: Buffer;
|
||||
sealedDev: number;
|
||||
sealedIno: number;
|
||||
}>;
|
||||
|
||||
export function providerGuardianSealedTempLeaf(
|
||||
kind: ProviderGuardianKind,
|
||||
nonce: Buffer,
|
||||
): string {
|
||||
const rawLeaf = baseLeaf(kind);
|
||||
assertV2Nonce(nonce);
|
||||
return `.${rawLeaf}.guardian-${nonce.subarray(0, 16).toString("hex")}.tmp`;
|
||||
}
|
||||
|
||||
export function providerGuardianRawStagingLeaf(
|
||||
kind: ProviderGuardianKind,
|
||||
nonce: Buffer,
|
||||
): string {
|
||||
const rawLeaf = baseLeaf(kind);
|
||||
assertV2Nonce(nonce);
|
||||
return `.${rawLeaf}.guardian-${nonce.subarray(0, 16).toString("hex")}.raw.tmp`;
|
||||
}
|
||||
|
||||
export function encodeProviderGuardianGuard(input: ProviderGuardianGuard): Buffer {
|
||||
assertV2Guard(input);
|
||||
return prefixFrame(encodeV2GuardPayload(input));
|
||||
}
|
||||
|
||||
export function decodeProviderGuardianGuard(
|
||||
payload: Buffer,
|
||||
options: Readonly<{ nowEpochMs: number; maxLeaseMs: number }>,
|
||||
): ProviderGuardianGuard {
|
||||
const value = parseRecord(payload, V2_GUARD_KEYS, "guard");
|
||||
const guard: ProviderGuardianGuard = {
|
||||
kind: value.kind as ProviderGuardianKind,
|
||||
nonce: parseV2Nonce(value.nonce),
|
||||
deadlineEpochMs: value.deadlineEpochMs as number,
|
||||
};
|
||||
if (value.type !== "guard" || value.version !== 2) {
|
||||
throw new TypeError("provider guardian guard version is invalid");
|
||||
}
|
||||
assertV2Guard(guard);
|
||||
if (
|
||||
!Number.isSafeInteger(options.nowEpochMs) ||
|
||||
!Number.isSafeInteger(options.maxLeaseMs) || options.maxLeaseMs <= 0 ||
|
||||
guard.deadlineEpochMs <= options.nowEpochMs ||
|
||||
guard.deadlineEpochMs > options.nowEpochMs + options.maxLeaseMs
|
||||
) {
|
||||
throw new TypeError("provider guardian guard deadline is invalid");
|
||||
}
|
||||
assertCanonical(payload, encodeV2GuardPayload(guard), "guard");
|
||||
return guard;
|
||||
}
|
||||
|
||||
export function encodeProviderGuardianReady(input: ProviderGuardianReady): Buffer {
|
||||
assertReady(input);
|
||||
return prefixFrame(encodeReadyPayload(input));
|
||||
}
|
||||
|
||||
export function decodeProviderGuardianReady(
|
||||
payload: Buffer,
|
||||
expectedNonce: Buffer,
|
||||
): ProviderGuardianReady {
|
||||
assertV2Nonce(expectedNonce);
|
||||
const value = parseRecord(payload, READY_KEYS, "READY");
|
||||
const ready: ProviderGuardianReady = {
|
||||
nonce: parseV2Nonce(value.nonce),
|
||||
rawDev: value.rawDev as number,
|
||||
rawIno: value.rawIno as number,
|
||||
sealedTempLeaf: value.sealedTempLeaf as string,
|
||||
sealedDev: value.sealedDev as number,
|
||||
sealedIno: value.sealedIno as number,
|
||||
};
|
||||
if (value.type !== "ready" || value.version !== 2) {
|
||||
throw new TypeError("provider guardian READY version is invalid");
|
||||
}
|
||||
assertReady(ready);
|
||||
assertAuthenticatedNonce(ready.nonce, expectedNonce, "READY");
|
||||
assertCanonical(payload, encodeReadyPayload(ready), "READY");
|
||||
return ready;
|
||||
}
|
||||
|
||||
export function encodeProviderGuardianPublish(input: ProviderGuardianPublish): Buffer {
|
||||
assertPublish(input);
|
||||
return prefixFrame(encodePublishPayload(input));
|
||||
}
|
||||
|
||||
export function decodeProviderGuardianPublish(
|
||||
payload: Buffer,
|
||||
expectedNonce: Buffer,
|
||||
): ProviderGuardianPublish {
|
||||
assertV2Nonce(expectedNonce);
|
||||
const value = parseRecord(payload, PUBLISH_KEYS, "publish");
|
||||
const publish: ProviderGuardianPublish = {
|
||||
nonce: parseV2Nonce(value.nonce),
|
||||
sealedDev: value.sealedDev as number,
|
||||
sealedIno: value.sealedIno as number,
|
||||
size: value.size as number,
|
||||
sha256: value.sha256 as string,
|
||||
};
|
||||
if (value.type !== "publish" || value.version !== 2) {
|
||||
throw new TypeError("provider guardian publish version is invalid");
|
||||
}
|
||||
assertPublish(publish);
|
||||
assertAuthenticatedNonce(publish.nonce, expectedNonce, "publish");
|
||||
assertCanonical(payload, encodePublishPayload(publish), "publish");
|
||||
return publish;
|
||||
}
|
||||
|
||||
export function encodeProviderGuardianPublished(input: ProviderGuardianPublished): Buffer {
|
||||
assertPublished(input);
|
||||
return prefixFrame(encodePublishedPayload(input));
|
||||
}
|
||||
|
||||
export function decodeProviderGuardianPublished(
|
||||
payload: Buffer,
|
||||
expectedNonce: Buffer,
|
||||
expectedIdentity: Readonly<{ dev: number; ino: number }>,
|
||||
): void {
|
||||
assertV2Nonce(expectedNonce);
|
||||
const value = parseRecord(payload, PUBLISHED_KEYS, "PUBLISHED");
|
||||
const published: ProviderGuardianPublished = {
|
||||
nonce: parseV2Nonce(value.nonce),
|
||||
sealedDev: value.sealedDev as number,
|
||||
sealedIno: value.sealedIno as number,
|
||||
};
|
||||
if (value.type !== "published" || value.version !== 2) {
|
||||
throw new TypeError("provider guardian PUBLISHED version is invalid");
|
||||
}
|
||||
assertPublished(published);
|
||||
assertAuthenticatedNonce(published.nonce, expectedNonce, "PUBLISHED");
|
||||
if (published.sealedDev !== expectedIdentity.dev || published.sealedIno !== expectedIdentity.ino) {
|
||||
throw new TypeError("provider guardian PUBLISHED identity is invalid");
|
||||
}
|
||||
assertCanonical(payload, encodePublishedPayload(published), "PUBLISHED");
|
||||
}
|
||||
|
||||
function encodeV2GuardPayload(input: ProviderGuardianGuard): Buffer {
|
||||
return Buffer.from(JSON.stringify({
|
||||
type: "guard",
|
||||
version: 2,
|
||||
kind: input.kind,
|
||||
nonce: input.nonce.toString("hex"),
|
||||
deadlineEpochMs: input.deadlineEpochMs,
|
||||
}));
|
||||
}
|
||||
|
||||
export function encodeProviderGuardianCommit(nonce: Buffer): Buffer {
|
||||
assertV2Nonce(nonce);
|
||||
return prefixFrame(encodeCommitPayload(nonce));
|
||||
}
|
||||
|
||||
export function decodeProviderGuardianCommit(payload: Buffer, expectedNonce: Buffer): void {
|
||||
assertPayloadSize(payload);
|
||||
assertV2Nonce(expectedNonce);
|
||||
const decoded = decodeUtf8(payload);
|
||||
let value: unknown;
|
||||
try {
|
||||
value = JSON.parse(decoded);
|
||||
} catch {
|
||||
throw new TypeError("provider guardian commit JSON is invalid");
|
||||
}
|
||||
if (!isRecord(value) || !hasExactKeys(value, COMMIT_KEYS)) {
|
||||
throw new TypeError("provider guardian commit fields are invalid");
|
||||
}
|
||||
const nonce = parseV2Nonce(value.nonce);
|
||||
if (
|
||||
value.type !== "commit" || value.version !== 2 ||
|
||||
nonce.byteLength !== expectedNonce.byteLength ||
|
||||
!timingSafeEqual(nonce, expectedNonce)
|
||||
) {
|
||||
throw new TypeError("provider guardian commit authentication failed");
|
||||
}
|
||||
if (!payload.equals(encodeCommitPayload(nonce))) {
|
||||
throw new TypeError("provider guardian commit is not canonical");
|
||||
}
|
||||
}
|
||||
|
||||
function encodeCommitPayload(nonce: Buffer): Buffer {
|
||||
return Buffer.from(JSON.stringify({
|
||||
type: "commit",
|
||||
version: 2,
|
||||
nonce: nonce.toString("hex"),
|
||||
}));
|
||||
}
|
||||
|
||||
function encodeReadyPayload(input: ProviderGuardianReady): Buffer {
|
||||
return Buffer.from(JSON.stringify({
|
||||
type: "ready",
|
||||
version: 2,
|
||||
nonce: input.nonce.toString("hex"),
|
||||
rawDev: input.rawDev,
|
||||
rawIno: input.rawIno,
|
||||
sealedTempLeaf: input.sealedTempLeaf,
|
||||
sealedDev: input.sealedDev,
|
||||
sealedIno: input.sealedIno,
|
||||
}));
|
||||
}
|
||||
|
||||
function encodePublishPayload(input: ProviderGuardianPublish): Buffer {
|
||||
return Buffer.from(JSON.stringify({
|
||||
type: "publish",
|
||||
version: 2,
|
||||
nonce: input.nonce.toString("hex"),
|
||||
sealedDev: input.sealedDev,
|
||||
sealedIno: input.sealedIno,
|
||||
size: input.size,
|
||||
sha256: input.sha256,
|
||||
}));
|
||||
}
|
||||
|
||||
function encodePublishedPayload(input: ProviderGuardianPublished): Buffer {
|
||||
return Buffer.from(JSON.stringify({
|
||||
type: "published",
|
||||
version: 2,
|
||||
nonce: input.nonce.toString("hex"),
|
||||
sealedDev: input.sealedDev,
|
||||
sealedIno: input.sealedIno,
|
||||
}));
|
||||
}
|
||||
|
||||
function prefixFrame(payload: Buffer): Buffer {
|
||||
if (payload.byteLength <= 0 || payload.byteLength > MAX_PROVIDER_GUARDIAN_FRAME_PAYLOAD_BYTES) {
|
||||
throw new TypeError("provider guardian frame size is invalid");
|
||||
}
|
||||
const frame = Buffer.allocUnsafe(payload.byteLength + 4);
|
||||
frame.writeUInt32BE(payload.byteLength, 0);
|
||||
payload.copy(frame, 4);
|
||||
return frame;
|
||||
}
|
||||
|
||||
function assertV2Guard(input: ProviderGuardianGuard): void {
|
||||
if (
|
||||
(input.kind !== "vulnerability" && input.kind !== "provenance") ||
|
||||
!isV2Nonce(input.nonce) ||
|
||||
!Number.isSafeInteger(input.deadlineEpochMs) || input.deadlineEpochMs <= 0
|
||||
) {
|
||||
throw new TypeError("provider guardian guard fields are invalid");
|
||||
}
|
||||
}
|
||||
|
||||
function baseLeaf(kind: ProviderGuardianKind): string {
|
||||
if (kind === "vulnerability") return "vulnerability-report.json";
|
||||
if (kind === "provenance") return "provenance-attestation.json";
|
||||
throw new TypeError("provider guardian kind is invalid");
|
||||
}
|
||||
|
||||
function assertReady(input: ProviderGuardianReady): void {
|
||||
if (
|
||||
!isV2Nonce(input.nonce) ||
|
||||
!isIdentityPart(input.rawDev) || !isIdentityPart(input.rawIno) ||
|
||||
typeof input.sealedTempLeaf !== "string" ||
|
||||
!/^\.(?:vulnerability-report|provenance-attestation)\.json\.guardian-[0-9a-f]{32}\.tmp$/u
|
||||
.test(input.sealedTempLeaf) ||
|
||||
!isIdentityPart(input.sealedDev) || !isIdentityPart(input.sealedIno)
|
||||
) {
|
||||
throw new TypeError("provider guardian READY fields are invalid");
|
||||
}
|
||||
}
|
||||
|
||||
function assertPublish(input: ProviderGuardianPublish): void {
|
||||
if (
|
||||
!isV2Nonce(input.nonce) ||
|
||||
!isIdentityPart(input.sealedDev) || !isIdentityPart(input.sealedIno) ||
|
||||
!Number.isSafeInteger(input.size) || input.size <= 0 || input.size > MAX_PROVIDER_SEALED_BYTES ||
|
||||
typeof input.sha256 !== "string" || !/^[0-9a-f]{64}$/u.test(input.sha256)
|
||||
) {
|
||||
throw new TypeError("provider guardian publish fields are invalid");
|
||||
}
|
||||
}
|
||||
|
||||
function assertPublished(input: ProviderGuardianPublished): void {
|
||||
if (
|
||||
!isV2Nonce(input.nonce) ||
|
||||
!isIdentityPart(input.sealedDev) || !isIdentityPart(input.sealedIno)
|
||||
) {
|
||||
throw new TypeError("provider guardian PUBLISHED fields are invalid");
|
||||
}
|
||||
}
|
||||
|
||||
function assertV2Nonce(nonce: Buffer): void {
|
||||
if (!isV2Nonce(nonce)) throw new TypeError("provider guardian nonce is invalid");
|
||||
}
|
||||
|
||||
function isV2Nonce(nonce: Buffer): boolean {
|
||||
return Buffer.isBuffer(nonce) && nonce.byteLength === 32;
|
||||
}
|
||||
|
||||
function parseV2Nonce(value: unknown): Buffer {
|
||||
return typeof value === "string" && /^[0-9a-f]{64}$/u.test(value)
|
||||
? Buffer.from(value, "hex")
|
||||
: Buffer.alloc(0);
|
||||
}
|
||||
|
||||
function isIdentityPart(value: unknown): value is number {
|
||||
return Number.isSafeInteger(value) && (value as number) > 0;
|
||||
}
|
||||
|
||||
function assertAuthenticatedNonce(received: Buffer, expected: Buffer, label: string): void {
|
||||
if (received.byteLength !== expected.byteLength || !timingSafeEqual(received, expected)) {
|
||||
throw new TypeError(`provider guardian ${label} authentication failed`);
|
||||
}
|
||||
}
|
||||
|
||||
function parseRecord(
|
||||
payload: Buffer,
|
||||
expectedKeys: readonly string[],
|
||||
label: string,
|
||||
): Record<string, unknown> {
|
||||
assertPayloadSize(payload);
|
||||
let value: unknown;
|
||||
try {
|
||||
value = JSON.parse(decodeUtf8(payload));
|
||||
} catch (error) {
|
||||
if (error instanceof TypeError && /provider guardian/u.test(error.message)) throw error;
|
||||
throw new TypeError(`provider guardian ${label} JSON is invalid`, { cause: error });
|
||||
}
|
||||
if (!isRecord(value) || !hasExactKeys(value, expectedKeys)) {
|
||||
throw new TypeError(`provider guardian ${label} fields are invalid`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function assertCanonical(payload: Buffer, canonical: Buffer, label: string): void {
|
||||
if (!payload.equals(canonical)) {
|
||||
throw new TypeError(`provider guardian ${label} frame is not canonical`);
|
||||
}
|
||||
}
|
||||
|
||||
function assertPayloadSize(payload: Buffer): void {
|
||||
if (!Buffer.isBuffer(payload) || payload.byteLength <= 0 || payload.byteLength > MAX_PROVIDER_GUARDIAN_FRAME_PAYLOAD_BYTES) {
|
||||
throw new TypeError("provider guardian frame size is invalid");
|
||||
}
|
||||
}
|
||||
|
||||
function decodeUtf8(payload: Buffer): string {
|
||||
let decoded: string;
|
||||
try {
|
||||
decoded = new TextDecoder("utf-8", { fatal: true }).decode(payload);
|
||||
} catch {
|
||||
throw new TypeError("provider guardian frame UTF-8 is invalid");
|
||||
}
|
||||
if (decoded.includes("\0")) throw new TypeError("provider guardian frame contains NUL");
|
||||
return decoded;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
}
|
||||
|
||||
function hasExactKeys(value: Record<string, unknown>, expected: readonly string[]): boolean {
|
||||
const keys = Object.keys(value);
|
||||
return keys.length === expected.length && keys.every((key, index) => key === expected[index]);
|
||||
}
|
||||
Reference in New Issue
Block a user