背景与价值HTTP消息签名以标准化组件列表构建签名基串并下发签名头,资源端可验证消息完整性与来源可信度,降低重放与篡改风险。统一规范组件列表:明确签名包含的头、方法与路径(如 `@method`、`@path`、`content-digest`)。算法限定:采用 `ES256`;`keyid` 标识公钥并支持轮换。时间窗口:可选 `created/expires` 提供短期有效窗口。核心实现组件与基串构建type Req = { method: string; path: string; headers: Record<string, string | undefined> } type Component = '@method' | '@path' | string // header name in lower-case function h(req: Req, k: string): string { return (req.headers[k] || '').trim() } function buildBase(req: Req, components: Component[]): string { const lines: string[] = [] for (const c of components) { if (c === '@method') lines.push(`"@method": ${req.method.toLowerCase()}`) else if (c === '@path') lines.push(`"@path": ${req.path}`) else lines.push(`"${c}": ${h(req, c)}`) } return lines.join('\n') } function buildSignatureInput(components: Component[], params: { alg: 'ES256'; keyid: string; created?: number; expires?: number }): string { const items = components.map(c => c).join(' ') const p: string[] = [`alg=${params.alg}`, `keyid="${params.keyid}"`] if (params.created) p.push(`created=${params.created}`) if (params.expires) p.push(`expires=${params.expires}`) return `(${items});` + p.join('; ') } 签名与验证(ES256)function enc(s: string): Uint8Array { return new TextEncoder().encode(s) } function b64u(b: ArrayBuffer): string { const u = new Uint8Array(b); let s=''; for (let i=0;i<u.length;i++) s+=String.fromCharCode(u[i]); return btoa(s).replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,'') } function buf(s: string): ArrayBuffer { const b = atob(s.replace(/-/g,'+').replace(/_/g,'/')); const u = new Uint8Array(b.length); for (let i=0;i<b.length;i++) u[i]=b.charCodeAt(i); return u.buffer } async function importPrivateKey(pkcs8: ArrayBuffer): Promise<CryptoKey> { return crypto.subtle.importKey('pkcs8', pkcs8, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['sign']) } async function importPublicKey(spki: ArrayBuffer): Promise<CryptoKey> { return crypto.subtle.importKey('spki', spki, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']) } async function signBase(base: string, key: CryptoKey): Promise<string> { const raw = await crypto.subtle.sign({ name: 'ECDSA', hash: 'SHA-256' }, key, enc(base)); return b64u(raw) } async function verifyBase(base: string, sigB64u: string, key: CryptoKey): Promise<boolean> { return crypto.subtle.verify({ name: 'ECDSA', hash: 'SHA-256' }, key, buf(sigB64u), enc(base)) } 窗口校验function timeNow(): number { return Math.floor(Date.now()/1000) } function within(created?: number, expires?: number, leeway = 300): boolean { const n = timeNow(); if (created && created - leeway > n) return false; if (expires && expires + leeway < n) return false; return true } 落地建议在请求包含 `Signature-Input` 与 `Signature`;组件与参数明确,启用时间窗口。资源端按组件构建基串并验签,校验 `created/expires` 与 `keyid` 轮换策略。验证清单组件列表是否正确;签名算法与基串校验是否通过;时间窗口是否有效。

发表评论 取消回复