forked from Proxmox/proxmox
acme-api: add function to extract certificate data from .pem data
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
This commit is contained in:
parent
fcaa4f6758
commit
7e4121d26e
@ -69,6 +69,7 @@ crossbeam-channel = "0.5"
|
||||
endian_trait = "0.6"
|
||||
env_logger = "0.10"
|
||||
flate2 = "1.0"
|
||||
foreign-types = "0.3"
|
||||
form_urlencoded = "1.1"
|
||||
futures = "0.3"
|
||||
handlebars = "3.0"
|
||||
|
@ -19,8 +19,12 @@ futures = { workspace = true, optional = true }
|
||||
http = { workspace = true, optional = true }
|
||||
log = { workspace = true, optional = true }
|
||||
nix = { workspace = true, optional = true }
|
||||
hex = { workspace = true, optional = true }
|
||||
lazy_static = { workspace = true, optional = true }
|
||||
|
||||
libc = { workspace = true, optional = true }
|
||||
openssl = { workspace = true, optional = true }
|
||||
foreign-types = { workspace = true, optional = true }
|
||||
|
||||
|
||||
proxmox-serde.workspace = true
|
||||
@ -30,6 +34,7 @@ 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-time = { 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 }
|
||||
@ -38,12 +43,14 @@ proxmox-product-config = { workspace = true, optional = true }
|
||||
default = []
|
||||
impl = [
|
||||
"dep:proxmox-uuid",
|
||||
"dep:proxmox-time",
|
||||
"dep:proxmox-config-digest",
|
||||
"proxmox-config-digest?/openssl",
|
||||
"dep:proxmox-product-config",
|
||||
"proxmox-acme/impl",
|
||||
"proxmox-acme/async-client",
|
||||
"dep:proxmox-section-config",
|
||||
"dep:hex",
|
||||
"dep:lazy_static",
|
||||
"dep:log",
|
||||
"dep:nix",
|
||||
@ -55,5 +62,7 @@ impl = [
|
||||
"dep:proxmox-rest-server",
|
||||
"dep:proxmox-router",
|
||||
"dep:base64",
|
||||
"dep:libc",
|
||||
"dep:openssl",
|
||||
"dep:foreign-types",
|
||||
]
|
||||
|
@ -58,12 +58,16 @@ Depends:
|
||||
${misc:Depends},
|
||||
librust-proxmox-acme-api-dev (= ${binary:Version}),
|
||||
librust-base64-0.13+default-dev,
|
||||
librust-foreign-types-0.3+default-dev,
|
||||
librust-futures-0.3+default-dev,
|
||||
librust-hex-0.4+default-dev,
|
||||
librust-http-0.2+default-dev,
|
||||
librust-hyper-0.14+default-dev (>= 0.14.5-~~),
|
||||
librust-lazy-static-1+default-dev (>= 1.4-~~),
|
||||
librust-libc-0.2+default-dev (>= 0.2.107-~~),
|
||||
librust-log-0.4+default-dev (>= 0.4.17-~~),
|
||||
librust-nix-0.26+default-dev (>= 0.26.1-~~),
|
||||
librust-openssl-0.10+default-dev,
|
||||
librust-proxmox-acme-0.5+api-types-dev (>= 0.5.2-~~),
|
||||
librust-proxmox-acme-0.5+async-client-dev (>= 0.5.2-~~),
|
||||
librust-proxmox-acme-0.5+impl-dev (>= 0.5.2-~~),
|
||||
@ -74,6 +78,8 @@ Depends:
|
||||
librust-proxmox-router-2+default-dev (>= 2.1.3-~~),
|
||||
librust-proxmox-section-config-2+default-dev,
|
||||
librust-proxmox-sys-0.5+default-dev (>= 0.5.5-~~),
|
||||
librust-proxmox-time-1+default-dev (>= 1.1.6-~~),
|
||||
librust-proxmox-uuid-1+default-dev (>= 1.0.1-~~),
|
||||
librust-tokio-1+default-dev (>= 1.6-~~),
|
||||
librust-tokio-1+fs-dev (>= 1.6-~~)
|
||||
Provides:
|
||||
|
@ -1,6 +1,9 @@
|
||||
use std::mem::MaybeUninit;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use foreign_types::ForeignTypeRef;
|
||||
|
||||
use anyhow::{bail, format_err, Error};
|
||||
use openssl::pkey::{PKey, Private};
|
||||
use openssl::rsa::Rsa;
|
||||
@ -11,6 +14,7 @@ use proxmox_rest_server::WorkerTask;
|
||||
use proxmox_sys::{task_log, task_warn};
|
||||
|
||||
use crate::types::{AcmeConfig, AcmeDomain};
|
||||
use crate::CertificateInfo;
|
||||
|
||||
pub async fn revoke_certificate(acme_config: &AcmeConfig, certificate: &[u8]) -> Result<(), Error> {
|
||||
let mut acme = super::account_config::load_account_config(&acme_config.account)
|
||||
@ -302,3 +306,92 @@ pub fn create_self_signed_cert(
|
||||
|
||||
Ok((privkey, x509.build()))
|
||||
}
|
||||
|
||||
impl CertificateInfo {
|
||||
pub fn from_pem(filename: &str, cert_pem: &[u8]) -> Result<Self, Error> {
|
||||
let x509 = openssl::x509::X509::from_pem(cert_pem)?;
|
||||
|
||||
let cert_pem = String::from_utf8(cert_pem.to_vec())
|
||||
.map_err(|_| format_err!("certificate in {:?} is not a valid PEM file", filename))?;
|
||||
|
||||
let pubkey = x509.public_key()?;
|
||||
|
||||
let subject = x509name_to_string(x509.subject_name())?;
|
||||
let issuer = x509name_to_string(x509.issuer_name())?;
|
||||
|
||||
let fingerprint = x509.digest(openssl::hash::MessageDigest::sha256())?;
|
||||
let fingerprint = hex::encode(fingerprint)
|
||||
.as_bytes()
|
||||
.chunks(2)
|
||||
.map(|v| std::str::from_utf8(v).unwrap())
|
||||
.collect::<Vec<&str>>()
|
||||
.join(":");
|
||||
|
||||
let public_key_type = openssl::nid::Nid::from_raw(pubkey.id().as_raw())
|
||||
.long_name()
|
||||
.unwrap_or("<unsupported key type>")
|
||||
.to_owned();
|
||||
|
||||
let san = x509
|
||||
.subject_alt_names()
|
||||
.map(|san| {
|
||||
san.into_iter()
|
||||
// FIXME: Support `.ipaddress()`?
|
||||
.filter_map(|name| name.dnsname().map(str::to_owned))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(CertificateInfo {
|
||||
filename: filename.to_string(),
|
||||
pem: Some(cert_pem),
|
||||
subject,
|
||||
issuer,
|
||||
fingerprint: Some(fingerprint),
|
||||
public_key_bits: Some(pubkey.bits()),
|
||||
notbefore: asn1_time_to_unix(x509.not_before()).ok(),
|
||||
notafter: asn1_time_to_unix(x509.not_after()).ok(),
|
||||
public_key_type,
|
||||
san,
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if the certificate is expired at or after a specific unix epoch.
|
||||
pub fn is_expired_after_epoch(&self, epoch: i64) -> Result<bool, Error> {
|
||||
if let Some(notafter) = self.notafter {
|
||||
Ok(notafter < epoch)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn x509name_to_string(name: &openssl::x509::X509NameRef) -> Result<String, Error> {
|
||||
let mut parts = Vec::new();
|
||||
for entry in name.entries() {
|
||||
parts.push(format!(
|
||||
"{} = {}",
|
||||
entry.object().nid().short_name()?,
|
||||
entry.data().as_utf8()?
|
||||
));
|
||||
}
|
||||
Ok(parts.join(", "))
|
||||
}
|
||||
|
||||
// C type:
|
||||
#[allow(non_camel_case_types)]
|
||||
type ASN1_TIME = <openssl::asn1::Asn1TimeRef as ForeignTypeRef>::CType;
|
||||
|
||||
extern "C" {
|
||||
fn ASN1_TIME_to_tm(s: *const ASN1_TIME, tm: *mut libc::tm) -> libc::c_int;
|
||||
}
|
||||
|
||||
fn asn1_time_to_unix(time: &openssl::asn1::Asn1TimeRef) -> Result<i64, Error> {
|
||||
let mut c_tm = MaybeUninit::<libc::tm>::uninit();
|
||||
let rc = unsafe { ASN1_TIME_to_tm(time.as_ptr(), c_tm.as_mut_ptr()) };
|
||||
if rc != 1 {
|
||||
bail!("failed to parse ASN1 time");
|
||||
}
|
||||
let mut c_tm = unsafe { c_tm.assume_init() };
|
||||
proxmox_time::timegm(&mut c_tm)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user