Implement SCRAM-SHA-256

This commit is contained in:
Ken-Håvard Lieng 2020-06-04 02:28:41 +02:00
parent ead3b37cf9
commit 876d9ebdd0
36 changed files with 5089 additions and 72 deletions

View File

@ -64,12 +64,6 @@ export default function handleSocket({
topic({ server, channel, topic, nick }) {
if (nick) {
dispatch(addEvent(server, channel, 'topic', nick, topic));
/* if (topic) {
dispatch(inform(`${nick} changed the topic to:`, server, channel));
dispatch(print(topic, server, channel));
} else {
dispatch(inform(`${nick} cleared the topic`, server, channel));
} */
}
},

5
go.mod
View File

@ -40,8 +40,11 @@ require (
github.com/tdewolff/minify/v2 v2.7.4
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect
github.com/tinylib/msgp v1.1.2 // indirect
github.com/xdg-go/scram v0.0.0-20180814205039-7eeb5667e42c
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c // indirect
github.com/xdg/stringprep v1.0.0 // indirect
go.etcd.io/bbolt v1.3.4
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 // indirect
golang.org/x/crypto v0.0.0-20200602180216-279210d13fed // indirect
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2
golang.org/x/sys v0.0.0-20200523222454-059865788121 // indirect
gopkg.in/ini.v1 v1.56.0 // indirect

10
go.sum
View File

@ -487,6 +487,12 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX
github.com/vultr/govultr v0.1.4/go.mod h1:9H008Uxr/C4vFNGLqKx232C206GL0PBHzOP0809bGNA=
github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc=
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
github.com/xdg-go/scram v0.0.0-20180814205039-7eeb5667e42c h1:Wm21TPasVdeOUTg1m/uNkRdMuvI+jIeYfTIwq98Z2V0=
github.com/xdg-go/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:FV1RpvYFmF8wnKtr3ArzkC0b+tAySCbw8eP7QSIvLKM=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0=
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
@ -521,8 +527,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200602180216-279210d13fed h1:g4KENRiCMEx58Q7/ecwfT0N2o8z35Fnbsjig/Alf2T4=
golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=

View File

@ -25,12 +25,32 @@ func (c *Client) HasCapability(name string, values ...string) bool {
return false
}
var clientWantedCaps = []string{}
var clientWantedCaps = []string{"cap-notify"}
func (c *Client) writeCAP() {
func (c *Client) beginCAP() {
c.write("CAP LS 302")
}
func (c *Client) beginSASL() bool {
if c.negotiating {
for i, mech := range c.saslMechanisms {
if c.HasCapability("sasl", mech.Name()) {
c.currentSASLIndex = i
c.authenticate(mech.Name())
return true
}
}
}
return false
}
func (c *Client) finishCAP() {
if c.negotiating {
c.negotiating = false
c.write("CAP END")
}
}
func (c *Client) handleCAP(msg *Message) {
if len(msg.Params) < 3 {
c.write("CAP END")
@ -39,9 +59,6 @@ func (c *Client) handleCAP(msg *Message) {
caps := parseCaps(msg.LastParam())
c.lock.Lock()
defer c.lock.Unlock()
switch msg.Params[1] {
case "LS":
for cap, values := range caps {
@ -58,6 +75,8 @@ func (c *Client) handleCAP(msg *Message) {
return
}
c.negotiating = true
reqCaps := []string{}
for cap := range c.requestedCapabilities {
reqCaps = append(reqCaps, cap)
@ -67,19 +86,17 @@ func (c *Client) handleCAP(msg *Message) {
}
case "ACK":
c.lock.Lock()
for cap := range caps {
if v, ok := c.requestedCapabilities[cap]; ok {
c.enabledCapabilities[cap] = v
delete(c.requestedCapabilities, cap)
}
}
c.lock.Unlock()
if len(c.requestedCapabilities) == 0 {
if c.Config.SASL != nil && c.HasCapability("sasl", c.Config.SASL.Name()) {
c.write("AUTHENTICATE " + c.Config.SASL.Name())
} else {
c.write("CAP END")
}
if len(c.requestedCapabilities) == 0 && !c.beginSASL() {
c.finishCAP()
}
case "NAK":
@ -87,8 +104,8 @@ func (c *Client) handleCAP(msg *Message) {
delete(c.requestedCapabilities, cap)
}
if len(c.requestedCapabilities) == 0 {
c.write("CAP END")
if len(c.requestedCapabilities) == 0 && !c.beginSASL() {
c.finishCAP()
}
case "NEW":
@ -107,9 +124,11 @@ func (c *Client) handleCAP(msg *Message) {
}
case "DEL":
c.lock.Lock()
for cap := range caps {
delete(c.enabledCapabilities, cap)
}
c.lock.Unlock()
}
}

View File

@ -16,11 +16,15 @@ type Config struct {
Port string
TLS bool
TLSConfig *tls.Config
ServerPassword string
Nick string
Password string
Username string
Realname string
SASL SASL
SASLMechanisms []string
Account string
Password string
// Version is the reply to VERSION and FINGER CTCP messages
Version string
// Source is the reply to SOURCE CTCP messages
@ -43,6 +47,9 @@ type Client struct {
wantedCapabilities []string
requestedCapabilities map[string][]string
enabledCapabilities map[string][]string
negotiating bool
saslMechanisms []SASL
currentSASLIndex int
conn net.Conn
connected bool
@ -76,10 +83,8 @@ func NewClient(config *Config) *Client {
config.Realname = config.Nick
}
wantedCapabilities := append([]string{}, clientWantedCaps...)
if config.SASL != nil {
wantedCapabilities = append(wantedCapabilities, "sasl")
if config.SASLMechanisms == nil {
config.SASLMechanisms = DefaultSASLMechanisms
}
client := &Client{
@ -88,7 +93,6 @@ func NewClient(config *Config) *Client {
ConnectionChanged: make(chan ConnectionState, 4),
Features: NewFeatures(),
nick: config.Nick,
wantedCapabilities: wantedCapabilities,
requestedCapabilities: map[string][]string{},
enabledCapabilities: map[string][]string{},
dialer: &net.Dialer{Timeout: 10 * time.Second},
@ -103,10 +107,44 @@ func NewClient(config *Config) *Client {
reconnect: make(chan struct{}),
}
client.state = newState(client)
client.initSASL()
return client
}
func (c *Client) initSASL() {
saslMechanisms := []SASL{}
for _, mech := range c.Config.SASLMechanisms {
if mech == "EXTERNAL" {
if c.Config.TLSConfig != nil && len(c.Config.TLSConfig.Certificates) > 0 {
saslMechanisms = append(saslMechanisms, &SASLExternal{})
}
} else if c.Config.Account != "" && c.Config.Password != "" {
if mech == "PLAIN" {
saslMechanisms = append(saslMechanisms, &SASLPlain{
Username: c.Config.Account,
Password: c.Config.Password,
})
} else if strings.HasPrefix(mech, "SCRAM-") {
saslMechanisms = append(saslMechanisms, &SASLScram{
Username: c.Config.Account,
Password: c.Config.Password,
Hash: mech[6:],
})
}
}
}
c.wantedCapabilities = append([]string{}, clientWantedCaps...)
if len(saslMechanisms) > 0 {
c.wantedCapabilities = append(c.wantedCapabilities, "sasl")
c.saslMechanisms = saslMechanisms
c.currentSASLIndex = 0
}
}
func (c *Client) GetNick() string {
c.lock.Lock()
nick := c.nick
@ -245,10 +283,14 @@ func (c *Client) writeUser(username, realname string) {
c.writef("USER %s 0 * :%s", username, realname)
}
func (c *Client) authenticate(response string) {
c.write("AUTHENTICATE " + response)
}
func (c *Client) register() {
c.writeCAP()
if c.Config.Password != "" {
c.writePass(c.Config.Password)
c.beginCAP()
if c.Config.ServerPassword != "" {
c.writePass(c.Config.ServerPassword)
}
c.writeNick(c.Config.Nick)
c.writeUser(c.Config.Username, c.Config.Realname)

View File

@ -152,7 +152,7 @@ func TestRegister(t *testing.T) {
assert.Equal(t, "NICK nick\r\n", <-out)
assert.Equal(t, "USER user 0 * :rn\r\n", <-out)
c.Config.Password = "pass"
c.Config.ServerPassword = "pass"
c.register()
assert.Equal(t, "CAP LS 302\r\n", <-out)
assert.Equal(t, "PASS pass\r\n", <-out)

View File

@ -64,6 +64,7 @@ func (c *Client) run() {
c.sendRecv.Wait()
c.reconnect = make(chan struct{})
c.state.reset()
c.initSASL()
time.Sleep(c.backoff.Duration())
c.tryConnect()

View File

@ -85,6 +85,7 @@ func (c *Client) handleMessage(msg *Message) {
if len(msg.Params) > 0 {
c.setNick(msg.Params[0])
}
c.negotiating = false
c.setRegistered(true)
c.flushChannels()

View File

@ -2,12 +2,28 @@ package irc
import (
"bytes"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/base64"
"errors"
"hash"
"strings"
"github.com/xdg-go/scram"
)
var DefaultSASLMechanisms = []string{
"EXTERNAL",
//"SCRAM-SHA-512",
"SCRAM-SHA-256",
//"SCRAM-SHA-1",
"PLAIN",
}
type SASL interface {
Name() string
Encode() string
Step(response string) (string, error)
}
type SASLPlain struct {
@ -19,7 +35,7 @@ func (s *SASLPlain) Name() string {
return "PLAIN"
}
func (s *SASLPlain) Encode() string {
func (s *SASLPlain) Step(string) (string, error) {
buf := bytes.Buffer{}
buf.WriteString(s.Username)
buf.WriteByte(0x0)
@ -27,7 +43,7 @@ func (s *SASLPlain) Encode() string {
buf.WriteByte(0x0)
buf.WriteString(s.Password)
return base64.StdEncoding.EncodeToString(buf.Bytes())
return base64.StdEncoding.EncodeToString(buf.Bytes()), nil
}
type SASLExternal struct{}
@ -36,29 +52,124 @@ func (s *SASLExternal) Name() string {
return "EXTERNAL"
}
func (s *SASLExternal) Encode() string {
return "+"
func (s *SASLExternal) Step(string) (string, error) {
return "+", nil
}
var (
scramHashes = map[string]scram.HashGeneratorFcn{
"SHA-512": func() hash.Hash { return sha512.New() },
"SHA-256": func() hash.Hash { return sha256.New() },
"SHA-1": func() hash.Hash { return sha1.New() },
}
ErrUnsupportedHash = errors.New("unsupported hash algorithm")
)
type SASLScram struct {
Username string
Password string
Hash string
conv *scram.ClientConversation
}
func (s *SASLScram) Name() string {
return "SCRAM-" + s.Hash
}
func (s *SASLScram) Step(response string) (string, error) {
if s.conv == nil {
if hash, ok := scramHashes[s.Hash]; ok {
client, err := hash.NewClient(s.Username, s.Password, "")
if err != nil {
return "", err
}
s.conv = client.NewConversation()
} else {
return "", ErrUnsupportedHash
}
}
challenge := ""
if response != "+" {
b, err := base64.StdEncoding.DecodeString(response)
if err != nil {
return "", err
}
challenge = string(b)
}
res, err := s.conv.Step(challenge)
if err != nil {
return "", err
}
if s.conv.Done() {
s.conv = nil
return "+", nil
} else {
return base64.StdEncoding.EncodeToString([]byte(res)), nil
}
}
func (c *Client) currentSASL() SASL {
return c.saslMechanisms[c.currentSASLIndex]
}
func (c *Client) nextSASL() SASL {
if c.currentSASLIndex < len(c.saslMechanisms)-1 {
c.currentSASLIndex++
return c.saslMechanisms[c.currentSASLIndex]
}
return nil
}
func (c *Client) handleSASL(msg *Message) {
switch msg.Command {
case AUTHENTICATE:
auth := c.Config.SASL.Encode()
auth, err := c.currentSASL().Step(msg.LastParam())
if err != nil {
c.finishCAP()
return
}
for len(auth) >= 400 {
c.write("AUTHENTICATE " + auth)
c.authenticate(auth)
auth = auth[400:]
}
if len(auth) > 0 {
c.write("AUTHENTICATE " + auth)
c.authenticate(auth)
} else {
c.write("AUTHENTICATE +")
c.authenticate("+")
}
case RPL_SASLSUCCESS:
c.write("CAP END")
case RPL_SASLMECHS:
if len(msg.Params) > 1 {
supportedMechs := strings.Split(msg.Params[1], ",")
case ERR_NICKLOCKED, ERR_SASLFAIL, ERR_SASLTOOLONG, ERR_SASLABORTED, RPL_SASLMECHS:
c.write("CAP END")
for i, mech := range c.saslMechanisms {
for _, supported := range supportedMechs {
if mech.Name() == supported && i > c.currentSASLIndex {
c.currentSASLIndex = i
c.authenticate(mech.Name())
return
}
}
}
}
c.finishCAP()
case ERR_SASLFAIL:
if next := c.nextSASL(); next != nil {
c.authenticate(next.Name())
} else {
c.finishCAP()
}
case RPL_SASLSUCCESS, RPL_LOGGEDIN:
c.finishCAP()
case ERR_NICKLOCKED, ERR_SASLTOOLONG, ERR_SASLABORTED:
c.finishCAP()
}
}

View File

@ -39,6 +39,8 @@ func connectIRC(server *storage.Server, state *State, srcIP []byte) *irc.Client
Nick: server.Nick,
Username: server.Username,
Realname: server.Realname,
Account: server.Account,
Password: server.Password,
Version: fmt.Sprintf("Dispatch %s (git: %s)", version.Tag, version.Commit),
Source: "https://github.com/khlieng/dispatch",
}
@ -57,19 +59,12 @@ func connectIRC(server *storage.Server, state *State, srcIP []byte) *irc.Client
ircCfg.Username = hex.EncodeToString(srcIP)
}
if server.Account != "" && server.Password != "" {
ircCfg.SASL = &irc.SASLPlain{
Username: server.Account,
Password: server.Password,
}
}
if server.ServerPassword == "" &&
cfg.Defaults.ServerPassword != "" &&
server.Host == cfg.Defaults.Host {
ircCfg.Password = cfg.Defaults.ServerPassword
ircCfg.ServerPassword = cfg.Defaults.ServerPassword
} else {
ircCfg.Password = server.ServerPassword
ircCfg.ServerPassword = server.ServerPassword
}
i := irc.NewClient(&ircCfg)

View File

@ -36,15 +36,6 @@ func NewUser(store Store) (*User, error) {
return nil, err
}
user.messageLog, err = GetMessageStore(user)
if err != nil {
return nil, err
}
user.messageIndex, err = GetMessageSearchProvider(user)
if err != nil {
return nil, err
}
err = os.MkdirAll(Path.User(user.Username), 0700)
if err != nil {
return nil, err
@ -54,6 +45,15 @@ func NewUser(store Store) (*User, error) {
return nil, err
}
user.messageLog, err = GetMessageStore(user)
if err != nil {
return nil, err
}
user.messageIndex, err = GetMessageSearchProvider(user)
if err != nil {
return nil, err
}
return user, nil
}

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

11
vendor/github.com/xdg-go/scram/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,11 @@
language: go
sudo: false
go:
- "1.7"
- "1.8"
- "1.9"
- "1.10"
- master
matrix:
allow_failures:
- go: master

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.

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

@ -0,0 +1,71 @@
[![GoDoc](https://godoc.org/github.com/xdg/scram?status.svg)](https://godoc.org/github.com/xdg/scram)
[![Build Status](https://travis-ci.org/xdg/scram.svg?branch=master)](https://travis-ci.org/xdg/scram)
# 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/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"
"golang.org/x/crypto/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

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
}

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

@ -0,0 +1,66 @@
// 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"
"fmt"
"hash"
"github.com/xdg/stringprep"
)
// 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() }
// NewClient constructs a SCRAM client component based on a given hash.Hash
// factory receiver. This constructor will normalize the username, password
// and authzID via the SASLprep algorithm, as recommended by RFC-5802. If
// SASLprep fails, the method returns an error.
func (f HashGeneratorFcn) NewClient(username, password, authzID string) (*Client, error) {
var userprep, passprep, authprep string
var err error
if userprep, err = stringprep.SASLprep.Prepare(username); err != nil {
return nil, fmt.Errorf("Error SASLprepping username '%s': %v", username, err)
}
if passprep, err = stringprep.SASLprep.Prepare(password); err != nil {
return nil, fmt.Errorf("Error SASLprepping password '%s': %v", password, err)
}
if authprep, err = stringprep.SASLprep.Prepare(authzID); err != nil {
return nil, fmt.Errorf("Error SASLprepping authzID '%s': %v", authzID, err)
}
return newClient(userprep, passprep, authprep, f), nil
}
// 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
}

0
vendor/github.com/xdg/stringprep/.gitignore generated vendored Normal file
View File

10
vendor/github.com/xdg/stringprep/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,10 @@
language: go
sudo: false
go:
- 1.7
- 1.8
- 1.9
- master
matrix:
allow_failures:
- go: master

175
vendor/github.com/xdg/stringprep/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.

27
vendor/github.com/xdg/stringprep/README.md generated vendored Normal file
View File

@ -0,0 +1,27 @@
[![GoDoc](https://godoc.org/github.com/xdg/stringprep?status.svg)](https://godoc.org/github.com/xdg/stringprep)
[![Build Status](https://travis-ci.org/xdg/stringprep.svg?branch=master)](https://travis-ci.org/xdg/stringprep)
# stringprep  Go implementation of RFC-3454 stringprep and RFC-4013 SASLprep
## Synopsis
```
import "github.com/xdg/stringprep"
prepped := stringprep.SASLprep.Prepare("TrustNô1")
```
## Description
This library provides an implementation of the stringprep algorithm
(RFC-3454) in Go, including all data tables.
A pre-built SASLprep (RFC-4013) profile is provided as well.
## 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

73
vendor/github.com/xdg/stringprep/bidi.go generated vendored Normal file
View File

@ -0,0 +1,73 @@
// 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 stringprep
var errHasLCat = "BiDi string can't have runes from category L"
var errFirstRune = "BiDi string first rune must have category R or AL"
var errLastRune = "BiDi string last rune must have category R or AL"
// Check for prohibited characters from table C.8
func checkBiDiProhibitedRune(s string) error {
for _, r := range s {
if TableC8.Contains(r) {
return Error{Msg: errProhibited, Rune: r}
}
}
return nil
}
// Check for LCat characters from table D.2
func checkBiDiLCat(s string) error {
for _, r := range s {
if TableD2.Contains(r) {
return Error{Msg: errHasLCat, Rune: r}
}
}
return nil
}
// Check first and last characters are in table D.1; requires non-empty string
func checkBadFirstAndLastRandALCat(s string) error {
rs := []rune(s)
if !TableD1.Contains(rs[0]) {
return Error{Msg: errFirstRune, Rune: rs[0]}
}
n := len(rs) - 1
if !TableD1.Contains(rs[n]) {
return Error{Msg: errLastRune, Rune: rs[n]}
}
return nil
}
// Look for RandALCat characters from table D.1
func hasBiDiRandALCat(s string) bool {
for _, r := range s {
if TableD1.Contains(r) {
return true
}
}
return false
}
// Check that BiDi rules are satisfied ; let empty string pass this rule
func passesBiDiRules(s string) error {
if len(s) == 0 {
return nil
}
if err := checkBiDiProhibitedRune(s); err != nil {
return err
}
if hasBiDiRandALCat(s) {
if err := checkBiDiLCat(s); err != nil {
return err
}
if err := checkBadFirstAndLastRandALCat(s); err != nil {
return err
}
}
return nil
}

10
vendor/github.com/xdg/stringprep/doc.go generated vendored Normal file
View File

@ -0,0 +1,10 @@
// 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 stringprep provides data tables and algorithms for RFC-3454,
// including errata (as of 2018-02). It also provides a profile for
// SASLprep as defined in RFC-4013.
package stringprep

14
vendor/github.com/xdg/stringprep/error.go generated vendored Normal file
View File

@ -0,0 +1,14 @@
package stringprep
import "fmt"
// Error describes problems encountered during stringprep, including what rune
// was problematic.
type Error struct {
Msg string
Rune rune
}
func (e Error) Error() string {
return fmt.Sprintf("%s (rune: '\\u%04x')", e.Msg, e.Rune)
}

21
vendor/github.com/xdg/stringprep/map.go generated vendored Normal file
View File

@ -0,0 +1,21 @@
// 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 stringprep
// Mapping represents a stringprep mapping, from a single rune to zero or more
// runes.
type Mapping map[rune][]rune
// Map maps a rune to a (possibly empty) rune slice via a stringprep Mapping.
// The ok return value is false if the rune was not found.
func (m Mapping) Map(r rune) (replacement []rune, ok bool) {
rs, ok := m[r]
if !ok {
return nil, false
}
return rs, true
}

75
vendor/github.com/xdg/stringprep/profile.go generated vendored Normal file
View File

@ -0,0 +1,75 @@
package stringprep
import (
"golang.org/x/text/unicode/norm"
)
// Profile represents a stringprep profile.
type Profile struct {
Mappings []Mapping
Normalize bool
Prohibits []Set
CheckBiDi bool
}
var errProhibited = "prohibited character"
// Prepare transforms an input string to an output string following
// the rules defined in the profile as defined by RFC-3454.
func (p Profile) Prepare(s string) (string, error) {
// Optimistically, assume output will be same length as input
temp := make([]rune, 0, len(s))
// Apply maps
for _, r := range s {
rs, ok := p.applyMaps(r)
if ok {
temp = append(temp, rs...)
} else {
temp = append(temp, r)
}
}
// Normalize
var out string
if p.Normalize {
out = norm.NFKC.String(string(temp))
} else {
out = string(temp)
}
// Check prohibited
for _, r := range out {
if p.runeIsProhibited(r) {
return "", Error{Msg: errProhibited, Rune: r}
}
}
// Check BiDi allowed
if p.CheckBiDi {
if err := passesBiDiRules(out); err != nil {
return "", err
}
}
return out, nil
}
func (p Profile) applyMaps(r rune) ([]rune, bool) {
for _, m := range p.Mappings {
rs, ok := m.Map(r)
if ok {
return rs, true
}
}
return nil, false
}
func (p Profile) runeIsProhibited(r rune) bool {
for _, s := range p.Prohibits {
if s.Contains(r) {
return true
}
}
return false
}

52
vendor/github.com/xdg/stringprep/saslprep.go generated vendored Normal file
View File

@ -0,0 +1,52 @@
package stringprep
var mapNonASCIISpaceToASCIISpace = Mapping{
0x00A0: []rune{0x0020},
0x1680: []rune{0x0020},
0x2000: []rune{0x0020},
0x2001: []rune{0x0020},
0x2002: []rune{0x0020},
0x2003: []rune{0x0020},
0x2004: []rune{0x0020},
0x2005: []rune{0x0020},
0x2006: []rune{0x0020},
0x2007: []rune{0x0020},
0x2008: []rune{0x0020},
0x2009: []rune{0x0020},
0x200A: []rune{0x0020},
0x200B: []rune{0x0020},
0x202F: []rune{0x0020},
0x205F: []rune{0x0020},
0x3000: []rune{0x0020},
}
// SASLprep is a pre-defined stringprep profile for user names and passwords
// as described in RFC-4013.
//
// Because the stringprep distinction between query and stored strings was
// intended for compatibility across profile versions, but SASLprep was never
// updated and is now deprecated, this profile only operates in stored
// strings mode, prohibiting unassigned code points.
var SASLprep Profile = saslprep
var saslprep = Profile{
Mappings: []Mapping{
TableB1,
mapNonASCIISpaceToASCIISpace,
},
Normalize: true,
Prohibits: []Set{
TableA1,
TableC1_2,
TableC2_1,
TableC2_2,
TableC3,
TableC4,
TableC5,
TableC6,
TableC7,
TableC8,
TableC9,
},
CheckBiDi: true,
}

36
vendor/github.com/xdg/stringprep/set.go generated vendored Normal file
View File

@ -0,0 +1,36 @@
// 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 stringprep
import "sort"
// RuneRange represents a close-ended range of runes: [N,M]. For a range
// consisting of a single rune, N and M will be equal.
type RuneRange [2]rune
// Contains returns true if a rune is within the bounds of the RuneRange.
func (rr RuneRange) Contains(r rune) bool {
return rr[0] <= r && r <= rr[1]
}
func (rr RuneRange) isAbove(r rune) bool {
return r <= rr[0]
}
// Set represents a stringprep data table used to identify runes of a
// particular type.
type Set []RuneRange
// Contains returns true if a rune is within any of the RuneRanges in the
// Set.
func (s Set) Contains(r rune) bool {
i := sort.Search(len(s), func(i int) bool { return s[i].Contains(r) || s[i].isAbove(r) })
if i < len(s) && s[i].Contains(r) {
return true
}
return false
}

3215
vendor/github.com/xdg/stringprep/tables.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

10
vendor/modules.txt vendored
View File

@ -216,10 +216,18 @@ github.com/tdewolff/parse/v2/strconv
github.com/tinylib/msgp/msgp
# github.com/willf/bitset v1.1.10
github.com/willf/bitset
# github.com/xdg-go/scram v0.0.0-20180814205039-7eeb5667e42c
## explicit
github.com/xdg-go/scram
# github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c
## explicit
# github.com/xdg/stringprep v1.0.0
## explicit
github.com/xdg/stringprep
# go.etcd.io/bbolt v1.3.4
## explicit
go.etcd.io/bbolt
# golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
# golang.org/x/crypto v0.0.0-20200602180216-279210d13fed
## explicit
golang.org/x/crypto/ed25519
golang.org/x/crypto/ed25519/internal/edwards25519