encrypt sync_token and cursor_token

This commit is contained in:
Peter Cai 2020-02-23 16:59:49 +08:00
parent 2eaf7d24e0
commit a340eb8c4f
No known key found for this signature in database
GPG Key ID: 71F5FB4E4F3FD54F
6 changed files with 86 additions and 7 deletions

View File

@ -1,2 +1,4 @@
SFRS_ENV=development
DATABASE_URL=./db/database.test.db
DATABASE_URL=./db/database.test.db
SYNC_TOKEN_SECRET=awesome_password
SYNC_TOKEN_SALT=awesome_salt

8
Cargo.lock generated
View File

@ -334,6 +334,12 @@ dependencies = [
"libc",
]
[[package]]
name = "hex"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35"
[[package]]
name = "hmac"
version = "0.7.1"
@ -1094,9 +1100,11 @@ dependencies = [
"diesel",
"diesel_migrations",
"dotenv",
"hex",
"itertools",
"lazy_static",
"regex 1.3.4",
"ring",
"rocket",
"rocket_contrib",
"rocket_cors",

View File

@ -18,4 +18,6 @@ uuid = { version = "0.8", features = ["v4"] }
chrono = "0.4"
serde_json = "1.0"
regex = "1"
itertools = "0.8"
itertools = "0.8"
ring = "0.13"
hex = "0.4"

View File

@ -217,7 +217,7 @@ fn items_sync(
// so all that can change the current_max_id for the current user
// is operations later in this function.
let new_sync_token = match item::SyncItem::get_current_max_id(&db.0, &u) {
Ok(Some(id)) => Some(id.to_string()),
Ok(Some(id)) => Some(crate::sync_tokens::max_id_to_token(id)),
Ok(None) => None,
Err(item::ItemOpError(e)) =>
return error_resp(Status::InternalServerError, vec![e])
@ -237,11 +237,19 @@ fn items_sync(
// If the client provides cursor_token,
// then, we return all records
// until sync_token (the head of the last sync)
cursor_token.parse().ok()
match crate::sync_tokens::token_to_max_id(&cursor_token) {
Err(()) =>
return error_resp(Status::InternalServerError, vec!["Invalid cursor_token".into()]),
Ok(id) => Some(id)
}
} else if let Some(sync_token) = inner_params.sync_token {
// If there is no cursor_token, then we are doing
// a normal sync, so just return all records from sync_token
sync_token.parse().ok()
match crate::sync_tokens::token_to_max_id(&sync_token) {
Err(()) =>
return error_resp(Status::InternalServerError, vec!["Invalid sync_token".into()]),
Ok(id) => Some(id)
}
} else {
None
};
@ -263,7 +271,7 @@ fn items_sync(
if let Some(limit) = inner_params.limit {
if items.len() as i64 == limit {
// We may still have something to fetch
resp.cursor_token = Some(next_from.to_string());
resp.cursor_token = Some(crate::sync_tokens::max_id_to_token(next_from));
}
}
}
@ -326,7 +334,7 @@ fn items_sync(
// LATEST known state of the system by the client,
// but it MAY still need to fill in a bit of history
// (that's where `cursor_token` comes into play)
resp.sync_token = Some(last_id.to_string());
resp.sync_token = Some(crate::sync_tokens::max_id_to_token(last_id));
}
// Remove conflicted items from retrieved items

View File

@ -15,6 +15,7 @@ extern crate lazy_static;
mod db;
mod schema;
mod sync_tokens;
mod api;
mod tokens;
mod user;

58
src/sync_tokens.rs Normal file
View File

@ -0,0 +1,58 @@
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(|_| ())
}