pub const NTLMSSP_SIGNATURE: &[u8; 8] = b"NTLMSSP\0";
pub const NTLMSSP_NEGOTIATE: u32 = 1;
pub const NTLMSSP_CHALLENGE: u32 = 2;
pub const NTLMSSP_AUTHENTICATE: u32 = 3;
pub const NEGOTIATE_UNICODE: u32 = 0x0000_0001;
pub const NEGOTIATE_OEM: u32 = 0x0000_0002;
pub const REQUEST_TARGET: u32 = 0x0000_0004;
pub const NEGOTIATE_SIGN: u32 = 0x0000_0010;
pub const NEGOTIATE_SEAL: u32 = 0x0000_0020;
pub const NEGOTIATE_NTLM: u32 = 0x0000_0200;
pub const NEGOTIATE_ANONYMOUS: u32 = 0x0000_0800;
pub const NEGOTIATE_ALWAYS_SIGN: u32 = 0x0000_8000;
pub const TARGET_TYPE_SERVER: u32 = 0x0002_0000;
pub const NEGOTIATE_EXTENDED_SESSIONSECURITY: u32 = 0x0008_0000;
pub const NEGOTIATE_TARGET_INFO: u32 = 0x0080_0000;
pub const NEGOTIATE_VERSION: u32 = 0x0200_0000;
pub const NEGOTIATE_128: u32 = 0x2000_0000;
pub const NEGOTIATE_KEY_EXCH: u32 = 0x4000_0000;
pub const NEGOTIATE_56: u32 = 0x8000_0000;
const ECHOED_FLAGS: u32 = NEGOTIATE_SIGN
| NEGOTIATE_SEAL
| NEGOTIATE_EXTENDED_SESSIONSECURITY
| NEGOTIATE_VERSION
| NEGOTIATE_128
| NEGOTIATE_KEY_EXCH
| NEGOTIATE_56;
const TARGET_NETBIOS: &str = "NETGET";
const TARGET_DNS: &str = "netget";
const NTLMSSP_OID: &[u8] = &[
0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x02, 0x0a,
];
const SPNEGO_OID: &[u8] = &[0x06, 0x06, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x02];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Wrapping {
Raw,
Spnego,
}
fn der(tag: u8, content: &[u8]) -> Vec<u8> {
let mut out = vec![tag];
let len = content.len();
if len < 0x80 {
out.push(len as u8);
} else if len <= 0xFF {
out.extend_from_slice(&[0x81, len as u8]);
} else {
out.extend_from_slice(&[0x82, (len >> 8) as u8, len as u8]);
}
out.extend_from_slice(content);
out
}
pub fn negotiate_blob() -> Vec<u8> {
let mech_types = der(0x30, NTLMSSP_OID);
let neg_token_init = der(0x30, &der(0xa0, &mech_types));
let mut inner = SPNEGO_OID.to_vec();
inner.extend_from_slice(&der(0xa0, &neg_token_init));
der(0x60, &inner)
}
fn neg_token_resp(neg_state: u8, supported_mech: bool, response_token: Option<&[u8]>) -> Vec<u8> {
let mut seq = der(0xa0, &[0x0a, 0x01, neg_state]);
if supported_mech {
seq.extend_from_slice(&der(0xa1, NTLMSSP_OID));
}
if let Some(token) = response_token {
seq.extend_from_slice(&der(0xa2, &der(0x04, token)));
}
der(0xa1, &der(0x30, &seq))
}
const NEG_STATE_ACCEPT_COMPLETED: u8 = 0;
const NEG_STATE_ACCEPT_INCOMPLETE: u8 = 1;
pub fn wrap_challenge(challenge: &[u8], wrapping: Wrapping) -> Vec<u8> {
match wrapping {
Wrapping::Raw => challenge.to_vec(),
Wrapping::Spnego => neg_token_resp(NEG_STATE_ACCEPT_INCOMPLETE, true, Some(challenge)),
}
}
pub fn accept_completed(wrapping: Wrapping) -> Vec<u8> {
match wrapping {
Wrapping::Raw => Vec::new(),
Wrapping::Spnego => neg_token_resp(NEG_STATE_ACCEPT_COMPLETED, false, None),
}
}
pub fn find_ntlmssp(buffer: &[u8]) -> Option<(&[u8], Wrapping)> {
if buffer.starts_with(NTLMSSP_SIGNATURE) {
return Some((buffer, Wrapping::Raw));
}
let start = buffer
.windows(NTLMSSP_SIGNATURE.len())
.position(|w| w == NTLMSSP_SIGNATURE)?;
Some((&buffer[start..], Wrapping::Spnego))
}
pub fn message_type(token: &[u8]) -> Option<u32> {
(token.len() >= 12 && token.starts_with(NTLMSSP_SIGNATURE)).then(|| le32(token, 8))
}
pub fn negotiate_flags(token: &[u8]) -> u32 {
if token.len() >= 16 {
le32(token, 12)
} else {
NEGOTIATE_UNICODE
}
}
pub fn challenge(client_flags: u32, server_challenge: [u8; 8], timestamp: u64) -> Vec<u8> {
let flags = NEGOTIATE_UNICODE
| REQUEST_TARGET
| NEGOTIATE_NTLM
| NEGOTIATE_ALWAYS_SIGN
| TARGET_TYPE_SERVER
| NEGOTIATE_TARGET_INFO
| (client_flags & ECHOED_FLAGS);
let target_name = utf16le(TARGET_NETBIOS);
let mut target_info = Vec::new();
for (id, value) in [
(2u16, utf16le(TARGET_NETBIOS)), (1u16, utf16le(TARGET_NETBIOS)), (4u16, utf16le(TARGET_DNS)), (3u16, utf16le(TARGET_DNS)), (7u16, timestamp.to_le_bytes().to_vec()), ] {
target_info.extend_from_slice(&id.to_le_bytes());
target_info.extend_from_slice(&(value.len() as u16).to_le_bytes());
target_info.extend_from_slice(&value);
}
target_info.extend_from_slice(&[0, 0, 0, 0]);
const FIXED: usize = 56;
let target_name_offset = FIXED as u32;
let target_info_offset = target_name_offset + target_name.len() as u32;
let mut out = Vec::with_capacity(FIXED + target_name.len() + target_info.len());
out.extend_from_slice(NTLMSSP_SIGNATURE);
out.extend_from_slice(&NTLMSSP_CHALLENGE.to_le_bytes());
push_fields(&mut out, target_name.len(), target_name_offset);
out.extend_from_slice(&flags.to_le_bytes());
out.extend_from_slice(&server_challenge);
out.extend_from_slice(&[0u8; 8]); push_fields(&mut out, target_info.len(), target_info_offset);
out.extend_from_slice(&[10, 0]);
out.extend_from_slice(&20348u16.to_le_bytes());
out.extend_from_slice(&[0, 0, 0, 0x0F]);
debug_assert_eq!(out.len(), FIXED);
out.extend_from_slice(&target_name);
out.extend_from_slice(&target_info);
out
}
fn push_fields(out: &mut Vec<u8>, len: usize, offset: u32) {
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()); }
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Authenticate {
pub user: String,
pub domain: String,
pub workstation: String,
pub anonymous: bool,
}
pub fn parse_authenticate(token: &[u8]) -> Option<Authenticate> {
if message_type(token)? != NTLMSSP_AUTHENTICATE || token.len() < 64 {
return None;
}
let flags = le32(token, 60);
let unicode = flags & NEGOTIATE_UNICODE != 0;
let lm = field(token, 12)?;
let nt = field(token, 20)?;
let text = |off: usize| -> Option<String> {
let bytes = field(token, off)?;
if unicode {
from_utf16le(bytes)
} else {
Some(String::from_utf8_lossy(bytes).into_owned())
}
};
let domain = text(28)?;
let user = text(36)?;
let workstation = text(44)?;
let anonymous = (flags & NEGOTIATE_ANONYMOUS != 0)
|| (user.is_empty() && nt.is_empty() && (lm.is_empty() || lm == [0]));
Some(Authenticate {
user,
domain,
workstation,
anonymous,
})
}
fn field(token: &[u8], at: usize) -> Option<&[u8]> {
let len = u16::from_le_bytes([*token.get(at)?, *token.get(at + 1)?]) as usize;
let offset = le32(token.get(at + 4..at + 8)?, 0) as usize;
if len == 0 {
return Some(&[]);
}
token.get(offset..offset.checked_add(len)?)
}
fn le32(b: &[u8], off: usize) -> u32 {
u32::from_le_bytes([b[off], b[off + 1], b[off + 2], b[off + 3]])
}
fn utf16le(s: &str) -> Vec<u8> {
super::wire::utf16le(s)
}
fn from_utf16le(b: &[u8]) -> Option<String> {
super::wire::from_utf16le(b)
}