Crate ntlmclient

Source
Expand description

A simple NTLM client library for Rust.

Sample usage:

use base64::prelude::{BASE64_STANDARD, Engine};

const EWS_URL: &str = "https://example.com/EWS/Exchange.asmx";

async fn initialize_authed_client(username: &str, password: &str, domain: &str, local_hostname: &str) -> reqwest::Client {
    let nego_flags
        = ntlmclient::Flags::NEGOTIATE_UNICODE
        | ntlmclient::Flags::REQUEST_TARGET
        | ntlmclient::Flags::NEGOTIATE_NTLM
        | ntlmclient::Flags::NEGOTIATE_WORKSTATION_SUPPLIED
        ;
    let nego_msg = ntlmclient::Message::Negotiate(ntlmclient::NegotiateMessage {
        flags: nego_flags,
        supplied_domain: String::new(),
        supplied_workstation: local_hostname.to_owned(),
        os_version: Default::default(),
    });
    let nego_msg_bytes = nego_msg.to_bytes()
        .expect("failed to encode NTLM negotiation message");
    let nego_b64 = BASE64_STANDARD.encode(&nego_msg_bytes);

    let client = reqwest::Client::builder()
        .cookie_store(true)
        .build()
        .expect("failed to build client");
    let resp = client.get(EWS_URL)
        .header("Authorization", format!("NTLM {}", nego_b64))
        .send().await
        .expect("failed to send challenge request to Exchange");
    let challenge_header = resp.headers().get("www-authenticate")
        .expect("response missing challenge header");

    // we might have been redirected to a specialized authentication URL
    let auth_url = resp.url();

    let challenge_b64 = challenge_header.to_str()
        .expect("challenge header not a string")
        .split(" ")
        .nth(1).expect("second chunk of challenge header missing");
    let challenge_bytes = BASE64_STANDARD.decode(challenge_b64)
        .expect("base64 decoding challenge message failed");
    let challenge = ntlmclient::Message::try_from(challenge_bytes.as_slice())
        .expect("decoding challenge message failed");
    let challenge_content = match challenge {
        ntlmclient::Message::Challenge(c) => c,
        other => panic!("wrong challenge message: {:?}", other),
    };
    let target_info_bytes: Vec<u8> = challenge_content.target_information
        .iter()
        .flat_map(|ie| ie.to_bytes())
        .collect();

    // calculate the response
    let creds = ntlmclient::Credentials {
        username: username.to_owned(),
        password: password.to_owned(),
        domain: domain.to_owned(),
    };
    let challenge_response = ntlmclient::respond_challenge_ntlm_v2(
        challenge_content.challenge,
        &target_info_bytes,
        ntlmclient::get_ntlm_time(),
        &creds,
    );

    // assemble the packet
    let auth_flags
        = ntlmclient::Flags::NEGOTIATE_UNICODE
        | ntlmclient::Flags::NEGOTIATE_NTLM
        ;
    let auth_msg = challenge_response.to_message(
        &creds,
        local_hostname,
        auth_flags,
    );
    let auth_msg_bytes = auth_msg.to_bytes()
        .expect("failed to encode NTLM authentication message");
    let auth_b64 = BASE64_STANDARD.encode(&auth_msg_bytes);

    client.get(auth_url.clone())
        .header("Authorization", format!("NTLM {}", auth_b64))
        .send().await
        .expect("failed to send authentication request to Exchange")
        .error_for_status()
        .expect("error response to authentication message");

    // try calling again, without the auth stuff (thanks to cookies)
    client.get(EWS_URL)
        .send().await
        .expect("failed to send refresher request to Exchange")
        .error_for_status()
        .expect("error response to refresher message");

    client
}

Structs§

AuthenticateMessage
The contents of an NTLM Authenticate message.
ChallengeMessage
The contents of an NTLM Challenge message.
ChallengeResponse
The response to an NTLM challenge.
Credentials
Standard NTLM credentials, consisting of username, password and domain.
Flags
NTLM operation flags.
NegotiateMessage
The contents of an NTLM Negotiate message.
OsVersion
A structure representing the version of an operating system as well as the NTLM revision used.
SecurityBuffer
An NTLM security buffer, pointing to a string contained later in the message.
TargetInfoEntry
An entry of additional target information included in the Challenge message.

Enums§

Message
An NTLM message.
ParsingError
An error that may occur while parsing existing NTLM packets.
StoringError
An error that may occur while writing an NTLM packet.
TargetInfoType
The type of additional target information included in the Challenge message.

Functions§

des_long
Performs the NTLMv1 DES encryption to calculate the response value to the challenge.
get_ntlm_time
Obtains the current NTLM timestamp.
lm_v1_password_func
Derives the encryption key from a password according to the LMv1 scheme.
ntlm_v1_password_func
Derives the encryption key from a password according to the NTLMv1 scheme.
ntlm_v2_password_func
Derives the encryption key from a password according to the NTLMv2 scheme.
respond_challenge_ntlm_v1
Calculates an NTLMv1 response to the given server challenge.
respond_challenge_ntlm_v2
Calculates an NTLMv2 response to the given server challenge, including target info and time value to protect against replay attacks.
respond_challenge_ntlm_v1_extended
Calculates an extended NTLMv1 response to the given server challenge.
respond_challenge_ntlm_v1_no_lm
Calculates an NTLMv1 response to the given server challenge.