Node.js Crypto无法验证FIDO2公钥与签名问题求助
解决AWS Cognito Passkey登录中COSE公钥验证的OpenSSL解码错误
问题根源
你遇到的error:1E08010C:DECODER routines::unsupported错误,本质是Node.js的crypto.verify不支持直接使用裸的椭圆曲线(EC)公钥字节(你拼接的0x04+x+y格式)。该方法要求密钥必须是带ASN.1结构的标准格式(如PEM或DER),而你当前传入的裸公钥缺少必要的编码包装,导致OpenSSL无法解析。
解决方案
方案1:在Node.js端将COSE密钥转为PEM格式
需要把从COSE解析出的裸公钥,包装成符合X.509标准的SubjectPublicKeyInfo结构,再转为PEM格式。以下是手动实现的代码示例:
const cbor = require('cbor'); const crypto = require('crypto'); function coseToPem(coseKeyBuffer) { const parsedKey = cbor.decodeFirstSync(coseKeyBuffer); // 提取P-256曲线的x、y坐标 const x = parsedKey.get(-2); const y = parsedKey.get(-3); // 构建ASN.1结构:SubjectPublicKeyInfo // 1. 算法标识符:id-ecPublicKey + secp256r1 const algorithmOid = Buffer.from([0x30, 0x0A, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07]); // 2. 公钥内容:0x04 + x + y,再包成BIT STRING const rawPublicKey = Buffer.concat([Buffer.from([0x04]), x, y]); const bitString = Buffer.concat([Buffer.from([0x00]), rawPublicKey]); // 前导0表示无未使用位 const subjectPublicKeyInfo = Buffer.concat([ Buffer.from([0x30, 0x59]), // 总长度 algorithmOid, Buffer.from([0x03, 0x42]), // BIT STRING长度 bitString ]); // 转为PEM格式 return `-----BEGIN PUBLIC KEY-----\n${subjectPublicKeyInfo.toString('base64').match(/.{1,64}/g).join('\n')}\n-----END PUBLIC KEY-----`; } // 使用示例 const coseKey = Buffer.from(publicKeyCredJSON.publicKey, 'base64'); const pemKey = coseToPem(coseKey); // 验证签名 const verifier = crypto.createVerify('SHA256'); verifier.update(Buffer.from(challengeAnswerJSON.response.authenticatorData, 'base64')); verifier.update(Buffer.from(challengeAnswerJSON.response.clientDataJSON, 'base64')); const isValid = verifier.verify(pemKey, challengeAnswerJSON.response.signature, 'base64');
方案2:在C#后端提前生成PEM格式公钥存储
既然你用Fido2NetLib处理凭证创建,可以在后端直接将COSE公钥转为PEM格式,存储到Cognito用户属性中,避免Node.js端的格式转换工作。C#代码示例:
using Fido2NetLib; using System.Security.Cryptography; using System.Text; // 假设你已经获取到AuthenticatorAttestationResponse var attestationResponse = ...; var credential = await _fido2.MakeNewCredentialAsync(attestationResponse, ...); // 将EC公钥转为PEM using var ec = ECDsa.Create(); ec.ImportParameters(new ECParameters { Curve = ECCurve.NamedCurves.nistP256, Q = new ECPoint { X = credential.PublicKey.X, Y = credential.PublicKey.Y } }); var pemBytes = ec.ExportSubjectPublicKeyInfo(); var pemString = $"-----BEGIN PUBLIC KEY-----\n{Convert.ToBase64String(pemBytes, Base64FormattingOptions.InsertLineBreaks)}\n-----END PUBLIC KEY-----"; // 将pemString存储到Cognito用户属性中,比如custom:public_key
之后在Node.js验证时,直接从Cognito取出PEM格式的公钥,传入crypto.verify即可。
方案3:使用WebAuthn专用验证库
直接使用支持COSE密钥的WebAuthn验证库,比如@simplewebauthn/server,它会自动处理COSE格式解析和签名验证,无需手动转换:
const { verifyAuthenticationResponse } = require('@simplewebauthn/server'); // 假设你已经从Cognito获取到用户存储的凭证信息(如credentialID、publicKey等) const storedCredential = { id: storedCredentialId, publicKey: storedPublicKeyCose, // 直接用COSE格式的公钥 transports: [], counter: storedCounter, }; const verificationResult = await verifyAuthenticationResponse({ response: challengeAnswerJSON.response, expectedChallenge: storedChallenge, expectedOrigin: "你的前端域名", expectedRPID: "你的RP ID", authenticator: storedCredential, }); const isValid = verificationResult.verified;
这种方式更简洁,也避免了手动处理密钥格式的出错风险。
内容的提问来源于stack exchange,提问作者Matt Matthias
相关产品推荐
相关产品推荐

