0c7441a81e
Bumps the following dependencies to the latest versions: - `elliptic-curve` v0.13 - `k256` v0.13 - `p256` v0.13 - `p384` v0.13 - `pbkdf2` v0.12 - `rsa` v0.9.0-pre.0 - `signature` v2
435 lines
15 KiB
Rust
435 lines
15 KiB
Rust
//! Management Key (MGM) for authenticating to the YubiKey management applet
|
|
|
|
// Adapted from yubico-piv-tool:
|
|
// <https://github.com/Yubico/yubico-piv-tool/>
|
|
//
|
|
// Copyright (c) 2014-2016 Yubico AB
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are
|
|
// met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
//
|
|
// * Redistributions in binary form must reproduce the above
|
|
// copyright notice, this list of conditions and the following
|
|
// disclaimer in the documentation and/or other materials provided
|
|
// with the distribution.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
use crate::{Error, Result};
|
|
use log::error;
|
|
use rand_core::{OsRng, RngCore};
|
|
use zeroize::{Zeroize, Zeroizing};
|
|
|
|
#[cfg(feature = "untested")]
|
|
use crate::{
|
|
consts::{TAG_ADMIN_FLAGS_1, TAG_ADMIN_SALT, TAG_PROTECTED_MGM},
|
|
metadata::{AdminData, ProtectedData},
|
|
yubikey::YubiKey,
|
|
};
|
|
use des::{
|
|
cipher::{generic_array::GenericArray, BlockDecrypt, BlockEncrypt, KeyInit},
|
|
TdesEde3,
|
|
};
|
|
#[cfg(feature = "untested")]
|
|
use {pbkdf2::pbkdf2_hmac, sha1::Sha1};
|
|
|
|
/// YubiKey MGMT Applet Name
|
|
#[cfg(feature = "untested")]
|
|
pub(crate) const APPLET_NAME: &str = "YubiKey MGMT";
|
|
|
|
/// MGMT Applet ID.
|
|
///
|
|
/// <https://developers.yubico.com/PIV/Introduction/Admin_access.html>
|
|
#[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;
|
|
|
|
#[cfg(feature = "untested")]
|
|
const CB_ADMIN_SALT: usize = 16;
|
|
|
|
/// Size of a DES key
|
|
const DES_LEN_DES: usize = 8;
|
|
|
|
/// Size of a 3DES key
|
|
pub(crate) const DES_LEN_3DES: usize = DES_LEN_DES * 3;
|
|
|
|
/// Number of PBKDF2 iterations to use when deriving from a password
|
|
#[cfg(feature = "untested")]
|
|
const ITER_MGM_PBKDF2: u32 = 10000;
|
|
|
|
/// Management Key (MGM) key types (manual/derived/protected).
|
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
|
pub enum MgmType {
|
|
/// Manual
|
|
Manual = 0,
|
|
|
|
/// Derived
|
|
Derived = 1,
|
|
|
|
/// Protected
|
|
Protected = 2,
|
|
}
|
|
|
|
/// Management Key (MGM).
|
|
///
|
|
/// This key is used to authenticate to the management applet running on
|
|
/// a YubiKey in order to perform administrative functions.
|
|
///
|
|
/// The only supported algorithm for MGM keys is 3DES.
|
|
#[derive(Clone)]
|
|
pub struct MgmKey([u8; DES_LEN_3DES]);
|
|
|
|
impl MgmKey {
|
|
/// Generate a random MGM key
|
|
pub fn generate() -> Self {
|
|
let mut key_bytes = [0u8; DES_LEN_3DES];
|
|
OsRng.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<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 is_weak_key(&key_bytes) {
|
|
error!(
|
|
"blacklisting key '{:?}' since it's weak (with odd parity)",
|
|
&key_bytes
|
|
);
|
|
|
|
return Err(Error::KeyError);
|
|
}
|
|
|
|
Ok(Self(key_bytes))
|
|
}
|
|
|
|
/// Get derived management key (MGM)
|
|
#[cfg(feature = "untested")]
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
|
pub fn get_derived(yubikey: &mut YubiKey, pin: &[u8]) -> Result<Self> {
|
|
let txn = yubikey.begin_transaction()?;
|
|
|
|
// recover management key
|
|
let admin_data = AdminData::read(&txn)?;
|
|
let salt = admin_data.get_item(TAG_ADMIN_SALT)?;
|
|
|
|
if salt.len() != CB_ADMIN_SALT {
|
|
error!(
|
|
"derived MGM salt exists, but is incorrect size: {} (expected {})",
|
|
salt.len(),
|
|
CB_ADMIN_SALT
|
|
);
|
|
|
|
return Err(Error::GenericError);
|
|
}
|
|
|
|
let mut mgm = [0u8; DES_LEN_3DES];
|
|
pbkdf2_hmac::<Sha1>(pin, salt, ITER_MGM_PBKDF2, &mut mgm);
|
|
MgmKey::from_bytes(mgm)
|
|
}
|
|
|
|
/// Get protected management key (MGM)
|
|
#[cfg(feature = "untested")]
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
|
pub fn get_protected(yubikey: &mut YubiKey) -> Result<Self> {
|
|
let txn = yubikey.begin_transaction()?;
|
|
|
|
let protected_data = ProtectedData::read(&txn).map_err(|e| {
|
|
error!("could not read protected data (err: {:?})", e);
|
|
e
|
|
})?;
|
|
|
|
let item = protected_data.get_item(TAG_PROTECTED_MGM).map_err(|e| {
|
|
error!("could not read protected MGM from metadata (err: {:?})", e);
|
|
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.
|
|
#[cfg(feature = "untested")]
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
|
pub fn set_default(yubikey: &mut YubiKey) -> Result<()> {
|
|
MgmKey::default().set_manual(yubikey, false)
|
|
}
|
|
|
|
/// Configures the given YubiKey to use this management key.
|
|
///
|
|
/// The management key must be stored by the user, and provided when performing key
|
|
/// management operations.
|
|
///
|
|
/// This will wipe any metadata related to derived and PIN-protected management keys.
|
|
#[cfg(feature = "untested")]
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
|
pub fn set_manual(&self, yubikey: &mut YubiKey, require_touch: bool) -> Result<()> {
|
|
let txn = yubikey.begin_transaction()?;
|
|
|
|
txn.set_mgm_key(self, require_touch).map_err(|e| {
|
|
// Log a warning, since the device mgm key is corrupt or we're in a state
|
|
// where we can't set the mgm key.
|
|
error!("could not set new derived mgm key, err = {}", e);
|
|
e
|
|
})?;
|
|
|
|
// After this point, we've set the mgm key, so the function should succeed,
|
|
// regardless of being able to set the metadata.
|
|
|
|
if let Ok(mut admin_data) = AdminData::read(&txn) {
|
|
// Clear the protected mgm key bit.
|
|
if let Ok(item) = admin_data.get_item(TAG_ADMIN_FLAGS_1) {
|
|
let mut flags_1 = [0u8; 1];
|
|
if item.len() == flags_1.len() {
|
|
flags_1.copy_from_slice(item);
|
|
flags_1[0] &= !ADMIN_FLAGS_1_PROTECTED_MGM;
|
|
|
|
if let Err(e) = admin_data.set_item(TAG_ADMIN_FLAGS_1, &flags_1) {
|
|
error!("could not set admin flags item, err = {}", e);
|
|
}
|
|
} else {
|
|
error!(
|
|
"admin data flags are an incorrect size: {} (expected {})",
|
|
item.len(),
|
|
flags_1.len()
|
|
);
|
|
}
|
|
}
|
|
|
|
// Remove any existing salt for a derived mgm key.
|
|
if let Err(e) = admin_data.set_item(TAG_ADMIN_SALT, &[]) {
|
|
error!("could not unset derived mgm salt (err = {})", e)
|
|
}
|
|
|
|
if let Err(e) = admin_data.write(&txn) {
|
|
error!("could not write admin data, err = {}", e);
|
|
}
|
|
}
|
|
|
|
// Clear any prior mgm key from protected data.
|
|
if let Ok(mut protected_data) = ProtectedData::read(&txn) {
|
|
if let Err(e) = protected_data.set_item(TAG_PROTECTED_MGM, &[]) {
|
|
error!("could not clear protected mgm item, err = {:?}", e);
|
|
} else if let Err(e) = protected_data.write(&txn) {
|
|
error!("could not write protected data, err = {:?}", e);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Configures the given YubiKey to use this as a PIN-protected management key.
|
|
///
|
|
/// This enables key management operations to be performed with access to the PIN.
|
|
#[cfg(feature = "untested")]
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
|
|
pub fn set_protected(&self, yubikey: &mut YubiKey) -> Result<()> {
|
|
let txn = yubikey.begin_transaction()?;
|
|
|
|
txn.set_mgm_key(self, false).map_err(|e| {
|
|
// log a warning, since the device mgm key is corrupt or we're in
|
|
// a state where we can't set the mgm key
|
|
error!("could not set new derived mgm key, err = {}", e);
|
|
e
|
|
})?;
|
|
|
|
// after this point, we've set the mgm key, so the function should
|
|
// succeed, regardless of being able to set the metadata
|
|
|
|
// Fetch the current protected data, or start a blank metadata blob.
|
|
let mut protected_data = ProtectedData::read(&txn).unwrap_or_default();
|
|
|
|
// Set the new mgm key in protected data.
|
|
if let Err(e) = protected_data.set_item(TAG_PROTECTED_MGM, self.as_ref()) {
|
|
error!("could not set protected mgm item, err = {:?}", e);
|
|
} else {
|
|
protected_data.write(&txn).map_err(|e| {
|
|
error!("could not write protected data, err = {:?}", e);
|
|
e
|
|
})?;
|
|
}
|
|
|
|
// set the protected mgm flag in admin data
|
|
|
|
let mut flags_1 = [0u8; 1];
|
|
|
|
let mut admin_data = if let Ok(mut admin_data) = AdminData::read(&txn) {
|
|
if let Ok(item) = admin_data.get_item(TAG_ADMIN_FLAGS_1) {
|
|
if item.len() == flags_1.len() {
|
|
flags_1.copy_from_slice(item);
|
|
} else {
|
|
error!(
|
|
"admin data flags are an incorrect size: {} (expected {})",
|
|
item.len(),
|
|
flags_1.len()
|
|
);
|
|
}
|
|
} else {
|
|
// flags are not set
|
|
error!("admin data exists, but flags are not present");
|
|
}
|
|
|
|
// remove any existing salt
|
|
if let Err(e) = admin_data.set_item(TAG_ADMIN_SALT, &[]) {
|
|
error!("could not unset derived mgm salt (err = {})", e)
|
|
}
|
|
|
|
admin_data
|
|
} else {
|
|
AdminData::default()
|
|
};
|
|
|
|
flags_1[0] |= ADMIN_FLAGS_1_PROTECTED_MGM;
|
|
|
|
if let Err(e) = admin_data.set_item(TAG_ADMIN_FLAGS_1, &flags_1) {
|
|
error!("could not set admin flags item, err = {}", e);
|
|
} else if let Err(e) = admin_data.write(&txn) {
|
|
error!("could not write admin data, err = {}", e);
|
|
}
|
|
|
|
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(GenericArray::from_slice(&self.0))
|
|
.encrypt_block(GenericArray::from_mut_slice(&mut output));
|
|
output
|
|
}
|
|
|
|
/// 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(GenericArray::from_slice(&self.0))
|
|
.decrypt_block(GenericArray::from_mut_slice(&mut output));
|
|
output
|
|
}
|
|
}
|
|
|
|
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> {
|
|
Self::new(key_bytes.try_into().map_err(|_| Error::SizeError)?)
|
|
}
|
|
}
|
|
|
|
/// Weak and semi weak DES keys as taken from:
|
|
/// %A D.W. Davies
|
|
/// %A W.L. Price
|
|
/// %T Security for Computer Networks
|
|
/// %I John Wiley & Sons
|
|
/// %D 1984
|
|
const WEAK_DES_KEYS: &[[u8; DES_LEN_DES]] = &[
|
|
// weak keys
|
|
[0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01],
|
|
[0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE],
|
|
[0x1F, 0x1F, 0x1F, 0x1F, 0x0E, 0x0E, 0x0E, 0x0E],
|
|
[0xE0, 0xE0, 0xE0, 0xE0, 0xF1, 0xF1, 0xF1, 0xF1],
|
|
// semi-weak keys
|
|
[0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE],
|
|
[0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01],
|
|
[0x1F, 0xE0, 0x1F, 0xE0, 0x0E, 0xF1, 0x0E, 0xF1],
|
|
[0xE0, 0x1F, 0xE0, 0x1F, 0xF1, 0x0E, 0xF1, 0x0E],
|
|
[0x01, 0xE0, 0x01, 0xE0, 0x01, 0xF1, 0x01, 0xF1],
|
|
[0xE0, 0x01, 0xE0, 0x01, 0xF1, 0x01, 0xF1, 0x01],
|
|
[0x1F, 0xFE, 0x1F, 0xFE, 0x0E, 0xFE, 0x0E, 0xFE],
|
|
[0xFE, 0x1F, 0xFE, 0x1F, 0xFE, 0x0E, 0xFE, 0x0E],
|
|
[0x01, 0x1F, 0x01, 0x1F, 0x01, 0x0E, 0x01, 0x0E],
|
|
[0x1F, 0x01, 0x1F, 0x01, 0x0E, 0x01, 0x0E, 0x01],
|
|
[0xE0, 0xFE, 0xE0, 0xFE, 0xF1, 0xFE, 0xF1, 0xFE],
|
|
[0xFE, 0xE0, 0xFE, 0xE0, 0xFE, 0xF1, 0xFE, 0xF1],
|
|
];
|
|
|
|
/// Is this 3DES key weak?
|
|
///
|
|
/// This check is performed automatically when the key is instantiated to
|
|
/// ensure no such keys are used.
|
|
fn is_weak_key(key: &[u8; DES_LEN_3DES]) -> bool {
|
|
// set odd parity of key
|
|
let mut tmp = Zeroizing::new([0u8; DES_LEN_3DES]);
|
|
|
|
for i in 0..DES_LEN_3DES {
|
|
// count number of set bits in byte, excluding the low-order bit - SWAR method
|
|
let mut c = key[i] & 0xFE;
|
|
|
|
c = (c & 0x55) + ((c >> 1) & 0x55);
|
|
c = (c & 0x33) + ((c >> 2) & 0x33);
|
|
c = (c & 0x0F) + ((c >> 4) & 0x0F);
|
|
|
|
// if count is even, set low key bit to 1, otherwise 0
|
|
tmp[i] = (key[i] & 0xFE) | u8::from(c & 0x01 != 0x01);
|
|
}
|
|
|
|
// check odd parity key against table by DES key block
|
|
let mut is_weak = false;
|
|
|
|
for weak_key in WEAK_DES_KEYS.iter() {
|
|
if weak_key == &tmp[0..DES_LEN_DES]
|
|
|| weak_key == &tmp[DES_LEN_DES..2 * DES_LEN_DES]
|
|
|| weak_key == &tmp[2 * DES_LEN_DES..3 * DES_LEN_DES]
|
|
{
|
|
is_weak = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
is_weak
|
|
}
|