authenticator_ctap2_2021/
ctap2_capi.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use crate::authenticatorservice::{
6    AuthenticatorService, CtapVersion, RegisterArgsCtap2, SignArgsCtap2,
7};
8use crate::ctap2::attestation::AttestationStatement;
9use crate::ctap2::commands::get_assertion::{Assertion, AssertionObject, GetAssertionOptions};
10use crate::ctap2::commands::make_credentials::MakeCredentialsOptions;
11use crate::ctap2::commands::CommandError;
12use crate::ctap2::server::{
13    PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, User,
14};
15use crate::errors::{AuthenticatorError, U2FTokenError};
16use crate::statecallback::StateCallback;
17use crate::{AttestationObject, CollectedClientData, Pin, StatusUpdate};
18use crate::{RegisterResult, SignResult};
19use libc::size_t;
20use rand::{thread_rng, Rng};
21use serde_cbor;
22use std::convert::TryFrom;
23use std::ffi::{CStr, CString};
24use std::os::raw::c_char;
25use std::sync::mpsc::channel;
26use std::thread;
27use std::{ptr, slice};
28
29type Ctap2RegisterResult = Result<(AttestationObject, CString), AuthenticatorError>;
30type Ctap2PubKeyCredDescriptors = Vec<PublicKeyCredentialDescriptor>;
31type Ctap2RegisterCallback = extern "C" fn(u64, *mut Ctap2RegisterResult);
32type Ctap2SignResult = Result<(AssertionObject, CString), AuthenticatorError>;
33type Ctap2SignCallback = extern "C" fn(u64, *mut Ctap2SignResult);
34type Ctap2StatusUpdateCallback = extern "C" fn(*mut StatusUpdate);
35
36const SIGN_RESULT_PUBKEY_CRED_ID: u8 = 1;
37const SIGN_RESULT_AUTH_DATA: u8 = 2;
38const SIGN_RESULT_SIGNATURE: u8 = 3;
39const SIGN_RESULT_USER_ID: u8 = 4;
40const SIGN_RESULT_USER_NAME: u8 = 5;
41
42const REGISTER_RESULT_ATTESTATION_STATEMENT: u8 = 1;
43const REGISTER_RESULT_AUTH_DATA: u8 = 2;
44
45const ATTESTATION_FORMAT_NONE: u8 = 0;
46const ATTESTATION_FORMAT_U2F: u8 = 1;
47const ATTESTATION_FORMAT_PACKED: u8 = 2;
48const ATTESTATION_FORMAT_UNPARSED: u8 = 3; // TODO(MS): Needs to go away
49
50#[repr(C)]
51pub struct AuthenticatorArgsUser {
52    id_ptr: *const u8,
53    id_len: usize,
54    name: *const c_char,
55}
56
57#[repr(C)]
58pub struct AuthenticatorArgsChallenge {
59    ptr: *const u8,
60    len: usize,
61}
62
63#[repr(C)]
64pub struct AuthenticatorArgsPubCred {
65    ptr: *const i32,
66    len: usize,
67}
68
69#[repr(C)]
70pub struct AuthenticatorArgsOptions {
71    resident_key: bool,
72    user_verification: bool,
73    user_presence: bool,
74}
75
76// Generates a new 64-bit transaction id with collision probability 2^-32.
77fn new_tid() -> u64 {
78    thread_rng().gen::<u64>()
79}
80
81unsafe fn from_raw(ptr: *const u8, len: usize) -> Vec<u8> {
82    slice::from_raw_parts(ptr, len).to_vec()
83}
84
85/// # Safety
86///
87/// This method must not be called on a handle twice, and the handle is unusable
88/// after.
89#[no_mangle]
90pub unsafe extern "C" fn rust_ctap2_mgr_free(mgr: *mut AuthenticatorService) {
91    if !mgr.is_null() {
92        Box::from_raw(mgr);
93    }
94}
95
96/// # Safety
97///
98/// The handle returned by this method must be freed by the caller.
99#[no_mangle]
100pub unsafe extern "C" fn rust_ctap2_pkcd_new() -> *mut Ctap2PubKeyCredDescriptors {
101    Box::into_raw(Box::new(vec![]))
102}
103
104/// # Safety
105///
106/// This method must be used on an actual Ctap2PubKeyCredDescriptors CredDescriptorse
107#[no_mangle]
108pub unsafe extern "C" fn rust_ctap2_pkcd_add(
109    pkcd: *mut Ctap2PubKeyCredDescriptors,
110    id_ptr: *const u8,
111    id_len: usize,
112    transports: u8,
113) {
114    (*pkcd).push(PublicKeyCredentialDescriptor {
115        id: from_raw(id_ptr, id_len),
116        transports: crate::AuthenticatorTransports::from_bits_truncate(transports).into(),
117    });
118}
119
120/// # Safety
121///
122/// This method must not be called on a handle twice, and the handle is unusable
123/// after.
124#[no_mangle]
125pub unsafe extern "C" fn rust_ctap2_pkcd_free(khs: *mut Ctap2PubKeyCredDescriptors) {
126    if !khs.is_null() {
127        Box::from_raw(khs);
128    }
129}
130
131/// # Safety
132///
133/// The handle returned by this method must be freed by the caller.
134/// The returned handle can be used with all rust_u2f_mgr_*-functions as well
135/// but uses CTAP2 as the underlying protocol. CTAP1 requests will be repackaged
136/// into CTAP2 (if the device supports it)
137#[no_mangle]
138pub extern "C" fn rust_ctap2_mgr_new() -> *mut AuthenticatorService {
139    if let Ok(mut mgr) = AuthenticatorService::new(CtapVersion::CTAP2) {
140        mgr.add_detected_transports();
141        Box::into_raw(Box::new(mgr))
142    } else {
143        ptr::null_mut()
144    }
145}
146
147fn rewrap_client_data(client_data: CollectedClientData) -> Result<CString, AuthenticatorError> {
148    let client_data_str = serde_json::to_string(&client_data).map_err(|e| CommandError::Json(e))?;
149    let s = CString::new(client_data_str).map_err(|_| {
150        AuthenticatorError::Custom("Failed to transform client_data to C String".to_string())
151    })?;
152    Ok(s)
153}
154
155fn rewrap_register_result(
156    attestation_object: AttestationObject,
157    client_data: CollectedClientData,
158) -> Ctap2RegisterResult {
159    let s = rewrap_client_data(client_data)?;
160    Ok((attestation_object, s))
161}
162
163fn rewrap_sign_result(
164    assertion_object: AssertionObject,
165    client_data: CollectedClientData,
166) -> Ctap2SignResult {
167    let s = rewrap_client_data(client_data)?;
168    Ok((assertion_object, s))
169}
170
171/// # Safety
172///
173/// This method should not be called on AuthenticatorService handles after
174/// they've been freed
175/// All input is copied and it is the callers responsibility to free appropriately.
176/// Note: `KeyHandles` are used as `PublicKeyCredentialDescriptor`s for the exclude_list
177///       to keep the API smaller, as they are essentially the same thing.
178///       `PublicKeyCredentialParameters` in pub_cred_params are represented as i32 with
179///       their COSE value (see: https://www.iana.org/assignments/cose/cose.xhtml#table-algorithms)
180#[no_mangle]
181pub unsafe extern "C" fn rust_ctap2_mgr_register(
182    mgr: *mut AuthenticatorService,
183    timeout: u64,
184    callback: Ctap2RegisterCallback,
185    status_callback: Ctap2StatusUpdateCallback,
186    challenge: AuthenticatorArgsChallenge,
187    relying_party_id: *const c_char,
188    origin_ptr: *const c_char,
189    user: AuthenticatorArgsUser,
190    pub_cred_params: AuthenticatorArgsPubCred,
191    exclude_list: *const Ctap2PubKeyCredDescriptors,
192    options: AuthenticatorArgsOptions,
193    pin_ptr: *const c_char,
194) -> u64 {
195    if mgr.is_null() {
196        return 0;
197    }
198
199    // Check buffers.
200    if challenge.ptr.is_null()
201        || origin_ptr.is_null()
202        || relying_party_id.is_null()
203        || user.id_ptr.is_null()
204        || user.name.is_null()
205        || exclude_list.is_null()
206    {
207        return 0;
208    }
209
210    let pub_cred_params = match slice::from_raw_parts(pub_cred_params.ptr, pub_cred_params.len)
211        .iter()
212        .map(|x| PublicKeyCredentialParameters::try_from(*x))
213        .collect()
214    {
215        Ok(x) => x,
216        Err(_) => {
217            return 0;
218        }
219    };
220    let pin = if pin_ptr.is_null() {
221        None
222    } else {
223        Some(Pin::new(&CStr::from_ptr(pin_ptr).to_string_lossy()))
224    };
225    let user = User {
226        id: from_raw(user.id_ptr, user.id_len),
227        name: Some(CStr::from_ptr(user.name).to_string_lossy().to_string()), // TODO(MS): Use to_str() and error out on failure?
228        display_name: None,
229        icon: None,
230    };
231    let rp = RelyingParty {
232        id: CStr::from_ptr(relying_party_id)
233            .to_string_lossy()
234            .to_string(),
235        name: None,
236        icon: None,
237    };
238    let origin = CStr::from_ptr(origin_ptr).to_string_lossy().to_string();
239    let challenge = from_raw(challenge.ptr, challenge.len);
240    let exclude_list = (*exclude_list).clone();
241
242    let (status_tx, status_rx) = channel::<crate::StatusUpdate>();
243    thread::spawn(move || loop {
244        match status_rx.recv() {
245            Ok(r) => {
246                let rb = Box::new(r);
247                status_callback(Box::into_raw(rb));
248            }
249            Err(e) => {
250                status_callback(ptr::null_mut());
251                error!("Error when receiving status update: {:?}", e);
252                return;
253            }
254        }
255    });
256
257    let tid = new_tid();
258
259    let state_callback = StateCallback::<crate::Result<RegisterResult>>::new(Box::new(move |rv| {
260        let res = match rv {
261            Ok(RegisterResult::CTAP1(..)) => Err(AuthenticatorError::VersionMismatch(
262                "rust_ctap2_mgr_register",
263                2,
264            )),
265            Ok(RegisterResult::CTAP2(attestation_object, client_data)) => {
266                rewrap_register_result(attestation_object, client_data)
267            }
268            Err(e) => Err(e),
269        };
270
271        callback(tid, Box::into_raw(Box::new(res)));
272    }));
273
274    let ctap_args = RegisterArgsCtap2 {
275        challenge,
276        relying_party: rp,
277        origin,
278        user,
279        pub_cred_params,
280        exclude_list,
281        options: MakeCredentialsOptions {
282            resident_key: options.resident_key.then(|| true),
283            user_verification: options.user_verification.then(|| true),
284        },
285        extensions: Default::default(),
286        pin,
287    };
288
289    let res = (*mgr).register(timeout, ctap_args.into(), status_tx, state_callback);
290
291    if res.is_ok() {
292        tid
293    } else {
294        0
295    }
296}
297
298/// # Safety
299///
300/// This method should not be called on AuthenticatorService handles after
301/// they've been freed
302/// Note: `KeyHandles` are used as `PublicKeyCredentialDescriptor`s for the exclude_list
303///       to keep the API smaller, as they are essentially the same thing.
304///       `PublicKeyCredentialParameters` in pub_cred_params are represented as i32 with
305///       their COSE value (see: https://www.iana.org/assignments/cose/cose.xhtml#table-algorithms)
306#[no_mangle]
307pub unsafe extern "C" fn rust_ctap2_mgr_sign(
308    mgr: *mut AuthenticatorService,
309    timeout: u64,
310    callback: Ctap2SignCallback,
311    status_callback: Ctap2StatusUpdateCallback,
312    challenge: AuthenticatorArgsChallenge,
313    relying_party_id: *const c_char,
314    origin_ptr: *const c_char,
315    allow_list: *const Ctap2PubKeyCredDescriptors,
316    options: AuthenticatorArgsOptions,
317    pin_ptr: *const c_char,
318) -> u64 {
319    if mgr.is_null() {
320        return 0;
321    }
322
323    // Check buffers.
324    if challenge.ptr.is_null()
325        || origin_ptr.is_null()
326        || relying_party_id.is_null()
327        || allow_list.is_null()
328    {
329        return 0;
330    }
331
332    let pin = if pin_ptr.is_null() {
333        None
334    } else {
335        Some(Pin::new(&CStr::from_ptr(pin_ptr).to_string_lossy()))
336    };
337    let rpid = CStr::from_ptr(relying_party_id)
338        .to_string_lossy()
339        .to_string();
340    let origin = CStr::from_ptr(origin_ptr).to_string_lossy().to_string();
341    let challenge = from_raw(challenge.ptr, challenge.len);
342    let allow_list: Vec<_> = (*allow_list).clone();
343
344    let (status_tx, status_rx) = channel::<crate::StatusUpdate>();
345    thread::spawn(move || loop {
346        match status_rx.recv() {
347            Ok(r) => {
348                let rb = Box::new(r);
349                status_callback(Box::into_raw(rb));
350            }
351            Err(e) => {
352                status_callback(ptr::null_mut());
353                error!("Error when receiving status update: {:?}", e);
354                return;
355            }
356        }
357    });
358
359    let single_key_handle = if allow_list.len() == 1 {
360        Some(allow_list.first().unwrap().clone())
361    } else {
362        None
363    };
364
365    let tid = new_tid();
366    let state_callback = StateCallback::<crate::Result<SignResult>>::new(Box::new(move |rv| {
367        let res = match rv {
368            Ok(SignResult::CTAP1(..)) => Err(AuthenticatorError::VersionMismatch(
369                "rust_ctap2_mgr_register",
370                2,
371            )),
372            Ok(SignResult::CTAP2(mut assertion_object, client_data)) => {
373                // The token can omit sending back credentials, if the allow_list had only one
374                // entry. Thus we re-add that here now for all found assertions before handing it out.
375                assertion_object.0.iter_mut().for_each(|x| {
376                    x.credentials = x.credentials.clone().or(single_key_handle.clone());
377                });
378                rewrap_sign_result(assertion_object, client_data)
379            }
380            Err(e) => Err(e),
381        };
382
383        callback(tid, Box::into_raw(Box::new(res)));
384    }));
385    let ctap_args = SignArgsCtap2 {
386        challenge,
387        origin,
388        relying_party_id: rpid,
389        allow_list,
390        options: GetAssertionOptions {
391            user_presence: options.user_presence.then(|| true),
392            user_verification: options.user_verification.then(|| true),
393        },
394        extensions: Default::default(),
395        pin,
396    };
397
398    let res = (*mgr).sign(timeout, ctap_args.into(), status_tx, state_callback);
399
400    if res.is_ok() {
401        tid
402    } else {
403        0
404    }
405}
406
407// /// # Safety
408// ///
409// /// This method must be used on an actual U2FResult handle
410#[no_mangle]
411pub unsafe extern "C" fn rust_ctap2_register_result_error(res: *const Ctap2RegisterResult) -> u8 {
412    if res.is_null() {
413        return U2FTokenError::Unknown as u8;
414    }
415
416    match &*res {
417        Ok(..) => 0, // No error, the request succeeded.
418        Err(e) => e.as_u2f_errorcode(),
419    }
420}
421
422#[no_mangle]
423pub unsafe extern "C" fn rust_ctap2_sign_result_error(res: *const Ctap2SignResult) -> u8 {
424    if res.is_null() {
425        return U2FTokenError::Unknown as u8;
426    }
427
428    match &*res {
429        Ok(..) => 0, // No error, the request succeeded.
430        Err(e) => e.as_u2f_errorcode(),
431    }
432}
433
434/// # Safety
435///
436/// This method should not be called on RegisterResult handles after they've been
437/// freed or a double-free will occur
438#[no_mangle]
439pub unsafe extern "C" fn rust_ctap2_register_res_free(res: *mut Ctap2RegisterResult) {
440    if !res.is_null() {
441        let _ = Box::from_raw(res);
442    }
443}
444
445/// # Safety
446///
447/// This method should not be called on SignResult handles after they've been
448/// freed or a double-free will occur
449#[no_mangle]
450pub unsafe extern "C" fn rust_ctap2_sign_res_free(res: *mut Ctap2SignResult) {
451    if !res.is_null() {
452        let _ = Box::from_raw(res);
453    }
454}
455
456/// # Safety
457///
458/// This method should not be called AuthenticatorService handles after they've
459/// been freed
460#[no_mangle]
461pub unsafe extern "C" fn rust_ctap2_mgr_cancel(mgr: *mut AuthenticatorService) {
462    if !mgr.is_null() {
463        // Ignore return value.
464        let _ = (*mgr).cancel();
465    }
466}
467
468unsafe fn client_data_len<T>(
469    res: *const Result<(T, CString), AuthenticatorError>,
470    len: *mut size_t,
471) -> bool {
472    if res.is_null() || len.is_null() {
473        return false;
474    }
475
476    match &*res {
477        Ok((_, client_data)) => {
478            *len = client_data.as_bytes().len();
479            true
480        }
481        Err(_) => false,
482    }
483}
484
485/// This function is used to get the length, prior to calling
486/// rust_ctap2_register_result_client_data_copy()
487#[no_mangle]
488pub unsafe extern "C" fn rust_ctap2_register_result_client_data_len(
489    res: *const Ctap2RegisterResult,
490    len: *mut size_t,
491) -> bool {
492    client_data_len(res, len)
493}
494
495/// This function is used to get the length, prior to calling
496/// rust_ctap2_sign_result_client_data_copy()
497#[no_mangle]
498pub unsafe extern "C" fn rust_ctap2_sign_result_client_data_len(
499    res: *const Ctap2SignResult,
500    len: *mut size_t,
501) -> bool {
502    client_data_len(res, len)
503}
504
505unsafe fn client_data_copy<T>(
506    res: *const Result<(T, CString), AuthenticatorError>,
507    dst: *mut c_char,
508) -> bool {
509    if dst.is_null() || res.is_null() {
510        return false;
511    }
512
513    match &*res {
514        Ok((_, client_data)) => {
515            ptr::copy_nonoverlapping(client_data.as_ptr(), dst, client_data.as_bytes().len());
516            return true;
517        }
518        Err(_) => false,
519    }
520}
521
522/// # Safety
523///
524/// This method does not ensure anything about dst before copying, so
525/// ensure it is long enough (using rust_ctap2_register_result_client_data_len)
526#[no_mangle]
527pub unsafe extern "C" fn rust_ctap2_register_result_client_data_copy(
528    res: *const Ctap2RegisterResult,
529    dst: *mut c_char,
530) -> bool {
531    client_data_copy(res, dst)
532}
533
534/// # Safety
535///
536/// This method does not ensure anything about dst before copying, so
537/// ensure it is long enough (using rust_ctap2_register_result_client_data_len)
538#[no_mangle]
539pub unsafe extern "C" fn rust_ctap2_sign_result_client_data_copy(
540    res: *const Ctap2SignResult,
541    dst: *mut c_char,
542) -> bool {
543    client_data_copy(res, dst)
544}
545
546fn register_result_item_len(attestation: &AttestationObject, item_idx: u8) -> Option<usize> {
547    match item_idx {
548        REGISTER_RESULT_ATTESTATION_STATEMENT => match &attestation.att_statement {
549            AttestationStatement::None => None,
550            AttestationStatement::Packed(x) => serde_cbor::to_vec(&x).ok().map(|x| x.len()),
551            AttestationStatement::FidoU2F(x) => serde_cbor::to_vec(&x).ok().map(|x| x.len()),
552            AttestationStatement::Unparsed(x) => Some(x.len()),
553        },
554        REGISTER_RESULT_AUTH_DATA => attestation.auth_data.to_vec().ok().map(|x| x.len()),
555        _ => None,
556    }
557}
558
559/// # Safety
560///
561/// This function is used to get how long the specific item is.
562#[no_mangle]
563pub unsafe extern "C" fn rust_ctap2_register_result_item_len(
564    res: *const Ctap2RegisterResult,
565    item_idx: u8,
566    len: *mut size_t,
567) -> bool {
568    if res.is_null() || len.is_null() {
569        return false;
570    }
571
572    match &*res {
573        Ok((attestation, _)) => {
574            if let Some(item_len) = register_result_item_len(&attestation, item_idx) {
575                *len = item_len;
576                true
577            } else {
578                false
579            }
580        }
581        Err(_) => false,
582    }
583}
584
585unsafe fn register_result_item_copy(
586    attestation: &AttestationObject,
587    item_idx: u8,
588    dst: *mut u8,
589) -> bool {
590    if dst.is_null() {
591        return false;
592    }
593
594    let tmp_val;
595    let item = match item_idx {
596        REGISTER_RESULT_ATTESTATION_STATEMENT => match &attestation.att_statement {
597            AttestationStatement::None => None,
598            AttestationStatement::Packed(x) => {
599                tmp_val = serde_cbor::to_vec(&x).ok();
600                tmp_val.as_ref().map(|x| x.as_ref())
601            }
602            AttestationStatement::FidoU2F(x) => {
603                tmp_val = serde_cbor::to_vec(&x).ok();
604                tmp_val.as_ref().map(|x| x.as_ref())
605            }
606            AttestationStatement::Unparsed(x) => Some(x.as_slice()),
607        },
608        REGISTER_RESULT_AUTH_DATA => {
609            tmp_val = attestation.auth_data.to_vec().ok();
610            tmp_val.as_ref().map(|x| x.as_ref())
611        }
612        _ => None,
613    };
614
615    if let Some(item) = item {
616        ptr::copy_nonoverlapping(item.as_ptr(), dst, item.len());
617        true
618    } else {
619        false
620    }
621}
622
623/// # Safety
624///
625/// This method does not ensure anything about dst before copying, so
626/// ensure it is long enough (using rust_ctap2_register_result_item_len)
627#[no_mangle]
628pub unsafe extern "C" fn rust_ctap2_register_result_item_copy(
629    res: *const Ctap2RegisterResult,
630    item_idx: u8,
631    dst: *mut u8,
632) -> bool {
633    if res.is_null() || dst.is_null() {
634        return false;
635    }
636
637    match &*res {
638        Ok((attestation, _)) => register_result_item_copy(&attestation, item_idx, dst),
639        Err(_) => false,
640    }
641}
642
643#[no_mangle]
644pub unsafe extern "C" fn rust_ctap2_register_result_attestation_format(
645    res: *const Ctap2RegisterResult,
646    fmt: *mut u8,
647) -> bool {
648    if res.is_null() || fmt.is_null() {
649        return false;
650    }
651
652    match &*res {
653        Ok((attestation_object, _client_data)) => {
654            *fmt = match &attestation_object.att_statement {
655                AttestationStatement::None => ATTESTATION_FORMAT_NONE,
656                AttestationStatement::FidoU2F(..) => ATTESTATION_FORMAT_U2F,
657                AttestationStatement::Packed(..) => ATTESTATION_FORMAT_PACKED,
658                AttestationStatement::Unparsed(..) => ATTESTATION_FORMAT_UNPARSED,
659            };
660            true
661        }
662        Err(_) => false,
663    }
664}
665
666/// This function is used to get how many assertions there are in total
667/// The returned number can be used as index-maximum to access individual
668/// fields
669#[no_mangle]
670pub unsafe extern "C" fn rust_ctap2_sign_result_assertions_len(
671    res: *const Ctap2SignResult,
672    len: *mut size_t,
673) -> bool {
674    if res.is_null() || len.is_null() {
675        return false;
676    }
677
678    match &*res {
679        Ok((assertions, _)) => {
680            *len = assertions.0.len();
681            return true;
682        }
683        Err(_) => false,
684    }
685}
686
687fn sign_result_item_len(assertion: &Assertion, item_idx: u8) -> Option<usize> {
688    match item_idx {
689        SIGN_RESULT_PUBKEY_CRED_ID => assertion.credentials.as_ref().map(|x| x.id.len()),
690        // This is inefficent! Converting twice here. Once for len, once for copy
691        SIGN_RESULT_AUTH_DATA => assertion.auth_data.to_vec().ok().map(|x| x.len()),
692        SIGN_RESULT_SIGNATURE => Some(assertion.signature.len()),
693        SIGN_RESULT_USER_ID => assertion.user.as_ref().map(|u| u.id.len()),
694        SIGN_RESULT_USER_NAME => assertion
695            .user
696            .as_ref()
697            .map(|u| {
698                u.display_name
699                    .as_ref()
700                    .or(u.name.as_ref())
701                    .map(|n| n.as_bytes().len())
702            })
703            .flatten(),
704        _ => None,
705    }
706}
707
708#[no_mangle]
709pub unsafe extern "C" fn rust_ctap2_sign_result_item_contains(
710    res: *const Ctap2SignResult,
711    assertion_idx: usize,
712    item_idx: u8,
713) -> bool {
714    if res.is_null() {
715        return false;
716    }
717
718    match &*res {
719        Ok((assertions, _)) => {
720            if assertion_idx >= assertions.0.len() {
721                return false;
722            }
723            if item_idx == SIGN_RESULT_AUTH_DATA {
724                // Short-cut to avoid serializing auth_data
725                return true;
726            }
727            sign_result_item_len(&assertions.0[assertion_idx], item_idx).is_some()
728        }
729        Err(_) => false,
730    }
731}
732
733/// # Safety
734///
735/// This function is used to get how long the specific item is.
736#[no_mangle]
737pub unsafe extern "C" fn rust_ctap2_sign_result_item_len(
738    res: *const Ctap2SignResult,
739    assertion_idx: usize,
740    item_idx: u8,
741    len: *mut size_t,
742) -> bool {
743    if res.is_null() || len.is_null() {
744        return false;
745    }
746
747    match &*res {
748        Ok((assertions, _)) => {
749            if assertion_idx >= assertions.0.len() {
750                return false;
751            }
752
753            if let Some(item_len) = sign_result_item_len(&assertions.0[assertion_idx], item_idx) {
754                *len = item_len;
755                true
756            } else {
757                false
758            }
759        }
760        Err(_) => false,
761    }
762}
763
764unsafe fn sign_result_item_copy(assertion: &Assertion, item_idx: u8, dst: *mut u8) -> bool {
765    if dst.is_null() {
766        return false;
767    }
768
769    let tmp_val;
770    let item = match item_idx {
771        SIGN_RESULT_PUBKEY_CRED_ID => assertion.credentials.as_ref().map(|x| x.id.as_ref()),
772        // This is inefficent! Converting twice here. Once for len, once for copy
773        SIGN_RESULT_AUTH_DATA => {
774            tmp_val = assertion.auth_data.to_vec().ok();
775            tmp_val.as_ref().map(|x| x.as_ref())
776        }
777        SIGN_RESULT_SIGNATURE => Some(assertion.signature.as_ref()),
778        SIGN_RESULT_USER_ID => assertion.user.as_ref().map(|u| u.id.as_ref()),
779        SIGN_RESULT_USER_NAME => assertion
780            .user
781            .as_ref()
782            .map(|u| {
783                u.display_name
784                    .as_ref()
785                    .or(u.name.as_ref())
786                    .map(|n| n.as_bytes().as_ref())
787            })
788            .flatten(),
789        _ => None,
790    };
791
792    if let Some(item) = item {
793        ptr::copy_nonoverlapping(item.as_ptr(), dst, item.len());
794        true
795    } else {
796        false
797    }
798}
799
800/// # Safety
801///
802/// This method does not ensure anything about dst before copying, so
803/// ensure it is long enough (using rust_ctap2_sign_result_item_len)
804#[no_mangle]
805pub unsafe extern "C" fn rust_ctap2_sign_result_item_copy(
806    res: *const Ctap2SignResult,
807    assertion_idx: usize,
808    item_idx: u8,
809    dst: *mut u8,
810) -> bool {
811    if res.is_null() || dst.is_null() {
812        return false;
813    }
814
815    match &*res {
816        Ok((assertions, _)) => {
817            if assertion_idx >= assertions.0.len() {
818                return false;
819            }
820
821            sign_result_item_copy(&assertions.0[assertion_idx], item_idx, dst)
822        }
823        Err(_) => false,
824    }
825}
826
827#[no_mangle]
828pub unsafe extern "C" fn rust_ctap2_sign_result_contains_username(
829    res: *const Ctap2SignResult,
830    assertion_idx: usize,
831) -> bool {
832    if res.is_null() {
833        return false;
834    }
835
836    match &*res {
837        Ok((assertions, _)) => {
838            if assertion_idx >= assertions.0.len() {
839                return false;
840            }
841            assertions.0[assertion_idx]
842                .user
843                .as_ref()
844                .map(|u| u.display_name.as_ref().or(u.name.as_ref()))
845                .is_some()
846        }
847        Err(_) => false,
848    }
849}
850
851/// # Safety
852///
853/// This function is used to get how long the specific username is.
854#[no_mangle]
855pub unsafe extern "C" fn rust_ctap2_sign_result_username_len(
856    res: *const Ctap2SignResult,
857    assertion_idx: usize,
858    len: *mut size_t,
859) -> bool {
860    if res.is_null() || len.is_null() {
861        return false;
862    }
863
864    match &*res {
865        Ok((assertions, _)) => {
866            if assertion_idx >= assertions.0.len() {
867                return false;
868            }
869
870            if let Some(name_len) = assertions.0[assertion_idx]
871                .user
872                .as_ref()
873                .map(|u| u.display_name.as_ref().or(u.name.as_ref()))
874                .flatten()
875                .map(|x| x.as_bytes().len())
876            {
877                *len = name_len;
878                true
879            } else {
880                false
881            }
882        }
883        Err(_) => false,
884    }
885}
886
887/// # Safety
888///
889/// This method does not ensure anything about dst before copying, so
890/// ensure it is long enough (using rust_ctap2_sign_result_username_len)
891#[no_mangle]
892pub unsafe extern "C" fn rust_ctap2_sign_result_username_copy(
893    res: *const Ctap2SignResult,
894    assertion_idx: usize,
895    dst: *mut c_char,
896) -> bool {
897    if res.is_null() || dst.is_null() {
898        return false;
899    }
900
901    match &*res {
902        Ok((assertions, _)) => {
903            if assertion_idx >= assertions.0.len() {
904                return false;
905            }
906
907            if let Some(name) = assertions.0[assertion_idx]
908                .user
909                .as_ref()
910                .map(|u| u.display_name.as_ref().or(u.name.as_ref()))
911                .flatten()
912                .map(|u| CString::new(u.clone()).ok())
913                .flatten()
914            {
915                ptr::copy_nonoverlapping(name.as_ptr(), dst, name.as_bytes().len());
916                true
917            } else {
918                false
919            }
920        }
921
922        Err(_) => false,
923    }
924}
925
926/// # Safety
927///
928/// This function is used to get how long the JSON-representation of a status update is.
929#[no_mangle]
930pub unsafe extern "C" fn rust_ctap2_status_update_len(
931    res: *const StatusUpdate,
932    len: *mut size_t,
933) -> bool {
934    if res.is_null() || len.is_null() {
935        return false;
936    }
937
938    match serde_json::to_string(&*res) {
939        Ok(s) => {
940            *len = s.len();
941            true
942        }
943        Err(e) => {
944            error!("Failed to parse {:?} into json: {:?}", &*res, e);
945            false
946        }
947    }
948}
949
950/// # Safety
951///
952/// This method does not ensure anything about dst before copying, so
953/// ensure it is long enough (using rust_ctap2_status_update_len)
954#[no_mangle]
955pub unsafe extern "C" fn rust_ctap2_status_update_copy_json(
956    res: *const StatusUpdate,
957    dst: *mut c_char,
958) -> bool {
959    if res.is_null() || dst.is_null() {
960        return false;
961    }
962
963    match serde_json::to_string(&*res) {
964        Ok(s) => {
965            if let Ok(cs) = CString::new(s) {
966                ptr::copy_nonoverlapping(cs.as_ptr(), dst, cs.as_bytes().len());
967                true
968            } else {
969                error!("Failed to convert String to CString");
970                false
971            }
972        }
973        Err(e) => {
974            error!("Failed to parse {:?} into json: {:?}", &*res, e);
975            false
976        }
977    }
978}
979
980/// # Safety
981///
982/// We copy the pin, so it is the callers responsibility to free the argument
983#[no_mangle]
984pub unsafe extern "C" fn rust_ctap2_status_update_send_pin(
985    res: *const StatusUpdate,
986    c_pin: *mut c_char,
987) -> bool {
988    if res.is_null() || c_pin.is_null() {
989        return false;
990    }
991
992    match &*res {
993        StatusUpdate::PinError(_, sender) => {
994            if let Ok(pin) = CStr::from_ptr(c_pin).to_str() {
995                sender
996                    .send(Pin::new(pin))
997                    .map_err(|e| {
998                        error!("Failed to send PIN to device-thread");
999                        e
1000                    })
1001                    .is_ok()
1002            } else {
1003                error!("Failed to convert PIN from c_char to String");
1004                false
1005            }
1006        }
1007        _ => {
1008            error!("Wrong state!");
1009            false
1010        }
1011    }
1012}
1013
1014/// # Safety
1015///
1016/// This function frees the memory of res!
1017#[no_mangle]
1018pub unsafe extern "C" fn rust_ctap2_destroy_status_update_res(res: *mut StatusUpdate) -> bool {
1019    if res.is_null() {
1020        return false;
1021    }
1022    // Dropping it when we go out of scope
1023    let _ = Box::from_raw(res);
1024    true
1025}