This commit is contained in:
Taneli Leppä 2021-10-04 22:07:47 +02:00 committed by GitHub
commit 24641a1b98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 281 additions and 93 deletions

22
Dockerfile Normal file
View File

@ -0,0 +1,22 @@
FROM golang:1.17
WORKDIR $GOPATH/src/github.com/amitbet/vncproxy
RUN mkdir -p $GOPATH/src/github.com/amitbet/vncproxy
COPY . .
RUN cd $GOPATH/src/github.com/amitbet/vncproxy/recorder/cmd && \
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o /recorder .
RUN cd $GOPATH/src/github.com/amitbet/vncproxy/proxy/cmd && \
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o /proxy .
RUN cd $GOPATH/src/github.com/amitbet/vncproxy/player/cmd && \
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o /player .
FROM scratch
COPY --from=0 /recorder /recorder
COPY --from=0 /proxy /proxy
COPY --from=0 /player /player
EXPOSE 5900
ENTRYPOINT ["/proxy"]

View File

@ -8,6 +8,7 @@ An RFB proxy, written in go that can save and replay FBS files
* Can also be used as:
* A screen recorder vnc-client
* A replay server to show fbs recordings to connecting clients
* Authentication proxy for reMarkable tablet (2.10+)
- Tested on tight encoding with:
- Tightvnc (client + java client + server)
@ -40,6 +41,21 @@ An RFB proxy, written in go that can save and replay FBS files
* Listens to Tcp & WS ports
* Replays a hard-coded FBS file in normal speed to all connecting vnc clients
### Examples of using with reMarkable
* Simply run the proxy with the `-reMarkable DEVICE_ID` flag
* To get the `DEVICE_ID`:
* Log into reMarkable via SSH
* Extract the `devicetoken` string (exclude the `@ByteArray` wrapper) the string from `/etc/remarkable.conf`
* Run the following Python snippet to decrypt the `devicetoken`:
```
pip3 install --user PyJWT
python3 -c 'import sys,jwt;t=jwt.decode(sys.argv[1],options={"verify_signature":False});print(t)' '(DEVICE TOKEN HERE)'
```
* In output, you should get a string starting with `auth0|`. The whole string is your device ID which should
be passed to be `-reMarkable` flag.
* After you should be able to connect to the reMarkable via the proxy with a normal
VNC client (tested with TightVNC)
## **Architecture**
![Image of Arch](https://github.com/amitbet/vncproxy/blob/master/architecture/proxy-arch.png?raw=true)

View File

@ -4,11 +4,12 @@ import (
"bytes"
"encoding/binary"
"fmt"
"github.com/amitbet/vncproxy/common"
"github.com/amitbet/vncproxy/logger"
"io"
"net"
"unicode"
"github.com/amitbet/vncproxy/common"
"github.com/amitbet/vncproxy/logger"
)
// A ServerMessage implements a message sent from the server to the client.

View File

@ -1,9 +1,13 @@
package client
import (
"bytes"
"crypto/des"
"crypto/sha256"
"encoding/binary"
"io"
"github.com/amitbet/vncproxy/logger"
)
// ClientAuthNone is the "none" authentication. See 7.2.1
@ -110,3 +114,46 @@ func (p *PasswordAuth) encrypt(key string, bytes []byte) ([]byte, error) {
return crypted, nil
}
// RemarkableAuth is the authentication method used by reMarkable tablet's
// Screen Sharing feature
type RemarkableAuth struct {
RemarkableTimestamp uint64
RemarkableDeviceId string
}
func (p *RemarkableAuth) SecurityType() uint8 {
return 100
}
func (p *RemarkableAuth) Handshake(c io.ReadWriteCloser) error {
userIdHash := sha256.Sum256([]byte(p.RemarkableDeviceId))
var tsBuf []byte
pb := bytes.NewBuffer(tsBuf)
err := binary.Write(pb, binary.BigEndian, p.RemarkableTimestamp)
if err != nil {
return err
}
logger.Debugf("Hash with timestamp: %x", pb.Bytes())
userIdAndTs := append(pb.Bytes(), userIdHash[:]...)
logger.Debugf("Hash with timestamp and user ID hash: %x", userIdAndTs)
challenge := sha256.Sum256(userIdAndTs)
var challengeLength uint32 = uint32(len(challenge))
logger.Debugf("Writing challenge length: %v", challengeLength)
if err := binary.Write(c, binary.BigEndian, challengeLength); err != nil {
return err
}
logger.Debugf("Writing challenge: %x", challenge)
if err := binary.Write(c, binary.BigEndian, challenge); err != nil {
return err
}
// Some reason there is another security result... we're just gonna ignore this.
var securityResult uint8
if err = binary.Read(c, binary.BigEndian, &securityResult); err != nil {
return err
}
return nil
}

2
go.mod
View File

@ -1,3 +1,5 @@
module github.com/amitbet/vncproxy
require golang.org/x/net v0.0.0-20181129055619-fae4c4e3ad76
go 1.13

View File

@ -4,6 +4,8 @@ import (
"flag"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/amitbet/vncproxy/logger"
vncproxy "github.com/amitbet/vncproxy/proxy"
@ -20,7 +22,9 @@ func main() {
var targetVncHost = flag.String("targHost", "", "target vnc server host (deprecated, use -target)")
var targetVncPass = flag.String("targPass", "", "target vnc password")
var logLevel = flag.String("logLevel", "info", "change logging level")
var reMarkable = flag.String("reMarkable", "", "reMarkable device ID (enable reMarkable 2.10+ support)")
var overrideEncodings = flag.String("overrideEncodings", "", "force a specific set of encodings to be sent to the target vnc server (encoding types separated by comma)")
var tls = flag.Bool("tls", false, "use TLS connection (turned on automatically if reMarkable)")
flag.Parse()
logger.SetLogLevel(*logLevel)
@ -48,6 +52,16 @@ func main() {
if *wsPort != "" {
wsURL = "http://0.0.0.0:" + string(*wsPort) + "/"
}
var overrideEncodingsList []uint32
if *overrideEncodings != "" {
for _, enc := range strings.Split(*overrideEncodings, ",") {
encI, err := strconv.Atoi(enc)
if err != nil {
panic(err)
}
overrideEncodingsList = append(overrideEncodingsList, uint32(encI))
}
}
proxy := &vncproxy.VncProxy{
WsListeningURL: wsURL, // empty = not listening on ws
TCPListeningURL: tcpURL,
@ -60,6 +74,9 @@ func main() {
ID: "dummySession",
Status: vncproxy.SessionStatusInit,
Type: vncproxy.SessionTypeProxyPass,
RemarkableDeviceId: *reMarkable,
TLS: *reMarkable != "" || *tls,
OverrideEncodings: overrideEncodingsList,
}, // to be used when not using sessions
UsingSessions: false, //false = single session - defined in the var above
}
@ -75,6 +92,10 @@ func main() {
} else {
logger.Info("FBS recording is turned off")
}
if *reMarkable != "" {
logger.Info("reMarkable 2.10+ support turned on")
proxy.Remarkable = true
}
proxy.StartListening()
}

View File

@ -9,6 +9,8 @@ import (
type ClientUpdater struct {
conn *client.ClientConn
suppressedMessageTypes []common.ClientMessageType
overrideEncodings []common.EncodingType
}
// Consume recieves vnc-server-bound messages (Client messages) and updates the server part of the proxy
@ -19,8 +21,15 @@ func (cc *ClientUpdater) Consume(seg *common.RfbSegment) error {
case common.SegmentFullyParsedClientMessage:
clientMsg := seg.Message.(common.ClientMessage)
logger.Debugf("ClientUpdater.Consume:(vnc-server-bound) got ClientMessage type=%s", clientMsg.Type())
switch clientMsg.Type() {
switch clientMsg.Type() {
case common.SetEncodingsMsgType:
if len(cc.overrideEncodings) > 0 {
logger.Debugf("ClientUpdater.Consume:(vnc-server-bound) overriding supported encodings with %v", cc.overrideEncodings)
encodingsMsg := clientMsg.(*server.MsgSetEncodings)
encodingsMsg.EncNum = uint16(len(cc.overrideEncodings))
encodingsMsg.Encodings = cc.overrideEncodings
}
case common.SetPixelFormatMsgType:
// update pixel format
logger.Debugf("ClientUpdater.Consume: updating pixel format")
@ -28,6 +37,18 @@ func (cc *ClientUpdater) Consume(seg *common.RfbSegment) error {
cc.conn.PixelFormat = pixFmtMsg.PF
}
suppressMessage := false
for _, suppressed := range cc.suppressedMessageTypes {
if suppressed == clientMsg.Type() {
suppressMessage = true
break
}
}
if suppressMessage {
logger.Infof("ClientUpdater.Consume:(vnc-server-bound) Suppressing client message type=%s", clientMsg.Type())
return nil
}
err := clientMsg.Write(cc.conn)
if err != nil {
logger.Errorf("ClientUpdater.Consume (vnc-server-bound, SegmentFullyParsedClientMessage): problem writing to port: %s", err)

View File

@ -1,6 +1,9 @@
package proxy
import (
"bytes"
"crypto/tls"
"encoding/binary"
"net"
"path"
"strconv"
@ -22,10 +25,11 @@ type VncProxy struct {
ProxyVncPassword string //empty = no auth
SingleSession *VncSession // to be used when not using sessions
UsingSessions bool //false = single session - defined in the var above
Remarkable bool
sessionManager *SessionManager
}
func (vp *VncProxy) createClientConnection(target string, vncPass string) (*client.ClientConn, error) {
func (vp *VncProxy) createClientConnection(target string, vncPass string, tlsEnabled bool, reMarkableDeviceId string) (*client.ClientConn, error) {
var (
nc net.Conn
err error
@ -35,6 +39,19 @@ func (vp *VncProxy) createClientConnection(target string, vncPass string) (*clie
nc, err = net.Dial("unix", target)
} else {
nc, err = net.Dial("tcp", target)
if tlsEnabled {
logger.Info("Upgrading to TLS connection...")
config := tls.Config{
InsecureSkipVerify: true,
}
tc := tls.Client(nc, &config)
err = tc.Handshake()
if err != nil {
return nil, err
}
nc = tc
}
}
if err != nil {
@ -44,6 +61,31 @@ func (vp *VncProxy) createClientConnection(target string, vncPass string) (*clie
var noauth client.ClientAuthNone
authArr := []client.ClientAuth{&client.PasswordAuth{Password: vncPass}, &noauth}
var rmTimestamp uint64
if reMarkableDeviceId != "" {
logger.Info("Enabling reMarkable 2.10+ support")
authC, err := net.ListenPacket("udp", ":5901")
if err != nil {
return nil, err
}
defer authC.Close()
buffer := make([]byte, 128) // datagram size 51 bytes
n, addr, err := authC.ReadFrom(buffer)
logger.Debugf("Received datagram from reMarkable (%v, %v bytes)", addr, n)
pb := bytes.NewBuffer(buffer)
err = binary.Read(pb, binary.BigEndian, &rmTimestamp)
if err != nil {
return nil, err
}
logger.Debugf("reMarkable timestamp is %v", rmTimestamp)
authArr = append(authArr, &client.RemarkableAuth{
RemarkableTimestamp: rmTimestamp,
RemarkableDeviceId: reMarkableDeviceId,
})
}
clientConn, err := client.NewClientConn(nc,
&client.ClientConfig{
@ -100,7 +142,7 @@ func (vp *VncProxy) newServerConnHandler(cfg *server.ServerConfig, sconn *server
target = session.TargetHostname + ":" + session.TargetPort
}
cconn, err := vp.createClientConnection(target, session.TargetPassword)
cconn, err := vp.createClientConnection(target, session.TargetPassword, session.TLS, session.RemarkableDeviceId)
if err != nil {
session.Status = SessionStatusError
logger.Errorf("Proxy.newServerConnHandler error creating connection: %s", err)
@ -119,7 +161,16 @@ func (vp *VncProxy) newServerConnHandler(cfg *server.ServerConfig, sconn *server
// gets the messages from the server part (from vnc-client),
// and write through the client to the actual vnc-server
clientUpdater := &ClientUpdater{cconn}
var clientUpdater *ClientUpdater
clientUpdater = &ClientUpdater{conn: cconn}
if session.RemarkableDeviceId != "" {
clientUpdater.suppressedMessageTypes = []common.ClientMessageType{common.SetPixelFormatMsgType}
}
if len(session.OverrideEncodings) > 0 {
for _, enc := range session.OverrideEncodings {
clientUpdater.overrideEncodings = append(clientUpdater.overrideEncodings, common.EncodingType(enc))
}
}
sconn.Listeners.AddListener(clientUpdater)
err = cconn.Connect()
@ -185,6 +236,9 @@ func (vp *VncProxy) StartListening() {
NewConnHandler: vp.newServerConnHandler,
UseDummySession: !vp.UsingSessions,
}
if vp.Remarkable {
cfg.DesktopName = []byte("reMarkable rfb")
}
if vp.TCPListeningURL != "" && vp.WsListeningURL != "" {
logger.Infof("running two listeners: tcp port: %s, ws url: %s", vp.TCPListeningURL, vp.WsListeningURL)

View File

@ -24,4 +24,7 @@ type VncSession struct {
Status SessionStatus
Type SessionType
ReplayFilePath string
RemarkableDeviceId string
TLS bool
OverrideEncodings []uint32
}

View File

@ -3,6 +3,7 @@ package server
import (
"encoding/binary"
"io"
"github.com/amitbet/vncproxy/common"
)