mirror of
https://github.com/ergochat/ergo.git
synced 2024-12-22 18:52:41 +01:00
Merge pull request #284 from slingamn/passhashing.1
new password hashing technique
This commit is contained in:
commit
c72a84e49f
2
Gopkg.lock
generated
2
Gopkg.lock
generated
@ -134,6 +134,7 @@
|
|||||||
packages = [
|
packages = [
|
||||||
"bcrypt",
|
"bcrypt",
|
||||||
"blowfish",
|
"blowfish",
|
||||||
|
"sha3",
|
||||||
"ssh/terminal",
|
"ssh/terminal",
|
||||||
]
|
]
|
||||||
pruneopts = ""
|
pruneopts = ""
|
||||||
@ -200,6 +201,7 @@
|
|||||||
"github.com/oragono/go-ident",
|
"github.com/oragono/go-ident",
|
||||||
"github.com/tidwall/buntdb",
|
"github.com/tidwall/buntdb",
|
||||||
"golang.org/x/crypto/bcrypt",
|
"golang.org/x/crypto/bcrypt",
|
||||||
|
"golang.org/x/crypto/sha3",
|
||||||
"golang.org/x/crypto/ssh/terminal",
|
"golang.org/x/crypto/ssh/terminal",
|
||||||
"golang.org/x/text/secure/precis",
|
"golang.org/x/text/secure/precis",
|
||||||
"gopkg.in/yaml.v2",
|
"gopkg.in/yaml.v2",
|
||||||
|
1
Makefile
1
Makefile
@ -22,5 +22,6 @@ test:
|
|||||||
cd irc/caps && go test . && go vet .
|
cd irc/caps && go test . && go vet .
|
||||||
cd irc/isupport && go test . && go vet .
|
cd irc/isupport && go test . && go vet .
|
||||||
cd irc/modes && go test . && go vet .
|
cd irc/modes && go test . && go vet .
|
||||||
|
cd irc/passwd && go test . && go vet .
|
||||||
cd irc/utils && go test . && go vet .
|
cd irc/utils && go test . && go vet .
|
||||||
./.check-gofmt.sh
|
./.check-gofmt.sh
|
||||||
|
111
irc/accounts.go
111
irc/accounts.go
@ -16,6 +16,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"github.com/oragono/oragono/irc/caps"
|
"github.com/oragono/oragono/irc/caps"
|
||||||
"github.com/oragono/oragono/irc/passwd"
|
"github.com/oragono/oragono/irc/passwd"
|
||||||
@ -175,7 +176,8 @@ func (am *AccountManager) Register(client *Client, account string, callbackNames
|
|||||||
}
|
}
|
||||||
|
|
||||||
// can't register a guest nickname
|
// can't register a guest nickname
|
||||||
renamePrefix := strings.ToLower(am.server.AccountConfig().NickReservation.RenamePrefix)
|
config := am.server.AccountConfig()
|
||||||
|
renamePrefix := strings.ToLower(config.NickReservation.RenamePrefix)
|
||||||
if renamePrefix != "" && strings.HasPrefix(casefoldedAccount, renamePrefix) {
|
if renamePrefix != "" && strings.HasPrefix(casefoldedAccount, renamePrefix) {
|
||||||
return errAccountAlreadyRegistered
|
return errAccountAlreadyRegistered
|
||||||
}
|
}
|
||||||
@ -188,34 +190,16 @@ func (am *AccountManager) Register(client *Client, account string, callbackNames
|
|||||||
verificationCodeKey := fmt.Sprintf(keyAccountVerificationCode, casefoldedAccount)
|
verificationCodeKey := fmt.Sprintf(keyAccountVerificationCode, casefoldedAccount)
|
||||||
certFPKey := fmt.Sprintf(keyCertToAccount, certfp)
|
certFPKey := fmt.Sprintf(keyCertToAccount, certfp)
|
||||||
|
|
||||||
var creds AccountCredentials
|
credStr, err := am.serializeCredentials(passphrase, certfp)
|
||||||
// always set passphrase salt
|
|
||||||
creds.PassphraseSalt, err = passwd.NewSalt()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errAccountCreation
|
return err
|
||||||
}
|
}
|
||||||
// it's fine if this is empty, that just means no certificate is authorized
|
|
||||||
creds.Certificate = certfp
|
|
||||||
if passphrase != "" {
|
|
||||||
creds.PassphraseHash, err = am.server.passwords.GenerateFromPassword(creds.PassphraseSalt, passphrase)
|
|
||||||
if err != nil {
|
|
||||||
am.server.logger.Error("internal", fmt.Sprintf("could not hash password: %v", err))
|
|
||||||
return errAccountCreation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
credText, err := json.Marshal(creds)
|
|
||||||
if err != nil {
|
|
||||||
am.server.logger.Error("internal", fmt.Sprintf("could not marshal credentials: %v", err))
|
|
||||||
return errAccountCreation
|
|
||||||
}
|
|
||||||
credStr := string(credText)
|
|
||||||
|
|
||||||
registeredTimeStr := strconv.FormatInt(time.Now().Unix(), 10)
|
registeredTimeStr := strconv.FormatInt(time.Now().Unix(), 10)
|
||||||
callbackSpec := fmt.Sprintf("%s:%s", callbackNamespace, callbackValue)
|
callbackSpec := fmt.Sprintf("%s:%s", callbackNamespace, callbackValue)
|
||||||
|
|
||||||
var setOptions *buntdb.SetOptions
|
var setOptions *buntdb.SetOptions
|
||||||
ttl := am.server.AccountConfig().Registration.VerifyTimeout
|
ttl := config.Registration.VerifyTimeout
|
||||||
if ttl != 0 {
|
if ttl != 0 {
|
||||||
setOptions = &buntdb.SetOptions{Expires: true, TTL: ttl}
|
setOptions = &buntdb.SetOptions{Expires: true, TTL: ttl}
|
||||||
}
|
}
|
||||||
@ -271,6 +255,75 @@ func (am *AccountManager) Register(client *Client, account string, callbackNames
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validatePassphrase checks whether a passphrase is allowed by our rules
|
||||||
|
func validatePassphrase(passphrase string) error {
|
||||||
|
// sanity check the length
|
||||||
|
if len(passphrase) == 0 || len(passphrase) > 600 {
|
||||||
|
return errAccountBadPassphrase
|
||||||
|
}
|
||||||
|
// for now, just enforce that spaces are not allowed
|
||||||
|
for _, r := range passphrase {
|
||||||
|
if unicode.IsSpace(r) {
|
||||||
|
return errAccountBadPassphrase
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper to assemble the serialized JSON for an account's credentials
|
||||||
|
func (am *AccountManager) serializeCredentials(passphrase string, certfp string) (result string, err error) {
|
||||||
|
var creds AccountCredentials
|
||||||
|
creds.Version = 1
|
||||||
|
// we need at least one of passphrase and certfp:
|
||||||
|
if passphrase == "" && certfp == "" {
|
||||||
|
return "", errAccountBadPassphrase
|
||||||
|
}
|
||||||
|
// but if we have one, it's fine if the other is missing, it just means no
|
||||||
|
// credential of that type will be accepted.
|
||||||
|
creds.Certificate = certfp
|
||||||
|
if passphrase != "" {
|
||||||
|
if validatePassphrase(passphrase) != nil {
|
||||||
|
return "", errAccountBadPassphrase
|
||||||
|
}
|
||||||
|
bcryptCost := int(am.server.Config().Accounts.Registration.BcryptCost)
|
||||||
|
creds.PassphraseHash, err = passwd.GenerateFromPassword([]byte(passphrase), bcryptCost)
|
||||||
|
if err != nil {
|
||||||
|
am.server.logger.Error("internal", fmt.Sprintf("could not hash password: %v", err))
|
||||||
|
return "", errAccountCreation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
credText, err := json.Marshal(creds)
|
||||||
|
if err != nil {
|
||||||
|
am.server.logger.Error("internal", fmt.Sprintf("could not marshal credentials: %v", err))
|
||||||
|
return "", errAccountCreation
|
||||||
|
}
|
||||||
|
return string(credText), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// changes the password for an account
|
||||||
|
func (am *AccountManager) setPassword(account string, password string) (err error) {
|
||||||
|
casefoldedAccount, err := CasefoldName(account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
act, err := am.LoadAccount(casefoldedAccount)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
credStr, err := am.serializeCredentials(password, act.Credentials.Certificate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
|
||||||
|
return am.server.store.Update(func(tx *buntdb.Tx) error {
|
||||||
|
_, _, err := tx.Set(credentialsKey, credStr, nil)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (am *AccountManager) dispatchCallback(client *Client, casefoldedAccount string, callbackNamespace string, callbackValue string) (string, error) {
|
func (am *AccountManager) dispatchCallback(client *Client, casefoldedAccount string, callbackNamespace string, callbackValue string) (string, error) {
|
||||||
if callbackNamespace == "*" || callbackNamespace == "none" {
|
if callbackNamespace == "*" || callbackNamespace == "none" {
|
||||||
return "", nil
|
return "", nil
|
||||||
@ -522,8 +575,15 @@ func (am *AccountManager) AuthenticateByPassphrase(client *Client, accountName s
|
|||||||
return errAccountUnverified
|
return errAccountUnverified
|
||||||
}
|
}
|
||||||
|
|
||||||
err = am.server.passwords.CompareHashAndPassword(
|
switch account.Credentials.Version {
|
||||||
account.Credentials.PassphraseHash, account.Credentials.PassphraseSalt, passphrase)
|
case 0:
|
||||||
|
err = handleLegacyPasswordV0(am.server, accountName, account.Credentials, passphrase)
|
||||||
|
case 1:
|
||||||
|
err = passwd.CompareHashAndPassword(account.Credentials.PassphraseHash, []byte(passphrase))
|
||||||
|
default:
|
||||||
|
err = errAccountInvalidCredentials
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errAccountInvalidCredentials
|
return errAccountInvalidCredentials
|
||||||
}
|
}
|
||||||
@ -982,7 +1042,8 @@ var (
|
|||||||
|
|
||||||
// AccountCredentials stores the various methods for verifying accounts.
|
// AccountCredentials stores the various methods for verifying accounts.
|
||||||
type AccountCredentials struct {
|
type AccountCredentials struct {
|
||||||
PassphraseSalt []byte
|
Version uint
|
||||||
|
PassphraseSalt []byte // legacy field, not used by v1 and later
|
||||||
PassphraseHash []byte
|
PassphraseHash []byte
|
||||||
Certificate string // fingerprint
|
Certificate string // fingerprint
|
||||||
}
|
}
|
||||||
|
@ -81,6 +81,7 @@ type AccountRegistrationConfig struct {
|
|||||||
VerifyMessage string `yaml:"verify-message"`
|
VerifyMessage string `yaml:"verify-message"`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BcryptCost uint `yaml:"bcrypt-cost"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type VHostConfig struct {
|
type VHostConfig struct {
|
||||||
@ -151,15 +152,6 @@ type OperConfig struct {
|
|||||||
Modes string
|
Modes string
|
||||||
}
|
}
|
||||||
|
|
||||||
// PasswordBytes returns the bytes represented by the password hash.
|
|
||||||
func (conf *OperConfig) PasswordBytes() []byte {
|
|
||||||
bytes, err := passwd.DecodePasswordHash(conf.Password)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("decode password error: ", err)
|
|
||||||
}
|
|
||||||
return bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
// LineLenConfig controls line lengths.
|
// LineLenConfig controls line lengths.
|
||||||
type LineLenLimits struct {
|
type LineLenLimits struct {
|
||||||
Tags int
|
Tags int
|
||||||
@ -375,7 +367,11 @@ func (conf *Config) Operators(oc map[string]*OperClass) (map[string]*Oper, error
|
|||||||
}
|
}
|
||||||
oper.Name = name
|
oper.Name = name
|
||||||
|
|
||||||
oper.Pass = opConf.PasswordBytes()
|
oper.Pass, err = decodeLegacyPasswordHash(opConf.Password)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
oper.Vhost = opConf.Vhost
|
oper.Vhost = opConf.Vhost
|
||||||
class, exists := oc[opConf.Class]
|
class, exists := oc[opConf.Class]
|
||||||
if !exists {
|
if !exists {
|
||||||
@ -704,11 +700,14 @@ func LoadConfig(filename string) (config *Config, err error) {
|
|||||||
config.Channels.defaultModes = ParseDefaultChannelModes(config.Channels.RawDefaultModes)
|
config.Channels.defaultModes = ParseDefaultChannelModes(config.Channels.RawDefaultModes)
|
||||||
|
|
||||||
if config.Server.Password != "" {
|
if config.Server.Password != "" {
|
||||||
bytes, err := passwd.DecodePasswordHash(config.Server.Password)
|
config.Server.passwordBytes, err = decodeLegacyPasswordHash(config.Server.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
config.Server.passwordBytes = bytes
|
}
|
||||||
|
|
||||||
|
if config.Accounts.Registration.BcryptCost == 0 {
|
||||||
|
config.Accounts.Registration.BcryptCost = passwd.DefaultCost
|
||||||
}
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
package irc
|
package irc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
@ -14,7 +13,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/oragono/oragono/irc/modes"
|
"github.com/oragono/oragono/irc/modes"
|
||||||
"github.com/oragono/oragono/irc/passwd"
|
|
||||||
"github.com/oragono/oragono/irc/utils"
|
"github.com/oragono/oragono/irc/utils"
|
||||||
|
|
||||||
"github.com/tidwall/buntdb"
|
"github.com/tidwall/buntdb"
|
||||||
@ -25,8 +23,6 @@ const (
|
|||||||
keySchemaVersion = "db.version"
|
keySchemaVersion = "db.version"
|
||||||
// latest schema of the db
|
// latest schema of the db
|
||||||
latestDbSchema = "3"
|
latestDbSchema = "3"
|
||||||
// key for the primary salt used by the ircd
|
|
||||||
keySalt = "crypto.salt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SchemaChanger func(*Config, *buntdb.Tx) error
|
type SchemaChanger func(*Config, *buntdb.Tx) error
|
||||||
@ -68,14 +64,6 @@ func InitDB(path string) {
|
|||||||
defer store.Close()
|
defer store.Close()
|
||||||
|
|
||||||
err = store.Update(func(tx *buntdb.Tx) error {
|
err = store.Update(func(tx *buntdb.Tx) error {
|
||||||
// set base db salt
|
|
||||||
salt, err := passwd.NewSalt()
|
|
||||||
encodedSalt := base64.StdEncoding.EncodeToString(salt)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Could not generate cryptographically-secure salt for the user:", err.Error())
|
|
||||||
}
|
|
||||||
tx.Set(keySalt, encodedSalt, nil)
|
|
||||||
|
|
||||||
// set schema version
|
// set schema version
|
||||||
tx.Set(keySchemaVersion, latestDbSchema, nil)
|
tx.Set(keySchemaVersion, latestDbSchema, nil)
|
||||||
return nil
|
return nil
|
||||||
|
@ -10,17 +10,19 @@ import "errors"
|
|||||||
// Runtime Errors
|
// Runtime Errors
|
||||||
var (
|
var (
|
||||||
errAccountAlreadyRegistered = errors.New("Account already exists")
|
errAccountAlreadyRegistered = errors.New("Account already exists")
|
||||||
|
errAccountAlreadyVerified = errors.New("Account is already verified")
|
||||||
|
errAccountCantDropPrimaryNick = errors.New("Can't unreserve primary nickname")
|
||||||
errAccountCreation = errors.New("Account could not be created")
|
errAccountCreation = errors.New("Account could not be created")
|
||||||
|
errAccountCredUpdate = errors.New("Could not update password hash to new method")
|
||||||
errAccountDoesNotExist = errors.New("Account does not exist")
|
errAccountDoesNotExist = errors.New("Account does not exist")
|
||||||
|
errAccountInvalidCredentials = errors.New("Invalid account credentials")
|
||||||
|
errAccountBadPassphrase = errors.New("Passphrase contains forbidden characters or is otherwise invalid")
|
||||||
|
errAccountNickReservationFailed = errors.New("Could not (un)reserve nick")
|
||||||
errAccountNotLoggedIn = errors.New("You're not logged into an account")
|
errAccountNotLoggedIn = errors.New("You're not logged into an account")
|
||||||
|
errAccountTooManyNicks = errors.New("Account has too many reserved nicks")
|
||||||
|
errAccountUnverified = errors.New("Account is not yet verified")
|
||||||
errAccountVerificationFailed = errors.New("Account verification failed")
|
errAccountVerificationFailed = errors.New("Account verification failed")
|
||||||
errAccountVerificationInvalidCode = errors.New("Invalid account verification code")
|
errAccountVerificationInvalidCode = errors.New("Invalid account verification code")
|
||||||
errAccountUnverified = errors.New("Account is not yet verified")
|
|
||||||
errAccountAlreadyVerified = errors.New("Account is already verified")
|
|
||||||
errAccountInvalidCredentials = errors.New("Invalid account credentials")
|
|
||||||
errAccountTooManyNicks = errors.New("Account has too many reserved nicks")
|
|
||||||
errAccountNickReservationFailed = errors.New("Could not (un)reserve nick")
|
|
||||||
errAccountCantDropPrimaryNick = errors.New("Can't unreserve primary nickname")
|
|
||||||
errAccountUpdateFailed = errors.New("Error while updating your account information")
|
errAccountUpdateFailed = errors.New("Error while updating your account information")
|
||||||
errCallbackFailed = errors.New("Account verification could not be sent")
|
errCallbackFailed = errors.New("Account verification could not be sent")
|
||||||
errCertfpAlreadyExists = errors.New("An account already exists with your certificate")
|
errCertfpAlreadyExists = errors.New("An account already exists with your certificate")
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/oragono/oragono/irc/modes"
|
"github.com/oragono/oragono/irc/modes"
|
||||||
"github.com/oragono/oragono/irc/passwd"
|
|
||||||
"github.com/oragono/oragono/irc/utils"
|
"github.com/oragono/oragono/irc/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,9 +27,7 @@ func (wc *webircConfig) Populate() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if wc.PasswordString != "" {
|
if wc.PasswordString != "" {
|
||||||
var password []byte
|
wc.Password, err = decodeLegacyPasswordHash(wc.PasswordString)
|
||||||
password, err = passwd.DecodePasswordHash(wc.PasswordString)
|
|
||||||
wc.Password = password
|
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,6 @@ import (
|
|||||||
"github.com/oragono/oragono/irc/caps"
|
"github.com/oragono/oragono/irc/caps"
|
||||||
"github.com/oragono/oragono/irc/custime"
|
"github.com/oragono/oragono/irc/custime"
|
||||||
"github.com/oragono/oragono/irc/modes"
|
"github.com/oragono/oragono/irc/modes"
|
||||||
"github.com/oragono/oragono/irc/passwd"
|
|
||||||
"github.com/oragono/oragono/irc/sno"
|
"github.com/oragono/oragono/irc/sno"
|
||||||
"github.com/oragono/oragono/irc/utils"
|
"github.com/oragono/oragono/irc/utils"
|
||||||
"github.com/tidwall/buntdb"
|
"github.com/tidwall/buntdb"
|
||||||
@ -155,6 +154,8 @@ func accRegisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, r
|
|||||||
} else if err == errAccountAlreadyRegistered {
|
} else if err == errAccountAlreadyRegistered {
|
||||||
msg = "Account already exists"
|
msg = "Account already exists"
|
||||||
code = ERR_ACCOUNT_ALREADY_EXISTS
|
code = ERR_ACCOUNT_ALREADY_EXISTS
|
||||||
|
} else if err == errAccountBadPassphrase {
|
||||||
|
msg = "Passphrase contains forbidden characters or is otherwise invalid"
|
||||||
}
|
}
|
||||||
if err == errAccountAlreadyRegistered || err == errAccountCreation || err == errCertfpAlreadyExists {
|
if err == errAccountAlreadyRegistered || err == errAccountCreation || err == errCertfpAlreadyExists {
|
||||||
msg = err.Error()
|
msg = err.Error()
|
||||||
@ -1818,7 +1819,7 @@ func passHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
|
|||||||
|
|
||||||
// check the provided password
|
// check the provided password
|
||||||
password := []byte(msg.Params[0])
|
password := []byte(msg.Params[0])
|
||||||
if passwd.ComparePassword(serverPassword, password) != nil {
|
if bcrypt.CompareHashAndPassword(serverPassword, password) != nil {
|
||||||
rb.Add(nil, server.name, ERR_PASSWDMISMATCH, client.nick, client.t("Password incorrect"))
|
rb.Add(nil, server.name, ERR_PASSWDMISMATCH, client.nick, client.t("Password incorrect"))
|
||||||
rb.Add(nil, server.name, "ERROR", client.t("Password incorrect"))
|
rb.Add(nil, server.name, "ERROR", client.t("Password incorrect"))
|
||||||
return true
|
return true
|
||||||
@ -2402,7 +2403,7 @@ func webircHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
|
|||||||
if isGatewayAllowed(client.socket.conn.RemoteAddr(), gateway) {
|
if isGatewayAllowed(client.socket.conn.RemoteAddr(), gateway) {
|
||||||
// confirm password and/or fingerprint
|
// confirm password and/or fingerprint
|
||||||
givenPassword := msg.Params[0]
|
givenPassword := msg.Params[0]
|
||||||
if 0 < len(info.Password) && passwd.ComparePasswordString(info.Password, givenPassword) != nil {
|
if 0 < len(info.Password) && bcrypt.CompareHashAndPassword(info.Password, []byte(givenPassword)) != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if 0 < len(info.Fingerprint) && client.certfp != info.Fingerprint {
|
if 0 < len(info.Fingerprint) && client.certfp != info.Fingerprint {
|
||||||
|
72
irc/legacy.go
Normal file
72
irc/legacy.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// Copyright (c) 2018 Shivaram Lingamneni
|
||||||
|
|
||||||
|
package irc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/tidwall/buntdb"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errInvalidPasswordHash = errors.New("invalid password hash")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Decode a hashed passphrase as it would appear in a config file,
|
||||||
|
// retaining compatibility with old versions of `oragono genpasswd`
|
||||||
|
// that used to apply a redundant layer of base64
|
||||||
|
func decodeLegacyPasswordHash(hash string) ([]byte, error) {
|
||||||
|
// a correctly formatted bcrypt hash is 60 bytes of printable ASCII
|
||||||
|
if len(hash) == 80 {
|
||||||
|
// double-base64, remove the outer layer:
|
||||||
|
return base64.StdEncoding.DecodeString(hash)
|
||||||
|
} else if len(hash) == 60 {
|
||||||
|
return []byte(hash), nil
|
||||||
|
} else {
|
||||||
|
return nil, errInvalidPasswordHash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper to check a version 0 password hash, with global and per-passphrase salts
|
||||||
|
func checkLegacyPasswordV0(hashedPassword, globalSalt, passphraseSalt []byte, passphrase string) error {
|
||||||
|
var assembledPasswordBytes []byte
|
||||||
|
assembledPasswordBytes = append(assembledPasswordBytes, globalSalt...)
|
||||||
|
assembledPasswordBytes = append(assembledPasswordBytes, '-')
|
||||||
|
assembledPasswordBytes = append(assembledPasswordBytes, passphraseSalt...)
|
||||||
|
assembledPasswordBytes = append(assembledPasswordBytes, '-')
|
||||||
|
assembledPasswordBytes = append(assembledPasswordBytes, []byte(passphrase)...)
|
||||||
|
return bcrypt.CompareHashAndPassword(hashedPassword, assembledPasswordBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checks a version 0 password hash; if successful, upgrades the database entry to version 1
|
||||||
|
func handleLegacyPasswordV0(server *Server, account string, credentials AccountCredentials, passphrase string) (err error) {
|
||||||
|
var globalSaltString string
|
||||||
|
err = server.store.View(func(tx *buntdb.Tx) (err error) {
|
||||||
|
globalSaltString, err = tx.Get("crypto.salt")
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
globalSalt, err := base64.StdEncoding.DecodeString(globalSaltString)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = checkLegacyPasswordV0(credentials.PassphraseHash, globalSalt, credentials.PassphraseSalt, passphrase)
|
||||||
|
if err != nil {
|
||||||
|
// invalid password
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// upgrade credentials
|
||||||
|
err = server.accounts.setPassword(account, passphrase)
|
||||||
|
if err != nil {
|
||||||
|
server.logger.Error("internal", fmt.Sprintf("could not upgrade user password: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -313,6 +313,8 @@ func nsRegisterHandler(server *Server, client *Client, command, params string, r
|
|||||||
errMsg = client.t("An account already exists for your certificate fingerprint")
|
errMsg = client.t("An account already exists for your certificate fingerprint")
|
||||||
} else if err == errAccountAlreadyRegistered {
|
} else if err == errAccountAlreadyRegistered {
|
||||||
errMsg = client.t("Account already exists")
|
errMsg = client.t("Account already exists")
|
||||||
|
} else if err == errAccountBadPassphrase {
|
||||||
|
errMsg = client.t("Passphrase contains forbidden characters or is otherwise invalid")
|
||||||
}
|
}
|
||||||
nsNotice(rb, errMsg)
|
nsNotice(rb, errMsg)
|
||||||
return
|
return
|
||||||
|
34
irc/passwd/bcrypt.go
Normal file
34
irc/passwd/bcrypt.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// Copyright (c) 2018 Shivaram Lingamneni
|
||||||
|
// released under the MIT license
|
||||||
|
|
||||||
|
package passwd
|
||||||
|
|
||||||
|
import "golang.org/x/crypto/bcrypt"
|
||||||
|
import "golang.org/x/crypto/sha3"
|
||||||
|
|
||||||
|
const (
|
||||||
|
MinCost = bcrypt.MinCost
|
||||||
|
DefaultCost = 12 // ballpark: 250 msec on a modern Intel CPU
|
||||||
|
)
|
||||||
|
|
||||||
|
// implements Dropbox's strategy of applying an initial pass of a "normal"
|
||||||
|
// (i.e., fast) cryptographically secure hash with 512 bits of output before
|
||||||
|
// applying bcrypt. This allows the use of, e.g., Diceware/XKCD-style passphrases
|
||||||
|
// that may be longer than the 80-character bcrypt limit.
|
||||||
|
// https://blogs.dropbox.com/tech/2016/09/how-dropbox-securely-stores-your-passwords/
|
||||||
|
|
||||||
|
// we are only using this for user-generated passwords, as opposed to the server
|
||||||
|
// and operator passwords that are hashed by `oragono genpasswd` and then
|
||||||
|
// hard-coded by the server admins into the config file, to avoid breaking
|
||||||
|
// backwards compatibility (since we can't upgrade the config file on the fly
|
||||||
|
// the way we can with the database).
|
||||||
|
|
||||||
|
func GenerateFromPassword(password []byte, cost int) (result []byte, err error) {
|
||||||
|
sum := sha3.Sum512(password)
|
||||||
|
return bcrypt.GenerateFromPassword(sum[:], cost)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CompareHashAndPassword(hashedPassword, password []byte) error {
|
||||||
|
sum := sha3.Sum512(password)
|
||||||
|
return bcrypt.CompareHashAndPassword(hashedPassword, sum[:])
|
||||||
|
}
|
58
irc/passwd/bcrypt_test.go
Normal file
58
irc/passwd/bcrypt_test.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// Copyright (c) 2018 Shivaram Lingamneni
|
||||||
|
// released under the MIT license
|
||||||
|
|
||||||
|
package passwd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBasic(t *testing.T) {
|
||||||
|
hash, err := GenerateFromPassword([]byte("this is my passphrase"), DefaultCost)
|
||||||
|
if err != nil || len(hash) != 60 {
|
||||||
|
t.Errorf("bad password hash output: error %s, output %s, len %d", err, hash, len(hash))
|
||||||
|
}
|
||||||
|
|
||||||
|
if CompareHashAndPassword(hash, []byte("this is my passphrase")) != nil {
|
||||||
|
t.Errorf("hash comparison failed unexpectedly")
|
||||||
|
}
|
||||||
|
|
||||||
|
if CompareHashAndPassword(hash, []byte("this is not my passphrase")) == nil {
|
||||||
|
t.Errorf("hash comparison succeeded unexpectedly")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLongPassphrases(t *testing.T) {
|
||||||
|
longPassphrase := make([]byte, 168)
|
||||||
|
for i := range longPassphrase {
|
||||||
|
longPassphrase[i] = 'a'
|
||||||
|
}
|
||||||
|
hash, err := GenerateFromPassword(longPassphrase, DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("bad password hash output: error %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if CompareHashAndPassword(hash, longPassphrase) != nil {
|
||||||
|
t.Errorf("hash comparison failed unexpectedly")
|
||||||
|
}
|
||||||
|
|
||||||
|
// change a byte of the passphrase beyond the normal 80-character
|
||||||
|
// bcrypt truncation boundary:
|
||||||
|
longPassphrase[150] = 'b'
|
||||||
|
if CompareHashAndPassword(hash, longPassphrase) == nil {
|
||||||
|
t.Errorf("hash comparison succeeded unexpectedly")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this could be useful for tuning the cost parameter on specific hardware
|
||||||
|
func BenchmarkComparisons(b *testing.B) {
|
||||||
|
pass := []byte("passphrase for benchmarking")
|
||||||
|
hash, err := GenerateFromPassword(pass, DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
b.Errorf("bad output")
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
CompareHashAndPassword(hash, pass)
|
||||||
|
}
|
||||||
|
}
|
@ -1,66 +0,0 @@
|
|||||||
// Copyright (c) 2016 Daniel Oaks <daniel@danieloaks.net>
|
|
||||||
// released under the MIT license
|
|
||||||
|
|
||||||
package passwd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// newSaltLen is how many bytes long newly-generated salts are.
|
|
||||||
newSaltLen = 30
|
|
||||||
// defaultPasswordCost is the bcrypt cost we use for passwords.
|
|
||||||
defaultPasswordCost = 14
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewSalt returns a salt for crypto uses.
|
|
||||||
func NewSalt() ([]byte, error) {
|
|
||||||
salt := make([]byte, newSaltLen)
|
|
||||||
_, err := rand.Read(salt)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
var emptySalt []byte
|
|
||||||
return emptySalt, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return salt, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaltedManager supports the hashing and comparing of passwords with the given salt.
|
|
||||||
type SaltedManager struct {
|
|
||||||
salt []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSaltedManager returns a new SaltedManager with the given salt.
|
|
||||||
func NewSaltedManager(salt []byte) SaltedManager {
|
|
||||||
return SaltedManager{
|
|
||||||
salt: salt,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// assemblePassword returns an assembled slice of bytes for the given password details.
|
|
||||||
func (sm *SaltedManager) assemblePassword(specialSalt []byte, password string) []byte {
|
|
||||||
var assembledPasswordBytes []byte
|
|
||||||
assembledPasswordBytes = append(assembledPasswordBytes, sm.salt...)
|
|
||||||
assembledPasswordBytes = append(assembledPasswordBytes, '-')
|
|
||||||
assembledPasswordBytes = append(assembledPasswordBytes, specialSalt...)
|
|
||||||
assembledPasswordBytes = append(assembledPasswordBytes, '-')
|
|
||||||
assembledPasswordBytes = append(assembledPasswordBytes, []byte(password)...)
|
|
||||||
return assembledPasswordBytes
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateFromPassword encrypts the given password.
|
|
||||||
func (sm *SaltedManager) GenerateFromPassword(specialSalt []byte, password string) ([]byte, error) {
|
|
||||||
assembledPasswordBytes := sm.assemblePassword(specialSalt, password)
|
|
||||||
return bcrypt.GenerateFromPassword(assembledPasswordBytes, defaultPasswordCost)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CompareHashAndPassword compares a hashed password with its possible plaintext equivalent.
|
|
||||||
// Returns nil on success, or an error on failure.
|
|
||||||
func (sm *SaltedManager) CompareHashAndPassword(hashedPassword []byte, specialSalt []byte, password string) error {
|
|
||||||
assembledPasswordBytes := sm.assemblePassword(specialSalt, password)
|
|
||||||
return bcrypt.CompareHashAndPassword(hashedPassword, assembledPasswordBytes)
|
|
||||||
}
|
|
@ -1,86 +0,0 @@
|
|||||||
// Copyright (c) 2016 Daniel Oaks <daniel@danieloaks.net>
|
|
||||||
// released under the MIT license
|
|
||||||
|
|
||||||
package passwd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SaltedPasswordTest struct {
|
|
||||||
ManagerSalt string
|
|
||||||
Salt string
|
|
||||||
Hash string
|
|
||||||
Password string
|
|
||||||
}
|
|
||||||
|
|
||||||
var SaltedPasswords = []SaltedPasswordTest{
|
|
||||||
{
|
|
||||||
ManagerSalt: "3TPITDVf/NGb4OlCyV1uZNW1H7zy3BFos+Dsu7dj",
|
|
||||||
Salt: "b6oVqshJUfcm1zWEtqwKqUVylqLONAZfqt17ns+Y",
|
|
||||||
Hash: "JDJhJDE0JFYuT28xOFFNZldaaTI1UWpzNENMeHVKdm5vS1lkL2tFL1lFVkQ2a0loUEY2Vzk3UTZSVDVP",
|
|
||||||
Password: "test",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ManagerSalt: "iNGeNEfuPihM8kYDZ/C6qAJ0JERKeKkUYp6wYDU0",
|
|
||||||
Salt: "U7TA6k6VLSLHfdjSsQH0vc3Jqq6cUezJNyd0DC9c",
|
|
||||||
Hash: "JDJhJDE0JEguY2Rva3VOTVRrNm1VeGdXWjAwamViMGNvV0xYZFdHcTZjenFCRWE3Ymt2N1JiSFJDZlYy",
|
|
||||||
Password: "test2",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ManagerSalt: "ghKJaaSNTjuFmgLRqrgY4FGfx8wXEGOBE02PZvbv",
|
|
||||||
Salt: "NO/mtrMhGjX1FGDGdpGrDJIi4jxsb0aFa7ybId7r",
|
|
||||||
Hash: "JDJhJDE0JEI0M055Z2NDcjNUanB5ZEJ5MzUybi5FT3o4Y1MyNXp2c1NDVS9hS0hOcUxSRDZTWmUxTnN5",
|
|
||||||
Password: "supermono",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSaltedPassword(t *testing.T) {
|
|
||||||
// check newly-generated password
|
|
||||||
managerSalt, err := NewSalt()
|
|
||||||
if err != nil {
|
|
||||||
t.Error("Could not generate manager salt")
|
|
||||||
}
|
|
||||||
|
|
||||||
salt, err := NewSalt()
|
|
||||||
if err != nil {
|
|
||||||
t.Error("Could not generate salt")
|
|
||||||
}
|
|
||||||
|
|
||||||
manager := NewSaltedManager(managerSalt)
|
|
||||||
|
|
||||||
passHash, err := manager.GenerateFromPassword(salt, "this is a test password")
|
|
||||||
if err != nil {
|
|
||||||
t.Error("Could not generate from password")
|
|
||||||
}
|
|
||||||
|
|
||||||
if manager.CompareHashAndPassword(passHash, salt, "this is a test password") != nil {
|
|
||||||
t.Error("Generated password does not match")
|
|
||||||
}
|
|
||||||
|
|
||||||
// check our stored passwords
|
|
||||||
for i, info := range SaltedPasswords {
|
|
||||||
// decode strings to bytes
|
|
||||||
managerSalt, err = base64.StdEncoding.DecodeString(info.ManagerSalt)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Could not decode manager salt for test %d", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
salt, err := base64.StdEncoding.DecodeString(info.Salt)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Could not decode salt for test %d", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
hash, err := base64.StdEncoding.DecodeString(info.Hash)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Could not decode hash for test %d", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure our test values are still correct
|
|
||||||
manager := NewSaltedManager(managerSalt)
|
|
||||||
if manager.CompareHashAndPassword(hash, salt, info.Password) != nil {
|
|
||||||
t.Errorf("Password does not match for [%s]", info.Password)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
// Copyright (c) 2012-2014 Jeremy Latt
|
|
||||||
// released under the MIT license
|
|
||||||
|
|
||||||
package passwd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrEmptyPassword means that an empty password was given.
|
|
||||||
ErrEmptyPassword = errors.New("empty password")
|
|
||||||
)
|
|
||||||
|
|
||||||
// GenerateEncodedPassword returns an encrypted password, encoded into a string with base64.
|
|
||||||
func GenerateEncodedPassword(passwd string) (encoded string, err error) {
|
|
||||||
if passwd == "" {
|
|
||||||
err = ErrEmptyPassword
|
|
||||||
return
|
|
||||||
}
|
|
||||||
bcrypted, err := bcrypt.GenerateFromPassword([]byte(passwd), bcrypt.MinCost)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
encoded = base64.StdEncoding.EncodeToString(bcrypted)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodePasswordHash takes a base64-encoded password hash and returns the appropriate bytes.
|
|
||||||
func DecodePasswordHash(encoded string) (decoded []byte, err error) {
|
|
||||||
if encoded == "" {
|
|
||||||
err = ErrEmptyPassword
|
|
||||||
return
|
|
||||||
}
|
|
||||||
decoded, err = base64.StdEncoding.DecodeString(encoded)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ComparePassword compares a given password with the given hash.
|
|
||||||
func ComparePassword(hash, password []byte) error {
|
|
||||||
return bcrypt.CompareHashAndPassword(hash, password)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ComparePasswordString compares a given password string with the given hash.
|
|
||||||
func ComparePasswordString(hash []byte, password string) error {
|
|
||||||
return ComparePassword(hash, []byte(password))
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
// Copyright (c) 2016 Daniel Oaks <daniel@danieloaks.net>
|
|
||||||
// released under the MIT license
|
|
||||||
|
|
||||||
package passwd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var UnsaltedPasswords = map[string]string{
|
|
||||||
"test1": "JDJhJDA0JFFwZ1V0RWZTMFVaMkFrdlRrTG9FZk9FNEZWbWkvVEhsdGFnSXlIUC5wVmpYTkNERFJPNlcu",
|
|
||||||
"test2": "JDJhJDA0JHpQTGNqczlIanc3V2NFQ3JEOVlTM09aNkRTbGRsQzRyNmt3Q01aSUs2Y2xyWURVODZ1V0px",
|
|
||||||
"supernomo": "JDJhJDA0JHdJekhnQmk1VXQ4WUphL0pIL0tXQWVKVXJ6dXcvRDJ3WFljWW9XOGhzNllIbW1DRlFkL1VL",
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnsaltedPassword(t *testing.T) {
|
|
||||||
for password, hash := range UnsaltedPasswords {
|
|
||||||
generatedHash, err := GenerateEncodedPassword(password)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Could not hash password for [%s]: %s", password, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
hashBytes, err := DecodePasswordHash(hash)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Could not decode hash for [%s]: %s", password, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
generatedHashBytes, err := DecodePasswordHash(generatedHash)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Could not decode generated hash for [%s]: %s", password, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
passwordBytes := []byte(password)
|
|
||||||
|
|
||||||
if ComparePassword(hashBytes, passwordBytes) != nil {
|
|
||||||
t.Errorf("Stored hash for [%s] did not match", password)
|
|
||||||
}
|
|
||||||
if ComparePassword(generatedHashBytes, passwordBytes) != nil {
|
|
||||||
t.Errorf("Generated hash for [%s] did not match", password)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnsaltedPasswordFailures(t *testing.T) {
|
|
||||||
_, err := GenerateEncodedPassword("")
|
|
||||||
if err != ErrEmptyPassword {
|
|
||||||
t.Error("Generating empty password did not fail as expected!")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = DecodePasswordHash("")
|
|
||||||
if err != ErrEmptyPassword {
|
|
||||||
t.Error("Decoding empty password hash did not fail as expected!")
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,7 +8,6 @@ package irc
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
@ -30,7 +29,6 @@ import (
|
|||||||
"github.com/oragono/oragono/irc/languages"
|
"github.com/oragono/oragono/irc/languages"
|
||||||
"github.com/oragono/oragono/irc/logger"
|
"github.com/oragono/oragono/irc/logger"
|
||||||
"github.com/oragono/oragono/irc/modes"
|
"github.com/oragono/oragono/irc/modes"
|
||||||
"github.com/oragono/oragono/irc/passwd"
|
|
||||||
"github.com/oragono/oragono/irc/sno"
|
"github.com/oragono/oragono/irc/sno"
|
||||||
"github.com/oragono/oragono/irc/utils"
|
"github.com/oragono/oragono/irc/utils"
|
||||||
"github.com/tidwall/buntdb"
|
"github.com/tidwall/buntdb"
|
||||||
@ -89,7 +87,6 @@ type Server struct {
|
|||||||
motdLines []string
|
motdLines []string
|
||||||
name string
|
name string
|
||||||
nameCasefolded string
|
nameCasefolded string
|
||||||
passwords *passwd.SaltedManager
|
|
||||||
rehashMutex sync.Mutex // tier 4
|
rehashMutex sync.Mutex // tier 4
|
||||||
rehashSignal chan os.Signal
|
rehashSignal chan os.Signal
|
||||||
pprofServer *http.Server
|
pprofServer *http.Server
|
||||||
@ -980,27 +977,6 @@ func (server *Server) loadDatastore(config *Config) error {
|
|||||||
server.loadDLines()
|
server.loadDLines()
|
||||||
server.loadKLines()
|
server.loadKLines()
|
||||||
|
|
||||||
// load password manager
|
|
||||||
server.logger.Debug("startup", "Loading passwords")
|
|
||||||
err = server.store.View(func(tx *buntdb.Tx) error {
|
|
||||||
saltString, err := tx.Get(keySalt)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Could not retrieve salt string: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
salt, err := base64.StdEncoding.DecodeString(saltString)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pwm := passwd.NewSaltedManager(salt)
|
|
||||||
server.passwords = &pwm
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Could not load salt: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
server.channelRegistry = NewChannelRegistry(server)
|
server.channelRegistry = NewChannelRegistry(server)
|
||||||
|
|
||||||
server.accounts = NewAccountManager(server)
|
server.accounts = NewAccountManager(server)
|
||||||
|
@ -17,7 +17,7 @@ import (
|
|||||||
"github.com/oragono/oragono/irc"
|
"github.com/oragono/oragono/irc"
|
||||||
"github.com/oragono/oragono/irc/logger"
|
"github.com/oragono/oragono/irc/logger"
|
||||||
"github.com/oragono/oragono/irc/mkcerts"
|
"github.com/oragono/oragono/irc/mkcerts"
|
||||||
"github.com/oragono/oragono/irc/passwd"
|
"golang.org/x/crypto/bcrypt"
|
||||||
"golang.org/x/crypto/ssh/terminal"
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -72,11 +72,11 @@ Options:
|
|||||||
if confirm != password {
|
if confirm != password {
|
||||||
log.Fatal("passwords do not match")
|
log.Fatal("passwords do not match")
|
||||||
}
|
}
|
||||||
encoded, err := passwd.GenerateEncodedPassword(password)
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("encoding error:", err.Error())
|
log.Fatal("encoding error:", err.Error())
|
||||||
}
|
}
|
||||||
fmt.Println(encoded)
|
fmt.Println(string(hash))
|
||||||
} else if arguments["initdb"].(bool) {
|
} else if arguments["initdb"].(bool) {
|
||||||
irc.InitDB(config.Datastore.Path)
|
irc.InitDB(config.Datastore.Path)
|
||||||
if !arguments["--quiet"].(bool) {
|
if !arguments["--quiet"].(bool) {
|
||||||
|
@ -76,7 +76,7 @@ server:
|
|||||||
fingerprint: 938dd33f4b76dcaf7ce5eb25c852369cb4b8fb47ba22fc235aa29c6623a5f182
|
fingerprint: 938dd33f4b76dcaf7ce5eb25c852369cb4b8fb47ba22fc235aa29c6623a5f182
|
||||||
|
|
||||||
# password the gateway uses to connect, made with oragono genpasswd
|
# password the gateway uses to connect, made with oragono genpasswd
|
||||||
password: JDJhJDA0JG9rTTVERlNRa0hpOEZpNkhjZE95SU9Da1BseFdlcWtOTEQxNEFERVlqbEZNTkdhOVlYUkMu
|
password: "$2a$04$sLEFDpIOyUp55e6gTMKbOeroT6tMXTjPFvA0eGvwvImVR9pkwv7ee"
|
||||||
|
|
||||||
# hosts that can use this webirc command
|
# hosts that can use this webirc command
|
||||||
# you should also add these addresses to the connection limits and throttling exemption lists
|
# you should also add these addresses to the connection limits and throttling exemption lists
|
||||||
@ -145,6 +145,9 @@ accounts:
|
|||||||
# can users register new accounts?
|
# can users register new accounts?
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
||||||
|
# this is the bcrypt cost we'll use for account passwords
|
||||||
|
bcrypt-cost: 12
|
||||||
|
|
||||||
# length of time a user has to verify their account before it can be re-registered
|
# length of time a user has to verify their account before it can be re-registered
|
||||||
verify-timeout: "32h"
|
verify-timeout: "32h"
|
||||||
|
|
||||||
@ -300,7 +303,7 @@ opers:
|
|||||||
|
|
||||||
# password to login with /OPER command
|
# password to login with /OPER command
|
||||||
# generated using "oragono genpasswd"
|
# generated using "oragono genpasswd"
|
||||||
password: JDJhJDA0JE1vZmwxZC9YTXBhZ3RWT2xBbkNwZnV3R2N6VFUwQUI0RUJRVXRBRHliZVVoa0VYMnlIaGsu
|
password: "$2a$04$LiytCxaY0lI.guDj2pBN4eLRD5cdM2OLDwqmGAgB6M2OPirbF5Jcu"
|
||||||
|
|
||||||
# logging, takes inspiration from Insp
|
# logging, takes inspiration from Insp
|
||||||
logging:
|
logging:
|
||||||
|
Loading…
Reference in New Issue
Block a user