diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/rust/authenticator/src/ctap2_capi.rs | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/authenticator/src/ctap2_capi.rs')
-rw-r--r-- | third_party/rust/authenticator/src/ctap2_capi.rs | 945 |
1 files changed, 945 insertions, 0 deletions
diff --git a/third_party/rust/authenticator/src/ctap2_capi.rs b/third_party/rust/authenticator/src/ctap2_capi.rs new file mode 100644 index 0000000000..f1dc4bb8f9 --- /dev/null +++ b/third_party/rust/authenticator/src/ctap2_capi.rs @@ -0,0 +1,945 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::authenticatorservice::{ + AuthenticatorService, CtapVersion, RegisterArgsCtap2, SignArgsCtap2, +}; +use crate::ctap2::attestation::AttestationStatement; +use crate::ctap2::commands::get_assertion::{Assertion, AssertionObject, GetAssertionOptions}; +use crate::ctap2::commands::make_credentials::MakeCredentialsOptions; +use crate::ctap2::server::{ + PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, User, +}; +use crate::errors::{AuthenticatorError, U2FTokenError}; +use crate::statecallback::StateCallback; +use crate::{AttestationObject, CollectedClientDataWrapper, Pin, StatusUpdate}; +use crate::{RegisterResult, SignResult}; +use libc::size_t; +use rand::{thread_rng, Rng}; +use serde_cbor; +use std::convert::TryFrom; +use std::ffi::{CStr, CString}; +use std::os::raw::c_char; +use std::sync::mpsc::channel; +use std::thread; +use std::{ptr, slice}; + +type Ctap2RegisterResult = Result<(AttestationObject, CString), AuthenticatorError>; +type Ctap2PubKeyCredDescriptors = Vec<PublicKeyCredentialDescriptor>; +type Ctap2RegisterCallback = extern "C" fn(u64, *mut Ctap2RegisterResult); +type Ctap2SignResult = Result<(AssertionObject, CString), AuthenticatorError>; +type Ctap2SignCallback = extern "C" fn(u64, *mut Ctap2SignResult); +type Ctap2StatusUpdateCallback = extern "C" fn(*mut StatusUpdate); + +const SIGN_RESULT_PUBKEY_CRED_ID: u8 = 1; +const SIGN_RESULT_AUTH_DATA: u8 = 2; +const SIGN_RESULT_SIGNATURE: u8 = 3; +const SIGN_RESULT_USER_ID: u8 = 4; +const SIGN_RESULT_USER_NAME: u8 = 5; + +#[repr(C)] +pub struct AuthenticatorArgsUser { + id_ptr: *const u8, + id_len: usize, + name: *const c_char, +} + +#[repr(C)] +pub struct AuthenticatorArgsChallenge { + ptr: *const u8, + len: usize, +} + +#[repr(C)] +pub struct AuthenticatorArgsPubCred { + ptr: *const i32, + len: usize, +} + +#[repr(C)] +pub struct AuthenticatorArgsOptions { + resident_key: bool, + user_verification: bool, + user_presence: bool, + force_none_attestation: bool, +} + +// Generates a new 64-bit transaction id with collision probability 2^-32. +fn new_tid() -> u64 { + thread_rng().gen::<u64>() +} + +unsafe fn from_raw(ptr: *const u8, len: usize) -> Vec<u8> { + slice::from_raw_parts(ptr, len).to_vec() +} + +/// # Safety +/// +/// This method must not be called on a handle twice, and the handle is unusable +/// after. +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_mgr_free(mgr: *mut AuthenticatorService) { + if !mgr.is_null() { + drop(Box::from_raw(mgr)); + } +} + +/// # Safety +/// +/// The handle returned by this method must be freed by the caller. +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_pkcd_new() -> *mut Ctap2PubKeyCredDescriptors { + Box::into_raw(Box::new(vec![])) +} + +/// # Safety +/// +/// This method must be used on an actual Ctap2PubKeyCredDescriptors CredDescriptorse +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_pkcd_add( + pkcd: *mut Ctap2PubKeyCredDescriptors, + id_ptr: *const u8, + id_len: usize, + transports: u8, +) { + (*pkcd).push(PublicKeyCredentialDescriptor { + id: from_raw(id_ptr, id_len), + transports: crate::AuthenticatorTransports::from_bits_truncate(transports).into(), + }); +} + +/// # Safety +/// +/// This method must not be called on a handle twice, and the handle is unusable +/// after. +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_pkcd_free(khs: *mut Ctap2PubKeyCredDescriptors) { + if !khs.is_null() { + drop(Box::from_raw(khs)); + } +} + +/// # Safety +/// +/// The handle returned by this method must be freed by the caller. +/// The returned handle can be used with all rust_u2f_mgr_*-functions as well +/// but uses CTAP2 as the underlying protocol. CTAP1 requests will be repackaged +/// into CTAP2 (if the device supports it) +#[no_mangle] +pub extern "C" fn rust_ctap2_mgr_new() -> *mut AuthenticatorService { + if let Ok(mut mgr) = AuthenticatorService::new(CtapVersion::CTAP2) { + mgr.add_detected_transports(); + Box::into_raw(Box::new(mgr)) + } else { + ptr::null_mut() + } +} + +fn rewrap_client_data( + client_data: CollectedClientDataWrapper, +) -> Result<CString, AuthenticatorError> { + let s = CString::new(client_data.serialized_data.clone()).map_err(|_| { + AuthenticatorError::Custom("Failed to transform client_data to C String".to_string()) + })?; + Ok(s) +} + +fn rewrap_register_result( + attestation_object: AttestationObject, + client_data: CollectedClientDataWrapper, +) -> Ctap2RegisterResult { + let s = rewrap_client_data(client_data)?; + Ok((attestation_object, s)) +} + +fn rewrap_sign_result( + assertion_object: AssertionObject, + client_data: CollectedClientDataWrapper, +) -> Ctap2SignResult { + let s = rewrap_client_data(client_data)?; + Ok((assertion_object, s)) +} + +/// # Safety +/// +/// This method should not be called on AuthenticatorService handles after +/// they've been freed +/// All input is copied and it is the callers responsibility to free appropriately. +/// Note: `KeyHandles` are used as `PublicKeyCredentialDescriptor`s for the exclude_list +/// to keep the API smaller, as they are essentially the same thing. +/// `PublicKeyCredentialParameters` in pub_cred_params are represented as i32 with +/// their COSE value (see: https://www.iana.org/assignments/cose/cose.xhtml#table-algorithms) +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_mgr_register( + mgr: *mut AuthenticatorService, + timeout: u64, + callback: Ctap2RegisterCallback, + status_callback: Ctap2StatusUpdateCallback, + challenge: AuthenticatorArgsChallenge, + relying_party_id: *const c_char, + origin_ptr: *const c_char, + user: AuthenticatorArgsUser, + pub_cred_params: AuthenticatorArgsPubCred, + exclude_list: *const Ctap2PubKeyCredDescriptors, + options: AuthenticatorArgsOptions, + pin_ptr: *const c_char, +) -> u64 { + if mgr.is_null() { + return 0; + } + + // Check buffers. + if challenge.ptr.is_null() + || origin_ptr.is_null() + || relying_party_id.is_null() + || user.id_ptr.is_null() + || user.name.is_null() + || exclude_list.is_null() + { + return 0; + } + + let pub_cred_params = match slice::from_raw_parts(pub_cred_params.ptr, pub_cred_params.len) + .iter() + .map(|x| PublicKeyCredentialParameters::try_from(*x)) + .collect() + { + Ok(x) => x, + Err(_) => { + return 0; + } + }; + let pin = if pin_ptr.is_null() { + None + } else { + Some(Pin::new(&CStr::from_ptr(pin_ptr).to_string_lossy())) + }; + let user = User { + id: from_raw(user.id_ptr, user.id_len), + name: Some(CStr::from_ptr(user.name).to_string_lossy().to_string()), // TODO(MS): Use to_str() and error out on failure? + display_name: None, + icon: None, + }; + let rp = RelyingParty { + id: CStr::from_ptr(relying_party_id) + .to_string_lossy() + .to_string(), + name: None, + icon: None, + }; + let origin = CStr::from_ptr(origin_ptr).to_string_lossy().to_string(); + let challenge = from_raw(challenge.ptr, challenge.len); + let exclude_list = (*exclude_list).clone(); + let force_none_attestation = options.force_none_attestation; + + let (status_tx, status_rx) = channel::<crate::StatusUpdate>(); + thread::spawn(move || loop { + match status_rx.recv() { + Ok(r) => { + let rb = Box::new(r); + status_callback(Box::into_raw(rb)); + } + Err(e) => { + status_callback(ptr::null_mut()); + error!("Error when receiving status update: {:?}", e); + return; + } + } + }); + + let tid = new_tid(); + + let state_callback = StateCallback::<crate::Result<RegisterResult>>::new(Box::new(move |rv| { + let res = match rv { + Ok(RegisterResult::CTAP1(..)) => Err(AuthenticatorError::VersionMismatch( + "rust_ctap2_mgr_register", + 2, + )), + Ok(RegisterResult::CTAP2(mut attestation_object, client_data)) => { + if force_none_attestation { + attestation_object.att_statement = AttestationStatement::None; + } + rewrap_register_result(attestation_object, client_data) + } + Err(e) => Err(e), + }; + + callback(tid, Box::into_raw(Box::new(res))); + })); + + let ctap_args = RegisterArgsCtap2 { + challenge, + relying_party: rp, + origin, + user, + pub_cred_params, + exclude_list, + options: MakeCredentialsOptions { + resident_key: options.resident_key.then(|| true), + user_verification: options.user_verification.then(|| true), + }, + extensions: Default::default(), + pin, + }; + + let res = (*mgr).register(timeout, ctap_args.into(), status_tx, state_callback); + + if res.is_ok() { + tid + } else { + 0 + } +} + +/// # Safety +/// +/// This method should not be called on AuthenticatorService handles after +/// they've been freed +/// Note: `KeyHandles` are used as `PublicKeyCredentialDescriptor`s for the exclude_list +/// to keep the API smaller, as they are essentially the same thing. +/// `PublicKeyCredentialParameters` in pub_cred_params are represented as i32 with +/// their COSE value (see: https://www.iana.org/assignments/cose/cose.xhtml#table-algorithms) +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_mgr_sign( + mgr: *mut AuthenticatorService, + timeout: u64, + callback: Ctap2SignCallback, + status_callback: Ctap2StatusUpdateCallback, + challenge: AuthenticatorArgsChallenge, + relying_party_id: *const c_char, + origin_ptr: *const c_char, + allow_list: *const Ctap2PubKeyCredDescriptors, + options: AuthenticatorArgsOptions, + pin_ptr: *const c_char, +) -> u64 { + if mgr.is_null() { + return 0; + } + + // Check buffers. + if challenge.ptr.is_null() + || origin_ptr.is_null() + || relying_party_id.is_null() + || allow_list.is_null() + { + return 0; + } + + let pin = if pin_ptr.is_null() { + None + } else { + Some(Pin::new(&CStr::from_ptr(pin_ptr).to_string_lossy())) + }; + let rpid = CStr::from_ptr(relying_party_id) + .to_string_lossy() + .to_string(); + let origin = CStr::from_ptr(origin_ptr).to_string_lossy().to_string(); + let challenge = from_raw(challenge.ptr, challenge.len); + let allow_list: Vec<_> = (*allow_list).clone(); + + let (status_tx, status_rx) = channel::<crate::StatusUpdate>(); + thread::spawn(move || loop { + match status_rx.recv() { + Ok(r) => { + let rb = Box::new(r); + status_callback(Box::into_raw(rb)); + } + Err(e) => { + status_callback(ptr::null_mut()); + error!("Error when receiving status update: {:?}", e); + return; + } + } + }); + + let single_key_handle = if allow_list.len() == 1 { + Some(allow_list.first().unwrap().clone()) + } else { + None + }; + + let tid = new_tid(); + let state_callback = StateCallback::<crate::Result<SignResult>>::new(Box::new(move |rv| { + let res = match rv { + Ok(SignResult::CTAP1(..)) => Err(AuthenticatorError::VersionMismatch( + "rust_ctap2_mgr_register", + 2, + )), + Ok(SignResult::CTAP2(mut assertion_object, client_data)) => { + // The token can omit sending back credentials, if the allow_list had only one + // entry. Thus we re-add that here now for all found assertions before handing it out. + assertion_object.0.iter_mut().for_each(|x| { + x.credentials = x.credentials.clone().or(single_key_handle.clone()); + }); + rewrap_sign_result(assertion_object, client_data) + } + Err(e) => Err(e), + }; + + callback(tid, Box::into_raw(Box::new(res))); + })); + let ctap_args = SignArgsCtap2 { + challenge, + origin, + relying_party_id: rpid, + allow_list, + options: GetAssertionOptions { + user_presence: options.user_presence.then(|| true), + user_verification: options.user_verification.then(|| true), + }, + extensions: Default::default(), + pin, + }; + + let res = (*mgr).sign(timeout, ctap_args.into(), status_tx, state_callback); + + if res.is_ok() { + tid + } else { + 0 + } +} + +// /// # Safety +// /// +// /// This method must be used on an actual U2FResult handle +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_register_result_error(res: *const Ctap2RegisterResult) -> u8 { + if res.is_null() { + return U2FTokenError::Unknown as u8; + } + + match &*res { + Ok(..) => 0, // No error, the request succeeded. + Err(e) => e.as_u2f_errorcode(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_sign_result_error(res: *const Ctap2SignResult) -> u8 { + if res.is_null() { + return U2FTokenError::Unknown as u8; + } + + match &*res { + Ok(..) => 0, // No error, the request succeeded. + Err(e) => e.as_u2f_errorcode(), + } +} + +/// # Safety +/// +/// This method should not be called on RegisterResult handles after they've been +/// freed or a double-free will occur +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_register_res_free(res: *mut Ctap2RegisterResult) { + if !res.is_null() { + drop(Box::from_raw(res)); + } +} + +/// # Safety +/// +/// This method should not be called on SignResult handles after they've been +/// freed or a double-free will occur +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_sign_res_free(res: *mut Ctap2SignResult) { + if !res.is_null() { + drop(Box::from_raw(res)); + } +} + +/// # Safety +/// +/// This method should not be called AuthenticatorService handles after they've +/// been freed +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_mgr_cancel(mgr: *mut AuthenticatorService) { + if !mgr.is_null() { + // Ignore return value. + let _ = (*mgr).cancel(); + } +} + +unsafe fn client_data_len<T>( + res: *const Result<(T, CString), AuthenticatorError>, + len: *mut size_t, +) -> bool { + if res.is_null() || len.is_null() { + return false; + } + + match &*res { + Ok((_, client_data)) => { + *len = client_data.as_bytes().len(); + true + } + Err(_) => false, + } +} + +/// This function is used to get the length, prior to calling +/// rust_ctap2_register_result_client_data_copy() +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_register_result_client_data_len( + res: *const Ctap2RegisterResult, + len: *mut size_t, +) -> bool { + client_data_len(res, len) +} + +/// This function is used to get the length, prior to calling +/// rust_ctap2_sign_result_client_data_copy() +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_sign_result_client_data_len( + res: *const Ctap2SignResult, + len: *mut size_t, +) -> bool { + client_data_len(res, len) +} + +unsafe fn client_data_copy<T>( + res: *const Result<(T, CString), AuthenticatorError>, + dst: *mut c_char, +) -> bool { + if dst.is_null() || res.is_null() { + return false; + } + + match &*res { + Ok((_, client_data)) => { + ptr::copy_nonoverlapping(client_data.as_ptr(), dst, client_data.as_bytes().len()); + return true; + } + Err(_) => false, + } +} + +/// # Safety +/// +/// This method does not ensure anything about dst before copying, so +/// ensure it is long enough (using rust_ctap2_register_result_client_data_len) +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_register_result_client_data_copy( + res: *const Ctap2RegisterResult, + dst: *mut c_char, +) -> bool { + client_data_copy(res, dst) +} + +/// # Safety +/// +/// This method does not ensure anything about dst before copying, so +/// ensure it is long enough (using rust_ctap2_register_result_client_data_len) +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_sign_result_client_data_copy( + res: *const Ctap2SignResult, + dst: *mut c_char, +) -> bool { + client_data_copy(res, dst) +} + +/// # Safety +/// +/// This function is used to get how long the specific item is. +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_register_result_attestation_len( + res: *const Ctap2RegisterResult, + len: *mut size_t, +) -> bool { + if res.is_null() || len.is_null() { + return false; + } + + if let Ok((attestation, _)) = &*res { + if let Some(item_len) = serde_cbor::to_vec(&attestation).ok().map(|x| x.len()) { + *len = item_len; + return true; + } + } + false +} + +/// # Safety +/// +/// This method does not ensure anything about dst before copying, so +/// ensure it is long enough (using rust_ctap2_register_result_item_len) +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_register_result_attestation_copy( + res: *const Ctap2RegisterResult, + dst: *mut u8, +) -> bool { + if res.is_null() || dst.is_null() { + return false; + } + + if let Ok((attestation, _)) = &*res { + if let Ok(item) = serde_cbor::to_vec(&attestation) { + ptr::copy_nonoverlapping(item.as_ptr(), dst, item.len()); + return true; + } + } + false +} + +/// This function is used to get how many assertions there are in total +/// The returned number can be used as index-maximum to access individual +/// fields +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_sign_result_assertions_len( + res: *const Ctap2SignResult, + len: *mut size_t, +) -> bool { + if res.is_null() || len.is_null() { + return false; + } + + match &*res { + Ok((assertions, _)) => { + *len = assertions.0.len(); + return true; + } + Err(_) => false, + } +} + +fn sign_result_item_len(assertion: &Assertion, item_idx: u8) -> Option<usize> { + match item_idx { + SIGN_RESULT_PUBKEY_CRED_ID => assertion.credentials.as_ref().map(|x| x.id.len()), + // This is inefficent! Converting twice here. Once for len, once for copy + SIGN_RESULT_AUTH_DATA => assertion.auth_data.to_vec().ok().map(|x| x.len()), + SIGN_RESULT_SIGNATURE => Some(assertion.signature.len()), + SIGN_RESULT_USER_ID => assertion.user.as_ref().map(|u| u.id.len()), + SIGN_RESULT_USER_NAME => assertion + .user + .as_ref() + .map(|u| { + u.display_name + .as_ref() + .or(u.name.as_ref()) + .map(|n| n.as_bytes().len()) + }) + .flatten(), + _ => None, + } +} + +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_sign_result_item_contains( + res: *const Ctap2SignResult, + assertion_idx: usize, + item_idx: u8, +) -> bool { + if res.is_null() { + return false; + } + + match &*res { + Ok((assertions, _)) => { + if assertion_idx >= assertions.0.len() { + return false; + } + if item_idx == SIGN_RESULT_AUTH_DATA { + // Short-cut to avoid serializing auth_data + return true; + } + sign_result_item_len(&assertions.0[assertion_idx], item_idx).is_some() + } + Err(_) => false, + } +} + +/// # Safety +/// +/// This function is used to get how long the specific item is. +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_sign_result_item_len( + res: *const Ctap2SignResult, + assertion_idx: usize, + item_idx: u8, + len: *mut size_t, +) -> bool { + if res.is_null() || len.is_null() { + return false; + } + + match &*res { + Ok((assertions, _)) => { + if assertion_idx >= assertions.0.len() { + return false; + } + + if let Some(item_len) = sign_result_item_len(&assertions.0[assertion_idx], item_idx) { + *len = item_len; + true + } else { + false + } + } + Err(_) => false, + } +} + +unsafe fn sign_result_item_copy(assertion: &Assertion, item_idx: u8, dst: *mut u8) -> bool { + if dst.is_null() { + return false; + } + + let tmp_val; + let item = match item_idx { + SIGN_RESULT_PUBKEY_CRED_ID => assertion.credentials.as_ref().map(|x| x.id.as_ref()), + // This is inefficent! Converting twice here. Once for len, once for copy + SIGN_RESULT_AUTH_DATA => { + tmp_val = assertion.auth_data.to_vec().ok(); + tmp_val.as_ref().map(|x| x.as_ref()) + } + SIGN_RESULT_SIGNATURE => Some(assertion.signature.as_ref()), + SIGN_RESULT_USER_ID => assertion.user.as_ref().map(|u| u.id.as_ref()), + SIGN_RESULT_USER_NAME => assertion + .user + .as_ref() + .map(|u| { + u.display_name + .as_ref() + .or(u.name.as_ref()) + .map(|n| n.as_bytes().as_ref()) + }) + .flatten(), + _ => None, + }; + + if let Some(item) = item { + ptr::copy_nonoverlapping(item.as_ptr(), dst, item.len()); + true + } else { + false + } +} + +/// # Safety +/// +/// This method does not ensure anything about dst before copying, so +/// ensure it is long enough (using rust_ctap2_sign_result_item_len) +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_sign_result_item_copy( + res: *const Ctap2SignResult, + assertion_idx: usize, + item_idx: u8, + dst: *mut u8, +) -> bool { + if res.is_null() || dst.is_null() { + return false; + } + + match &*res { + Ok((assertions, _)) => { + if assertion_idx >= assertions.0.len() { + return false; + } + + sign_result_item_copy(&assertions.0[assertion_idx], item_idx, dst) + } + Err(_) => false, + } +} + +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_sign_result_contains_username( + res: *const Ctap2SignResult, + assertion_idx: usize, +) -> bool { + if res.is_null() { + return false; + } + + match &*res { + Ok((assertions, _)) => { + if assertion_idx >= assertions.0.len() { + return false; + } + assertions.0[assertion_idx] + .user + .as_ref() + .map(|u| u.display_name.as_ref().or(u.name.as_ref())) + .is_some() + } + Err(_) => false, + } +} + +/// # Safety +/// +/// This function is used to get how long the specific username is. +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_sign_result_username_len( + res: *const Ctap2SignResult, + assertion_idx: usize, + len: *mut size_t, +) -> bool { + if res.is_null() || len.is_null() { + return false; + } + + match &*res { + Ok((assertions, _)) => { + if assertion_idx >= assertions.0.len() { + return false; + } + + if let Some(name_len) = assertions.0[assertion_idx] + .user + .as_ref() + .map(|u| u.display_name.as_ref().or(u.name.as_ref())) + .flatten() + .map(|x| x.as_bytes().len()) + { + *len = name_len; + true + } else { + false + } + } + Err(_) => false, + } +} + +/// # Safety +/// +/// This method does not ensure anything about dst before copying, so +/// ensure it is long enough (using rust_ctap2_sign_result_username_len) +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_sign_result_username_copy( + res: *const Ctap2SignResult, + assertion_idx: usize, + dst: *mut c_char, +) -> bool { + if res.is_null() || dst.is_null() { + return false; + } + + match &*res { + Ok((assertions, _)) => { + if assertion_idx >= assertions.0.len() { + return false; + } + + if let Some(name) = assertions.0[assertion_idx] + .user + .as_ref() + .map(|u| u.display_name.as_ref().or(u.name.as_ref())) + .flatten() + .map(|u| CString::new(u.clone()).ok()) + .flatten() + { + ptr::copy_nonoverlapping(name.as_ptr(), dst, name.as_bytes().len()); + true + } else { + false + } + } + + Err(_) => false, + } +} + +/// # Safety +/// +/// This function is used to get how long the JSON-representation of a status update is. +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_status_update_len( + res: *const StatusUpdate, + len: *mut size_t, +) -> bool { + if res.is_null() || len.is_null() { + return false; + } + + match serde_json::to_string(&*res) { + Ok(s) => { + *len = s.len(); + true + } + Err(e) => { + error!("Failed to parse {:?} into json: {:?}", &*res, e); + false + } + } +} + +/// # Safety +/// +/// This method does not ensure anything about dst before copying, so +/// ensure it is long enough (using rust_ctap2_status_update_len) +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_status_update_copy_json( + res: *const StatusUpdate, + dst: *mut c_char, +) -> bool { + if res.is_null() || dst.is_null() { + return false; + } + + match serde_json::to_string(&*res) { + Ok(s) => { + if let Ok(cs) = CString::new(s) { + ptr::copy_nonoverlapping(cs.as_ptr(), dst, cs.as_bytes().len()); + true + } else { + error!("Failed to convert String to CString"); + false + } + } + Err(e) => { + error!("Failed to parse {:?} into json: {:?}", &*res, e); + false + } + } +} + +/// # Safety +/// +/// We copy the pin, so it is the callers responsibility to free the argument +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_status_update_send_pin( + res: *const StatusUpdate, + c_pin: *mut c_char, +) -> bool { + if res.is_null() || c_pin.is_null() { + return false; + } + + match &*res { + StatusUpdate::PinError(_, sender) => { + if let Ok(pin) = CStr::from_ptr(c_pin).to_str() { + sender + .send(Pin::new(pin)) + .map_err(|e| { + error!("Failed to send PIN to device-thread"); + e + }) + .is_ok() + } else { + error!("Failed to convert PIN from c_char to String"); + false + } + } + _ => { + error!("Wrong state!"); + false + } + } +} + +/// # Safety +/// +/// This function frees the memory of res! +#[no_mangle] +pub unsafe extern "C" fn rust_ctap2_destroy_status_update_res(res: *mut StatusUpdate) -> bool { + if res.is_null() { + return false; + } + // Dropping it when we go out of scope + drop(Box::from_raw(res)); + true +} |