Skip to content

Commit

Permalink
Adds support for YubiHSM Auth
Browse files Browse the repository at this point in the history
This adds support for the YubiHSM Auth protocol as described in
https://docs.yubico.com/yesdk/users-manual/application-yubihsm-auth/interacting-yubihsm-2.html

This protocol ensure the derivation password for the authentication keys are kept
in secure devices.
  • Loading branch information
baloo committed Dec 12, 2023
1 parent c597e83 commit 482437a
Show file tree
Hide file tree
Showing 7 changed files with 346 additions and 81 deletions.
26 changes: 18 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,22 @@ env:

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
platform:
- ubuntu-latest
- macos-latest
toolchain:
- stable
- 1.67.0 # MSRV
include:
- platform: ubuntu-latest
toolchain: stable
deps: sudo apt-get install libpcsclite-dev
- platform: macos-latest
toolchain: stable
deps: true
- platform: ubuntu-latest
toolchain: 1.67.0 # MSRV
deps: sudo apt-get install libpcsclite-dev
- platform: macos-latest
toolchain: 1.67.0 # MSRV
deps: true
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v1
- name: cache .cargo/registry
Expand All @@ -41,14 +48,15 @@ jobs:
with:
toolchain: ${{ matrix.toolchain }}
override: true
- run: ${{ matrix.deps }}
- run: cargo build --release
- run: cargo build --release --no-default-features
- run: cargo build --release --no-default-features --features=passwords
- run: cargo build --release --features=usb
- run: cargo build --release --features=yubihsm-auth
- run: cargo build --benches

test:
runs-on: ubuntu-latest
strategy:
matrix:
platform:
Expand All @@ -57,6 +65,7 @@ jobs:
toolchain:
- stable
- 1.67.0 # MSRV
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v1
- name: cache .cargo/registry
Expand Down Expand Up @@ -103,6 +112,7 @@ jobs:
toolchain: 1.71.0 # pinned to prevent CI breakages
components: clippy
override: true
- run: sudo apt-get install libpcsclite-dev
- uses: actions-rs/cargo@v1
with:
command: clippy
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ pbkdf2 = { version = "0.12", optional = true, default-features = false, features
serde_json = { version = "1", optional = true }
rusb = { version = "0.9", optional = true }
tiny_http = { version = "0.12", optional = true }
yubikey = { git = "https://github.com/baloo/yubikey.rs", branch = "baloo/yubihsm-auth", optional = true }

[dev-dependencies]
ed25519-dalek = "2"
Expand All @@ -66,6 +67,7 @@ secp256k1 = ["k256"]
setup = ["passwords", "serde_json", "uuid/serde"]
untested = []
usb = ["rusb"]
yubihsm-auth = ["yubikey"]

[package.metadata.docs.rs]
all-features = true
Expand Down
29 changes: 29 additions & 0 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ use std::{
#[cfg(feature = "passwords")]
use std::{thread, time::SystemTime};

#[cfg(feature = "yubihsm-auth")]
use crate::session::PendingSession;

#[cfg(feature = "untested")]
use {
crate::{
Expand Down Expand Up @@ -106,6 +109,20 @@ impl Client {
Ok(client)
}

/// Open session with YubiHSM Auth scheme
#[cfg(feature = "yubihsm-auth")]
pub fn yubihsm_auth(
connector: Connector,
authentication_key_id: object::Id,
host_challenge: session::securechannel::Challenge,
) -> Result<PendingSession, Error> {
let timeout = session::Timeout::default();

let session =
PendingSession::new(connector, timeout, authentication_key_id, host_challenge)?;
Ok(session)
}

/// Borrow this client's YubiHSM connector (which is `Clone`able)
pub fn connector(&self) -> &Connector {
&self.connector
Expand Down Expand Up @@ -1165,3 +1182,15 @@ impl Client {
.0)
}
}

impl From<Session> for Client {
fn from(session: Session) -> Self {
let connector = session.connector();
let session = Arc::new(Mutex::new(Some(session)));
Self {
connector,
session,
credentials: None,
}
}
}
120 changes: 114 additions & 6 deletions src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub use self::{
error::{Error, ErrorKind},
guard::Guard,
id::Id,
securechannel::{Challenge, Context, SessionKeys},
timeout::Timeout,
};

Expand All @@ -30,13 +31,108 @@ use crate::{
};
use std::time::{Duration, Instant};

#[cfg(feature = "yubihsm-auth")]
use crate::object;

/// Timeout fuzz factor: to avoid races/skew with the YubiHSM's clock,
/// we consider sessions to be timed out slightly earlier than the actual
/// timeout. This should (hopefully) ensure we always time out first,
/// and therefore generate appropriate timeout-related errors rather
/// than opaque "lost connection to HSM"-style errors.
const TIMEOUT_FUZZ_FACTOR: Duration = Duration::from_secs(1);

/// Session created on the device for which we do not
/// have credentials for yet.
///
/// This is used for YubiHSM Auth scheme support.
#[cfg(feature = "yubihsm-auth")]
pub struct PendingSession {
///// HSM Public key
//card_public_key: PublicKey,
/// Connector which communicates with the HSM (HTTP or USB)
connector: Connector,

/// Session creation timestamp
created_at: Instant,

/// Timestamp when this session was last active
last_active: Instant,

/// Inactivity timeout for this session
timeout: Timeout,

/// Challenge generate by the HSM.
hsm_challenge: Challenge,

/// ID for this session
id: Id,

context: Context,
}

#[cfg(feature = "yubihsm-auth")]
impl PendingSession {
/// Creates a new session with the device.
pub fn new(
connector: Connector,
timeout: Timeout,
authentication_key_id: object::Id,
host_challenge: Challenge,
) -> Result<Self, Error> {
let (id, session_response) =
SecureChannel::create(&connector, authentication_key_id, host_challenge)?;

let hsm_challenge = session_response.card_challenge;
let context = Context::from_challenges(host_challenge, hsm_challenge);

let created_at = Instant::now();
let last_active = Instant::now();

Ok(PendingSession {
id,
connector,
created_at,
last_active,
timeout,
context,
hsm_challenge,
})
}

/// Create the session with the provided session keys
pub fn realize(self, session_keys: SessionKeys) -> Result<Session, Error> {
let secure_channel = Some(SecureChannel::with_session_keys(
self.id,
self.context,
session_keys,
));

let mut session = Session {
id: self.id,
secure_channel,
connector: self.connector,
created_at: self.created_at,
last_active: self.last_active,
timeout: self.timeout,
};

let response = session.start_authenticate()?;
session.finish_authenticate_session(&response)?;

Ok(session)
}

/// Return the challenge emitted by the HSM when opening the session
pub fn get_challenge(&self) -> Challenge {
self.hsm_challenge
}

/// Return the id of the session
pub fn id(&self) -> Id {
self.id
}
}

/// Authenticated and encrypted (SCP03) `Session` with the HSM. A `Session` is
/// needed to perform any command.
///
Expand Down Expand Up @@ -247,13 +343,9 @@ impl Session {
credentials.authentication_key_id
);

let command = self.secure_channel()?.authenticate_session()?;
let response = self.send_message(command)?;
let response = self.start_authenticate()?;

if let Err(e) = self
.secure_channel()?
.finish_authenticate_session(&response)
{
if let Err(e) = self.finish_authenticate_session(&response) {
session_error!(
self,
"failed={:?} key={} err={:?}",
Expand All @@ -269,10 +361,26 @@ impl Session {
Ok(())
}

/// Send the message to the card to start authentication
fn start_authenticate(&mut self) -> Result<response::Message, Error> {
let command = self.secure_channel()?.authenticate_session()?;
self.send_message(command)
}

/// Read authenticate session message from the card
fn finish_authenticate_session(&mut self, response: &response::Message) -> Result<(), Error> {
self.secure_channel()?.finish_authenticate_session(response)
}

/// Get the underlying channel or return an error
fn secure_channel(&mut self) -> Result<&mut SecureChannel, Error> {
self.secure_channel
.as_mut()
.ok_or_else(|| format_err!(ErrorKind::ClosedError, "session is already closed").into())
}

/// Get the underlying connector used by this session
pub(crate) fn connector(&self) -> Connector {
self.connector.clone()
}
}
Loading

0 comments on commit 482437a

Please sign in to comment.