oper: TLS client-cert fingerprint login — oper block gains an optional fp=<sha256>; password=* means cert-only. Named OperBlock struct replaces the (name,pass,level) tuple. (Password login was never broken — verified live.)

This commit is contained in:
Jean Chevronnet 2026-08-18 22:55:07 +00:00
parent 853be58d18
commit c4456cf002
6 changed files with 66 additions and 15 deletions

View file

@ -308,7 +308,7 @@ impl Command for Stats {
);
}
'o' => {
let opers: Vec<String> = s.opers.iter().map(|(n, _, _)| n.clone()).collect();
let opers: Vec<String> = s.opers.iter().map(|o| o.name.clone()).collect();
for n in opers {
s.numeric(uid, RPL_STATSOLINE, &format!("O * * {n} :oper"));
}

View file

@ -123,15 +123,37 @@ impl Command for Oper {
}
fn handle(&self, s: &mut Server, uid: Uid, params: &[String]) -> CmdResult {
let (name, pass) = (params[0].clone(), params[1].clone());
let Some((hash, level)) = s
.opers
.iter()
.find(|(n, _, _)| *n == name)
.map(|(_, p, lvl)| (p.clone(), *lvl))
else {
let Some(block) = s.opers.iter().find(|o| o.name == name).cloned() else {
s.numeric(uid, ERR_PASSWDMISMATCH, ":Password incorrect");
return CmdResult::Fail;
};
let (hash, level) = (block.password.clone(), block.level);
// fingerprint login: the block demands a specific TLS client-cert SHA-256
// fingerprint, so the user must be on a matching certificate.
if let Some(want_fp) = &block.fingerprint {
let user_fp = s.users.get(&uid).and_then(|u| u.certfp.clone());
if !user_fp
.as_deref()
.is_some_and(|f| f.eq_ignore_ascii_case(want_fp))
{
s.snotice_c(
'o',
&format!("Failed OPER for {name}: certificate fingerprint mismatch"),
);
s.numeric(
uid,
ERR_PASSWDMISMATCH,
":Password incorrect (a matching TLS client certificate is required)",
);
return CmdResult::Fail;
}
}
// `password = *` means cert-only: the fingerprint above is the whole check.
if hash == "*" {
s.oper_up(uid);
crate::modules::operlevels::set(s, uid, level);
return CmdResult::Ok;
}
// a KDF password (bcrypt / pbkdf2) is slow — verify it off the core thread
// (result arrives as OperAuth), so it can't freeze the server or be a DoS.
if crate::modules::password_hash::is_slow(&hash) {