This document explains how the MeshCentral Android Agent authenticates the server and how relay tunnels (remote desktop, files, and file transfer) are trusted. Tunnels do not run their own cryptographic handshake; instead they inherit their trust from the already-authenticated agent control channel and from TLS certificate pinning.
The agent opens two categories of WebSocket connections to the MeshCentral server:
| Connection | Endpoint | Purpose |
|---|---|---|
| Control channel | wss://<server>/agent.ashx |
Long-lived, mutually authenticated command channel. |
| Relay tunnel | wss://<server>/meshrelay.ashx?... |
Short-lived session for one remote desktop, files, or file-transfer operation. |
The control channel is where the real authentication happens. Every tunnel is requested over that authenticated channel, so a tunnel is only as trustworthy as the control channel that spawned it.
Relevant code:
Before any connection, the device is paired with a server using an mc://
pairing link (scanned by QR code, opened as a deep link, or typed manually):
mc://<server-host>,<server-identity-hash>,<device-group-id>
<server-host> is the host the agent connects to.<server-identity-hash> is the expected hash of the server’s agent
certificate public key. The agent stores it as serverCertHash and uses it to
verify the server during the handshake.<device-group-id> is the mesh/device group the device enrolls into.The agent also generates its own 2048-bit RSA key pair and a self-signed X.509 certificate at first run. This is the device identity used to sign the handshake.
The control channel handshake is a mutual challenge/response that binds the application-layer identities to the underlying TLS session. It runs over four binary commands.
The agent connects with a custom TrustManager that does not reject the
server’s TLS certificate, but records the SHA-384 hash of it into
serverTlsCertHash. Hostname verification is also bypassed. Transport security
therefore does not come from a public CA; it comes from pinning this hash
during the application handshake and again on every tunnel (see below).
See getUnsafeOkHttpClient() in
MeshAgent.kt.
On connection open, the agent generates a 48-byte random nonce and sends:
[0x00, 0x01] + serverTlsCertHash (SHA-384) + nonce
The server replies with command 1 containing its own view of the TLS certificate hash plus a server nonce. The agent:
serverTlsCertHash.
A mismatch aborts the connection.serverTlsCertHash + serverNonce + agentNonce with the agent’s private
key using SHA384withRSA.This proves to the server that the agent owns the private key for its identity and that both sides are on the same TLS session.
The server sends its own certificate and a signature. The agent:
serverCertHash from the pairing link. This is what proves the agent is
talking to the correct server, not just any server.serverTlsCertHash + agentNonce + serverNonce.If either check fails, the connection is dropped. On success the agent marks the server side of the connection as verified and sends its core information (command 3): agent type, platform, device group id, capabilities, and device name.
When the server accepts the agent’s identity, it sends command 4. Once both the
server-verified bit and the server-confirmed bit are set, the channel becomes
fully authenticated (state = 3) and normal JSON messaging begins.
Remote sessions are never initiated by an unauthenticated peer. The server sends
a JSON msg of type tunnel over the authenticated control channel:
{
"action": "msg",
"type": "tunnel",
"value": "*/meshrelay.ashx?...",
"usage": 5,
"servertlshash": "97eaf674...",
"userid": "user//admin",
"username": "admin",
"rights": 4294967295,
"consent": 0
}
Key fields:
value is the relay URL. It carries a one-time, server-generated
authentication cookie in its query string. A leading */ is rewritten to
wss://<host>/meshrelay.ashx?....servertlshash is the SHA-384 hash the tunnel must see on the relay server’s
TLS certificate.usage selects the session type: 2 = remote desktop, 5 = files,
10 = file transfer.userid, username, rights, and consent describe the operator and their
permissions for consent prompts and the privacy bar.The agent constructs a MeshTunnel from this message and starts it. See the
"tunnel" handler in
MeshAgent.kt and
Start() in
MeshTunnel.kt.
The tunnel opens a new WebSocket to the relay URL. Its TrustManager pins the
relay server’s TLS certificate: it computes the SHA-384 hash of the presented
certificate and accepts the connection only if the hash matches either:
servertlshash supplied in the authenticated tunnel request, orserverTlsCertHash recorded on the control channel.Any other certificate throws a CertificateException and the tunnel is closed.
This ties the relay connection back to the server the agent already
authenticated, so the one-time relay cookie cannot be replayed against a
different TLS endpoint.
See getUnsafeOkHttpClient() / checkServerTrusted in
MeshTunnel.kt.
After the relay socket connects, the tunnel advances through a small state
machine driven by onMessage:
c or cr to signal the two
endpoints are connected.usage (and optional
options JSON). The agent validates the usage value and confirms it matches
the usage from the original tunnel request via isTunnelUsageAllowed(...).
A mismatch closes the tunnel.For a remote desktop tunnel (usage == 2), if the automatic-consent preference
is off and no capture session is active, the agent asks the user to grant screen
sharing before streaming begins. With automatic consent enabled, or an existing
capture already running, the session starts immediately.