]>
Commit | Line | Data |
---|---|---|
357c0614 WB |
1 | //! Certificate utility methods for convenience (such as CSR generation). |
2 | ||
a947050e WB |
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; | |
7e6aa273 | 9 | use openssl::x509::{self, X509Name, X509Req}; |
a947050e WB |
10 | |
11 | use crate::Error; | |
12 | ||
357c0614 | 13 | /// A certificate signing request. |
a947050e WB |
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() { | |
95381262 | 32 | return Err(Error::Csr("cannot generate empty CSR".to_string())); |
a947050e WB |
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()?; | |
7e6aa273 WB |
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)?)?; | |
a947050e WB |
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 | } |