1use smb_server_auth::crypto::{hmac_md5, nt_hash, ntlmv1_response};
5use smb_server_auth::ntlm::Type3;
6use std::collections::HashMap;
7
8#[derive(Debug, Clone)]
10pub struct AuthOutcome {
11 pub ok: bool,
13 pub guest: bool,
15 pub user: String,
17 pub session_key: Option<[u8; 16]>,
21}
22
23#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] pub fn authenticate_ntlmssp(
29 users: &HashMap<String, String>,
30 allow_guest: bool,
31 challenge: &[u8; 8],
32 t3: &Type3,
33) -> AuthOutcome {
34 let user = t3.user.trim().to_string();
35 const NO_KEY: Option<[u8; 16]> = None;
36
37 tracing::debug!(
38 user = %user,
39 domain = %t3.domain,
40 ntlm_len = t3.ntlm_response.len(),
41 lm_len = t3.lm_response.len(),
42 flags = format!("{:#010x}", t3.flags),
43 "ntlmssp authenticate"
44 );
45
46 if t3.ntlm_response.is_empty() && t3.lm_response.is_empty()
48 || t3.flags & smb_server_auth::ntlm::NEGOTIATE_ANONYMOUS != 0
49 {
50 return AuthOutcome { ok: true, guest: true, user: "nobody".into(), session_key: NO_KEY };
51 }
52
53 if users.is_empty() && allow_guest {
54 return AuthOutcome { ok: true, guest: false, user, session_key: NO_KEY };
57 }
58
59 let Some(pass) = users.get(&user.to_lowercase()).cloned() else {
60 return if allow_guest {
61 AuthOutcome { ok: true, guest: true, user, session_key: NO_KEY }
62 } else {
63 AuthOutcome { ok: false, guest: false, user, session_key: NO_KEY }
64 };
65 };
66
67 let nthash = nt_hash(&pass);
68
69 if t3.ntlm_response.len() >= 24 {
73 for identity in [
74 format!("{}{}", user.to_uppercase(), t3.domain),
75 format!("{}{}", user.to_uppercase(), ""),
76 ] {
77 let idb = utf16_le(&identity);
78 let ntv2 = hmac_md5(&nthash, &idb);
79 let mut msg = Vec::with_capacity(8 + t3.ntlm_response.len() - 16);
80 msg.extend_from_slice(challenge);
81 msg.extend_from_slice(&t3.ntlm_response[16..]);
82 let proof = hmac_md5(&ntv2, &msg);
83 if proof.as_slice() == &t3.ntlm_response[..16] {
84 let key = derive_session_key(&nthash, &idb, &proof, t3);
86 return AuthOutcome {
87 ok: true,
88 guest: false,
89 user,
90 session_key: key,
91 };
92 }
93 }
94 }
95
96 if t3.ntlm_response.len() == 24 {
99 let mut h21 = [0u8; 21];
100 h21[..16].copy_from_slice(&nthash);
101 let expect = ntlmv1_response(&h21, challenge);
102 if expect.as_slice() == &t3.ntlm_response[..24] {
103 return AuthOutcome { ok: true, guest: false, user, session_key: NO_KEY };
104 }
105 }
106
107 tracing::warn!(user = %user, domain = %t3.domain, ntlm_len = t3.ntlm_response.len(), "ntlm proof mismatch");
108 AuthOutcome { ok: false, guest: false, user, session_key: NO_KEY }
109}
110
111#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] fn derive_session_key(
118 nthash: &[u8; 16],
119 identity_utf16le: &[u8],
120 proof: &[u8],
121 t3: &Type3,
122) -> Option<[u8; 16]> {
123 use smb_server_auth::crypto::{hmac_md5, rc4};
124
125 let ntv2 = hmac_md5(nthash, identity_utf16le);
126 let key_exchange_key = hmac_md5(&ntv2, proof);
127
128 match t3.encrypted_session_key.as_slice() {
129 enc if enc.len() == 16 => {
130 let out = rc4(&key_exchange_key, enc);
131 Some(out.try_into().unwrap())
132 }
133 _ => Some(key_exchange_key),
135 }
136}
137
138fn utf16_le(s: &str) -> Vec<u8> {
139 s.encode_utf16().flat_map(|u| u.to_le_bytes()).collect()
140}