Applets management (#568)

This commit is contained in:
Arthur Gautier
2025-02-11 18:13:01 -08:00
committed by GitHub
parent 235eb6215e
commit 13bdf9a585
9 changed files with 684 additions and 54 deletions
Generated
+8 -1
View File
@@ -93,6 +93,12 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.11.0-rc.3" version = "0.11.0-rc.3"
@@ -549,7 +555,7 @@ version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37cab0be9d04e808a8d8059fa54befcd71dc8b168f9f0c04bdb7e59832abbab4" checksum = "37cab0be9d04e808a8d8059fa54befcd71dc8b168f9f0c04bdb7e59832abbab4"
dependencies = [ dependencies = [
"bitflags", "bitflags 1.3.2",
"pcsc-sys", "pcsc-sys",
] ]
@@ -1094,6 +1100,7 @@ name = "yubikey"
version = "0.8.0" version = "0.8.0"
dependencies = [ dependencies = [
"base16ct", "base16ct",
"bitflags 2.5.0",
"der", "der",
"des", "des",
"ecdsa", "ecdsa",
+1
View File
@@ -24,6 +24,7 @@ sha2 = "=0.11.0-pre.4"
x509-cert = { version = "=0.3.0-pre.0", features = [ "builder", "hazmat" ] } x509-cert = { version = "=0.3.0-pre.0", features = [ "builder", "hazmat" ] }
[dependencies] [dependencies]
bitflags = "2.5.0"
der = "=0.8.0-rc.1" der = "=0.8.0-rc.1"
des = "=0.9.0-pre.2" des = "=0.9.0-pre.2"
elliptic-curve = "=0.14.0-rc.1" elliptic-curve = "=0.14.0-rc.1"
+10
View File
@@ -0,0 +1,10 @@
#![cfg(feature = "untested")]
use yubikey::{mgm, YubiKey};
fn main() {
let yubikey = YubiKey::open().unwrap();
let mut mgmt = mgm::Manager::new(yubikey).unwrap();
mgmt.enable_yubihsm().unwrap();
}
+20
View File
@@ -204,6 +204,15 @@ pub enum Ins {
/// Get slot metadata /// Get slot metadata
GetMetadata, GetMetadata,
/// Management // Read Config
ReadConfig,
/// Management // Write Config
WriteConfig,
/// Management // DeviceReset
DeviceReset,
/// Other/unrecognized instruction codes /// Other/unrecognized instruction codes
Other(u8), Other(u8),
} }
@@ -229,6 +238,12 @@ impl Ins {
Ins::Attest => 0xf9, Ins::Attest => 0xf9,
Ins::GetSerial => 0xf8, Ins::GetSerial => 0xf8,
Ins::GetMetadata => 0xf7, Ins::GetMetadata => 0xf7,
// Management
Ins::ReadConfig => 0x1d,
Ins::WriteConfig => 0x1c,
Ins::DeviceReset => 0x1f,
Ins::Other(code) => code, Ins::Other(code) => code,
} }
} }
@@ -237,6 +252,11 @@ impl Ins {
impl From<u8> for Ins { impl From<u8> for Ins {
fn from(code: u8) -> Self { fn from(code: u8) -> Self {
match code { match code {
// Management
0x1d => Ins::ReadConfig,
0x1c => Ins::WriteConfig,
0x1f => Ins::DeviceReset,
0x20 => Ins::Verify, 0x20 => Ins::Verify,
0x24 => Ins::ChangeReference, 0x24 => Ins::ChangeReference,
0x2c => Ins::ResetRetry, 0x2c => Ins::ResetRetry,
+17
View File
@@ -1,3 +1,4 @@
#![allow(dead_code)]
//! Miscellaneous constant values //! Miscellaneous constant values
/// YubiKey max buffer size /// YubiKey max buffer size
@@ -18,3 +19,19 @@ pub(crate) const TAG_ADMIN_TIMESTAMP: u8 = 0x83;
// Protected tags // Protected tags
pub(crate) const TAG_PROTECTED_FLAGS_1: u8 = 0x81; pub(crate) const TAG_PROTECTED_FLAGS_1: u8 = 0x81;
pub(crate) const TAG_PROTECTED_MGM: u8 = 0x89; pub(crate) const TAG_PROTECTED_MGM: u8 = 0x89;
// Management
pub(crate) const TAG_USB_SUPPORTED: u8 = 0x01;
pub(crate) const TAG_SERIAL: u8 = 0x02;
pub(crate) const TAG_USB_ENABLED: u8 = 0x03;
pub(crate) const TAG_FORM_FACTOR: u8 = 0x04;
pub(crate) const TAG_VERSION: u8 = 0x05;
pub(crate) const TAG_AUTO_EJECT_TIMEOUT: u8 = 0x06;
pub(crate) const TAG_CHALRESP_TIMEOUT: u8 = 0x07;
pub(crate) const TAG_DEVICE_FLAGS: u8 = 0x08;
pub(crate) const TAG_APP_VERSIONS: u8 = 0x09;
pub(crate) const TAG_CONFIG_LOCK: u8 = 0x0A;
pub(crate) const TAG_UNLOCK: u8 = 0x0B;
pub(crate) const TAG_REBOOT: u8 = 0x0C;
pub(crate) const TAG_NFC_SUPPORTED: u8 = 0x0D;
pub(crate) const TAG_NFC_ENABLED: u8 = 0x0E;
+1 -1
View File
@@ -51,7 +51,7 @@ mod config;
mod consts; mod consts;
mod error; mod error;
mod metadata; mod metadata;
mod mgm; pub mod mgm;
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
mod mscmap; mod mscmap;
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
+483 -9
View File
@@ -35,20 +35,30 @@ use log::error;
use rand_core::{OsRng, RngCore}; use rand_core::{OsRng, RngCore};
use zeroize::Zeroize; use zeroize::Zeroize;
#[cfg(feature = "untested")]
use crate::{
consts::{TAG_ADMIN_FLAGS_1, TAG_ADMIN_SALT, TAG_PROTECTED_MGM},
metadata::{AdminData, ProtectedData},
piv::{ManagementSlotId, SlotAlgorithmId},
transaction::Transaction,
yubikey::YubiKey,
};
use des::{ use des::{
cipher::{BlockCipherDecrypt, BlockCipherEncrypt, KeyInit}, cipher::{BlockCipherDecrypt, BlockCipherEncrypt, KeyInit},
TdesEde3, TdesEde3,
}; };
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
use {pbkdf2::pbkdf2_hmac, sha1::Sha1}; use {
crate::{
consts::{
CB_BUF_MAX, TAG_ADMIN_FLAGS_1, TAG_ADMIN_SALT, TAG_AUTO_EJECT_TIMEOUT,
TAG_CHALRESP_TIMEOUT, TAG_CONFIG_LOCK, TAG_DEVICE_FLAGS, TAG_FORM_FACTOR,
TAG_NFC_ENABLED, TAG_NFC_SUPPORTED, TAG_PROTECTED_MGM, TAG_REBOOT, TAG_SERIAL,
TAG_UNLOCK, TAG_USB_ENABLED, TAG_USB_SUPPORTED, TAG_VERSION,
},
metadata::{AdminData, ProtectedData},
piv::{ManagementSlotId, SlotAlgorithmId},
serialization::Tlv,
transaction::Transaction,
yubikey::YubiKey,
Serial, Version,
},
bitflags::bitflags,
pbkdf2::pbkdf2_hmac,
sha1::Sha1,
};
/// YubiKey MGMT Applet Name /// YubiKey MGMT Applet Name
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
@@ -413,3 +423,467 @@ impl<'a> TryFrom<&'a [u8]> for MgmKey {
Self::new(key_bytes.try_into().map_err(|_| Error::SizeError)?) Self::new(key_bytes.try_into().map_err(|_| Error::SizeError)?)
} }
} }
/// Manager for the YubiKey
/// Allows to enable applications hosted on the YubiKey
#[cfg(feature = "untested")]
pub struct Manager {
client: YubiKey,
}
#[cfg(feature = "untested")]
impl Manager {
/// Open the manager applet on the YubiKey
pub fn new(mut client: YubiKey) -> Result<Self> {
Transaction::new(&mut client.card)?.select_application(
APPLET_ID,
APPLET_NAME,
"failed selecting YkHSM auth application",
)?;
Ok(Self { client })
}
/// Enable YubiHSM applet
pub fn enable_yubihsm(&mut self) -> Result<()> {
let mut config = Transaction::new(&mut self.client.card)?.read_config()?;
config.config.usb_enabled_apps |= Capability::HSMAUTH;
Transaction::new(&mut self.client.card)?.write_config(
self.client.version,
config.config,
None,
None,
)?;
Ok(())
}
/// Enable PIV applet
pub fn enable_piv(&mut self) -> Result<()> {
let mut config = Transaction::new(&mut self.client.card)?.read_config()?;
config.config.usb_enabled_apps |= Capability::PIV;
Transaction::new(&mut self.client.card)?.write_config(
self.client.version,
config.config,
None,
None,
)?;
Ok(())
}
/// Disable NFC interface on the device
pub fn disable_nfc(&mut self) -> Result<()> {
let mut config = Transaction::new(&mut self.client.card)?.read_config()?;
config.config.nfc_enabled_apps = Some(Capability::empty());
Transaction::new(&mut self.client.card)?.write_config(
self.client.version,
config.config,
None,
None,
)?;
Ok(())
}
/// Lock configuration of the device
pub fn lock_config(
&mut self,
current_lock: Option<Lock>,
new_lock: Option<Lock>,
) -> Result<()> {
let config = Transaction::new(&mut self.client.card)?.read_config()?;
Transaction::new(&mut self.client.card)?.write_config(
self.client.version,
config.config,
current_lock,
new_lock,
)?;
Ok(())
}
/// Read configuration from yubikey
pub fn read_config(&mut self) -> Result<DeviceInfo> {
Transaction::new(&mut self.client.card)?.read_config()
}
/// Return the inner [`YubiKey`]
pub fn into_inner(mut self) -> Result<YubiKey> {
Transaction::new(&mut self.client.card)?.select_piv_application()?;
Ok(self.client)
}
}
/// secret to lock YubiKey's configuration
pub struct Lock(pub [u8; 16]);
impl Lock {
/// Clear the locking of configuration
pub const UNLOCKED: Lock = Lock([0; 16]);
}
/// Configuration for a device
#[derive(Clone, Debug, PartialEq)]
#[cfg(feature = "untested")]
pub struct DeviceConfig {
/// List of applets that are available on the USB interface
pub usb_enabled_apps: Capability,
/// List of applets that are available on the NFC interface.
/// Field will be set to None if the device doesn't support NFC
pub nfc_enabled_apps: Option<Capability>,
/// When set, the smartcard will automatically eject after the given time.
pub auto_eject_timeout: Option<u16>,
/// The timeout when waiting for touch for challenge response.
pub challenge_response_timeout: Option<u8>,
/// Configuration flags
pub device_flags: Option<DeviceFlags>,
}
#[cfg(feature = "untested")]
impl DeviceConfig {
pub(crate) fn as_tlv(
&self,
reboot: bool,
current_lock: Option<Lock>,
new_lock: Option<Lock>,
) -> Result<Vec<u8>> {
let mut data = [0u8; CB_BUF_MAX];
let mut len = data.len();
let mut data_remaining = &mut data[1..];
if reboot {
let offset = Tlv::write(data_remaining, TAG_REBOOT, &[])?;
data_remaining = &mut data_remaining[offset..];
}
if let Some(lock) = current_lock {
let offset = Tlv::write(data_remaining, TAG_UNLOCK, &lock.0[..])?;
data_remaining = &mut data_remaining[offset..];
}
{
let offset = Tlv::write(
data_remaining,
TAG_USB_ENABLED,
&self.usb_enabled_apps.bits().to_be_bytes()[..],
)?;
data_remaining = &mut data_remaining[offset..];
}
if let Some(nfc_enabled_apps) = self.nfc_enabled_apps {
let offset = Tlv::write(
data_remaining,
TAG_NFC_ENABLED,
&nfc_enabled_apps.bits().to_be_bytes()[..],
)?;
data_remaining = &mut data_remaining[offset..];
}
if let Some(auto_eject_timeout) = self.auto_eject_timeout {
let offset = Tlv::write(
data_remaining,
TAG_AUTO_EJECT_TIMEOUT,
&auto_eject_timeout.to_be_bytes()[..],
)?;
data_remaining = &mut data_remaining[offset..];
}
if let Some(challenge_response_timeout) = self.challenge_response_timeout {
let offset = Tlv::write(
data_remaining,
TAG_CHALRESP_TIMEOUT,
&challenge_response_timeout.to_be_bytes()[..],
)?;
data_remaining = &mut data_remaining[offset..];
}
if let Some(device_flags) = self.device_flags {
let offset = Tlv::write(
data_remaining,
TAG_DEVICE_FLAGS,
&device_flags.bits().to_be_bytes()[..],
)?;
data_remaining = &mut data_remaining[offset..];
}
if let Some(lock) = new_lock {
let offset = Tlv::write(data_remaining, TAG_CONFIG_LOCK, &lock.0[..])?;
data_remaining = &mut data_remaining[offset..];
}
len -= data_remaining.len();
data[0] = (len - 1) as u8;
Ok(data[..len].to_vec())
}
/// Is the NFC interface active
pub fn nfc_enabled(&self) -> bool {
// It happens that the yubikey will disable the NFC interface entirely if every applet is
// disabled.
self.nfc_enabled_apps
.map(|nfc| !nfc.is_empty())
.unwrap_or(false)
}
}
#[cfg(feature = "untested")]
bitflags! {
/// Represents a set of applications.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Capability: u16 {
/// One Time Password
const OTP =0x01;
/// U2F
const U2F = 0x02;
/// FIDO2
const FIDO2 = 0x200;
/// OATH
const OATH = 0x20;
/// PIV
const PIV = 0x10;
/// OpenPGP
const OPENPGP = 0x08;
/// HSM Auth
const HSMAUTH = 0x100;
/// General CCID bit
const GENERAL_CCID = 0x04;
}
}
/// Device information
/// This represents the configuration and the status of the device
#[cfg(feature = "untested")]
pub struct DeviceInfo {
/// Configuration of the YubiKey
pub config: DeviceConfig,
/// Is the configuration locked with a password?
pub is_locked: bool,
}
#[cfg(feature = "untested")]
impl DeviceInfo {
pub(crate) fn parse(input: &[u8]) -> Result<Self> {
use nom::{
bytes::complete::take,
combinator::{eof, map},
multi::fold_many1,
number::complete::{be_u16, be_u32, u8},
};
fn u8_parser(i: &[u8]) -> nom::IResult<&[u8], u8> {
let (i, v) = u8(i)?;
let (i, _) = eof(i)?;
Ok((i, v))
}
fn u16_parser(i: &[u8]) -> nom::IResult<&[u8], u16> {
let (i, v) = be_u16(i)?;
let (i, _) = eof(i)?;
Ok((i, v))
}
fn capability_parser(i: &[u8]) -> nom::IResult<&[u8], Capability> {
let (i, v) = map(be_u16, Capability::from_bits_retain)(i)?;
let (i, _) = eof(i)?;
Ok((i, v))
}
fn serial_parser(i: &[u8]) -> nom::IResult<&[u8], Serial> {
let (i, v) = map(be_u32, Serial)(i)?;
let (i, _) = eof(i)?;
Ok((i, v))
}
#[derive(Debug, Default)]
struct Info {
usb_supported_apps: Option<Capability>,
usb_enabled_apps: Option<Capability>,
nfc_supported_apps: Option<Capability>,
nfc_enabled_apps: Option<Capability>,
serial: Option<Serial>,
form_factor: Option<FormFactor>,
version: Option<Version>,
auto_eject_timeout: Option<u16>,
challenge_response_timeout: Option<u8>,
device_flags: Option<DeviceFlags>,
is_locked: bool,
}
let (input, len) = u8(input).map_err(|_: nom::Err<()>| Error::ParseError)?;
let (input, rest) = take(len)(input).map_err(|_: nom::Err<()>| Error::ParseError)?;
let (_, _) = eof(input).map_err(|_: nom::Err<()>| Error::ParseError)?;
let out = fold_many1(
|input| Tlv::parse(input).map_err(|_| nom::Err::Error(())),
|| Ok(Info::default()),
|acc: Result<Info>, tlv| match acc {
Ok(mut config) => {
match tlv.tag {
v if v == TAG_USB_SUPPORTED => {
config.usb_supported_apps = Some(
capability_parser(tlv.value)
.map_err(|_| Error::ParseError)?
.1,
);
Ok(config)
}
v if v == TAG_USB_ENABLED => {
config.usb_enabled_apps = Some(
capability_parser(tlv.value)
.map_err(|_| Error::ParseError)?
.1,
);
Ok(config)
}
v if v == TAG_NFC_SUPPORTED => {
config.nfc_supported_apps = Some(
capability_parser(tlv.value)
.map_err(|_| Error::ParseError)?
.1,
);
Ok(config)
}
v if v == TAG_NFC_ENABLED => {
config.nfc_enabled_apps = Some(
capability_parser(tlv.value)
.map_err(|_| Error::ParseError)?
.1,
);
Ok(config)
}
v if v == TAG_SERIAL => {
config.serial =
Some(serial_parser(tlv.value).map_err(|_| Error::ParseError)?.1);
Ok(config)
}
v if v == TAG_FORM_FACTOR => {
config.form_factor = Some(FormFactor::parse(tlv.value)?);
Ok(config)
}
v if v == TAG_VERSION => {
config.version = Some(Version::parse(tlv.value)?);
Ok(config)
}
v if v == TAG_AUTO_EJECT_TIMEOUT => {
config.auto_eject_timeout =
Some(u16_parser(tlv.value).map_err(|_| Error::ParseError)?.1);
Ok(config)
}
v if v == TAG_CHALRESP_TIMEOUT => {
config.challenge_response_timeout =
Some(u8_parser(tlv.value).map_err(|_| Error::ParseError)?.1);
Ok(config)
}
v if v == TAG_DEVICE_FLAGS => {
config.device_flags = Some(
DeviceFlags::parse(tlv.value)
.map_err(|_| Error::ParseError)?
.1,
);
Ok(config)
}
v if v == TAG_CONFIG_LOCK => {
config.is_locked = tlv.value == b"\x01";
Ok(config)
}
// TODO(baloo): implement config lock
_unsupported => {
// New unsupported tags
Ok(config)
}
}
}
err => err,
},
)(rest)
.map_err(|_: nom::Err<()>| Error::ParseError)?
.1?;
let Some(usb_enabled_apps) = out.usb_enabled_apps else {
return Err(Error::ParseError);
};
let config = DeviceConfig {
usb_enabled_apps,
nfc_enabled_apps: out.nfc_enabled_apps,
auto_eject_timeout: out.auto_eject_timeout,
challenge_response_timeout: out.challenge_response_timeout,
device_flags: out.device_flags,
};
Ok(DeviceInfo {
config,
is_locked: out.is_locked,
})
}
}
/// FormFactor of the YubiKey
#[cfg(feature = "untested")]
#[derive(Debug, Copy, Clone, PartialEq)]
#[repr(u8)]
pub enum FormFactor {
/// YubiKey reported an unknown form factor
Unknown = 0x00,
/// Full size USB-A YubiKey
UsbAKeychain = 0x01,
/// Nano USB-A YubiKey
UsbANano = 0x02,
/// Full size USB-C YubiKey
UsbCKeychain = 0x03,
/// Nano USB-C YubiKey
UsbCNano = 0x04,
/// Lightning // USB-C YubiKey
UsbCLightning = 0x05,
/// USB-A YubiKey with a fingerprint reader
UsbABio = 0x06,
/// USB-C YubiKey with a fingerprint reader
UsbCBio = 0x07,
/// Unsupported form factor
Unsupported(u8),
}
#[cfg(feature = "untested")]
impl FormFactor {
fn parse(input: &[u8]) -> Result<Self> {
use nom::{combinator::eof, number::complete::u8};
let (i, v) = u8(input).map_err(|_: nom::Err<()>| Error::ParseError)?;
let (_i, _) = eof(i).map_err(|_: nom::Err<()>| Error::ParseError)?;
Ok(match v {
0 => Self::Unknown,
1 => Self::UsbAKeychain,
2 => Self::UsbANano,
3 => Self::UsbCKeychain,
4 => Self::UsbCNano,
5 => Self::UsbCLightning,
6 => Self::UsbABio,
7 => Self::UsbCBio,
v => Self::Unsupported(v),
})
}
}
#[cfg(feature = "untested")]
bitflags! {
/// Represents configuration flags.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceFlags: u8{
/// Remote wake-up
const REMOTE_WAKEUP = 0x40;
/// Eject
const Eject = 0x80;
}
}
#[cfg(feature = "untested")]
impl DeviceFlags {
fn parse(i: &[u8]) -> nom::IResult<&[u8], Self> {
use nom::{
combinator::{eof, map},
number::complete::u8,
};
let (i, v) = map(u8, Self::from_bits_retain)(i)?;
let (i, _) = eof(i)?;
Ok((i, v))
}
}
+91 -40
View File
@@ -15,7 +15,7 @@ use log::{error, trace};
use zeroize::Zeroizing; use zeroize::Zeroizing;
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
use crate::mgm::{MgmKey, DES_LEN_3DES}; use crate::mgm::{DeviceConfig, DeviceInfo, Lock, MgmKey, DES_LEN_3DES};
const CB_PIN_MAX: usize = 8; const CB_PIN_MAX: usize = 8;
@@ -60,23 +60,32 @@ impl<'tx> Transaction<'tx> {
Ok(recv_buffer) Ok(recv_buffer)
} }
/// Select PIV application.
pub fn select_piv_application(&self) -> Result<()> {
self.select_application(
piv::APPLET_ID,
piv::APPLET_NAME,
"failed selecting application",
)
}
/// Select application. /// Select application.
pub fn select_application(&self) -> Result<()> { pub fn select_application(
&self,
applet: &[u8],
applet_name: &'static str,
error: &'static str,
) -> Result<()> {
let response = Apdu::new(Ins::SelectApplication) let response = Apdu::new(Ins::SelectApplication)
.p1(0x04) .p1(0x04)
.data(piv::APPLET_ID) .data(applet)
.transmit(self, 0xFF) .transmit(self, 0xFF)
.inspect_err(|e| error!("failed communicating with card: '{}'", e))?; .inspect_err(|e| error!("failed communicating with card: '{}'", e))?;
if !response.is_success() { if !response.is_success() {
error!( error!("{}: {:04x}", error, response.status_words().code());
"failed selecting application: {:04x}",
response.status_words().code()
);
return Err(match response.status_words() { return Err(match response.status_words() {
StatusWords::NotFoundError => Error::AppletNotFound { StatusWords::NotFoundError => Error::AppletNotFound { applet_name },
applet_name: piv::APPLET_NAME,
},
_ => Error::GenericError, _ => Error::GenericError,
}); });
} }
@@ -101,21 +110,11 @@ impl<'tx> Transaction<'tx> {
match version.major { match version.major {
// YK4 requires switching to the YK applet to retrieve the serial // YK4 requires switching to the YK applet to retrieve the serial
4 => { 4 => {
let sw = Apdu::new(Ins::SelectApplication) self.select_application(
.p1(0x04) otp::APPLET_ID,
.data(otp::APPLET_ID) otp::APPLET_NAME,
.transmit(self, 0xFF)? "failed selecting yk application",
.status_words(); )?;
if !sw.is_success() {
error!("failed selecting yk application: {:04x}", sw.code());
return Err(match sw {
StatusWords::NotFoundError => Error::AppletNotFound {
applet_name: otp::APPLET_NAME,
},
_ => Error::GenericError,
});
}
let response = Apdu::new(0x01).p1(0x10).transmit(self, 0xFF)?; let response = Apdu::new(0x01).p1(0x10).transmit(self, 0xFF)?;
@@ -129,21 +128,11 @@ impl<'tx> Transaction<'tx> {
} }
// reselect the PIV applet // reselect the PIV applet
let sw = Apdu::new(Ins::SelectApplication) self.select_application(
.p1(0x04) piv::APPLET_ID,
.data(piv::APPLET_ID) piv::APPLET_NAME,
.transmit(self, 0xFF)? "failed selecting application",
.status_words(); )?;
if !sw.is_success() {
error!("failed selecting application: {:04x}", sw.code());
return Err(match sw {
StatusWords::NotFoundError => Error::AppletNotFound {
applet_name: piv::APPLET_NAME,
},
_ => Error::GenericError,
});
}
response.data().try_into() response.data().try_into()
} }
@@ -524,4 +513,66 @@ impl<'tx> Transaction<'tx> {
_ => Err(Error::GenericError), _ => Err(Error::GenericError),
} }
} }
/// Write configuration to the YubiKey
#[cfg(feature = "untested")]
pub fn write_config(
&mut self,
version: Version,
config: DeviceConfig,
current_lock: Option<Lock>,
new_lock: Option<Lock>,
) -> Result<()> {
if version
< (Version {
major: 5,
minor: 0,
patch: 0,
})
{
return Err(Error::NotSupported);
}
let data = config.as_tlv(true, current_lock, new_lock)?;
let response = Apdu::new(Ins::WriteConfig)
.params(0x00, 0x00)
.data(&data)
.transmit(self, 2)?;
if !response.is_success() {
error!(
"Unable to write_config: {:04x}",
response.status_words().code()
);
return Err(Error::GenericError);
}
Ok(())
}
/// Write configuration to the YubiKey
#[cfg(feature = "untested")]
pub fn read_config(&mut self) -> Result<DeviceInfo> {
let mut data = [0u8; CB_BUF_MAX];
let mut len = data.len();
let data_remaining = &mut data[..];
len -= data_remaining.len();
let response = Apdu::new(Ins::ReadConfig)
.params(0x00, 0x00)
.data(&data[..len])
.transmit(self, CB_BUF_MAX + 2)?;
if !response.is_success() {
error!(
"Unable to read configuration: {:04x}",
response.status_words().code()
);
return Err(Error::GenericError);
}
let data = response.data();
DeviceInfo::parse(data)
}
} }
+53 -3
View File
@@ -45,6 +45,7 @@ use log::{error, info};
use pcsc::Card; use pcsc::Card;
use rand_core::{OsRng, RngCore}; use rand_core::{OsRng, RngCore};
use std::{ use std::{
cmp::{Ord, Ordering},
fmt::{self, Display}, fmt::{self, Display},
str::FromStr, str::FromStr,
}; };
@@ -143,6 +144,22 @@ impl Version {
patch: bytes[2], patch: bytes[2],
} }
} }
#[cfg(feature = "untested")]
pub(crate) fn parse(input: &[u8]) -> Result<Self> {
use nom::{combinator::eof, number::complete::u8};
let (i, major) = u8(input).map_err(|_: nom::Err<()>| Error::ParseError)?;
let (i, minor) = u8(i).map_err(|_: nom::Err<()>| Error::ParseError)?;
let (i, patch) = u8(i).map_err(|_: nom::Err<()>| Error::ParseError)?;
let (_i, _) = eof(i).map_err(|_: nom::Err<()>| Error::ParseError)?;
Ok(Self {
major,
minor,
patch,
})
}
} }
impl Display for Version { impl Display for Version {
@@ -151,6 +168,39 @@ impl Display for Version {
} }
} }
impl Ord for Version {
fn cmp(&self, other: &Self) -> Ordering {
if self.major > other.major {
return Ordering::Greater;
}
if self.major < other.major {
return Ordering::Less;
}
if self.minor > other.minor {
return Ordering::Greater;
}
if self.minor < other.minor {
return Ordering::Less;
}
if self.patch > other.patch {
return Ordering::Greater;
}
if self.patch < other.patch {
return Ordering::Less;
}
Ordering::Equal
}
}
impl PartialOrd for Version {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
/// YubiKey device: primary API for opening a session and performing various operations. /// YubiKey device: primary API for opening a session and performing various operations.
/// ///
/// Almost all functionality in this library will require an open session /// Almost all functionality in this library will require an open session
@@ -275,7 +325,7 @@ impl YubiKey {
.map(|p| Buffer::new(p.expose_secret().clone())); .map(|p| Buffer::new(p.expose_secret().clone()));
let txn = Transaction::new(&mut self.card)?; let txn = Transaction::new(&mut self.card)?;
txn.select_application()?; txn.select_piv_application()?;
if let Some(p) = &pin { if let Some(p) = &pin {
txn.verify_pin(p)?; txn.verify_pin(p)?;
@@ -462,7 +512,7 @@ impl YubiKey {
// Force a re-select to unverify, because once verified the spec dictates that // Force a re-select to unverify, because once verified the spec dictates that
// subsequent verify calls will return a "verification not needed" instead of // subsequent verify calls will return a "verification not needed" instead of
// the number of tries left... // the number of tries left...
txn.select_application()?; txn.select_piv_application()?;
// WRONG_PIN is expected on successful query. // WRONG_PIN is expected on successful query.
match txn.verify_pin(&[]) { match txn.verify_pin(&[]) {
@@ -708,7 +758,7 @@ impl<'a> TryFrom<&'a Reader<'_>> for YubiKey {
let mut app_version_serial = || -> Result<(Version, Serial)> { let mut app_version_serial = || -> Result<(Version, Serial)> {
let txn = Transaction::new(&mut card)?; let txn = Transaction::new(&mut card)?;
txn.select_application()?; txn.select_piv_application()?;
let v = txn.get_version()?; let v = txn.get_version()?;
let s = txn.get_serial(v)?; let s = txn.get_serial(v)?;