20231128 freeipa migration ()

* Add more weak password formats for freeipa
* Verification of freeipa migration from older ipa versions
This commit is contained in:
Firstyear 2023-11-29 10:43:15 +10:00 committed by GitHub
parent bca2fbcf4e
commit 31b939fca3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 454 additions and 75 deletions
Cargo.lockCargo.toml
libs/crypto/src
proto/src
server
tools
iam_migrations
freeipa/src
ldap/src
orca/src

150
Cargo.lock generated
View file

@ -724,9 +724,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.4.8"
version = "4.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64"
checksum = "41fffed7514f420abec6d183b1d3acfd9099c79c3a10a06ade4f8203f1411272"
dependencies = [
"clap_builder",
"clap_derive",
@ -734,9 +734,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.4.8"
version = "4.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc"
checksum = "63361bae7eef3771745f02d8d892bec2fee5f6e34af316ba556e7f97a7069ff1"
dependencies = [
"anstream",
"anstyle",
@ -1500,12 +1500,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.7"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
"libc",
"windows-sys 0.48.0",
"windows-sys 0.52.0",
]
[[package]]
@ -2067,7 +2067,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "409268480841ad008e81c17ca5a293393fbf9f2b6c2f85b8ab9de1f0c5176a16"
dependencies = [
"gix-hash",
"hashbrown 0.14.2",
"hashbrown 0.14.3",
"parking_lot 0.12.1",
]
@ -2564,9 +2564,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.14.2"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
dependencies = [
"ahash 0.8.6",
"allocator-api2",
@ -2579,7 +2579,7 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
dependencies = [
"hashbrown 0.14.2",
"hashbrown 0.14.3",
]
[[package]]
@ -2855,7 +2855,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
dependencies = [
"equivalent",
"hashbrown 0.14.2",
"hashbrown 0.14.3",
"serde",
]
@ -2955,9 +2955,9 @@ checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e"
[[package]]
name = "js-sys"
version = "0.3.65"
version = "0.3.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8"
checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca"
dependencies = [
"wasm-bindgen",
]
@ -3179,7 +3179,7 @@ dependencies = [
"compact_jwt 0.3.2",
"csv",
"futures",
"hashbrown 0.14.2",
"hashbrown 0.14.3",
"kanidm-hsm-crypto",
"kanidm_build_profiles",
"kanidm_client",
@ -3231,7 +3231,7 @@ dependencies = [
"filetime",
"futures",
"futures-util",
"hashbrown 0.14.2",
"hashbrown 0.14.3",
"http",
"hyper",
"kanidm_build_profiles",
@ -3280,7 +3280,7 @@ dependencies = [
"enum-iterator",
"fernet",
"futures",
"hashbrown 0.14.2",
"hashbrown 0.14.3",
"hex",
"idlset",
"image 0.24.7",
@ -3503,11 +3503,9 @@ dependencies = [
[[package]]
name = "ldap3_client"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a229cd5ee2a4e5a1a279b6216494aa2a5053a189c5ce37bb31f9156b63b63de"
version = "0.4.2"
dependencies = [
"base64 0.13.1",
"base64 0.21.5",
"base64urlsafedata",
"futures-util",
"ldap3_proto",
@ -3523,15 +3521,15 @@ dependencies = [
[[package]]
name = "ldap3_proto"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93d073b5c98def43cec39ccd95e536b3b2448e36289222ecd64dfdf1389d1702"
version = "0.4.2"
dependencies = [
"base64 0.21.5",
"bytes",
"lber",
"nom",
"peg",
"serde",
"thiserror",
"tokio-util",
"tracing",
"uuid",
@ -4259,7 +4257,7 @@ dependencies = [
"csv",
"dialoguer",
"futures-util",
"hashbrown 0.14.2",
"hashbrown 0.14.3",
"kanidm_build_profiles",
"kanidm_client",
"kanidm_proto",
@ -4674,9 +4672,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
[[package]]
name = "proc-macro2"
version = "1.0.69"
version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
dependencies = [
"unicode-ident",
]
@ -6358,9 +6356,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.88"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce"
checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e"
dependencies = [
"cfg-if",
"serde",
@ -6370,9 +6368,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.88"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217"
checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826"
dependencies = [
"bumpalo",
"log",
@ -6385,9 +6383,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.38"
version = "0.4.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02"
checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12"
dependencies = [
"cfg-if",
"js-sys",
@ -6397,9 +6395,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.88"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2"
checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -6407,9 +6405,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.88"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907"
checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
dependencies = [
"proc-macro2",
"quote",
@ -6420,15 +6418,15 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.88"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b"
checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
[[package]]
name = "wasm-bindgen-test"
version = "0.3.38"
version = "0.3.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6433b7c56db97397842c46b67e11873eda263170afeb3a2dc74a7cb370fee0d"
checksum = "2cf9242c0d27999b831eae4767b2a146feb0b27d332d553e605864acd2afd403"
dependencies = [
"console_error_panic_hook",
"js-sys",
@ -6440,9 +6438,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-test-macro"
version = "0.3.38"
version = "0.3.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "493fcbab756bb764fa37e6bee8cec2dd709eb4273d06d0c282a5e74275ded735"
checksum = "794645f5408c9a039fd09f4d113cdfb2e7eba5ff1956b07bcf701cf4b394fe89"
dependencies = [
"proc-macro2",
"quote",
@ -6699,6 +6697,15 @@ dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.0",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
@ -6729,6 +6736,21 @@ dependencies = [
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
dependencies = [
"windows_aarch64_gnullvm 0.52.0",
"windows_aarch64_msvc 0.52.0",
"windows_i686_gnu 0.52.0",
"windows_i686_msvc 0.52.0",
"windows_x86_64_gnu 0.52.0",
"windows_x86_64_gnullvm 0.52.0",
"windows_x86_64_msvc 0.52.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.41.0"
@ -6747,6 +6769,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
[[package]]
name = "windows_aarch64_msvc"
version = "0.41.0"
@ -6765,6 +6793,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
[[package]]
name = "windows_i686_gnu"
version = "0.41.0"
@ -6783,6 +6817,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
[[package]]
name = "windows_i686_msvc"
version = "0.41.0"
@ -6801,6 +6841,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
[[package]]
name = "windows_x86_64_gnu"
version = "0.41.0"
@ -6819,6 +6865,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.41.0"
@ -6837,6 +6889,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
[[package]]
name = "windows_x86_64_msvc"
version = "0.41.0"
@ -6855,6 +6913,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "winnow"
version = "0.5.19"

View file

@ -148,8 +148,8 @@ js-sys = "^0.3.65"
kanidmd_web_ui_shared = { path = "./server/web_ui/shared" }
# REMOVE this
lazy_static = "^1.4.0"
ldap3_client = "^0.3.5"
ldap3_proto = { version = "^0.3.5", features = ["serde"] }
ldap3_client = "^0.4.2"
ldap3_proto = { version = "^0.4.2", features = ["serde"] }
libc = "^0.2.150"
libnss = "^0.4.0"

View file

@ -8,7 +8,7 @@
#![deny(clippy::await_holding_lock)]
#![deny(clippy::needless_pass_by_value)]
#![deny(clippy::trivially_copy_pass_by_ref)]
#![allow(clippy::unreachable)]
#![deny(clippy::unreachable)]
use argon2::{Algorithm, Argon2, Params, PasswordHash, Version};
use base64::engine::GeneralPurpose;
@ -27,7 +27,7 @@ use openssl::error::ErrorStack as OpenSSLErrorStack;
use openssl::hash::{self, MessageDigest};
use openssl::nid::Nid;
use openssl::pkcs5::pbkdf2_hmac;
use openssl::sha::Sha512;
use openssl::sha::{Sha1, Sha256, Sha512};
use kanidm_hsm_crypto::{HmacKey, Tpm};
@ -48,8 +48,10 @@ const PBKDF2_KEY_LEN: usize = 64;
const PBKDF2_MIN_NIST_KEY_LEN: usize = 32;
const PBKDF2_SHA1_MIN_KEY_LEN: usize = 19;
const DS_SSHA512_SALT_LEN: usize = 8;
const DS_SSHA512_HASH_LEN: usize = 64;
const DS_SHA_SALT_LEN: usize = 8;
const DS_SHA1_HASH_LEN: usize = 20;
const DS_SHA256_HASH_LEN: usize = 32;
const DS_SHA512_HASH_LEN: usize = 64;
// Taken from the argon2 library and rfc 9106
const ARGON2_VERSION: u32 = 19;
@ -122,6 +124,11 @@ pub enum DbPasswordV1 {
PBKDF2(usize, Vec<u8>, Vec<u8>),
PBKDF2_SHA1(usize, Vec<u8>, Vec<u8>),
PBKDF2_SHA512(usize, Vec<u8>, Vec<u8>),
SHA1(Vec<u8>),
SSHA1(Vec<u8>, Vec<u8>),
SHA256(Vec<u8>),
SSHA256(Vec<u8>, Vec<u8>),
SHA512(Vec<u8>),
SSHA512(Vec<u8>, Vec<u8>),
NT_MD4(Vec<u8>),
}
@ -160,6 +167,23 @@ pub enum ReplPasswordV1 {
salt: Base64UrlSafeData,
hash: Base64UrlSafeData,
},
SHA1 {
hash: Base64UrlSafeData,
},
SSHA1 {
salt: Base64UrlSafeData,
hash: Base64UrlSafeData,
},
SHA256 {
hash: Base64UrlSafeData,
},
SSHA256 {
salt: Base64UrlSafeData,
hash: Base64UrlSafeData,
},
SHA512 {
hash: Base64UrlSafeData,
},
SSHA512 {
salt: Base64UrlSafeData,
hash: Base64UrlSafeData,
@ -177,6 +201,11 @@ impl fmt::Debug for DbPasswordV1 {
DbPasswordV1::PBKDF2(_, _, _) => write!(f, "PBKDF2"),
DbPasswordV1::PBKDF2_SHA1(_, _, _) => write!(f, "PBKDF2_SHA1"),
DbPasswordV1::PBKDF2_SHA512(_, _, _) => write!(f, "PBKDF2_SHA512"),
DbPasswordV1::SHA1(_) => write!(f, "SHA1"),
DbPasswordV1::SSHA1(_, _) => write!(f, "SSHA1"),
DbPasswordV1::SHA256(_) => write!(f, "SHA256"),
DbPasswordV1::SSHA256(_, _) => write!(f, "SSHA256"),
DbPasswordV1::SHA512(_) => write!(f, "SHA512"),
DbPasswordV1::SSHA512(_, _) => write!(f, "SSHA512"),
DbPasswordV1::NT_MD4(_) => write!(f, "NT_MD4"),
}
@ -386,6 +415,11 @@ enum Kdf {
// cost, salt, hash
PBKDF2_SHA512(usize, Vec<u8>, Vec<u8>),
// salt hash
SHA1(Vec<u8>),
SSHA1(Vec<u8>, Vec<u8>),
SHA256(Vec<u8>),
SSHA256(Vec<u8>, Vec<u8>),
SHA512(Vec<u8>),
SSHA512(Vec<u8>, Vec<u8>),
// hash
NT_MD4(Vec<u8>),
@ -430,6 +464,21 @@ impl TryFrom<DbPasswordV1> for Password {
DbPasswordV1::PBKDF2_SHA512(c, s, h) => Ok(Password {
material: Kdf::PBKDF2_SHA512(c, s, h),
}),
DbPasswordV1::SHA1(h) => Ok(Password {
material: Kdf::SHA1(h),
}),
DbPasswordV1::SSHA1(s, h) => Ok(Password {
material: Kdf::SSHA1(s, h),
}),
DbPasswordV1::SHA256(h) => Ok(Password {
material: Kdf::SHA256(h),
}),
DbPasswordV1::SSHA256(s, h) => Ok(Password {
material: Kdf::SSHA256(s, h),
}),
DbPasswordV1::SHA512(h) => Ok(Password {
material: Kdf::SHA512(h),
}),
DbPasswordV1::SSHA512(s, h) => Ok(Password {
material: Kdf::SSHA512(s, h),
}),
@ -488,6 +537,21 @@ impl TryFrom<&ReplPasswordV1> for Password {
ReplPasswordV1::PBKDF2_SHA512 { cost, salt, hash } => Ok(Password {
material: Kdf::PBKDF2_SHA512(*cost, salt.0.clone(), hash.0.clone()),
}),
ReplPasswordV1::SHA1 { hash } => Ok(Password {
material: Kdf::SHA1(hash.0.clone()),
}),
ReplPasswordV1::SSHA1 { salt, hash } => Ok(Password {
material: Kdf::SSHA1(salt.0.clone(), hash.0.clone()),
}),
ReplPasswordV1::SHA256 { hash } => Ok(Password {
material: Kdf::SHA256(hash.0.clone()),
}),
ReplPasswordV1::SSHA256 { salt, hash } => Ok(Password {
material: Kdf::SSHA256(salt.0.clone(), hash.0.clone()),
}),
ReplPasswordV1::SHA512 { hash } => Ok(Password {
material: Kdf::SHA512(hash.0.clone()),
}),
ReplPasswordV1::SSHA512 { salt, hash } => Ok(Password {
material: Kdf::SSHA512(salt.0.clone(), hash.0.clone()),
}),
@ -556,12 +620,14 @@ impl TryFrom<&str> for Password {
let nt_md4 = match value.split_once(' ') {
Some((_, v)) => v,
None => {
unreachable!();
return Err(());
}
};
let h = base64::engine::general_purpose::STANDARD_NO_PAD
// Great work.
let h = base64::engine::general_purpose::URL_SAFE_NO_PAD
.decode(nt_md4)
.or_else(|_| base64::engine::general_purpose::URL_SAFE.decode(nt_md4))
.map_err(|_| ())?;
return Ok(Password {
@ -573,7 +639,7 @@ impl TryFrom<&str> for Password {
let nt_md4 = match value.split_once(' ') {
Some((_, v)) => v,
None => {
unreachable!();
return Err(());
}
};
@ -584,12 +650,71 @@ impl TryFrom<&str> for Password {
}
// Test 389ds formats
if let Some(ds_ssha1) = value.strip_prefix("{SHA}") {
let h = general_purpose::STANDARD.decode(ds_ssha1).map_err(|_| ())?;
if h.len() != DS_SHA1_HASH_LEN {
return Err(());
}
return Ok(Password {
material: Kdf::SHA1(h.to_vec()),
});
}
if let Some(ds_ssha1) = value.strip_prefix("{SSHA}") {
let sh = general_purpose::STANDARD.decode(ds_ssha1).map_err(|_| ())?;
let (h, s) = sh.split_at(DS_SHA1_HASH_LEN);
if s.len() != DS_SHA_SALT_LEN {
return Err(());
}
return Ok(Password {
material: Kdf::SSHA1(s.to_vec(), h.to_vec()),
});
}
if let Some(ds_ssha256) = value.strip_prefix("{SHA256}") {
let h = general_purpose::STANDARD
.decode(ds_ssha256)
.map_err(|_| ())?;
if h.len() != DS_SHA256_HASH_LEN {
return Err(());
}
return Ok(Password {
material: Kdf::SHA256(h.to_vec()),
});
}
if let Some(ds_ssha256) = value.strip_prefix("{SSHA256}") {
let sh = general_purpose::STANDARD
.decode(ds_ssha256)
.map_err(|_| ())?;
let (h, s) = sh.split_at(DS_SHA256_HASH_LEN);
if s.len() != DS_SHA_SALT_LEN {
return Err(());
}
return Ok(Password {
material: Kdf::SSHA256(s.to_vec(), h.to_vec()),
});
}
if let Some(ds_ssha512) = value.strip_prefix("{SHA512}") {
let h = general_purpose::STANDARD
.decode(ds_ssha512)
.map_err(|_| ())?;
if h.len() != DS_SHA512_HASH_LEN {
return Err(());
}
return Ok(Password {
material: Kdf::SHA512(h.to_vec()),
});
}
if let Some(ds_ssha512) = value.strip_prefix("{SSHA512}") {
let sh = general_purpose::STANDARD
.decode(ds_ssha512)
.map_err(|_| ())?;
let (h, s) = sh.split_at(DS_SSHA512_HASH_LEN);
if s.len() != DS_SSHA512_SALT_LEN {
let (h, s) = sh.split_at(DS_SHA512_HASH_LEN);
if s.len() != DS_SHA_SALT_LEN {
return Err(());
}
return Ok(Password {
@ -606,7 +731,7 @@ impl TryFrom<&str> for Password {
let ol_pbkdf2 = match value.split_once('}') {
Some((_, v)) => v,
None => {
unreachable!();
return Err(());
}
};
@ -663,7 +788,7 @@ impl TryFrom<&str> for Password {
}
// Should be no way to get here!
unreachable!();
return Err(());
} else {
warn!("oldap pbkdf2 found but invalid number of elements?");
}
@ -1025,6 +1150,38 @@ impl Password {
})
.map_err(|e| e.into())
}
(Kdf::SHA1(key), _) => {
let mut hasher = Sha1::new();
hasher.update(cleartext.as_bytes());
let r = hasher.finish();
Ok(key == &(r.to_vec()))
}
(Kdf::SSHA1(salt, key), _) => {
let mut hasher = Sha1::new();
hasher.update(cleartext.as_bytes());
hasher.update(salt);
let r = hasher.finish();
Ok(key == &(r.to_vec()))
}
(Kdf::SHA256(key), _) => {
let mut hasher = Sha256::new();
hasher.update(cleartext.as_bytes());
let r = hasher.finish();
Ok(key == &(r.to_vec()))
}
(Kdf::SSHA256(salt, key), _) => {
let mut hasher = Sha256::new();
hasher.update(cleartext.as_bytes());
hasher.update(salt);
let r = hasher.finish();
Ok(key == &(r.to_vec()))
}
(Kdf::SHA512(key), _) => {
let mut hasher = Sha512::new();
hasher.update(cleartext.as_bytes());
let r = hasher.finish();
Ok(key == &(r.to_vec()))
}
(Kdf::SSHA512(salt, key), _) => {
let mut hasher = Sha512::new();
hasher.update(cleartext.as_bytes());
@ -1099,6 +1256,11 @@ impl Password {
Kdf::PBKDF2_SHA512(cost, salt, hash) => {
DbPasswordV1::PBKDF2_SHA512(*cost, salt.clone(), hash.clone())
}
Kdf::SHA1(hash) => DbPasswordV1::SHA1(hash.clone()),
Kdf::SSHA1(salt, hash) => DbPasswordV1::SSHA1(salt.clone(), hash.clone()),
Kdf::SHA256(hash) => DbPasswordV1::SHA256(hash.clone()),
Kdf::SSHA256(salt, hash) => DbPasswordV1::SSHA256(salt.clone(), hash.clone()),
Kdf::SHA512(hash) => DbPasswordV1::SHA512(hash.clone()),
Kdf::SSHA512(salt, hash) => DbPasswordV1::SSHA512(salt.clone(), hash.clone()),
Kdf::NT_MD4(hash) => DbPasswordV1::NT_MD4(hash.clone()),
}
@ -1151,6 +1313,23 @@ impl Password {
salt: salt.clone().into(),
hash: hash.clone().into(),
},
Kdf::SHA1(hash) => ReplPasswordV1::SHA1 {
hash: hash.clone().into(),
},
Kdf::SSHA1(salt, hash) => ReplPasswordV1::SSHA1 {
salt: salt.clone().into(),
hash: hash.clone().into(),
},
Kdf::SHA256(hash) => ReplPasswordV1::SHA256 {
hash: hash.clone().into(),
},
Kdf::SSHA256(salt, hash) => ReplPasswordV1::SSHA256 {
salt: salt.clone().into(),
hash: hash.clone().into(),
},
Kdf::SHA512(hash) => ReplPasswordV1::SHA512 {
hash: hash.clone().into(),
},
Kdf::SSHA512(salt, hash) => ReplPasswordV1::SSHA512 {
salt: salt.clone().into(),
hash: hash.clone().into(),
@ -1187,6 +1366,11 @@ impl Password {
Kdf::PBKDF2(_, _, _)
| Kdf::PBKDF2_SHA512(_, _, _)
| Kdf::PBKDF2_SHA1(_, _, _)
| Kdf::SHA1(_)
| Kdf::SSHA1(_, _)
| Kdf::SHA256(_)
| Kdf::SSHA256(_, _)
| Kdf::SHA512(_)
| Kdf::SSHA512(_, _)
| Kdf::NT_MD4(_) => true,
}
@ -1243,6 +1427,56 @@ mod tests {
assert!(r.verify(password).unwrap_or(false));
}
#[test]
fn test_password_from_ds_sha1() {
let im_pw = "{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=";
let password = "password";
let r = Password::try_from(im_pw).expect("Failed to parse");
// Known weak, require upgrade.
assert!(r.requires_upgrade());
assert!(r.verify(password).unwrap_or(false));
}
#[test]
fn test_password_from_ds_ssha1() {
let im_pw = "{SSHA}EyzbBiP4u4zxOrLpKTORI/RX3HC6TCTJtnVOCQ==";
let password = "password";
let r = Password::try_from(im_pw).expect("Failed to parse");
// Known weak, require upgrade.
assert!(r.requires_upgrade());
assert!(r.verify(password).unwrap_or(false));
}
#[test]
fn test_password_from_ds_sha256() {
let im_pw = "{SHA256}XohImNooBHFR0OVvjcYpJ3NgPQ1qq73WKhHvch0VQtg=";
let password = "password";
let r = Password::try_from(im_pw).expect("Failed to parse");
// Known weak, require upgrade.
assert!(r.requires_upgrade());
assert!(r.verify(password).unwrap_or(false));
}
#[test]
fn test_password_from_ds_ssha256() {
let im_pw = "{SSHA256}luYWfFJOZgxySTsJXHgIaCYww4yMpu6yest69j/wO5n5OycuHFV/GQ==";
let password = "password";
let r = Password::try_from(im_pw).expect("Failed to parse");
// Known weak, require upgrade.
assert!(r.requires_upgrade());
assert!(r.verify(password).unwrap_or(false));
}
#[test]
fn test_password_from_ds_sha512() {
let im_pw = "{SHA512}sQnzu7wkTrgkQZF+0G1hi5AI3Qmzvv0bXgc5THBqi7mAsdd4Xll27ASbRt9fEyavWi6m0QP9B8lThf+rDKy8hg==";
let password = "password";
let r = Password::try_from(im_pw).expect("Failed to parse");
// Known weak, require upgrade.
assert!(r.requires_upgrade());
assert!(r.verify(password).unwrap_or(false));
}
#[test]
fn test_password_from_ds_ssha512() {
let im_pw = "{SSHA512}JwrSUHkI7FTAfHRVR6KoFlSN0E3dmaQWARjZ+/UsShYlENOqDtFVU77HJLLrY2MuSp0jve52+pwtdVl2QUAHukQ0XUf5LDtM";
@ -1343,6 +1577,9 @@ mod tests {
}
}
}
let im_pw = "ipaNTHash: pS43DjQLcUYhaNF_cd_Vhw==";
Password::try_from(im_pw).expect("Failed to parse");
}
#[test]

View file

@ -114,6 +114,7 @@ pub const ATTR_MUST: &str = "must";
pub const ATTR_NAME_HISTORY: &str = "name_history";
pub const ATTR_NAME: &str = "name";
pub const ATTR_NO_INDEX: &str = "no-index";
pub const ATTR_NSACCOUNTLOCK: &str = "nsaccountlock";
pub const ATTR_NSUNIQUEID: &str = "nsuniqueid";
pub const ATTR_OAUTH2_ALLOW_INSECURE_CLIENT_DISABLE_PKCE: &str =
@ -183,7 +184,7 @@ pub const OAUTH2_SCOPE_READ: &str = "read";
pub const OAUTH2_SCOPE_SUPPLEMENT: &str = "supplement";
pub const LDAP_ATTR_CN: &str = "cn";
pub const LDAP_ATTR_DISPLAY_NAME: &str = "displayName";
pub const LDAP_ATTR_DISPLAY_NAME: &str = "displayname";
pub const LDAP_ATTR_EMAIL_ALTERNATIVE: &str = "emailalternative";
pub const LDAP_ATTR_EMAIL_PRIMARY: &str = "emailprimary";
pub const LDAP_ATTR_ENTRYDN: &str = "entrydn";
@ -195,7 +196,7 @@ pub const LDAP_ATTR_MAIL_PRIMARY: &str = "mail;primary";
pub const LDAP_ATTR_MAIL: &str = "mail";
pub const LDAP_ATTR_MEMBER: &str = "member";
pub const LDAP_ATTR_NAME: &str = "name";
pub const LDAP_ATTR_OBJECTCLASS: &str = "objectClass";
pub const LDAP_ATTR_OBJECTCLASS: &str = "objectclass";
pub const LDAP_ATTR_OU: &str = "ou";
pub const LDAP_ATTR_UID: &str = "uid";
pub const LDAP_CLASS_GROUPOFNAMES: &str = "groupofnames";

View file

@ -9,8 +9,8 @@ pub use scim_proto::user::MultiValueAttr;
use scim_proto::*;
use crate::constants::{
ATTR_DESCRIPTION, ATTR_DISPLAYNAME, ATTR_GIDNUMBER, ATTR_LOGINSHELL, ATTR_MAIL, ATTR_MEMBER,
ATTR_NAME, ATTR_SSH_PUBLICKEY,
ATTR_ACCOUNT_EXPIRE, ATTR_ACCOUNT_VALID_FROM, ATTR_DESCRIPTION, ATTR_DISPLAYNAME,
ATTR_GIDNUMBER, ATTR_LOGINSHELL, ATTR_MAIL, ATTR_MEMBER, ATTR_NAME, ATTR_SSH_PUBLICKEY,
};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, ToSchema)]
@ -142,6 +142,8 @@ pub struct ScimSyncPerson {
pub login_shell: Option<String>,
pub mail: Vec<MultiValueAttr>,
pub ssh_publickey: Vec<ScimSshPubKey>,
pub account_valid_from: Option<String>,
pub account_expire: Option<String>,
}
// Need to allow this because clippy is broken and doesn't realise scimentry is out of crate
@ -160,6 +162,8 @@ impl Into<ScimEntry> for ScimSyncPerson {
login_shell,
mail,
ssh_publickey,
account_valid_from,
account_expire,
} = self;
let schemas = if gidnumber.is_some() {
@ -185,6 +189,8 @@ impl Into<ScimEntry> for ScimSyncPerson {
set_option_string!(attrs, ATTR_LOGINSHELL, login_shell);
set_multi_complex!(attrs, ATTR_MAIL, mail);
set_multi_complex!(attrs, ATTR_SSH_PUBLICKEY, ssh_publickey); // with the underscore
set_option_string!(attrs, ATTR_ACCOUNT_EXPIRE, account_expire);
set_option_string!(attrs, ATTR_ACCOUNT_VALID_FROM, account_valid_from);
ScimEntry {
schemas,

View file

@ -68,8 +68,8 @@ async fn client_process(
return;
};
let (r, w) = tokio::io::split(tlsstream);
let mut r = FramedRead::new(r, LdapCodec);
let mut w = FramedWrite::new(w, LdapCodec);
let mut r = FramedRead::new(r, LdapCodec::default());
let mut w = FramedWrite::new(w, LdapCodec::default());
// This is a connected client session. we need to associate some state to the session
let mut session = LdapSession::new();

View file

@ -115,6 +115,7 @@ pub enum Attribute {
NameHistory,
NoIndex,
NsUniqueId,
NsAccountLock,
OAuth2AllowInsecureClientDisablePkce,
OAuth2ConsentScopeMap,
OAuth2JwtLegacyCryptoEnable,
@ -295,6 +296,7 @@ impl TryFrom<String> for Attribute {
ATTR_NAME_HISTORY => Attribute::NameHistory,
ATTR_NO_INDEX => Attribute::NoIndex,
ATTR_NSUNIQUEID => Attribute::NsUniqueId,
ATTR_NSACCOUNTLOCK => Attribute::NsAccountLock,
ATTR_OAUTH2_ALLOW_INSECURE_CLIENT_DISABLE_PKCE => {
Attribute::OAuth2AllowInsecureClientDisablePkce
}
@ -452,6 +454,7 @@ impl From<Attribute> for &'static str {
Attribute::NameHistory => ATTR_NAME_HISTORY,
Attribute::NoIndex => ATTR_NO_INDEX,
Attribute::NsUniqueId => ATTR_NSUNIQUEID,
Attribute::NsAccountLock => ATTR_NSACCOUNTLOCK,
Attribute::OAuth2AllowInsecureClientDisablePkce => {
ATTR_OAUTH2_ALLOW_INSECURE_CLIENT_DISABLE_PKCE
}

View file

@ -273,7 +273,7 @@ pub static ref SCHEMA_ATTR_NSUNIQUEID: SchemaAttribute = SchemaAttribute {
pub static ref SCHEMA_ATTR_ACCOUNT_EXPIRE: SchemaAttribute = SchemaAttribute {
uuid: UUID_SCHEMA_ATTR_ACCOUNT_EXPIRE,
name: Attribute::AccountExpire.into(),
description: "The datetime after which this accounnt no longer may authenticate.to_string().".to_string(),
description: "The datetime after which this account no longer may authenticate.to_string().".to_string(),
sync_allowed: true,
syntax: SyntaxType::DateTime,

View file

@ -1119,6 +1119,20 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
}
Ok(vs)
}
(
SyntaxType::DateTime,
false,
ScimAttr::SingleSimple(ScimSimpleAttr::String(value)),
) => {
Value::new_datetime_s(value)
.map(|v| vec![v])
.ok_or_else(|| {
error!("Invalid value attribute - must be scim simple string with rfc3339 formatted datetime");
OperationError::InvalidAttribute(format!(
"value must be scim simple string with rfc3339 formatted datetime - {scim_attr_name}"
))
})
}
(syn, mv, sa) => {
error!(?syn, ?mv, ?sa, "Unsupported scim attribute conversion. This may be a syntax error in your import, or a missing feature in Kanidm.");
Err(OperationError::InvalidAttribute(format!(
@ -3287,7 +3301,9 @@ mod tests {
"gidnumber": 12345,
"loginshell": "/bin/sh",
"name": "testuser",
"password_import": "ipaNTHash: iEb36u6PsRetBr3YMLdYbA"
"password_import": "ipaNTHash: iEb36u6PsRetBr3YMLdYbA",
"account_valid_from": "2021-11-28T04:57:55Z",
"account_expire": "2023-11-28T04:57:55Z"
},
{
"schemas": [

View file

@ -235,7 +235,7 @@ impl SchemaAttribute {
if r {
Ok(())
} else {
trace!(
error!(
?a,
?self,
?v,
@ -288,7 +288,7 @@ impl SchemaAttribute {
if r {
Ok(())
} else {
trace!(
error!(
?a,
?self,
?v,
@ -311,10 +311,9 @@ impl SchemaAttribute {
if valid && ava.validate(self) {
Ok(())
} else {
admin_error!(
error!(
?a,
"validate_ava - InvalidAttributeSyntax for {:?}",
self.syntax
"validate_ava - InvalidAttributeSyntax for {:?}", self.syntax
);
Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
}

View file

@ -1880,6 +1880,8 @@ impl Value {
"value contains invalid whitespace chars forbidden by \"{}\"",
*SINGLELINE_RE
);
// Trace only, could be an injection attack of some kind.
trace!(?s, "Invalid whitespace");
false
}
}
@ -1888,6 +1890,8 @@ impl Value {
// Look for and prevent certain types of string escapes and injections.
if UNICODE_CONTROL_RE.is_match(s) {
error!("value contains invalid unicode control character",);
// Trace only, could be an injection attack of some kind.
trace!(?s, "Invalid Uncode Control");
false
} else {
true

View file

@ -361,7 +361,6 @@ async fn run_sync(
// LdapFilter::Equality(LDAP_ATTR_OBJECTCLASS.into(), "domain".to_string()),
LdapFilter::And(vec![
LdapFilter::Equality(LDAP_ATTR_OBJECTCLASS.into(), "person".to_string()),
LdapFilter::Equality(LDAP_ATTR_OBJECTCLASS.into(), "ipantuserattrs".to_string()),
LdapFilter::Equality(LDAP_ATTR_OBJECTCLASS.into(), "posixaccount".to_string()),
]),
LdapFilter::And(vec![
@ -382,6 +381,15 @@ async fn run_sync(
LDAP_ATTR_CN.into(),
"ipausers".to_string(),
))),
// Ignore editors/trust admins
LdapFilter::Not(Box::new(LdapFilter::Equality(
LDAP_ATTR_CN.into(),
"editors".to_string(),
))),
LdapFilter::Not(Box::new(LdapFilter::Equality(
LDAP_ATTR_CN.into(),
"trust admins".to_string(),
))),
]),
// Fetch TOTP's so we know when/if they change.
LdapFilter::And(vec![
@ -785,6 +793,7 @@ fn ipa_to_scim_entry(
.attrs
.get(LDAP_ATTR_OBJECTCLASS)
.ok_or_else(|| {
debug!(?sync_entry);
error!("Invalid entry - no object class {}", dn);
})?;
@ -823,14 +832,16 @@ fn ipa_to_scim_entry(
error!("Missing required attribute {}", Attribute::Cn);
})?;
// There are some installs that incorrectly assign this to a shared
// group.
let gidnumber = if let Some(number) = entry_config.map_gidnumber {
Some(number)
} else {
entry
.remove_ava_single(Attribute::GidNumber.as_ref())
.map(|gid| {
u32::from_str(&gid).map_err(|_| {
error!("Invalid {}", Attribute::GidNumber);
.remove_ava_single(Attribute::UidNumber.as_ref())
.map(|uid| {
u32::from_str(&uid).map_err(|_| {
error!("Invalid {}", Attribute::UidNumber);
})
})
.transpose()?
@ -887,6 +898,23 @@ fn ipa_to_scim_entry(
})
.unwrap_or_default();
let account_disabled: bool = entry
.remove_ava(Attribute::NsAccountLock.as_ref())
.map(|set| {
set.into_iter()
.any(|value| value != "FALSE" && value != "false")
})
.unwrap_or_default();
// Account is not valid
let account_expire = if account_disabled {
Some(chrono::DateTime::UNIX_EPOCH.to_rfc3339())
} else {
None
};
let account_valid_from = None;
let login_shell = entry.remove_ava_single(Attribute::LoginShell.as_ref());
let external_id = Some(entry.dn);
@ -902,6 +930,8 @@ fn ipa_to_scim_entry(
login_shell,
mail,
ssh_publickey,
account_expire,
account_valid_from,
}
.into(),
))

View file

@ -20,6 +20,7 @@ use chrono::Utc;
use clap::Parser;
use cron::Schedule;
use kanidm_proto::constants::ATTR_OBJECTCLASS;
use kanidmd_lib::prelude::Attribute;
use std::fs::metadata;
use std::fs::File;
use std::io::Read;
@ -582,6 +583,22 @@ fn ldap_to_scim_entry(
})
.unwrap_or_default();
let account_disabled: bool = entry
.remove_ava(Attribute::NsAccountLock.as_ref())
.map(|set| {
set.into_iter()
.any(|value| value != "FALSE" && value != "false")
})
.unwrap_or_default();
// Account is not valid
let account_expire = if account_disabled {
Some(chrono::DateTime::UNIX_EPOCH.to_rfc3339())
} else {
None
};
let account_valid_from = None;
let login_shell = entry.remove_ava_single(&sync_config.person_attr_login_shell);
let external_id = Some(entry.dn);
@ -597,6 +614,8 @@ fn ldap_to_scim_entry(
login_shell,
mail,
ssh_publickey,
account_expire,
account_valid_from,
}
.into(),
))

View file

@ -110,7 +110,7 @@ impl LdapClient {
error!("Failed to initialise TLS -> {:?}", e);
})?;
let mut framed = Framed::new(tlsstream, LdapCodec);
let mut framed = Framed::new(tlsstream, LdapCodec::default());
framed.send(msg).await.map_err(|e| {
error!("Unable to bind -> {:?}", e);