1mod auth;
21mod cbor_helpers;
22pub mod credential_mgmt;
23
24use crate::error::{Error, Result};
25use crate::request::{GetAssertionRequest, MakeCredentialRequest};
26use crate::transport::Transport;
27
28use soft_fido2_ctap::cbor::{MapBuilder, Value};
29
30use serde::Serialize;
31use sha2::{Digest, Sha256};
32use smallvec::SmallVec;
33
34pub struct Client;
38
39impl Client {
40 pub fn make_credential(
44 transport: &mut Transport,
45 request: MakeCredentialRequest,
46 ) -> Result<Vec<u8>> {
47 let mut builder = MapBuilder::new();
48
49 builder = builder
50 .insert_bytes(1, request.client_data_hash().as_slice())
51 .map_err(|_| Error::Other)?;
52
53 let mut rp_fields: SmallVec<[(&str, &str); 2]> = SmallVec::new();
54 rp_fields.push(("id", request.rp().id.as_str()));
55 if let Some(name) = &request.rp().name {
56 rp_fields.push(("name", name.as_str()));
57 }
58 builder = builder
59 .insert_text_map(2, &rp_fields)
60 .map_err(|_| Error::Other)?;
61
62 let user_cbor = soft_fido2_ctap::cbor::encode(&request.user()).map_err(|_| Error::Other)?;
63 builder = builder
64 .insert(
65 3,
66 &soft_fido2_ctap::cbor::decode::<Value>(&user_cbor).map_err(|_| Error::Other)?,
67 )
68 .map_err(|_| Error::Other)?;
69
70 #[derive(Serialize)]
71 struct PubKeyCredParam {
72 alg: i32,
73 #[serde(rename = "type")]
74 cred_type: &'static str,
75 }
76
77 let alg_param = PubKeyCredParam {
78 alg: -7,
79 cred_type: "public-key",
80 };
81 let alg_params: SmallVec<[PubKeyCredParam; 1]> = SmallVec::from_buf([alg_param]);
82 builder = builder.insert(4, alg_params).map_err(|_| Error::Other)?;
83
84 if request.resident_key.is_some() || request.user_verification.is_some() {
85 #[derive(Serialize)]
86 struct Options {
87 #[serde(skip_serializing_if = "Option::is_none")]
88 rk: Option<bool>,
89 #[serde(skip_serializing_if = "Option::is_none")]
90 uv: Option<bool>,
91 }
92
93 let options = Options {
94 rk: request.resident_key,
95 uv: request.user_verification,
96 };
97
98 builder = builder.insert(7, &options).map_err(|_| Error::Other)?;
99 }
100
101 if let Some(pin_auth) = request.pin_uv_auth() {
102 builder = builder
103 .insert_bytes(8, pin_auth.param())
104 .map_err(|_| Error::Other)?;
105
106 builder = builder
107 .insert(9, pin_auth.protocol_u8())
108 .map_err(|_| Error::Other)?;
109 }
110
111 let request_bytes = builder.build().map_err(|_| Error::Other)?;
112 let response = transport.send_ctap_command(0x01, &request_bytes, request.timeout_ms)?;
113
114 Ok(response)
115 }
116
117 pub fn get_assertion(
121 transport: &mut Transport,
122 request: GetAssertionRequest,
123 ) -> Result<Vec<u8>> {
124 let mut builder = MapBuilder::new();
125
126 builder = builder
127 .insert(1, request.rp_id())
128 .map_err(|_| Error::Other)?;
129
130 builder = builder
131 .insert_bytes(2, request.client_data_hash().as_slice())
132 .map_err(|_| Error::Other)?;
133
134 if !request.allow_list().is_empty() {
135 #[derive(Serialize)]
136 struct Credential<'a> {
137 id: &'a [u8],
138 #[serde(rename = "type")]
139 credential_type: &'a str,
140 }
141
142 let allow_list: SmallVec<[Credential; 4]> = request
143 .allow_list()
144 .iter()
145 .map(|cred| Credential {
146 id: cred.id.as_slice(),
147 credential_type: cred.credential_type.as_str(),
148 })
149 .collect();
150
151 builder = builder.insert(3, &allow_list).map_err(|_| Error::Other)?;
152 }
153
154 if request.user_verification.is_some() {
155 #[derive(Serialize)]
156 struct Options {
157 #[serde(skip_serializing_if = "Option::is_none")]
158 uv: Option<bool>,
159 }
160
161 let options = Options {
162 uv: request.user_verification,
163 };
164
165 builder = builder.insert(5, options).map_err(|_| Error::Other)?;
166 }
167
168 if let Some(pin_auth) = request.pin_uv_auth() {
169 builder = builder
170 .insert_bytes(6, pin_auth.param())
171 .map_err(|_| Error::Other)?;
172
173 builder = builder
174 .insert(7, pin_auth.protocol_u8())
175 .map_err(|_| Error::Other)?;
176 }
177
178 let request_bytes = builder.build().map_err(|_| Error::Other)?;
179 let response = transport.send_ctap_command(0x02, &request_bytes, request.timeout_ms)?;
180
181 Ok(response)
182 }
183
184 pub fn authenticator_get_info(transport: &mut Transport) -> Result<Vec<u8>> {
186 let response = transport.send_ctap_command(0x04, &[], 30000)?;
187 Ok(response)
188 }
189
190 pub fn make_credential_buf(
195 transport: &mut Transport,
196 request: MakeCredentialRequest,
197 response: &mut [u8],
198 ) -> Result<usize> {
199 let mut builder = MapBuilder::new();
200
201 builder = builder
202 .insert_bytes(1, request.client_data_hash().as_slice())
203 .map_err(|_| Error::Other)?;
204
205 let mut rp_fields: SmallVec<[(&str, &str); 2]> = SmallVec::new();
206 rp_fields.push(("id", request.rp().id.as_str()));
207 if let Some(name) = &request.rp().name {
208 rp_fields.push(("name", name.as_str()));
209 }
210 builder = builder
211 .insert_text_map(2, &rp_fields)
212 .map_err(|_| Error::Other)?;
213
214 let user_cbor = soft_fido2_ctap::cbor::encode(&request.user()).map_err(|_| Error::Other)?;
215
216 builder = builder
217 .insert(
218 3,
219 &soft_fido2_ctap::cbor::decode::<Value>(&user_cbor).map_err(|_| Error::Other)?,
220 )
221 .map_err(|_| Error::Other)?;
222
223 #[derive(Serialize)]
224 struct PubKeyCredParam {
225 alg: i32,
226 #[serde(rename = "type")]
227 cred_type: &'static str,
228 }
229
230 let alg_param = PubKeyCredParam {
231 alg: -7,
232 cred_type: "public-key",
233 };
234 let alg_params: SmallVec<[PubKeyCredParam; 1]> = SmallVec::from_buf([alg_param]);
235 builder = builder.insert(4, alg_params).map_err(|_| Error::Other)?;
236
237 if request.resident_key.is_some() || request.user_verification.is_some() {
238 #[derive(Serialize)]
239 struct Options {
240 #[serde(skip_serializing_if = "Option::is_none")]
241 rk: Option<bool>,
242 #[serde(skip_serializing_if = "Option::is_none")]
243 uv: Option<bool>,
244 }
245
246 let options = Options {
247 rk: request.resident_key,
248 uv: request.user_verification,
249 };
250
251 builder = builder.insert(7, &options).map_err(|_| Error::Other)?;
252 }
253
254 if let Some(pin_auth) = request.pin_uv_auth() {
255 builder = builder
256 .insert_bytes(8, pin_auth.param())
257 .map_err(|_| Error::Other)?;
258
259 builder = builder
260 .insert(9, pin_auth.protocol_u8())
261 .map_err(|_| Error::Other)?;
262 }
263
264 let request_bytes = builder.build().map_err(|_| Error::Other)?;
265 transport.send_ctap_command_buf(0x01, &request_bytes, response, request.timeout_ms)
266 }
267
268 pub fn get_assertion_buf(
273 transport: &mut Transport,
274 request: GetAssertionRequest,
275 response: &mut [u8],
276 ) -> Result<usize> {
277 let mut builder = MapBuilder::new();
278
279 builder = builder
280 .insert(1, request.rp_id())
281 .map_err(|_| Error::Other)?;
282
283 builder = builder
284 .insert_bytes(2, request.client_data_hash().as_slice())
285 .map_err(|_| Error::Other)?;
286
287 if !request.allow_list().is_empty() {
288 #[derive(Serialize)]
289 struct Credential<'a> {
290 id: &'a [u8],
291 #[serde(rename = "type")]
292 credential_type: &'a str,
293 }
294
295 let allow_list: SmallVec<[Credential; 4]> = request
296 .allow_list()
297 .iter()
298 .map(|cred| Credential {
299 id: cred.id.as_slice(),
300 credential_type: cred.credential_type.as_str(),
301 })
302 .collect();
303
304 builder = builder.insert(3, &allow_list).map_err(|_| Error::Other)?;
305 }
306
307 if request.user_verification.is_some() {
308 #[derive(Serialize)]
309 struct Options {
310 #[serde(skip_serializing_if = "Option::is_none")]
311 uv: Option<bool>,
312 }
313
314 let options = Options {
315 uv: request.user_verification,
316 };
317
318 builder = builder.insert(5, options).map_err(|_| Error::Other)?;
319 }
320
321 if let Some(pin_auth) = request.pin_uv_auth() {
322 builder = builder
323 .insert_bytes(6, pin_auth.param())
324 .map_err(|_| Error::Other)?;
325
326 builder = builder
327 .insert(7, pin_auth.protocol_u8())
328 .map_err(|_| Error::Other)?;
329 }
330
331 let request_bytes = builder.build().map_err(|_| Error::Other)?;
332 transport.send_ctap_command_buf(0x02, &request_bytes, response, request.timeout_ms)
333 }
334
335 pub fn authenticator_get_info_buf(
340 transport: &mut Transport,
341 response: &mut [u8],
342 ) -> Result<usize> {
343 transport.send_ctap_command_buf(0x04, &[], response, 30000)
344 }
345
346 pub fn get_credentials_metadata(
348 transport: &mut Transport,
349 request: crate::request::CredentialManagementRequest,
350 ) -> Result<crate::response::CredentialsMetadata> {
351 credential_mgmt::get_credentials_metadata(transport, request)
352 }
353
354 pub fn enumerate_rps_begin(
356 transport: &mut Transport,
357 request: crate::request::CredentialManagementRequest,
358 ) -> Result<crate::response::RpEnumerationBeginResponse> {
359 credential_mgmt::enumerate_rps_begin(transport, request)
360 }
361
362 pub fn enumerate_rps_get_next(transport: &mut Transport) -> Result<crate::response::RpInfo> {
364 credential_mgmt::enumerate_rps_get_next(transport)
365 }
366
367 pub fn enumerate_rps(
369 transport: &mut Transport,
370 request: crate::request::CredentialManagementRequest,
371 ) -> Result<Vec<crate::response::RpInfo>> {
372 credential_mgmt::enumerate_rps(transport, request)
373 }
374
375 pub fn enumerate_credentials_begin(
377 transport: &mut Transport,
378 request: crate::request::EnumerateCredentialsRequest,
379 ) -> Result<crate::response::CredentialEnumerationBeginResponse> {
380 credential_mgmt::enumerate_credentials_begin(transport, request)
381 }
382
383 pub fn enumerate_credentials_get_next(
385 transport: &mut Transport,
386 ) -> Result<crate::response::CredentialInfo> {
387 credential_mgmt::enumerate_credentials_get_next(transport)
388 }
389
390 pub fn enumerate_credentials(
392 transport: &mut Transport,
393 request: crate::request::EnumerateCredentialsRequest,
394 ) -> Result<Vec<crate::response::CredentialInfo>> {
395 credential_mgmt::enumerate_credentials(transport, request)
396 }
397
398 pub fn delete_credential(
400 transport: &mut Transport,
401 request: crate::request::DeleteCredentialRequest,
402 ) -> Result<()> {
403 credential_mgmt::delete_credential(transport, request)
404 }
405
406 pub fn update_user_information(
408 transport: &mut Transport,
409 request: crate::request::UpdateUserRequest,
410 ) -> Result<()> {
411 credential_mgmt::update_user_information(transport, request)
412 }
413
414 pub fn get_pin_token_for_credential_management(
418 transport: &mut Transport,
419 pin: &str,
420 protocol: crate::pin::PinProtocol,
421 ) -> Result<crate::request::PinUvAuth> {
422 use crate::pin::PinUvAuthEncapsulation;
423 use crate::request::Permission;
424
425 let mut encapsulation = PinUvAuthEncapsulation::new(transport, protocol)?;
426
427 let permissions = Permission::CredentialManagement as u8;
428 let pin_token = encapsulation.get_pin_uv_auth_token_using_pin_with_permissions(
429 transport,
430 pin,
431 permissions,
432 None,
433 )?;
434
435 Ok(crate::request::PinUvAuth::new(pin_token, protocol.into()))
436 }
437
438 pub fn get_uv_token_for_credential_management(
443 transport: &mut Transport,
444 protocol: crate::pin::PinProtocol,
445 ) -> Result<crate::request::PinUvAuth> {
446 use crate::pin::PinUvAuthEncapsulation;
447 use crate::request::Permission;
448
449 let mut encapsulation = PinUvAuthEncapsulation::new(transport, protocol)?;
450
451 let permissions = Permission::CredentialManagement as u8;
452 let uv_token = encapsulation.get_pin_uv_auth_token_using_uv_with_permissions(
453 transport,
454 permissions,
455 None,
456 )?;
457
458 Ok(crate::request::PinUvAuth::new(uv_token, protocol.into()))
459 }
460}
461
462pub fn compute_rp_id_hash(rp_id: &str) -> [u8; 32] {
464 let mut hasher = Sha256::new();
465 hasher.update(rp_id.as_bytes());
466 hasher.finalize().into()
467}
468
469#[cfg(test)]
470mod tests {
471 use super::*;
472
473 use crate::request::ClientDataHash;
474
475 use soft_fido2_ctap::types::{RelyingParty, User};
476
477 #[test]
478 fn test_make_credential_request_encoding() {
479 let hash = ClientDataHash::new([0u8; 32]);
480 let rp = RelyingParty {
481 id: "example.com".to_string(),
482 name: Some("Example Corp".to_string()),
483 };
484 let user = User {
485 id: vec![1, 2, 3, 4],
486 name: Some("alice@example.com".to_string()),
487 display_name: Some("Alice".to_string()),
488 };
489
490 let request = MakeCredentialRequest::new(hash, rp, user);
491
492 assert_eq!(request.rp().id, "example.com");
493 assert_eq!(request.user().id, vec![1, 2, 3, 4]);
494 }
495
496 #[test]
497 fn test_get_assertion_request_encoding() {
498 let hash = ClientDataHash::new([0u8; 32]);
499 let request = GetAssertionRequest::new(hash, "example.com".to_string());
500
501 assert_eq!(request.rp_id(), "example.com");
502 }
503
504 #[test]
505 fn test_compute_rp_id_hash() {
506 let hash = compute_rp_id_hash("example.com");
507 assert_eq!(hash.len(), 32);
508 }
509}