Skip to content

feat: support TCP KeyExchange so logged-in clients >=1.4.1 can connect - #691

Open
cuongnq93 wants to merge 2 commits into
rustdesk:masterfrom
cuongnq93:feat/tcp-key-exchange
Open

feat: support TCP KeyExchange so logged-in clients >=1.4.1 can connect#691
cuongnq93 wants to merge 2 commits into
rustdesk:masterfrom
cuongnq93:feat/tcp-key-exchange

Conversation

@cuongnq93

@cuongnq93 cuongnq93 commented Jul 29, 2026

Copy link
Copy Markdown

Clients from 1.4.1 onwards call secure_tcp() against the rendezvous server whenever an account is logged in on an API server and a key is configured. The OSS server never answered the KeyExchange message, so those clients hung until 'Failed to secure tcp: deadline has elapsed'.

Implements both handshake phases on port 21116/TCP:

  • phase 1: send this connection's box public key, signed with the server key
  • phase 2: open the client-sealed symmetric key and upgrade the channel

Messages are then secretbox-encrypted with a sequence-number nonce, matching hbb_common::tcp::Encrypt on the client side. WebSocket connections are left untouched since wss already encrypts the transport.

Summary by CodeRabbit

  • New Features
    • Added encrypted TCP communication using per-connection symmetric keys.
    • Added a secure TCP key-exchange flow to establish encryption at connection setup.
    • Outbound and inbound TCP messages are now protected after negotiation.
  • Bug Fixes
    • Improved handling of malformed or invalid key-exchange data to prevent unsafe decryption.
  • Tests
    • Added unit tests covering successful key exchange and rejection of incorrect inputs.
  • Chores
    • Added a custom Docker build to produce a smaller runtime image with the Rust binaries.

Greptile Summary

This PR implements the two-phase TCP key exchange handshake on port 21116 so that RustDesk clients ≥ 1.4.1 can connect without hanging when a server key and API account are both configured. The Sink type is refactored from a plain enum into a struct that carries both the transport variant and an Arc<Mutex<Option<Encrypt>>> channel key, with secretbox encryption matching hbb_common::tcp::Encrypt.

  • A per-connection ephemeral box keypair is generated in phase 1, signed with the server's sign key, and sent to the client; the secret half is stored in Sink.exchange_sk and discarded after phase 2.
  • Phase 2 opens the client-sealed symmetric key, installs an Encrypt object, and from that point all inbound frames are decrypted and all outbound send_to_sink calls encrypt before writing.
  • WebSocket connections are left untouched: KeyExchange on a WS connection is logged and silently ignored so the secretbox layer is never applied to WS frames.

Confidence Score: 5/5

Safe to merge. The handshake logic is correct, ephemeral keypairs provide forward secrecy, and the WebSocket guard prevents mis-applying secretbox to WS frames.

The implementation is consistent with the hbb_common::tcp::Encrypt protocol: sequence numbers start at 0 and are incremented before use on both sides, nonces are correctly derived, and the bidirectional Arc<Mutex<Option>> is accessed safely through tokio's async mutex. Both previously raised concerns (static box keypair and unguarded WebSocket path) are addressed in the submitted code. Three unit tests cover the happy path, garbage rejection, and cross-connection key isolation.

Files Needing Attention: No files require special attention. The core changes in src/rendezvous_server.rs are well-structured and tested.

Important Files Changed

Filename Overview
src/rendezvous_server.rs Core change: adds per-connection keypair generation, KeyExchange phase 1/2 handlers, and secretbox encryption/decryption in the TCP receive loop. Logic is consistent with hbb_common::tcp::Encrypt (seqnums start at 0, incremented before use). WebSocket guard correctly prevents secretbox being applied to WS frames. Tests cover round-trip, garbage rejection, and cross-connection key isolation.
Dockerfile.custom New multi-stage Dockerfile for building and running hbbs/hbbr with the TCP KeyExchange patch. Standard Rust builder + debian-slim runtime pattern; no security concerns.

Sequence Diagram

sequenceDiagram
    participant C as Client (>=1.4.1)
    participant S as hbbs TCP :21116

    C->>S: TCP connect
    Note over S: key_exchange_phase1()<br/>gen_keypair() per-connection
    S->>C: "KeyExchange { keys: [sign(box_pk, server_sk)] }"
    Note over C: verify signature,<br/>extract server box_pk,<br/>gen_key() symmetric,<br/>seal(sym_key, nonce=0, server_box_pk, client_sk)
    C->>S: "KeyExchange { keys: [client_box_pk, sealed_sym_key] }"
    Note over S: key_exchange_phase2()<br/>box::open -> sym_key<br/>Encrypt installed in Sink.key<br/>exchange_sk dropped
    Note over C,S: Channel now secretbox-encrypted<br/>(seqnum nonce, starting at 1)
    C->>S: "secretbox(PunchHoleRequest, nonce=1)"
    Note over S: recv loop: enc.dec() -> plaintext<br/>handle_tcp -> sink moved to tcp_punch
    S->>C: "secretbox(PunchHoleResponse, nonce=1)"
Loading

Reviews (2): Last reviewed commit: "fix: ignore KeyExchange over WebSocket, ..." | Re-trigger Greptile

Clients from 1.4.1 onwards call secure_tcp() against the rendezvous server
whenever an account is logged in on an API server and a key is configured.
The OSS server never answered the KeyExchange message, so those clients hung
until 'Failed to secure tcp: deadline has elapsed'.

Implements both handshake phases on port 21116/TCP:
- phase 1: send this connection's box public key, signed with the server key
- phase 2: open the client-sealed symmetric key and upgrade the channel

Messages are then secretbox-encrypted with a sequence-number nonce, matching
hbb_common::tcp::Encrypt on the client side. WebSocket connections are left
untouched since wss already encrypts the transport.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds per-connection symmetric encryption for TCP rendezvous sessions through asymmetric key exchange, encrypted message handling, validation tests, and a multi-stage Docker image. WebSocket connections remain without this handshake.

Changes

TCP connection encryption

Layer / File(s) Summary
Encryption state and primitives
src/rendezvous_server.rs
Adds sequence-number-based symmetric encryption and per-connection asymmetric exchange state.
TCP key-exchange flow
src/rendezvous_server.rs
Routes KeyExchange, sends a signed server key, validates the sealed client key, and installs encryption.
Encrypted transport and validation
src/rendezvous_server.rs
Encrypts outgoing TCP payloads, decrypts inbound frames, leaves WebSocket encryption disabled, and tests malformed and cross-connection inputs.

Container packaging

Layer / File(s) Summary
Multi-stage server image
Dockerfile.custom
Builds hbbs and hbbr with Rust, packages them in Debian slim, exposes configured ports, and starts hbbs.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TCPClient
  participant RendezvousServer
  participant Sink
  TCPClient->>RendezvousServer: Initiate KeyExchange
  RendezvousServer->>TCPClient: Signed server public key
  TCPClient->>RendezvousServer: Sealed symmetric key
  RendezvousServer->>Sink: Install Encrypt
  TCPClient->>RendezvousServer: Encrypted payload
  RendezvousServer->>Sink: Decrypted message
  Sink->>TCPClient: Encrypted response
Loading

Possibly related PRs

Suggested reviewers: rustdesk

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding TCP KeyExchange support for newer logged-in clients.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread src/rendezvous_server.rs
Comment thread src/rendezvous_server.rs Outdated
…keypair

Two issues from review:

- handle_tcp serves both plain TCP and WebSocket, so a WebSocket client could
  install a secretbox layer on its own connection and make every later reply
  unreadable to itself. The client skips the handshake under wss anyway, so
  the message is now ignored there.

- The box keypair was generated once per process, so anyone who later recovered
  that secret could unseal the symmetric key of every recorded session. It now
  lives on the Sink: generated per connection, taken (not borrowed) on use, and
  dropped when the connection ends.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/rendezvous_server.rs`:
- Around line 1355-1387: Update key_exchange_phase2 so it only reports success
after installing the negotiated Encrypt state into an available sink. Treat sink
being None as a failure: log the failed key installation, return false, and do
not log that the connection was secured; preserve the existing key validation
and installation behavior when sink is present.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6d58388d-4a93-4ac8-8368-01c730b782f8

📥 Commits

Reviewing files that changed from the base of the PR and between 6e7de5b and d6c2799.

📒 Files selected for processing (1)
  • src/rendezvous_server.rs

Comment thread src/rendezvous_server.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
Dockerfile.custom (1)

2-2: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Pin the base images for reproducible releases.

rust:1-bookworm and debian:bookworm-slim can change between builds. Use explicitly reviewed versions with immutable digests, while retaining a scheduled process for security updates.

Also applies to: 11-11

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Dockerfile.custom` at line 2, Pin the Dockerfile’s builder and runtime base
images to explicitly reviewed version tags with immutable digests, replacing the
floating rust:1-bookworm and debian:bookworm-slim references. Preserve the
existing image roles and ensure the chosen versions remain compatible with the
build while relying on the established scheduled process for future security
updates.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Dockerfile.custom`:
- Around line 11-20: Update the final Dockerfile stage to create a dedicated
non-root system user with its home directory, replace the /root working
directory with that home directory, and add USER before CMD so hbbs runs under
the dedicated user.

---

Nitpick comments:
In `@Dockerfile.custom`:
- Line 2: Pin the Dockerfile’s builder and runtime base images to explicitly
reviewed version tags with immutable digests, replacing the floating
rust:1-bookworm and debian:bookworm-slim references. Preserve the existing image
roles and ensure the chosen versions remain compatible with the build while
relying on the established scheduled process for future security updates.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2d9b4b5a-9ede-4970-b1a2-de6b81174be3

📥 Commits

Reviewing files that changed from the base of the PR and between d6c2799 and 9ff251d.

📒 Files selected for processing (2)
  • Dockerfile.custom
  • src/rendezvous_server.rs

Comment thread Dockerfile.custom
Comment on lines +11 to +20
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /root
COPY --from=builder /build/target/release/hbbs /usr/bin/hbbs
COPY --from=builder /build/target/release/hbbr /usr/bin/hbbr
# hbbs: 21115 (nat test), 21116 tcp+udp (rendezvous), 21118 (ws)
# hbbr: 21117 (relay), 21119 (ws relay)
EXPOSE 21115 21116 21116/udp 21117 21118 21119
CMD ["hbbs"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Run the server as a non-root user.

The final image defaults to root, and /root is used as the working directory. Create a dedicated system user/home directory, switch the working directory there, and add USER before CMD.

Proposed fix
 FROM debian:bookworm-slim
 RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \
-    && rm -rf /var/lib/apt/lists/*
-WORKDIR /root
+    && rm -rf /var/lib/apt/lists/* \
+    && groupadd --system hbbs \
+    && useradd --system --gid hbbs --create-home --home-dir /var/lib/hbbs hbbs
+WORKDIR /var/lib/hbbs
 COPY --from=builder /build/target/release/hbbs /usr/bin/hbbs
 COPY --from=builder /build/target/release/hbbr /usr/bin/hbbr
+USER hbbs
 CMD ["hbbs"]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /root
COPY --from=builder /build/target/release/hbbs /usr/bin/hbbs
COPY --from=builder /build/target/release/hbbr /usr/bin/hbbr
# hbbs: 21115 (nat test), 21116 tcp+udp (rendezvous), 21118 (ws)
# hbbr: 21117 (relay), 21119 (ws relay)
EXPOSE 21115 21116 21116/udp 21117 21118 21119
CMD ["hbbs"]
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/* \
&& groupadd --system hbbs \
&& useradd --system --gid hbbs --create-home --home-dir /var/lib/hbbs hbbs
WORKDIR /var/lib/hbbs
COPY --from=builder /build/target/release/hbbs /usr/bin/hbbs
COPY --from=builder /build/target/release/hbbr /usr/bin/hbbr
# hbbs: 21115 (nat test), 21116 tcp+udp (rendezvous), 21118 (ws)
# hbbr: 21117 (relay), 21119 (ws relay)
EXPOSE 21115 21116 21116/udp 21117 21118 21119
USER hbbs
CMD ["hbbs"]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Dockerfile.custom` around lines 11 - 20, Update the final Dockerfile stage to
create a dedicated non-root system user with its home directory, replace the
/root working directory with that home directory, and add USER before CMD so
hbbs runs under the dedicated user.

Source: Linters/SAST tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants