1pub const NTLMSSP_SIGNATURE: &[u8; 8] = b"NTLMSSP\0";
17
18pub const NTLMSSP_NEGOTIATE: u32 = 1;
19pub const NTLMSSP_CHALLENGE: u32 = 2;
20pub const NTLMSSP_AUTHENTICATE: u32 = 3;
21
22pub const NEGOTIATE_UNICODE: u32 = 0x0000_0001;
24pub const NEGOTIATE_OEM: u32 = 0x0000_0002;
25pub const REQUEST_TARGET: u32 = 0x0000_0004;
26pub const NEGOTIATE_SIGN: u32 = 0x0000_0010;
27pub const NEGOTIATE_SEAL: u32 = 0x0000_0020;
28pub const NEGOTIATE_NTLM: u32 = 0x0000_0200;
29pub const NEGOTIATE_ANONYMOUS: u32 = 0x0000_0800;
30pub const NEGOTIATE_ALWAYS_SIGN: u32 = 0x0000_8000;
31pub const TARGET_TYPE_SERVER: u32 = 0x0002_0000;
32pub const NEGOTIATE_EXTENDED_SESSIONSECURITY: u32 = 0x0008_0000;
33pub const NEGOTIATE_TARGET_INFO: u32 = 0x0080_0000;
34pub const NEGOTIATE_VERSION: u32 = 0x0200_0000;
35pub const NEGOTIATE_128: u32 = 0x2000_0000;
36pub const NEGOTIATE_KEY_EXCH: u32 = 0x4000_0000;
37pub const NEGOTIATE_56: u32 = 0x8000_0000;
38
39const ECHOED_FLAGS: u32 = NEGOTIATE_SIGN
41 | NEGOTIATE_SEAL
42 | NEGOTIATE_EXTENDED_SESSIONSECURITY
43 | NEGOTIATE_VERSION
44 | NEGOTIATE_128
45 | NEGOTIATE_KEY_EXCH
46 | NEGOTIATE_56;
47
48const TARGET_NETBIOS: &str = "NETGET";
50const TARGET_DNS: &str = "netget";
51
52const NTLMSSP_OID: &[u8] = &[
54 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x02, 0x0a,
55];
56const SPNEGO_OID: &[u8] = &[0x06, 0x06, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x02];
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub enum Wrapping {
62 Raw,
64 Spnego,
66}
67
68fn der(tag: u8, content: &[u8]) -> Vec<u8> {
70 let mut out = vec![tag];
71 let len = content.len();
72 if len < 0x80 {
73 out.push(len as u8);
74 } else if len <= 0xFF {
75 out.extend_from_slice(&[0x81, len as u8]);
76 } else {
77 out.extend_from_slice(&[0x82, (len >> 8) as u8, len as u8]);
78 }
79 out.extend_from_slice(content);
80 out
81}
82
83pub fn negotiate_blob() -> Vec<u8> {
86 let mech_types = der(0x30, NTLMSSP_OID);
87 let neg_token_init = der(0x30, &der(0xa0, &mech_types));
88 let mut inner = SPNEGO_OID.to_vec();
89 inner.extend_from_slice(&der(0xa0, &neg_token_init));
90 der(0x60, &inner)
91}
92
93fn neg_token_resp(neg_state: u8, supported_mech: bool, response_token: Option<&[u8]>) -> Vec<u8> {
95 let mut seq = der(0xa0, &[0x0a, 0x01, neg_state]);
96 if supported_mech {
97 seq.extend_from_slice(&der(0xa1, NTLMSSP_OID));
98 }
99 if let Some(token) = response_token {
100 seq.extend_from_slice(&der(0xa2, &der(0x04, token)));
101 }
102 der(0xa1, &der(0x30, &seq))
103}
104
105const NEG_STATE_ACCEPT_COMPLETED: u8 = 0;
106const NEG_STATE_ACCEPT_INCOMPLETE: u8 = 1;
107
108pub fn wrap_challenge(challenge: &[u8], wrapping: Wrapping) -> Vec<u8> {
110 match wrapping {
111 Wrapping::Raw => challenge.to_vec(),
112 Wrapping::Spnego => neg_token_resp(NEG_STATE_ACCEPT_INCOMPLETE, true, Some(challenge)),
113 }
114}
115
116pub fn accept_completed(wrapping: Wrapping) -> Vec<u8> {
119 match wrapping {
120 Wrapping::Raw => Vec::new(),
121 Wrapping::Spnego => neg_token_resp(NEG_STATE_ACCEPT_COMPLETED, false, None),
122 }
123}
124
125pub fn find_ntlmssp(buffer: &[u8]) -> Option<(&[u8], Wrapping)> {
133 if buffer.starts_with(NTLMSSP_SIGNATURE) {
134 return Some((buffer, Wrapping::Raw));
135 }
136 let start = buffer
137 .windows(NTLMSSP_SIGNATURE.len())
138 .position(|w| w == NTLMSSP_SIGNATURE)?;
139 Some((&buffer[start..], Wrapping::Spnego))
140}
141
142pub fn message_type(token: &[u8]) -> Option<u32> {
144 (token.len() >= 12 && token.starts_with(NTLMSSP_SIGNATURE)).then(|| le32(token, 8))
145}
146
147pub fn negotiate_flags(token: &[u8]) -> u32 {
149 if token.len() >= 16 {
150 le32(token, 12)
151 } else {
152 NEGOTIATE_UNICODE
153 }
154}
155
156pub fn challenge(client_flags: u32, server_challenge: [u8; 8], timestamp: u64) -> Vec<u8> {
158 let flags = NEGOTIATE_UNICODE
159 | REQUEST_TARGET
160 | NEGOTIATE_NTLM
161 | NEGOTIATE_ALWAYS_SIGN
162 | TARGET_TYPE_SERVER
163 | NEGOTIATE_TARGET_INFO
164 | (client_flags & ECHOED_FLAGS);
165
166 let target_name = utf16le(TARGET_NETBIOS);
167 let mut target_info = Vec::new();
168 for (id, value) in [
169 (2u16, utf16le(TARGET_NETBIOS)), (1u16, utf16le(TARGET_NETBIOS)), (4u16, utf16le(TARGET_DNS)), (3u16, utf16le(TARGET_DNS)), (7u16, timestamp.to_le_bytes().to_vec()), ] {
175 target_info.extend_from_slice(&id.to_le_bytes());
176 target_info.extend_from_slice(&(value.len() as u16).to_le_bytes());
177 target_info.extend_from_slice(&value);
178 }
179 target_info.extend_from_slice(&[0, 0, 0, 0]); const FIXED: usize = 56;
182 let target_name_offset = FIXED as u32;
183 let target_info_offset = target_name_offset + target_name.len() as u32;
184
185 let mut out = Vec::with_capacity(FIXED + target_name.len() + target_info.len());
186 out.extend_from_slice(NTLMSSP_SIGNATURE);
187 out.extend_from_slice(&NTLMSSP_CHALLENGE.to_le_bytes());
188 push_fields(&mut out, target_name.len(), target_name_offset);
189 out.extend_from_slice(&flags.to_le_bytes());
190 out.extend_from_slice(&server_challenge);
191 out.extend_from_slice(&[0u8; 8]); push_fields(&mut out, target_info.len(), target_info_offset);
193 out.extend_from_slice(&[10, 0]);
196 out.extend_from_slice(&20348u16.to_le_bytes());
197 out.extend_from_slice(&[0, 0, 0, 0x0F]);
198 debug_assert_eq!(out.len(), FIXED);
199 out.extend_from_slice(&target_name);
200 out.extend_from_slice(&target_info);
201 out
202}
203
204fn push_fields(out: &mut Vec<u8>, len: usize, offset: u32) {
205 out.extend_from_slice(&(len as u16).to_le_bytes()); out.extend_from_slice(&(len as u16).to_le_bytes()); out.extend_from_slice(&offset.to_le_bytes()); }
209
210#[derive(Debug, Clone, PartialEq, Eq)]
212pub struct Authenticate {
213 pub user: String,
214 pub domain: String,
215 pub workstation: String,
216 pub anonymous: bool,
219}
220
221pub fn parse_authenticate(token: &[u8]) -> Option<Authenticate> {
224 if message_type(token)? != NTLMSSP_AUTHENTICATE || token.len() < 64 {
225 return None;
226 }
227 let flags = le32(token, 60);
228 let unicode = flags & NEGOTIATE_UNICODE != 0;
229 let lm = field(token, 12)?;
230 let nt = field(token, 20)?;
231 let text = |off: usize| -> Option<String> {
232 let bytes = field(token, off)?;
233 if unicode {
234 from_utf16le(bytes)
235 } else {
236 Some(String::from_utf8_lossy(bytes).into_owned())
237 }
238 };
239 let domain = text(28)?;
240 let user = text(36)?;
241 let workstation = text(44)?;
242 let anonymous = (flags & NEGOTIATE_ANONYMOUS != 0)
243 || (user.is_empty() && nt.is_empty() && (lm.is_empty() || lm == [0]));
244 Some(Authenticate {
245 user,
246 domain,
247 workstation,
248 anonymous,
249 })
250}
251
252fn field(token: &[u8], at: usize) -> Option<&[u8]> {
254 let len = u16::from_le_bytes([*token.get(at)?, *token.get(at + 1)?]) as usize;
255 let offset = le32(token.get(at + 4..at + 8)?, 0) as usize;
256 if len == 0 {
257 return Some(&[]);
258 }
259 token.get(offset..offset.checked_add(len)?)
260}
261
262fn le32(b: &[u8], off: usize) -> u32 {
263 u32::from_le_bytes([b[off], b[off + 1], b[off + 2], b[off + 3]])
264}
265
266fn utf16le(s: &str) -> Vec<u8> {
267 super::wire::utf16le(s)
268}
269
270fn from_utf16le(b: &[u8]) -> Option<String> {
271 super::wire::from_utf16le(b)
272}