diff --git a/Cargo.lock b/Cargo.lock index e0bdca8f6..2bdbb9def 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2786,8 +2786,6 @@ dependencies = [ [[package]] name = "ironrdp-rdcleanpath" version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3f5401de43e86384ac0f7f356af8c0bdc321671853f76095da5d480d6998e0" dependencies = [ "der", ] diff --git a/devolutions-gateway/Cargo.toml b/devolutions-gateway/Cargo.toml index 4a546a080..29d5497da 100644 --- a/devolutions-gateway/Cargo.toml +++ b/devolutions-gateway/Cargo.toml @@ -28,8 +28,8 @@ job-queue = { path = "../crates/job-queue" } job-queue-libsql = { path = "../crates/job-queue-libsql" } ironrdp-pdu = { version = "0.5", features = ["std"] } ironrdp-core = { version = "0.1", features = ["std"] } -ironrdp-rdcleanpath = "0.1" ironrdp-tokio = "0.5" +ironrdp-rdcleanpath = { path = "C:\\dev\\IronRDP\\crates\\ironrdp-rdcleanpath" } ironrdp-connector = { version = "0.5" } ironrdp-acceptor = { version = "0.5" } ceviche = "0.6.1" diff --git a/devolutions-gateway/src/rd_clean_path.rs b/devolutions-gateway/src/rd_clean_path.rs index 8c4edb5f3..66dcbbbb0 100644 --- a/devolutions-gateway/src/rd_clean_path.rs +++ b/devolutions-gateway/src/rd_clean_path.rs @@ -77,10 +77,7 @@ async fn read_cleanpath_pdu(mut stream: impl AsyncRead + Unpin + Send) -> io::Re std::cmp::Ordering::Less => {} std::cmp::Ordering::Equal => break, std::cmp::Ordering::Greater => { - return Err(io::Error::new( - ErrorKind::Other, - "no leftover is expected when reading cleanpath PDU", - )); + return Err(io::Error::other("no leftover is expected when reading cleanpath PDU")); } } } @@ -164,7 +161,7 @@ struct CleanPathResult { destination: TargetAddr, server_addr: SocketAddr, server_stream: tokio_rustls::client::TlsStream, - x224_rsp: Vec, + x224_rsp: Option>, } async fn process_cleanpath( @@ -234,31 +231,74 @@ async fn process_cleanpath( debug!(%selected_target, "Connected to destination server"); span.record("target", selected_target.to_string()); - // Send preconnection blob if applicable - if let Some(pcb) = cleanpath_pdu.preconnection_blob { - server_stream.write_all(pcb.as_bytes()).await?; - } + // Preconnection Blob (PCB) is currently used for Hyper-V VMs almost exclusively in practice. + // However, we still leave space for future extensions of usages of PCB. + // + // Connection sequence with Hyper-V VMs (PCB included and X224 connection request is not present): + // ┌───────────────────────┐ ┌───────────────────────────────────────────────────────────────┐ + // │ handled by │ │ handled by IronRDP client │ + // │ Gateway │ │ │ + // └───────────────────────┘ └───────────────────────────────────────────────────────────────┘ + // │ PCB → TLS handshake │ → │ CredSSP → X224 connection request → X224 connection response │ + // └───────────────────────┘ └───────────────────────────────────────────────────────────────┘ + // + // Connection sequence without Hyper-V VMs (PCB optional): + // ┌───────────────────────────────────────────────────────────────┐ ┌───────────────────────┐ + // │ handled by Gateway │ │ handled by IronRDP │ + // │ │ │ client │ + // └───────────────────────────────────────────────────────────────┘ └───────────────────────┘ + // │ PCB → X224 connection request → X224 connection response → TLS| │ → CredSSP → ... │ + // └───────────────────────────────────────────────────────────────┘ └───────────────────────┘ + // + // Summary: + // - With PCB but not X224 connection request: Gateway handles (1) sending PCB/VmConnectID, (2) TLS handshake, then leaves CredSSP + // and X224 connection request/response to IronRDP client. + // - With PCB and X224 connection request: Gateway handles (1) sending PCB/VmConnectID, (2) X224 connection request, (3) X224 connection response, (4) TLS handshake, + // then leaves CredSSP to IronRDP client. + // - Without PCB: In this case, X224 MUST be present! Gateway handles (1) X224 connection request, (2) X224 connection response, (3) TLS handshake, then leaves CredSSP to IronRDP client. + let pcb_bytes = match (&cleanpath_pdu.preconnection_blob, &cleanpath_pdu.x224_connection_pdu) { + (None, None) => { + return Err(CleanPathError::BadRequest(anyhow::anyhow!( + "RDCleanPath PDU is missing both preconnection blob and X224 connection PDU" + ))); + } + (None, Some(_)) => None, // no preconnection blob to send + (Some(general_pcb), Some(_)) => Some(general_pcb), + (Some(vmconnect), None) => Some(vmconnect), + }; - // Send X224 connection request - let x224_req = cleanpath_pdu - .x224_connection_pdu - .context("request is missing X224 connection PDU") - .map_err(CleanPathError::BadRequest)?; - server_stream.write_all(x224_req.as_bytes()).await?; + if let Some(pcb_bytes) = pcb_bytes { + let pcb = ironrdp_pdu::pcb::PreconnectionBlob { + version: ironrdp_pdu::pcb::PcbVersion::V2, + id: 0, + v2_payload: Some(pcb_bytes.to_owned()), + }; - // Receive server X224 connection response + let encoded = ironrdp_core::encode_vec(&pcb) + .context("failed to encode preconnection blob") + .map_err(CleanPathError::BadRequest)?; - trace!("Receiving X224 response"); + server_stream.write_all(&encoded).await?; + } - let x224_rsp = read_x224_response(&mut server_stream) - .await - .with_context(|| format!("read X224 response from {selected_target}")) - .map_err(CleanPathError::BadRequest)?; + // Send X224 connection request if present + let x224_rsp = if let Some(x224_connection_pdu) = &cleanpath_pdu.x224_connection_pdu { + server_stream.write_all(x224_connection_pdu.as_bytes()).await?; + debug!("X224 connection request sent"); - trace!("Establishing TLS connection with server"); + let x224_rsp = read_x224_response(&mut server_stream) + .await + .with_context(|| format!("read X224 response from {selected_target}")) + .map_err(CleanPathError::BadRequest)?; + trace!("Receiving X224 response"); - // Establish TLS connection with server + Some(x224_rsp) + } else { + None + }; + // Establish TLS connection + trace!("Establishing TLS connection with server"); let server_stream = crate::tls::connect(selected_target.host().to_owned(), server_stream) .await .map_err(|source| CleanPathError::TlsHandshake { @@ -295,7 +335,7 @@ pub async fn handle( .await .context("couldn’t read clean cleanpath PDU")?; - trace!("Processing RDCleanPath"); + trace!(RDCleanPath = ?cleanpath_pdu,"Processing RDCleanPath"); let CleanPathResult { claims, @@ -337,7 +377,7 @@ pub async fn handle( trace!("Sending RDCleanPath response"); let rdcleanpath_rsp = RDCleanPathPdu::new_response(server_addr.to_string(), x224_rsp, x509_chain) - .map_err(|e| anyhow::anyhow!("couldn’t build RDCleanPath response: {e}"))?; + .context("couldn’t build RDCleanPath response")?; send_clean_path_response(&mut client_stream, &rdcleanpath_rsp).await?; @@ -456,7 +496,7 @@ enum WsaError { WSAESTALE = 10070, WSAEREMOTE = 10071, WSASYSNOTREADY = 10091, - WSAVERNOTSUPPORTED = 10092, + WSAVERNOT_SUPPORTED = 10092, WSANOTINITIALISED = 10093, WSAEDISCON = 10101, WSAENOMORE = 10102, diff --git a/devolutions-gateway/src/rdp_pcb.rs b/devolutions-gateway/src/rdp_pcb.rs index 3ff270deb..22c4293b4 100644 --- a/devolutions-gateway/src/rdp_pcb.rs +++ b/devolutions-gateway/src/rdp_pcb.rs @@ -54,6 +54,7 @@ fn decode_pcb(buf: &[u8]) -> Result, io::Erro Ok(pcb) => { let pdu_size = ironrdp_core::size(&pcb); let read_len = cursor.pos(); + debug!(pdu_size, read_len, "read preconnection blob",); // NOTE: sanity check (reporting the wrong number will corrupt the communication) if read_len != pdu_size { diff --git a/devolutions-gateway/src/tls.rs b/devolutions-gateway/src/tls.rs index 293206fe2..39d327c6b 100644 --- a/devolutions-gateway/src/tls.rs +++ b/devolutions-gateway/src/tls.rs @@ -76,7 +76,7 @@ pub fn build_server_config(cert_source: CertificateSource) -> anyhow::Result { let first_certificate = certificates.first().context("empty certificate list")?; - if let Ok(report) = check_certificate_now(&first_certificate) { + if let Ok(report) = check_certificate_now(first_certificate) { if report.issues.intersects( CertIssues::MISSING_SERVER_AUTH_EXTENDED_KEY_USAGE | CertIssues::MISSING_SUBJECT_ALT_NAME, ) {