+ 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 b38c67a269
7 changed files with 205 additions and 95 deletions

13
.vscode/tasks.json vendored Normal file
View File

@ -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": []
}
]
}

55
Dependencies.md Normal file
View File

@ -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"
---

View File

@ -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")
---

View File

@ -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

View File

@ -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
---

View File

@ -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}
}
---
```

View File

@ -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")
}