背景与价值
HTTP消息签名以标准化组件列表构建签名基串并下发签名头,资源端可验证消息完整性与来源可信度,降低重放与篡改风险。
统一规范
- 组件列表:明确签名包含的头、方法与路径(如 `@method`、`@path`、`content-digest`)。
- 算法限定:采用 `ES256`;`keyid` 标识公钥并支持轮换。
- 时间窗口:可选 `created/expires` 提供短期有效窗口。
核心实现
组件与基串构建
```ts
type Req = { method: string; path: string; headers: Record }
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)
```ts
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 { return crypto.subtle.importKey('pkcs8', pkcs8, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['sign']) }
async function importPublicKey(spki: ArrayBuffer): Promise { return crypto.subtle.importKey('spki', spki, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']) }
async function signBase(base: string, key: CryptoKey): Promise { 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 { return crypto.subtle.verify({ name: 'ECDSA', hash: 'SHA-256' }, key, buf(sigB64u), enc(base)) }
```
窗口校验
```ts
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` 轮换策略。
验证清单
- 组件列表是否正确;签名算法与基串校验是否通过;时间窗口是否有效。
发表评论 取消回复