1use std::fs;
6use std::io::Read;
7use std::io::Write;
8use std::process::Command;
9use std::process::Stdio;
10
11use tempfile::Builder as TempBuilder;
12
13pub use cert::pem_to_cert_key_pair;
14pub use cert::Cert;
15pub use cert::Pkcs12;
16pub use cert::Pkcs12AndPassword;
17pub use cert::PrivateKey;
18use once_cell::sync::Lazy;
19
20mod cert;
21
22#[derive(Debug, PartialEq, Clone)]
23pub struct CertAndPrivateKey {
24 pub cert: Cert,
25 pub key: PrivateKey,
26}
27
28impl CertAndPrivateKey {
29 pub fn to_pem_incorrect(&self) -> String {
31 self.cert.to_pem() + &self.key.to_pem_incorrect()
32 }
33}
34
35pub struct ClientKeys {
37 pub ca: Cert,
38}
39
40pub struct ServerKeys {
42 pub cert_and_key_pkcs12: Pkcs12AndPassword,
44
45 pub cert_and_key: CertAndPrivateKey,
47}
48
49pub struct Keys {
51 pub client: ClientKeys,
53 pub server: ServerKeys,
55}
56
57fn gen_root_ca() -> CertAndPrivateKey {
58 let temp_dir = TempBuilder::new()
59 .prefix("rust-test-cert-gen-gen-root-ca")
60 .tempdir()
61 .unwrap();
62
63 let config = temp_dir.path().join("openssl.config");
64 let keyfile = temp_dir.path().join("root_ca.key");
65 let certfile = temp_dir.path().join("root_ca.crt");
66
67 fs::write(
68 &config,
69 b"\
70 [req]\n\
71 distinguished_name=dn\n\
72 [dn]\n\
73 CN=my.ca\n\
74 [ext]\n\
75 basicConstraints=CA:TRUE,pathlen:0\n\
76 subjectAltName = @alt_names\n\
77 extendedKeyUsage=serverAuth,clientAuth\n\
78 [alt_names]\n\
79 DNS.1 = my.ca\n\
80 ",
81 )
82 .unwrap();
83
84 let subj = "/C=US/ST=Denial/L=Sprintfield/O=Dis/CN=my.ca";
85 let gen_ca = Command::new("openssl")
87 .arg("req")
88 .arg("-nodes")
89 .arg("-x509")
90 .args(["-newkey", "rsa:2048"])
91 .arg("-config")
92 .arg(&config)
93 .args(["-extensions", "ext"])
94 .arg("-subj")
95 .arg(subj)
96 .arg("-keyout")
97 .arg(&keyfile)
98 .arg("-out")
99 .arg(&certfile)
100 .args(["-days", "1"])
101 .output()
104 .unwrap();
105 assert!(gen_ca.status.success());
106
107 let cert = fs::read_to_string(&certfile).unwrap();
108 let key = fs::read_to_string(&keyfile).unwrap();
109
110 assert_eq!(1, pem::parse_many(cert.as_bytes()).len());
111 assert_eq!(1, pem::parse_many(key.as_bytes()).len());
112
113 CertAndPrivateKey {
114 cert: Cert::from_pem(&cert),
115 key: PrivateKey::from_pem(&key),
116 }
117}
118
119fn gen_cert_for_domain(domain: &str, ca: &CertAndPrivateKey) -> CertAndPrivateKey {
120 assert!(!domain.is_empty());
121
122 let temp_dir = TempBuilder::new().prefix("pem-to-der").tempdir().unwrap();
123 let privkey_pem_path = temp_dir.path().join("privkey.pem");
124 let csr = temp_dir.path().join("csr.pem");
125 let ca_pem = temp_dir.path().join("ca.pem");
126 let ca_key_path = temp_dir.path().join("ca-key.pem");
127 let cert_path = temp_dir.path().join("cert.pem");
128 let conf_path = temp_dir.path().join("conf");
129 let conf2_path = temp_dir.path().join("conf2");
130
131 fs::write(&ca_pem, ca.cert.to_pem()).unwrap();
132 fs::write(&ca_key_path, ca.key.to_pem_incorrect()).unwrap();
133
134 assert!(Command::new("openssl")
135 .arg("genrsa")
136 .arg("-out")
137 .arg(&privkey_pem_path)
138 .arg("2048")
139 .output()
140 .unwrap()
141 .status
142 .success());
143
144 fs::write(
145 &conf_path,
146 format!(
147 "\
148 [req]\n\
149 req_extensions = v3_req\n\
150 distinguished_name = req_distinguished_name\n\
151 [v3_req]\n\
152 basicConstraints = CA:FALSE\n\
153 keyUsage = digitalSignature, keyEncipherment\n\
154 extendedKeyUsage = serverAuth\n\
155 subjectAltName = DNS.0:{}\n\
156 [req_distinguished_name]\n\
157 # empty\n\
158 ",
159 domain
160 ),
161 )
162 .unwrap();
163
164 assert!(Command::new("openssl")
166 .arg("req")
167 .arg("-new")
168 .arg("-key")
169 .arg(&privkey_pem_path)
170 .arg("-sha256")
171 .arg("-out")
172 .arg(&csr)
173 .args([
174 "-subj",
175 &format!("/C=US/ST=Utah/L=Provo/O=ACME Service/CN={}", domain)
176 ])
177 .arg("-config")
178 .arg(&conf_path)
179 .stderr(Stdio::inherit())
180 .output()
181 .unwrap()
182 .status
183 .success());
184
185 fs::write(
186 &conf2_path,
187 format!(
188 "\
189 subjectAltName = DNS.0:{}\n\
190 extendedKeyUsage = serverAuth\n\
191 ",
192 domain
193 ),
194 )
195 .unwrap();
196
197 assert!(Command::new("openssl")
199 .arg("x509")
200 .arg("-req")
201 .arg("-in")
202 .arg(&csr)
203 .arg("-CA")
204 .arg(&ca_pem)
205 .arg("-CAkey")
206 .arg(&ca_key_path)
207 .arg("-CAcreateserial")
208 .arg("-extfile")
209 .arg(&conf2_path)
210 .arg("-out")
211 .arg(&cert_path)
212 .args(["-days", "1"])
213 .arg("-sha256")
214 .output()
215 .unwrap()
216 .status
217 .success());
218
219 let key = fs::read_to_string(&privkey_pem_path).unwrap();
220 let cert = fs::read_to_string(&cert_path).unwrap();
221
222 assert_eq!(1, pem::parse_many(cert.as_bytes()).len());
224 assert_eq!(1, pem::parse_many(key.as_bytes()).len());
225
226 CertAndPrivateKey {
227 cert: Cert::from_pem(&cert),
228 key: PrivateKey::from_pem(&key),
229 }
230}
231
232pub fn gen_keys() -> Keys {
233 let root_ca_pem = gen_root_ca();
234
235 let server_cert_pem = gen_cert_for_domain("localhost", &root_ca_pem);
236
237 let server_cert_pkcs12 = pem_to_pkcs12_some_password(&server_cert_pem);
238
239 Keys {
240 client: ClientKeys {
241 ca: root_ca_pem.cert,
242 },
243 server: ServerKeys {
244 cert_and_key: server_cert_pem,
245 cert_and_key_pkcs12: server_cert_pkcs12,
246 },
247 }
248}
249
250pub fn keys() -> &'static Keys {
252 static KEYS: Lazy<Keys> = Lazy::new(|| gen_keys());
253 &KEYS
254}
255
256fn _pkcs12_to_pem(pkcs12: &Pkcs12, passin: &str) -> String {
257 let mut command = Command::new("openssl")
258 .arg("pkcs12")
259 .args(["-passin", &format!("pass:{}", passin)])
260 .arg("-nodes")
261 .stdin(Stdio::piped())
262 .stdout(Stdio::piped())
263 .spawn()
264 .unwrap();
265
266 command
267 .stdin
268 .as_mut()
269 .unwrap()
270 .write_all(&pkcs12.0)
271 .unwrap();
272
273 let mut pem = String::new();
274 command
275 .stdout
276 .as_mut()
277 .unwrap()
278 .read_to_string(&mut pem)
279 .unwrap();
280
281 assert!(command.wait().unwrap().success());
282
283 pem
284}
285
286fn pem_to_pkcs12(cert: &CertAndPrivateKey, pass: &str) -> Pkcs12 {
287 let temp_dir = TempBuilder::new()
288 .prefix("pem-to-pkcs12")
289 .tempdir()
290 .unwrap();
291
292 let certfile = temp_dir.path().join("cert.pem");
293 let keyfile = temp_dir.path().join("key.pem");
294
295 fs::write(&certfile, cert.cert.to_pem()).unwrap();
296 fs::write(&keyfile, cert.key.to_pem_incorrect()).unwrap();
297
298 let pkcs12out = Command::new("openssl")
299 .arg("pkcs12")
300 .arg("-export")
301 .arg("-descert")
302 .arg("-inkey")
303 .arg(&keyfile)
304 .arg("-in")
305 .arg(&certfile)
306 .args(["-password", &format!("pass:{}", pass)])
307 .output()
308 .unwrap();
309 assert!(pkcs12out.status.success());
310 Pkcs12(pkcs12out.stdout)
311}
312
313fn pem_to_pkcs12_some_password(cert: &CertAndPrivateKey) -> Pkcs12AndPassword {
314 let password = "serp".to_owned();
315 let pkcs12 = pem_to_pkcs12(cert, &password);
316 Pkcs12AndPassword { pkcs12, password }
317}
318
319#[cfg(test)]
320mod test {
321 use crate::gen_keys;
322 use std::fs;
323 use std::io::BufRead;
324 use std::io::BufReader;
325 use std::io::Write;
326 use std::process::Command;
327 use std::process::Stdio;
328 use std::sync::mpsc;
329 use std::thread;
330
331 use tempfile::Builder as TempBuilder;
332
333 #[test]
334 fn test() {
335 super::keys();
337 }
338
339 #[test]
340 fn verify() {
341 let temp_dir = TempBuilder::new().prefix("t").tempdir().unwrap();
342
343 let keys = gen_keys();
344
345 let ca_pem = temp_dir.path().join("ca.pem");
346 let server_pem = temp_dir.path().join("server.pem");
347
348 fs::write(&ca_pem, keys.client.ca.to_pem()).unwrap();
349 fs::write(&server_pem, &keys.server.cert_and_key.to_pem_incorrect()).unwrap();
350
351 let status = Command::new("openssl")
356 .arg("verify")
357 .arg("-CAfile")
358 .arg(&ca_pem)
359 .arg(&server_pem)
360 .stderr(Stdio::inherit())
361 .spawn()
362 .unwrap()
363 .wait()
364 .unwrap();
365 assert!(status.success())
366 }
367
368 #[test]
369 #[ignore] fn client_server() {
371 let temp_dir = TempBuilder::new()
372 .prefix("client_server")
373 .tempdir()
374 .unwrap();
375
376 let keys = gen_keys();
377
378 let client = temp_dir.path().join("client");
379 let server = temp_dir.path().join("server.pem");
380
381 fs::write(&client, keys.client.ca.get_der()).unwrap();
382 fs::write(&server, keys.server.cert_and_key.to_pem_incorrect()).unwrap();
383
384 let port = 1234;
385
386 let mut s_server = Command::new("openssl")
387 .arg("s_server")
388 .arg("-accept")
389 .arg(port.to_string())
390 .arg("-cert")
391 .arg(&server)
392 .stdin(Stdio::piped())
393 .stdout(Stdio::piped())
394 .spawn()
395 .unwrap();
396
397 let (signal_tx, signal_rx) = mpsc::channel();
398
399 let client = thread::spawn(move || {
400 let mut s_client = Command::new("openssl")
401 .arg("s_client")
402 .stdin(Stdio::piped())
403 .stdout(Stdio::piped())
404 .arg("-connect")
405 .arg(format!("localhost:{}", port))
406 .arg("-verify_return_error")
407 .spawn()
408 .unwrap();
409 s_client
410 .stdin
411 .as_mut()
412 .unwrap()
413 .write_all(b"ping\n")
414 .unwrap();
415 let _ = signal_rx.recv();
416 s_client.kill().unwrap();
417 });
418
419 let lines = BufReader::new(s_server.stdout.as_mut().unwrap()).lines();
420 for line in lines {
421 let line = line.unwrap();
422 println!("> {}", line);
423 if line == "ping" {
424 break;
425 }
426 }
427
428 s_server.kill().unwrap();
429 let _ = signal_tx.send(());
430 client.join().unwrap();
431 }
432}