[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