如何从AttestationObject中正确提取公钥?(Passkeys服务端)
Passkeys注册阶段服务端提取公钥及验证问题
研究Passkeys时,注册阶段无法从客户端发送的attestationData中正确提取公钥。Web端教程多依赖JavaScript的getPublicKey()方法,但Flutter的Passkeys库没有这个方法,只能在NodeJS服务端手动提取,尝试多种方法均未解决。
测试数据
const attestation = { "attestationData": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVikSZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2NFAAAAAK3OAAI1vMYKZIsLJfHwVQMAILxBOipox2vwkvO0SAE7mme20vs2iVsGk8VPFu/6lQPxpQECAyYgASFYIIGsoPOPLUt8AB40ssEf95YNmqgO16rKvXydLpU+A3TSIlggLr7aHpKAoRMWN1lGVRiBsMS1kdB10QEf1pxryoDQZ8A=", "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoib202bjVIdFVjZ1Q4T0NiTjJUZXQ4OWJtU3BFaUhpNUY3aWlZOVhQZmFUSSIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCIsImNyb3NzT3JpZ2luIjpmYWxzZSwib3RoZXJfa2V5c19jYW5fYmVfYWRkZWRfaGVyZSI6ImRvIG5vdCBjb21wYXJlIGNsaWVudERhdGFKU09OIGFnYWluc3QgYSB0ZW1wbGF0ZS4gU2VlIGh0dHBzOi8vZ29vLmdsL3lhYlBleCJ9", "publicKey": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgayg848tS3wAHjSywR/3lg2aqA7Xqsq9fJ0ulT4DdNIuvtoekoChExY3WUZVGIGwxLWR0HXRAR/WnGvKgNBnwA==" } const assertion = { "signature": "MEYCIQCaO2mh+E8SEWUOGW1XLMPq3z/LofM67/vUr6ut/Z9apgIhALWuIhawe+nzWjd//Zd670IxrP9gksMW0o7Gh/FYHkcG", "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2MFAAAAAA==" }
Base64编解码方法
function decodeBase64(data) { return Uint8Array.from(atob(data).split(""), (x) => x.charCodeAt(0)); } function encodeBase64(data) { return btoa(String.fromCharCode(...new Uint8Array(data))); }
NodeJS提取公钥步骤
- 使用CBOR解码attestationData:
const ctapMakeCredResp = cbor.decodeFirstSync(decodeBase64(attestation.attestationData)); const authData = ctapMakeCredResp.authData; const rpidHash = authData.subarray(0, 32); const flags = authData.subarray(32, 33); const counter = authData.subarray(33, 37).readUint32BE(0); const aaguid = authData.subarray(37, 53); const credIdLength = authData.subarray(53, 55).readUInt16BE(0); const credID = authData.subarray(55, 55 + credIdLength); const COSEPublicKey = authData.subarray(55 + credIdLength);
- 解码COSEPublicKey得到CBOR Map:
const publicKeyMap = cbor.decodeFirstSync(COSEPublicKey); const publicKeyData = { kty: publicKeyMap.get(1), alg: publicKeyMap.get(3), crv: publicKeyMap.get(-1), x: publicKeyMap.get(-2), y: publicKeyMap.get(-3), };
提取及公钥生成问题
- 调试发现:从前端Base64公钥提取的Y值与COSEPublicKey中的一致,但X值始终不同。
- 尝试以下两种方式生成公钥时,均触发
Error: error:0680009B:asn1 encoding routines::too long错误:
const publicKey = crypto.createPublicKey({ key: Buffer.from([0x04, ...publicKeyData.x, ...publicKeyData.y]), format: 'der', type: 'spki', });
const publicKey = await crypto.subtle.importKey( 'spki', Buffer.from([0x04, publicKeyData.x, publicKeyData.y]), { name: 'ECDSA', namedCurve: 'P-256' }, true, ['verify'] );
签名验证问题
使用前端提供的Base64公钥导入后,签名验证结果为false,验证代码如下:
const signature = decodeBase64(authSignature); const authDataDecoded = decodeBase64(authenticatorData); const clientData = decodeBase64(clientDataJSON); const rawSig = fromAsn1DERtoRSSignature(signature, 256); const digest = concatBuffer( authDataDecoded, await crypto.subtle.digest('SHA-256', clientData) ); const result = await crypto.subtle.verify( { name: 'ECDSA', hash: { name: 'SHA-256' } }, publicKey, rawSig, digest );
内容的提问来源于stack exchange,提问作者Leo Letto
相关产品推荐
相关产品推荐

