export const ATTESTATION_HEADER = "pinboard-attestation"; export const ATTESTATION_TTL_S = 30; const encoder = new TextEncoder(); const b64url = (bytes: Uint8Array): string => { let binary = ""; for (const byte of bytes) binary += String.fromCharCode(byte); return btoa(binary) .replace(/\+/g, "-") .replace(/\//g, "_") .replace(/=+$/, ""); }; const b64urlJson = (value: unknown): string => b64url(encoder.encode(JSON.stringify(value))); const JOSE_HEADER = b64urlJson({ alg: "HS256", typ: "pinboard+jwt" }); export type AttestationClaims = { caller?: string; depth?: number; acct?: Record; }; export const mintAttestation = async ( secret: string, claims: { aud: string } & AttestationClaims, nowMs: number, ): Promise => { const iat = Math.floor(nowMs / 1000); const payload = b64urlJson({ aud: claims.aud, iat, exp: iat + ATTESTATION_TTL_S, ...(claims.caller !== undefined && { caller: claims.caller }), ...(claims.depth !== undefined && { depth: claims.depth }), ...(claims.acct !== undefined && { acct: claims.acct }), }); const signingInput = `${JOSE_HEADER}.${payload}`; const key = await crypto.subtle.importKey( "raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"], ); const mac = new Uint8Array( await crypto.subtle.sign("HMAC", key, encoder.encode(signingInput)), ); return `${signingInput}.${b64url(mac)}`; }; export const attestationHeaders = async ( worker: { sessionId: string; secret: string }, claims: AttestationClaims, nowMs: number, ): Promise> => ({ [ATTESTATION_HEADER]: await mintAttestation( worker.secret, { aud: worker.sessionId, ...claims }, nowMs, ), });