From: Dietmar Maurer Date: Tue, 29 Jan 2019 11:59:07 +0000 (+0100) Subject: tools/ticket.rs: add basic ticket support X-Git-Tag: v0.1.3~1661 X-Git-Url: https://git.proxmox.com/?p=proxmox-backup.git;a=commitdiff_plain;h=8d04280b2994cbe2130d7985e53ec41f900db950 tools/ticket.rs: add basic ticket support --- diff --git a/Cargo.toml b/Cargo.toml index 1a4d5599..06005e88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,4 +33,4 @@ siphasher = "0.3" endian_trait = "0.6" walkdir = "2" md5 = "0.6" - +base64 = "0.10" diff --git a/src/bin/proxmox-backup-api.rs b/src/bin/proxmox-backup-api.rs index 89b01176..e1a808ba 100644 --- a/src/bin/proxmox-backup-api.rs +++ b/src/bin/proxmox-backup-api.rs @@ -2,19 +2,48 @@ extern crate proxmox_backup; use std::sync::Arc; +use proxmox_backup::tools; use proxmox_backup::api::schema::*; use proxmox_backup::api::router::*; use proxmox_backup::api::config::*; use proxmox_backup::server::rest::*; use proxmox_backup::getopts; -//use failure::*; +use failure::*; use lazy_static::lazy_static; +use openssl::rsa::{Rsa}; +use std::path::PathBuf; use futures::future::Future; use hyper; +pub fn gen_auth_key() -> Result<(), Error> { + + let priv_path = PathBuf::from("/etc/proxmox-backup/authkey.key"); + + let mut public_path = priv_path.clone(); + public_path.set_extension("pub"); + + if priv_path.exists() && public_path.exists() { return Ok(()); } + + let rsa = Rsa::generate(4096).unwrap(); + + let priv_pem = rsa.private_key_to_pem()?; + + use nix::sys::stat::Mode; + + tools::file_set_contents( + &priv_path, &priv_pem, Some(Mode::from_bits_truncate(0o0600)))?; + + + let public_pem = rsa.public_key_to_pem()?; + + tools::file_set_contents(&public_path, &public_pem, None)?; + + Ok(()) +} + fn main() { if let Err(err) = syslog::init( @@ -25,6 +54,11 @@ fn main() { std::process::exit(-1); } + if let Err(err) = gen_auth_key() { + eprintln!("unable to generate auth key: {}", err); + std::process::exit(-1); + } + let command : Arc = StringSchema::new("Command.") .format(Arc::new(ApiStringFormat::Enum(vec![ "start".into(), diff --git a/src/tools.rs b/src/tools.rs index 81747e64..8b522cd3 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -23,6 +23,7 @@ pub mod timer; pub mod wrapped_reader_stream; #[macro_use] pub mod common_regex; +pub mod ticket; /// The `BufferedReader` trait provides a single function /// `buffered_read`. It returns a reference to an internal buffer. The diff --git a/src/tools/ticket.rs b/src/tools/ticket.rs new file mode 100644 index 00000000..fc34c0d0 --- /dev/null +++ b/src/tools/ticket.rs @@ -0,0 +1,123 @@ +//! Generate and verify Authentification tickets + +use crate::tools; + +use failure::*; +use std::path::PathBuf; +use base64; + +use openssl::rsa::{Rsa}; +use openssl::pkey::{PKey, Public, Private}; +use openssl::sign::{Signer, Verifier}; +use openssl::hash::MessageDigest; + +pub fn assemble_rsa_ticket( + keypair: &PKey, + prefix: &str, + data: Option<&str>, + secret_data: Option<&str>, +) -> Result { + + let epoch = std::time::SystemTime::now().duration_since( + std::time::SystemTime::UNIX_EPOCH)?.as_secs(); + + let timestamp = format!("{:08X}", epoch); + + let mut plain = prefix.to_owned(); + plain.push(':'); + + if let Some(data) = data { + plain.push_str(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( + keypair: &PKey, + prefix: &str, + ticket: &str, + secret_data: Option<&str>, + min_age: i64, + max_age: i64, +) -> Result<(i64, Option), 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 = std::time::SystemTime::now().duration_since( + std::time::SystemTime::UNIX_EPOCH)?.as_secs() 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."); + } + + + println!("TEST: {:?}", parts); + println!("TEST1: {:?}", full); + println!("TEST2: {} {}", timestamp, age); + + Ok((age, data)) +}