ncryptf/
authorization.rs

1use crate::{
2    error::NcryptfError as Error, signature::Signature, token::Token, util::randombytes_buf,
3};
4
5use chrono::{offset::Utc, DateTime, Timelike};
6use hkdf::Hkdf;
7use hmac::{Hmac, Mac};
8use serde::{Deserialize, Serialize};
9use sha2::Sha256;
10use base64::{Engine as _, engine::general_purpose};
11
12/// HMAC Auth Info header
13const AUTH_INFO: &str = "HMAC|AuthenticationKey";
14
15/// Internal deserialization helper struct
16#[derive(Debug, Deserialize)]
17struct AuthParamsJson {
18    pub access_token: String,
19    pub hmac: String,
20    pub salt: String,
21    pub date: String,
22}
23
24/// Parameters as extracted from the request header
25#[derive(Debug)]
26pub struct AuthParams {
27    pub access_token: String,
28    pub hmac: Vec<u8>,
29    pub salt: Vec<u8>,
30    pub version: Option<i8>,
31    pub date: Option<DateTime<Utc>>,
32}
33
34/// Generates, validates, and parses Authorization header information
35#[derive(Debug)]
36pub struct Authorization {
37    pub token: Token,
38    pub salt: Vec<u8>,
39    pub date: DateTime<Utc>,
40    pub signature: String,
41    pub hmac: Vec<u8>,
42    pub version: Option<i8>,
43}
44
45impl Authorization {
46    /// Generates an Authorization struct from the given parameters
47    pub fn from(
48        method: String,
49        uri: String,
50        token: Token,
51        date: DateTime<Utc>,
52        payload: String,
53        salt: Option<Vec<u8>>,
54        version: Option<i8>,
55    ) -> Result<Self, Error> {
56        let m = method.to_uppercase();
57        let s = match salt {
58            Some(s) => s,
59            None => randombytes_buf(32),
60        };
61
62        let v = match version {
63            Some(v) => Some(v),
64            None => Some(crate::NCRYPTF_CURRENT_VERSION),
65        };
66
67        let sr: &[u8; 32] = &s.clone().try_into().unwrap();
68        let ikm: &[u8; 32] = &token.clone().ikm.try_into().unwrap();
69        let signature = Signature::derive(m, uri, s.clone(), date, payload, v);
70
71        let hkdf = Hkdf::<Sha256>::new(Some(sr), ikm);
72        let mut okm = [0u8; 32];
73        match hkdf.expand(&AUTH_INFO.as_bytes(), &mut okm) {
74            Err(_) => {
75                return Err(Error::InvalidArgument(format!(
76                    "Unable to generate HMAC for token."
77                )));
78            }
79            Ok(_) => {}
80        };
81
82        let hk = okm.to_vec();
83        let hkdf_string = hex::encode(hk.clone()).to_string().to_lowercase();
84
85        let mut hmac = Hmac::<Sha256>::new_from_slice(hkdf_string.as_bytes())
86            .expect("HMAC can take key of any size");
87        hmac.update(signature.as_bytes());
88        let result = hmac.finalize();
89        let bytes = result.into_bytes();
90        let hmac = bytes.to_vec();
91        return Ok(Authorization {
92            token,
93            salt: s.clone(),
94            date,
95            signature,
96            hmac: hmac,
97            version: v,
98        });
99    }
100
101    /// Returns the date
102    pub fn get_date(&self) -> DateTime<Utc> {
103        return self.date;
104    }
105
106    /// Returns the date as a string
107    pub fn get_date_string(&self) -> String {
108        return self.date.format("%a, %d %b %Y %H:%M:%S %z").to_string();
109    }
110
111    /// Returns the raw HMAC
112    pub fn get_hmac(&self) -> Vec<u8> {
113        return self.hmac.clone();
114    }
115
116    /// Returns the base64 encoded HMAC
117    pub fn get_encoded_hmac(&self) -> String {
118        let hmac = self.get_hmac();
119        return general_purpose::STANDARD.encode(hmac);
120    }
121
122    /// Returns the base64 encoded salt
123    pub fn get_encoded_salt(&self) -> String {
124        let salt = self.salt.clone();
125        return general_purpose::STANDARD.encode(salt);
126    }
127
128    /// Returns the signature string
129    pub fn get_signature_string(&self) -> String {
130        return self.signature.clone();
131    }
132
133    /// Returns the time drift between the current time and the provided time
134    pub fn get_time_drift(date: DateTime<Utc>) -> i32 {
135        let now = Utc::now();
136        return now.second().abs_diff(date.second()).try_into().unwrap();
137    }
138
139    /// Verifies whether or not a given HMAC is equal to the one on record
140    /// This comparison occurs in constant time to avoid timing attack
141    pub fn verify(&self, hmac: Vec<u8>, drift_allowance: i32) -> bool {
142        let drift = Self::get_time_drift(self.get_date());
143        if drift >= drift_allowance {
144            return false;
145        }
146
147        if constant_time_eq::constant_time_eq(&hmac, &self.get_hmac()) {
148            return true;
149        }
150
151        return false;
152    }
153
154    /// Returns the authorization header as a string
155    pub fn get_header(&self) -> String {
156        let salt = self.get_encoded_salt();
157        let hmac = self.get_encoded_hmac();
158
159        match self.version {
160            Some(2) => {
161                let d = AuthStruct {
162                    access_token: self.token.access_token.clone(),
163                    date: self.get_date_string(),
164                    hmac: hmac.clone(),
165                    salt: salt.clone(),
166                    v: 2,
167                };
168
169                // The double escape is for library compatability with tests
170                let json = serde_json::to_string(&d)
171                    .unwrap()
172                    .to_string()
173                    .replace("/", "\\/");
174                return format!("HMAC {}", general_purpose::STANDARD.encode(json));
175            }
176            _ => {
177                return format!("HMAC {},{},{}", self.token.access_token, hmac, salt);
178            }
179        };
180    }
181
182    /// Extracts the parameters from the header string
183    pub fn extract_params_from_header_string(header: String) -> Result<AuthParams, Error> {
184        if header.starts_with("HMAC ") {
185            let auth_header = header.replace("HMAC ", "");
186            if auth_header.contains(",") {
187                let params: Vec<String> = auth_header.split(",").map(|s| s.to_string()).collect();
188                if params.len() != 3 {
189                    return Err(Error::InvalidArgument(String::from(
190                        "Header parameters are not valid.",
191                    )));
192                }
193
194                return Ok(AuthParams {
195                    access_token: params[0].clone(),
196                    hmac: general_purpose::STANDARD.decode(params[1].clone()).unwrap(),
197                    salt: general_purpose::STANDARD.decode(params[2].clone()).unwrap(),
198                    version: Some(1),
199                    date: None,
200                });
201            } else {
202                let json = general_purpose::STANDARD.decode(auth_header).unwrap();
203                match serde_json::from_str::<AuthParamsJson>(
204                    String::from_utf8(json).unwrap().as_str(),
205                ) {
206                    Ok(params) => {
207                        let date = chrono::DateTime::parse_from_rfc2822(&params.date);
208                        if date.is_ok() {
209                            let d = date.unwrap().with_timezone(&Utc);
210                            return Ok(AuthParams {
211                                access_token: params.access_token,
212                                hmac: general_purpose::STANDARD.decode(params.hmac).unwrap(),
213                                salt: general_purpose::STANDARD.decode(params.salt).unwrap(),
214                                version: Some(2),
215                                date: Some(d),
216                            });
217                        }
218                    }
219                    _ => {}
220                };
221            }
222        }
223
224        return Err(Error::InvalidArgument(String::from(
225            "Header parameters are not valid.",
226        )));
227    }
228}
229
230/// Internal structure for JSON serialization
231#[derive(Debug, Clone, Serialize, Deserialize)]
232struct AuthStruct {
233    pub access_token: String,
234    pub date: String,
235    pub hmac: String,
236    pub salt: String,
237    pub v: i8,
238}