Skip to content

Commit

Permalink
YubiKey 5 vendor commands (kanidm#415)
Browse files Browse the repository at this point in the history
* implement yubikey config dumping
* document yubikey commands, fix warnings
* add yubikey to CI
* ber: add X690 references, reject some edge cases (and test for that), note yubikey weirdness
  • Loading branch information
micolous committed Apr 10, 2024
1 parent 6ff6ab2 commit 297a418
Show file tree
Hide file tree
Showing 15 changed files with 933 additions and 3 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,15 @@ jobs:
# --help should be enough to find an issue.
- run: cargo run --bin cable-tunnel-server-backend -- --help
- run: cargo run --bin cable-tunnel-server-frontend -- --help
- run: cargo run --bin fido-mds-tool -- --help
# fido-key-manager requires elevation on Windows, which cargo can't
# handle.
- if: runner.os != 'windows'
run: cargo run --bin fido-key-manager -- --help
- if: runner.os != 'windows'
run: cargo run --bin fido-key-manager --features solokey -- --help
- run: cargo run --bin fido-mds-tool -- --help
- if: runner.os != 'windows'
run: cargo run --bin fido-key-manager --features yubikey -- --help

authenticator:
name: webauthn-authenticator-rs test
Expand All @@ -92,7 +94,7 @@ jobs:
- softtoken
- usb
- bluetooth,nfc,usb,ctap2-management
- bluetooth,cable,cable-override-tunnel,ctap2-management,nfc,softpasskey,softtoken,usb,vendor-solokey
- bluetooth,cable,cable-override-tunnel,ctap2-management,nfc,softpasskey,softtoken,usb,vendor-solokey,vendor-yubikey
os:
- ubuntu-latest
- windows-latest
Expand Down
1 change: 1 addition & 0 deletions fido-key-manager/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ bluetooth = ["webauthn-authenticator-rs/bluetooth"]
nfc = ["webauthn-authenticator-rs/nfc"]
usb = ["webauthn-authenticator-rs/usb"]
solokey = ["webauthn-authenticator-rs/vendor-solokey"]
yubikey = ["webauthn-authenticator-rs/vendor-yubikey"]

default = ["nfc", "usb"]

Expand Down
20 changes: 20 additions & 0 deletions fido-key-manager/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,26 @@ Command | Description
`solo-key-info` | get all connected SoloKeys' unique ID, firmware version and secure boot status
`solo-key-random` | get some random bytes from a SoloKey

### YubiKey

> **Tip:** this functionality is only available when `fido-key-manager` is built
> with `--features yubikey`.
This only supports [YubiKey 5 series][yk5] and [Security Key by Yubico][sky]
devices via USB HID with the CTAP 2.0 interface (FIDO2) enabled. NFC support may
be added in future.

YubiKey 4 and earlier support is not planned - they do not support CTAP 2.0,
they use a different config format and protocol, and some firmware versions
report bogus data.

Command | Description
------- | -----------
`yubikey-get-config` | gets a connected YubiKey's device info, firmware version and interface configuration

[yk5]: https://www.yubico.com/products/yubikey-5-overview/
[sky]: https://www.yubico.com/products/security-key/

## Platform-specific notes

Bluetooth is currently disabled by default, as it's not particularly reliable on
Expand Down
20 changes: 20 additions & 0 deletions fido-key-manager/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ use hex::{FromHex, FromHexError};
use std::io::{stdin, stdout, Write};
use std::time::Duration;
use tokio_stream::StreamExt;
#[cfg(feature = "yubikey")]
use webauthn_authenticator_rs::ctap2::YubiKeyAuthenticator;
#[cfg(feature = "solokey")]
use webauthn_authenticator_rs::{ctap2::SoloKeyAuthenticator, prelude::WebauthnCError};
use webauthn_authenticator_rs::{
Expand Down Expand Up @@ -205,6 +207,8 @@ pub enum Opt {
#[cfg(feature = "solokey")]
/// Gets some random bytes from a connected SoloKey 2 or Trussed device.
SoloKeyRandom,
#[cfg(feature = "yubikey")]
YubikeyGetConfig,
}

#[derive(Debug, clap::Parser)]
Expand Down Expand Up @@ -754,5 +758,21 @@ async fn main() {
.expect("Error getting random data");
println!("Random bytes: {}", hex::encode(r));
}

#[cfg(feature = "yubikey")]
Opt::YubikeyGetConfig => {
// TODO: filter this to just YubiKey devices in a safe way
println!("Insert a YubiKey device...");
let mut token: CtapAuthenticator<AnyToken, Cli> =
select_one_device(stream, &ui).await.unwrap();

let cfg = token
.get_yubikey_config()
.await
.expect("Error getting YubiKey config");

println!("YubiKey config:");
println!("{cfg}")
}
}
}
2 changes: 2 additions & 0 deletions webauthn-authenticator-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ ctap2 = [
ctap2-management = ["ctap2"]
# Support for SoloKey's vendor commands
vendor-solokey = []
# Support for YubiKey's vendor commands
vendor-yubikey = []
nfc = ["ctap2", "dep:pcsc"]
# TODO: allow running softpasskey without softtoken
softpasskey = ["crypto", "softtoken"]
Expand Down
5 changes: 4 additions & 1 deletion webauthn-authenticator-rs/src/ctap2/ctap20.rs
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,10 @@ impl<'a, T: Token, U: UiCallback> Ctap20Authenticator<'a, T, U> {
let ret = self.token.transmit(mc, self.ui_callback).await;

if let Err(WebauthnCError::Ctap(e)) = ret {
if e == CtapError::Ctap2PinAuthInvalid || e == CtapError::Ctap2PinNotSet {
if e == CtapError::Ctap2PinAuthInvalid
|| e == CtapError::Ctap2PinNotSet
|| e == CtapError::Ctap2PinInvalid
{
// User pressed the button
return Ok(());
}
Expand Down
7 changes: 7 additions & 0 deletions webauthn-authenticator-rs/src/ctap2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ mod pin_uv;
#[cfg(any(all(doc, not(doctest)), feature = "vendor-solokey"))]
#[doc(hidden)]
mod solokey;
#[cfg(any(all(doc, not(doctest)), feature = "vendor-yubikey"))]
#[doc(hidden)]
mod yubikey;

use std::ops::{Deref, DerefMut};
use std::pin::Pin;
Expand Down Expand Up @@ -166,6 +169,10 @@ pub use self::{
#[doc(inline)]
pub use self::solokey::SoloKeyAuthenticator;

#[cfg(any(all(doc, not(doctest)), feature = "vendor-yubikey"))]
#[doc(inline)]
pub use self::yubikey::YubiKeyAuthenticator;

/// Abstraction for different versions of the CTAP2 protocol.
///
/// All tokens can [Deref] into [Ctap20Authenticator].
Expand Down
37 changes: 37 additions & 0 deletions webauthn-authenticator-rs/src/ctap2/yubikey.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use async_trait::async_trait;

use crate::{
prelude::WebauthnCError,
transport::{
yubikey::{YubiKeyConfig, YubiKeyToken},
Token,
},
ui::UiCallback,
};

use super::Ctap20Authenticator;

/// YubiKey vendor-specific commands.
///
/// ## Warning
///
/// These commands currently operate on *any* [`Ctap20Authenticator`][], and do
/// not filter to just YubiKey devices. Due to the nature of CTAP
/// vendor-specific commands, this may cause unexpected or undesirable behaviour
/// on other vendors' keys.
///
/// Protocol notes are in [`crate::transport::yubikey`].
#[async_trait]
pub trait YubiKeyAuthenticator {
async fn get_yubikey_config(&mut self) -> Result<YubiKeyConfig, WebauthnCError>;
}

#[async_trait]
impl<'a, T: Token + YubiKeyToken, U: UiCallback> YubiKeyAuthenticator
for Ctap20Authenticator<'a, T, U>
{
#[inline]
async fn get_yubikey_config(&mut self) -> Result<YubiKeyConfig, WebauthnCError> {
self.token.get_yubikey_config().await
}
}
2 changes: 2 additions & 0 deletions webauthn-authenticator-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ mod crypto;
#[cfg(any(all(doc, not(doctest)), feature = "ctap2"))]
pub mod ctap2;
pub mod error;
#[cfg(any(all(doc, not(doctest)), feature = "vendor-yubikey"))]
mod tlv;
#[cfg(any(all(doc, not(doctest)), feature = "ctap2"))]
pub mod transport;
pub mod types;
Expand Down
Loading

0 comments on commit 297a418

Please sign in to comment.