Support credBlob for non-resident credentials (#518)

* Support credBlob for non-resident credentials

- Add a upper limit of max_cred_blob_length
- Add test cases for cred_blob in non-resident flows
- Modify the test helper functions in ctap/mod.rs a bit

* Fix some styles in credential_id.rs

Co-authored-by: kaczmarczyck <43844792+kaczmarczyck@users.noreply.github.com>
This commit is contained in:
hcyang
2022-08-16 22:23:49 +08:00
committed by GitHub
parent 5daf5f81d1
commit 87839af572
4 changed files with 212 additions and 101 deletions

View File

@@ -880,7 +880,7 @@ impl CtapState {
let has_cred_blob_output = extensions.cred_blob.is_some();
let cred_blob = extensions
.cred_blob
.filter(|c| options.rk && c.len() <= env.customization().max_cred_blob_length());
.filter(|c| c.len() <= env.customization().max_cred_blob_length());
let cred_blob_output = if has_cred_blob_output {
Some(cred_blob.is_some())
} else {
@@ -928,7 +928,13 @@ impl CtapState {
storage::store_credential(env, credential_source)?;
random_id
} else {
encrypt_to_credential_id(env, &private_key, &rp_id_hash, cred_protect_policy)?
encrypt_to_credential_id(
env,
&private_key,
&rp_id_hash,
cred_protect_policy,
cred_blob,
)?
};
let mut auth_data = self.generate_auth_data(env, &rp_id_hash, flags)?;
@@ -1555,13 +1561,13 @@ mod test {
const VENDOR_CHANNEL: Channel = Channel::VendorHid([0x12, 0x34, 0x56, 0x78]);
fn check_make_response(
make_credential_response: Result<ResponseData, Ctap2StatusCode>,
make_credential_response: &Result<ResponseData, Ctap2StatusCode>,
flags: u8,
expected_aaguid: &[u8],
expected_credential_id_size: u8,
expected_extension_cbor: &[u8],
) {
match make_credential_response.unwrap() {
match make_credential_response.as_ref().unwrap() {
ResponseData::AuthenticatorMakeCredential(make_credential_response) => {
let AuthenticatorMakeCredentialResponse {
fmt,
@@ -1590,7 +1596,7 @@ mod test {
);
assert!(ep_att.is_none());
assert_eq!(att_stmt.alg, SignatureAlgorithm::Es256 as i64);
assert_eq!(large_blob_key, None);
assert_eq!(large_blob_key, &None);
}
_ => panic!("Invalid response type"),
}
@@ -1708,6 +1714,22 @@ mod test {
make_credential_params
}
fn parse_credential_id_from_non_resident_make_credential_response(
env: &mut impl Env,
make_credential_response: ResponseData,
) -> Vec<u8> {
match make_credential_response {
ResponseData::AuthenticatorMakeCredential(make_credential_response) => {
let auth_data = make_credential_response.auth_data;
let offset = 37 + storage::aaguid(env).unwrap().len();
assert_eq!(auth_data[offset], 0x00);
assert_eq!(auth_data[offset + 1] as usize, CBOR_CREDENTIAL_ID_SIZE);
auth_data[offset + 2..offset + 2 + CBOR_CREDENTIAL_ID_SIZE].to_vec()
}
_ => panic!("Invalid response type"),
}
}
#[test]
fn test_resident_process_make_credential() {
let mut env = TestEnv::new();
@@ -1718,7 +1740,7 @@ mod test {
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
check_make_response(
make_credential_response,
&make_credential_response,
0x41,
&storage::aaguid(&mut env).unwrap(),
0x20,
@@ -1737,7 +1759,7 @@ mod test {
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
check_make_response(
make_credential_response,
&make_credential_response,
0x41,
&storage::aaguid(&mut env).unwrap(),
CBOR_CREDENTIAL_ID_SIZE as u8,
@@ -1857,16 +1879,10 @@ mod test {
let make_credential_response =
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
assert!(make_credential_response.is_ok());
let credential_id = match make_credential_response.unwrap() {
ResponseData::AuthenticatorMakeCredential(make_credential_response) => {
let auth_data = make_credential_response.auth_data;
let offset = 37 + storage::aaguid(&mut env).unwrap().len();
assert_eq!(auth_data[offset], 0x00);
assert_eq!(auth_data[offset + 1] as usize, CBOR_CREDENTIAL_ID_SIZE);
auth_data[offset + 2..offset + 2 + CBOR_CREDENTIAL_ID_SIZE].to_vec()
}
_ => panic!("Invalid response type"),
};
let credential_id = parse_credential_id_from_non_resident_make_credential_response(
&mut env,
make_credential_response.unwrap(),
);
let make_credential_params =
create_make_credential_parameters_with_exclude_list(&credential_id);
let make_credential_response =
@@ -1883,16 +1899,10 @@ mod test {
let make_credential_response =
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
assert!(make_credential_response.is_ok());
let credential_id = match make_credential_response.unwrap() {
ResponseData::AuthenticatorMakeCredential(make_credential_response) => {
let auth_data = make_credential_response.auth_data;
let offset = 37 + storage::aaguid(&mut env).unwrap().len();
assert_eq!(auth_data[offset], 0x00);
assert_eq!(auth_data[offset + 1] as usize, CBOR_CREDENTIAL_ID_SIZE);
auth_data[offset + 2..offset + 2 + CBOR_CREDENTIAL_ID_SIZE].to_vec()
}
_ => panic!("Invalid response type"),
};
let credential_id = parse_credential_id_from_non_resident_make_credential_response(
&mut env,
make_credential_response.unwrap(),
);
let make_credential_params =
create_make_credential_parameters_with_exclude_list(&credential_id);
let make_credential_response =
@@ -1919,7 +1929,7 @@ mod test {
0xA1, 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0xF5,
];
check_make_response(
make_credential_response,
&make_credential_response,
0xC1,
&storage::aaguid(&mut env).unwrap(),
CBOR_CREDENTIAL_ID_SIZE as u8,
@@ -1945,7 +1955,7 @@ mod test {
0xA1, 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0xF5,
];
check_make_response(
make_credential_response,
&make_credential_response,
0xC1,
&storage::aaguid(&mut env).unwrap(),
0x20,
@@ -1968,7 +1978,7 @@ mod test {
let make_credential_response =
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
check_make_response(
make_credential_response,
&make_credential_response,
0x41,
&storage::aaguid(&mut env).unwrap(),
0x20,
@@ -1994,7 +2004,7 @@ mod test {
0x04,
];
check_make_response(
make_credential_response,
&make_credential_response,
0xC1,
&storage::aaguid(&mut env).unwrap(),
0x20,
@@ -2019,7 +2029,7 @@ mod test {
0xA1, 0x68, 0x63, 0x72, 0x65, 0x64, 0x42, 0x6C, 0x6F, 0x62, 0xF5,
];
check_make_response(
make_credential_response,
&make_credential_response,
0xC1,
&storage::aaguid(&mut env).unwrap(),
0x20,
@@ -2051,7 +2061,7 @@ mod test {
0xA1, 0x68, 0x63, 0x72, 0x65, 0x64, 0x42, 0x6C, 0x6F, 0x62, 0xF4,
];
check_make_response(
make_credential_response,
&make_credential_response,
0xC1,
&storage::aaguid(&mut env).unwrap(),
0x20,
@@ -2125,7 +2135,7 @@ mod test {
);
check_make_response(
make_credential_response,
&make_credential_response,
0x45,
&storage::aaguid(&mut env).unwrap(),
0x20,
@@ -2162,7 +2172,7 @@ mod test {
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
check_make_response(
make_credential_response,
&make_credential_response,
0x41,
&storage::aaguid(&mut env).unwrap(),
CBOR_CREDENTIAL_ID_SIZE as u8,
@@ -2350,7 +2360,7 @@ mod test {
fn check_assertion_response_with_user(
response: Result<ResponseData, Ctap2StatusCode>,
expected_user: PublicKeyCredentialUserEntity,
expected_user: Option<PublicKeyCredentialUserEntity>,
flags: u8,
signature_counter: u32,
expected_number_of_credentials: Option<u64>,
@@ -2379,23 +2389,23 @@ mod test {
);
expected_auth_data.extend(expected_extension_cbor);
assert_eq!(auth_data, expected_auth_data);
assert_eq!(user, Some(expected_user));
assert_eq!(user, expected_user);
assert_eq!(number_of_credentials, expected_number_of_credentials);
}
fn check_assertion_response_with_extension(
response: Result<ResponseData, Ctap2StatusCode>,
expected_user_id: Vec<u8>,
expected_user_id: Option<Vec<u8>>,
signature_counter: u32,
expected_number_of_credentials: Option<u64>,
expected_extension_cbor: &[u8],
) {
let expected_user = PublicKeyCredentialUserEntity {
user_id: expected_user_id,
let expected_user = expected_user_id.map(|user_id| PublicKeyCredentialUserEntity {
user_id,
user_name: None,
user_display_name: None,
user_icon: None,
};
});
check_assertion_response_with_user(
response,
expected_user,
@@ -2420,7 +2430,7 @@ mod test {
};
check_assertion_response_with_user(
response,
expected_user,
Some(expected_user),
0x00,
signature_counter,
expected_number_of_credentials,
@@ -2528,16 +2538,10 @@ mod test {
let make_credential_response =
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
assert!(make_credential_response.is_ok());
let credential_id = match make_credential_response.unwrap() {
ResponseData::AuthenticatorMakeCredential(make_credential_response) => {
let auth_data = make_credential_response.auth_data;
let offset = 37 + storage::aaguid(&mut env).unwrap().len();
assert_eq!(auth_data[offset], 0x00);
assert_eq!(auth_data[offset + 1] as usize, CBOR_CREDENTIAL_ID_SIZE);
auth_data[offset + 2..offset + 2 + CBOR_CREDENTIAL_ID_SIZE].to_vec()
}
_ => panic!("Invalid response type"),
};
let credential_id = parse_credential_id_from_non_resident_make_credential_response(
&mut env,
make_credential_response.unwrap(),
);
let client_pin_params = AuthenticatorClientPinParameters {
pin_uv_auth_protocol,
@@ -2760,16 +2764,10 @@ mod test {
let make_credential_response =
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
assert!(make_credential_response.is_ok());
let credential_id = match make_credential_response.unwrap() {
ResponseData::AuthenticatorMakeCredential(make_credential_response) => {
let auth_data = make_credential_response.auth_data;
let offset = 37 + storage::aaguid(&mut env).unwrap().len();
assert_eq!(auth_data[offset], 0x00);
assert_eq!(auth_data[offset + 1] as usize, CBOR_CREDENTIAL_ID_SIZE);
auth_data[offset + 2..offset + 2 + CBOR_CREDENTIAL_ID_SIZE].to_vec()
}
_ => panic!("Invalid response type"),
};
let credential_id = parse_credential_id_from_non_resident_make_credential_response(
&mut env,
make_credential_response.unwrap(),
);
let cred_desc = PublicKeyCredentialDescriptor {
key_type: PublicKeyCredentialType::PublicKey,
key_id: credential_id,
@@ -2802,16 +2800,10 @@ mod test {
let make_credential_response =
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
assert!(make_credential_response.is_ok());
let credential_id = match make_credential_response.unwrap() {
ResponseData::AuthenticatorMakeCredential(make_credential_response) => {
let auth_data = make_credential_response.auth_data;
let offset = 37 + storage::aaguid(&mut env).unwrap().len();
assert_eq!(auth_data[offset], 0x00);
assert_eq!(auth_data[offset + 1] as usize, CBOR_CREDENTIAL_ID_SIZE);
auth_data[offset + 2..offset + 2 + CBOR_CREDENTIAL_ID_SIZE].to_vec()
}
_ => panic!("Invalid response type"),
};
let credential_id = parse_credential_id_from_non_resident_make_credential_response(
&mut env,
make_credential_response.unwrap(),
);
let cred_desc = PublicKeyCredentialDescriptor {
key_type: PublicKeyCredentialType::PublicKey,
key_id: credential_id,
@@ -2892,7 +2884,76 @@ mod test {
];
check_assertion_response_with_extension(
get_assertion_response,
vec![0x1D],
Some(vec![0x1D]),
signature_counter,
None,
&expected_extension_cbor,
);
}
#[test]
fn test_non_resident_process_get_assertion_with_cred_blob() {
let mut env = TestEnv::new();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let extensions = MakeCredentialExtensions {
cred_blob: Some(vec![0xCB]),
..Default::default()
};
let mut make_credential_params = create_minimal_make_credential_parameters();
make_credential_params.extensions = extensions;
make_credential_params.options.rk = false;
let make_credential_response =
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
let expected_extension_cbor = [
0xA1, 0x68, 0x63, 0x72, 0x65, 0x64, 0x42, 0x6C, 0x6F, 0x62, 0xF5,
];
check_make_response(
&make_credential_response,
0xC1,
&storage::aaguid(&mut env).unwrap(),
CBOR_CREDENTIAL_ID_SIZE as u8,
&expected_extension_cbor,
);
let credential_id = parse_credential_id_from_non_resident_make_credential_response(
&mut env,
make_credential_response.unwrap(),
);
let cred_desc = PublicKeyCredentialDescriptor {
key_type: PublicKeyCredentialType::PublicKey,
key_id: credential_id,
transports: None,
};
let extensions = GetAssertionExtensions {
cred_blob: true,
..Default::default()
};
let get_assertion_params = AuthenticatorGetAssertionParameters {
rp_id: String::from("example.com"),
client_data_hash: vec![0xCD],
allow_list: Some(vec![cred_desc]),
extensions,
options: GetAssertionOptions {
up: false,
uv: false,
},
pin_uv_auth_param: None,
pin_uv_auth_protocol: None,
};
let get_assertion_response = ctap_state.process_get_assertion(
&mut env,
get_assertion_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
let signature_counter = storage::global_signature_counter(&mut env).unwrap();
let expected_extension_cbor = [
0xA1, 0x68, 0x63, 0x72, 0x65, 0x64, 0x42, 0x6C, 0x6F, 0x62, 0x41, 0xCB,
];
check_assertion_response_with_extension(
get_assertion_response,
None,
signature_counter,
None,
&expected_extension_cbor,
@@ -3018,7 +3079,7 @@ mod test {
let signature_counter = storage::global_signature_counter(&mut env).unwrap();
check_assertion_response_with_user(
get_assertion_response,
user2,
Some(user2),
0x04,
signature_counter,
Some(2),
@@ -3028,7 +3089,7 @@ mod test {
let get_assertion_response = ctap_state.process_get_next_assertion(&mut env);
check_assertion_response_with_user(
get_assertion_response,
user1,
Some(user1),
0x04,
signature_counter,
None,