diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e283f2e3..3b03cf99 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 @@ -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: @@ -57,6 +65,7 @@ jobs: toolchain: - stable - 1.67.0 # MSRV + runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v1 - name: cache .cargo/registry @@ -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 diff --git a/Cargo.lock b/Cargo.lock index c81c064c..f94fe02b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,6 +47,12 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.4.0" @@ -231,10 +237,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" dependencies = [ "const-oid", + "der_derive", + "flagset", "pem-rfc7468", "zeroize", ] +[[package]] +name = "der_derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fe87ce4529967e0ba1dcf8450bab64d97dfd5010a6256187ffe2e43e6f0e049" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "deranged" version = "0.3.7" @@ -244,6 +263,15 @@ dependencies = [ "serde", ] +[[package]] +name = "des" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" +dependencies = [ + "cipher", +] + [[package]] name = "digest" version = "0.10.7" @@ -306,6 +334,7 @@ dependencies = [ "ff", "generic-array", "group", + "hkdf", "pem-rfc7468", "pkcs8", "rand_core", @@ -330,6 +359,12 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" +[[package]] +name = "flagset" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda653ca797810c02f7ca4b804b40b8b95ae046eb989d356bce17919a8c25499" + [[package]] name = "generic-array" version = "0.14.7" @@ -363,6 +398,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -447,6 +491,28 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-bigint-dig" version = "0.8.4" @@ -460,6 +526,7 @@ dependencies = [ "num-iter", "num-traits", "rand", + "serde", "smallvec", "zeroize", ] @@ -535,6 +602,25 @@ dependencies = [ "hmac", ] +[[package]] +name = "pcsc" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37cab0be9d04e808a8d8059fa54befcd71dc8b168f9f0c04bdb7e59832abbab4" +dependencies = [ + "bitflags 1.3.2", + "pcsc-sys", +] + +[[package]] +name = "pcsc-sys" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b7bfecba2c0f1b5efb0e7caf7533ab1c295024165bcbb066231f60d33e23ea" +dependencies = [ + "pkg-config", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -616,6 +702,7 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", "rand_chacha", "rand_core", ] @@ -665,6 +752,7 @@ dependencies = [ "pkcs1", "pkcs8", "rand_core", + "sha2", "signature", "spki", "subtle", @@ -710,6 +798,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "zeroize", +] + [[package]] name = "semver" version = "1.0.18" @@ -747,6 +844,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.7" @@ -896,6 +1004,7 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" dependencies = [ + "getrandom", "serde", ] @@ -917,12 +1026,25 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "x509-cert" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25eefca1d99701da3a57feb07e5079fc62abba059fc139e98c13bbb250f3ef29" +dependencies = [ + "const-oid", + "der", + "sha1", + "signature", + "spki", +] + [[package]] name = "yubihsm" version = "0.42.1" dependencies = [ "aes", - "bitflags", + "bitflags 2.4.0", "cbc", "ccm", "cmac", @@ -949,6 +1071,39 @@ dependencies = [ "time", "tiny_http", "uuid", + "yubikey", + "zeroize", +] + +[[package]] +name = "yubikey" +version = "0.8.0" +source = "git+https://github.com/baloo/yubikey.rs?branch=baloo/yubihsm-auth#9a1ab0d0d65fd5c79d7c70718fc0bc3f6f085286" +dependencies = [ + "base16ct", + "der", + "des", + "ecdsa", + "elliptic-curve", + "hmac", + "log", + "nom", + "num-bigint-dig", + "num-integer", + "num-traits", + "p256", + "p384", + "pbkdf2", + "pcsc", + "rand_core", + "rsa", + "secrecy", + "sha1", + "sha2", + "signature", + "subtle", + "uuid", + "x509-cert", "zeroize", ] diff --git a/Cargo.toml b/Cargo.toml index 1134cf96..9445739c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ serde_json = { version = "1", optional = true } rusb = { version = "0.9", optional = true } sha2 = { version = "0.10", 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" @@ -63,6 +64,7 @@ secp256k1 = ["k256"] setup = ["passwords", "serde_json", "uuid/serde"] untested = ["sha2"] usb = ["rusb"] +yubihsm-auth = ["yubikey"] [package.metadata.docs.rs] all-features = true diff --git a/src/client.rs b/src/client.rs index 038e63fd..60babd12 100644 --- a/src/client.rs +++ b/src/client.rs @@ -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::{ @@ -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 { + 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 @@ -1165,3 +1182,15 @@ impl Client { .0) } } + +impl From for Client { + fn from(session: Session) -> Self { + let connector = session.connector(); + let session = Arc::new(Mutex::new(Some(session))); + Self { + connector, + session, + credentials: None, + } + } +} diff --git a/src/session.rs b/src/session.rs index 6bd14231..883c4c7d 100644 --- a/src/session.rs +++ b/src/session.rs @@ -17,6 +17,7 @@ pub use self::{ error::{Error, ErrorKind}, guard::Guard, id::Id, + securechannel::{Challenge, Context, SessionKeys}, timeout::Timeout, }; @@ -30,6 +31,9 @@ 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, @@ -37,6 +41,98 @@ use std::time::{Duration, Instant}; /// 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 { + 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 { + 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. /// @@ -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={:?}", @@ -269,10 +361,26 @@ impl Session { Ok(()) } + /// Send the message to the card to start authentication + fn start_authenticate(&mut self) -> Result { + 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() + } } diff --git a/src/session/securechannel.rs b/src/session/securechannel.rs index 05c2e146..95dc3ea1 100644 --- a/src/session/securechannel.rs +++ b/src/session/securechannel.rs @@ -24,9 +24,9 @@ mod cryptogram; mod kdf; mod mac; +pub use self::{challenge::Challenge, context::Context}; pub(crate) use self::{ - challenge::{Challenge, CHALLENGE_SIZE}, - context::Context, + challenge::CHALLENGE_SIZE, cryptogram::{Cryptogram, CRYPTOGRAM_SIZE}, mac::Mac, }; @@ -35,7 +35,7 @@ use crate::{ authentication::{self, Credentials}, command, connector::Connector, - device, response, + device, object, response, serialization::deserialize, session::{self, ErrorKind}, }; @@ -47,6 +47,7 @@ use aes::{ Aes128, }; use cmac::{digest::Mac as _, Cmac}; +use serde::Serialize; use subtle::ConstantTimeEq; use zeroize::{Zeroize, Zeroizing}; @@ -70,6 +71,42 @@ const AES_BLOCK_SIZE: usize = 16; type Aes128CbcEnc = cbc::Encryptor; type Aes128CbcDec = cbc::Decryptor; +/// SCP03 AES Session Keys +#[derive(Serialize)] +pub struct SessionKeys { + /// Session encryption key (S-ENC) + pub enc_key: [u8; KEY_SIZE], + + /// Session Command MAC key (S-MAC) + pub mac_key: [u8; KEY_SIZE], + + /// Session Respose MAC key (S-RMAC) + pub rmac_key: [u8; KEY_SIZE], +} + +impl Zeroize for SessionKeys { + fn zeroize(&mut self) { + self.enc_key.zeroize(); + self.mac_key.zeroize(); + self.rmac_key.zeroize(); + } +} + +#[cfg(feature = "yubihsm-auth")] +impl From for SessionKeys { + fn from(keys: yubikey::hsmauth::SessionKeys) -> Self { + let enc_key = *keys.enc_key; + let mac_key = *keys.mac_key; + let rmac_key = *keys.rmac_key; + + Self { + enc_key, + mac_key, + rmac_key, + } + } +} + /// SCP03 Secure Channel pub(crate) struct SecureChannel { /// ID of this channel (a.k.a. session ID) @@ -85,14 +122,8 @@ pub(crate) struct SecureChannel { /// Context (card + host challenges) context: Context, - /// Session encryption key (S-ENC) - enc_key: [u8; KEY_SIZE], - - /// Session Command MAC key (S-MAC) - mac_key: [u8; KEY_SIZE], - - /// Session Respose MAC key (S-RMAC) - rmac_key: [u8; KEY_SIZE], + /// Session keys + session_keys: SessionKeys, /// Chaining value to be included when computing MACs mac_chaining_value: [u8; Mac::BYTE_SIZE * 2], @@ -107,45 +138,8 @@ impl SecureChannel { ) -> Result { let host_challenge = Challenge::new(); - let command_message = command::Message::from(&CreateSessionCommand { - authentication_key_id: credentials.authentication_key_id, - host_challenge, - }); - - let uuid = command_message.uuid; - let response_body = connector.send_message(uuid, command_message.into())?; - let response_message = response::Message::parse(response_body)?; - - if response_message.is_err() { - match device::ErrorKind::from_response_message(&response_message) { - Some(device::ErrorKind::ObjectNotFound) => fail!( - ErrorKind::AuthenticationError, - "auth key not found: 0x{:04x}", - credentials.authentication_key_id - ), - Some(kind) => return Err(kind.into()), - None => fail!( - ErrorKind::ResponseError, - "HSM error: {:?}", - response_message.code - ), - } - } - - if response_message.command().unwrap() != command::Code::CreateSession { - fail!( - ErrorKind::ProtocolError, - "command type mismatch: expected {:?}, got {:?}", - command::Code::CreateSession, - response_message.command().unwrap() - ); - } - - let id = response_message - .session_id - .ok_or_else(|| format_err!(ErrorKind::CreateFailed, "no session ID in response"))?; - - let session_response: CreateSessionResponse = deserialize(response_message.data.as_ref())?; + let (id, session_response) = + Self::create(connector, credentials.authentication_key_id, host_challenge)?; // Derive session keys from the combination of host and card challenges. // If either of them are incorrect (indicating a key mismatch) it will @@ -185,6 +179,20 @@ impl SecureChannel { let enc_key = derive_key(authentication_key.enc_key(), 0b100, &context); let mac_key = derive_key(authentication_key.mac_key(), 0b110, &context); let rmac_key = derive_key(authentication_key.mac_key(), 0b111, &context); + + let session_keys = SessionKeys { + enc_key, + mac_key, + rmac_key, + }; + Self::with_session_keys(id, context, session_keys) + } + + pub(crate) fn with_session_keys( + id: session::Id, + context: Context, + session_keys: SessionKeys, + ) -> Self { let mac_chaining_value = [0u8; Mac::BYTE_SIZE * 2]; Self { @@ -192,13 +200,62 @@ impl SecureChannel { counter: 0, security_level: SecurityLevel::None, context, - enc_key, - mac_key, - rmac_key, + session_keys, mac_chaining_value, } } + /// Open a SecureChannel with the HSM. This will not complete authentication. + /// + /// This will return the session id as well as the card challenge. + pub(crate) fn create( + connector: &Connector, + authentication_key_id: object::Id, + host_challenge: Challenge, + ) -> Result<(session::Id, CreateSessionResponse), session::Error> { + let command_message = command::Message::from(&CreateSessionCommand { + authentication_key_id, //: credentials.authentication_key_id, + host_challenge, + }); + + let uuid = command_message.uuid; + let response_body = connector.send_message(uuid, command_message.into())?; + let response_message = response::Message::parse(response_body)?; + + if response_message.is_err() { + match device::ErrorKind::from_response_message(&response_message) { + Some(device::ErrorKind::ObjectNotFound) => fail!( + ErrorKind::AuthenticationError, + "auth key not found: 0x{:04x}", + authentication_key_id + ), + Some(kind) => return Err(kind.into()), + None => fail!( + ErrorKind::ResponseError, + "HSM error: {:?}", + response_message.code + ), + } + } + + if response_message.command().unwrap() != command::Code::CreateSession { + fail!( + ErrorKind::ProtocolError, + "command type mismatch: expected {:?}, got {:?}", + command::Code::CreateSession, + response_message.command().unwrap() + ); + } + + let id = response_message + .session_id + .ok_or_else(|| format_err!(ErrorKind::CreateFailed, "no session ID in response"))?; + + let session_response: CreateSessionResponse = deserialize(response_message.data.as_ref())?; + + Ok((id, session_response)) + } + /// Get the channel (i.e. session) ID pub fn id(&self) -> session::Id { self.id @@ -207,14 +264,24 @@ impl SecureChannel { /// Calculate the card's cryptogram for this session pub fn card_cryptogram(&self) -> Cryptogram { let mut result_bytes = Zeroizing::new([0u8; CRYPTOGRAM_SIZE]); - kdf::derive(&self.mac_key, 0, &self.context, result_bytes.as_mut()); + kdf::derive( + &self.session_keys.mac_key, + 0, + &self.context, + result_bytes.as_mut(), + ); Cryptogram::from_slice(result_bytes.as_ref()) } /// Calculate the host's cryptogram for this session pub fn host_cryptogram(&self) -> Cryptogram { let mut result_bytes = Zeroizing::new([0u8; CRYPTOGRAM_SIZE]); - kdf::derive(&self.mac_key, 1, &self.context, result_bytes.as_mut()); + kdf::derive( + &self.session_keys.mac_key, + 1, + &self.context, + result_bytes.as_mut(), + ); Cryptogram::from_slice(result_bytes.as_ref()) } @@ -233,7 +300,8 @@ impl SecureChannel { ); } - let mut mac = as KeyInit>::new_from_slice(self.mac_key.as_ref()).unwrap(); + let mut mac = + as KeyInit>::new_from_slice(self.session_keys.mac_key.as_ref()).unwrap(); mac.update(&self.mac_chaining_value); mac.update(&[command_type.to_u8()]); @@ -298,7 +366,7 @@ impl SecureChannel { // Provide space at the end of the vec for the padding message.extend_from_slice(&[0u8; AES_BLOCK_SIZE]); - let cipher = Aes128::new_from_slice(&self.enc_key).unwrap(); + let cipher = Aes128::new_from_slice(&self.session_keys.enc_key).unwrap(); let icv = compute_icv(&cipher, self.counter); let cbc_encryptor = Aes128CbcEnc::inner_iv_init(cipher, &icv); let ciphertext = cbc_encryptor @@ -315,7 +383,7 @@ impl SecureChannel { ) -> Result { assert_eq!(self.security_level, SecurityLevel::Authenticated); - let cipher = Aes128::new_from_slice(&self.enc_key).unwrap(); + let cipher = Aes128::new_from_slice(&self.session_keys.enc_key).unwrap(); let icv = compute_icv(&cipher, self.counter); self.verify_response_mac(&encrypted_response)?; @@ -364,7 +432,8 @@ impl SecureChannel { ); } - let mut mac = as KeyInit>::new_from_slice(self.rmac_key.as_ref()).unwrap(); + let mut mac = + as KeyInit>::new_from_slice(self.session_keys.rmac_key.as_ref()).unwrap(); mac.update(&self.mac_chaining_value); mac.update(&[response.code.to_u8()]); @@ -441,12 +510,12 @@ impl SecureChannel { ) -> Result { assert_eq!(self.security_level, SecurityLevel::Authenticated); - let cipher = Aes128::new_from_slice(&self.enc_key).unwrap(); + let cipher = Aes128::new_from_slice(&self.session_keys.enc_key).unwrap(); let icv = compute_icv(&cipher, self.counter); self.verify_command_mac(&encrypted_command)?; - let cipher = Aes128::new_from_slice(&self.enc_key).unwrap(); + let cipher = Aes128::new_from_slice(&self.session_keys.enc_key).unwrap(); let cbc_decryptor = Aes128CbcDec::inner_iv_init(cipher, &icv); let mut command_data = encrypted_command.data; @@ -479,7 +548,8 @@ impl SecureChannel { command.session_id ); - let mut mac = as KeyInit>::new_from_slice(self.mac_key.as_ref()).unwrap(); + let mut mac = + as KeyInit>::new_from_slice(self.session_keys.mac_key.as_ref()).unwrap(); mac.update(&self.mac_chaining_value); mac.update(&[command.command_type.to_u8()]); @@ -519,7 +589,7 @@ impl SecureChannel { // Provide space at the end of the vec for the padding message.extend_from_slice(&[0u8; AES_BLOCK_SIZE]); - let cipher = Aes128::new_from_slice(&self.enc_key).unwrap(); + let cipher = Aes128::new_from_slice(&self.session_keys.enc_key).unwrap(); let icv = compute_icv(&cipher, self.counter); let cbc_encryptor = Aes128CbcEnc::inner_iv_init(cipher, &icv); @@ -548,7 +618,8 @@ impl SecureChannel { assert_eq!(self.security_level, SecurityLevel::Authenticated); let body = response_data.into(); - let mut mac = as KeyInit>::new_from_slice(self.rmac_key.as_ref()).unwrap(); + let mut mac = + as KeyInit>::new_from_slice(self.session_keys.rmac_key.as_ref()).unwrap(); mac.update(&self.mac_chaining_value); mac.update(&[code.to_u8()]); @@ -584,9 +655,7 @@ impl SecureChannel { /// Terminate the session fn terminate(&mut self) { self.security_level = SecurityLevel::Terminated; - self.enc_key.zeroize(); - self.mac_key.zeroize(); - self.rmac_key.zeroize(); + self.session_keys.zeroize(); } } diff --git a/src/session/securechannel/challenge.rs b/src/session/securechannel/challenge.rs index dc03c4ee..3069bb8f 100644 --- a/src/session/securechannel/challenge.rs +++ b/src/session/securechannel/challenge.rs @@ -3,6 +3,9 @@ use rand_core::{OsRng, RngCore}; use serde::{Deserialize, Serialize}; +#[cfg(feature = "yubihsm-auth")] +use crate::session::error::{Error, ErrorKind}; + /// Size of a challenge message pub const CHALLENGE_SIZE: usize = 8; @@ -35,4 +38,41 @@ impl Challenge { pub fn as_slice(&self) -> &[u8] { &self.0 } + + /// Creates `Challenge` from a `yubikey::hsmauth::Challenge`. + /// + /// `YubiKey` firmware 5.4.3 will generate an empty challenge, this will + /// generate one from RNG if we're provided an empty challenge + // Note(baloo): because of the side-effect described above, this is not + // made a regular From. + #[cfg(feature = "yubihsm-auth")] + pub fn from_yubikey_challenge(yc: yubikey::hsmauth::Challenge) -> Self { + if yc.is_empty() { + Self::new() + } else { + let mut challenge = [0u8; CHALLENGE_SIZE]; + challenge.copy_from_slice(yc.as_slice()); + Challenge(challenge) + } + } +} + +#[cfg(feature = "yubihsm-auth")] +impl TryFrom for yubikey::hsmauth::Challenge { + type Error = Error; + + fn try_from(c: Challenge) -> Result { + let mut challenge = yubikey::hsmauth::Challenge::default(); + challenge + .copy_from_slice(c.as_slice()) + .map_err(|e| Error::from(ErrorKind::ProtocolError.context(e)))?; + + Ok(challenge) + } +} + +impl Default for Challenge { + fn default() -> Self { + Self::new() + } } diff --git a/src/session/securechannel/context.rs b/src/session/securechannel/context.rs index 1a50769c..e84a34d1 100644 --- a/src/session/securechannel/context.rs +++ b/src/session/securechannel/context.rs @@ -22,3 +22,10 @@ impl Context { &self.0 } } + +#[cfg(feature = "yubihsm-auth")] +impl From for yubikey::hsmauth::Context { + fn from(context: Context) -> Self { + Self::from_buf(context.0) + } +}