Skip to main content

authenticator_ctap2_2021/
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, RegisterArgsCtap1, SignArgsCtap1,
7};
8use crate::errors;
9use crate::statecallback::StateCallback;
10use crate::{RegisterResult, SignResult};
11use libc::size_t;
12use rand::{thread_rng, Rng};
13use std::collections::HashMap;
14use std::sync::mpsc::channel;
15use std::thread;
16use std::{ptr, slice};
17
18type U2FAppIds = Vec<crate::AppId>;
19type U2FKeyHandles = Vec<crate::KeyHandle>;
20type U2FCallback = extern "C" fn(u64, *mut U2FResult);
21
22pub enum U2FResult {
23    Success(HashMap<u8, Vec<u8>>),
24    Error(errors::AuthenticatorError),
25}
26
27const RESBUF_ID_REGISTRATION: u8 = 0;
28const RESBUF_ID_KEYHANDLE: u8 = 1;
29const RESBUF_ID_SIGNATURE: u8 = 2;
30const RESBUF_ID_APPID: u8 = 3;
31const RESBUF_ID_VENDOR_NAME: u8 = 4;
32const RESBUF_ID_DEVICE_NAME: u8 = 5;
33const RESBUF_ID_FIRMWARE_MAJOR: u8 = 6;
34const RESBUF_ID_FIRMWARE_MINOR: u8 = 7;
35const RESBUF_ID_FIRMWARE_BUILD: u8 = 8;
36
37// Generates a new 64-bit transaction id with collision probability 2^-32.
38fn new_tid() -> u64 {
39    thread_rng().gen::<u64>()
40}
41
42unsafe fn from_raw(ptr: *const u8, len: usize) -> Vec<u8> {
43    slice::from_raw_parts(ptr, len).to_vec()
44}
45
46/// # Safety
47///
48/// The handle returned by this method must be freed by the caller.
49#[no_mangle]
50pub extern "C" fn rust_u2f_mgr_new() -> *mut AuthenticatorService {
51    if let Ok(mut mgr) = AuthenticatorService::new(CtapVersion::CTAP1) {
52        mgr.add_detected_transports();
53        Box::into_raw(Box::new(mgr))
54    } else {
55        ptr::null_mut()
56    }
57}
58
59/// # Safety
60///
61/// This method must not be called on a handle twice, and the handle is unusable
62/// after.
63#[no_mangle]
64pub unsafe extern "C" fn rust_u2f_mgr_free(mgr: *mut AuthenticatorService) {
65    if !mgr.is_null() {
66        Box::from_raw(mgr);
67    }
68}
69
70/// # Safety
71///
72/// The handle returned by this method must be freed by the caller.
73#[no_mangle]
74pub unsafe extern "C" fn rust_u2f_app_ids_new() -> *mut U2FAppIds {
75    Box::into_raw(Box::new(vec![]))
76}
77
78/// # Safety
79///
80/// This method must be used on an actual U2FAppIds handle
81#[no_mangle]
82pub unsafe extern "C" fn rust_u2f_app_ids_add(
83    ids: *mut U2FAppIds,
84    id_ptr: *const u8,
85    id_len: usize,
86) {
87    (*ids).push(from_raw(id_ptr, id_len));
88}
89
90/// # Safety
91///
92/// This method must not be called on a handle twice, and the handle is unusable
93/// after.
94#[no_mangle]
95pub unsafe extern "C" fn rust_u2f_app_ids_free(ids: *mut U2FAppIds) {
96    if !ids.is_null() {
97        Box::from_raw(ids);
98    }
99}
100
101/// # Safety
102///
103/// The handle returned by this method must be freed by the caller.
104#[no_mangle]
105pub unsafe extern "C" fn rust_u2f_khs_new() -> *mut U2FKeyHandles {
106    Box::into_raw(Box::new(vec![]))
107}
108
109/// # Safety
110///
111/// This method must be used on an actual U2FKeyHandles handle
112#[no_mangle]
113pub unsafe extern "C" fn rust_u2f_khs_add(
114    khs: *mut U2FKeyHandles,
115    key_handle_ptr: *const u8,
116    key_handle_len: usize,
117    transports: u8,
118) {
119    (*khs).push(crate::KeyHandle {
120        credential: from_raw(key_handle_ptr, key_handle_len),
121        transports: crate::AuthenticatorTransports::from_bits_truncate(transports),
122    });
123}
124
125/// # Safety
126///
127/// This method must not be called on a handle twice, and the handle is unusable
128/// after.
129#[no_mangle]
130pub unsafe extern "C" fn rust_u2f_khs_free(khs: *mut U2FKeyHandles) {
131    if !khs.is_null() {
132        Box::from_raw(khs);
133    }
134}
135
136/// # Safety
137///
138/// This method must be used on an actual U2FResult handle
139#[no_mangle]
140pub unsafe extern "C" fn rust_u2f_result_error(res: *const U2FResult) -> u8 {
141    if res.is_null() {
142        return errors::U2FTokenError::Unknown as u8;
143    }
144
145    if let U2FResult::Error(ref err) = *res {
146        return err.as_u2f_errorcode();
147    }
148
149    0 /* No error, the request succeeded. */
150}
151
152/// # Safety
153///
154/// This method must be used before rust_u2f_resbuf_copy
155#[no_mangle]
156pub unsafe extern "C" fn rust_u2f_resbuf_contains(res: *const U2FResult, bid: u8) -> bool {
157    if res.is_null() {
158        return false;
159    }
160
161    if let U2FResult::Success(ref bufs) = *res {
162        return bufs.contains_key(&bid);
163    }
164
165    false
166}
167
168/// # Safety
169///
170/// This method must be used before rust_u2f_resbuf_copy
171#[no_mangle]
172pub unsafe extern "C" fn rust_u2f_resbuf_length(
173    res: *const U2FResult,
174    bid: u8,
175    len: *mut size_t,
176) -> bool {
177    if res.is_null() {
178        return false;
179    }
180
181    if let U2FResult::Success(ref bufs) = *res {
182        if let Some(buf) = bufs.get(&bid) {
183            *len = buf.len();
184            return true;
185        }
186    }
187
188    false
189}
190
191/// # Safety
192///
193/// This method does not ensure anything about dst before copying, so
194/// ensure it is long enough (using rust_u2f_resbuf_length)
195#[no_mangle]
196pub unsafe extern "C" fn rust_u2f_resbuf_copy(
197    res: *const U2FResult,
198    bid: u8,
199    dst: *mut u8,
200) -> bool {
201    if res.is_null() {
202        return false;
203    }
204
205    if let U2FResult::Success(ref bufs) = *res {
206        if let Some(buf) = bufs.get(&bid) {
207            ptr::copy_nonoverlapping(buf.as_ptr(), dst, buf.len());
208            return true;
209        }
210    }
211
212    false
213}
214
215/// # Safety
216///
217/// This method should not be called on U2FResult handles after they've been
218/// freed or a double-free will occur
219#[no_mangle]
220pub unsafe extern "C" fn rust_u2f_res_free(res: *mut U2FResult) {
221    if !res.is_null() {
222        Box::from_raw(res);
223    }
224}
225
226/// # Safety
227///
228/// This method should not be called on AuthenticatorService handles after
229/// they've been freed
230#[no_mangle]
231pub unsafe extern "C" fn rust_u2f_mgr_register(
232    mgr: *mut AuthenticatorService,
233    flags: u64,
234    timeout: u64,
235    callback: U2FCallback,
236    challenge_ptr: *const u8,
237    challenge_len: usize,
238    application_ptr: *const u8,
239    application_len: usize,
240    khs: *const U2FKeyHandles,
241) -> u64 {
242    if mgr.is_null() {
243        return 0;
244    }
245
246    // Check buffers.
247    if challenge_ptr.is_null() || application_ptr.is_null() {
248        return 0;
249    }
250
251    let flags = crate::RegisterFlags::from_bits_truncate(flags);
252    let challenge = from_raw(challenge_ptr, challenge_len);
253    let application = from_raw(application_ptr, application_len);
254    let key_handles = (*khs).clone();
255
256    let (status_tx, status_rx) = channel::<crate::StatusUpdate>();
257    thread::spawn(move || loop {
258        // Issue https://github.com/mozilla/authenticator-rs/issues/132 will
259        // plumb the status channel through to the actual C API signatures
260        match status_rx.recv() {
261            Ok(_) => {}
262            Err(_recv_error) => return,
263        }
264    });
265
266    let tid = new_tid();
267
268    let state_callback = StateCallback::<crate::Result<RegisterResult>>::new(Box::new(move |rv| {
269        let result = match rv {
270            Ok(RegisterResult::CTAP1(registration, dev_info)) => {
271                let mut bufs = HashMap::new();
272                bufs.insert(RESBUF_ID_REGISTRATION, registration);
273                bufs.insert(RESBUF_ID_VENDOR_NAME, dev_info.vendor_name);
274                bufs.insert(RESBUF_ID_DEVICE_NAME, dev_info.device_name);
275                bufs.insert(RESBUF_ID_FIRMWARE_MAJOR, vec![dev_info.version_major]);
276                bufs.insert(RESBUF_ID_FIRMWARE_MINOR, vec![dev_info.version_minor]);
277                bufs.insert(RESBUF_ID_FIRMWARE_BUILD, vec![dev_info.version_build]);
278                U2FResult::Success(bufs)
279            }
280            Ok(RegisterResult::CTAP2(..)) => U2FResult::Error(
281                errors::AuthenticatorError::VersionMismatch("rust_u2f_mgr_register", 1),
282            ),
283            Err(e) => U2FResult::Error(e),
284        };
285
286        callback(tid, Box::into_raw(Box::new(result)));
287    }));
288    let ctap_args = RegisterArgsCtap1 {
289        flags,
290        challenge,
291        application,
292        key_handles,
293    };
294
295    let res = (*mgr).register(timeout, ctap_args.into(), status_tx, state_callback);
296
297    if res.is_ok() {
298        tid
299    } else {
300        0
301    }
302}
303
304/// # Safety
305///
306/// This method should not be called on AuthenticatorService handles after
307/// they've been freed
308#[no_mangle]
309pub unsafe extern "C" fn rust_u2f_mgr_sign(
310    mgr: *mut AuthenticatorService,
311    flags: u64,
312    timeout: u64,
313    callback: U2FCallback,
314    challenge_ptr: *const u8,
315    challenge_len: usize,
316    app_ids: *const U2FAppIds,
317    khs: *const U2FKeyHandles,
318) -> u64 {
319    if mgr.is_null() || khs.is_null() {
320        return 0;
321    }
322
323    // Check buffers.
324    if challenge_ptr.is_null() {
325        return 0;
326    }
327
328    // Need at least one app_id.
329    if (*app_ids).is_empty() {
330        return 0;
331    }
332
333    let flags = crate::SignFlags::from_bits_truncate(flags);
334    let challenge = from_raw(challenge_ptr, challenge_len);
335    let app_ids = (*app_ids).clone();
336    let key_handles = (*khs).clone();
337
338    let (status_tx, status_rx) = channel::<crate::StatusUpdate>();
339    thread::spawn(move || loop {
340        // Issue https://github.com/mozilla/authenticator-rs/issues/132 will
341        // plumb the status channel through to the actual C API signatures
342        match status_rx.recv() {
343            Ok(_) => {}
344            Err(_recv_error) => return,
345        }
346    });
347
348    let tid = new_tid();
349    let state_callback = StateCallback::<crate::Result<SignResult>>::new(Box::new(move |rv| {
350        let result = match rv {
351            Ok(SignResult::CTAP1(app_id, key_handle, signature, dev_info)) => {
352                let mut bufs = HashMap::new();
353                bufs.insert(RESBUF_ID_KEYHANDLE, key_handle);
354                bufs.insert(RESBUF_ID_SIGNATURE, signature);
355                bufs.insert(RESBUF_ID_APPID, app_id);
356                bufs.insert(RESBUF_ID_VENDOR_NAME, dev_info.vendor_name);
357                bufs.insert(RESBUF_ID_DEVICE_NAME, dev_info.device_name);
358                bufs.insert(RESBUF_ID_FIRMWARE_MAJOR, vec![dev_info.version_major]);
359                bufs.insert(RESBUF_ID_FIRMWARE_MINOR, vec![dev_info.version_minor]);
360                bufs.insert(RESBUF_ID_FIRMWARE_BUILD, vec![dev_info.version_build]);
361                U2FResult::Success(bufs)
362            }
363            Ok(SignResult::CTAP2(..)) => U2FResult::Error(
364                errors::AuthenticatorError::VersionMismatch("rust_u2f_mgr_sign", 1),
365            ),
366            Err(e) => U2FResult::Error(e),
367        };
368
369        callback(tid, Box::into_raw(Box::new(result)));
370    }));
371
372    let res = (*mgr).sign(
373        timeout,
374        SignArgsCtap1 {
375            flags,
376            challenge,
377            app_ids,
378            key_handles,
379        }
380        .into(),
381        status_tx,
382        state_callback,
383    );
384
385    if res.is_ok() {
386        tid
387    } else {
388        0
389    }
390}
391
392/// # Safety
393///
394/// This method should not be called AuthenticatorService handles after they've
395/// been freed
396#[no_mangle]
397pub unsafe extern "C" fn rust_u2f_mgr_cancel(mgr: *mut AuthenticatorService) {
398    if !mgr.is_null() {
399        // Ignore return value.
400        let _ = (*mgr).cancel();
401    }
402}