From fcaa4f6758ce8a5f68d02f2f095b82fd1fd1a371 Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Thu, 6 Jun 2024 12:38:03 +0200 Subject: [PATCH] acme-api: implement funtion to create self signed certificates Signed-off-by: Dietmar Maurer --- proxmox-acme-api/Cargo.toml | 5 ++ proxmox-acme-api/src/certificate_helpers.rs | 90 +++++++++++++++++++++ proxmox-acme-api/src/lib.rs | 2 +- 3 files changed, 96 insertions(+), 1 deletion(-) diff --git a/proxmox-acme-api/Cargo.toml b/proxmox-acme-api/Cargo.toml index 283d95e6..ad55f644 100644 --- a/proxmox-acme-api/Cargo.toml +++ b/proxmox-acme-api/Cargo.toml @@ -20,6 +20,8 @@ http = { workspace = true, optional = true } log = { workspace = true, optional = true } nix = { workspace = true, optional = true } lazy_static = { workspace = true, optional = true } +openssl = { workspace = true, optional = true } + proxmox-serde.workspace = true proxmox-section-config = { workspace = true, optional = true } @@ -27,6 +29,7 @@ proxmox-rest-server = { workspace = true, optional = true } proxmox-router = { workspace = true, optional = true } proxmox-sys = { workspace = true, optional = true } proxmox-schema = { workspace = true, features = ["api-macro", "api-types"] } +proxmox-uuid = { workspace = true, optional = true } proxmox-acme = { workspace = true, features = ["api-types"] } proxmox-config-digest = { workspace = true, optional = true } proxmox-product-config = { workspace = true, optional = true } @@ -34,6 +37,7 @@ proxmox-product-config = { workspace = true, optional = true } [features] default = [] impl = [ + "dep:proxmox-uuid", "dep:proxmox-config-digest", "proxmox-config-digest?/openssl", "dep:proxmox-product-config", @@ -51,4 +55,5 @@ impl = [ "dep:proxmox-rest-server", "dep:proxmox-router", "dep:base64", + "dep:openssl", ] diff --git a/proxmox-acme-api/src/certificate_helpers.rs b/proxmox-acme-api/src/certificate_helpers.rs index 0db10c40..ae9463a1 100644 --- a/proxmox-acme-api/src/certificate_helpers.rs +++ b/proxmox-acme-api/src/certificate_helpers.rs @@ -2,6 +2,9 @@ use std::sync::Arc; use std::time::Duration; use anyhow::{bail, format_err, Error}; +use openssl::pkey::{PKey, Private}; +use openssl::rsa::Rsa; +use openssl::x509::{X509Builder, X509}; use proxmox_acme::async_client::AcmeClient; use proxmox_rest_server::WorkerTask; @@ -212,3 +215,90 @@ async fn request_validation( } } } + +pub fn create_self_signed_cert( + product_name: &str, + nodename: &str, + domain: Option<&str>, +) -> Result<(PKey, X509), Error> { + let rsa = Rsa::generate(4096).unwrap(); + + let mut x509 = X509Builder::new()?; + + x509.set_version(2)?; + + let today = openssl::asn1::Asn1Time::days_from_now(0)?; + x509.set_not_before(&today)?; + let expire = openssl::asn1::Asn1Time::days_from_now(365 * 1000)?; + x509.set_not_after(&expire)?; + + let mut fqdn = nodename.to_owned(); + + if let Some(domain) = domain { + fqdn.push('.'); + fqdn.push_str(domain); + } + + // we try to generate an unique 'subject' to avoid browser problems + //(reused serial numbers, ..) + let uuid = proxmox_uuid::Uuid::generate(); + + let mut subject_name = openssl::x509::X509NameBuilder::new()?; + subject_name.append_entry_by_text("O", product_name)?; + subject_name.append_entry_by_text("OU", &format!("{:X}", uuid))?; + subject_name.append_entry_by_text("CN", &fqdn)?; + let subject_name = subject_name.build(); + + x509.set_subject_name(&subject_name)?; + x509.set_issuer_name(&subject_name)?; + + let bc = openssl::x509::extension::BasicConstraints::new(); // CA = false + let bc = bc.build()?; + x509.append_extension(bc)?; + + let usage = openssl::x509::extension::ExtendedKeyUsage::new() + .server_auth() + .build()?; + x509.append_extension(usage)?; + + let context = x509.x509v3_context(None, None); + + let mut alt_names = openssl::x509::extension::SubjectAlternativeName::new(); + + alt_names.ip("127.0.0.1"); + alt_names.ip("::1"); + + alt_names.dns("localhost"); + + if nodename != "localhost" { + alt_names.dns(nodename); + } + if nodename != fqdn { + alt_names.dns(&fqdn); + } + + let alt_names = alt_names.build(&context)?; + + x509.append_extension(alt_names)?; + + let pub_pem = rsa.public_key_to_pem()?; + let pubkey = PKey::public_key_from_pem(&pub_pem)?; + + x509.set_pubkey(&pubkey)?; + + let context = x509.x509v3_context(None, None); + let ext = openssl::x509::extension::SubjectKeyIdentifier::new().build(&context)?; + x509.append_extension(ext)?; + + let context = x509.x509v3_context(None, None); + let ext = openssl::x509::extension::AuthorityKeyIdentifier::new() + .keyid(true) + .build(&context)?; + x509.append_extension(ext)?; + + let privkey = PKey::from_rsa(rsa)?; + + x509.sign(&privkey, openssl::hash::MessageDigest::sha256())?; + + Ok((privkey, x509.build())) +} diff --git a/proxmox-acme-api/src/lib.rs b/proxmox-acme-api/src/lib.rs index 9990717c..09bde85c 100644 --- a/proxmox-acme-api/src/lib.rs +++ b/proxmox-acme-api/src/lib.rs @@ -43,4 +43,4 @@ pub(crate) mod acme_plugin; #[cfg(feature = "impl")] mod certificate_helpers; #[cfg(feature = "impl")] -pub use certificate_helpers::{order_certificate, revoke_certificate}; +pub use certificate_helpers::{create_self_signed_cert, order_certificate, revoke_certificate};