Skip to main content

fido_authenticator/
dispatch.rs

1//! Dispatch of incoming requests over CTAPHID or NFC APDUs into CTAP1 and CTAP2.
2
3mod apdu;
4mod ctaphid;
5
6#[allow(unused_imports)]
7use crate::msp;
8use crate::{Authenticator, TrussedRequirements, UserPresence};
9
10use ctap_types::{ctap1, ctap2};
11use heapless::VecView;
12use iso7816::{command::CommandView, Status};
13
14impl<UP, T> iso7816::App for Authenticator<UP, T>
15where
16    UP: UserPresence,
17{
18    fn aid(&self) -> iso7816::Aid {
19        iso7816::Aid::new(&[0xA0, 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01])
20    }
21}
22
23#[inline(never)]
24/// Deserialize U2F, call authenticator, serialize response *Result*.
25fn handle_ctap1_from_hid<T, UP>(
26    authenticator: &mut Authenticator<UP, T>,
27    data: &[u8],
28    response: &mut VecView<u8>,
29) where
30    T: TrussedRequirements,
31    UP: UserPresence,
32{
33    debug!(
34        "handle CTAP1: remaining stack: {} bytes",
35        msp() - 0x2000_0000
36    );
37    {
38        let command = match CommandView::try_from(data) {
39            Ok(command) => command,
40            Err(_status) => {
41                let code: [u8; 2] = (Status::IncorrectDataParameter).into();
42                debug!("CTAP1 parse error: {:?} ({})", _status, hex_str!(&code));
43                response.extend_from_slice(&code).ok();
44                return;
45            }
46        };
47
48        // debug!("1A SP: {:X}", msp());
49        match try_handle_ctap1(authenticator, command, response) {
50            Ok(()) => {
51                debug!("U2F response {} bytes", response.len());
52                // Need to add x9000 success code (normally the apdu-dispatch does this, but
53                // since u2f uses apdus over ctaphid, we must do it here.)
54                response.extend_from_slice(&[0x90, 0x00]).ok();
55            }
56            Err(status) => {
57                let code: [u8; 2] = status.into();
58                debug!("CTAP1 error: {:?} ({})", status, hex_str!(&code));
59                response.extend_from_slice(&code).ok();
60            }
61        }
62    }
63    // debug!("1B SP: {:X}", msp());
64    debug!("end handle CTAP1");
65}
66
67#[inline(never)]
68/// Deserialize CBOR, call authenticator, serialize response *Result*.
69fn handle_ctap2<T, UP>(
70    authenticator: &mut Authenticator<UP, T>,
71    data: &[u8],
72    response: &mut VecView<u8>,
73) where
74    T: TrussedRequirements,
75    UP: UserPresence,
76{
77    debug!(
78        "handle CTAP2: remaining stack: {} bytes",
79        msp() - 0x2000_0000
80    );
81
82    debug!("1a SP: {:X}", msp());
83    // debug!("2A SP: {:X}", msp());
84    if let Err(error) = try_handle_ctap2(authenticator, data, response) {
85        debug!("CTAP2 error: {:02X}", error);
86        response.push(error).ok();
87    }
88    // debug!("2B SP: {:X}", msp());
89    debug!("end handle CTAP2");
90}
91
92#[inline(never)]
93fn try_handle_ctap1<T, UP>(
94    authenticator: &mut Authenticator<UP, T>,
95    command: CommandView<'_>,
96    response: &mut VecView<u8>,
97) -> Result<(), Status>
98where
99    T: TrussedRequirements,
100    UP: UserPresence,
101{
102    // Annoyance: We can't load in fido-authenticator constructor.
103    authenticator
104        .state
105        .persistent
106        .load_if_not_initialised(&mut authenticator.trussed);
107
108    // let command = apdu_dispatch::Command::try_from(data)
109    //     .map_err(|_| Status::IncorrectDataParameter)?;
110    // let ctap_request = ctap1::Request::try_from(&command)
111    //     .map_err(|_| Status::IncorrectDataParameter)?;
112    // drop(command);
113    // let ctap_response = ctap1::Authenticator::call_ctap1(authenticator, &ctap_request)?;
114    // drop(ctap_request);
115
116    // Goal of these nested scopes is to keep stack small.
117    let ctap_response = {
118        let ctap_request = ctap1::Request::try_from(command)?;
119        ctap1::Authenticator::call_ctap1(authenticator, &ctap_request)?
120    };
121    // debug!("1b SP: {:X}", msp());
122
123    ctap_response.serialize(response).ok();
124    Ok(())
125}
126
127#[inline(never)]
128fn try_handle_ctap2<T, UP>(
129    authenticator: &mut Authenticator<UP, T>,
130    data: &[u8],
131    response: &mut VecView<u8>,
132) -> Result<(), u8>
133where
134    T: TrussedRequirements,
135    UP: UserPresence,
136{
137    // Annoyance: We can't load in fido-authenticator constructor.
138    authenticator
139        .state
140        .persistent
141        .load_if_not_initialised(&mut authenticator.trussed);
142
143    debug!(
144        "try_handle CTAP2: remaining stack: {} bytes",
145        msp() - 0x2000_0000
146    );
147
148    // let ctap_request = ctap2::Request::deserialize(data)
149    //     .map_err(|error| error as u8)?;
150    // let ctap_response = ctap2::Authenticator::call_ctap2(authenticator, &ctap_request)
151    //         .map_err(|error| error as u8)?;
152
153    // Goal of these nested scopes is to keep stack small.
154    let ctap_response = try_get_ctap2_response(authenticator, data)?;
155    ctap_response.serialize(response);
156    Ok(())
157}
158
159#[inline(never)]
160fn try_get_ctap2_response<T, UP>(
161    authenticator: &mut Authenticator<UP, T>,
162    data: &[u8],
163) -> Result<ctap2::Response, u8>
164where
165    T: TrussedRequirements,
166    UP: UserPresence,
167{
168    // Annoyance: We can't load in fido-authenticator constructor.
169    authenticator
170        .state
171        .persistent
172        .load_if_not_initialised(&mut authenticator.trussed);
173
174    debug!(
175        "try_get CTAP2: remaining stack: {} bytes",
176        msp() - 0x2000_0000
177    );
178
179    // Goal of these nested scopes is to keep stack small.
180    let ctap_request = ctap2::Request::deserialize(data)
181        .inspect(|_request| {
182            info!("Received CTAP2 request {:?}", request_operation(_request));
183            trace!("CTAP2 request: {:?}", _request);
184        })
185        .map_err(|error| {
186            error!("Failed to deserialize CTAP2 request: {:?}", error);
187            trace!("The problematic input data was: {}", hex_str!(data));
188            error as u8
189        })?;
190    debug!("2a SP: {:X}", msp());
191    use ctap2::Authenticator;
192    authenticator
193        .call_ctap2(&ctap_request)
194        .inspect(|_response| {
195            info!("Sending CTAP2 response {:?}", response_operation(_response));
196            trace!("CTAP2 response: {:?}", _response);
197        })
198        .map_err(|error| {
199            info!("CTAP2 error: {:?}", error);
200            error as u8
201        })
202}
203
204#[allow(unused)]
205fn request_operation(request: &ctap2::Request) -> Option<ctap2::Operation> {
206    // TODO: move into ctap-types
207    match request {
208        ctap2::Request::MakeCredential(_) => Some(ctap2::Operation::MakeCredential),
209        ctap2::Request::GetAssertion(_) => Some(ctap2::Operation::GetAssertion),
210        ctap2::Request::GetNextAssertion => Some(ctap2::Operation::GetNextAssertion),
211        ctap2::Request::GetInfo => Some(ctap2::Operation::GetInfo),
212        ctap2::Request::ClientPin(_) => Some(ctap2::Operation::ClientPin),
213        ctap2::Request::Reset => Some(ctap2::Operation::Reset),
214        ctap2::Request::CredentialManagement(_) => Some(ctap2::Operation::CredentialManagement),
215        ctap2::Request::Selection => Some(ctap2::Operation::Selection),
216        ctap2::Request::LargeBlobs(_) => Some(ctap2::Operation::LargeBlobs),
217        ctap2::Request::Vendor(operation) => Some(ctap2::Operation::Vendor(*operation)),
218        _ => None,
219    }
220}
221
222#[allow(unused)]
223fn response_operation(request: &ctap2::Response) -> Option<ctap2::Operation> {
224    match request {
225        ctap2::Response::MakeCredential(_) => Some(ctap2::Operation::MakeCredential),
226        ctap2::Response::GetAssertion(_) => Some(ctap2::Operation::GetAssertion),
227        ctap2::Response::GetNextAssertion(_) => Some(ctap2::Operation::GetNextAssertion),
228        ctap2::Response::GetInfo(_) => Some(ctap2::Operation::GetInfo),
229        ctap2::Response::ClientPin(_) => Some(ctap2::Operation::ClientPin),
230        ctap2::Response::Reset => Some(ctap2::Operation::Reset),
231        ctap2::Response::CredentialManagement(_) => Some(ctap2::Operation::CredentialManagement),
232        ctap2::Response::Selection => Some(ctap2::Operation::Selection),
233        ctap2::Response::LargeBlobs(_) => Some(ctap2::Operation::LargeBlobs),
234        ctap2::Response::Vendor => None,
235        _ => None,
236    }
237}