mirror of
https://github.com/amitbet/vncproxy.git
synced 2025-08-29 10:24:35 +00:00
Merge 2293f40fbf
into ea8f9b5109
This commit is contained in:
commit
24641a1b98
22
Dockerfile
Normal file
22
Dockerfile
Normal 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"]
|
16
README.md
16
README.md
@ -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**
|
||||
|
||||

|
||||
|
@ -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.
|
||||
|
@ -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
2
go.mod
@ -1,3 +1,5 @@
|
||||
module github.com/amitbet/vncproxy
|
||||
|
||||
require golang.org/x/net v0.0.0-20181129055619-fae4c4e3ad76
|
||||
|
||||
go 1.13
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -24,4 +24,7 @@ type VncSession struct {
|
||||
Status SessionStatus
|
||||
Type SessionType
|
||||
ReplayFilePath string
|
||||
RemarkableDeviceId string
|
||||
TLS bool
|
||||
OverrideEncodings []uint32
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package server
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/amitbet/vncproxy/common"
|
||||
)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user