From c5d0eaa03419dac38b6e76b3f06c004eacc39af4 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Thu, 16 Feb 2023 02:19:14 +0100 Subject: [PATCH] [security] Add security number to PeerUI to make verification of peer-to-peer encryption possible. --- docs/faq.md | 2 +- public/scripts/network.js | 28 ++++++++++++++- public/scripts/ui.js | 12 ++++--- public/scripts/util.js | 18 ++++++++++ public/styles.css | 8 ++++- .../scripts/network.js | 35 +++++++++++++++++-- public_included_ws_fallback/scripts/ui.js | 12 ++++--- public_included_ws_fallback/scripts/util.js | 18 ++++++++++ public_included_ws_fallback/styles.css | 21 +++++------ 9 files changed, 131 insertions(+), 23 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 770a7eb..2385a47 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -51,7 +51,7 @@ WebRTC encrypts the files on transit. If your devices are paired and behind a NAT, the public TURN Server from [Open Relay](https://www.metered.ca/tools/openrelay/) is used to route your files and messages. ### What about security? Are my files encrypted while being sent between the computers? -Yes. Your files are sent using WebRTC, which encrypts them on transit. +Yes. Your files are sent using WebRTC, which encrypts them on transit. To ensure the connection is secure and there is no MITM, compare the security number shown under the device name on both devices. The security number is different for every connection. ### Transferring many files with paired devices takes too long Naturally, if traffic needs to be routed through the turn server transfer speed decreases. diff --git a/public/scripts/network.js b/public/scripts/network.js index f59c74f..be1389f 100644 --- a/public/scripts/network.js +++ b/public/scripts/network.js @@ -558,7 +558,7 @@ class RTCPeer extends Peer { _onChannelOpened(event) { console.log('RTC: channel opened with', this._peerId); - Events.fire('peer-connected', this._peerId); + Events.fire('peer-connected', {peerId: this._peerId, connectionHash: this.getConnectionHash()}); const channel = event.channel || event.target; channel.binaryType = 'arraybuffer'; channel.onmessage = e => this._onMessage(e.data); @@ -568,6 +568,32 @@ class RTCPeer extends Peer { this._channel = channel; } + getConnectionHash() { + const localDescriptionLines = this._conn.localDescription.sdp.split("\r\n"); + const remoteDescriptionLines = this._conn.remoteDescription.sdp.split("\r\n"); + let localConnectionFingerprint, remoteConnectionFingerprint; + for (let i=0; i this._onPeerJoined(e.detail)); - Events.on('peer-connected', e => this._onPeerConnected(e.detail)); + Events.on('peer-connected', e => this._onPeerConnected(e.detail.peerId, e.detail.connectionHash)); Events.on('peer-disconnected', e => this._onPeerDisconnected(e.detail)); Events.on('peers', e => this._onPeers(e.detail)); Events.on('set-progress', e => this._onSetProgress(e.detail)); @@ -63,9 +63,9 @@ class PeersUI { this.peers[peer.id] = peer; } - _onPeerConnected(peerId) { + _onPeerConnected(peerId, connectionHash) { if(this.peers[peerId] && !$(peerId)) - new PeerUI(this.peers[peerId]); + new PeerUI(this.peers[peerId], connectionHash); } _redrawPeer(peer) { @@ -235,17 +235,21 @@ class PeerUI {
+ `; this.$el.querySelector('svg use').setAttribute('xlink:href', this._icon()); this.$el.querySelector('.name').textContent = this._displayName(); this.$el.querySelector('.device-name').textContent = this._deviceName(); + this.$el.querySelector('.connection-hash').textContent = + this._connectionHash.substring(0, 4) + " " + this._connectionHash.substring(4, 8) + " " + this._connectionHash.substring(8, 12) + " " + this._connectionHash.substring(12, 16); } - constructor(peer) { + constructor(peer, connectionHash) { this._peer = peer; this._roomType = peer.roomType; this._roomSecret = peer.roomSecret; + this._connectionHash = connectionHash; this._initDom(); this._bindListeners(); $$('x-peers').appendChild(this.$el); diff --git a/public/scripts/util.js b/public/scripts/util.js index 439dd5c..8b333ee 100644 --- a/public/scripts/util.js +++ b/public/scripts/util.js @@ -380,3 +380,21 @@ const mime = (() => { }; })(); + +/* + cyrb53 (c) 2018 bryc (github.com/bryc) + A fast and simple hash function with decent collision resistance. + Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity. + Public domain. Attribution appreciated. +*/ +const cyrb53 = function(str, seed = 0) { + let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed; + for (let i = 0, ch; i < str.length; i++) { + ch = str.charCodeAt(i); + h1 = Math.imul(h1 ^ ch, 2654435761); + h2 = Math.imul(h2 ^ ch, 1597334677); + } + h1 = Math.imul(h1 ^ (h1>>>16), 2246822507) ^ Math.imul(h2 ^ (h2>>>13), 3266489909); + h2 = Math.imul(h2 ^ (h2>>>16), 2246822507) ^ Math.imul(h1 ^ (h1>>>13), 3266489909); + return 4294967296 * (2097151 & h2) + (h1>>>0); +}; diff --git a/public/styles.css b/public/styles.css index 2df6f27..7497b77 100644 --- a/public/styles.css +++ b/public/styles.css @@ -304,7 +304,8 @@ x-peer[status] x-icon { } .status, -.device-name { +.device-name, +.connection-hash { height: 18px; opacity: 0.7; } @@ -314,6 +315,11 @@ x-peer[status] x-icon { white-space: nowrap; } +.connection-hash { + font-size: 12px; + white-space: nowrap; +} + x-peer[status=transfer] .status:before { content: 'Transferring...'; } diff --git a/public_included_ws_fallback/scripts/network.js b/public_included_ws_fallback/scripts/network.js index 3f8e9db..f739465 100644 --- a/public_included_ws_fallback/scripts/network.js +++ b/public_included_ws_fallback/scripts/network.js @@ -568,7 +568,7 @@ class RTCPeer extends Peer { _onChannelOpened(event) { console.log('RTC: channel opened with', this._peerId); - Events.fire('peer-connected', this._peerId); + Events.fire('peer-connected', {peerId: this._peerId, connectionHash: this.getConnectionHash()}); const channel = event.channel || event.target; channel.binaryType = 'arraybuffer'; channel.onmessage = e => this._onMessage(e.data); @@ -578,6 +578,32 @@ class RTCPeer extends Peer { this._channel = channel; } + getConnectionHash() { + const localDescriptionLines = this._conn.localDescription.sdp.split("\r\n"); + const remoteDescriptionLines = this._conn.remoteDescription.sdp.split("\r\n"); + let localConnectionFingerprint, remoteConnectionFingerprint; + for (let i=0; i this._onPeerJoined(e.detail)); - Events.on('peer-connected', e => this._onPeerConnected(e.detail)); + Events.on('peer-connected', e => this._onPeerConnected(e.detail.peerId, e.detail.connectionHash)); Events.on('peer-disconnected', e => this._onPeerDisconnected(e.detail)); Events.on('peers', e => this._onPeers(e.detail)); Events.on('set-progress', e => this._onSetProgress(e.detail)); @@ -63,9 +63,9 @@ class PeersUI { this.peers[peer.id] = peer; } - _onPeerConnected(peerId) { + _onPeerConnected(peerId, connectionHash) { if(this.peers[peerId] && !$(peerId)) - new PeerUI(this.peers[peerId]); + new PeerUI(this.peers[peerId], connectionHash); } _redrawPeer(peer) { @@ -235,17 +235,21 @@ class PeerUI {
+ `; this.$el.querySelector('svg use').setAttribute('xlink:href', this._icon()); this.$el.querySelector('.name').textContent = this._displayName(); this.$el.querySelector('.device-name').textContent = this._deviceName(); + this.$el.querySelector('.connection-hash').textContent = + this._connectionHash.substring(0, 4) + " " + this._connectionHash.substring(4, 8) + " " + this._connectionHash.substring(8, 12) + " " + this._connectionHash.substring(12, 16); } - constructor(peer) { + constructor(peer, connectionHash) { this._peer = peer; this._roomType = peer.roomType; this._roomSecret = peer.roomSecret; + this._connectionHash = connectionHash; this._initDom(); this._bindListeners(); $$('x-peers').appendChild(this.$el); diff --git a/public_included_ws_fallback/scripts/util.js b/public_included_ws_fallback/scripts/util.js index 8eadf01..05a9bc1 100644 --- a/public_included_ws_fallback/scripts/util.js +++ b/public_included_ws_fallback/scripts/util.js @@ -381,6 +381,24 @@ const mime = (() => { })(); +/* + cyrb53 (c) 2018 bryc (github.com/bryc) + A fast and simple hash function with decent collision resistance. + Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity. + Public domain. Attribution appreciated. +*/ +const cyrb53 = function(str, seed = 0) { + let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed; + for (let i = 0, ch; i < str.length; i++) { + ch = str.charCodeAt(i); + h1 = Math.imul(h1 ^ ch, 2654435761); + h2 = Math.imul(h2 ^ ch, 1597334677); + } + h1 = Math.imul(h1 ^ (h1>>>16), 2246822507) ^ Math.imul(h2 ^ (h2>>>13), 3266489909); + h2 = Math.imul(h2 ^ (h2>>>16), 2246822507) ^ Math.imul(h1 ^ (h1>>>13), 3266489909); + return 4294967296 * (2097151 & h2) + (h1>>>0); +}; + function arrayBufferToBase64(buffer) { var binary = ''; var bytes = new Uint8Array(buffer); diff --git a/public_included_ws_fallback/styles.css b/public_included_ws_fallback/styles.css index 8779a8c..8d3c5e0 100644 --- a/public_included_ws_fallback/styles.css +++ b/public_included_ws_fallback/styles.css @@ -313,7 +313,8 @@ x-peer[status] x-icon { } .status, -.device-name { +.device-name, +.connection-hash { height: 18px; opacity: 0.7; } @@ -323,6 +324,11 @@ x-peer[status] x-icon { white-space: nowrap; } +.connection-hash { + font-size: 12px; + white-space: nowrap; +} + x-peer[status=transfer] .status:before { content: 'Transferring...'; } @@ -389,22 +395,17 @@ footer .logo { footer .font-body2 { color: var(--primary-color); - text-underline-position: under; margin: auto 18px; } #on-this-network { - text-decoration-line: underline; - text-decoration-style: solid; - text-decoration-color: var(--primary-color); - text-decoration-thickness: 4px; + border-bottom: solid 4px var(--primary-color); + padding-bottom: 1px; } #paired-devices { - text-decoration-line: underline; - text-decoration-style: solid; - text-decoration-color: var(--paired-device-color); - text-decoration-thickness: 4px; + border-bottom: solid 4px var(--paired-device-color); + padding-bottom: 1px; } /* Dialog */