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

在Rust中从WebAuthn Auth Data提取公钥并验证时遭遇ASN.1编码错误的问题咨询

在Rust中从WebAuthn Auth Data提取公钥并验证时遭遇ASN.1编码错误的问题咨询

我现在卡在从WebAuthn API的Auth Data里提取公钥的环节了,目前用coset、openssl和ciborium这几个Rust crate来处理COSE密钥和公钥转换,但在验证签名的时候遇到了ASN.1编码相关的错误,想请大家帮忙看看问题出在哪。

我的实现代码

首先是从COSE密钥中提取公钥的函数:

pub(super) fn extract_public_key_from_cose(&self, cose_key_value: ciborium::value::Value) -> Option<Vec<u8>> {
        if let ciborium::value::Value::Map(cose_key_map) = cose_key_value {
            let mut cose_key_builder: CoseKeyBuilder = CoseKeyBuilder::new();
            for (key, value) in cose_key_map {
                if let ciborium::value::Value::Integer(param) = key {
                    if param == Integer::from(1) {
                        if let ciborium::value::Value::Integer(kty) = value {
                            cose_key_builder = cose_key_builder.kty(KeyType::Assigned(iana::KeyType::from_i64(kty.try_into().ok()?)?));
                        }
                    } else if param == Integer::from(3) {
                        if let ciborium::value::Value::Integer(alg) = value {
                            cose_key_builder = cose_key_builder.algorithm(iana::Algorithm::from_i64(alg.try_into().ok()?)?);
                        }
                    } else if param == Integer::from(-2) {
                        if let ciborium::value::Value::Bytes(x) = value {
                            cose_key_builder = cose_key_builder.param(-2, ciborium::value::Value::Bytes(x));
                        }
                    } else if param == Integer::from(-3) {
                        if let ciborium::value::Value::Bytes(y) = value {
                            cose_key_builder = cose_key_builder.param(-3, ciborium::value::Value::Bytes(y));
                        }
                    }
                }
            }

            let cose_key: CoseKey = cose_key_builder.build();
            let cbor_key: Vec<u8> = cose_key.to_cbor_value().unwrap().to_vec().unwrap();

            return Some(cbor_key);
        }

        None
    }

然后是从Auth Data中提取COSE密钥并调用上面函数的逻辑:

pub(super) fn extract_public_key(&self, auth_data: &[u8]) -> Option<Vec<u8>> {
        // The length of the authenticator data is at least 37 bytes
        if auth_data.len() < 37 {
            return None;
        }

        // The attestedCredentialData starts at byte 37
        let attested_credential_data = &auth_data[37..];

        // The AAGUID is 16 bytes, and the credential ID length is 2 bytes
        let aaguid_length = 16;
        let credential_id_length = 2;

        // Ensure there is enough data for the AAGUID and credential ID length
        if attested_credential_data.len() < aaguid_length + credential_id_length {
            return None;
        }

        // Extract the credential ID length
        let credential_id_length = u16::from_be_bytes([
            attested_credential_data[aaguid_length],
            attested_credential_data[aaguid_length + 1],
        ]) as usize;

        // Ensure there is enough data for the credential ID and public key
        let public_key_start = aaguid_length + 2 + credential_id_length;
        if attested_credential_data.len() <= public_key_start {
            return None;
        }
        // Extract the public key in COSE format
        let public_key_cose = &attested_credential_data[public_key_start..];

        // Manually deserialize the COSE key
        let mut cursor = Cursor::new(public_key_cose);
        let cose_key_value: ciborium::Value = match ciborium::from_reader(&mut cursor) {
            Ok(value) => value,
            Err(e) => {
                println!("Failed to deserialize public_key_cose: {:?}", e);
                return None;
            }
        };

        // Extract the public key from the COSE key
        self.extract_public_key_from_cose(cose_key_value)
    }

最后是核心的签名验证逻辑:

// Extract the public key from authData
let public_key = if let ciborium::Value::Bytes(ref auth_data_bytes) = auth_data {
    self.extract_public_key(auth_data_bytes)
} else {
    return Err("Invalid authData format".into());
};

let auth_data_bytes = if let ciborium::Value::Bytes(auth_data_bytes) = auth_data {
    auth_data_bytes.clone()
} else {
    println!("Invalid authData format");
    return Err("Invalid authData format".into());
};

let mut signature_base = Vec::new();
signature_base.extend_from_slice(&auth_data_bytes);
signature_base.extend_from_slice(client_data_json);

// Verify the signature using the public key
return self.verify_signature_with_public_key(sig, &signature_base, &public_key.unwrap(), alg);

// --- 验证函数内部 ---
let public_key = openssl::pkey::PKey::public_key_from_der(public_key)?;
let message_digest = match alg {
    -7 => openssl::hash::MessageDigest::sha256(),
    -35 => openssl::hash::MessageDigest::sha384(),
    -36 => openssl::hash::MessageDigest::sha512(),
    _ => return Err("Unsupported algorithm".into()),
};

let mut verifier = openssl::sign::Verifier::new(message_digest, &public_key)?;
verifier.update(data)?;
Ok(verifier.verify(&sig)?)

遇到的错误

当执行到openssl::pkey::PKey::public_key_from_der(public_key)?这一行时,抛出了ASN.1编码错误:

error:068000A8:asn1 encoding routines:asn1_check_tlen:wrong 
tag:crypto/asn1/tasn_dec.c:1194:, error:0688010A:asn1 encoding 
routines:asn1_item_embed_d2i:nested asn1 error:crypto/asn1/tasn_dec.c:349:Type=X509_PUBKEY

我的疑问

是不是我的实现哪里出错了?特别是extract_public_key_from_cose这个函数,它返回的是COSE密钥的CBOR序列化字节,但openssl::pkey::PKey::public_key_from_der需要的是DER格式的公钥,是不是这里的格式转换逻辑错了?应该怎么正确把COSE密钥转换成openssl能识别的DER格式公钥?


备注:内容来源于stack exchange,提问作者notnuyy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.16 02:54:51