]> git.proxmox.com Git - proxmox-acme-rs.git/blob - src/util.rs
update d/control
[proxmox-acme-rs.git] / src / util.rs
1 //! Certificate utility methods for convenience (such as CSR generation).
2
3 use std::collections::HashMap;
4
5 use openssl::hash::MessageDigest;
6 use openssl::nid::Nid;
7 use openssl::pkey::PKey;
8 use openssl::rsa::Rsa;
9 use openssl::x509::{X509Extension, X509Name, X509Req};
10
11 use crate::Error;
12
13 /// A certificate signing request.
14 pub struct Csr {
15 /// DER encoded certificate request.
16 pub data: Vec<u8>,
17
18 /// PEM formatted PKCS#8 private key.
19 pub private_key_pem: Vec<u8>,
20 }
21
22 impl Csr {
23 /// Generate a CSR in DER format with a PEM formatted PKCS8 private key.
24 ///
25 /// The `identifiers` should be a list of domains. The `attributes` should have standard names
26 /// recognized by openssl.
27 pub fn generate(
28 identifiers: &[impl AsRef<str>],
29 attributes: &HashMap<String, &str>,
30 ) -> Result<Self, Error> {
31 if identifiers.is_empty() {
32 return Err(Error::Csr("cannot generate empty CSR".to_string()));
33 }
34
35 let private_key = Rsa::generate(4096)
36 .and_then(PKey::from_rsa)
37 .map_err(|err| Error::Ssl("failed to generate RSA key: {}", err))?;
38
39 let private_key_pem = private_key
40 .private_key_to_pem_pkcs8()
41 .map_err(|err| Error::Ssl("failed to format private key as PEM pkcs8: {}", err))?;
42
43 let mut name = X509Name::builder()?;
44 if !attributes.contains_key("CN") {
45 name.append_entry_by_nid(Nid::COMMONNAME, identifiers[0].as_ref())?;
46 }
47 for (key, value) in attributes {
48 name.append_entry_by_text(key, value)?;
49 }
50 let name = name.build();
51
52 let mut csr = X509Req::builder()?;
53 csr.set_subject_name(&name)?;
54 csr.set_pubkey(&private_key)?;
55
56 let context = csr.x509v3_context(None);
57 let mut ext = openssl::stack::Stack::new()?;
58 ext.push(X509Extension::new_nid(
59 None,
60 None,
61 Nid::BASIC_CONSTRAINTS,
62 "CA:FALSE",
63 )?)?;
64 ext.push(X509Extension::new_nid(
65 None,
66 None,
67 Nid::KEY_USAGE,
68 "digitalSignature,keyEncipherment",
69 )?)?;
70 ext.push(X509Extension::new_nid(
71 None,
72 None,
73 Nid::EXT_KEY_USAGE,
74 "serverAuth,clientAuth",
75 )?)?;
76 ext.push(X509Extension::new_nid(
77 None,
78 Some(&context),
79 Nid::SUBJECT_ALT_NAME,
80 &identifiers
81 .iter()
82 .try_fold(String::new(), |mut acc, dns| {
83 if !acc.is_empty() {
84 acc.push(',');
85 }
86 use std::fmt::Write;
87 write!(acc, "DNS:{}", dns.as_ref())?;
88 Ok::<_, std::fmt::Error>(acc)
89 })
90 .map_err(|err| Error::Csr(err.to_string()))?,
91 )?)?;
92 csr.add_extensions(&ext)?;
93
94 csr.sign(&private_key, MessageDigest::sha256())?;
95
96 Ok(Self {
97 data: csr.build().to_der()?,
98 private_key_pem,
99 })
100 }
101 }