replace and remove old ticket functions
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
parent
593f917742
commit
72dc68323c
|
@ -7,8 +7,7 @@ use proxmox::api::router::{Router, SubdirMap};
|
||||||
use proxmox::{sortable, identity};
|
use proxmox::{sortable, identity};
|
||||||
use proxmox::{http_err, list_subdirs_api_method};
|
use proxmox::{http_err, list_subdirs_api_method};
|
||||||
|
|
||||||
use crate::tools;
|
use crate::tools::ticket::{self, Empty, Ticket};
|
||||||
use crate::tools::ticket::*;
|
|
||||||
use crate::auth_helpers::*;
|
use crate::auth_helpers::*;
|
||||||
use crate::api2::types::*;
|
use crate::api2::types::*;
|
||||||
|
|
||||||
|
@ -35,27 +34,31 @@ fn authenticate_user(
|
||||||
bail!("user account disabled or expired.");
|
bail!("user account disabled or expired.");
|
||||||
}
|
}
|
||||||
|
|
||||||
let ticket_lifetime = tools::ticket::TICKET_LIFETIME;
|
|
||||||
|
|
||||||
if password.starts_with("PBS:") {
|
if password.starts_with("PBS:") {
|
||||||
if let Ok((_age, Some(ticket_username))) = tools::ticket::verify_rsa_ticket(public_auth_key(), "PBS", password, None, -300, ticket_lifetime) {
|
if let Ok(ticket_userid) = Ticket::<Userid>::parse(password)
|
||||||
if *userid == ticket_username {
|
.and_then(|ticket| ticket.verify(public_auth_key(), "PBS", None))
|
||||||
|
{
|
||||||
|
if *userid == ticket_userid {
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
} else {
|
|
||||||
bail!("ticket login failed - wrong userid");
|
|
||||||
}
|
}
|
||||||
|
bail!("ticket login failed - wrong userid");
|
||||||
}
|
}
|
||||||
} else if password.starts_with("PBSTERM:") {
|
} else if password.starts_with("PBSTERM:") {
|
||||||
if path.is_none() || privs.is_none() || port.is_none() {
|
if path.is_none() || privs.is_none() || port.is_none() {
|
||||||
bail!("cannot check termnal ticket without path, priv and port");
|
bail!("cannot check termnal ticket without path, priv and port");
|
||||||
}
|
}
|
||||||
|
|
||||||
let path = path.unwrap();
|
let path = path.ok_or_else(|| format_err!("missing path for termproxy ticket"))?;
|
||||||
let privilege_name = privs.unwrap();
|
let privilege_name = privs
|
||||||
let port = port.unwrap();
|
.ok_or_else(|| format_err!("missing privilege name for termproxy ticket"))?;
|
||||||
|
let port = port.ok_or_else(|| format_err!("missing port for termproxy ticket"))?;
|
||||||
|
|
||||||
if let Ok((_age, _data)) =
|
if let Ok(Empty) = Ticket::parse(password)
|
||||||
tools::ticket::verify_term_ticket(public_auth_key(), &userid, &path, port, password)
|
.and_then(|ticket| ticket.verify(
|
||||||
|
public_auth_key(),
|
||||||
|
ticket::TERM_PREFIX,
|
||||||
|
Some(&ticket::term_aad(userid, &path, port)),
|
||||||
|
))
|
||||||
{
|
{
|
||||||
for (name, privilege) in PRIVILEGES {
|
for (name, privilege) in PRIVILEGES {
|
||||||
if *name == privilege_name {
|
if *name == privilege_name {
|
||||||
|
@ -138,7 +141,7 @@ fn create_ticket(
|
||||||
) -> Result<Value, Error> {
|
) -> Result<Value, Error> {
|
||||||
match authenticate_user(&username, &password, path, privs, port) {
|
match authenticate_user(&username, &password, path, privs, port) {
|
||||||
Ok(true) => {
|
Ok(true) => {
|
||||||
let ticket = assemble_rsa_ticket(private_auth_key(), "PBS", Some(&username), None)?;
|
let ticket = Ticket::new("PBS", &username)?.sign(private_auth_key(), None)?;
|
||||||
|
|
||||||
let token = assemble_csrf_prevention_token(csrf_secret(), &username);
|
let token = assemble_csrf_prevention_token(csrf_secret(), &username);
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ use crate::api2::types::*;
|
||||||
use crate::config::acl::PRIV_SYS_CONSOLE;
|
use crate::config::acl::PRIV_SYS_CONSOLE;
|
||||||
use crate::server::WorkerTask;
|
use crate::server::WorkerTask;
|
||||||
use crate::tools;
|
use crate::tools;
|
||||||
|
use crate::tools::ticket::{self, Empty, Ticket};
|
||||||
|
|
||||||
pub mod disks;
|
pub mod disks;
|
||||||
pub mod dns;
|
pub mod dns;
|
||||||
|
@ -105,11 +106,10 @@ async fn termproxy(
|
||||||
let listener = TcpListener::bind("localhost:0")?;
|
let listener = TcpListener::bind("localhost:0")?;
|
||||||
let port = listener.local_addr()?.port();
|
let port = listener.local_addr()?.port();
|
||||||
|
|
||||||
let ticket = tools::ticket::assemble_term_ticket(
|
let ticket = Ticket::new(ticket::TERM_PREFIX, &Empty)?
|
||||||
|
.sign(
|
||||||
crate::auth_helpers::private_auth_key(),
|
crate::auth_helpers::private_auth_key(),
|
||||||
&userid,
|
Some(&ticket::term_aad(&userid, &path, port)),
|
||||||
&path,
|
|
||||||
port,
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut command = Vec::new();
|
let mut command = Vec::new();
|
||||||
|
@ -273,16 +273,15 @@ fn upgrade_to_websocket(
|
||||||
) -> ApiResponseFuture {
|
) -> ApiResponseFuture {
|
||||||
async move {
|
async move {
|
||||||
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
let ticket = tools::required_string_param(¶m, "vncticket")?.to_owned();
|
let ticket = tools::required_string_param(¶m, "vncticket")?;
|
||||||
let port: u16 = tools::required_integer_param(¶m, "port")? as u16;
|
let port: u16 = tools::required_integer_param(¶m, "port")? as u16;
|
||||||
|
|
||||||
// will be checked again by termproxy
|
// will be checked again by termproxy
|
||||||
tools::ticket::verify_term_ticket(
|
Ticket::<Empty>::parse(ticket)?
|
||||||
|
.verify(
|
||||||
crate::auth_helpers::public_auth_key(),
|
crate::auth_helpers::public_auth_key(),
|
||||||
&userid,
|
ticket::TERM_PREFIX,
|
||||||
&"/system",
|
Some(&ticket::term_aad(&userid, "/system", port)),
|
||||||
port,
|
|
||||||
&ticket,
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let (ws, response) = WebSocket::new(parts.headers)?;
|
let (ws, response) = WebSocket::new(parts.headers)?;
|
||||||
|
|
|
@ -9,7 +9,7 @@ use proxmox_backup::tools;
|
||||||
use proxmox_backup::config;
|
use proxmox_backup::config;
|
||||||
use proxmox_backup::api2::{self, types::* };
|
use proxmox_backup::api2::{self, types::* };
|
||||||
use proxmox_backup::client::*;
|
use proxmox_backup::client::*;
|
||||||
use proxmox_backup::tools::ticket::*;
|
use proxmox_backup::tools::ticket::Ticket;
|
||||||
use proxmox_backup::auth_helpers::*;
|
use proxmox_backup::auth_helpers::*;
|
||||||
|
|
||||||
mod proxmox_backup_manager;
|
mod proxmox_backup_manager;
|
||||||
|
@ -59,12 +59,8 @@ fn connect() -> Result<HttpClient, Error> {
|
||||||
.verify_cert(false); // not required for connection to localhost
|
.verify_cert(false); // not required for connection to localhost
|
||||||
|
|
||||||
let client = if uid.is_root() {
|
let client = if uid.is_root() {
|
||||||
let ticket = assemble_rsa_ticket(
|
let ticket = Ticket::new("PBS", Userid::root_userid())?
|
||||||
private_auth_key(),
|
.sign(private_auth_key(), None)?;
|
||||||
"PBS",
|
|
||||||
Some(Userid::root_userid()),
|
|
||||||
None,
|
|
||||||
)?;
|
|
||||||
options = options.password(Some(ticket));
|
options = options.password(Some(ticket));
|
||||||
HttpClient::new("localhost", Userid::root_userid(), options)?
|
HttpClient::new("localhost", Userid::root_userid(), options)?
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -29,6 +29,7 @@ use super::ApiConfig;
|
||||||
use crate::auth_helpers::*;
|
use crate::auth_helpers::*;
|
||||||
use crate::api2::types::Userid;
|
use crate::api2::types::Userid;
|
||||||
use crate::tools;
|
use crate::tools;
|
||||||
|
use crate::tools::ticket::Ticket;
|
||||||
use crate::config::cached_user_info::CachedUserInfo;
|
use crate::config::cached_user_info::CachedUserInfo;
|
||||||
|
|
||||||
extern "C" { fn tzset(); }
|
extern "C" { fn tzset(); }
|
||||||
|
@ -463,17 +464,11 @@ fn check_auth(
|
||||||
token: &Option<String>,
|
token: &Option<String>,
|
||||||
user_info: &CachedUserInfo,
|
user_info: &CachedUserInfo,
|
||||||
) -> Result<Userid, Error> {
|
) -> Result<Userid, Error> {
|
||||||
|
|
||||||
let ticket_lifetime = tools::ticket::TICKET_LIFETIME;
|
let ticket_lifetime = tools::ticket::TICKET_LIFETIME;
|
||||||
|
|
||||||
let userid = match ticket {
|
let ticket = ticket.as_ref().map(String::as_str);
|
||||||
Some(ticket) => match tools::ticket::verify_rsa_ticket(public_auth_key(), "PBS", &ticket, None, -300, ticket_lifetime) {
|
let userid: Userid = Ticket::parse(&ticket.ok_or_else(|| format_err!("missing ticket"))?)?
|
||||||
Ok((_age, Some(userid))) => userid,
|
.verify_with_time_frame(public_auth_key(), "PBS", None, -300..ticket_lifetime)?;
|
||||||
Ok((_, None)) => bail!("ticket without username."),
|
|
||||||
Err(err) => return Err(err),
|
|
||||||
}
|
|
||||||
None => bail!("missing ticket"),
|
|
||||||
};
|
|
||||||
|
|
||||||
if !user_info.is_active_user(&userid) {
|
if !user_info.is_active_user(&userid) {
|
||||||
bail!("user account disabled or expired.");
|
bail!("user account disabled or expired.");
|
||||||
|
|
|
@ -7,7 +7,7 @@ use std::marker::PhantomData;
|
||||||
use anyhow::{bail, format_err, Error};
|
use anyhow::{bail, format_err, Error};
|
||||||
use base64;
|
use base64;
|
||||||
|
|
||||||
use openssl::pkey::{PKey, Public, Private, HasPublic};
|
use openssl::pkey::{PKey, Private, HasPublic};
|
||||||
use openssl::sign::{Signer, Verifier};
|
use openssl::sign::{Signer, Verifier};
|
||||||
use openssl::hash::MessageDigest;
|
use openssl::hash::MessageDigest;
|
||||||
use percent_encoding::{AsciiSet, percent_decode_str, percent_encode};
|
use percent_encoding::{AsciiSet, percent_decode_str, percent_encode};
|
||||||
|
@ -266,7 +266,7 @@ pub fn term_aad(userid: &Userid, path: &str, port: u16) -> String {
|
||||||
mod test {
|
mod test {
|
||||||
use openssl::pkey::{PKey, Private};
|
use openssl::pkey::{PKey, Private};
|
||||||
|
|
||||||
use super::{Ticket, TICKET_LIFETIME};
|
use super::Ticket;
|
||||||
use crate::api2::types::Userid;
|
use crate::api2::types::Userid;
|
||||||
use crate::tools::epoch_now_u64;
|
use crate::tools::epoch_now_u64;
|
||||||
|
|
||||||
|
@ -288,13 +288,6 @@ mod test {
|
||||||
.expect("failed to verify test ticket");
|
.expect("failed to verify test ticket");
|
||||||
|
|
||||||
assert_eq!(*userid, check);
|
assert_eq!(*userid, check);
|
||||||
|
|
||||||
// Compat check:
|
|
||||||
let (_age, uid) =
|
|
||||||
super::verify_rsa_ticket(key, "PREFIX", &ticket, aad, -300, TICKET_LIFETIME)
|
|
||||||
.expect("failed compatibility verification");
|
|
||||||
let uid = uid.expect("compat did not return a userid");
|
|
||||||
assert_eq!(*userid, uid);
|
|
||||||
} else {
|
} else {
|
||||||
parsed
|
parsed
|
||||||
.verify(key, "PREFIX", aad)
|
.verify(key, "PREFIX", aad)
|
||||||
|
@ -320,151 +313,5 @@ mod test {
|
||||||
t.change_time(epoch_now_u64().unwrap() as i64 + 0x1000_0000);
|
t.change_time(epoch_now_u64().unwrap() as i64 + 0x1000_0000);
|
||||||
false
|
false
|
||||||
});
|
});
|
||||||
|
|
||||||
// compat check:
|
|
||||||
let ticket =
|
|
||||||
super::assemble_rsa_ticket(&key, "PREFIX", Some(Userid::root_userid()), Some("stuff"))
|
|
||||||
.expect("failed to assemble compatibility ticket");
|
|
||||||
let parsed_uid: Userid = Ticket::parse(&ticket)
|
|
||||||
.expect("failed to parse compatibility ticket")
|
|
||||||
.verify(&key, Some("stuff"), -300..TICKET_LIFETIME)
|
|
||||||
.expect("failed to verify compatibility ticket");
|
|
||||||
assert_eq!(parsed_uid, *Userid::root_userid());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn assemble_term_ticket(
|
|
||||||
keypair: &PKey<Private>,
|
|
||||||
userid: &Userid,
|
|
||||||
path: &str,
|
|
||||||
port: u16,
|
|
||||||
) -> Result<String, Error> {
|
|
||||||
assemble_rsa_ticket(
|
|
||||||
keypair,
|
|
||||||
TERM_PREFIX,
|
|
||||||
None,
|
|
||||||
Some(&format!("{}{}{}", userid, path, port)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn verify_term_ticket(
|
|
||||||
keypair: &PKey<Public>,
|
|
||||||
userid: &Userid,
|
|
||||||
path: &str,
|
|
||||||
port: u16,
|
|
||||||
ticket: &str,
|
|
||||||
) -> Result<(i64, Option<Userid>), Error> {
|
|
||||||
verify_rsa_ticket(
|
|
||||||
keypair,
|
|
||||||
TERM_PREFIX,
|
|
||||||
ticket,
|
|
||||||
Some(&format!("{}{}{}", userid, path, port)),
|
|
||||||
-300,
|
|
||||||
TICKET_LIFETIME,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn assemble_rsa_ticket(
|
|
||||||
keypair: &PKey<Private>,
|
|
||||||
prefix: &str,
|
|
||||||
data: Option<&Userid>,
|
|
||||||
secret_data: Option<&str>,
|
|
||||||
) -> Result<String, Error> {
|
|
||||||
|
|
||||||
let epoch = epoch_now_u64()?;
|
|
||||||
|
|
||||||
let timestamp = format!("{:08X}", epoch);
|
|
||||||
|
|
||||||
let mut plain = prefix.to_owned();
|
|
||||||
plain.push(':');
|
|
||||||
|
|
||||||
if let Some(data) = data {
|
|
||||||
use std::fmt::Write;
|
|
||||||
write!(plain, "{}", data)?;
|
|
||||||
plain.push(':');
|
|
||||||
}
|
|
||||||
|
|
||||||
plain.push_str(×tamp);
|
|
||||||
|
|
||||||
let mut full = plain.clone();
|
|
||||||
if let Some(secret) = secret_data {
|
|
||||||
full.push(':');
|
|
||||||
full.push_str(secret);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut signer = Signer::new(MessageDigest::sha256(), &keypair)?;
|
|
||||||
signer.update(full.as_bytes())?;
|
|
||||||
let sign = signer.sign_to_vec()?;
|
|
||||||
|
|
||||||
let sign_b64 = base64::encode_config(&sign, base64::STANDARD_NO_PAD);
|
|
||||||
|
|
||||||
Ok(format!("{}::{}", plain, sign_b64))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn verify_rsa_ticket<P: HasPublic>(
|
|
||||||
keypair: &PKey<P>,
|
|
||||||
prefix: &str,
|
|
||||||
ticket: &str,
|
|
||||||
secret_data: Option<&str>,
|
|
||||||
min_age: i64,
|
|
||||||
max_age: i64,
|
|
||||||
) -> Result<(i64, Option<Userid>), Error> {
|
|
||||||
|
|
||||||
use std::collections::VecDeque;
|
|
||||||
|
|
||||||
let mut parts: VecDeque<&str> = ticket.split(':').collect();
|
|
||||||
|
|
||||||
match parts.pop_front() {
|
|
||||||
Some(text) => if text != prefix { bail!("ticket with invalid prefix"); }
|
|
||||||
None => bail!("ticket without prefix"),
|
|
||||||
}
|
|
||||||
|
|
||||||
let sign_b64 = match parts.pop_back() {
|
|
||||||
Some(v) => v,
|
|
||||||
None => bail!("ticket without signature"),
|
|
||||||
};
|
|
||||||
|
|
||||||
match parts.pop_back() {
|
|
||||||
Some(text) => if text != "" { bail!("ticket with invalid signature separator"); }
|
|
||||||
None => bail!("ticket without signature separator"),
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut data = None;
|
|
||||||
|
|
||||||
let mut full = match parts.len() {
|
|
||||||
2 => {
|
|
||||||
data = Some(parts[0].to_owned());
|
|
||||||
format!("{}:{}:{}", prefix, parts[0], parts[1])
|
|
||||||
}
|
|
||||||
1 => format!("{}:{}", prefix, parts[0]),
|
|
||||||
_ => bail!("ticket with invalid number of components"),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(secret) = secret_data {
|
|
||||||
full.push(':');
|
|
||||||
full.push_str(secret);
|
|
||||||
}
|
|
||||||
|
|
||||||
let sign = base64::decode_config(sign_b64, base64::STANDARD_NO_PAD)?;
|
|
||||||
|
|
||||||
let mut verifier = Verifier::new(MessageDigest::sha256(), &keypair)?;
|
|
||||||
verifier.update(full.as_bytes())?;
|
|
||||||
|
|
||||||
if !verifier.verify(&sign)? {
|
|
||||||
bail!("ticket with invalid signature");
|
|
||||||
}
|
|
||||||
|
|
||||||
let timestamp = i64::from_str_radix(parts.pop_back().unwrap(), 16)?;
|
|
||||||
let now = epoch_now_u64()? as i64;
|
|
||||||
|
|
||||||
let age = now - timestamp;
|
|
||||||
if age < min_age {
|
|
||||||
bail!("invalid ticket - timestamp newer than expected.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if age > max_age {
|
|
||||||
bail!("invalid ticket - timestamp too old.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((age, data.map(|s| s.parse()).transpose()?))
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue