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