From 33229a0b3c57dccdef77617cb7baf0d5b7b6b2db Mon Sep 17 00:00:00 2001 From: shimun Date: Sun, 26 Apr 2020 14:24:39 +0200 Subject: [PATCH] impl make credential --- src/protocol/cbor.rs | 460 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 395 insertions(+), 65 deletions(-) diff --git a/src/protocol/cbor.rs b/src/protocol/cbor.rs index 1b5c2da..d41203d 100644 --- a/src/protocol/cbor.rs +++ b/src/protocol/cbor.rs @@ -8,6 +8,8 @@ use std::collections::{BTreeMap, HashMap}; use std::fmt; use std::fmt::Debug; use std::io::{Read, Write}; +use std::borrow::Cow; +use failure::_core::marker::PhantomData; #[deny(missing_debug_implementations)] @@ -41,7 +43,7 @@ pub enum CborRequests { // 0x05 ____Unused, // 0x06 - ClientPin, + ClientPin(GetClientPinRequest), // 0x07 Reset, // 0x08 @@ -58,7 +60,7 @@ pub enum CborRequests { pub type GetInfoRequest = (); -#[derive(Debug, Clone, Serialize, Deserialize,Eq, PartialEq )] +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub struct GetInfoResponse { /// Not used in protocol but required to bump the packed index to 0x01 // 0x00 @@ -100,7 +102,7 @@ pub struct GetInfoResponse { default_cred_protect: CredProtect, } -#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub struct GetInfoOptions { #[serde(default)] plat: bool, @@ -118,7 +120,7 @@ pub struct GetInfoOptions { config: bool, } -#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub struct PublicKeyCredentialParameters { /// Not used in protocol but required to bump the packed index to 0x01 // 0x00 @@ -131,10 +133,13 @@ pub struct PublicKeyCredentialParameters { alg: PublicKeyCredentialAlgorithm, } -#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub enum PublicKeyCredentialType { #[serde(rename = "public-key")] PublicKey = 0, + NoTwo = 2, + #[serde(other)] + Other, } impl Default for PublicKeyCredentialType { @@ -143,11 +148,13 @@ impl Default for PublicKeyCredentialType { } } -#[derive(Debug, Clone, FromPrimitive, ToPrimitive, Eq, PartialEq)] +#[derive(Debug, Clone, FromPrimitive, ToPrimitive, Eq, PartialEq)] pub enum PublicKeyCredentialAlgorithm { ES256 = -7, // Unsupported EdDSA = -8, + // Compatibility reasons + ECDH_ES_HKDF_256 = -25, // Unsupported ES384 = -35, // Unsupported @@ -214,7 +221,7 @@ impl<'de> Deserialize<'de> for PublicKeyCredentialAlgorithm { } } -#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub enum CredProtect { VerificationOptional, VerificationOptionalWithCredentialIDList, @@ -227,7 +234,7 @@ impl Default for CredProtect { } } -#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub struct GetClientPinRequest { /// Not used in protocol but required to bump the packed index to 0x01 // 0x00 @@ -252,7 +259,7 @@ pub struct GetClientPinRequest { #[serde(rename = "pinHashEnc", with = "serde_bytes", default)] pin_hash_enc: Vec, } -#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub enum GetClientPinSubommand { /// Not used in protocol but required to bump the packed index to 0x01 // 0x00 @@ -280,7 +287,7 @@ pub enum GetClientPinSubommand { GetUVRetries, } -#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub struct GetClientPinResponse { /// Not used in protocol but required to bump the packed index to 0x01 // 0x00 @@ -312,32 +319,13 @@ pub struct GetClientPinResponse { #[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, + // remainder parameters: BTreeMap, } @@ -346,7 +334,9 @@ impl Serialize for CoseKey { where S: Serializer, { - let mut s = serializer.serialize_map(Some(self.parameters.len() + 2 + (!self.id.is_empty()) as usize))?; + 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); @@ -364,15 +354,17 @@ impl<'de> Deserialize<'de> for CoseKey { where D: Deserializer<'de>, { - enum Field { - Type, Alg, Id, Other(i32) + Type, + Alg, + Id, + Other(i32), } impl<'de> Deserialize<'de> for Field { fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, + where + D: Deserializer<'de>, { struct FieldVisitor; @@ -384,8 +376,8 @@ impl<'de> Deserialize<'de> for CoseKey { } fn visit_str(self, value: &str) -> Result - where - E: de::Error, + where + E: de::Error, { match value { "alg" => Ok(Field::Alg), @@ -394,24 +386,38 @@ impl<'de> Deserialize<'de> for CoseKey { _ => Err(de::Error::unknown_field(value, &["type", "alg", "id"])), } } + fn visit_bytes(self, value: &[u8]) -> Result + where + E: de::Error, + { + match value { + b"alg" => Ok(Field::Alg), + b"type" => Ok(Field::Type), + b"id" => Ok(Field::Id), + _ => Err(de::Error::unknown_field( + &format!("{:?}", value), + &["type", "alg", "id"], + )), + } + } fn visit_i64(self, value: i64) -> Result - where - E: de::Error, + where + E: de::Error, { match value { 0x01 => Ok(Field::Type), 0x02 => Ok(Field::Id), - 0x03=> Ok(Field::Alg), + 0x03 => Ok(Field::Alg), other => Ok(Field::Other(other as i32)), } } fn visit_u64(self, value: u64) -> Result - where - E: de::Error, + where + E: de::Error, { let signed: i64 = value as i64; - self.visit_i64(signed) + self.visit_i64(signed) } } @@ -438,16 +444,18 @@ impl<'de> Deserialize<'de> for CoseKey { let mut parameters: BTreeMap = BTreeMap::new(); while let Some(k) = map.next_key()? { match k { - Field::Type => type_ = Some(map.next_value()?), + 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()?);} + 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{ + Ok(CoseKey { type_, id, alg, @@ -460,13 +468,246 @@ impl<'de> Deserialize<'de> for CoseKey { } } +#[derive(Debug, Default, Clone,Serialize, Deserialize, PartialEq, Eq)] +pub struct MakeCredentialRequest<'a> { + /// Not used in protocol but required to bump the packed index to 0x01 + // 0x00 + #[serde(default)] + __bump: (), + // 0x01 + #[serde(rename = "clientDataHash")] + client_data_hash: Cow<'a, serde_bytes::Bytes>, + // 0x02 + rp: PublicKeyCredentialRpEntity<'a>, + // 0x03 + user: PublicKeyCredentialUserEntity<'a>, + // 0x04 + #[serde(rename = "pubKeyCredParams")] + pubkey_cred_params: Cow<'a, [PublicKeyCredentialParameters]>, + // 0x05 + #[serde(rename = "excludeList", default)] + exclude_list: Cow<'a, [PublicKeyCredentialDescriptor<'a>]>, + // 0x06 + #[serde(default)] + extensions: BTreeMap, Cow<'a ,serde_cbor::value::Value>>, + #[serde(default)] + options: AuthenticatorOptions<'a>, + #[serde(rename = "pinUvAuthParam", default)] + pin_auth_param: Option>, + #[serde(rename = "pinUvAuthProtocol", default)] + pin_auth_protocol: Option, +} + +#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq)] +pub struct PublicKeyCredentialRpEntity<'a> { + id: Cow<'a ,str>, + #[serde(default)] + name: Option>, + #[serde(default)] + icon: Option>, +} + +// Ensure keys aren't packed but instead serialized as string +impl<'a> Serialize for PublicKeyCredentialRpEntity<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut s = serializer.serialize_map(Some(1 + self.name.is_some() as usize + self.icon.is_some() as usize))?; + for (k, v) in &[("id", Some(&self.id)), ("name", self.name.as_ref()), ("icon", self.icon.as_ref())] { + s.serialize_entry(k, v)?; + } + s.end() + } +} + +#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq)] +pub struct PublicKeyCredentialUserEntity<'a> { + id: Cow<'a ,[u8]>, + #[serde(default)] + name: Option>, + #[serde(default)] + icon: Option>, +} + +// Ensure keys aren't packed but instead serialized as string +impl<'a> Serialize for PublicKeyCredentialUserEntity<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut s = serializer.serialize_map(Some(1 + self.name.is_some() as usize + self.icon.is_some() as usize))?; + s.serialize_entry("id", &self.id)?; + for (k, v) in &[("name", self.name.as_ref()), ("icon", self.icon.as_ref())] { + s.serialize_entry(k, v)?; + } + s.end() + } +} + +#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq)] +pub struct PublicKeyCredentialDescriptor<'a> { + #[serde(rename = "type")] + type_: PublicKeyCredentialType, + id: Cow<'a ,[u8]>, +} + +// Ensure keys aren't packed but instead serialized as string +impl<'a> Serialize for PublicKeyCredentialDescriptor<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut s = serializer.serialize_map(Some(2))?; + s.serialize_entry("type", &self.type_)?; + s.serialize_entry("id", &self.id)?; + s.end() + } +} + +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct AuthenticatorOptions<'a> { + rk: bool, + uv: bool, + other: BTreeMap, bool>, +} + +impl<'a> Serialize for AuthenticatorOptions<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut s = serializer.serialize_map(Some( + self.other.len() + [self.rk, self.uv].iter().filter(|o| **o).count(), + ))?; + for (k, v) in &[("rk", self.rk), ("uv", self.uv)] { + if *v { + s.serialize_entry(k, v)?; + } + } + for (k, v) in &self.other { + s.serialize_entry(k, v)?; + } + s.end() + } +} + +impl<'a, 'de> Deserialize<'de> for AuthenticatorOptions<'a> { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + enum Field { + RK, + UV, + Other(String), + } + + 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 { + "rk" => Ok(Field::RK), + "uv" => Ok(Field::UV), + other => Ok(Field::Other(other.into())), + } + } + fn visit_bytes(self, value: &[u8]) -> Result + where + E: de::Error, + { + match value { + b"rk" => Ok(Field::RK), + b"uv" => Ok(Field::UV), + _ => Err(de::Error::unknown_field( + &format!("{:?}", value), + &["rk", "uv"], + )), + } + } + } + + deserializer.deserialize_identifier(FieldVisitor) + } + } + + struct AuthenticatorOptionsVisitor; + + impl<'de> Visitor<'de> for AuthenticatorOptionsVisitor { + type Value = AuthenticatorOptions<'static>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("struct AuthenticatorOptions") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut options = AuthenticatorOptions::default(); + while let Some(k) = map.next_key()? { + match k { + Field::RK => options.rk = map.next_value()?, + Field::UV => options.uv = map.next_value()?, + Field::Other(i) => { + options.other.insert(Cow::Owned(i), map.next_value()?); + } + }; + } + Ok(options) + } + } + + deserializer.deserialize_map(AuthenticatorOptionsVisitor) + } +} + +#[derive(Debug, Default, Clone,Serialize, Deserialize, PartialEq, Eq)] +pub struct MakeCredentialResponse { + /// Not used in protocol but required to bump the packed index to 0x01 + // 0x00 + #[serde(default)] + __bump: (), + // 0x01 + fmt: String, + // 0x02 + #[serde(rename = "authData", with = "serde_bytes")] + auth_data: Vec, + // 0x03 + #[serde(rename = "attStmt")] + attestation_statement: BTreeMap, +} + #[cfg(test)] mod test { use super::*; fn assert_ok(res: Result) { - assert!(dbg!(res).is_ok()) + assert!(dbg!(res).is_ok()); + } + + fn encode_round<'a, T: Serialize + Deserialize<'a> + Debug + Eq>(val: T) { + let mut buf = Vec::new(); + val.serialize_cbor(&mut buf).unwrap(); + let mut buf_slice = &buf[..]; + let val2 = T::deserialize_cbor(&mut buf_slice).unwrap(); + assert_eq!(val, val2); } mod info { @@ -477,7 +718,6 @@ mod test { } #[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, @@ -495,7 +735,6 @@ mod test { } #[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, @@ -520,8 +759,8 @@ mod test { fn test_response(mut resp: &[u8]) { let res = GetClientPinResponse::deserialize_cbor(&mut resp).unwrap(); - let cose = res.key_agreement.unwrap(); - dbg!(cose.alg); + //let cose = res.key_agreement; + //dbg!(cose.alg); //assert_ne!(cose.alg, PublicKeyCredentialAlgorithm::Unknown); /*let mut buf = Vec::new(); cose.serialize_cbor(&mut buf).unwrap(); @@ -534,23 +773,41 @@ mod test { test_request(&[162, 1, 1, 2, 2]) } - #[test] - fn cose_key_round() { - let key = CoseKey{ + fn cose_key() -> CoseKey { + CoseKey { type_: PublicKeyCredentialType::PublicKey, - id: vec![1u8, 3 ,4, 5], + id: vec![8, 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); + parameters: vec![ + (-3, serde_cbor::value::Value::Text("test".into())), + ( + -42, + serde_cbor::value::Value::Bytes("test".as_bytes().to_vec()), + ), + ] + .into_iter() + .collect(), + } + } + + #[test] + fn cose_key_round() { + encode_round(cose_key()); + } + + #[test] + fn client_pin_resp_round() { + encode_round(GetClientPinResponse { + __bump: (), + key_agreement: Some(cose_key()), + pin_auth_token: vec![1, 2, 3, 4], + pin_retries: 3, + requires_power_cycle: true, + uv_retries: 1, + }); } #[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, @@ -563,7 +820,6 @@ mod test { // 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, @@ -576,7 +832,6 @@ mod test { // 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, @@ -587,6 +842,81 @@ mod test { ]); } } + + mod make_credential { + use super::*; + + fn rp_entity<'a>() -> PublicKeyCredentialRpEntity<'a> { + PublicKeyCredentialRpEntity { + id: Cow::Borrowed("test"), + name: Some("Test".into()), + icon: None + } + } + + fn user_entity<'a>() -> PublicKeyCredentialUserEntity<'a> { + PublicKeyCredentialUserEntity { + id: Cow::Borrowed(&[0u8]), + name: Some("Tester".into()), + icon: None + } + } + + fn pubkey_cred_desc<'a>() -> PublicKeyCredentialDescriptor<'a> { + PublicKeyCredentialDescriptor{ + type_: Default::default(), + id: Cow::Owned(vec![1, 2, 3, 4]) + } + } + + fn authenticator_options<'a>() -> AuthenticatorOptions<'a> { + AuthenticatorOptions{ + rk: false, + uv: true, + other: vec![(Cow::Borrowed("whatever the future may bring"), true)].into_iter().collect() + } + } + + fn make_credential_request<'a>() -> MakeCredentialRequest<'a> { + MakeCredentialRequest{ + __bump: (), + client_data_hash: Cow::Owned(serde_bytes::ByteBuf::from(vec![0u8; 32].into_boxed_slice())), + rp: rp_entity(), + user: user_entity(), + pubkey_cred_params: Cow::Owned(vec![PublicKeyCredentialParameters{ + __bump: (), + type_: Default::default(), + alg: PublicKeyCredentialAlgorithm::EdDSA + }]) , + exclude_list: Cow::Borrowed(&[]), + extensions: Default::default(), + options: authenticator_options(), + pin_auth_param: None, + pin_auth_protocol: None + } + } + + #[test] + fn rp_entity_round() { + encode_round(rp_entity()); + } + + #[test] + fn user_entity_round() { + encode_round(user_entity()); + } + + #[test] + fn pubkey_cred_desc_round() { + encode_round(pubkey_cred_desc()); + } + + #[test] + fn authenticator_options_round() { + encode_round(authenticator_options()); + } + + } //#[test] fn vec_layout() {