+ Finished authentication
* Cleaned up prose * Moved dependencies to their own file
This commit is contained in:
parent
a3852405e6
commit
b38c67a269
13
.vscode/tasks.json
vendored
Normal file
13
.vscode/tasks.json
vendored
Normal 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
55
Dependencies.md
Normal 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"
|
||||
|
||||
---
|
@ -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")
|
||||
---
|
||||
|
4
Makefile
4
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
|
||||
|
@ -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
|
||||
|
||||
---
|
@ -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}
|
||||
}
|
||||
---
|
||||
```
|
@ -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")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user