Bcrypt the Generated token for form implanting along with private cookies they wont be able to easily generate the original key. Started Config Options.

This commit is contained in:
Andrew Wheeler 2021-01-27 17:00:40 -05:00
parent 5257f9e644
commit 1148a492b0
3 changed files with 79 additions and 39 deletions

View File

@ -14,5 +14,7 @@ publish = true
[dependencies]
base64 = { version = "0.13.0" }
rand = { version = "0.7.3" }
rocket = { version = "0.4.5", features = ["private-cookies"] }
rand = { version = "0.8.3" }
rocket = { version = "0.4.6", features = ["private-cookies"] }
time = "0.2.25"
bcrypt = "0.9"

View File

@ -1,17 +1,43 @@
use rand::RngCore;
use rocket::{Data, Request};
use rocket::fairing::{Fairing as RocketFairing, Info, Kind};
use rocket::http::{Cookie, Status};
use rocket::request::{FromRequest, Outcome};
use rand::{distributions::Standard, Rng};
use rocket::{
fairing::{Fairing as RocketFairing, Info, Kind},
http::{Cookie, Status},
request::{FromRequest, Outcome},
Data, Request, Rocket, State,
};
use bcrypt::{hash, verify, DEFAULT_COST};
use std::borrow::Cow;
use time::Duration;
const COOKIE_NAME: &str = "csrf_token";
const _PARAM_NAME: &str = "authenticity_token";
const _HEADER_NAME: &str = "X-CSRF-Token";
const _PARAM_META_NAME: &str = "csrf-param";
const _TOKEN_META_NAME: &str = "csrf-token";
const RAW_TOKEN_LENGTH: usize = 32;
pub struct Fairing;
#[derive(Debug, Clone)]
pub struct CsrfConfig {
/// CSRF Cookie lifespan
lifespan: Duration,
/// CSRF cookie name
cookie_name: Cow<'static, str>,
/// CSRF Token character length
cookie_len: usize,
}
impl Default for CsrfConfig {
fn default() -> Self {
Self {
/// Set to 6hour for default in Database Session stores.
lifespan: Duration::day(),
cookie_name: "csrf_token".into(),
cookie_len: 32,
}
}
}
pub struct Fairing {
config: CsrfConfig,
}
pub struct CsrfToken(String);
@ -19,22 +45,21 @@ pub struct VerificationFailure;
impl Fairing {
pub fn new() -> Self {
Self {}
Self {
config: Default::default(),
}
}
}
impl CsrfToken {
pub fn authenticity_token(&self) -> &str {
self.0.as_ref()
pub fn authenticity_token(&self) -> String {
hash(&self.0, DEFAULT_COST).unwrap()
}
pub fn verify(&self, form_authenticity_token: &str)
-> Result<(), VerificationFailure>
{
if self.0 == form_authenticity_token {
pub fn verify(&self, form_authenticity_token: &String) -> Result<(), VerificationFailure> {
if verify(&self.0, form_authenticity_token).unwrap_or(false) {
Ok(())
}
else {
} else {
Err(VerificationFailure {})
}
}
@ -44,27 +69,35 @@ impl RocketFairing for Fairing {
fn info(&self) -> Info {
Info {
name: "CSRF",
kind: Kind::Request,
kind: Kind::Attach | Kind::Request,
}
}
fn on_request(&self, request: &mut Request, _: &Data) {
if let Some(_) = request.valid_csrf_token_from_session() { return }
let mut raw = [0u8; RAW_TOKEN_LENGTH];
rand::thread_rng().fill_bytes(&mut raw);
let encoded = base64::encode(raw);
request.cookies().add_private(Cookie::new(COOKIE_NAME, encoded));
fn on_attach(&self, rocket: Rocket) -> std::result::Result<Rocket, Rocket> {
Ok(rocket.manage(self.config.clone()))
}
fn on_request(&self, request: &mut Request, _: &Data) {
let config = request.guard::<State<CsrfConfig>>().unwrap();
if let Some(_) = request.valid_csrf_token_from_session(&config) {
return;
}
let values: Vec<u8> = rand::thread_rng().sample_iter(Standard).take(config.cookie_len).collect();
let encoded = base64::encode(&values[..]);
request
.cookies()
.add_private(Cookie::new(config.cookie_name.clone(), encoded));
}
}
impl<'a, 'r> FromRequest<'a, 'r> for CsrfToken {
type Error = ();
fn from_request(request: &'a Request<'r>) -> Outcome<Self, Self::Error> {
match request.valid_csrf_token_from_session() {
let config = request.guard::<State<CsrfConfig>>().unwrap();
match request.valid_csrf_token_from_session(&config) {
None => Outcome::Failure((Status::Forbidden, ())),
Some(token) => Outcome::Success(Self(base64::encode(token))),
}
@ -72,19 +105,23 @@ impl<'a, 'r> FromRequest<'a, 'r> for CsrfToken {
}
trait RequestCsrf {
fn valid_csrf_token_from_session(&self) -> Option<Vec<u8>> {
self.csrf_token_from_session()
.and_then(|raw|
if raw.len() >= RAW_TOKEN_LENGTH { Some(raw) } else { None }
)
fn valid_csrf_token_from_session(&self, config: &CsrfConfig) -> Option<Vec<u8>> {
self.csrf_token_from_session(config).and_then(|raw| {
if raw.len() >= config.cookie_len {
Some(raw)
} else {
None
}
})
}
fn csrf_token_from_session(&self) -> Option<Vec<u8>>;
fn csrf_token_from_session(&self, config: &CsrfConfig) -> Option<Vec<u8>>;
}
impl RequestCsrf for Request<'_> {
fn csrf_token_from_session(&self) -> Option<Vec<u8>> {
self.cookies().get_private(COOKIE_NAME)
fn csrf_token_from_session(&self, config: &CsrfConfig) -> Option<Vec<u8>> {
self.cookies()
.get_private(&config.cookie_name)
.and_then(|cookie| base64::decode(cookie.value()).ok())
}
}

View File

@ -5,6 +5,7 @@
use rand::RngCore;
use rocket::http::Cookie;
use rocket_csrf::CsrfToken;
use bcrypt::verify;
fn client() -> rocket::local::Client {
rocket::local::Client::new(rocket()).unwrap()
@ -37,5 +38,5 @@ fn respond_with_valid_authenticity_token() {
.into_string()
.unwrap();
assert_eq!(body, encoded);
assert!(verify(&encoded, &body).unwrap());
}