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