diff --git a/EnvironmentVariables.md b/EnvironmentVariables.md index 6256e6c..3f80b2e 100644 --- a/EnvironmentVariables.md +++ b/EnvironmentVariables.md @@ -3,13 +3,20 @@ ## Always use xdotool -Some users may always want xdotool, see the [Streaming.md](Streaming.md) file for more information. +Some users may always want xdotool, see the [Streaming.md](Streaming.md) file for more information +``` go --- always use xdotool environment variable - -alwaysUseXdotool, alwaysUseXdotoolExists := os.LookupEnv("KEYBOARD_ALWAYS_USE_XDOTOOL") +var alwaysUseXdotool := false +alwaysUseXdotoolEnv, alwaysUseXdotoolExists := os.LookupEnv("KEYBOARD_ALWAYS_USE_XDOTOOL") +if alwaysUseXdotoolExists { + if alwaysUseXdotoolEnv == "true" || alwaysUseXdotoolEnv == "1" { + alwaysUseXdotool = true + } +} --- +``` ## Authentication token file diff --git a/Makefile b/Makefile index b2645a5..9ce5d72 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,10 @@ weave: - srcweave --formatter srcweave-format --weave docs/ ReadMe.md security/Authentication.md EnvironmentVariables.md Dependencies.md Server.md Streaming.md ThreatModel.md Client.md + srcweave --formatter srcweave-format --weave docs/ ReadMe.md security/Authentication.md EnvironmentVariables.md Dependencies.md server/Server.md server/Streaming.md \ + server/XdotoolCommands.md ThreatModel.md Client.md util/removefencedcode.py tangle: - srcweave --formatter srcweave-format --tangle smartkeyboard/ ReadMe.md security/Authentication.md EnvironmentVariables.md Dependencies.md Server.md Streaming.md ThreatModel.md Client.md tools/Tools.md tools/Editor.md tools/Input.md + srcweave --formatter srcweave-format --tangle smartkeyboard/ ReadMe.md security/Authentication.md EnvironmentVariables.md Dependencies.md \ + server/Server.md server/Streaming.md server/Sendkeys.md server/XdotoolCommands.md ThreatModel.md Client.md tools/Tools.md tools/Editor.md tools/Input.md clean: rm -rf docs find smartkeyboard/ -type f -not -name "*_test.go" -delete diff --git a/Streaming.md b/Streaming.md deleted file mode 100644 index 227e324..0000000 --- a/Streaming.md +++ /dev/null @@ -1,170 +0,0 @@ -# Streaming keyboard input - -We use the Gorilla websocket library to handle the websocket connection. - -Most of the time, we can use sendkeys (which uses libinput) to effeciently press keys. However, if we need to send a character that sendkeys doesn't know about, we can use the xdotool command. xdotool is also useful if one does not want to use root. - -xdotool spawns a new process for each keypress, so it's not as effecient as sendkeys. - -To specify xdotool usage, the client should send a message with the format `{kb_cmd:xdotool}:message` where message is a utf-8 string. - -``` go ---- streaming keyboard input - -func clientConnected(w http.ResponseWriter, r *http.Request) { - keyboard, err := sendkeys.NewKBWrapWithOptions(sendkeys.Noisy) - - - if err != nil { - panic(err) - } - c, err := upgrader.Upgrade(w, r, nil) - if err != nil { - log.Print("upgrade:", err) - return - } - defer c.Close() - - // get auth token - _, message, err := c.ReadMessage() - if err != nil { - log.Println("read:", err) - return - } - - if auth.CheckAuthToken(string(message)) != nil { - log.Println("invalid token") - c.WriteMessage(websocket.TextMessage, []byte("invalid token")) - return - } - c.WriteMessage(websocket.TextMessage, []byte("authenticated")) - - for { - time.Sleep(25 * time.Millisecond) - _, message, err := c.ReadMessage() - if err != nil { - log.Println("read:", err) - break - } - log.Printf("recv: %s", message) - message_string := string(message) - if message_string == "" { - message_string = "\n" - } - - @{send keys to system} - } -} - ---- -``` - -# Sending the keys - -Sending the keys is a bit tricky as we need to manually convert backspace, tab, enter and modifier keys. - -``` go - ---- send keys to system - - -// regex if string has characters we need to convert to key presses -characterRegex, _ := regexp.Compile(`[^\x08]\x08|\t|\n`) - -doXDoTool := func(command string, keys string)(err error) { - var cmd *exec.Cmd - if command == "type" { - cmd = exec.Command("xdotool", command, "--delay", "25", keys) - } else { - cmd = exec.Command("xdotool", command, keys) - } - return cmd.Run() -} - -if strings.HasPrefix(message_string, "{kb_cmd:xdotool}:") { - message_string = strings.TrimPrefix(message_string, "{kb_cmd:xdotool}:") - if message_string == "" { - message_string = "\n" - } - - - if characterRegex.MatchString(message_string) { - for _, character := range message_string { - charString := string(character) - if charString == "\n" { - charString = "Enter" - } else if charString == "\t" { - charString = "Tab" - } else if charString == "\b" { - charString = "BackSpace" - } else{ - doXDoTool("type", charString) - continue - } - // key is required for special characters - err = doXDoTool("key", charString) - continue - } - continue - } else { - doXDoTool("type", message_string) - } - continue -} else if strings.HasPrefix(message_string, "{kb_cmd:xdotoolk}:") { - message_string = strings.TrimPrefix(message_string, "{kb_cmd:xdotoolk}:") - if message_string == "" { - message_string = "\n" - } - if characterRegex.MatchString(message_string) { - for _, character := range message_string { - charString := string(character) - if charString == "\n" { - charString = "Enter" - } else if charString == "\t" { - charString = "Tab" - } else if charString == "\b" { - charString = "BackSpace" - } else{ - doXDoTool("key", charString) - continue - } - // key is required for special characters - err = doXDoTool("key", charString) - continue - } - continue - } else { - doXDoTool("key", message_string) - } - continue -} - -if characterRegex.MatchString(message_string) { - for _, character := range message_string { - charString := string(character) - if charString == "\n" { - keyboard.Enter() - continue - } - if charString == "\t" { - keyboard.Tab() - continue - } - if charString == "\b" { - keyboard.BackSpace() - continue - } - err = keyboard.Type(charString) - if err != nil { - log.Println("type:", err) - } - continue - } - continue -} -err = keyboard.Type(message_string) -if err != nil { - log.Println("type:", err) -} ---- -``` \ No newline at end of file diff --git a/server/Sendkeys.md b/server/Sendkeys.md new file mode 100644 index 0000000..b3f5c27 --- /dev/null +++ b/server/Sendkeys.md @@ -0,0 +1,39 @@ +# Send keys streaming approach + +When applicable to use send keys, we need to detect Enter, Tab, and BackSpace and use the applicatble functions. + +We test if it meets the characterRegex, and if so we iterate for each character and send the corresponding key. +Otherwise we send the entire string at once. + +``` go +--- do streaming sendkeys approach +if characterRegex.MatchString(message_string) { + for _, character := range message_string { + charString := string(character) + if charString == "\n" { + keyboard.Enter() + continue + } + if charString == "\t" { + keyboard.Tab() + continue + } + if charString == "\b" { + keyboard.BackSpace() + continue + } + err = keyboard.Type(charString) + if err != nil { + log.Println("type:", err) + } + continue + } + continue +} +err = keyboard.Type(message_string) +if err != nil { + log.Println("type:", err) +} +--- + +``` \ No newline at end of file diff --git a/Server.md b/server/Server.md similarity index 100% rename from Server.md rename to server/Server.md diff --git a/server/Streaming.md b/server/Streaming.md new file mode 100644 index 0000000..620bdea --- /dev/null +++ b/server/Streaming.md @@ -0,0 +1,90 @@ +# Streaming keyboard input + +We use the Gorilla websocket library to handle the websocket connection. + +Most of the time, we can use sendkeys (which uses libinput) to effeciently press keys. However, if we need to send a character that sendkeys doesn't know about, we can use the xdotool command. xdotool is also useful if one does not want to use root. + +xdotool spawns a new process for each keypress, so it's not as effecient as sendkeys. + +To specify xdotool usage, the client should send a message with the format `{kb_cmd:xdotool}:message` where message is a utf-8 string. + +``` go +--- streaming keyboard input + +func clientConnected(w http.ResponseWriter, r *http.Request) { + // regex if string has characters we need to convert to key presses + characterRegex, _ := regexp.Compile(`[^\x08]\x08|\t|\n`) + keyboard, err := sendkeys.NewKBWrapWithOptions(sendkeys.Noisy) + + @{always use xdotool environment variable} + + + if err != nil { + panic(err) + } + c, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Print("upgrade:", err) + return + } + defer c.Close() + + // get auth token + _, message, err := c.ReadMessage() + if err != nil { + log.Println("read:", err) + return + } + + if auth.CheckAuthToken(string(message)) != nil { + log.Println("invalid token") + c.WriteMessage(websocket.TextMessage, []byte("invalid token")) + return + } + c.WriteMessage(websocket.TextMessage, []byte("authenticated")) + + for { + time.Sleep(25 * time.Millisecond) + _, message, err := c.ReadMessage() + if err != nil { + log.Println("read:", err) + break + } + log.Printf("recv: %s", message) + message_string := string(message) + if message_string == "" { + message_string = "\n" + } + + @{send keys to system} + } +} + +--- +``` + +# Sending the keys + +Sending the keys is a bit tricky as we need to manually convert backspace, tab, enter and modifier keys. + +``` go + +--- send keys to system + + +doXDoTool := func(command string, keys string)(err error) { + var cmd *exec.Cmd + if command == "type" { + cmd = exec.Command("xdotool", command, "--delay", "25", keys) + } else { + cmd = exec.Command("xdotool", command, keys) + } + return cmd.Run() +} + +@{handle xdotoool commands} + +@{do streaming sendkeys approach} + +--- +``` \ No newline at end of file diff --git a/server/XdotoolCommands.md b/server/XdotoolCommands.md new file mode 100644 index 0000000..2ac2d31 --- /dev/null +++ b/server/XdotoolCommands.md @@ -0,0 +1,54 @@ +# Handle xdotool commands + +Currently the two commands are `type` and `key`. `type` is used to type a character and `key` is used to type a special character like `Enter` or `Backspace`. + +`type` is specified by '{kb_cmd:xdotool}:', and `key` is specified by '{kb_cmd:kxdotool}:'. If the command is not specified and `alwaysUseXdotool` is set from the environment variable, it will default to `type`. + +``` go + +``` go + + +``` go +--- handle xdotoool commands + +if alwaysUseXdotool || strings.HasPrefix(message_string, "{kb_cmd:xdotool}:") { + message_string = strings.TrimPrefix(message_string, "{kb_cmd:xdotool}:") + if message_string == "" { + message_string = "\n" + } + + + if characterRegex.MatchString(message_string) { + for _, character := range message_string { + charString := string(character) + if charString == "\n" { + charString = "Enter" + } else if charString == "\t" { + charString = "Tab" + } else if charString == "\b" { + charString = "BackSpace" + } else{ + doXDoTool("type", charString) + continue + } + // key is required for special characters + err = doXDoTool("key", charString) + continue + } + continue + } else { + doXDoTool("type", message_string) + } + continue +} else if strings.HasPrefix(message_string, "{kb_cmd:kxdotool}:") { + message_string = strings.TrimPrefix(message_string, "{kb_cmd:kxdotool}:") + if message_string == "" { + message_string = "\n" + } + doXDoTool("key", message_string) + continue +} + +--- +``` \ No newline at end of file