Browse Source

implement SCRAM-SHA-256

tags/v2.8.0-rc1
Shivaram Lingamneni 2 years ago
parent
commit
e1401934df

+ 3
- 0
go.mod View File

@@ -19,9 +19,12 @@ require (
19 19
 	github.com/stretchr/testify v1.4.0 // indirect
20 20
 	github.com/tidwall/buntdb v1.2.3
21 21
 	github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208
22
+	github.com/xdg-go/scram v1.0.2
22 23
 	golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc
23 24
 	golang.org/x/text v0.3.6
24 25
 	gopkg.in/yaml.v2 v2.4.0
25 26
 )
26 27
 
27 28
 replace github.com/gorilla/websocket => github.com/ergochat/websocket v1.4.2-oragono1
29
+
30
+replace github.com/xdg-go/scram => github.com/ergochat/scram v1.0.2-ergo1

+ 7
- 0
go.sum View File

@@ -12,6 +12,8 @@ github.com/ergochat/go-ident v0.0.0-20200511222032-830550b1d775 h1:QSJIdpr3HOzJD
12 12
 github.com/ergochat/go-ident v0.0.0-20200511222032-830550b1d775/go.mod h1:d2qvgjD0TvGNSvUs+mZgX090RiJlrzUYW6vtANGOy3A=
13 13
 github.com/ergochat/irc-go v0.0.0-20210617222258-256f1601d3ce h1:RfyjeynouKZjmnN8WGzCSrtuHGZ9dwfSYBq405FPoqs=
14 14
 github.com/ergochat/irc-go v0.0.0-20210617222258-256f1601d3ce/go.mod h1:2vi7KNpIPWnReB5hmLpl92eMywQvuIeIIGdt/FQCph0=
15
+github.com/ergochat/scram v1.0.2-ergo1 h1:2bYXiRFQH636pT0msOG39fmEYl4Eq+OuutcyDsCix/g=
16
+github.com/ergochat/scram v1.0.2-ergo1/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
15 17
 github.com/ergochat/websocket v1.4.2-oragono1 h1:plMUunFBM6UoSCIYCKKclTdy/TkkHfUslhOfJQzfueM=
16 18
 github.com/ergochat/websocket v1.4.2-oragono1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
17 19
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -57,6 +59,10 @@ github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaym
57 59
 github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw=
58 60
 github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 h1:PM5hJF7HVfNWmCjMdEfbuOBNXSVF2cMFGgQTPdKCbwM=
59 61
 github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns=
62
+github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
63
+github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
64
+github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc=
65
+github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
60 66
 golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc h1:+q90ECDSAQirdykUN6sPEiBXBsp8Csjcca8Oy7bgLTA=
61 67
 golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
62 68
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -71,6 +77,7 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4
71 77
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
72 78
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
73 79
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
80
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
74 81
 golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
75 82
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
76 83
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

+ 52
- 5
irc/accounts.go View File

@@ -5,6 +5,7 @@ package irc
5 5
 
6 6
 import (
7 7
 	"bytes"
8
+	"crypto/rand"
8 9
 	"crypto/x509"
9 10
 	"encoding/json"
10 11
 	"fmt"
@@ -16,6 +17,7 @@ import (
16 17
 	"unicode"
17 18
 
18 19
 	"github.com/ergochat/irc-go/ircutils"
20
+	"github.com/xdg-go/scram"
19 21
 
20 22
 	"github.com/ergochat/ergo/irc/connection_limits"
21 23
 	"github.com/ergochat/ergo/irc/email"
@@ -517,8 +519,8 @@ func validatePassphrase(passphrase string) error {
517 519
 }
518 520
 
519 521
 // changes the password for an account
520
-func (am *AccountManager) setPassword(account string, password string, hasPrivs bool) (err error) {
521
-	cfAccount, err := CasefoldName(account)
522
+func (am *AccountManager) setPassword(accountName string, password string, hasPrivs bool) (err error) {
523
+	cfAccount, err := CasefoldName(accountName)
522 524
 	if err != nil {
523 525
 		return errAccountDoesNotExist
524 526
 	}
@@ -1912,9 +1914,10 @@ func (am *AccountManager) Logout(client *Client) {
1912 1914
 var (
1913 1915
 	// EnabledSaslMechanisms contains the SASL mechanisms that exist and that we support.
1914 1916
 	// This can be moved to some other data structure/place if we need to load/unload mechs later.
1915
-	EnabledSaslMechanisms = map[string]func(*Server, *Client, string, []byte, *ResponseBuffer) bool{
1916
-		"PLAIN":    authPlainHandler,
1917
-		"EXTERNAL": authExternalHandler,
1917
+	EnabledSaslMechanisms = map[string]func(*Server, *Client, *Session, []byte, *ResponseBuffer) bool{
1918
+		"PLAIN":         authPlainHandler,
1919
+		"EXTERNAL":      authExternalHandler,
1920
+		"SCRAM-SHA-256": authScramHandler,
1918 1921
 	}
1919 1922
 )
1920 1923
 
@@ -1933,6 +1936,12 @@ type AccountCredentials struct {
1933 1936
 	Version        CredentialsVersion
1934 1937
 	PassphraseHash []byte
1935 1938
 	Certfps        []string
1939
+	SCRAMCreds     struct {
1940
+		Salt      []byte
1941
+		Iters     int
1942
+		StoredKey []byte
1943
+		ServerKey []byte
1944
+	}
1936 1945
 }
1937 1946
 
1938 1947
 func (ac *AccountCredentials) Empty() bool {
@@ -1964,9 +1973,47 @@ func (ac *AccountCredentials) SetPassphrase(passphrase string, bcryptCost uint)
1964 1973
 		return errAccountBadPassphrase
1965 1974
 	}
1966 1975
 
1976
+	// we can pass an empty account name because it won't actually be incorporated
1977
+	// into the credentials; it's just a quirk of the xdg-go/scram API that the way
1978
+	// to produce server credentials is to call NewClient* and then GetStoredCredentials
1979
+	scramClient, err := scram.SHA256.NewClientUnprepped("", passphrase, "")
1980
+	if err != nil {
1981
+		return errAccountBadPassphrase
1982
+	}
1983
+	salt := make([]byte, 16)
1984
+	rand.Read(salt)
1985
+	// xdg-go/scram says: "Clients have a default minimum PBKDF2 iteration count of 4096."
1986
+	minIters := 4096
1987
+	scramCreds := scramClient.GetStoredCredentials(scram.KeyFactors{Salt: string(salt), Iters: minIters})
1988
+	ac.SCRAMCreds.Salt = salt
1989
+	ac.SCRAMCreds.Iters = minIters
1990
+	ac.SCRAMCreds.StoredKey = scramCreds.StoredKey
1991
+	ac.SCRAMCreds.ServerKey = scramCreds.ServerKey
1992
+
1967 1993
 	return nil
1968 1994
 }
1969 1995
 
1996
+func (am *AccountManager) NewScramConversation() *scram.ServerConversation {
1997
+	server, _ := scram.SHA256.NewServer(am.lookupSCRAMCreds)
1998
+	return server.NewConversation()
1999
+}
2000
+
2001
+func (am *AccountManager) lookupSCRAMCreds(accountName string) (creds scram.StoredCredentials, err error) {
2002
+	acct, err := am.LoadAccount(accountName)
2003
+	if err != nil {
2004
+		return
2005
+	}
2006
+	if acct.Credentials.SCRAMCreds.Iters == 0 {
2007
+		err = errNoSCRAMCredentials
2008
+		return
2009
+	}
2010
+	creds.Salt = string(acct.Credentials.SCRAMCreds.Salt)
2011
+	creds.Iters = acct.Credentials.SCRAMCreds.Iters
2012
+	creds.StoredKey = acct.Credentials.SCRAMCreds.StoredKey
2013
+	creds.ServerKey = acct.Credentials.SCRAMCreds.ServerKey
2014
+	return
2015
+}
2016
+
1970 2017
 func (ac *AccountCredentials) AddCertfp(certfp string) (err error) {
1971 2018
 	// XXX we require that certfp is already normalized (rather than normalize here
1972 2019
 	// and pass back the normalized version as an additional return parameter);

+ 2
- 0
irc/client.go View File

@@ -20,6 +20,7 @@ import (
20 20
 	"github.com/ergochat/irc-go/ircfmt"
21 21
 	"github.com/ergochat/irc-go/ircmsg"
22 22
 	"github.com/ergochat/irc-go/ircreader"
23
+	"github.com/xdg-go/scram"
23 24
 
24 25
 	"github.com/ergochat/ergo/irc/caps"
25 26
 	"github.com/ergochat/ergo/irc/connection_limits"
@@ -116,6 +117,7 @@ type Client struct {
116 117
 type saslStatus struct {
117 118
 	mechanism string
118 119
 	value     string
120
+	scramConv *scram.ServerConversation
119 121
 }
120 122
 
121 123
 func (s *saslStatus) Clear() {

+ 1
- 1
irc/config.go View File

@@ -1379,7 +1379,7 @@ func LoadConfig(filename string) (config *Config, err error) {
1379 1379
 		config.Accounts.VHosts.validRegexp = defaultValidVhostRegex
1380 1380
 	}
1381 1381
 
1382
-	config.Server.capValues[caps.SASL] = "PLAIN,EXTERNAL"
1382
+	config.Server.capValues[caps.SASL] = "PLAIN,EXTERNAL,SCRAM-SHA-256"
1383 1383
 	if !config.Accounts.AuthenticationEnabled {
1384 1384
 		config.Server.supportedCaps.Disable(caps.SASL)
1385 1385
 	}

+ 1
- 0
irc/errors.go View File

@@ -63,6 +63,7 @@ var (
63 63
 	errCASFailed                      = errors.New("Compare-and-swap update of database value failed")
64 64
 	errEmptyCredentials               = errors.New("No more credentials are approved")
65 65
 	errCredsExternallyManaged         = errors.New("Credentials are externally managed and cannot be changed here")
66
+	errNoSCRAMCredentials             = errors.New("SCRAM credentials are not initialized for this account; consult the user guide")
66 67
 	errInvalidMultilineBatch          = errors.New("Invalid multiline batch")
67 68
 	errTimedOut                       = errors.New("Operation timed out")
68 69
 	errInvalidUtf8                    = errors.New("Message rejected for invalid utf8")

+ 66
- 4
irc/handlers.go View File

@@ -211,6 +211,7 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.Message, rb
211 211
 	var err error
212 212
 	if session.sasl.value != "+" {
213 213
 		data, err = base64.StdEncoding.DecodeString(session.sasl.value)
214
+		session.sasl.value = ""
214 215
 		if err != nil {
215 216
 			rb.Add(nil, server.name, ERR_SASLFAIL, details.nick, client.t("SASL authentication failed: Invalid b64 encoding"))
216 217
 			session.sasl.Clear()
@@ -229,14 +230,15 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.Message, rb
229 230
 	}
230 231
 
231 232
 	// let the SASL handler do its thing
232
-	exiting := handler(server, client, session.sasl.mechanism, data, rb)
233
-	session.sasl.Clear()
233
+	exiting := handler(server, client, session, data, rb)
234 234
 
235 235
 	return exiting
236 236
 }
237 237
 
238 238
 // AUTHENTICATE PLAIN
239
-func authPlainHandler(server *Server, client *Client, mechanism string, value []byte, rb *ResponseBuffer) bool {
239
+func authPlainHandler(server *Server, client *Client, session *Session, value []byte, rb *ResponseBuffer) bool {
240
+	defer session.sasl.Clear()
241
+
240 242
 	splitValue := bytes.Split(value, []byte{'\000'})
241 243
 
242 244
 	// PLAIN has separate "authorization ID" (which user you want to become)
@@ -301,7 +303,9 @@ func authErrorToMessage(server *Server, err error) (msg string) {
301 303
 }
302 304
 
303 305
 // AUTHENTICATE EXTERNAL
304
-func authExternalHandler(server *Server, client *Client, mechanism string, value []byte, rb *ResponseBuffer) bool {
306
+func authExternalHandler(server *Server, client *Client, session *Session, value []byte, rb *ResponseBuffer) bool {
307
+	defer session.sasl.Clear()
308
+
305 309
 	if rb.session.certfp == "" {
306 310
 		rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, client.t("SASL authentication failed, you are not connecting with a certificate"))
307 311
 		return false
@@ -343,6 +347,64 @@ func authExternalHandler(server *Server, client *Client, mechanism string, value
343 347
 	return false
344 348
 }
345 349
 
350
+// AUTHENTICATE SCRAM-SHA-256
351
+func authScramHandler(server *Server, client *Client, session *Session, value []byte, rb *ResponseBuffer) bool {
352
+	continueAuth := true
353
+	defer func() {
354
+		if !continueAuth {
355
+			session.sasl.Clear()
356
+		}
357
+	}()
358
+
359
+	// first message? if so, initialize the SCRAM conversation
360
+	if session.sasl.scramConv == nil {
361
+		throttled, remainingTime := client.loginThrottle.Touch()
362
+		if throttled {
363
+			rb.Add(nil, server.name, ERR_SASLFAIL, client.Nick(), fmt.Sprintf(client.t("Please wait at least %v and try again"), remainingTime))
364
+			continueAuth = false
365
+			return false
366
+		}
367
+		session.sasl.scramConv = server.accounts.NewScramConversation()
368
+	}
369
+
370
+	// wait for a final AUTHENTICATE + from the client to conclude authentication
371
+	if session.sasl.scramConv.Done() {
372
+		continueAuth = false
373
+		if session.sasl.scramConv.Valid() {
374
+			accountName := session.sasl.scramConv.Username()
375
+			authzid := session.sasl.scramConv.AuthzID()
376
+			if authzid != "" && authzid != accountName {
377
+				rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, client.t("SASL authentication failed: authcid and authzid should be the same"))
378
+				return false
379
+			}
380
+			account, err := server.accounts.LoadAccount(accountName)
381
+			if err == nil {
382
+				server.accounts.Login(client, account)
383
+				if fixupNickEqualsAccount(client, rb, server.Config(), "") {
384
+					sendSuccessfulAccountAuth(nil, client, rb, true)
385
+				}
386
+			} else {
387
+				server.logger.Error("internal", "SCRAM succeeded but couldn't load account", accountName, err.Error())
388
+				rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, client.t("SASL authentication failed"))
389
+			}
390
+		} else {
391
+			rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, client.t("SASL authentication failed"))
392
+		}
393
+		return false
394
+	}
395
+
396
+	response, err := session.sasl.scramConv.Step(string(value))
397
+	if err == nil {
398
+		rb.Add(nil, server.name, "AUTHENTICATE", base64.StdEncoding.EncodeToString([]byte(response)))
399
+	} else {
400
+		continueAuth = false
401
+		rb.Add(nil, server.name, ERR_SASLFAIL, client.Nick(), err.Error())
402
+		return false
403
+	}
404
+
405
+	return false
406
+}
407
+
346 408
 // AWAY [<message>]
347 409
 func awayHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
348 410
 	var isAway bool

+ 12
- 0
vendor/github.com/xdg-go/pbkdf2/.gitignore View File

@@ -0,0 +1,12 @@
1
+# Binaries for programs and plugins
2
+*.exe
3
+*.exe~
4
+*.dll
5
+*.so
6
+*.dylib
7
+
8
+# Test binary, build with `go test -c`
9
+*.test
10
+
11
+# Output of the go coverage tool, specifically when used with LiteIDE
12
+*.out

+ 175
- 0
vendor/github.com/xdg-go/pbkdf2/LICENSE View File

@@ -0,0 +1,175 @@
1
+
2
+                                 Apache License
3
+                           Version 2.0, January 2004
4
+                        http://www.apache.org/licenses/
5
+
6
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+   1. Definitions.
9
+
10
+      "License" shall mean the terms and conditions for use, reproduction,
11
+      and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+      "Licensor" shall mean the copyright owner or entity authorized by
14
+      the copyright owner that is granting the License.
15
+
16
+      "Legal Entity" shall mean the union of the acting entity and all
17
+      other entities that control, are controlled by, or are under common
18
+      control with that entity. For the purposes of this definition,
19
+      "control" means (i) the power, direct or indirect, to cause the
20
+      direction or management of such entity, whether by contract or
21
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+      outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+      "You" (or "Your") shall mean an individual or Legal Entity
25
+      exercising permissions granted by this License.
26
+
27
+      "Source" form shall mean the preferred form for making modifications,
28
+      including but not limited to software source code, documentation
29
+      source, and configuration files.
30
+
31
+      "Object" form shall mean any form resulting from mechanical
32
+      transformation or translation of a Source form, including but
33
+      not limited to compiled object code, generated documentation,
34
+      and conversions to other media types.
35
+
36
+      "Work" shall mean the work of authorship, whether in Source or
37
+      Object form, made available under the License, as indicated by a
38
+      copyright notice that is included in or attached to the work
39
+      (an example is provided in the Appendix below).
40
+
41
+      "Derivative Works" shall mean any work, whether in Source or Object
42
+      form, that is based on (or derived from) the Work and for which the
43
+      editorial revisions, annotations, elaborations, or other modifications
44
+      represent, as a whole, an original work of authorship. For the purposes
45
+      of this License, Derivative Works shall not include works that remain
46
+      separable from, or merely link (or bind by name) to the interfaces of,
47
+      the Work and Derivative Works thereof.
48
+
49
+      "Contribution" shall mean any work of authorship, including
50
+      the original version of the Work and any modifications or additions
51
+      to that Work or Derivative Works thereof, that is intentionally
52
+      submitted to Licensor for inclusion in the Work by the copyright owner
53
+      or by an individual or Legal Entity authorized to submit on behalf of
54
+      the copyright owner. For the purposes of this definition, "submitted"
55
+      means any form of electronic, verbal, or written communication sent
56
+      to the Licensor or its representatives, including but not limited to
57
+      communication on electronic mailing lists, source code control systems,
58
+      and issue tracking systems that are managed by, or on behalf of, the
59
+      Licensor for the purpose of discussing and improving the Work, but
60
+      excluding communication that is conspicuously marked or otherwise
61
+      designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+      "Contributor" shall mean Licensor and any individual or Legal Entity
64
+      on behalf of whom a Contribution has been received by Licensor and
65
+      subsequently incorporated within the Work.
66
+
67
+   2. Grant of Copyright License. Subject to the terms and conditions of
68
+      this License, each Contributor hereby grants to You a perpetual,
69
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+      copyright license to reproduce, prepare Derivative Works of,
71
+      publicly display, publicly perform, sublicense, and distribute the
72
+      Work and such Derivative Works in Source or Object form.
73
+
74
+   3. Grant of Patent License. Subject to the terms and conditions of
75
+      this License, each Contributor hereby grants to You a perpetual,
76
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+      (except as stated in this section) patent license to make, have made,
78
+      use, offer to sell, sell, import, and otherwise transfer the Work,
79
+      where such license applies only to those patent claims licensable
80
+      by such Contributor that are necessarily infringed by their
81
+      Contribution(s) alone or by combination of their Contribution(s)
82
+      with the Work to which such Contribution(s) was submitted. If You
83
+      institute patent litigation against any entity (including a
84
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+      or a Contribution incorporated within the Work constitutes direct
86
+      or contributory patent infringement, then any patent licenses
87
+      granted to You under this License for that Work shall terminate
88
+      as of the date such litigation is filed.
89
+
90
+   4. Redistribution. You may reproduce and distribute copies of the
91
+      Work or Derivative Works thereof in any medium, with or without
92
+      modifications, and in Source or Object form, provided that You
93
+      meet the following conditions:
94
+
95
+      (a) You must give any other recipients of the Work or
96
+          Derivative Works a copy of this License; and
97
+
98
+      (b) You must cause any modified files to carry prominent notices
99
+          stating that You changed the files; and
100
+
101
+      (c) You must retain, in the Source form of any Derivative Works
102
+          that You distribute, all copyright, patent, trademark, and
103
+          attribution notices from the Source form of the Work,
104
+          excluding those notices that do not pertain to any part of
105
+          the Derivative Works; and
106
+
107
+      (d) If the Work includes a "NOTICE" text file as part of its
108
+          distribution, then any Derivative Works that You distribute must
109
+          include a readable copy of the attribution notices contained
110
+          within such NOTICE file, excluding those notices that do not
111
+          pertain to any part of the Derivative Works, in at least one
112
+          of the following places: within a NOTICE text file distributed
113
+          as part of the Derivative Works; within the Source form or
114
+          documentation, if provided along with the Derivative Works; or,
115
+          within a display generated by the Derivative Works, if and
116
+          wherever such third-party notices normally appear. The contents
117
+          of the NOTICE file are for informational purposes only and
118
+          do not modify the License. You may add Your own attribution
119
+          notices within Derivative Works that You distribute, alongside
120
+          or as an addendum to the NOTICE text from the Work, provided
121
+          that such additional attribution notices cannot be construed
122
+          as modifying the License.
123
+
124
+      You may add Your own copyright statement to Your modifications and
125
+      may provide additional or different license terms and conditions
126
+      for use, reproduction, or distribution of Your modifications, or
127
+      for any such Derivative Works as a whole, provided Your use,
128
+      reproduction, and distribution of the Work otherwise complies with
129
+      the conditions stated in this License.
130
+
131
+   5. Submission of Contributions. Unless You explicitly state otherwise,
132
+      any Contribution intentionally submitted for inclusion in the Work
133
+      by You to the Licensor shall be under the terms and conditions of
134
+      this License, without any additional terms or conditions.
135
+      Notwithstanding the above, nothing herein shall supersede or modify
136
+      the terms of any separate license agreement you may have executed
137
+      with Licensor regarding such Contributions.
138
+
139
+   6. Trademarks. This License does not grant permission to use the trade
140
+      names, trademarks, service marks, or product names of the Licensor,
141
+      except as required for reasonable and customary use in describing the
142
+      origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+   7. Disclaimer of Warranty. Unless required by applicable law or
145
+      agreed to in writing, Licensor provides the Work (and each
146
+      Contributor provides its Contributions) on an "AS IS" BASIS,
147
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+      implied, including, without limitation, any warranties or conditions
149
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+      PARTICULAR PURPOSE. You are solely responsible for determining the
151
+      appropriateness of using or redistributing the Work and assume any
152
+      risks associated with Your exercise of permissions under this License.
153
+
154
+   8. Limitation of Liability. In no event and under no legal theory,
155
+      whether in tort (including negligence), contract, or otherwise,
156
+      unless required by applicable law (such as deliberate and grossly
157
+      negligent acts) or agreed to in writing, shall any Contributor be
158
+      liable to You for damages, including any direct, indirect, special,
159
+      incidental, or consequential damages of any character arising as a
160
+      result of this License or out of the use or inability to use the
161
+      Work (including but not limited to damages for loss of goodwill,
162
+      work stoppage, computer failure or malfunction, or any and all
163
+      other commercial damages or losses), even if such Contributor
164
+      has been advised of the possibility of such damages.
165
+
166
+   9. Accepting Warranty or Additional Liability. While redistributing
167
+      the Work or Derivative Works thereof, You may choose to offer,
168
+      and charge a fee for, acceptance of support, warranty, indemnity,
169
+      or other liability obligations and/or rights consistent with this
170
+      License. However, in accepting such obligations, You may act only
171
+      on Your own behalf and on Your sole responsibility, not on behalf
172
+      of any other Contributor, and only if You agree to indemnify,
173
+      defend, and hold each Contributor harmless for any liability
174
+      incurred by, or claims asserted against, such Contributor by reason
175
+      of your accepting any such warranty or additional liability.

+ 17
- 0
vendor/github.com/xdg-go/pbkdf2/README.md View File

@@ -0,0 +1,17 @@
1
+[![Go Reference](https://pkg.go.dev/badge/github.com/xdg-go/pbkdf2.svg)](https://pkg.go.dev/github.com/xdg-go/pbkdf2)
2
+[![Go Report Card](https://goreportcard.com/badge/github.com/xdg-go/pbkdf2)](https://goreportcard.com/report/github.com/xdg-go/pbkdf2)
3
+[![Github Actions](https://github.com/xdg-go/pbkdf2/actions/workflows/test.yml/badge.svg)](https://github.com/xdg-go/pbkdf2/actions/workflows/test.yml)
4
+
5
+# pbkdf2 – Go implementation of PBKDF2
6
+
7
+## Description
8
+
9
+Package pbkdf2 provides password-based key derivation based on
10
+[RFC 8018](https://tools.ietf.org/html/rfc8018).
11
+
12
+## Copyright and License
13
+
14
+Copyright 2021 by David A. Golden. All rights reserved.
15
+
16
+Licensed under the Apache License, Version 2.0 (the "License"). You may
17
+obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

+ 3
- 0
vendor/github.com/xdg-go/pbkdf2/go.mod View File

@@ -0,0 +1,3 @@
1
+module github.com/xdg-go/pbkdf2
2
+
3
+go 1.9

+ 76
- 0
vendor/github.com/xdg-go/pbkdf2/pbkdf2.go View File

@@ -0,0 +1,76 @@
1
+// Copyright 2021 by David A. Golden. All rights reserved.
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+// not use this file except in compliance with the License. You may obtain
5
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+// Package pbkdf2 implements password-based key derivation using the PBKDF2
8
+// algorithm described in RFC 2898 and RFC 8018.
9
+//
10
+// It provides a drop-in replacement for `golang.org/x/crypto/pbkdf2`, with
11
+// the following benefits:
12
+//
13
+// - Released as a module with semantic versioning
14
+//
15
+// - Does not pull in dependencies for unrelated `x/crypto/*` packages
16
+//
17
+// - Supports Go 1.9+
18
+//
19
+// See https://tools.ietf.org/html/rfc8018#section-4 for security considerations
20
+// in the selection of a salt and iteration count.
21
+package pbkdf2
22
+
23
+import (
24
+	"crypto/hmac"
25
+	"encoding/binary"
26
+	"hash"
27
+)
28
+
29
+// Key generates a derived key from a password using the PBKDF2 algorithm. The
30
+// inputs include salt bytes, the iteration count, desired key length, and a
31
+// constructor for a hashing function.  For example, for a 32-byte key using
32
+// SHA-256:
33
+//
34
+//  key := Key([]byte("trustNo1"), salt, 10000, 32, sha256.New)
35
+func Key(password, salt []byte, iterCount, keyLen int, h func() hash.Hash) []byte {
36
+	prf := hmac.New(h, password)
37
+	hLen := prf.Size()
38
+	numBlocks := keyLen / hLen
39
+	// Get an extra block if keyLen is not an even number of hLen blocks.
40
+	if keyLen%hLen > 0 {
41
+		numBlocks++
42
+	}
43
+
44
+	Ti := make([]byte, hLen)
45
+	Uj := make([]byte, hLen)
46
+	dk := make([]byte, 0, hLen*numBlocks)
47
+	buf := make([]byte, 4)
48
+
49
+	for i := uint32(1); i <= uint32(numBlocks); i++ {
50
+		// Initialize Uj for j == 1 from salt and block index.
51
+		// Initialize Ti = U1.
52
+		binary.BigEndian.PutUint32(buf, i)
53
+		prf.Reset()
54
+		prf.Write(salt)
55
+		prf.Write(buf)
56
+		Uj = Uj[:0]
57
+		Uj = prf.Sum(Uj)
58
+
59
+		// Ti = U1 ^ U2 ^ ... ^ Ux
60
+		copy(Ti, Uj)
61
+		for j := 2; j <= iterCount; j++ {
62
+			prf.Reset()
63
+			prf.Write(Uj)
64
+			Uj = Uj[:0]
65
+			Uj = prf.Sum(Uj)
66
+			for k := range Uj {
67
+				Ti[k] ^= Uj[k]
68
+			}
69
+		}
70
+
71
+		// DK = concat(T1, T2, ... Tn)
72
+		dk = append(dk, Ti...)
73
+	}
74
+
75
+	return dk[0:keyLen]
76
+}

+ 0
- 0
vendor/github.com/xdg-go/scram/.gitignore View File


+ 14
- 0
vendor/github.com/xdg-go/scram/CHANGELOG.md View File

@@ -0,0 +1,14 @@
1
+# CHANGELOG
2
+
3
+## v1.0.2 - 2021-03-28
4
+
5
+- Switch PBKDF2 dependency to github.com/xdg-go/pbkdf2 to
6
+  minimize transitive dependencies and support Go 1.9+.
7
+
8
+## v1.0.1 - 2021-03-27
9
+
10
+- Bump stringprep dependency to v1.0.2 for Go 1.11 support.
11
+
12
+## v1.0.0 - 2021-03-27
13
+
14
+- First release as a Go module

+ 175
- 0
vendor/github.com/xdg-go/scram/LICENSE View File

@@ -0,0 +1,175 @@
1
+
2
+                                 Apache License
3
+                           Version 2.0, January 2004
4
+                        http://www.apache.org/licenses/
5
+
6
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+   1. Definitions.
9
+
10
+      "License" shall mean the terms and conditions for use, reproduction,
11
+      and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+      "Licensor" shall mean the copyright owner or entity authorized by
14
+      the copyright owner that is granting the License.
15
+
16
+      "Legal Entity" shall mean the union of the acting entity and all
17
+      other entities that control, are controlled by, or are under common
18
+      control with that entity. For the purposes of this definition,
19
+      "control" means (i) the power, direct or indirect, to cause the
20
+      direction or management of such entity, whether by contract or
21
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+      outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+      "You" (or "Your") shall mean an individual or Legal Entity
25
+      exercising permissions granted by this License.
26
+
27
+      "Source" form shall mean the preferred form for making modifications,
28
+      including but not limited to software source code, documentation
29
+      source, and configuration files.
30
+
31
+      "Object" form shall mean any form resulting from mechanical
32
+      transformation or translation of a Source form, including but
33
+      not limited to compiled object code, generated documentation,
34
+      and conversions to other media types.
35
+
36
+      "Work" shall mean the work of authorship, whether in Source or
37
+      Object form, made available under the License, as indicated by a
38
+      copyright notice that is included in or attached to the work
39
+      (an example is provided in the Appendix below).
40
+
41
+      "Derivative Works" shall mean any work, whether in Source or Object
42
+      form, that is based on (or derived from) the Work and for which the
43
+      editorial revisions, annotations, elaborations, or other modifications
44
+      represent, as a whole, an original work of authorship. For the purposes
45
+      of this License, Derivative Works shall not include works that remain
46
+      separable from, or merely link (or bind by name) to the interfaces of,
47
+      the Work and Derivative Works thereof.
48
+
49
+      "Contribution" shall mean any work of authorship, including
50
+      the original version of the Work and any modifications or additions
51
+      to that Work or Derivative Works thereof, that is intentionally
52
+      submitted to Licensor for inclusion in the Work by the copyright owner
53
+      or by an individual or Legal Entity authorized to submit on behalf of
54
+      the copyright owner. For the purposes of this definition, "submitted"
55
+      means any form of electronic, verbal, or written communication sent
56
+      to the Licensor or its representatives, including but not limited to
57
+      communication on electronic mailing lists, source code control systems,
58
+      and issue tracking systems that are managed by, or on behalf of, the
59
+      Licensor for the purpose of discussing and improving the Work, but
60
+      excluding communication that is conspicuously marked or otherwise
61
+      designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+      "Contributor" shall mean Licensor and any individual or Legal Entity
64
+      on behalf of whom a Contribution has been received by Licensor and
65
+      subsequently incorporated within the Work.
66
+
67
+   2. Grant of Copyright License. Subject to the terms and conditions of
68
+      this License, each Contributor hereby grants to You a perpetual,
69
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+      copyright license to reproduce, prepare Derivative Works of,
71
+      publicly display, publicly perform, sublicense, and distribute the
72
+      Work and such Derivative Works in Source or Object form.
73
+
74
+   3. Grant of Patent License. Subject to the terms and conditions of
75
+      this License, each Contributor hereby grants to You a perpetual,
76
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+      (except as stated in this section) patent license to make, have made,
78
+      use, offer to sell, sell, import, and otherwise transfer the Work,
79
+      where such license applies only to those patent claims licensable
80
+      by such Contributor that are necessarily infringed by their
81
+      Contribution(s) alone or by combination of their Contribution(s)
82
+      with the Work to which such Contribution(s) was submitted. If You
83
+      institute patent litigation against any entity (including a
84
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+      or a Contribution incorporated within the Work constitutes direct
86
+      or contributory patent infringement, then any patent licenses
87
+      granted to You under this License for that Work shall terminate
88
+      as of the date such litigation is filed.
89
+
90
+   4. Redistribution. You may reproduce and distribute copies of the
91
+      Work or Derivative Works thereof in any medium, with or without
92
+      modifications, and in Source or Object form, provided that You
93
+      meet the following conditions:
94
+
95
+      (a) You must give any other recipients of the Work or
96
+          Derivative Works a copy of this License; and
97
+
98
+      (b) You must cause any modified files to carry prominent notices
99
+          stating that You changed the files; and
100
+
101
+      (c) You must retain, in the Source form of any Derivative Works
102
+          that You distribute, all copyright, patent, trademark, and
103
+          attribution notices from the Source form of the Work,
104
+          excluding those notices that do not pertain to any part of
105
+          the Derivative Works; and
106
+
107
+      (d) If the Work includes a "NOTICE" text file as part of its
108
+          distribution, then any Derivative Works that You distribute must
109
+          include a readable copy of the attribution notices contained
110
+          within such NOTICE file, excluding those notices that do not
111
+          pertain to any part of the Derivative Works, in at least one
112
+          of the following places: within a NOTICE text file distributed
113
+          as part of the Derivative Works; within the Source form or
114
+          documentation, if provided along with the Derivative Works; or,
115
+          within a display generated by the Derivative Works, if and
116
+          wherever such third-party notices normally appear. The contents
117
+          of the NOTICE file are for informational purposes only and
118
+          do not modify the License. You may add Your own attribution
119
+          notices within Derivative Works that You distribute, alongside
120
+          or as an addendum to the NOTICE text from the Work, provided
121
+          that such additional attribution notices cannot be construed
122
+          as modifying the License.
123
+
124
+      You may add Your own copyright statement to Your modifications and
125
+      may provide additional or different license terms and conditions
126
+      for use, reproduction, or distribution of Your modifications, or
127
+      for any such Derivative Works as a whole, provided Your use,
128
+      reproduction, and distribution of the Work otherwise complies with
129
+      the conditions stated in this License.
130
+
131
+   5. Submission of Contributions. Unless You explicitly state otherwise,
132
+      any Contribution intentionally submitted for inclusion in the Work
133
+      by You to the Licensor shall be under the terms and conditions of
134
+      this License, without any additional terms or conditions.
135
+      Notwithstanding the above, nothing herein shall supersede or modify
136
+      the terms of any separate license agreement you may have executed
137
+      with Licensor regarding such Contributions.
138
+
139
+   6. Trademarks. This License does not grant permission to use the trade
140
+      names, trademarks, service marks, or product names of the Licensor,
141
+      except as required for reasonable and customary use in describing the
142
+      origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+   7. Disclaimer of Warranty. Unless required by applicable law or
145
+      agreed to in writing, Licensor provides the Work (and each
146
+      Contributor provides its Contributions) on an "AS IS" BASIS,
147
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+      implied, including, without limitation, any warranties or conditions
149
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+      PARTICULAR PURPOSE. You are solely responsible for determining the
151
+      appropriateness of using or redistributing the Work and assume any
152
+      risks associated with Your exercise of permissions under this License.
153
+
154
+   8. Limitation of Liability. In no event and under no legal theory,
155
+      whether in tort (including negligence), contract, or otherwise,
156
+      unless required by applicable law (such as deliberate and grossly
157
+      negligent acts) or agreed to in writing, shall any Contributor be
158
+      liable to You for damages, including any direct, indirect, special,
159
+      incidental, or consequential damages of any character arising as a
160
+      result of this License or out of the use or inability to use the
161
+      Work (including but not limited to damages for loss of goodwill,
162
+      work stoppage, computer failure or malfunction, or any and all
163
+      other commercial damages or losses), even if such Contributor
164
+      has been advised of the possibility of such damages.
165
+
166
+   9. Accepting Warranty or Additional Liability. While redistributing
167
+      the Work or Derivative Works thereof, You may choose to offer,
168
+      and charge a fee for, acceptance of support, warranty, indemnity,
169
+      or other liability obligations and/or rights consistent with this
170
+      License. However, in accepting such obligations, You may act only
171
+      on Your own behalf and on Your sole responsibility, not on behalf
172
+      of any other Contributor, and only if You agree to indemnify,
173
+      defend, and hold each Contributor harmless for any liability
174
+      incurred by, or claims asserted against, such Contributor by reason
175
+      of your accepting any such warranty or additional liability.

+ 72
- 0
vendor/github.com/xdg-go/scram/README.md View File

@@ -0,0 +1,72 @@
1
+[![Go Reference](https://pkg.go.dev/badge/github.com/xdg-go/scram.svg)](https://pkg.go.dev/github.com/xdg-go/scram)
2
+[![Go Report Card](https://goreportcard.com/badge/github.com/xdg-go/scram)](https://goreportcard.com/report/github.com/xdg-go/scram)
3
+[![Github Actions](https://github.com/xdg-go/scram/actions/workflows/test.yml/badge.svg)](https://github.com/xdg-go/scram/actions/workflows/test.yml)
4
+
5
+# scram – Go implementation of RFC-5802
6
+
7
+## Description
8
+
9
+Package scram provides client and server implementations of the Salted
10
+Challenge Response Authentication Mechanism (SCRAM) described in
11
+[RFC-5802](https://tools.ietf.org/html/rfc5802) and
12
+[RFC-7677](https://tools.ietf.org/html/rfc7677).
13
+
14
+It includes both client and server side support.
15
+
16
+Channel binding and extensions are not (yet) supported.
17
+
18
+## Examples
19
+
20
+### Client side
21
+
22
+    package main
23
+
24
+    import "github.com/xdg-go/scram"
25
+
26
+    func main() {
27
+        // Get Client with username, password and (optional) authorization ID.
28
+        clientSHA1, err := scram.SHA1.NewClient("mulder", "trustno1", "")
29
+        if err != nil {
30
+            panic(err)
31
+        }
32
+
33
+        // Prepare the authentication conversation. Use the empty string as the
34
+        // initial server message argument to start the conversation.
35
+        conv := clientSHA1.NewConversation()
36
+        var serverMsg string
37
+
38
+        // Get the first message, send it and read the response.
39
+        firstMsg, err := conv.Step(serverMsg)
40
+        if err != nil {
41
+            panic(err)
42
+        }
43
+        serverMsg = sendClientMsg(firstMsg)
44
+
45
+        // Get the second message, send it, and read the response.
46
+        secondMsg, err := conv.Step(serverMsg)
47
+        if err != nil {
48
+            panic(err)
49
+        }
50
+        serverMsg = sendClientMsg(secondMsg)
51
+
52
+        // Validate the server's final message.  We have no further message to
53
+        // send so ignore that return value.
54
+        _, err = conv.Step(serverMsg)
55
+        if err != nil {
56
+            panic(err)
57
+        }
58
+
59
+        return
60
+    }
61
+
62
+    func sendClientMsg(s string) string {
63
+        // A real implementation would send this to a server and read a reply.
64
+        return ""
65
+    }
66
+
67
+## Copyright and License
68
+
69
+Copyright 2018 by David A. Golden. All rights reserved.
70
+
71
+Licensed under the Apache License, Version 2.0 (the "License"). You may
72
+obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

+ 130
- 0
vendor/github.com/xdg-go/scram/client.go View File

@@ -0,0 +1,130 @@
1
+// Copyright 2018 by David A. Golden. All rights reserved.
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+// not use this file except in compliance with the License. You may obtain
5
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+package scram
8
+
9
+import (
10
+	"sync"
11
+
12
+	"github.com/xdg-go/pbkdf2"
13
+)
14
+
15
+// Client implements the client side of SCRAM authentication.  It holds
16
+// configuration values needed to initialize new client-side conversations for
17
+// a specific username, password and authorization ID tuple.  Client caches
18
+// the computationally-expensive parts of a SCRAM conversation as described in
19
+// RFC-5802.  If repeated authentication conversations may be required for a
20
+// user (e.g. disconnect/reconnect), the user's Client should be preserved.
21
+//
22
+// For security reasons, Clients have a default minimum PBKDF2 iteration count
23
+// of 4096.  If a server requests a smaller iteration count, an authentication
24
+// conversation will error.
25
+//
26
+// A Client can also be used by a server application to construct the hashed
27
+// authentication values to be stored for a new user.  See StoredCredentials()
28
+// for more.
29
+type Client struct {
30
+	sync.RWMutex
31
+	username string
32
+	password string
33
+	authzID  string
34
+	minIters int
35
+	nonceGen NonceGeneratorFcn
36
+	hashGen  HashGeneratorFcn
37
+	cache    map[KeyFactors]derivedKeys
38
+}
39
+
40
+func newClient(username, password, authzID string, fcn HashGeneratorFcn) *Client {
41
+	return &Client{
42
+		username: username,
43
+		password: password,
44
+		authzID:  authzID,
45
+		minIters: 4096,
46
+		nonceGen: defaultNonceGenerator,
47
+		hashGen:  fcn,
48
+		cache:    make(map[KeyFactors]derivedKeys),
49
+	}
50
+}
51
+
52
+// WithMinIterations changes minimum required PBKDF2 iteration count.
53
+func (c *Client) WithMinIterations(n int) *Client {
54
+	c.Lock()
55
+	defer c.Unlock()
56
+	c.minIters = n
57
+	return c
58
+}
59
+
60
+// WithNonceGenerator replaces the default nonce generator (base64 encoding of
61
+// 24 bytes from crypto/rand) with a custom generator.  This is provided for
62
+// testing or for users with custom nonce requirements.
63
+func (c *Client) WithNonceGenerator(ng NonceGeneratorFcn) *Client {
64
+	c.Lock()
65
+	defer c.Unlock()
66
+	c.nonceGen = ng
67
+	return c
68
+}
69
+
70
+// NewConversation constructs a client-side authentication conversation.
71
+// Conversations cannot be reused, so this must be called for each new
72
+// authentication attempt.
73
+func (c *Client) NewConversation() *ClientConversation {
74
+	c.RLock()
75
+	defer c.RUnlock()
76
+	return &ClientConversation{
77
+		client:   c,
78
+		nonceGen: c.nonceGen,
79
+		hashGen:  c.hashGen,
80
+		minIters: c.minIters,
81
+	}
82
+}
83
+
84
+func (c *Client) getDerivedKeys(kf KeyFactors) derivedKeys {
85
+	dk, ok := c.getCache(kf)
86
+	if !ok {
87
+		dk = c.computeKeys(kf)
88
+		c.setCache(kf, dk)
89
+	}
90
+	return dk
91
+}
92
+
93
+// GetStoredCredentials takes a salt and iteration count structure and
94
+// provides the values that must be stored by a server to authentication a
95
+// user.  These values are what the Server credential lookup function must
96
+// return for a given username.
97
+func (c *Client) GetStoredCredentials(kf KeyFactors) StoredCredentials {
98
+	dk := c.getDerivedKeys(kf)
99
+	return StoredCredentials{
100
+		KeyFactors: kf,
101
+		StoredKey:  dk.StoredKey,
102
+		ServerKey:  dk.ServerKey,
103
+	}
104
+}
105
+
106
+func (c *Client) computeKeys(kf KeyFactors) derivedKeys {
107
+	h := c.hashGen()
108
+	saltedPassword := pbkdf2.Key([]byte(c.password), []byte(kf.Salt), kf.Iters, h.Size(), c.hashGen)
109
+	clientKey := computeHMAC(c.hashGen, saltedPassword, []byte("Client Key"))
110
+
111
+	return derivedKeys{
112
+		ClientKey: clientKey,
113
+		StoredKey: computeHash(c.hashGen, clientKey),
114
+		ServerKey: computeHMAC(c.hashGen, saltedPassword, []byte("Server Key")),
115
+	}
116
+}
117
+
118
+func (c *Client) getCache(kf KeyFactors) (derivedKeys, bool) {
119
+	c.RLock()
120
+	defer c.RUnlock()
121
+	dk, ok := c.cache[kf]
122
+	return dk, ok
123
+}
124
+
125
+func (c *Client) setCache(kf KeyFactors, dk derivedKeys) {
126
+	c.Lock()
127
+	defer c.Unlock()
128
+	c.cache[kf] = dk
129
+	return
130
+}

+ 149
- 0
vendor/github.com/xdg-go/scram/client_conv.go View File

@@ -0,0 +1,149 @@
1
+// Copyright 2018 by David A. Golden. All rights reserved.
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+// not use this file except in compliance with the License. You may obtain
5
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+package scram
8
+
9
+import (
10
+	"crypto/hmac"
11
+	"encoding/base64"
12
+	"errors"
13
+	"fmt"
14
+	"strings"
15
+)
16
+
17
+type clientState int
18
+
19
+const (
20
+	clientStarting clientState = iota
21
+	clientFirst
22
+	clientFinal
23
+	clientDone
24
+)
25
+
26
+// ClientConversation implements the client-side of an authentication
27
+// conversation with a server.  A new conversation must be created for
28
+// each authentication attempt.
29
+type ClientConversation struct {
30
+	client   *Client
31
+	nonceGen NonceGeneratorFcn
32
+	hashGen  HashGeneratorFcn
33
+	minIters int
34
+	state    clientState
35
+	valid    bool
36
+	gs2      string
37
+	nonce    string
38
+	c1b      string
39
+	serveSig []byte
40
+}
41
+
42
+// Step takes a string provided from a server (or just an empty string for the
43
+// very first conversation step) and attempts to move the authentication
44
+// conversation forward.  It returns a string to be sent to the server or an
45
+// error if the server message is invalid.  Calling Step after a conversation
46
+// completes is also an error.
47
+func (cc *ClientConversation) Step(challenge string) (response string, err error) {
48
+	switch cc.state {
49
+	case clientStarting:
50
+		cc.state = clientFirst
51
+		response, err = cc.firstMsg()
52
+	case clientFirst:
53
+		cc.state = clientFinal
54
+		response, err = cc.finalMsg(challenge)
55
+	case clientFinal:
56
+		cc.state = clientDone
57
+		response, err = cc.validateServer(challenge)
58
+	default:
59
+		response, err = "", errors.New("Conversation already completed")
60
+	}
61
+	return
62
+}
63
+
64
+// Done returns true if the conversation is completed or has errored.
65
+func (cc *ClientConversation) Done() bool {
66
+	return cc.state == clientDone
67
+}
68
+
69
+// Valid returns true if the conversation successfully authenticated with the
70
+// server, including counter-validation that the server actually has the
71
+// user's stored credentials.
72
+func (cc *ClientConversation) Valid() bool {
73
+	return cc.valid
74
+}
75
+
76
+func (cc *ClientConversation) firstMsg() (string, error) {
77
+	// Values are cached for use in final message parameters
78
+	cc.gs2 = cc.gs2Header()
79
+	cc.nonce = cc.client.nonceGen()
80
+	cc.c1b = fmt.Sprintf("n=%s,r=%s", encodeName(cc.client.username), cc.nonce)
81
+
82
+	return cc.gs2 + cc.c1b, nil
83
+}
84
+
85
+func (cc *ClientConversation) finalMsg(s1 string) (string, error) {
86
+	msg, err := parseServerFirst(s1)
87
+	if err != nil {
88
+		return "", err
89
+	}
90
+
91
+	// Check nonce prefix and update
92
+	if !strings.HasPrefix(msg.nonce, cc.nonce) {
93
+		return "", errors.New("server nonce did not extend client nonce")
94
+	}
95
+	cc.nonce = msg.nonce
96
+
97
+	// Check iteration count vs minimum
98
+	if msg.iters < cc.minIters {
99
+		return "", fmt.Errorf("server requested too few iterations (%d)", msg.iters)
100
+	}
101
+
102
+	// Create client-final-message-without-proof
103
+	c2wop := fmt.Sprintf(
104
+		"c=%s,r=%s",
105
+		base64.StdEncoding.EncodeToString([]byte(cc.gs2)),
106
+		cc.nonce,
107
+	)
108
+
109
+	// Create auth message
110
+	authMsg := cc.c1b + "," + s1 + "," + c2wop
111
+
112
+	// Get derived keys from client cache
113
+	dk := cc.client.getDerivedKeys(KeyFactors{Salt: string(msg.salt), Iters: msg.iters})
114
+
115
+	// Create proof as clientkey XOR clientsignature
116
+	clientSignature := computeHMAC(cc.hashGen, dk.StoredKey, []byte(authMsg))
117
+	clientProof := xorBytes(dk.ClientKey, clientSignature)
118
+	proof := base64.StdEncoding.EncodeToString(clientProof)
119
+
120
+	// Cache ServerSignature for later validation
121
+	cc.serveSig = computeHMAC(cc.hashGen, dk.ServerKey, []byte(authMsg))
122
+
123
+	return fmt.Sprintf("%s,p=%s", c2wop, proof), nil
124
+}
125
+
126
+func (cc *ClientConversation) validateServer(s2 string) (string, error) {
127
+	msg, err := parseServerFinal(s2)
128
+	if err != nil {
129
+		return "", err
130
+	}
131
+
132
+	if len(msg.err) > 0 {
133
+		return "", fmt.Errorf("server error: %s", msg.err)
134
+	}
135
+
136
+	if !hmac.Equal(msg.verifier, cc.serveSig) {
137
+		return "", errors.New("server validation failed")
138
+	}
139
+
140
+	cc.valid = true
141
+	return "", nil
142
+}
143
+
144
+func (cc *ClientConversation) gs2Header() string {
145
+	if cc.client.authzID == "" {
146
+		return "n,,"
147
+	}
148
+	return fmt.Sprintf("n,%s,", encodeName(cc.client.authzID))
149
+}

+ 97
- 0
vendor/github.com/xdg-go/scram/common.go View File

@@ -0,0 +1,97 @@
1
+// Copyright 2018 by David A. Golden. All rights reserved.
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+// not use this file except in compliance with the License. You may obtain
5
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+package scram
8
+
9
+import (
10
+	"crypto/hmac"
11
+	"crypto/rand"
12
+	"encoding/base64"
13
+	"strings"
14
+)
15
+
16
+// NonceGeneratorFcn defines a function that returns a string of high-quality
17
+// random printable ASCII characters EXCLUDING the comma (',') character.  The
18
+// default nonce generator provides Base64 encoding of 24 bytes from
19
+// crypto/rand.
20
+type NonceGeneratorFcn func() string
21
+
22
+// derivedKeys collects the three cryptographically derived values
23
+// into one struct for caching.
24
+type derivedKeys struct {
25
+	ClientKey []byte
26
+	StoredKey []byte
27
+	ServerKey []byte
28
+}
29
+
30
+// KeyFactors represent the two server-provided factors needed to compute
31
+// client credentials for authentication.  Salt is decoded bytes (i.e. not
32
+// base64), but in string form so that KeyFactors can be used as a map key for
33
+// cached credentials.
34
+type KeyFactors struct {
35
+	Salt  string
36
+	Iters int
37
+}
38
+
39
+// StoredCredentials are the values that a server must store for a given
40
+// username to allow authentication.  They include the salt and iteration
41
+// count, plus the derived values to authenticate a client and for the server
42
+// to authenticate itself back to the client.
43
+//
44
+// NOTE: these are specific to a given hash function.  To allow a user to
45
+// authenticate with either SCRAM-SHA-1 or SCRAM-SHA-256, two sets of
46
+// StoredCredentials must be created and stored, one for each hash function.
47
+type StoredCredentials struct {
48
+	KeyFactors
49
+	StoredKey []byte
50
+	ServerKey []byte
51
+}
52
+
53
+// CredentialLookup is a callback to provide StoredCredentials for a given
54
+// username.  This is used to configure Server objects.
55
+//
56
+// NOTE: these are specific to a given hash function.  The callback provided
57
+// to a Server with a given hash function must provide the corresponding
58
+// StoredCredentials.
59
+type CredentialLookup func(string) (StoredCredentials, error)
60
+
61
+func defaultNonceGenerator() string {
62
+	raw := make([]byte, 24)
63
+	nonce := make([]byte, base64.StdEncoding.EncodedLen(len(raw)))
64
+	rand.Read(raw)
65
+	base64.StdEncoding.Encode(nonce, raw)
66
+	return string(nonce)
67
+}
68
+
69
+func encodeName(s string) string {
70
+	return strings.Replace(strings.Replace(s, "=", "=3D", -1), ",", "=2C", -1)
71
+}
72
+
73
+func decodeName(s string) (string, error) {
74
+	// TODO Check for = not followed by 2C or 3D
75
+	return strings.Replace(strings.Replace(s, "=2C", ",", -1), "=3D", "=", -1), nil
76
+}
77
+
78
+func computeHash(hg HashGeneratorFcn, b []byte) []byte {
79
+	h := hg()
80
+	h.Write(b)
81
+	return h.Sum(nil)
82
+}
83
+
84
+func computeHMAC(hg HashGeneratorFcn, key, data []byte) []byte {
85
+	mac := hmac.New(hg, key)
86
+	mac.Write(data)
87
+	return mac.Sum(nil)
88
+}
89
+
90
+func xorBytes(a, b []byte) []byte {
91
+	// TODO check a & b are same length, or just xor to smallest
92
+	xor := make([]byte, len(a))
93
+	for i := range a {
94
+		xor[i] = a[i] ^ b[i]
95
+	}
96
+	return xor
97
+}

+ 24
- 0
vendor/github.com/xdg-go/scram/doc.go View File

@@ -0,0 +1,24 @@
1
+// Copyright 2018 by David A. Golden. All rights reserved.
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+// not use this file except in compliance with the License. You may obtain
5
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+// Package scram provides client and server implementations of the Salted
8
+// Challenge Response Authentication Mechanism (SCRAM) described in RFC-5802
9
+// and RFC-7677.
10
+//
11
+// Usage
12
+//
13
+// The scram package provides two variables, `SHA1` and `SHA256`, that are
14
+// used to construct Client or Server objects.
15
+//
16
+//     clientSHA1,   err := scram.SHA1.NewClient(username, password, authID)
17
+//     clientSHA256, err := scram.SHA256.NewClient(username, password, authID)
18
+//
19
+//     serverSHA1,   err := scram.SHA1.NewServer(credentialLookupFcn)
20
+//     serverSHA256, err := scram.SHA256.NewServer(credentialLookupFcn)
21
+//
22
+// These objects are used to construct ClientConversation or
23
+// ServerConversation objects that are used to carry out authentication.
24
+package scram

+ 8
- 0
vendor/github.com/xdg-go/scram/go.mod View File

@@ -0,0 +1,8 @@
1
+module github.com/xdg-go/scram
2
+
3
+go 1.11
4
+
5
+require (
6
+	github.com/xdg-go/pbkdf2 v1.0.0
7
+	github.com/xdg-go/stringprep v1.0.2
8
+)

+ 7
- 0
vendor/github.com/xdg-go/scram/go.sum View File

@@ -0,0 +1,7 @@
1
+github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
2
+github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
3
+github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc=
4
+github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
5
+golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
6
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
7
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

+ 205
- 0
vendor/github.com/xdg-go/scram/parse.go View File

@@ -0,0 +1,205 @@
1
+// Copyright 2018 by David A. Golden. All rights reserved.
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+// not use this file except in compliance with the License. You may obtain
5
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+package scram
8
+
9
+import (
10
+	"encoding/base64"
11
+	"errors"
12
+	"fmt"
13
+	"strconv"
14
+	"strings"
15
+)
16
+
17
+type c1Msg struct {
18
+	gs2Header string
19
+	authzID   string
20
+	username  string
21
+	nonce     string
22
+	c1b       string
23
+}
24
+
25
+type c2Msg struct {
26
+	cbind []byte
27
+	nonce string
28
+	proof []byte
29
+	c2wop string
30
+}
31
+
32
+type s1Msg struct {
33
+	nonce string
34
+	salt  []byte
35
+	iters int
36
+}
37
+
38
+type s2Msg struct {
39
+	verifier []byte
40
+	err      string
41
+}
42
+
43
+func parseField(s, k string) (string, error) {
44
+	t := strings.TrimPrefix(s, k+"=")
45
+	if t == s {
46
+		return "", fmt.Errorf("error parsing '%s' for field '%s'", s, k)
47
+	}
48
+	return t, nil
49
+}
50
+
51
+func parseGS2Flag(s string) (string, error) {
52
+	if s[0] == 'p' {
53
+		return "", fmt.Errorf("channel binding requested but not supported")
54
+	}
55
+
56
+	if s == "n" || s == "y" {
57
+		return s, nil
58
+	}
59
+
60
+	return "", fmt.Errorf("error parsing '%s' for gs2 flag", s)
61
+}
62
+
63
+func parseFieldBase64(s, k string) ([]byte, error) {
64
+	raw, err := parseField(s, k)
65
+	if err != nil {
66
+		return nil, err
67
+	}
68
+
69
+	dec, err := base64.StdEncoding.DecodeString(raw)
70
+	if err != nil {
71
+		return nil, err
72
+	}
73
+
74
+	return dec, nil
75
+}
76
+
77
+func parseFieldInt(s, k string) (int, error) {
78
+	raw, err := parseField(s, k)
79
+	if err != nil {
80
+		return 0, err
81
+	}
82
+
83
+	num, err := strconv.Atoi(raw)
84
+	if err != nil {
85
+		return 0, fmt.Errorf("error parsing field '%s': %v", k, err)
86
+	}
87
+
88
+	return num, nil
89
+}
90
+
91
+func parseClientFirst(c1 string) (msg c1Msg, err error) {
92
+
93
+	fields := strings.Split(c1, ",")
94
+	if len(fields) < 4 {
95
+		err = errors.New("not enough fields in first server message")
96
+		return
97
+	}
98
+
99
+	gs2flag, err := parseGS2Flag(fields[0])
100
+	if err != nil {
101
+		return
102
+	}
103
+
104
+	// 'a' field is optional
105
+	if len(fields[1]) > 0 {
106
+		msg.authzID, err = parseField(fields[1], "a")
107
+		if err != nil {
108
+			return
109
+		}
110
+	}
111
+
112
+	// Recombine and save the gs2 header
113
+	msg.gs2Header = gs2flag + "," + msg.authzID + ","
114
+
115
+	// Check for unsupported extensions field "m".
116
+	if strings.HasPrefix(fields[2], "m=") {
117
+		err = errors.New("SCRAM message extensions are not supported")
118
+		return
119
+	}
120
+
121
+	msg.username, err = parseField(fields[2], "n")
122
+	if err != nil {
123
+		return
124
+	}
125
+
126
+	msg.nonce, err = parseField(fields[3], "r")
127
+	if err != nil {
128
+		return
129
+	}
130
+
131
+	msg.c1b = strings.Join(fields[2:], ",")
132
+
133
+	return
134
+}
135
+
136
+func parseClientFinal(c2 string) (msg c2Msg, err error) {
137
+	fields := strings.Split(c2, ",")
138
+	if len(fields) < 3 {
139
+		err = errors.New("not enough fields in first server message")
140
+		return
141
+	}
142
+
143
+	msg.cbind, err = parseFieldBase64(fields[0], "c")
144
+	if err != nil {
145
+		return
146
+	}
147
+
148
+	msg.nonce, err = parseField(fields[1], "r")
149
+	if err != nil {
150
+		return
151
+	}
152
+
153
+	// Extension fields may come between nonce and proof, so we
154
+	// grab the *last* fields as proof.
155
+	msg.proof, err = parseFieldBase64(fields[len(fields)-1], "p")
156
+	if err != nil {
157
+		return
158
+	}
159
+
160
+	msg.c2wop = c2[:strings.LastIndex(c2, ",")]
161
+
162
+	return
163
+}
164
+
165
+func parseServerFirst(s1 string) (msg s1Msg, err error) {
166
+
167
+	// Check for unsupported extensions field "m".
168
+	if strings.HasPrefix(s1, "m=") {
169
+		err = errors.New("SCRAM message extensions are not supported")
170
+		return
171
+	}
172
+
173
+	fields := strings.Split(s1, ",")
174
+	if len(fields) < 3 {
175
+		err = errors.New("not enough fields in first server message")
176
+		return
177
+	}
178
+
179
+	msg.nonce, err = parseField(fields[0], "r")
180
+	if err != nil {
181
+		return
182
+	}
183
+
184
+	msg.salt, err = parseFieldBase64(fields[1], "s")
185
+	if err != nil {
186
+		return
187
+	}
188
+
189
+	msg.iters, err = parseFieldInt(fields[2], "i")
190
+
191
+	return
192
+}
193
+
194
+func parseServerFinal(s2 string) (msg s2Msg, err error) {
195
+	fields := strings.Split(s2, ",")
196
+
197
+	msg.verifier, err = parseFieldBase64(fields[0], "v")
198
+	if err == nil {
199
+		return
200
+	}
201
+
202
+	msg.err, err = parseField(fields[0], "e")
203
+
204
+	return
205
+}

+ 42
- 0
vendor/github.com/xdg-go/scram/scram.go View File

@@ -0,0 +1,42 @@
1
+// Copyright 2018 by David A. Golden. All rights reserved.
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+// not use this file except in compliance with the License. You may obtain
5
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+package scram
8
+
9
+import (
10
+	"crypto/sha1"
11
+	"crypto/sha256"
12
+	"hash"
13
+)
14
+
15
+// HashGeneratorFcn abstracts a factory function that returns a hash.Hash
16
+// value to be used for SCRAM operations.  Generally, one would use the
17
+// provided package variables, `scram.SHA1` and `scram.SHA256`, for the most
18
+// common forms of SCRAM.
19
+type HashGeneratorFcn func() hash.Hash
20
+
21
+// SHA1 is a function that returns a crypto/sha1 hasher and should be used to
22
+// create Client objects configured for SHA-1 hashing.
23
+var SHA1 HashGeneratorFcn = func() hash.Hash { return sha1.New() }
24
+
25
+// SHA256 is a function that returns a crypto/sha256 hasher and should be used
26
+// to create Client objects configured for SHA-256 hashing.
27
+var SHA256 HashGeneratorFcn = func() hash.Hash { return sha256.New() }
28
+
29
+// NewClientUnprepped acts like NewClient, except none of the arguments will
30
+// be normalized via SASLprep.  This is not generally recommended, but is
31
+// provided for users that may have custom normalization needs.
32
+func (f HashGeneratorFcn) NewClientUnprepped(username, password, authzID string) (*Client, error) {
33
+	return newClient(username, password, authzID, f), nil
34
+}
35
+
36
+// NewServer constructs a SCRAM server component based on a given hash.Hash
37
+// factory receiver.  To be maximally generic, it uses dependency injection to
38
+// handle credential lookup, which is the process of turning a username string
39
+// into a struct with stored credentials for authentication.
40
+func (f HashGeneratorFcn) NewServer(cl CredentialLookup) (*Server, error) {
41
+	return newServer(cl, f)
42
+}

+ 50
- 0
vendor/github.com/xdg-go/scram/server.go View File

@@ -0,0 +1,50 @@
1
+// Copyright 2018 by David A. Golden. All rights reserved.
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+// not use this file except in compliance with the License. You may obtain
5
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+package scram
8
+
9
+import "sync"
10
+
11
+// Server implements the server side of SCRAM authentication.  It holds
12
+// configuration values needed to initialize new server-side conversations.
13
+// Generally, this can be persistent within an application.
14
+type Server struct {
15
+	sync.RWMutex
16
+	credentialCB CredentialLookup
17
+	nonceGen     NonceGeneratorFcn
18
+	hashGen      HashGeneratorFcn
19
+}
20
+
21
+func newServer(cl CredentialLookup, fcn HashGeneratorFcn) (*Server, error) {
22
+	return &Server{
23
+		credentialCB: cl,
24
+		nonceGen:     defaultNonceGenerator,
25
+		hashGen:      fcn,
26
+	}, nil
27
+}
28
+
29
+// WithNonceGenerator replaces the default nonce generator (base64 encoding of
30
+// 24 bytes from crypto/rand) with a custom generator.  This is provided for
31
+// testing or for users with custom nonce requirements.
32
+func (s *Server) WithNonceGenerator(ng NonceGeneratorFcn) *Server {
33
+	s.Lock()
34
+	defer s.Unlock()
35
+	s.nonceGen = ng
36
+	return s
37
+}
38
+
39
+// NewConversation constructs a server-side authentication conversation.
40
+// Conversations cannot be reused, so this must be called for each new
41
+// authentication attempt.
42
+func (s *Server) NewConversation() *ServerConversation {
43
+	s.RLock()
44
+	defer s.RUnlock()
45
+	return &ServerConversation{
46
+		nonceGen:     s.nonceGen,
47
+		hashGen:      s.hashGen,
48
+		credentialCB: s.credentialCB,
49
+	}
50
+}

+ 151
- 0
vendor/github.com/xdg-go/scram/server_conv.go View File

@@ -0,0 +1,151 @@
1
+// Copyright 2018 by David A. Golden. All rights reserved.
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+// not use this file except in compliance with the License. You may obtain
5
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+package scram
8
+
9
+import (
10
+	"crypto/hmac"
11
+	"encoding/base64"
12
+	"errors"
13
+	"fmt"
14
+)
15
+
16
+type serverState int
17
+
18
+const (
19
+	serverFirst serverState = iota
20
+	serverFinal
21
+	serverDone
22
+)
23
+
24
+// ServerConversation implements the server-side of an authentication
25
+// conversation with a client.  A new conversation must be created for
26
+// each authentication attempt.
27
+type ServerConversation struct {
28
+	nonceGen     NonceGeneratorFcn
29
+	hashGen      HashGeneratorFcn
30
+	credentialCB CredentialLookup
31
+	state        serverState
32
+	credential   StoredCredentials
33
+	valid        bool
34
+	gs2Header    string
35
+	username     string
36
+	authzID      string
37
+	nonce        string
38
+	c1b          string
39
+	s1           string
40
+}
41
+
42
+// Step takes a string provided from a client and attempts to move the
43
+// authentication conversation forward.  It returns a string to be sent to the
44
+// client or an error if the client message is invalid.  Calling Step after a
45
+// conversation completes is also an error.
46
+func (sc *ServerConversation) Step(challenge string) (response string, err error) {
47
+	switch sc.state {
48
+	case serverFirst:
49
+		sc.state = serverFinal
50
+		response, err = sc.firstMsg(challenge)
51
+	case serverFinal:
52
+		sc.state = serverDone
53
+		response, err = sc.finalMsg(challenge)
54
+	default:
55
+		response, err = "", errors.New("Conversation already completed")
56
+	}
57
+	return
58
+}
59
+
60
+// Done returns true if the conversation is completed or has errored.
61
+func (sc *ServerConversation) Done() bool {
62
+	return sc.state == serverDone
63
+}
64
+
65
+// Valid returns true if the conversation successfully authenticated the
66
+// client.
67
+func (sc *ServerConversation) Valid() bool {
68
+	return sc.valid
69
+}
70
+
71
+// Username returns the client-provided username.  This is valid to call
72
+// if the first conversation Step() is successful.
73
+func (sc *ServerConversation) Username() string {
74
+	return sc.username
75
+}
76
+
77
+// AuthzID returns the (optional) client-provided authorization identity, if
78
+// any.  If one was not provided, it returns the empty string.  This is valid
79
+// to call if the first conversation Step() is successful.
80
+func (sc *ServerConversation) AuthzID() string {
81
+	return sc.authzID
82
+}
83
+
84
+func (sc *ServerConversation) firstMsg(c1 string) (string, error) {
85
+	msg, err := parseClientFirst(c1)
86
+	if err != nil {
87
+		sc.state = serverDone
88
+		return "", err
89
+	}
90
+
91
+	sc.gs2Header = msg.gs2Header
92
+	sc.username = msg.username
93
+	sc.authzID = msg.authzID
94
+
95
+	sc.credential, err = sc.credentialCB(msg.username)
96
+	if err != nil {
97
+		sc.state = serverDone
98
+		return "e=unknown-user", err
99
+	}
100
+
101
+	sc.nonce = msg.nonce + sc.nonceGen()
102
+	sc.c1b = msg.c1b
103
+	sc.s1 = fmt.Sprintf("r=%s,s=%s,i=%d",
104
+		sc.nonce,
105
+		base64.StdEncoding.EncodeToString([]byte(sc.credential.Salt)),
106
+		sc.credential.Iters,
107
+	)
108
+
109
+	return sc.s1, nil
110
+}
111
+
112
+// For errors, returns server error message as well as non-nil error.  Callers
113
+// can choose whether to send server error or not.
114
+func (sc *ServerConversation) finalMsg(c2 string) (string, error) {
115
+	msg, err := parseClientFinal(c2)
116
+	if err != nil {
117
+		return "", err
118
+	}
119
+
120
+	// Check channel binding matches what we expect; in this case, we expect
121
+	// just the gs2 header we received as we don't support channel binding
122
+	// with a data payload.  If we add binding, we need to independently
123
+	// compute the header to match here.
124
+	if string(msg.cbind) != sc.gs2Header {
125
+		return "e=channel-bindings-dont-match", fmt.Errorf("channel binding received '%s' doesn't match expected '%s'", msg.cbind, sc.gs2Header)
126
+	}
127
+
128
+	// Check nonce received matches what we sent
129
+	if msg.nonce != sc.nonce {
130
+		return "e=other-error", errors.New("nonce received did not match nonce sent")
131
+	}
132
+
133
+	// Create auth message
134
+	authMsg := sc.c1b + "," + sc.s1 + "," + msg.c2wop
135
+
136
+	// Retrieve ClientKey from proof and verify it
137
+	clientSignature := computeHMAC(sc.hashGen, sc.credential.StoredKey, []byte(authMsg))
138
+	clientKey := xorBytes([]byte(msg.proof), clientSignature)
139
+	storedKey := computeHash(sc.hashGen, clientKey)
140
+
141
+	// Compare with constant-time function
142
+	if !hmac.Equal(storedKey, sc.credential.StoredKey) {
143
+		return "e=invalid-proof", errors.New("challenge proof invalid")
144
+	}
145
+
146
+	sc.valid = true
147
+
148
+	// Compute and return server verifier
149
+	serverSignature := computeHMAC(sc.hashGen, sc.credential.ServerKey, []byte(authMsg))
150
+	return "v=" + base64.StdEncoding.EncodeToString(serverSignature), nil
151
+}

+ 6
- 0
vendor/modules.txt View File

@@ -73,6 +73,11 @@ github.com/tidwall/tinyqueue
73 73
 # github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208
74 74
 ## explicit
75 75
 github.com/toorop/go-dkim
76
+# github.com/xdg-go/pbkdf2 v1.0.0
77
+github.com/xdg-go/pbkdf2
78
+# github.com/xdg-go/scram v1.0.2 => github.com/ergochat/scram v1.0.2-ergo1
79
+## explicit
80
+github.com/xdg-go/scram
76 81
 # golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc
77 82
 ## explicit
78 83
 golang.org/x/crypto/bcrypt
@@ -107,3 +112,4 @@ golang.org/x/text/width
107 112
 ## explicit
108 113
 gopkg.in/yaml.v2
109 114
 # github.com/gorilla/websocket => github.com/ergochat/websocket v1.4.2-oragono1
115
+# github.com/xdg-go/scram => github.com/ergochat/scram v1.0.2-ergo1

Loading…
Cancel
Save