kanidm / webauthn-rs

An implementation of webauthn components for Rustlang servers
Mozilla Public License 2.0
484 stars 80 forks source link

webauthn-authenticator-rs: build bindings from Transport/Token -> AuthenticatorBackend #214

Open micolous opened 1 year ago

micolous commented 1 year ago

Context: https://github.com/kanidm/webauthn-rs/blob/master/designs/authenticator-library.md#implement-an-authenticatorbackend-for-transporttoken

This will:

PRs:

Pending work ideas:

Related:

micolous commented 1 year ago
  • Handle devices being plugged in / appearing after the prompt flow has started
    • make Transport.tokens() async and return a Stream of Token?

I think this is should be the first thing to look at in terms of improving the user experience. It would also unlock being able to handle authenticator selection.

Looking at device monitoring (hot-plug events) this on a transport level:

BTLE

This should be possible with Adapter::events(). The library already uses something similar for cable; and it may need to be merged together.

caBLE

The caBLE UI flow works differently, and already has to listen for BTLE advertisements to start the handshake process.

NFC

The library already tracks reader states, and we can listen for events with Context::get_status_change.

USB HID

It looks like hidapi-rs won't do this. You can only poll for devices with HidApi::refresh_devices() and HidApi::device_list(). Monitoring for device connection / disconnection also isn't supported by the underlying hidapi C library, so would require basically rewriting hidapi-rs (to eliminate the hidapi dependency) to make it work, but there is a plan to do this upstream.

rusb looked promising, but it looks like it's using libusb underneath, so HID device access will be problematic.

Mozilla's authenticator-rs has its own HID handling code, which is Rust-native and supports monitoring for devices. This tries to avoid taking dependencies on platform-specific crates, and instead provides its own bindings for everything.

I'm leaning towards forking Mozilla's authenticator-rs HID handling code; it seems to be the most complete here, but it will mean that we'd need to ship a pile of platform-specific code for it... which is something I had explicitly hoped to avoid.

micolous commented 1 year ago

I started looking into refactoring around USB HID on Windows first in https://github.com/micolous/webauthn-rs/tree/mozilla-hid

It appears that authenticator-rs on Windows uses the old USB HID APIs which are not async at all, and discover devices based on polling:

https://github.com/mozilla/authenticator-rs/blob/110b691ff105062352bcc9f00c36c77c42d4e45c/src/windows/monitor.rs#L33-L61

There is a way to make this all async, and that's to use the new UWP APIs (Devices::HumanInterfaceDevice::HidDevice) and to watch for events with DeviceInformation::CreateWatcher. This will only work on Windows 10 and later, which is in line with the support bar for the rest of this library.

Where I'm at:

Once I've gotten the Windows side of things working, I should be able to then pivot to the others.

micolous commented 1 year ago

My mozilla-hid branch is now a bit cleaner, implements discovery, and works on macOS and Linux too.

I didn't end up using much of the Mozilla authenticator-rs implementation at all; but it was helpful for finding the right APIs to use on macOS, but its models (particularly around threads) are completely different, and I ended up rewriting a lot of the FFI bindings to use core_foundation types. Unfortunately, macOS doesn't have an "enumeration complete" event (and it provides a list of all existing devices when starting discovery), so that's currently faked.

Mozilla's code supports FreeBSD and NetBSD as well, but I'm not targeting those for now. It looks like at least on FreeBSD there is some Linux HID ioctl compatibility, but I think this would still need to replace all the udev stuff.

What's left to do:

micolous commented 1 year ago

I've started working on NFC support, and gotten basic device watching working. There are still some rough edges when tokens and/or NFC transceivers disappear at inopportune times.

I've also got AnyTransport working with both NFC and USB, but need to put the feature flag gates back in.

> .\target\debug\fido-key-manager.exe info --watch
DANGER: make sure you only have one key connected
2023-05-26T07:22:02.135341Z TRACE webauthn_authenticator_rs::usb::platform::os: watch_devices
2023-05-26T07:22:02.136409Z TRACE webauthn_authenticator_rs::nfc: New reader: "ACS ACR122 0"
2023-05-26T07:22:02.136667Z TRACE webauthn_authenticator_rs::nfc: Reader "ACS ACR122 0" current_state: UNAWARE, event_state: CHANGED | EMPTY
2023-05-26T07:22:02.149468Z TRACE webauthn_authenticator_rs::usb::platform::os: Starting WindowsDeviceWatcher
2023-05-26T07:22:02.150626Z TRACE webauthn_authenticator_rs::transport::any: NFC event: EnumerationComplete
2023-05-26T07:22:02.163129Z TRACE webauthn_authenticator_rs::usb: watch_tokens event: EnumerationComplete
2023-05-26T07:22:02.163228Z TRACE webauthn_authenticator_rs::transport::any: Sending enumeration complete from USB
Initial enumeration completed, watching for more devices...
Press Ctrl-C to stop watching.
2023-05-26T07:22:08.855336Z TRACE webauthn_authenticator_rs::usb: watch_tokens event: Added(WindowsUSBDeviceInfo { id: "\\\\?\\HID#VID_1050&PID_0402#9&6bfd493&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}", name: "YubiKey FIDO" })
2023-05-26T07:22:08.855528Z TRACE webauthn_authenticator_rs::usb::platform::os: Opening device: WindowsUSBDeviceInfo { id: "\\\\?\\HID#VID_1050&PID_0402#9&6bfd493&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}", name: "YubiKey FIDO" }
2023-05-26T07:22:08.860221Z TRACE webauthn_authenticator_rs::usb: >>> 00ffffffff860008a38f776aaf65069a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
2023-05-26T07:22:08.864201Z TRACE webauthn_authenticator_rs::usb: <<< ffffffff860011a38f776aaf65069a2aa6b262020504030d00000000000000000000000000000000000000000000000000000000000000000000000000000000
2023-05-26T07:22:08.864305Z TRACE webauthn_authenticator_rs::usb: i=InitResponse { nonce: [163, 143, 119, 106, 175, 101, 6, 154], cid: 715567714, protocol_version: 2, device_version_major: 5, device_version_minor: 4, device_version_build: 3, capabilities: 13 }
2023-05-26T07:22:08.864406Z TRACE webauthn_authenticator_rs::transport: >>> 04
2023-05-26T07:22:08.864476Z TRACE webauthn_authenticator_rs::usb: >>> 002aa6b262900001040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
2023-05-26T07:22:08.868109Z TRACE webauthn_authenticator_rs::usb: <<< 2aa6b2629000c500ac0182684649444f5f325f306c4649444f5f325f315f50524502826b6372656450726f746563746b686d61632d7365637265740350149a20
2023-05-26T07:22:08.870104Z TRACE webauthn_authenticator_rs::usb: <<< 2aa6b26200218ef6413396b881f8d5b7f1f504a562726bf5627570f564706c6174f469636c69656e7450696ef57563726564656e7469616c4d676d7450726576
2023-05-26T07:22:08.872160Z TRACE webauthn_authenticator_rs::usb: <<< 2aa6b26201696577f5051904b00682020107080818800982636e6663637573620a82a263616c672664747970656a7075626c69632d6b6579a263616c67276474
2023-05-26T07:22:08.874182Z TRACE webauthn_authenticator_rs::usb: <<< 2aa6b262027970656a7075626c69632d6b65790d040e1a0005040300000000000000000000000000000000000000000000000000000000000000000000000000
2023-05-26T07:22:08.874366Z TRACE webauthn_authenticator_rs::transport: <<< ac0182684649444f5f325f306c4649444f5f325f315f50524502826b6372656450726f746563746b686d61632d7365637265740350149a20218ef6413396b881f8d5b7f1f504a562726bf5627570f564706c6174f469636c69656e7450696ef57563726564656e7469616c4d676d7450726576696577f5051904b00682020107080818800982636e6663637573620a82a263616c672664747970656a7075626c69632d6b6579a263616c672764747970656a7075626c69632d6b65790d040e1a00050403
2023-05-26T07:22:08.874621Z TRACE webauthn_authenticator_rs::ctap2::commands::get_info: raw = {1: Array([Text("FIDO_2_0"), Text("FIDO_2_1_PRE")]), 2: Array([Text("credProtect"), Text("hmac-secret")]), 3: Bytes([20, 154, 32, 33, 142, 246, 65, 51, 150, 184, 129, 248, 213, 183, 241, 245]), 4: Map({Text("rk"): Bool(true), Text("up"): Bool(true), Text("plat"): Bool(false), Text("clientPin"): Bool(true), Text("credentialMgmtPreview"): Bool(true)}), 5: Integer(1200), 6: Array([Integer(2), Integer(1)]), 7: Integer(8), 8: Integer(128), 9: Array([Text("nfc"), Text("usb")]), 10: Array([Map({Text("alg"): Integer(-7), Text("type"): Text("public-key")}), Map({Text("alg"): Integer(-8), Text("type"): Text("public-key")})]), 13: Integer(4), 14: Integer(328707)}
versions: FIDO_2_0 FIDO_2_1_PRE
extensions: credProtect hmac-secret
aaguid: 149a2021-8ef6-4133-96b8-81f8d5b7f1f5
options: clientPin:true credentialMgmtPreview:true plat:false rk:true up:true
max message size: 1200
PIN protocols: 2 1
max cred count in list: 8
max cred ID length: 128
transports: nfc usb
algorithms: Array([Map({Text("alg"): Integer(-7), Text("type"): Text("public-key")}), Map({Text("alg"): Integer(-8), Text("type"): Text("public-key")})])
force PIN change: false
minimum PIN length: 4
firmware version: 0x50403

2023-05-26T07:22:10.869012Z TRACE webauthn_authenticator_rs::usb: watch_tokens event: Removed(\\?\HID#VID_1050&PID_0402#9&6bfd493&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030})
2023-05-26T07:22:13.203573Z TRACE webauthn_authenticator_rs::nfc: Reader "ACS ACR122 0" current_state: CHANGED | EMPTY, event_state: CHANGED | PRESENT
2023-05-26T07:22:13.203721Z TRACE webauthn_authenticator_rs::nfc: ATR: 3b8d80018073c021c057597562694b6579f9
2023-05-26T07:22:13.203801Z TRACE webauthn_authenticator_rs::nfc: Parsed: Atr { protocols: [0, 1], t1: [128, 115, 192, 33, 192, 87, 89, 117, 98, 105, 75, 101, 121], storage_card: false, card_issuers_data: Some([89, 117, 98, 105, 75, 101, 121]), command_chaining: Some(true), extended_lc: Some(true) }
2023-05-26T07:22:13.209686Z TRACE webauthn_authenticator_rs::nfc: >>> 00a4040008a0000006472f000100
2023-05-26T07:22:13.210497Z TRACE webauthn_authenticator_rs::nfc: Reader "ACS ACR122 0" current_state: CHANGED | PRESENT, event_state: CHANGED | PRESENT | EXCLUSIVE | INUSE
2023-05-26T07:22:13.210589Z TRACE webauthn_authenticator_rs::nfc: ignoring in-use card
2023-05-26T07:22:13.488751Z TRACE webauthn_authenticator_rs::nfc: <<< 5532465f56329000
2023-05-26T07:22:13.488917Z TRACE webauthn_authenticator_rs::transport::any: NFC event: Added(NFCCard { reader_name: "ACS ACR122 0", atr: Atr { protocols: [0, 1], t1: [128, 115, 192, 33, 192, 87, 89, 117, 98, 105, 75, 101, 121], storage_card: false, card_issuers_data: Some([89, 117, 98, 105, 75, 101, 121]), command_chaining: Some(true), extended_lc: Some(true) } })
2023-05-26T07:22:13.489041Z TRACE webauthn_authenticator_rs::nfc: >>> 00a4040008a0000006472f000100
2023-05-26T07:22:13.507438Z TRACE webauthn_authenticator_rs::nfc: <<< 5532465f56329000
2023-05-26T07:22:13.507526Z TRACE webauthn_authenticator_rs::transport: >>> 04
2023-05-26T07:22:13.507610Z TRACE webauthn_authenticator_rs::nfc: >>> 80100000010400
2023-05-26T07:22:13.618258Z TRACE webauthn_authenticator_rs::nfc: <<< 00ac0182684649444f5f325f306c4649444f5f325f315f50524502826b6372656450726f746563746b686d61632d7365637265740350149a20218ef6413396b881f8d5b7f1f504a562726bf5627570f564706c6174f469636c69656e7450696ef57563726564656e7469616c4d676d7450726576696577f5051904b00682020107080818800982636e6663637573620a82a263616c672664747970656a7075626c69632d6b6579a263616c672764747970656a7075626c69632d6b65790d040e1a000504039000
2023-05-26T07:22:13.618421Z TRACE webauthn_authenticator_rs::transport: <<< ac0182684649444f5f325f306c4649444f5f325f315f50524502826b6372656450726f746563746b686d61632d7365637265740350149a20218ef6413396b881f8d5b7f1f504a562726bf5627570f564706c6174f469636c69656e7450696ef57563726564656e7469616c4d676d7450726576696577f5051904b00682020107080818800982636e6663637573620a82a263616c672664747970656a7075626c69632d6b6579a263616c672764747970656a7075626c69632d6b65790d040e1a00050403
2023-05-26T07:22:13.618604Z TRACE webauthn_authenticator_rs::ctap2::commands::get_info: raw = {1: Array([Text("FIDO_2_0"), Text("FIDO_2_1_PRE")]), 2: Array([Text("credProtect"), Text("hmac-secret")]), 3: Bytes([20, 154, 32, 33, 142, 246, 65, 51, 150, 184, 129, 248, 213, 183, 241, 245]), 4: Map({Text("rk"): Bool(true), Text("up"): Bool(true), Text("plat"): Bool(false), Text("clientPin"): Bool(true), Text("credentialMgmtPreview"): Bool(true)}), 5: Integer(1200), 6: Array([Integer(2), Integer(1)]), 7: Integer(8), 8: Integer(128), 9: Array([Text("nfc"), Text("usb")]), 10: Array([Map({Text("alg"): Integer(-7), Text("type"): Text("public-key")}), Map({Text("alg"): Integer(-8), Text("type"): Text("public-key")})]), 13: Integer(4), 14: Integer(328707)}
versions: FIDO_2_0 FIDO_2_1_PRE
extensions: credProtect hmac-secret
aaguid: 149a2021-8ef6-4133-96b8-81f8d5b7f1f5
options: clientPin:true credentialMgmtPreview:true plat:false rk:true up:true
max message size: 1200
PIN protocols: 2 1
max cred count in list: 8
max cred ID length: 128
transports: nfc usb
algorithms: Array([Map({Text("alg"): Integer(-7), Text("type"): Text("public-key")}), Map({Text("alg"): Integer(-8), Text("type"): Text("public-key")})])
force PIN change: false
minimum PIN length: 4
firmware version: 0x50403

2023-05-26T07:22:13.620314Z TRACE webauthn_authenticator_rs::nfc: Reader "ACS ACR122 0" current_state: CHANGED | PRESENT | EXCLUSIVE | INUSE, event_state: CHANGED | PRESENT
2023-05-26T07:22:14.546287Z TRACE webauthn_authenticator_rs::nfc: Reader "ACS ACR122 0" current_state: CHANGED | PRESENT, event_state: CHANGED | EMPTY