Skip to content

Commit 5069708

Browse files
authored
Merge pull request #242 from himmelblau-idm/stable-0.6.x_hello_pin_hardening
Hello PIN length and offline fixes
2 parents 6320c21 + b08e336 commit 5069708

File tree

8 files changed

+95
-20
lines changed

8 files changed

+95
-20
lines changed

Cargo.toml

+3-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ members = [
1616
resolver = "2"
1717

1818
[workspace.package]
19-
version = "0.6.9"
19+
version = "0.6.10"
2020
authors = [
2121
"David Mulder <[email protected]>"
2222
]
@@ -37,7 +37,7 @@ tracing-subscriber = "^0.3.17"
3737
tracing = "^0.1.37"
3838
himmelblau_unix_common = { path = "src/common" }
3939
kanidm_unix_common = { path = "src/glue" }
40-
libhimmelblau = { version = "0.3.4" }
40+
libhimmelblau = { version = "0.3.5" }
4141
clap = { version = "^4.5", features = ["derive", "env"] }
4242
clap_complete = "^4.4.1"
4343
reqwest = { version = "^0.12.2", features = ["json"] }
@@ -76,7 +76,7 @@ tracing-forest = "^0.1.6"
7676
rusqlite = "^0.32.0"
7777
hashbrown = { version = "0.14.0", features = ["serde", "inline-more", "ahash"] }
7878
lru = "^0.12.3"
79-
kanidm_lib_crypto = { path = "./src/crypto", version = "0.6.9" }
79+
kanidm_lib_crypto = { path = "./src/crypto", version = "0.6.10" }
8080
kanidm_utils_users = { path = "./src/users" }
8181
walkdir = "2"
8282
csv = "1.2.2"

src/cli/src/main.rs

+11-4
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use std::time::Duration;
3131
include!("./opt/tool.rs");
3232

3333
macro_rules! match_sm_auth_client_response {
34-
($expr:expr, $req:ident, $($pat:pat => $result:expr),*) => {
34+
($expr:expr, $req:ident, $hello_pin_min_length:ident, $($pat:pat => $result:expr),*) => {
3535
match $expr {
3636
Ok(r) => match r {
3737
$($pat => $result),*
@@ -56,7 +56,13 @@ macro_rules! match_sm_auth_client_response {
5656
let mut confirm;
5757
loop {
5858
pin = match prompt_password("New PIN: ") {
59-
Ok(password) => password,
59+
Ok(password) => {
60+
if password.len() < $hello_pin_min_length {
61+
println!("Chosen pin is too short! {} chars required.", $hello_pin_min_length);
62+
continue;
63+
}
64+
password
65+
},
6066
Err(err) => {
6167
println!("unable to get pin: {:?}", err);
6268
return ExitCode::FAILURE;
@@ -154,10 +160,11 @@ async fn main() -> ExitCode {
154160
return ExitCode::FAILURE;
155161
}
156162
};
163+
let pin_min_len = cfg.get_hello_pin_min_length();
157164

158165
let mut req = ClientRequest::PamAuthenticateInit(account_id.clone());
159166
loop {
160-
match_sm_auth_client_response!(daemon_client.call_and_wait(&req, timeout), req,
167+
match_sm_auth_client_response!(daemon_client.call_and_wait(&req, timeout), req, pin_min_len,
161168
ClientResponse::PamAuthenticateStepResponse(PamAuthResponse::Password) => {
162169
// Prompt for and get the password
163170
let cred = match prompt_password("Password: ") {
@@ -210,7 +217,7 @@ async fn main() -> ExitCode {
210217
// will shutdown. This allows the resolver to dynamically extend the
211218
// timeout if needed, and removes logic from the front end.
212219
match_sm_auth_client_response!(
213-
daemon_client.call_and_wait(&req, timeout), req,
220+
daemon_client.call_and_wait(&req, timeout), req, pin_min_len,
214221
ClientResponse::PamAuthenticateStepResponse(
215222
PamAuthResponse::MFAPollWait,
216223
) => {

src/common/src/config.rs

+33-5
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ use std::path::PathBuf;
55
use tracing::{debug, error};
66

77
use crate::constants::{
8-
BROKER_APP_ID, CN_NAME_MAPPING, DEFAULT_CACHE_TIMEOUT, DEFAULT_CONFIG_PATH,
9-
DEFAULT_CONN_TIMEOUT, DEFAULT_DB_PATH, DEFAULT_HELLO_ENABLED, DEFAULT_HOME_ALIAS,
10-
DEFAULT_HOME_ATTR, DEFAULT_HOME_PREFIX, DEFAULT_HSM_PIN_PATH, DEFAULT_ID_ATTR_MAP,
11-
DEFAULT_ODC_PROVIDER, DEFAULT_SELINUX, DEFAULT_SFA_FALLBACK_ENABLED, DEFAULT_SHELL,
12-
DEFAULT_SOCK_PATH, DEFAULT_TASK_SOCK_PATH, DEFAULT_USE_ETC_SKEL, SERVER_CONFIG_PATH,
8+
BROKER_APP_ID, CN_NAME_MAPPING, DEFAULT_AUTHORITY_HOST, DEFAULT_CACHE_TIMEOUT,
9+
DEFAULT_CONFIG_PATH, DEFAULT_CONN_TIMEOUT, DEFAULT_DB_PATH, DEFAULT_HELLO_ENABLED,
10+
DEFAULT_HELLO_PIN_MIN_LEN, DEFAULT_HOME_ALIAS, DEFAULT_HOME_ATTR, DEFAULT_HOME_PREFIX,
11+
DEFAULT_HSM_PIN_PATH, DEFAULT_ID_ATTR_MAP, DEFAULT_ODC_PROVIDER, DEFAULT_SELINUX,
12+
DEFAULT_SFA_FALLBACK_ENABLED, DEFAULT_SHELL, DEFAULT_SOCK_PATH, DEFAULT_TASK_SOCK_PATH,
13+
DEFAULT_USE_ETC_SKEL, SERVER_CONFIG_PATH,
1314
};
1415
use crate::unix_config::{HomeAttr, HsmType};
1516
use idmap::DEFAULT_IDMAP_RANGE;
@@ -401,6 +402,33 @@ impl HimmelblauConfig {
401402
CN_NAME_MAPPING,
402403
)
403404
}
405+
406+
pub fn get_hello_pin_min_length(&self) -> usize {
407+
match self.config.get("global", "hello_pin_min_length") {
408+
Some(val) => match val.parse::<usize>() {
409+
Ok(n) => n,
410+
Err(_) => {
411+
error!("Failed parsing hello_pin_min_length from config: {}", val);
412+
DEFAULT_HELLO_PIN_MIN_LEN
413+
}
414+
},
415+
None => DEFAULT_HELLO_PIN_MIN_LEN,
416+
}
417+
}
418+
419+
pub fn get_authority_host(&self, domain: &str) -> String {
420+
match self.config.get(domain, "authority_host") {
421+
Some(val) => val,
422+
None => {
423+
debug!("authority_host unset, using defaults");
424+
String::from(DEFAULT_AUTHORITY_HOST)
425+
}
426+
}
427+
}
428+
429+
pub fn get_tenant_id(&self, domain: &str) -> Option<String> {
430+
self.config.get(domain, "tenant_id")
431+
}
404432
}
405433

406434
impl fmt::Debug for HimmelblauConfig {

src/common/src/constants.rs

+1
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ pub const DEFAULT_ID_ATTR_MAP: IdAttr = IdAttr::Name;
2626
pub const BROKER_APP_ID: &str = "29d9ed98-a469-4536-ade2-f981bc1d605e";
2727
pub const BROKER_CLIENT_IDENT: &str = "38aa3b87-a06d-4817-b275-7a316988d93b";
2828
pub const CN_NAME_MAPPING: bool = true;
29+
pub const DEFAULT_HELLO_PIN_MIN_LEN: usize = 6;

src/common/src/idprovider/himmelblau.rs

+18-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use super::interface::{
55
use crate::config::split_username;
66
use crate::config::HimmelblauConfig;
77
use crate::config::IdAttr;
8+
use crate::constants::DEFAULT_GRAPH;
89
use crate::db::KeyStoreTxn;
910
use crate::idprovider::interface::tpm;
1011
use crate::unix_proto::PamAuthRequest;
@@ -92,9 +93,23 @@ impl HimmelblauMultiProvider {
9293
debug!("Adding provider for domain {}", domain);
9394
let range = cfg.get_idmap_range(&domain);
9495
let mut idmap_lk = idmap.write().await;
95-
let graph = Graph::new(&cfg.get_odc_provider(&domain), &domain)
96-
.await
97-
.map_err(|e| anyhow!("{:?}", e))?;
96+
let authority_host = cfg.get_authority_host(&domain);
97+
let tenant_id = cfg.get_tenant_id(&domain);
98+
let graph = match Graph::new(
99+
&cfg.get_odc_provider(&domain),
100+
&domain,
101+
Some(&authority_host),
102+
tenant_id.as_deref(),
103+
Some(DEFAULT_GRAPH),
104+
)
105+
.await
106+
{
107+
Ok(graph) => graph,
108+
Err(e) => {
109+
error!("Failed initializing provider: {:?}", e);
110+
continue;
111+
}
112+
};
98113
let authority_host = graph.authority_host();
99114
let tenant_id = graph.tenant_id();
100115
idmap_lk

src/config/himmelblau.conf.example

+5
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@
3232
# when the host is public facing (such as via SSH).
3333
# enable_hello = true
3434
#
35+
# The minimum length of the Hello authentication PIN. This PIN length cannot
36+
# be less than 6, and cannot exceed 32 characters. These are hard requirements
37+
# for the encryption algorithm.
38+
# hello_pin_min_length = 6
39+
#
3540
# Whether to permit attempting a SFA (password only) authentication when MFA
3641
# methods are unavailable. Sometimes this is possible when MFA has yet to be
3742
# configured. This is disabled by default.

src/glue/src/unix_config.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use himmelblau_unix_common::config::HimmelblauConfig;
2-
use himmelblau_unix_common::constants::{DEFAULT_CONN_TIMEOUT, DEFAULT_SOCK_PATH};
2+
use himmelblau_unix_common::constants::{
3+
DEFAULT_CONN_TIMEOUT, DEFAULT_HELLO_PIN_MIN_LEN, DEFAULT_SOCK_PATH,
4+
};
35
use himmelblau_unix_common::unix_passwd::parse_etc_passwd;
46
use std::fs::File;
57
use std::io::Read;
@@ -9,6 +11,7 @@ pub struct KanidmUnixdConfig {
911
pub unix_sock_timeout: u64,
1012
pub sock_path: String,
1113
pub cn_name_mapping: bool,
14+
pub hello_pin_min_length: usize,
1215
}
1316

1417
impl KanidmUnixdConfig {
@@ -18,6 +21,7 @@ impl KanidmUnixdConfig {
1821
sock_path: DEFAULT_SOCK_PATH.to_string(),
1922
unix_sock_timeout: DEFAULT_CONN_TIMEOUT * 2,
2023
cn_name_mapping: false,
24+
hello_pin_min_length: DEFAULT_HELLO_PIN_MIN_LEN,
2125
}
2226
}
2327

@@ -28,6 +32,7 @@ impl KanidmUnixdConfig {
2832
sock_path: config.get_socket_path(),
2933
unix_sock_timeout: config.get_connection_timeout() * 2,
3034
cn_name_mapping: config.get_cn_name_mapping(),
35+
hello_pin_min_length: config.get_hello_pin_min_length(),
3136
})
3237
}
3338

src/pam/src/pam/mod.rs

+18-4
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ pub struct PamKanidm;
112112
pam_hooks!(PamKanidm);
113113

114114
macro_rules! match_sm_auth_client_response {
115-
($expr:expr, $opts:ident, $conv:ident, $req:ident, $authtok:ident, $($pat:pat => $result:expr),*) => {
115+
($expr:expr, $opts:ident, $conv:ident, $req:ident, $authtok:ident, $cfg:ident, $($pat:pat => $result:expr),*) => {
116116
match $expr {
117117
Ok(r) => match r {
118118
$($pat => $result),*
@@ -147,7 +147,21 @@ macro_rules! match_sm_auth_client_response {
147147
loop {
148148
pin = match $conv.send(PAM_PROMPT_ECHO_OFF, "New PIN: ") {
149149
Ok(password) => match password {
150-
Some(cred) => cred,
150+
Some(cred) => {
151+
if cred.len() < $cfg.hello_pin_min_length {
152+
match $conv.send(PAM_TEXT_INFO, &format!("Chosen pin is too short! {} chars required.", $cfg.hello_pin_min_length)) {
153+
Ok(_) => {}
154+
Err(err) => {
155+
if $opts.debug {
156+
println!("Message prompt failed");
157+
}
158+
return err;
159+
}
160+
}
161+
continue;
162+
}
163+
cred
164+
},
151165
None => {
152166
debug!("no pin");
153167
return PamResultCode::PAM_CRED_INSUFFICIENT;
@@ -373,7 +387,7 @@ impl PamHooks for PamKanidm {
373387
let mut req = ClientRequest::PamAuthenticateInit(account_id);
374388

375389
loop {
376-
match_sm_auth_client_response!(daemon_client.call_and_wait(&req, timeout), opts, conv, req, authtok,
390+
match_sm_auth_client_response!(daemon_client.call_and_wait(&req, timeout), opts, conv, req, authtok, cfg,
377391
ClientResponse::PamAuthenticateStepResponse(PamAuthResponse::Password) => {
378392
let mut consume_authtok = None;
379393
// Swap the authtok out with a None, so it can only be consumed once.
@@ -468,7 +482,7 @@ impl PamHooks for PamKanidm {
468482
// will shutdown. This allows the resolver to dynamically extend the
469483
// timeout if needed, and removes logic from the front end.
470484
match_sm_auth_client_response!(
471-
daemon_client.call_and_wait(&req, timeout), opts, conv, req, authtok,
485+
daemon_client.call_and_wait(&req, timeout), opts, conv, req, authtok, cfg,
472486
ClientResponse::PamAuthenticateStepResponse(
473487
PamAuthResponse::MFAPollWait,
474488
) => {

0 commit comments

Comments
 (0)