diff --git a/Cargo.toml b/Cargo.toml index ce7a1c2..59d60af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,10 @@ rust-crypto = "0.2" csv-core = "0.1.6" derive_builder = "0.9.0" crossbeam = { version = "0.7.3", optional = true } +serde_derive = "1.0.106" +serde = "1.0.106" +serde_cbor = "0.11.1" +serde_bytes = "0.11.3" [dev-dependencies] crossbeam = "0.7.3" hex = "0.4.0" diff --git a/src/protocol/cbor.rs b/src/protocol/cbor.rs new file mode 100644 index 0000000..1b5c2da --- /dev/null +++ b/src/protocol/cbor.rs @@ -0,0 +1,615 @@ +use num_traits::{FromPrimitive, ToPrimitive}; +use serde::de::{Error, MapAccess, Visitor}; +use serde::ser::{SerializeMap, SerializeStruct}; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use serde_cbor::de::IoRead; +use serde_cbor::ser::IoWrite; +use std::collections::{BTreeMap, HashMap}; +use std::fmt; +use std::fmt::Debug; +use std::io::{Read, Write}; + +#[deny(missing_debug_implementations)] + +pub trait CborSerializable: Serialize { + fn serialize_cbor(&self, sink: &mut impl Write) -> Result<(), serde_cbor::error::Error> { + self.serialize(&mut serde_cbor::Serializer::new(IoWrite::new(sink)).packed_format()) + } +} + +impl CborSerializable for T {} + +pub trait CborDeserializable<'a>: Deserialize<'a> { + fn deserialize_cbor(source: &mut impl Read) -> Result { + Self::deserialize(&mut serde_cbor::Deserializer::from_reader(source)) + } +} + +impl<'a, T: Deserialize<'a>> CborDeserializable<'a> for T {} + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub enum CborRequests { + __Bump, + // 0x01 + MakeCredential, + // 0x02 + GetAssertion, + // 0x03 + ___Unused, + // 0x04 + GetInfo(GetInfoRequest), + // 0x05 + ____Unused, + // 0x06 + ClientPin, + // 0x07 + Reset, + // 0x08 + GetNextAssertion, + // 0x09 + BioEnrolment, + // 0x0A + CredentialManagement, + // 0x0B + Selection, + // 0x0C + Config, +} + +pub type GetInfoRequest = (); + +#[derive(Debug, Clone, Serialize, Deserialize,Eq, PartialEq )] +pub struct GetInfoResponse { + /// Not used in protocol but required to bump the packed index to 0x01 + // 0x00 + #[serde(default)] + __bump: (), + // 0x01 + versions: Vec, + // 0x02 + extensions: Vec, + // 0x03 + #[serde(with = "serde_bytes")] + aaguid: Vec, + // 0x04 + options: GetInfoOptions, + // FIDO2.1 from here on therefore optional + // 0x05 + #[serde(rename = "maxMsgSize", default)] + max_msg_size: u64, + // 0x06 + #[serde(rename = "pinUvAuthProtocols")] + pin_auth_protocols: Vec, + // 0x07 + #[serde(rename = "maxCredentialCountInList", default)] + max_credential_count: u64, + // 0x08 + #[serde(rename = "maxCredentialIdLength", default)] + max_credential_id_len: u64, + // 0x09 + #[serde(default)] + transports: Vec, + // 0x0A + #[serde(default)] + algorithms: Vec, + // 0x0B + #[serde(rename = "maxAuthenticatorConfigLength", default)] + max_config_len: u64, + // 0x0C + #[serde(rename = "defaultCredProtect", default)] + default_cred_protect: CredProtect, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct GetInfoOptions { + #[serde(default)] + plat: bool, + #[serde(default)] + rk: bool, + #[serde(default, rename = "clientPin")] + client_pin: Option, + #[serde(default)] + up: bool, + #[serde(default)] + uv: bool, + #[serde(default, rename = "uvToken")] + uv_token: bool, + #[serde(default)] + config: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct PublicKeyCredentialParameters { + /// Not used in protocol but required to bump the packed index to 0x01 + // 0x00 + #[serde(default)] + __bump: (), + // 0x01 + #[serde(default, rename = "type")] + type_: PublicKeyCredentialType, + // 0x02 + alg: PublicKeyCredentialAlgorithm, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub enum PublicKeyCredentialType { + #[serde(rename = "public-key")] + PublicKey = 0, +} + +impl Default for PublicKeyCredentialType { + fn default() -> Self { + PublicKeyCredentialType::PublicKey + } +} + +#[derive(Debug, Clone, FromPrimitive, ToPrimitive, Eq, PartialEq)] +pub enum PublicKeyCredentialAlgorithm { + ES256 = -7, + // Unsupported + EdDSA = -8, + // Unsupported + ES384 = -35, + // Unsupported + ES512 = -36, + // Unsupported + PS256 = -37, + // Unsupported + PS384 = -38, + // Unsupported + PS512 = -39, + // Unsupported + RS256 = -257, + // Unsupported + //#[serde(other)] + Unknown = std::i32::MIN as isize, +} + +impl Default for PublicKeyCredentialAlgorithm { + fn default() -> Self { + PublicKeyCredentialAlgorithm::Unknown + } +} + +impl Serialize for PublicKeyCredentialAlgorithm { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_i32(self.to_i32().unwrap()) + } +} + +impl<'de> Deserialize<'de> for PublicKeyCredentialAlgorithm { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct AlgorithmVisitor; + impl<'de> Visitor<'de> for AlgorithmVisitor { + type Value = i32; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("an integer between -2^31 and 2^31") + } + + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + Ok(value as i32) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + Ok(value as i32) + } + } + Ok(PublicKeyCredentialAlgorithm::from_i32(dbg!( + deserializer.deserialize_i32(AlgorithmVisitor)? + )) + .unwrap_or_default()) //Not ideal + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub enum CredProtect { + VerificationOptional, + VerificationOptionalWithCredentialIDList, + VerificationRequired, +} + +impl Default for CredProtect { + fn default() -> Self { + CredProtect::VerificationOptional + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct GetClientPinRequest { + /// Not used in protocol but required to bump the packed index to 0x01 + // 0x00 + #[serde(default)] + __bump: (), + // 0x01 + #[serde(rename = "pinUvAuthProtocol")] + pin_auth_protocols: u64, + // 0x02 + #[serde(rename = "subCommand")] + sub_command: GetClientPinSubommand, + // 0x03 + #[serde(rename = "keyAgreement", default)] + key_agreement: Option, + // 0x04 + #[serde(rename = "pinUvAuthParam", with = "serde_bytes", default)] + pin_auth_param: Vec, + // 0x05 + #[serde(rename = "newPinEnc", with = "serde_bytes", default)] + new_pin_enc: Vec, + // 0x06 + #[serde(rename = "pinHashEnc", with = "serde_bytes", default)] + pin_hash_enc: Vec, +} +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub enum GetClientPinSubommand { + /// Not used in protocol but required to bump the packed index to 0x01 + // 0x00 + __bump, + // 0x01 + #[serde(rename = "getPINRetries")] + GetPINRetries, + // 0x02 + #[serde(rename = "getKeyAgreement")] + GetKeyAgreement, + // 0x03 + #[serde(rename = "setPIN")] + SetPIN, + // 0x04 + #[serde(rename = "changePIN")] + ChangePIN, + // 0x05 + #[serde(rename = "getPinUvAuthTokenUsingPin")] + GetPinUvAuthTokenUsingPin, + // 0x06 + #[serde(rename = "getPinUvAuthTokenUsingUv")] + GetPinUvAuthTokenUsingUv, + // 0x07 + #[serde(rename = "getUVRetries")] + GetUVRetries, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct GetClientPinResponse { + /// Not used in protocol but required to bump the packed index to 0x01 + // 0x00 + #[serde(default)] + __bump: (), + // 0x01 + #[serde(rename = "keyAgreement", default)] + key_agreement: Option, + // 0x02 + /// Encrypted pinUvAuthToken using sharedSecret used in subsequent authenticatorMakeCredential authenticatorGetAssertion operations + #[serde(rename = "pinUvAuthToken", with = "serde_bytes", default)] + pin_auth_token: Vec, + // 0x03 + /// Number of PIN attempts remaining before lockout. + #[serde(rename = "pinRetries", default)] + pin_retries: u8, + // 0x04 + /// Present and true if the authenticator requires a power cycle + /// before any future PIN operation, false if no power cycle + /// needed. If the field is omitted, no information is given about + /// whether a power cycle is needed or not. + #[serde(rename = "powerCycleState", default)] + requires_power_cycle: bool, + // 0x05 + /// Number of uv attempts remaining before lockout. + #[serde(rename = "uvRetries", default)] + uv_retries: u8, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct CoseKey { + /// Not used in protocol but required to bump the packed index to 0x01 + // 0x00 + // #[serde(default)] + //__bump: (), + // 0x01 + //#[serde(rename = "type", default)] + type_: PublicKeyCredentialType, + // 0x02 + //#[serde(with = "serde_bytes", default)] + id: Vec, + // 0x03 + alg: PublicKeyCredentialAlgorithm, + parameters: BTreeMap, +} + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct CoseKeyDescriptor { + /// Not used in protocol but required to bump the packed index to 0x01 + // 0x00 + #[serde(default)] + __bump: (), + #[serde(rename = "type")] + type_: PublicKeyCredentialType, + #[serde(with = "serde_bytes", default)] + id: Vec, + alg: PublicKeyCredentialAlgorithm, + parameters: BTreeMap, +} + +impl Serialize for CoseKey { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut s = serializer.serialize_map(Some(self.parameters.len() + 2 + (!self.id.is_empty()) as usize))?; + s.serialize_entry(&0x01, &self.type_); + if !self.id.is_empty() { + s.serialize_entry(&0x02, &self.id); + } + s.serialize_entry(&0x03, &self.alg); + for (k, v) in &self.parameters { + s.serialize_entry(k, v)?; + } + s.end() + } +} + +impl<'de> Deserialize<'de> for CoseKey { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + + enum Field { + Type, Alg, Id, Other(i32) + } + + impl<'de> Deserialize<'de> for Field { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct FieldVisitor; + + impl<'de> Visitor<'de> for FieldVisitor { + type Value = Field; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("type, alg, id or other") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + match value { + "alg" => Ok(Field::Alg), + "type" => Ok(Field::Type), + "id" => Ok(Field::Id), + _ => Err(de::Error::unknown_field(value, &["type", "alg", "id"])), + } + } + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + match value { + 0x01 => Ok(Field::Type), + 0x02 => Ok(Field::Id), + 0x03=> Ok(Field::Alg), + other => Ok(Field::Other(other as i32)), + } + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + let signed: i64 = value as i64; + self.visit_i64(signed) + } + } + + deserializer.deserialize_identifier(FieldVisitor) + } + } + + struct CoseKeyVisitor; + + impl<'de> Visitor<'de> for CoseKeyVisitor { + type Value = CoseKey; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("struct CoseKey") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut type_: Option = None; + let mut alg: Option = None; + let mut id: Option = None; + let mut parameters: BTreeMap = BTreeMap::new(); + while let Some(k) = map.next_key()? { + match k { + Field::Type => type_ = Some(map.next_value()?), + Field::Id => id = Some(map.next_value()?), + Field::Alg => alg = Some(map.next_value()?), + Field::Other(i) => {parameters.insert(i, map.next_value()?);} + }; + } + let type_ = type_.ok_or_else(|| serde::de::Error::missing_field("type"))?; + let id = id.map(|id| id.into_vec()).unwrap_or_else(|| vec![]); + let alg = alg.ok_or_else(|| serde::de::Error::missing_field("alg"))?; + Ok(CoseKey{ + type_, + id, + alg, + parameters, + }) + } + } + + deserializer.deserialize_map(CoseKeyVisitor) + } +} + +#[cfg(test)] +mod test { + + use super::*; + + fn assert_ok(res: Result) { + assert!(dbg!(res).is_ok()) + } + + mod info { + use super::*; + + fn test_response(mut resp: &[u8]) { + assert_ok(GetInfoResponse::deserialize_cbor(&mut resp)); + } + + #[test] + #[ignore] + fn response_yubikey() { + test_response(&[ + 170, 1, 131, 102, 85, 50, 70, 95, 86, 50, 104, 70, 73, 68, 79, 95, 50, 95, 48, 108, + 70, 73, 68, 79, 95, 50, 95, 49, 95, 80, 82, 69, 2, 130, 107, 99, 114, 101, 100, 80, + 114, 111, 116, 101, 99, 116, 107, 104, 109, 97, 99, 45, 115, 101, 99, 114, 101, + 116, 3, 80, 185, 44, 63, 154, 192, 20, 64, 86, 136, 127, 20, 10, 37, 1, 22, 59, 4, + 165, 98, 114, 107, 245, 98, 117, 112, 245, 100, 112, 108, 97, 116, 244, 105, 99, + 108, 105, 101, 110, 116, 80, 105, 110, 244, 117, 99, 114, 101, 100, 101, 110, 116, + 105, 97, 108, 77, 103, 109, 116, 80, 114, 101, 118, 105, 101, 119, 245, 5, 25, 4, + 176, 6, 129, 1, 7, 8, 8, 24, 128, 9, 129, 99, 117, 115, 98, 10, 130, 162, 99, 97, + 108, 103, 38, 100, 116, 121, 112, 101, 106, 112, 117, 98, 108, 105, 99, 45, 107, + 101, 121, 162, 99, 97, 108, 103, 39, 100, 116, 121, 112, 101, 106, 112, 117, 98, + 108, 105, 99, 45, 107, 101, 121, + ]); + } + + #[test] + #[ignore] + fn response_solokey() { + test_response(&[ + 168, 1, 131, 102, 85, 50, 70, 95, 86, 50, 104, 70, 73, 68, 79, 95, 50, 95, 48, 108, + 70, 73, 68, 79, 95, 50, 95, 49, 95, 80, 82, 69, 2, 130, 107, 104, 109, 97, 99, 45, + 115, 101, 99, 114, 101, 116, 107, 99, 114, 101, 100, 80, 114, 111, 116, 101, 99, + 116, 3, 80, 136, 118, 99, 27, 212, 160, 66, 127, 87, 115, 14, 199, 28, 158, 2, 121, + 4, 165, 98, 114, 107, 245, 98, 117, 112, 245, 100, 112, 108, 97, 116, 244, 104, 99, + 114, 101, 100, 77, 103, 109, 116, 245, 105, 99, 108, 105, 101, 110, 116, 80, 105, + 110, 244, 5, 25, 4, 176, 6, 129, 1, 7, 20, 8, 24, 128, + ]); + } + } + + mod client_pin { + use super::*; + + use crate::protocol::GetClientPinRequest; + + fn test_request(mut req: &[u8]) { + assert_ok(GetClientPinRequest::deserialize_cbor(&mut req)); + } + + fn test_response(mut resp: &[u8]) { + let res = GetClientPinResponse::deserialize_cbor(&mut resp).unwrap(); + let cose = res.key_agreement.unwrap(); + dbg!(cose.alg); + //assert_ne!(cose.alg, PublicKeyCredentialAlgorithm::Unknown); + /*let mut buf = Vec::new(); + cose.serialize_cbor(&mut buf).unwrap(); + let mut buf_slice = &buf[..]; + assert_eq!(cose, CoseKey::deserialize_cbor(&mut buf_slice).unwrap())*/ + } + + #[test] + fn req_key_agreement() { + test_request(&[162, 1, 1, 2, 2]) + } + + #[test] + fn cose_key_round() { + let key = CoseKey{ + type_: PublicKeyCredentialType::PublicKey, + id: vec![1u8, 3 ,4, 5], + alg: PublicKeyCredentialAlgorithm::PS512, + parameters: vec![(-3, serde_cbor::value::Value::Text("test".into()))].into_iter().collect() + }; + let mut buf = Vec::new(); + key.serialize_cbor(&mut buf).unwrap(); + let mut buf_slice = &buf[..]; + let key2 = CoseKey::deserialize_cbor(&mut buf_slice).unwrap(); + assert_eq!(key, key2); + } + + #[test] + #[ignore] + fn resp_key_agreement_yubikey() { + test_response(&[ + 161, 1, 165, 1, 2, 3, 56, 24, 32, 1, 33, 88, 32, 225, 230, 133, 231, 92, 119, 250, + 236, 61, 49, 83, 182, 138, 31, 226, 185, 149, 235, 126, 130, 32, 69, 185, 206, 169, + 0, 247, 169, 221, 63, 98, 253, 34, 88, 32, 61, 125, 120, 237, 105, 178, 59, 168, + 222, 89, 214, 218, 225, 237, 207, 166, 25, 51, 4, 85, 200, 172, 177, 121, 208, 241, + 75, 16, 68, 234, 86, 56, + ]); + } + + // SoloKey v1 fw 4.0.0 + #[test] + #[ignore] + fn resp_key_agreement_solokey() { + test_response(&[ + 161, 1, 165, 1, 2, 3, 56, 24, 32, 1, 33, 88, 32, 9, 161, 111, 235, 155, 109, 166, + 62, 78, 140, 241, 254, 59, 243, 234, 160, 36, 11, 234, 22, 156, 39, 195, 18, 114, + 187, 7, 51, 117, 33, 163, 39, 34, 88, 32, 122, 88, 3, 24, 219, 125, 161, 38, 156, + 117, 45, 246, 233, 246, 32, 124, 155, 33, 236, 78, 216, 17, 50, 134, 30, 80, 78, + 82, 247, 155, 238, 60, + ]) + } + + // Trezor Model T fw 2.3.0 + #[test] + #[ignore] + fn resp_key_agreement_trezor() { + test_response(&[ + 161, 1, 165, 1, 2, 3, 56, 24, 32, 1, 33, 88, 32, 164, 211, 77, 255, 139, 183, 45, + 143, 125, 177, 191, 110, 155, 139, 204, 44, 59, 191, 143, 107, 179, 129, 182, 9, + 254, 209, 183, 7, 135, 188, 110, 111, 34, 88, 32, 255, 220, 244, 80, 159, 87, 162, + 4, 52, 93, 145, 18, 189, 11, 246, 139, 99, 180, 132, 151, 79, 3, 220, 96, 108, 107, + 45, 138, 23, 64, 241, 27, + ]); + } + } + + //#[test] + fn vec_layout() { + #[derive(Debug, Clone, Serialize, Deserialize)] + struct VecLayout { + len: u8, + item1: String, + item2: String, + } + let vec = vec!["hello".to_string(), "world".into()]; + let mut native_bytes = Vec::new(); + vec.serialize(&mut serde_cbor::Serializer::new(&mut native_bytes).packed_format()) + .unwrap(); + let layout = VecLayout { + len: 2, + item1: "hello".into(), + item2: "world".into(), + }; + let mut layout_bytes = Vec::new(); + layout + .serialize(&mut serde_cbor::Serializer::new(&mut layout_bytes).packed_format()) + .unwrap(); + layout_bytes.push(0); + assert_eq!(dbg!(native_bytes), layout_bytes); + } +} diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs new file mode 100644 index 0000000..6baad76 --- /dev/null +++ b/src/protocol/mod.rs @@ -0,0 +1,3 @@ +mod cbor; + +pub use self::cbor::*;