Files
nexus/rust/vendor/sspi/tests/sspi/client_server/ntlm.rs
T
Andy Dixon 50c0104d57 feat(core): RDP client on IronRDP
clients::rdp connects over any caller-dialled TCP stream (direct, proxy
or SSH tunnel), upgrades to TLS and logs on with NLA (CredSSP/NTLM),
optionally falling back to TLS-only logon for servers without NLA such
as xrdp (credentials are passed so they log straight in). The server
certificate is pinned through a caller check on its SHA256 fingerprint
before any credentials are sent. The desktop decodes into the VNC
client's RGBA framebuffer; input (IronRDP's scancode/Unicode/mouse
operations), Display Control resizes and the deactivation-reactivation
sequence run on the session thread.

IronRDP's releases need three vendored patches (vendor/README.md):
picky and sspi with their RustCrypto release-candidate pins relaxed so
they share russh's stable versions (their own suites, 402 tests, pass
against them), and ironrdp-session with two unreleased upstream bitmap
fixes (#1486 stride, #1524 RDP6 orientation) without which xrdp desktops
render sheared.

Verified live against xrdp (scottyhardy/docker-remote-desktop): logon,
full desktop rendering, keyboard and mouse input.
2026-10-12 10:50:24 +01:00

165 lines
5.6 KiB
Rust

use sspi::builders::{AcquireCredentialsHandle, WithoutCredentialUse};
use sspi::credssp::SspiContext;
use sspi::ntlm::NtlmConfig;
use sspi::{
AcquireCredentialsHandleResult, AuthIdentity, BufferType, ClientRequestFlags, CredentialUse, Credentials,
DataRepresentation, EncryptionFlags, InitializeSecurityContextResult, Ntlm, Secret, SecurityBuffer,
SecurityBufferFlags, SecurityBufferRef, SecurityStatus, ServerRequestFlags, Sspi, Username,
};
use crate::client_server::{TARGET_NAME, test_encryption, test_rpc_request_encryption, test_stream_buffer_encryption};
// Simulates readonly buffers encryption. In that case, the NTLM security package should not encrypt the data,
// but instead compute the checksum and write into the token buffer.
//
// More info: https://github.com/Devolutions/sspi-rs/pull/629.
//
// This is supported only by NTLM security package and do not implemented for Kerberos.
fn test_readonly_buffers_encryption(client: &mut SspiContext, server: &mut SspiContext) {
let plain_message = b"Devolutions/sspi-rs";
let mut token = [0; 1024];
let mut data_1 = plain_message.to_vec();
let mut data_2 = plain_message.to_vec();
let mut message = vec![
SecurityBufferRef::token_buf(token.as_mut_slice()),
SecurityBufferRef::data_buf(data_1.as_mut_slice()).with_flags(SecurityBufferFlags::SECBUFFER_READONLY),
SecurityBufferRef::data_buf(data_2.as_mut_slice())
.with_flags(SecurityBufferFlags::SECBUFFER_READONLY_WITH_CHECKSUM),
];
client.encrypt_message(EncryptionFlags::empty(), &mut message).unwrap();
// Make sure that readonly buffers was not encrypted.
assert_eq!(plain_message, message[1].data());
assert_eq!(plain_message, message[2].data());
server.decrypt_message(&mut message).unwrap();
assert_eq!(plain_message, message[1].data());
assert_eq!(plain_message, message[2].data());
}
fn run_ntlm(config: NtlmConfig, target_name: Option<&str>, username: &str, password: &str) {
let credentials = Credentials::AuthIdentity(AuthIdentity {
username: Username::parse(username).unwrap(),
password: Secret::from(password.to_owned()),
});
let mut client = SspiContext::Ntlm(Ntlm::with_config(config.clone()));
let mut server = SspiContext::Ntlm(Ntlm::with_config(config));
let builder = AcquireCredentialsHandle::<'_, _, _, WithoutCredentialUse>::new();
let AcquireCredentialsHandleResult {
credentials_handle: mut client_credentials_handle,
..
} = builder
.with_auth_data(&credentials)
.with_credential_use(CredentialUse::Outbound)
.execute(&mut client)
.unwrap();
let builder = AcquireCredentialsHandle::<'_, _, _, WithoutCredentialUse>::new();
let AcquireCredentialsHandleResult {
credentials_handle: mut server_credentials_handle,
..
} = builder
.with_auth_data(&credentials)
.with_credential_use(CredentialUse::Inbound)
.execute(&mut server)
.unwrap();
let mut input_token = [SecurityBuffer::new(Vec::new(), BufferType::Token)];
let mut output_token = [SecurityBuffer::new(Vec::new(), BufferType::Token)];
for _ in 0..3 {
let mut builder = client
.initialize_security_context()
.with_credentials_handle(&mut client_credentials_handle)
.with_context_requirements(
ClientRequestFlags::MUTUAL_AUTH
| ClientRequestFlags::USE_SESSION_KEY
| ClientRequestFlags::INTEGRITY
| ClientRequestFlags::CONFIDENTIALITY,
)
.with_target_data_representation(DataRepresentation::Native)
.with_input(&mut input_token)
.with_output(&mut output_token);
builder.target_name = target_name;
let InitializeSecurityContextResult { status, .. } =
client.initialize_security_context_sync(&mut builder).unwrap();
input_token[0].buffer.clear();
let builder = server
.accept_security_context()
.with_credentials_handle(&mut server_credentials_handle)
.with_context_requirements(ServerRequestFlags::empty())
.with_target_data_representation(DataRepresentation::Native)
.with_input(&mut output_token)
.with_output(&mut input_token);
server.accept_security_context_sync(builder).unwrap();
output_token[0].buffer.clear();
if status == SecurityStatus::Ok {
test_encryption(&mut client, &mut server);
test_stream_buffer_encryption(&mut client, &mut server);
test_rpc_request_encryption(&mut client, &mut server);
test_readonly_buffers_encryption(&mut client, &mut server);
return;
}
}
panic!("NTLM authentication should not exceed 3 steps")
}
#[test]
fn ntlm_with_computer_name() {
run_ntlm(
NtlmConfig {
client_computer_name: Some("DESKTOP-3D83IAN.example.com".to_owned()),
},
Some(TARGET_NAME),
"test_user",
"test_password",
);
}
#[test]
fn ntlm_without_computer_name() {
run_ntlm(
NtlmConfig {
client_computer_name: None,
},
Some(TARGET_NAME),
"test_user",
"test_password",
);
}
#[test]
fn ntlm_guest_logon() {
run_ntlm(
NtlmConfig {
client_computer_name: None,
},
Some("cifs/DESKTOP-8F33RFH.example.com"),
"/GUEST",
"",
);
}
#[test]
fn ntlm_without_target_name() {
run_ntlm(
NtlmConfig {
client_computer_name: None,
},
None,
"test_user",
"test_password",
);
}