From 2a33dbe5dc3acb2ff1375f8fbfd799165add1c53 Mon Sep 17 00:00:00 2001 From: Morgan Hill Date: Fri, 5 Oct 2018 15:31:59 +0200 Subject: [PATCH 1/5] feat: Add support for SCRAM-SHA-256 authentication Support more future proof hashing algorithm for SCRAM authentication. Compatible with mongoDB 4.0 and higher. --- README.md | 1 + auth.go | 10 +++++++--- auth_test.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e1269d7c8..a5409cd6a 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ A [sub-package](https://godoc.org/github.com/globalsign/mgo/bson) that implement MongoDB 4.0 is currently experimental - we would happily accept PRs to help improve support! ## Changes +* Adds support for SCRAM-SHA-256 authentication mechanism * Fixes attempting to authenticate before every query ([details](https://github.com/go-mgo/mgo/issues/254)) * Removes bulk update / delete batch size limitations ([details](https://github.com/go-mgo/mgo/issues/288)) * Adds native support for `time.Duration` marshalling ([details](https://github.com/go-mgo/mgo/pull/373)) diff --git a/auth.go b/auth.go index 75d2ebc36..b8e49f804 100644 --- a/auth.go +++ b/auth.go @@ -29,9 +29,11 @@ package mgo import ( "crypto/md5" "crypto/sha1" + "crypto/sha256" "encoding/hex" "errors" "fmt" + "hash" "sync" "github.com/globalsign/mgo/bson" @@ -276,7 +278,9 @@ func (socket *mongoSocket) loginSASL(cred Credential) error { var err error if cred.Mechanism == "SCRAM-SHA-1" { // SCRAM is handled without external libraries. - sasl = saslNewScram(cred) + sasl = saslNewScram(sha1.New, cred) + } else if cred.Mechanism == "SCRAM-SHA-256" { + sasl = saslNewScram(sha256.New, cred) } else if len(cred.ServiceHost) > 0 { sasl, err = saslNew(cred, cred.ServiceHost) } else { @@ -353,10 +357,10 @@ func (socket *mongoSocket) loginSASL(cred Credential) error { return nil } -func saslNewScram(cred Credential) *saslScram { +func saslNewScram(hash func() hash.Hash, cred Credential) *saslScram { credsum := md5.New() credsum.Write([]byte(cred.Username + ":mongo:" + cred.Password)) - client := scram.NewClient(sha1.New, cred.Username, hex.EncodeToString(credsum.Sum(nil))) + client := scram.NewClient(hash, cred.Username, hex.EncodeToString(credsum.Sum(nil))) return &saslScram{cred: cred, client: client} } diff --git a/auth_test.go b/auth_test.go index 955a57b8b..bcdaec75b 100644 --- a/auth_test.go +++ b/auth_test.go @@ -884,6 +884,55 @@ func (s *S) TestAuthScramSha1URL(c *C) { c.Assert(err, Equals, mgo.ErrNotFound) } +func (s *S) TestAuthScramSha256Cred(c *C) { + if !s.versionAtLeast(4, 0, 0) { + c.Skip("SCRAM-SHA-256 tests depend on 4.0.0") + } + cred := &mgo.Credential{ + Username: "root", + Password: "rapadura", + Mechanism: "SCRAM-SHA-256", + Source: "admin", + } + host := "localhost:40002" + c.Logf("Connecting to %s...", host) + session, err := mgo.Dial(host) + c.Assert(err, IsNil) + defer session.Close() + + mycoll := session.DB("admin").C("mycoll") + + c.Logf("Connected! Testing the need for authentication...") + err = mycoll.Find(nil).One(nil) + c.Assert(err, ErrorMatches, "unauthorized|not authorized .*|.* requires authentication") + + c.Logf("Authenticating...") + err = session.Login(cred) + c.Assert(err, IsNil) + c.Logf("Authenticated!") + + c.Logf("Connected! Testing the need for authentication...") + err = mycoll.Find(nil).One(nil) + c.Assert(err, Equals, mgo.ErrNotFound) +} + +func (s *S) TestAuthScramSha256URL(c *C) { + if !s.versionAtLeast(4, 0, 0) { + c.Skip("SCRAM-SHA-256 tests depend on 4.0.0") + } + host := "localhost:40002" + c.Logf("Connecting to %s...", host) + session, err := mgo.Dial(fmt.Sprintf("root:rapadura@%s?authMechanism=SCRAM-SHA-1", host)) + c.Assert(err, IsNil) + defer session.Close() + + mycoll := session.DB("admin").C("mycoll") + + c.Logf("Connected! Testing the need for authentication...") + err = mycoll.Find(nil).One(nil) + c.Assert(err, Equals, mgo.ErrNotFound) +} + func (s *S) TestAuthX509Cred(c *C) { session, err := mgo.Dial("localhost:40001") c.Assert(err, IsNil) From 730fc77952e3848b00373fdfdea278edf1221071 Mon Sep 17 00:00:00 2001 From: Morgan Hill Date: Mon, 8 Oct 2018 10:23:20 +0200 Subject: [PATCH 2/5] Fix SCRAM-SHA-256 URI test --- auth_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth_test.go b/auth_test.go index bcdaec75b..6062b1916 100644 --- a/auth_test.go +++ b/auth_test.go @@ -922,7 +922,7 @@ func (s *S) TestAuthScramSha256URL(c *C) { } host := "localhost:40002" c.Logf("Connecting to %s...", host) - session, err := mgo.Dial(fmt.Sprintf("root:rapadura@%s?authMechanism=SCRAM-SHA-1", host)) + session, err := mgo.Dial(fmt.Sprintf("root:rapadura@%s?authMechanism=SCRAM-SHA-256", host)) c.Assert(err, IsNil) defer session.Close() From ae432860f8748d3c345be85c9c8bdd7da1ded1c9 Mon Sep 17 00:00:00 2001 From: Morgan Hill Date: Mon, 8 Oct 2018 15:21:55 +0200 Subject: [PATCH 3/5] Use external library for SCRAM authentication Removes custom SCRAM implementation replacing it with a wrapper for the existing xdg-go/scram library. Changes the saslNewScram interface to take a new type *scram.Method argument replacing the func () hash.Hash type. Adds a scram.NewMethod function that validates and returns a supported method. --- .travis.yml | 1 + auth.go | 17 +-- internal/scram/scram.go | 240 +++++++++-------------------------- internal/scram/scram_test.go | 61 ++------- 4 files changed, 77 insertions(+), 242 deletions(-) diff --git a/.travis.yml b/.travis.yml index 29bf80d8b..32b91354f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,6 +31,7 @@ install: - go get gopkg.in/yaml.v2 - go get gopkg.in/tomb.v2 - go get golang.org/x/lint/golint + - go get github.com/xdg-go/scram before_script: - golint ./... | grep -v 'ID' | cat diff --git a/auth.go b/auth.go index b8e49f804..443fca97d 100644 --- a/auth.go +++ b/auth.go @@ -28,12 +28,9 @@ package mgo import ( "crypto/md5" - "crypto/sha1" - "crypto/sha256" "encoding/hex" "errors" "fmt" - "hash" "sync" "github.com/globalsign/mgo/bson" @@ -276,11 +273,11 @@ func (socket *mongoSocket) loginPlain(cred Credential) error { func (socket *mongoSocket) loginSASL(cred Credential) error { var sasl saslStepper var err error - if cred.Mechanism == "SCRAM-SHA-1" { - // SCRAM is handled without external libraries. - sasl = saslNewScram(sha1.New, cred) - } else if cred.Mechanism == "SCRAM-SHA-256" { - sasl = saslNewScram(sha256.New, cred) + if cred.Mechanism == "SCRAM-SHA-1" || cred.Mechanism == "SCRAM-SHA-256" { + // SCRAM is handled with github.com/xdg-go/scram. + var method *scram.Method + method, err = scram.NewMethod(cred.Mechanism) + sasl = saslNewScram(method, cred) } else if len(cred.ServiceHost) > 0 { sasl, err = saslNew(cred, cred.ServiceHost) } else { @@ -357,10 +354,10 @@ func (socket *mongoSocket) loginSASL(cred Credential) error { return nil } -func saslNewScram(hash func() hash.Hash, cred Credential) *saslScram { +func saslNewScram(method *scram.Method, cred Credential) *saslScram { credsum := md5.New() credsum.Write([]byte(cred.Username + ":mongo:" + cred.Password)) - client := scram.NewClient(hash, cred.Username, hex.EncodeToString(credsum.Sum(nil))) + client := scram.NewClient(method, cred.Username, hex.EncodeToString(credsum.Sum(nil))) return &saslScram{cred: cred, client: client} } diff --git a/internal/scram/scram.go b/internal/scram/scram.go index 03c14daf7..48406eace 100644 --- a/internal/scram/scram.go +++ b/internal/scram/scram.go @@ -32,21 +32,23 @@ package scram import ( "bytes" - "crypto/hmac" - "crypto/rand" - "encoding/base64" - "fmt" - "hash" - "strconv" - "strings" + "errors" + + xdg "github.com/xdg-go/scram" ) -// Client implements a SCRAM-* client (SCRAM-SHA-1, SCRAM-SHA-256, etc). +// Client adapts a SCRAM client (SCRAM-SHA-1, SCRAM-SHA-256). // // A Client may be used within a SASL conversation with logic resembling: // +// mechanism, err := scram.NewMethod("SCRAM-SHA-256") +// +// if err != nil { +// log.Fatal(err) +// } +// // var in []byte -// var client = scram.NewClient(sha1.New, user, pass) +// var client = scram.NewClient(, user, pass) // for client.Step(in) { // out := client.Out() // // send out to server @@ -57,34 +59,62 @@ import ( // } // type Client struct { - newHash func() hash.Hash - - user string - pass string - step int out bytes.Buffer err error + conv *xdg.ClientConversation +} + +// Method defines the variant of SCRAM to use +type Method struct { + method string +} + +const ( + // ScramSha1 use the SCRAM-SHA-1 variant + ScramSha1 = "SCRAM-SHA-1" + + // ScramSha256 use the SCRAM-SHA-256 variant + ScramSha256 = "SCRAM-SHA-256" +) - clientNonce []byte - serverNonce []byte - saltedPass []byte - authMsg bytes.Buffer +// NewMethod returns a Method if the input method string is supported +// otherwise it returns an error. +// Supported method strings: +// - "SCRAM-SHA-1" +// - "SCRAM-SHA-256" +func NewMethod(methodString string) (*Method, error) { + switch methodString { + case ScramSha1, ScramSha256: + return &Method{method: methodString}, nil + default: + return nil, errors.New("invalid SCRAM mechanism") + } } -// NewClient returns a new SCRAM-* client with the provided hash algorithm. +// NewClient returns a new SCRAM client with the provided hash algorithm. // // For SCRAM-SHA-1, for example, use: // -// client := scram.NewClient(sha1.New, user, pass) +// method, _ := scram.NewMethod("SCRAM-SHA-1") +// +// client := scram.NewClient(method, user, pass) // -func NewClient(newHash func() hash.Hash, user, pass string) *Client { +func NewClient(method *Method, user, pass string) *Client { + var client *xdg.Client + var err error + + switch method.method { + case ScramSha1: + client, err = xdg.SHA1.NewClient(user, pass, "") + case ScramSha256: + client, err = xdg.SHA256.NewClient(user, pass, "") + } + c := &Client{ - newHash: newHash, - user: user, - pass: pass, + conv: client.NewConversation(), + err: err, } c.out.Grow(256) - c.authMsg.Grow(256) return c } @@ -101,166 +131,14 @@ func (c *Client) Err() error { return c.err } -// SetNonce sets the client nonce to the provided value. -// If not set, the nonce is generated automatically out of crypto/rand on the first step. -func (c *Client) SetNonce(nonce []byte) { - c.clientNonce = nonce -} - -var escaper = strings.NewReplacer("=", "=3D", ",", "=2C") - // Step processes the incoming data from the server and makes the // next round of data for the server available via Client.Out. // Step returns false if there are no errors and more data is // still expected. func (c *Client) Step(in []byte) bool { + var resp string c.out.Reset() - if c.step > 2 || c.err != nil { - return false - } - c.step++ - switch c.step { - case 1: - c.err = c.step1(in) - case 2: - c.err = c.step2(in) - case 3: - c.err = c.step3(in) - } - return c.step > 2 || c.err != nil -} - -func (c *Client) step1(in []byte) error { - if len(c.clientNonce) == 0 { - const nonceLen = 6 - buf := make([]byte, nonceLen+b64.EncodedLen(nonceLen)) - if _, err := rand.Read(buf[:nonceLen]); err != nil { - return fmt.Errorf("cannot read random SCRAM-SHA-1 nonce from operating system: %v", err) - } - c.clientNonce = buf[nonceLen:] - b64.Encode(c.clientNonce, buf[:nonceLen]) - } - c.authMsg.WriteString("n=") - escaper.WriteString(&c.authMsg, c.user) - c.authMsg.WriteString(",r=") - c.authMsg.Write(c.clientNonce) - - c.out.WriteString("n,,") - c.out.Write(c.authMsg.Bytes()) - return nil -} - -var b64 = base64.StdEncoding - -func (c *Client) step2(in []byte) error { - c.authMsg.WriteByte(',') - c.authMsg.Write(in) - - fields := bytes.Split(in, []byte(",")) - if len(fields) != 3 { - return fmt.Errorf("expected 3 fields in first SCRAM-SHA-1 server message, got %d: %q", len(fields), in) - } - if !bytes.HasPrefix(fields[0], []byte("r=")) || len(fields[0]) < 2 { - return fmt.Errorf("server sent an invalid SCRAM-SHA-1 nonce: %q", fields[0]) - } - if !bytes.HasPrefix(fields[1], []byte("s=")) || len(fields[1]) < 6 { - return fmt.Errorf("server sent an invalid SCRAM-SHA-1 salt: %q", fields[1]) - } - if !bytes.HasPrefix(fields[2], []byte("i=")) || len(fields[2]) < 6 { - return fmt.Errorf("server sent an invalid SCRAM-SHA-1 iteration count: %q", fields[2]) - } - - c.serverNonce = fields[0][2:] - if !bytes.HasPrefix(c.serverNonce, c.clientNonce) { - return fmt.Errorf("server SCRAM-SHA-1 nonce is not prefixed by client nonce: got %q, want %q+\"...\"", c.serverNonce, c.clientNonce) - } - - salt := make([]byte, b64.DecodedLen(len(fields[1][2:]))) - n, err := b64.Decode(salt, fields[1][2:]) - if err != nil { - return fmt.Errorf("cannot decode SCRAM-SHA-1 salt sent by server: %q", fields[1]) - } - salt = salt[:n] - iterCount, err := strconv.Atoi(string(fields[2][2:])) - if err != nil { - return fmt.Errorf("server sent an invalid SCRAM-SHA-1 iteration count: %q", fields[2]) - } - c.saltPassword(salt, iterCount) - - c.authMsg.WriteString(",c=biws,r=") - c.authMsg.Write(c.serverNonce) - - c.out.WriteString("c=biws,r=") - c.out.Write(c.serverNonce) - c.out.WriteString(",p=") - c.out.Write(c.clientProof()) - return nil -} - -func (c *Client) step3(in []byte) error { - var isv, ise bool - var fields = bytes.Split(in, []byte(",")) - if len(fields) == 1 { - isv = bytes.HasPrefix(fields[0], []byte("v=")) - ise = bytes.HasPrefix(fields[0], []byte("e=")) - } - if ise { - return fmt.Errorf("SCRAM-SHA-1 authentication error: %s", fields[0][2:]) - } else if !isv { - return fmt.Errorf("unsupported SCRAM-SHA-1 final message from server: %q", in) - } - if !bytes.Equal(c.serverSignature(), fields[0][2:]) { - return fmt.Errorf("cannot authenticate SCRAM-SHA-1 server signature: %q", fields[0][2:]) - } - return nil -} - -func (c *Client) saltPassword(salt []byte, iterCount int) { - mac := hmac.New(c.newHash, []byte(c.pass)) - mac.Write(salt) - mac.Write([]byte{0, 0, 0, 1}) - ui := mac.Sum(nil) - hi := make([]byte, len(ui)) - copy(hi, ui) - for i := 1; i < iterCount; i++ { - mac.Reset() - mac.Write(ui) - mac.Sum(ui[:0]) - for j, b := range ui { - hi[j] ^= b - } - } - c.saltedPass = hi -} - -func (c *Client) clientProof() []byte { - mac := hmac.New(c.newHash, c.saltedPass) - mac.Write([]byte("Client Key")) - clientKey := mac.Sum(nil) - hash := c.newHash() - hash.Write(clientKey) - storedKey := hash.Sum(nil) - mac = hmac.New(c.newHash, storedKey) - mac.Write(c.authMsg.Bytes()) - clientProof := mac.Sum(nil) - for i, b := range clientKey { - clientProof[i] ^= b - } - clientProof64 := make([]byte, b64.EncodedLen(len(clientProof))) - b64.Encode(clientProof64, clientProof) - return clientProof64 -} - -func (c *Client) serverSignature() []byte { - mac := hmac.New(c.newHash, c.saltedPass) - mac.Write([]byte("Server Key")) - serverKey := mac.Sum(nil) - - mac = hmac.New(c.newHash, serverKey) - mac.Write(c.authMsg.Bytes()) - serverSignature := mac.Sum(nil) - - encoded := make([]byte, b64.EncodedLen(len(serverSignature))) - b64.Encode(encoded, serverSignature) - return encoded + resp, c.err = c.conv.Step(string(in)) + _, c.err = c.out.Write([]byte(resp)) + return c.conv.Valid() || c.err != nil } diff --git a/internal/scram/scram_test.go b/internal/scram/scram_test.go index a54a56b7b..2aa6e07b6 100644 --- a/internal/scram/scram_test.go +++ b/internal/scram/scram_test.go @@ -1,11 +1,8 @@ package scram_test import ( - "crypto/sha1" "testing" - "strings" - "github.com/globalsign/mgo/internal/scram" . "gopkg.in/check.v1" ) @@ -16,53 +13,15 @@ func Test(t *testing.T) { TestingT(t) } type S struct{} -var tests = [][]string{{ - "U: user pencil", - "N: fyko+d2lbbFgONRv9qkxdawL", - "C: n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL", - "S: r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096", - "C: c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=", - "S: v=rmF9pqV8S7suAoZWja4dJRkFsKQ=", -}, { - "U: root fe8c89e308ec08763df36333cbf5d3a2", - "N: OTcxNDk5NjM2MzE5", - "C: n,,n=root,r=OTcxNDk5NjM2MzE5", - "S: r=OTcxNDk5NjM2MzE581Ra3provgG0iDsMkDiIAlrh4532dDLp,s=XRDkVrFC9JuL7/F4tG0acQ==,i=10000", - "C: c=biws,r=OTcxNDk5NjM2MzE581Ra3provgG0iDsMkDiIAlrh4532dDLp,p=6y1jp9R7ETyouTXS9fW9k5UHdBc=", - "S: v=LBnd9dUJRxdqZiEq91NKP3z/bHA=", -}} +func (s *S) TestNewMethod(c *C) { + var err error + + _, err = scram.NewMethod("SCRAM-SHA-1") + c.Assert(err, Equals, IsNil) + + _, err = scram.NewMethod("SCRAM-SHA-256") + c.Assert(err, IsNil) -func (s *S) TestExamples(c *C) { - for _, steps := range tests { - if len(steps) < 2 || len(steps[0]) < 3 || !strings.HasPrefix(steps[0], "U: ") { - c.Fatalf("Invalid test: %#v", steps) - } - auth := strings.Fields(steps[0][3:]) - client := scram.NewClient(sha1.New, auth[0], auth[1]) - first, done := true, false - c.Logf("-----") - c.Logf("%s", steps[0]) - for _, step := range steps[1:] { - c.Logf("%s", step) - switch step[:3] { - case "N: ": - client.SetNonce([]byte(step[3:])) - case "C: ": - if first { - first = false - done = client.Step(nil) - } - c.Assert(done, Equals, false) - c.Assert(client.Err(), IsNil) - c.Assert(string(client.Out()), Equals, step[3:]) - case "S: ": - first = false - done = client.Step([]byte(step[3:])) - default: - panic("invalid test line: " + step) - } - } - c.Assert(done, Equals, true) - c.Assert(client.Err(), IsNil) - } + _, err = scram.NewMethod("example") + c.Assert(err, NotNil) } From cdbd809951537f1c2b5912c76cd8ab95b3a3db6f Mon Sep 17 00:00:00 2001 From: Morgan Hill Date: Mon, 8 Oct 2018 16:54:32 +0200 Subject: [PATCH 4/5] Only check sasl is done once in loop Responsibility for checking if the authentication process is completed it placed in the Step function, hence there should be no need to check the response object done field independently. --- auth.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/auth.go b/auth.go index 443fca97d..f411703ef 100644 --- a/auth.go +++ b/auth.go @@ -318,7 +318,7 @@ func (socket *mongoSocket) loginSASL(cred Credential) error { if err != nil { return err } - if done && res.Done { + if done { socket.dropAuth(cred.Source) socket.creds = append(socket.creds, cred) break @@ -344,11 +344,7 @@ func (socket *mongoSocket) loginSASL(cred Credential) error { if err != nil { return err } - if done && res.Done { - socket.dropAuth(cred.Source) - socket.creds = append(socket.creds, cred) - break - } + } return nil From 049d3f9408e687bdf058267287fd48b884973058 Mon Sep 17 00:00:00 2001 From: Morgan Hill Date: Mon, 15 Oct 2018 13:26:51 +0200 Subject: [PATCH 5/5] Implement the saslStepper interface in SCRAM --- auth.go | 24 +++--------------- internal/scram/scram.go | 55 ++++++++++++++++------------------------- 2 files changed, 25 insertions(+), 54 deletions(-) diff --git a/auth.go b/auth.go index f411703ef..ee8024de4 100644 --- a/auth.go +++ b/auth.go @@ -277,7 +277,10 @@ func (socket *mongoSocket) loginSASL(cred Credential) error { // SCRAM is handled with github.com/xdg-go/scram. var method *scram.Method method, err = scram.NewMethod(cred.Mechanism) - sasl = saslNewScram(method, cred) + if err != nil { + return err + } + sasl, err = scram.NewClient(method, cred.Username, cred.Password) } else if len(cred.ServiceHost) > 0 { sasl, err = saslNew(cred, cred.ServiceHost) } else { @@ -350,25 +353,6 @@ func (socket *mongoSocket) loginSASL(cred Credential) error { return nil } -func saslNewScram(method *scram.Method, cred Credential) *saslScram { - credsum := md5.New() - credsum.Write([]byte(cred.Username + ":mongo:" + cred.Password)) - client := scram.NewClient(method, cred.Username, hex.EncodeToString(credsum.Sum(nil))) - return &saslScram{cred: cred, client: client} -} - -type saslScram struct { - cred Credential - client *scram.Client -} - -func (s *saslScram) Close() {} - -func (s *saslScram) Step(serverData []byte) (clientData []byte, done bool, err error) { - more := s.client.Step(serverData) - return s.client.Out(), !more, s.client.Err() -} - func (socket *mongoSocket) loginRun(db string, query, result interface{}, f func() error) error { var mutex sync.Mutex var replyErr error diff --git a/internal/scram/scram.go b/internal/scram/scram.go index 48406eace..dcd53636e 100644 --- a/internal/scram/scram.go +++ b/internal/scram/scram.go @@ -31,7 +31,6 @@ package scram import ( - "bytes" "errors" xdg "github.com/xdg-go/scram" @@ -59,8 +58,6 @@ import ( // } // type Client struct { - out bytes.Buffer - err error conv *xdg.ClientConversation } @@ -97,48 +94,38 @@ func NewMethod(methodString string) (*Method, error) { // // method, _ := scram.NewMethod("SCRAM-SHA-1") // -// client := scram.NewClient(method, user, pass) +// client, _ := scram.NewClient(method, user, pass) // -func NewClient(method *Method, user, pass string) *Client { - var client *xdg.Client - var err error +func NewClient(method *Method, user, pass string) (client *Client, err error) { + var internalClient *xdg.Client switch method.method { case ScramSha1: - client, err = xdg.SHA1.NewClient(user, pass, "") + internalClient, err = xdg.SHA1.NewClient(user, pass, "") case ScramSha256: - client, err = xdg.SHA256.NewClient(user, pass, "") + internalClient, err = xdg.SHA256.NewClient(user, pass, "") } - c := &Client{ - conv: client.NewConversation(), - err: err, + client = &Client{ + conv: internalClient.NewConversation(), } - c.out.Grow(256) - return c + return } -// Out returns the data to be sent to the server in the current step. -func (c *Client) Out() []byte { - if c.out.Len() == 0 { - return []byte{} - } - return c.out.Bytes() -} - -// Err returns the error that occurred, or nil if there were no errors. -func (c *Client) Err() error { - return c.err +// Implement saslStepper (auth.go) +type saslStepper interface { + Step(serverData []byte) (clientData []byte, done bool, err error) + Close() } -// Step processes the incoming data from the server and makes the -// next round of data for the server available via Client.Out. -// Step returns false if there are no errors and more data is -// still expected. -func (c *Client) Step(in []byte) bool { +// Step progresses the underlying SASL SCRAM process +func (c *Client) Step(serverData []byte) (clientData []byte, done bool, err error) { var resp string - c.out.Reset() - resp, c.err = c.conv.Step(string(in)) - _, c.err = c.out.Write([]byte(resp)) - return c.conv.Valid() || c.err != nil + resp, err = c.conv.Step(string(serverData)) + clientData = []byte(resp) + done = c.conv.Done() + return } + +// Close is a no opp to fit the saslStepper interface +func (c *Client) Close() {}