From ea15c9f620fd8b659e563f811da436a51573782e Mon Sep 17 00:00:00 2001 From: Nicolas Stalder Date: Fri, 5 Feb 2021 21:36:04 +0100 Subject: [PATCH] Continue cleanup; adjust to Trussed changes --- Cargo.toml | 31 ++ src/constants.rs | 434 ++++++++++++++++++++ src/der.rs | 345 ++++++++++++++++ src/derp.rs | 85 ++++ src/error.rs | 88 ++++ src/lib.rs | 1001 ++++++++++++++++++++++++++++++++++++++++++++++ src/state.rs | 389 ++++++++++++++++++ 7 files changed, 2373 insertions(+) create mode 100644 Cargo.toml create mode 100644 src/constants.rs create mode 100644 src/der.rs create mode 100644 src/derp.rs create mode 100644 src/error.rs create mode 100644 src/lib.rs create mode 100644 src/state.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0797104 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "piv-authenticator" +version = "0.0.0-unreleased" +authors = ["Nicolas Stalder "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +apdu-dispatch = { path = "../apdu-dispatch" } +delog = "0.1.0-alpha.3" +heapless = "0.5.6" +heapless-bytes = "0.1.0" +interchange = "0.1.0" +iso7816 = { path = "../iso7816" } +serde = { version = "1.0", default-features = false } +trussed = { git = "ssh://git@github.com/trussed-dev/trussed.git", branch = "main" } +untrusted = "0.7.1" + +[features] +default = [] + +log-all = [] +log-none = [] +log-info = [] +log-debug = [] +log-warn = [] +log-error = [] + +[patch.crates-io] +heapless = { git = "https://github.com/nickray/heapless", branch = "nickray-udebug" } diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 0000000..cdc669a --- /dev/null +++ b/src/constants.rs @@ -0,0 +1,434 @@ +// https://developers.yubico.com/PIV/Introduction/Yubico_extensions.html + +pub const RID_LENGTH: usize = 5; + +// top nibble of first byte is "category", here "A" = International +// this category has 5 byte "registered application provider identifier" +// (international RID, the other 9 nibbles is between 0x0 and 0x9). +pub const NIST_RID: &[u8; 5] = &[0xa0, 0x00, 0x00, 0x03, 0x08]; +pub const YUBICO_RID: &[u8; 5] = &[0xa0, 0x00, 0x00, 0x05, 0x27]; +// our very own RID (847 = 7*11*11 FWIW) +pub const SOLOKEYS_RID: &[u8; 5] = &[0xa0, 0x00, 0x00, 0x08, 0x47]; + +pub const PIV_APP: [u8; 4] = [0x00, 0x00, 0x10, 0x00]; +pub const DERIVED_PIV_APP: [u8; 4] = [0x00, 0x00, 0x10, 0x00]; +pub const PIV_VERSION: [u8; 2] = [0x01, 0x00]; +pub const PIV_PIX: [u8; 6] = [0x00, 0x00, 0x10, 0x00, 0x01, 0x00]; +pub const DERIVED_PIV_PIX: [u8; 6] = [0x00, 0x00, 0x20, 0x00, 0x01, 0x00]; + +pub const PIV_TRUNCATED_AID: [u8; 9] + = [0xa0, 0x00, 0x00, 0x03, 0x08, 0x00, 0x00, 0x10, 0x00]; + +pub const PIV_AID: [u8; 11] + = [0xa0, 0x00, 0x00, 0x03, 0x08, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00]; + +pub const DERIVED_PIV_AID: [u8; 11] + = [0xa0, 0x00, 0x00, 0x03, 0x08, 0x00, 0x00, 0x20, 0x00, 0x01, 0x00]; + +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_URL: &[u8] = b"https://piv.is/SoloKeys/PIV/1.0.0-alpha1"; + + +// https://git.io/JfWuD +pub const YUBICO_OTP_PIX: &[u8; 3] = &[0x20, 0x01, 0x01]; +pub const YUBICO_OTP_AID: [u8; 8] = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01, 0x01]; +// they use it to "deauthenticate user PIN and mgmt key": https://git.io/JfWgN +pub const YUBICO_MGMT_PIX: &[u8; 3] = &[0x47, 0x11, 0x17]; +pub const YUBICO_MGMT_AID: &[u8; 8] = &[0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01, 0x01]; + +// https://git.io/JfW28 +// const ( +// // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-78-4.pdf#page=17 +// algTag = 0x80 +// alg3DES = 0x03 +// algRSA1024 = 0x06 +// algRSA2048 = 0x07 +// algECCP256 = 0x11 +// algECCP384 = 0x14 + +// // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-78-4.pdf#page=16 +// keyAuthentication = 0x9a +// keyCardManagement = 0x9b +// keySignature = 0x9c +// keyKeyManagement = 0x9d +// keyCardAuthentication = 0x9e +// keyAttestation = 0xf9 + +// insVerify = 0x20 +// insChangeReference = 0x24 +// insResetRetry = 0x2c +// insGenerateAsymmetric = 0x47 +// insAuthenticate = 0x87 +// insGetData = 0xcb +// insPutData = 0xdb +// insSelectApplication = 0xa4 +// insGetResponseAPDU = 0xc0 + +// // https://github.com/Yubico/yubico-piv-tool/blob/yubico-piv-tool-1.7.0/lib/ykpiv.h#L656 +// insGetSerial = 0xf8 +// insAttest = 0xf9 +// insSetPINRetries = 0xfa +// insReset = 0xfb +// insGetVersion = 0xfd +// insImportKey = 0xfe +// insSetMGMKey = 0xff +// ) + +pub const OK: &[u8; 2] = &[0x90, 0x00]; + +// pub const SELECT: (u8, u8, u8, u8, usize) = ( +pub const SELECT: (u8, u8, u8, u8) = ( + 0x00, // interindustry, channel 0, no chain, no secure messaging, + 0xa4, // SELECT + // p1 + 0x04, // data is DF name, may be AID, possibly right-truncated + // p2: i think this is dummy here + 0x00, // b2, b1 zero means "file occurence": first/only occurence, + // b4, b3 zero means "file control information": return FCI template + // 256, +); + +// +// See SP 800-73 Part 1, Table 7 +// for list of all objects and minimum container capacity +// - CCC: 287 +// - CHUID: 2916 +// - discovery: 19 +// - key history: 256 +// - x5c: 1905B +// - etc. +// +// pub const GET_DATA: (u8, u8, u8, u8, usize) = ( +pub const GET_DATA: (u8, u8, u8, u8) = ( + 0x00, // as before, would be 0x0C for secure messaging + 0xCB, // GET DATA. There's also `CA`, setting bit 1 here + // means (7816-4, sec. 5.1.2): use BER-TLV, as opposed + // to "no indication provided". + // P1, P2: 7816-4, sec. 7.4.1: bit 1 of INS set => P1,P2 identifies + // a file. And 0x3FFF identifies current DF + 0x3F, + 0xFF, + // 256, +); + +// SW (SP 800-73 Part 1, Table 6) +// == == == == == == == == == == == +// 61, xx success, more response data bytes +// +// 63, 00 verification failed +// 63, Cx verification failed, x furtehr retries or resets +// +// 68, 82 secure messaging not supported +// +// 69, 82 security status not satisfied +// 69, 83 authn method blocked +// : (more secure messaging stuff) +// + +//// ISO/IEC 7816-4, 5.1.3 "Status bytes" +//#[derive(Copy, Clone, Debug, Eq, PartialEq)] +//pub enum StatusWord { + +//////////////////////////////// +//// Normal processing (90, 61) +//////////////////////////////// + +// // 9000 +// Success, + +// // 61XX +// MoreAvailable(u8), + +///////////////////////////////// +//// Warning processing (62, 63) +///////////////////////////////// + +// // 62XX: state of non-volatile memory unchanged (cf. SW2) + +// // 63XX: state of non-volatile memory changed (cf. SW2) +// VerificationFailed, +// FailedRetries(u8), + +////////////////////////////////// +//// Execution error (64, 65, 66) +////////////////////////////////// + +// // 64XX: persistent memory unchanged (cf. SW2) +// // 65XX: persistent memory changed (cf. SW2) +// // 66XX: security related issues + +///////////////////////////////// +//// Checking error (67 - 6F) +///////////////////////////////// + +// // 6700: wrong length, no further indication + +// // 68XX: functions in CLA not supported (cf. SW2) +// SecureMessagingNotSupported, +// CommandChainingNotSupported, + +// // 69xx: command not allowed (cf. SW2) +// SecurityStatusNotSatisfied, +// OperationBlocked, + +// // 6Axx: wrong parameters P1-P2 (cf. SW2) +// IncorrectDataParameter, +// FunctionNotSupported, +// NotFound, +// NotEnoughMemory, +// IncorrectP1OrP2Parameter, +// KeyReferenceNotFound, + +// // 6BXX: wrong parameters P1-P2 + +// // 6CXX: wrong Le field, SW2 encodes available bytes + +// // 6D00: instruction code not supported or invalid +// InstructionNotSupportedOrInvalid, + +// // 6E00: class not supported +// ClassNotSupported, + +// // 6F00: no precise diagnosis +// UnspecifiedCheckingError, +//} + +//impl Into for StatusWord { +// #[inline] +// fn into(self) -> u16 { +// match self { +// Self::VerificationFailed => 0x6300, +// Self::FailedRetries(x) => { +// assert!(x < 16); +// u16::from_be_bytes([0x63, 0xc0 + x]) +// } + +// Self::SecureMessagingNotSupported => 0x6882, +// Self::CommandChainingNotSupported => 0x6884, + +// Self::SecurityStatusNotSatisfied => 0x6982, +// Self::OperationBlocked => 0x6983, + +// Self::IncorrectDataParameter => 0x6a80, +// Self::FunctionNotSupported => 0x6a81, +// Self::NotFound => 0x6a82, +// Self::NotEnoughMemory => 0x6a84, +// Self::IncorrectP1OrP2Parameter => 0x6a86, +// Self::KeyReferenceNotFound => 0x6a88, + +// Self::InstructionNotSupportedOrInvalid => 0x6d00, +// Self::ClassNotSupported => 0x6e00, +// Self::UnspecifiedCheckingError => 0x6f00, + +// Self::Success => 0x9000, +// Self::MoreAvailable(x) => u16::from_be_bytes([0x61, x]), +// } +// } +//} + +//impl Into<[u8; 2]> for StatusWord { +// #[inline] +// fn into(self) -> [u8; 2] { +// let sw: u16 = self.into(); +// sw.to_be_bytes() +// } +//} + + +// 6A, 80 incorrect parameter in command data field +// 6A, 81 function not supported +// 6A, 82 data object not found ( = NOT FOUND for files, e.g. certificate, e.g. after GET-DATA) +// 6A, 84 not enough memory +// 6A, 86 incorrect parameter in P1/P2 +// 6A, 88 reference(d) data not found ( = NOT FOUND for keys, e.g. global PIN, e.g. after VERIFY) +// +// 90, 00 SUCCESS! +// == == == == == == == == == == == + +// #[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct DataObjects {} +#[allow(non_upper_case_globals)] +impl DataObjects { + pub const DiscoveryObject: &'static [u8] = &[0x7e]; + pub const BiometricInformationTemplate: &'static [u8] = &[0x7f, 0x61]; + + pub const X509CertificateForCardAuthentication: &'static [u8] = &[0x5f, 0xc1, 0x01]; + // CHUID, contains GUID + pub const CardHolderUniqueIdentifier: &'static [u8] = &[0x5f, 0xc1, 0x02]; + pub const X509CertificateForPivAuthentication: &'static [u8] = &[0x5f, 0xc1, 0x05]; + pub const X509CertificateForDigitalSignature: &'static [u8] = &[0x5f, 0xc1, 0x0a]; + pub const X509CertificateForKeyManagement: &'static [u8] = &[0x5f, 0xc1, 0x0b]; + + pub const KeyHistoryObject: &'static [u8] = &[0x5f, 0xc1, 0x0c]; +} + +// #[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct YubicoObjects {} +#[allow(non_upper_case_globals)] +impl YubicoObjects { + pub const AttestationCertificate: &'static [u8] = b"\x5f\xff\x01"; +} + +// https://developers.yubico.com/PIV/Introduction/Yubico_extensions.html +#[repr(u8)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum YubicoPivExtension { + SetManagementKey = 0xff, + ImportAsymmetricKey = 0xfe, + GetVersion = 0xfd, + Reset = 0xfb, + SetPinRetries = 0xfa, + Attest = 0xf9, + GetSerial = 0xf8, // also used via 0x01 + GetMetadata = 0xf7, +} + +impl core::convert::TryFrom for YubicoPivExtension { + type Error = (); + fn try_from(ins: u8) -> core::result::Result { + Ok(match ins { + // (0x00, 0x01, 0x10, 0x00) + 0x01 => YubicoPivExtension::GetSerial, + 0xff => YubicoPivExtension::SetManagementKey, + 0xfe => YubicoPivExtension::ImportAsymmetricKey, + 0xfd => YubicoPivExtension::GetVersion, + 0xfb => YubicoPivExtension::Reset, + 0xfa => YubicoPivExtension::SetPinRetries, + // (0x00, 0xf9, 0x9a, 0x00) + 0xf9 => YubicoPivExtension::Attest, + // (0x00, 0xf8, 0x00, 0x00) + 0xf8 => YubicoPivExtension::GetSerial, + 0xf7 => YubicoPivExtension::GetMetadata, + _ => return Err(()), + }) + } +} + +pub const YUBICO_PIV_AUTHENTICATION_CERTIFICATE: &'static [u8; 351] = &[ + 0x53, 0x82, 0x01, 0x5b, 0x70, 0x82, 0x01, 0x52, 0x30, 0x82, 0x01, 0x4e, 0x30, 0x81, 0xf5, 0xa0, + 0x03, 0x02, 0x01, 0x02, 0x02, 0x11, 0x00, 0x8e, 0x46, 0x32, 0xd8, 0xf0, 0xc1, 0xf7, 0xc1, 0x4d, + 0x67, 0xd1, 0x4b, 0xfd, 0xe3, 0x64, 0x8e, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, + 0x04, 0x03, 0x02, 0x30, 0x2a, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, + 0x79, 0x75, 0x62, 0x69, 0x6b, 0x65, 0x79, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x31, 0x10, 0x30, + 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07, 0x28, 0x64, 0x65, 0x76, 0x65, 0x6c, 0x29, 0x30, + 0x20, 0x17, 0x0d, 0x32, 0x30, 0x30, 0x35, 0x30, 0x39, 0x31, 0x32, 0x30, 0x30, 0x34, 0x39, 0x5a, + 0x18, 0x0f, 0x32, 0x30, 0x36, 0x32, 0x30, 0x35, 0x30, 0x39, 0x31, 0x33, 0x30, 0x30, 0x34, 0x39, + 0x5a, 0x30, 0x12, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x07, 0x53, 0x53, + 0x48, 0x20, 0x6b, 0x65, 0x79, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, + 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, + 0x83, 0x2a, 0x92, 0x47, 0x8b, 0x4e, 0xb5, 0x7a, 0x46, 0x1b, 0x2a, 0x5e, 0x0e, 0x44, 0x25, 0x03, + 0x9b, 0xfb, 0x27, 0x94, 0x56, 0x78, 0xed, 0x48, 0x2b, 0x1c, 0xf2, 0x21, 0x61, 0x6d, 0xda, 0xbd, + 0x3d, 0x8f, 0xb6, 0x2b, 0x75, 0xc6, 0xac, 0x3f, 0x83, 0x4a, 0x59, 0x4e, 0x5a, 0xdf, 0xed, 0xe7, + 0x3a, 0xe4, 0x99, 0x1a, 0xe7, 0x33, 0x2f, 0x61, 0x2b, 0xcf, 0x6c, 0x0e, 0xd6, 0x78, 0x72, 0xeb, + 0xa3, 0x12, 0x30, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, + 0x03, 0x02, 0x03, 0x88, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, + 0x03, 0x48, 0x00, 0x30, 0x45, 0x02, 0x20, 0x03, 0x09, 0xe2, 0x84, 0x47, 0xdc, 0xb7, 0xc5, 0x32, + 0xee, 0x97, 0x5b, 0x9e, 0x44, 0xfa, 0x42, 0x06, 0xf2, 0x26, 0x67, 0xc1, 0xa6, 0xc6, 0x4a, 0xdc, + 0x6a, 0x0b, 0x5d, 0xa9, 0x87, 0x63, 0x8b, 0x02, 0x21, 0x00, 0xbb, 0x4e, 0xcb, 0x18, 0x72, 0xcc, + 0x12, 0x39, 0xd3, 0xd4, 0x18, 0x36, 0x14, 0x18, 0xe4, 0xa9, 0xf3, 0x83, 0x81, 0x4b, 0x74, 0x0f, + 0x93, 0x33, 0xb8, 0x47, 0xa9, 0x73, 0xc2, 0x82, 0x92, 0x3e, 0x71, 0x01, 0x00, 0xfe, 0x00, +]; + +pub const YUBICO_ATTESTATION_CERTIFICATE: &'static [u8; 754] = &[ + 0x53, 0x82, 0x02, 0xee, 0x70, 0x82, 0x02, 0xea, 0x30, 0x82, 0x02, 0xe6, 0x30, 0x82, 0x01, 0xce, + 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x09, 0x00, 0xa4, 0x85, 0x22, 0xaa, 0x34, 0xaf, 0xae, 0x4f, + 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, + 0x2b, 0x31, 0x29, 0x30, 0x27, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x20, 0x59, 0x75, 0x62, 0x69, + 0x63, 0x6f, 0x20, 0x50, 0x49, 0x56, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, 0x53, + 0x65, 0x72, 0x69, 0x61, 0x6c, 0x20, 0x32, 0x36, 0x33, 0x37, 0x35, 0x31, 0x30, 0x20, 0x17, 0x0d, + 0x31, 0x36, 0x30, 0x33, 0x31, 0x34, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x18, 0x0f, 0x32, + 0x30, 0x35, 0x32, 0x30, 0x34, 0x31, 0x37, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x21, + 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x16, 0x59, 0x75, 0x62, 0x69, 0x63, + 0x6f, 0x20, 0x50, 0x49, 0x56, 0x20, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, + 0x01, 0x00, 0xab, 0xa9, 0x0b, 0x16, 0x9b, 0xef, 0x31, 0xcc, 0x3e, 0xac, 0x18, 0x5a, 0x2d, 0x45, + 0x80, 0x75, 0x70, 0xc7, 0x58, 0xb0, 0x6c, 0x3f, 0x1b, 0x59, 0x0d, 0x49, 0xb9, 0x89, 0xe8, 0x6f, + 0xce, 0xbb, 0x27, 0x6f, 0xd8, 0x3c, 0x60, 0x3a, 0x85, 0x00, 0xef, 0x5c, 0xbc, 0x40, 0x99, 0x3d, + 0x41, 0xee, 0xea, 0xc0, 0x81, 0x7f, 0x76, 0x48, 0xe4, 0xa9, 0x4c, 0xbc, 0xd5, 0x6b, 0xe1, 0x1f, + 0x0a, 0x60, 0x93, 0xc6, 0xfe, 0xaa, 0xd2, 0x8d, 0x8e, 0xe2, 0xb7, 0xcd, 0x8b, 0x2b, 0xf7, 0x9b, + 0xdd, 0x5a, 0xab, 0x2f, 0xcf, 0xb9, 0x0e, 0x54, 0xce, 0xec, 0x8d, 0xf5, 0x5e, 0xd7, 0x7b, 0x91, + 0xc3, 0xa7, 0x56, 0x9c, 0xdc, 0xc1, 0x06, 0x86, 0x76, 0x36, 0x44, 0x53, 0xfb, 0x08, 0x25, 0xd8, + 0x06, 0xb9, 0x06, 0x8c, 0x81, 0xfd, 0x63, 0x67, 0xca, 0x3c, 0xa8, 0xb8, 0xea, 0x1c, 0xa6, 0xca, + 0xdb, 0x44, 0x7b, 0x12, 0xca, 0xb2, 0x34, 0x01, 0x7e, 0x73, 0xe4, 0x36, 0x83, 0xdf, 0xeb, 0xf9, + 0x23, 0x00, 0x07, 0x01, 0x6a, 0x07, 0x19, 0x8a, 0x64, 0x56, 0x9d, 0x10, 0x8a, 0xc5, 0x73, 0x02, + 0x3d, 0x18, 0x6e, 0xaf, 0x3f, 0xc3, 0x02, 0xa7, 0xc0, 0xf7, 0xa2, 0xfd, 0x6d, 0x5a, 0x42, 0x76, + 0x4e, 0xd6, 0xc0, 0x1e, 0xd6, 0xc0, 0xc6, 0xaa, 0x5d, 0xa7, 0x1a, 0x9f, 0x10, 0xdb, 0x30, 0x57, + 0x18, 0x5c, 0xb5, 0xb5, 0xfd, 0x0c, 0xbe, 0x49, 0x24, 0x22, 0xaf, 0x1e, 0x56, 0x4a, 0x34, 0x44, + 0xd4, 0xaa, 0xd4, 0xe1, 0xae, 0x95, 0x4c, 0x75, 0xc0, 0x88, 0x61, 0xf4, 0x8c, 0x7e, 0x54, 0xf3, + 0x13, 0xeb, 0x0f, 0xe5, 0x2b, 0x52, 0x60, 0x5a, 0x6e, 0xba, 0xd7, 0xe5, 0x8c, 0x63, 0xda, 0x51, + 0x1a, 0xbb, 0x22, 0x5c, 0x37, 0x2b, 0xd7, 0xd1, 0x70, 0x57, 0x4c, 0x2e, 0xdc, 0x35, 0x3c, 0x22, + 0x98, 0x9b, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x15, 0x30, 0x13, 0x30, 0x11, 0x06, 0x0a, 0x2b, + 0x06, 0x01, 0x04, 0x01, 0x82, 0xc4, 0x0a, 0x03, 0x03, 0x04, 0x03, 0x04, 0x03, 0x03, 0x30, 0x0d, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, + 0x01, 0x00, 0x52, 0x80, 0x5a, 0x6d, 0xc3, 0x9e, 0xdf, 0x47, 0xa8, 0xf1, 0xb2, 0xa5, 0x9c, 0xa3, + 0x80, 0x81, 0x3b, 0x1d, 0x6a, 0xeb, 0x6a, 0x12, 0x62, 0x4b, 0x11, 0xfd, 0x8d, 0x30, 0xf1, 0x7b, + 0xfc, 0x71, 0x10, 0xc9, 0xb2, 0x08, 0xfc, 0xd1, 0x4e, 0x35, 0x7f, 0x45, 0xf2, 0x10, 0xa2, 0x52, + 0xb9, 0xd4, 0xb3, 0x02, 0x1a, 0x01, 0x56, 0x07, 0x6b, 0xfa, 0x64, 0xa7, 0x08, 0xf0, 0x03, 0xfb, + 0x27, 0xa9, 0x60, 0x8d, 0x0d, 0xd3, 0xac, 0x5a, 0x10, 0xcf, 0x20, 0x96, 0x4e, 0x82, 0xbc, 0x9d, + 0xe3, 0x37, 0xda, 0xc1, 0x4c, 0x50, 0xe1, 0x3d, 0x16, 0xb4, 0xca, 0xf4, 0x1b, 0xff, 0x08, 0x64, + 0xc9, 0x74, 0x4f, 0x2a, 0x3a, 0x43, 0xe0, 0xde, 0x42, 0x79, 0xf2, 0x13, 0xae, 0x77, 0xa1, 0xe2, + 0xae, 0x6b, 0xdf, 0x72, 0xa5, 0xb6, 0xce, 0xd7, 0x4c, 0x90, 0x13, 0xdf, 0xde, 0xdb, 0xf2, 0x8b, + 0x34, 0x45, 0x8b, 0x30, 0xdc, 0x51, 0xab, 0xa9, 0x34, 0xf8, 0xa9, 0xe5, 0x0c, 0x47, 0x29, 0xaa, + 0x2f, 0x42, 0x54, 0xf2, 0xf8, 0x19, 0x5a, 0xb4, 0x89, 0xfe, 0x1b, 0x9f, 0x19, 0x7a, 0x16, 0xc8, + 0xc8, 0xba, 0x8f, 0x18, 0x17, 0x7a, 0x07, 0xa9, 0x97, 0xa1, 0x56, 0xb9, 0x52, 0x5d, 0xa1, 0x21, + 0xc0, 0x81, 0x67, 0x2d, 0xe8, 0x0e, 0xa6, 0x51, 0xb9, 0x08, 0xb0, 0x9d, 0xd3, 0x60, 0x1c, 0x70, + 0xa3, 0x0f, 0xfa, 0xd8, 0x62, 0xd8, 0x79, 0x2b, 0x0a, 0xe6, 0x42, 0xfc, 0xf8, 0x2d, 0xf5, 0xe4, + 0xcd, 0xfb, 0x15, 0x96, 0x23, 0xff, 0xb6, 0xc0, 0xa7, 0xa7, 0xe2, 0x85, 0x83, 0xf9, 0x70, 0xc8, + 0x19, 0x6b, 0xf3, 0xc1, 0x3f, 0x37, 0x44, 0x65, 0x27, 0xfb, 0x67, 0x88, 0xc8, 0x83, 0xb7, 0x2f, + 0x85, 0x1f, 0x80, 0x44, 0xbb, 0x72, 0xce, 0x06, 0x82, 0x59, 0x2d, 0x83, 0x00, 0xe1, 0x94, 0x8d, + 0xa0, 0x85, +]; + +pub const YUBICO_ATTESTATION_CERTIFICATE_FOR_9A: &'static [u8; 584] = &[ + 0x30, 0x82, 0x02, 0x44, 0x30, 0x82, 0x01, 0x2c, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x11, 0x00, + 0xc6, 0x36, 0xe7, 0xb3, 0xa5, 0xa5, 0xa4, 0x98, 0x5d, 0x13, 0x6e, 0x43, 0x36, 0x2d, 0x13, 0xf7, + 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, + 0x21, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x16, 0x59, 0x75, 0x62, 0x69, + 0x63, 0x6f, 0x20, 0x50, 0x49, 0x56, 0x20, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x30, 0x20, 0x17, 0x0d, 0x31, 0x36, 0x30, 0x33, 0x31, 0x34, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x5a, 0x18, 0x0f, 0x32, 0x30, 0x35, 0x32, 0x30, 0x34, 0x31, 0x37, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x5a, 0x30, 0x25, 0x31, 0x23, 0x30, 0x21, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, + 0x1a, 0x59, 0x75, 0x62, 0x69, 0x4b, 0x65, 0x79, 0x20, 0x50, 0x49, 0x56, 0x20, 0x41, 0x74, 0x74, + 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x39, 0x61, 0x30, 0x59, 0x30, 0x13, 0x06, + 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, + 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0x83, 0x2a, 0x92, 0x47, 0x8b, 0x4e, 0xb5, 0x7a, 0x46, 0x1b, + 0x2a, 0x5e, 0x0e, 0x44, 0x25, 0x03, 0x9b, 0xfb, 0x27, 0x94, 0x56, 0x78, 0xed, 0x48, 0x2b, 0x1c, + 0xf2, 0x21, 0x61, 0x6d, 0xda, 0xbd, 0x3d, 0x8f, 0xb6, 0x2b, 0x75, 0xc6, 0xac, 0x3f, 0x83, 0x4a, + 0x59, 0x4e, 0x5a, 0xdf, 0xed, 0xe7, 0x3a, 0xe4, 0x99, 0x1a, 0xe7, 0x33, 0x2f, 0x61, 0x2b, 0xcf, + 0x6c, 0x0e, 0xd6, 0x78, 0x72, 0xeb, 0xa3, 0x3c, 0x30, 0x3a, 0x30, 0x11, 0x06, 0x0a, 0x2b, 0x06, + 0x01, 0x04, 0x01, 0x82, 0xc4, 0x0a, 0x03, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x30, 0x13, 0x06, + 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xc4, 0x0a, 0x03, 0x07, 0x04, 0x05, 0x02, 0x03, 0x52, + 0xf7, 0x43, 0x30, 0x10, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xc4, 0x0a, 0x03, 0x08, + 0x04, 0x02, 0x02, 0x02, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, + 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x02, 0x17, 0x38, 0xa8, 0xf6, 0x1d, 0x17, 0x35, + 0xe1, 0x30, 0x9d, 0xd2, 0xd5, 0xc4, 0xd4, 0xd0, 0x0d, 0xe1, 0x9f, 0x37, 0x9a, 0xbe, 0xcf, 0x63, + 0x6a, 0x0e, 0x2b, 0xd0, 0xd7, 0xa4, 0x04, 0x5c, 0x40, 0x7d, 0xf7, 0x43, 0x9b, 0xe4, 0xee, 0x7d, + 0x96, 0x55, 0xd2, 0x91, 0xdc, 0x32, 0x82, 0x54, 0xfe, 0x2d, 0x9f, 0x19, 0x23, 0x54, 0xbb, 0xdd, + 0x7d, 0x6b, 0xe9, 0x61, 0x2a, 0x1d, 0xc8, 0x13, 0x65, 0xe2, 0x04, 0x9f, 0xa2, 0x87, 0xde, 0x61, + 0x92, 0xd5, 0xde, 0x46, 0xd4, 0xa4, 0xc2, 0xa6, 0xb4, 0x80, 0x5d, 0x4a, 0xa4, 0xd1, 0x1b, 0xa7, + 0x34, 0xf2, 0x97, 0x7b, 0x7a, 0x5a, 0xad, 0x9a, 0xa8, 0x5d, 0x2a, 0xd4, 0x7f, 0xb1, 0x57, 0xbf, + 0x26, 0x1d, 0x3d, 0xa6, 0xb3, 0xea, 0x3d, 0x3d, 0xf7, 0x94, 0xcd, 0x16, 0x36, 0x40, 0x24, 0xcd, + 0x7c, 0x8e, 0x7a, 0xdb, 0x2d, 0xf9, 0x22, 0xda, 0x26, 0xb3, 0xc1, 0xc8, 0x00, 0xa3, 0x47, 0x97, + 0x52, 0x10, 0x12, 0x73, 0x4b, 0xaf, 0x12, 0xfe, 0xb7, 0x0d, 0x9e, 0x91, 0x30, 0xa7, 0x52, 0xcf, + 0x12, 0xd8, 0x2b, 0xdf, 0x12, 0x6a, 0xb6, 0x2f, 0x39, 0x24, 0xc6, 0x04, 0xa2, 0x6f, 0xed, 0x70, + 0xb5, 0xf2, 0x0d, 0x2a, 0x73, 0xe3, 0x38, 0xa9, 0x9c, 0xfe, 0x35, 0x3e, 0xdc, 0x17, 0x40, 0x55, + 0xd5, 0x95, 0x7f, 0x05, 0x8e, 0x24, 0xc2, 0xb3, 0xb1, 0x05, 0x2d, 0x69, 0x0c, 0xcf, 0x5b, 0xf7, + 0x06, 0x40, 0x17, 0x36, 0x0a, 0xc3, 0xa5, 0xdb, 0x3c, 0xda, 0x62, 0xf8, 0x53, 0x2d, 0xf1, 0x3f, + 0x04, 0x55, 0x70, 0x0c, 0x43, 0x7b, 0x1f, 0xa3, 0x63, 0xb1, 0xa0, 0x5e, 0x89, 0x28, 0x5b, 0x4f, + 0x76, 0xa7, 0x05, 0xe1, 0x4c, 0x45, 0x55, 0x14, 0xff, 0x10, 0x10, 0x89, 0x69, 0x6a, 0x13, 0x3d, + 0x89, 0xf2, 0xca, 0xfd, 0x14, 0x9a, 0xc4, 0xd0, +]; +// pub const YUBICO_DEFAULT_MANAGEMENT_KEY: &'static [u8; 24] = b"123456781234567812345678"; +pub const YUBICO_DEFAULT_MANAGEMENT_KEY: &'static [u8; 24] = &[ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, +]; + +// stolen from le yubico +pub const DISCOVERY_OBJECT: &'static [u8; 20] = b"~\x12O\x0b\xa0\x00\x00\x03\x08\x00\x00\x10\x00\x01\x00_/\x02@\x00"; + +// import secrets; secrets.token_bytes(16) +pub const GUID: &'static [u8; 16] = b"\x0c\x92\xc9\x04\xd0\xdeL\xd9\xf6\xd1\xa2\x9fE3\xca\xeb"; diff --git a/src/der.rs b/src/der.rs new file mode 100644 index 0000000..5b44343 --- /dev/null +++ b/src/der.rs @@ -0,0 +1,345 @@ +pub use heapless_bytes::{consts, ArrayLength, Bytes}; + +const CONSTRUCTED: u8 = 1 << 5; +// const CONTEXT_SPECIFIC: u8 = 2 << 6; + +/// ASN.1 Tags +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(u8)] +pub enum Tag { + // Eoc = 0x00, + // Boolean = 0x01, + Integer = 0x02, + // BitString = 0x03, + // OctetString = 0x04, + // Null = 0x05, + // Oid = 0x06, + Sequence = CONSTRUCTED | 0x10, + // UtcTime = 0x17, + // GeneralizedTime = 0x18, + // ContextSpecificConstructed0 = CONTEXT_SPECIFIC | CONSTRUCTED | 0, + // ContextSpecificConstructed1 = CONTEXT_SPECIFIC | CONSTRUCTED | 1, + // ContextSpecificConstructed2 = CONTEXT_SPECIFIC | CONSTRUCTED | 2, + // ContextSpecificConstructed3 = CONTEXT_SPECIFIC | CONSTRUCTED | 3, +} + +// impl From for usize { +// fn from(tag: Tag) -> Self { +// tag as Self +// } +// } + +// impl From for u8 { +// fn from(tag: Tag) -> Self { +// tag as Self +// } +// } + +// the only error is buffer overflow +type Result = core::result::Result<(), ()>; + +/// DER writer +#[derive(Debug)] +pub struct Der(Bytes) +where + N: ArrayLength; + +impl> Default for Der { + fn default() -> Self { + Self::new() + } +} + +impl> core::ops::Deref for Der { + type Target = Bytes; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl> core::ops::DerefMut for Der { + fn deref_mut(&mut self) -> &mut Bytes { + &mut self.0 + } +} + +impl> Der { + /// Create a new `Der` structure that writes values to the given buffer + pub fn new() -> Self { + Der(Bytes::new()) + } + + // // equivalent of method in std::io::Write + // fn write_all(&mut self, data: &[u8]) -> Result { + // self.0.extend_from_slice(data) + // } + + /// Return underlying buffer + pub fn into_inner(self) -> Bytes { + self.0 + } + + // https://docs.microsoft.com/en-us/windows/win32/seccertenroll/about-encoded-length-and-value-bytes + fn write_length_field(&mut self, length: usize) -> Result { + if length < 0x80 { + // values under 128: write length directly as u8 + self.extend_from_slice(&[length as u8]) + } else { + // values at least 128: + // - write number of bytes needed as u8, setting bit 7 + // - write l as big-endian bytes representation, with minimal length + + let mut repr = &length.to_be_bytes()[..]; + while repr[0] == 0 { + repr = &repr[1..]; + } + self.extend_from_slice(&[0x80 | repr.len() as u8])?; + self.extend_from_slice(repr) + } + } + + // // /// Write a `NULL` tag. + // // pub fn null(&mut self) -> Result { + // // self.0.extend_from_slice(&[Tag::Null as u8, 0])?; + // // Ok(()) + // // } + + // /// Write an arbitrary tag-length-value + // pub fn raw_tlv(&mut self, tag: Tag, value: &[u8]) -> Result { + // self.extend_from_slice(&[tag as u8])?; + // self.write_length_field(value.len())?; + // self.extend_from_slice(value) + // } + + /// Write an arbitrary tag-length-value + pub fn raw_tlv(&mut self, tag: u8, value: &[u8]) -> Result { + self.extend_from_slice(&[tag])?; + self.write_length_field(value.len())?; + self.extend_from_slice(value) + } + + /// Write an arbitrary tag-length-value with 2-byte tag + /// NB: everything in ISO 7816 is big-endian + pub fn raw_tlv2(&mut self, tag: u16, value: &[u8]) -> Result { + self.extend_from_slice(&tag.to_be_bytes())?; + self.write_length_field(value.len())?; + self.extend_from_slice(value) + } + + ///// Write the given input as integer. + ///// + ///// Assumes `input` is the big-endian representation of a non-negative `Integer` + ///// + ///// Not sure about good references, maybe: + ///// https://docs.microsoft.com/en-us/windows/win32/seccertenroll/about-integer + ///// + ///// From: https://docs.rs/ecdsa/0.3.0/src/ecdsa/convert.rs.html#205-219 + ///// Compute ASN.1 DER encoded length for the provided scalar. + ///// The ASN.1 encoding is signed, so its leading bit must have value 0; + ///// it must also be of minimal length (so leading bytes of value 0 must be + ///// removed, except if that would contradict the rule about the sign bit). + //pub fn non_negative_integer(&mut self, mut integer: &[u8]) -> Result { + // self.extend_from_slice(&[Tag::Integer as u8])?; + + // // strip leading zero bytes + // while !integer.is_empty() && integer[0] == 0 { + // integer = &integer[1..]; + // } + + // if integer.is_empty() || integer[0] >= 0x80 { + // self.write_length_field(integer.len() + 1)?; + // self.extend_from_slice(&[0x00])?; + // } else { + // self.write_length_field(integer.len())?; + // } + + // self.extend_from_slice(integer) + //} + + /// Write a nested structure by passing in a handling function that writes + /// the serialized intermediate structure. + pub fn nested(&mut self, tag: u8, f: F) -> Result + where + F: FnOnce(&mut Der) -> Result, + { + let before = self.len(); + + // serialize the nested structure + f(self)?; + let written = self.len() - before; + + // generate Tag-Length prefix + // 1 for tag, 1 for length prefix, 4 or 8 for usize itself + // + // could try something like: type PrefixSize =>::Output; + // but not couldn't find a consts::Usize type; + type PrefixSize = consts::U12; + let mut prefix = Der::::new(); + + // generate prefix consisting of "tag" and length of nested structure + prefix.extend_from_slice(&[tag])?; + prefix.write_length_field(written)?; + + self.insert_slice_at(&prefix, before) + } + + /// Write a `SEQUENCE` by passing in a handling function that writes to an intermediate `Vec` + /// before writing the whole sequence to `self`. + pub fn sequence(&mut self, f: F) -> Result + where + F: FnOnce(&mut Der) -> Result, + { + self.nested(Tag::Sequence as u8, f) + } +} + +#[cfg(test)] +mod test { + use super::*; + + // #[test] + // fn max_prefix() { + // let mut u32_buf = [0u8; core::mem::size_of::() + 2]; + // let mut prefix = Der::new(&mut u32_buf); + // prefix.0.extend_from_slice(&[0u8]).unwrap(); + // assert!(prefix.write_length_field(u32::max_value() as usize).is_ok()); + // assert_eq!([0u8, 132, 255, 255, 255, 255], prefix.as_ref()); + + // let mut u64_buf = [0u8; core::mem::size_of::() + 2]; + // let mut prefix = Der::new(&mut u64_buf); + // prefix.0.extend_from_slice(&[0u8]).unwrap(); + // assert!(prefix.write_length_field(u64::max_value() as usize).is_ok()); + // assert_eq!([0, 136, 255, 255, 255, 255, 255, 255, 255, 255], prefix.as_ref()); + // } + + #[test] + fn write_asn1_der_ecdsa_signature() { + let r = [ + 167u8, 156, 58, 251, 253, 197, 176, 208, 165, 146, 155, 16, 217, 152, 192, 243, 206, + 76, 214, 207, 207, 180, 237, 8, 156, 160, 64, 32, 147, 82, 213, 158, + ]; + let s = [ + 184, 156, 136, 100, 87, 142, 84, 61, 235, 27, 193, 223, 254, 97, 11, 111, 80, 37, 46, + 150, 121, 96, 165, 96, 65, 242, 211, 180, 175, 91, 158, 88, + ]; + // let mut buf = [0u8; 1024]; + let mut der = Der::::new(); + der.sequence(|der| { + der.non_negative_integer(&r)?; + der.non_negative_integer(&s) + }) + .unwrap(); + + #[rustfmt::skip] + let expected = [ + 48u8, 70, + 2, 33, + 0, 167, 156, 58, 251, 253, 197, 176, 208, 165, 146, 155, 16, 217, 152, + 192, 243, 206, 76, 214, 207, 207, 180, 237, 8, 156, 160, 64, 32, 147, 82, 213, 158, + 2, 33, + 0, 184, 156, 136, 100, 87, 142, 84, 61, 235, 27, 193, 223, 254, 97, 11, 111, 80, + 37, 46, 150, 121, 96, 165, 96, 65, 242, 211, 180, 175, 91, 158, 88, + ]; + assert_eq!(der.len(), expected.len()); + assert_eq!( + Bytes::::try_from_slice(&der).unwrap(), + Bytes::::try_from_slice(&expected).unwrap(), + ); + // assert_eq!(&got[..32], &expected[..32]); + // assert_eq!(&got[32..64], &expected[32..64]); + // assert_eq!(&got[64..], &expected[64..]); + } +} + +//// let mut der = Der::new(&mut buf); +//// der.sequence(|der| { +//// der.positive_integer(n)?; +//// der.positive_integer(e) +//// }) +//// .unwrap(); + +//// /// Write an `OBJECT IDENTIFIER`. +//// pub fn oid(&mut self, input: &[u8]) -> Result<()> { +//// self.writer.0.extend_from_slice(&[Tag::Oid as u8])?; +//// self.write_length_field(input.len())?; +//// self.writer.0.extend_from_slice(&input)?; +//// Ok(()) +//// } + +//// /// Write raw bytes to `self`. This does not calculate length or apply. This should only be used +//// /// when you know you are dealing with bytes that are already DER encoded. +//// pub fn raw(&mut self, input: &[u8]) -> Result<()> { +//// Ok(self.writer.0.extend_from_slice(input)?) +//// } + +//// /// Write a `BIT STRING`. +//// pub fn bit_string(&mut self, unused_bits: u8, bit_string: &[u8]) -> Result<()> { +//// self.writer.0.extend_from_slice(&[Tag::BitString as u8])?; +//// self.write_length_field(bit_string.len() + 1)?; +//// self.writer.0.extend_from_slice(&[unused_bits])?; +//// self.writer.0.extend_from_slice(&bit_string)?; +//// Ok(()) +//// } + +//// /// Write an `OCTET STRING`. +//// pub fn octet_string(&mut self, octet_string: &[u8]) -> Result<()> { +//// self.writer.0.extend_from_slice(&[Tag::OctetString as u8])?; +//// self.write_length_field(octet_string.len())?; +//// self.writer.0.extend_from_slice(&octet_string)?; +//// Ok(()) +//// } +//// } + +//// #[cfg(test)] +//// mod test { +//// use super::*; +//// use untrusted::Input; +//// use Error; + +//// static RSA_2048_PKCS1: &'static [u8] = include_bytes!("../tests/rsa-2048.pkcs1.der"); + +//// #[test] +//// fn write_pkcs1() { +//// let input = Input::from(RSA_2048_PKCS1); +//// let (n, e) = input +//// .read_all(Error::Read, |input| { +//// der::nested(input, Tag::Sequence, |input| { +//// let n = der::positive_integer(input)?; +//// let e = der::positive_integer(input)?; +//// Ok((n.as_slice_less_safe(), e.as_slice_less_safe())) +//// }) +//// }) +//// .unwrap(); + +//// let mut buf = Vec::new(); +//// { +//// let mut der = Der::new(&mut buf); +//// der.sequence(|der| { +//// der.positive_integer(n)?; +//// der.positive_integer(e) +//// }) +//// .unwrap(); +//// } + +//// assert_eq!(buf.as_slice(), RSA_2048_PKCS1); +//// } + +//// #[test] +//// fn write_octet_string() { +//// let mut buf = Vec::new(); +//// { +//// let mut der = Der::new(&mut buf); +//// der.octet_string(&[]).unwrap(); +//// } + +//// assert_eq!(&buf, &[0x04, 0x00]); + +//// let mut buf = Vec::new(); +//// { +//// let mut der = Der::new(&mut buf); +//// der.octet_string(&[0x0a, 0x0b, 0x0c]).unwrap(); +//// } + +//// assert_eq!(&buf, &[0x04, 0x03, 0x0a, 0x0b, 0x0c]); +//// } +//// } diff --git a/src/derp.rs b/src/derp.rs new file mode 100644 index 0000000..0ece355 --- /dev/null +++ b/src/derp.rs @@ -0,0 +1,85 @@ +pub use untrusted::{Input, Reader}; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Error { + HighTagNumberForm, + LongLengthNotSupported, + NonCanonical, + Read, + UnexpectedEnd, + WrongTag, + WrongValue, +} + +pub type Result = core::result::Result; + +impl From for Error { + fn from(_: untrusted::EndOfInput) -> Error { + Error::UnexpectedEnd + } +} + +/// Return the value of the given tag and apply a decoding function to it. +pub fn nested<'a, F, R>(input: &mut Reader<'a>, tag: u8, decoder: F) -> Result +where + F: FnOnce(&mut untrusted::Reader<'a>) -> Result, +{ + let inner = expect_tag_and_get_value(input, tag)?; + inner.read_all(Error::Read, decoder) +} + +/// Read a tag and return it's value. Errors when the expect and actual tag do not match. +pub fn expect_tag_and_get_value<'a>(input: &mut Reader<'a>, tag: u8) -> Result> { + let (actual_tag, inner) = read_tag_and_get_value(input)?; + if usize::from(tag) != usize::from(actual_tag) { + return Err(Error::WrongTag); + } + Ok(inner) +} + +/// Read a tag and its value. Errors when the expected and actual tag and values do not match. +pub fn expect_tag_and_value<'a>(input: &mut Reader<'a>, tag: u8, value: &[u8]) -> Result<()> { + let (actual_tag, inner) = read_tag_and_get_value(input)?; + if usize::from(tag) != usize::from(actual_tag) { + return Err(Error::WrongTag); + } + if value != inner.as_slice_less_safe() { + return Err(Error::WrongValue); + } + Ok(()) +} + +/// Read the next tag, and return it and its value. +pub fn read_tag_and_get_value<'a>(input: &mut Reader<'a>) -> Result<(u8, Input<'a>)> { + let tag = input.read_byte()?; + if (tag & 0x1F) == 0x1F { + return Err(Error::HighTagNumberForm); + } + + // If the high order bit of the first byte is set to zero then the length + // is encoded in the seven remaining bits of that byte. Otherwise, those + // seven bits represent the number of bytes used to encode the length. + let length = match input.read_byte()? { + n if (n & 0x80) == 0 => usize::from(n), + 0x81 => { + let second_byte = input.read_byte()?; + if second_byte < 128 { + return Err(Error::NonCanonical); + } + usize::from(second_byte) + } + 0x82 => { + let second_byte = usize::from(input.read_byte()?); + let third_byte = usize::from(input.read_byte()?); + let combined = (second_byte << 8) | third_byte; + if combined < 256 { + return Err(Error::NonCanonical); + } + combined + } + _ => return Err(Error::LongLengthNotSupported), + }; + + let inner = input.read_bytes(length)?; + Ok((tag, inner)) +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..13f5a4b --- /dev/null +++ b/src/error.rs @@ -0,0 +1,88 @@ + +// macro_rules! status_word { +// ($($Name:ident: [$sw1:expr, $sw2:tt],)*) => { +// $( +// // pub struct $Name {} + +// status_word! ($Name, $sw1, $sw2); +// )* + +// pub enum StatusWord { +// $($Name($Name),)* +// } +// }; + +// ($Name:ident, $sw1:expr, XX) => { +// pub struct $Name { +// sw2: u8, +// } + +// impl $Name { +// const SW1: u8 = $sw1; + +// pub fn new(sw2: u8) -> Self { +// Self { sw2 } +// } + +// pub fn as_bytes(&self) -> [u8; 2] { +// [Self::SW1, self.sw2] +// } + +// } + +// // impl core::ops::Deref for $Name { +// // type Target = [u8; 2]; +// // fn deref(&self) -> &Self::Target { +// // &[Self::SW1, self.sw2] +// // } +// // } + +// }; + +// ($Name:ident, $sw1:expr, $sw2:expr) => { +// #[derive(Default)] +// pub struct $Name {} + +// impl $Name { +// const SW1: u8 = $sw1; +// const SW2: u8 = $sw2; + +// pub fn new() -> Self { +// Default::default() +// } + +// pub fn as_bytes(&self) -> [u8; 2] { +// [Self::SW1, Self::SW2] +// } +// } +// }; +// } + +// status_word! { +// SecurityStatusNotSatisfied: [0x69, 0x82], +// NotFound: [0x6a, 0x82], +// Success: [0x90, 0x00], + +// SuccessBytesRemaining: [0x61, XX ], +// } + +// pub trait StatusWordTrait { +// fn sw1(&self) -> u8; +// fn sw2(&self) -> u8; +// fn sw(&self) -> [u8; 2] { +// [self.sw1(), self.sw2()] +// } +// } + +// #[cfg(test)] +// mod tests { +// use super::*; + +// #[test] +// fn deref() { +// let sw = SuccessBytesRemaining::new(42); +// println!("SW: {:?}", &sw.as_bytes()); +// } + +// } + diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..2475eb5 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,1001 @@ +#![cfg_attr(not(test), no_std)] + +#[macro_use] +extern crate delog; +generate_macros!(); + +pub mod constants; +pub mod state; +pub mod derp; +pub mod der; + +use core::convert::{TryFrom, TryInto}; + +use heapless::consts; +use iso7816::{ + Command, Instruction, Status, + response::{ + Result as ResponseResult, + Data as ResponseData, + }, +}; +use apdu_dispatch::applet; +// use apdu_dispatch::{Aid, Applet, Result as applet::Result, applet::Response}; +use trussed::Client as TrussedClient; +use trussed::{block, syscall}; + +use der::Der; + +use constants::*; + +pub struct App +where T: TrussedClient +{ + state: state::State, + trussed: T, + // trussed: RefCell, +} + +impl App +where T: TrussedClient +{ + pub fn new( + trussed: T, + ) + -> Self + { + // seems like RefCell is not the right thing, we want something like `Rc` instead, + // which can be cloned and injected into other parts of the App that use Trussed. + // let trussed = RefCell::new(trussed); + Self { + // state: state::State::new(trussed.clone()), + state: Default::default(), + trussed, + } + } + + fn try_handle(&mut self, command: &Command) -> ResponseResult { + + // TEMP + // blocking::dbg!(self.state.persistent(&mut self.trussed).timestamp(&mut self.trussed)); + + // handle CLA + // - command chaining not supported + // - secure messaging not supported + // - only channel zero supported + // - ensure INS known to us + + let last_or_only = command.class().chain().last_or_only(); + + // TODO: avoid owned copy? + let owned_command = match self.state.runtime.chained_command.as_mut() { + Some(command_so_far) => { + // TODO: make sure the prefix matches, e.g. '00 DB 3F FF' + command_so_far.data_mut().extend_from_slice(command.data()).unwrap(); + + if last_or_only { + let total_command = command_so_far.clone(); + self.state.runtime.chained_command = None; + total_command + } else { + return Ok(Default::default()); + } + } + + None => { + if last_or_only { + // Command + command.clone() + } else { + self.state.runtime.chained_command = Some(command.clone()); + return Ok(Default::default()); + } + } + }; + + let command = &owned_command; + + let class = command.class(); + + if !class.secure_messaging().none() { + return Err(Status::SecureMessagingNotSupported); + } + + if class.channel() != Some(0) { + return Err(Status::LogicalChannelNotSupported); + } + + // info_now!("CLA = {:?}", &command.class()); + info_now!("INS = {:?}, P1 = {:X}, P2 = {:X}", + &command.instruction(), + command.p1, command.p2, + ); + // info_now!("extended = {:?}", command.extended); + + // info_now!("INS = {:?}" &command.instruction()); + match command.instruction() { + Instruction::GetData => self.get_data(command), + Instruction::PutData => self.put_data(command), + Instruction::Verify => self.verify(command), + Instruction::ChangeReferenceData => self.change_reference_data(command), + Instruction::GeneralAuthenticate => self.general_authenticate(command), + Instruction::GenerateAsymmetricKeyPair => self.generate_asymmetric_keypair(command), + + Instruction::Unknown(ins) => { + + // see if it's a Yubico thing + if let Ok(instruction) = YubicoPivExtension::try_from(ins) { + self.yubico_piv_extension(command, instruction) + } else { + Err(Status::FunctionNotSupported) + } + } + + _ => Err(Status::FunctionNotSupported), + } + } + + // SP 800-73-4, Part 2, Section 3.2.4 + // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=92 + // + // General use: + // - PIV authn keys (9A, 9B, 9E): + // - card/app to client (INTERNAL) + // - entity to card (EXTERNAL) + // - mutual card/external (MUTUAL) + // - Signature key (9C): => Appendix A.4 + // - signing data hashed off card + // - Management key (9D, retired 82-95): => Appendix A.5 + // - key establishment schems in SP 800-78 (ECDH) + // - PIV secure messaging key (04, alg 27, 2E) + // + // Data field tags: + // - 80 witness + // - 81 challenge + // - 82 response + // - 83 exponentiation + // + // Request for requests: + // - '80 00' returns '80 TL ' + // - '81 00' returns '81 TL ' + // + // Errors: + // - 9000, 61XX for success + // - 6982 security status + // - 6A80, 6A86 for data, P1/P2 issue + fn general_authenticate(&mut self, command: &Command) -> ResponseResult { + + // For "SSH", we need implement A.4.2 in SP-800-73-4 Part 2, ECDSA signatures + // + // ins = 87 = general authenticate + // p1 = 11 = alg P256 + // p2 = 9a = keyref "PIV authentication" + // 00 87 11 9A 26 + // # 7c = specified template + // 7C 24 + // # 82 = response, 00 = "request for request" + // 82 00 + // # 81 = challenge + // 81 20 + // # 32B nonce + // 95 AE 21 F9 5E 00 01 E6 23 27 F4 FD A5 05 F1 F5 B7 95 0F 11 75 BC 4D A2 06 B1 00 6B DA 90 C3 3A + // + // expected response: "7C L1 82 L2 SEQ(INT r, INT s)" + + let _alg = command.p1; + let _slot = command.p2; + let mut data = command.data().as_slice(); + + // refine as we gain more capability + if data.len() < 2 { + return Err(Status::IncorrectDataParameter); + } + + let tag = data[0]; + if tag != 0x7c { + return Err(Status::IncorrectDataParameter); + } + + if data[1] > 0x81 { + panic!("unhandled >1B lengths"); + } + if data[1] == 0x81 { + data[2] as usize; + data = &data[3..]; + } else { + data[1] as usize; // ~158 for ssh ed25519 signatures (which have a ~150B commitment) + data = &data[2..]; + }; + + // step 1 of piv-go/ykAuthenticate + // https://github.com/go-piv/piv-go/blob/d5ec95eb3bec9c20d60611fb77b7caeed7d886b6/piv/piv.go#L359-L384 + if data.starts_with(&[0x80, 0x00]) { + // "request for witness" + // hint that this is an attempt to SetManagementKey + data = &data[2..]; + return self.request_for_witness(command, data); + } + + // step 2 of piv-go/ykAuthenticate + // https://github.com/go-piv/piv-go/blob/d5ec95eb3bec9c20d60611fb77b7caeed7d886b6/piv/piv.go#L415-L420 + if data.starts_with(&[0x80, 0x08]) { + data = &data[2..]; + return self.request_for_challenge(command, data); + } + + // expect '82 00' + if !data.starts_with(&[0x82, 0x00]) { + return Err(Status::IncorrectDataParameter); + } + data = &data[2..]; + + // // expect '81 20' + // if !data.starts_with(&[0x81, 0x20]) { + // return Err(Status::IncorrectDataParameter); + // } + // data = &data[2..]; + + // expect '81 81 96' + // if !data.starts_with(&[0x81, 0x81, 0x96]) { + if !data.starts_with(&[0x81, 0x81]) { + return Err(Status::IncorrectDataParameter); + } + let len = data[2] as usize; + data = &data[3..]; + + // if data.len() != 32 { + // return Err(Status::IncorrectDataParameter); + // } + if data.len() != len { + return Err(Status::IncorrectDataParameter); + } + + let mechanism = trussed::types::Mechanism::Ed25519; + let commitment = data; // 32B of data // 150B for ed25519 + // blocking::dbg!(commitment); + let serialization = trussed::types::SignatureSerialization::Asn1Der; // ed25519 disregards + + info_now!("looking for keyreference"); + let key_handle = match self.state.persistent(&mut self.trussed).keys.authentication_key { + Some(key) => key, + None => return Err(Status::KeyReferenceNotFound), + }; + + let signature = block!(self.trussed.sign(mechanism, key_handle, commitment, serialization).unwrap()) + .map_err(|_error| { + // NoSuchKey + debug_now!("{:?}", &_error); + Status::UnspecifiedNonpersistentExecutionError } + )? + .signature; + // blocking::dbg!(&signature); + + let mut der: Der = Default::default(); + // 7c = Dynamic Authentication Template tag + der.nested(0x7c, |der| { + // 82 = response + der.raw_tlv(0x82, &signature) + }).unwrap(); + // blocking::dbg!(&der); + + let response_data: ResponseData = der.to_bytes(); + // blocking::dbg!(&response_data); + return Ok(response_data); + + // blocking::dbg!("NOW WE SHOULD WORK"); + // Err(Status::FunctionNotSupported) + } + + fn request_for_challenge(&mut self, command: &Command, remaining_data: &[u8]) -> ResponseResult { + // - data is of the form + // 00 87 03 9B 16 7C 14 80 08 99 6D 71 40 E7 05 DF 7F 81 08 6E EF 9C 02 00 69 73 E8 + // - remaining data contains 81 08 + // - we must a) verify the decrypted challenge, b) decrypt the counter challenge + + if command.p1 != 0x03 || command.p2 != 0x9b { + return Err(Status::IncorrectP1OrP2Parameter); + } + + if remaining_data.len() != 8 + 2 + 8 { + return Err(Status::IncorrectDataParameter); + } + + // A) verify decrypted challenge + let (response, data) = remaining_data.split_at(8); + + use state::{AuthenticateManagement, CommandCache}; + let our_challenge = match self.state.runtime.command_cache { + Some(CommandCache::AuthenticateManagement(AuthenticateManagement { challenge } )) + => challenge, + _ => { return Err(Status::InstructionNotSupportedOrInvalid); } + }; + // no retries ;) + self.state.runtime.command_cache = None; + + if &our_challenge != response { + debug_now!("{:?}", &our_challenge); + debug_now!("{:?}", &response); + return Err(Status::IncorrectDataParameter); + } + + self.state.runtime.app_security_status.management_verified = true; + + // B) encrypt their challenge + let (header, challenge) = data.split_at(2); + if header != &[0x81, 0x08] { + return Err(Status::IncorrectDataParameter); + } + + let key = self.state.persistent(&mut self.trussed).keys.management_key; + + let encrypted_challenge = syscall!(self.trussed.encrypt_tdes(&key, &challenge)).ciphertext; + + let mut der: Der = Default::default(); + // 7c = Dynamic Authentication Template tag + der.nested(0x7c, |der| { + // 82 = response + der.raw_tlv(0x82, &encrypted_challenge) + }).unwrap(); + + let response_data: ResponseData = der.to_bytes(); + // blocking::dbg!(&response_data); + return Ok(response_data); + } + + fn request_for_witness(&mut self, command: &Command, remaining_data: &[u8]) -> ResponseResult { + // invariants: parsed data was '7C L1 80 00' + remaining_data + + if command.p1 != 0x03 || command.p2 != 0x9b { + return Err(Status::IncorrectP1OrP2Parameter); + } + + if !remaining_data.is_empty() { + return Err(Status::IncorrectDataParameter); + } + + let key = self.state.persistent(&mut self.trussed).keys.management_key; + + let challenge = syscall!(self.trussed.random_bytes(8)).bytes; + let command_cache = state::AuthenticateManagement { challenge: challenge[..].try_into().unwrap() }; + self.state.runtime.command_cache = Some(state::CommandCache::AuthenticateManagement(command_cache)); + + let encrypted_challenge = block!(self.trussed.encrypt_tdes(&key, &challenge).unwrap()).unwrap().ciphertext; + + let mut der: Der = Default::default(); + // 7c = Dynamic Authentication Template tag + der.nested(0x7c, |der| { + // 80 = witness + der.raw_tlv(0x80, &encrypted_challenge) + }).unwrap(); + + return Ok(der.to_bytes()); + + } + + fn change_reference_data(&mut self, command: &Command) -> ResponseResult { + // The way `piv-go` blocks PUK (which it needs to do because Yubikeys only + // allow their Reset if PIN+PUK are blocked) is that it sends "change PUK" + // with random (i.e. incorrect) PUK listed as both old and new PUK. + // + // 00 24 00 81 10 + // 32 38 36 34 31 39 30 36 32 38 36 34 31 39 30 36 + // + // For now, we don't support PUK, so we can just return "Blocked" directly + // if the key reference in P2 is '81' = PUK + + // application PIN + if command.p2 == 0x80 { + let remaining_retries = self.state.persistent(&mut self.trussed).remaining_pin_retries(); + + if remaining_retries == 0 { + return Err(Status::OperationBlocked); + } + + if command.data().len() != 16 { + return Err(Status::IncorrectDataParameter); + } + + let (old_pin, new_pin) = command.data().split_at(8); + + let old_pin = match state::Pin::try_new(old_pin) { + Ok(pin) => pin, + _ => return Err(Status::IncorrectDataParameter), + }; + + let new_pin = match state::Pin::try_new(new_pin) { + Ok(pin) => pin, + _ => return Err(Status::IncorrectDataParameter), + }; + + if !self.state.persistent(&mut self.trussed).verify_pin(&old_pin) { + let remaining = self.state.persistent(&mut self.trussed).increment_consecutive_pin_mismatches(&mut self.trussed); + self.state.runtime.app_security_status.pin_verified = false; + return Err(Status::RemainingRetries(remaining)); + } + + self.state.persistent(&mut self.trussed).reset_consecutive_pin_mismatches(&mut self.trussed); + self.state.persistent(&mut self.trussed).set_pin(&mut self.trussed, new_pin); + self.state.runtime.app_security_status.pin_verified = true; + return Ok(Default::default()); + } + + // PUK + if command.p2 == 0x81 { + let remaining_retries = self.state.persistent(&mut self.trussed).remaining_puk_retries(); + + if remaining_retries == 0 { + return Err(Status::OperationBlocked); + } + + if command.data().len() != 16 { + return Err(Status::IncorrectDataParameter); + } + + let (old_puk, new_puk) = command.data().split_at(8); + + let old_puk = match state::Pin::try_new(old_puk) { + Ok(puk) => puk, + _ => return Err(Status::IncorrectDataParameter), + }; + + let new_puk = match state::Pin::try_new(new_puk) { + Ok(puk) => puk, + _ => return Err(Status::IncorrectDataParameter), + }; + + if !self.state.persistent(&mut self.trussed).verify_puk(&old_puk) { + let remaining = self.state.persistent(&mut self.trussed).increment_consecutive_puk_mismatches(&mut self.trussed); + self.state.runtime.app_security_status.puk_verified = false; + return Err(Status::RemainingRetries(remaining)); + } + + self.state.persistent(&mut self.trussed).reset_consecutive_puk_mismatches(&mut self.trussed); + self.state.persistent(&mut self.trussed).set_puk(&mut self.trussed, new_puk); + self.state.runtime.app_security_status.puk_verified = true; + return Ok(Default::default()); + } + + + Err(Status::KeyReferenceNotFound) + } + + fn verify(&mut self, command: &Command) -> ResponseResult { + // we only implement our own PIN, not global Pin, not OCC data, not pairing code + if command.p2 != 0x80 { + return Err(Status::KeyReferenceNotFound); + } + + let p1 = command.p1; + if p1 != 0x00 && p1 != 0xFF { + return Err(Status::IncorrectP1OrP2Parameter); + } + + // all above failures shall not change security status or retry counter + + // 1) If p1 is FF, "log out" of PIN + if p1 == 0xFF { + if command.data().len() != 0 { + return Err(Status::IncorrectDataParameter); + } else { + self.state.runtime.app_security_status.pin_verified = false; + return Ok(Default::default()); + } + } + + // 2) Get retries (or whether verification is even needed) by passing no data + if p1 == 0x00 && command.data().len() == 0 { + if self.state.runtime.app_security_status.pin_verified { + return Ok(Default::default()); + } else { + let retries = self.state.persistent(&mut self.trussed).remaining_pin_retries(); + return Err(Status::RemainingRetries(retries)); + } + } + + // if malformed PIN is sent, no security implication + if command.data().len() != 8 { + return Err(Status::IncorrectDataParameter); + } + + let sent_pin = match state::Pin::try_new(&command.data()) { + Ok(pin) => pin, + _ => return Err(Status::IncorrectDataParameter), + }; + + // 3) Verify le PIN! + let remaining_retries = self.state.persistent(&mut self.trussed).remaining_pin_retries(); + if remaining_retries == 0 { + return Err(Status::OperationBlocked); + } + + if self.state.persistent(&mut self.trussed).verify_pin(&sent_pin) { + self.state.persistent(&mut self.trussed).reset_consecutive_pin_mismatches(&mut self.trussed); + self.state.runtime.app_security_status.pin_verified = true; + Ok(Default::default()) + + } else { + let remaining = self.state.persistent(&mut self.trussed).increment_consecutive_pin_mismatches(&mut self.trussed); + self.state.runtime.app_security_status.pin_verified = false; + Err(Status::RemainingRetries(remaining)) + } + } + + fn generate_asymmetric_keypair(&mut self, command: &Command) -> ResponseResult { + if !self.state.runtime.app_security_status.management_verified { + return Err(Status::SecurityStatusNotSatisfied); + } + + if command.p1 != 0x00 { + return Err(Status::IncorrectP1OrP2Parameter); + } + + if command.p2 != 0x9a { + // TODO: make more general + return Err(Status::FunctionNotSupported); + } + + // example: 00 47 00 9A 0B + // AC 09 + // # P256 + // 80 01 11 + // # 0xAA = Yubico extension (of course...), PinPolicy, 0x2 = + // AA 01 02 + // # 0xAB = Yubico extension (of course...), TouchPolicy, 0x2 = + // AB 01 02 + // + // var touchPolicyMap = map[TouchPolicy]byte{ + // TouchPolicyNever: 0x01, + // TouchPolicyAlways: 0x02, + // TouchPolicyCached: 0x03, + // } + + // var pinPolicyMap = map[PINPolicy]byte{ + // PINPolicyNever: 0x01, + // PINPolicyOnce: 0x02, + // PINPolicyAlways: 0x03, + // } + + // TODO: iterate on this, don't expect tags.. + let input = derp::Input::from(&command.data()); + // let (mechanism, parameter) = input.read_all(derp::Error::Read, |input| { + let (mechanism, _pin_policy, _touch_policy) = input.read_all(derp::Error::Read, |input| { + derp::nested(input, 0xac, |input| { + let mechanism = derp::expect_tag_and_get_value(input, 0x80)?; + // let parameter = derp::expect_tag_and_get_value(input, 0x81)?; + let pin_policy = derp::expect_tag_and_get_value(input, 0xaa)?; + let touch_policy = derp::expect_tag_and_get_value(input, 0xab)?; + // Ok((mechanism.as_slice_less_safe(), parameter.as_slice_less_safe())) + Ok(( + mechanism.as_slice_less_safe(), + pin_policy.as_slice_less_safe(), + touch_policy.as_slice_less_safe(), + )) + }) + }).map_err(|_e| { + info_now!("error parsing GenerateAsymmetricKeypair: {:?}", &_e); + Status::IncorrectDataParameter + })?; + + // if mechanism != &[0x11] { + // HA! patch in Ed25519 + if mechanism != &[0x22] { + return Err(Status::InstructionNotSupportedOrInvalid); + } + + // ble policy + + if let Some(key) = self.state.persistent(&mut self.trussed).keys.authentication_key { + syscall!(self.trussed.delete(key)); + } + + // let key = syscall!(self.trussed.generate_p256_private_key( + // let key = syscall!(self.trussed.generate_p256_private_key( + let key = syscall!(self.trussed.generate_ed25519_private_key( + trussed::types::StorageLocation::Internal, + )).key; + + + // // TEMP + // let mechanism = trussed::types::Mechanism::P256Prehashed; + // let mechanism = trussed::types::Mechanism::P256; + // let commitment = &[37u8; 32]; + // // blocking::dbg!(commitment); + // let serialization = trussed::types::SignatureSerialization::Asn1Der; + // // blocking::dbg!(&key); + // let signature = block!(self.trussed.sign(mechanism, key.clone(), commitment, serialization).map_err(|e| { + // blocking::dbg!(e); + // e + // }).unwrap()) + // .map_err(|error| { + // // NoSuchKey + // blocking::dbg!(error); + // Status::UnspecifiedNonpersistentExecutionError } + // )? + // .signature; + // blocking::dbg!(&signature); + + self.state.persistent(&mut self.trussed).keys.authentication_key = Some(key); + self.state.persistent(&mut self.trussed).save(&mut self.trussed); + + // let public_key = syscall!(self.trussed.derive_p256_public_key( + let public_key = syscall!(self.trussed.derive_ed25519_public_key( + &key, + trussed::types::StorageLocation::Volatile, + )).key; + + let serialized_public_key = syscall!(self.trussed.serialize_key( + // trussed::types::Mechanism::P256, + trussed::types::Mechanism::Ed25519, + public_key.clone(), + trussed::types::KeySerialization::Raw, + )).serialized_key; + + // info_now!("supposed SEC1 pubkey, len {}: {:X?}", serialized_public_key.len(), &serialized_public_key); + + // P256 SEC1 has 65 bytes, Ed25519 pubkeys have 32 + // let l2 = 65; + let l2 = 32; + let l1 = l2 + 2; + let mut data = ResponseData::try_from_slice(&[0x7f, 0x49, l1, 0x86, l2]).unwrap(); + // data.extend_from_slice(&[0x04]).unwrap(); + data.extend_from_slice(&serialized_public_key).unwrap(); + + Ok(data) + } + + fn put_data(&mut self, command: &Command) -> ResponseResult { + info_now!("PutData"); + if command.p1 != 0x3f || command.p2 != 0xff { + return Err(Status::IncorrectP1OrP2Parameter); + } + + // if !self.state.runtime.app_security_status.management_verified { + // return Err(Status::SecurityStatusNotSatisfied); + // } + + // # PutData + // 00 DB 3F FF 23 + // # data object: 5FC109 + // 5C 03 5F C1 09 + // # data: + // 53 1C + // # actual data + // 88 1A 89 18 AA 81 D5 48 A5 EC 26 01 60 BA 06 F6 EC 3B B6 05 00 2E B6 3D 4B 28 7F 86 + // + + let input = derp::Input::from(&command.data()); + let (data_object, data) = input.read_all(derp::Error::Read, |input| { + let data_object = derp::expect_tag_and_get_value(input, 0x5c)?; + let data = derp::expect_tag_and_get_value(input, 0x53)?; + Ok((data_object.as_slice_less_safe(), data.as_slice_less_safe())) + // }).unwrap(); + }).map_err(|_e| { + info_now!("error parsing PutData: {:?}", &_e); + Status::IncorrectDataParameter + })?; + + // info_now!("PutData in {:?}: {:?}", data_object, data); + + if data_object == &[0x5f, 0xc1, 0x09] { + // "Printed Information", supposedly + // Yubico uses this to store its "Metadata" + // + // 88 1A + // 89 18 + // # we see here the raw management key? amazing XD + // AA 81 D5 48 A5 EC 26 01 60 BA 06 F6 EC 3B B6 05 00 2E B6 3D 4B 28 7F 86 + + // TODO: use smarter quota rule, actual data sent is 28B + if data.len() >= 512 { + return Err(Status::UnspecifiedCheckingError); + } + + block!(self.trussed.write_file( + trussed::types::StorageLocation::Internal, + trussed::types::PathBuf::from(b"printed-information"), + trussed::types::Message::try_from_slice(data).unwrap(), + None, + ).unwrap()).map_err(|_| Status::NotEnoughMemory)?; + + return Ok(Default::default()); + } + + if data_object == &[0x5f, 0xc1, 0x05] { + // "X.509 Certificate for PIV Authentication", supposedly + // IOW, the cert for "authentication key" + // Yubico uses this to store its "Metadata" + // + // 88 1A + // 89 18 + // # we see here the raw management key? amazing XD + // AA 81 D5 48 A5 EC 26 01 60 BA 06 F6 EC 3B B6 05 00 2E B6 3D 4B 28 7F 86 + + // TODO: use smarter quota rule, actual data sent is 28B + if data.len() >= 512 { + return Err(Status::UnspecifiedCheckingError); + } + + block!(self.trussed.write_file( + trussed::types::StorageLocation::Internal, + trussed::types::PathBuf::from(b"authentication-key.x5c"), + trussed::types::Message::try_from_slice(data).unwrap(), + None, + ).unwrap()).map_err(|_| Status::NotEnoughMemory)?; + + return Ok(Default::default()); + } + + Err(Status::IncorrectDataParameter) + } + + fn get_data(&mut self, command: &Command) -> ResponseResult { + if command.p1 != 0x3f || command.p2 != 0xff { + return Err(Status::IncorrectP1OrP2Parameter); + } + + // TODO: adapt `derp` and use a proper DER parser + + 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); + + // 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 => { + // Err(Status::InstructionNotSupportedOrInvalid) + let data = ResponseData::try_from_slice(DISCOVERY_OBJECT).unwrap(); + Ok(data) + // todo!("discovery object"), + } + + DataObjects::BiometricInformationTemplate => { + Err(Status::InstructionNotSupportedOrInvalid) + // todo!("biometric information template"), + } + + // '5FC1 02' (351B) + DataObjects::CardHolderUniqueIdentifier => { + // pivy: https://git.io/JfzBo + // https://www.idmanagement.gov/wp-content/uploads/sites/1171/uploads/TIG_SCEPACS_v2.3.pdf + let mut der = Der::::default(); + der.nested(0x53, |der| { + // der.raw_tlv(0x30, FASC_N)?; // pivy: 26B, TIG: 25B + der.raw_tlv(0x30, &[0x99, 0x99])?; // 9999 = non-federal; pivy: 26B, TIG: 25B + // der.raw_tlv(0x34, DUNS)?; // ? - pivy skips + der.raw_tlv(0x34, GUID)?; // 16B type 1,2,4 UUID + // der.raw_tlv(0x35, EXPIRATION_DATE)?; // [u8; 8], YYYYMMDD + der.raw_tlv(0x35, b"22220101")?; // [u8; 8], YYYYMMDD + // der.raw_tlv(0x36, CARDHOLDER_UUID)?; // 16B, like GUID + // der.raw_tlv(0x3E, SIGNATURE)?; // ? - pivy only checks for non-zero entry + der.raw_tlv(0x3E, b" ")?; // ? - pivy only checks for non-zero entry + Ok(()) + }).unwrap(); + + Ok(der.to_bytes()) + } + + // '5FC1 05' (351B) + DataObjects::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 = block!(self.trussed.read_file( + trussed::types::StorageLocation::Internal, + trussed::types::PathBuf::from(b"authentication-key.x5c"), + ).unwrap()).map_err(|_| { + // info_now!("error loading: {:?}", &e); + Status::NotFound + } )?.data; + // info_now!("got the data: {:?}", &data); + + let mut der: Der = Default::default(); + der.raw_tlv(0x53, &data).unwrap(); + Ok(der.to_bytes()) + } + + // '5F FF01' (754B) + YubicoObjects::AttestationCertificate => { + let data = ResponseData::try_from_slice(YUBICO_ATTESTATION_CERTIFICATE).unwrap(); + Ok(data) + } + + _ => return Err(Status::NotFound), + } + } + + fn yubico_piv_extension(&mut self, command: &Command, instruction: YubicoPivExtension) -> ResponseResult { + info_now!("yubico extension: {:?}", &instruction); + match instruction { + YubicoPivExtension::GetSerial => { + // make up a 4-byte serial + let data = ResponseData::try_from_slice( + &[0x00, 0x52, 0xf7, 0x43]).unwrap(); + Ok(data) + } + + YubicoPivExtension::GetVersion => { + // make up a version, be >= 5.0.0 + let data = ResponseData::try_from_slice( + &[0x06, 0x06, 0x06]).unwrap(); + Ok(data) + } + + YubicoPivExtension::Attest => { + if command.p2 != 0x00 { + return Err(Status::IncorrectP1OrP2Parameter); + } + + let slot = command.p1; + + if slot == 0x9a { + let data = ResponseData::try_from_slice(YUBICO_ATTESTATION_CERTIFICATE_FOR_9A).unwrap(); + return Ok(data); + } + + Err(Status::FunctionNotSupported) + } + + YubicoPivExtension::Reset => { + if command.p1 != 0x00 || command.p2 != 0x00 { + return Err(Status::IncorrectP1OrP2Parameter); + } + + // TODO: find out what all needs resetting :) + self.state.persistent(&mut self.trussed).reset_pin(&mut self.trussed); + self.state.persistent(&mut self.trussed).reset_puk(&mut self.trussed); + self.state.persistent(&mut self.trussed).reset_management_key(&mut self.trussed); + self.state.runtime.app_security_status.pin_verified = false; + self.state.runtime.app_security_status.puk_verified = false; + self.state.runtime.app_security_status.management_verified = false; + + block!(self.trussed.remove_file( + trussed::types::StorageLocation::Internal, + trussed::types::PathBuf::from(b"printed-information"), + ).unwrap()).ok(); + + block!(self.trussed.remove_file( + trussed::types::StorageLocation::Internal, + trussed::types::PathBuf::from(b"authentication-key.x5c"), + ).unwrap()).ok(); + + Ok(Default::default()) + } + + YubicoPivExtension::SetManagementKey => { + // cmd := apdu{ + // instruction: insSetMGMKey, + // param1: 0xff, + // param2: 0xff, + // data: append([]byte{ + // alg3DES, keyCardManagement, 24, + // }, key[:]...), + // } + // TODO check we are authenticated with old management key + if command.p1 != 0xff || (command.p2 != 0xff && command.p2 != 0xfe) { + return Err(Status::IncorrectP1OrP2Parameter); + } + + let data = &command.data(); + + // example: 03 9B 18 + // B0 20 7A 20 DC 39 0B 1B A5 56 CC EB 8D CE 7A 8A C8 23 E6 F5 0D 89 17 AA + if data.len() != 3 + 24 { + return Err(Status::IncorrectDataParameter); + } + let (prefix, new_management_key) = data.split_at(3); + if prefix != &[0x03, 0x9b, 0x18] { + return Err(Status::IncorrectDataParameter); + } + let new_management_key: [u8; 24] = new_management_key.try_into().unwrap(); + self.state.persistent(&mut self.trussed).set_management_key(&mut self.trussed, &new_management_key); + + Ok(Default::default()) + } + + _ => Err(Status::FunctionNotSupported), + } + } + +} + + +impl applet::Aid for App +where T: TrussedClient +{ + + fn aid(&self) -> &'static [u8] { + &constants::PIV_AID + } + + fn right_truncated_length(&self) -> usize { + 11 + } +} + + +impl applet::Applet for App +where T: TrussedClient +{ + fn select(&mut self, _apdu: &Command) -> applet::Result { + let mut der: Der = Default::default(); + der.nested(0x61, |der| { + // Application identifier of application: + // -> PIX (without RID, with version) + der.raw_tlv(0x4f, &PIV_PIX)?; + + // Application label: + // "Text describing the application; e.g., for use on a man-machine interface." + der.raw_tlv(0x50, APPLICATION_LABEL)?; + + // Uniform resource locator: + // "Reference to the specification describing the application." + der.raw_tlv2(0x5F50, APPLICATION_URL)?; + + // Cryptographic algorithms supported: + // "Cryptographic algorithm identifier template. See Table 5." + der.nested(0xAC, |der| { + // 0x80: Cryptographic algorithm identifier + // "For values see [SP800-78, Table 6-2]" + + // 0C: AES-256 + der.raw_tlv(0x80, &[0x0C])?; + // 11: ECC-P256 + der.raw_tlv(0x80, &[0x11])?; + + // 22 (non-standard!): Ed25519 + der.raw_tlv(0x80, &[0x22])?; + + // mandatory "Object identifier" with value set to 0x00 + der.raw_tlv(0x06, &[0x00]) + })?; + + // Coexistent tag allocation authority + der.nested(0x79, |der| { + // Application identifier + der.raw_tlv(0x4f, NIST_RID) + // })?; + }) + }).unwrap(); + + return Ok(applet::Response::Respond(der.to_bytes())); + } + + fn deselect(&mut self) {} + + fn call(&mut self, _type: applet::InterfaceType, apdu: &Command) -> applet::Result { + match self.try_handle(apdu) { + Ok(data) => { + Ok(applet::Response::Respond(data)) + } + Err(status) => { + Err(status) + } + } + } +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..0f8bcea --- /dev/null +++ b/src/state.rs @@ -0,0 +1,389 @@ +use heapless_bytes::Bytes; +use trussed::{ + block, + Client as TrussedClient, + syscall, + types::{ObjectHandle, PathBuf, StorageLocation}, +}; + +use crate::constants::*; + +pub type Result = core::result::Result; + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct State { + // at startup, trussed is not callable yet. + // moreover, when worst comes to worst, filesystems are not available + persistent: Option, + pub runtime: Runtime, + // temporary "state", to be removed again + // pub hack: Hack, + // trussed: RefCell>, +} + +impl State { + pub fn new() -> Self { + Default::default() + } + + // it would be nicer to do this during "board bringup", by using TrussedService as Syscall + // + // TODO: it is really not good to overwrite user data on failure to decode old state. + // To fix this, need a flag to detect if we're "fresh", and/or initialize state in factory. + pub fn persistent(&mut self, trussed: &mut T) -> &mut Persistent { + if self.persistent.is_none() { + self.persistent = Some(match Persistent::load(trussed) { + Ok(previous_self) => { + // hprintln!("loading succeeded!\n{:?}", &previous_self).ok(); + previous_self + } + Err(_) => { + + // hprintln!("loading failed: {:?}", e).ok(); + Persistent::initialize(trussed) + } + }); + } + self.persistent.as_mut().unwrap() + } +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct Pin { + // padded_pin: [u8; 8] + padded_pin: heapless_bytes::Bytes, +} + +// impl Default for Pin { +// /// Default is "202020" +// /// But right now we have to use "123456" cause.. Filo +// fn default() -> Self { +// // Self::try_new(b"202020\xff\xff").unwrap() +// Self::try_new(b"123456\xff\xff").unwrap() +// } +// } + +impl Pin { + pub fn try_new(padded_pin: &[u8]) -> Result { + if padded_pin.len() != 8 { + return Err(()); + } + let first_pad_byte = padded_pin.iter().position(|&b| b == 0xff); + let unpadded_pin = match first_pad_byte { + Some(l) => &padded_pin[..l], + None => padded_pin, + }; + if unpadded_pin.len() < 6 { + return Err(()); + } + let valid_bytes = unpadded_pin.iter().all(|&b| b >= b'0' && b <= b'9'); + if valid_bytes { + Ok(Self { + // padded_pin: padded_pin.try_into().unwrap(), + padded_pin: Bytes::try_from_slice(padded_pin).unwrap(),//padded_pin.try_into().unwrap(), + }) + } else { + Err(()) + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct Persistent { + pub keys: Keys, + consecutive_pin_mismatches: u8, + consecutive_puk_mismatches: u8, + // the PIN can be 6-8 digits, padded with 0xFF if <8 + // we just store all of them for now. + pin: Pin, + // the PUK should be 8 digits, but it seems Yubico allows 6-8 + // like for PIN + puk: Pin, + // pin_hash: Option<[u8; 16]>, + // Ideally, we'd dogfood a "Monotonic Counter" from `trussed`. + timestamp: u32, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct Runtime { + // aid: Option< + // consecutive_pin_mismatches: u8, + + pub global_security_status: GlobalSecurityStatus, + pub currently_selected_application: SelectableAid, + pub app_security_status: AppSecurityStatus, + pub command_cache: Option, + pub chained_command: Option, +} + +pub trait Aid { + const AID: &'static [u8]; + const RIGHT_TRUNCATED_LENGTH: usize; + + fn len() -> usize { + Self::AID.len() + } + + fn full() -> &'static [u8] { + Self::AID + } + + fn right_truncated() -> &'static [u8] { + &Self::AID[..Self::RIGHT_TRUNCATED_LENGTH] + } + + fn pix() -> &'static [u8] { + &Self::AID[5..] + } + + fn rid() -> &'static [u8] { + &Self::AID[..5] + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum SelectableAid { + Piv(PivAid), + YubicoOtp(YubicoOtpAid), +} + +impl Default for SelectableAid { + fn default() -> Self { + Self::Piv(Default::default()) + } +} + +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +pub struct PivAid {} + +impl Aid for PivAid { + const AID: &'static [u8] = &PIV_AID; + const RIGHT_TRUNCATED_LENGTH: usize = 9; +} + +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +pub struct YubicoOtpAid {} + +impl Aid for YubicoOtpAid { + const AID: &'static [u8] = &YUBICO_OTP_AID; + const RIGHT_TRUNCATED_LENGTH: usize = 8; +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct GlobalSecurityStatus { +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct AppSecurityStatus { + pub pin_verified: bool, + pub puk_verified: bool, + pub management_verified: bool, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum CommandCache { + GetData(GetData), + AuthenticateManagement(AuthenticateManagement), +} + + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct GetData { +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AuthenticateManagement { + pub challenge: [u8; 8], +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct Keys { + // 9a "PIV Authentication Key" (YK: PIV Authentication) + #[serde(skip_serializing_if = "Option::is_none")] + pub authentication_key: Option, + // 9b "PIV Card Application Administration Key" (YK: PIV Management) + pub management_key: ObjectHandle, + // 9c "Digital Signature Key" (YK: Digital Signature) + #[serde(skip_serializing_if = "Option::is_none")] + pub signature_key: Option, + // 9d "Key Management Key" (YK: Key Management) + #[serde(skip_serializing_if = "Option::is_none")] + pub encryption_key: Option, + // 9e "Card Authentication Key" (YK: Card Authentication) + #[serde(skip_serializing_if = "Option::is_none")] + pub pinless_authentication_key: Option, +} + +impl Persistent +{ + pub const PIN_RETRIES_DEFAULT: u8 = 3; + // hmm...! + pub const PUK_RETRIES_DEFAULT: u8 = 5; + const FILENAME: &'static [u8] = b"persistent-state.cbor"; + const DEFAULT_PIN: &'static [u8] = b"123456\xff\xff"; + const DEFAULT_PUK: &'static [u8] = b"12345678"; + + pub fn remaining_pin_retries(&self) -> u8 { + if self.consecutive_pin_mismatches >= Self::PIN_RETRIES_DEFAULT { + 0 + } else { + Self::PIN_RETRIES_DEFAULT - self.consecutive_pin_mismatches + } + } + + pub fn remaining_puk_retries(&self) -> u8 { + if self.consecutive_puk_mismatches >= Self::PUK_RETRIES_DEFAULT { + 0 + } else { + Self::PUK_RETRIES_DEFAULT - self.consecutive_puk_mismatches + } + } + + pub fn verify_pin(&self, other_pin: &Pin) -> bool { + // hprintln!("verifying pin {:?} against {:?}", other_pin, &self.pin).ok(); + self.pin == *other_pin + } + + pub fn verify_puk(&self, other_puk: &Pin) -> bool { + // hprintln!("verifying puk {:?} against {:?}", other_puk, &self.puk).ok(); + self.puk == *other_puk + } + + pub fn set_pin(&mut self, trussed: &mut T, new_pin: Pin) { + self.pin = new_pin; + self.save(trussed); + } + + pub fn set_puk(&mut self, trussed: &mut T, new_puk: Pin) { + self.puk = new_puk; + self.save(trussed); + } + + pub fn reset_pin(&mut self, trussed: &mut T) { + self.set_pin(trussed, Pin::try_new(Self::DEFAULT_PIN).unwrap()); + self.reset_consecutive_pin_mismatches(trussed); + } + + pub fn reset_puk(&mut self, trussed: &mut T) { + self.set_puk(trussed, Pin::try_new(Self::DEFAULT_PUK).unwrap()); + self.reset_consecutive_puk_mismatches(trussed); + } + + pub fn increment_consecutive_pin_mismatches(&mut self, trussed: &mut T) -> u8 { + if self.consecutive_pin_mismatches >= Self::PIN_RETRIES_DEFAULT { + return 0; + } + + self.consecutive_pin_mismatches += 1; + self.save(trussed); + Self::PIN_RETRIES_DEFAULT - self.consecutive_pin_mismatches + } + + pub fn increment_consecutive_puk_mismatches(&mut self, trussed: &mut T) -> u8 { + if self.consecutive_puk_mismatches >= Self::PUK_RETRIES_DEFAULT { + return 0; + } + + self.consecutive_puk_mismatches += 1; + self.save(trussed); + Self::PUK_RETRIES_DEFAULT - self.consecutive_puk_mismatches + } + + pub fn reset_consecutive_pin_mismatches(&mut self, trussed: &mut T) -> u8 { + if self.consecutive_pin_mismatches != 0 { + self.consecutive_pin_mismatches = 0; + self.save(trussed); + } + + Self::PIN_RETRIES_DEFAULT + } + + pub fn reset_consecutive_puk_mismatches(&mut self, trussed: &mut T) -> u8 { + if self.consecutive_puk_mismatches != 0 { + self.consecutive_puk_mismatches = 0; + self.save(trussed); + } + + Self::PUK_RETRIES_DEFAULT + } + + pub fn reset_management_key(&mut self, trussed: &mut T) { + self.set_management_key(trussed, YUBICO_DEFAULT_MANAGEMENT_KEY); + } + + pub fn set_management_key(&mut self, trussed: &mut T, management_key: &[u8; 24]) { + let new_management_key = syscall!(trussed.unsafe_inject_tdes_key( + management_key, + trussed::types::StorageLocation::Internal, + )).key; + let old_management_key = self.keys.management_key; + self.keys.management_key = new_management_key; + self.save(trussed); + syscall!(trussed.delete(old_management_key)); + } + + pub fn initialize(trussed: &mut T) -> Self { + let management_key = syscall!(trussed.unsafe_inject_tdes_key( + YUBICO_DEFAULT_MANAGEMENT_KEY, + trussed::types::StorageLocation::Internal, + )).key; + + let keys = Keys { + authentication_key: None, + management_key: management_key, + signature_key: None, + encryption_key: None, + pinless_authentication_key: None, + }; + + Self { + keys, + consecutive_pin_mismatches: 0, + consecutive_puk_mismatches: 0, + pin: Pin::try_new(Self::DEFAULT_PIN).unwrap(), + puk: Pin::try_new(Self::DEFAULT_PUK).unwrap(), + timestamp: 0, + } + } + + pub fn load(trussed: &mut T) -> Result { + let data = block!(trussed.read_file( + StorageLocation::Internal, + PathBuf::from(Self::FILENAME), + ).unwrap() + ).map_err(|e| { + // hprintln!("loading error: {:?}", &e).ok(); + drop(e) + })?.data; + + let previous_state: Persistent = trussed::cbor_deserialize(&data).map_err(|e| { + // hprintln!("cbor deser error: {:?}", e); + // hprintln!("data: {:X?}", &data).ok(); + drop(e) + })?; + // horrible deser bug to forget Ok here :) + Ok(previous_state) + } + + pub fn save(&self, trussed: &mut T) { + let data: trussed::types::Message = trussed::cbor_serialize_bytebuf(self).unwrap(); + + syscall!(trussed.write_file( + StorageLocation::Internal, + PathBuf::from(Self::FILENAME), + data, + None, + )); + } + + pub fn timestamp(&mut self, trussed: &mut T) -> u32 { + self.timestamp += 1; + self.save(trussed); + self.timestamp + } + +} + +impl Runtime { +} +