]> git.proxmox.com Git - proxmox-backup.git/blob - src/tools/cert.rs
move more helpers to pbs-tools
[proxmox-backup.git] / src / tools / cert.rs
1 use std::path::PathBuf;
2 use std::mem::MaybeUninit;
3
4 use anyhow::{bail, format_err, Error};
5 use foreign_types::ForeignTypeRef;
6 use openssl::x509::{X509, GeneralName};
7 use openssl::stack::Stack;
8 use openssl::pkey::{Public, PKey};
9
10 use pbs_buildcfg::configdir;
11
12 // C type:
13 #[allow(non_camel_case_types)]
14 type ASN1_TIME = <openssl::asn1::Asn1TimeRef as ForeignTypeRef>::CType;
15
16 extern "C" {
17 fn ASN1_TIME_to_tm(s: *const ASN1_TIME, tm: *mut libc::tm) -> libc::c_int;
18 }
19
20 fn asn1_time_to_unix(time: &openssl::asn1::Asn1TimeRef) -> Result<i64, Error> {
21 let mut c_tm = MaybeUninit::<libc::tm>::uninit();
22 let rc = unsafe { ASN1_TIME_to_tm(time.as_ptr(), c_tm.as_mut_ptr()) };
23 if rc != 1 {
24 bail!("failed to parse ASN1 time");
25 }
26 let mut c_tm = unsafe { c_tm.assume_init() };
27 proxmox::tools::time::timegm(&mut c_tm)
28 }
29
30 pub struct CertInfo {
31 x509: X509,
32 }
33
34 fn x509name_to_string(name: &openssl::x509::X509NameRef) -> Result<String, Error> {
35 let mut parts = Vec::new();
36 for entry in name.entries() {
37 parts.push(format!("{} = {}", entry.object().nid().short_name()?, entry.data().as_utf8()?));
38 }
39 Ok(parts.join(", "))
40 }
41
42 impl CertInfo {
43 pub fn new() -> Result<Self, Error> {
44 Self::from_path(PathBuf::from(configdir!("/proxy.pem")))
45 }
46
47 pub fn from_path(path: PathBuf) -> Result<Self, Error> {
48 Self::from_pem(&proxmox::tools::fs::file_get_contents(&path)?)
49 .map_err(|err| format_err!("failed to load certificate from {:?} - {}", path, err))
50 }
51
52 pub fn from_pem(cert_pem: &[u8]) -> Result<Self, Error> {
53 let x509 = openssl::x509::X509::from_pem(&cert_pem)?;
54 Ok(Self{
55 x509
56 })
57 }
58
59 pub fn subject_alt_names(&self) -> Option<Stack<GeneralName>> {
60 self.x509.subject_alt_names()
61 }
62
63 pub fn subject_name(&self) -> Result<String, Error> {
64 Ok(x509name_to_string(self.x509.subject_name())?)
65 }
66
67 pub fn issuer_name(&self) -> Result<String, Error> {
68 Ok(x509name_to_string(self.x509.issuer_name())?)
69 }
70
71 pub fn fingerprint(&self) -> Result<String, Error> {
72 let fp = self.x509.digest(openssl::hash::MessageDigest::sha256())?;
73 let fp_string = proxmox::tools::digest_to_hex(&fp);
74 let fp_string = fp_string.as_bytes().chunks(2).map(|v| std::str::from_utf8(v).unwrap())
75 .collect::<Vec<&str>>().join(":");
76 Ok(fp_string)
77 }
78
79 pub fn public_key(&self) -> Result<PKey<Public>, Error> {
80 let pubkey = self.x509.public_key()?;
81 Ok(pubkey)
82 }
83
84 pub fn not_before(&self) -> &openssl::asn1::Asn1TimeRef {
85 self.x509.not_before()
86 }
87
88 pub fn not_after(&self) -> &openssl::asn1::Asn1TimeRef {
89 self.x509.not_after()
90 }
91
92 pub fn not_before_unix(&self) -> Result<i64, Error> {
93 asn1_time_to_unix(&self.not_before())
94 }
95
96 pub fn not_after_unix(&self) -> Result<i64, Error> {
97 asn1_time_to_unix(&self.not_after())
98 }
99
100 /// Check if the certificate is expired at or after a specific unix epoch.
101 pub fn is_expired_after_epoch(&self, epoch: i64) -> Result<bool, Error> {
102 Ok(self.not_after_unix()? < epoch)
103 }
104 }