Files
rustdesk/src/connection.ts

268 lines
7.8 KiB
TypeScript
Raw Normal View History

2022-01-20 15:41:11 +08:00
import Websock from './websock';
import * as message from './message.js';
import * as rendezvous from './rendezvous.js';
import { loadVp9, loadOpus } from './codec';
2022-01-20 18:02:20 +08:00
import * as sha256 from "fast-sha256";
2022-01-20 15:41:11 +08:00
import * as globals from './globals';
2022-01-20 01:00:35 +08:00
2022-01-20 02:27:49 +08:00
const PORT = 21116;
2022-01-20 15:41:11 +08:00
const HOST = 'rs-sg.rustdesk.com';
const licenceKey = '';
const SCHEMA = 'ws://';
2022-01-20 01:00:35 +08:00
2022-01-20 18:02:20 +08:00
type MsgboxCallback = (type: string, title: string, text: string) => void;
2022-01-20 02:27:49 +08:00
export default class Connection {
2022-01-20 01:00:35 +08:00
_msgs: any[];
_ws: Websock | undefined;
_interval: any;
_id: string;
2022-01-20 18:02:20 +08:00
_hash: message.Hash | undefined;
_msgbox: MsgboxCallback | undefined;
2022-01-20 18:41:35 +08:00
_peerInfo: message.PeerInfo | undefined;
2022-01-20 18:44:28 +08:00
_firstFrame: Boolean | undefined;
2022-01-20 01:00:35 +08:00
constructor() {
this._msgs = [];
2022-01-20 12:49:57 +08:00
this._id = '';
2022-01-20 01:00:35 +08:00
this._interval = setInterval(() => {
while (this._msgs.length) {
this._ws?.sendMessage(this._msgs[0]);
this._msgs.splice(0, 1);
}
}, 1);
}
2022-01-20 02:27:49 +08:00
async start(id: string) {
2022-01-20 18:02:20 +08:00
const uri = getDefaultUri();
const ws = new Websock(uri);
2022-01-20 01:00:35 +08:00
this._ws = ws;
this._id = id;
2022-01-20 18:02:20 +08:00
console.log(new Date() + ': Conntecting to rendezvoous server: ' + uri);
2022-01-20 01:00:35 +08:00
await ws.open();
2022-01-20 18:02:20 +08:00
console.log(new Date() + ': Connected to rendezvoous server');
2022-01-20 01:00:35 +08:00
const connType = rendezvous.ConnType.DEFAULT_CONN;
const natType = rendezvous.NatType.SYMMETRIC;
2022-01-20 15:41:11 +08:00
const punchHoleRequest = rendezvous.PunchHoleRequest.fromPartial({
2022-01-20 01:00:35 +08:00
id,
licenceKey,
connType,
natType,
});
ws.sendRendezvous({ punchHoleRequest });
const msg = ws.parseRendezvous(await ws.next());
2022-01-20 18:02:20 +08:00
ws.close();
console.log(new Date() + ': Got relay response');
2022-01-20 01:00:35 +08:00
const phr = msg.punchHoleResponse;
const rr = msg.relayResponse;
if (phr) {
if (phr.failure != rendezvous.PunchHoleResponse_Failure.UNKNOWN) {
switch (phr?.failure) {
case rendezvous.PunchHoleResponse_Failure.ID_NOT_EXIST:
2022-01-20 18:02:20 +08:00
this.msgbox('error', 'Error', 'ID does not exist');
2022-01-20 01:00:35 +08:00
break;
2022-01-20 18:02:20 +08:00
case rendezvous.PunchHoleResponse_Failure.OFFLINE:
this.msgbox('error', 'Error', 'Remote desktop is offline');
break;
case rendezvous.PunchHoleResponse_Failure.LICENSE_MISMATCH:
this.msgbox('error', 'Error', 'Key mismatch');
break;
default:
if (phr?.otherFailure) {
this.msgbox('error', 'Error', phr?.otherFailure);
}
2022-01-20 01:00:35 +08:00
}
}
} else if (rr) {
await this.connectRelay(rr);
}
}
async connectRelay(rr: rendezvous.RelayResponse) {
const pk = rr.pk;
let uri = rr.relayServer;
2022-01-20 02:27:49 +08:00
if (uri) {
uri = getrUriFromRs(uri);
2022-01-20 01:00:35 +08:00
} else {
2022-01-20 02:27:49 +08:00
uri = getDefaultUri(true);
2022-01-20 01:00:35 +08:00
}
const uuid = rr.uuid;
2022-01-20 18:02:20 +08:00
console.log(new Date() + ': Connecting to relay server: ' + uri);
2022-01-20 02:27:49 +08:00
const ws = new Websock(uri);
2022-01-20 01:00:35 +08:00
await ws.open();
2022-01-20 18:02:20 +08:00
console.log(new Date() + ': Connected to relay server');
2022-01-20 01:00:35 +08:00
this._ws = ws;
2022-01-20 15:41:11 +08:00
const requestRelay = rendezvous.RequestRelay.fromPartial({
2022-01-20 01:00:35 +08:00
licenceKey,
uuid,
});
ws.sendRendezvous({ requestRelay });
await this.secure(pk);
2022-01-20 18:02:20 +08:00
await this.msgLoop();
2022-01-20 01:00:35 +08:00
}
async secure(pk: Uint8Array | undefined) {
2022-01-20 12:49:57 +08:00
if (pk) {
const RS_PK = 'OeVuKk5nlHiXp+APNn0Y3pC1Iwpwn44JGqrQCsWqmBw=';
2022-01-20 15:41:11 +08:00
try {
pk = await globals.verify(pk, RS_PK).catch();
if (pk?.length != 32) {
pk = undefined;
}
} catch (e) {
console.error(e);
pk = undefined;
}
if (!pk) console.error('Handshake failed: invalid public key from rendezvous server');
}
if (!pk) {
// send an empty message out in case server is setting up secure and waiting for first message
await this._ws?.sendMessage({});
return;
}
const msg = this._ws?.parseMessage(await this._ws?.next());
let signedId: any = msg?.signedId;
if (!signedId) {
console.error("Handshake failed: invalid message type");
await this._ws?.sendMessage({});
return;
}
try {
signedId = await globals.verify(signedId.id, Uint8Array.from(pk!));
} catch (e) {
console.error(e);
// fall back to non-secure connection in case pk mismatch
console.error("pk mismatch, fall back to non-secure");
const publicKey = message.PublicKey.fromPartial({});
await this._ws?.sendMessage({ publicKey });
return;
}
signedId = new TextDecoder().decode(signedId!);
const tmp = signedId.split('\0');
const id = tmp[0];
let theirPk = tmp[1];
if (id != this._id!) {
console.error("Handshake failed: sign failure");
await this._ws?.sendMessage({});
return;
}
theirPk = globals.decodeBase64(theirPk);
if (theirPk.length != 32) {
console.error("Handshake failed: invalid public box key length from peer");
await this._ws?.sendMessage({});
return;
2022-01-20 12:49:57 +08:00
}
2022-01-20 15:41:11 +08:00
const [mySk, asymmetricValue] = globals.genBoxKeyPair();
const secretKey = globals.genSecretKey();
const symmetricValue = globals.seal(secretKey, theirPk, mySk);
const publicKey = message.PublicKey.fromPartial({ asymmetricValue, symmetricValue });
await this._ws?.sendMessage({ publicKey });
this._ws?.setSecretKey(secretKey)
2022-01-20 01:00:35 +08:00
}
2022-01-20 18:02:20 +08:00
async msgLoop() {
while (true) {
const msg = this._ws?.parseMessage(await this._ws?.next());
if (msg?.hash) {
this._hash = msg?.hash;
2022-01-20 18:41:35 +08:00
await this.handleHash();
2022-01-20 18:02:20 +08:00
this.msgbox("input-password", "Password Required", "");
} else if (msg?.testDelay) {
const testDelay = msg?.testDelay;
if (!testDelay.fromClient) {
await this._ws?.sendMessage({ testDelay });
}
2022-01-20 18:41:35 +08:00
} else if (msg?.loginResponse) {
const r = msg?.loginResponse;
if (r.error) {
this.msgbox('error', 'Error', r.error);
} else if (r.peerInfo) {
this._peerInfo = r.peerInfo;
this.msgbox('success', 'Successful', 'Connected, waiting for image...');
}
2022-01-20 18:44:28 +08:00
} else if (msg?.videoFrame) {
this.handleVideoFrame();
2022-01-20 18:02:20 +08:00
}
}
}
2022-01-20 18:41:35 +08:00
async handleHash() {
await this._sendLoginMessage();
}
2022-01-20 18:02:20 +08:00
msgbox(type_: string, title: string, text: string) {
this._msgbox?.(type_, title, text);
}
2022-01-20 18:41:35 +08:00
close() {
clearInterval(this._interval);
this._ws?.close();
}
setMsgbox(callback: MsgboxCallback) {
this._msgbox = callback;
}
async login(password: string) {
this.msgbox('connecting', 'Connecting...', 'Logging in...');
let salt = this._hash?.salt;
if (salt) {
let p = hash([password, salt]);
let challenge = this._hash?.challenge;
if (challenge) {
p = hash([p, challenge]);
await this._sendLoginMessage(p);
}
}
}
async _sendLoginMessage(password: Uint8Array | undefined = undefined) {
const loginRequest = message.LoginRequest.fromPartial({
username: this._id!,
myId: 'web', // to-do
myName: 'web', // to-do
password,
});
await this._ws?.sendMessage({ loginRequest });
}
2022-01-20 18:44:28 +08:00
handleVideoFrame() {
if (!this._firstFrame) {
this.msgbox('', '', '');
this._firstFrame = true;
}
}
2022-01-20 01:00:35 +08:00
}
async function testDelay() {
2022-01-20 02:27:49 +08:00
const ws = new Websock(getDefaultUri(false));
2022-01-20 01:00:35 +08:00
await ws.open();
console.log(ws.latency());
}
2022-01-20 02:27:49 +08:00
function getDefaultUri(isRelay: Boolean = false): string {
2022-01-20 15:41:11 +08:00
const host = localStorage.getItem('host');
return SCHEMA + (host || HOST) + ':' + (PORT + (isRelay ? 3 : 2));
2022-01-20 02:27:49 +08:00
}
function getrUriFromRs(uri: string): string {
2022-01-20 15:41:11 +08:00
if (uri.indexOf(':') > 0) {
const tmp = uri.split(':');
2022-01-20 02:27:49 +08:00
const port = parseInt(tmp[1]);
2022-01-20 15:41:11 +08:00
uri = tmp[0] + ':' + (port + 2);
2022-01-20 02:27:49 +08:00
} else {
2022-01-20 15:41:11 +08:00
uri += ':' + (PORT + 3);
2022-01-20 02:27:49 +08:00
}
2022-01-20 12:49:57 +08:00
return SCHEMA + uri;
2022-01-20 18:02:20 +08:00
}
2022-01-20 18:41:35 +08:00
function hash(datas: (string | Uint8Array)[]): Uint8Array {
2022-01-20 18:02:20 +08:00
const hasher = new sha256.Hash();
2022-01-20 18:41:35 +08:00
datas.forEach((data) => {
if (typeof data == 'string') {
data = new TextEncoder().encode(data);
}
return hasher.update(data);
});
2022-01-20 18:02:20 +08:00
return hasher.digest();
2022-01-20 12:49:57 +08:00
}