index : sfrs

A Standard Notes Sync Server written in Rust

diff options
context:
space:
mode:
authorPeter Cai <[email protected]>2020-02-23 16:59:49 +0800
committerPeter Cai <[email protected]>2020-02-23 16:59:49 +0800
commita340eb8c4f2ae7164fef942c3c6bc93535d6a613 (patch)
tree1443bb446d7de922a81aea36dcdca54d82a82794
parent2eaf7d24e066dfd63a075f62a4f4f90fcafb99f4 (diff)
downloadsfrs-a340eb8c4f2ae7164fef942c3c6bc93535d6a613.tar.gz
encrypt sync_token and cursor_token
-rw-r--r--.env.test4
-rw-r--r--Cargo.lock8
-rw-r--r--Cargo.toml4
-rw-r--r--src/api.rs18
-rw-r--r--src/main.rs1
-rw-r--r--src/sync_tokens.rs58
6 files changed, 86 insertions, 7 deletions
diff --git a/.env.test b/.env.test
index a9be4ee..b3ed7c5 100644
--- a/.env.test
+++ b/.env.test
@@ -1,2 +1,4 @@
SFRS_ENV=development
-DATABASE_URL=./db/database.test.db \ No newline at end of file
+DATABASE_URL=./db/database.test.db
+SYNC_TOKEN_SECRET=awesome_password
+SYNC_TOKEN_SALT=awesome_salt \ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index 3888690..180df7c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -335,6 +335,12 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1094,9 +1100,11 @@ dependencies = [
"diesel",
"diesel_migrations",
"dotenv",
+ "hex",
"itertools",
"lazy_static",
"regex 1.3.4",
+ "ring",
"rocket",
"rocket_contrib",
"rocket_cors",
diff --git a/Cargo.toml b/Cargo.toml
index 071be2a..52384f5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,4 +18,6 @@ uuid = { version = "0.8", features = ["v4"] }
chrono = "0.4"
serde_json = "1.0"
regex = "1"
-itertools = "0.8" \ No newline at end of file
+itertools = "0.8"
+ring = "0.13"
+hex = "0.4" \ No newline at end of file
diff --git a/src/api.rs b/src/api.rs
index de5a758..7999aa9 100644
--- a/src/api.rs
+++ b/src/api.rs
@@ -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
diff --git a/src/main.rs b/src/main.rs
index 851e279..58e3a31 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -15,6 +15,7 @@ extern crate lazy_static;
mod db;
mod schema;
+mod sync_tokens;
mod api;
mod tokens;
mod user;
diff --git a/src/sync_tokens.rs b/src/sync_tokens.rs
new file mode 100644
index 0000000..2dd025a
--- /dev/null
+++ b/src/sync_tokens.rs
@@ -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(|_| ())
+} \ No newline at end of file