sh3lly/auth/auth.go

328 rindas
9.1 KiB
Go

package auth
import (
"encoding/binary"
"encoding/json"
"errors"
"strings"
"sync"
"time"
"git.tcp.direct/Mirrors/bitcask-mirror"
"git.tcp.direct/kayos/common/entropy"
"golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/ssh"
"git.tcp.direct/kayos/sh3lly/bus"
"git.tcp.direct/kayos/sh3lly/config"
)
// RegisteredUser represents a user from our user system.
type RegisteredUser struct {
// ID is a users identifying number.
ID uint32
// Username is the friendly nickname of the user.
Username string
// Hash is a users hashed password.
Hash []byte
// PublicKey is the byte slice form of the SSH public key bound to the user's account.
PublicKey []byte
// Privs represents the PrivLevel (permissions) of the user's account.
Privs PrivLevel
// Notes is a place for admins to store arbitrary information about an account.
Notes []string
// AuthLog stores the time of the last 10 logins that were successful. We will not store any further info.
AuthLog [10]time.Time
}
// Authenticator is used for pluggable authentication backends
type Authenticator interface {
// GetUser returns user type from username string.
GetUser(user string) (*RegisteredUser, error)
// Register registers a new user to our user system.
Register(user, pass string) (*RegisteredUser, error)
// Ban bans a user's account from logging in.
Ban(user string) error
// Delete delete's a user from our user system.
Delete(user string) error
// PassphraseLogin checks the provided passphrase against the provided account's stored credentials.
PassphraseLogin(user, pass string) error
// KeyLogin checks the provided public key against the provided account's stored public key.
KeyLogin(user string, publickey ssh.PublicKey) error
// GetPrivs checks the provided account's privileges and returns a Privilege level.
GetPrivs(user *RegisteredUser) PrivLevel
// SetPrivLevel changes a user's PrivLevel.
SetPrivLevel(user *RegisteredUser, level PrivLevel) error
// UserExists checks our user system for a user and returns true if they exist, false if not.
UserExists(user string) bool
// KeyExists checks the entire user system for any duplicate public keys.
KeyExists(pubkey ssh.PublicKey) (*RegisteredUser, bool)
// CheckPublicKey checks a given a public key, returns nil if the connection should be allowed.
CheckPublicKey(user string, key ssh.PublicKey) error
keyboardInteractive(conn ssh.ConnMetadata, challenge ssh.KeyboardInteractiveChallenge) (*ssh.Permissions, error)
publicKeyCallback(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error)
}
func (users *UserDB) regListen() {
rl, err := bus.GetMessenger("reg")
for {
if err == nil {
break
}
rl, err = bus.GetMessenger("reg")
}
for {
in := rl.Recv()
instr := in.(string)
spl := strings.Split(instr, " ")
tmp := entropy.RandStr(10)
u, err := users.Register(spl[0], tmp)
if err != nil {
rl.Send(err)
continue
}
u.PublicKey = []byte(strings.Join(spl[1:], " "))
rl.Send("Success! temp password: " + tmp)
}
}
// UserDB is our account database for passphrase authentication.
type UserDB struct {
DB *bitcask.Bitcask
mu *sync.RWMutex
}
// AllowAnonymous determines if we allow anonymous connection to our server.
func (users *UserDB) AllowAnonymous() bool {
if config.AllowAnon {
println("allowanon")
}
return config.AllowAnon
}
// AcceptPassphrase determines if we allow password based authentication.
func (users *UserDB) AcceptPassphrase() bool {
return true
}
// CheckPublicKey compares a public key to the target users stored/accepted public key.
func (users *UserDB) CheckPublicKey(user string, key ssh.PublicKey) error {
u, err := users.GetUser(user)
if err != nil {
return errors.New("user does not exist")
}
if string(key.Marshal()) == string(u.PublicKey) {
return nil
}
return errors.New("invalid key")
}
// NewUserDB returns a new UserDB with a bitcask database that stores it's data in path.
func NewUserDB(path string) (db *UserDB, err error) {
var b *bitcask.Bitcask
if b, err = bitcask.Open(path); err != nil {
return
}
db = &UserDB{DB: b, mu: &sync.RWMutex{}}
go db.regListen()
return
}
func uint32ToBytes(ui uint32) []byte {
buf := make([]byte, 4)
binary.LittleEndian.PutUint32(buf, ui)
return buf
}
func (users *UserDB) getNewID() uint32 {
users.mu.Lock()
defer users.mu.Unlock()
newid := uint32(10 + users.DB.Len())
for users.DB.Has(uint32ToBytes(newid)) {
// we choose the 10 offset because we store bans in the earlier ID spaces
newid = uint32(10 + users.DB.Len())
}
return newid
}
// Register registers a new user into our database.
func (users *UserDB) Register(user, pass string) (*RegisteredUser, error) {
var err error
var ubytes []byte
if users.UserExists(user) {
return nil, errors.New("username already exists: " + user)
}
if len(pass) < 5 {
return nil, errors.New("password must be at least 5 characters")
}
u := &RegisteredUser{ID: users.getNewID(), Username: user, Hash: HashPassword(pass), Privs: Default}
if ubytes, err = json.Marshal(u); err != nil {
return nil, err
}
err = users.DB.Put(uint32ToBytes(u.ID), ubytes)
return u, err
}
// AssignPublicKeyToUser attaches an SSH public key to the target registered user.
func (users *UserDB) AssignPublicKeyToUser(user *RegisteredUser, key ssh.PublicKey) error {
users.mu.Lock()
defer users.mu.Unlock()
user.PublicKey = key.Marshal()
userJSON, err := json.Marshal(user)
if err != nil {
return err
}
return users.DB.Put(uint32ToBytes(user.ID), userJSON)
}
// Delete removes a user from our database.
func (users *UserDB) Delete(user string) error {
users.mu.Lock()
defer users.mu.Unlock()
u, err := users.GetUser(user)
if err != nil {
return err
}
buf := make([]byte, 4)
binary.LittleEndian.PutUint32(buf, u.ID)
return users.DB.Delete(buf)
}
// PassphraseLogin attempts to validate the provided credentials against our UserDB
func (users *UserDB) PassphraseLogin(user, pass string) error {
var u *RegisteredUser
var err error
users.mu.RLock()
defer users.mu.RUnlock()
if u, err = users.GetUser(user); err != nil {
return err
}
if !CheckPasswordHash(pass, u.Hash) {
return errors.New("incorrect credentials")
}
return nil
}
// KeyLogin attempts to validate a user with their ssh public key
func (users *UserDB) KeyLogin(user string, pubkey ssh.PublicKey) error {
users.mu.RLock()
defer users.mu.RUnlock()
var (
u *RegisteredUser
ukey ssh.PublicKey
err error
)
if u, err = users.GetUser(user); err != nil {
return err
}
if ukey, err = ssh.ParsePublicKey(u.PublicKey); err != nil {
log.Warn().Err(err).Msg("failed to parse public key from database")
return errors.New("internal server error")
}
if ukey == pubkey {
return nil
}
return errors.New("invalid key for user")
}
// UserExists checks for the existence of the given username in our database.
func (users *UserDB) UserExists(user string) bool {
_, err := users.GetUser(user)
return err == nil
}
// GetUser iterates through all RegisteredUser instances in the database and returns a pointer to the one that matches the requested username.
func (users *UserDB) GetUser(targetUser string) (*RegisteredUser, error) {
var (
usrbytes []byte
err error
userStruct = &RegisteredUser{}
)
if users.DB.Len() <= 0 {
return &RegisteredUser{}, errors.New("no users in database")
}
err = users.DB.Fold(func(key []byte) error {
if usrbytes, err = users.DB.Get(key); err != nil {
log.Fatal().Err(err).Bytes("key", key).Msg("failed to get userbytes")
// continue
}
if err := json.Unmarshal(usrbytes, &userStruct); err != nil {
log.Fatal().Err(err).Msg("failed to unmarshal")
// continue
}
if userStruct.Username == targetUser {
return errors.New("done")
}
return nil
})
if err != nil {
if err.Error() == "done" {
return userStruct, nil
}
}
return &RegisteredUser{}, errors.New("targetUser does not exist: " + targetUser)
}
// KeyExists iterates through all RegisteredUser instances in the database and returns the corresponding RegisteredUser and true if a public key is present.
func (users *UserDB) KeyExists(pubkey ssh.PublicKey) (*RegisteredUser, bool) {
var (
err error
usr *RegisteredUser
usrbytes []byte
k ssh.PublicKey
)
if users.DB.Len() < 1 {
return &RegisteredUser{}, false
}
ukeys := users.DB.Keys()
for {
key, ok := <-ukeys
if !ok {
break
}
if usrbytes, err = users.DB.Get(key); err != nil {
continue
}
if err := json.Unmarshal(usrbytes, &usr); err != nil {
log.Error().Err(err).Msg("failed to unmarshal")
continue
}
if k, err = ssh.ParsePublicKey(usr.PublicKey); err != nil {
log.Warn().Err(err).Msg("failed to parse public key from database")
continue
}
if k == pubkey {
return usr, true
}
}
return &RegisteredUser{}, false
}
// HashPassword hashes the given password string using bcrypt.
func HashPassword(password string) []byte {
var bytes []byte
var err error
if bytes, err = bcrypt.GenerateFromPassword([]byte(password), 14); err != nil {
panic(err)
}
return bytes
}
// CheckPasswordHash checks if a given password string, when hashed, matches the given hash.
func CheckPasswordHash(password string, hash []byte) bool {
err := bcrypt.CompareHashAndPassword(hash, []byte(password))
return err == nil
}