1个不稳定版本

0.1.0 2023年9月19日

#577 in 身份验证

Download history 51/week @ 2024-03-11 30/week @ 2024-03-18 81/week @ 2024-03-25 134/week @ 2024-04-01 71/week @ 2024-04-08 64/week @ 2024-04-15 74/week @ 2024-04-22 80/week @ 2024-04-29 57/week @ 2024-05-06 73/week @ 2024-05-13 34/week @ 2024-05-20 65/week @ 2024-05-27 82/week @ 2024-06-03 41/week @ 2024-06-10 53/week @ 2024-06-17 52/week @ 2024-06-24

每月下载 233次
legba 中使用

CC0 许可

56KB
950

ntlmclient

简单的Rust NTLM客户端库。


lib.rs:

一个简单的Rust NTLM客户端库。

示例用法

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");

    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(EWS_URL)
        .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
}

依赖项

~2–39MB
~561K SLoC