webauthn_authenticator_rs/ctap2/commands/
get_assertion.rs

1use base64urlsafedata::Base64UrlSafeData;
2use serde::{Deserialize, Serialize};
3use serde_cbor_2::Value;
4use std::{collections::BTreeMap, str::FromStr};
5use webauthn_rs_proto::{AllowCredentials, AuthenticatorTransport, PublicKeyCredentialDescriptor};
6
7use crate::ctap2::commands::{value_to_map, value_to_vec, value_to_vec_string};
8
9use super::{
10    value_to_bool, value_to_set_string, value_to_string, value_to_u32, value_to_vec_u8, CBORCommand,
11};
12
13/// `authenticatorGetAssertion` request type.
14///
15/// Reference: <https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#authenticatorGetAssertion>
16#[derive(Serialize, Debug, Clone)]
17#[serde(into = "BTreeMap<u32, Value>", try_from = "BTreeMap<u32, Value>")]
18pub struct GetAssertionRequest {
19    pub rp_id: String,
20    pub client_data_hash: Vec<u8>,
21    pub allow_list: Vec<AllowCredentials>,
22    // TODO: extensions
23    pub options: Option<BTreeMap<String, bool>>,
24    pub pin_uv_auth_param: Option<Vec<u8>>,
25    pub pin_uv_auth_proto: Option<u32>,
26}
27
28impl CBORCommand for GetAssertionRequest {
29    const CMD: u8 = 0x02;
30    type Response = GetAssertionResponse;
31}
32
33/// `authenticatorGetAssertion` response type.
34///
35/// Reference: <https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#authenticatorgetassertion-response-structure>
36// Note: this needs to have the same names as AttestationObjectInner
37#[derive(Deserialize, Serialize, Debug, Clone)]
38#[serde(rename_all = "camelCase")]
39pub struct GetAssertionResponse {
40    pub credential: Option<PublicKeyCredentialDescriptor>,
41    pub auth_data: Option<Vec<u8>>,
42    pub signature: Option<Vec<u8>>,
43    // TODO: pub user: Option<User>,
44    pub number_of_credentials: Option<u32>,
45    pub user_selected: Option<bool>,
46    pub large_blob_key: Option<Vec<u8>>,
47    // TODO: extensions
48}
49
50impl From<GetAssertionRequest> for BTreeMap<u32, Value> {
51    fn from(r: GetAssertionRequest) -> Self {
52        let GetAssertionRequest {
53            rp_id,
54            client_data_hash,
55            allow_list,
56            options,
57            pin_uv_auth_param,
58            pin_uv_auth_proto,
59        } = r;
60
61        let mut keys = BTreeMap::new();
62        keys.insert(0x01, Value::Text(rp_id));
63        keys.insert(0x02, Value::Bytes(client_data_hash));
64
65        if !allow_list.is_empty() {
66            keys.insert(
67                0x03,
68                Value::Array(
69                    allow_list
70                        .iter()
71                        .map(|a| {
72                            let mut m = BTreeMap::from([
73                                (
74                                    Value::Text("type".to_string()),
75                                    Value::Text(a.type_.to_owned()),
76                                ),
77                                (Value::Text("id".to_string()), Value::Bytes(a.id.to_vec())),
78                            ]);
79
80                            if let Some(transports) = &a.transports {
81                                let transports: Vec<Value> = transports
82                                    .iter()
83                                    .map(|t| Value::Text(t.to_string()))
84                                    .collect();
85
86                                if !transports.is_empty() {
87                                    m.insert(
88                                        Value::Text("transports".to_string()),
89                                        Value::Array(transports),
90                                    );
91                                }
92                            }
93
94                            Value::Map(m)
95                        })
96                        .collect(),
97                ),
98            );
99        }
100        // TODO: extensions
101        if let Some(v) = options {
102            keys.insert(
103                0x05,
104                Value::Map(BTreeMap::from_iter(
105                    v.iter()
106                        .map(|(k, o)| (Value::Text(k.to_owned()), Value::Bool(*o))),
107                )),
108            );
109        }
110
111        if let Some(v) = pin_uv_auth_param {
112            keys.insert(0x06, Value::Bytes(v));
113        }
114        if let Some(v) = pin_uv_auth_proto {
115            keys.insert(0x07, Value::Integer(v.into()));
116        }
117
118        keys
119    }
120}
121
122impl TryFrom<BTreeMap<u32, Value>> for GetAssertionRequest {
123    type Error = &'static str;
124    fn try_from(mut raw: BTreeMap<u32, Value>) -> Result<Self, Self::Error> {
125        trace!("raw: {:?}", raw);
126        Ok(Self {
127            rp_id: raw
128                .remove(&0x01)
129                .and_then(|v| value_to_string(v, "0x01"))
130                .ok_or("parsing rpId")?,
131            client_data_hash: raw
132                .remove(&0x02)
133                .and_then(|v| value_to_vec_u8(v, "0x02"))
134                .ok_or("parsing clientDataHash")?,
135            allow_list: raw
136                .remove(&0x03)
137                .and_then(|v| value_to_vec(v, "0x03"))
138                .map(|v| {
139                    v.into_iter()
140                        .filter_map(|a| {
141                            let mut a = value_to_map(a, "0x03")?;
142                            let type_ = value_to_string(
143                                a.remove(&Value::Text("type".to_string()))?,
144                                "type",
145                            )?;
146                            let id = Base64UrlSafeData::from(value_to_vec_u8(
147                                a.remove(&Value::Text("id".to_string()))?,
148                                "id",
149                            )?);
150                            let transports = a
151                                .remove(&Value::Text("transports".to_string()))
152                                .and_then(|v| value_to_vec_string(v, "transports"))
153                                .map(|v| {
154                                    v.into_iter()
155                                        .filter_map(|t| AuthenticatorTransport::from_str(&t).ok())
156                                        .collect()
157                                });
158                            Some(AllowCredentials {
159                                id,
160                                type_,
161                                transports,
162                            })
163                        })
164                        .collect()
165                })
166                .unwrap_or_default(),
167            // TODO
168            options: None,
169            pin_uv_auth_param: None,
170            pin_uv_auth_proto: None,
171        })
172    }
173}
174
175impl From<GetAssertionResponse> for BTreeMap<u32, Value> {
176    fn from(r: GetAssertionResponse) -> Self {
177        let GetAssertionResponse {
178            credential,
179            auth_data,
180            signature,
181            number_of_credentials,
182            user_selected,
183            large_blob_key,
184        } = r;
185
186        let mut keys = BTreeMap::new();
187        if let Some(credential) = credential {
188            let mut m = BTreeMap::from([
189                (
190                    Value::Text("id".to_string()),
191                    Value::Bytes(credential.id.into()),
192                ),
193                (
194                    Value::Text("type".to_string()),
195                    Value::Text(credential.type_),
196                ),
197            ]);
198            if let Some(transports) = credential.transports {
199                let transports = transports
200                    .into_iter()
201                    .map(|t| Value::Text(t.to_string()))
202                    .collect();
203                m.insert(
204                    Value::Text("transports".to_string()),
205                    Value::Array(transports),
206                );
207            };
208            keys.insert(0x01, Value::Map(m));
209        }
210        if let Some(auth_data) = auth_data {
211            keys.insert(0x02, Value::Bytes(auth_data));
212        }
213        if let Some(signature) = signature {
214            keys.insert(0x03, Value::Bytes(signature));
215        }
216        if let Some(number_of_credentials) = number_of_credentials {
217            keys.insert(0x05, Value::Integer(number_of_credentials.into()));
218        }
219        if let Some(user_selected) = user_selected {
220            keys.insert(0x06, Value::Bool(user_selected));
221        }
222        if let Some(large_blob_key) = large_blob_key {
223            keys.insert(0x07, Value::Bytes(large_blob_key));
224        }
225
226        keys
227    }
228}
229
230impl TryFrom<BTreeMap<u32, Value>> for GetAssertionResponse {
231    type Error = &'static str;
232    fn try_from(mut raw: BTreeMap<u32, Value>) -> Result<Self, Self::Error> {
233        trace!(?raw);
234        Ok(Self {
235            credential: raw.remove(&0x01).and_then(|v| {
236                if let Value::Map(mut v) = v {
237                    let id = v
238                        .remove(&Value::Text("id".to_string()))
239                        .and_then(|v| value_to_vec_u8(v, "0x01.id"))
240                        .map(Base64UrlSafeData::from);
241                    let type_ = v
242                        .remove(&Value::Text("type".to_string()))
243                        .and_then(|v| value_to_string(v, "0x01.type"));
244
245                    let transports: Option<Vec<AuthenticatorTransport>> = v
246                        .remove(&Value::Text("transports".to_string()))
247                        .and_then(|v| value_to_set_string(v, "0x01.transports"))
248                        .map(|v| {
249                            v.iter()
250                                .filter_map(|t| AuthenticatorTransport::from_str(t).ok())
251                                .collect()
252                        });
253                    id.and_then(|id| {
254                        type_.map(|type_| PublicKeyCredentialDescriptor {
255                            type_,
256                            id,
257                            transports,
258                        })
259                    })
260                } else {
261                    None
262                }
263            }),
264            auth_data: raw.remove(&0x02).and_then(|v| value_to_vec_u8(v, "0x02")),
265            signature: raw.remove(&0x03).and_then(|v| value_to_vec_u8(v, "0x03")),
266            // TODO: user: None, (0x04)
267            number_of_credentials: raw.remove(&0x05).and_then(|v| value_to_u32(&v, "0x05")),
268            user_selected: raw.remove(&0x06).and_then(|v| value_to_bool(&v, "0x06")),
269            large_blob_key: raw.remove(&0x07).and_then(|v| value_to_vec_u8(v, "0x07")),
270        })
271    }
272}
273
274crate::deserialize_cbor!(GetAssertionRequest);
275crate::deserialize_cbor!(GetAssertionResponse);
276
277#[cfg(test)]
278mod tests {
279    // TODO
280}