diff --git a/.gitignore b/.gitignore index f888b44..07c322a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -docs/* bin/* *.go !smartkeyboard/auth/*_test.go diff --git a/docs/Authentication.html b/docs/Authentication.html new file mode 100644 index 0000000..2eb1ad5 --- /dev/null +++ b/docs/Authentication.html @@ -0,0 +1,159 @@ + + +
+ + +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 +scope of this project.
+ +We perform application level authentication using the system random device. token generation
+ +We hash the 32 byte token using sha3-256 to avoid accidentally exposing the token to a +readonly attacker. Since the token is very high entropy, we do not need a salt or +KDF.
+ + +authToken := [32]byte{}
+rand.Read(authToken[:])
+
+authTokenString = base64.StdEncoding.EncodeToString(authToken[:])
+hashedID := sha3.Sum256(authToken[:])
+
+Used by 1
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.’
The following is used when we need to get the token file path:
+ + +@{get authTokenFile from environment}
+
+if authTokenFileIsSet == false {
+ authTokenFile = filepath.Join(xdg.ConfigHome, "smartkeyboard", "auth-token")
+}
+
+When a client connects, the websocket endpoint checks the token they send against the stored token.
+ +We use a constant time comparison to avoid timing attacks, although it is not clear if this is necessary in this case.
+ + +func CheckAuthToken(token string) error {
+ @{define authentication token file}
+ // compare sha3_256 hash to hash in file
+ tokenBytes, err := base64.StdEncoding.DecodeString(token)
+ hashedToken := sha3.Sum256(tokenBytes)
+ storedToken, err := os.ReadFile(authTokenFile)
+ if err != nil {
+ return err
+ }
+ if subtle.ConstantTimeCompare(hashedToken[:], storedToken) != 1 {
+ return errors.New("invalid token")
+ }
+ return nil
+}
+
+Used by 1
func ProvisionToken() (authTokenString string, failed error){
+ @{define authentication token file}
+
+ if _, err := os.Stat(authTokenFile); err == nil {
+ return "", nil
+ }
+
+ @{token generation}
+
+ // create directories if they don't exist
+ os.MkdirAll(filepath.Dir(authTokenFile), 0700)
+ fo, err := os.Create(authTokenFile)
+ defer fo.Close()
+ if err != nil {
+ panic(err)
+ }
+ fo.Write(hashedID[:])
+ return authTokenString, nil
+}
+
+Used by 1
The following is the structure of the authentication package.
+ +Both CheckAuthToken and ProvisionToken are exported. +The former is used by the server on client connect and the latter is called on startup.
+ +package auth
+
+import(
+ "os"
+ "path/filepath"
+ "errors"
+ "encoding/base64"
+ "crypto/rand"
+ "crypto/subtle"
+@{sha3 import string}
+@{xdg import string}
+)
+
+//var authToken = ""
+
+@{provision token function}
+
+@{check token function}
+
+