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