sfrs/src/sync_tokens.rs

58 lines
2.1 KiB
Rust

use ring::aead::*;
use ring::digest::*;
use ring::pbkdf2::*;
use ring::rand::{SecureRandom, SystemRandom};
// In the API endpoint `/items/sync`, we use `max_id` of the
// current user as the sync token. However, this may be prone
// to side-channel leakage since all users in database share
// the same auto-incrementing ID. An attacker may be able to
// call `/items/sync` with one update each time and extract
// what others' are doing based on changes in ID.
// Therefore, we should at least not send the ID as a token
// in plain-text to the client.
lazy_static! {
static ref TOKEN_KEY: [u8; 32] = get_token_key();
}
pub fn get_token_key() -> [u8; 32] {
let pwd = std::env::var("SYNC_TOKEN_SECRET")
.expect("Please set SYNC_TOKEN_SECRET").into_bytes();
let salt = std::env::var("SYNC_TOKEN_SALT")
.expect("Please set SYNC_TOKEN_SALT").into_bytes();
let mut ret = [0; 32];
derive(&SHA256, 100, &salt, &pwd, &mut ret);
ret
}
pub fn max_id_to_token(max_id: i64) -> String {
let sealing_key = SealingKey::new(&CHACHA20_POLY1305, &*TOKEN_KEY).unwrap();
let mut nonce = [0u8; 12];
SystemRandom::new().fill(&mut nonce).unwrap();
let mut id_str = max_id.to_string().as_bytes().to_vec();
id_str.resize(id_str.len() + CHACHA20_POLY1305.tag_len(), 0);
let out_len = seal_in_place(&sealing_key, &nonce, &[], &mut id_str, CHACHA20_POLY1305.tag_len())
.unwrap();
let mut out = id_str[0..out_len].to_vec();
out.extend_from_slice(&nonce);
hex::encode(out)
}
pub fn token_to_max_id(token: &str) -> Result<i64, ()> {
let opening_key = OpeningKey::new(&CHACHA20_POLY1305, &*TOKEN_KEY).unwrap();
let data = hex::decode(token).map_err(|_| ())?;
let len = data.len();
if len <= 12 {
return Err(());
}
let mut id_str = (&data[0..(len - 12)]).to_vec();
let nonce = &data[(len - 12)..len];
let decrypted = open_in_place(&opening_key, nonce, &[], 0, &mut id_str)
.map_err(|_| ())?;
String::from_utf8(decrypted.to_vec())
.map_err(|_| ())?
.parse()
.map_err(|_| ())
}