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.
165 lines
5.6 KiB
Rust
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",
|
|
);
|
|
}
|