mgm: Generalize TDES logic to enable other algorithms (#625)
Co-authored-by: Jack Grigg <thestr4d@gmail.com> Co-authored-by: Greg Bowyer <gbowyer@fastmail.co.uk>
This commit is contained in:
committed by
GitHub
parent
7eb7a31a28
commit
1e1fe34734
@@ -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<MgmAlgorithmId>` 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])
|
||||
|
||||
Generated
+2
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
+185
-89
@@ -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<des::TdesEde3>),
|
||||
}
|
||||
|
||||
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)
|
||||
/// Generates a random MGM key for the given algorithm.
|
||||
pub fn generate(alg: MgmAlgorithmId, rng: &mut impl TryCryptoRng) -> Result<Self> {
|
||||
match alg {
|
||||
MgmAlgorithmId::ThreeDes => {
|
||||
des::TdesEde3::try_generate_key_with_rng(rng).map(MgmKeyKind::Tdes)
|
||||
}
|
||||
}
|
||||
.map_err(|e| {
|
||||
error!("RNG failure: {}", e);
|
||||
Error::KeyError
|
||||
})
|
||||
.map(Self)
|
||||
}
|
||||
|
||||
/// Create an MGM key from byte slice.
|
||||
/// 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<Self> {
|
||||
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 the wrong size or the key is weak.
|
||||
pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self> {
|
||||
bytes.as_ref().try_into()
|
||||
}
|
||||
|
||||
/// Create an MGM key from the given byte array.
|
||||
/// Returns an error if the slice is an invalid size or the key is weak.
|
||||
///
|
||||
/// Returns an error if the key is weak.
|
||||
pub fn new(key_bytes: [u8; DES_LEN_3DES]) -> Result<Self> {
|
||||
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);
|
||||
/// 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<MgmAlgorithmId>) -> Result<Self> {
|
||||
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),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self(key_bytes))
|
||||
/// 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<Self> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get derived management key (MGM)
|
||||
/// 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<Self> {
|
||||
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::<des::TdesEde3>::default();
|
||||
pbkdf2_hmac::<Sha1>(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 {
|
||||
Self::parse_key(alg, item).map_err(|e| match e {
|
||||
Error::SizeError => {
|
||||
error!(
|
||||
"protected data contains MGM, but is the wrong size: {} (expected {})",
|
||||
"protected data contains MGM, but is the wrong size: {} (expected {:?})",
|
||||
item.len(),
|
||||
DES_LEN_3DES
|
||||
alg,
|
||||
);
|
||||
|
||||
return Err(Error::AuthenticationError);
|
||||
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)
|
||||
_ => 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(_) => <des::TdesEde3 as KeySizeUser>::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<Self> {
|
||||
match alg {
|
||||
MgmAlgorithmId::ThreeDes => {
|
||||
let key =
|
||||
Key::<des::TdesEde3>::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<Vec<u8>> {
|
||||
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
|
||||
impl AsRef<[u8]> for MgmKey {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
match &self.0 {
|
||||
MgmKeyKind::Tdes(k) => k.as_ref(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
Self::new(key_bytes.try_into().map_err(|_| Error::SizeError)?)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -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)
|
||||
|
||||
+26
-25
@@ -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.
|
||||
|
||||
+24
-15
@@ -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<KT: yubikey_signer::KeyType>() -> 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 = [
|
||||
|
||||
Reference in New Issue
Block a user