Skip to main content

u2f/
protocol.rs

1use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
2use chrono::TimeDelta;
3use serde::{Deserialize, Serialize};
4
5use crate::authorization::*;
6use crate::messages::*;
7use crate::register::*;
8use crate::u2ferror::U2fError;
9use crate::util::*;
10
11#[cfg(feature = "oapi-schema")]
12use impulse_server_kit::salvo;
13
14type Result<T> = ::std::result::Result<T, U2fError>;
15
16#[derive(Clone)]
17pub struct U2f {
18  app_id: String,
19}
20
21#[cfg_attr(feature = "oapi-schema", derive(salvo::oapi::ToSchema))]
22#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, Hash, Default)]
23#[serde(rename_all = "camelCase")]
24pub struct Challenge {
25  pub app_id: String,
26  pub challenge: String,
27  pub timestamp: String,
28}
29
30impl U2f {
31  // The app ID is a string used to uniquely identify an U2F app
32  pub fn new(app_id: String) -> Self {
33    U2f { app_id }
34  }
35
36  #[cfg(feature = "rand")]
37  pub fn generate_challenge(&self) -> Challenge {
38    let utc: chrono::DateTime<chrono::Utc> = chrono::Utc::now();
39    let challenge_bytes = generate_challenge(32);
40
41    Challenge {
42      challenge: URL_SAFE_NO_PAD.encode(&challenge_bytes),
43      timestamp: format!("{utc:?}"),
44      app_id: self.app_id.clone(),
45    }
46  }
47
48  pub fn request(&self, challenge: Challenge, registrations: Vec<Registration>) -> Result<U2fRegisterRequest> {
49    let u2f_request = U2fRegisterRequest {
50      app_id: self.app_id.clone(),
51      register_requests: self.register_request(challenge),
52      registered_keys: self.registered_keys(registrations),
53    };
54
55    Ok(u2f_request)
56  }
57
58  fn register_request(&self, challenge: Challenge) -> Vec<RegisterRequest> {
59    let mut requests: Vec<RegisterRequest> = vec![];
60
61    let request = RegisterRequest {
62      version: U2F_V2.into(),
63      challenge: challenge.challenge,
64    };
65    requests.push(request);
66
67    requests
68  }
69
70  pub fn register_response(&self, challenge: Challenge, response: RegisterResponse) -> Result<Registration> {
71    if expiration(challenge.timestamp) > TimeDelta::seconds(300) {
72      return Err(U2fError::ChallengeExpired);
73    }
74
75    let registration_data: Vec<u8> = URL_SAFE_NO_PAD.decode(&response.registration_data[..]).unwrap();
76    let client_data: Vec<u8> = URL_SAFE_NO_PAD.decode(&response.client_data[..]).unwrap();
77
78    parse_registration(challenge.app_id, client_data, registration_data)
79  }
80
81  fn registered_keys(&self, registrations: Vec<Registration>) -> Vec<RegisteredKey> {
82    let mut keys: Vec<RegisteredKey> = vec![];
83
84    for registration in registrations {
85      keys.push(get_registered_key(self.app_id.clone(), registration.key_handle));
86    }
87
88    keys
89  }
90
91  pub fn sign_request(&self, challenge: Challenge, registrations: Vec<Registration>) -> U2fSignRequest {
92    let mut keys: Vec<RegisteredKey> = vec![];
93
94    for registration in registrations {
95      keys.push(get_registered_key(self.app_id.clone(), registration.key_handle));
96    }
97
98    U2fSignRequest {
99      app_id: self.app_id.clone(),
100      challenge: URL_SAFE_NO_PAD.encode(challenge.challenge.as_bytes()),
101      registered_keys: keys,
102    }
103  }
104
105  pub fn sign_response(&self, challenge: Challenge, reg: Registration, sign_resp: SignResponse, counter: u32) -> Result<u32> {
106    if expiration(challenge.timestamp) > TimeDelta::seconds(300) {
107      return Err(U2fError::ChallengeExpired);
108    }
109
110    if sign_resp.key_handle != get_encoded(&reg.key_handle[..]) {
111      return Err(U2fError::WrongKeyHandler);
112    }
113
114    let client_data: Vec<u8> = URL_SAFE_NO_PAD
115      .decode(&sign_resp.client_data[..])
116      .map_err(|_e| U2fError::InvalidClientData)?;
117    let sign_data: Vec<u8> = URL_SAFE_NO_PAD
118      .decode(&sign_resp.signature_data[..])
119      .map_err(|_e| U2fError::InvalidSignatureData)?;
120
121    let public_key = reg.pub_key;
122
123    let auth = parse_sign_response(self.app_id.clone(), client_data.clone(), public_key, sign_data.clone());
124
125    match auth {
126      Ok(ref res) => {
127        // CounterTooLow is raised when the counter value received from the device is
128        // lower than last stored counter value.
129        if res.counter < counter {
130          Err(U2fError::CounterTooLow)
131        } else {
132          Ok(res.counter)
133        }
134      }
135      Err(e) => Err(e),
136    }
137  }
138}