tools/ticket.rs: add basic ticket support
This commit is contained in:
		@ -33,4 +33,4 @@ siphasher = "0.3"
 | 
			
		||||
endian_trait = "0.6"
 | 
			
		||||
walkdir = "2"
 | 
			
		||||
md5 = "0.6"
 | 
			
		||||
 | 
			
		||||
base64 = "0.10"
 | 
			
		||||
 | 
			
		||||
@ -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<Schema> = StringSchema::new("Command.")
 | 
			
		||||
        .format(Arc::new(ApiStringFormat::Enum(vec![
 | 
			
		||||
            "start".into(),
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										123
									
								
								src/tools/ticket.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								src/tools/ticket.rs
									
									
									
									
									
										Normal file
									
								
							@ -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<Private>,
 | 
			
		||||
    prefix: &str,
 | 
			
		||||
    data: Option<&str>,
 | 
			
		||||
    secret_data: Option<&str>,
 | 
			
		||||
) -> Result<String, Error> {
 | 
			
		||||
 | 
			
		||||
    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<Public>,
 | 
			
		||||
    prefix: &str,
 | 
			
		||||
    ticket: &str,
 | 
			
		||||
    secret_data: Option<&str>,
 | 
			
		||||
    min_age: i64,
 | 
			
		||||
    max_age: i64,
 | 
			
		||||
) -> Result<(i64, Option<String>), 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))
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user