Implement SCRAM-SHA-256
This commit is contained in:
parent
ead3b37cf9
commit
876d9ebdd0
@ -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
5
go.mod
@ -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
10
go.sum
@ -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=
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,15 +12,19 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Host string
|
||||
Port string
|
||||
TLS bool
|
||||
TLSConfig *tls.Config
|
||||
Nick string
|
||||
Password string
|
||||
Username string
|
||||
Realname string
|
||||
SASL SASL
|
||||
Host string
|
||||
Port string
|
||||
TLS bool
|
||||
TLSConfig *tls.Config
|
||||
ServerPassword string
|
||||
Nick string
|
||||
Username string
|
||||
Realname string
|
||||
|
||||
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)
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
||||
|
137
pkg/irc/sasl.go
137
pkg/irc/sasl.go
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
0
vendor/github.com/xdg-go/scram/.gitignore
generated
vendored
Normal file
11
vendor/github.com/xdg-go/scram/.travis.yml
generated
vendored
Normal file
11
vendor/github.com/xdg-go/scram/.travis.yml
generated
vendored
Normal 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
175
vendor/github.com/xdg-go/scram/LICENSE
generated
vendored
Normal 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
71
vendor/github.com/xdg-go/scram/README.md
generated
vendored
Normal 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
130
vendor/github.com/xdg-go/scram/client.go
generated
vendored
Normal 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
149
vendor/github.com/xdg-go/scram/client_conv.go
generated
vendored
Normal 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
97
vendor/github.com/xdg-go/scram/common.go
generated
vendored
Normal 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
24
vendor/github.com/xdg-go/scram/doc.go
generated
vendored
Normal 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
205
vendor/github.com/xdg-go/scram/parse.go
generated
vendored
Normal 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
66
vendor/github.com/xdg-go/scram/scram.go
generated
vendored
Normal 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
50
vendor/github.com/xdg-go/scram/server.go
generated
vendored
Normal 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
151
vendor/github.com/xdg-go/scram/server_conv.go
generated
vendored
Normal 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
0
vendor/github.com/xdg/stringprep/.gitignore
generated
vendored
Normal file
10
vendor/github.com/xdg/stringprep/.travis.yml
generated
vendored
Normal file
10
vendor/github.com/xdg/stringprep/.travis.yml
generated
vendored
Normal 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
175
vendor/github.com/xdg/stringprep/LICENSE
generated
vendored
Normal 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
27
vendor/github.com/xdg/stringprep/README.md
generated
vendored
Normal 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
73
vendor/github.com/xdg/stringprep/bidi.go
generated
vendored
Normal 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
10
vendor/github.com/xdg/stringprep/doc.go
generated
vendored
Normal 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
14
vendor/github.com/xdg/stringprep/error.go
generated
vendored
Normal 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
21
vendor/github.com/xdg/stringprep/map.go
generated
vendored
Normal 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
75
vendor/github.com/xdg/stringprep/profile.go
generated
vendored
Normal 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
52
vendor/github.com/xdg/stringprep/saslprep.go
generated
vendored
Normal 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
36
vendor/github.com/xdg/stringprep/set.go
generated
vendored
Normal 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
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
10
vendor/modules.txt
vendored
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user