diff --git a/CHANGELOG.md b/CHANGELOG.md index 1509cf0..c16056d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `yubikey::certificate::SelfSigned` - `yubikey::Error::CertificateBuilder` - `yubikey::MgmAlgorithmId` +- `yubikey::mgm`: + - `MgmKey::generate_for` + - `MgmKey::get_default` + - `impl AsRef<[u8]> for MgmKey` ### Changed - MSRV is now 1.81. @@ -20,12 +24,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `rsa 0.10.0-pre.3` - `sha2 0.11.0-pre.4` - `x509-cert 0.3.0-pre.0` +- `yubikey::mgm`: + - `MgmKey::generate` now takes a `rand::TryCryptoRng` argument. + - `MgmKey::generate` now requires the caller to specify the key algorithm via + an `MgmAlgorithmId` parameter. + - Use `MgmKey::generate_for` if you want to generate a key using the + preferred algorithm for a given Yubikey's firmware version. + - `MgmKey::from_bytes` now takes an `Option` argument, to + disambiguate algorithms with the same key length. - `yubikey::piv`: - `ManagementAlgorithmId` has been renamed to `SlotAlgorithmId`, and its `ThreeDes` variant has been replaced by `SlotAlgorithmId::Management` containing a `yubikey::MgmAlgorithmId`. - Metadata command returns `Error:NotFound` instead of `Error::GenericError` when the object doesn't exist ([#558]). +### Removed +- `yubikey::mgm`: + - `MgmKey::new` (use `MgmKey::from_bytes(_, Some(MgmAlgorithmId::ThreeDes))` + instead). + - `impl AsRef<[u8; DES_LEN_3DES]> for MgmKey` (use + `impl AsRef<[u8]> for MgmKey` instead). + - `impl Default for MgmKey` (use `MgmKey::get_default` instead). + - `impl TryFrom<&[u8]> for MgmKey` (use `MgmKey::from_bytes` instead). + ## 0.8.0 (2023-08-15) ### Added - `impl Debug for {Context, YubiKey}` ([#457]) diff --git a/Cargo.lock b/Cargo.lock index 001e765..e49dd9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -197,6 +197,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a23fa214dea9efd4dacee5a5614646b30216ae0f05d4bb51bafb50e9da1c5be" dependencies = [ "hybrid-array", + "rand_core", ] [[package]] @@ -1078,6 +1079,7 @@ version = "0.8.0" dependencies = [ "base16ct", "bitflags 2.5.0", + "cipher", "der", "des", "ecdsa", diff --git a/Cargo.toml b/Cargo.toml index 3bb970e..65182f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ x509-cert = { version = "0.3.0-rc.1", features = ["builder", "hazmat"] } [dependencies] bitflags = "2.5.0" +cipher = { version = "0.5.0-rc.0", features = ["rand_core"] } der = "0.8.0-rc.7" des = "0.9.0-rc.0" elliptic-curve = "0.14.0-rc.7" diff --git a/src/mgm.rs b/src/mgm.rs index e82c7d8..0de6303 100644 --- a/src/mgm.rs +++ b/src/mgm.rs @@ -35,17 +35,14 @@ use crate::{ metadata::{AdminData, ProtectedData}, piv::{ManagementSlotId, SlotAlgorithmId}, transaction::Transaction, - Error, Result, YubiKey, + Error, Result, Version, YubiKey, }; use bitflags::bitflags; -use log::error; -use rand_core::{OsRng, RngCore, TryRngCore}; -use zeroize::Zeroize; - -use des::{ - cipher::{BlockCipherDecrypt, BlockCipherEncrypt, KeyInit}, - TdesEde3, +use cipher::{ + typenum::Unsigned, BlockCipherDecrypt, BlockCipherEncrypt, Key, KeyInit, KeySizeUser, }; +use log::error; +use rand::TryCryptoRng; #[cfg(feature = "untested")] use { @@ -56,7 +53,7 @@ use { TAG_SERIAL, TAG_UNLOCK, TAG_USB_ENABLED, TAG_USB_SUPPORTED, TAG_VERSION, }, serialization::Tlv, - Serial, Version, + Serial, }, pbkdf2::pbkdf2_hmac, sha1::Sha1, @@ -72,17 +69,22 @@ pub(crate) const APPLET_NAME: &str = "YubiKey MGMT"; #[cfg(feature = "untested")] pub(crate) const APPLET_ID: &[u8] = &[0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17]; -pub(crate) const ADMIN_FLAGS_1_PROTECTED_MGM: u8 = 0x02; - /// Size of a DES key -pub(super) const DES_LEN_DES: usize = 8; +const DES_LEN_DES: usize = 8; /// Size of a 3DES key -pub(crate) const DES_LEN_3DES: usize = DES_LEN_DES * 3; +pub(super) const DES_LEN_3DES: usize = DES_LEN_DES * 3; + +pub(crate) const ADMIN_FLAGS_1_PROTECTED_MGM: u8 = 0x02; #[cfg(feature = "untested")] const CB_ADMIN_SALT: usize = 16; +/// The default MGM key loaded for both Triple-DES and AES keys +const DEFAULT_MGM_KEY: [u8; 24] = [ + 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, +]; + /// Number of PBKDF2 iterations to use when deriving from a password #[cfg(feature = "untested")] const ITER_MGM_PBKDF2: u32 = 10000; @@ -153,41 +155,101 @@ impl MgmAlgorithmId { /// /// The only supported algorithm for MGM keys is 3DES. #[derive(Clone)] -pub struct MgmKey([u8; DES_LEN_3DES]); +pub struct MgmKey(MgmKeyKind); + +#[derive(Clone)] +enum MgmKeyKind { + Tdes(Key), +} impl MgmKey { - /// Generate a random MGM key - pub fn generate() -> Self { - let mut key_bytes = [0u8; DES_LEN_3DES]; - let mut rng = OsRng.unwrap_err(); - rng.fill_bytes(&mut key_bytes); - Self(key_bytes) - } - - /// Create an MGM key from byte slice. - /// - /// Returns an error if the slice is the wrong size or the key is weak. - pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result { - bytes.as_ref().try_into() - } - - /// Create an MGM key from the given byte array. - /// - /// Returns an error if the key is weak. - pub fn new(key_bytes: [u8; DES_LEN_3DES]) -> Result { - if TdesEde3::weak_key_test(key_bytes.as_ref()).is_err() { - error!( - "blacklisting key '{:?}' since it's weak (with odd parity)", - &key_bytes - ); - - return Err(Error::KeyError); + /// Generates a random MGM key for the given algorithm. + pub fn generate(alg: MgmAlgorithmId, rng: &mut impl TryCryptoRng) -> Result { + match alg { + MgmAlgorithmId::ThreeDes => { + des::TdesEde3::try_generate_key_with_rng(rng).map(MgmKeyKind::Tdes) + } } - - Ok(Self(key_bytes)) + .map_err(|e| { + error!("RNG failure: {}", e); + Error::KeyError + }) + .map(Self) } - /// Get derived management key (MGM) + /// Generates a random MGM key using the preferred algorithm for the given Yubikey's + /// firmware version. + pub fn generate_for(yubikey: &YubiKey, rng: &mut impl TryCryptoRng) -> Result { + match yubikey.version() { + // Initial firmware versions default to 3DES. + Version { major: ..=4, .. } + | Version { + major: 5, + minor: ..=6, + .. + } => Self::generate(MgmAlgorithmId::ThreeDes, rng), + // Firmware 5.7.0 and above default to AES-192. + Version { + major: 5, + minor: 7.., + .. + } + | Version { major: 6.., .. } => Err(Error::NotSupported), + } + } + + /// Parses an MGM key from the given byte slice. + /// + /// Returns an error if the slice is an invalid size or the key is weak. + /// + /// If `alg` is `None`, the algorithm will be selected based on the length of the + /// slice, returning an error if there is not a unique match. + pub fn from_bytes(bytes: impl AsRef<[u8]>, alg: Option) -> Result { + match alg { + Some(alg) => Self::parse_key(alg, bytes), + None => match bytes.as_ref().len() { + DES_LEN_3DES => Self::parse_key(MgmAlgorithmId::ThreeDes, bytes), + _ => Err(Error::ParseError), + }, + } + } + + /// Gets the default management key for the given Yubikey's firmware version. + /// + /// Returns an error if the Yubikey's default algorithm is unsupported. + pub fn get_default(yubikey: &YubiKey) -> Result { + match yubikey.version() { + // Initial firmware versions default to 3DES. + Version { major: ..=4, .. } + | Version { + major: 5, + minor: ..=6, + .. + } => Ok(Self(MgmKeyKind::Tdes(DEFAULT_MGM_KEY.into()))), + // Firmware 5.7.0 and above default to AES-192. + Version { + major: 5, + minor: 7.., + .. + } + | Version { major: 6.., .. } => Err(Error::NotSupported), + } + } + + /// Resets the management key for the given YubiKey to the default value for that + /// Yubikey's firmware version. + /// + /// This will wipe any metadata related to derived and PIN-protected management keys. + pub fn set_default(yubikey: &mut YubiKey) -> Result<()> { + Self::get_default(yubikey)?.set_manual(yubikey, false) + } + + /// Derives a 3DES management key (MGM) from a stored salt. + /// + /// # Security + /// + /// Warning: PIN-derived mode is not secure. You should not use this technique. It is + /// offered only for backwards compatibility. #[cfg(feature = "untested")] pub fn get_derived(yubikey: &mut YubiKey, pin: &[u8]) -> Result { let txn = yubikey.begin_transaction()?; @@ -212,9 +274,10 @@ impl MgmKey { return Err(Error::GenericError); } - let mut mgm = [0u8; DES_LEN_3DES]; + let mut mgm = Key::::default(); pbkdf2_hmac::(pin, salt, ITER_MGM_PBKDF2, &mut mgm); - MgmKey::from_bytes(mgm) + des::TdesEde3::weak_key_test(&mgm).map_err(|_| Error::KeyError)?; + Ok(Self(MgmKeyKind::Tdes(mgm))) } /// Get protected management key (MGM) @@ -234,24 +297,17 @@ impl MgmKey { .get_item(TAG_PROTECTED_MGM) .inspect_err(|e| error!("could not read protected MGM from metadata (err: {:?})", e))?; - if item.len() != DES_LEN_3DES { - error!( - "protected data contains MGM, but is the wrong size: {} (expected {})", - item.len(), - DES_LEN_3DES - ); - - return Err(Error::AuthenticationError); - } - - MgmKey::from_bytes(item) - } - - /// Resets the management key for the given YubiKey to the default value. - /// - /// This will wipe any metadata related to derived and PIN-protected management keys. - pub fn set_default(yubikey: &mut YubiKey) -> Result<()> { - MgmKey::default().set_manual(yubikey, false) + Self::parse_key(alg, item).map_err(|e| match e { + Error::SizeError => { + error!( + "protected data contains MGM, but is the wrong size: {} (expected {:?})", + item.len(), + alg, + ); + Error::AuthenticationError + } + _ => e, + }) } /// Configures the given YubiKey to use this management key. @@ -380,47 +436,87 @@ impl MgmKey { Ok(()) } - /// Encrypt with 3DES key - pub(crate) fn encrypt(&self, input: &[u8; DES_LEN_DES]) -> [u8; DES_LEN_DES] { - let mut output = input.to_owned(); - TdesEde3::new(&self.0.into()).encrypt_block((&mut output).into()); - output + /// Returns the ID used to identify the key algorithm with APDU packets. + pub(crate) fn algorithm_id(&self) -> MgmAlgorithmId { + match &self.0 { + MgmKeyKind::Tdes(_) => MgmAlgorithmId::ThreeDes, + } } - /// Decrypt with 3DES key - pub(crate) fn decrypt(&self, input: &[u8; DES_LEN_DES]) -> [u8; DES_LEN_DES] { - let mut output = input.to_owned(); - TdesEde3::new(&self.0.into()).decrypt_block((&mut output).into()); - output + /// Returns the key size in bytes. + pub(crate) fn key_size(&self) -> u8 { + match &self.0 { + MgmKeyKind::Tdes(_) => ::KeySize::U8, + } + } + + /// Parses an MGM key from the given byte slice. + /// + /// Returns an error if the algorithm is unsupported, or the slice is the wrong size, + /// or the key is weak. + fn parse_key(alg: MgmAlgorithmId, bytes: impl AsRef<[u8]>) -> Result { + match alg { + MgmAlgorithmId::ThreeDes => { + let key = + Key::::try_from(bytes.as_ref()).map_err(|_| Error::SizeError)?; + des::TdesEde3::weak_key_test(&key).map_err(|_| Error::KeyError)?; + Ok(MgmKeyKind::Tdes(key)) + } + } + .map(Self) + } + + /// Encrypts a block with this key. + /// + /// Returns an error if the block is the wrong size. + fn encrypt_block(&self, block: &mut [u8]) -> Result<()> { + match &self.0 { + MgmKeyKind::Tdes(k) => { + des::TdesEde3::new(k).encrypt_block(block.try_into().map_err(|_| Error::SizeError)?) + } + } + Ok(()) + } + + /// Decrypts a block with this key. + /// + /// Returns an error if the block is the wrong size. + fn decrypt_block(&self, block: &mut [u8]) -> Result<()> { + match &self.0 { + MgmKeyKind::Tdes(k) => { + des::TdesEde3::new(k).decrypt_block(block.try_into().map_err(|_| Error::SizeError)?) + } + } + Ok(()) + } + + /// Given a challenge from a card, decrypts it and return the value + pub(crate) fn card_challenge(&self, challenge: &[u8]) -> Result> { + let mut output = challenge.to_owned(); + self.decrypt_block(output.as_mut_slice())?; + Ok(output) + } + + /// Checks the authentication matches the challenge and auth data + pub(crate) fn check_challenge(&self, challenge: &[u8], auth_data: &[u8]) -> Result<()> { + let mut response = challenge.to_owned(); + + self.encrypt_block(response.as_mut_slice())?; + + use subtle::ConstantTimeEq; + if response.ct_eq(auth_data).unwrap_u8() != 1 { + return Err(Error::AuthenticationError); + } + + Ok(()) } } -impl AsRef<[u8; DES_LEN_3DES]> for MgmKey { - fn as_ref(&self) -> &[u8; DES_LEN_3DES] { - &self.0 - } -} - -/// Default MGM key configured on all YubiKeys -impl Default for MgmKey { - fn default() -> Self { - MgmKey([ - 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, - ]) - } -} - -impl Drop for MgmKey { - fn drop(&mut self) { - self.0.zeroize(); - } -} - -impl<'a> TryFrom<&'a [u8]> for MgmKey { - type Error = Error; - - fn try_from(key_bytes: &'a [u8]) -> Result { - Self::new(key_bytes.try_into().map_err(|_| Error::SizeError)?) +impl AsRef<[u8]> for MgmKey { + fn as_ref(&self) -> &[u8] { + match &self.0 { + MgmKeyKind::Tdes(k) => k.as_ref(), + } } } diff --git a/src/transaction.rs b/src/transaction.rs index 47ee95c..bc046f8 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -5,7 +5,7 @@ use crate::{ apdu::{Apdu, Ins, StatusWords}, consts::{CB_BUF_MAX, CB_OBJ_MAX}, error::{Error, Result}, - mgm::{MgmKey, DES_LEN_3DES}, + mgm::MgmKey, otp, piv::{self, AlgorithmId, SlotId}, serialization::*, @@ -251,11 +251,11 @@ impl<'tx> Transaction<'tx> { pub fn set_mgm_key(&self, new_key: &MgmKey, require_touch: bool) -> Result<()> { let p2 = if require_touch { 0xfe } else { 0xff }; - let mut data = [0u8; DES_LEN_3DES + 3]; - data[0] = ALGO_3DES; - data[1] = KEY_CARDMGM; - data[2] = DES_LEN_3DES as u8; - data[3..3 + DES_LEN_3DES].copy_from_slice(new_key.as_ref()); + let mut data = Vec::with_capacity(usize::from(new_key.key_size()) + 3); + data.push(new_key.algorithm_id().into()); + data.push(KEY_CARDMGM); + data.push(new_key.key_size()); + data.extend_from_slice(new_key.as_ref()); let status_words = Apdu::new(Ins::SetMgmKey) .params(0xff, p2) diff --git a/src/yubikey.rs b/src/yubikey.rs index c7dabd2..a0fb370 100644 --- a/src/yubikey.rs +++ b/src/yubikey.rs @@ -68,6 +68,7 @@ use { pub(crate) const ADMIN_FLAGS_1_PUK_BLOCKED: u8 = 0x01; /// 3DES authentication +#[cfg(feature = "untested")] pub(crate) const ALGO_3DES: u8 = 0x03; /// Card management key @@ -410,38 +411,45 @@ impl YubiKey { } /// Authenticate to the card using the provided management key (MGM). - pub fn authenticate(&mut self, mgm_key: MgmKey) -> Result<()> { + pub fn authenticate(&mut self, mgm_key: &MgmKey) -> Result<()> { let txn = self.begin_transaction()?; // get a challenge from the card - let challenge = Apdu::new(Ins::Authenticate) - .params(ALGO_3DES, KEY_CARDMGM) + let card_response = Apdu::new(Ins::Authenticate) + .params(mgm_key.algorithm_id().into(), KEY_CARDMGM) .data([TAG_DYN_AUTH, 0x02, 0x80, 0x00]) .transmit(&txn, 261)?; - if !challenge.is_success() || challenge.data().len() < 12 { + if !card_response.is_success() || card_response.data().len() < 5 { return Err(Error::AuthenticationError); } // send a response to the cards challenge and a challenge of our own. - let response = mgm_key.decrypt(challenge.data()[4..12].try_into()?); + let card_challenge = mgm_key.card_challenge(&card_response.data()[4..])?; + let challenge_len = card_challenge.len(); - let mut data = [0u8; 22]; - data[0] = TAG_DYN_AUTH; - data[1] = 20; // 2 + 8 + 2 +8 - data[2] = 0x80; - data[3] = 8; - data[4..12].copy_from_slice(&response); - data[12] = 0x81; - data[13] = 8; + // If this exceeds a `u8` then the card is giving us unexpected data. + let auth_len = (2 + challenge_len + 2 + challenge_len) + .try_into() + .map_err(|_| Error::AuthenticationError)?; + + let mut data = Vec::with_capacity(4 + challenge_len + 2 + challenge_len); + data.push(TAG_DYN_AUTH); + data.push(auth_len); + data.push(0x80); + data.push(challenge_len as u8); + data.extend_from_slice(&card_challenge); + data.push(0x81); + data.push(challenge_len as u8); + + let mut host_challenge = vec![0u8; challenge_len]; let mut rng = OsRng.unwrap_err(); - rng.fill_bytes(&mut data[14..22]); + rng.fill_bytes(&mut host_challenge); - let mut challenge = [0u8; 8]; - challenge.copy_from_slice(&data[14..22]); + data.extend_from_slice(&host_challenge); let authentication = Apdu::new(Ins::Authenticate) - .params(ALGO_3DES, KEY_CARDMGM) + .params(mgm_key.algorithm_id().into(), KEY_CARDMGM) .data(data) .transmit(&txn, 261)?; @@ -450,14 +458,7 @@ impl YubiKey { } // compare the response from the card with our challenge - let response = mgm_key.encrypt(&challenge); - - use subtle::ConstantTimeEq; - if response.ct_eq(&authentication.data()[4..12]).unwrap_u8() != 1 { - return Err(Error::AuthenticationError); - } - - Ok(()) + mgm_key.check_challenge(&host_challenge, &authentication.data()[4..]) } /// Get the PIV keys contained in this YubiKey. diff --git a/tests/integration.rs b/tests/integration.rs index e3c7164..742cb3a 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -114,32 +114,37 @@ fn test_verify_pin() { #[test] #[ignore] fn test_set_mgmkey() { + let mut rng = OsRng; let mut yubikey = YUBIKEY.lock().unwrap(); + let default_key = MgmKey::get_default(&yubikey).unwrap(); assert!(yubikey.verify_pin(b"123456").is_ok()); assert!(MgmKey::get_protected(&mut yubikey).is_err()); - assert!(yubikey.authenticate(MgmKey::default()).is_ok()); + assert!(yubikey.authenticate(&default_key).is_ok()); // Set a protected management key. - assert!(MgmKey::generate().set_protected(&mut yubikey).is_ok()); + assert!(MgmKey::generate_for(&yubikey, &mut rng) + .unwrap() + .set_protected(&mut yubikey) + .is_ok()); let protected = MgmKey::get_protected(&mut yubikey).unwrap(); - assert!(yubikey.authenticate(MgmKey::default()).is_err()); - assert!(yubikey.authenticate(protected.clone()).is_ok()); + assert!(yubikey.authenticate(&default_key).is_err()); + assert!(yubikey.authenticate(&protected).is_ok()); // Set a manual management key. - let manual = MgmKey::generate(); + let manual = MgmKey::generate_for(&yubikey, &mut rng).unwrap(); assert!(manual.set_manual(&mut yubikey, false).is_ok()); assert!(MgmKey::get_protected(&mut yubikey).is_err()); - assert!(yubikey.authenticate(MgmKey::default()).is_err()); - assert!(yubikey.authenticate(protected.clone()).is_err()); - assert!(yubikey.authenticate(manual.clone()).is_ok()); + assert!(yubikey.authenticate(&default_key).is_err()); + assert!(yubikey.authenticate(&protected).is_err()); + assert!(yubikey.authenticate(&manual).is_ok()); // Set back to the default management key. assert!(MgmKey::set_default(&mut yubikey).is_ok()); assert!(MgmKey::get_protected(&mut yubikey).is_err()); - assert!(yubikey.authenticate(protected).is_err()); - assert!(yubikey.authenticate(manual).is_err()); - assert!(yubikey.authenticate(MgmKey::default()).is_ok()); + assert!(yubikey.authenticate(&protected).is_err()); + assert!(yubikey.authenticate(&manual).is_err()); + assert!(yubikey.authenticate(&default_key).is_ok()); } // @@ -148,9 +153,10 @@ fn test_set_mgmkey() { fn generate_self_signed_cert() -> Certificate { let mut yubikey = YUBIKEY.lock().unwrap(); + let default_key = MgmKey::get_default(&yubikey).unwrap(); assert!(yubikey.verify_pin(b"123456").is_ok()); - assert!(yubikey.authenticate(MgmKey::default()).is_ok()); + assert!(yubikey.authenticate(&default_key).is_ok()); let slot = SlotId::Retired(RetiredSlotId::R1); @@ -215,8 +221,9 @@ fn generate_self_signed_rsa_cert() { fn generate_rsa3072() { let mut yubikey = YUBIKEY.lock().unwrap(); let version = yubikey.version(); + let default_key = MgmKey::get_default(&yubikey).unwrap(); - assert!(yubikey.authenticate(MgmKey::default()).is_ok()); + assert!(yubikey.authenticate(&default_key).is_ok()); let slot = SlotId::Retired(RetiredSlotId::R1); @@ -314,9 +321,10 @@ fn test_slot_id_display() { #[ignore] fn test_read_metadata() { let mut yubikey = YUBIKEY.lock().unwrap(); + let default_key = MgmKey::get_default(&yubikey).unwrap(); assert!(yubikey.verify_pin(b"123456").is_ok()); - assert!(yubikey.authenticate(MgmKey::default()).is_ok()); + assert!(yubikey.authenticate(&default_key).is_ok()); let slot = SlotId::Retired(RetiredSlotId::R1); @@ -344,9 +352,10 @@ fn test_read_metadata() { #[ignore] fn test_read_metadata_missing_key() { let mut yubikey = YUBIKEY.lock().unwrap(); + let default_key = MgmKey::get_default(&yubikey).unwrap(); assert!(yubikey.verify_pin(b"123456").is_ok()); - assert!(yubikey.authenticate(MgmKey::default()).is_ok()); + assert!(yubikey.authenticate(&default_key).is_ok()); // we assume that at least one of these slots is empty let slots_to_check = [