From 20ebac295ddae9830e3d1e35c3b9d6d74cdb9e16 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Fri, 22 Apr 2022 15:21:22 +0200 Subject: [PATCH] Implement User Interaction Flag (UIF) --- openpgp-card/src/card_do.rs | 195 ++++++++++++++++++++++++++++++++---- openpgp-card/src/openpgp.rs | 26 ++++- 2 files changed, 200 insertions(+), 21 deletions(-) diff --git a/openpgp-card/src/card_do.rs b/openpgp-card/src/card_do.rs index f4ad3a4..3e39ab0 100644 --- a/openpgp-card/src/card_do.rs +++ b/openpgp-card/src/card_do.rs @@ -177,26 +177,42 @@ impl ApplicationRelatedData { // fn key_information() { // unimplemented!() // } - // - // #[allow(dead_code)] - // fn uif_pso_cds() { - // unimplemented!() - // } - // - // #[allow(dead_code)] - // fn uif_pso_dec() { - // unimplemented!() - // } - // - // #[allow(dead_code)] - // fn uif_pso_aut() { - // unimplemented!() - // } - // - // #[allow(dead_code)] - // fn uif_attestation() { - // unimplemented!() - // } + + pub fn uif_pso_cds(&self) -> Result, Error> { + let uif = self.0.find(&[0xd6].into()); + + match uif { + None => Ok(None), + Some(v) => Ok(Some(v.serialize().try_into()?)), + } + } + + pub fn uif_pso_dec(&self) -> Result, Error> { + let uif = self.0.find(&[0xd7].into()); + + match uif { + None => Ok(None), + Some(v) => Ok(Some(v.serialize().try_into()?)), + } + } + + pub fn uif_pso_aut(&self) -> Result, Error> { + let uif = self.0.find(&[0xd8].into()); + + match uif { + None => Ok(None), + Some(v) => Ok(Some(v.serialize().try_into()?)), + } + } + + pub fn uif_attestation(&self) -> Result, Error> { + let uif = self.0.find(&[0xd9].into()); + + match uif { + None => Ok(None), + Some(v) => Ok(Some(v.serialize().try_into()?)), + } + } } /// Security support template (see spec pg. 24) @@ -230,6 +246,145 @@ impl KeyGenerationTime { } } +/// User Interaction Flag (UIF) (see spec pg. 24) +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub struct UIF([u8; 2]); + +impl TryFrom> for UIF { + type Error = Error; + + fn try_from(v: Vec) -> Result { + if v.len() == 2 { + Ok(UIF(v.try_into().unwrap())) + } else { + Err(Error::ParseError(format!("Can't get UID from {:x?}", v))) + } + } +} + +impl UIF { + pub fn touch_policy(&self) -> TouchPolicy { + self.0[0].into() + } + + pub fn set_touch_policy(&mut self, tm: TouchPolicy) { + self.0[0] = tm.into(); + } + + pub fn features(&self) -> Features { + self.0[1].into() + } + + pub(crate) fn as_bytes(&self) -> &[u8] { + &self.0[..] + } +} + +impl Display for UIF { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Touch policy: {} [Features: {}]", + self.touch_policy(), + self.features() + ) + } +} + +/// User interaction setting. +/// +/// See spec pg 24 and https://github.com/Yubico/yubikey-manager/blob/main/ykman/openpgp.py +#[non_exhaustive] +pub enum TouchPolicy { + Off, + On, + Fixed, + Cached, + CachedFixed, + Unknown(u8), +} + +impl Display for TouchPolicy { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + TouchPolicy::Off => write!(f, "Off"), + TouchPolicy::On => write!(f, "On"), + TouchPolicy::Fixed => write!(f, "Fixed"), + TouchPolicy::Cached => write!(f, "Cached"), + TouchPolicy::CachedFixed => write!(f, "CachedFixed"), + TouchPolicy::Unknown(i) => write!(f, "Unknown({})", i), + } + } +} + +impl From for u8 { + fn from(tm: TouchPolicy) -> Self { + match tm { + TouchPolicy::Off => 0, + TouchPolicy::On => 1, + TouchPolicy::Fixed => 2, + TouchPolicy::Cached => 3, + TouchPolicy::CachedFixed => 4, + TouchPolicy::Unknown(i) => i, + } + } +} + +impl From for TouchPolicy { + fn from(i: u8) -> Self { + match i { + 0 => TouchPolicy::Off, + 1 => TouchPolicy::On, + 2 => TouchPolicy::Fixed, + 3 => TouchPolicy::Cached, + 4 => TouchPolicy::CachedFixed, + _ => TouchPolicy::Unknown(i), + } + } +} + +/// "additional hardware for user interaction" (see spec 4.1.3.2) +pub struct Features(u8); + +impl From for Features { + fn from(i: u8) -> Self { + Features(i) + } +} + +impl Display for Features { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut ft = vec![]; + + if self.0 & 0x80 != 0 { + ft.push("Display") + } + if self.0 & 0x40 != 0 { + ft.push("Biometric input sensor") + } + if self.0 & 0x20 != 0 { + ft.push("Button") + } + if self.0 & 0x10 != 0 { + ft.push("Keypad") + } + if self.0 & 0x8 != 0 { + ft.push("LED") + } + if self.0 & 0x4 != 0 { + ft.push("Loudspeaker") + } + if self.0 & 0x2 != 0 { + ft.push("Microphone") + } + if self.0 & 0x1 != 0 { + ft.push("Touchscreen") + } + + write!(f, "{}", ft.join(", ")) + } +} + /// 4.2.1 Application Identifier (AID) #[derive(Debug, Eq, PartialEq)] pub struct ApplicationIdentifier { diff --git a/openpgp-card/src/openpgp.rs b/openpgp-card/src/openpgp.rs index a74019a..fabcd89 100644 --- a/openpgp-card/src/openpgp.rs +++ b/openpgp-card/src/openpgp.rs @@ -8,7 +8,7 @@ use crate::apdu::commands; use crate::apdu::response::RawResponse; use crate::card_do::{ ApplicationRelatedData, CardholderRelatedData, Fingerprint, KeyGenerationTime, Lang, - PWStatusBytes, SecuritySupportTemplate, Sex, + PWStatusBytes, SecuritySupportTemplate, Sex, UIF, }; use crate::crypto_data::{CardUploadableKey, Cryptogram, Hash, PublicKeyMaterial}; use crate::tlv::{value::Value, Tlv}; @@ -719,6 +719,30 @@ impl<'a> OpenPgpTransaction<'a> { apdu::send_command(self.tx(), cmd, false)?.try_into() } + /// Set UIF for PSO:CDS + pub fn set_uif_pso_cds(&mut self, uif: &UIF) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_uif_pso_cds"); + + let cmd = commands::put_data(&[0xd6], uif.as_bytes().to_vec()); + apdu::send_command(self.tx(), cmd, false)?.try_into() + } + + /// Set UIF for PSO:DEC + pub fn set_uif_pso_dec(&mut self, uif: &UIF) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_uif_pso_dec"); + + let cmd = commands::put_data(&[0xd7], uif.as_bytes().to_vec()); + apdu::send_command(self.tx(), cmd, false)?.try_into() + } + + /// Set UIF for PSO:AUT + pub fn set_uif_pso_aut(&mut self, uif: &UIF) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_uif_pso_aut"); + + let cmd = commands::put_data(&[0xd8], uif.as_bytes().to_vec()); + apdu::send_command(self.tx(), cmd, false)?.try_into() + } + /// Import an existing private key to the card. /// (This implicitly sets the algorithm info, fingerprint and timestamp) pub fn key_import(