]> git.proxmox.com Git - proxmox.git/blob - proxmox-acme/src/util.rs
move to proxmox-acme
[proxmox.git] / proxmox-acme / 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::{self, 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(x509::extension::BasicConstraints::new().build()?)?;
59 ext.push(
60 x509::extension::KeyUsage::new()
61 .digital_signature()
62 .key_encipherment()
63 .build()?,
64 )?;
65 ext.push(
66 x509::extension::ExtendedKeyUsage::new()
67 .server_auth()
68 .client_auth()
69 .build()?,
70 )?;
71 let mut san = x509::extension::SubjectAlternativeName::new();
72 for dns in identifiers {
73 san.dns(dns.as_ref());
74 }
75 ext.push({ san }.build(&context)?)?;
76 csr.add_extensions(&ext)?;
77
78 csr.sign(&private_key, MessageDigest::sha256())?;
79
80 Ok(Self {
81 data: csr.build().to_der()?,
82 private_key_pem,
83 })
84 }
85 }