From b38c67a26987e4f5c2980a424cf7cf6595148d2f Mon Sep 17 00:00:00 2001 From: Kevin F Date: Sat, 10 Sep 2022 14:23:48 -0500 Subject: [PATCH] + Finished authentication * Cleaned up prose * Moved dependencies to their own file --- .vscode/tasks.json | 13 ++++ Dependencies.md | 55 +++++++++++++++++ EnvironmentVariables.md | 19 ++++++ Makefile | 4 +- ReadMe.go.md => ReadMe.md | 55 ++++++----------- security/Authentication.md | 104 ++++++++++++++++---------------- smartkeyboard/auth/auth_test.go | 50 +++++++++++++-- 7 files changed, 205 insertions(+), 95 deletions(-) create mode 100644 .vscode/tasks.json create mode 100644 Dependencies.md rename ReadMe.go.md => ReadMe.md (70%) diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..fd566ac --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,13 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "wrap and tangle when changed", + "type": "shell", + "command": "while true; do inotifywait --event modify security/*.md *.md; make tangle; done", + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/Dependencies.md b/Dependencies.md new file mode 100644 index 0000000..c0b4167 --- /dev/null +++ b/Dependencies.md @@ -0,0 +1,55 @@ +# Project Dependencies + +This project has the following dependencies, excluding the Go standard library: + + +# uuid + +We use uuidv4s to generate authentication tokens + +--- uuid import string + + "github.com/google/uuid" + +--- + +# xdg + +We use the xdg package to get the user's config directory. + +--- xdg import string + + "github.com/adrg/xdg" + +--- + +# sha3 + +We use sha3 to hash authentication tokens. It is not in the crypto standard library. + +--- sha3 import string + + "golang.org/x/crypto/sha3" + +--- + +# keybd_event + + +In order to avoid coding key press simulation for every major platform, we use [keybd_event](https://github.com/micmonay/keybd_event). This is a cross-platform library that uses the OS's native key press simulation. + +--- keybd_event import string + + "github.com/micmonay/keybd_event" + +--- + +# gorilla/websocket + +We also rely on gorilla/websocket for the websocket server that processes keyboard input. + +--- gorilla/websocket import string + + "github.com/gorilla/websocket" + +--- \ No newline at end of file diff --git a/EnvironmentVariables.md b/EnvironmentVariables.md index b84206d..7a512f1 100644 --- a/EnvironmentVariables.md +++ b/EnvironmentVariables.md @@ -1,5 +1,21 @@ # GoSmartKeyboard Environment Variables + +## Authentication token file + +The authentication token configuration file is set by the environment variable `KEYBOARD_AUTH_TOKEN_FILE`, but defaults to +`XDG_CONFIG_HOME/smartkeyboard/auth-token`. + + +``` go +--- get authTokenFile from environment + +authTokenFile, authTokenFileIsSet := os.LookupEnv("KEYBOARD_AUTH_TOKEN_FILE") + +--- +``` + + ## HTTP Bind Settings @@ -12,6 +28,7 @@ One should prefer a unix socket if their reverse proxy supports it and is on the same machine. --- unixSocketPath + unixSocketPath, unixSocketPathExists := os.LookupEnv("KEYBOARD_UNIX_SOCKET_PATH") --- @@ -20,9 +37,11 @@ If the unix socket path is set, we use it. Otherwise, we use the TCP socket. The TCP socket is configured by the following environment variables: --- TCPBindAddress + tcpBindAddress, tcpBindAddressExists := os.LookupEnv("KEYBOARD_TCP_BIND_ADDRESS") --- --- TCPBindPort + tcpBindPort, tcpBindPortExists := os.LookupEnv("KEYBOARD_TCP_BIND_PORT") --- diff --git a/Makefile b/Makefile index 4fab93e..3b9dc9d 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ weave: - srcweave --formatter srcweave-format --weave docs/ ReadMe.go.md security/Authentication.md + srcweave --formatter srcweave-format --weave docs/ ReadMe.go.md security/Authentication.md Dependencies.md tangle: - srcweave --formatter srcweave-format --tangle smartkeyboard/ ReadMe.go.md security/Authentication.md + srcweave --formatter srcweave-format --tangle smartkeyboard/ ReadMe.go.md security/Authentication.md EnvironmentVariables.md Dependencies.md clean: rm -rf docs find smartkeyboard/ -type f -not -name "*_test.go" -delete diff --git a/ReadMe.go.md b/ReadMe.md similarity index 70% rename from ReadMe.go.md rename to ReadMe.md index 0d35af3..9c006af 100644 --- a/ReadMe.go.md +++ b/ReadMe.md @@ -46,54 +46,33 @@ A smart keyboard could, for example, be used for the following: First, we call --- entrypoint -func main(){ - fmt.Println("Hello, World!") -} + + func main(){ + fmt.Println("Hello, World!") + } + --- --- /main.go -@{includes} -@{set network bind globals} + package main -@{entrypoint} + @{set network bind globals} + + @{entrypoint} --- -## Dependencies - -In order to avoid coding key press simulation for every major platform, we use [keybd_event](https://github.com/micmonay/keybd_event). This is a cross-platform library that uses the OS's native key press simulation. - - ---- include keybd event -import( - "github.com/micmonay/keybd_event" -) ---- - -We also rely on gorilla/websocket for the websocket server: - ---- include gorilla websocket -import( - "github.com/gorilla/websocket" -) ---- - ---- includes -package main -import( -"fmt" -) ---- - --- set network bind globals -var string unixSocketPath -var bool unixSocketPathExists -var string tcpBindAddress -var bool tcpBindAddressExists -var string tcpBindPort -var bool tcpBindPortExists + + var string unixSocketPath + var bool unixSocketPathExists + var string tcpBindAddress + var bool tcpBindAddressExists + var string tcpBindPort + var bool tcpBindPortExists + --- \ No newline at end of file diff --git a/security/Authentication.md b/security/Authentication.md index 11aee88..9874a6f 100644 --- a/security/Authentication.md +++ b/security/Authentication.md @@ -1,40 +1,44 @@ # Keyboard connection authentication -Keyboarding obviously has a ton of sensitive data, so we need to both encrypt and -authenticate the connections. +Keyboarding is a very sensitive activity, so this app naturally needs to encrypt and authenticate connections. -All connections are encrypted using an external TLS proxy (e.g. Caddy) outside the +All connections are encrypted using an external TLS proxy (e.g. [Caddy](https://caddyserver.com)) outside the scope of this project, but we perform application level authentication using two randomly generated UUIDv4s in a manner similar to a passphrase. @{token generation} We hash the token using sha3-256 to avoid accidentally exposing the token to a -readonly attacker. - - -## Generating the authentication token +readonly attacker. Since the token is very high entropy, we do not need a salt or +KDF. ``` go --- token generation -id := uuid.New() + uuid.New() -hashedID := sha3.Sum256([]byte(id)) +authToken = uuid.New().String() + uuid.New().String() +hashedID := sha3.Sum256([]byte(authToken)) --- ``` -## Storing the authentication token -We then store the token in a file, which is set +## Authentication token file + +We store the token in a file, which is set by the environment variable `KEYBOARD_AUTH_TOKEN_FILE`, but defaults to -'XDG_CONFIG_HOME/smartkeyboard/auth-token.' @{defineProvisionTokenFile} +'XDG_CONFIG_HOME/smartkeyboard/auth-token.' +The following is used when we need to get the token file path: ``` go ---- store token -if err := os.WriteFile(KEYBOARD_AUTH_TOKEN_FILE, hashedID, 0666); err != nil { - log.Fatal(err) + +--- define authentication token file + +@{get authTokenFile from environment} + +if authTokenFileIsSet == false { + authTokenFile = filepath.Join(xdg.ConfigHome, "smartkeyboard", "auth-token") } --- ``` + ## Checking authentication When a client connects, the [websocket server](Server.md) checks the token they send against the stored token. @@ -42,57 +46,55 @@ When a client connects, the [websocket server](Server.md) checks the token they ``` go --- check token func checkAuthToken(token string) error { + @{define authentication token file} + // compare sha3_256 hash to hash in file hashedToken := sha3.Sum256([]byte(token)) - storedToken, err := os.ReadFile(KEYBOARD_AUTH_TOKEN_FILE) + storedToken, err := os.ReadFile(authTokenFile) if err != nil { return err } - if subtle.ConstantTimeCompare(hashedToken[:], storedToken) != 1 { return errors.New("invalid token") } - return nil } --- -``` - --- /auth/auth.go -package main +package auth + +import( + "os" + "path/filepath" + "errors" + "crypto/subtle" +@{sha3 import string} +@{uuid import string} +@{xdg import string} +) + +var authToken = "" + +func provisionToken() (error){ + @{define authentication token file} + + if _, err := os.Stat(authTokenFile); err == nil { + return nil + } + + @{token generation} + + fo, err := os.Create(authTokenFile) + if err != nil { + panic(err) + } + fo.Write(hashedID[:]) + fo.Close() + return nil +} -@{auth imports} @{check token} --- ---- auth imports -import( - //"github.com/google/uuid" - //"encoding/base64" - "golang.org/x/crypto/sha3" - "os" - "errors" - "crypto/subtle" -) ---- - -``` go ---- defineProvisionTokenFile -provisionTokenFile, keyboardAuthExists := os.Getenv("KEYBOARD_AUTH_TOKEN_FILE") - -if keyboardAuthExists == false { - provisionTokenFile = filepath.Join(xdg.ConfigHome, "smartkeyboard", "auth-token") -} ---- -``` - -```go ---- provisionToken -func provisionToken() (error){ - @{defineProvisionTokenFile} - @{token generation} - @{store token} -} ---- ``` \ No newline at end of file diff --git a/smartkeyboard/auth/auth_test.go b/smartkeyboard/auth/auth_test.go index cf55172..0f3c092 100644 --- a/smartkeyboard/auth/auth_test.go +++ b/smartkeyboard/auth/auth_test.go @@ -1,16 +1,58 @@ -package main +package auth import ( + "golang.org/x/crypto/sha3" + "os" "testing" ) func TestAuthPasswordHashBad(t *testing.T) { + os.RemoveAll("test-auth-token") + os.Setenv("KEYBOARD_AUTH_TOKEN_FILE", "test-auth-token") + os.WriteFile("test-auth-token", []byte("junk"), 0644) t.Log("TestAuthPasswordHash") - expectedResult := error(nil) // OK + password := "wrong password" result := checkAuthToken(password) - if result != expectedResult { - t.Errorf("Expected %s, got %s", expectedResult, result) + if result == nil { + t.Errorf("Expected error, got nil") } + os.RemoveAll("test-auth-token") +} + +func TestAuthPasswordEmpty(t *testing.T) { + os.RemoveAll("test-auth-token") + os.Setenv("KEYBOARD_AUTH_TOKEN_FILE", "test-auth-token") + os.WriteFile("test-auth-token", []byte("c0067d4af4e87f00dbac63b6156828237059172d1bbeac67427345d6a9fda484"), 0644) + t.Log("TestAuthPasswordHash") + + password := "" + + result := checkAuthToken(password) + if result == nil { + t.Errorf("Expected error, got nil") + } + os.RemoveAll("test-auth-token") +} + +func TestAuthPasswordHashGood(t *testing.T) { + os.RemoveAll("test-auth-token") + os.Setenv("KEYBOARD_AUTH_TOKEN_FILE", "test-auth-token") + //os.WriteFile("test-auth-token", []byte("c0067d4af4e87f00dbac63b6156828237059172d1bbeac67427345d6a9fda484"), 0644) + expectedHash := sha3.Sum256([]byte("password")) + fo, err := os.Create("test-auth-token") + if err != nil { + panic(err) + } + fo.Write(expectedHash[:]) + t.Log("TestAuthPasswordHash") + + password := "password" + + result := checkAuthToken(password) + if result != nil { + t.Errorf("Expected nil, got error") + } + os.RemoveAll("test-auth-token") }