Remove dependencies to unmatained crats

Remove dependency csrf 0.3 as unmatained and can't compile on arm8
Add ring to do required cryptography
Remove dependency to rand as ring provid CPRNG
dependabot/cargo/ring-approx-0.14.0
Trinity Pointard 5 years ago
parent 717fad53cf
commit 9eb7b3f98b

@ -9,9 +9,8 @@ license = "GPL-3.0"
keywords = ["rocket", "csrf", "security"] keywords = ["rocket", "csrf", "security"]
[dependencies] [dependencies]
csrf = "~0.3.0"
data-encoding = "~2.1.1" data-encoding = "~2.1.1"
rand = "~0.6.1" ring = "~0.13.5"
rocket = "0.4.0-rc.1" rocket = "0.4.0-rc.1"
serde = "~1.0" serde = "~1.0"
time = "~0.1.40" time = "~0.1.40"

@ -0,0 +1,99 @@
use ring::aead::{CHACHA20_POLY1305, OpeningKey, open_in_place, SealingKey, seal_in_place};
use ring::rand::{SecureRandom, SystemRandom};
use std::time::SystemTime;
pub struct CsrfProtection {
aead_key: [u8; 32],// 256b
}
impl CsrfProtection {
pub fn from_key(aead_key: [u8; 32]) -> Self {
CsrfProtection { aead_key }
}
pub fn parse_cookie<'a>(&self, cookie: &'a mut [u8]) -> Result<CsrfCookie<'a>, CsrfError> {
let key = OpeningKey::new(&CHACHA20_POLY1305, &self.aead_key).map_err(|_| CsrfError::UnknownError)?;
if cookie.len() < 12 {
return Err(CsrfError::ValidationError);// cookie is too short to be valid
}
let (nonce, token) = cookie.split_at_mut(12);// 96b
let token = open_in_place(&key, nonce, &[], 0, token).map_err(|_| CsrfError::ValidationError)?;
if token.len() < 8 {// shorter than a timestamp, must be invalid
return Err(CsrfError::ValidationError);
}
let mut expires = [0;8];
expires.copy_from_slice(&token[..8]);
let expires = u64::from_be_bytes(expires);
let token = &token[8..];
Ok(CsrfCookie{
token,
expires,
})
}
pub fn parse_token<'a>(&self, token: &'a mut [u8]) -> Result<CsrfToken<'a>, CsrfError> {
let key = OpeningKey::new(&CHACHA20_POLY1305, &self.aead_key).map_err(|_| CsrfError::UnknownError)?;
if token.len() < 12 {
return Err(CsrfError::ValidationError);// cookie is too short to be valid
}
let (nonce, token) = token.split_at_mut(12);// 96b
let token = open_in_place(&key, nonce, &[], 0, token).map_err(|_| CsrfError::ValidationError)?;
Ok(CsrfToken{
token,
})
}
pub fn verify_token_pair(&self, token: CsrfToken, cookie: CsrfCookie) -> bool {
let token_ok = &token.token == &cookie.token;
let not_expired = cookie.time_left() > 0;
token_ok && not_expired
}
pub fn generate_token_pair<'a>(&self, previous_token: Option<CsrfCookie>, ttl_seconds: u64, source_buffer: &'a mut[u8; 64*2+16*2+12*2+8]) -> Result<(&'a[u8], &'a[u8]), CsrfError> {
let key = SealingKey::new(&CHACHA20_POLY1305, &self.aead_key).map_err(|_| CsrfError::UnknownError)?;
let (token, cookie) = source_buffer.split_at_mut(64+16+12);
let expire = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).map(|d| d.as_secs() + ttl_seconds).map_err(|_| CsrfError::UnknownError)?;
cookie[12..8+12].copy_from_slice(&expire.to_be_bytes());
let rand = SystemRandom::new();
if let Some(previous_token) = previous_token {
cookie[20..64+20].copy_from_slice(previous_token.token);
token[12..64+12].copy_from_slice(previous_token.token);
} else {
rand.fill(&mut token[12..64+12]).map_err(|_| CsrfError::UnknownError)?;
cookie[20..64+20].copy_from_slice(&token[12..64+12]);
}
let mut nonce = [0;12];
rand.fill(&mut nonce).map_err(|_| CsrfError::UnknownError)?;
token[..12].copy_from_slice(&nonce);
seal_in_place(&key, &nonce, &[], &mut token[12..], CHACHA20_POLY1305.tag_len()).map_err(|_| CsrfError::UnknownError)?;
rand.fill(&mut nonce).map_err(|_| CsrfError::UnknownError)?;
cookie[..12].copy_from_slice(&nonce);
seal_in_place(&key, &nonce, &[], &mut cookie[12..], CHACHA20_POLY1305.tag_len()).map_err(|_| CsrfError::UnknownError)?;
return Ok((token, cookie));
}
}
pub struct CsrfToken<'a> {
token: &'a[u8],
}
pub struct CsrfCookie<'a> {
token: &'a[u8],
expires: u64
}
impl<'a> CsrfCookie<'a> {
pub fn time_left(&self) -> u64 {
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).ok().and_then(|now| self.expires.checked_sub(now.as_secs())).unwrap_or(0)
}
}
pub enum CsrfError {
ValidationError,
UnknownError,
}

@ -1,7 +1,5 @@
use csrf::{AesGcmCsrfProtection, CsrfProtection, CSRF_COOKIE_NAME, CSRF_FORM_FIELD};
use data_encoding::{BASE64, BASE64URL_NOPAD}; use data_encoding::{BASE64, BASE64URL_NOPAD};
use rand::prelude::thread_rng; use ring::rand::{SecureRandom, SystemRandom};
use rand::Rng;
use rocket::fairing::{Fairing, Info, Kind}; use rocket::fairing::{Fairing, Info, Kind};
use rocket::http::uri::{Origin, Uri}; use rocket::http::uri::{Origin, Uri};
use rocket::http::Cookie; use rocket::http::Cookie;
@ -15,12 +13,12 @@ use std::io::{Cursor, Read};
use std::str::from_utf8; use std::str::from_utf8;
use time::Duration; use time::Duration;
use crypto::CsrfProtection;
use csrf_proxy::CsrfProxy; use csrf_proxy::CsrfProxy;
use csrf_token::CsrfToken; use csrf_token::CsrfToken;
use path::Path; use path::Path;
use utils::parse_args; use utils::parse_args;
use {CSRF_COOKIE_NAME, CSRF_FORM_FIELD, CSRF_FORM_FIELD_MULTIPART};
const CSRF_FORM_FIELD_MULTIPART: &[u8] = b"Content-Disposition: form-data; name=\"csrf-token\"";
/// Builder for [CsrfFairing](struct.CsrfFairing.html) /// Builder for [CsrfFairing](struct.CsrfFairing.html)
/// ///
@ -57,7 +55,7 @@ const CSRF_FORM_FIELD_MULTIPART: &[u8] = b"Content-Disposition: form-data; name=
/// ``` /// ```
pub struct CsrfFairingBuilder { pub struct CsrfFairingBuilder {
duration: i64, duration: u64,
default_target: (String, Method), default_target: (String, Method),
exceptions: Vec<(String, String, Method)>, exceptions: Vec<(String, String, Method)>,
secret: Option<[u8; 32]>, secret: Option<[u8; 32]>,
@ -70,7 +68,7 @@ impl CsrfFairingBuilder {
/// Create a new builder with default values. /// Create a new builder with default values.
pub fn new() -> Self { pub fn new() -> Self {
CsrfFairingBuilder { CsrfFairingBuilder {
duration: 60 * 60, duration: 60 * 60 * 12,
default_target: (String::from("/"), Get), default_target: (String::from("/"), Get),
exceptions: Vec::new(), exceptions: Vec::new(),
secret: None, secret: None,
@ -81,8 +79,8 @@ impl CsrfFairingBuilder {
} }
/// Set the timeout (in seconds) of CSRF tokens generated by the final Fairing. Default timeout /// Set the timeout (in seconds) of CSRF tokens generated by the final Fairing. Default timeout
/// is one hour. /// is twelve hour.
pub fn set_timeout(mut self, timeout: i64) -> Self { pub fn set_timeout(mut self, timeout: u64) -> Self {
self.duration = timeout; self.duration = timeout;
self self
} }
@ -107,7 +105,6 @@ impl CsrfFairingBuilder {
/// //add your routes, other fairings... /// //add your routes, other fairings...
/// .launch(); /// .launch();
/// } /// }
pub fn set_default_target(mut self, default_target: String, method: Method) -> Self { pub fn set_default_target(mut self, default_target: String, method: Method) -> Self {
self.default_target = (default_target, method); self.default_target = (default_target, method);
self self
@ -232,7 +229,10 @@ impl CsrfFairingBuilder {
})//else get secret environment variable })//else get secret environment variable
.unwrap_or_else(|| { .unwrap_or_else(|| {
eprintln!("[rocket_csrf] No secret key was found, you should consider set one to keep token validity across application restart"); eprintln!("[rocket_csrf] No secret key was found, you should consider set one to keep token validity across application restart");
thread_rng().gen() let rand = SystemRandom::new();
let mut array = [0;32];
rand.fill(&mut array).unwrap();
array
}) //if environment variable is not set, generate a random secret and print a warning }) //if environment variable is not set, generate a random secret and print a warning
}); });
@ -272,7 +272,7 @@ impl Default for CsrfFairingBuilder {
/// ///
/// [`CsrfFairingBuilder`]: /rocket_csrf/struct.CsrfFairing.html /// [`CsrfFairingBuilder`]: /rocket_csrf/struct.CsrfFairing.html
pub struct CsrfFairing { pub struct CsrfFairing {
duration: i64, duration: u64,
default_target: (Path, Method), default_target: (Path, Method),
exceptions: Vec<(Path, Path, Method)>, exceptions: Vec<(Path, Path, Method)>,
secret: [u8; 32], secret: [u8; 32],
@ -297,7 +297,7 @@ impl Fairing for CsrfFairing {
} }
fn on_attach(&self, rocket: Rocket) -> Result<Rocket, Rocket> { fn on_attach(&self, rocket: Rocket) -> Result<Rocket, Rocket> {
Ok(rocket.manage((AesGcmCsrfProtection::from_key(self.secret), self.duration))) //add the Csrf engine to Rocket's managed state Ok(rocket.manage((CsrfProtection::from_key(self.secret), self.duration))) //add the Csrf engine to Rocket's managed state
} }
fn on_request(&self, request: &mut Request, data: &Data) { fn on_request(&self, request: &mut Request, data: &Data) {
@ -316,17 +316,17 @@ impl Fairing for CsrfFairing {
} }
let (csrf_engine, _) = request let (csrf_engine, _) = request
.guard::<State<(AesGcmCsrfProtection, i64)>>() .guard::<State<(CsrfProtection, u64)>>()
.unwrap() .unwrap()
.inner(); .inner();
let cookie = request let mut cookie = request
.cookies() .cookies()
.get(CSRF_COOKIE_NAME) .get(CSRF_COOKIE_NAME)
.and_then(|cookie| BASE64URL_NOPAD.decode(cookie.value().as_bytes()).ok()) .and_then(|cookie| BASE64URL_NOPAD.decode(cookie.value().as_bytes()).ok());
.and_then(|cookie| csrf_engine.parse_cookie(&cookie).ok()); //get and parse Csrf cookie let cookie = cookie.as_mut().and_then(|c| csrf_engine.parse_cookie(&mut *c).ok()); //get and parse Csrf cookie
let token = if request let mut token = if request
.content_type() .content_type()
.map(|c| c.media_type()) .map(|c| c.media_type())
.filter(|m| m.top() == "multipart" && m.sub() == "form-data") .filter(|m| m.top() == "multipart" && m.sub() == "form-data")
@ -348,12 +348,12 @@ impl Fairing for CsrfFairing {
} }
}) })
.next() .next()
}.and_then(|token| BASE64URL_NOPAD.decode(&token).ok()) }.and_then(|token| BASE64URL_NOPAD.decode(&token).ok());
.and_then(|token| csrf_engine.parse_token(&token).ok()); let token = token.as_mut().and_then(|token| csrf_engine.parse_token(&mut *token).ok());
if let Some(token) = token { if let Some(token) = token {
if let Some(cookie) = cookie { if let Some(cookie) = cookie {
if csrf_engine.verify_token_pair(&token, &cookie) { if csrf_engine.verify_token_pair(token, cookie) {
return; //if we got both token and cookie, and they match each other, we do nothing return; //if we got both token and cookie, and they match each other, we do nothing
} }
} }
@ -452,7 +452,7 @@ impl Fairing for CsrfFairing {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use csrf::{CSRF_COOKIE_NAME, CSRF_FORM_FIELD}; use {CSRF_COOKIE_NAME, CSRF_FORM_FIELD};
use rocket::{ use rocket::{
http::{Cookie, Header, Method}, http::{Cookie, Header, Method},
local::{Client, LocalRequest}, local::{Client, LocalRequest},

@ -1,4 +1,4 @@
use csrf::{AesGcmCsrfProtection, CsrfProtection, CSRF_COOKIE_NAME}; use CSRF_COOKIE_NAME;
use data_encoding::BASE64URL_NOPAD; use data_encoding::BASE64URL_NOPAD;
use rocket::http::{Cookie, SameSite, Status}; use rocket::http::{Cookie, SameSite, Status};
use rocket::outcome::Outcome; use rocket::outcome::Outcome;
@ -7,6 +7,8 @@ use rocket::{Request, State};
use serde::{Serialize, Serializer}; use serde::{Serialize, Serializer};
use time::Duration; use time::Duration;
use crypto::CsrfProtection;
/// Csrf token to insert into pages. /// Csrf token to insert into pages.
/// ///
/// The `CsrfToken` type allow you to add tokens into your pages anywhere you want, and is mainly /// The `CsrfToken` type allow you to add tokens into your pages anywhere you want, and is mainly
@ -40,7 +42,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for CsrfToken {
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, ()> { fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, ()> {
let (csrf_engine, duration) = request let (csrf_engine, duration) = request
.guard::<State<(AesGcmCsrfProtection, i64)>>() .guard::<State<(CsrfProtection, u64)>>()
.unwrap() .unwrap()
.inner(); .inner();
@ -50,35 +52,26 @@ impl<'a, 'r> FromRequest<'a, 'r> for CsrfToken {
{ {
Outcome::Forward(()) Outcome::Forward(())
} else { } else {
let token_value = cookies let mut token_value = cookies
.get(CSRF_COOKIE_NAME) .get(CSRF_COOKIE_NAME)
.and_then(|cookie| BASE64URL_NOPAD.decode(cookie.value().as_bytes()).ok()) .and_then(|cookie| BASE64URL_NOPAD.decode(cookie.value().as_bytes()).ok());
.and_then(|cookie| csrf_engine.parse_cookie(&cookie).ok()) let token_value = token_value.as_mut().and_then(|cookie| csrf_engine.parse_cookie(&mut *cookie).ok());
.and_then(|cookie| {
let value = cookie.value();
if value.len() == 64 {
let mut array = [0; 64];
array.copy_from_slice(&value);
Some(array)
} else {
None
}
}); //when request guard is called, parse cookie to get it's encrypted secret (if there is a cookie)
match csrf_engine.generate_token_pair(token_value.as_ref(), *duration) { let mut buf = [0; 192];
match csrf_engine.generate_token_pair(token_value, *duration, &mut buf) {
Ok((token, cookie)) => { Ok((token, cookie)) => {
let mut c = let mut c =
Cookie::build(CSRF_COOKIE_NAME, BASE64URL_NOPAD.encode(cookie.value())) Cookie::build(CSRF_COOKIE_NAME, BASE64URL_NOPAD.encode(cookie))
.http_only(true) .http_only(true)
.secure(true) .secure(true)
.same_site(SameSite::Strict) .same_site(SameSite::Strict)
.path("/") .path("/")
.max_age(Duration::seconds(*duration)) .max_age(Duration::seconds(*duration as i64))
.finish(); .finish();
cookies.add(c); cookies.add(c);
Outcome::Success(CsrfToken { Outcome::Success(CsrfToken {
value: BASE64URL_NOPAD.encode(token.value()), value: BASE64URL_NOPAD.encode(token),
}) })
} }
Err(_) => Outcome::Failure((Status::InternalServerError, ())), Err(_) => Outcome::Failure((Status::InternalServerError, ())),

@ -43,9 +43,8 @@
//! You should define a route for csrf violation error, and registe it in the builder, otherwise //! You should define a route for csrf violation error, and registe it in the builder, otherwise
//! errors will simply be redirected to the route matching `/` //! errors will simply be redirected to the route matching `/`
//! //!
extern crate csrf;
extern crate data_encoding; extern crate data_encoding;
extern crate rand; extern crate ring;
extern crate serde; extern crate serde;
extern crate test; extern crate test;
extern crate time; extern crate time;
@ -60,9 +59,14 @@ mod csrf_proxy;
mod csrf_token; mod csrf_token;
mod path; mod path;
mod utils; mod utils;
mod crypto;
pub use self::csrf_fairing::{CsrfFairing, CsrfFairingBuilder}; pub use self::csrf_fairing::{CsrfFairing, CsrfFairingBuilder};
pub use self::csrf_token::CsrfToken; pub use self::csrf_token::CsrfToken;
const CSRF_COOKIE_NAME: &str = "csrf";
const CSRF_FORM_FIELD: &str = "csrf-token";
const CSRF_FORM_FIELD_MULTIPART: &[u8] = b"Content-Disposition: form-data; name=\"csrf-token\"";
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

Loading…
Cancel
Save