authenticator_ctap2_2021/ctap2/
client_data.rs1use serde::de::{self, Deserializer, Error as SerdeError, MapAccess, Visitor};
2use serde::ser::SerializeMap;
3use serde::{Deserialize, Serialize, Serializer};
4use serde_json as json;
5use sha2::{Digest, Sha256};
6use std::fmt;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
36pub enum TokenBinding {
37 Present(String),
38 Supported,
39}
40
41impl Serialize for TokenBinding {
42 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
43 where
44 S: Serializer,
45 {
46 let mut map = serializer.serialize_map(Some(2))?;
47 match *self {
48 TokenBinding::Supported => {
49 map.serialize_entry(&"status", &"supported")?;
50 }
51 TokenBinding::Present(ref v) => {
52 map.serialize_entry(&"status", "present")?;
53 map.serialize_entry(&"id", &v)?;
57 }
58 }
59 map.end()
60 }
61}
62
63impl<'de> Deserialize<'de> for TokenBinding {
64 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
65 where
66 D: Deserializer<'de>,
67 {
68 struct TokenBindingVisitor;
69
70 impl<'de> Visitor<'de> for TokenBindingVisitor {
71 type Value = TokenBinding;
72
73 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
74 formatter.write_str("a byte string")
75 }
76
77 fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
78 where
79 M: MapAccess<'de>,
80 {
81 let mut id = None;
82 let mut status = None;
83
84 while let Some(key) = map.next_key()? {
85 match key {
86 "status" => {
87 status = Some(map.next_value()?);
88 }
89 "id" => {
90 id = Some(map.next_value()?);
91 }
92 k => {
93 return Err(M::Error::custom(format!("unexpected key: {:?}", k)));
94 }
95 }
96 }
97
98 if let Some(stat) = status {
99 match stat {
100 "present" => {
101 if let Some(id) = id {
102 Ok(TokenBinding::Present(id))
103 } else {
104 Err(SerdeError::missing_field("id"))
105 }
106 }
107 "supported" => Ok(TokenBinding::Supported),
108 k => {
109 return Err(M::Error::custom(format!(
110 "unexpected status key: {:?}",
111 k
112 )));
113 }
114 }
115 } else {
116 Err(SerdeError::missing_field("status"))
117 }
118 }
119 }
120
121 deserializer.deserialize_map(TokenBindingVisitor)
122 }
123}
124
125#[derive(Debug, Copy, Clone, PartialEq, Eq)]
134pub enum WebauthnType {
135 Create,
136 Get,
137}
138
139impl Serialize for WebauthnType {
140 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
141 where
142 S: Serializer,
143 {
144 match *self {
145 WebauthnType::Create => serializer.serialize_str(&"webauthn.create"),
146 WebauthnType::Get => serializer.serialize_str(&"webauthn.get"),
147 }
148 }
149}
150
151impl<'de> Deserialize<'de> for WebauthnType {
152 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
153 where
154 D: Deserializer<'de>,
155 {
156 struct WebauthnTypeVisitor;
157
158 impl<'de> Visitor<'de> for WebauthnTypeVisitor {
159 type Value = WebauthnType;
160
161 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
162 formatter.write_str("a string")
163 }
164
165 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
166 where
167 E: de::Error,
168 {
169 match v {
170 "webauthn.create" => Ok(WebauthnType::Create),
171 "webauthn.get" => Ok(WebauthnType::Get),
172 _ => Err(E::custom("unexpected webauthn_type")),
173 }
174 }
175 }
176
177 deserializer.deserialize_str(WebauthnTypeVisitor)
178 }
179}
180
181#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
182pub struct Challenge(pub String);
183
184impl Challenge {
185 pub fn new(input: Vec<u8>) -> Self {
186 let value = base64::encode_config(&input, base64::URL_SAFE_NO_PAD);
187 Challenge(value)
188 }
189}
190
191impl From<Vec<u8>> for Challenge {
192 fn from(v: Vec<u8>) -> Challenge {
193 Challenge::new(v)
194 }
195}
196
197impl AsRef<[u8]> for Challenge {
198 fn as_ref(&self) -> &[u8] {
199 self.0.as_bytes()
200 }
201}
202
203pub type Origin = String;
204
205#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
206pub struct CollectedClientData {
207 #[serde(rename = "type")]
208 pub webauthn_type: WebauthnType,
209 pub challenge: Challenge,
210 pub origin: Origin,
211 #[serde(rename = "crossOrigin", default)]
215 pub cross_origin: bool,
216 #[serde(rename = "tokenBinding", skip_serializing_if = "Option::is_none")]
217 pub token_binding: Option<TokenBinding>,
218}
219
220#[derive(Debug, Eq, PartialEq)]
221pub struct ClientDataHash([u8; 32]);
222
223impl PartialEq<[u8]> for ClientDataHash {
224 fn eq(&self, other: &[u8]) -> bool {
225 self.0.eq(other)
226 }
227}
228
229impl AsRef<[u8]> for ClientDataHash {
230 fn as_ref(&self) -> &[u8] {
231 &self.0
232 }
233}
234
235impl Serialize for ClientDataHash {
236 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
237 where
238 S: Serializer,
239 {
240 serializer.serialize_bytes(&self.0)
241 }
242}
243
244#[cfg(test)]
245impl<'de> Deserialize<'de> for ClientDataHash {
246 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
247 where
248 D: Deserializer<'de>,
249 {
250 struct ClientDataHashVisitor;
251
252 impl<'de> Visitor<'de> for ClientDataHashVisitor {
253 type Value = ClientDataHash;
254
255 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
256 formatter.write_str("a byte string")
257 }
258
259 fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
260 where
261 E: de::Error,
262 {
263 let mut out = [0u8; 32];
264 if out.len() != v.len() {
265 return Err(E::invalid_length(v.len(), &"32"));
266 }
267 out.copy_from_slice(v);
268 Ok(ClientDataHash(out))
269 }
270 }
271
272 deserializer.deserialize_bytes(ClientDataHashVisitor)
273 }
274}
275
276impl CollectedClientData {
277 pub fn hash(&self) -> json::Result<ClientDataHash> {
278 let data = json::to_vec(&self)?;
285 let mut hasher = Sha256::new();
286 hasher.update(&data);
287
288 let mut output = [0u8; 32];
289 output.copy_from_slice(hasher.finalize().as_slice());
290
291 Ok(ClientDataHash(output))
292 }
293}
294
295#[cfg(test)]
296mod test {
297 use super::{Challenge, ClientDataHash, CollectedClientData, TokenBinding, WebauthnType};
298 use serde_json as json;
299
300 #[test]
301 fn test_token_binding_status() {
302 let tok = TokenBinding::Present("AAECAw".to_string());
303
304 let json_value = json::to_string(&tok).unwrap();
305 assert_eq!(json_value, "{\"status\":\"present\",\"id\":\"AAECAw\"}");
306
307 let tok = TokenBinding::Supported;
308
309 let json_value = json::to_string(&tok).unwrap();
310 assert_eq!(json_value, "{\"status\":\"supported\"}");
311 }
312
313 #[test]
314 fn test_webauthn_type() {
315 let t = WebauthnType::Create;
316
317 let json_value = json::to_string(&t).unwrap();
318 assert_eq!(json_value, "\"webauthn.create\"");
319
320 let t = WebauthnType::Get;
321 let json_value = json::to_string(&t).unwrap();
322 assert_eq!(json_value, "\"webauthn.get\"");
323 }
324
325 #[test]
326 fn test_collected_client_data_parsing() {
327 let original_str = "{\"type\":\"webauthn.create\",\"challenge\":\"AAECAw\",\"origin\":\"example.com\",\"crossOrigin\":false,\"tokenBinding\":{\"status\":\"present\",\"id\":\"AAECAw\"}}";
328 let parsed: CollectedClientData = serde_json::from_str(&original_str).unwrap();
329 let expected = CollectedClientData {
330 webauthn_type: WebauthnType::Create,
331 challenge: Challenge::new(vec![0x00, 0x01, 0x02, 0x03]),
332 origin: String::from("example.com"),
333 cross_origin: false,
334 token_binding: Some(TokenBinding::Present("AAECAw".to_string())),
335 };
336 assert_eq!(parsed, expected);
337
338 let back_again = serde_json::to_string(&expected).unwrap();
339 assert_eq!(back_again, original_str);
340 }
341
342 #[test]
343 fn test_collected_client_data_defaults() {
344 let cross_origin_str = "{\"type\":\"webauthn.create\",\"challenge\":\"AAECAw\",\"origin\":\"example.com\",\"crossOrigin\":false,\"tokenBinding\":{\"status\":\"present\",\"id\":\"AAECAw\"}}";
345 let no_cross_origin_str = "{\"type\":\"webauthn.create\",\"challenge\":\"AAECAw\",\"origin\":\"example.com\",\"tokenBinding\":{\"status\":\"present\",\"id\":\"AAECAw\"}}";
346 let parsed: CollectedClientData = serde_json::from_str(&no_cross_origin_str).unwrap();
347 let expected = CollectedClientData {
348 webauthn_type: WebauthnType::Create,
349 challenge: Challenge::new(vec![0x00, 0x01, 0x02, 0x03]),
350 origin: String::from("example.com"),
351 cross_origin: false,
352 token_binding: Some(TokenBinding::Present("AAECAw".to_string())),
353 };
354 assert_eq!(parsed, expected);
355
356 let back_again = serde_json::to_string(&expected).unwrap();
357 assert_eq!(back_again, cross_origin_str);
358 }
359
360 #[test]
361 fn test_collected_client_data() {
362 let client_data = CollectedClientData {
363 webauthn_type: WebauthnType::Create,
364 challenge: Challenge::new(vec![0x00, 0x01, 0x02, 0x03]),
365 origin: String::from("example.com"),
366 cross_origin: false,
367 token_binding: Some(TokenBinding::Present("AAECAw".to_string())),
368 };
369
370 assert_eq!(
371 client_data.hash().unwrap(),
372 ClientDataHash {
374 0: [
375 0x75, 0x35, 0x35, 0x7d, 0x49, 0x6e, 0x33, 0xc8, 0x18, 0x7f, 0xea, 0x8d, 0x11,
376 0x32, 0x64, 0xaa, 0xa4, 0x52, 0x3e, 0x13, 0x40, 0x14, 0x9f, 0xbe, 0x00, 0x3f,
377 0x10, 0x87, 0x54, 0xc3, 0x2d, 0x80
378 ]
379 }
380 );
381 }
382}