diff --git a/auth/plain.go b/auth/plain.go index a0e75683..ca0d1517 100644 --- a/auth/plain.go +++ b/auth/plain.go @@ -1,9 +1,20 @@ +// Copyright 2014-2015 Apcera Inc. All rights reserved. + package auth import ( + "strings" + "github.com/nats-io/gnatsd/server" + "golang.org/x/crypto/bcrypt" ) +const BcryptPrefix = "$2a$" + +func isBcrypt(password string) bool { + return strings.HasPrefix(password, BcryptPrefix) +} + type Plain struct { Username string Password string @@ -11,7 +22,15 @@ type Plain struct { func (p *Plain) Check(c server.ClientAuth) bool { opts := c.GetOpts() - if p.Username != opts.Username || p.Password != opts.Password { + if p.Username != opts.Username { + return false + } + // Check to see if the password is a bcrypt hash + if isBcrypt(p.Password) { + if err := bcrypt.CompareHashAndPassword([]byte(p.Password), []byte(opts.Password)); err != nil { + return false + } + } else if p.Password != opts.Password { return false } diff --git a/auth/token.go b/auth/token.go index 651f92d7..66815575 100644 --- a/auth/token.go +++ b/auth/token.go @@ -2,6 +2,7 @@ package auth import ( "github.com/nats-io/gnatsd/server" + "golang.org/x/crypto/bcrypt" ) type Token struct { @@ -10,7 +11,12 @@ type Token struct { func (p *Token) Check(c server.ClientAuth) bool { opts := c.GetOpts() - if p.Token != opts.Authorization { + // Check to see if the token is a bcrypt hash + if isBcrypt(p.Token) { + if err := bcrypt.CompareHashAndPassword([]byte(p.Token), []byte(opts.Authorization)); err != nil { + return false + } + } else if p.Token != opts.Authorization { return false } diff --git a/test/auth_test.go b/test/auth_test.go index 543b4186..59d414de 100644 --- a/test/auth_test.go +++ b/test/auth_test.go @@ -156,3 +156,75 @@ func TestPasswordClientGoodConnect(t *testing.T) { doAuthConnect(t, c, "", AUTH_USER, AUTH_PASS) expectResult(t, c, okRe) } + +//////////////////////////////////////////////////////////// +// The bcrypt username/password version +//////////////////////////////////////////////////////////// + +// Generated with util/mkpasswd +const BCRYPT_AUTH_PASS = "#00L2zPr!j11VsT@e9QGPt" +const BCRYPT_AUTH_HASH = "$2a$11$wDaOBnEx0GbcFTOzJRywpexI/dxH3sqV3.adFefmDDMJggnOTNqKS" + +func runAuthServerWithBcryptUserPass() *server.Server { + opts := DefaultTestOptions + opts.Port = AUTH_PORT + opts.Username = AUTH_USER + opts.Password = BCRYPT_AUTH_HASH + + auth := &auth.Plain{Username: AUTH_USER, Password: BCRYPT_AUTH_HASH} + return RunServerWithAuth(&opts, auth) +} + +func TestBadBcryptPassword(t *testing.T) { + s := runAuthServerWithBcryptUserPass() + defer s.Shutdown() + c := createClientConn(t, "localhost", AUTH_PORT) + defer c.Close() + expectAuthRequired(t, c) + doAuthConnect(t, c, "", AUTH_USER, BCRYPT_AUTH_HASH) + expectResult(t, c, errRe) +} + +func TestGoodBcryptPassword(t *testing.T) { + s := runAuthServerWithBcryptUserPass() + defer s.Shutdown() + c := createClientConn(t, "localhost", AUTH_PORT) + defer c.Close() + expectAuthRequired(t, c) + doAuthConnect(t, c, "", AUTH_USER, BCRYPT_AUTH_PASS) + expectResult(t, c, okRe) +} + +//////////////////////////////////////////////////////////// +// The bcrypt authorization token version +//////////////////////////////////////////////////////////// + +const BCRYPT_AUTH_TOKEN = "743&@WeTlIwtHDytI5Bnxl" +const BCRYPT_AUTH_TOKEN_HASH = "$2a$11$Gp5x2rvdzfm9rUREuyQeBOFd61oPYKoLWSI2fJN7DAFMF34Z9o4s2" + +func runAuthServerWithBcryptToken() *server.Server { + opts := DefaultTestOptions + opts.Port = AUTH_PORT + opts.Authorization = BCRYPT_AUTH_TOKEN_HASH + return RunServerWithAuth(&opts, &auth.Token{Token: BCRYPT_AUTH_TOKEN_HASH}) +} + +func TestBadBcryptToken(t *testing.T) { + s := runAuthServerWithBcryptToken() + defer s.Shutdown() + c := createClientConn(t, "localhost", AUTH_PORT) + defer c.Close() + expectAuthRequired(t, c) + doAuthConnect(t, c, BCRYPT_AUTH_TOKEN_HASH, "", "") + expectResult(t, c, errRe) +} + +func TestGoodBcryptToken(t *testing.T) { + s := runAuthServerWithBcryptToken() + defer s.Shutdown() + c := createClientConn(t, "localhost", AUTH_PORT) + defer c.Close() + expectAuthRequired(t, c) + doAuthConnect(t, c, BCRYPT_AUTH_TOKEN, "", "") + expectResult(t, c, okRe) +} diff --git a/util/mkpasswd.go b/util/mkpasswd.go new file mode 100644 index 00000000..f5e97e1a --- /dev/null +++ b/util/mkpasswd.go @@ -0,0 +1,76 @@ +// Copyright 2015 Apcera Inc. All rights reserved. +// +build ignore + +package main + +import ( + "bytes" + "crypto/rand" + "flag" + "fmt" + "log" + "math/big" + + "golang.org/x/crypto/bcrypt" + "golang.org/x/crypto/ssh/terminal" +) + +func usage() { + log.Fatalf("Usage: mkpasswd -p \n") +} + +const ( + PasswordLength = 22 + Cost = 11 +) + +func main() { + var pw = flag.Bool("p", false, "Input password via stdin") + + log.SetFlags(0) + flag.Usage = usage + flag.Parse() + + var password string + + if *pw { + fmt.Printf("Enter Password: ") + bytePassword, _ := terminal.ReadPassword(0) + fmt.Printf("\nReenter Password: ") + bytePassword2, _ := terminal.ReadPassword(0) + if !bytes.Equal(bytePassword, bytePassword2) { + log.Fatalf("Error, passwords do not match\n") + } + password = string(bytePassword) + } + + if password == "" { + password = genPassword() + } + + if *pw { + fmt.Printf("\n") + } else { + fmt.Printf("pass: %s\n", password) + } + + cb, err := bcrypt.GenerateFromPassword([]byte(password), Cost) + if err != nil { + log.Fatalf("Error producing bcrypt hash: %v\n", err) + } + fmt.Printf("bcrypt hash: %s\n", cb) +} + +func genPassword() string { + var ch = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@$#%^&*()") + b := make([]byte, PasswordLength) + max := big.NewInt(int64(len(ch))) + for i := range b { + ri, err := rand.Int(rand.Reader, max) + if err != nil { + log.Fatalf("Error producing random integer: %v\n", err) + } + b[i] = ch[int(ri.Int64())] + } + return string(b) +}