使用Web Crypto API签名CloudKit请求遇认证失败问题排查
CloudKit Web服务Web Crypto API认证失败问题
已验证可用的Node Crypto实现代码
import Crypto from 'crypto' export async function cloudKitRequest(body, method, operationSubpath) { const subpath = `/database/1/${env.CLOUDKIT_CONTAINER}/${env.CLOUDKIT_ENVIRONMENT}/${operationSubpath}` const bodyString = JSON.stringify(body) const hash = Crypto.createHash('sha256') const sign = Crypto.createSign('SHA256') const date = new Date().toISOString().replace(/\.[0-9]+?Z/, 'Z') hash.update(bodyString, 'utf8') sign.update(`${date}:${hash.digest('base64')}:${subpath}`) return await fetch(`https://api.apple-cloudkit.com${subpath}`, { body: bodyString, method: method, headers: { 'Content-Type': 'application/json', 'X-Apple-Cloudkit-Request-KeyID': env.CLOUDKIT_KEY_ID, 'X-Apple-CloudKit-Request-ISO8601Date': date, 'X-Apple-CloudKit-Request-SignatureV1': sign.sign( env.CLOUDKIT_PRIVATE_KEY, 'base64' ), }, }) }
出现认证错误的Web Crypto实现代码
const encoder = new TextEncoder() export async function cloudKitRequest(body: any, method: string, operationSubpath: string, env: any) { const subpath = `/database/1/${env.CLOUDKIT_CONTAINER}/${env.CLOUDKIT_ENVIRONMENT}/${operationSubpath}` const date = new Date().toISOString().replace(/\.[0-9]+?Z/, 'Z') const bodyHash = await hashBody(body) const message = `${date}:${bodyHash}:${subpath}` const privateKey = await loadPrivateKey(env.CLOUDKIT_PRIVATE_KEY) const signature = await signMessage(privateKey, message) const headers = new Headers({ 'Content-Type': 'application/json', 'X-Apple-CloudKit-Request-KeyID': env.CLOUDKIT_KEY_ID, 'X-Apple-CloudKit-Request-ISO8601Date': date, 'X-Apple-CloudKit-Request-SignatureV1': signature, }) const options = { method: method, headers: headers, body: JSON.stringify(body), } return await fetch(`https://api.apple-cloudkit.com${subpath}`, options) } const hashBody = async (requestBody: any) => { const encodedBody = encoder.encode(JSON.stringify(requestBody)) const hashBuffer = await crypto.subtle.digest('SHA-256', encodedBody) return btoa(String.fromCharCode(...new Uint8Array(hashBuffer))) } const b642ab = (base64_string: string) => { return Uint8Array.from(atob(base64_string), (c) => c.charCodeAt(0)) } const signMessage = async (privateKey: CryptoKey, message: string) => { const encoder = new TextEncoder() const encodedMessage = encoder.encode(message) const signature = await crypto.subtle.sign( { name: 'ECDSA', hash: { name: 'SHA-256' }, }, privateKey, encodedMessage ) return btoa(String.fromCharCode(...new Uint8Array(signature))) } const loadPrivateKey = async (pem: string) => { const binaryDer = b642ab(pem) const importParams = { name: 'ECDSA', namedCurve: 'P-256', } return await crypto.subtle.importKey('pkcs8', binaryDer.buffer, importParams, true, ['sign']) }
问题详情
通过Node Crypto API实现CloudKit Web服务请求认证已验证可用,但切换到Web Crypto API后始终返回“Authentication Failed”错误,已确认请求体哈希计算正确。
私钥通过命令 openssl ecparam -name prime256v1 -genkey -noout -out eckey.pem 生成,Node环境直接使用该pem文件的完整内容即可正常工作;Web Crypto环境中,尝试过直接使用原pem内容,也尝试过通过 openssl pkcs8 -topk8 -nocrypt -in eckey.pem -out eckey_okcs8.pem 转换为PKCS8格式,移除头部、尾部和换行后使用,均无法通过认证。
示例调用代码
await cloudKitRequest({ operations: { operationType: 'create', recordName: 'randomuuid', record: { recordType: 'MyRecord', fields: { name: { value: 'Test Name', } }, }, }, }, 'POST', 'public/records/modify', context.env )
内容的提问来源于stack exchange,提问作者Finn Voorhees
相关产品推荐
相关产品推荐

