1use std::fs;
2use std::io::{self, prelude::*};
3use std::os::unix::fs::PermissionsExt;
4use std::path::PathBuf;
5
6use anyhow::{bail, Context, Result};
7use sha2::{Digest, Sha256};
8use tracing::{info, warn};
9
10pub fn load_known_certs(config_dir: &PathBuf) -> Result<Vec<rustls_pki_types::CertificateDer<'static>>> {
11 let mut certs = vec![];
12 for path in fs::read_dir(init_known_certs_dir(config_dir)?)? {
13 let path = path?;
14 let filetype = path.file_type()?;
15 if !filetype.is_file() {
16 continue;
17 }
18 certs.push(load_cert(path.path())?);
19 }
20 Ok(certs)
21}
22
23fn splash(label: &str, fingerprint: &str) {
24 println!(
25 r"
26 \\ //
27 \V/
28 U
29 | nikau {}
30 | {}
31",
32 label, fingerprint
33 );
34}
35
36pub fn load_keypair<'a>(
37 splash_label: &str,
38 config_dir: &PathBuf,
39) -> Result<(rustls_pki_types::CertificateDer<'a>, rustls_pki_types::PrivateKeyDer<'a>)> {
40 let file_path = config_dir.join("private.pem");
41 if file_path.is_file() {
42 read_existing_keypair(splash_label, &file_path)
43 } else {
44 write_new_keypair(splash_label, &file_path)
45 }
46}
47
48fn read_existing_keypair<'a>(
49 splash_label: &str,
50 file_path: &PathBuf,
51) -> Result<(rustls_pki_types::CertificateDer<'a>, rustls_pki_types::PrivateKeyDer<'a>)> {
52 let mut reader =
53 io::BufReader::new(fs::File::open(&file_path).with_context(|| {
54 format!("Failed to open keypair file: {}", file_path.display())
55 })?);
56 let mut cert: Option<rustls_pki_types::CertificateDer> = None;
57 let mut key: Option<rustls_pki_types::PrivateKeyDer> = None;
58 for item in rustls_pemfile::read_all(&mut reader) {
59 match item.with_context(|| format!("Failed to read keypair file: {}", file_path.display()))? {
60 rustls_pemfile::Item::X509Certificate(filecert) => {
61 cert = Some(rustls_pki_types::CertificateDer::from(filecert));
62 }
63 rustls_pemfile::Item::Pkcs8Key(filekey) => {
64 key = Some(rustls_pki_types::PrivateKeyDer::from(filekey));
65 }
66 _ => {
67 warn!("Unexpected item in {}", file_path.display());
69 }
70 }
71 }
72 if let (Some(cert), Some(key)) = (cert, key) {
73 splash(splash_label, &fingerprint(&cert));
74 info!("Using keypair from {}", file_path.display());
75 Ok((cert, key))
76 } else {
77 bail!("Incomplete cert/key content in {}", file_path.display());
78 }
79}
80
81fn write_new_keypair<'a>(
82 splash_label: &str,
83 file_path: &PathBuf,
84) -> Result<(rustls_pki_types::CertificateDer<'a>, rustls_pki_types::PrivateKeyDer<'a>)> {
85 let pair = rcgen::generate_simple_self_signed(vec![])
86 .context("Failed to generate self-signed cert")?;
87
88 info!("Writing a new keypair to {}", file_path.display());
89 let mut outfile = fs::File::create(&file_path).with_context(|| {
90 format!(
91 "Failed to open keypair file for writing: {}",
92 file_path.display()
93 )
94 })?;
95 ensure_permissions(&file_path, 0o600).with_context(|| {
96 format!(
97 "Failed to set permissions on keypair file: {}",
98 file_path.display()
99 )
100 })?;
101 outfile
102 .write_all(pair.cert.pem().as_bytes())
103 .with_context(|| format!("Failed to write public key to file: {}", file_path.display()))?;
104 outfile
105 .write_all(pair.signing_key.serialize_pem().as_bytes())
106 .with_context(|| format!("Failed to write private key to file: {}", file_path.display()))?;
107
108 let rustls_cert = rustls_pki_types::CertificateDer::from(pair.cert.der().to_vec());
109 splash(splash_label, &fingerprint(&rustls_cert));
110 Ok((
111 rustls_cert,
112 rustls_pki_types::PrivateKeyDer::from(rustls_pki_types::PrivatePkcs8KeyDer::from(pair.signing_key.serialize_der())),
113 ))
114}
115
116pub fn fingerprint(cert: &rustls_pki_types::CertificateDer) -> String {
120 format!("{:x}", Sha256::digest(cert))
121}
122
123pub fn write_approved_cert(
124 cert: &rustls_pki_types::CertificateDer,
125 fingerprint: &str,
126 config_dir: &PathBuf,
127) -> Result<()> {
128 let file_path = init_known_certs_dir(config_dir)
129 .context("Failed to init known_certs dir")?
130 .join(format!("{}.pem", fingerprint));
131 let content = pem::encode_config(
132 &pem::Pem::new("CERTIFICATE", cert.as_ref()),
133 pem::EncodeConfig::new().set_line_ending(pem::LineEnding::LF),
134 );
135 let mut outfile = fs::File::create(&file_path).with_context(|| {
136 format!(
137 "Failed to open known cert file for writing: {}",
138 file_path.display()
139 )
140 })?;
141 ensure_permissions(&file_path, 0o644).with_context(|| {
142 format!(
143 "Failed to set permissions on known cert file: {}",
144 file_path.display()
145 )
146 })?;
147 outfile.write_all(content.as_bytes()).with_context(|| {
148 format!(
149 "Failed to write known cert to file: {}",
150 file_path.display()
151 )
152 })?;
153 info!("Wrote approved cert to {}", file_path.display());
154 Ok(())
155}
156
157fn load_cert<'a>(file_path: PathBuf) -> Result<rustls_pki_types::CertificateDer<'a>> {
158 let mut reader = io::BufReader::new(
159 fs::File::open(&file_path)
160 .with_context(|| format!("Failed to open cert file: {}", file_path.display()))?,
161 );
162 if let Some(rustls_pemfile::Item::X509Certificate(filecert)) =
163 rustls_pemfile::read_one(&mut reader)
164 .with_context(|| format!("Failed to read cert file: {}", file_path.display()))?
165 {
166 Ok(rustls_pki_types::CertificateDer::from(filecert))
167 } else {
168 bail!("Public certificate not found in {}", file_path.display());
169 }
170}
171
172fn init_known_certs_dir(config_dir: &PathBuf) -> Result<PathBuf> {
173 let dir_path = config_dir.join("known_certs");
174 fs::create_dir_all(&dir_path)
175 .with_context(|| format!("Failed to ensure certs dir exists: {}", dir_path.display()))?;
176 ensure_permissions(&dir_path, 0o755).with_context(|| {
177 format!(
178 "Failed to set permissions on certs dir: {}",
179 dir_path.display()
180 )
181 })?;
182 Ok(dir_path)
183}
184
185fn ensure_permissions(path: &PathBuf, perms: u32) -> Result<()> {
186 let mut permissions = fs::metadata(path)
187 .with_context(|| format!("Failed to read file metadata: {}", path.display()))?
188 .permissions();
189 if permissions.mode() != perms {
190 permissions.set_mode(perms);
191 }
192 Ok(())
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198 use tempfile;
199
200 #[test]
201 fn can_write_read_keys() {
202 let dir = tempfile::tempdir().unwrap();
203 let (cert1, privkey1) = load_keypair("foo", &dir.path().to_path_buf()).expect("couldn't load");
205 let (cert2, privkey2) = load_keypair("foo", &dir.path().to_path_buf()).expect("couldn't load");
207 assert!(fingerprint(&cert1) == fingerprint(&cert2));
209 assert!(cert1 == cert2);
210 assert!(privkey1 == privkey2);
211 }
212}