]>
Commit | Line | Data |
---|---|---|
ec01eead | 1 | use std::path::PathBuf; |
0796b642 | 2 | use std::mem::MaybeUninit; |
ec01eead | 3 | |
0796b642 WB |
4 | use anyhow::{bail, format_err, Error}; |
5 | use foreign_types::ForeignTypeRef; | |
ec01eead DC |
6 | use openssl::x509::{X509, GeneralName}; |
7 | use openssl::stack::Stack; | |
8 | use openssl::pkey::{Public, PKey}; | |
9 | ||
af06decd | 10 | use pbs_buildcfg::configdir; |
ec01eead | 11 | |
0796b642 WB |
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 | ||
ec01eead DC |
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> { | |
0796b642 WB |
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> { | |
ec01eead DC |
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 | } | |
0796b642 WB |
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 | } | |
134ed9e1 WB |
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 | } | |
ec01eead | 104 | } |