3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-15 00:19:29 +01:00

Merge pull request #1764 from slingamn/scram.3

implement SCRAM-SHA-256
This commit is contained in:
Shivaram Lingamneni 2021-08-01 22:00:14 -04:00 committed by GitHub
commit d8dc24dee8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1546 additions and 28 deletions

3
go.mod
View File

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

7
go.sum
View File

@ -12,6 +12,8 @@ github.com/ergochat/go-ident v0.0.0-20200511222032-830550b1d775 h1:QSJIdpr3HOzJD
github.com/ergochat/go-ident v0.0.0-20200511222032-830550b1d775/go.mod h1:d2qvgjD0TvGNSvUs+mZgX090RiJlrzUYW6vtANGOy3A=
github.com/ergochat/irc-go v0.0.0-20210617222258-256f1601d3ce h1:RfyjeynouKZjmnN8WGzCSrtuHGZ9dwfSYBq405FPoqs=
github.com/ergochat/irc-go v0.0.0-20210617222258-256f1601d3ce/go.mod h1:2vi7KNpIPWnReB5hmLpl92eMywQvuIeIIGdt/FQCph0=
github.com/ergochat/scram v1.0.2-ergo1 h1:2bYXiRFQH636pT0msOG39fmEYl4Eq+OuutcyDsCix/g=
github.com/ergochat/scram v1.0.2-ergo1/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
github.com/ergochat/websocket v1.4.2-oragono1 h1:plMUunFBM6UoSCIYCKKclTdy/TkkHfUslhOfJQzfueM=
github.com/ergochat/websocket v1.4.2-oragono1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
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
github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw=
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 h1:PM5hJF7HVfNWmCjMdEfbuOBNXSVF2cMFGgQTPdKCbwM=
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc=
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc h1:+q90ECDSAQirdykUN6sPEiBXBsp8Csjcca8Oy7bgLTA=
golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
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
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -5,6 +5,7 @@ package irc
import (
"bytes"
"crypto/rand"
"crypto/x509"
"encoding/json"
"fmt"
@ -16,6 +17,7 @@ import (
"unicode"
"github.com/ergochat/irc-go/ircutils"
"github.com/xdg-go/scram"
"github.com/ergochat/ergo/irc/connection_limits"
"github.com/ergochat/ergo/irc/email"
@ -517,8 +519,8 @@ func validatePassphrase(passphrase string) error {
}
// changes the password for an account
func (am *AccountManager) setPassword(account string, password string, hasPrivs bool) (err error) {
cfAccount, err := CasefoldName(account)
func (am *AccountManager) setPassword(accountName string, password string, hasPrivs bool) (err error) {
cfAccount, err := CasefoldName(accountName)
if err != nil {
return errAccountDoesNotExist
}
@ -1912,9 +1914,10 @@ func (am *AccountManager) Logout(client *Client) {
var (
// EnabledSaslMechanisms contains the SASL mechanisms that exist and that we support.
// This can be moved to some other data structure/place if we need to load/unload mechs later.
EnabledSaslMechanisms = map[string]func(*Server, *Client, string, []byte, *ResponseBuffer) bool{
"PLAIN": authPlainHandler,
"EXTERNAL": authExternalHandler,
EnabledSaslMechanisms = map[string]func(*Server, *Client, *Session, []byte, *ResponseBuffer) bool{
"PLAIN": authPlainHandler,
"EXTERNAL": authExternalHandler,
"SCRAM-SHA-256": authScramHandler,
}
)
@ -1933,6 +1936,12 @@ type AccountCredentials struct {
Version CredentialsVersion
PassphraseHash []byte
Certfps []string
SCRAMCreds struct {
Salt []byte
Iters int
StoredKey []byte
ServerKey []byte
}
}
func (ac *AccountCredentials) Empty() bool {
@ -1964,9 +1973,47 @@ func (ac *AccountCredentials) SetPassphrase(passphrase string, bcryptCost uint)
return errAccountBadPassphrase
}
// we can pass an empty account name because it won't actually be incorporated
// into the credentials; it's just a quirk of the xdg-go/scram API that the way
// to produce server credentials is to call NewClient* and then GetStoredCredentials
scramClient, err := scram.SHA256.NewClientUnprepped("", passphrase, "")
if err != nil {
return errAccountBadPassphrase
}
salt := make([]byte, 16)
rand.Read(salt)
// xdg-go/scram says: "Clients have a default minimum PBKDF2 iteration count of 4096."
minIters := 4096
scramCreds := scramClient.GetStoredCredentials(scram.KeyFactors{Salt: string(salt), Iters: minIters})
ac.SCRAMCreds.Salt = salt
ac.SCRAMCreds.Iters = minIters
ac.SCRAMCreds.StoredKey = scramCreds.StoredKey
ac.SCRAMCreds.ServerKey = scramCreds.ServerKey
return nil
}
func (am *AccountManager) NewScramConversation() *scram.ServerConversation {
server, _ := scram.SHA256.NewServer(am.lookupSCRAMCreds)
return server.NewConversation()
}
func (am *AccountManager) lookupSCRAMCreds(accountName string) (creds scram.StoredCredentials, err error) {
acct, err := am.LoadAccount(accountName)
if err != nil {
return
}
if acct.Credentials.SCRAMCreds.Iters == 0 {
err = errNoSCRAMCredentials
return
}
creds.Salt = string(acct.Credentials.SCRAMCreds.Salt)
creds.Iters = acct.Credentials.SCRAMCreds.Iters
creds.StoredKey = acct.Credentials.SCRAMCreds.StoredKey
creds.ServerKey = acct.Credentials.SCRAMCreds.ServerKey
return
}
func (ac *AccountCredentials) AddCertfp(certfp string) (err error) {
// XXX we require that certfp is already normalized (rather than normalize here
// and pass back the normalized version as an additional return parameter);

View File

@ -20,6 +20,7 @@ import (
"github.com/ergochat/irc-go/ircfmt"
"github.com/ergochat/irc-go/ircmsg"
"github.com/ergochat/irc-go/ircreader"
"github.com/xdg-go/scram"
"github.com/ergochat/ergo/irc/caps"
"github.com/ergochat/ergo/irc/connection_limits"
@ -116,6 +117,7 @@ type Client struct {
type saslStatus struct {
mechanism string
value string
scramConv *scram.ServerConversation
}
func (s *saslStatus) Clear() {

View File

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

View File

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

View File

@ -166,6 +166,12 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.Message, rb
// start new sasl session
if session.sasl.mechanism == "" {
throttled, remainingTime := client.loginThrottle.Touch()
if throttled {
rb.Add(nil, server.name, ERR_SASLFAIL, client.Nick(), fmt.Sprintf(client.t("Please wait at least %v and try again"), remainingTime))
return false
}
mechanism := strings.ToUpper(msg.Params[0])
_, mechanismIsEnabled := EnabledSaslMechanisms[mechanism]
@ -211,6 +217,7 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.Message, rb
var err error
if session.sasl.value != "+" {
data, err = base64.StdEncoding.DecodeString(session.sasl.value)
session.sasl.value = ""
if err != nil {
rb.Add(nil, server.name, ERR_SASLFAIL, details.nick, client.t("SASL authentication failed: Invalid b64 encoding"))
session.sasl.Clear()
@ -219,24 +226,14 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.Message, rb
}
// call actual handler
handler, handlerExists := EnabledSaslMechanisms[session.sasl.mechanism]
// like 100% not required, but it's good to be safe I guess
if !handlerExists {
rb.Add(nil, server.name, ERR_SASLFAIL, details.nick, client.t("SASL authentication failed"))
session.sasl.Clear()
return false
}
// let the SASL handler do its thing
exiting := handler(server, client, session.sasl.mechanism, data, rb)
session.sasl.Clear()
return exiting
handler := EnabledSaslMechanisms[session.sasl.mechanism]
return handler(server, client, session, data, rb)
}
// AUTHENTICATE PLAIN
func authPlainHandler(server *Server, client *Client, mechanism string, value []byte, rb *ResponseBuffer) bool {
func authPlainHandler(server *Server, client *Client, session *Session, value []byte, rb *ResponseBuffer) bool {
defer session.sasl.Clear()
splitValue := bytes.Split(value, []byte{'\000'})
// PLAIN has separate "authorization ID" (which user you want to become)
@ -256,12 +253,6 @@ func authPlainHandler(server *Server, client *Client, mechanism string, value []
return false
}
throttled, remainingTime := client.loginThrottle.Touch()
if throttled {
rb.Add(nil, server.name, ERR_SASLFAIL, client.Nick(), fmt.Sprintf(client.t("Please wait at least %v and try again"), remainingTime))
return false
}
// see #843: strip the device ID for the benefit of clients that don't
// distinguish user/ident from account name
if strudelIndex := strings.IndexByte(authcid, '@'); strudelIndex != -1 {
@ -301,7 +292,9 @@ func authErrorToMessage(server *Server, err error) (msg string) {
}
// AUTHENTICATE EXTERNAL
func authExternalHandler(server *Server, client *Client, mechanism string, value []byte, rb *ResponseBuffer) bool {
func authExternalHandler(server *Server, client *Client, session *Session, value []byte, rb *ResponseBuffer) bool {
defer session.sasl.Clear()
if rb.session.certfp == "" {
rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, client.t("SASL authentication failed, you are not connecting with a certificate"))
return false
@ -343,6 +336,58 @@ func authExternalHandler(server *Server, client *Client, mechanism string, value
return false
}
// AUTHENTICATE SCRAM-SHA-256
func authScramHandler(server *Server, client *Client, session *Session, value []byte, rb *ResponseBuffer) bool {
continueAuth := true
defer func() {
if !continueAuth {
session.sasl.Clear()
}
}()
// first message? if so, initialize the SCRAM conversation
if session.sasl.scramConv == nil {
session.sasl.scramConv = server.accounts.NewScramConversation()
}
// wait for a final AUTHENTICATE + from the client to conclude authentication
if session.sasl.scramConv.Done() {
continueAuth = false
if session.sasl.scramConv.Valid() {
accountName := session.sasl.scramConv.Username()
authzid := session.sasl.scramConv.AuthzID()
if authzid != "" && authzid != accountName {
rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, client.t("SASL authentication failed: authcid and authzid should be the same"))
return false
}
account, err := server.accounts.LoadAccount(accountName)
if err == nil {
server.accounts.Login(client, account)
if fixupNickEqualsAccount(client, rb, server.Config(), "") {
sendSuccessfulAccountAuth(nil, client, rb, true)
}
} else {
server.logger.Error("internal", "SCRAM succeeded but couldn't load account", accountName, err.Error())
rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, client.t("SASL authentication failed"))
}
} else {
rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, client.t("SASL authentication failed"))
}
return false
}
response, err := session.sasl.scramConv.Step(string(value))
if err == nil {
rb.Add(nil, server.name, "AUTHENTICATE", base64.StdEncoding.EncodeToString([]byte(response)))
} else {
continueAuth = false
rb.Add(nil, server.name, ERR_SASLFAIL, client.Nick(), err.Error())
return false
}
return false
}
// AWAY [<message>]
func awayHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
var isAway bool

12
vendor/github.com/xdg-go/pbkdf2/.gitignore generated vendored Normal file
View File

@ -0,0 +1,12 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out

175
vendor/github.com/xdg-go/pbkdf2/LICENSE generated vendored Normal file
View File

@ -0,0 +1,175 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

17
vendor/github.com/xdg-go/pbkdf2/README.md generated vendored Normal file
View File

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

3
vendor/github.com/xdg-go/pbkdf2/go.mod generated vendored Normal file
View File

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

76
vendor/github.com/xdg-go/pbkdf2/pbkdf2.go generated vendored Normal file
View File

@ -0,0 +1,76 @@
// Copyright 2021 by David A. Golden. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Package pbkdf2 implements password-based key derivation using the PBKDF2
// algorithm described in RFC 2898 and RFC 8018.
//
// It provides a drop-in replacement for `golang.org/x/crypto/pbkdf2`, with
// the following benefits:
//
// - Released as a module with semantic versioning
//
// - Does not pull in dependencies for unrelated `x/crypto/*` packages
//
// - Supports Go 1.9+
//
// See https://tools.ietf.org/html/rfc8018#section-4 for security considerations
// in the selection of a salt and iteration count.
package pbkdf2
import (
"crypto/hmac"
"encoding/binary"
"hash"
)
// Key generates a derived key from a password using the PBKDF2 algorithm. The
// inputs include salt bytes, the iteration count, desired key length, and a
// constructor for a hashing function. For example, for a 32-byte key using
// SHA-256:
//
// key := Key([]byte("trustNo1"), salt, 10000, 32, sha256.New)
func Key(password, salt []byte, iterCount, keyLen int, h func() hash.Hash) []byte {
prf := hmac.New(h, password)
hLen := prf.Size()
numBlocks := keyLen / hLen
// Get an extra block if keyLen is not an even number of hLen blocks.
if keyLen%hLen > 0 {
numBlocks++
}
Ti := make([]byte, hLen)
Uj := make([]byte, hLen)
dk := make([]byte, 0, hLen*numBlocks)
buf := make([]byte, 4)
for i := uint32(1); i <= uint32(numBlocks); i++ {
// Initialize Uj for j == 1 from salt and block index.
// Initialize Ti = U1.
binary.BigEndian.PutUint32(buf, i)
prf.Reset()
prf.Write(salt)
prf.Write(buf)
Uj = Uj[:0]
Uj = prf.Sum(Uj)
// Ti = U1 ^ U2 ^ ... ^ Ux
copy(Ti, Uj)
for j := 2; j <= iterCount; j++ {
prf.Reset()
prf.Write(Uj)
Uj = Uj[:0]
Uj = prf.Sum(Uj)
for k := range Uj {
Ti[k] ^= Uj[k]
}
}
// DK = concat(T1, T2, ... Tn)
dk = append(dk, Ti...)
}
return dk[0:keyLen]
}

0
vendor/github.com/xdg-go/scram/.gitignore generated vendored Normal file
View File

14
vendor/github.com/xdg-go/scram/CHANGELOG.md generated vendored Normal file
View File

@ -0,0 +1,14 @@
# CHANGELOG
## v1.0.2 - 2021-03-28
- Switch PBKDF2 dependency to github.com/xdg-go/pbkdf2 to
minimize transitive dependencies and support Go 1.9+.
## v1.0.1 - 2021-03-27
- Bump stringprep dependency to v1.0.2 for Go 1.11 support.
## v1.0.0 - 2021-03-27
- First release as a Go module

175
vendor/github.com/xdg-go/scram/LICENSE generated vendored Normal file
View File

@ -0,0 +1,175 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

72
vendor/github.com/xdg-go/scram/README.md generated vendored Normal file
View File

@ -0,0 +1,72 @@
[![Go Reference](https://pkg.go.dev/badge/github.com/xdg-go/scram.svg)](https://pkg.go.dev/github.com/xdg-go/scram)
[![Go Report Card](https://goreportcard.com/badge/github.com/xdg-go/scram)](https://goreportcard.com/report/github.com/xdg-go/scram)
[![Github Actions](https://github.com/xdg-go/scram/actions/workflows/test.yml/badge.svg)](https://github.com/xdg-go/scram/actions/workflows/test.yml)
# scram  Go implementation of RFC-5802
## Description
Package scram provides client and server implementations of the Salted
Challenge Response Authentication Mechanism (SCRAM) described in
[RFC-5802](https://tools.ietf.org/html/rfc5802) and
[RFC-7677](https://tools.ietf.org/html/rfc7677).
It includes both client and server side support.
Channel binding and extensions are not (yet) supported.
## Examples
### Client side
package main
import "github.com/xdg-go/scram"
func main() {
// Get Client with username, password and (optional) authorization ID.
clientSHA1, err := scram.SHA1.NewClient("mulder", "trustno1", "")
if err != nil {
panic(err)
}
// Prepare the authentication conversation. Use the empty string as the
// initial server message argument to start the conversation.
conv := clientSHA1.NewConversation()
var serverMsg string
// Get the first message, send it and read the response.
firstMsg, err := conv.Step(serverMsg)
if err != nil {
panic(err)
}
serverMsg = sendClientMsg(firstMsg)
// Get the second message, send it, and read the response.
secondMsg, err := conv.Step(serverMsg)
if err != nil {
panic(err)
}
serverMsg = sendClientMsg(secondMsg)
// Validate the server's final message. We have no further message to
// send so ignore that return value.
_, err = conv.Step(serverMsg)
if err != nil {
panic(err)
}
return
}
func sendClientMsg(s string) string {
// A real implementation would send this to a server and read a reply.
return ""
}
## Copyright and License
Copyright 2018 by David A. Golden. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"). You may
obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

130
vendor/github.com/xdg-go/scram/client.go generated vendored Normal file
View File

@ -0,0 +1,130 @@
// Copyright 2018 by David A. Golden. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package scram
import (
"sync"
"github.com/xdg-go/pbkdf2"
)
// Client implements the client side of SCRAM authentication. It holds
// configuration values needed to initialize new client-side conversations for
// a specific username, password and authorization ID tuple. Client caches
// the computationally-expensive parts of a SCRAM conversation as described in
// RFC-5802. If repeated authentication conversations may be required for a
// user (e.g. disconnect/reconnect), the user's Client should be preserved.
//
// For security reasons, Clients have a default minimum PBKDF2 iteration count
// of 4096. If a server requests a smaller iteration count, an authentication
// conversation will error.
//
// A Client can also be used by a server application to construct the hashed
// authentication values to be stored for a new user. See StoredCredentials()
// for more.
type Client struct {
sync.RWMutex
username string
password string
authzID string
minIters int
nonceGen NonceGeneratorFcn
hashGen HashGeneratorFcn
cache map[KeyFactors]derivedKeys
}
func newClient(username, password, authzID string, fcn HashGeneratorFcn) *Client {
return &Client{
username: username,
password: password,
authzID: authzID,
minIters: 4096,
nonceGen: defaultNonceGenerator,
hashGen: fcn,
cache: make(map[KeyFactors]derivedKeys),
}
}
// WithMinIterations changes minimum required PBKDF2 iteration count.
func (c *Client) WithMinIterations(n int) *Client {
c.Lock()
defer c.Unlock()
c.minIters = n
return c
}
// WithNonceGenerator replaces the default nonce generator (base64 encoding of
// 24 bytes from crypto/rand) with a custom generator. This is provided for
// testing or for users with custom nonce requirements.
func (c *Client) WithNonceGenerator(ng NonceGeneratorFcn) *Client {
c.Lock()
defer c.Unlock()
c.nonceGen = ng
return c
}
// NewConversation constructs a client-side authentication conversation.
// Conversations cannot be reused, so this must be called for each new
// authentication attempt.
func (c *Client) NewConversation() *ClientConversation {
c.RLock()
defer c.RUnlock()
return &ClientConversation{
client: c,
nonceGen: c.nonceGen,
hashGen: c.hashGen,
minIters: c.minIters,
}
}
func (c *Client) getDerivedKeys(kf KeyFactors) derivedKeys {
dk, ok := c.getCache(kf)
if !ok {
dk = c.computeKeys(kf)
c.setCache(kf, dk)
}
return dk
}
// GetStoredCredentials takes a salt and iteration count structure and
// provides the values that must be stored by a server to authentication a
// user. These values are what the Server credential lookup function must
// return for a given username.
func (c *Client) GetStoredCredentials(kf KeyFactors) StoredCredentials {
dk := c.getDerivedKeys(kf)
return StoredCredentials{
KeyFactors: kf,
StoredKey: dk.StoredKey,
ServerKey: dk.ServerKey,
}
}
func (c *Client) computeKeys(kf KeyFactors) derivedKeys {
h := c.hashGen()
saltedPassword := pbkdf2.Key([]byte(c.password), []byte(kf.Salt), kf.Iters, h.Size(), c.hashGen)
clientKey := computeHMAC(c.hashGen, saltedPassword, []byte("Client Key"))
return derivedKeys{
ClientKey: clientKey,
StoredKey: computeHash(c.hashGen, clientKey),
ServerKey: computeHMAC(c.hashGen, saltedPassword, []byte("Server Key")),
}
}
func (c *Client) getCache(kf KeyFactors) (derivedKeys, bool) {
c.RLock()
defer c.RUnlock()
dk, ok := c.cache[kf]
return dk, ok
}
func (c *Client) setCache(kf KeyFactors, dk derivedKeys) {
c.Lock()
defer c.Unlock()
c.cache[kf] = dk
return
}

149
vendor/github.com/xdg-go/scram/client_conv.go generated vendored Normal file
View File

@ -0,0 +1,149 @@
// Copyright 2018 by David A. Golden. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package scram
import (
"crypto/hmac"
"encoding/base64"
"errors"
"fmt"
"strings"
)
type clientState int
const (
clientStarting clientState = iota
clientFirst
clientFinal
clientDone
)
// ClientConversation implements the client-side of an authentication
// conversation with a server. A new conversation must be created for
// each authentication attempt.
type ClientConversation struct {
client *Client
nonceGen NonceGeneratorFcn
hashGen HashGeneratorFcn
minIters int
state clientState
valid bool
gs2 string
nonce string
c1b string
serveSig []byte
}
// Step takes a string provided from a server (or just an empty string for the
// very first conversation step) and attempts to move the authentication
// conversation forward. It returns a string to be sent to the server or an
// error if the server message is invalid. Calling Step after a conversation
// completes is also an error.
func (cc *ClientConversation) Step(challenge string) (response string, err error) {
switch cc.state {
case clientStarting:
cc.state = clientFirst
response, err = cc.firstMsg()
case clientFirst:
cc.state = clientFinal
response, err = cc.finalMsg(challenge)
case clientFinal:
cc.state = clientDone
response, err = cc.validateServer(challenge)
default:
response, err = "", errors.New("Conversation already completed")
}
return
}
// Done returns true if the conversation is completed or has errored.
func (cc *ClientConversation) Done() bool {
return cc.state == clientDone
}
// Valid returns true if the conversation successfully authenticated with the
// server, including counter-validation that the server actually has the
// user's stored credentials.
func (cc *ClientConversation) Valid() bool {
return cc.valid
}
func (cc *ClientConversation) firstMsg() (string, error) {
// Values are cached for use in final message parameters
cc.gs2 = cc.gs2Header()
cc.nonce = cc.client.nonceGen()
cc.c1b = fmt.Sprintf("n=%s,r=%s", encodeName(cc.client.username), cc.nonce)
return cc.gs2 + cc.c1b, nil
}
func (cc *ClientConversation) finalMsg(s1 string) (string, error) {
msg, err := parseServerFirst(s1)
if err != nil {
return "", err
}
// Check nonce prefix and update
if !strings.HasPrefix(msg.nonce, cc.nonce) {
return "", errors.New("server nonce did not extend client nonce")
}
cc.nonce = msg.nonce
// Check iteration count vs minimum
if msg.iters < cc.minIters {
return "", fmt.Errorf("server requested too few iterations (%d)", msg.iters)
}
// Create client-final-message-without-proof
c2wop := fmt.Sprintf(
"c=%s,r=%s",
base64.StdEncoding.EncodeToString([]byte(cc.gs2)),
cc.nonce,
)
// Create auth message
authMsg := cc.c1b + "," + s1 + "," + c2wop
// Get derived keys from client cache
dk := cc.client.getDerivedKeys(KeyFactors{Salt: string(msg.salt), Iters: msg.iters})
// Create proof as clientkey XOR clientsignature
clientSignature := computeHMAC(cc.hashGen, dk.StoredKey, []byte(authMsg))
clientProof := xorBytes(dk.ClientKey, clientSignature)
proof := base64.StdEncoding.EncodeToString(clientProof)
// Cache ServerSignature for later validation
cc.serveSig = computeHMAC(cc.hashGen, dk.ServerKey, []byte(authMsg))
return fmt.Sprintf("%s,p=%s", c2wop, proof), nil
}
func (cc *ClientConversation) validateServer(s2 string) (string, error) {
msg, err := parseServerFinal(s2)
if err != nil {
return "", err
}
if len(msg.err) > 0 {
return "", fmt.Errorf("server error: %s", msg.err)
}
if !hmac.Equal(msg.verifier, cc.serveSig) {
return "", errors.New("server validation failed")
}
cc.valid = true
return "", nil
}
func (cc *ClientConversation) gs2Header() string {
if cc.client.authzID == "" {
return "n,,"
}
return fmt.Sprintf("n,%s,", encodeName(cc.client.authzID))
}

97
vendor/github.com/xdg-go/scram/common.go generated vendored Normal file
View File

@ -0,0 +1,97 @@
// Copyright 2018 by David A. Golden. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package scram
import (
"crypto/hmac"
"crypto/rand"
"encoding/base64"
"strings"
)
// NonceGeneratorFcn defines a function that returns a string of high-quality
// random printable ASCII characters EXCLUDING the comma (',') character. The
// default nonce generator provides Base64 encoding of 24 bytes from
// crypto/rand.
type NonceGeneratorFcn func() string
// derivedKeys collects the three cryptographically derived values
// into one struct for caching.
type derivedKeys struct {
ClientKey []byte
StoredKey []byte
ServerKey []byte
}
// KeyFactors represent the two server-provided factors needed to compute
// client credentials for authentication. Salt is decoded bytes (i.e. not
// base64), but in string form so that KeyFactors can be used as a map key for
// cached credentials.
type KeyFactors struct {
Salt string
Iters int
}
// StoredCredentials are the values that a server must store for a given
// username to allow authentication. They include the salt and iteration
// count, plus the derived values to authenticate a client and for the server
// to authenticate itself back to the client.
//
// NOTE: these are specific to a given hash function. To allow a user to
// authenticate with either SCRAM-SHA-1 or SCRAM-SHA-256, two sets of
// StoredCredentials must be created and stored, one for each hash function.
type StoredCredentials struct {
KeyFactors
StoredKey []byte
ServerKey []byte
}
// CredentialLookup is a callback to provide StoredCredentials for a given
// username. This is used to configure Server objects.
//
// NOTE: these are specific to a given hash function. The callback provided
// to a Server with a given hash function must provide the corresponding
// StoredCredentials.
type CredentialLookup func(string) (StoredCredentials, error)
func defaultNonceGenerator() string {
raw := make([]byte, 24)
nonce := make([]byte, base64.StdEncoding.EncodedLen(len(raw)))
rand.Read(raw)
base64.StdEncoding.Encode(nonce, raw)
return string(nonce)
}
func encodeName(s string) string {
return strings.Replace(strings.Replace(s, "=", "=3D", -1), ",", "=2C", -1)
}
func decodeName(s string) (string, error) {
// TODO Check for = not followed by 2C or 3D
return strings.Replace(strings.Replace(s, "=2C", ",", -1), "=3D", "=", -1), nil
}
func computeHash(hg HashGeneratorFcn, b []byte) []byte {
h := hg()
h.Write(b)
return h.Sum(nil)
}
func computeHMAC(hg HashGeneratorFcn, key, data []byte) []byte {
mac := hmac.New(hg, key)
mac.Write(data)
return mac.Sum(nil)
}
func xorBytes(a, b []byte) []byte {
// TODO check a & b are same length, or just xor to smallest
xor := make([]byte, len(a))
for i := range a {
xor[i] = a[i] ^ b[i]
}
return xor
}

24
vendor/github.com/xdg-go/scram/doc.go generated vendored Normal file
View File

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

8
vendor/github.com/xdg-go/scram/go.mod generated vendored Normal file
View File

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

7
vendor/github.com/xdg-go/scram/go.sum generated vendored Normal file
View File

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

205
vendor/github.com/xdg-go/scram/parse.go generated vendored Normal file
View File

@ -0,0 +1,205 @@
// Copyright 2018 by David A. Golden. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package scram
import (
"encoding/base64"
"errors"
"fmt"
"strconv"
"strings"
)
type c1Msg struct {
gs2Header string
authzID string
username string
nonce string
c1b string
}
type c2Msg struct {
cbind []byte
nonce string
proof []byte
c2wop string
}
type s1Msg struct {
nonce string
salt []byte
iters int
}
type s2Msg struct {
verifier []byte
err string
}
func parseField(s, k string) (string, error) {
t := strings.TrimPrefix(s, k+"=")
if t == s {
return "", fmt.Errorf("error parsing '%s' for field '%s'", s, k)
}
return t, nil
}
func parseGS2Flag(s string) (string, error) {
if s[0] == 'p' {
return "", fmt.Errorf("channel binding requested but not supported")
}
if s == "n" || s == "y" {
return s, nil
}
return "", fmt.Errorf("error parsing '%s' for gs2 flag", s)
}
func parseFieldBase64(s, k string) ([]byte, error) {
raw, err := parseField(s, k)
if err != nil {
return nil, err
}
dec, err := base64.StdEncoding.DecodeString(raw)
if err != nil {
return nil, err
}
return dec, nil
}
func parseFieldInt(s, k string) (int, error) {
raw, err := parseField(s, k)
if err != nil {
return 0, err
}
num, err := strconv.Atoi(raw)
if err != nil {
return 0, fmt.Errorf("error parsing field '%s': %v", k, err)
}
return num, nil
}
func parseClientFirst(c1 string) (msg c1Msg, err error) {
fields := strings.Split(c1, ",")
if len(fields) < 4 {
err = errors.New("not enough fields in first server message")
return
}
gs2flag, err := parseGS2Flag(fields[0])
if err != nil {
return
}
// 'a' field is optional
if len(fields[1]) > 0 {
msg.authzID, err = parseField(fields[1], "a")
if err != nil {
return
}
}
// Recombine and save the gs2 header
msg.gs2Header = gs2flag + "," + msg.authzID + ","
// Check for unsupported extensions field "m".
if strings.HasPrefix(fields[2], "m=") {
err = errors.New("SCRAM message extensions are not supported")
return
}
msg.username, err = parseField(fields[2], "n")
if err != nil {
return
}
msg.nonce, err = parseField(fields[3], "r")
if err != nil {
return
}
msg.c1b = strings.Join(fields[2:], ",")
return
}
func parseClientFinal(c2 string) (msg c2Msg, err error) {
fields := strings.Split(c2, ",")
if len(fields) < 3 {
err = errors.New("not enough fields in first server message")
return
}
msg.cbind, err = parseFieldBase64(fields[0], "c")
if err != nil {
return
}
msg.nonce, err = parseField(fields[1], "r")
if err != nil {
return
}
// Extension fields may come between nonce and proof, so we
// grab the *last* fields as proof.
msg.proof, err = parseFieldBase64(fields[len(fields)-1], "p")
if err != nil {
return
}
msg.c2wop = c2[:strings.LastIndex(c2, ",")]
return
}
func parseServerFirst(s1 string) (msg s1Msg, err error) {
// Check for unsupported extensions field "m".
if strings.HasPrefix(s1, "m=") {
err = errors.New("SCRAM message extensions are not supported")
return
}
fields := strings.Split(s1, ",")
if len(fields) < 3 {
err = errors.New("not enough fields in first server message")
return
}
msg.nonce, err = parseField(fields[0], "r")
if err != nil {
return
}
msg.salt, err = parseFieldBase64(fields[1], "s")
if err != nil {
return
}
msg.iters, err = parseFieldInt(fields[2], "i")
return
}
func parseServerFinal(s2 string) (msg s2Msg, err error) {
fields := strings.Split(s2, ",")
msg.verifier, err = parseFieldBase64(fields[0], "v")
if err == nil {
return
}
msg.err, err = parseField(fields[0], "e")
return
}

42
vendor/github.com/xdg-go/scram/scram.go generated vendored Normal file
View File

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

50
vendor/github.com/xdg-go/scram/server.go generated vendored Normal file
View File

@ -0,0 +1,50 @@
// Copyright 2018 by David A. Golden. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package scram
import "sync"
// Server implements the server side of SCRAM authentication. It holds
// configuration values needed to initialize new server-side conversations.
// Generally, this can be persistent within an application.
type Server struct {
sync.RWMutex
credentialCB CredentialLookup
nonceGen NonceGeneratorFcn
hashGen HashGeneratorFcn
}
func newServer(cl CredentialLookup, fcn HashGeneratorFcn) (*Server, error) {
return &Server{
credentialCB: cl,
nonceGen: defaultNonceGenerator,
hashGen: fcn,
}, nil
}
// WithNonceGenerator replaces the default nonce generator (base64 encoding of
// 24 bytes from crypto/rand) with a custom generator. This is provided for
// testing or for users with custom nonce requirements.
func (s *Server) WithNonceGenerator(ng NonceGeneratorFcn) *Server {
s.Lock()
defer s.Unlock()
s.nonceGen = ng
return s
}
// NewConversation constructs a server-side authentication conversation.
// Conversations cannot be reused, so this must be called for each new
// authentication attempt.
func (s *Server) NewConversation() *ServerConversation {
s.RLock()
defer s.RUnlock()
return &ServerConversation{
nonceGen: s.nonceGen,
hashGen: s.hashGen,
credentialCB: s.credentialCB,
}
}

151
vendor/github.com/xdg-go/scram/server_conv.go generated vendored Normal file
View File

@ -0,0 +1,151 @@
// Copyright 2018 by David A. Golden. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package scram
import (
"crypto/hmac"
"encoding/base64"
"errors"
"fmt"
)
type serverState int
const (
serverFirst serverState = iota
serverFinal
serverDone
)
// ServerConversation implements the server-side of an authentication
// conversation with a client. A new conversation must be created for
// each authentication attempt.
type ServerConversation struct {
nonceGen NonceGeneratorFcn
hashGen HashGeneratorFcn
credentialCB CredentialLookup
state serverState
credential StoredCredentials
valid bool
gs2Header string
username string
authzID string
nonce string
c1b string
s1 string
}
// Step takes a string provided from a client and attempts to move the
// authentication conversation forward. It returns a string to be sent to the
// client or an error if the client message is invalid. Calling Step after a
// conversation completes is also an error.
func (sc *ServerConversation) Step(challenge string) (response string, err error) {
switch sc.state {
case serverFirst:
sc.state = serverFinal
response, err = sc.firstMsg(challenge)
case serverFinal:
sc.state = serverDone
response, err = sc.finalMsg(challenge)
default:
response, err = "", errors.New("Conversation already completed")
}
return
}
// Done returns true if the conversation is completed or has errored.
func (sc *ServerConversation) Done() bool {
return sc.state == serverDone
}
// Valid returns true if the conversation successfully authenticated the
// client.
func (sc *ServerConversation) Valid() bool {
return sc.valid
}
// Username returns the client-provided username. This is valid to call
// if the first conversation Step() is successful.
func (sc *ServerConversation) Username() string {
return sc.username
}
// AuthzID returns the (optional) client-provided authorization identity, if
// any. If one was not provided, it returns the empty string. This is valid
// to call if the first conversation Step() is successful.
func (sc *ServerConversation) AuthzID() string {
return sc.authzID
}
func (sc *ServerConversation) firstMsg(c1 string) (string, error) {
msg, err := parseClientFirst(c1)
if err != nil {
sc.state = serverDone
return "", err
}
sc.gs2Header = msg.gs2Header
sc.username = msg.username
sc.authzID = msg.authzID
sc.credential, err = sc.credentialCB(msg.username)
if err != nil {
sc.state = serverDone
return "e=unknown-user", err
}
sc.nonce = msg.nonce + sc.nonceGen()
sc.c1b = msg.c1b
sc.s1 = fmt.Sprintf("r=%s,s=%s,i=%d",
sc.nonce,
base64.StdEncoding.EncodeToString([]byte(sc.credential.Salt)),
sc.credential.Iters,
)
return sc.s1, nil
}
// For errors, returns server error message as well as non-nil error. Callers
// can choose whether to send server error or not.
func (sc *ServerConversation) finalMsg(c2 string) (string, error) {
msg, err := parseClientFinal(c2)
if err != nil {
return "", err
}
// Check channel binding matches what we expect; in this case, we expect
// just the gs2 header we received as we don't support channel binding
// with a data payload. If we add binding, we need to independently
// compute the header to match here.
if string(msg.cbind) != sc.gs2Header {
return "e=channel-bindings-dont-match", fmt.Errorf("channel binding received '%s' doesn't match expected '%s'", msg.cbind, sc.gs2Header)
}
// Check nonce received matches what we sent
if msg.nonce != sc.nonce {
return "e=other-error", errors.New("nonce received did not match nonce sent")
}
// Create auth message
authMsg := sc.c1b + "," + sc.s1 + "," + msg.c2wop
// Retrieve ClientKey from proof and verify it
clientSignature := computeHMAC(sc.hashGen, sc.credential.StoredKey, []byte(authMsg))
clientKey := xorBytes([]byte(msg.proof), clientSignature)
storedKey := computeHash(sc.hashGen, clientKey)
// Compare with constant-time function
if !hmac.Equal(storedKey, sc.credential.StoredKey) {
return "e=invalid-proof", errors.New("challenge proof invalid")
}
sc.valid = true
// Compute and return server verifier
serverSignature := computeHMAC(sc.hashGen, sc.credential.ServerKey, []byte(authMsg))
return "v=" + base64.StdEncoding.EncodeToString(serverSignature), nil
}

6
vendor/modules.txt vendored
View File

@ -73,6 +73,11 @@ github.com/tidwall/tinyqueue
# github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208
## explicit
github.com/toorop/go-dkim
# github.com/xdg-go/pbkdf2 v1.0.0
github.com/xdg-go/pbkdf2
# github.com/xdg-go/scram v1.0.2 => github.com/ergochat/scram v1.0.2-ergo1
## explicit
github.com/xdg-go/scram
# golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc
## explicit
golang.org/x/crypto/bcrypt
@ -107,3 +112,4 @@ golang.org/x/text/width
## explicit
gopkg.in/yaml.v2
# github.com/gorilla/websocket => github.com/ergochat/websocket v1.4.2-oragono1
# github.com/xdg-go/scram => github.com/ergochat/scram v1.0.2-ergo1