2 unstable releases
0.2.0 | Nov 19, 2024 |
---|---|
0.1.0 | Sep 19, 2023 |
#199 in Authentication
350 downloads per month
Used in 2 crates
57KB
950 lines
ntlmclient
Simple NTLM client library for Rust.
lib.rs
:
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
}
Dependencies
~2–37MB
~561K SLoC