/src/OpenSK/libraries/opensk/src/ctap/client_pin.rs
Line | Count | Source |
1 | | // Copyright 2020-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::command::AuthenticatorClientPinParameters; |
16 | | use super::data_formats::{ |
17 | | ok_or_missing, ClientPinSubCommand, CoseKey, GetAssertionHmacSecretInput, PinUvAuthProtocol, |
18 | | }; |
19 | | #[cfg(feature = "fingerprint")] |
20 | | use super::fingerprint::perform_built_in_uv; |
21 | | use super::pin_protocol::{verify_pin_uv_auth_token, PinProtocol, SharedSecret}; |
22 | | use super::response::{AuthenticatorClientPinResponse, ResponseData}; |
23 | | use super::secret::Secret; |
24 | | use super::status_code::{Ctap2StatusCode, CtapResult}; |
25 | | use super::token_state::PinUvAuthTokenState; |
26 | | use super::{storage, Channel}; |
27 | | #[cfg(test)] |
28 | | use crate::api::crypto::ecdh::SecretKey as _; |
29 | | use crate::api::crypto::hmac256::Hmac256; |
30 | | use crate::api::crypto::sha256::Sha256; |
31 | | use crate::api::customization::Customization; |
32 | | use crate::api::key_store::KeyStore; |
33 | | use crate::api::persist::Persist; |
34 | | #[cfg(test)] |
35 | | use crate::env::EcdhSk; |
36 | | use crate::env::{Env, Hmac, Sha}; |
37 | | use alloc::str; |
38 | | use alloc::string::String; |
39 | | use alloc::vec::Vec; |
40 | | use arrayref::array_ref; |
41 | | #[cfg(test)] |
42 | | use enum_iterator::IntoEnumIterator; |
43 | | use subtle::ConstantTimeEq; |
44 | | |
45 | | /// The prefix length of the PIN hash that is stored and compared. |
46 | | /// |
47 | | /// The code assumes that this value is a multiple of the AES block length, fits |
48 | | /// an u8 and is at most as long as a SHA256. The value is fixed for all PIN |
49 | | /// protocols. |
50 | | pub const PIN_AUTH_LENGTH: usize = 16; |
51 | | |
52 | | /// The length of the pinUvAuthToken used throughout PIN protocols. |
53 | | /// |
54 | | /// The code assumes that this value is a multiple of the AES block length. It |
55 | | /// is fixed since CTAP2.1, and the specification suggests that it coincides |
56 | | /// with the HMAC key length. Therefore a change would require a more general |
57 | | /// HMAC implementation. |
58 | | pub const PIN_TOKEN_LENGTH: usize = 32; |
59 | | |
60 | | /// The length of the encrypted PINs when received by SetPin or ChangePin. |
61 | | /// |
62 | | /// The code assumes that this value is a multiple of the AES block length. It |
63 | | /// is fixed since CTAP2.1. |
64 | | const PIN_PADDED_LENGTH: usize = 64; |
65 | | |
66 | | /// Decrypts the new_pin_enc and outputs the found PIN. |
67 | 0 | fn decrypt_pin<E: Env>( |
68 | 0 | shared_secret: &SharedSecret<E>, |
69 | 0 | new_pin_enc: Vec<u8>, |
70 | 0 | ) -> CtapResult<Secret<[u8]>> { |
71 | 0 | let decrypted_pin = shared_secret.decrypt(&new_pin_enc)?; |
72 | 0 | if decrypted_pin.len() != PIN_PADDED_LENGTH { |
73 | 0 | return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); |
74 | 0 | } |
75 | | // In CTAP 2.1, the specification changed. The new wording might lead to |
76 | | // different behavior when there are non-zero bytes after zero bytes. |
77 | | // This implementation consistently ignores those degenerate cases. |
78 | 0 | let len = decrypted_pin |
79 | 0 | .iter() |
80 | 0 | .position(|&c| c == 0) |
81 | 0 | .unwrap_or(decrypted_pin.len()); |
82 | 0 | let mut result = Secret::new(len); |
83 | 0 | result.copy_from_slice(&decrypted_pin[..len]); |
84 | 0 | Ok(result) |
85 | 0 | } |
86 | | |
87 | | /// Stores a hash prefix of the new PIN in the persistent storage, if correct. |
88 | | /// |
89 | | /// The new PIN is passed encrypted, so it is first decrypted and stripped from |
90 | | /// padding. Next, it is checked against the PIN policy. Last, it is hashed and |
91 | | /// truncated for persistent storage. |
92 | 0 | fn check_and_store_new_pin<E: Env>( |
93 | 0 | env: &mut E, |
94 | 0 | shared_secret: &SharedSecret<E>, |
95 | 0 | new_pin_enc: Vec<u8>, |
96 | 0 | ) -> CtapResult<()> { |
97 | 0 | let pin = decrypt_pin(shared_secret, new_pin_enc)?; |
98 | 0 | let min_pin_length = storage::min_pin_length(env)? as usize; |
99 | 0 | let pin_length = str::from_utf8(&pin).unwrap_or("").chars().count(); |
100 | 0 | if pin_length < min_pin_length || pin.len() == PIN_PADDED_LENGTH { |
101 | 0 | return Err(Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION); |
102 | 0 | } |
103 | 0 | let mut pin_hash = Secret::default(); |
104 | 0 | Sha::<E>::digest_mut(&pin, &mut pin_hash); |
105 | 0 | let pin_hash = env |
106 | 0 | .key_store() |
107 | 0 | .encrypt_pin_hash(array_ref![pin_hash, 0, PIN_AUTH_LENGTH])?; |
108 | | // The PIN length is always < PIN_PADDED_LENGTH < 256. |
109 | 0 | env.persist() |
110 | 0 | .set_pin(array_ref!(pin_hash, 0, PIN_AUTH_LENGTH), pin_length as u8)?; |
111 | 0 | Ok(()) |
112 | 0 | } |
113 | | |
114 | | #[cfg_attr(test, derive(IntoEnumIterator))] |
115 | | pub enum PinPermission { |
116 | | // All variants should use integers with a single bit set. |
117 | | MakeCredential = 0x01, |
118 | | GetAssertion = 0x02, |
119 | | CredentialManagement = 0x04, |
120 | | #[cfg(feature = "fingerprint")] |
121 | | BioEnrollment = 0x08, |
122 | | LargeBlobWrite = 0x10, |
123 | | AuthenticatorConfiguration = 0x20, |
124 | | } |
125 | | |
126 | | pub struct ClientPin<E: Env> { |
127 | | pin_protocol_v1: PinProtocol<E>, |
128 | | pin_protocol_v2: PinProtocol<E>, |
129 | | consecutive_pin_mismatches: u8, |
130 | | pin_uv_auth_token_state: PinUvAuthTokenState<E>, |
131 | | } |
132 | | |
133 | | impl<E: Env> ClientPin<E> { |
134 | 16.5k | pub fn new(env: &mut E) -> Self { |
135 | 16.5k | ClientPin { |
136 | 16.5k | pin_protocol_v1: PinProtocol::new(env), |
137 | 16.5k | pin_protocol_v2: PinProtocol::new(env), |
138 | 16.5k | consecutive_pin_mismatches: 0, |
139 | 16.5k | pin_uv_auth_token_state: PinUvAuthTokenState::new(), |
140 | 16.5k | } |
141 | 16.5k | } |
142 | | |
143 | | /// Checks if a PIN UV token is in use. |
144 | | pub fn has_token(&mut self, env: &mut E) -> bool { |
145 | | self.update_timeouts(env); |
146 | | self.pin_uv_auth_token_state.is_in_use() |
147 | | } |
148 | | |
149 | | /// Gets a reference to the PIN protocol of the given version. |
150 | 1.86k | fn get_pin_protocol(&self, pin_uv_auth_protocol: PinUvAuthProtocol) -> &PinProtocol<E> { |
151 | 1.86k | match pin_uv_auth_protocol { |
152 | 954 | PinUvAuthProtocol::V1 => &self.pin_protocol_v1, |
153 | 912 | PinUvAuthProtocol::V2 => &self.pin_protocol_v2, |
154 | | } |
155 | 1.86k | } |
156 | | |
157 | | /// Gets a mutable reference to the PIN protocol of the given version. |
158 | 0 | fn get_mut_pin_protocol( |
159 | 0 | &mut self, |
160 | 0 | pin_uv_auth_protocol: PinUvAuthProtocol, |
161 | 0 | ) -> &mut PinProtocol<E> { |
162 | 0 | match pin_uv_auth_protocol { |
163 | 0 | PinUvAuthProtocol::V1 => &mut self.pin_protocol_v1, |
164 | 0 | PinUvAuthProtocol::V2 => &mut self.pin_protocol_v2, |
165 | | } |
166 | 0 | } |
167 | | |
168 | | /// Computes the shared secret for the given version. |
169 | 393 | fn get_shared_secret( |
170 | 393 | &self, |
171 | 393 | pin_uv_auth_protocol: PinUvAuthProtocol, |
172 | 393 | key_agreement: CoseKey, |
173 | 393 | ) -> CtapResult<SharedSecret<E>> { |
174 | 393 | self.get_pin_protocol(pin_uv_auth_protocol) |
175 | 393 | .decapsulate(key_agreement, pin_uv_auth_protocol) |
176 | 393 | } |
177 | | |
178 | | /// Checks the given encrypted PIN hash against the stored PIN hash. |
179 | | /// |
180 | | /// Decrypts the encrypted pin_hash and compares it to the stored pin_hash. |
181 | | /// Resets or decreases the PIN retries, depending on success or failure. |
182 | | /// Also, in case of failure, the key agreement key is randomly reset. |
183 | 0 | fn verify_pin_hash_enc( |
184 | 0 | &mut self, |
185 | 0 | env: &mut E, |
186 | 0 | pin_uv_auth_protocol: PinUvAuthProtocol, |
187 | 0 | shared_secret: &SharedSecret<E>, |
188 | 0 | pin_hash_enc: Vec<u8>, |
189 | 0 | ) -> CtapResult<()> { |
190 | 0 | match env.persist().pin_hash()? { |
191 | 0 | Some(pin_hash) => { |
192 | 0 | if self.consecutive_pin_mismatches >= 3 { |
193 | 0 | return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_BLOCKED); |
194 | 0 | } |
195 | 0 | storage::decr_pin_retries(env)?; |
196 | 0 | let pin_hash = env.key_store().decrypt_pin_hash(&pin_hash)?; |
197 | 0 | let pin_hash_dec = shared_secret |
198 | 0 | .decrypt(&pin_hash_enc) |
199 | 0 | .map_err(|_| Ctap2StatusCode::CTAP2_ERR_PIN_INVALID)?; |
200 | | |
201 | 0 | if !bool::from(pin_hash.ct_eq(&pin_hash_dec)) { |
202 | 0 | self.get_mut_pin_protocol(pin_uv_auth_protocol) |
203 | 0 | .regenerate(env); |
204 | 0 | if storage::pin_retries(env)? == 0 { |
205 | 0 | return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED); |
206 | 0 | } |
207 | 0 | self.consecutive_pin_mismatches += 1; |
208 | 0 | if self.consecutive_pin_mismatches >= 3 { |
209 | 0 | return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_BLOCKED); |
210 | 0 | } |
211 | 0 | return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID); |
212 | 0 | } |
213 | | } |
214 | | // This status code is not explicitly mentioned in the specification. |
215 | 0 | None => return Err(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED), |
216 | | } |
217 | 0 | storage::reset_pin_retries(env)?; |
218 | | #[cfg(feature = "fingerprint")] |
219 | | storage::reset_uv_retries(env)?; |
220 | 0 | self.consecutive_pin_mismatches = 0; |
221 | 0 | Ok(()) |
222 | 0 | } |
223 | | |
224 | 620 | fn process_get_pin_retries(&self, env: &mut E) -> CtapResult<AuthenticatorClientPinResponse> { |
225 | | Ok(AuthenticatorClientPinResponse { |
226 | 620 | key_agreement: None, |
227 | 620 | pin_uv_auth_token: None, |
228 | 620 | retries: Some(storage::pin_retries(env)? as u64), |
229 | 620 | power_cycle_state: Some(self.consecutive_pin_mismatches >= 3), |
230 | 620 | uv_retries: None, |
231 | | }) |
232 | 620 | } |
233 | | |
234 | 1.47k | fn process_get_key_agreement( |
235 | 1.47k | &self, |
236 | 1.47k | client_pin_params: AuthenticatorClientPinParameters, |
237 | 1.47k | ) -> CtapResult<AuthenticatorClientPinResponse> { |
238 | 1.47k | let key_agreement = Some( |
239 | 1.47k | self.get_pin_protocol(client_pin_params.pin_uv_auth_protocol) |
240 | 1.47k | .get_public_key(), |
241 | 1.47k | ); |
242 | 1.47k | Ok(AuthenticatorClientPinResponse { |
243 | 1.47k | key_agreement, |
244 | 1.47k | pin_uv_auth_token: None, |
245 | 1.47k | retries: None, |
246 | 1.47k | power_cycle_state: None, |
247 | 1.47k | uv_retries: None, |
248 | 1.47k | }) |
249 | 1.47k | } |
250 | | |
251 | 534 | fn process_set_pin( |
252 | 534 | &mut self, |
253 | 534 | env: &mut E, |
254 | 534 | client_pin_params: AuthenticatorClientPinParameters, |
255 | 534 | ) -> CtapResult<()> { |
256 | | let AuthenticatorClientPinParameters { |
257 | 534 | pin_uv_auth_protocol, |
258 | 534 | key_agreement, |
259 | 534 | pin_uv_auth_param, |
260 | 534 | new_pin_enc, |
261 | | .. |
262 | 534 | } = client_pin_params; |
263 | 534 | let key_agreement = ok_or_missing(key_agreement)?; |
264 | 370 | let pin_uv_auth_param = ok_or_missing(pin_uv_auth_param)?; |
265 | 360 | let new_pin_enc = ok_or_missing(new_pin_enc)?; |
266 | | |
267 | 356 | if env.persist().pin_hash()?.is_some() { |
268 | 0 | return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID); |
269 | 356 | } |
270 | 356 | let shared_secret = self.get_shared_secret(pin_uv_auth_protocol, key_agreement)?; |
271 | 0 | shared_secret.verify(&new_pin_enc, &pin_uv_auth_param)?; |
272 | | |
273 | 0 | check_and_store_new_pin(env, &shared_secret, new_pin_enc)?; |
274 | 0 | storage::reset_pin_retries(env)?; |
275 | 0 | Ok(()) |
276 | 534 | } |
277 | | |
278 | 209 | fn process_change_pin( |
279 | 209 | &mut self, |
280 | 209 | env: &mut E, |
281 | 209 | client_pin_params: AuthenticatorClientPinParameters, |
282 | 209 | ) -> CtapResult<()> { |
283 | | let AuthenticatorClientPinParameters { |
284 | 209 | pin_uv_auth_protocol, |
285 | 209 | key_agreement, |
286 | 209 | pin_uv_auth_param, |
287 | 209 | new_pin_enc, |
288 | 209 | pin_hash_enc, |
289 | | .. |
290 | 209 | } = client_pin_params; |
291 | 209 | let key_agreement = ok_or_missing(key_agreement)?; |
292 | 30 | let pin_uv_auth_param = ok_or_missing(pin_uv_auth_param)?; |
293 | 20 | let new_pin_enc = ok_or_missing(new_pin_enc)?; |
294 | 9 | let pin_hash_enc = ok_or_missing(pin_hash_enc)?; |
295 | | |
296 | 6 | if storage::pin_retries(env)? == 0 { |
297 | 0 | return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED); |
298 | 6 | } |
299 | 6 | let shared_secret = self.get_shared_secret(pin_uv_auth_protocol, key_agreement)?; |
300 | 0 | let mut auth_param_data = new_pin_enc.clone(); |
301 | 0 | auth_param_data.extend(&pin_hash_enc); |
302 | 0 | shared_secret.verify(&auth_param_data, &pin_uv_auth_param)?; |
303 | 0 | self.verify_pin_hash_enc(env, pin_uv_auth_protocol, &shared_secret, pin_hash_enc)?; |
304 | | |
305 | 0 | check_and_store_new_pin(env, &shared_secret, new_pin_enc)?; |
306 | 0 | self.pin_protocol_v1.reset_pin_uv_auth_token(env); |
307 | 0 | self.pin_protocol_v2.reset_pin_uv_auth_token(env); |
308 | 0 | Ok(()) |
309 | 209 | } |
310 | | |
311 | 242 | fn process_get_pin_token( |
312 | 242 | &mut self, |
313 | 242 | env: &mut E, |
314 | 242 | client_pin_params: AuthenticatorClientPinParameters, |
315 | 242 | ) -> CtapResult<AuthenticatorClientPinResponse> { |
316 | | let AuthenticatorClientPinParameters { |
317 | 242 | pin_uv_auth_protocol, |
318 | 242 | key_agreement, |
319 | 242 | pin_hash_enc, |
320 | 242 | permissions, |
321 | 242 | permissions_rp_id, |
322 | | .. |
323 | 242 | } = client_pin_params; |
324 | 242 | let key_agreement = ok_or_missing(key_agreement)?; |
325 | 71 | let pin_hash_enc = ok_or_missing(pin_hash_enc)?; |
326 | 51 | if permissions.is_some() || permissions_rp_id.is_some() { |
327 | 20 | return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); |
328 | 31 | } |
329 | | |
330 | 31 | if storage::pin_retries(env)? == 0 { |
331 | 0 | return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED); |
332 | 31 | } |
333 | 31 | let shared_secret = self.get_shared_secret(pin_uv_auth_protocol, key_agreement)?; |
334 | 0 | self.verify_pin_hash_enc(env, pin_uv_auth_protocol, &shared_secret, pin_hash_enc)?; |
335 | 0 | if env.persist().has_force_pin_change()? { |
336 | 0 | return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID); |
337 | 0 | } |
338 | | |
339 | 0 | self.pin_protocol_v1.reset_pin_uv_auth_token(env); |
340 | 0 | self.pin_protocol_v2.reset_pin_uv_auth_token(env); |
341 | 0 | self.pin_uv_auth_token_state |
342 | 0 | .begin_using_pin_uv_auth_token(env, false); |
343 | 0 | self.pin_uv_auth_token_state.set_default_permissions(); |
344 | 0 | let pin_uv_auth_token = shared_secret.encrypt( |
345 | 0 | env, |
346 | 0 | self.get_pin_protocol(pin_uv_auth_protocol) |
347 | 0 | .get_pin_uv_auth_token(), |
348 | 0 | )?; |
349 | | |
350 | 0 | Ok(AuthenticatorClientPinResponse { |
351 | 0 | key_agreement: None, |
352 | 0 | pin_uv_auth_token: Some(pin_uv_auth_token), |
353 | 0 | retries: None, |
354 | 0 | power_cycle_state: None, |
355 | 0 | uv_retries: None, |
356 | 0 | }) |
357 | 242 | } |
358 | | |
359 | | #[cfg(feature = "fingerprint")] |
360 | | fn process_get_pin_uv_auth_token_using_uv_with_permissions( |
361 | | &mut self, |
362 | | env: &mut E, |
363 | | client_pin_params: AuthenticatorClientPinParameters, |
364 | | channel: Channel, |
365 | | ) -> CtapResult<AuthenticatorClientPinResponse> { |
366 | | let AuthenticatorClientPinParameters { |
367 | | pin_uv_auth_protocol, |
368 | | key_agreement, |
369 | | permissions, |
370 | | permissions_rp_id, |
371 | | .. |
372 | | } = client_pin_params; |
373 | | let key_agreement = ok_or_missing(key_agreement)?; |
374 | | let permissions = permissions.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?; |
375 | | |
376 | | if permissions == 0 { |
377 | | return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); |
378 | | } |
379 | | // Since credMgmt, uvBioEnroll and largeBlobs are always true in the options, |
380 | | // we only have to check uvAcfg for step 3.4 in CTAP 2.2. |
381 | | // TODO implement perCredMgmtRO |
382 | | #[cfg(not(feature = "config_command"))] |
383 | | if permissions & PinPermission::AuthenticatorConfiguration as u8 > 0 { |
384 | | return Err(Ctap2StatusCode::CTAP2_ERR_UNAUTHORIZED_PERMISSION); |
385 | | } |
386 | | // This check is not mentioned protocol steps, but mentioned in a side note. |
387 | | // Unsure if this is intended by the specification, so I'll keep it strict for now. |
388 | | let mc_gw_permission = |
389 | | PinPermission::MakeCredential as u8 | PinPermission::GetAssertion as u8; |
390 | | if permissions & mc_gw_permission != 0 && permissions_rp_id.is_none() { |
391 | | return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); |
392 | | } |
393 | | |
394 | | let internal_retry = env.customization().preferred_platform_uv_attempts() == 1; |
395 | | perform_built_in_uv(env, channel, internal_retry)?; |
396 | | |
397 | | let shared_secret = self.get_shared_secret(pin_uv_auth_protocol, key_agreement)?; |
398 | | if env.persist().has_force_pin_change()? { |
399 | | return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID); |
400 | | } |
401 | | |
402 | | self.pin_protocol_v1.reset_pin_uv_auth_token(env); |
403 | | self.pin_protocol_v2.reset_pin_uv_auth_token(env); |
404 | | self.pin_uv_auth_token_state |
405 | | .begin_using_pin_uv_auth_token(env, true); |
406 | | let pin_uv_auth_token = shared_secret.encrypt( |
407 | | env, |
408 | | self.get_pin_protocol(pin_uv_auth_protocol) |
409 | | .get_pin_uv_auth_token(), |
410 | | )?; |
411 | | |
412 | | self.pin_uv_auth_token_state.set_permissions(permissions); |
413 | | self.pin_uv_auth_token_state |
414 | | .set_permissions_rp_id(permissions_rp_id); |
415 | | |
416 | | Ok(AuthenticatorClientPinResponse { |
417 | | key_agreement: None, |
418 | | pin_uv_auth_token: Some(pin_uv_auth_token), |
419 | | retries: None, |
420 | | power_cycle_state: None, |
421 | | uv_retries: None, |
422 | | }) |
423 | | } |
424 | | |
425 | | #[cfg(not(feature = "fingerprint"))] |
426 | 135 | fn process_get_pin_uv_auth_token_using_uv_with_permissions( |
427 | 135 | &mut self, |
428 | 135 | _env: &mut E, |
429 | 135 | _client_pin_params: AuthenticatorClientPinParameters, |
430 | 135 | _channel: Channel, |
431 | 135 | ) -> CtapResult<AuthenticatorClientPinResponse> { |
432 | 135 | Err(Ctap2StatusCode::CTAP2_ERR_INVALID_SUBCOMMAND) |
433 | 135 | } |
434 | | |
435 | | #[cfg(feature = "fingerprint")] |
436 | | fn process_get_uv_retries(&self, env: &mut E) -> CtapResult<AuthenticatorClientPinResponse> { |
437 | | Ok(AuthenticatorClientPinResponse { |
438 | | key_agreement: None, |
439 | | pin_uv_auth_token: None, |
440 | | retries: None, |
441 | | power_cycle_state: None, |
442 | | uv_retries: Some(storage::uv_retries(env)? as u64), |
443 | | }) |
444 | | } |
445 | | |
446 | | #[cfg(not(feature = "fingerprint"))] |
447 | 124 | fn process_get_uv_retries(&self, _env: &mut E) -> CtapResult<AuthenticatorClientPinResponse> { |
448 | 124 | Err(Ctap2StatusCode::CTAP2_ERR_INVALID_SUBCOMMAND) |
449 | 124 | } |
450 | | |
451 | 275 | fn process_get_pin_uv_auth_token_using_pin_with_permissions( |
452 | 275 | &mut self, |
453 | 275 | env: &mut E, |
454 | 275 | mut client_pin_params: AuthenticatorClientPinParameters, |
455 | 275 | ) -> CtapResult<AuthenticatorClientPinResponse> { |
456 | | // Mutating client_pin_params is just an optimization to move it into |
457 | | // process_get_pin_token, without cloning permissions_rp_id here. |
458 | | // getPinToken requires permissions* to be None. |
459 | 275 | let permissions = ok_or_missing(client_pin_params.permissions.take())?; |
460 | 118 | let permissions_rp_id = client_pin_params.permissions_rp_id.take(); |
461 | | |
462 | 118 | if permissions == 0 { |
463 | 27 | return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); |
464 | 91 | } |
465 | | // Since credMgmt, uvBioEnroll and largeBlobs are always true in the options, |
466 | | // and noMcGaPermissionsWithClientPin and noMcGaPermissionsWithClientPin are both false, |
467 | | // we only have to check uvAcfg for step 3.4 in CTAP 2.2. |
468 | | // TODO implement perCredMgmtRO |
469 | | #[cfg(not(feature = "config_command"))] |
470 | | if permissions & PinPermission::AuthenticatorConfiguration as u8 > 0 { |
471 | | return Err(Ctap2StatusCode::CTAP2_ERR_UNAUTHORIZED_PERMISSION); |
472 | | } |
473 | | // This check is not mentioned protocol steps, but mentioned in a side note. |
474 | | // Unsure if this is intended by the specification, so I'll keep it strict for now. |
475 | 91 | let mc_gw_permission = |
476 | 91 | PinPermission::MakeCredential as u8 | PinPermission::GetAssertion as u8; |
477 | 91 | if permissions & mc_gw_permission != 0 && permissions_rp_id.is_none() { |
478 | 12 | return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); |
479 | 79 | } |
480 | | |
481 | 79 | let response = self.process_get_pin_token(env, client_pin_params)?; |
482 | 0 | self.pin_uv_auth_token_state.set_permissions(permissions); |
483 | 0 | self.pin_uv_auth_token_state |
484 | 0 | .set_permissions_rp_id(permissions_rp_id); |
485 | | |
486 | 0 | Ok(response) |
487 | 275 | } |
488 | | |
489 | | /// Processes the authenticatorClientPin command. |
490 | 3.53k | pub fn process_command( |
491 | 3.53k | &mut self, |
492 | 3.53k | env: &mut E, |
493 | 3.53k | client_pin_params: AuthenticatorClientPinParameters, |
494 | 3.53k | channel: Channel, |
495 | 3.53k | ) -> CtapResult<ResponseData> { |
496 | 3.53k | if !env.customization().allows_pin_protocol_v1() |
497 | 0 | && client_pin_params.pin_uv_auth_protocol == PinUvAuthProtocol::V1 |
498 | | { |
499 | 0 | return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); |
500 | 3.53k | } |
501 | 3.53k | let response = match client_pin_params.sub_command { |
502 | 620 | ClientPinSubCommand::GetPinRetries => Some(self.process_get_pin_retries(env)?), |
503 | | ClientPinSubCommand::GetKeyAgreement => { |
504 | 1.47k | Some(self.process_get_key_agreement(client_pin_params)?) |
505 | | } |
506 | | ClientPinSubCommand::SetPin => { |
507 | 534 | self.process_set_pin(env, client_pin_params)?; |
508 | 0 | None |
509 | | } |
510 | | ClientPinSubCommand::ChangePin => { |
511 | 209 | self.process_change_pin(env, client_pin_params)?; |
512 | 0 | None |
513 | | } |
514 | | ClientPinSubCommand::GetPinToken => { |
515 | 163 | Some(self.process_get_pin_token(env, client_pin_params)?) |
516 | | } |
517 | | ClientPinSubCommand::GetPinUvAuthTokenUsingUvWithPermissions => Some( |
518 | 135 | self.process_get_pin_uv_auth_token_using_uv_with_permissions( |
519 | 135 | env, |
520 | 135 | client_pin_params, |
521 | 135 | channel, |
522 | 135 | )?, |
523 | | ), |
524 | 124 | ClientPinSubCommand::GetUvRetries => Some(self.process_get_uv_retries(env)?), |
525 | | ClientPinSubCommand::GetPinUvAuthTokenUsingPinWithPermissions => Some( |
526 | 275 | self.process_get_pin_uv_auth_token_using_pin_with_permissions( |
527 | 275 | env, |
528 | 275 | client_pin_params, |
529 | 275 | )?, |
530 | | ), |
531 | | }; |
532 | 2.09k | Ok(ResponseData::AuthenticatorClientPin(response)) |
533 | 3.53k | } |
534 | | |
535 | | /// Verifies the HMAC for the pinUvAuthToken of the given version. |
536 | 50 | pub fn verify_pin_uv_auth_token( |
537 | 50 | &self, |
538 | 50 | hmac_contents: &[u8], |
539 | 50 | pin_uv_auth_param: &[u8], |
540 | 50 | pin_uv_auth_protocol: PinUvAuthProtocol, |
541 | 50 | ) -> CtapResult<()> { |
542 | 50 | if !self.pin_uv_auth_token_state.is_in_use() { |
543 | 50 | return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID); |
544 | 0 | } |
545 | 0 | verify_pin_uv_auth_token::<E>( |
546 | 0 | self.get_pin_protocol(pin_uv_auth_protocol) |
547 | 0 | .get_pin_uv_auth_token(), |
548 | 0 | hmac_contents, |
549 | 0 | pin_uv_auth_param, |
550 | 0 | pin_uv_auth_protocol, |
551 | | ) |
552 | 50 | } |
553 | | |
554 | | /// Resets all held state. |
555 | 33 | pub fn reset(&mut self, env: &mut E) { |
556 | 33 | self.pin_protocol_v1.regenerate(env); |
557 | 33 | self.pin_protocol_v1.reset_pin_uv_auth_token(env); |
558 | 33 | self.pin_protocol_v2.regenerate(env); |
559 | 33 | self.pin_protocol_v2.reset_pin_uv_auth_token(env); |
560 | 33 | self.consecutive_pin_mismatches = 0; |
561 | 33 | self.pin_uv_auth_token_state.stop_using_pin_uv_auth_token(); |
562 | 33 | } |
563 | | |
564 | | /// Verifies, computes and encrypts the HMAC-secret outputs. |
565 | | /// |
566 | | /// The salt_enc is |
567 | | /// - verified with the shared secret and salt_auth, |
568 | | /// - decrypted with the shared secret, |
569 | | /// - HMAC'ed with cred_random. |
570 | | /// |
571 | | /// The length of the output matches salt_enc and has to be 1 or 2 blocks of |
572 | | /// 32 byte. |
573 | 0 | pub fn process_hmac_secret( |
574 | 0 | &self, |
575 | 0 | env: &mut E, |
576 | 0 | hmac_secret_input: GetAssertionHmacSecretInput, |
577 | 0 | cred_random: &[u8; 32], |
578 | 0 | ) -> CtapResult<Vec<u8>> { |
579 | | let GetAssertionHmacSecretInput { |
580 | 0 | key_agreement, |
581 | 0 | salt_enc, |
582 | 0 | salt_auth, |
583 | 0 | pin_uv_auth_protocol, |
584 | 0 | } = hmac_secret_input; |
585 | 0 | let shared_secret = self |
586 | 0 | .get_pin_protocol(pin_uv_auth_protocol) |
587 | 0 | .decapsulate(key_agreement, pin_uv_auth_protocol)?; |
588 | 0 | shared_secret.verify(&salt_enc, &salt_auth)?; |
589 | | |
590 | 0 | let decrypted_salts = shared_secret.decrypt(&salt_enc)?; |
591 | 0 | if decrypted_salts.len() != 32 && decrypted_salts.len() != 64 { |
592 | 0 | return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); |
593 | 0 | } |
594 | 0 | let mut output = Secret::new(decrypted_salts.len()); |
595 | 0 | Hmac::<E>::mac( |
596 | 0 | cred_random, |
597 | 0 | &decrypted_salts[..32], |
598 | 0 | array_mut_ref![&mut output, 0, 32], |
599 | | ); |
600 | 0 | if decrypted_salts.len() == 64 { |
601 | 0 | Hmac::<E>::mac( |
602 | 0 | cred_random, |
603 | 0 | &decrypted_salts[32..], |
604 | 0 | array_mut_ref![&mut output, 32, 32], |
605 | 0 | ); |
606 | 0 | } |
607 | 0 | shared_secret.encrypt(env, &output) |
608 | 0 | } |
609 | | |
610 | | /// Consumes flags and permissions related to the pinUvAuthToken. |
611 | 3.16k | pub fn clear_token_flags(&mut self) { |
612 | 3.16k | self.pin_uv_auth_token_state.clear_user_present_flag(); |
613 | 3.16k | self.pin_uv_auth_token_state.clear_user_verified_flag(); |
614 | 3.16k | self.pin_uv_auth_token_state |
615 | 3.16k | .clear_pin_uv_auth_token_permissions_except_lbw(); |
616 | 3.16k | } |
617 | | |
618 | | /// Updates the running timers, triggers timeout events. |
619 | 14.7k | pub fn update_timeouts(&mut self, env: &mut E) { |
620 | 14.7k | self.pin_uv_auth_token_state |
621 | 14.7k | .pin_uv_auth_token_usage_timer_observer(env); |
622 | 14.7k | } |
623 | | |
624 | | /// Returns if user presence is cached for use of the pinUvAuthToken. |
625 | 0 | pub fn get_user_present_flag(&mut self) -> bool { |
626 | 0 | self.pin_uv_auth_token_state.get_user_present_flag_value() |
627 | 0 | } |
628 | | |
629 | | /// Checks if user verification is cached for use of the pinUvAuthToken. |
630 | 0 | pub fn check_user_verified_flag(&mut self) -> CtapResult<()> { |
631 | 0 | if self.pin_uv_auth_token_state.get_user_verified_flag_value() { |
632 | 0 | Ok(()) |
633 | | } else { |
634 | 0 | Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) |
635 | | } |
636 | 0 | } |
637 | | |
638 | | /// Check if the required command's token permission is granted. |
639 | 0 | pub fn has_permission(&self, permission: PinPermission) -> CtapResult<()> { |
640 | 0 | self.pin_uv_auth_token_state.has_permission(permission) |
641 | 0 | } |
642 | | |
643 | | /// Check if no RP ID is associated with the token permission. |
644 | 0 | pub fn has_no_rp_id_permission(&self) -> CtapResult<()> { |
645 | 0 | self.pin_uv_auth_token_state.has_no_permissions_rp_id() |
646 | 0 | } |
647 | | |
648 | | /// Check if no or the passed RP ID is associated with the token permission. |
649 | 0 | pub fn has_no_or_rp_id_permission(&mut self, rp_id: &str) -> CtapResult<()> { |
650 | 0 | self.pin_uv_auth_token_state |
651 | 0 | .has_no_permissions_rp_id() |
652 | 0 | .or_else(|_| self.pin_uv_auth_token_state.has_permissions_rp_id(rp_id)) |
653 | 0 | } |
654 | | |
655 | | /// Check if no RP ID is associated with the token permission, or it matches the hash. |
656 | 0 | pub fn has_no_or_rp_id_hash_permission(&self, rp_id_hash: &[u8]) -> CtapResult<()> { |
657 | 0 | self.pin_uv_auth_token_state |
658 | 0 | .has_no_permissions_rp_id() |
659 | 0 | .or_else(|_| { |
660 | 0 | self.pin_uv_auth_token_state |
661 | 0 | .has_permissions_rp_id_hash(rp_id_hash) |
662 | 0 | }) |
663 | 0 | } |
664 | | |
665 | | /// Check if the passed RP ID is associated with the token permission. |
666 | | /// |
667 | | /// If no RP ID is associated, associate the passed RP ID as a side effect. |
668 | 0 | pub fn ensure_rp_id_permission(&mut self, rp_id: &str) -> CtapResult<()> { |
669 | 0 | if self |
670 | 0 | .pin_uv_auth_token_state |
671 | 0 | .has_no_permissions_rp_id() |
672 | 0 | .is_ok() |
673 | | { |
674 | 0 | self.pin_uv_auth_token_state |
675 | 0 | .set_permissions_rp_id(Some(String::from(rp_id))); |
676 | 0 | return Ok(()); |
677 | 0 | } |
678 | 0 | self.pin_uv_auth_token_state.has_permissions_rp_id(rp_id) |
679 | 0 | } |
680 | | |
681 | | #[cfg(test)] |
682 | | pub fn new_test( |
683 | | env: &mut E, |
684 | | key_agreement_key: EcdhSk<E>, |
685 | | pin_uv_auth_token: [u8; PIN_TOKEN_LENGTH], |
686 | | pin_uv_auth_protocol: PinUvAuthProtocol, |
687 | | ) -> Self { |
688 | | let random_key = EcdhSk::<E>::random(env.rng()); |
689 | | let (key_agreement_key_v1, key_agreement_key_v2) = match pin_uv_auth_protocol { |
690 | | PinUvAuthProtocol::V1 => (key_agreement_key, random_key), |
691 | | PinUvAuthProtocol::V2 => (random_key, key_agreement_key), |
692 | | }; |
693 | | let mut pin_uv_auth_token_state = PinUvAuthTokenState::new(); |
694 | | pin_uv_auth_token_state.set_permissions(0xFF); |
695 | | pin_uv_auth_token_state.begin_using_pin_uv_auth_token(env, true); |
696 | | Self { |
697 | | pin_protocol_v1: PinProtocol::new_test(key_agreement_key_v1, pin_uv_auth_token), |
698 | | pin_protocol_v2: PinProtocol::new_test(key_agreement_key_v2, pin_uv_auth_token), |
699 | | consecutive_pin_mismatches: 0, |
700 | | pin_uv_auth_token_state, |
701 | | } |
702 | | } |
703 | | } |
704 | | |
705 | | #[cfg(test)] |
706 | | mod test { |
707 | | use super::super::pin_protocol::authenticate_pin_uv_auth_token; |
708 | | use super::*; |
709 | | use crate::api::crypto::HASH_SIZE; |
710 | | use crate::env::test::TestEnv; |
711 | | use crate::env::EcdhSk; |
712 | | use alloc::vec; |
713 | | |
714 | | // Dummy channel to send keepalives on. |
715 | | const DUMMY_CHANNEL: Channel = Channel::MainHid([0x12, 0x34, 0x56, 0x78]); |
716 | | |
717 | | /// Stores a PIN hash corresponding to the dummy PIN "1234". |
718 | | fn set_standard_pin(env: &mut TestEnv) { |
719 | | let mut pin = [0u8; 64]; |
720 | | pin[..4].copy_from_slice(b"1234"); |
721 | | let mut pin_hash = [0u8; 16]; |
722 | | pin_hash.copy_from_slice(&Sha::<TestEnv>::digest(&pin[..])[..16]); |
723 | | env.persist().set_pin(&pin_hash, 4).unwrap(); |
724 | | } |
725 | | |
726 | | /// Fails on PINs bigger than 64 bytes. |
727 | | fn encrypt_pin(shared_secret: &SharedSecret<TestEnv>, pin: Vec<u8>) -> Vec<u8> { |
728 | | assert!(pin.len() <= 64); |
729 | | let mut env = TestEnv::default(); |
730 | | let mut padded_pin = [0u8; 64]; |
731 | | padded_pin[..pin.len()].copy_from_slice(&pin[..]); |
732 | | shared_secret.encrypt(&mut env, &padded_pin).unwrap() |
733 | | } |
734 | | |
735 | | /// Generates a ClientPin instance and a shared secret for testing. |
736 | | /// |
737 | | /// The shared secret for the desired PIN protocol is generated in a |
738 | | /// handshake with itself. The other protocol has a random private key, so |
739 | | /// tests using the wrong combination of PIN protocol and shared secret |
740 | | /// should fail. |
741 | | fn create_client_pin_and_shared_secret( |
742 | | pin_uv_auth_protocol: PinUvAuthProtocol, |
743 | | ) -> (ClientPin<TestEnv>, SharedSecret<TestEnv>) { |
744 | | let mut env = TestEnv::default(); |
745 | | let key_agreement_key = EcdhSk::<TestEnv>::random(env.rng()); |
746 | | let pk = key_agreement_key.public_key(); |
747 | | let key_agreement = CoseKey::from_ecdh_public_key::<TestEnv>(pk); |
748 | | let pin_uv_auth_token = [0x91; PIN_TOKEN_LENGTH]; |
749 | | let client_pin = ClientPin::<TestEnv>::new_test( |
750 | | &mut env, |
751 | | key_agreement_key, |
752 | | pin_uv_auth_token, |
753 | | pin_uv_auth_protocol, |
754 | | ); |
755 | | let shared_secret = client_pin |
756 | | .get_pin_protocol(pin_uv_auth_protocol) |
757 | | .decapsulate(key_agreement, pin_uv_auth_protocol) |
758 | | .unwrap(); |
759 | | (client_pin, shared_secret) |
760 | | } |
761 | | |
762 | | /// Generates standard input parameters to the ClientPin command. |
763 | | /// |
764 | | /// All fields are populated for simplicity, even though most are unused. |
765 | | fn create_client_pin_and_parameters( |
766 | | pin_uv_auth_protocol: PinUvAuthProtocol, |
767 | | sub_command: ClientPinSubCommand, |
768 | | ) -> (ClientPin<TestEnv>, AuthenticatorClientPinParameters) { |
769 | | let mut env = TestEnv::default(); |
770 | | let (client_pin, shared_secret) = create_client_pin_and_shared_secret(pin_uv_auth_protocol); |
771 | | |
772 | | let pin = b"1234"; |
773 | | let mut padded_pin = [0u8; 64]; |
774 | | padded_pin[..pin.len()].copy_from_slice(&pin[..]); |
775 | | let pin_hash = Sha::<TestEnv>::digest(&padded_pin); |
776 | | let new_pin_enc = shared_secret.encrypt(&mut env, &padded_pin).unwrap(); |
777 | | let pin_uv_auth_param = shared_secret.authenticate(&new_pin_enc); |
778 | | let pin_hash_enc = shared_secret.encrypt(&mut env, &pin_hash[..16]).unwrap(); |
779 | | let (permissions, permissions_rp_id) = match sub_command { |
780 | | ClientPinSubCommand::GetPinUvAuthTokenUsingUvWithPermissions |
781 | | | ClientPinSubCommand::GetPinUvAuthTokenUsingPinWithPermissions => { |
782 | | (Some(0x03), Some("example.com".to_string())) |
783 | | } |
784 | | _ => (None, None), |
785 | | }; |
786 | | let params = AuthenticatorClientPinParameters { |
787 | | pin_uv_auth_protocol, |
788 | | sub_command, |
789 | | key_agreement: Some( |
790 | | client_pin |
791 | | .get_pin_protocol(pin_uv_auth_protocol) |
792 | | .get_public_key(), |
793 | | ), |
794 | | pin_uv_auth_param: Some(pin_uv_auth_param), |
795 | | new_pin_enc: Some(new_pin_enc), |
796 | | pin_hash_enc: Some(pin_hash_enc), |
797 | | permissions, |
798 | | permissions_rp_id, |
799 | | }; |
800 | | (client_pin, params) |
801 | | } |
802 | | |
803 | | #[test] |
804 | | fn test_mix_pin_protocols() { |
805 | | let mut env = TestEnv::default(); |
806 | | let client_pin = ClientPin::<TestEnv>::new(&mut env); |
807 | | let pin_protocol_v1 = client_pin.get_pin_protocol(PinUvAuthProtocol::V1); |
808 | | let pin_protocol_v2 = client_pin.get_pin_protocol(PinUvAuthProtocol::V2); |
809 | | let message = vec![0xAA; 16]; |
810 | | |
811 | | let shared_secret_v1 = pin_protocol_v1 |
812 | | .decapsulate(pin_protocol_v1.get_public_key(), PinUvAuthProtocol::V1) |
813 | | .unwrap(); |
814 | | let shared_secret_v2 = pin_protocol_v2 |
815 | | .decapsulate(pin_protocol_v2.get_public_key(), PinUvAuthProtocol::V2) |
816 | | .unwrap(); |
817 | | let ciphertext = shared_secret_v1.encrypt(&mut env, &message).unwrap(); |
818 | | let plaintext = shared_secret_v2.decrypt(&ciphertext).unwrap(); |
819 | | assert_ne!(&message, &*plaintext); |
820 | | let ciphertext = shared_secret_v2.encrypt(&mut env, &message).unwrap(); |
821 | | let plaintext = shared_secret_v1.decrypt(&ciphertext).unwrap(); |
822 | | assert_ne!(&message, &*plaintext); |
823 | | |
824 | | let fake_secret_v1 = pin_protocol_v1 |
825 | | .decapsulate(pin_protocol_v2.get_public_key(), PinUvAuthProtocol::V1) |
826 | | .unwrap(); |
827 | | let ciphertext = shared_secret_v1.encrypt(&mut env, &message).unwrap(); |
828 | | let plaintext = fake_secret_v1.decrypt(&ciphertext).unwrap(); |
829 | | assert_ne!(&message, &*plaintext); |
830 | | let ciphertext = fake_secret_v1.encrypt(&mut env, &message).unwrap(); |
831 | | let plaintext = shared_secret_v1.decrypt(&ciphertext).unwrap(); |
832 | | assert_ne!(&message, &*plaintext); |
833 | | |
834 | | let fake_secret_v2 = pin_protocol_v2 |
835 | | .decapsulate(pin_protocol_v1.get_public_key(), PinUvAuthProtocol::V2) |
836 | | .unwrap(); |
837 | | let ciphertext = shared_secret_v2.encrypt(&mut env, &message).unwrap(); |
838 | | let plaintext = fake_secret_v2.decrypt(&ciphertext).unwrap(); |
839 | | assert_ne!(&message, &*plaintext); |
840 | | let ciphertext = fake_secret_v2.encrypt(&mut env, &message).unwrap(); |
841 | | let plaintext = shared_secret_v2.decrypt(&ciphertext).unwrap(); |
842 | | assert_ne!(&message, &*plaintext); |
843 | | } |
844 | | |
845 | | fn test_helper_verify_pin_hash_enc(pin_uv_auth_protocol: PinUvAuthProtocol) { |
846 | | let mut env = TestEnv::default(); |
847 | | let mut client_pin = ClientPin::<TestEnv>::new(&mut env); |
848 | | let pin_protocol = client_pin.get_pin_protocol(pin_uv_auth_protocol); |
849 | | let shared_secret = pin_protocol |
850 | | .decapsulate(pin_protocol.get_public_key(), pin_uv_auth_protocol) |
851 | | .unwrap(); |
852 | | // The PIN is "1234". |
853 | | let pin_hash = [ |
854 | | 0x01, 0xD9, 0x88, 0x40, 0x50, 0xBB, 0xD0, 0x7A, 0x23, 0x1A, 0xEB, 0x69, 0xD8, 0x36, |
855 | | 0xC4, 0x12, |
856 | | ]; |
857 | | env.persist().set_pin(&pin_hash, 4).unwrap(); |
858 | | |
859 | | let pin_hash_enc = shared_secret.encrypt(&mut env, &pin_hash).unwrap(); |
860 | | assert_eq!( |
861 | | client_pin.verify_pin_hash_enc( |
862 | | &mut env, |
863 | | pin_uv_auth_protocol, |
864 | | &shared_secret, |
865 | | pin_hash_enc |
866 | | ), |
867 | | Ok(()) |
868 | | ); |
869 | | |
870 | | let pin_hash_enc = vec![0xEE; 16]; |
871 | | assert_eq!( |
872 | | client_pin.verify_pin_hash_enc( |
873 | | &mut env, |
874 | | pin_uv_auth_protocol, |
875 | | &shared_secret, |
876 | | pin_hash_enc |
877 | | ), |
878 | | Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID) |
879 | | ); |
880 | | |
881 | | let pin_hash_enc = shared_secret.encrypt(&mut env, &pin_hash).unwrap(); |
882 | | client_pin.consecutive_pin_mismatches = 3; |
883 | | assert_eq!( |
884 | | client_pin.verify_pin_hash_enc( |
885 | | &mut env, |
886 | | pin_uv_auth_protocol, |
887 | | &shared_secret, |
888 | | pin_hash_enc |
889 | | ), |
890 | | Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_BLOCKED) |
891 | | ); |
892 | | client_pin.consecutive_pin_mismatches = 0; |
893 | | |
894 | | let pin_hash_enc = vec![0x77; PIN_AUTH_LENGTH - 1]; |
895 | | assert_eq!( |
896 | | client_pin.verify_pin_hash_enc( |
897 | | &mut env, |
898 | | pin_uv_auth_protocol, |
899 | | &shared_secret, |
900 | | pin_hash_enc |
901 | | ), |
902 | | Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID) |
903 | | ); |
904 | | |
905 | | let pin_hash_enc = vec![0x77; PIN_AUTH_LENGTH + 1]; |
906 | | assert_eq!( |
907 | | client_pin.verify_pin_hash_enc( |
908 | | &mut env, |
909 | | pin_uv_auth_protocol, |
910 | | &shared_secret, |
911 | | pin_hash_enc |
912 | | ), |
913 | | Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID) |
914 | | ); |
915 | | } |
916 | | |
917 | | #[test] |
918 | | fn test_verify_pin_hash_enc_v1() { |
919 | | test_helper_verify_pin_hash_enc(PinUvAuthProtocol::V1); |
920 | | } |
921 | | |
922 | | #[test] |
923 | | fn test_verify_pin_hash_enc_v2() { |
924 | | test_helper_verify_pin_hash_enc(PinUvAuthProtocol::V2); |
925 | | } |
926 | | |
927 | | fn test_helper_process_get_pin_retries(pin_uv_auth_protocol: PinUvAuthProtocol) { |
928 | | let (mut client_pin, params) = create_client_pin_and_parameters( |
929 | | pin_uv_auth_protocol, |
930 | | ClientPinSubCommand::GetPinRetries, |
931 | | ); |
932 | | let mut env = TestEnv::default(); |
933 | | let expected_response = Some(AuthenticatorClientPinResponse { |
934 | | key_agreement: None, |
935 | | pin_uv_auth_token: None, |
936 | | retries: Some(storage::pin_retries(&mut env).unwrap() as u64), |
937 | | power_cycle_state: Some(false), |
938 | | uv_retries: None, |
939 | | }); |
940 | | assert_eq!( |
941 | | client_pin.process_command(&mut env, params.clone(), DUMMY_CHANNEL), |
942 | | Ok(ResponseData::AuthenticatorClientPin(expected_response)) |
943 | | ); |
944 | | |
945 | | client_pin.consecutive_pin_mismatches = 3; |
946 | | let expected_response = Some(AuthenticatorClientPinResponse { |
947 | | key_agreement: None, |
948 | | pin_uv_auth_token: None, |
949 | | retries: Some(storage::pin_retries(&mut env).unwrap() as u64), |
950 | | power_cycle_state: Some(true), |
951 | | uv_retries: None, |
952 | | }); |
953 | | assert_eq!( |
954 | | client_pin.process_command(&mut env, params, DUMMY_CHANNEL), |
955 | | Ok(ResponseData::AuthenticatorClientPin(expected_response)) |
956 | | ); |
957 | | } |
958 | | |
959 | | #[test] |
960 | | fn test_process_get_pin_retries_v1() { |
961 | | test_helper_process_get_pin_retries(PinUvAuthProtocol::V1); |
962 | | } |
963 | | |
964 | | #[test] |
965 | | fn test_process_get_pin_retries_v2() { |
966 | | test_helper_process_get_pin_retries(PinUvAuthProtocol::V2); |
967 | | } |
968 | | |
969 | | fn test_helper_process_get_key_agreement(pin_uv_auth_protocol: PinUvAuthProtocol) { |
970 | | let (mut client_pin, params) = create_client_pin_and_parameters( |
971 | | pin_uv_auth_protocol, |
972 | | ClientPinSubCommand::GetKeyAgreement, |
973 | | ); |
974 | | let mut env = TestEnv::default(); |
975 | | let expected_response = Some(AuthenticatorClientPinResponse { |
976 | | key_agreement: params.key_agreement.clone(), |
977 | | pin_uv_auth_token: None, |
978 | | retries: None, |
979 | | power_cycle_state: None, |
980 | | uv_retries: None, |
981 | | }); |
982 | | assert_eq!( |
983 | | client_pin.process_command(&mut env, params, DUMMY_CHANNEL), |
984 | | Ok(ResponseData::AuthenticatorClientPin(expected_response)) |
985 | | ); |
986 | | } |
987 | | |
988 | | #[test] |
989 | | fn test_process_get_key_agreement_v1() { |
990 | | test_helper_process_get_key_agreement(PinUvAuthProtocol::V1); |
991 | | } |
992 | | |
993 | | #[test] |
994 | | fn test_process_get_key_agreement_v2() { |
995 | | test_helper_process_get_key_agreement(PinUvAuthProtocol::V2); |
996 | | } |
997 | | |
998 | | #[test] |
999 | | fn test_process_get_key_agreement_v1_not_allowed() { |
1000 | | let (mut client_pin, params) = create_client_pin_and_parameters( |
1001 | | PinUvAuthProtocol::V1, |
1002 | | ClientPinSubCommand::GetKeyAgreement, |
1003 | | ); |
1004 | | let mut env = TestEnv::default(); |
1005 | | env.customization_mut().set_allows_pin_protocol_v1(false); |
1006 | | assert_eq!( |
1007 | | client_pin.process_command(&mut env, params, DUMMY_CHANNEL), |
1008 | | Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) |
1009 | | ); |
1010 | | } |
1011 | | |
1012 | | fn test_helper_process_set_pin(pin_uv_auth_protocol: PinUvAuthProtocol) { |
1013 | | let (mut client_pin, params) = |
1014 | | create_client_pin_and_parameters(pin_uv_auth_protocol, ClientPinSubCommand::SetPin); |
1015 | | let mut env = TestEnv::default(); |
1016 | | assert_eq!( |
1017 | | client_pin.process_command(&mut env, params, DUMMY_CHANNEL), |
1018 | | Ok(ResponseData::AuthenticatorClientPin(None)) |
1019 | | ); |
1020 | | } |
1021 | | |
1022 | | #[test] |
1023 | | fn test_process_set_pin_v1() { |
1024 | | test_helper_process_set_pin(PinUvAuthProtocol::V1); |
1025 | | } |
1026 | | |
1027 | | #[test] |
1028 | | fn test_process_set_pin_v2() { |
1029 | | test_helper_process_set_pin(PinUvAuthProtocol::V2); |
1030 | | } |
1031 | | |
1032 | | fn test_helper_process_change_pin(pin_uv_auth_protocol: PinUvAuthProtocol) { |
1033 | | let (mut client_pin, mut params) = |
1034 | | create_client_pin_and_parameters(pin_uv_auth_protocol, ClientPinSubCommand::ChangePin); |
1035 | | let shared_secret = client_pin |
1036 | | .get_pin_protocol(pin_uv_auth_protocol) |
1037 | | .decapsulate( |
1038 | | params.key_agreement.clone().unwrap(), |
1039 | | params.pin_uv_auth_protocol, |
1040 | | ) |
1041 | | .unwrap(); |
1042 | | let mut env = TestEnv::default(); |
1043 | | set_standard_pin(&mut env); |
1044 | | |
1045 | | let mut auth_param_data = params.new_pin_enc.clone().unwrap(); |
1046 | | auth_param_data.extend(params.pin_hash_enc.as_ref().unwrap()); |
1047 | | let pin_uv_auth_param = shared_secret.authenticate(&auth_param_data); |
1048 | | params.pin_uv_auth_param = Some(pin_uv_auth_param); |
1049 | | assert_eq!( |
1050 | | client_pin.process_command(&mut env, params.clone(), DUMMY_CHANNEL), |
1051 | | Ok(ResponseData::AuthenticatorClientPin(None)) |
1052 | | ); |
1053 | | |
1054 | | let mut bad_params = params.clone(); |
1055 | | bad_params.pin_hash_enc = Some(vec![0xEE; 16]); |
1056 | | assert_eq!( |
1057 | | client_pin.process_command(&mut env, bad_params, DUMMY_CHANNEL), |
1058 | | Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) |
1059 | | ); |
1060 | | |
1061 | | while storage::pin_retries(&mut env).unwrap() > 0 { |
1062 | | storage::decr_pin_retries(&mut env).unwrap(); |
1063 | | } |
1064 | | assert_eq!( |
1065 | | client_pin.process_command(&mut env, params, DUMMY_CHANNEL), |
1066 | | Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED) |
1067 | | ); |
1068 | | } |
1069 | | |
1070 | | #[test] |
1071 | | fn test_process_change_pin_v1() { |
1072 | | test_helper_process_change_pin(PinUvAuthProtocol::V1); |
1073 | | } |
1074 | | |
1075 | | #[test] |
1076 | | fn test_process_change_pin_v2() { |
1077 | | test_helper_process_change_pin(PinUvAuthProtocol::V2); |
1078 | | } |
1079 | | |
1080 | | fn test_helper_process_get_pin_token(pin_uv_auth_protocol: PinUvAuthProtocol) { |
1081 | | let (mut client_pin, params) = create_client_pin_and_parameters( |
1082 | | pin_uv_auth_protocol, |
1083 | | ClientPinSubCommand::GetPinToken, |
1084 | | ); |
1085 | | let shared_secret = client_pin |
1086 | | .get_pin_protocol(pin_uv_auth_protocol) |
1087 | | .decapsulate( |
1088 | | params.key_agreement.clone().unwrap(), |
1089 | | params.pin_uv_auth_protocol, |
1090 | | ) |
1091 | | .unwrap(); |
1092 | | let mut env = TestEnv::default(); |
1093 | | set_standard_pin(&mut env); |
1094 | | |
1095 | | let response = client_pin |
1096 | | .process_command(&mut env, params.clone(), DUMMY_CHANNEL) |
1097 | | .unwrap(); |
1098 | | let encrypted_token = match response { |
1099 | | ResponseData::AuthenticatorClientPin(Some(response)) => { |
1100 | | response.pin_uv_auth_token.unwrap() |
1101 | | } |
1102 | | _ => panic!("Invalid response type"), |
1103 | | }; |
1104 | | assert_eq!( |
1105 | | &*shared_secret.decrypt(&encrypted_token).unwrap(), |
1106 | | client_pin |
1107 | | .get_pin_protocol(pin_uv_auth_protocol) |
1108 | | .get_pin_uv_auth_token() |
1109 | | ); |
1110 | | assert_eq!( |
1111 | | client_pin |
1112 | | .pin_uv_auth_token_state |
1113 | | .has_permission(PinPermission::MakeCredential), |
1114 | | Ok(()) |
1115 | | ); |
1116 | | assert_eq!( |
1117 | | client_pin |
1118 | | .pin_uv_auth_token_state |
1119 | | .has_permission(PinPermission::GetAssertion), |
1120 | | Ok(()) |
1121 | | ); |
1122 | | assert_eq!( |
1123 | | client_pin |
1124 | | .pin_uv_auth_token_state |
1125 | | .has_no_permissions_rp_id(), |
1126 | | Ok(()) |
1127 | | ); |
1128 | | |
1129 | | let mut bad_params = params; |
1130 | | bad_params.pin_hash_enc = Some(vec![0xEE; 16]); |
1131 | | assert_eq!( |
1132 | | client_pin.process_command(&mut env, bad_params, DUMMY_CHANNEL), |
1133 | | Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID) |
1134 | | ); |
1135 | | } |
1136 | | |
1137 | | #[test] |
1138 | | fn test_process_get_pin_token_v1() { |
1139 | | test_helper_process_get_pin_token(PinUvAuthProtocol::V1); |
1140 | | } |
1141 | | |
1142 | | #[test] |
1143 | | fn test_process_get_pin_token_v2() { |
1144 | | test_helper_process_get_pin_token(PinUvAuthProtocol::V2); |
1145 | | } |
1146 | | |
1147 | | fn test_helper_process_get_pin_token_force_pin_change(pin_uv_auth_protocol: PinUvAuthProtocol) { |
1148 | | let (mut client_pin, params) = create_client_pin_and_parameters( |
1149 | | pin_uv_auth_protocol, |
1150 | | ClientPinSubCommand::GetPinToken, |
1151 | | ); |
1152 | | let mut env = TestEnv::default(); |
1153 | | set_standard_pin(&mut env); |
1154 | | |
1155 | | assert_eq!(env.persist().force_pin_change(), Ok(())); |
1156 | | assert_eq!( |
1157 | | client_pin.process_command(&mut env, params, DUMMY_CHANNEL), |
1158 | | Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID), |
1159 | | ); |
1160 | | } |
1161 | | |
1162 | | #[test] |
1163 | | fn test_process_get_pin_token_force_pin_change_v1() { |
1164 | | test_helper_process_get_pin_token_force_pin_change(PinUvAuthProtocol::V1); |
1165 | | } |
1166 | | |
1167 | | #[test] |
1168 | | fn test_process_get_pin_token_force_pin_change_v2() { |
1169 | | test_helper_process_get_pin_token_force_pin_change(PinUvAuthProtocol::V2); |
1170 | | } |
1171 | | |
1172 | | #[cfg(feature = "fingerprint")] |
1173 | | fn test_helper_process_get_pin_uv_auth_token_using_uv_with_permissions( |
1174 | | pin_uv_auth_protocol: PinUvAuthProtocol, |
1175 | | ) { |
1176 | | let (mut client_pin, params) = create_client_pin_and_parameters( |
1177 | | pin_uv_auth_protocol, |
1178 | | ClientPinSubCommand::GetPinUvAuthTokenUsingUvWithPermissions, |
1179 | | ); |
1180 | | let shared_secret = client_pin |
1181 | | .get_pin_protocol(pin_uv_auth_protocol) |
1182 | | .decapsulate( |
1183 | | params.key_agreement.clone().unwrap(), |
1184 | | params.pin_uv_auth_protocol, |
1185 | | ) |
1186 | | .unwrap(); |
1187 | | let mut env = TestEnv::default(); |
1188 | | set_standard_pin(&mut env); |
1189 | | assert!(env.create_fingerprint().is_ok()); |
1190 | | |
1191 | | let response = client_pin |
1192 | | .process_command(&mut env, params.clone(), DUMMY_CHANNEL) |
1193 | | .unwrap(); |
1194 | | let encrypted_token = match response { |
1195 | | ResponseData::AuthenticatorClientPin(Some(response)) => { |
1196 | | response.pin_uv_auth_token.unwrap() |
1197 | | } |
1198 | | _ => panic!("Invalid response type"), |
1199 | | }; |
1200 | | assert_eq!( |
1201 | | &*shared_secret.decrypt(&encrypted_token).unwrap(), |
1202 | | client_pin |
1203 | | .get_pin_protocol(pin_uv_auth_protocol) |
1204 | | .get_pin_uv_auth_token() |
1205 | | ); |
1206 | | assert_eq!( |
1207 | | client_pin |
1208 | | .pin_uv_auth_token_state |
1209 | | .has_permission(PinPermission::MakeCredential), |
1210 | | Ok(()) |
1211 | | ); |
1212 | | assert_eq!( |
1213 | | client_pin |
1214 | | .pin_uv_auth_token_state |
1215 | | .has_permission(PinPermission::GetAssertion), |
1216 | | Ok(()) |
1217 | | ); |
1218 | | assert_eq!( |
1219 | | client_pin |
1220 | | .pin_uv_auth_token_state |
1221 | | .has_permissions_rp_id("example.com"), |
1222 | | Ok(()) |
1223 | | ); |
1224 | | |
1225 | | let mut bad_params = params.clone(); |
1226 | | bad_params.permissions = Some(0x00); |
1227 | | assert_eq!( |
1228 | | client_pin.process_command(&mut env, bad_params, DUMMY_CHANNEL), |
1229 | | Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) |
1230 | | ); |
1231 | | |
1232 | | let mut bad_params = params; |
1233 | | bad_params.permissions_rp_id = None; |
1234 | | assert_eq!( |
1235 | | client_pin.process_command(&mut env, bad_params, DUMMY_CHANNEL), |
1236 | | Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) |
1237 | | ); |
1238 | | } |
1239 | | |
1240 | | #[test] |
1241 | | #[cfg(feature = "fingerprint")] |
1242 | | fn test_process_get_pin_uv_auth_token_using_uv_with_permissions_v1() { |
1243 | | test_helper_process_get_pin_uv_auth_token_using_uv_with_permissions(PinUvAuthProtocol::V1); |
1244 | | } |
1245 | | |
1246 | | #[test] |
1247 | | #[cfg(feature = "fingerprint")] |
1248 | | fn test_process_get_pin_uv_auth_token_using_uv_with_permissions_v2() { |
1249 | | test_helper_process_get_pin_uv_auth_token_using_uv_with_permissions(PinUvAuthProtocol::V2); |
1250 | | } |
1251 | | |
1252 | | #[cfg(feature = "fingerprint")] |
1253 | | fn test_helper_process_get_uv_retries(pin_uv_auth_protocol: PinUvAuthProtocol) { |
1254 | | let (mut client_pin, params) = create_client_pin_and_parameters( |
1255 | | pin_uv_auth_protocol, |
1256 | | ClientPinSubCommand::GetUvRetries, |
1257 | | ); |
1258 | | let mut env = TestEnv::default(); |
1259 | | let expected_response = Some(AuthenticatorClientPinResponse { |
1260 | | key_agreement: None, |
1261 | | pin_uv_auth_token: None, |
1262 | | retries: None, |
1263 | | power_cycle_state: None, |
1264 | | uv_retries: Some(storage::uv_retries(&mut env).unwrap() as u64), |
1265 | | }); |
1266 | | assert_eq!( |
1267 | | client_pin.process_command(&mut env, params.clone(), DUMMY_CHANNEL), |
1268 | | Ok(ResponseData::AuthenticatorClientPin(expected_response)) |
1269 | | ); |
1270 | | } |
1271 | | |
1272 | | #[test] |
1273 | | #[cfg(feature = "fingerprint")] |
1274 | | fn test_process_get_uv_retries_v1() { |
1275 | | test_helper_process_get_uv_retries(PinUvAuthProtocol::V1); |
1276 | | } |
1277 | | |
1278 | | #[test] |
1279 | | #[cfg(feature = "fingerprint")] |
1280 | | fn test_process_get_uv_retries_v2() { |
1281 | | test_helper_process_get_uv_retries(PinUvAuthProtocol::V2); |
1282 | | } |
1283 | | |
1284 | | fn test_helper_process_get_pin_uv_auth_token_using_pin_with_permissions( |
1285 | | pin_uv_auth_protocol: PinUvAuthProtocol, |
1286 | | ) { |
1287 | | let (mut client_pin, params) = create_client_pin_and_parameters( |
1288 | | pin_uv_auth_protocol, |
1289 | | ClientPinSubCommand::GetPinUvAuthTokenUsingPinWithPermissions, |
1290 | | ); |
1291 | | let shared_secret = client_pin |
1292 | | .get_pin_protocol(pin_uv_auth_protocol) |
1293 | | .decapsulate( |
1294 | | params.key_agreement.clone().unwrap(), |
1295 | | params.pin_uv_auth_protocol, |
1296 | | ) |
1297 | | .unwrap(); |
1298 | | let mut env = TestEnv::default(); |
1299 | | set_standard_pin(&mut env); |
1300 | | |
1301 | | let response = client_pin |
1302 | | .process_command(&mut env, params.clone(), DUMMY_CHANNEL) |
1303 | | .unwrap(); |
1304 | | let encrypted_token = match response { |
1305 | | ResponseData::AuthenticatorClientPin(Some(response)) => { |
1306 | | response.pin_uv_auth_token.unwrap() |
1307 | | } |
1308 | | _ => panic!("Invalid response type"), |
1309 | | }; |
1310 | | assert_eq!( |
1311 | | &*shared_secret.decrypt(&encrypted_token).unwrap(), |
1312 | | client_pin |
1313 | | .get_pin_protocol(pin_uv_auth_protocol) |
1314 | | .get_pin_uv_auth_token() |
1315 | | ); |
1316 | | assert_eq!( |
1317 | | client_pin |
1318 | | .pin_uv_auth_token_state |
1319 | | .has_permission(PinPermission::MakeCredential), |
1320 | | Ok(()) |
1321 | | ); |
1322 | | assert_eq!( |
1323 | | client_pin |
1324 | | .pin_uv_auth_token_state |
1325 | | .has_permission(PinPermission::GetAssertion), |
1326 | | Ok(()) |
1327 | | ); |
1328 | | assert_eq!( |
1329 | | client_pin |
1330 | | .pin_uv_auth_token_state |
1331 | | .has_permissions_rp_id("example.com"), |
1332 | | Ok(()) |
1333 | | ); |
1334 | | |
1335 | | let mut bad_params = params.clone(); |
1336 | | bad_params.permissions = Some(0x00); |
1337 | | assert_eq!( |
1338 | | client_pin.process_command(&mut env, bad_params, DUMMY_CHANNEL), |
1339 | | Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) |
1340 | | ); |
1341 | | |
1342 | | let mut bad_params = params.clone(); |
1343 | | bad_params.permissions_rp_id = None; |
1344 | | assert_eq!( |
1345 | | client_pin.process_command(&mut env, bad_params, DUMMY_CHANNEL), |
1346 | | Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) |
1347 | | ); |
1348 | | |
1349 | | let mut bad_params = params; |
1350 | | bad_params.pin_hash_enc = Some(vec![0xEE; 16]); |
1351 | | assert_eq!( |
1352 | | client_pin.process_command(&mut env, bad_params, DUMMY_CHANNEL), |
1353 | | Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID) |
1354 | | ); |
1355 | | } |
1356 | | |
1357 | | #[test] |
1358 | | fn test_process_get_pin_uv_auth_token_using_pin_with_permissions_v1() { |
1359 | | test_helper_process_get_pin_uv_auth_token_using_pin_with_permissions(PinUvAuthProtocol::V1); |
1360 | | } |
1361 | | |
1362 | | #[test] |
1363 | | fn test_process_get_pin_uv_auth_token_using_pin_with_permissions_v2() { |
1364 | | test_helper_process_get_pin_uv_auth_token_using_pin_with_permissions(PinUvAuthProtocol::V2); |
1365 | | } |
1366 | | |
1367 | | fn test_helper_process_get_pin_uv_auth_token_using_pin_with_permissions_force_pin_change( |
1368 | | pin_uv_auth_protocol: PinUvAuthProtocol, |
1369 | | ) { |
1370 | | let (mut client_pin, params) = create_client_pin_and_parameters( |
1371 | | pin_uv_auth_protocol, |
1372 | | ClientPinSubCommand::GetPinUvAuthTokenUsingPinWithPermissions, |
1373 | | ); |
1374 | | let mut env = TestEnv::default(); |
1375 | | set_standard_pin(&mut env); |
1376 | | |
1377 | | assert_eq!(env.persist().force_pin_change(), Ok(())); |
1378 | | assert_eq!( |
1379 | | client_pin.process_command(&mut env, params, DUMMY_CHANNEL), |
1380 | | Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID) |
1381 | | ); |
1382 | | } |
1383 | | |
1384 | | #[test] |
1385 | | fn test_process_get_pin_uv_auth_token_using_pin_with_permissions_force_pin_change_v1() { |
1386 | | test_helper_process_get_pin_uv_auth_token_using_pin_with_permissions_force_pin_change( |
1387 | | PinUvAuthProtocol::V1, |
1388 | | ); |
1389 | | } |
1390 | | |
1391 | | #[test] |
1392 | | fn test_process_get_pin_uv_auth_token_using_pin_with_permissions_force_pin_change_v2() { |
1393 | | test_helper_process_get_pin_uv_auth_token_using_pin_with_permissions_force_pin_change( |
1394 | | PinUvAuthProtocol::V2, |
1395 | | ); |
1396 | | } |
1397 | | |
1398 | | fn test_helper_decrypt_pin(pin_uv_auth_protocol: PinUvAuthProtocol) { |
1399 | | let mut env = TestEnv::default(); |
1400 | | let pin_protocol = PinProtocol::<TestEnv>::new(&mut env); |
1401 | | let shared_secret = pin_protocol |
1402 | | .decapsulate(pin_protocol.get_public_key(), pin_uv_auth_protocol) |
1403 | | .unwrap(); |
1404 | | |
1405 | | let new_pin_enc = encrypt_pin(&shared_secret, b"1234".to_vec()); |
1406 | | assert_eq!( |
1407 | | &*decrypt_pin::<TestEnv>(&shared_secret, new_pin_enc).unwrap(), |
1408 | | b"1234", |
1409 | | ); |
1410 | | |
1411 | | let new_pin_enc = encrypt_pin(&shared_secret, b"123".to_vec()); |
1412 | | assert_eq!( |
1413 | | &*decrypt_pin::<TestEnv>(&shared_secret, new_pin_enc).unwrap(), |
1414 | | b"123", |
1415 | | ); |
1416 | | |
1417 | | // Encrypted PIN is too short. |
1418 | | let new_pin_enc = vec![0x44; 63]; |
1419 | | assert_eq!( |
1420 | | decrypt_pin::<TestEnv>(&shared_secret, new_pin_enc), |
1421 | | Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) |
1422 | | ); |
1423 | | |
1424 | | // Encrypted PIN is too long. |
1425 | | let new_pin_enc = vec![0x44; 65]; |
1426 | | assert_eq!( |
1427 | | decrypt_pin::<TestEnv>(&shared_secret, new_pin_enc), |
1428 | | Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) |
1429 | | ); |
1430 | | } |
1431 | | |
1432 | | #[test] |
1433 | | fn test_decrypt_pin_v1() { |
1434 | | test_helper_decrypt_pin(PinUvAuthProtocol::V1); |
1435 | | } |
1436 | | |
1437 | | #[test] |
1438 | | fn test_decrypt_pin_v2() { |
1439 | | test_helper_decrypt_pin(PinUvAuthProtocol::V2); |
1440 | | } |
1441 | | |
1442 | | fn test_helper_check_and_store_new_pin(pin_uv_auth_protocol: PinUvAuthProtocol) { |
1443 | | let mut env = TestEnv::default(); |
1444 | | let pin_protocol = PinProtocol::<TestEnv>::new(&mut env); |
1445 | | let shared_secret = pin_protocol |
1446 | | .decapsulate(pin_protocol.get_public_key(), pin_uv_auth_protocol) |
1447 | | .unwrap(); |
1448 | | |
1449 | | let test_cases = vec![ |
1450 | | // Accept PIN "1234". |
1451 | | (b"1234".to_vec(), Ok(())), |
1452 | | // Reject PIN "123" since it is too short. |
1453 | | ( |
1454 | | b"123".to_vec(), |
1455 | | Err(Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION), |
1456 | | ), |
1457 | | // Reject PIN "12'\0'4" (a zero byte at index 2). |
1458 | | ( |
1459 | | [b'1', b'2', 0, b'4'].to_vec(), |
1460 | | Err(Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION), |
1461 | | ), |
1462 | | // PINs must be at most 63 bytes long, to allow for a trailing 0u8 padding. |
1463 | | ( |
1464 | | vec![0x30; 64], |
1465 | | Err(Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION), |
1466 | | ), |
1467 | | ]; |
1468 | | for (pin, result) in test_cases { |
1469 | | let old_pin_hash = env.persist().pin_hash().unwrap(); |
1470 | | let new_pin_enc = encrypt_pin(&shared_secret, pin); |
1471 | | |
1472 | | assert_eq!( |
1473 | | check_and_store_new_pin(&mut env, &shared_secret, new_pin_enc), |
1474 | | result |
1475 | | ); |
1476 | | if result.is_ok() { |
1477 | | assert_ne!(old_pin_hash, env.persist().pin_hash().unwrap()); |
1478 | | } else { |
1479 | | assert_eq!(old_pin_hash, env.persist().pin_hash().unwrap()); |
1480 | | } |
1481 | | } |
1482 | | } |
1483 | | |
1484 | | #[test] |
1485 | | fn test_check_and_store_new_pin_v1() { |
1486 | | test_helper_check_and_store_new_pin(PinUvAuthProtocol::V1); |
1487 | | } |
1488 | | |
1489 | | #[test] |
1490 | | fn test_check_and_store_new_pin_v2() { |
1491 | | test_helper_check_and_store_new_pin(PinUvAuthProtocol::V2); |
1492 | | } |
1493 | | |
1494 | | /// Generates valid inputs for process_hmac_secret and returns the output. |
1495 | | fn get_process_hmac_secret_decrypted_output( |
1496 | | pin_uv_auth_protocol: PinUvAuthProtocol, |
1497 | | cred_random: &[u8; 32], |
1498 | | salt: Vec<u8>, |
1499 | | ) -> CtapResult<Vec<u8>> { |
1500 | | let mut env = TestEnv::default(); |
1501 | | let (client_pin, shared_secret) = create_client_pin_and_shared_secret(pin_uv_auth_protocol); |
1502 | | |
1503 | | let salt_enc = shared_secret.encrypt(&mut env, &salt).unwrap(); |
1504 | | let salt_auth = shared_secret.authenticate(&salt_enc); |
1505 | | let hmac_secret_input = GetAssertionHmacSecretInput { |
1506 | | key_agreement: client_pin |
1507 | | .get_pin_protocol(pin_uv_auth_protocol) |
1508 | | .get_public_key(), |
1509 | | salt_enc, |
1510 | | salt_auth, |
1511 | | pin_uv_auth_protocol, |
1512 | | }; |
1513 | | let output = client_pin.process_hmac_secret(&mut env, hmac_secret_input, cred_random); |
1514 | | output.map(|v| shared_secret.decrypt(&v).unwrap().expose_secret_to_vec()) |
1515 | | } |
1516 | | |
1517 | | fn test_helper_process_hmac_secret_bad_salt_auth(pin_uv_auth_protocol: PinUvAuthProtocol) { |
1518 | | let mut env = TestEnv::default(); |
1519 | | let (client_pin, shared_secret) = create_client_pin_and_shared_secret(pin_uv_auth_protocol); |
1520 | | let cred_random = [0xC9; 32]; |
1521 | | |
1522 | | let salt_enc = vec![0x01; 32]; |
1523 | | let mut salt_auth = shared_secret.authenticate(&salt_enc); |
1524 | | salt_auth[0] ^= 0x01; |
1525 | | let hmac_secret_input = GetAssertionHmacSecretInput { |
1526 | | key_agreement: client_pin |
1527 | | .get_pin_protocol(pin_uv_auth_protocol) |
1528 | | .get_public_key(), |
1529 | | salt_enc, |
1530 | | salt_auth, |
1531 | | pin_uv_auth_protocol, |
1532 | | }; |
1533 | | let output = client_pin.process_hmac_secret(&mut env, hmac_secret_input, &cred_random); |
1534 | | assert_eq!(output, Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)); |
1535 | | } |
1536 | | |
1537 | | #[test] |
1538 | | fn test_process_hmac_secret_bad_salt_auth_v1() { |
1539 | | test_helper_process_hmac_secret_bad_salt_auth(PinUvAuthProtocol::V1); |
1540 | | } |
1541 | | |
1542 | | #[test] |
1543 | | fn test_process_hmac_secret_bad_salt_auth_v2() { |
1544 | | test_helper_process_hmac_secret_bad_salt_auth(PinUvAuthProtocol::V2); |
1545 | | } |
1546 | | |
1547 | | fn test_helper_process_hmac_secret_one_salt(pin_uv_auth_protocol: PinUvAuthProtocol) { |
1548 | | let cred_random = [0xC9; 32]; |
1549 | | |
1550 | | let salt = vec![0x01; 32]; |
1551 | | let mut expected_output = [0; HASH_SIZE]; |
1552 | | Hmac::<TestEnv>::mac(&cred_random, &salt, &mut expected_output); |
1553 | | |
1554 | | let output = |
1555 | | get_process_hmac_secret_decrypted_output(pin_uv_auth_protocol, &cred_random, salt) |
1556 | | .unwrap(); |
1557 | | assert_eq!(&*output, &expected_output); |
1558 | | } |
1559 | | |
1560 | | #[test] |
1561 | | fn test_process_hmac_secret_one_salt_v1() { |
1562 | | test_helper_process_hmac_secret_one_salt(PinUvAuthProtocol::V1); |
1563 | | } |
1564 | | |
1565 | | #[test] |
1566 | | fn test_process_hmac_secret_one_salt_v2() { |
1567 | | test_helper_process_hmac_secret_one_salt(PinUvAuthProtocol::V2); |
1568 | | } |
1569 | | |
1570 | | fn test_helper_process_hmac_secret_two_salts(pin_uv_auth_protocol: PinUvAuthProtocol) { |
1571 | | let cred_random = [0xC9; 32]; |
1572 | | |
1573 | | let salt1 = [0x01; 32]; |
1574 | | let salt2 = [0x02; 32]; |
1575 | | let mut expected_output1 = [0; HASH_SIZE]; |
1576 | | let mut expected_output2 = [0; HASH_SIZE]; |
1577 | | Hmac::<TestEnv>::mac(&cred_random, &salt1, &mut expected_output1); |
1578 | | Hmac::<TestEnv>::mac(&cred_random, &salt2, &mut expected_output2); |
1579 | | |
1580 | | let mut salt12 = vec![0x00; 64]; |
1581 | | salt12[..32].copy_from_slice(&salt1); |
1582 | | salt12[32..].copy_from_slice(&salt2); |
1583 | | let output = |
1584 | | get_process_hmac_secret_decrypted_output(pin_uv_auth_protocol, &cred_random, salt12) |
1585 | | .unwrap(); |
1586 | | assert_eq!(&output[..32], &expected_output1); |
1587 | | assert_eq!(&output[32..], &expected_output2); |
1588 | | |
1589 | | let mut salt02 = vec![0x00; 64]; |
1590 | | salt02[32..].copy_from_slice(&salt2); |
1591 | | let output = |
1592 | | get_process_hmac_secret_decrypted_output(pin_uv_auth_protocol, &cred_random, salt02) |
1593 | | .unwrap(); |
1594 | | assert_eq!(&output[32..], &expected_output2); |
1595 | | |
1596 | | let mut salt10 = vec![0x00; 64]; |
1597 | | salt10[..32].copy_from_slice(&salt1); |
1598 | | let output = |
1599 | | get_process_hmac_secret_decrypted_output(pin_uv_auth_protocol, &cred_random, salt10) |
1600 | | .unwrap(); |
1601 | | assert_eq!(&output[..32], &expected_output1); |
1602 | | } |
1603 | | |
1604 | | #[test] |
1605 | | fn test_process_hmac_secret_two_salts_v1() { |
1606 | | test_helper_process_hmac_secret_two_salts(PinUvAuthProtocol::V1); |
1607 | | } |
1608 | | |
1609 | | #[test] |
1610 | | fn test_process_hmac_secret_two_salts_v2() { |
1611 | | test_helper_process_hmac_secret_two_salts(PinUvAuthProtocol::V2); |
1612 | | } |
1613 | | |
1614 | | fn test_helper_process_hmac_secret_wrong_length(pin_uv_auth_protocol: PinUvAuthProtocol) { |
1615 | | let cred_random = [0xC9; 32]; |
1616 | | |
1617 | | let output = get_process_hmac_secret_decrypted_output( |
1618 | | pin_uv_auth_protocol, |
1619 | | &cred_random, |
1620 | | vec![0x5E; 48], |
1621 | | ); |
1622 | | assert_eq!(output, Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)); |
1623 | | } |
1624 | | |
1625 | | #[test] |
1626 | | fn test_process_hmac_secret_wrong_length_v1() { |
1627 | | test_helper_process_hmac_secret_wrong_length(PinUvAuthProtocol::V1); |
1628 | | } |
1629 | | |
1630 | | #[test] |
1631 | | fn test_process_hmac_secret_wrong_length_v2() { |
1632 | | test_helper_process_hmac_secret_wrong_length(PinUvAuthProtocol::V2); |
1633 | | } |
1634 | | |
1635 | | #[test] |
1636 | | fn test_has_permission() { |
1637 | | let mut env = TestEnv::default(); |
1638 | | let mut client_pin = ClientPin::<TestEnv>::new(&mut env); |
1639 | | client_pin.pin_uv_auth_token_state.set_permissions(0x7F); |
1640 | | for permission in PinPermission::into_enum_iter() { |
1641 | | assert_eq!( |
1642 | | client_pin |
1643 | | .pin_uv_auth_token_state |
1644 | | .has_permission(permission), |
1645 | | Ok(()) |
1646 | | ); |
1647 | | } |
1648 | | client_pin.pin_uv_auth_token_state.set_permissions(0x00); |
1649 | | for permission in PinPermission::into_enum_iter() { |
1650 | | assert_eq!( |
1651 | | client_pin |
1652 | | .pin_uv_auth_token_state |
1653 | | .has_permission(permission), |
1654 | | Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) |
1655 | | ); |
1656 | | } |
1657 | | } |
1658 | | |
1659 | | #[test] |
1660 | | fn test_has_no_rp_id_permission() { |
1661 | | let mut env = TestEnv::default(); |
1662 | | let mut client_pin = ClientPin::<TestEnv>::new(&mut env); |
1663 | | assert_eq!(client_pin.has_no_rp_id_permission(), Ok(())); |
1664 | | client_pin |
1665 | | .pin_uv_auth_token_state |
1666 | | .set_permissions_rp_id(Some("example.com".to_string())); |
1667 | | assert_eq!( |
1668 | | client_pin.has_no_rp_id_permission(), |
1669 | | Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) |
1670 | | ); |
1671 | | } |
1672 | | |
1673 | | #[test] |
1674 | | fn test_has_no_or_rp_id_permission() { |
1675 | | let mut env = TestEnv::default(); |
1676 | | let mut client_pin = ClientPin::<TestEnv>::new(&mut env); |
1677 | | assert_eq!(client_pin.has_no_or_rp_id_permission("example.com"), Ok(())); |
1678 | | client_pin |
1679 | | .pin_uv_auth_token_state |
1680 | | .set_permissions_rp_id(Some("example.com".to_string())); |
1681 | | assert_eq!(client_pin.has_no_or_rp_id_permission("example.com"), Ok(())); |
1682 | | assert_eq!( |
1683 | | client_pin.has_no_or_rp_id_permission("another.example.com"), |
1684 | | Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) |
1685 | | ); |
1686 | | } |
1687 | | |
1688 | | #[test] |
1689 | | fn test_has_no_or_rp_id_hash_permission() { |
1690 | | let mut env = TestEnv::default(); |
1691 | | let mut client_pin = ClientPin::<TestEnv>::new(&mut env); |
1692 | | let rp_id_hash = Sha::<TestEnv>::digest(b"example.com"); |
1693 | | assert_eq!( |
1694 | | client_pin.has_no_or_rp_id_hash_permission(&rp_id_hash), |
1695 | | Ok(()) |
1696 | | ); |
1697 | | client_pin |
1698 | | .pin_uv_auth_token_state |
1699 | | .set_permissions_rp_id(Some("example.com".to_string())); |
1700 | | assert_eq!( |
1701 | | client_pin.has_no_or_rp_id_hash_permission(&rp_id_hash), |
1702 | | Ok(()) |
1703 | | ); |
1704 | | assert_eq!( |
1705 | | client_pin.has_no_or_rp_id_hash_permission(&[0x4A; 32]), |
1706 | | Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) |
1707 | | ); |
1708 | | } |
1709 | | |
1710 | | #[test] |
1711 | | fn test_ensure_rp_id_permission() { |
1712 | | let mut env = TestEnv::default(); |
1713 | | let mut client_pin = ClientPin::<TestEnv>::new(&mut env); |
1714 | | assert_eq!(client_pin.ensure_rp_id_permission("example.com"), Ok(())); |
1715 | | assert_eq!( |
1716 | | client_pin |
1717 | | .pin_uv_auth_token_state |
1718 | | .has_permissions_rp_id("example.com"), |
1719 | | Ok(()) |
1720 | | ); |
1721 | | assert_eq!(client_pin.ensure_rp_id_permission("example.com"), Ok(())); |
1722 | | assert_eq!( |
1723 | | client_pin.ensure_rp_id_permission("another.example.com"), |
1724 | | Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) |
1725 | | ); |
1726 | | } |
1727 | | |
1728 | | #[test] |
1729 | | fn test_verify_pin_uv_auth_token() { |
1730 | | let mut env = TestEnv::default(); |
1731 | | let mut client_pin = ClientPin::<TestEnv>::new(&mut env); |
1732 | | let message = [0xAA]; |
1733 | | assert!(!client_pin.has_token(&mut env)); |
1734 | | client_pin |
1735 | | .pin_uv_auth_token_state |
1736 | | .begin_using_pin_uv_auth_token(&mut env, false); |
1737 | | assert!(client_pin.has_token(&mut env)); |
1738 | | |
1739 | | let pin_uv_auth_token_v1 = client_pin |
1740 | | .get_pin_protocol(PinUvAuthProtocol::V1) |
1741 | | .get_pin_uv_auth_token(); |
1742 | | let pin_uv_auth_param_v1 = |
1743 | | authenticate_pin_uv_auth_token(pin_uv_auth_token_v1, &message, PinUvAuthProtocol::V1); |
1744 | | let pin_uv_auth_token_v2 = client_pin |
1745 | | .get_pin_protocol(PinUvAuthProtocol::V2) |
1746 | | .get_pin_uv_auth_token(); |
1747 | | let pin_uv_auth_param_v2 = |
1748 | | authenticate_pin_uv_auth_token(pin_uv_auth_token_v2, &message, PinUvAuthProtocol::V2); |
1749 | | let pin_uv_auth_param_v1_from_v2_token = |
1750 | | authenticate_pin_uv_auth_token(pin_uv_auth_token_v2, &message, PinUvAuthProtocol::V1); |
1751 | | let pin_uv_auth_param_v2_from_v1_token = |
1752 | | authenticate_pin_uv_auth_token(pin_uv_auth_token_v1, &message, PinUvAuthProtocol::V2); |
1753 | | |
1754 | | assert_eq!( |
1755 | | client_pin.verify_pin_uv_auth_token( |
1756 | | &message, |
1757 | | &pin_uv_auth_param_v1, |
1758 | | PinUvAuthProtocol::V1 |
1759 | | ), |
1760 | | Ok(()) |
1761 | | ); |
1762 | | assert_eq!( |
1763 | | client_pin.verify_pin_uv_auth_token( |
1764 | | &message, |
1765 | | &pin_uv_auth_param_v2, |
1766 | | PinUvAuthProtocol::V2 |
1767 | | ), |
1768 | | Ok(()) |
1769 | | ); |
1770 | | assert_eq!( |
1771 | | client_pin.verify_pin_uv_auth_token( |
1772 | | &message, |
1773 | | &pin_uv_auth_param_v1, |
1774 | | PinUvAuthProtocol::V2 |
1775 | | ), |
1776 | | Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) |
1777 | | ); |
1778 | | assert_eq!( |
1779 | | client_pin.verify_pin_uv_auth_token( |
1780 | | &message, |
1781 | | &pin_uv_auth_param_v2, |
1782 | | PinUvAuthProtocol::V1 |
1783 | | ), |
1784 | | Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) |
1785 | | ); |
1786 | | assert_eq!( |
1787 | | client_pin.verify_pin_uv_auth_token( |
1788 | | &message, |
1789 | | &pin_uv_auth_param_v1_from_v2_token, |
1790 | | PinUvAuthProtocol::V1 |
1791 | | ), |
1792 | | Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) |
1793 | | ); |
1794 | | assert_eq!( |
1795 | | client_pin.verify_pin_uv_auth_token( |
1796 | | &message, |
1797 | | &pin_uv_auth_param_v2_from_v1_token, |
1798 | | PinUvAuthProtocol::V2 |
1799 | | ), |
1800 | | Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) |
1801 | | ); |
1802 | | } |
1803 | | |
1804 | | #[test] |
1805 | | fn test_verify_pin_uv_auth_token_not_in_use() { |
1806 | | let mut env = TestEnv::default(); |
1807 | | let client_pin = ClientPin::<TestEnv>::new(&mut env); |
1808 | | let message = [0xAA]; |
1809 | | |
1810 | | let pin_uv_auth_token_v1 = client_pin |
1811 | | .get_pin_protocol(PinUvAuthProtocol::V1) |
1812 | | .get_pin_uv_auth_token(); |
1813 | | let pin_uv_auth_param_v1 = |
1814 | | authenticate_pin_uv_auth_token(pin_uv_auth_token_v1, &message, PinUvAuthProtocol::V1); |
1815 | | |
1816 | | assert_eq!( |
1817 | | client_pin.verify_pin_uv_auth_token( |
1818 | | &message, |
1819 | | &pin_uv_auth_param_v1, |
1820 | | PinUvAuthProtocol::V1 |
1821 | | ), |
1822 | | Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) |
1823 | | ); |
1824 | | } |
1825 | | |
1826 | | #[test] |
1827 | | fn test_reset() { |
1828 | | let mut env = TestEnv::default(); |
1829 | | let mut client_pin = ClientPin::<TestEnv>::new(&mut env); |
1830 | | let public_key_v1 = client_pin.pin_protocol_v1.get_public_key(); |
1831 | | let public_key_v2 = client_pin.pin_protocol_v2.get_public_key(); |
1832 | | let token_v1 = *client_pin.pin_protocol_v1.get_pin_uv_auth_token(); |
1833 | | let token_v2 = *client_pin.pin_protocol_v2.get_pin_uv_auth_token(); |
1834 | | client_pin.pin_uv_auth_token_state.set_permissions(0xFF); |
1835 | | client_pin |
1836 | | .pin_uv_auth_token_state |
1837 | | .set_permissions_rp_id(Some(String::from("example.com"))); |
1838 | | client_pin.reset(&mut env); |
1839 | | assert_ne!(public_key_v1, client_pin.pin_protocol_v1.get_public_key()); |
1840 | | assert_ne!(public_key_v2, client_pin.pin_protocol_v2.get_public_key()); |
1841 | | assert_ne!( |
1842 | | &token_v1, |
1843 | | client_pin.pin_protocol_v1.get_pin_uv_auth_token() |
1844 | | ); |
1845 | | assert_ne!( |
1846 | | &token_v2, |
1847 | | client_pin.pin_protocol_v2.get_pin_uv_auth_token() |
1848 | | ); |
1849 | | for permission in PinPermission::into_enum_iter() { |
1850 | | assert_eq!( |
1851 | | client_pin.has_permission(permission), |
1852 | | Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) |
1853 | | ); |
1854 | | } |
1855 | | assert_eq!(client_pin.has_no_rp_id_permission(), Ok(())); |
1856 | | } |
1857 | | |
1858 | | #[test] |
1859 | | fn test_update_timeouts() { |
1860 | | let (mut client_pin, mut params) = create_client_pin_and_parameters( |
1861 | | PinUvAuthProtocol::V2, |
1862 | | ClientPinSubCommand::GetPinUvAuthTokenUsingPinWithPermissions, |
1863 | | ); |
1864 | | let mut env = TestEnv::default(); |
1865 | | set_standard_pin(&mut env); |
1866 | | params.permissions = Some(0xFF); |
1867 | | |
1868 | | assert!(client_pin |
1869 | | .process_command(&mut env, params, DUMMY_CHANNEL) |
1870 | | .is_ok()); |
1871 | | for permission in PinPermission::into_enum_iter() { |
1872 | | assert_eq!( |
1873 | | client_pin |
1874 | | .pin_uv_auth_token_state |
1875 | | .has_permission(permission), |
1876 | | Ok(()) |
1877 | | ); |
1878 | | } |
1879 | | assert_eq!( |
1880 | | client_pin |
1881 | | .pin_uv_auth_token_state |
1882 | | .has_permissions_rp_id("example.com"), |
1883 | | Ok(()) |
1884 | | ); |
1885 | | assert!(client_pin.has_token(&mut env)); |
1886 | | |
1887 | | env.clock().advance(30001); |
1888 | | client_pin.update_timeouts(&mut env); |
1889 | | for permission in PinPermission::into_enum_iter() { |
1890 | | assert_eq!( |
1891 | | client_pin |
1892 | | .pin_uv_auth_token_state |
1893 | | .has_permission(permission), |
1894 | | Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) |
1895 | | ); |
1896 | | } |
1897 | | assert_eq!( |
1898 | | client_pin |
1899 | | .pin_uv_auth_token_state |
1900 | | .has_permissions_rp_id("example.com"), |
1901 | | Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) |
1902 | | ); |
1903 | | assert!(!client_pin.has_token(&mut env)); |
1904 | | } |
1905 | | |
1906 | | #[test] |
1907 | | fn test_clear_token_flags() { |
1908 | | let (mut client_pin, mut params) = create_client_pin_and_parameters( |
1909 | | PinUvAuthProtocol::V2, |
1910 | | ClientPinSubCommand::GetPinUvAuthTokenUsingPinWithPermissions, |
1911 | | ); |
1912 | | let mut env = TestEnv::default(); |
1913 | | set_standard_pin(&mut env); |
1914 | | params.permissions = Some(0xFF); |
1915 | | #[cfg(not(feature = "config_command"))] |
1916 | | { |
1917 | | params.permissions = params |
1918 | | .permissions |
1919 | | .map(|p| p & !(PinPermission::AuthenticatorConfiguration as u8)); |
1920 | | } |
1921 | | |
1922 | | assert!(client_pin |
1923 | | .process_command(&mut env, params, DUMMY_CHANNEL) |
1924 | | .is_ok()); |
1925 | | for permission in PinPermission::into_enum_iter() { |
1926 | | assert_eq!( |
1927 | | client_pin |
1928 | | .pin_uv_auth_token_state |
1929 | | .has_permission(permission), |
1930 | | Ok(()) |
1931 | | ); |
1932 | | } |
1933 | | assert_eq!(client_pin.check_user_verified_flag(), Ok(())); |
1934 | | |
1935 | | client_pin.clear_token_flags(); |
1936 | | assert_eq!( |
1937 | | client_pin |
1938 | | .pin_uv_auth_token_state |
1939 | | .has_permission(PinPermission::CredentialManagement), |
1940 | | Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) |
1941 | | ); |
1942 | | assert_eq!( |
1943 | | client_pin |
1944 | | .pin_uv_auth_token_state |
1945 | | .has_permission(PinPermission::LargeBlobWrite), |
1946 | | Ok(()) |
1947 | | ); |
1948 | | assert_eq!( |
1949 | | client_pin.check_user_verified_flag(), |
1950 | | Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) |
1951 | | ); |
1952 | | } |
1953 | | } |