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::certificate::SelfSigned`
|
||||||
- `yubikey::Error::CertificateBuilder`
|
- `yubikey::Error::CertificateBuilder`
|
||||||
- `yubikey::MgmAlgorithmId`
|
- `yubikey::MgmAlgorithmId`
|
||||||
|
- `yubikey::mgm`:
|
||||||
|
- `MgmKey::generate_for`
|
||||||
|
- `MgmKey::get_default`
|
||||||
|
- `impl AsRef<[u8]> for MgmKey`
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- MSRV is now 1.81.
|
- 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`
|
- `rsa 0.10.0-pre.3`
|
||||||
- `sha2 0.11.0-pre.4`
|
- `sha2 0.11.0-pre.4`
|
||||||
- `x509-cert 0.3.0-pre.0`
|
- `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`:
|
- `yubikey::piv`:
|
||||||
- `ManagementAlgorithmId` has been renamed to `SlotAlgorithmId`, and its
|
- `ManagementAlgorithmId` has been renamed to `SlotAlgorithmId`, and its
|
||||||
`ThreeDes` variant has been replaced by `SlotAlgorithmId::Management`
|
`ThreeDes` variant has been replaced by `SlotAlgorithmId::Management`
|
||||||
containing a `yubikey::MgmAlgorithmId`.
|
containing a `yubikey::MgmAlgorithmId`.
|
||||||
- Metadata command returns `Error:NotFound` instead of `Error::GenericError` when the object doesn't exist ([#558]).
|
- 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)
|
## 0.8.0 (2023-08-15)
|
||||||
### Added
|
### Added
|
||||||
- `impl Debug for {Context, YubiKey}` ([#457])
|
- `impl Debug for {Context, YubiKey}` ([#457])
|
||||||
|
|||||||
Generated
+2
@@ -197,6 +197,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "8a23fa214dea9efd4dacee5a5614646b30216ae0f05d4bb51bafb50e9da1c5be"
|
checksum = "8a23fa214dea9efd4dacee5a5614646b30216ae0f05d4bb51bafb50e9da1c5be"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hybrid-array",
|
"hybrid-array",
|
||||||
|
"rand_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1078,6 +1079,7 @@ version = "0.8.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"base16ct",
|
"base16ct",
|
||||||
"bitflags 2.5.0",
|
"bitflags 2.5.0",
|
||||||
|
"cipher",
|
||||||
"der",
|
"der",
|
||||||
"des",
|
"des",
|
||||||
"ecdsa",
|
"ecdsa",
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ x509-cert = { version = "0.3.0-rc.1", features = ["builder", "hazmat"] }
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitflags = "2.5.0"
|
bitflags = "2.5.0"
|
||||||
|
cipher = { version = "0.5.0-rc.0", features = ["rand_core"] }
|
||||||
der = "0.8.0-rc.7"
|
der = "0.8.0-rc.7"
|
||||||
des = "0.9.0-rc.0"
|
des = "0.9.0-rc.0"
|
||||||
elliptic-curve = "0.14.0-rc.7"
|
elliptic-curve = "0.14.0-rc.7"
|
||||||
|
|||||||
+195
-99
@@ -35,17 +35,14 @@ use crate::{
|
|||||||
metadata::{AdminData, ProtectedData},
|
metadata::{AdminData, ProtectedData},
|
||||||
piv::{ManagementSlotId, SlotAlgorithmId},
|
piv::{ManagementSlotId, SlotAlgorithmId},
|
||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
Error, Result, YubiKey,
|
Error, Result, Version, YubiKey,
|
||||||
};
|
};
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use log::error;
|
use cipher::{
|
||||||
use rand_core::{OsRng, RngCore, TryRngCore};
|
typenum::Unsigned, BlockCipherDecrypt, BlockCipherEncrypt, Key, KeyInit, KeySizeUser,
|
||||||
use zeroize::Zeroize;
|
|
||||||
|
|
||||||
use des::{
|
|
||||||
cipher::{BlockCipherDecrypt, BlockCipherEncrypt, KeyInit},
|
|
||||||
TdesEde3,
|
|
||||||
};
|
};
|
||||||
|
use log::error;
|
||||||
|
use rand::TryCryptoRng;
|
||||||
|
|
||||||
#[cfg(feature = "untested")]
|
#[cfg(feature = "untested")]
|
||||||
use {
|
use {
|
||||||
@@ -56,7 +53,7 @@ use {
|
|||||||
TAG_SERIAL, TAG_UNLOCK, TAG_USB_ENABLED, TAG_USB_SUPPORTED, TAG_VERSION,
|
TAG_SERIAL, TAG_UNLOCK, TAG_USB_ENABLED, TAG_USB_SUPPORTED, TAG_VERSION,
|
||||||
},
|
},
|
||||||
serialization::Tlv,
|
serialization::Tlv,
|
||||||
Serial, Version,
|
Serial,
|
||||||
},
|
},
|
||||||
pbkdf2::pbkdf2_hmac,
|
pbkdf2::pbkdf2_hmac,
|
||||||
sha1::Sha1,
|
sha1::Sha1,
|
||||||
@@ -72,17 +69,22 @@ pub(crate) const APPLET_NAME: &str = "YubiKey MGMT";
|
|||||||
#[cfg(feature = "untested")]
|
#[cfg(feature = "untested")]
|
||||||
pub(crate) const APPLET_ID: &[u8] = &[0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17];
|
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
|
/// Size of a DES key
|
||||||
pub(super) const DES_LEN_DES: usize = 8;
|
const DES_LEN_DES: usize = 8;
|
||||||
|
|
||||||
/// Size of a 3DES key
|
/// 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")]
|
#[cfg(feature = "untested")]
|
||||||
const CB_ADMIN_SALT: usize = 16;
|
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
|
/// Number of PBKDF2 iterations to use when deriving from a password
|
||||||
#[cfg(feature = "untested")]
|
#[cfg(feature = "untested")]
|
||||||
const ITER_MGM_PBKDF2: u32 = 10000;
|
const ITER_MGM_PBKDF2: u32 = 10000;
|
||||||
@@ -153,41 +155,101 @@ impl MgmAlgorithmId {
|
|||||||
///
|
///
|
||||||
/// The only supported algorithm for MGM keys is 3DES.
|
/// The only supported algorithm for MGM keys is 3DES.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct MgmKey([u8; DES_LEN_3DES]);
|
pub struct MgmKey(MgmKeyKind);
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum MgmKeyKind {
|
||||||
|
Tdes(Key<des::TdesEde3>),
|
||||||
|
}
|
||||||
|
|
||||||
impl MgmKey {
|
impl MgmKey {
|
||||||
/// Generate a random MGM key
|
/// Generates a random MGM key for the given algorithm.
|
||||||
pub fn generate() -> Self {
|
pub fn generate(alg: MgmAlgorithmId, rng: &mut impl TryCryptoRng) -> Result<Self> {
|
||||||
let mut key_bytes = [0u8; DES_LEN_3DES];
|
match alg {
|
||||||
let mut rng = OsRng.unwrap_err();
|
MgmAlgorithmId::ThreeDes => {
|
||||||
rng.fill_bytes(&mut key_bytes);
|
des::TdesEde3::try_generate_key_with_rng(rng).map(MgmKeyKind::Tdes)
|
||||||
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<Self> {
|
|
||||||
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<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);
|
|
||||||
}
|
}
|
||||||
|
.map_err(|e| {
|
||||||
Ok(Self(key_bytes))
|
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<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 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<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),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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")]
|
#[cfg(feature = "untested")]
|
||||||
pub fn get_derived(yubikey: &mut YubiKey, pin: &[u8]) -> Result<Self> {
|
pub fn get_derived(yubikey: &mut YubiKey, pin: &[u8]) -> Result<Self> {
|
||||||
let txn = yubikey.begin_transaction()?;
|
let txn = yubikey.begin_transaction()?;
|
||||||
@@ -212,9 +274,10 @@ impl MgmKey {
|
|||||||
return Err(Error::GenericError);
|
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);
|
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)
|
/// Get protected management key (MGM)
|
||||||
@@ -234,24 +297,17 @@ impl MgmKey {
|
|||||||
.get_item(TAG_PROTECTED_MGM)
|
.get_item(TAG_PROTECTED_MGM)
|
||||||
.inspect_err(|e| error!("could not read protected MGM from metadata (err: {:?})", e))?;
|
.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!(
|
Error::SizeError => {
|
||||||
"protected data contains MGM, but is the wrong size: {} (expected {})",
|
error!(
|
||||||
item.len(),
|
"protected data contains MGM, but is the wrong size: {} (expected {:?})",
|
||||||
DES_LEN_3DES
|
item.len(),
|
||||||
);
|
alg,
|
||||||
|
);
|
||||||
return Err(Error::AuthenticationError);
|
Error::AuthenticationError
|
||||||
}
|
}
|
||||||
|
_ => e,
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configures the given YubiKey to use this management key.
|
/// Configures the given YubiKey to use this management key.
|
||||||
@@ -380,47 +436,87 @@ impl MgmKey {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encrypt with 3DES key
|
/// Returns the ID used to identify the key algorithm with APDU packets.
|
||||||
pub(crate) fn encrypt(&self, input: &[u8; DES_LEN_DES]) -> [u8; DES_LEN_DES] {
|
pub(crate) fn algorithm_id(&self) -> MgmAlgorithmId {
|
||||||
let mut output = input.to_owned();
|
match &self.0 {
|
||||||
TdesEde3::new(&self.0.into()).encrypt_block((&mut output).into());
|
MgmKeyKind::Tdes(_) => MgmAlgorithmId::ThreeDes,
|
||||||
output
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decrypt with 3DES key
|
/// Returns the key size in bytes.
|
||||||
pub(crate) fn decrypt(&self, input: &[u8; DES_LEN_DES]) -> [u8; DES_LEN_DES] {
|
pub(crate) fn key_size(&self) -> u8 {
|
||||||
let mut output = input.to_owned();
|
match &self.0 {
|
||||||
TdesEde3::new(&self.0.into()).decrypt_block((&mut output).into());
|
MgmKeyKind::Tdes(_) => <des::TdesEde3 as KeySizeUser>::KeySize::U8,
|
||||||
output
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
impl AsRef<[u8]> for MgmKey {
|
||||||
fn as_ref(&self) -> &[u8; DES_LEN_3DES] {
|
fn as_ref(&self) -> &[u8] {
|
||||||
&self.0
|
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},
|
apdu::{Apdu, Ins, StatusWords},
|
||||||
consts::{CB_BUF_MAX, CB_OBJ_MAX},
|
consts::{CB_BUF_MAX, CB_OBJ_MAX},
|
||||||
error::{Error, Result},
|
error::{Error, Result},
|
||||||
mgm::{MgmKey, DES_LEN_3DES},
|
mgm::MgmKey,
|
||||||
otp,
|
otp,
|
||||||
piv::{self, AlgorithmId, SlotId},
|
piv::{self, AlgorithmId, SlotId},
|
||||||
serialization::*,
|
serialization::*,
|
||||||
@@ -251,11 +251,11 @@ impl<'tx> Transaction<'tx> {
|
|||||||
pub fn set_mgm_key(&self, new_key: &MgmKey, require_touch: bool) -> Result<()> {
|
pub fn set_mgm_key(&self, new_key: &MgmKey, require_touch: bool) -> Result<()> {
|
||||||
let p2 = if require_touch { 0xfe } else { 0xff };
|
let p2 = if require_touch { 0xfe } else { 0xff };
|
||||||
|
|
||||||
let mut data = [0u8; DES_LEN_3DES + 3];
|
let mut data = Vec::with_capacity(usize::from(new_key.key_size()) + 3);
|
||||||
data[0] = ALGO_3DES;
|
data.push(new_key.algorithm_id().into());
|
||||||
data[1] = KEY_CARDMGM;
|
data.push(KEY_CARDMGM);
|
||||||
data[2] = DES_LEN_3DES as u8;
|
data.push(new_key.key_size());
|
||||||
data[3..3 + DES_LEN_3DES].copy_from_slice(new_key.as_ref());
|
data.extend_from_slice(new_key.as_ref());
|
||||||
|
|
||||||
let status_words = Apdu::new(Ins::SetMgmKey)
|
let status_words = Apdu::new(Ins::SetMgmKey)
|
||||||
.params(0xff, p2)
|
.params(0xff, p2)
|
||||||
|
|||||||
+26
-25
@@ -68,6 +68,7 @@ use {
|
|||||||
pub(crate) const ADMIN_FLAGS_1_PUK_BLOCKED: u8 = 0x01;
|
pub(crate) const ADMIN_FLAGS_1_PUK_BLOCKED: u8 = 0x01;
|
||||||
|
|
||||||
/// 3DES authentication
|
/// 3DES authentication
|
||||||
|
#[cfg(feature = "untested")]
|
||||||
pub(crate) const ALGO_3DES: u8 = 0x03;
|
pub(crate) const ALGO_3DES: u8 = 0x03;
|
||||||
|
|
||||||
/// Card management key
|
/// Card management key
|
||||||
@@ -410,38 +411,45 @@ impl YubiKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Authenticate to the card using the provided management key (MGM).
|
/// 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()?;
|
let txn = self.begin_transaction()?;
|
||||||
|
|
||||||
// get a challenge from the card
|
// get a challenge from the card
|
||||||
let challenge = Apdu::new(Ins::Authenticate)
|
let card_response = Apdu::new(Ins::Authenticate)
|
||||||
.params(ALGO_3DES, KEY_CARDMGM)
|
.params(mgm_key.algorithm_id().into(), KEY_CARDMGM)
|
||||||
.data([TAG_DYN_AUTH, 0x02, 0x80, 0x00])
|
.data([TAG_DYN_AUTH, 0x02, 0x80, 0x00])
|
||||||
.transmit(&txn, 261)?;
|
.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);
|
return Err(Error::AuthenticationError);
|
||||||
}
|
}
|
||||||
|
|
||||||
// send a response to the cards challenge and a challenge of our own.
|
// 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];
|
// If this exceeds a `u8` then the card is giving us unexpected data.
|
||||||
data[0] = TAG_DYN_AUTH;
|
let auth_len = (2 + challenge_len + 2 + challenge_len)
|
||||||
data[1] = 20; // 2 + 8 + 2 +8
|
.try_into()
|
||||||
data[2] = 0x80;
|
.map_err(|_| Error::AuthenticationError)?;
|
||||||
data[3] = 8;
|
|
||||||
data[4..12].copy_from_slice(&response);
|
let mut data = Vec::with_capacity(4 + challenge_len + 2 + challenge_len);
|
||||||
data[12] = 0x81;
|
data.push(TAG_DYN_AUTH);
|
||||||
data[13] = 8;
|
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();
|
let mut rng = OsRng.unwrap_err();
|
||||||
rng.fill_bytes(&mut data[14..22]);
|
rng.fill_bytes(&mut host_challenge);
|
||||||
|
|
||||||
let mut challenge = [0u8; 8];
|
data.extend_from_slice(&host_challenge);
|
||||||
challenge.copy_from_slice(&data[14..22]);
|
|
||||||
|
|
||||||
let authentication = Apdu::new(Ins::Authenticate)
|
let authentication = Apdu::new(Ins::Authenticate)
|
||||||
.params(ALGO_3DES, KEY_CARDMGM)
|
.params(mgm_key.algorithm_id().into(), KEY_CARDMGM)
|
||||||
.data(data)
|
.data(data)
|
||||||
.transmit(&txn, 261)?;
|
.transmit(&txn, 261)?;
|
||||||
|
|
||||||
@@ -450,14 +458,7 @@ impl YubiKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// compare the response from the card with our challenge
|
// compare the response from the card with our challenge
|
||||||
let response = mgm_key.encrypt(&challenge);
|
mgm_key.check_challenge(&host_challenge, &authentication.data()[4..])
|
||||||
|
|
||||||
use subtle::ConstantTimeEq;
|
|
||||||
if response.ct_eq(&authentication.data()[4..12]).unwrap_u8() != 1 {
|
|
||||||
return Err(Error::AuthenticationError);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the PIV keys contained in this YubiKey.
|
/// Get the PIV keys contained in this YubiKey.
|
||||||
|
|||||||
+24
-15
@@ -114,32 +114,37 @@ fn test_verify_pin() {
|
|||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
#[ignore]
|
||||||
fn test_set_mgmkey() {
|
fn test_set_mgmkey() {
|
||||||
|
let mut rng = OsRng;
|
||||||
let mut yubikey = YUBIKEY.lock().unwrap();
|
let mut yubikey = YUBIKEY.lock().unwrap();
|
||||||
|
let default_key = MgmKey::get_default(&yubikey).unwrap();
|
||||||
|
|
||||||
assert!(yubikey.verify_pin(b"123456").is_ok());
|
assert!(yubikey.verify_pin(b"123456").is_ok());
|
||||||
assert!(MgmKey::get_protected(&mut yubikey).is_err());
|
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.
|
// 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();
|
let protected = MgmKey::get_protected(&mut yubikey).unwrap();
|
||||||
assert!(yubikey.authenticate(MgmKey::default()).is_err());
|
assert!(yubikey.authenticate(&default_key).is_err());
|
||||||
assert!(yubikey.authenticate(protected.clone()).is_ok());
|
assert!(yubikey.authenticate(&protected).is_ok());
|
||||||
|
|
||||||
// Set a manual management key.
|
// 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!(manual.set_manual(&mut yubikey, false).is_ok());
|
||||||
assert!(MgmKey::get_protected(&mut yubikey).is_err());
|
assert!(MgmKey::get_protected(&mut yubikey).is_err());
|
||||||
assert!(yubikey.authenticate(MgmKey::default()).is_err());
|
assert!(yubikey.authenticate(&default_key).is_err());
|
||||||
assert!(yubikey.authenticate(protected.clone()).is_err());
|
assert!(yubikey.authenticate(&protected).is_err());
|
||||||
assert!(yubikey.authenticate(manual.clone()).is_ok());
|
assert!(yubikey.authenticate(&manual).is_ok());
|
||||||
|
|
||||||
// Set back to the default management key.
|
// Set back to the default management key.
|
||||||
assert!(MgmKey::set_default(&mut yubikey).is_ok());
|
assert!(MgmKey::set_default(&mut yubikey).is_ok());
|
||||||
assert!(MgmKey::get_protected(&mut yubikey).is_err());
|
assert!(MgmKey::get_protected(&mut yubikey).is_err());
|
||||||
assert!(yubikey.authenticate(protected).is_err());
|
assert!(yubikey.authenticate(&protected).is_err());
|
||||||
assert!(yubikey.authenticate(manual).is_err());
|
assert!(yubikey.authenticate(&manual).is_err());
|
||||||
assert!(yubikey.authenticate(MgmKey::default()).is_ok());
|
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 {
|
fn generate_self_signed_cert<KT: yubikey_signer::KeyType>() -> Certificate {
|
||||||
let mut yubikey = YUBIKEY.lock().unwrap();
|
let mut yubikey = YUBIKEY.lock().unwrap();
|
||||||
|
let default_key = MgmKey::get_default(&yubikey).unwrap();
|
||||||
|
|
||||||
assert!(yubikey.verify_pin(b"123456").is_ok());
|
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);
|
let slot = SlotId::Retired(RetiredSlotId::R1);
|
||||||
|
|
||||||
@@ -215,8 +221,9 @@ fn generate_self_signed_rsa_cert() {
|
|||||||
fn generate_rsa3072() {
|
fn generate_rsa3072() {
|
||||||
let mut yubikey = YUBIKEY.lock().unwrap();
|
let mut yubikey = YUBIKEY.lock().unwrap();
|
||||||
let version = yubikey.version();
|
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);
|
let slot = SlotId::Retired(RetiredSlotId::R1);
|
||||||
|
|
||||||
@@ -314,9 +321,10 @@ fn test_slot_id_display() {
|
|||||||
#[ignore]
|
#[ignore]
|
||||||
fn test_read_metadata() {
|
fn test_read_metadata() {
|
||||||
let mut yubikey = YUBIKEY.lock().unwrap();
|
let mut yubikey = YUBIKEY.lock().unwrap();
|
||||||
|
let default_key = MgmKey::get_default(&yubikey).unwrap();
|
||||||
|
|
||||||
assert!(yubikey.verify_pin(b"123456").is_ok());
|
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);
|
let slot = SlotId::Retired(RetiredSlotId::R1);
|
||||||
|
|
||||||
@@ -344,9 +352,10 @@ fn test_read_metadata() {
|
|||||||
#[ignore]
|
#[ignore]
|
||||||
fn test_read_metadata_missing_key() {
|
fn test_read_metadata_missing_key() {
|
||||||
let mut yubikey = YUBIKEY.lock().unwrap();
|
let mut yubikey = YUBIKEY.lock().unwrap();
|
||||||
|
let default_key = MgmKey::get_default(&yubikey).unwrap();
|
||||||
|
|
||||||
assert!(yubikey.verify_pin(b"123456").is_ok());
|
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
|
// we assume that at least one of these slots is empty
|
||||||
let slots_to_check = [
|
let slots_to_check = [
|
||||||
|
|||||||
Reference in New Issue
Block a user