diff --git a/src/commands.rs b/src/commands.rs index 06c3b27..e8290b5 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -9,16 +9,16 @@ use core::convert::{TryFrom, TryInto}; use iso7816::{Instruction, Status}; use apdu_dispatch::{Command as IsoCommand, command::Data}; -pub use crate::{Pin, Puk}; +pub use crate::{container as containers, piv_types, Pin, Puk}; -#[derive(Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Command<'l> { /// Select the application /// /// Resets security indicators if we are implicitly deselected. Select(Select<'l>), /// Get a data object / container. - GetData(GetData), + GetData(containers::Container), /// Check PIN /// /// This verifies that the sent PIN (global or PIV) is correct. @@ -49,7 +49,7 @@ impl<'l> Command<'l> { } /// TODO: change into enum -#[derive(Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct Select<'l> { pub aid: &'l [u8], } @@ -67,18 +67,27 @@ impl<'l> TryFrom<&'l Data> for Select<'l> { } -#[derive(Clone, Copy, Eq, PartialEq)] -pub enum GetData { -} +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct GetData(containers::Container); impl TryFrom<&Data> for GetData { type Error = Status; fn try_from(data: &Data) -> Result { - todo!(); + let mut decoder = flexiber::Decoder::new(data); + let tagged_slice: flexiber::TaggedSlice = decoder.decode().map_err(|_| Status::IncorrectDataParameter)?; + if tagged_slice.tag() != flexiber::Tag::application(0x1C) { + return Err(Status::IncorrectDataParameter); + } + let container: containers::Container = containers::Tag::new(tagged_slice.as_bytes()) + .try_into() + .map_err(|_| Status::IncorrectDataParameter)?; + + info_now!("request to GetData for container {:?}", container); + Ok(Self(container)) } } -#[derive(Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(u8)] pub enum VerifyKeyReference { GlobalPin = 0x00, @@ -108,7 +117,7 @@ impl TryFrom for VerifyKeyReference { } } -#[derive(Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct VerifyLogout(bool); impl TryFrom for VerifyLogout { @@ -122,21 +131,21 @@ impl TryFrom for VerifyLogout { } } -#[derive(Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct VerifyArguments<'l> { pub key_reference: VerifyKeyReference, pub logout: VerifyLogout, pub data: &'l Data } -#[derive(Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[non_exhaustive] pub enum VerifyLogin { PivPin(Pin), GlobalPin([u8; 8]), } -#[derive(Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Verify { Login(VerifyLogin), Logout(VerifyKeyReference), @@ -160,7 +169,7 @@ impl TryFrom> for Verify { } } -#[derive(Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(u8)] pub enum ChangeReferenceKeyReference { GlobalPin = 0x00, @@ -180,13 +189,13 @@ impl TryFrom for ChangeReferenceKeyReference { } } -#[derive(Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct ChangeReferenceArguments<'l> { pub key_reference: ChangeReferenceKeyReference, pub data: &'l Data } -#[derive(Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ChangeReference { ChangePin { old_pin: Pin, new_pin: Pin }, ChangePuk { old_puk: Puk, new_puk: Puk }, @@ -217,7 +226,7 @@ impl TryFrom> for ChangeReference { } } -#[derive(Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct ResetPinRetries { pub padded_pin: [u8; 8], pub puk: [u8; 8], @@ -236,7 +245,7 @@ impl TryFrom<&Data> for ResetPinRetries { } } -#[derive(Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(u8)] pub enum AuthenticateKeyReference { SecureMessaging = 0x04, @@ -302,7 +311,7 @@ impl TryFrom for AuthenticateKeyReference { } } -#[derive(Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct AuthenticateArguments<'l> { /// To allow the authenticator to have additional algorithms beyond NIST SP 800-78-4, /// this is passed through as-is. @@ -311,7 +320,7 @@ pub struct AuthenticateArguments<'l> { pub data: &'l Data } -#[derive(Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Authenticate { } @@ -322,7 +331,7 @@ impl TryFrom> for Authenticate { } } -#[derive(Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct PutData { } @@ -333,7 +342,7 @@ impl TryFrom<&Data> for PutData { } } -#[derive(Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(u8)] pub enum GenerateAsymmetricKeyReference { SecureMessaging = 0x04, @@ -357,13 +366,13 @@ impl TryFrom for GenerateAsymmetricKeyReference { } } -#[derive(Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct GenerateAsymmetricArguments<'l> { pub key_reference: GenerateAsymmetricKeyReference, pub data: &'l Data } -#[derive(Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum GenerateAsymmetric { } @@ -403,7 +412,7 @@ impl<'l> TryFrom<&'l IsoCommand> for Command<'l> { } (0x00, Instruction::GetData, 0x3F, 0xFF) => { - Self::GetData(GetData::try_from(data)?) + Self::GetData(GetData::try_from(data)?.0) } (0x00, Instruction::Verify, p1, p2) => { diff --git a/src/constants.rs b/src/constants.rs index 8ca4785..e7cf77b 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -22,8 +22,8 @@ pub const PIV_AID: &[u8] = &hex!("A000000308 00001000 0100"); pub const DERIVED_PIV_AID: [u8; 11] = hex!("A000000308 00002000 0100"); -pub const APPLICATION_LABEL: &[u8] = b"SoloKeys PIV v1.0.0-alpha1"; -pub const APPLICATION_URL: &[u8] = b"https://piv.codes/SoloKeys/PIV/1.0.0-alpha1"; +pub const APPLICATION_LABEL: &[u8] = b"SoloKeys PIV"; +pub const APPLICATION_URL: &[u8] = b"https://github.com/solokeys/piv-authenticator"; // pub const APPLICATION_URL: &[u8] = b"https://piv.is/SoloKeys/PIV/1.0.0-alpha1"; diff --git a/src/container.rs b/src/container.rs index 0d1a934..18971f6 100644 --- a/src/container.rs +++ b/src/container.rs @@ -2,12 +2,17 @@ use core::convert::TryFrom; use flexiber::{Decodable, Encodable}; pub struct Tag<'a>(&'a [u8]); +impl<'a> Tag<'a> { + pub fn new(slice: &'a [u8]) -> Self { + Self(slice) + } +} -#[derive(Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct RetiredIndex(u8); // #[repr(u8)] -#[derive(Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum KeyReference { GlobalPin, ApplicationPin, @@ -51,6 +56,7 @@ impl From for u8 { /// The 36 data objects defined by PIV (SP 800-37-4, Part 1). /// +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Container { CardCapabilityContainer, CardHolderUniqueIdentifier, diff --git a/src/lib.rs b/src/lib.rs index ce02219..9bb7780 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ extern crate hex_literal; pub mod commands; pub use commands::Command; pub mod constants; +pub mod container; pub mod state; pub mod derp; pub mod piv_types; @@ -60,6 +61,7 @@ where pub fn select(&mut self, _apdu: &IsoCommand, reply: &mut response::Data) -> Result { use piv_types::Algorithms::*; + info_now!("selecting PIV maybe"); let application_property_template = piv_types::ApplicationPropertyTemplate::default() .with_application_label(APPLICATION_LABEL) @@ -73,10 +75,12 @@ where application_property_template .encode_to_heapless_vec(reply) .unwrap(); + info_now!("returning: {}", hex_str!(reply)); Ok(()) } pub fn respond(&mut self, command: &IsoCommand, reply: &mut response::Data) -> Result { + info_now!("PIV responding to {:?}", command); let last_or_only = command.class().chain().last_or_only(); // TODO: avoid owned copy? @@ -107,10 +111,12 @@ where // parse Iso7816Command as PivCommand let command: Command = (&entire_command).try_into()?; + info_now!("parsed: {:?}", &command); match command { Command::Verify(verify) => self.verify(verify), Command::ChangeReference(change_reference) => self.change_reference(change_reference), + Command::GetData(container) => self.get_data(container, reply), _ => todo!(), } } @@ -863,90 +869,74 @@ where Err(Status::IncorrectDataParameter) } - fn get_data(&mut self, command: &IsoCommand, reply: &mut response::Data) -> Result { - if command.p1 != 0x3f || command.p2 != 0xff { - return Err(Status::IncorrectP1OrP2Parameter); - } - // TODO: adapt `derp` and use a proper DER parser + // match container { + // containers::Container::CardHolderUniqueIdentifier => + // piv_types::CardHolderUniqueIdentifier::default() + // .encode + // _ => todo!(), + // } + // todo!(); - let data = command.data(); - - if data.len() < 3 { - return Err(Status::IncorrectDataParameter); - } - - let tag = data[0]; - if tag != 0x5c { - return Err(Status::IncorrectDataParameter); - } - - let len = data[1] as usize; - let data = &data[2..]; - if data.len() != len { - return Err(Status::IncorrectDataParameter); - } - - if data.len() == 0 || data.len() > 3 { - return Err(Status::IncorrectDataParameter); - } - - // lookup what is asked for - info_now!("looking up {:?}", data); + fn get_data(&mut self, container: container::Container, reply: &mut response::Data) -> Result { // TODO: check security status, else return Status::SecurityStatusNotSatisfied // Table 3, Part 1, SP 800-73-4 // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=30 - match data { - DataObjects::DiscoveryObject => { + use crate::container::Container; + match container { + Container::DiscoveryObject => { // Err(Status::InstructionNotSupportedOrInvalid) let data = response::Data::try_from_slice(DISCOVERY_OBJECT).unwrap(); reply.extend_from_slice(&data).ok(); // todo!("discovery object"), } - DataObjects::BiometricInformationTemplate => { + Container::BiometricInformationTemplatesGroupTemplate => { return Err(Status::InstructionNotSupportedOrInvalid) // todo!("biometric information template"), } // '5FC1 02' (351B) - DataObjects::CardHolderUniqueIdentifier => { + Container::CardHolderUniqueIdentifier => { + let guid = self.state.persistent(&mut self.trussed).guid(); piv_types::CardHolderUniqueIdentifier::default() + .with_guid(guid) .encode_to_heapless_vec(reply) .unwrap(); + info_now!("returning CHUID {}", hex_str!(reply)); } - // '5FC1 05' (351B) - DataObjects::X509CertificateForPivAuthentication => { - // return Err(Status::NotFound); + // // '5FC1 05' (351B) + // Container::X509CertificateForPivAuthentication => { + // // return Err(Status::NotFound); - // info_now!("loading 9a cert"); - // it seems like fetching this certificate is the way Filo's agent decides - // whether the key is "already setup": - // https://github.com/FiloSottile/yubikey-agent/blob/8781bc0082db5d35712a2244e3ab3086f415dd59/setup.go#L69-L70 - let data = try_syscall!(self.trussed.read_file( - trussed::types::Location::Internal, - trussed::types::PathBuf::from(b"authentication-key.x5c"), - )).map_err(|_| { - // info_now!("error loading: {:?}", &e); - Status::NotFound - } )?.data; + // // info_now!("loading 9a cert"); + // // it seems like fetching this certificate is the way Filo's agent decides + // // whether the key is "already setup": + // // https://github.com/FiloSottile/yubikey-agent/blob/8781bc0082db5d35712a2244e3ab3086f415dd59/setup.go#L69-L70 + // let data = try_syscall!(self.trussed.read_file( + // trussed::types::Location::Internal, + // trussed::types::PathBuf::from(b"authentication-key.x5c"), + // )).map_err(|_| { + // // info_now!("error loading: {:?}", &e); + // Status::NotFound + // } )?.data; - // todo: cleanup - let tag = flexiber::Tag::application(0x13); // 0x53 - flexiber::TaggedSlice::from(tag, &data) - .unwrap() - .encode_to_heapless_vec(reply) - .unwrap(); - } + // // todo: cleanup + // let tag = flexiber::Tag::application(0x13); // 0x53 + // flexiber::TaggedSlice::from(tag, &data) + // .unwrap() + // .encode_to_heapless_vec(reply) + // .unwrap(); + // } - // '5F FF01' (754B) - YubicoObjects::AttestationCertificate => { - let data = response::Data::try_from_slice(YUBICO_ATTESTATION_CERTIFICATE).unwrap(); - reply.extend_from_slice(&data).ok(); - } + // // '5F FF01' (754B) + // YubicoObjects::AttestationCertificate => { + // let data = response::Data::try_from_slice(YUBICO_ATTESTATION_CERTIFICATE).unwrap(); + // reply.extend_from_slice(&data).ok(); + // } _ => return Err(Status::NotFound), } diff --git a/src/piv_types.rs b/src/piv_types.rs index a94a369..5b53eee 100644 --- a/src/piv_types.rs +++ b/src/piv_types.rs @@ -71,10 +71,12 @@ impl Encodable for CryptographicAlgorithmTemplate<'_> { } fn encode(&self, encoder: &mut flexiber::Encoder<'_>) -> flexiber::Result<()> { - let cryptographic_algorithm_identifier_tag = flexiber::Tag::application(0); + // '80' + let cryptographic_algorithm_identifier_tag = flexiber::Tag::context(0); for alg in self.algorithms.iter() { encoder.encode(&flexiber::TaggedSlice::from(cryptographic_algorithm_identifier_tag, &[*alg as _])?)?; } + // '06' let object_identifier_tag = flexiber::Tag::universal(6); encoder.encode(&flexiber::TaggedSlice::from(object_identifier_tag, &[0x00])?) } @@ -223,15 +225,21 @@ pub struct CardHolderUniqueIdentifier<'l> { #[tlv(simple = "0x34")] // 16B type 1,2,4 UUID - guid: &'l [u8], + guid: [u8; 16], /// YYYYMMDD #[tlv(simple = "0x35")] expiration_date: [u8; 8], - #[tlv(simple = "0x36")] - // 16B, like guid - cardholder_uuid: Option<&'l [u8]>, + // Having this with "None" serialized as '36 00', which throws e.g. pivy-tool off. + // This is in fact incorrect: + // -> should be '36 10 <...>' with a 16-byte valid UUID of version 1, 4 or 5 + // + // Need to fix in `flexiber`. + // + // #[tlv(simple = "0x36")] + // // 16B, like guid + // cardholder_uuid: Option<&'l [u8]>, #[tlv(simple = "0x3E")] issuer_asymmetric_signature: &'l [u8], @@ -249,12 +257,20 @@ impl Default for CardHolderUniqueIdentifier<'static> { Self { // 9999 = non-federal fasc_n: &[0x99, 0x99], - guid: crate::constants::GUID, + guid: hex!("00000000000040008000000000000000"), expiration_date: *b"99991231", - cardholder_uuid: None, + // cardholder_uuid: None, // at least pivy only checks for non-empty entry issuer_asymmetric_signature: b" ", error_detection_code: [0u8; 0], } } } + +impl CardHolderUniqueIdentifier<'_> { + pub fn with_guid(self, guid: [u8; 16]) -> Self { + let mut modified_self = self; + modified_self.guid = guid; + modified_self + } +} diff --git a/src/state.rs b/src/state.rs index f1128e9..b793dd9 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,9 +1,9 @@ -use core::convert::TryFrom; +use core::convert::{TryFrom, TryInto}; use trussed::{ block, Client as TrussedClient, - syscall, + syscall, try_syscall, types::{KeyId, PathBuf, Location}, }; @@ -217,6 +217,8 @@ pub struct PersistentState { // pin_hash: Option<[u8; 16]>, // Ideally, we'd dogfood a "Monotonic Counter" from `trussed`. timestamp: u32, + // must be a valid RFC 4122 UUID 1, 2 or 4 + guid: [u8; 16], } #[derive(Debug, Eq, PartialEq)] @@ -348,6 +350,10 @@ where const DEFAULT_PIN: &'static [u8] = b"123456\xff\xff"; const DEFAULT_PUK: &'static [u8] = b"12345678"; + pub fn guid(&self) -> [u8; 16] { + self.state.guid + } + pub fn remaining_pin_retries(&self) -> u8 { if self.state.consecutive_pin_mismatches >= Self::PIN_RETRIES_DEFAULT { 0 @@ -449,11 +455,21 @@ where } pub fn initialize(trussed: &'t mut T) -> Self { + info_now!("initializing PIV state"); let management_key = syscall!(trussed.unsafe_inject_shared_key( YUBICO_DEFAULT_MANAGEMENT_KEY, trussed::types::Location::Internal, )).key; + let mut guid: [u8; 16] = syscall!(trussed.random_bytes(16)) + .bytes + .as_ref() + .try_into() + .unwrap(); + + guid[6] = (guid[6] & 0xf) | 0x40; + guid[8] = (guid[8] & 0x3f) | 0x80; + let keys = Keys { authentication_key: None, management_key: management_key, @@ -463,7 +479,7 @@ where retired_keys: Default::default(), }; - Self { + let mut state = Self { trussed, state: PersistentState { keys, @@ -472,8 +488,11 @@ where pin: Pin::try_from(Self::DEFAULT_PIN).unwrap(), puk: Puk::try_from(Self::DEFAULT_PUK).unwrap(), timestamp: 0, + guid, } - } + }; + state.save(); + state } pub fn load(trussed: &'t mut T) -> Result { @@ -482,13 +501,13 @@ where PathBuf::from(Self::FILENAME), ).unwrap() ).map_err(|e| { - // hprintln!("loading error: {:?}", &e).ok(); + info!("loading error: {:?}", &e); drop(e) })?.data; let previous_state: PersistentState = trussed::cbor_deserialize(&data).map_err(|e| { - // hprintln!("cbor deser error: {:?}", e); - // hprintln!("data: {:X?}", &data).ok(); + info!("cbor deser error: {:?}", e); + info!("data: {:X?}", &data); drop(e) })?; // horrible deser bug to forget Ok here :) @@ -497,15 +516,11 @@ where pub fn load_or_initialize(trussed: &'t mut T) -> Self { // todo: can't seem to combine load + initialize without code repetition - let data = block!(trussed.read_file( - Location::Internal, - PathBuf::from(Self::FILENAME), - ).unwrap() - ); + let data = try_syscall!(trussed.read_file(Location::Internal, PathBuf::from(Self::FILENAME))); if let Ok(data) = data { let previous_state = trussed::cbor_deserialize(&data.data).map_err(|e| { - // hprintln!("cbor deser error: {:?}", e); - // hprintln!("data: {:X?}", &data).ok(); + info!("cbor deser error: {:?}", e); + info!("data: {:X?}", &data); drop(e) }); if let Ok(state) = previous_state {