+ Finished authentication

* Cleaned up prose
* Moved dependencies to their own file
This commit is contained in:
Kevin F 2022-09-10 14:23:48 -05:00
parent a3852405e6
commit 09184cba54
5 changed files with 137 additions and 95 deletions

View File

@ -1,5 +1,21 @@
# GoSmartKeyboard Environment Variables # 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 ## 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. same machine.
--- unixSocketPath --- unixSocketPath
unixSocketPath, unixSocketPathExists := os.LookupEnv("KEYBOARD_UNIX_SOCKET_PATH") 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: The TCP socket is configured by the following environment variables:
--- TCPBindAddress --- TCPBindAddress
tcpBindAddress, tcpBindAddressExists := os.LookupEnv("KEYBOARD_TCP_BIND_ADDRESS") tcpBindAddress, tcpBindAddressExists := os.LookupEnv("KEYBOARD_TCP_BIND_ADDRESS")
--- ---
--- TCPBindPort --- TCPBindPort
tcpBindPort, tcpBindPortExists := os.LookupEnv("KEYBOARD_TCP_BIND_PORT") tcpBindPort, tcpBindPortExists := os.LookupEnv("KEYBOARD_TCP_BIND_PORT")
--- ---

View File

@ -1,7 +1,7 @@
weave: 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: 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: clean:
rm -rf docs rm -rf docs
find smartkeyboard/ -type f -not -name "*_test.go" -delete find smartkeyboard/ -type f -not -name "*_test.go" -delete

View File

@ -46,54 +46,33 @@ A smart keyboard could, for example, be used for the following:
First, we call First, we call
--- entrypoint --- entrypoint
func main(){
func main(){
fmt.Println("Hello, World!") fmt.Println("Hello, World!")
} }
--- ---
--- /main.go --- /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 --- set network bind globals
var string unixSocketPath
var bool unixSocketPathExists var string unixSocketPath
var string tcpBindAddress var bool unixSocketPathExists
var bool tcpBindAddressExists var string tcpBindAddress
var string tcpBindPort var bool tcpBindAddressExists
var bool tcpBindPortExists var string tcpBindPort
var bool tcpBindPortExists
--- ---

View File

@ -1,40 +1,44 @@
# Keyboard connection authentication # Keyboard connection authentication
Keyboarding obviously has a ton of sensitive data, so we need to both encrypt and Keyboarding is a very sensitive activity, so this app naturally needs to encrypt and authenticate connections.
authenticate the 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 scope of this project, but we perform application level authentication using two
randomly generated UUIDv4s in a manner similar to a passphrase. @{token generation} 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 We hash the token using sha3-256 to avoid accidentally exposing the token to a
readonly attacker. readonly attacker. Since the token is very high entropy, we do not need a salt or
KDF.
## Generating the authentication token
``` go ``` go
--- token generation --- token generation
id := uuid.New() + uuid.New() authToken = uuid.New().String() + uuid.New().String()
hashedID := sha3.Sum256([]byte(id)) 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 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 ``` go
--- store token
if err := os.WriteFile(KEYBOARD_AUTH_TOKEN_FILE, hashedID, 0666); err != nil { --- define authentication token file
log.Fatal(err)
@{get authTokenFile from environment}
if authTokenFileIsSet == false {
authTokenFile = filepath.Join(xdg.ConfigHome, "smartkeyboard", "auth-token")
} }
--- ---
``` ```
## Checking authentication ## Checking authentication
When a client connects, the [websocket server](Server.md) checks the token they send against the stored token. 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 ``` go
--- check token --- check token
func checkAuthToken(token string) error { func checkAuthToken(token string) error {
@{define authentication token file}
// compare sha3_256 hash to hash in file
hashedToken := sha3.Sum256([]byte(token)) hashedToken := sha3.Sum256([]byte(token))
storedToken, err := os.ReadFile(KEYBOARD_AUTH_TOKEN_FILE) storedToken, err := os.ReadFile(authTokenFile)
if err != nil { if err != nil {
return err return err
} }
if subtle.ConstantTimeCompare(hashedToken[:], storedToken) != 1 { if subtle.ConstantTimeCompare(hashedToken[:], storedToken) != 1 {
return errors.New("invalid token") return errors.New("invalid token")
} }
return nil return nil
} }
--- ---
```
--- /auth/auth.go --- /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} @{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}
}
---
``` ```

View File

@ -1,16 +1,58 @@
package main package auth
import ( import (
"golang.org/x/crypto/sha3"
"os"
"testing" "testing"
) )
func TestAuthPasswordHashBad(t *testing.T) { 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") t.Log("TestAuthPasswordHash")
expectedResult := error(nil) // OK
password := "wrong password" password := "wrong password"
result := checkAuthToken(password) result := checkAuthToken(password)
if result != expectedResult { if result == nil {
t.Errorf("Expected %s, got %s", expectedResult, result) 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")
} }