328 rindas
9.1 KiB
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
|
|
}
|