1use openssl::ec::{EcGroup, EcKey};
5use openssl::error::ErrorStack;
6use openssl::nid::Nid;
7use openssl::pkey::{PKeyRef, Private};
8use openssl::rsa::Rsa;
9use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod, SslSessionCacheMode, SslVerifyMode};
10use openssl::x509::{
11 extension::{
12 AuthorityKeyIdentifier, BasicConstraints, ExtendedKeyUsage, KeyUsage,
13 SubjectAlternativeName, SubjectKeyIdentifier,
14 },
15 X509NameBuilder, X509ReqBuilder, X509,
16};
17use openssl::{asn1, bn, hash, pkey};
18use sketching::*;
19
20use crate::config::TlsConfiguration;
21
22use std::fs;
23use std::io::{ErrorKind, Read, Write};
24use std::path::Path;
25
26const CA_VALID_DAYS: u32 = 30;
27const CERT_VALID_DAYS: u32 = 5;
28
29const RSA_MIN_KEY_SIZE_BITS: u64 = 2048;
45const EC_MIN_KEY_SIZE_BITS: u64 = 224;
46
47fn get_signing_func() -> hash::MessageDigest {
49 hash::MessageDigest::sha256()
50}
51
52pub fn check_privkey_minimums(privkey: &PKeyRef<Private>) -> Result<(), String> {
54 if let Ok(key) = privkey.rsa() {
55 if key.size() < (RSA_MIN_KEY_SIZE_BITS / 8) as u32 {
56 Err(format!(
57 "TLS RSA key is less than {} bits!",
58 RSA_MIN_KEY_SIZE_BITS
59 ))
60 } else {
61 debug!(
62 "The RSA private key size is: {} bits, that's OK!",
63 key.size() * 8
64 );
65 Ok(())
66 }
67 } else if let Ok(key) = privkey.ec_key() {
68 #[allow(clippy::panic)]
70 let key_bits: u64 = key.private_key().num_bits().try_into().unwrap_or_else(|_| {
71 panic!(
72 "Failed to convert EC bitlength {} to u64",
73 key.private_key().num_bits()
74 )
75 });
76
77 if key_bits < EC_MIN_KEY_SIZE_BITS {
78 Err(format!(
79 "TLS EC key is less than {} bits! Got: {}",
80 EC_MIN_KEY_SIZE_BITS, key_bits
81 ))
82 } else {
83 #[cfg(any(test, debug_assertions))]
84 println!("The EC private key size is: {} bits, that's OK!", key_bits);
85 debug!("The EC private key size is: {} bits, that's OK!", key_bits);
86 Ok(())
87 }
88 } else {
89 error!("TLS key is not RSA or EC, cannot check minimums!");
90 Ok(())
91 }
92}
93
94pub fn setup_tls(
97 tls_config: &Option<TlsConfiguration>,
98) -> Result<Option<SslAcceptor>, std::io::Error> {
99 let Some(tls_param) = tls_config.as_ref() else {
100 return Ok(None);
101 };
102
103 let mut tls_builder = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls())?;
104
105 tls_builder
106 .set_certificate_chain_file(tls_param.chain.clone())
107 .map_err(|err| {
108 std::io::Error::new(
109 ErrorKind::Other,
110 format!("Failed to create TLS listener: {:?}", err),
111 )
112 })?;
113
114 tls_builder
115 .set_private_key_file(tls_param.key.clone(), SslFiletype::PEM)
116 .map_err(|err| {
117 std::io::Error::new(
118 ErrorKind::Other,
119 format!("Failed to create TLS listener: {:?}", err),
120 )
121 })?;
122
123 tls_builder.check_private_key().map_err(|err| {
124 std::io::Error::new(
125 ErrorKind::Other,
126 format!("Failed to create TLS listener: {:?}", err),
127 )
128 })?;
129
130 if let Some(client_ca) = tls_param.client_ca.as_ref() {
132 info!("Loading client certificates from {}", client_ca.display());
133
134 let verify = SslVerifyMode::PEER;
135 tls_builder.set_verify(verify);
138
139 tls_builder.set_session_cache_mode(SslSessionCacheMode::OFF);
151
152 let read_dir = fs::read_dir(client_ca).map_err(|err| {
153 std::io::Error::new(
154 ErrorKind::Other,
155 format!(
156 "Failed to create TLS listener while loading client ca from {}: {:?}",
157 client_ca.display(),
158 err
159 ),
160 )
161 })?;
162
163 for cert_dir_ent in read_dir.filter_map(|item| item.ok()).filter(|item| {
164 item.file_name()
165 .to_str()
166 .map(|fname| fname.ends_with(".0"))
169 .unwrap_or_default()
170 }) {
171 let mut cert_pem = String::new();
172 fs::File::open(cert_dir_ent.path())
173 .and_then(|mut file| file.read_to_string(&mut cert_pem))
174 .map_err(|err| {
175 std::io::Error::new(
176 ErrorKind::Other,
177 format!("Failed to create TLS listener: {:?}", err),
178 )
179 })?;
180
181 let cert = X509::from_pem(cert_pem.as_bytes()).map_err(|err| {
182 std::io::Error::new(
183 ErrorKind::Other,
184 format!("Failed to create TLS listener: {:?}", err),
185 )
186 })?;
187
188 let cert_store = tls_builder.cert_store_mut();
189 cert_store.add_cert(cert.clone()).map_err(|err| {
190 std::io::Error::new(
191 ErrorKind::Other,
192 format!(
193 "Failed to load cert store while creating TLS listener: {:?}",
194 err
195 ),
196 )
197 })?;
198 tls_builder.add_client_ca(&cert).map_err(|err| {
201 std::io::Error::new(
202 ErrorKind::Other,
203 format!("Failed to create TLS listener: {:?}", err),
204 )
205 })?;
206 }
207
208 if enabled!(tracing::Level::TRACE) {
213 tls_builder.set_verify_callback(verify, |status, x509store| {
214 if let Some(current_cert) = x509store.current_cert() {
215 let cert_text_bytes = current_cert.to_text().unwrap_or_default();
216 let cert_text = String::from_utf8_lossy(cert_text_bytes.as_slice());
217 tracing::warn!(client_cert = %cert_text);
218 };
219
220 if let Some(chain) = x509store.chain() {
221 for cert in chain.iter() {
222 let cert_text_bytes = cert.to_text().unwrap_or_default();
223 let cert_text = String::from_utf8_lossy(cert_text_bytes.as_slice());
224 tracing::warn!(chain_cert = %cert_text);
225 }
226 }
227
228 status
229 });
230 }
231
232 }
234
235 let tls_acceptor = tls_builder.build();
236
237 let privkey = tls_acceptor.context().private_key().ok_or_else(|| {
239 std::io::Error::new(
240 ErrorKind::Other,
241 "Failed to access tls_acceptor private key".to_string(),
242 )
243 })?;
244
245 check_privkey_minimums(privkey).map_err(|err| {
246 std::io::Error::new(
247 ErrorKind::Other,
248 format!("Private key minimums were not met: {:?}", err),
249 )
250 })?;
251
252 Ok(Some(tls_acceptor))
253}
254
255fn get_ec_group() -> Result<EcGroup, ErrorStack> {
256 EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)
257}
258
259#[derive(Debug)]
260pub(crate) struct CaHandle {
261 key: pkey::PKey<pkey::Private>,
262 cert: X509,
263}
264
265pub(crate) fn write_ca(
266 key_ar: impl AsRef<Path>,
267 cert_ar: impl AsRef<Path>,
268 handle: &CaHandle,
269) -> Result<(), ()> {
270 let key_path: &Path = key_ar.as_ref();
271 let cert_path: &Path = cert_ar.as_ref();
272
273 let key_pem = handle.key.private_key_to_pem_pkcs8().map_err(|e| {
274 error!(err = ?e, "Failed to convert key to PEM");
275 })?;
276
277 let cert_pem = handle.cert.to_pem().map_err(|e| {
278 error!(err = ?e, "Failed to convert cert to PEM");
279 })?;
280
281 fs::File::create(key_path)
282 .and_then(|mut file| file.write_all(&key_pem))
283 .map_err(|e| {
284 error!(err = ?e, "Failed to create {:?}", key_path);
285 })?;
286
287 fs::File::create(cert_path)
288 .and_then(|mut file| file.write_all(&cert_pem))
289 .map_err(|e| {
290 error!(err = ?e, "Failed to create {:?}", cert_path);
291 })
292}
293
294#[derive(Debug)]
295pub enum KeyType {
296 #[allow(dead_code)]
297 Rsa,
298 Ec,
299}
300impl Default for KeyType {
301 fn default() -> Self {
302 Self::Ec
303 }
304}
305
306#[derive(Debug)]
307pub struct CAConfig {
308 pub key_type: KeyType,
309 pub key_bits: u64,
310 pub skip_enforce_minimums: bool,
311}
312
313impl Default for CAConfig {
314 fn default() -> Self {
315 #[allow(clippy::expect_used)]
316 Self::new(KeyType::Ec, 256, false)
317 .expect("Somehow the defaults failed to pass validation while building a CA Config?")
318 }
319}
320
321impl CAConfig {
322 fn new(key_type: KeyType, key_bits: u64, skip_enforce_minimums: bool) -> Result<Self, String> {
323 let res = Self {
324 key_type,
325 key_bits,
326 skip_enforce_minimums,
327 };
328 if !skip_enforce_minimums {
329 res.enforce_minimums()?;
330 };
331 Ok(res)
332 }
333
334 fn enforce_minimums(&self) -> Result<(), String> {
336 match self.key_type {
337 KeyType::Rsa => {
338 trace!(
339 "Generating CA Config for RSA Key with {} bits",
340 self.key_bits
341 );
342 if self.key_bits < RSA_MIN_KEY_SIZE_BITS {
343 return Err(format!(
344 "RSA key size must be at least {} bits",
345 RSA_MIN_KEY_SIZE_BITS
346 ));
347 }
348 }
349 KeyType::Ec => {
350 trace!("Generating CA Config for EcKey with {} bits", self.key_bits);
351 if self.key_bits < EC_MIN_KEY_SIZE_BITS {
352 return Err(format!(
353 "EC key size must be at least {} bits",
354 EC_MIN_KEY_SIZE_BITS
355 ));
356 }
357 }
358 };
359 Ok(())
360 }
361}
362
363pub(crate) fn gen_private_key(
364 key_type: &KeyType,
365 key_bits: Option<u64>,
366) -> Result<pkey::PKey<pkey::Private>, ErrorStack> {
367 match key_type {
368 KeyType::Rsa => {
369 let key_bits = key_bits.unwrap_or(RSA_MIN_KEY_SIZE_BITS);
370 let rsa = Rsa::generate(key_bits as u32)?;
371 pkey::PKey::from_rsa(rsa)
372 }
373 KeyType::Ec => {
374 let ecgroup = get_ec_group()?;
376 let eckey = EcKey::generate(&ecgroup)?;
377 pkey::PKey::from_ec_key(eckey)
378 }
379 }
380}
381
382pub(crate) fn build_ca(ca_config: Option<CAConfig>) -> Result<CaHandle, ErrorStack> {
384 let ca_config = ca_config.unwrap_or_default();
385
386 let ca_key = gen_private_key(&ca_config.key_type, Some(ca_config.key_bits))?;
387
388 if !ca_config.skip_enforce_minimums {
389 check_privkey_minimums(&ca_key).map_err(|err| {
390 admin_error!("failed to build_ca due to privkey minimums {}", err);
391 #[cfg(any(test, debug_assertions))]
392 println!("failed to build_ca due to privkey minimums: {}", err);
393 ErrorStack::get() })?;
395 }
396 let mut x509_name = X509NameBuilder::new()?;
397
398 x509_name.append_entry_by_text("C", "AU")?;
399 x509_name.append_entry_by_text("ST", "QLD")?;
400 x509_name.append_entry_by_text("O", "Kanidm")?;
401 x509_name.append_entry_by_text("CN", "Kanidm Generated CA")?;
402 x509_name.append_entry_by_text("OU", "Development and Evaluation - NOT FOR PRODUCTION")?;
403 let x509_name = x509_name.build();
404
405 let mut cert_builder = X509::builder()?;
406 cert_builder.set_version(2)?;
408
409 let serial_number = bn::BigNum::from_u32(1).and_then(|serial| serial.to_asn1_integer())?;
410
411 cert_builder.set_serial_number(&serial_number)?;
412 cert_builder.set_subject_name(&x509_name)?;
413 cert_builder.set_issuer_name(&x509_name)?;
414
415 let not_before = asn1::Asn1Time::days_from_now(0)?;
416 cert_builder.set_not_before(¬_before)?;
417 let not_after = asn1::Asn1Time::days_from_now(CA_VALID_DAYS)?;
418 cert_builder.set_not_after(¬_after)?;
419
420 cert_builder.append_extension(BasicConstraints::new().critical().ca().pathlen(0).build()?)?;
421 cert_builder.append_extension(
422 KeyUsage::new()
423 .critical()
424 .key_cert_sign()
425 .crl_sign()
426 .build()?,
427 )?;
428
429 let subject_key_identifier =
430 SubjectKeyIdentifier::new().build(&cert_builder.x509v3_context(None, None))?;
431 cert_builder.append_extension(subject_key_identifier)?;
432
433 cert_builder.set_pubkey(&ca_key)?;
434
435 cert_builder.sign(&ca_key, get_signing_func())?;
436 let ca_cert = cert_builder.build();
437
438 Ok(CaHandle {
439 key: ca_key,
440 cert: ca_cert,
441 })
442}
443
444pub(crate) fn load_ca(
445 ca_key_ar: impl AsRef<Path>,
446 ca_cert_ar: impl AsRef<Path>,
447) -> Result<CaHandle, ()> {
448 let ca_key_path: &Path = ca_key_ar.as_ref();
449 let ca_cert_path: &Path = ca_cert_ar.as_ref();
450
451 let mut ca_key_pem = vec![];
452 fs::File::open(ca_key_path)
453 .and_then(|mut file| file.read_to_end(&mut ca_key_pem))
454 .map_err(|e| {
455 error!(err = ?e, "Failed to read {:?}", ca_key_path);
456 })?;
457
458 let mut ca_cert_pem = vec![];
459 fs::File::open(ca_cert_path)
460 .and_then(|mut file| file.read_to_end(&mut ca_cert_pem))
461 .map_err(|e| {
462 error!(err = ?e, "Failed to read {:?}", ca_cert_path);
463 })?;
464
465 let ca_key = pkey::PKey::private_key_from_pem(&ca_key_pem).map_err(|e| {
466 error!(err = ?e, "Failed to convert PEM to key");
467 })?;
468
469 check_privkey_minimums(&ca_key).map_err(|err| {
470 #[cfg(any(test, debug_assertions))]
471 println!("{:?}", err);
472 admin_error!("{}", err);
473 })?;
474
475 let ca_cert = X509::from_pem(&ca_cert_pem).map_err(|e| {
476 error!(err = ?e, "Failed to convert PEM to cert");
477 })?;
478
479 Ok(CaHandle {
480 key: ca_key,
481 cert: ca_cert,
482 })
483}
484
485pub(crate) struct CertHandle {
486 key: pkey::PKey<pkey::Private>,
487 cert: X509,
488 chain: Vec<X509>,
489}
490
491pub(crate) fn write_cert(
492 key_ar: impl AsRef<Path>,
493 chain_ar: impl AsRef<Path>,
494 cert_ar: impl AsRef<Path>,
495 handle: &CertHandle,
496) -> Result<(), ()> {
497 let key_path: &Path = key_ar.as_ref();
498 let chain_path: &Path = chain_ar.as_ref();
499 let cert_path: &Path = cert_ar.as_ref();
500
501 let key_pem = handle.key.private_key_to_pem_pkcs8().map_err(|e| {
502 error!(err = ?e, "Failed to convert key to PEM");
503 })?;
504
505 let cert_pem = handle.cert.to_pem().map_err(|e| {
506 error!(err = ?e, "Failed to convert cert to PEM");
507 })?;
508
509 let mut chain_pem = cert_pem.clone();
510
511 for ca_cert in &handle.chain {
513 match ca_cert.to_pem() {
514 Ok(c) => {
515 chain_pem.extend_from_slice(&c);
516 }
517 Err(e) => {
518 error!(err = ?e, "Failed to convert cert to PEM");
519 return Err(());
520 }
521 }
522 }
523
524 fs::File::create(key_path)
525 .and_then(|mut file| file.write_all(&key_pem))
526 .map_err(|e| {
527 error!(err = ?e, "Failed to create {:?}", key_path);
528 })?;
529
530 fs::File::create(chain_path)
531 .and_then(|mut file| file.write_all(&chain_pem))
532 .map_err(|e| {
533 error!(err = ?e, "Failed to create {:?}", chain_path);
534 })?;
535
536 fs::File::create(cert_path)
537 .and_then(|mut file| file.write_all(&cert_pem))
538 .map_err(|e| {
539 error!(err = ?e, "Failed to create {:?}", cert_path);
540 })
541}
542
543pub(crate) fn build_cert(
544 domain_name: &str,
545 ca_handle: &CaHandle,
546 key_type: Option<KeyType>,
547 key_bits: Option<u64>,
548) -> Result<CertHandle, ErrorStack> {
549 let key_type = key_type.unwrap_or_default();
550 let int_key = gen_private_key(&key_type, key_bits)?;
551
552 let mut req_builder = X509ReqBuilder::new()?;
553 req_builder.set_pubkey(&int_key)?;
554
555 let mut x509_name = X509NameBuilder::new()?;
556 x509_name.append_entry_by_text("C", "AU")?;
557 x509_name.append_entry_by_text("ST", "QLD")?;
558 x509_name.append_entry_by_text("O", "Kanidm")?;
559 x509_name.append_entry_by_text("CN", domain_name)?;
560 x509_name.append_entry_by_text("OU", "Development and Evaluation - NOT FOR PRODUCTION")?;
562 let x509_name = x509_name.build();
563
564 req_builder.set_subject_name(&x509_name)?;
565 req_builder.sign(&int_key, get_signing_func())?;
566 let req = req_builder.build();
567 let mut cert_builder = X509::builder()?;
570 cert_builder.set_version(2)?;
572 let serial_number = bn::BigNum::from_u32(2).and_then(|serial| serial.to_asn1_integer())?;
573
574 cert_builder.set_pubkey(&int_key)?;
575
576 cert_builder.set_serial_number(&serial_number)?;
577 cert_builder.set_subject_name(req.subject_name())?;
578 cert_builder.set_issuer_name(ca_handle.cert.subject_name())?;
579
580 let not_before = asn1::Asn1Time::days_from_now(0)?;
581 cert_builder.set_not_before(¬_before)?;
582 let not_after = asn1::Asn1Time::days_from_now(CERT_VALID_DAYS)?;
583 cert_builder.set_not_after(¬_after)?;
584
585 cert_builder.append_extension(BasicConstraints::new().build()?)?;
586
587 cert_builder.append_extension(
588 KeyUsage::new()
589 .critical()
590 .digital_signature()
591 .key_encipherment()
592 .build()?,
593 )?;
594
595 cert_builder.append_extension(
596 ExtendedKeyUsage::new()
597 .server_auth()
599 .build()?,
600 )?;
601
602 let subject_key_identifier = SubjectKeyIdentifier::new()
603 .build(&cert_builder.x509v3_context(Some(&ca_handle.cert), None))?;
604 cert_builder.append_extension(subject_key_identifier)?;
605
606 let auth_key_identifier = AuthorityKeyIdentifier::new()
607 .keyid(false)
608 .issuer(false)
609 .build(&cert_builder.x509v3_context(Some(&ca_handle.cert), None))?;
610 cert_builder.append_extension(auth_key_identifier)?;
611
612 let subject_alt_name = SubjectAlternativeName::new()
613 .dns(domain_name)
614 .build(&cert_builder.x509v3_context(Some(&ca_handle.cert), None))?;
615 cert_builder.append_extension(subject_alt_name)?;
616
617 cert_builder.sign(&ca_handle.key, get_signing_func())?;
618 let int_cert = cert_builder.build();
619
620 Ok(CertHandle {
621 key: int_key,
622 cert: int_cert,
623 chain: vec![ca_handle.cert.clone()],
624 })
625}
626
627#[test]
628fn test_enforced_minimums() {
630 let good_ca_configs = vec![
631 (KeyType::Rsa, 4096, false),
633 (KeyType::Rsa, 2048, false),
635 (KeyType::Ec, 256, false),
637 ];
638 good_ca_configs.into_iter().for_each(|config| {
639 dbg!(&config);
640 assert!(CAConfig::new(config.0, config.1, config.2).is_ok());
641 });
642 let bad_ca_configs = vec![
643 (KeyType::Rsa, 1024, false),
645 (KeyType::Ec, 128, false),
647 ];
648 bad_ca_configs.into_iter().for_each(|config| {
649 dbg!(&config);
650 assert!(CAConfig::new(config.0, config.1, config.2).is_err());
651 });
652}
653
654#[test]
655fn test_ca_loader() {
656 let ca_key_tempfile = tempfile::NamedTempFile::new().unwrap();
657 let ca_cert_tempfile = tempfile::NamedTempFile::new().unwrap();
658 let ca_config = CAConfig::default();
661 if let Ok(ca) = build_ca(Some(ca_config)) {
662 write_ca(ca_key_tempfile.path(), ca_cert_tempfile.path(), &ca).unwrap();
663 assert!(load_ca(ca_key_tempfile.path(), ca_cert_tempfile.path()).is_ok());
664 };
665
666 let good_ca_configs = vec![
667 (KeyType::Rsa, 4096, false),
669 (KeyType::Rsa, 2048, false),
671 (KeyType::Ec, 256, false),
673 ];
674 good_ca_configs.into_iter().for_each(|config| {
675 println!("testing good config {:?}", config);
676 let ca_config = CAConfig::new(config.0, config.1, config.2).unwrap();
677 let ca = build_ca(Some(ca_config)).unwrap();
678 write_ca(ca_key_tempfile.path(), ca_cert_tempfile.path(), &ca).unwrap();
679 let ca_result = load_ca(ca_key_tempfile.path(), ca_cert_tempfile.path());
680 println!("result: {:?}", ca_result);
681 assert!(ca_result.is_ok());
682 });
683 let bad_ca_configs = vec![
684 (KeyType::Rsa, 1024, true),
686 ];
687 bad_ca_configs.into_iter().for_each(|config| {
688 println!(
689 "\ntesting bad config keytype: {:?} key size: {}, skip_enforce_minimums: {}",
690 config.0, config.1, config.2
691 );
692 let ca_config = CAConfig::new(config.0, config.1, config.2).unwrap();
693 let ca = build_ca(Some(ca_config)).unwrap();
694 write_ca(ca_key_tempfile.path(), ca_cert_tempfile.path(), &ca).unwrap();
695 let ca_result = load_ca(ca_key_tempfile.path(), ca_cert_tempfile.path());
696 println!("result: {:?}", ca_result);
697 assert!(ca_result.is_err());
698 });
699}