acme-api: implement funtion to create self signed certificates

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
This commit is contained in:
Dietmar Maurer 2024-06-06 12:38:03 +02:00
parent f7a22604ae
commit fcaa4f6758
3 changed files with 96 additions and 1 deletions

View File

@ -20,6 +20,8 @@ http = { workspace = true, optional = true }
log = { workspace = true, optional = true } log = { workspace = true, optional = true }
nix = { workspace = true, optional = true } nix = { workspace = true, optional = true }
lazy_static = { workspace = true, optional = true } lazy_static = { workspace = true, optional = true }
openssl = { workspace = true, optional = true }
proxmox-serde.workspace = true proxmox-serde.workspace = true
proxmox-section-config = { workspace = true, optional = 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-router = { workspace = true, optional = true }
proxmox-sys = { workspace = true, optional = true } proxmox-sys = { workspace = true, optional = true }
proxmox-schema = { workspace = true, features = ["api-macro", "api-types"] } proxmox-schema = { workspace = true, features = ["api-macro", "api-types"] }
proxmox-uuid = { workspace = true, optional = true }
proxmox-acme = { workspace = true, features = ["api-types"] } proxmox-acme = { workspace = true, features = ["api-types"] }
proxmox-config-digest = { workspace = true, optional = true } proxmox-config-digest = { workspace = true, optional = true }
proxmox-product-config = { workspace = true, optional = true } proxmox-product-config = { workspace = true, optional = true }
@ -34,6 +37,7 @@ proxmox-product-config = { workspace = true, optional = true }
[features] [features]
default = [] default = []
impl = [ impl = [
"dep:proxmox-uuid",
"dep:proxmox-config-digest", "dep:proxmox-config-digest",
"proxmox-config-digest?/openssl", "proxmox-config-digest?/openssl",
"dep:proxmox-product-config", "dep:proxmox-product-config",
@ -51,4 +55,5 @@ impl = [
"dep:proxmox-rest-server", "dep:proxmox-rest-server",
"dep:proxmox-router", "dep:proxmox-router",
"dep:base64", "dep:base64",
"dep:openssl",
] ]

View File

@ -2,6 +2,9 @@ use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use anyhow::{bail, format_err, Error}; 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_acme::async_client::AcmeClient;
use proxmox_rest_server::WorkerTask; 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<Private>, 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()))
}

View File

@ -43,4 +43,4 @@ pub(crate) mod acme_plugin;
#[cfg(feature = "impl")] #[cfg(feature = "impl")]
mod certificate_helpers; mod certificate_helpers;
#[cfg(feature = "impl")] #[cfg(feature = "impl")]
pub use certificate_helpers::{order_certificate, revoke_certificate}; pub use certificate_helpers::{create_self_signed_cert, order_certificate, revoke_certificate};