|
| 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 | + |
| 5 | +use std::io; |
| 6 | +use std::sync::mpsc::Sender; |
| 7 | +use pcsc::*; |
| 8 | +use std::ffi::CString; |
| 9 | +use runloop::RunLoop; |
| 10 | +use std::collections::HashMap; |
| 11 | +use std::option::Option; |
| 12 | +use std::thread; |
| 13 | +use std::time::Duration; |
| 14 | +use std::sync::{Mutex, Arc}; |
| 15 | + |
| 16 | +use crate::consts::*; |
| 17 | +use crate::apdu::*; |
| 18 | +use crate::util::{io_err, trace_hex}; |
| 19 | +use crate::errors; |
| 20 | + |
| 21 | +use crate::authenticatorservice::AuthenticatorTransport; |
| 22 | +use crate::statecallback::StateCallback; |
| 23 | +use crate::statemachine::StateMachine; |
| 24 | +use crate::u2ftypes::{U2FInfoQueryable, U2FDeviceInfo}; |
| 25 | + |
| 26 | +fn sendrecv(card: &mut Card, send: &[u8]) -> io::Result<Vec<u8>> |
| 27 | +{ |
| 28 | + trace_hex("NFC send", send); |
| 29 | + let mut rapdu_buf = [0; MAX_BUFFER_SIZE]; |
| 30 | + match card.transmit(send, &mut rapdu_buf) { |
| 31 | + Ok(rapdu) => { |
| 32 | + trace_hex("NFC recv", rapdu); |
| 33 | + Ok(rapdu.to_vec()) |
| 34 | + }, |
| 35 | + Err(err) => { |
| 36 | + trace!("NFC error: {}", err); |
| 37 | + let s = format!("{}", err); |
| 38 | + Err(io_err(&s)) |
| 39 | + } |
| 40 | + } |
| 41 | +} |
| 42 | + |
| 43 | +impl APDUDevice for Card { |
| 44 | + fn init_apdu(&mut self) -> io::Result<()> { |
| 45 | + let out = APDU::serialize_short(U2F_SELECT_FILE, U2F_SELECT_DIRECT, &U2F_AID)?; |
| 46 | + let ret = sendrecv(self, &out)?; |
| 47 | + let (_, status) = APDU::deserialize(ret)?; |
| 48 | + apdu_status_to_result(status, ()) |
| 49 | + } |
| 50 | + |
| 51 | + fn send_apdu(&mut self, cmd: u8, p1: u8, send: &[u8]) -> io::Result<(Vec<u8>, [u8; 2])> { |
| 52 | + // Some devices, such as the Yubikey 4, freak out if an APDU which _would_ fit a short command |
| 53 | + // is sent as an extended command. This means we must use short, even though that means chaining |
| 54 | + // the responses together. |
| 55 | + // The whole response would have fit in an extended reply, but it seems we can't have nice things. |
| 56 | + let mut data: Vec<u8> = Vec::new(); |
| 57 | + |
| 58 | + let out = APDU::serialize_short(cmd, p1, send)?; |
| 59 | + let ret = sendrecv(self, &out)?; |
| 60 | + let (mut more, [s1, s2]) = APDU::deserialize(ret)?; |
| 61 | + data.append(&mut more); |
| 62 | + |
| 63 | + if s1 != U2F_MORE_DATA { |
| 64 | + return Ok((data, [s1, s2])); |
| 65 | + } |
| 66 | + |
| 67 | + loop { |
| 68 | + let out = APDU::serialize_short(U2F_GET_RESPONSE, 0x00, &[])?; |
| 69 | + let ret = sendrecv(self, &out)?; |
| 70 | + let (mut more, [s1, s2]) = APDU::deserialize(ret)?; |
| 71 | + data.append(&mut more); |
| 72 | + if s1 != U2F_MORE_DATA { |
| 73 | + return Ok((data, [s1, s2])); |
| 74 | + } |
| 75 | + } |
| 76 | + } |
| 77 | +} |
| 78 | + |
| 79 | +impl U2FInfoQueryable for Card { |
| 80 | + fn get_device_info(&self) -> U2FDeviceInfo { |
| 81 | + // TODO: actuall return something sane here! |
| 82 | + let vendor = String::from("Unknown Vendor"); |
| 83 | + let product = String::from("Unknown Device"); |
| 84 | + |
| 85 | + U2FDeviceInfo { |
| 86 | + vendor_name: vendor.as_bytes().to_vec(), |
| 87 | + device_name: product.as_bytes().to_vec(), |
| 88 | + version_interface: 0, |
| 89 | + version_major: 0, |
| 90 | + version_minor: 0, |
| 91 | + version_build: 0, |
| 92 | + cap_flags: 0, |
| 93 | + } |
| 94 | + } |
| 95 | +} |
| 96 | + |
| 97 | +pub struct NFCManager { |
| 98 | + run_loop: Option<RunLoop>, |
| 99 | +} |
| 100 | + |
| 101 | +impl NFCManager { |
| 102 | + fn run<E, F>(&mut self, timeout: u64, fatal_error: E, f: F) -> crate::Result<()> |
| 103 | + where |
| 104 | + E: Fn() + Sync + Send + 'static, |
| 105 | + F: Fn(&mut Card, &dyn Fn() -> bool) + Sync + Send + Clone + 'static, |
| 106 | + { |
| 107 | + if self.run_loop.is_some() { |
| 108 | + return Err(errors::AuthenticatorError::InternalError(String::from("nfc run loop is already in use"))); |
| 109 | + } |
| 110 | + |
| 111 | + let ctx = Context::establish(Scope::User) |
| 112 | + .map_err(|_| errors::AuthenticatorError::Platform)?; |
| 113 | + |
| 114 | + let rl = RunLoop::new_with_timeout(move |alive| { |
| 115 | + let mut child_loops: HashMap<CString, RunLoop> = HashMap::new(); |
| 116 | + |
| 117 | + let mut readers_buf = [0; 2048]; |
| 118 | + // We _could_ insert `ReaderState::new(PNP_NOTIFICATION(), State::UNAWARE)` |
| 119 | + // to be reminded of reader insertion/removal, |
| 120 | + // but this is not guaranteed to be supported |
| 121 | + // and we need to poll anyways. |
| 122 | + let mut reader_states: Vec<ReaderState> = Vec::new(); |
| 123 | + |
| 124 | + while alive() { |
| 125 | + // Add new readers. |
| 126 | + let names = match ctx.list_readers(&mut readers_buf) { |
| 127 | + Ok(n) => n, |
| 128 | + Err(_) => { |
| 129 | + fatal_error(); |
| 130 | + break; }, |
| 131 | + }; |
| 132 | + for name in names { |
| 133 | + if !reader_states.iter().any(|reader| reader.name() == name) { |
| 134 | + debug!("Adding reader {:?}", name); |
| 135 | + reader_states.push(ReaderState::new(name, State::UNAWARE)); |
| 136 | + } |
| 137 | + } |
| 138 | + |
| 139 | + // Remove dead readers. |
| 140 | + fn is_dead(reader: &ReaderState) -> bool { |
| 141 | + reader.event_state().intersects(State::UNKNOWN | State::IGNORE) |
| 142 | + } |
| 143 | + for reader in &reader_states { |
| 144 | + if is_dead(reader) { |
| 145 | + debug!("Removing reader {:?}", reader.name()); |
| 146 | + } |
| 147 | + } |
| 148 | + reader_states.retain(|reader| !is_dead(reader)); |
| 149 | + |
| 150 | + // Let backend know that we know about the reader state. |
| 151 | + // Otherwise it will keep trying to update us. |
| 152 | + for rs in &mut reader_states { |
| 153 | + rs.sync_current_state(); |
| 154 | + } |
| 155 | + |
| 156 | + if reader_states.len() == 0 { |
| 157 | + // No readers available. This means that `get_status_change` will return |
| 158 | + // immediately without any work, causing a busy-loop. |
| 159 | + // Let's wait for a bit and look for a new reader. |
| 160 | + thread::sleep(Duration::from_millis(500)); |
| 161 | + continue; |
| 162 | + } |
| 163 | + |
| 164 | + // This call is blocking, so we must give it _some_ timeout in order for |
| 165 | + // the `alive()` check to work. |
| 166 | + let timeout = Duration::from_millis(100); |
| 167 | + if let Err(e) = ctx.get_status_change(timeout, &mut reader_states) { |
| 168 | + if e == Error::Timeout { |
| 169 | + continue; |
| 170 | + } |
| 171 | + fatal_error(); |
| 172 | + break; |
| 173 | + } |
| 174 | + |
| 175 | + for reader in &mut reader_states { |
| 176 | + let state = reader.event_state(); |
| 177 | + let name = CString::from(reader.name()); |
| 178 | + |
| 179 | + trace!("Reader {:?}: state {:?}", name, state); |
| 180 | + |
| 181 | + // TODO: this will keep spamming yubikeys with usb auth attempts??? |
| 182 | + // probably not harmful, but is there a way to avoid it? |
| 183 | + if state.contains(State::PRESENT) && !state.contains(State::EXCLUSIVE) { |
| 184 | + let mut card = match ctx.connect(&name, ShareMode::Shared, Protocols::ANY) { |
| 185 | + Ok(card) => card, |
| 186 | + _ => continue, |
| 187 | + }; |
| 188 | + |
| 189 | + let my_f = f.clone(); |
| 190 | + let cl = RunLoop::new(move |alive| { |
| 191 | + if alive() { |
| 192 | + my_f(&mut card, alive); |
| 193 | + } |
| 194 | + }); |
| 195 | + |
| 196 | + if let Ok(x) = cl { |
| 197 | + child_loops.insert(name, x); |
| 198 | + } |
| 199 | + } else { |
| 200 | + if let Some(cl) = child_loops.remove(&name) { |
| 201 | + cl.cancel(); |
| 202 | + } |
| 203 | + } |
| 204 | + } |
| 205 | + } |
| 206 | + |
| 207 | + for (_, child) in child_loops { |
| 208 | + child.cancel(); |
| 209 | + } |
| 210 | + }, timeout) |
| 211 | + .map_err(|_| errors::AuthenticatorError::Platform)?; |
| 212 | + self.run_loop = Some(rl); |
| 213 | + Ok(()) |
| 214 | + } |
| 215 | + |
| 216 | + pub fn new() -> Self { |
| 217 | + Self { |
| 218 | + run_loop: None, |
| 219 | + } |
| 220 | + } |
| 221 | + |
| 222 | + fn stop(&mut self) { |
| 223 | + if let Some(rl) = &self.run_loop { |
| 224 | + rl.cancel(); |
| 225 | + self.run_loop = None; |
| 226 | + } |
| 227 | + } |
| 228 | +} |
| 229 | + |
| 230 | +impl Drop for NFCManager { |
| 231 | + fn drop(&mut self) { |
| 232 | + self.stop(); |
| 233 | + } |
| 234 | +} |
| 235 | + |
| 236 | +impl AuthenticatorTransport for NFCManager { |
| 237 | + fn register( |
| 238 | + &mut self, |
| 239 | + flags: crate::RegisterFlags, |
| 240 | + timeout: u64, |
| 241 | + challenge: Vec<u8>, |
| 242 | + application: crate::AppId, |
| 243 | + key_handles: Vec<crate::KeyHandle>, |
| 244 | + status: Sender<crate::StatusUpdate>, |
| 245 | + callback: StateCallback<crate::Result<crate::RegisterResult>>, |
| 246 | + ) -> crate::Result<()> { |
| 247 | + let status_mutex = Arc::new(Mutex::new(status)); |
| 248 | + let cbc = callback.clone(); |
| 249 | + let err = move || { cbc.call(Err(errors::AuthenticatorError::Platform)) }; |
| 250 | + self.run(timeout, err, move |card, alive| { |
| 251 | + StateMachine::register(card, flags, &challenge, &application, &key_handles, &status_mutex, &callback, alive); |
| 252 | + }) |
| 253 | + } |
| 254 | + |
| 255 | + fn sign( |
| 256 | + &mut self, |
| 257 | + flags: crate::SignFlags, |
| 258 | + timeout: u64, |
| 259 | + challenge: Vec<u8>, |
| 260 | + app_ids: Vec<crate::AppId>, |
| 261 | + key_handles: Vec<crate::KeyHandle>, |
| 262 | + status: Sender<crate::StatusUpdate>, |
| 263 | + callback: StateCallback<crate::Result<crate::SignResult>>, |
| 264 | + ) -> crate::Result<()> { |
| 265 | + let status_mutex = Arc::new(Mutex::new(status)); |
| 266 | + let cbc = callback.clone(); |
| 267 | + let err = move || { cbc.call(Err(errors::AuthenticatorError::Platform)) }; |
| 268 | + self.run(timeout, err, move |card, alive| { |
| 269 | + StateMachine::sign(card, flags, &challenge, &app_ids, &key_handles, &status_mutex, &callback, alive); |
| 270 | + }) |
| 271 | + } |
| 272 | + |
| 273 | + fn cancel(&mut self) -> crate::Result<()> { |
| 274 | + self.stop(); |
| 275 | + Ok(()) |
| 276 | + } |
| 277 | +} |
0 commit comments