use super::*;
use ciborium::value::Value;
use ring::digest;
const TEST_RP_ID: &str = "example.com";
fn create_valid_public_key_cbor() -> Vec<u8> {
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();
public_key_bytes
}
fn create_auth_data(
rp_id: &str,
user_present: bool,
user_verified: bool,
attested_cred_data: bool,
cred_id_len: u16,
include_public_key: bool,
) -> Vec<u8> {
let mut auth_data = Vec::new();
let rp_id_hash = digest::digest(&digest::SHA256, rp_id.as_bytes());
auth_data.extend_from_slice(rp_id_hash.as_ref());
let mut flags = 0u8;
if user_present {
flags |= 0x01;
}
if user_verified {
flags |= 0x04;
}
if attested_cred_data {
flags |= 0x40;
}
auth_data.push(flags);
auth_data.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
if attested_cred_data {
auth_data.extend_from_slice(&[0x01; 16]);
auth_data.extend_from_slice(&[(cred_id_len >> 8) as u8, (cred_id_len & 0xFF) as u8]);
auth_data.extend_from_slice(&vec![0x02; cred_id_len as usize]);
if include_public_key {
let public_key_bytes = create_valid_public_key_cbor();
auth_data.extend_from_slice(&public_key_bytes);
}
}
auth_data
}
fn create_test_attestation_with_params(
empty_att_stmt: bool,
rp_id: &str,
user_present: bool,
user_verified: bool,
attested_cred_data: bool,
cred_id_len: u16,
include_public_key: bool,
) -> AttestationObject {
let auth_data = create_auth_data(
rp_id,
user_present,
user_verified,
attested_cred_data,
cred_id_len,
include_public_key,
);
AttestationObject {
fmt: "none".to_string(),
auth_data,
att_stmt: if empty_att_stmt {
Vec::new()
} else {
vec![(Value::Text("alg".to_string()), Value::Integer(1i64.into()))]
},
}
}
fn create_valid_attestation() -> AttestationObject {
create_test_attestation_with_params(true, TEST_RP_ID, true, true, true, 16, true)
}
#[test]
fn test_verify_none_attestation_success() {
let attestation = create_valid_attestation();
let result = verify_none_attestation_with_config(&attestation, TEST_RP_ID, true);
assert!(result.is_ok());
}
#[test]
fn test_verify_none_attestation_non_empty_att_stmt() {
let attestation =
create_test_attestation_with_params(false, TEST_RP_ID, true, true, true, 16, true);
let result = verify_none_attestation_with_config(&attestation, TEST_RP_ID, true);
assert!(result.is_err());
if let Err(PasskeyError::Format(msg)) = result {
assert!(msg.contains("attStmt must be empty"));
} else {
panic!("Expected PasskeyError::Format");
}
}
#[test]
fn test_verify_none_attestation_invalid_rp_id_hash() {
let attestation = create_valid_attestation();
let result = verify_none_attestation_with_config(&attestation, "different.com", true);
assert!(result.is_err());
if let Err(PasskeyError::Verification(msg)) = result {
assert!(msg.contains("Invalid RP ID hash"));
} else {
panic!("Expected PasskeyError::Verification");
}
}
#[test]
fn test_verify_none_attestation_user_present_not_set() {
let attestation =
create_test_attestation_with_params(true, TEST_RP_ID, false, true, true, 16, true);
let result = verify_none_attestation_with_config(&attestation, TEST_RP_ID, true);
assert!(result.is_err());
if let Err(PasskeyError::AuthenticatorData(msg)) = result {
assert!(msg.contains("User Present flag not set"));
} else {
panic!("Expected PasskeyError::AuthenticatorData");
}
}
#[test]
fn test_verify_none_attestation_no_attested_cred_data() {
let attestation =
create_test_attestation_with_params(true, TEST_RP_ID, true, true, false, 0, false);
let result = verify_none_attestation_with_config(&attestation, TEST_RP_ID, true);
assert!(result.is_err());
if let Err(PasskeyError::AuthenticatorData(msg)) = result {
assert!(msg.contains("No attested credential data"));
} else {
panic!("Expected PasskeyError::AuthenticatorData");
}
}
#[test]
fn test_verify_none_attestation_user_verification_required() {
let attestation =
create_test_attestation_with_params(true, TEST_RP_ID, true, false, true, 16, true);
let result = verify_none_attestation_with_config(&attestation, TEST_RP_ID, true);
assert!(result.is_err());
if let Err(PasskeyError::AuthenticatorData(msg)) = result {
assert!(msg.contains("User Verification required but flag not set"));
} else {
panic!("Expected PasskeyError::AuthenticatorData");
}
let result = verify_none_attestation_with_config(&attestation, TEST_RP_ID, false);
assert!(result.is_ok());
}
#[test]
fn test_verify_none_attestation_invalid_public_key() {
let attestation =
create_test_attestation_with_params(true, TEST_RP_ID, true, true, true, 16, false);
let result = verify_none_attestation_with_config(&attestation, TEST_RP_ID, true);
assert!(result.is_err());
if let Err(PasskeyError::Verification(msg)) = result {
assert!(msg.contains("Auth data too short for public key"));
} else {
panic!("Expected PasskeyError::Verification");
}
}
#[test]
fn test_verify_none_attestation_auth_data_too_short_basic() {
let attestation = AttestationObject {
fmt: "none".to_string(),
auth_data: vec![0; 36], att_stmt: Vec::new(),
};
let result = verify_none_attestation_with_config(&attestation, TEST_RP_ID, false);
assert!(result.is_err());
if let Err(PasskeyError::Verification(msg)) = result {
assert!(msg.contains("Auth data too short for basic structure"));
} else {
panic!("Expected PasskeyError::Verification");
}
}
#[test]
fn test_verify_none_attestation_auth_data_too_short_for_attested_data() {
let auth_data = create_auth_data(TEST_RP_ID, true, true, true, 16, false);
let short_auth_data = auth_data[..54].to_vec();
let attestation = AttestationObject {
fmt: "none".to_string(),
auth_data: short_auth_data,
att_stmt: Vec::new(),
};
let result = verify_none_attestation_with_config(&attestation, TEST_RP_ID, false);
assert!(result.is_err());
if let Err(PasskeyError::Verification(msg)) = result {
assert!(msg.contains("Auth data too short for attested credential data"));
} else {
panic!("Expected PasskeyError::Verification");
}
}
#[test]
fn test_verify_none_attestation_invalid_credential_id_length() {
let attestation =
create_test_attestation_with_params(true, TEST_RP_ID, true, true, true, 65535, false);
let result = verify_none_attestation_with_config(&attestation, TEST_RP_ID, false);
assert!(result.is_err());
if let Err(PasskeyError::Verification(msg)) = result {
assert!(msg.contains("Auth data too short for public key"));
} else {
panic!("Expected PasskeyError::Verification");
}
}
#[test]
fn test_verify_none_attestation_zero_credential_id_length() {
let attestation =
create_test_attestation_with_params(true, TEST_RP_ID, true, true, true, 0, true);
let result = verify_none_attestation_with_config(&attestation, TEST_RP_ID, false);
assert!(result.is_ok());
}
#[test]
fn test_verify_none_attestation_malformed_cbor_public_key() {
let mut auth_data = create_auth_data(TEST_RP_ID, true, true, true, 16, false);
auth_data.extend_from_slice(&[0xFF, 0xFE, 0xFD]);
let attestation = AttestationObject {
fmt: "none".to_string(),
auth_data,
att_stmt: Vec::new(),
};
let result = verify_none_attestation_with_config(&attestation, TEST_RP_ID, false);
assert!(result.is_err());
if let Err(PasskeyError::Format(msg)) = result {
assert!(msg.contains("Invalid public key CBOR"));
} else {
panic!("Expected PasskeyError::Format");
}
}
#[test]
fn test_verify_none_attestation_different_flag_combinations() {
let attestation1 =
create_test_attestation_with_params(true, TEST_RP_ID, false, false, false, 0, false);
let result1 = verify_none_attestation_with_config(&attestation1, TEST_RP_ID, false);
assert!(result1.is_err());
let attestation2 =
create_test_attestation_with_params(true, TEST_RP_ID, true, false, false, 0, false);
let result2 = verify_none_attestation_with_config(&attestation2, TEST_RP_ID, false);
assert!(result2.is_err());
let attestation3 =
create_test_attestation_with_params(true, TEST_RP_ID, true, true, false, 0, false);
let result3 = verify_none_attestation_with_config(&attestation3, TEST_RP_ID, false);
assert!(result3.is_err()); }
#[test]
fn test_verify_none_attestation_boundary_credential_id_lengths() {
let test_lengths = vec![1, 16, 32, 64, 128, 255, 256];
for len in test_lengths {
let attestation =
create_test_attestation_with_params(true, TEST_RP_ID, true, true, true, len, true);
let result = verify_none_attestation_with_config(&attestation, TEST_RP_ID, false);
if result.is_err() {
println!("Failed for credential ID length: {len}");
println!("Error: {result:?}");
}
assert!(
result.is_ok(),
"Should succeed for credential ID length: {len}"
);
}
}
#[test]
fn test_verify_none_attestation_empty_rp_id() {
let attestation = create_test_attestation_with_params(true, "", true, true, true, 16, true);
let result = verify_none_attestation_with_config(&attestation, TEST_RP_ID, false);
assert!(result.is_err());
if let Err(PasskeyError::Verification(msg)) = result {
assert!(msg.contains("Invalid RP ID hash"));
} else {
panic!("Expected PasskeyError::Verification");
}
}
#[test]
fn test_verify_none_attestation_very_long_rp_id() {
let long_rp_id = "a".repeat(1000);
let attestation =
create_test_attestation_with_params(true, &long_rp_id, true, true, true, 16, true);
let result = verify_none_attestation_with_config(&attestation, &long_rp_id, false);
assert!(result.is_ok());
}
#[test]
fn test_verify_none_attestation_minimal_auth_data_length() {
let mut auth_data = Vec::new();
let rp_id_hash = digest::digest(&digest::SHA256, TEST_RP_ID.as_bytes());
auth_data.extend_from_slice(rp_id_hash.as_ref());
auth_data.push(0x01);
auth_data.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
assert_eq!(auth_data.len(), 37);
let attestation = AttestationObject {
fmt: "none".to_string(),
auth_data,
att_stmt: Vec::new(),
};
let result = verify_none_attestation_with_config(&attestation, TEST_RP_ID, false);
assert!(result.is_err());
if let Err(PasskeyError::AuthenticatorData(msg)) = result {
assert!(msg.contains("No attested credential data"));
} else {
panic!("Expected PasskeyError::AuthenticatorData");
}
}
#[test]
fn test_verify_none_attestation_invalid_public_key_coordinates() {
let mut auth_data = create_auth_data(TEST_RP_ID, true, true, true, 16, false);
let invalid_public_key_entries = vec![
(Value::Integer(1i64.into()), Value::Integer(2i64.into())),
];
let invalid_public_key = Value::Map(invalid_public_key_entries);
let mut invalid_public_key_bytes = Vec::new();
ciborium::ser::into_writer(&invalid_public_key, &mut invalid_public_key_bytes).unwrap();
auth_data.extend_from_slice(&invalid_public_key_bytes);
let attestation = AttestationObject {
fmt: "none".to_string(),
auth_data,
att_stmt: Vec::new(),
};
let result = verify_none_attestation_with_config(&attestation, TEST_RP_ID, false);
assert!(result.is_err());
if let Err(PasskeyError::Verification(msg)) = result {
assert!(msg.contains("Invalid public key coordinates"));
} else {
panic!("Expected PasskeyError::Verification");
}
}
#[test]
fn test_verify_none_attestation_exactly_minimum_attested_data_length() {
let mut auth_data = Vec::new();
let rp_id_hash = digest::digest(&digest::SHA256, TEST_RP_ID.as_bytes());
auth_data.extend_from_slice(rp_id_hash.as_ref());
auth_data.push(0x41);
auth_data.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
auth_data.extend_from_slice(&[0x01; 16]);
auth_data.extend_from_slice(&[0x00, 0x00]);
assert_eq!(auth_data.len(), 55);
let attestation = AttestationObject {
fmt: "none".to_string(),
auth_data,
att_stmt: Vec::new(),
};
let result = verify_none_attestation_with_config(&attestation, TEST_RP_ID, false);
assert!(result.is_err());
if let Err(PasskeyError::Verification(msg)) = result {
assert!(msg.contains("Auth data too short for public key"));
} else {
panic!("Expected PasskeyError::Verification");
}
}
#[test]
fn test_verify_none_attestation_maximum_valid_credential_id() {
let large_cred_id_len = 1024; let attestation = create_test_attestation_with_params(
true,
TEST_RP_ID,
true,
true,
true,
large_cred_id_len,
true,
);
let result = verify_none_attestation_with_config(&attestation, TEST_RP_ID, false);
assert!(result.is_ok());
}
#[test]
fn test_verify_none_attestation_truncated_public_key_cbor() {
let mut auth_data = create_auth_data(TEST_RP_ID, true, true, true, 16, false);
auth_data.push(0xa1); auth_data.push(0x01);
let attestation = AttestationObject {
fmt: "none".to_string(),
auth_data,
att_stmt: Vec::new(),
};
let result = verify_none_attestation_with_config(&attestation, TEST_RP_ID, false);
assert!(result.is_err());
if let Err(PasskeyError::Format(msg)) = result {
assert!(msg.contains("Invalid public key CBOR"));
} else {
panic!("Expected PasskeyError::Format");
}
}
#[test]
fn test_verify_none_attestation_all_optional_flags_set() {
let mut auth_data = Vec::new();
let rp_id_hash = digest::digest(&digest::SHA256, TEST_RP_ID.as_bytes());
auth_data.extend_from_slice(rp_id_hash.as_ref());
auth_data.push(0xFF);
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_bytes = create_valid_public_key_cbor();
auth_data.extend_from_slice(&public_key_bytes);
let attestation = AttestationObject {
fmt: "none".to_string(),
auth_data,
att_stmt: Vec::new(),
};
let result = verify_none_attestation_with_config(&attestation, TEST_RP_ID, false);
assert!(result.is_ok());
}
#[test]
fn test_verify_none_attestation_rp_id_unicode_characters() {
let unicode_rp_id = "tëst-éxample.cöm";
let attestation =
create_test_attestation_with_params(true, unicode_rp_id, true, true, true, 16, true);
let result = verify_none_attestation_with_config(&attestation, unicode_rp_id, false);
assert!(result.is_ok());
}
#[test]
fn test_verify_none_attestation_user_verification_edge_cases() {
let attestation1 =
create_test_attestation_with_params(true, TEST_RP_ID, false, true, true, 16, true);
let result1 = verify_none_attestation_with_config(&attestation1, TEST_RP_ID, true);
assert!(result1.is_err());
if let Err(PasskeyError::AuthenticatorData(msg)) = result1 {
assert!(msg.contains("User Present flag not set"));
}
let attestation2 =
create_test_attestation_with_params(true, TEST_RP_ID, true, false, true, 16, true);
let result2 = verify_none_attestation_with_config(&attestation2, TEST_RP_ID, false);
assert!(result2.is_ok());
let attestation3 =
create_test_attestation_with_params(true, TEST_RP_ID, true, true, true, 16, true);
let result3 = verify_none_attestation_with_config(&attestation3, TEST_RP_ID, true);
assert!(result3.is_ok());
}