1use std::net::{SocketAddr, TcpListener};
2use std::path::PathBuf;
3use std::{fs, process};
4
5use socket2::{Domain, Socket, Type};
6use tempfile::TempDir;
7
8#[derive(Clone, Debug)]
9pub struct TlsFilePaths {
10 pub redis_crt: PathBuf,
11 pub redis_key: PathBuf,
12 pub ca_crt: PathBuf,
13}
14
15pub fn build_keys_and_certs_for_tls(tempdir: &TempDir) -> TlsFilePaths {
16 build_keys_and_certs_for_tls_ext(tempdir, true)
17}
18
19pub fn build_keys_and_certs_for_tls_ext(tempdir: &TempDir, with_ip_alts: bool) -> TlsFilePaths {
20 let ca_crt = tempdir.path().join("ca.crt");
23 let ca_key = tempdir.path().join("ca.key");
24 let ca_serial = tempdir.path().join("ca.txt");
25 let redis_crt = tempdir.path().join("redis.crt");
26 let redis_key = tempdir.path().join("redis.key");
27 let ext_file = tempdir.path().join("openssl.cnf");
28
29 fn make_key<S: AsRef<std::ffi::OsStr>>(name: S, size: usize) {
30 process::Command::new("openssl")
31 .arg("genrsa")
32 .arg("-out")
33 .arg(name)
34 .arg(format!("{size}"))
35 .stdout(process::Stdio::piped())
36 .stderr(process::Stdio::piped())
37 .spawn()
38 .expect("failed to spawn openssl")
39 .wait()
40 .expect("failed to create key");
41 }
42
43 make_key(&ca_key, 4096);
45
46 make_key(&redis_key, 2048);
48
49 let status = process::Command::new("openssl")
51 .arg("req")
52 .arg("-x509")
53 .arg("-new")
54 .arg("-nodes")
55 .arg("-sha256")
56 .arg("-key")
57 .arg(&ca_key)
58 .arg("-days")
59 .arg("3650")
60 .arg("-subj")
61 .arg("/O=Redis Test/CN=Certificate Authority")
62 .arg("-out")
63 .arg(&ca_crt)
64 .stdout(process::Stdio::piped())
65 .stderr(process::Stdio::piped())
66 .spawn()
67 .expect("failed to spawn openssl")
68 .wait()
69 .expect("failed to create CA cert");
70 assert!(
71 status.success(),
72 "`openssl req` failed to create CA cert: {status}"
73 );
74
75 let ext = if with_ip_alts {
77 "\
78 keyUsage = digitalSignature, keyEncipherment\n\
79 subjectAltName = @alt_names\n\
80 [alt_names]\n\
81 IP.1 = 127.0.0.1\n\
82 "
83 } else {
84 "\
85 [req]\n\
86 distinguished_name = req_distinguished_name\n\
87 x509_extensions = v3_req\n\
88 prompt = no\n\
89 \n\
90 [req_distinguished_name]\n\
91 CN = localhost.example.com\n\
92 \n\
93 [v3_req]\n\
94 basicConstraints = CA:FALSE\n\
95 keyUsage = nonRepudiation, digitalSignature, keyEncipherment\n\
96 subjectAltName = @alt_names\n\
97 \n\
98 [alt_names]\n\
99 DNS.1 = localhost.example.com\n\
100 "
101 };
102 fs::write(&ext_file, ext).expect("failed to create x509v3 extensions file");
103
104 let mut key_cmd = process::Command::new("openssl")
106 .arg("req")
107 .arg("-new")
108 .arg("-sha256")
109 .arg("-subj")
110 .arg("/O=Redis Test/CN=Generic-cert")
111 .arg("-key")
112 .arg(&redis_key)
113 .stdout(process::Stdio::piped())
114 .stderr(process::Stdio::piped())
115 .spawn()
116 .expect("failed to spawn openssl");
117
118 let mut command2 = process::Command::new("openssl");
120 command2
121 .arg("x509")
122 .arg("-req")
123 .arg("-sha256")
124 .arg("-CA")
125 .arg(&ca_crt)
126 .arg("-CAkey")
127 .arg(&ca_key)
128 .arg("-CAserial")
129 .arg(&ca_serial)
130 .arg("-CAcreateserial")
131 .arg("-days")
132 .arg("365")
133 .arg("-extfile")
134 .arg(&ext_file);
135 if !with_ip_alts {
136 command2.arg("-extensions").arg("v3_req");
137 }
138 let status2 = command2
139 .arg("-out")
140 .arg(&redis_crt)
141 .stdin(key_cmd.stdout.take().expect("should have stdout"))
142 .spawn()
143 .expect("failed to spawn openssl")
144 .wait()
145 .expect("failed to create redis cert");
146
147 let status = key_cmd.wait().expect("failed to create redis key");
148 assert!(
149 status.success(),
150 "`openssl req` failed to create request for Redis cert: {status}"
151 );
152 assert!(
153 status2.success(),
154 "`openssl x509` failed to create Redis cert: {status2}"
155 );
156
157 TlsFilePaths {
158 redis_crt,
159 redis_key,
160 ca_crt,
161 }
162}
163
164pub fn get_listener_on_free_port() -> TcpListener {
165 let addr = &"127.0.0.1:0".parse::<SocketAddr>().unwrap().into();
166 let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap();
167 socket.set_reuse_address(true).unwrap();
168 socket.bind(addr).unwrap();
169 socket.listen(1).unwrap();
170 TcpListener::from(socket)
171}
172
173pub fn get_random_available_port() -> u16 {
179 for _ in 0..10000 {
180 let listener = get_listener_on_free_port();
181 let port = listener.local_addr().unwrap().port();
182 if port < 55535 {
183 return port;
184 }
185 }
186 panic!("Couldn't get a valid port");
187}