|
@@ -1,18 +1,18 @@
|
|
|
package auth
|
|
package auth
|
|
|
|
|
+
|
|
|
// package for authenticating websockets
|
|
// package for authenticating websockets
|
|
|
// we use a homespun jwt knock-off because no tls on lan
|
|
// we use a homespun jwt knock-off because no tls on lan
|
|
|
// authentication adds you to the AuthenticatedClients map
|
|
// authentication adds you to the AuthenticatedClients map
|
|
|
// broadcasts get sent to members of this map
|
|
// broadcasts get sent to members of this map
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
|
- "crypto/rand"
|
|
|
|
|
- "crypto/sha256"
|
|
|
|
|
- "encoding/base64"
|
|
|
|
|
|
|
+ "crypto/sha512"
|
|
|
"encoding/hex"
|
|
"encoding/hex"
|
|
|
"encoding/json"
|
|
"encoding/json"
|
|
|
"fmt"
|
|
"fmt"
|
|
|
"goseg/config"
|
|
"goseg/config"
|
|
|
"goseg/structs"
|
|
"goseg/structs"
|
|
|
|
|
+ "io/ioutil"
|
|
|
"net"
|
|
"net"
|
|
|
"net/http"
|
|
"net/http"
|
|
|
"strings"
|
|
"strings"
|
|
@@ -20,94 +20,142 @@ import (
|
|
|
"time"
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/gorilla/websocket"
|
|
"github.com/gorilla/websocket"
|
|
|
- "golang.org/x/crypto/nacl/secretbox"
|
|
|
|
|
|
|
+ fernet "github.com/fernet/fernet-go"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
var (
|
|
|
// maps a websocket conn to a tokenid
|
|
// maps a websocket conn to a tokenid
|
|
|
// tokenid's can be referenced from the global conf
|
|
// tokenid's can be referenced from the global conf
|
|
|
AuthenticatedClients = struct {
|
|
AuthenticatedClients = struct {
|
|
|
- Conns map[*websocket.Conn]string
|
|
|
|
|
|
|
+ Conns map[string]*websocket.Conn
|
|
|
sync.RWMutex
|
|
sync.RWMutex
|
|
|
}{
|
|
}{
|
|
|
- Conns: make(map[*websocket.Conn]string),
|
|
|
|
|
|
|
+ Conns: make(map[string]*websocket.Conn),
|
|
|
}
|
|
}
|
|
|
UnauthClients = struct {
|
|
UnauthClients = struct {
|
|
|
- Conns map[*websocket.Conn]string
|
|
|
|
|
|
|
+ Conns map[string]*websocket.Conn
|
|
|
sync.RWMutex
|
|
sync.RWMutex
|
|
|
}{
|
|
}{
|
|
|
- Conns: make(map[*websocket.Conn]string),
|
|
|
|
|
|
|
+ Conns: make(map[string]*websocket.Conn),
|
|
|
}
|
|
}
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
-// check if websocket session is in the auth map
|
|
|
|
|
-func WsIsAuthenticated(conn *websocket.Conn) bool {
|
|
|
|
|
- AuthenticatedClients.Lock()
|
|
|
|
|
- defer AuthenticatedClients.Unlock()
|
|
|
|
|
- _, exists := AuthenticatedClients.Conns[conn]
|
|
|
|
|
- return exists
|
|
|
|
|
|
|
+// check if websocket-token pair is auth'd
|
|
|
|
|
+func WsIsAuthenticated(conn *websocket.Conn, token string) bool {
|
|
|
|
|
+ AuthenticatedClients.RLock()
|
|
|
|
|
+ defer AuthenticatedClients.RUnlock()
|
|
|
|
|
+ if AuthenticatedClients.Conns[token] == conn {
|
|
|
|
|
+ return true
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// check if a hash is in the auth map
|
|
|
|
|
-func hashAuthenticated(hash string) bool {
|
|
|
|
|
- AuthenticatedClients.RLock() // Acquire read lock
|
|
|
|
|
- defer AuthenticatedClients.RUnlock() // Release read lock
|
|
|
|
|
- for _, v := range AuthenticatedClients.Conns {
|
|
|
|
|
- if v == hash {
|
|
|
|
|
|
|
+// quick check if websocket is authed at all for unauth broadcast (not for actual auth)
|
|
|
|
|
+func WsAuthCheck(conn *websocket.Conn) bool {
|
|
|
|
|
+ AuthenticatedClients.RLock()
|
|
|
|
|
+ defer AuthenticatedClients.RUnlock()
|
|
|
|
|
+ for _, con := range AuthenticatedClients.Conns {
|
|
|
|
|
+ if con == conn {
|
|
|
|
|
+ config.Logger.Info("Client in auth map")
|
|
|
return true
|
|
return true
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+ config.Logger.Info("Client not in auth map")
|
|
|
return false
|
|
return false
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// check the validity of the token
|
|
|
|
|
-func CheckToken(token string, conn *websocket.Conn, r *http.Request, setup bool) (bool, string, error) {
|
|
|
|
|
- // no token? we have problem.
|
|
|
|
|
- if token == "" {
|
|
|
|
|
- authStatus := false
|
|
|
|
|
- if setup {
|
|
|
|
|
- authStatus = true
|
|
|
|
|
|
|
+// this takes a bool for auth/unauth -- also persists to config
|
|
|
|
|
+func AddToAuthMap(conn *websocket.Conn, token map[string]string, authed bool) error {
|
|
|
|
|
+ tokenStr := token["token"]
|
|
|
|
|
+ tokenId := token["id"]
|
|
|
|
|
+ hashed := sha512.Sum512([]byte(tokenStr))
|
|
|
|
|
+ hash := hex.EncodeToString(hashed[:])
|
|
|
|
|
+ if authed {
|
|
|
|
|
+ AuthenticatedClients.Lock()
|
|
|
|
|
+ AuthenticatedClients.Conns[tokenId] = conn
|
|
|
|
|
+ AuthenticatedClients.Unlock()
|
|
|
|
|
+ UnauthClients.Lock()
|
|
|
|
|
+ if _, ok := UnauthClients.Conns[tokenId]; ok {
|
|
|
|
|
+ delete(UnauthClients.Conns, tokenId)
|
|
|
}
|
|
}
|
|
|
- // you take token.
|
|
|
|
|
- newToken, err := CreateToken(conn, r, setup)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return false, "", err
|
|
|
|
|
|
|
+ UnauthClients.Unlock()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ UnauthClients.Lock()
|
|
|
|
|
+ UnauthClients.Conns[tokenId] = conn
|
|
|
|
|
+ UnauthClients.Unlock()
|
|
|
|
|
+ AuthenticatedClients.Lock()
|
|
|
|
|
+ if _, ok := AuthenticatedClients.Conns[tokenId]; ok {
|
|
|
|
|
+ delete(AuthenticatedClients.Conns, tokenId)
|
|
|
}
|
|
}
|
|
|
- return authStatus, newToken["token"], nil
|
|
|
|
|
|
|
+ AuthenticatedClients.Unlock()
|
|
|
|
|
+ }
|
|
|
|
|
+ now := time.Now().Format("2006-01-02_15:04:05")
|
|
|
|
|
+ err := AddSession(tokenId, hash, now, authed)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// the same but the other way
|
|
|
|
|
+func RemoveFromAuthMap(tokenId string, fromAuthorized bool) error {
|
|
|
|
|
+ if fromAuthorized {
|
|
|
|
|
+ AuthenticatedClients.Lock()
|
|
|
|
|
+ if _, ok := AuthenticatedClients.Conns[tokenId]; ok {
|
|
|
|
|
+ delete(AuthenticatedClients.Conns, tokenId)
|
|
|
|
|
+ }
|
|
|
|
|
+ AuthenticatedClients.Unlock()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ UnauthClients.Lock()
|
|
|
|
|
+ if _, ok := UnauthClients.Conns[tokenId]; ok {
|
|
|
|
|
+ delete(UnauthClients.Conns, tokenId)
|
|
|
|
|
+ }
|
|
|
|
|
+ UnauthClients.Unlock()
|
|
|
|
|
+ }
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+// check the validity of the token
|
|
|
|
|
+func CheckToken(token map[string]string, conn *websocket.Conn, r *http.Request, setup bool) bool {
|
|
|
|
|
+ // great you have token. we see if valid.
|
|
|
|
|
+ if token["token"] == "" {
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+ config.Logger.Info(fmt.Sprintf("Checking token %s",token["id"]))
|
|
|
|
|
+ conf := config.Conf()
|
|
|
|
|
+ key := conf.KeyFile
|
|
|
|
|
+ res, err := KeyfileDecrypt(token["token"], key)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ config.Logger.Warn("Invalid token provided")
|
|
|
|
|
+ return false
|
|
|
} else {
|
|
} else {
|
|
|
- // great you have token. we see if valid.
|
|
|
|
|
- conf := config.Conf()
|
|
|
|
|
- key := conf.KeyFile
|
|
|
|
|
- res, err := KeyfileDecrypt(token, key)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- config.Logger.Warn("Invalid token provided")
|
|
|
|
|
- return false, "", err
|
|
|
|
|
|
|
+ // so you decrypt. now we see the useragent and ip.
|
|
|
|
|
+ var ip string
|
|
|
|
|
+ if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" {
|
|
|
|
|
+ ip = strings.Split(forwarded, ",")[0]
|
|
|
} else {
|
|
} else {
|
|
|
- // so you decrypt. now we see the useragent and ip.
|
|
|
|
|
- var ip string
|
|
|
|
|
- if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" {
|
|
|
|
|
- ip = strings.Split(forwarded, ",")[0]
|
|
|
|
|
- } else {
|
|
|
|
|
- ip, _, _ = net.SplitHostPort(r.RemoteAddr)
|
|
|
|
|
- }
|
|
|
|
|
- userAgent := r.Header.Get("User-Agent")
|
|
|
|
|
- hashed := sha256.Sum256([]byte(token))
|
|
|
|
|
- hash := hex.EncodeToString(hashed[:])
|
|
|
|
|
- // you in auth map?
|
|
|
|
|
- if hashAuthenticated(hash) {
|
|
|
|
|
- if ip == res["ip"] && userAgent == res["user_agent"] {
|
|
|
|
|
- return true, res["id"], nil
|
|
|
|
|
- } else {
|
|
|
|
|
- config.Logger.Warn("Token doesn't match session!")
|
|
|
|
|
- return false, res["id"], err
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ ip, _, _ = net.SplitHostPort(r.RemoteAddr)
|
|
|
|
|
+ }
|
|
|
|
|
+ userAgent := r.Header.Get("User-Agent")
|
|
|
|
|
+ hashed := sha512.Sum512([]byte(token["token"]))
|
|
|
|
|
+ hash := hex.EncodeToString(hashed[:])
|
|
|
|
|
+ // you in auth map?
|
|
|
|
|
+ if WsIsAuthenticated(conn, hash) {
|
|
|
|
|
+ if ip == res["ip"] && userAgent == res["user_agent"] {
|
|
|
|
|
+ config.Logger.Info("Token authenticated")
|
|
|
|
|
+ return true
|
|
|
} else {
|
|
} else {
|
|
|
- config.Logger.Warn("Token isn't an authenticated session")
|
|
|
|
|
- return false, res["id"], err
|
|
|
|
|
|
|
+ config.Logger.Warn("Token doesn't match session!")
|
|
|
|
|
+ return false
|
|
|
}
|
|
}
|
|
|
|
|
+ } else {
|
|
|
|
|
+ config.Logger.Warn("Token isn't an authenticated session")
|
|
|
|
|
+ return false
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- return false, "", nil
|
|
|
|
|
|
|
+ return false
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// create a new session token
|
|
// create a new session token
|
|
@@ -139,16 +187,16 @@ func CreateToken(conn *websocket.Conn, r *http.Request, setup bool) (map[string]
|
|
|
key := conf.KeyFile
|
|
key := conf.KeyFile
|
|
|
encryptedText, err := KeyfileEncrypt(contents, key)
|
|
encryptedText, err := KeyfileEncrypt(contents, key)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
|
|
+ config.Logger.Error(fmt.Sprintf("failed to encrypt token: %v", err))
|
|
|
return nil, fmt.Errorf("failed to encrypt token: %v", err)
|
|
return nil, fmt.Errorf("failed to encrypt token: %v", err)
|
|
|
}
|
|
}
|
|
|
- hashed := sha256.Sum256([]byte(encryptedText))
|
|
|
|
|
- hash := hex.EncodeToString(hashed[:])
|
|
|
|
|
- // Update sessions in the system's configuration
|
|
|
|
|
- AddSession(id, hash, now, setup)
|
|
|
|
|
- return map[string]string{
|
|
|
|
|
|
|
+ token := map[string]string{
|
|
|
"id": id,
|
|
"id": id,
|
|
|
"token": encryptedText,
|
|
"token": encryptedText,
|
|
|
- }, nil
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+ // Update sessions in the system's configuration
|
|
|
|
|
+ AddToAuthMap(conn, token, setup)
|
|
|
|
|
+ return token, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// take session details and add to SysConfig
|
|
// take session details and add to SysConfig
|
|
@@ -160,31 +208,29 @@ func AddSession(tokenID string, hash string, created string, authorized bool) er
|
|
|
if authorized {
|
|
if authorized {
|
|
|
update := map[string]interface{}{
|
|
update := map[string]interface{}{
|
|
|
"Sessions": map[string]interface{}{
|
|
"Sessions": map[string]interface{}{
|
|
|
- "Authorized": map[string]string{
|
|
|
|
|
- "Hash": session.Hash,
|
|
|
|
|
- "Created": session.Created,
|
|
|
|
|
|
|
+ "Authorized": map[string]structs.SessionInfo{
|
|
|
|
|
+ tokenID: session,
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
}
|
|
}
|
|
|
if err := config.UpdateConf(update); err != nil {
|
|
if err := config.UpdateConf(update); err != nil {
|
|
|
return fmt.Errorf("Error adding session: %v", err)
|
|
return fmt.Errorf("Error adding session: %v", err)
|
|
|
}
|
|
}
|
|
|
- if err := config.RemoveSession(tokenID, false); err != nil {
|
|
|
|
|
|
|
+ if err := RemoveFromAuthMap(tokenID, false); err != nil {
|
|
|
return fmt.Errorf("Error removing session: %v", err)
|
|
return fmt.Errorf("Error removing session: %v", err)
|
|
|
}
|
|
}
|
|
|
- } else {
|
|
|
|
|
- update := map[string]interface{}{
|
|
|
|
|
- "Sessions": map[string]interface{}{
|
|
|
|
|
- "Unauthorized": map[string]string{
|
|
|
|
|
- "Hash": session.Hash,
|
|
|
|
|
- "Created": session.Created,
|
|
|
|
|
|
|
+ } else {
|
|
|
|
|
+ update := map[string]interface{}{
|
|
|
|
|
+ "Sessions": map[string]interface{}{
|
|
|
|
|
+ "Unauthorized": map[string]structs.SessionInfo{
|
|
|
|
|
+ tokenID: session,
|
|
|
|
|
+ },
|
|
|
},
|
|
},
|
|
|
- },
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ }
|
|
|
if err := config.UpdateConf(update); err != nil {
|
|
if err := config.UpdateConf(update); err != nil {
|
|
|
return fmt.Errorf("Error adding session: %v", err)
|
|
return fmt.Errorf("Error adding session: %v", err)
|
|
|
}
|
|
}
|
|
|
- if err := config.RemoveSession(tokenID, true); err != nil {
|
|
|
|
|
|
|
+ if err := RemoveFromAuthMap(tokenID, true); err != nil {
|
|
|
return fmt.Errorf("Error removing session: %v", err)
|
|
return fmt.Errorf("Error removing session: %v", err)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -192,59 +238,53 @@ func AddSession(tokenID string, hash string, created string, authorized bool) er
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// encrypt the contents using stored keyfile val
|
|
// encrypt the contents using stored keyfile val
|
|
|
-func KeyfileEncrypt(contents map[string]string, key string) (string, error) {
|
|
|
|
|
- contentBytes, err := json.Marshal(contents)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return "", err
|
|
|
|
|
- }
|
|
|
|
|
- // convert key to bytes
|
|
|
|
|
- keyBytes := []byte(key)
|
|
|
|
|
- if len(keyBytes) != 32 {
|
|
|
|
|
- return "", fmt.Errorf("key must be 32 bytes in length")
|
|
|
|
|
- }
|
|
|
|
|
- var keyArray [32]byte
|
|
|
|
|
- copy(keyArray[:], keyBytes)
|
|
|
|
|
- // generate nonce
|
|
|
|
|
- var nonce [24]byte
|
|
|
|
|
- if _, err := rand.Read(nonce[:]); err != nil {
|
|
|
|
|
- return "", err
|
|
|
|
|
- }
|
|
|
|
|
- // encrypt contents
|
|
|
|
|
- encrypted := secretbox.Seal(nonce[:], contentBytes, &nonce, &keyArray)
|
|
|
|
|
- return base64.URLEncoding.EncodeToString(encrypted), nil
|
|
|
|
|
|
|
+func KeyfileEncrypt(contents map[string]string, keyStr string) (string, error) {
|
|
|
|
|
+ fileBytes, err := ioutil.ReadFile(keyStr)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return "", err
|
|
|
|
|
+ }
|
|
|
|
|
+ contentBytes, err := json.Marshal(contents)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return "", err
|
|
|
|
|
+ }
|
|
|
|
|
+ key, err := fernet.DecodeKey(string(fileBytes))
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return "", err
|
|
|
|
|
+ }
|
|
|
|
|
+ tok, err := fernet.EncryptAndSign(contentBytes, key)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return "", err
|
|
|
|
|
+ }
|
|
|
|
|
+ return string(tok), nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// decrypt routine
|
|
|
|
|
-func KeyfileDecrypt(encryptedText string, key string) (map[string]string, error) {
|
|
|
|
|
- // get bytes
|
|
|
|
|
- keyBytes := []byte(key)
|
|
|
|
|
- var keyArray [32]byte
|
|
|
|
|
- copy(keyArray[:], keyBytes)
|
|
|
|
|
- encryptedBytes, err := base64.URLEncoding.DecodeString(encryptedText)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return nil, err
|
|
|
|
|
- }
|
|
|
|
|
- // get nonce
|
|
|
|
|
- var nonce [24]byte
|
|
|
|
|
- copy(nonce[:], encryptedBytes[:24])
|
|
|
|
|
- // attempt decrypt
|
|
|
|
|
- decrypted, ok := secretbox.Open(nil, encryptedBytes[24:], &nonce, &keyArray)
|
|
|
|
|
- if !ok {
|
|
|
|
|
- return nil, fmt.Errorf("Decryption failed")
|
|
|
|
|
- }
|
|
|
|
|
- var contents map[string]string
|
|
|
|
|
- if err := json.Unmarshal(decrypted, &contents); err != nil {
|
|
|
|
|
- return nil, err
|
|
|
|
|
- }
|
|
|
|
|
- return contents, nil
|
|
|
|
|
|
|
+func KeyfileDecrypt(tokenStr string, keyStr string) (map[string]string, error) {
|
|
|
|
|
+ fileBytes, err := ioutil.ReadFile(keyStr)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, err
|
|
|
|
|
+ }
|
|
|
|
|
+ key, err := fernet.DecodeKey(string(fileBytes))
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, err
|
|
|
|
|
+ }
|
|
|
|
|
+ decrypted := fernet.VerifyAndDecrypt([]byte(tokenStr), 60*time.Second, []*fernet.Key{key})
|
|
|
|
|
+ if decrypted == nil {
|
|
|
|
|
+ return nil, fmt.Errorf("verification or decryption failed")
|
|
|
|
|
+ }
|
|
|
|
|
+ var contents map[string]string
|
|
|
|
|
+ err = json.Unmarshal(decrypted, &contents)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, err
|
|
|
|
|
+ }
|
|
|
|
|
+ return contents, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// salted sha256
|
|
|
|
|
|
|
+// salted sha512
|
|
|
func Hasher(password string) string {
|
|
func Hasher(password string) string {
|
|
|
conf := config.Conf()
|
|
conf := config.Conf()
|
|
|
salt := conf.Salt
|
|
salt := conf.Salt
|
|
|
toHash := salt + password
|
|
toHash := salt + password
|
|
|
- res := sha256.Sum256([]byte(toHash))
|
|
|
|
|
|
|
+ res := sha512.Sum512([]byte(toHash))
|
|
|
return hex.EncodeToString(res[:])
|
|
return hex.EncodeToString(res[:])
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -255,7 +295,7 @@ func AuthenticateLogin(password string) bool {
|
|
|
if hash == conf.PwHash {
|
|
if hash == conf.PwHash {
|
|
|
return true
|
|
return true
|
|
|
} else {
|
|
} else {
|
|
|
- config.Logger.Warn(fmt.Sprintf("debug: failed pw hash: %v",hash))
|
|
|
|
|
|
|
+ config.Logger.Warn(fmt.Sprintf("debug: failed pw hash: %v", hash))
|
|
|
return false
|
|
return false
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|