/src/OpenSK/libraries/opensk/src/ctap/command.rs
Line | Count | Source |
1 | | // Copyright 2019-2023 Google LLC |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | |
15 | | use super::cbor_read; |
16 | | #[cfg(feature = "fingerprint")] |
17 | | use super::data_formats::extract_bool; |
18 | | use super::data_formats::{ |
19 | | extract_array, extract_byte_string, extract_map, extract_text_string, extract_unsigned, |
20 | | ok_or_missing, ClientPinSubCommand, CoseKey, CredentialManagementSubCommand, |
21 | | CredentialManagementSubCommandParameters, GetAssertionExtensions, GetAssertionOptions, |
22 | | MakeCredentialExtensions, MakeCredentialOptions, PinUvAuthProtocol, |
23 | | PublicKeyCredentialDescriptor, PublicKeyCredentialParameter, PublicKeyCredentialRpEntity, |
24 | | PublicKeyCredentialUserEntity, |
25 | | }; |
26 | | #[cfg(feature = "config_command")] |
27 | | use super::data_formats::{ConfigSubCommand, ConfigSubCommandParams, SetMinPinLengthParams}; |
28 | | use super::status_code::Ctap2StatusCode; |
29 | | use crate::ctap::status_code::CtapResult; |
30 | | use alloc::string::String; |
31 | | use alloc::vec::Vec; |
32 | | #[cfg(feature = "fuzz")] |
33 | | use arbitrary::Arbitrary; |
34 | | use core::convert::TryFrom; |
35 | | #[cfg(all(test, feature = "fingerprint"))] |
36 | | use enum_iterator::IntoEnumIterator; |
37 | | use sk_cbor as cbor; |
38 | | #[cfg(feature = "fingerprint")] |
39 | | use sk_cbor::cbor_map_options; |
40 | | use sk_cbor::destructure_cbor_map; |
41 | | |
42 | | // CTAP specification (version 20190130) section 6.1 |
43 | | #[derive(Debug, PartialEq, Eq)] |
44 | | #[allow(clippy::enum_variant_names)] |
45 | | pub enum Command { |
46 | | AuthenticatorMakeCredential(AuthenticatorMakeCredentialParameters), |
47 | | AuthenticatorGetAssertion(AuthenticatorGetAssertionParameters), |
48 | | AuthenticatorGetInfo, |
49 | | AuthenticatorClientPin(AuthenticatorClientPinParameters), |
50 | | AuthenticatorReset, |
51 | | AuthenticatorGetNextAssertion, |
52 | | #[cfg(feature = "fingerprint")] |
53 | | AuthenticatorBioEnrollment(AuthenticatorBioEnrollmentParameters), |
54 | | AuthenticatorCredentialManagement(AuthenticatorCredentialManagementParameters), |
55 | | AuthenticatorSelection, |
56 | | AuthenticatorLargeBlobs(AuthenticatorLargeBlobsParameters), |
57 | | #[cfg(feature = "config_command")] |
58 | | AuthenticatorConfig(AuthenticatorConfigParameters), |
59 | | } |
60 | | |
61 | | impl Command { |
62 | | const AUTHENTICATOR_MAKE_CREDENTIAL: u8 = 0x01; |
63 | | const AUTHENTICATOR_GET_ASSERTION: u8 = 0x02; |
64 | | const AUTHENTICATOR_GET_INFO: u8 = 0x04; |
65 | | const AUTHENTICATOR_CLIENT_PIN: u8 = 0x06; |
66 | | const AUTHENTICATOR_RESET: u8 = 0x07; |
67 | | const AUTHENTICATOR_GET_NEXT_ASSERTION: u8 = 0x08; |
68 | | #[cfg(feature = "fingerprint")] |
69 | | const AUTHENTICATOR_BIO_ENROLLMENT: u8 = 0x09; |
70 | | const AUTHENTICATOR_CREDENTIAL_MANAGEMENT: u8 = 0x0A; |
71 | | const AUTHENTICATOR_SELECTION: u8 = 0x0B; |
72 | | const AUTHENTICATOR_LARGE_BLOBS: u8 = 0x0C; |
73 | | #[cfg(feature = "config_command")] |
74 | | const AUTHENTICATOR_CONFIG: u8 = 0x0D; |
75 | | const _AUTHENTICATOR_VENDOR_FIRST: u8 = 0x40; |
76 | | // This commands is the same as AUTHENTICATOR_CREDENTIAL_MANAGEMENT but is duplicated as a |
77 | | // vendor command for legacy and compatibility reasons. See |
78 | | // https://github.com/Yubico/libfido2/issues/628 for more information. |
79 | | const AUTHENTICATOR_VENDOR_CREDENTIAL_MANAGEMENT: u8 = 0x41; |
80 | | const _AUTHENTICATOR_VENDOR_LAST: u8 = 0xBF; |
81 | | |
82 | 8.48k | pub fn deserialize(bytes: &[u8]) -> CtapResult<Command> { |
83 | 8.48k | if bytes.is_empty() { |
84 | | // The error to return is not specified, missing parameter seems to fit best. |
85 | 242 | return Err(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER); |
86 | 8.24k | } |
87 | | |
88 | 8.24k | let command_value = bytes[0]; |
89 | 8.24k | match command_value { |
90 | | Command::AUTHENTICATOR_MAKE_CREDENTIAL => { |
91 | 2.22k | let decoded_cbor = cbor_read(&bytes[1..])?; |
92 | | Ok(Command::AuthenticatorMakeCredential( |
93 | 2.03k | AuthenticatorMakeCredentialParameters::try_from(decoded_cbor)?, |
94 | | )) |
95 | | } |
96 | | Command::AUTHENTICATOR_GET_ASSERTION => { |
97 | 1.61k | let decoded_cbor = cbor_read(&bytes[1..])?; |
98 | | Ok(Command::AuthenticatorGetAssertion( |
99 | 1.41k | AuthenticatorGetAssertionParameters::try_from(decoded_cbor)?, |
100 | | )) |
101 | | } |
102 | | Command::AUTHENTICATOR_GET_INFO => { |
103 | | // Parameters are ignored. |
104 | 45 | Ok(Command::AuthenticatorGetInfo) |
105 | | } |
106 | | Command::AUTHENTICATOR_CLIENT_PIN => { |
107 | 2.09k | let decoded_cbor = cbor_read(&bytes[1..])?; |
108 | | Ok(Command::AuthenticatorClientPin( |
109 | 1.95k | AuthenticatorClientPinParameters::try_from(decoded_cbor)?, |
110 | | )) |
111 | | } |
112 | | Command::AUTHENTICATOR_RESET => { |
113 | | // Parameters are ignored. |
114 | 33 | Ok(Command::AuthenticatorReset) |
115 | | } |
116 | | Command::AUTHENTICATOR_GET_NEXT_ASSERTION => { |
117 | | // Parameters are ignored. |
118 | 2 | Ok(Command::AuthenticatorGetNextAssertion) |
119 | | } |
120 | | #[cfg(feature = "fingerprint")] |
121 | | Command::AUTHENTICATOR_BIO_ENROLLMENT => { |
122 | | let decoded_cbor = cbor_read(&bytes[1..])?; |
123 | | Ok(Command::AuthenticatorBioEnrollment( |
124 | | AuthenticatorBioEnrollmentParameters::try_from(decoded_cbor)?, |
125 | | )) |
126 | | } |
127 | | Command::AUTHENTICATOR_CREDENTIAL_MANAGEMENT |
128 | | | Command::AUTHENTICATOR_VENDOR_CREDENTIAL_MANAGEMENT => { |
129 | 522 | let decoded_cbor = cbor_read(&bytes[1..])?; |
130 | | Ok(Command::AuthenticatorCredentialManagement( |
131 | 383 | AuthenticatorCredentialManagementParameters::try_from(decoded_cbor)?, |
132 | | )) |
133 | | } |
134 | | Command::AUTHENTICATOR_SELECTION => { |
135 | | // Parameters are ignored. |
136 | 1 | Ok(Command::AuthenticatorSelection) |
137 | | } |
138 | | Command::AUTHENTICATOR_LARGE_BLOBS => { |
139 | 971 | let decoded_cbor = cbor_read(&bytes[1..])?; |
140 | | Ok(Command::AuthenticatorLargeBlobs( |
141 | 815 | AuthenticatorLargeBlobsParameters::try_from(decoded_cbor)?, |
142 | | )) |
143 | | } |
144 | | #[cfg(feature = "config_command")] |
145 | | Command::AUTHENTICATOR_CONFIG => { |
146 | 739 | let decoded_cbor = cbor_read(&bytes[1..])?; |
147 | | Ok(Command::AuthenticatorConfig( |
148 | 506 | AuthenticatorConfigParameters::try_from(decoded_cbor)?, |
149 | | )) |
150 | | } |
151 | 1 | _ => Err(Ctap2StatusCode::CTAP1_ERR_INVALID_COMMAND), |
152 | | } |
153 | 8.48k | } |
154 | | } |
155 | | |
156 | | #[derive(Clone, Debug, PartialEq, Eq)] |
157 | 0 | #[cfg_attr(feature = "fuzz", derive(Arbitrary))] Unexecuted instantiation: <opensk::ctap::command::AuthenticatorMakeCredentialParameters as arbitrary::Arbitrary>::arbitrary_take_rest Unexecuted instantiation: <opensk::ctap::command::AuthenticatorMakeCredentialParameters as arbitrary::Arbitrary>::arbitrary Unexecuted instantiation: <opensk::ctap::command::AuthenticatorMakeCredentialParameters as arbitrary::Arbitrary>::shrink::{closure#0} |
158 | | pub struct AuthenticatorMakeCredentialParameters { |
159 | | pub client_data_hash: Vec<u8>, |
160 | | pub rp: PublicKeyCredentialRpEntity, |
161 | | pub user: PublicKeyCredentialUserEntity, |
162 | | pub pub_key_cred_params: Vec<PublicKeyCredentialParameter>, |
163 | | pub exclude_list: Option<Vec<PublicKeyCredentialDescriptor>>, |
164 | | // Extensions are optional, but we can use defaults for all missing fields. |
165 | | pub extensions: MakeCredentialExtensions, |
166 | | // Same for options, use defaults when not present. |
167 | | pub options: MakeCredentialOptions, |
168 | | pub pin_uv_auth_param: Option<Vec<u8>>, |
169 | | pub pin_uv_auth_protocol: Option<PinUvAuthProtocol>, |
170 | | pub enterprise_attestation: Option<u64>, |
171 | | } |
172 | | |
173 | | impl TryFrom<cbor::Value> for AuthenticatorMakeCredentialParameters { |
174 | | type Error = Ctap2StatusCode; |
175 | | |
176 | 4.76k | fn try_from(cbor_value: cbor::Value) -> CtapResult<Self> { |
177 | 4.61k | destructure_cbor_map! { |
178 | | let { |
179 | | 0x01 => client_data_hash, |
180 | | 0x02 => rp, |
181 | | 0x03 => user, |
182 | | 0x04 => cred_param_vec, |
183 | | 0x05 => exclude_list, |
184 | | 0x06 => extensions, |
185 | | 0x07 => options, |
186 | | 0x08 => pin_uv_auth_param, |
187 | | 0x09 => pin_uv_auth_protocol, |
188 | | 0x0A => enterprise_attestation, |
189 | 4.76k | } = extract_map(cbor_value)?; |
190 | | } |
191 | | |
192 | 4.61k | let client_data_hash = extract_byte_string(ok_or_missing(client_data_hash)?)?; |
193 | 4.20k | let rp = PublicKeyCredentialRpEntity::try_from(ok_or_missing(rp)?)?; |
194 | 4.09k | let user = PublicKeyCredentialUserEntity::try_from(ok_or_missing(user)?)?; |
195 | | |
196 | 4.03k | let cred_param_vec = extract_array(ok_or_missing(cred_param_vec)?)?; |
197 | 3.99k | let pub_key_cred_params = cred_param_vec |
198 | 3.99k | .into_iter() |
199 | 3.99k | .map(PublicKeyCredentialParameter::try_from) |
200 | 3.99k | .collect::<CtapResult<Vec<PublicKeyCredentialParameter>>>()?; |
201 | | |
202 | 3.84k | let exclude_list = match exclude_list { |
203 | 485 | Some(entry) => { |
204 | 485 | let exclude_list_vec = extract_array(entry)?; |
205 | 480 | let exclude_list = exclude_list_vec |
206 | 480 | .into_iter() |
207 | 480 | .map(PublicKeyCredentialDescriptor::try_from) |
208 | 480 | .collect::<CtapResult<Vec<PublicKeyCredentialDescriptor>>>()?; |
209 | 341 | Some(exclude_list) |
210 | | } |
211 | 3.36k | None => None, |
212 | | }; |
213 | | |
214 | 3.70k | let extensions = extensions |
215 | 3.70k | .map(MakeCredentialExtensions::try_from) |
216 | 3.70k | .transpose()? |
217 | 3.39k | .unwrap_or_default(); |
218 | | |
219 | 3.39k | let options = options |
220 | 3.39k | .map(MakeCredentialOptions::try_from) |
221 | 3.39k | .transpose()? |
222 | 3.32k | .unwrap_or_default(); |
223 | | |
224 | 3.32k | let pin_uv_auth_param = pin_uv_auth_param.map(extract_byte_string).transpose()?; |
225 | 3.31k | let pin_uv_auth_protocol = pin_uv_auth_protocol |
226 | 3.31k | .map(PinUvAuthProtocol::try_from) |
227 | 3.31k | .transpose()?; |
228 | | // We don't convert into EnterpriseAttestationMode to maintain the correct order of errors. |
229 | 3.30k | let enterprise_attestation = enterprise_attestation.map(extract_unsigned).transpose()?; |
230 | | |
231 | 3.29k | Ok(AuthenticatorMakeCredentialParameters { |
232 | 3.29k | client_data_hash, |
233 | 3.29k | rp, |
234 | 3.29k | user, |
235 | 3.29k | pub_key_cred_params, |
236 | 3.29k | exclude_list, |
237 | 3.29k | extensions, |
238 | 3.29k | options, |
239 | 3.29k | pin_uv_auth_param, |
240 | 3.29k | pin_uv_auth_protocol, |
241 | 3.29k | enterprise_attestation, |
242 | 3.29k | }) |
243 | 4.76k | } |
244 | | } |
245 | | |
246 | | #[derive(Debug, PartialEq, Eq)] |
247 | 0 | #[cfg_attr(feature = "fuzz", derive(Arbitrary))] Unexecuted instantiation: <opensk::ctap::command::AuthenticatorGetAssertionParameters as arbitrary::Arbitrary>::arbitrary_take_rest Unexecuted instantiation: <opensk::ctap::command::AuthenticatorGetAssertionParameters as arbitrary::Arbitrary>::arbitrary Unexecuted instantiation: <opensk::ctap::command::AuthenticatorGetAssertionParameters as arbitrary::Arbitrary>::shrink::{closure#0} |
248 | | pub struct AuthenticatorGetAssertionParameters { |
249 | | pub rp_id: String, |
250 | | pub client_data_hash: Vec<u8>, |
251 | | pub allow_list: Option<Vec<PublicKeyCredentialDescriptor>>, |
252 | | // Extensions are optional, but we can use defaults for all missing fields. |
253 | | pub extensions: GetAssertionExtensions, |
254 | | // Same for options, use defaults when not present. |
255 | | pub options: GetAssertionOptions, |
256 | | pub pin_uv_auth_param: Option<Vec<u8>>, |
257 | | pub pin_uv_auth_protocol: Option<PinUvAuthProtocol>, |
258 | | } |
259 | | |
260 | | impl TryFrom<cbor::Value> for AuthenticatorGetAssertionParameters { |
261 | | type Error = Ctap2StatusCode; |
262 | | |
263 | 3.50k | fn try_from(cbor_value: cbor::Value) -> CtapResult<Self> { |
264 | 3.35k | destructure_cbor_map! { |
265 | | let { |
266 | | 0x01 => rp_id, |
267 | | 0x02 => client_data_hash, |
268 | | 0x03 => allow_list, |
269 | | 0x04 => extensions, |
270 | | 0x05 => options, |
271 | | 0x06 => pin_uv_auth_param, |
272 | | 0x07 => pin_uv_auth_protocol, |
273 | 3.50k | } = extract_map(cbor_value)?; |
274 | | } |
275 | | |
276 | 3.35k | let rp_id = extract_text_string(ok_or_missing(rp_id)?)?; |
277 | 3.03k | let client_data_hash = extract_byte_string(ok_or_missing(client_data_hash)?)?; |
278 | | |
279 | 3.02k | let allow_list = match allow_list { |
280 | 1.02k | Some(entry) => { |
281 | 1.02k | let allow_list_vec = extract_array(entry)?; |
282 | 1.01k | let allow_list = allow_list_vec |
283 | 1.01k | .into_iter() |
284 | 1.01k | .map(PublicKeyCredentialDescriptor::try_from) |
285 | 1.01k | .collect::<CtapResult<Vec<PublicKeyCredentialDescriptor>>>()?; |
286 | 767 | Some(allow_list) |
287 | | } |
288 | 1.99k | None => None, |
289 | | }; |
290 | | |
291 | 2.76k | let extensions = extensions |
292 | 2.76k | .map(GetAssertionExtensions::try_from) |
293 | 2.76k | .transpose()? |
294 | 2.51k | .unwrap_or_default(); |
295 | | |
296 | 2.51k | let options = options |
297 | 2.51k | .map(GetAssertionOptions::try_from) |
298 | 2.51k | .transpose()? |
299 | 2.44k | .unwrap_or_default(); |
300 | | |
301 | 2.44k | let pin_uv_auth_param = pin_uv_auth_param.map(extract_byte_string).transpose()?; |
302 | 2.42k | let pin_uv_auth_protocol = pin_uv_auth_protocol |
303 | 2.42k | .map(PinUvAuthProtocol::try_from) |
304 | 2.42k | .transpose()?; |
305 | | |
306 | 2.40k | Ok(AuthenticatorGetAssertionParameters { |
307 | 2.40k | rp_id, |
308 | 2.40k | client_data_hash, |
309 | 2.40k | allow_list, |
310 | 2.40k | extensions, |
311 | 2.40k | options, |
312 | 2.40k | pin_uv_auth_param, |
313 | 2.40k | pin_uv_auth_protocol, |
314 | 2.40k | }) |
315 | 3.50k | } |
316 | | } |
317 | | |
318 | | #[derive(Clone, Debug, PartialEq, Eq)] |
319 | 0 | #[cfg_attr(feature = "fuzz", derive(Arbitrary))] Unexecuted instantiation: <opensk::ctap::command::AuthenticatorClientPinParameters as arbitrary::Arbitrary>::arbitrary_take_rest Unexecuted instantiation: <opensk::ctap::command::AuthenticatorClientPinParameters as arbitrary::Arbitrary>::arbitrary Unexecuted instantiation: <opensk::ctap::command::AuthenticatorClientPinParameters as arbitrary::Arbitrary>::shrink::{closure#0} |
320 | | pub struct AuthenticatorClientPinParameters { |
321 | | pub pin_uv_auth_protocol: PinUvAuthProtocol, |
322 | | pub sub_command: ClientPinSubCommand, |
323 | | pub key_agreement: Option<CoseKey>, |
324 | | pub pin_uv_auth_param: Option<Vec<u8>>, |
325 | | pub new_pin_enc: Option<Vec<u8>>, |
326 | | pub pin_hash_enc: Option<Vec<u8>>, |
327 | | pub permissions: Option<u8>, |
328 | | pub permissions_rp_id: Option<String>, |
329 | | } |
330 | | |
331 | | impl TryFrom<cbor::Value> for AuthenticatorClientPinParameters { |
332 | | type Error = Ctap2StatusCode; |
333 | | |
334 | 3.98k | fn try_from(cbor_value: cbor::Value) -> CtapResult<Self> { |
335 | 3.83k | destructure_cbor_map! { |
336 | | let { |
337 | | 0x01 => pin_uv_auth_protocol, |
338 | | 0x02 => sub_command, |
339 | | 0x03 => key_agreement, |
340 | | 0x04 => pin_uv_auth_param, |
341 | | 0x05 => new_pin_enc, |
342 | | 0x06 => pin_hash_enc, |
343 | | 0x09 => permissions, |
344 | | 0x0A => permissions_rp_id, |
345 | 3.98k | } = extract_map(cbor_value)?; |
346 | | } |
347 | | |
348 | 3.53k | let pin_uv_auth_protocol = |
349 | 3.83k | PinUvAuthProtocol::try_from(ok_or_missing(pin_uv_auth_protocol)?)?; |
350 | 3.53k | let sub_command = ClientPinSubCommand::try_from(ok_or_missing(sub_command)?)?; |
351 | 3.36k | let key_agreement = key_agreement.map(CoseKey::try_from).transpose()?; |
352 | 3.18k | let pin_uv_auth_param = pin_uv_auth_param.map(extract_byte_string).transpose()?; |
353 | 3.18k | let new_pin_enc = new_pin_enc.map(extract_byte_string).transpose()?; |
354 | 3.17k | let pin_hash_enc = pin_hash_enc.map(extract_byte_string).transpose()?; |
355 | | // We expect a bit field of 8 bits, and drop everything else. |
356 | | // This means we ignore extensions in future versions. |
357 | 3.16k | let permissions = permissions |
358 | 3.16k | .map(extract_unsigned) |
359 | 3.16k | .transpose()? |
360 | 3.13k | .map(|p| p as u8); |
361 | 3.13k | let permissions_rp_id = permissions_rp_id.map(extract_text_string).transpose()?; |
362 | | |
363 | 3.11k | Ok(AuthenticatorClientPinParameters { |
364 | 3.11k | pin_uv_auth_protocol, |
365 | 3.11k | sub_command, |
366 | 3.11k | key_agreement, |
367 | 3.11k | pin_uv_auth_param, |
368 | 3.11k | new_pin_enc, |
369 | 3.11k | pin_hash_enc, |
370 | 3.11k | permissions, |
371 | 3.11k | permissions_rp_id, |
372 | 3.11k | }) |
373 | 3.98k | } |
374 | | } |
375 | | |
376 | | #[cfg(feature = "fingerprint")] |
377 | | #[derive(Clone, Debug, PartialEq, Eq)] |
378 | | pub struct AuthenticatorBioEnrollmentParameters { |
379 | | pub modality: Option<u64>, |
380 | | pub sub_command: Option<BioEnrollmentSubCommand>, |
381 | | pub sub_command_params: Option<BioEnrollmentSubCommandParams>, |
382 | | pub pin_uv_auth_protocol: Option<PinUvAuthProtocol>, |
383 | | pub pin_uv_auth_param: Option<Vec<u8>>, |
384 | | pub get_modality: Option<bool>, |
385 | | } |
386 | | |
387 | | #[cfg(feature = "fingerprint")] |
388 | | impl TryFrom<cbor::Value> for AuthenticatorBioEnrollmentParameters { |
389 | | type Error = Ctap2StatusCode; |
390 | | |
391 | | fn try_from(cbor_value: cbor::Value) -> Result<Self, Self::Error> { |
392 | | destructure_cbor_map! { |
393 | | let { |
394 | | 0x01 => modality, |
395 | | 0x02 => sub_command, |
396 | | 0x03 => sub_command_params, |
397 | | 0x04 => pin_uv_auth_protocol, |
398 | | 0x05 => pin_uv_auth_param, |
399 | | 0x06 => get_modality, |
400 | | } = extract_map(cbor_value)?; |
401 | | } |
402 | | |
403 | | let modality = modality.map(extract_unsigned).transpose()?; |
404 | | let sub_command = sub_command |
405 | | .map(BioEnrollmentSubCommand::try_from) |
406 | | .transpose()?; |
407 | | let sub_command_params = sub_command_params |
408 | | .map(BioEnrollmentSubCommandParams::try_from) |
409 | | .transpose()?; |
410 | | let pin_uv_auth_protocol = pin_uv_auth_protocol |
411 | | .map(PinUvAuthProtocol::try_from) |
412 | | .transpose()?; |
413 | | let pin_uv_auth_param = pin_uv_auth_param.map(extract_byte_string).transpose()?; |
414 | | let get_modality = get_modality.map(extract_bool).transpose()?; |
415 | | |
416 | | Ok(AuthenticatorBioEnrollmentParameters { |
417 | | modality, |
418 | | sub_command, |
419 | | sub_command_params, |
420 | | pin_uv_auth_protocol, |
421 | | pin_uv_auth_param, |
422 | | get_modality, |
423 | | }) |
424 | | } |
425 | | } |
426 | | |
427 | | #[cfg(feature = "fingerprint")] |
428 | | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
429 | | #[cfg_attr(test, derive(IntoEnumIterator))] |
430 | | pub enum BioEnrollmentSubCommand { |
431 | | EnrollBegin = 0x01, |
432 | | EnrollCaptureNextSample = 0x02, |
433 | | CancelCurrentEnrollment = 0x03, |
434 | | EnumerateEnrollments = 0x04, |
435 | | SetFriendlyName = 0x05, |
436 | | RemoveEnrollment = 0x06, |
437 | | GetFingerprintSensorInfo = 0x07, |
438 | | } |
439 | | |
440 | | #[cfg(feature = "fingerprint")] |
441 | | impl From<BioEnrollmentSubCommand> for cbor::Value { |
442 | | fn from(subcommand: BioEnrollmentSubCommand) -> Self { |
443 | | (subcommand as u64).into() |
444 | | } |
445 | | } |
446 | | |
447 | | #[cfg(feature = "fingerprint")] |
448 | | impl TryFrom<cbor::Value> for BioEnrollmentSubCommand { |
449 | | type Error = Ctap2StatusCode; |
450 | | |
451 | | fn try_from(cbor_value: cbor::Value) -> CtapResult<Self> { |
452 | | let subcommand_int = extract_unsigned(cbor_value)?; |
453 | | match subcommand_int { |
454 | | 0x01 => Ok(BioEnrollmentSubCommand::EnrollBegin), |
455 | | 0x02 => Ok(BioEnrollmentSubCommand::EnrollCaptureNextSample), |
456 | | 0x03 => Ok(BioEnrollmentSubCommand::CancelCurrentEnrollment), |
457 | | 0x04 => Ok(BioEnrollmentSubCommand::EnumerateEnrollments), |
458 | | 0x05 => Ok(BioEnrollmentSubCommand::SetFriendlyName), |
459 | | 0x06 => Ok(BioEnrollmentSubCommand::RemoveEnrollment), |
460 | | 0x07 => Ok(BioEnrollmentSubCommand::GetFingerprintSensorInfo), |
461 | | _ => Err(Ctap2StatusCode::CTAP2_ERR_INVALID_SUBCOMMAND), |
462 | | } |
463 | | } |
464 | | } |
465 | | |
466 | | #[cfg(feature = "fingerprint")] |
467 | | #[derive(Clone, Debug, PartialEq, Eq)] |
468 | | pub struct BioEnrollmentSubCommandParams { |
469 | | pub template_id: Option<Vec<u8>>, |
470 | | pub template_friendly_name: Option<String>, |
471 | | pub timeout_milliseconds: Option<usize>, |
472 | | } |
473 | | |
474 | | #[cfg(feature = "fingerprint")] |
475 | | impl TryFrom<cbor::Value> for BioEnrollmentSubCommandParams { |
476 | | type Error = Ctap2StatusCode; |
477 | | |
478 | | fn try_from(cbor_value: cbor::Value) -> Result<Self, Self::Error> { |
479 | | destructure_cbor_map! { |
480 | | let { |
481 | | 0x01 => template_id, |
482 | | 0x02 => template_friendly_name, |
483 | | 0x03 => timeout_milliseconds, |
484 | | } = extract_map(cbor_value)?; |
485 | | } |
486 | | |
487 | | let template_id = template_id.map(extract_byte_string).transpose()?; |
488 | | let template_friendly_name = template_friendly_name |
489 | | .map(extract_text_string) |
490 | | .transpose()?; |
491 | | let timeout_milliseconds = timeout_milliseconds |
492 | | .map(extract_unsigned) |
493 | | .transpose()? |
494 | | .map(usize::try_from) |
495 | | .transpose() |
496 | | .map_err(|_| Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)?; |
497 | | |
498 | | Ok(BioEnrollmentSubCommandParams { |
499 | | template_id, |
500 | | template_friendly_name, |
501 | | timeout_milliseconds, |
502 | | }) |
503 | | } |
504 | | } |
505 | | |
506 | | #[cfg(feature = "fingerprint")] |
507 | | impl From<BioEnrollmentSubCommandParams> for cbor::Value { |
508 | | fn from(sub_command_params: BioEnrollmentSubCommandParams) -> Self { |
509 | | cbor_map_options! { |
510 | | 0x01 => sub_command_params.template_id, |
511 | | 0x02 => sub_command_params.template_friendly_name, |
512 | | 0x03 => sub_command_params.timeout_milliseconds.map(|u| u as u64), |
513 | | } |
514 | | } |
515 | | } |
516 | | |
517 | | #[derive(Debug, PartialEq, Eq)] |
518 | | pub struct AuthenticatorLargeBlobsParameters { |
519 | | pub get: Option<usize>, |
520 | | pub set: Option<Vec<u8>>, |
521 | | pub offset: usize, |
522 | | pub length: Option<usize>, |
523 | | pub pin_uv_auth_param: Option<Vec<u8>>, |
524 | | pub pin_uv_auth_protocol: Option<PinUvAuthProtocol>, |
525 | | } |
526 | | |
527 | | impl TryFrom<cbor::Value> for AuthenticatorLargeBlobsParameters { |
528 | | type Error = Ctap2StatusCode; |
529 | | |
530 | 815 | fn try_from(cbor_value: cbor::Value) -> CtapResult<Self> { |
531 | 782 | destructure_cbor_map! { |
532 | | let { |
533 | | 0x01 => get, |
534 | | 0x02 => set, |
535 | | 0x03 => offset, |
536 | | 0x04 => length, |
537 | | 0x05 => pin_uv_auth_param, |
538 | | 0x06 => pin_uv_auth_protocol, |
539 | 815 | } = extract_map(cbor_value)?; |
540 | | } |
541 | | |
542 | | // careful: some missing parameters here are CTAP1_ERR_INVALID_PARAMETER |
543 | 782 | let get = get.map(extract_unsigned).transpose()?.map(|u| u as usize); |
544 | 781 | let set = set.map(extract_byte_string).transpose()?; |
545 | 700 | let offset = |
546 | 776 | extract_unsigned(offset.ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)?)? as usize; |
547 | 700 | let length = length |
548 | 700 | .map(extract_unsigned) |
549 | 700 | .transpose()? |
550 | 697 | .map(|u| u as usize); |
551 | 697 | let pin_uv_auth_param = pin_uv_auth_param.map(extract_byte_string).transpose()?; |
552 | 693 | let pin_uv_auth_protocol = pin_uv_auth_protocol |
553 | 693 | .map(PinUvAuthProtocol::try_from) |
554 | 693 | .transpose()?; |
555 | | |
556 | 690 | if get.is_some() == set.is_some() { |
557 | 6 | return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); |
558 | 684 | } |
559 | 684 | if get.is_some() |
560 | 333 | && (length.is_some() || pin_uv_auth_param.is_some() || pin_uv_auth_protocol.is_some()) |
561 | | { |
562 | 10 | return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); |
563 | 674 | } |
564 | 674 | if set.is_some() && ((offset == 0) != length.is_some()) { |
565 | 2 | return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); |
566 | 672 | } |
567 | | |
568 | 672 | Ok(AuthenticatorLargeBlobsParameters { |
569 | 672 | get, |
570 | 672 | set, |
571 | 672 | offset, |
572 | 672 | length, |
573 | 672 | pin_uv_auth_param, |
574 | 672 | pin_uv_auth_protocol, |
575 | 672 | }) |
576 | 815 | } |
577 | | } |
578 | | |
579 | | #[cfg(feature = "config_command")] |
580 | | #[derive(Debug, PartialEq, Eq)] |
581 | | pub struct AuthenticatorConfigParameters { |
582 | | pub sub_command: ConfigSubCommand, |
583 | | pub sub_command_params: Option<ConfigSubCommandParams>, |
584 | | pub pin_uv_auth_protocol: Option<PinUvAuthProtocol>, |
585 | | pub pin_uv_auth_param: Option<Vec<u8>>, |
586 | | } |
587 | | |
588 | | #[cfg(feature = "config_command")] |
589 | | impl TryFrom<cbor::Value> for AuthenticatorConfigParameters { |
590 | | type Error = Ctap2StatusCode; |
591 | | |
592 | 506 | fn try_from(cbor_value: cbor::Value) -> CtapResult<Self> { |
593 | 474 | destructure_cbor_map! { |
594 | | let { |
595 | | 0x01 => sub_command, |
596 | | 0x02 => sub_command_params, |
597 | | 0x03 => pin_uv_auth_protocol, |
598 | | 0x04 => pin_uv_auth_param, |
599 | 506 | } = extract_map(cbor_value)?; |
600 | | } |
601 | | |
602 | 474 | let sub_command = ConfigSubCommand::try_from(ok_or_missing(sub_command)?)?; |
603 | 364 | let sub_command_params = match sub_command { |
604 | | ConfigSubCommand::SetMinPinLength => Some(ConfigSubCommandParams::SetMinPinLength( |
605 | 344 | SetMinPinLengthParams::try_from(ok_or_missing(sub_command_params)?)?, |
606 | | )), |
607 | 20 | _ => None, |
608 | | }; |
609 | 247 | let pin_uv_auth_protocol = pin_uv_auth_protocol |
610 | 247 | .map(PinUvAuthProtocol::try_from) |
611 | 247 | .transpose()?; |
612 | 239 | let pin_uv_auth_param = pin_uv_auth_param.map(extract_byte_string).transpose()?; |
613 | | |
614 | 231 | Ok(AuthenticatorConfigParameters { |
615 | 231 | sub_command, |
616 | 231 | sub_command_params, |
617 | 231 | pin_uv_auth_protocol, |
618 | 231 | pin_uv_auth_param, |
619 | 231 | }) |
620 | 506 | } |
621 | | } |
622 | | |
623 | | #[derive(Debug, PartialEq, Eq)] |
624 | | pub struct AuthenticatorCredentialManagementParameters { |
625 | | pub sub_command: CredentialManagementSubCommand, |
626 | | pub sub_command_params: Option<CredentialManagementSubCommandParameters>, |
627 | | pub pin_uv_auth_protocol: Option<PinUvAuthProtocol>, |
628 | | pub pin_uv_auth_param: Option<Vec<u8>>, |
629 | | } |
630 | | |
631 | | impl TryFrom<cbor::Value> for AuthenticatorCredentialManagementParameters { |
632 | | type Error = Ctap2StatusCode; |
633 | | |
634 | 383 | fn try_from(cbor_value: cbor::Value) -> CtapResult<Self> { |
635 | 363 | destructure_cbor_map! { |
636 | | let { |
637 | | 0x01 => sub_command, |
638 | | 0x02 => sub_command_params, |
639 | | 0x03 => pin_uv_auth_protocol, |
640 | | 0x04 => pin_uv_auth_param, |
641 | 383 | } = extract_map(cbor_value)?; |
642 | | } |
643 | | |
644 | 363 | let sub_command = CredentialManagementSubCommand::try_from(ok_or_missing(sub_command)?)?; |
645 | 239 | let sub_command_params = sub_command_params |
646 | 239 | .map(CredentialManagementSubCommandParameters::try_from) |
647 | 239 | .transpose()?; |
648 | 126 | let pin_uv_auth_protocol = pin_uv_auth_protocol |
649 | 126 | .map(PinUvAuthProtocol::try_from) |
650 | 126 | .transpose()?; |
651 | 119 | let pin_uv_auth_param = pin_uv_auth_param.map(extract_byte_string).transpose()?; |
652 | | |
653 | 106 | Ok(AuthenticatorCredentialManagementParameters { |
654 | 106 | sub_command, |
655 | 106 | sub_command_params, |
656 | 106 | pin_uv_auth_protocol, |
657 | 106 | pin_uv_auth_param, |
658 | 106 | }) |
659 | 383 | } |
660 | | } |
661 | | |
662 | | #[cfg(test)] |
663 | | mod test { |
664 | | use super::super::data_formats::{ |
665 | | AuthenticatorTransport, PublicKeyCredentialRpEntity, PublicKeyCredentialType, |
666 | | PublicKeyCredentialUserEntity, |
667 | | }; |
668 | | use super::super::ES256_CRED_PARAM; |
669 | | use super::*; |
670 | | #[cfg(feature = "fingerprint")] |
671 | | use cbor::cbor_int; |
672 | | use cbor::{cbor_array, cbor_map}; |
673 | | |
674 | | #[test] |
675 | | fn test_from_cbor_make_credential_parameters() { |
676 | | let cbor_value = cbor_map! { |
677 | | 0x01 => vec![0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F], |
678 | | 0x02 => cbor_map! { |
679 | | "id" => "example.com", |
680 | | "icon" => "example.com/icon.png", |
681 | | "name" => "Example", |
682 | | }, |
683 | | 0x03 => cbor_map! { |
684 | | "id" => vec![0x1D, 0x1D, 0x1D, 0x1D], |
685 | | "icon" => "example.com/foo/icon.png", |
686 | | "name" => "foo", |
687 | | "displayName" => "bar", |
688 | | }, |
689 | | 0x04 => cbor_array![ES256_CRED_PARAM], |
690 | | 0x05 => cbor_array![], |
691 | | 0x08 => vec![0x12, 0x34], |
692 | | 0x09 => 1, |
693 | | 0x0A => 2, |
694 | | }; |
695 | | let returned_make_credential_parameters = |
696 | | AuthenticatorMakeCredentialParameters::try_from(cbor_value).unwrap(); |
697 | | |
698 | | let client_data_hash = vec![ |
699 | | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, |
700 | | 0x0E, 0x0F, |
701 | | ]; |
702 | | let rp = PublicKeyCredentialRpEntity { |
703 | | rp_id: "example.com".to_string(), |
704 | | rp_name: Some("Example".to_string()), |
705 | | rp_icon: Some("example.com/icon.png".to_string()), |
706 | | }; |
707 | | let user = PublicKeyCredentialUserEntity { |
708 | | user_id: vec![0x1D, 0x1D, 0x1D, 0x1D], |
709 | | user_name: Some("foo".to_string()), |
710 | | user_display_name: Some("bar".to_string()), |
711 | | user_icon: Some("example.com/foo/icon.png".to_string()), |
712 | | }; |
713 | | let options = MakeCredentialOptions { |
714 | | rk: false, |
715 | | uv: false, |
716 | | }; |
717 | | let expected_make_credential_parameters = AuthenticatorMakeCredentialParameters { |
718 | | client_data_hash, |
719 | | rp, |
720 | | user, |
721 | | pub_key_cred_params: vec![ES256_CRED_PARAM], |
722 | | exclude_list: Some(vec![]), |
723 | | extensions: MakeCredentialExtensions::default(), |
724 | | options, |
725 | | pin_uv_auth_param: Some(vec![0x12, 0x34]), |
726 | | pin_uv_auth_protocol: Some(PinUvAuthProtocol::V1), |
727 | | enterprise_attestation: Some(2), |
728 | | }; |
729 | | |
730 | | assert_eq!( |
731 | | returned_make_credential_parameters, |
732 | | expected_make_credential_parameters |
733 | | ); |
734 | | } |
735 | | |
736 | | #[test] |
737 | | fn test_from_cbor_get_assertion_parameters() { |
738 | | let cbor_value = cbor_map! { |
739 | | 0x01 => "example.com", |
740 | | 0x02 => vec![0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F], |
741 | | 0x03 => cbor_array![ cbor_map! { |
742 | | "id" => vec![0x2D, 0x2D, 0x2D, 0x2D], |
743 | | "type" => "public-key", |
744 | | "transports" => cbor_array!["usb"], |
745 | | } ], |
746 | | 0x06 => vec![0x12, 0x34], |
747 | | 0x07 => 1, |
748 | | }; |
749 | | let returned_get_assertion_parameters = |
750 | | AuthenticatorGetAssertionParameters::try_from(cbor_value).unwrap(); |
751 | | |
752 | | let rp_id = "example.com".to_string(); |
753 | | let client_data_hash = vec![ |
754 | | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, |
755 | | 0x0E, 0x0F, |
756 | | ]; |
757 | | let pub_key_cred_descriptor = PublicKeyCredentialDescriptor { |
758 | | key_type: PublicKeyCredentialType::PublicKey, |
759 | | key_id: vec![0x2D, 0x2D, 0x2D, 0x2D], |
760 | | transports: Some(vec![AuthenticatorTransport::Usb]), |
761 | | }; |
762 | | let options = GetAssertionOptions { |
763 | | up: true, |
764 | | uv: false, |
765 | | }; |
766 | | let expected_get_assertion_parameters = AuthenticatorGetAssertionParameters { |
767 | | rp_id, |
768 | | client_data_hash, |
769 | | allow_list: Some(vec![pub_key_cred_descriptor]), |
770 | | extensions: GetAssertionExtensions::default(), |
771 | | options, |
772 | | pin_uv_auth_param: Some(vec![0x12, 0x34]), |
773 | | pin_uv_auth_protocol: Some(PinUvAuthProtocol::V1), |
774 | | }; |
775 | | |
776 | | assert_eq!( |
777 | | returned_get_assertion_parameters, |
778 | | expected_get_assertion_parameters |
779 | | ); |
780 | | } |
781 | | |
782 | | #[test] |
783 | | fn test_from_cbor_client_pin_parameters() { |
784 | | let cose_key = CoseKey::example_ecdh_pubkey(); |
785 | | let cbor_value = cbor_map! { |
786 | | 0x01 => 1, |
787 | | 0x02 => ClientPinSubCommand::GetPinRetries, |
788 | | 0x03 => cbor::Value::from(cose_key.clone()), |
789 | | 0x04 => vec! [0xBB], |
790 | | 0x05 => vec! [0xCC], |
791 | | 0x06 => vec! [0xDD], |
792 | | 0x09 => 0x03, |
793 | | 0x0A => "example.com", |
794 | | }; |
795 | | let returned_client_pin_parameters = |
796 | | AuthenticatorClientPinParameters::try_from(cbor_value).unwrap(); |
797 | | |
798 | | let expected_client_pin_parameters = AuthenticatorClientPinParameters { |
799 | | pin_uv_auth_protocol: PinUvAuthProtocol::V1, |
800 | | sub_command: ClientPinSubCommand::GetPinRetries, |
801 | | key_agreement: Some(cose_key), |
802 | | pin_uv_auth_param: Some(vec![0xBB]), |
803 | | new_pin_enc: Some(vec![0xCC]), |
804 | | pin_hash_enc: Some(vec![0xDD]), |
805 | | permissions: Some(0x03), |
806 | | permissions_rp_id: Some("example.com".to_string()), |
807 | | }; |
808 | | assert_eq!( |
809 | | returned_client_pin_parameters, |
810 | | expected_client_pin_parameters |
811 | | ); |
812 | | } |
813 | | |
814 | | #[test] |
815 | | fn test_deserialize_get_info() { |
816 | | let cbor_bytes = [Command::AUTHENTICATOR_GET_INFO]; |
817 | | let command = Command::deserialize(&cbor_bytes); |
818 | | assert_eq!(command, Ok(Command::AuthenticatorGetInfo)); |
819 | | } |
820 | | |
821 | | #[test] |
822 | | fn test_deserialize_reset() { |
823 | | // Adding some random bytes to see if they are ignored. |
824 | | let cbor_bytes = [Command::AUTHENTICATOR_RESET, 0xAB, 0xCD, 0xEF]; |
825 | | let command = Command::deserialize(&cbor_bytes); |
826 | | assert_eq!(command, Ok(Command::AuthenticatorReset)); |
827 | | } |
828 | | |
829 | | #[test] |
830 | | fn test_deserialize_get_next_assertion() { |
831 | | let cbor_bytes = [Command::AUTHENTICATOR_GET_NEXT_ASSERTION]; |
832 | | let command = Command::deserialize(&cbor_bytes); |
833 | | assert_eq!(command, Ok(Command::AuthenticatorGetNextAssertion)); |
834 | | } |
835 | | |
836 | | #[test] |
837 | | #[cfg(feature = "fingerprint")] |
838 | | fn test_from_cbor_bio_enrollment_parameters() { |
839 | | let sub_command_params_cbor_value = cbor_map! { |
840 | | 0x01 => vec![0x01], |
841 | | 0x02 => "Name", |
842 | | 0x03 => 1000, |
843 | | }; |
844 | | let cbor_value = cbor_map! { |
845 | | 0x01 => 1, |
846 | | 0x02 => BioEnrollmentSubCommand::EnrollBegin, |
847 | | 0x03 => sub_command_params_cbor_value, |
848 | | 0x04 => 1, |
849 | | 0x05 => vec![0xBB], |
850 | | 0x06 => true, |
851 | | }; |
852 | | let returned_bio_enrollment_parameters = |
853 | | AuthenticatorBioEnrollmentParameters::try_from(cbor_value).unwrap(); |
854 | | |
855 | | let expected_sub_command_params = BioEnrollmentSubCommandParams { |
856 | | template_id: Some(vec![0x01]), |
857 | | template_friendly_name: Some(String::from("Name")), |
858 | | timeout_milliseconds: Some(1000), |
859 | | }; |
860 | | let expected_bio_enrollment_parameters = AuthenticatorBioEnrollmentParameters { |
861 | | modality: Some(1), |
862 | | sub_command: Some(BioEnrollmentSubCommand::EnrollBegin), |
863 | | sub_command_params: Some(expected_sub_command_params), |
864 | | pin_uv_auth_protocol: Some(PinUvAuthProtocol::V1), |
865 | | pin_uv_auth_param: Some(vec![0xBB]), |
866 | | get_modality: Some(true), |
867 | | }; |
868 | | assert_eq!( |
869 | | returned_bio_enrollment_parameters, |
870 | | expected_bio_enrollment_parameters |
871 | | ); |
872 | | } |
873 | | |
874 | | #[test] |
875 | | #[cfg(feature = "fingerprint")] |
876 | | fn test_from_into_bio_enrollment_sub_command() { |
877 | | let cbor_sub_command: cbor::Value = cbor_int!(0x01); |
878 | | let sub_command = BioEnrollmentSubCommand::try_from(cbor_sub_command.clone()); |
879 | | let expected_sub_command = BioEnrollmentSubCommand::EnrollBegin; |
880 | | assert_eq!(sub_command, Ok(expected_sub_command)); |
881 | | let created_cbor: cbor::Value = sub_command.unwrap().into(); |
882 | | assert_eq!(created_cbor, cbor_sub_command); |
883 | | |
884 | | for command in BioEnrollmentSubCommand::into_enum_iter() { |
885 | | let created_cbor: cbor::Value = command.clone().into(); |
886 | | let reconstructed = BioEnrollmentSubCommand::try_from(created_cbor).unwrap(); |
887 | | assert_eq!(command, reconstructed); |
888 | | } |
889 | | } |
890 | | |
891 | | #[test] |
892 | | #[cfg(feature = "fingerprint")] |
893 | | fn test_from_into_bio_enrollment_sub_command_params() { |
894 | | let cbor_sub_command_params = cbor_map! { |
895 | | 0x01 => vec![0x1D; 32], |
896 | | 0x02 => "Name", |
897 | | 0x03 => 30_000, |
898 | | }; |
899 | | let sub_command_params = |
900 | | BioEnrollmentSubCommandParams::try_from(cbor_sub_command_params.clone()); |
901 | | let expected_sub_command_params = BioEnrollmentSubCommandParams { |
902 | | template_id: Some(vec![0x1D; 32]), |
903 | | template_friendly_name: Some(String::from("Name")), |
904 | | timeout_milliseconds: Some(30_000), |
905 | | }; |
906 | | assert_eq!(sub_command_params, Ok(expected_sub_command_params)); |
907 | | let created_cbor: cbor::Value = sub_command_params.unwrap().into(); |
908 | | assert_eq!(created_cbor, cbor_sub_command_params); |
909 | | } |
910 | | |
911 | | #[test] |
912 | | #[cfg(feature = "config_command")] |
913 | | fn test_from_cbor_config_parameters() { |
914 | | let cbor_value = cbor_map! { |
915 | | 0x01 => ConfigSubCommand::SetMinPinLength as u64, |
916 | | 0x02 => cbor_map!{ |
917 | | 0x01 => 6, |
918 | | 0x02 => cbor_array![String::from("example.com")], |
919 | | 0x03 => true, |
920 | | }, |
921 | | 0x03 => 1, |
922 | | 0x04 => vec! [0x9A; 16], |
923 | | }; |
924 | | let returned_config_parameters = |
925 | | AuthenticatorConfigParameters::try_from(cbor_value).unwrap(); |
926 | | |
927 | | let sub_command_params = ConfigSubCommandParams::SetMinPinLength(SetMinPinLengthParams { |
928 | | new_min_pin_length: Some(6), |
929 | | min_pin_length_rp_ids: Some(vec![String::from("example.com")]), |
930 | | force_change_pin: Some(true), |
931 | | }); |
932 | | let expected_config_parameters = AuthenticatorConfigParameters { |
933 | | sub_command: ConfigSubCommand::SetMinPinLength, |
934 | | sub_command_params: Some(sub_command_params), |
935 | | pin_uv_auth_protocol: Some(PinUvAuthProtocol::V1), |
936 | | pin_uv_auth_param: Some(vec![0x9A; 16]), |
937 | | }; |
938 | | |
939 | | assert_eq!(returned_config_parameters, expected_config_parameters); |
940 | | } |
941 | | |
942 | | #[test] |
943 | | fn test_from_cbor_cred_management_parameters() { |
944 | | let cbor_value = cbor_map! { |
945 | | 0x01 => CredentialManagementSubCommand::EnumerateCredentialsBegin as u64, |
946 | | 0x02 => cbor_map!{ |
947 | | 0x01 => vec![0x1D; 32], |
948 | | }, |
949 | | 0x03 => 1, |
950 | | 0x04 => vec! [0x9A; 16], |
951 | | }; |
952 | | let returned_cred_management_parameters = |
953 | | AuthenticatorCredentialManagementParameters::try_from(cbor_value).unwrap(); |
954 | | |
955 | | let params = CredentialManagementSubCommandParameters { |
956 | | rp_id_hash: Some(vec![0x1D; 32]), |
957 | | credential_id: None, |
958 | | user: None, |
959 | | }; |
960 | | let expected_cred_management_parameters = AuthenticatorCredentialManagementParameters { |
961 | | sub_command: CredentialManagementSubCommand::EnumerateCredentialsBegin, |
962 | | sub_command_params: Some(params), |
963 | | pin_uv_auth_protocol: Some(PinUvAuthProtocol::V1), |
964 | | pin_uv_auth_param: Some(vec![0x9A; 16]), |
965 | | }; |
966 | | |
967 | | assert_eq!( |
968 | | returned_cred_management_parameters, |
969 | | expected_cred_management_parameters |
970 | | ); |
971 | | } |
972 | | |
973 | | #[test] |
974 | | fn test_deserialize_selection() { |
975 | | let cbor_bytes = [Command::AUTHENTICATOR_SELECTION]; |
976 | | let command = Command::deserialize(&cbor_bytes); |
977 | | assert_eq!(command, Ok(Command::AuthenticatorSelection)); |
978 | | } |
979 | | |
980 | | #[test] |
981 | | fn test_from_cbor_large_blobs_parameters() { |
982 | | const MIN_LARGE_BLOB_LEN: usize = 17; |
983 | | |
984 | | // successful get |
985 | | let cbor_value = cbor_map! { |
986 | | 0x01 => 2, |
987 | | 0x03 => 4, |
988 | | }; |
989 | | let returned_large_blobs_parameters = |
990 | | AuthenticatorLargeBlobsParameters::try_from(cbor_value).unwrap(); |
991 | | let expected_large_blobs_parameters = AuthenticatorLargeBlobsParameters { |
992 | | get: Some(2), |
993 | | set: None, |
994 | | offset: 4, |
995 | | length: None, |
996 | | pin_uv_auth_param: None, |
997 | | pin_uv_auth_protocol: None, |
998 | | }; |
999 | | assert_eq!( |
1000 | | returned_large_blobs_parameters, |
1001 | | expected_large_blobs_parameters |
1002 | | ); |
1003 | | |
1004 | | // successful first set |
1005 | | let cbor_value = cbor_map! { |
1006 | | 0x02 => vec! [0x5E], |
1007 | | 0x03 => 0, |
1008 | | 0x04 => MIN_LARGE_BLOB_LEN as u64, |
1009 | | 0x05 => vec! [0xA9], |
1010 | | 0x06 => 1, |
1011 | | }; |
1012 | | let returned_large_blobs_parameters = |
1013 | | AuthenticatorLargeBlobsParameters::try_from(cbor_value).unwrap(); |
1014 | | let expected_large_blobs_parameters = AuthenticatorLargeBlobsParameters { |
1015 | | get: None, |
1016 | | set: Some(vec![0x5E]), |
1017 | | offset: 0, |
1018 | | length: Some(MIN_LARGE_BLOB_LEN), |
1019 | | pin_uv_auth_param: Some(vec![0xA9]), |
1020 | | pin_uv_auth_protocol: Some(PinUvAuthProtocol::V1), |
1021 | | }; |
1022 | | assert_eq!( |
1023 | | returned_large_blobs_parameters, |
1024 | | expected_large_blobs_parameters |
1025 | | ); |
1026 | | |
1027 | | // successful next set |
1028 | | let cbor_value = cbor_map! { |
1029 | | 0x02 => vec! [0x5E], |
1030 | | 0x03 => 1, |
1031 | | 0x05 => vec! [0xA9], |
1032 | | 0x06 => 1, |
1033 | | }; |
1034 | | let returned_large_blobs_parameters = |
1035 | | AuthenticatorLargeBlobsParameters::try_from(cbor_value).unwrap(); |
1036 | | let expected_large_blobs_parameters = AuthenticatorLargeBlobsParameters { |
1037 | | get: None, |
1038 | | set: Some(vec![0x5E]), |
1039 | | offset: 1, |
1040 | | length: None, |
1041 | | pin_uv_auth_param: Some(vec![0xA9]), |
1042 | | pin_uv_auth_protocol: Some(PinUvAuthProtocol::V1), |
1043 | | }; |
1044 | | assert_eq!( |
1045 | | returned_large_blobs_parameters, |
1046 | | expected_large_blobs_parameters |
1047 | | ); |
1048 | | |
1049 | | // failing with neither get nor set |
1050 | | let cbor_value = cbor_map! { |
1051 | | 0x03 => 4, |
1052 | | 0x05 => vec! [0xA9], |
1053 | | 0x06 => 1, |
1054 | | }; |
1055 | | assert_eq!( |
1056 | | AuthenticatorLargeBlobsParameters::try_from(cbor_value), |
1057 | | Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) |
1058 | | ); |
1059 | | |
1060 | | // failing with get and set |
1061 | | let cbor_value = cbor_map! { |
1062 | | 0x01 => 2, |
1063 | | 0x02 => vec! [0x5E], |
1064 | | 0x03 => 4, |
1065 | | 0x05 => vec! [0xA9], |
1066 | | 0x06 => 1, |
1067 | | }; |
1068 | | assert_eq!( |
1069 | | AuthenticatorLargeBlobsParameters::try_from(cbor_value), |
1070 | | Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) |
1071 | | ); |
1072 | | |
1073 | | // failing with get and length |
1074 | | let cbor_value = cbor_map! { |
1075 | | 0x01 => 2, |
1076 | | 0x03 => 4, |
1077 | | 0x04 => MIN_LARGE_BLOB_LEN as u64, |
1078 | | 0x05 => vec! [0xA9], |
1079 | | 0x06 => 1, |
1080 | | }; |
1081 | | assert_eq!( |
1082 | | AuthenticatorLargeBlobsParameters::try_from(cbor_value), |
1083 | | Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) |
1084 | | ); |
1085 | | |
1086 | | // failing with zero offset and no length present |
1087 | | let cbor_value = cbor_map! { |
1088 | | 0x02 => vec! [0x5E], |
1089 | | 0x03 => 0, |
1090 | | 0x05 => vec! [0xA9], |
1091 | | 0x06 => 1, |
1092 | | }; |
1093 | | assert_eq!( |
1094 | | AuthenticatorLargeBlobsParameters::try_from(cbor_value), |
1095 | | Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) |
1096 | | ); |
1097 | | |
1098 | | // failing with non-zero offset and length present |
1099 | | let cbor_value = cbor_map! { |
1100 | | 0x02 => vec! [0x5E], |
1101 | | 0x03 => 4, |
1102 | | 0x04 => MIN_LARGE_BLOB_LEN as u64, |
1103 | | 0x05 => vec! [0xA9], |
1104 | | 0x06 => 1, |
1105 | | }; |
1106 | | assert_eq!( |
1107 | | AuthenticatorLargeBlobsParameters::try_from(cbor_value), |
1108 | | Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) |
1109 | | ); |
1110 | | } |
1111 | | } |