mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 20:47:01 +01:00
Enforce TLS key size minimums (#2145)
* Enforce TLS key size minimums - Fixes #2144 * at some point clippy got mad
This commit is contained in:
parent
c998a1eda5
commit
c7a269575c
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -3011,6 +3011,7 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
"sketching",
|
"sketching",
|
||||||
|
"tempfile",
|
||||||
"time",
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-openssl",
|
"tokio-openssl",
|
||||||
|
@ -5019,7 +5020,6 @@ dependencies = [
|
||||||
name = "testkit-macros"
|
name = "testkit-macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"kanidmd_core",
|
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.37",
|
||||||
|
|
|
@ -173,7 +173,8 @@ sketching = { path = "./libs/sketching" }
|
||||||
smartstring = "^1.0.1"
|
smartstring = "^1.0.1"
|
||||||
smolset = "^1.3.1"
|
smolset = "^1.3.1"
|
||||||
sshkeys = "^0.3.1"
|
sshkeys = "^0.3.1"
|
||||||
syn = { version = "2.0.37", features = ["full"] }
|
syn = { version = "2.0.32", features = ["full"] }
|
||||||
|
tempfile = "3.8.0"
|
||||||
testkit-macros = { path = "./server/testkit-macros" }
|
testkit-macros = { path = "./server/testkit-macros" }
|
||||||
time = { version = "^0.3.21", features = ["formatting", "local-offset"] }
|
time = { version = "^0.3.21", features = ["formatting", "local-offset"] }
|
||||||
|
|
||||||
|
|
|
@ -158,3 +158,7 @@ docker run --rm -i -t -u 1000:1000 -v kanidmd:/data kanidm/server:latest /sbin/k
|
||||||
|
|
||||||
> **HINT** You need to use the UID or GID number with the `-u` argument, as the container can't
|
> **HINT** You need to use the UID or GID number with the `-u` argument, as the container can't
|
||||||
> resolve usernames from the host system.
|
> resolve usernames from the host system.
|
||||||
|
|
||||||
|
## Minimum TLS key lengths
|
||||||
|
|
||||||
|
We enforce a minimum RSA key length of 2048 bits, and EC keys need 224 bits.
|
||||||
|
|
|
@ -24,4 +24,4 @@ base64 = { workspace = true }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
base64 = { workspace = true }
|
base64 = { workspace = true }
|
||||||
gix = { workspace = true }
|
gix = { workspace = true, default-features = false }
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
name = "kanidmd_core"
|
name = "kanidmd_core"
|
||||||
description = "Kanidm Server Core and Library"
|
description = "Kanidm Server Core and Library"
|
||||||
documentation = "https://docs.rs/kanidm/latest/kanidm/"
|
documentation = "https://docs.rs/kanidm/latest/kanidm/"
|
||||||
autotests = false
|
|
||||||
|
|
||||||
version = { workspace = true }
|
version = { workspace = true }
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
|
@ -14,7 +13,7 @@ repository = { workspace = true }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-trait = { workspace = true }
|
async-trait = { workspace = true }
|
||||||
axum = { workspace=true }
|
axum = { workspace = true }
|
||||||
axum-auth = "0.4.0"
|
axum-auth = "0.4.0"
|
||||||
axum-csp = { workspace = true }
|
axum-csp = { workspace = true }
|
||||||
axum-macros = "0.3.8"
|
axum-macros = "0.3.8"
|
||||||
|
@ -28,6 +27,7 @@ futures-util = { workspace = true }
|
||||||
http = "0.2.9"
|
http = "0.2.9"
|
||||||
hyper = { workspace = true }
|
hyper = { workspace = true }
|
||||||
kanidm_proto = { workspace = true }
|
kanidm_proto = { workspace = true }
|
||||||
|
kanidm_utils_users = { workspace = true }
|
||||||
kanidmd_lib = { workspace = true }
|
kanidmd_lib = { workspace = true }
|
||||||
ldap3_proto = { workspace = true }
|
ldap3_proto = { workspace = true }
|
||||||
libc = { workspace = true }
|
libc = { workspace = true }
|
||||||
|
@ -36,20 +36,29 @@ rand = { workspace = true }
|
||||||
regex = { workspace = true }
|
regex = { workspace = true }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
|
serde_with = { workspace = true }
|
||||||
sketching = { workspace = true }
|
sketching = { workspace = true }
|
||||||
time = { workspace = true, features = ["serde", "std","local-offset"] }
|
time = { workspace = true, features = ["serde", "std", "local-offset"] }
|
||||||
tokio = { workspace = true, features = ["net", "sync", "io-util", "macros"] }
|
tokio = { workspace = true, features = ["net", "sync", "io-util", "macros"] }
|
||||||
tokio-openssl = { workspace = true }
|
tokio-openssl = { workspace = true }
|
||||||
tokio-util = { workspace = true, features = ["codec"] }
|
tokio-util = { workspace = true, features = ["codec"] }
|
||||||
toml = {workspace = true}
|
toml = { workspace = true }
|
||||||
tower = { version = "0.4.13", features = ["tokio-stream", "tracing"] }
|
tower = { version = "0.4.13", features = ["tokio-stream", "tracing"] }
|
||||||
tower-http = { version = "0.4.4", features = ["tokio", "tracing", "uuid", "compression-gzip", "compression-zstd", "trace", "fs"] }
|
tower-http = { version = "0.4.4", features = [
|
||||||
|
"compression-gzip",
|
||||||
|
"compression-zstd",
|
||||||
|
"fs",
|
||||||
|
"tokio",
|
||||||
|
"trace",
|
||||||
|
"tracing",
|
||||||
|
"uuid",
|
||||||
|
] }
|
||||||
tracing = { workspace = true, features = ["attributes"] }
|
tracing = { workspace = true, features = ["attributes"] }
|
||||||
tracing-subscriber = { workspace = true, features = ["time", "json"] }
|
tracing-subscriber = { workspace = true, features = ["time", "json"] }
|
||||||
urlencoding = { workspace = true }
|
urlencoding = { workspace = true }
|
||||||
kanidm_utils_users = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
uuid = { workspace = true, features = ["serde", "v4" ] }
|
uuid = { workspace = true, features = ["serde", "v4"] }
|
||||||
serde_with = { workspace = true }
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
kanidm_build_profiles = { workspace = true }
|
kanidm_build_profiles = { workspace = true }
|
||||||
|
|
|
@ -4,7 +4,9 @@
|
||||||
use openssl::ec::{EcGroup, EcKey};
|
use openssl::ec::{EcGroup, EcKey};
|
||||||
use openssl::error::ErrorStack;
|
use openssl::error::ErrorStack;
|
||||||
use openssl::nid::Nid;
|
use openssl::nid::Nid;
|
||||||
use openssl::ssl::{SslAcceptor, SslAcceptorBuilder, SslFiletype, SslMethod};
|
use openssl::pkey::{PKeyRef, Private};
|
||||||
|
use openssl::rsa::Rsa;
|
||||||
|
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
|
||||||
use openssl::x509::{
|
use openssl::x509::{
|
||||||
extension::{
|
extension::{
|
||||||
AuthorityKeyIdentifier, BasicConstraints, ExtendedKeyUsage, KeyUsage,
|
AuthorityKeyIdentifier, BasicConstraints, ExtendedKeyUsage, KeyUsage,
|
||||||
|
@ -13,6 +15,7 @@ use openssl::x509::{
|
||||||
X509NameBuilder, X509ReqBuilder, X509,
|
X509NameBuilder, X509ReqBuilder, X509,
|
||||||
};
|
};
|
||||||
use openssl::{asn1, bn, hash, pkey};
|
use openssl::{asn1, bn, hash, pkey};
|
||||||
|
use sketching::*;
|
||||||
|
|
||||||
use crate::config::Configuration;
|
use crate::config::Configuration;
|
||||||
|
|
||||||
|
@ -23,25 +26,110 @@ use std::path::Path;
|
||||||
const CA_VALID_DAYS: u32 = 30;
|
const CA_VALID_DAYS: u32 = 30;
|
||||||
const CERT_VALID_DAYS: u32 = 5;
|
const CERT_VALID_DAYS: u32 = 5;
|
||||||
|
|
||||||
|
// Basing minimums off https://www.keylength.com setting "year" to 2030 - tested as at 2023-09-25
|
||||||
|
//
|
||||||
|
// |Method |Date |Symmetric| FM |DL Key| DL Group|Elliptic Curve|Hash|
|
||||||
|
// | --- | --- | --- | --- | --- | --- | --- | ---|
|
||||||
|
// |Lenstra / Verheul|2030 | 93 |2493^2016|165 | 2493 | 176 | 186|
|
||||||
|
// |Lenstra Updated |2030 | 88 |1698^2063|176 | 1698 | 176 | 176|
|
||||||
|
// |ECRYPT |2029-2068| 256 |15360 |512 | 15360 | 512 | 512|
|
||||||
|
// |NIST |2019-2030| 112 |2048 |224 | 2048 | 224 | 224|
|
||||||
|
// |ANSSI |> 2030 | 128 |3072 |200 | 3072 | 256 | 256|
|
||||||
|
// |NSA |- | 256 |3072 |- | - | 384 | 384|
|
||||||
|
// |RFC3766 |- | - | - | - | - | - | - |
|
||||||
|
// |BSI |- | - | - | - | - | - | - |
|
||||||
|
// DL - Discrete Logarithm
|
||||||
|
// FM - Factoring Modulus
|
||||||
|
|
||||||
|
const RSA_MIN_KEY_SIZE_BITS: u64 = 2048;
|
||||||
|
const EC_MIN_KEY_SIZE_BITS: u64 = 224;
|
||||||
|
|
||||||
|
/// returns a signing function that meets a sensible minimum
|
||||||
|
fn get_signing_func() -> hash::MessageDigest {
|
||||||
|
hash::MessageDigest::sha256()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensure we're enforcing safe minimums for TLS keys
|
||||||
|
pub fn check_privkey_minimums(privkey: &PKeyRef<Private>) -> Result<(), String> {
|
||||||
|
if let Ok(key) = privkey.rsa() {
|
||||||
|
if key.size() < (RSA_MIN_KEY_SIZE_BITS / 8) as u32 {
|
||||||
|
Err(format!(
|
||||||
|
"TLS RSA key is less than {} bits!",
|
||||||
|
RSA_MIN_KEY_SIZE_BITS
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
debug!(
|
||||||
|
"The RSA private key size is: {} bits, that's OK!",
|
||||||
|
key.size() * 8
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
} else if let Ok(key) = privkey.ec_key() {
|
||||||
|
// allowing this to panic because ... it's an i32 and hopefully we don't have negative bit lengths?
|
||||||
|
#[allow(clippy::panic)]
|
||||||
|
let key_bits: u64 = key.private_key().num_bits().try_into().unwrap_or_else(|_| {
|
||||||
|
panic!(
|
||||||
|
"Failed to convert EC bitlength {} to u64",
|
||||||
|
key.private_key().num_bits()
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
if key_bits < EC_MIN_KEY_SIZE_BITS {
|
||||||
|
Err(format!(
|
||||||
|
"TLS EC key is less than {} bits! Got: {}",
|
||||||
|
EC_MIN_KEY_SIZE_BITS, key_bits
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
#[cfg(any(test, debug_assertions))]
|
||||||
|
println!("The EC private key size is: {} bits, that's OK!", key_bits);
|
||||||
|
debug!("The EC private key size is: {} bits, that's OK!", key_bits);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!("TLS key is not RSA or EC, cannot check minimums!");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// From the server configuration, generate an OpenSSL acceptor that we can use
|
/// From the server configuration, generate an OpenSSL acceptor that we can use
|
||||||
/// to build our sockets for https/ldaps.
|
/// to build our sockets for HTTPS/LDAPS.
|
||||||
pub fn setup_tls(config: &Configuration) -> Result<Option<SslAcceptorBuilder>, ErrorStack> {
|
pub fn setup_tls(config: &Configuration) -> Result<Option<SslAcceptor>, ErrorStack> {
|
||||||
match &config.tls_config {
|
match &config.tls_config {
|
||||||
Some(tls_config) => {
|
Some(tls_config) => {
|
||||||
|
// Signing algorithm minimums are enforced by the SSLAcceptor - it won't start up with a sha1-signed cert.
|
||||||
let mut ssl_builder = SslAcceptor::mozilla_modern(SslMethod::tls())?;
|
let mut ssl_builder = SslAcceptor::mozilla_modern(SslMethod::tls())?;
|
||||||
ssl_builder.set_certificate_chain_file(&tls_config.chain)?;
|
ssl_builder.set_certificate_chain_file(&tls_config.chain)?;
|
||||||
|
|
||||||
ssl_builder.set_private_key_file(&tls_config.key, SslFiletype::PEM)?;
|
ssl_builder.set_private_key_file(&tls_config.key, SslFiletype::PEM)?;
|
||||||
ssl_builder.check_private_key()?;
|
ssl_builder.check_private_key()?;
|
||||||
Ok(Some(ssl_builder))
|
|
||||||
|
let acceptor = ssl_builder.build();
|
||||||
|
|
||||||
|
// let's enforce some TLS minimums!
|
||||||
|
#[allow(clippy::expect_used)]
|
||||||
|
let privkey = acceptor
|
||||||
|
.context()
|
||||||
|
.private_key()
|
||||||
|
.expect("Couldn't pull TLS key after configuring one!");
|
||||||
|
|
||||||
|
check_privkey_minimums(privkey).map_err(|err| {
|
||||||
|
#[cfg(any(test, debug_assertions))]
|
||||||
|
println!("{}", err);
|
||||||
|
admin_error!("{}", err);
|
||||||
|
ErrorStack::get() // this probably should be a real errorstack but... how?
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(Some(acceptor))
|
||||||
}
|
}
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_group() -> Result<EcGroup, ErrorStack> {
|
fn get_ec_group() -> Result<EcGroup, ErrorStack> {
|
||||||
EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)
|
EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub(crate) struct CaHandle {
|
pub(crate) struct CaHandle {
|
||||||
key: pkey::PKey<pkey::Private>,
|
key: pkey::PKey<pkey::Private>,
|
||||||
cert: X509,
|
cert: X509,
|
||||||
|
@ -76,10 +164,108 @@ pub(crate) fn write_ca(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn build_ca() -> Result<CaHandle, ErrorStack> {
|
#[derive(Debug)]
|
||||||
let ecgroup = get_group()?;
|
pub enum KeyType {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
Rsa,
|
||||||
|
Ec,
|
||||||
|
}
|
||||||
|
impl Default for KeyType {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Ec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CAConfig {
|
||||||
|
pub key_type: KeyType,
|
||||||
|
pub key_bits: u64,
|
||||||
|
pub skip_enforce_minimums: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CAConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
#[allow(clippy::expect_used)]
|
||||||
|
Self::new(KeyType::Ec, 256, false)
|
||||||
|
.expect("Somehow the defaults failed to pass validation while building a CA Config?")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CAConfig {
|
||||||
|
fn new(key_type: KeyType, key_bits: u64, skip_enforce_minimums: bool) -> Result<Self, String> {
|
||||||
|
let res = Self {
|
||||||
|
key_type,
|
||||||
|
key_bits,
|
||||||
|
skip_enforce_minimums,
|
||||||
|
};
|
||||||
|
if !skip_enforce_minimums {
|
||||||
|
res.enforce_minimums()?;
|
||||||
|
};
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make sure we're meeting the minimum spec for key length etc
|
||||||
|
fn enforce_minimums(&self) -> Result<(), String> {
|
||||||
|
match self.key_type {
|
||||||
|
KeyType::Rsa => {
|
||||||
|
trace!(
|
||||||
|
"Generating CA Config for RSA Key with {} bits",
|
||||||
|
self.key_bits
|
||||||
|
);
|
||||||
|
if self.key_bits < RSA_MIN_KEY_SIZE_BITS {
|
||||||
|
return Err(format!(
|
||||||
|
"RSA key size must be at least {} bits",
|
||||||
|
RSA_MIN_KEY_SIZE_BITS
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyType::Ec => {
|
||||||
|
trace!("Generating CA Config for EcKey with {} bits", self.key_bits);
|
||||||
|
if self.key_bits < EC_MIN_KEY_SIZE_BITS {
|
||||||
|
return Err(format!(
|
||||||
|
"EC key size must be at least {} bits",
|
||||||
|
EC_MIN_KEY_SIZE_BITS
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn gen_private_key(
|
||||||
|
key_type: &KeyType,
|
||||||
|
key_bits: Option<u64>,
|
||||||
|
) -> Result<pkey::PKey<pkey::Private>, ErrorStack> {
|
||||||
|
match key_type {
|
||||||
|
KeyType::Rsa => {
|
||||||
|
let key_bits = key_bits.unwrap_or(RSA_MIN_KEY_SIZE_BITS);
|
||||||
|
let rsa = Rsa::generate(key_bits as u32)?;
|
||||||
|
pkey::PKey::from_rsa(rsa)
|
||||||
|
}
|
||||||
|
KeyType::Ec => {
|
||||||
|
// TODO: take key bitlength and use it for the curve group, somehow?
|
||||||
|
let ecgroup = get_ec_group()?;
|
||||||
let eckey = EcKey::generate(&ecgroup)?;
|
let eckey = EcKey::generate(&ecgroup)?;
|
||||||
let ca_key = pkey::PKey::from_ec_key(eckey)?;
|
pkey::PKey::from_ec_key(eckey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// build up a CA certificate and key.
|
||||||
|
pub(crate) fn build_ca(ca_config: Option<CAConfig>) -> Result<CaHandle, ErrorStack> {
|
||||||
|
let ca_config = ca_config.unwrap_or(CAConfig::default());
|
||||||
|
|
||||||
|
let ca_key = gen_private_key(&ca_config.key_type, Some(ca_config.key_bits))?;
|
||||||
|
|
||||||
|
if !ca_config.skip_enforce_minimums {
|
||||||
|
check_privkey_minimums(&ca_key).map_err(|err| {
|
||||||
|
admin_error!("failed to build_ca due to privkey minimums {}", err);
|
||||||
|
#[cfg(any(test, debug_assertions))]
|
||||||
|
println!("failed to build_ca due to privkey minimums: {}", err);
|
||||||
|
ErrorStack::get() // this probably should be a real errorstack but... how?
|
||||||
|
})?;
|
||||||
|
}
|
||||||
let mut x509_name = X509NameBuilder::new()?;
|
let mut x509_name = X509NameBuilder::new()?;
|
||||||
|
|
||||||
x509_name.append_entry_by_text("C", "AU")?;
|
x509_name.append_entry_by_text("C", "AU")?;
|
||||||
|
@ -119,7 +305,7 @@ pub(crate) fn build_ca() -> Result<CaHandle, ErrorStack> {
|
||||||
|
|
||||||
cert_builder.set_pubkey(&ca_key)?;
|
cert_builder.set_pubkey(&ca_key)?;
|
||||||
|
|
||||||
cert_builder.sign(&ca_key, hash::MessageDigest::sha256())?;
|
cert_builder.sign(&ca_key, get_signing_func())?;
|
||||||
let ca_cert = cert_builder.build();
|
let ca_cert = cert_builder.build();
|
||||||
|
|
||||||
Ok(CaHandle {
|
Ok(CaHandle {
|
||||||
|
@ -153,6 +339,12 @@ pub(crate) fn load_ca(
|
||||||
error!(err = ?e, "Failed to convert PEM to key");
|
error!(err = ?e, "Failed to convert PEM to key");
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
check_privkey_minimums(&ca_key).map_err(|err| {
|
||||||
|
#[cfg(any(test, debug_assertions))]
|
||||||
|
println!("{:?}", err);
|
||||||
|
admin_error!("{}", err);
|
||||||
|
})?;
|
||||||
|
|
||||||
let ca_cert = X509::from_pem(&ca_cert_pem).map_err(|e| {
|
let ca_cert = X509::from_pem(&ca_cert_pem).map_err(|e| {
|
||||||
error!(err = ?e, "Failed to convert PEM to cert");
|
error!(err = ?e, "Failed to convert PEM to cert");
|
||||||
})?;
|
})?;
|
||||||
|
@ -224,12 +416,12 @@ pub(crate) fn write_cert(
|
||||||
pub(crate) fn build_cert(
|
pub(crate) fn build_cert(
|
||||||
domain_name: &str,
|
domain_name: &str,
|
||||||
ca_handle: &CaHandle,
|
ca_handle: &CaHandle,
|
||||||
|
key_type: Option<KeyType>,
|
||||||
|
key_bits: Option<u64>,
|
||||||
) -> Result<CertHandle, ErrorStack> {
|
) -> Result<CertHandle, ErrorStack> {
|
||||||
let ecgroup = get_group()?;
|
let key_type = key_type.unwrap_or(KeyType::default());
|
||||||
let eckey = EcKey::generate(&ecgroup)?;
|
let int_key = gen_private_key(&key_type, key_bits)?;
|
||||||
let int_key = pkey::PKey::from_ec_key(eckey)?;
|
|
||||||
|
|
||||||
//
|
|
||||||
let mut req_builder = X509ReqBuilder::new()?;
|
let mut req_builder = X509ReqBuilder::new()?;
|
||||||
req_builder.set_pubkey(&int_key)?;
|
req_builder.set_pubkey(&int_key)?;
|
||||||
|
|
||||||
|
@ -243,7 +435,7 @@ pub(crate) fn build_cert(
|
||||||
let x509_name = x509_name.build();
|
let x509_name = x509_name.build();
|
||||||
|
|
||||||
req_builder.set_subject_name(&x509_name)?;
|
req_builder.set_subject_name(&x509_name)?;
|
||||||
req_builder.sign(&int_key, hash::MessageDigest::sha256())?;
|
req_builder.sign(&int_key, get_signing_func())?;
|
||||||
let req = req_builder.build();
|
let req = req_builder.build();
|
||||||
// ==
|
// ==
|
||||||
|
|
||||||
|
@ -296,7 +488,7 @@ pub(crate) fn build_cert(
|
||||||
.build(&cert_builder.x509v3_context(Some(&ca_handle.cert), None))?;
|
.build(&cert_builder.x509v3_context(Some(&ca_handle.cert), None))?;
|
||||||
cert_builder.append_extension(subject_alt_name)?;
|
cert_builder.append_extension(subject_alt_name)?;
|
||||||
|
|
||||||
cert_builder.sign(&ca_handle.key, hash::MessageDigest::sha256())?;
|
cert_builder.sign(&ca_handle.key, get_signing_func())?;
|
||||||
let int_cert = cert_builder.build();
|
let int_cert = cert_builder.build();
|
||||||
|
|
||||||
Ok(CertHandle {
|
Ok(CertHandle {
|
||||||
|
@ -305,3 +497,77 @@ pub(crate) fn build_cert(
|
||||||
chain: vec![ca_handle.cert.clone()],
|
chain: vec![ca_handle.cert.clone()],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// might as well test my logic
|
||||||
|
fn test_enforced_minimums() {
|
||||||
|
let good_ca_configs = vec![
|
||||||
|
// test rsa 4096 (ok)
|
||||||
|
(KeyType::Rsa, 4096, false),
|
||||||
|
// test rsa 2048 (ok)
|
||||||
|
(KeyType::Rsa, 2048, false),
|
||||||
|
// test ec 256 (ok)
|
||||||
|
(KeyType::Ec, 256, false),
|
||||||
|
];
|
||||||
|
good_ca_configs.into_iter().for_each(|config| {
|
||||||
|
dbg!(&config);
|
||||||
|
assert!(CAConfig::new(config.0, config.1, config.2).is_ok());
|
||||||
|
});
|
||||||
|
let bad_ca_configs = vec![
|
||||||
|
// test rsa 1024 (no)
|
||||||
|
(KeyType::Rsa, 1024, false),
|
||||||
|
// test ec 128 (no)
|
||||||
|
(KeyType::Ec, 128, false),
|
||||||
|
];
|
||||||
|
bad_ca_configs.into_iter().for_each(|config| {
|
||||||
|
dbg!(&config);
|
||||||
|
assert!(CAConfig::new(config.0, config.1, config.2).is_err());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ca_loader() {
|
||||||
|
let ca_key_tempfile = tempfile::NamedTempFile::new().unwrap();
|
||||||
|
let ca_cert_tempfile = tempfile::NamedTempFile::new().unwrap();
|
||||||
|
// let's test the defaults first
|
||||||
|
|
||||||
|
let ca_config = CAConfig::default();
|
||||||
|
if let Ok(ca) = build_ca(Some(ca_config)) {
|
||||||
|
write_ca(&ca_key_tempfile.path(), &ca_cert_tempfile.path(), &ca).unwrap();
|
||||||
|
assert!(load_ca(&ca_key_tempfile.path(), &ca_cert_tempfile.path()).is_ok());
|
||||||
|
};
|
||||||
|
|
||||||
|
let good_ca_configs = vec![
|
||||||
|
// test rsa 4096 (ok)
|
||||||
|
(KeyType::Rsa, 4096, false),
|
||||||
|
// test rsa 2048 (ok)
|
||||||
|
(KeyType::Rsa, 2048, false),
|
||||||
|
// test ec 256 (ok)
|
||||||
|
(KeyType::Ec, 256, false),
|
||||||
|
];
|
||||||
|
good_ca_configs.into_iter().for_each(|config| {
|
||||||
|
println!("testing good config {:?}", config);
|
||||||
|
let ca_config = CAConfig::new(config.0, config.1, config.2).unwrap();
|
||||||
|
let ca = build_ca(Some(ca_config)).unwrap();
|
||||||
|
write_ca(&ca_key_tempfile.path(), &ca_cert_tempfile.path(), &ca).unwrap();
|
||||||
|
let ca_result = load_ca(&ca_key_tempfile.path(), &ca_cert_tempfile.path());
|
||||||
|
println!("result: {:?}", ca_result);
|
||||||
|
assert!(ca_result.is_ok());
|
||||||
|
});
|
||||||
|
let bad_ca_configs = vec![
|
||||||
|
// test rsa 1024 (bad)
|
||||||
|
(KeyType::Rsa, 1024, true),
|
||||||
|
];
|
||||||
|
bad_ca_configs.into_iter().for_each(|config| {
|
||||||
|
println!(
|
||||||
|
"\ntesting bad config keytype: {:?} key size: {}, skip_enforce_minimums: {}",
|
||||||
|
config.0, config.1, config.2
|
||||||
|
);
|
||||||
|
let ca_config = CAConfig::new(config.0, config.1, config.2).unwrap();
|
||||||
|
let ca = build_ca(Some(ca_config)).unwrap();
|
||||||
|
write_ca(&ca_key_tempfile.path(), &ca_cert_tempfile.path(), &ca).unwrap();
|
||||||
|
let ca_result = load_ca(&ca_key_tempfile.path(), &ca_cert_tempfile.path());
|
||||||
|
println!("result: {:?}", ca_result);
|
||||||
|
assert!(ca_result.is_err());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ use kanidmd_lib::idm::ldap::{LdapBoundToken, LdapResponseState};
|
||||||
use kanidmd_lib::prelude::*;
|
use kanidmd_lib::prelude::*;
|
||||||
use ldap3_proto::proto::LdapMsg;
|
use ldap3_proto::proto::LdapMsg;
|
||||||
use ldap3_proto::LdapCodec;
|
use ldap3_proto::LdapCodec;
|
||||||
use openssl::ssl::{Ssl, SslAcceptor, SslAcceptorBuilder};
|
use openssl::ssl::{Ssl, SslAcceptor};
|
||||||
use tokio::io::{AsyncRead, AsyncWrite};
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
use tokio_openssl::SslStream;
|
use tokio_openssl::SslStream;
|
||||||
|
@ -108,7 +108,7 @@ async fn client_process<W: AsyncWrite + Unpin, R: AsyncRead + Unpin>(
|
||||||
/// TLS LDAP Listener, hands off to [client_process]
|
/// TLS LDAP Listener, hands off to [client_process]
|
||||||
async fn tls_acceptor(
|
async fn tls_acceptor(
|
||||||
listener: TcpListener,
|
listener: TcpListener,
|
||||||
tls_parms: SslAcceptor,
|
ssl_acceptor: SslAcceptor,
|
||||||
qe_r_ref: &'static QueryServerReadV1,
|
qe_r_ref: &'static QueryServerReadV1,
|
||||||
mut rx: broadcast::Receiver<CoreAction>,
|
mut rx: broadcast::Receiver<CoreAction>,
|
||||||
) {
|
) {
|
||||||
|
@ -124,7 +124,7 @@ async fn tls_acceptor(
|
||||||
Ok((tcpstream, client_socket_addr)) => {
|
Ok((tcpstream, client_socket_addr)) => {
|
||||||
// Start the event
|
// Start the event
|
||||||
// From the parameters we need to create an SslContext.
|
// From the parameters we need to create an SslContext.
|
||||||
let mut tlsstream = match Ssl::new(tls_parms.context())
|
let mut tlsstream = match Ssl::new(ssl_acceptor.context())
|
||||||
.and_then(|tls_obj| SslStream::new(tls_obj, tcpstream))
|
.and_then(|tls_obj| SslStream::new(tls_obj, tcpstream))
|
||||||
{
|
{
|
||||||
Ok(ta) => ta,
|
Ok(ta) => ta,
|
||||||
|
@ -154,7 +154,7 @@ async fn tls_acceptor(
|
||||||
|
|
||||||
pub(crate) async fn create_ldap_server(
|
pub(crate) async fn create_ldap_server(
|
||||||
address: &str,
|
address: &str,
|
||||||
opt_tls_params: Option<SslAcceptorBuilder>,
|
opt_ssl_acceptor: Option<SslAcceptor>,
|
||||||
qe_r_ref: &'static QueryServerReadV1,
|
qe_r_ref: &'static QueryServerReadV1,
|
||||||
rx: broadcast::Receiver<CoreAction>,
|
rx: broadcast::Receiver<CoreAction>,
|
||||||
) -> Result<tokio::task::JoinHandle<()>, ()> {
|
) -> Result<tokio::task::JoinHandle<()>, ()> {
|
||||||
|
@ -175,11 +175,11 @@ pub(crate) async fn create_ldap_server(
|
||||||
);
|
);
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let ldap_acceptor_handle = match opt_tls_params {
|
let ldap_acceptor_handle = match opt_ssl_acceptor {
|
||||||
Some(tls_params) => {
|
Some(ssl_acceptor) => {
|
||||||
info!("Starting LDAPS interface ldaps://{} ...", address);
|
info!("Starting LDAPS interface ldaps://{} ...", address);
|
||||||
let tls_parms = tls_params.build();
|
|
||||||
tokio::spawn(tls_acceptor(listener, tls_parms, qe_r_ref, rx))
|
tokio::spawn(tls_acceptor(listener, ssl_acceptor, qe_r_ref, rx))
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
error!("The server won't run without TLS!");
|
error!("The server won't run without TLS!");
|
||||||
|
|
|
@ -599,7 +599,7 @@ pub fn cert_generate_core(config: &Configuration) {
|
||||||
|
|
||||||
let ca_handle = if !ca_cert.exists() || !ca_key.exists() {
|
let ca_handle = if !ca_cert.exists() || !ca_key.exists() {
|
||||||
// Generate the CA again.
|
// Generate the CA again.
|
||||||
let ca_handle = match crypto::build_ca() {
|
let ca_handle = match crypto::build_ca(None) {
|
||||||
Ok(ca_handle) => ca_handle,
|
Ok(ca_handle) => ca_handle,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(err = ?e, "Failed to build CA");
|
error!(err = ?e, "Failed to build CA");
|
||||||
|
@ -625,7 +625,7 @@ pub fn cert_generate_core(config: &Configuration) {
|
||||||
|
|
||||||
if !tls_key_path.exists() || !tls_chain_path.exists() || !tls_cert_path.exists() {
|
if !tls_key_path.exists() || !tls_chain_path.exists() || !tls_cert_path.exists() {
|
||||||
// Generate the cert from the ca.
|
// Generate the cert from the ca.
|
||||||
let cert_handle = match crypto::build_cert(origin_domain, &ca_handle) {
|
let cert_handle = match crypto::build_cert(origin_domain, &ca_handle, None, None) {
|
||||||
Ok(cert_handle) => cert_handle,
|
Ok(cert_handle) => cert_handle,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(err = ?e, "Failed to build certificate");
|
error!(err = ?e, "Failed to build certificate");
|
||||||
|
@ -886,7 +886,7 @@ pub async fn create_server_core(
|
||||||
// If we have been requested to init LDAP, configure it now.
|
// If we have been requested to init LDAP, configure it now.
|
||||||
let maybe_ldap_acceptor_handle = match &config.ldapaddress {
|
let maybe_ldap_acceptor_handle = match &config.ldapaddress {
|
||||||
Some(la) => {
|
Some(la) => {
|
||||||
let opt_ldap_tls_params = match crypto::setup_tls(&config) {
|
let opt_ldap_ssl_acceptor = match crypto::setup_tls(&config) {
|
||||||
Ok(t) => t,
|
Ok(t) => t,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to configure LDAP TLS parameters -> {:?}", e);
|
error!("Failed to configure LDAP TLS parameters -> {:?}", e);
|
||||||
|
@ -897,7 +897,7 @@ pub async fn create_server_core(
|
||||||
// ⚠️ only start the sockets and listeners in non-config-test modes.
|
// ⚠️ only start the sockets and listeners in non-config-test modes.
|
||||||
let h = ldaps::create_ldap_server(
|
let h = ldaps::create_ldap_server(
|
||||||
la.as_str(),
|
la.as_str(),
|
||||||
opt_ldap_tls_params,
|
opt_ldap_ssl_acceptor,
|
||||||
server_read_ref,
|
server_read_ref,
|
||||||
broadcast_tx.subscribe(),
|
broadcast_tx.subscribe(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -144,12 +144,12 @@ fn search_oauth2_filter_entry<'a>(
|
||||||
security_access!(entry = ?entry.get_uuid(), ident = ?iuser.entry.get_uuid2rdn(), "ident is a memberof a group granted an oauth2 scope by this entry");
|
security_access!(entry = ?entry.get_uuid(), ident = ?iuser.entry.get_uuid2rdn(), "ident is a memberof a group granted an oauth2 scope by this entry");
|
||||||
|
|
||||||
return AccessResult::Allow(btreeset!(
|
return AccessResult::Allow(btreeset!(
|
||||||
ATTR_CLASS.clone(),
|
ATTR_CLASS,
|
||||||
ATTR_DISPLAYNAME.clone(),
|
ATTR_DISPLAYNAME,
|
||||||
ATTR_UUID.clone(),
|
ATTR_UUID,
|
||||||
ATTR_OAUTH2_RS_NAME.clone(),
|
ATTR_OAUTH2_RS_NAME,
|
||||||
ATTR_OAUTH2_RS_ORIGIN.clone(),
|
ATTR_OAUTH2_RS_ORIGIN,
|
||||||
ATTR_OAUTH2_RS_ORIGIN_LANDING.clone()
|
ATTR_OAUTH2_RS_ORIGIN_LANDING
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
AccessResult::Ignore
|
AccessResult::Ignore
|
||||||
|
|
|
@ -7,9 +7,6 @@ edition = "2021"
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
kanidmd_core.workspace = true
|
|
||||||
proc-macro2 = { workspace = true }
|
proc-macro2 = { workspace = true }
|
||||||
quote = { workspace = true }
|
quote = { workspace = true }
|
||||||
syn = { workspace = true }
|
syn = { workspace = true }
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue