]> git.proxmox.com Git - proxmox-backup.git/commitdiff
tools/ticket.rs: add basic ticket support
authorDietmar Maurer <dietmar@proxmox.com>
Tue, 29 Jan 2019 11:59:07 +0000 (12:59 +0100)
committerDietmar Maurer <dietmar@proxmox.com>
Tue, 29 Jan 2019 11:59:07 +0000 (12:59 +0100)
Cargo.toml
src/bin/proxmox-backup-api.rs
src/tools.rs
src/tools/ticket.rs [new file with mode: 0644]

index 1a4d559963e5f071ef434efde5ff1850fc7dea43..06005e88dc12e5282e501429432496e8e2809b57 100644 (file)
@@ -33,4 +33,4 @@ siphasher = "0.3"
 endian_trait = "0.6"
 walkdir = "2"
 md5 = "0.6"
-
+base64 = "0.10"
index 89b01176fbc9b47fb2b2b9211f1980c531663a6b..e1a808bae29b0a3a5be880516c29cd66a7f39750 100644 (file)
@@ -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(),
index 81747e64244a24271a3417821de0025e23f73e36..8b522cd3feb00d518f77b901b0f202cea6adbba3 100644 (file)
@@ -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 (file)
index 0000000..fc34c0d
--- /dev/null
@@ -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(&timestamp);
+
+    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))
+}