cleanup auth code, verify CSRF prevention token
This commit is contained in:
parent
c8fd2dd2b8
commit
5ddf8cb10a
@ -9,24 +9,79 @@ use openssl::sha;
|
|||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn compute_csrf_secret_digest(
|
||||||
|
timestamp: i64,
|
||||||
|
secret: &[u8],
|
||||||
|
username: &str,
|
||||||
|
) -> String {
|
||||||
|
|
||||||
|
let mut hasher = sha::Sha256::new();
|
||||||
|
let data = format!("{:08X}:{}:", timestamp, username);
|
||||||
|
hasher.update(data.as_bytes());
|
||||||
|
hasher.update(secret);
|
||||||
|
|
||||||
|
base64::encode_config(&hasher.finish(), base64::STANDARD_NO_PAD)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn assemble_csrf_prevention_token(
|
pub fn assemble_csrf_prevention_token(
|
||||||
secret: &[u8],
|
secret: &[u8],
|
||||||
username: &str,
|
username: &str,
|
||||||
) -> String {
|
) -> String {
|
||||||
|
|
||||||
let epoch = std::time::SystemTime::now().duration_since(
|
let epoch = std::time::SystemTime::now().duration_since(
|
||||||
std::time::SystemTime::UNIX_EPOCH).unwrap().as_secs();
|
std::time::SystemTime::UNIX_EPOCH).unwrap().as_secs() as i64;
|
||||||
|
|
||||||
let mut hasher = sha::Sha256::new();
|
let digest = compute_csrf_secret_digest(epoch, secret, username);
|
||||||
let data = format!("{:08X}:{}:", epoch, username);
|
|
||||||
hasher.update(data.as_bytes());
|
|
||||||
hasher.update(secret);
|
|
||||||
|
|
||||||
let digest = base64::encode_config(&hasher.finish(), base64::STANDARD_NO_PAD);
|
|
||||||
|
|
||||||
format!("{:08X}:{}", epoch, digest)
|
format!("{:08X}:{}", epoch, digest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn verify_csrf_prevention_token(
|
||||||
|
secret: &[u8],
|
||||||
|
username: &str,
|
||||||
|
token: &str,
|
||||||
|
min_age: i64,
|
||||||
|
max_age: i64,
|
||||||
|
) -> Result<i64, Error> {
|
||||||
|
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
let mut parts: VecDeque<&str> = token.split(':').collect();
|
||||||
|
|
||||||
|
try_block!({
|
||||||
|
|
||||||
|
if parts.len() != 2 {
|
||||||
|
bail!("format error - wrong number of parts.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let timestamp = parts.pop_front().unwrap();
|
||||||
|
let sig = parts.pop_front().unwrap();
|
||||||
|
|
||||||
|
let ttime = i64::from_str_radix(timestamp, 16).
|
||||||
|
map_err(|err| format_err!("timestamp format error - {}", err))?;
|
||||||
|
|
||||||
|
let digest = compute_csrf_secret_digest(ttime, secret, username);
|
||||||
|
|
||||||
|
if digest != sig {
|
||||||
|
bail!("invalid signature.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let now = std::time::SystemTime::now().duration_since(
|
||||||
|
std::time::SystemTime::UNIX_EPOCH)?.as_secs() as i64;
|
||||||
|
|
||||||
|
let age = now - ttime;
|
||||||
|
if age < min_age {
|
||||||
|
bail!("timestamp newer than expected.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if age > max_age {
|
||||||
|
bail!("timestamp too old.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(age)
|
||||||
|
}).map_err(|err| format_err!("invalid csrf token - {}", err))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn generate_csrf_key() -> Result<(), Error> {
|
pub fn generate_csrf_key() -> Result<(), Error> {
|
||||||
|
|
||||||
let path = PathBuf::from(configdir!("/csrf.key"));
|
let path = PathBuf::from(configdir!("/csrf.key"));
|
||||||
|
@ -422,6 +422,48 @@ fn handle_static_file_download(filename: PathBuf) -> BoxFut {
|
|||||||
return Box::new(response);
|
return Box::new(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extract_auth_data(headers: &http::HeaderMap) -> (Option<String>, Option<String>) {
|
||||||
|
|
||||||
|
let mut ticket = None;
|
||||||
|
if let Some(raw_cookie) = headers.get("COOKIE") {
|
||||||
|
if let Ok(cookie) = raw_cookie.to_str() {
|
||||||
|
ticket = tools::extract_auth_cookie(cookie, "PBSAuthCookie");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let token = match headers.get("CSRFPreventionToken").map(|v| v.to_str()) {
|
||||||
|
Some(Ok(v)) => Some(v.to_owned()),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
(ticket, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_auth(method: &hyper::Method, ticket: Option<String>, token: Option<String>) -> Result<String, Error> {
|
||||||
|
|
||||||
|
let ticket_lifetime = 3600*2; // 2 hours
|
||||||
|
|
||||||
|
let username = match ticket {
|
||||||
|
Some(ticket) => match tools::ticket::verify_rsa_ticket(public_auth_key(), "PBS", &ticket, None, -300, ticket_lifetime) {
|
||||||
|
Ok((_age, Some(username))) => username.to_owned(),
|
||||||
|
Ok((_, None)) => bail!("ticket without username."),
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
}
|
||||||
|
None => bail!("missing ticket"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if method != hyper::Method::GET {
|
||||||
|
if let Some(token) = token {
|
||||||
|
println!("CSRF prev token: {:?}", token);
|
||||||
|
verify_csrf_prevention_token(csrf_secret(), &username, &token, -300, ticket_lifetime)?;
|
||||||
|
} else {
|
||||||
|
bail!("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(username)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> BoxFut {
|
pub fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> BoxFut {
|
||||||
|
|
||||||
let (parts, body) = req.into_parts();
|
let (parts, body) = req.into_parts();
|
||||||
@ -457,20 +499,9 @@ pub fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> BoxFut {
|
|||||||
|
|
||||||
let delay_unauth_time = std::time::Instant::now() + std::time::Duration::from_millis(3000);
|
let delay_unauth_time = std::time::Instant::now() + std::time::Duration::from_millis(3000);
|
||||||
|
|
||||||
if let Some(raw_cookie) = parts.headers.get("COOKIE") {
|
|
||||||
if let Ok(cookie) = raw_cookie.to_str() {
|
|
||||||
if let Some(ticket) = tools::extract_auth_cookie(cookie, "PBSAuthCookie") {
|
|
||||||
if let Ok((_, Some(username))) = tools::ticket::verify_rsa_ticket(
|
|
||||||
public_auth_key(), "PBS", &ticket, None, -300, 3600*2) {
|
|
||||||
rpcenv.set_user(Some(username));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if comp_len >= 1 && components[0] == "api2" {
|
if comp_len >= 1 && components[0] == "api2" {
|
||||||
println!("GOT API REQUEST");
|
println!("GOT API REQUEST");
|
||||||
|
|
||||||
if comp_len >= 2 {
|
if comp_len >= 2 {
|
||||||
let format = components[1];
|
let format = components[1];
|
||||||
let formatter = match format {
|
let formatter = match format {
|
||||||
@ -486,11 +517,18 @@ pub fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> BoxFut {
|
|||||||
if comp_len == 4 && components[2] == "access" && components[3] == "ticket" {
|
if comp_len == 4 && components[2] == "access" && components[3] == "ticket" {
|
||||||
// explicitly allow those calls without auth
|
// explicitly allow those calls without auth
|
||||||
} else {
|
} else {
|
||||||
if let Some(_username) = rpcenv.get_user() {
|
let (ticket, token) = extract_auth_data(&parts.headers);
|
||||||
|
match check_auth(&method, ticket, token) {
|
||||||
|
Ok(username) => {
|
||||||
|
|
||||||
// fixme: check permissions
|
// fixme: check permissions
|
||||||
} else {
|
|
||||||
|
rpcenv.set_user(Some(username));
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
// always delay unauthorized calls by 3 seconds (from start of request)
|
// always delay unauthorized calls by 3 seconds (from start of request)
|
||||||
let resp = (formatter.format_error)(http_err!(UNAUTHORIZED, "permission check failed.".into()));
|
let err = http_err!(UNAUTHORIZED, format!("permission check failed - {}", err));
|
||||||
|
let resp = (formatter.format_error)(err);
|
||||||
let delayed_response = tokio::timer::Delay::new(delay_unauth_time)
|
let delayed_response = tokio::timer::Delay::new(delay_unauth_time)
|
||||||
.map_err(|err| http_err!(INTERNAL_SERVER_ERROR, format!("tokio timer delay error: {}", err)))
|
.map_err(|err| http_err!(INTERNAL_SERVER_ERROR, format!("tokio timer delay error: {}", err)))
|
||||||
.and_then(|_| Ok(resp));
|
.and_then(|_| Ok(resp));
|
||||||
@ -498,6 +536,7 @@ pub fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> BoxFut {
|
|||||||
return Box::new(delayed_response);
|
return Box::new(delayed_response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match api.find_method(&components[2..], method, &mut uri_param) {
|
match api.find_method(&components[2..], method, &mut uri_param) {
|
||||||
MethodDefinition::None => {}
|
MethodDefinition::None => {}
|
||||||
|
Loading…
Reference in New Issue
Block a user