1use std::convert::TryFrom;
2use std::io::{BufRead, Cursor as IOCursor, Error as IOError, ErrorKind as IOErrorKind, Read};
3use std::time::Duration;
4
5use byteorder::{BigEndian, ReadBytesExt};
6use sha2::{Digest, Sha256};
7use x509_parser::prelude::{FromDer, X509Certificate};
8
9use crate::proto::ctap1::apdu::{ApduResponse, ApduResponseStatus};
10use crate::proto::ctap2::Ctap2Transport;
11use crate::webauthn::CtapError;
12
13#[derive(Debug, Clone, Copy)]
14pub enum Ctap1Transport {
15 Bt,
16 Ble,
17 Nfc,
18 Usb,
19}
20
21impl TryFrom<&Ctap2Transport> for Ctap1Transport {
22 type Error = CtapError;
23 fn try_from(ctap2: &Ctap2Transport) -> Result<Ctap1Transport, Self::Error> {
24 match ctap2 {
25 Ctap2Transport::Ble => Ok(Ctap1Transport::Ble),
26 Ctap2Transport::Usb => Ok(Ctap1Transport::Usb),
27 Ctap2Transport::Nfc => Ok(Ctap1Transport::Nfc),
28 Ctap2Transport::Internal => Err(CtapError::UnsupportedOption),
29 Ctap2Transport::Hybrid => Err(CtapError::UnsupportedOption),
30 }
31 }
32}
33
34#[derive(Debug, Clone, Copy)]
35pub enum Ctap1Version {
36 U2fV2,
37}
38
39#[derive(Debug, Clone)]
40pub struct Ctap1RegisteredKey {
41 pub version: Ctap1Version,
42 pub key_handle: Vec<u8>,
43 pub transports: Option<Vec<Ctap1Transport>>,
44 pub app_id: Option<String>,
45}
46
47impl Ctap1RegisteredKey {
48 pub fn new_u2f_v2(key_handle: &[u8]) -> Ctap1RegisteredKey {
49 Ctap1RegisteredKey {
50 version: Ctap1Version::U2fV2,
51 key_handle: Vec::from(key_handle),
52 transports: None,
53 app_id: None,
54 }
55 }
56}
57
58#[derive(Debug, Clone)]
59pub struct Ctap1RegisterRequest {
60 pub version: Ctap1Version,
61 pub app_id_hash: Vec<u8>,
62 pub challenge: Vec<u8>,
63 pub registered_keys: Vec<Ctap1RegisteredKey>,
64 pub timeout: Duration,
65 pub require_user_presence: bool,
66}
67
68impl Ctap1RegisterRequest {
69 pub fn new_u2f_v2(
70 app_id: &str,
71 challenge: &[u8],
72 registered_keys: Vec<Ctap1RegisteredKey>,
73 timeout: Duration,
74 require_user_presence: bool,
75 ) -> Ctap1RegisterRequest {
76 let mut hasher = Sha256::default();
77 hasher.update(app_id);
78 let app_id_hash = hasher.finalize().to_vec();
79
80 Ctap1RegisterRequest {
81 version: Ctap1Version::U2fV2,
82 app_id_hash,
83 challenge: Vec::from(challenge),
84 registered_keys,
85 timeout,
86 require_user_presence,
87 }
88 }
89
90 pub fn dummy(timeout: Duration) -> Self {
91 Ctap1RegisterRequest {
92 version: Ctap1Version::U2fV2,
93 app_id_hash: vec![0; 32],
94 challenge: vec![0; 32],
95 registered_keys: Vec::new(),
96 timeout,
97 require_user_presence: true,
98 }
99 }
100}
101
102#[derive(Debug)]
103pub struct Ctap1RegisterResponse {
104 pub version: Ctap1Version,
105 pub public_key: Vec<u8>,
106 pub key_handle: Vec<u8>,
107 pub attestation: Vec<u8>,
108 pub signature: Vec<u8>,
109}
110
111impl TryFrom<ApduResponse> for Ctap1RegisterResponse {
112 type Error = IOError;
113
114 fn try_from(apdu: ApduResponse) -> Result<Self, Self::Error> {
115 if apdu.status()? != ApduResponseStatus::NoError {
116 return Err(IOError::new(
117 IOErrorKind::InvalidInput,
118 "APDU packets need to have status NoError to be converted..",
119 ));
120 }
121
122 let data = apdu.data.ok_or(IOError::new(
123 IOErrorKind::InvalidInput,
124 "Emtpy APDU packet.",
125 ))?;
126
127 let mut cursor = IOCursor::new(data);
128 cursor.consume(1); let mut public_key = vec![0u8; 65];
131 cursor.read_exact(&mut public_key)?;
132
133 let key_handle_len = cursor.read_u8()? as u64;
134 let mut key_handle = vec![0u8; key_handle_len as usize];
135 cursor.read_exact(&mut key_handle)?;
136
137 let mut remaining = vec![];
138 cursor.read_to_end(&mut remaining)?;
139
140 let (signature, _) = X509Certificate::from_der(&remaining).or(Err(IOError::new(
141 IOErrorKind::InvalidData,
142 "Failed to parse X509 attestation data",
143 )))?;
144 let signature = Vec::from(signature);
145 let attestation = Vec::from(&remaining[0..remaining.len() - signature.len()]);
146
147 Ok(Ctap1RegisterResponse {
148 version: Ctap1Version::U2fV2,
149 public_key,
150 key_handle,
151 attestation,
152 signature,
153 })
154 }
155}
156
157impl Ctap1RegisterResponse {
158 pub fn as_registered_key(&self) -> Result<Ctap1RegisteredKey, IOError> {
159 Ok(Ctap1RegisteredKey::new_u2f_v2(&self.key_handle))
160 }
161}
162
163#[derive(Debug, Clone)]
164pub struct Ctap1SignRequest {
165 pub app_id_hash: Vec<u8>,
166 pub challenge: Vec<u8>,
167 pub key_handle: Vec<u8>,
168 pub timeout: Duration,
169 pub require_user_presence: bool,
170}
171
172impl Ctap1SignRequest {
173 pub fn new(
174 app_id: &str,
175 challenge: &[u8],
176 key_handle: &[u8],
177 timeout: Duration,
178 require_user_presence: bool,
179 ) -> Ctap1SignRequest {
180 let mut hasher = Sha256::default();
181 hasher.update(app_id);
182 let app_id_hash = hasher.finalize().to_vec();
183
184 Ctap1SignRequest {
185 app_id_hash: app_id_hash,
186 challenge: Vec::from(challenge),
187 key_handle: Vec::from(key_handle),
188 timeout,
189 require_user_presence,
190 }
191 }
192
193 pub fn new_preflight(
194 app_id_hash: &[u8],
195 challenge: &[u8],
196 key_handle: &[u8],
197 timeout: Duration,
198 ) -> Ctap1SignRequest {
199 Ctap1SignRequest {
200 app_id_hash: Vec::from(app_id_hash),
201 challenge: Vec::from(challenge),
202 key_handle: Vec::from(key_handle),
203 timeout,
204 require_user_presence: false,
205 }
206 }
207}
208
209#[derive(Debug)]
210pub struct Ctap1VersionRequest {}
211
212impl Ctap1VersionRequest {
213 pub fn new() -> Ctap1VersionRequest {
214 Ctap1VersionRequest {}
215 }
216}
217
218#[derive(Debug)]
219pub struct Ctap1VersionResponse {
220 pub version: Ctap1Version,
221}
222
223impl TryFrom<ApduResponse> for Ctap1VersionResponse {
224 type Error = IOError;
225
226 fn try_from(apdu: ApduResponse) -> Result<Self, Self::Error> {
227 if apdu.status()? != ApduResponseStatus::NoError {
228 return Err(IOError::new(
229 IOErrorKind::InvalidInput,
230 "APDU packets need to have status NoError to be converted..",
231 ));
232 }
233
234 let data = apdu.data.ok_or(IOError::new(
235 IOErrorKind::InvalidInput,
236 "Emtpy APDU packet.",
237 ))?;
238
239 let version_string = String::from_utf8(data).or(Err(IOError::new(
240 IOErrorKind::InvalidInput,
241 "Invalid UTF-8 bytes in CTAP1 version string",
242 )))?;
243
244 let version = match version_string.as_str() {
245 "U2F_V2" => Ctap1Version::U2fV2,
246 _ => {
247 return Err(IOError::new(
248 IOErrorKind::InvalidInput,
249 format!("Invalid CTAP1 version string: {:}", version_string),
250 ))
251 }
252 };
253
254 Ok(Ctap1VersionResponse { version })
255 }
256}
257
258#[derive(Debug, Clone)]
259pub struct Ctap1SignResponse {
260 pub user_presence_verified: bool,
261 pub counter: u32,
262 pub signature: Vec<u8>,
263}
264
265impl TryFrom<ApduResponse> for Ctap1SignResponse {
266 type Error = IOError;
267
268 fn try_from(apdu: ApduResponse) -> Result<Self, Self::Error> {
269 if apdu.status()? != ApduResponseStatus::NoError {
270 return Err(IOError::new(
271 IOErrorKind::InvalidInput,
272 "APDU packets need to have status NoError to be converted..",
273 ));
274 }
275
276 let data = apdu.data.ok_or(IOError::new(
277 IOErrorKind::InvalidInput,
278 "Emtpy APDU packet.",
279 ))?;
280
281 let mut cursor = IOCursor::new(data);
282 let user_presence_verified = match cursor.read_u8()? {
283 0x01 => true,
284 _ => false,
285 };
286 let counter = cursor.read_u32::<BigEndian>()?;
287
288 let mut signature = vec![];
289 cursor.read_to_end(&mut signature)?;
290
291 Ok(Ctap1SignResponse {
292 user_presence_verified,
293 counter,
294 signature,
295 })
296 }
297}
298
299pub trait Preflight<P>: Sized {
300 fn preflight(&self) -> Result<(Self, Vec<P>), CtapError>;
302}
303
304impl Preflight<Ctap1SignRequest> for Ctap1RegisterRequest {
305 fn preflight(&self) -> Result<(Self, Vec<Ctap1SignRequest>), CtapError> {
306 let preflight_requests: Vec<Ctap1SignRequest> = self
307 .registered_keys
308 .iter()
309 .map(|registered_key| {
310 Ctap1SignRequest::new_preflight(
311 &self.app_id_hash,
312 &[0u8; 32],
313 ®istered_key.key_handle,
314 self.timeout,
315 )
316 })
317 .collect();
318
319 let mut modified = self.to_owned();
320 modified.registered_keys = vec![];
321 Ok((modified, preflight_requests))
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use crate::proto::ctap1::apdu::ApduResponse;
328 use crate::proto::ctap1::Ctap1RegisterResponse;
329 use std::convert::TryInto;
330
331 #[test]
332 fn register_response_apdu_to_ctap1() {
333 let apdu = hex::decode("05046DDBE3C25D974C9A403D6C648ED41C219D44734C43986B4053B325BE01C31E28F146731E5C21BA0E0E1938DA4C1FECAD650A2971A13CF6076BF52B52C19F8D0E40602CFD267868E84D4852BD5B008BC6CE0211D4858C8A647328A13B7D5C0A42B3893D63A58FCA7BD3EBB74F55CE537195DFF0113D4C561BBB7DFAC0C0ECD1AFB53082015930820100A003020102020102300A06082A8648CE3D0403023028311530130603550403130C5365637572697479204B6579310F300D060355040A1306476F6F676C653022180F32303030303130313030303030305A180F32303939313233313233353935395A3028311530130603550403130C5365637572697479204B6579310F300D060355040A1306476F6F676C653059301306072A8648CE3D020106082A8648CE3D030107034200040393AF897BE858E88C1953876A1A538477C4DA6E6EA14ACF0A2FD89A4DCCF95878A8CD2929029CC1D794BFFB9C37547CBBB5BB31AB3A6756ACF74F123CECD45CA31730153013060B2B0601040182E51C020101040403020470300A06082A8648CE3D040302034700304402207F958ABE6CF08CB2E9A03774D52DF8C0EA261E1AC0C283409FEDD8D36DFAF09302204EEB7501C720428D206E1B092D8D26CA8536B70F5F09AEA99562390BEF1BA7EC3044022031413D6E238A5F998B26B3931655C411847D99776B6E5CF15AA2E11BFAF325F00220098745DA82C11BB242934BAC6AE95155EAAD68520D695D46982DA9B2C94F94E3").unwrap();
334 let apdu = ApduResponse::new_success(&apdu);
335 let decoded: Ctap1RegisterResponse = apdu.try_into().unwrap();
336
337 assert_eq!(decoded.public_key, hex::decode("046DDBE3C25D974C9A403D6C648ED41C219D44734C43986B4053B325BE01C31E28F146731E5C21BA0E0E1938DA4C1FECAD650A2971A13CF6076BF52B52C19F8D0E").unwrap());
338 assert_eq!(decoded.key_handle, hex::decode("602CFD267868E84D4852BD5B008BC6CE0211D4858C8A647328A13B7D5C0A42B3893D63A58FCA7BD3EBB74F55CE537195DFF0113D4C561BBB7DFAC0C0ECD1AFB5").unwrap());
339 assert_eq!(decoded.attestation, hex::decode("3082015930820100A003020102020102300A06082A8648CE3D0403023028311530130603550403130C5365637572697479204B6579310F300D060355040A1306476F6F676C653022180F32303030303130313030303030305A180F32303939313233313233353935395A3028311530130603550403130C5365637572697479204B6579310F300D060355040A1306476F6F676C653059301306072A8648CE3D020106082A8648CE3D030107034200040393AF897BE858E88C1953876A1A538477C4DA6E6EA14ACF0A2FD89A4DCCF95878A8CD2929029CC1D794BFFB9C37547CBBB5BB31AB3A6756ACF74F123CECD45CA31730153013060B2B0601040182E51C020101040403020470300A06082A8648CE3D040302034700304402207F958ABE6CF08CB2E9A03774D52DF8C0EA261E1AC0C283409FEDD8D36DFAF09302204EEB7501C720428D206E1B092D8D26CA8536B70F5F09AEA99562390BEF1BA7EC").unwrap());
340 assert_eq!(decoded.signature, hex::decode("3044022031413D6E238A5F998B26B3931655C411847D99776B6E5CF15AA2E11BFAF325F00220098745DA82C11BB242934BAC6AE95155EAAD68520D695D46982DA9B2C94F94E3").unwrap());
341 }
342}