use super::*;
use crate::test_utils;
use ciborium::value::Value;
use ring::digest;
async fn create_test_auth_data() -> Vec<u8> {
test_utils::init_test_environment().await;
let mut auth_data = Vec::new();
let rp_id_hash = digest::digest(&digest::SHA256, "example.com".as_bytes());
auth_data.extend_from_slice(rp_id_hash.as_ref());
auth_data.push(0x01 | 0x04 | 0x40);
auth_data.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
auth_data.extend_from_slice(&[0x01; 16]);
auth_data.extend_from_slice(&[0x00, 0x10]); auth_data.extend_from_slice(&[0x02; 16]);
let public_key_entries = vec![
(Value::Integer(1i64.into()), Value::Integer(2i64.into())),
(Value::Integer(3i64.into()), Value::Integer((-7i64).into())),
(Value::Integer((-1i64).into()), Value::Integer(1i64.into())),
(Value::Integer((-2i64).into()), Value::Bytes(vec![0x02; 32])),
(Value::Integer((-3i64).into()), Value::Bytes(vec![0x03; 32])),
];
let public_key = Value::Map(public_key_entries);
let mut public_key_bytes = Vec::new();
ciborium::ser::into_writer(&public_key, &mut public_key_bytes).unwrap();
auth_data.extend_from_slice(&public_key_bytes);
auth_data
}
fn create_test_client_data_hash() -> Vec<u8> {
let client_data = r#"{"type":"webauthn.create","challenge":"dGVzdGNoYWxsZW5nZQ","origin":"https://example.com"}"#;
let hash = digest::digest(&digest::SHA256, client_data.as_bytes());
hash.as_ref().to_vec()
}
fn create_test_u2f_att_stmt(
include_sig: bool,
include_x5c: bool,
empty_x5c: bool,
) -> Vec<(CborValue, CborValue)> {
let mut att_stmt = Vec::new();
if include_sig {
att_stmt.push((
Value::Text("sig".to_string()),
Value::Bytes(vec![0x01, 0x02, 0x03, 0x04]), ));
}
if include_x5c {
let certs = if empty_x5c {
vec![Value::Text("not a certificate".to_string())]
} else {
vec![Value::Bytes(vec![0x30, 0x82, 0x01, 0x01])] };
att_stmt.push((Value::Text("x5c".to_string()), Value::Array(certs)));
}
att_stmt
}
#[tokio::test]
async fn test_verify_u2f_attestation_missing_sig() {
let auth_data = create_test_auth_data().await;
let client_data_hash = create_test_client_data_hash();
let att_stmt = create_test_u2f_att_stmt(
false, true, false, );
let result = verify_u2f_attestation(&auth_data, &client_data_hash, &att_stmt);
assert!(result.is_err());
if let Err(PasskeyError::Verification(msg)) = result {
assert!(msg.contains("Missing signature in FIDO-U2F attestation"));
} else {
panic!("Expected PasskeyError::Verification");
}
}
#[tokio::test]
async fn test_verify_u2f_attestation_missing_x5c() {
let auth_data = create_test_auth_data().await;
let client_data_hash = create_test_client_data_hash();
let att_stmt = create_test_u2f_att_stmt(
true, false, false, );
let result = verify_u2f_attestation(&auth_data, &client_data_hash, &att_stmt);
assert!(result.is_err());
if let Err(PasskeyError::Verification(msg)) = result {
assert!(msg.contains("Missing x5c in FIDO-U2F attestation"));
} else {
panic!("Expected PasskeyError::Verification");
}
}
#[tokio::test]
async fn test_verify_u2f_attestation_empty_x5c() {
let auth_data = create_test_auth_data().await;
let client_data_hash = create_test_client_data_hash();
let att_stmt = create_test_u2f_att_stmt(
true, true, true, );
let result = verify_u2f_attestation(&auth_data, &client_data_hash, &att_stmt);
assert!(result.is_err());
if let Err(PasskeyError::Verification(msg)) = result {
assert!(msg.contains("Missing x5c in FIDO-U2F attestation"));
} else {
panic!("Expected PasskeyError::Verification");
}
}
#[tokio::test]
async fn test_verify_u2f_attestation_invalid_certificate() {
let auth_data = create_test_auth_data().await;
let client_data_hash = create_test_client_data_hash();
let mut att_stmt = Vec::new();
att_stmt.push((
Value::Text("sig".to_string()),
Value::Bytes(vec![0x01, 0x02, 0x03, 0x04]), ));
let malformed_cert = vec![0xFF, 0xEE, 0xDD, 0xCC]; att_stmt.push((
Value::Text("x5c".to_string()),
Value::Array(vec![Value::Bytes(malformed_cert)]),
));
let result = verify_u2f_attestation(&auth_data, &client_data_hash, &att_stmt);
assert!(result.is_err());
if let Err(PasskeyError::Verification(msg)) = result {
assert!(msg.contains("Failed to parse U2F attestation certificate"));
} else {
panic!("Expected PasskeyError::Verification for certificate parsing");
}
}
#[test]
fn test_verify_u2f_attestation_short_auth_data() {
let client_data_hash = create_test_client_data_hash();
let mut att_stmt = Vec::new();
att_stmt.push((
Value::Text("sig".to_string()),
Value::Bytes(vec![0x01, 0x02, 0x03, 0x04]), ));
let malformed_cert = vec![0xFF, 0xEE, 0xDD, 0xCC]; att_stmt.push((
Value::Text("x5c".to_string()),
Value::Array(vec![Value::Bytes(malformed_cert)]),
));
let auth_data = vec![0x00; 54];
let result = verify_u2f_attestation(&auth_data, &client_data_hash, &att_stmt);
assert!(result.is_err());
match result {
Err(PasskeyError::Verification(msg)) => {
assert!(msg.contains("Failed to parse U2F attestation certificate"));
}
Err(other_error) => {
panic!("Expected PasskeyError::Verification but got: {other_error:?}");
}
Ok(_) => panic!("Expected an error but got Ok"),
}
}
#[test]
fn test_verify_u2f_attestation_invalid_credential_id_length() {
let client_data_hash = create_test_client_data_hash();
let mut att_stmt = Vec::new();
att_stmt.push((
Value::Text("sig".to_string()),
Value::Bytes(vec![0x01, 0x02, 0x03, 0x04]), ));
let malformed_cert = vec![0xFF, 0xEE, 0xDD, 0xCC]; att_stmt.push((
Value::Text("x5c".to_string()),
Value::Array(vec![Value::Bytes(malformed_cert)]),
));
let mut auth_data = Vec::new();
auth_data.extend_from_slice(&[0x01; 32]);
auth_data.push(0x01 | 0x04 | 0x40);
auth_data.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
auth_data.extend_from_slice(&[0x01; 16]);
auth_data.extend_from_slice(&[0xFF, 0xFF]);
auth_data.extend_from_slice(&[0x02; 10]);
let result = verify_u2f_attestation(&auth_data, &client_data_hash, &att_stmt);
assert!(result.is_err());
if let Err(PasskeyError::Verification(msg)) = result {
assert!(msg.contains("Failed to parse U2F attestation certificate"));
} else {
panic!(
"Expected PasskeyError::Verification for certificate parsing (which happens before auth_data validation)"
);
}
}
#[test]
fn test_verify_u2f_attestation_malformed_public_key() {
let client_data_hash = create_test_client_data_hash();
let mut att_stmt = Vec::new();
att_stmt.push((
Value::Text("sig".to_string()),
Value::Bytes(vec![0x01, 0x02, 0x03, 0x04]), ));
let malformed_cert = vec![0xFF, 0xEE, 0xDD, 0xCC]; att_stmt.push((
Value::Text("x5c".to_string()),
Value::Array(vec![Value::Bytes(malformed_cert)]),
));
let mut auth_data = Vec::new();
auth_data.extend_from_slice(&[0x01; 32]);
auth_data.push(0x01 | 0x04 | 0x40);
auth_data.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
auth_data.extend_from_slice(&[0x01; 16]);
auth_data.extend_from_slice(&[0x00, 0x04]); auth_data.extend_from_slice(&[0x02; 4]);
auth_data.extend_from_slice(&[0xFF, 0xFF, 0xFF]);
let result = verify_u2f_attestation(&auth_data, &client_data_hash, &att_stmt);
assert!(result.is_err());
if let Err(PasskeyError::Verification(msg)) = result {
assert!(msg.contains("Failed to parse U2F attestation certificate"));
} else {
panic!(
"Expected PasskeyError::Verification for certificate parsing (which happens before CBOR parsing)"
);
}
}
#[tokio::test]
async fn test_verify_u2f_attestation_truly_empty_x5c() {
let auth_data = create_test_auth_data().await;
let client_data_hash = create_test_client_data_hash();
let att_stmt = vec![
(
Value::Text("sig".to_string()),
Value::Bytes(vec![0x01, 0x02, 0x03, 0x04]), ),
(
Value::Text("x5c".to_string()),
Value::Array(vec![]), ),
];
let result = verify_u2f_attestation(&auth_data, &client_data_hash, &att_stmt);
assert!(result.is_err());
if let Err(PasskeyError::Verification(msg)) = result {
assert!(msg.contains("Missing x5c in FIDO-U2F attestation"));
} else {
panic!("Expected PasskeyError::Verification for empty x5c array");
}
}
#[test]
fn test_verify_u2f_attestation_invalid_public_key_coords() {
let client_data_hash = create_test_client_data_hash();
let mut att_stmt = Vec::new();
att_stmt.push((
Value::Text("sig".to_string()),
Value::Bytes(vec![0x01, 0x02, 0x03, 0x04]), ));
let malformed_cert = vec![0xFF, 0xEE, 0xDD, 0xCC]; att_stmt.push((
Value::Text("x5c".to_string()),
Value::Array(vec![Value::Bytes(malformed_cert)]),
));
let mut auth_data = Vec::new();
auth_data.extend_from_slice(&[0x01; 32]);
auth_data.push(0x01 | 0x04 | 0x40);
auth_data.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
auth_data.extend_from_slice(&[0x01; 16]);
auth_data.extend_from_slice(&[0x00, 0x04]); auth_data.extend_from_slice(&[0x02; 4]);
let invalid_public_key = Value::Map(vec![
(Value::Integer(1i64.into()), Value::Integer(2i64.into())), (Value::Integer(3i64.into()), Value::Integer((-7i64).into())), ]);
let mut public_key_bytes = Vec::new();
ciborium::ser::into_writer(&invalid_public_key, &mut public_key_bytes).unwrap();
auth_data.extend_from_slice(&public_key_bytes);
let result = verify_u2f_attestation(&auth_data, &client_data_hash, &att_stmt);
assert!(result.is_err());
match result {
Err(PasskeyError::Verification(msg)) => {
assert!(msg.contains("Failed to parse U2F attestation certificate"));
}
_ => panic!(
"Expected PasskeyError::Verification for certificate parsing (which happens before coordinate extraction)"
),
}
}
#[test]
fn test_auth_data_bounds_check_position() {
let client_data_hash = create_test_client_data_hash();
let mut att_stmt = Vec::new();
att_stmt.push((
Value::Text("sig".to_string()),
Value::Bytes(vec![0x01, 0x02, 0x03, 0x04]),
));
let malformed_cert = vec![0xFF, 0xEE, 0xDD, 0xCC];
att_stmt.push((
Value::Text("x5c".to_string()),
Value::Array(vec![Value::Bytes(malformed_cert)]),
));
let short_auth_data = vec![0x00; 54]; let result = verify_u2f_attestation(&short_auth_data, &client_data_hash, &att_stmt);
assert!(result.is_err());
if let Err(PasskeyError::Verification(msg)) = result {
assert!(msg.contains("Failed to parse U2F attestation certificate"));
}
}