Implementing GetData; pivy-tool list works again

This commit is contained in:
Nicolas Stalder
2021-06-05 18:22:12 +02:00
committed by Nicolas Stalder
parent 8f653cf8d4
commit efd65019dc
6 changed files with 144 additions and 108 deletions
+34 -25
View File
@@ -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<Self, Self::Error> {
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<u8> for VerifyKeyReference {
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct VerifyLogout(bool);
impl TryFrom<u8> for VerifyLogout {
@@ -122,21 +131,21 @@ impl TryFrom<u8> 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<VerifyArguments<'_>> 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<u8> 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<ChangeReferenceArguments<'_>> 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<u8> 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<AuthenticateArguments<'_>> 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<u8> 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) => {
+2 -2
View File
@@ -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";
+8 -2
View File
@@ -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<KeyReference> 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,
+48 -58
View File
@@ -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),
}
+23 -7
View File
@@ -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
}
}
+29 -14
View File
@@ -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<Self> {
@@ -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 {