[pbs-devel] [RFC proxmox 3/3] new proxmox-cert-management crate

Dominik Csapak d.csapak at proxmox.com
Wed Oct 18 12:39:10 CEST 2023


refactored from proxmox-backup, but uses the new ServerConfig global
settings for the users and directories.

Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
---
 Cargo.toml                                   |   2 +
 proxmox-cert-management/Cargo.toml           |  23 +++
 proxmox-cert-management/debian/changelog     |   5 +
 proxmox-cert-management/debian/control       |  53 ++++++
 proxmox-cert-management/debian/copyright     |  18 ++
 proxmox-cert-management/debian/debcargo.toml |   7 +
 proxmox-cert-management/src/lib.rs           | 182 +++++++++++++++++++
 7 files changed, 290 insertions(+)
 create mode 100644 proxmox-cert-management/Cargo.toml
 create mode 100644 proxmox-cert-management/debian/changelog
 create mode 100644 proxmox-cert-management/debian/control
 create mode 100644 proxmox-cert-management/debian/copyright
 create mode 100644 proxmox-cert-management/debian/debcargo.toml
 create mode 100644 proxmox-cert-management/src/lib.rs

diff --git a/Cargo.toml b/Cargo.toml
index 4b4b787..0a4ad06 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,6 +5,7 @@ members = [
     "proxmox-async",
     "proxmox-auth-api",
     "proxmox-borrow",
+    "proxmox-cert-management",
     "proxmox-client",
     "proxmox-compression",
     "proxmox-http",
@@ -91,6 +92,7 @@ webauthn-rs = "0.3"
 zstd = { version = "0.12", features = [ "bindgen" ] }
 
 # workspace dependencies
+proxmox-auth-api = { version = "0.3", path = "proxmox-auth-api" }
 proxmox-api-macro = { version = "1.0.6", path = "proxmox-api-macro" }
 proxmox-async = { version = "0.4.1", path = "proxmox-async" }
 proxmox-compression = { version = "0.2.0", path = "proxmox-compression" }
diff --git a/proxmox-cert-management/Cargo.toml b/proxmox-cert-management/Cargo.toml
new file mode 100644
index 0000000..816f4b6
--- /dev/null
+++ b/proxmox-cert-management/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "proxmox-cert-management"
+version = "0.1.0"
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+description = "Certificate management and utilities"
+
+exclude.workspace = true
+
+[dependencies]
+anyhow.workspace = true
+base64.workspace = true
+lazy_static.workspace = true
+nix.workspace = true
+openssl.workspace = true
+
+proxmox-auth-api = { workspace = true, features = ["api-types"] }
+proxmox-lang.workspace = true
+proxmox-sys.workspace = true
+proxmox-server-config.workspace = true
+proxmox-time.workspace = true
diff --git a/proxmox-cert-management/debian/changelog b/proxmox-cert-management/debian/changelog
new file mode 100644
index 0000000..ed5d4d6
--- /dev/null
+++ b/proxmox-cert-management/debian/changelog
@@ -0,0 +1,5 @@
+rust-proxmox-cert-management (0.1.0-1) stable; urgency=medium
+
+  * initial version
+
+ -- Proxmox Support Team <support at proxmox.com>  Tue, 17 Oct 2023 13:56:35 +0200
diff --git a/proxmox-cert-management/debian/control b/proxmox-cert-management/debian/control
new file mode 100644
index 0000000..593ea6c
--- /dev/null
+++ b/proxmox-cert-management/debian/control
@@ -0,0 +1,53 @@
+Source: rust-proxmox-cert-management
+Section: rust
+Priority: optional
+Build-Depends: debhelper (>= 12),
+ dh-cargo (>= 25),
+ cargo:native <!nocheck>,
+ rustc:native <!nocheck>,
+ libstd-rust-dev <!nocheck>,
+ librust-anyhow-1+default-dev <!nocheck>,
+ librust-base64-0.13+default-dev <!nocheck>,
+ librust-lazy-static-1+default-dev (>= 1.4-~~) <!nocheck>,
+ librust-nix-0.26+default-dev (>= 0.26.1-~~) <!nocheck>,
+ librust-openssl-0.10+default-dev <!nocheck>,
+ librust-proxmox-auth-api-0.3+api-types-dev <!nocheck>,
+ librust-proxmox-auth-api-0.3+default-dev <!nocheck>,
+ librust-proxmox-lang-1+default-dev (>= 1.1-~~) <!nocheck>,
+ librust-proxmox-server-config-0.1+default-dev <!nocheck>,
+ librust-proxmox-sys-0.5+default-dev <!nocheck>,
+ librust-proxmox-time-1+default-dev (>= 1.1.4-~~) <!nocheck>
+Maintainer: Proxmox Support Team <support at proxmox.com>
+Standards-Version: 4.6.1
+Vcs-Git: git://git.proxmox.com/git/proxmox.git
+Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
+X-Cargo-Crate: proxmox-cert-management
+Rules-Requires-Root: no
+
+Package: librust-proxmox-cert-management-dev
+Architecture: any
+Multi-Arch: same
+Depends:
+ ${misc:Depends},
+ librust-anyhow-1+default-dev,
+ librust-base64-0.13+default-dev,
+ librust-lazy-static-1+default-dev (>= 1.4-~~),
+ librust-nix-0.26+default-dev (>= 0.26.1-~~),
+ librust-openssl-0.10+default-dev,
+ librust-proxmox-auth-api-0.3+api-types-dev,
+ librust-proxmox-auth-api-0.3+default-dev,
+ librust-proxmox-lang-1+default-dev (>= 1.1-~~),
+ librust-proxmox-server-config-0.1+default-dev,
+ librust-proxmox-sys-0.5+default-dev,
+ librust-proxmox-time-1+default-dev (>= 1.1.4-~~)
+Provides:
+ librust-proxmox-cert-management+default-dev (= ${binary:Version}),
+ librust-proxmox-cert-management-0-dev (= ${binary:Version}),
+ librust-proxmox-cert-management-0+default-dev (= ${binary:Version}),
+ librust-proxmox-cert-management-0.1-dev (= ${binary:Version}),
+ librust-proxmox-cert-management-0.1+default-dev (= ${binary:Version}),
+ librust-proxmox-cert-management-0.1.0-dev (= ${binary:Version}),
+ librust-proxmox-cert-management-0.1.0+default-dev (= ${binary:Version})
+Description: Certificate management and utilities - Rust source code
+ This package contains the source for the Rust proxmox-cert-management crate,
+ packaged by debcargo for use with cargo and dh-cargo.
diff --git a/proxmox-cert-management/debian/copyright b/proxmox-cert-management/debian/copyright
new file mode 100644
index 0000000..0d9eab3
--- /dev/null
+++ b/proxmox-cert-management/debian/copyright
@@ -0,0 +1,18 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+
+Files:
+ *
+Copyright: 2019 - 2023 Proxmox Server Solutions GmbH <support at proxmox.com>
+License: AGPL-3.0-or-later
+ This program is free software: you can redistribute it and/or modify it under
+ the terms of the GNU Affero General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option) any
+ later version.
+ .
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+ details.
+ .
+ You should have received a copy of the GNU Affero General Public License along
+ with this program. If not, see <https://www.gnu.org/licenses/>.
diff --git a/proxmox-cert-management/debian/debcargo.toml b/proxmox-cert-management/debian/debcargo.toml
new file mode 100644
index 0000000..b7864cd
--- /dev/null
+++ b/proxmox-cert-management/debian/debcargo.toml
@@ -0,0 +1,7 @@
+overlay = "."
+crate_src_path = ".."
+maintainer = "Proxmox Support Team <support at proxmox.com>"
+
+[source]
+vcs_git = "git://git.proxmox.com/git/proxmox.git"
+vcs_browser = "https://git.proxmox.com/?p=proxmox.git"
diff --git a/proxmox-cert-management/src/lib.rs b/proxmox-cert-management/src/lib.rs
new file mode 100644
index 0000000..dd327f7
--- /dev/null
+++ b/proxmox-cert-management/src/lib.rs
@@ -0,0 +1,182 @@
+use anyhow::{bail, format_err, Error};
+use lazy_static::lazy_static;
+use openssl::pkey::{PKey, Private, Public};
+use openssl::rsa::Rsa;
+use openssl::sha;
+
+use proxmox_auth_api::types::Userid;
+use proxmox_lang::try_block;
+use proxmox_server_config::get_server_config;
+use proxmox_sys::fs::{file_get_contents, replace_file, CreateOptions};
+
+fn compute_csrf_secret_digest(timestamp: i64, secret: &[u8], userid: &Userid) -> String {
+    let mut hasher = sha::Sha256::new();
+    let data = format!("{:08X}:{}:", timestamp, userid);
+    hasher.update(data.as_bytes());
+    hasher.update(secret);
+
+    base64::encode_config(hasher.finish(), base64::STANDARD_NO_PAD)
+}
+
+pub fn assemble_csrf_prevention_token(secret: &[u8], userid: &Userid) -> String {
+    let epoch = proxmox_time::epoch_i64();
+
+    let digest = compute_csrf_secret_digest(epoch, secret, userid);
+
+    format!("{:08X}:{}", epoch, digest)
+}
+
+pub fn verify_csrf_prevention_token(
+    secret: &[u8],
+    userid: &Userid,
+    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, userid);
+
+        if digest != sig {
+            bail!("invalid signature.");
+        }
+
+        let now = proxmox_time::epoch_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> {
+    let server_config = get_server_config()?;
+    let path = server_config.config_dir().join("csrf.key");
+
+    if path.exists() {
+        return Ok(());
+    }
+
+    let rsa = Rsa::generate(2048).unwrap();
+
+    let pem = rsa.private_key_to_pem()?;
+
+    use nix::sys::stat::Mode;
+
+    replace_file(
+        &path,
+        &pem,
+        CreateOptions::new()
+            .perm(Mode::from_bits_truncate(0o0640))
+            .owner(server_config.privileged_user().uid)
+            .group(server_config.user().gid),
+        true,
+    )?;
+
+    Ok(())
+}
+
+pub fn generate_auth_key() -> Result<(), Error> {
+    let server_config = get_server_config()?;
+    let priv_path = server_config.config_dir().join("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;
+
+    replace_file(
+        &priv_path,
+        &priv_pem,
+        CreateOptions::new().perm(Mode::from_bits_truncate(0o0600)),
+        true,
+    )?;
+
+    let public_pem = rsa.public_key_to_pem()?;
+
+    replace_file(
+        &public_path,
+        &public_pem,
+        CreateOptions::new()
+            .perm(Mode::from_bits_truncate(0o0640))
+            .owner(server_config.privileged_user().uid)
+            .group(server_config.user().gid),
+        true,
+    )?;
+
+    Ok(())
+}
+
+pub fn csrf_secret() -> &'static [u8] {
+    lazy_static! {
+        static ref SECRET: Vec<u8> = {
+            let dir = get_server_config().unwrap().config_dir().join("csrf.key");
+            file_get_contents(dir).unwrap()
+        };
+    }
+
+    &SECRET
+}
+
+fn load_public_auth_key() -> Result<PKey<Public>, Error> {
+    let pem_path = get_server_config()?.config_dir().join("authkey.pub");
+    let pem = file_get_contents(pem_path)?;
+    let rsa = Rsa::public_key_from_pem(&pem)?;
+    let key = PKey::from_rsa(rsa)?;
+
+    Ok(key)
+}
+
+pub fn public_auth_key() -> &'static PKey<Public> {
+    lazy_static! {
+        static ref KEY: PKey<Public> = load_public_auth_key().unwrap();
+    }
+
+    &KEY
+}
+
+fn load_private_auth_key() -> Result<PKey<Private>, Error> {
+    let pem_path = get_server_config()?.config_dir().join("authkey.key");
+    let pem = file_get_contents(pem_path)?;
+    let rsa = Rsa::private_key_from_pem(&pem)?;
+    let key = PKey::from_rsa(rsa)?;
+
+    Ok(key)
+}
+
+pub fn private_auth_key() -> &'static PKey<Private> {
+    lazy_static! {
+        static ref KEY: PKey<Private> = load_private_auth_key().unwrap();
+    }
+
+    &KEY
+}
-- 
2.30.2






More information about the pbs-devel mailing list