You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 18:25:57