summaryrefslogtreecommitdiffstats
path: root/src/server/setup.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-10-28 22:55:51 +0100
committersalmonik <salmonik@tuta.io>2026-10-28 22:55:51 +0100
commit87440d5a23f0fd894d949295a7c1ff85043550fc (patch)
tree53b2bada6872b6e565bb79982213616a50419b1c /src/server/setup.rs
parentv0.3.1: clean up dependencies, stale tests and document verification (diff)
downloadzagan-bet-87440d5a23f0fd894d949295a7c1ff85043550fc.tar.gz
zagan-bet-87440d5a23f0fd894d949295a7c1ff85043550fc.zip
v0.4.0: add first-time TLS setup and server help
Diffstat (limited to '')
-rw-r--r--src/server/setup.rs140
1 files changed, 140 insertions, 0 deletions
diff --git a/src/server/setup.rs b/src/server/setup.rs
new file mode 100644
index 0000000..96982c5
--- /dev/null
+++ b/src/server/setup.rs
@@ -0,0 +1,140 @@
1//! First-time TLS identity generation for the server.
2
3use std::path::{Path, PathBuf};
4use std::process::Command;
5
6#[derive(Debug)]
7pub struct SetupReport {
8 pub identity: PathBuf,
9 pub key: PathBuf,
10 pub cert: PathBuf,
11}
12
13/// Generate a self-signed RSA identity in `dir`.
14///
15/// Produces `identity.p12` (empty password, matching `native_tls`), `key.pem`,
16/// and `cert.pem`. Refuses to overwrite an existing identity.
17pub fn init_identity(dir: &Path, common_name: &str) -> Result<SetupReport, String> {
18 let common_name = if common_name.trim().is_empty() {
19 "localhost"
20 } else {
21 common_name.trim()
22 };
23
24 let identity = dir.join("identity.p12");
25 let key = dir.join("key.pem");
26 let cert = dir.join("cert.pem");
27
28 let existing: Vec<String> = [&identity, &key, &cert]
29 .into_iter()
30 .filter(|path| path.exists())
31 .map(|path| path.display().to_string())
32 .collect();
33 if !existing.is_empty() {
34 return Err(format!(
35 "Refusing to overwrite existing TLS files: {}\n\
36 Remove them first if you really want a new identity.",
37 existing.join(", ")
38 ));
39 }
40
41 std::fs::create_dir_all(dir)
42 .map_err(|error| format!("Could not create {}: {error}", dir.display()))?;
43
44 let manual = manual_instructions(common_name);
45 let subject = format!("/CN={common_name}");
46
47 run_openssl(
48 dir,
49 &[
50 "req",
51 "-x509",
52 "-newkey",
53 "rsa:2048",
54 "-keyout",
55 "key.pem",
56 "-out",
57 "cert.pem",
58 "-days",
59 "3650",
60 "-nodes",
61 "-subj",
62 &subject,
63 ],
64 "generate the certificate",
65 &manual,
66 )?;
67
68 run_openssl(
69 dir,
70 &[
71 "pkcs12",
72 "-export",
73 "-out",
74 "identity.p12",
75 "-inkey",
76 "key.pem",
77 "-in",
78 "cert.pem",
79 "-passout",
80 "pass:",
81 "-keypbe",
82 "PBE-SHA1-3DES",
83 "-certpbe",
84 "PBE-SHA1-3DES",
85 "-macalg",
86 "sha1",
87 ],
88 "build identity.p12",
89 &manual,
90 )?;
91
92 #[cfg(unix)]
93 {
94 use std::os::unix::fs::PermissionsExt;
95
96 for path in [&key, &identity] {
97 let metadata = std::fs::metadata(path)
98 .map_err(|error| format!("Could not inspect {}: {error}", path.display()))?;
99 let mut permissions = metadata.permissions();
100 permissions.set_mode(0o600);
101 std::fs::set_permissions(path, permissions).map_err(|error| {
102 format!("Could not protect {}: {error}", path.display())
103 })?;
104 }
105 }
106
107 Ok(SetupReport {
108 identity,
109 key,
110 cert,
111 })
112}
113
114fn run_openssl(dir: &Path, args: &[&str], action: &str, manual: &str) -> Result<(), String> {
115 let output = Command::new("openssl")
116 .current_dir(dir)
117 .args(args)
118 .output()
119 .map_err(|error| format!("Could not run openssl to {action}: {error}\n\n{manual}"))?;
120
121 if output.status.success() {
122 return Ok(());
123 }
124
125 let stderr = String::from_utf8_lossy(&output.stderr);
126 Err(format!(
127 "openssl failed to {action}:\n{}\n\n{manual}",
128 stderr.trim()
129 ))
130}
131
132fn manual_instructions(common_name: &str) -> String {
133 format!(
134 "Install openssl, or run these commands manually:\n\
135 \x20 openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem \\\n\
136 \x20 -days 3650 -nodes -subj \"/CN={common_name}\"\n\
137 \x20 openssl pkcs12 -export -out identity.p12 -inkey key.pem -in cert.pem \\\n\
138 \x20 -passout pass:"
139 )
140}