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
|
//! 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:"
)
}
|