Bug 1444547 - Propagate proper error codes from u2f-hid-rs to WebAuthn r=jcj a=jcristau
authorTim Taubert <ttaubert@mozilla.com>
Wed, 14 Mar 2018 14:09:02 +0100
changeset 780119 6e38c6caaba99339451655ac362182e1e1e5fa99
parent 780118 49d67e0e77a91e6b522985828ca9166fa8e2817f
child 780120 35cabd15daab63e60c54cccc25e10ba5f28cf77f
push id105964
push userbmo:ntim.bugs@gmail.com
push dateWed, 11 Apr 2018 07:37:32 +0000
reviewersjcj, jcristau
bugs1444547
milestone60.0
Bug 1444547 - Propagate proper error codes from u2f-hid-rs to WebAuthn r=jcj a=jcristau Reviewers: jcj Reviewed By: jcj Bug #: 1444547 Differential Revision: https://phabricator.services.mozilla.com/D717
dom/webauthn/U2FHIDTokenManager.cpp
dom/webauthn/U2FHIDTokenManager.h
dom/webauthn/u2f-hid-rs/src/capi.rs
dom/webauthn/u2f-hid-rs/src/lib.rs
dom/webauthn/u2f-hid-rs/src/linux/device.rs
dom/webauthn/u2f-hid-rs/src/linux/transaction.rs
dom/webauthn/u2f-hid-rs/src/macos/transaction.rs
dom/webauthn/u2f-hid-rs/src/manager.rs
dom/webauthn/u2f-hid-rs/src/statemachine.rs
dom/webauthn/u2f-hid-rs/src/stub/transaction.rs
dom/webauthn/u2f-hid-rs/src/u2fhid-capi.h
dom/webauthn/u2f-hid-rs/src/util.rs
dom/webauthn/u2f-hid-rs/src/windows/transaction.rs
--- a/dom/webauthn/U2FHIDTokenManager.cpp
+++ b/dom/webauthn/U2FHIDTokenManager.cpp
@@ -13,39 +13,41 @@ namespace dom {
 
 static StaticMutex gInstanceMutex;
 static U2FHIDTokenManager* gInstance;
 static nsIThread* gPBackgroundThread;
 
 static void
 u2f_register_callback(uint64_t aTransactionId, rust_u2f_result* aResult)
 {
+  UniquePtr<U2FResult> rv = MakeUnique<U2FResult>(aTransactionId, aResult);
+
   StaticMutexAutoLock lock(gInstanceMutex);
   if (!gInstance || NS_WARN_IF(!gPBackgroundThread)) {
     return;
   }
 
-  UniquePtr<U2FResult> rv = MakeUnique<U2FResult>(aTransactionId, aResult);
   nsCOMPtr<nsIRunnable> r(NewRunnableMethod<UniquePtr<U2FResult>&&>(
       "U2FHIDTokenManager::HandleRegisterResult", gInstance,
       &U2FHIDTokenManager::HandleRegisterResult, Move(rv)));
 
   MOZ_ALWAYS_SUCCEEDS(gPBackgroundThread->Dispatch(r.forget(),
                                                    NS_DISPATCH_NORMAL));
 }
 
 static void
 u2f_sign_callback(uint64_t aTransactionId, rust_u2f_result* aResult)
 {
+  UniquePtr<U2FResult> rv = MakeUnique<U2FResult>(aTransactionId, aResult);
+
   StaticMutexAutoLock lock(gInstanceMutex);
   if (!gInstance || NS_WARN_IF(!gPBackgroundThread)) {
     return;
   }
 
-  UniquePtr<U2FResult> rv = MakeUnique<U2FResult>(aTransactionId, aResult);
   nsCOMPtr<nsIRunnable> r(NewRunnableMethod<UniquePtr<U2FResult>&&>(
       "U2FHIDTokenManager::HandleSignResult", gInstance,
       &U2FHIDTokenManager::HandleSignResult, Move(rv)));
 
   MOZ_ALWAYS_SUCCEEDS(gPBackgroundThread->Dispatch(r.forget(),
                                                    NS_DISPATCH_NORMAL));
 }
 
@@ -213,16 +215,21 @@ U2FHIDTokenManager::HandleRegisterResult
   mozilla::ipc::AssertIsOnBackgroundThread();
 
   if (aResult->GetTransactionId() != mTransactionId) {
     return;
   }
 
   MOZ_ASSERT(!mRegisterPromise.IsEmpty());
 
+  if (aResult->IsError()) {
+    mRegisterPromise.Reject(aResult->GetError(), __func__);
+    return;
+  }
+
   nsTArray<uint8_t> registration;
   if (!aResult->CopyRegistration(registration)) {
     mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
     return;
   }
 
   // Will be set by the U2FTokenManager.
   bool directAttestationPermitted = false;
@@ -236,16 +243,21 @@ U2FHIDTokenManager::HandleSignResult(Uni
   mozilla::ipc::AssertIsOnBackgroundThread();
 
   if (aResult->GetTransactionId() != mTransactionId) {
     return;
   }
 
   MOZ_ASSERT(!mSignPromise.IsEmpty());
 
+  if (aResult->IsError()) {
+    mSignPromise.Reject(aResult->GetError(), __func__);
+    return;
+  }
+
   nsTArray<uint8_t> appId;
   if (!aResult->CopyAppId(appId)) {
     mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
     return;
   }
 
   nsTArray<uint8_t> keyHandle;
   if (!aResult->CopyKeyHandle(keyHandle)) {
--- a/dom/webauthn/U2FHIDTokenManager.h
+++ b/dom/webauthn/U2FHIDTokenManager.h
@@ -59,22 +59,42 @@ private:
   rust_u2f_key_handles* mKeyHandles;
 };
 
 class U2FResult {
 public:
   explicit U2FResult(uint64_t aTransactionId, rust_u2f_result* aResult)
     : mTransactionId(aTransactionId)
     , mResult(aResult)
-  { }
+  {
+    MOZ_ASSERT(mResult);
+  }
 
   ~U2FResult() { rust_u2f_res_free(mResult); }
 
   uint64_t GetTransactionId() { return mTransactionId; }
 
+  bool IsError() { return NS_FAILED(GetError()); }
+
+  nsresult GetError() {
+    switch (rust_u2f_result_error(mResult)) {
+      case U2F_ERROR_UKNOWN:
+      case U2F_ERROR_CONSTRAINT:
+        return NS_ERROR_DOM_UNKNOWN_ERR;
+      case U2F_ERROR_NOT_SUPPORTED:
+        return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+      case U2F_ERROR_INVALID_STATE:
+        return NS_ERROR_DOM_INVALID_STATE_ERR;
+      case U2F_ERROR_NOT_ALLOWED:
+        return NS_ERROR_DOM_NOT_ALLOWED_ERR;
+      default:
+        return NS_OK;
+    }
+  }
+
   bool CopyRegistration(nsTArray<uint8_t>& aBuffer)
   {
     return CopyBuffer(U2F_RESBUF_ID_REGISTRATION, aBuffer);
   }
 
   bool CopyKeyHandle(nsTArray<uint8_t>& aBuffer)
   {
     return CopyBuffer(U2F_RESBUF_ID_KEYHANDLE, aBuffer);
@@ -87,20 +107,16 @@ public:
 
   bool CopyAppId(nsTArray<uint8_t>& aBuffer)
   {
     return CopyBuffer(U2F_RESBUF_ID_APPID, aBuffer);
   }
 
 private:
   bool CopyBuffer(uint8_t aResBufID, nsTArray<uint8_t>& aBuffer) {
-    if (!mResult) {
-      return false;
-    }
-
     size_t len;
     if (!rust_u2f_resbuf_length(mResult, aResBufID, &len)) {
       return false;
     }
 
     if (!aBuffer.SetLength(len, fallible)) {
       return false;
     }
--- a/dom/webauthn/u2f-hid-rs/src/capi.rs
+++ b/dom/webauthn/u2f-hid-rs/src/capi.rs
@@ -6,19 +6,23 @@ use libc::size_t;
 use rand::{thread_rng, Rng};
 use std::collections::HashMap;
 use std::{ptr, slice};
 
 use U2FManager;
 
 type U2FAppIds = Vec<::AppId>;
 type U2FKeyHandles = Vec<::KeyHandle>;
-type U2FResult = HashMap<u8, Vec<u8>>;
 type U2FCallback = extern "C" fn(u64, *mut U2FResult);
 
+pub enum U2FResult {
+    Success(HashMap<u8, Vec<u8>>),
+    Error(::Error),
+}
+
 const RESBUF_ID_REGISTRATION: u8 = 0;
 const RESBUF_ID_KEYHANDLE: u8 = 1;
 const RESBUF_ID_SIGNATURE: u8 = 2;
 const RESBUF_ID_APPID: u8 = 3;
 
 // Generates a new 64-bit transaction id with collision probability 2^-32.
 fn new_tid() -> u64 {
     thread_rng().gen::<u64>()
@@ -86,46 +90,63 @@ pub unsafe extern "C" fn rust_u2f_khs_ad
 #[no_mangle]
 pub unsafe extern "C" fn rust_u2f_khs_free(khs: *mut U2FKeyHandles) {
     if !khs.is_null() {
         Box::from_raw(khs);
     }
 }
 
 #[no_mangle]
+pub unsafe extern "C" fn rust_u2f_result_error(res: *const U2FResult) -> u8 {
+    if res.is_null() {
+        return ::Error::Unknown as u8;
+    }
+
+    if let U2FResult::Error(ref err) = *res {
+        return *err as u8;
+    }
+
+    return 0; /* No error, the request succeeded. */
+}
+
+#[no_mangle]
 pub unsafe extern "C" fn rust_u2f_resbuf_length(
     res: *const U2FResult,
     bid: u8,
     len: *mut size_t,
 ) -> bool {
     if res.is_null() {
         return false;
     }
 
-    if let Some(buf) = (*res).get(&bid) {
-        *len = buf.len();
-        return true;
+    if let U2FResult::Success(ref bufs) = *res {
+        if let Some(buf) = bufs.get(&bid) {
+            *len = buf.len();
+            return true;
+        }
     }
 
     false
 }
 
 #[no_mangle]
 pub unsafe extern "C" fn rust_u2f_resbuf_copy(
     res: *const U2FResult,
     bid: u8,
     dst: *mut u8,
 ) -> bool {
     if res.is_null() {
         return false;
     }
 
-    if let Some(buf) = (*res).get(&bid) {
-        ptr::copy_nonoverlapping(buf.as_ptr(), dst, buf.len());
-        return true;
+    if let U2FResult::Success(ref bufs) = *res {
+        if let Some(buf) = bufs.get(&bid) {
+            ptr::copy_nonoverlapping(buf.as_ptr(), dst, buf.len());
+            return true;
+        }
     }
 
     false
 }
 
 #[no_mangle]
 pub unsafe extern "C" fn rust_u2f_res_free(res: *mut U2FResult) {
     if !res.is_null() {
@@ -162,23 +183,26 @@ pub unsafe extern "C" fn rust_u2f_mgr_re
     let tid = new_tid();
     let res = (*mgr).register(
         flags,
         timeout,
         challenge,
         application,
         key_handles,
         move |rv| {
-            if let Ok(registration) = rv {
-                let mut result = U2FResult::new();
-                result.insert(RESBUF_ID_REGISTRATION, registration);
-                callback(tid, Box::into_raw(Box::new(result)));
-            } else {
-                callback(tid, ptr::null_mut());
+            let result = match rv {
+                Ok(registration) => {
+                    let mut bufs = HashMap::new();
+                    bufs.insert(RESBUF_ID_REGISTRATION, registration);
+                    U2FResult::Success(bufs)
+                }
+                Err(e) => U2FResult::Error(e),
             };
+
+            callback(tid, Box::into_raw(Box::new(result)));
         },
     );
 
     if res.is_ok() {
         tid
     } else {
         0
     }
@@ -211,25 +235,28 @@ pub unsafe extern "C" fn rust_u2f_mgr_si
 
     let flags = ::SignFlags::from_bits_truncate(flags);
     let challenge = from_raw(challenge_ptr, challenge_len);
     let app_ids = (*app_ids).clone();
     let key_handles = (*khs).clone();
 
     let tid = new_tid();
     let res = (*mgr).sign(flags, timeout, challenge, app_ids, key_handles, move |rv| {
-        if let Ok((app_id, key_handle, signature)) = rv {
-            let mut result = U2FResult::new();
-            result.insert(RESBUF_ID_KEYHANDLE, key_handle);
-            result.insert(RESBUF_ID_SIGNATURE, signature);
-            result.insert(RESBUF_ID_APPID, app_id);
-            callback(tid, Box::into_raw(Box::new(result)));
-        } else {
-            callback(tid, ptr::null_mut());
+        let result = match rv {
+            Ok((app_id, key_handle, signature)) => {
+                let mut bufs = HashMap::new();
+                bufs.insert(RESBUF_ID_KEYHANDLE, key_handle);
+                bufs.insert(RESBUF_ID_SIGNATURE, signature);
+                bufs.insert(RESBUF_ID_APPID, app_id);
+                U2FResult::Success(bufs)
+            }
+            Err(e) => U2FResult::Error(e),
         };
+
+        callback(tid, Box::into_raw(Box::new(result)));
     });
 
     if res.is_ok() {
         tid
     } else {
         0
     }
 }
--- a/dom/webauthn/u2f-hid-rs/src/lib.rs
+++ b/dom/webauthn/u2f-hid-rs/src/lib.rs
@@ -74,14 +74,23 @@ pub struct KeyHandle {
     pub credential: Vec<u8>,
     pub transports: AuthenticatorTransports,
 }
 
 pub type AppId = Vec<u8>;
 pub type RegisterResult = Vec<u8>;
 pub type SignResult = (AppId, Vec<u8>, Vec<u8>);
 
+#[derive(Debug, Clone, Copy)]
+pub enum Error {
+    Unknown = 1,
+    NotSupported = 2,
+    InvalidState = 3,
+    ConstraintError = 4,
+    NotAllowed = 5,
+}
+
 #[cfg(fuzzing)]
 pub use u2fprotocol::*;
 #[cfg(fuzzing)]
 pub use u2ftypes::*;
 #[cfg(fuzzing)]
 pub use consts::*;
--- a/dom/webauthn/u2f-hid-rs/src/linux/device.rs
+++ b/dom/webauthn/u2f-hid-rs/src/linux/device.rs
@@ -6,29 +6,29 @@ extern crate libc;
 
 use std::ffi::{CString, OsString};
 use std::io;
 use std::io::{Read, Write};
 use std::os::unix::prelude::*;
 
 use consts::CID_BROADCAST;
 use platform::hidraw;
-use util::{from_unix_result, to_io_err};
+use util::from_unix_result;
 use u2ftypes::U2FDevice;
 
 #[derive(Debug)]
 pub struct Device {
     path: OsString,
     fd: libc::c_int,
     cid: [u8; 4],
 }
 
 impl Device {
     pub fn new(path: OsString) -> io::Result<Self> {
-        let cstr = CString::new(path.as_bytes()).map_err(to_io_err)?;
+        let cstr = CString::new(path.as_bytes())?;
         let fd = unsafe { libc::open(cstr.as_ptr(), libc::O_RDWR) };
         let fd = from_unix_result(fd)?;
         Ok(Self {
             path,
             fd,
             cid: CID_BROADCAST,
         })
     }
--- a/dom/webauthn/u2f-hid-rs/src/linux/transaction.rs
+++ b/dom/webauthn/u2f-hid-rs/src/linux/transaction.rs
@@ -1,42 +1,45 @@
 /* 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 platform::monitor::Monitor;
 use runloop::RunLoop;
 use std::ffi::OsString;
-use std::io;
-use util::{io_err, OnceCallback};
+use util::OnceCallback;
 
 pub struct Transaction {
     // Handle to the thread loop.
     thread: Option<RunLoop>,
 }
 
 impl Transaction {
-    pub fn new<F, T>(timeout: u64, callback: OnceCallback<T>, new_device_cb: F) -> io::Result<Self>
+    pub fn new<F, T>(
+        timeout: u64,
+        callback: OnceCallback<T>,
+        new_device_cb: F,
+    ) -> Result<Self, ::Error>
     where
         F: Fn(OsString, &Fn() -> bool) + Sync + Send + 'static,
         T: 'static,
     {
         let thread = RunLoop::new_with_timeout(
             move |alive| {
                 // Create a new device monitor.
                 let mut monitor = Monitor::new(new_device_cb);
 
                 // Start polling for new devices.
-                try_or!(monitor.run(alive), |e| callback.call(Err(e)));
+                try_or!(monitor.run(alive), |_| callback.call(Err(::Error::Unknown)));
 
                 // Send an error, if the callback wasn't called already.
-                callback.call(Err(io_err("aborted or timed out")));
+                callback.call(Err(::Error::NotAllowed));
             },
             timeout,
-        )?;
+        ).map_err(|_| ::Error::Unknown)?;
 
         Ok(Self {
             thread: Some(thread),
         })
     }
 
     pub fn cancel(&mut self) {
         // This must never be None.
--- a/dom/webauthn/u2f-hid-rs/src/macos/transaction.rs
+++ b/dom/webauthn/u2f-hid-rs/src/macos/transaction.rs
@@ -3,65 +3,70 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 extern crate libc;
 
 use core_foundation_sys::runloop::*;
 use libc::c_void;
 use platform::iokit::{CFRunLoopEntryObserver, IOHIDDeviceRef, SendableRunLoop};
 use platform::monitor::Monitor;
-use std::io;
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::thread;
-use util::{io_err, to_io_err, OnceCallback};
+use util::OnceCallback;
 
 // A transaction will run the given closure in a new thread, thereby using a
 // separate per-thread state machine for each HID. It will either complete or
 // fail through user action, timeout, or be cancelled when overridden by a new
 // transaction.
 pub struct Transaction {
     runloop: Option<SendableRunLoop>,
     thread: Option<thread::JoinHandle<()>>,
 }
 
 impl Transaction {
-    pub fn new<F, T>(timeout: u64, callback: OnceCallback<T>, new_device_cb: F) -> io::Result<Self>
+    pub fn new<F, T>(
+        timeout: u64,
+        callback: OnceCallback<T>,
+        new_device_cb: F,
+    ) -> Result<Self, ::Error>
     where
         F: Fn((IOHIDDeviceRef, Receiver<Vec<u8>>), &Fn() -> bool) + Sync + Send + 'static,
         T: 'static,
     {
         let (tx, rx) = channel();
         let timeout = (timeout as f64) / 1000.0;
 
         let builder = thread::Builder::new();
-        let thread = builder.spawn(move || {
-            // Add a runloop observer that will be notified when we enter the
-            // runloop and tx.send() the current runloop to the owning thread.
-            // We need to ensure the runloop was entered before unblocking
-            // Transaction::new(), so we can always properly cancel.
-            let context = &tx as *const _ as *mut c_void;
-            let obs = CFRunLoopEntryObserver::new(Transaction::observe, context);
-            obs.add_to_current_runloop();
+        let thread = builder
+            .spawn(move || {
+                // Add a runloop observer that will be notified when we enter the
+                // runloop and tx.send() the current runloop to the owning thread.
+                // We need to ensure the runloop was entered before unblocking
+                // Transaction::new(), so we can always properly cancel.
+                let context = &tx as *const _ as *mut c_void;
+                let obs = CFRunLoopEntryObserver::new(Transaction::observe, context);
+                obs.add_to_current_runloop();
 
-            // Create a new HID device monitor and start polling.
-            let mut monitor = Monitor::new(new_device_cb);
-            try_or!(monitor.start(), |e| callback.call(Err(e)));
+                // Create a new HID device monitor and start polling.
+                let mut monitor = Monitor::new(new_device_cb);
+                try_or!(monitor.start(), |_| callback.call(Err(::Error::Unknown)));
 
-            // This will block until completion, abortion, or timeout.
-            unsafe { CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, 0) };
+                // This will block until completion, abortion, or timeout.
+                unsafe { CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, 0) };
 
-            // Close the monitor and its devices.
-            monitor.stop();
+                // Close the monitor and its devices.
+                monitor.stop();
 
-            // Send an error, if the callback wasn't called already.
-            callback.call(Err(io_err("aborted or timed out")));
-        })?;
+                // Send an error, if the callback wasn't called already.
+                callback.call(Err(::Error::NotAllowed));
+            })
+            .map_err(|_| ::Error::Unknown)?;
 
         // Block until we enter the CFRunLoop.
-        let runloop = rx.recv().map_err(to_io_err)?;
+        let runloop = rx.recv().map_err(|_| ::Error::Unknown)?;
 
         Ok(Self {
             runloop: Some(runloop),
             thread: Some(thread),
         })
     }
 
     extern "C" fn observe(_: CFRunLoopObserverRef, _: CFRunLoopActivity, context: *mut c_void) {
--- a/dom/webauthn/u2f-hid-rs/src/manager.rs
+++ b/dom/webauthn/u2f-hid-rs/src/manager.rs
@@ -4,17 +4,17 @@
 
 use std::io;
 use std::sync::mpsc::{channel, RecvTimeoutError, Sender};
 use std::time::Duration;
 
 use consts::PARAMETER_SIZE;
 use statemachine::StateMachine;
 use runloop::RunLoop;
-use util::{to_io_err, OnceCallback};
+use util::OnceCallback;
 
 enum QueueAction {
     Register {
         flags: ::RegisterFlags,
         timeout: u64,
         challenge: Vec<u8>,
         application: ::AppId,
         key_handles: Vec<::KeyHandle>,
@@ -100,108 +100,92 @@ impl U2FManager {
     pub fn register<F>(
         &self,
         flags: ::RegisterFlags,
         timeout: u64,
         challenge: Vec<u8>,
         application: ::AppId,
         key_handles: Vec<::KeyHandle>,
         callback: F,
-    ) -> io::Result<()>
+    ) -> Result<(), ::Error>
     where
-        F: FnOnce(io::Result<::RegisterResult>),
+        F: FnOnce(Result<::RegisterResult, ::Error>),
         F: Send + 'static,
     {
         if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
-            return Err(io::Error::new(
-                io::ErrorKind::InvalidInput,
-                "Invalid parameter sizes",
-            ));
+            return Err(::Error::Unknown);
         }
 
         for key_handle in &key_handles {
             if key_handle.credential.len() > 256 {
-                return Err(io::Error::new(
-                    io::ErrorKind::InvalidInput,
-                    "Key handle too large",
-                ));
+                return Err(::Error::Unknown);
             }
         }
 
         let callback = OnceCallback::new(callback);
         let action = QueueAction::Register {
             flags,
             timeout,
             challenge,
             application,
             key_handles,
             callback,
         };
-        self.tx.send(action).map_err(to_io_err)
+        self.tx.send(action).map_err(|_| ::Error::Unknown)
     }
 
     pub fn sign<F>(
         &self,
         flags: ::SignFlags,
         timeout: u64,
         challenge: Vec<u8>,
         app_ids: Vec<::AppId>,
         key_handles: Vec<::KeyHandle>,
         callback: F,
-    ) -> io::Result<()>
+    ) -> Result<(), ::Error>
     where
-        F: FnOnce(io::Result<::SignResult>),
+        F: FnOnce(Result<::SignResult, ::Error>),
         F: Send + 'static,
     {
         if challenge.len() != PARAMETER_SIZE {
-            return Err(io::Error::new(
-                io::ErrorKind::InvalidInput,
-                "Invalid parameter sizes",
-            ));
+            return Err(::Error::Unknown);
         }
 
         if app_ids.len() < 1 {
-            return Err(io::Error::new(
-                io::ErrorKind::InvalidInput,
-                "No app IDs given",
-            ));
+            return Err(::Error::Unknown);
         }
 
         for app_id in &app_ids {
             if app_id.len() != PARAMETER_SIZE {
-                return Err(io::Error::new(
-                    io::ErrorKind::InvalidInput,
-                    "Invalid app_id size",
-                ));
+                return Err(::Error::Unknown);
             }
         }
 
         for key_handle in &key_handles {
             if key_handle.credential.len() > 256 {
-                return Err(io::Error::new(
-                    io::ErrorKind::InvalidInput,
-                    "Key handle too large",
-                ));
+                return Err(::Error::Unknown);
             }
         }
 
         let callback = OnceCallback::new(callback);
         let action = QueueAction::Sign {
             flags,
             timeout,
             challenge,
             app_ids,
             key_handles,
             callback,
         };
-        self.tx.send(action).map_err(to_io_err)
+        self.tx.send(action).map_err(|_| ::Error::Unknown)
     }
 
-    pub fn cancel(&self) -> io::Result<()> {
-        self.tx.send(QueueAction::Cancel).map_err(to_io_err)
+    pub fn cancel(&self) -> Result<(), ::Error> {
+        self.tx
+            .send(QueueAction::Cancel)
+            .map_err(|_| ::Error::Unknown)
     }
 }
 
 impl Drop for U2FManager {
     fn drop(&mut self) {
         self.queue.cancel();
     }
 }
--- a/dom/webauthn/u2f-hid-rs/src/statemachine.rs
+++ b/dom/webauthn/u2f-hid-rs/src/statemachine.rs
@@ -2,17 +2,17 @@
  * 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 consts::PARAMETER_SIZE;
 use platform::device::Device;
 use platform::transaction::Transaction;
 use std::thread;
 use std::time::Duration;
-use util::{io_err, OnceCallback};
+use util::OnceCallback;
 use u2fprotocol::{u2f_init_device, u2f_is_keyhandle_valid, u2f_register, u2f_sign};
 
 fn is_valid_transport(transports: ::AuthenticatorTransports) -> bool {
     transports.is_empty() || transports.contains(::AuthenticatorTransports::USB)
 }
 
 fn find_valid_key_handles<'a, F>(
     app_ids: &'a Vec<::AppId>,
@@ -94,34 +94,32 @@ impl StateMachine {
                     && u2f_is_keyhandle_valid(dev, &challenge, &application, &key_handle.credential)
                         .unwrap_or(false) /* no match on failure */
             });
 
             while alive() {
                 if excluded {
                     let blank = vec![0u8; PARAMETER_SIZE];
                     if let Ok(_) = u2f_register(dev, &blank, &blank) {
-                        callback.call(Err(io_err("duplicate registration")));
+                        callback.call(Err(::Error::InvalidState));
                         break;
                     }
                 } else {
                     if let Ok(bytes) = u2f_register(dev, &challenge, &application) {
                         callback.call(Ok(bytes));
                         break;
                     }
                 }
 
                 // Sleep a bit before trying again.
                 thread::sleep(Duration::from_millis(100));
             }
         });
 
-        self.transaction = Some(try_or!(transaction, |_| cbc.call(Err(io_err(
-            "couldn't create transaction"
-        )))));
+        self.transaction = Some(try_or!(transaction, |e| cbc.call(Err(e))));
     }
 
     pub fn sign(
         &mut self,
         flags: ::SignFlags,
         timeout: u64,
         challenge: Vec<u8>,
         app_ids: Vec<::AppId>,
@@ -178,17 +176,17 @@ impl StateMachine {
             }
 
             while alive() {
                 // If the device matches none of the given key handles
                 // then just make it blink with bogus data.
                 if valid_handles.is_empty() {
                     let blank = vec![0u8; PARAMETER_SIZE];
                     if let Ok(_) = u2f_register(dev, &blank, &blank) {
-                        callback.call(Err(io_err("invalid key")));
+                        callback.call(Err(::Error::InvalidState));
                         break;
                     }
                 } else {
                     // Otherwise, try to sign.
                     for key_handle in &valid_handles {
                         if let Ok(bytes) = u2f_sign(dev, &challenge, app_id, &key_handle.credential)
                         {
                             callback.call(Ok((
@@ -201,19 +199,17 @@ impl StateMachine {
                     }
                 }
 
                 // Sleep a bit before trying again.
                 thread::sleep(Duration::from_millis(100));
             }
         });
 
-        self.transaction = Some(try_or!(transaction, |_| cbc.call(Err(io_err(
-            "couldn't create transaction"
-        )))));
+        self.transaction = Some(try_or!(transaction, |e| cbc.call(Err(e))));
     }
 
     // This blocks.
     pub fn cancel(&mut self) {
         if let Some(mut transaction) = self.transaction.take() {
             transaction.cancel();
         }
     }
--- a/dom/webauthn/u2f-hid-rs/src/stub/transaction.rs
+++ b/dom/webauthn/u2f-hid-rs/src/stub/transaction.rs
@@ -1,22 +1,25 @@
 /* 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 std::io;
-use util::{io_err, OnceCallback};
+use util::OnceCallback;
 
 pub struct Transaction {}
 
 impl Transaction {
-    pub fn new<F, T>(timeout: u64, callback: OnceCallback<T>, new_device_cb: F) -> io::Result<Self>
+    pub fn new<F, T>(
+        timeout: u64,
+        callback: OnceCallback<T>,
+        new_device_cb: F,
+    ) -> Result<Self, ::Error>
     where
         F: Fn(String, &Fn() -> bool),
     {
-        callback.call(Err(io_err("not implemented")));
-        Err(io_err("not implemented"))
+        callback.call(Err(::Error::NotSupported));
+        Err(::Error::NotSupported)
     }
 
     pub fn cancel(&mut self) {
         /* No-op. */
     }
 }
--- a/dom/webauthn/u2f-hid-rs/src/u2fhid-capi.h
+++ b/dom/webauthn/u2f-hid-rs/src/u2fhid-capi.h
@@ -19,16 +19,22 @@ const uint8_t U2F_RESBUF_ID_APPID = 3;
 const uint64_t U2F_FLAG_REQUIRE_RESIDENT_KEY = 1;
 const uint64_t U2F_FLAG_REQUIRE_USER_VERIFICATION = 2;
 const uint64_t U2F_FLAG_REQUIRE_PLATFORM_ATTACHMENT = 4;
 
 const uint8_t U2F_AUTHENTICATOR_TRANSPORT_USB = 1;
 const uint8_t U2F_AUTHENTICATOR_TRANSPORT_NFC = 2;
 const uint8_t U2F_AUTHENTICATOR_TRANSPORT_BLE = 4;
 
+const uint8_t U2F_ERROR_UKNOWN = 1;
+const uint8_t U2F_ERROR_NOT_SUPPORTED = 2;
+const uint8_t U2F_ERROR_INVALID_STATE = 3;
+const uint8_t U2F_ERROR_CONSTRAINT = 4;
+const uint8_t U2F_ERROR_NOT_ALLOWED = 5;
+
 // NOTE: Preconditions
 // * All rust_u2f_mgr* pointers must refer to pointers which are returned
 //   by rust_u2f_mgr_new, and must be freed with rust_u2f_mgr_free.
 // * All rust_u2f_khs* pointers must refer to pointers which are returned
 //   by rust_u2f_khs_new, and must be freed with rust_u2f_khs_free.
 // * All rust_u2f_res* pointers must refer to pointers passed to the
 //   register() and sign() callbacks. They can be null on failure.
 
@@ -91,16 +97,19 @@ void rust_u2f_khs_add(rust_u2f_key_handl
                       const uint8_t* key_handle,
                       size_t key_handle_len,
                       uint8_t transports);
 /* unsafe */ void rust_u2f_khs_free(rust_u2f_key_handles* khs);
 
 
 /// U2FResult functions.
 
+// Returns 0 for success, or the U2F_ERROR error code >= 1.
+uint8_t rust_u2f_result_error(const rust_u2f_result *res);
+
 // Call this before `[..]_copy()` to allocate enough space.
 bool rust_u2f_resbuf_length(const rust_u2f_result *res, uint8_t bid, size_t* len);
 bool rust_u2f_resbuf_copy(const rust_u2f_result *res, uint8_t bid, uint8_t* dst);
 /* unsafe */ void rust_u2f_res_free(rust_u2f_result* res);
 
 }
 
 #endif // __U2FHID_CAPI
--- a/dom/webauthn/u2f-hid-rs/src/util.rs
+++ b/dom/webauthn/u2f-hid-rs/src/util.rs
@@ -1,15 +1,14 @@
 /* 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/. */
 
 extern crate libc;
 
-use std::error::Error;
 use std::io;
 use std::sync::{Arc, Mutex};
 
 use boxfnonce::SendBoxFnOnce;
 
 macro_rules! try_or {
     ($val:expr, $or:expr) => {
         match $val {
@@ -44,37 +43,33 @@ pub fn from_unix_result<T: Signed>(rv: T
         Ok(rv)
     }
 }
 
 pub fn io_err(msg: &str) -> io::Error {
     io::Error::new(io::ErrorKind::Other, msg)
 }
 
-pub fn to_io_err<T: Error>(err: T) -> io::Error {
-    io_err(err.description())
-}
-
 pub struct OnceCallback<T> {
-    callback: Arc<Mutex<Option<SendBoxFnOnce<(io::Result<T>,)>>>>,
+    callback: Arc<Mutex<Option<SendBoxFnOnce<(Result<T, ::Error>,)>>>>,
 }
 
 impl<T> OnceCallback<T> {
     pub fn new<F>(cb: F) -> Self
     where
-        F: FnOnce(io::Result<T>),
+        F: FnOnce(Result<T, ::Error>),
         F: Send + 'static,
     {
         let cb = Some(SendBoxFnOnce::from(cb));
         Self {
             callback: Arc::new(Mutex::new(cb)),
         }
     }
 
-    pub fn call(&self, rv: io::Result<T>) {
+    pub fn call(&self, rv: Result<T, ::Error>) {
         if let Ok(mut cb) = self.callback.lock() {
             if let Some(cb) = cb.take() {
                 cb.call(rv);
             }
         }
     }
 }
 
--- a/dom/webauthn/u2f-hid-rs/src/windows/transaction.rs
+++ b/dom/webauthn/u2f-hid-rs/src/windows/transaction.rs
@@ -1,41 +1,44 @@
 /* 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 platform::monitor::Monitor;
 use runloop::RunLoop;
-use std::io;
-use util::{io_err, OnceCallback};
+use util::OnceCallback;
 
 pub struct Transaction {
     // Handle to the thread loop.
     thread: Option<RunLoop>,
 }
 
 impl Transaction {
-    pub fn new<F, T>(timeout: u64, callback: OnceCallback<T>, new_device_cb: F) -> io::Result<Self>
+    pub fn new<F, T>(
+        timeout: u64,
+        callback: OnceCallback<T>,
+        new_device_cb: F,
+    ) -> Result<Self, ::Error>
     where
         F: Fn(String, &Fn() -> bool) + Sync + Send + 'static,
         T: 'static,
     {
         let thread = RunLoop::new_with_timeout(
             move |alive| {
                 // Create a new device monitor.
                 let mut monitor = Monitor::new(new_device_cb);
 
                 // Start polling for new devices.
-                try_or!(monitor.run(alive), |e| callback.call(Err(e)));
+                try_or!(monitor.run(alive), |_| callback.call(Err(::Error::Unknown)));
 
                 // Send an error, if the callback wasn't called already.
-                callback.call(Err(io_err("aborted or timed out")));
+                callback.call(Err(::Error::NotAllowed));
             },
             timeout,
-        )?;
+        ).map_err(|_| ::Error::Unknown)?;
 
         Ok(Self {
             thread: Some(thread),
         })
     }
 
     pub fn cancel(&mut self) {
         // This must never be None.