Skip to content

Commit

Permalink
feat(credssp): support for Kerberos (#260)
Browse files Browse the repository at this point in the history
Issue: ARC-139
Co-authored-by: Benoît Cortier <bcortier@proton.me>
  • Loading branch information
irvingoujAtDevolution and CBenoit authored Nov 17, 2023
1 parent 89f25b4 commit 5530550
Show file tree
Hide file tree
Showing 23 changed files with 977 additions and 738 deletions.
623 changes: 214 additions & 409 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ ironrdp = { version = "0.5", path = "crates/ironrdp" }
expect-test = "1"
proptest = "1.3"
rstest = "0.18"
sspi = "0.10"
sspi = "0.11"
tracing = { version = "0.1", features = ["log"] }
thiserror = "1.0"

Expand Down
119 changes: 110 additions & 9 deletions crates/ironrdp-async/src/connector.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
use ironrdp_connector::{
ClientConnector, ClientConnectorState, ConnectionResult, ConnectorResult, Sequence as _, State as _,
credssp_sequence::{CredsspProcessGenerator, CredsspSequence},
custom_err,
sspi::{credssp::ClientState, generator::GeneratorState},
ClientConnector, ClientConnectorState, ConnectionResult, ConnectorResult, KerberosConfig, Sequence as _,
ServerName, State as _, Written,
};
use ironrdp_pdu::write_buf::WriteBuf;

use crate::framed::{Framed, FramedRead, FramedWrite};
use crate::{
framed::{Framed, FramedRead, FramedWrite},
AsyncNetworkClient,
};

#[non_exhaustive]
pub struct ShouldUpgrade;
Expand Down Expand Up @@ -33,9 +40,8 @@ pub fn skip_connect_begin(connector: &mut ClientConnector) -> ShouldUpgrade {
pub struct Upgraded;

#[instrument(skip_all)]
pub fn mark_as_upgraded(_: ShouldUpgrade, connector: &mut ClientConnector, server_public_key: Vec<u8>) -> Upgraded {
pub fn mark_as_upgraded(_: ShouldUpgrade, connector: &mut ClientConnector) -> Upgraded {
trace!("Marked as upgraded");
connector.attach_server_public_key(server_public_key);
connector.mark_security_upgrade_as_done();
Upgraded
}
Expand All @@ -44,15 +50,28 @@ pub fn mark_as_upgraded(_: ShouldUpgrade, connector: &mut ClientConnector, serve
pub async fn connect_finalize<S>(
_: Upgraded,
framed: &mut Framed<S>,
server_name: ServerName,
server_public_key: Vec<u8>,
network_client: Option<&mut dyn AsyncNetworkClient>,
mut connector: ClientConnector,
kerberos_config: Option<KerberosConfig>,
) -> ConnectorResult<ConnectionResult>
where
S: FramedRead + FramedWrite,
{
let mut buf = WriteBuf::new();

while connector.is_credssp_step() {
single_connect_step(framed, &mut connector, &mut buf).await?;
if connector.should_perform_credssp() {
perform_credssp_step(
framed,
&mut connector,
&mut buf,
server_name,
server_public_key,
network_client,
kerberos_config,
)
.await?;
}

let result = loop {
Expand All @@ -68,17 +87,99 @@ where
Ok(result)
}

async fn resolve_generator(
generator: &mut CredsspProcessGenerator<'_>,
network_client: &mut dyn AsyncNetworkClient,
) -> ConnectorResult<ClientState> {
let mut state = generator.start();
loop {
match state {
GeneratorState::Suspended(request) => {
let response = network_client.send(&request).await?;
state = generator.resume(Ok(response));
}
GeneratorState::Completed(client_state) => {
break Ok(client_state.map_err(|e| custom_err!("cannot resolve generator state", e))?)
}
}
}
}

#[instrument(level = "trace", skip(network_client, framed, buf, server_name, server_public_key))]
async fn perform_credssp_step<S>(
framed: &mut Framed<S>,
connector: &mut ClientConnector,
buf: &mut WriteBuf,
server_name: ServerName,
server_public_key: Vec<u8>,
mut network_client: Option<&mut dyn AsyncNetworkClient>,
kerberos_config: Option<KerberosConfig>,
) -> ConnectorResult<()>
where
S: FramedRead + FramedWrite,
{
assert!(connector.should_perform_credssp());
let mut credssp_sequence = CredsspSequence::new(connector, server_name, server_public_key, kerberos_config)?;
while !credssp_sequence.is_done() {
buf.clear();
let input = if let Some(next_pdu_hint) = credssp_sequence.next_pdu_hint() {
debug!(
connector.state = connector.state.name(),
hint = ?next_pdu_hint,
"Wait for PDU"
);

let pdu = framed
.read_by_hint(next_pdu_hint)
.await
.map_err(|e| ironrdp_connector::custom_err!("read frame by hint", e))?;

trace!(length = pdu.len(), "PDU received");
Some(pdu.to_vec())
} else {
None
};

if credssp_sequence.wants_request_from_server() {
credssp_sequence.read_request_from_server(&input.unwrap_or_else(|| [].to_vec()))?;
}
let client_state = {
let mut generator = credssp_sequence.process();
if let Some(network_client_ref) = network_client.as_deref_mut() {
trace!("resolving network");
resolve_generator(&mut generator, network_client_ref).await?
} else {
generator
.resolve_to_result()
.map_err(|e| custom_err!(" cannot resolve generator without a network client", e))?
}
}; // drop generator
let written = credssp_sequence.handle_process_result(client_state, buf)?;

if let Some(response_len) = written.size() {
let response = &buf[..response_len];
trace!(response_len, "Send response");
framed
.write_all(response)
.await
.map_err(|e| ironrdp_connector::custom_err!("write all", e))?;
}
}
connector.mark_credssp_as_done();
Ok(())
}

pub async fn single_connect_step<S>(
framed: &mut Framed<S>,
connector: &mut ClientConnector,
buf: &mut WriteBuf,
) -> ConnectorResult<ironrdp_connector::Written>
) -> ConnectorResult<()>
where
S: FramedWrite + FramedRead,
{
buf.clear();

let written = if let Some(next_pdu_hint) = connector.next_pdu_hint() {
let written: Written = if let Some(next_pdu_hint) = connector.next_pdu_hint() {
debug!(
connector.state = connector.state.name(),
hint = ?next_pdu_hint,
Expand Down Expand Up @@ -107,5 +208,5 @@ where
.map_err(|e| ironrdp_connector::custom_err!("write all", e))?;
}

Ok(written)
Ok(())
}
13 changes: 13 additions & 0 deletions crates/ironrdp-async/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@ mod connector;
mod framed;
mod session;

use std::future::Future;
use std::pin::Pin;

use ironrdp_connector::sspi::generator::NetworkRequest;
use ironrdp_connector::ConnectorResult;

pub use self::connector::*;
pub use self::framed::*;
// pub use self::session::*;

pub trait AsyncNetworkClient {
fn send<'a>(
&'a mut self,
network_request: &'a NetworkRequest,
) -> Pin<Box<dyn Future<Output = ConnectorResult<Vec<u8>>> + 'a>>;
}
101 changes: 96 additions & 5 deletions crates/ironrdp-blocking/src/connector.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
use std::io::{Read, Write};

use ironrdp_connector::{
ClientConnector, ClientConnectorState, ConnectionResult, ConnectorResult, Sequence as _, State as _,
credssp_sequence::{CredsspProcessGenerator, CredsspSequence},
custom_err,
sspi::{credssp::ClientState, generator::GeneratorState, network_client::NetworkClient},
ClientConnector, ClientConnectorState, ConnectionResult, ConnectorResult, KerberosConfig, Sequence as _,
ServerName, State as _,
};
use ironrdp_pdu::write_buf::WriteBuf;

Expand Down Expand Up @@ -35,9 +39,8 @@ pub fn skip_connect_begin(connector: &mut ClientConnector) -> ShouldUpgrade {
pub struct Upgraded;

#[instrument(skip_all)]
pub fn mark_as_upgraded(_: ShouldUpgrade, connector: &mut ClientConnector, server_public_key: Vec<u8>) -> Upgraded {
pub fn mark_as_upgraded(_: ShouldUpgrade, connector: &mut ClientConnector) -> Upgraded {
trace!("Marked as upgraded");
connector.attach_server_public_key(server_public_key);
connector.mark_security_upgrade_as_done();
Upgraded
}
Expand All @@ -46,7 +49,11 @@ pub fn mark_as_upgraded(_: ShouldUpgrade, connector: &mut ClientConnector, serve
pub fn connect_finalize<S>(
_: Upgraded,
framed: &mut Framed<S>,
server_name: ServerName,
server_public_key: Vec<u8>,
network_client: &mut impl NetworkClient,
mut connector: ClientConnector,
kerberos_config: Option<KerberosConfig>,
) -> ConnectorResult<ConnectionResult>
where
S: Read + Write,
Expand All @@ -55,8 +62,16 @@ where

debug!("CredSSP procedure");

while connector.is_credssp_step() {
single_connect_step(framed, &mut connector, &mut buf)?;
if connector.should_perform_credssp() {
perform_credssp_step(
framed,
&mut connector,
&mut buf,
server_name,
server_public_key,
network_client,
kerberos_config,
)?;
}

debug!("Remaining of connection sequence");
Expand All @@ -74,6 +89,82 @@ where
Ok(result)
}

#[instrument(level = "info", skip(generator, network_client))]
fn resolve_generator(
generator: &mut CredsspProcessGenerator<'_>,
network_client: &mut impl NetworkClient,
) -> ConnectorResult<ClientState> {
let mut state = generator.start();
let res = loop {
match state {
GeneratorState::Suspended(request) => {
let response = network_client.send(&request).unwrap();
state = generator.resume(Ok(response));
}
GeneratorState::Completed(client_state) => {
break client_state.map_err(|e| custom_err!("failed to resolve generator", e))?
}
}
};
debug!("client state = {:?}", &res);
Ok(res)
}

#[instrument(level = "trace", skip(network_client, framed, buf, server_name, server_public_key))]
fn perform_credssp_step<S>(
framed: &mut Framed<S>,
connector: &mut ClientConnector,
buf: &mut WriteBuf,
server_name: ServerName,
server_public_key: Vec<u8>,
network_client: &mut impl NetworkClient,
kerberos_config: Option<KerberosConfig>,
) -> ConnectorResult<()>
where
S: Read + Write,
{
assert!(connector.should_perform_credssp());
let mut credssp_sequence = CredsspSequence::new(connector, server_name, server_public_key, kerberos_config)?;
while !credssp_sequence.is_done() {
buf.clear();
let input = if let Some(next_pdu_hint) = credssp_sequence.next_pdu_hint() {
debug!(
connector.state = connector.state.name(),
hint = ?next_pdu_hint,
"Wait for PDU"
);

let pdu = framed
.read_by_hint(next_pdu_hint)
.map_err(|e| ironrdp_connector::custom_err!("read frame by hint", e))?;

trace!(length = pdu.len(), "PDU received");
Some(pdu.to_vec())
} else {
None
};

if credssp_sequence.wants_request_from_server() {
credssp_sequence.read_request_from_server(&input.unwrap_or_else(|| [].to_vec()))?;
}
let client_state = {
let mut generator = credssp_sequence.process();
resolve_generator(&mut generator, network_client)?
}; // drop generator
let written = credssp_sequence.handle_process_result(client_state, buf)?;

if let Some(response_len) = written.size() {
let response = &buf[..response_len];
trace!(response_len, "Send response");
framed
.write_all(response)
.map_err(|e| ironrdp_connector::custom_err!("write all", e))?;
}
}
connector.mark_credssp_as_done();
Ok(())
}

pub fn single_connect_step<S>(
framed: &mut Framed<S>,
connector: &mut ClientConnector,
Expand Down
1 change: 0 additions & 1 deletion crates/ironrdp-blocking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,3 @@ mod session;

pub use self::connector::*;
pub use self::framed::*;
// pub use self::session::*;
2 changes: 2 additions & 0 deletions crates/ironrdp-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ anyhow = "1"
smallvec = "1.11"
tap = "1"
semver = "1"
reqwest = "0.11.22"
url = "2.4.1"

[target.'cfg(windows)'.dependencies]
windows = { version = "0.48", features = [
Expand Down
1 change: 1 addition & 0 deletions crates/ironrdp-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ extern crate tracing;
pub mod clipboard;
pub mod config;
pub mod gui;
pub mod network_client;
pub mod rdp;
Loading

0 comments on commit 5530550

Please sign in to comment.