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 }) {
|
topic({ server, channel, topic, nick }) {
|
||||||
if (nick) {
|
if (nick) {
|
||||||
dispatch(addEvent(server, channel, 'topic', nick, topic));
|
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/tdewolff/minify/v2 v2.7.4
|
||||||
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect
|
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect
|
||||||
github.com/tinylib/msgp v1.1.2 // 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
|
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/net v0.0.0-20200520182314-0ba52f642ac2
|
||||||
golang.org/x/sys v0.0.0-20200523222454-059865788121 // indirect
|
golang.org/x/sys v0.0.0-20200523222454-059865788121 // indirect
|
||||||
gopkg.in/ini.v1 v1.56.0 // 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/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 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc=
|
||||||
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
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/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/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=
|
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-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 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
|
||||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
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-20200602180216-279210d13fed h1:g4KENRiCMEx58Q7/ecwfT0N2o8z35Fnbsjig/Alf2T4=
|
||||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
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-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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var clientWantedCaps = []string{}
|
var clientWantedCaps = []string{"cap-notify"}
|
||||||
|
|
||||||
func (c *Client) writeCAP() {
|
func (c *Client) beginCAP() {
|
||||||
c.write("CAP LS 302")
|
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) {
|
func (c *Client) handleCAP(msg *Message) {
|
||||||
if len(msg.Params) < 3 {
|
if len(msg.Params) < 3 {
|
||||||
c.write("CAP END")
|
c.write("CAP END")
|
||||||
@ -39,9 +59,6 @@ func (c *Client) handleCAP(msg *Message) {
|
|||||||
|
|
||||||
caps := parseCaps(msg.LastParam())
|
caps := parseCaps(msg.LastParam())
|
||||||
|
|
||||||
c.lock.Lock()
|
|
||||||
defer c.lock.Unlock()
|
|
||||||
|
|
||||||
switch msg.Params[1] {
|
switch msg.Params[1] {
|
||||||
case "LS":
|
case "LS":
|
||||||
for cap, values := range caps {
|
for cap, values := range caps {
|
||||||
@ -58,6 +75,8 @@ func (c *Client) handleCAP(msg *Message) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.negotiating = true
|
||||||
|
|
||||||
reqCaps := []string{}
|
reqCaps := []string{}
|
||||||
for cap := range c.requestedCapabilities {
|
for cap := range c.requestedCapabilities {
|
||||||
reqCaps = append(reqCaps, cap)
|
reqCaps = append(reqCaps, cap)
|
||||||
@ -67,19 +86,17 @@ func (c *Client) handleCAP(msg *Message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "ACK":
|
case "ACK":
|
||||||
|
c.lock.Lock()
|
||||||
for cap := range caps {
|
for cap := range caps {
|
||||||
if v, ok := c.requestedCapabilities[cap]; ok {
|
if v, ok := c.requestedCapabilities[cap]; ok {
|
||||||
c.enabledCapabilities[cap] = v
|
c.enabledCapabilities[cap] = v
|
||||||
delete(c.requestedCapabilities, cap)
|
delete(c.requestedCapabilities, cap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
c.lock.Unlock()
|
||||||
|
|
||||||
if len(c.requestedCapabilities) == 0 {
|
if len(c.requestedCapabilities) == 0 && !c.beginSASL() {
|
||||||
if c.Config.SASL != nil && c.HasCapability("sasl", c.Config.SASL.Name()) {
|
c.finishCAP()
|
||||||
c.write("AUTHENTICATE " + c.Config.SASL.Name())
|
|
||||||
} else {
|
|
||||||
c.write("CAP END")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case "NAK":
|
case "NAK":
|
||||||
@ -87,8 +104,8 @@ func (c *Client) handleCAP(msg *Message) {
|
|||||||
delete(c.requestedCapabilities, cap)
|
delete(c.requestedCapabilities, cap)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.requestedCapabilities) == 0 {
|
if len(c.requestedCapabilities) == 0 && !c.beginSASL() {
|
||||||
c.write("CAP END")
|
c.finishCAP()
|
||||||
}
|
}
|
||||||
|
|
||||||
case "NEW":
|
case "NEW":
|
||||||
@ -107,9 +124,11 @@ func (c *Client) handleCAP(msg *Message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "DEL":
|
case "DEL":
|
||||||
|
c.lock.Lock()
|
||||||
for cap := range caps {
|
for cap := range caps {
|
||||||
delete(c.enabledCapabilities, cap)
|
delete(c.enabledCapabilities, cap)
|
||||||
}
|
}
|
||||||
|
c.lock.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,11 +16,15 @@ type Config struct {
|
|||||||
Port string
|
Port string
|
||||||
TLS bool
|
TLS bool
|
||||||
TLSConfig *tls.Config
|
TLSConfig *tls.Config
|
||||||
|
ServerPassword string
|
||||||
Nick string
|
Nick string
|
||||||
Password string
|
|
||||||
Username string
|
Username string
|
||||||
Realname string
|
Realname string
|
||||||
SASL SASL
|
|
||||||
|
SASLMechanisms []string
|
||||||
|
Account string
|
||||||
|
Password string
|
||||||
|
|
||||||
// Version is the reply to VERSION and FINGER CTCP messages
|
// Version is the reply to VERSION and FINGER CTCP messages
|
||||||
Version string
|
Version string
|
||||||
// Source is the reply to SOURCE CTCP messages
|
// Source is the reply to SOURCE CTCP messages
|
||||||
@ -43,6 +47,9 @@ type Client struct {
|
|||||||
wantedCapabilities []string
|
wantedCapabilities []string
|
||||||
requestedCapabilities map[string][]string
|
requestedCapabilities map[string][]string
|
||||||
enabledCapabilities map[string][]string
|
enabledCapabilities map[string][]string
|
||||||
|
negotiating bool
|
||||||
|
saslMechanisms []SASL
|
||||||
|
currentSASLIndex int
|
||||||
|
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
connected bool
|
connected bool
|
||||||
@ -76,10 +83,8 @@ func NewClient(config *Config) *Client {
|
|||||||
config.Realname = config.Nick
|
config.Realname = config.Nick
|
||||||
}
|
}
|
||||||
|
|
||||||
wantedCapabilities := append([]string{}, clientWantedCaps...)
|
if config.SASLMechanisms == nil {
|
||||||
|
config.SASLMechanisms = DefaultSASLMechanisms
|
||||||
if config.SASL != nil {
|
|
||||||
wantedCapabilities = append(wantedCapabilities, "sasl")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &Client{
|
client := &Client{
|
||||||
@ -88,7 +93,6 @@ func NewClient(config *Config) *Client {
|
|||||||
ConnectionChanged: make(chan ConnectionState, 4),
|
ConnectionChanged: make(chan ConnectionState, 4),
|
||||||
Features: NewFeatures(),
|
Features: NewFeatures(),
|
||||||
nick: config.Nick,
|
nick: config.Nick,
|
||||||
wantedCapabilities: wantedCapabilities,
|
|
||||||
requestedCapabilities: map[string][]string{},
|
requestedCapabilities: map[string][]string{},
|
||||||
enabledCapabilities: map[string][]string{},
|
enabledCapabilities: map[string][]string{},
|
||||||
dialer: &net.Dialer{Timeout: 10 * time.Second},
|
dialer: &net.Dialer{Timeout: 10 * time.Second},
|
||||||
@ -103,10 +107,44 @@ func NewClient(config *Config) *Client {
|
|||||||
reconnect: make(chan struct{}),
|
reconnect: make(chan struct{}),
|
||||||
}
|
}
|
||||||
client.state = newState(client)
|
client.state = newState(client)
|
||||||
|
client.initSASL()
|
||||||
|
|
||||||
return client
|
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 {
|
func (c *Client) GetNick() string {
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
nick := c.nick
|
nick := c.nick
|
||||||
@ -245,10 +283,14 @@ func (c *Client) writeUser(username, realname string) {
|
|||||||
c.writef("USER %s 0 * :%s", username, realname)
|
c.writef("USER %s 0 * :%s", username, realname)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) authenticate(response string) {
|
||||||
|
c.write("AUTHENTICATE " + response)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) register() {
|
func (c *Client) register() {
|
||||||
c.writeCAP()
|
c.beginCAP()
|
||||||
if c.Config.Password != "" {
|
if c.Config.ServerPassword != "" {
|
||||||
c.writePass(c.Config.Password)
|
c.writePass(c.Config.ServerPassword)
|
||||||
}
|
}
|
||||||
c.writeNick(c.Config.Nick)
|
c.writeNick(c.Config.Nick)
|
||||||
c.writeUser(c.Config.Username, c.Config.Realname)
|
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, "NICK nick\r\n", <-out)
|
||||||
assert.Equal(t, "USER user 0 * :rn\r\n", <-out)
|
assert.Equal(t, "USER user 0 * :rn\r\n", <-out)
|
||||||
|
|
||||||
c.Config.Password = "pass"
|
c.Config.ServerPassword = "pass"
|
||||||
c.register()
|
c.register()
|
||||||
assert.Equal(t, "CAP LS 302\r\n", <-out)
|
assert.Equal(t, "CAP LS 302\r\n", <-out)
|
||||||
assert.Equal(t, "PASS pass\r\n", <-out)
|
assert.Equal(t, "PASS pass\r\n", <-out)
|
||||||
|
@ -64,6 +64,7 @@ func (c *Client) run() {
|
|||||||
c.sendRecv.Wait()
|
c.sendRecv.Wait()
|
||||||
c.reconnect = make(chan struct{})
|
c.reconnect = make(chan struct{})
|
||||||
c.state.reset()
|
c.state.reset()
|
||||||
|
c.initSASL()
|
||||||
|
|
||||||
time.Sleep(c.backoff.Duration())
|
time.Sleep(c.backoff.Duration())
|
||||||
c.tryConnect()
|
c.tryConnect()
|
||||||
|
@ -85,6 +85,7 @@ func (c *Client) handleMessage(msg *Message) {
|
|||||||
if len(msg.Params) > 0 {
|
if len(msg.Params) > 0 {
|
||||||
c.setNick(msg.Params[0])
|
c.setNick(msg.Params[0])
|
||||||
}
|
}
|
||||||
|
c.negotiating = false
|
||||||
c.setRegistered(true)
|
c.setRegistered(true)
|
||||||
c.flushChannels()
|
c.flushChannels()
|
||||||
|
|
||||||
|
137
pkg/irc/sasl.go
137
pkg/irc/sasl.go
@ -2,12 +2,28 @@ package irc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
"encoding/base64"
|
"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 {
|
type SASL interface {
|
||||||
Name() string
|
Name() string
|
||||||
Encode() string
|
Step(response string) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type SASLPlain struct {
|
type SASLPlain struct {
|
||||||
@ -19,7 +35,7 @@ func (s *SASLPlain) Name() string {
|
|||||||
return "PLAIN"
|
return "PLAIN"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SASLPlain) Encode() string {
|
func (s *SASLPlain) Step(string) (string, error) {
|
||||||
buf := bytes.Buffer{}
|
buf := bytes.Buffer{}
|
||||||
buf.WriteString(s.Username)
|
buf.WriteString(s.Username)
|
||||||
buf.WriteByte(0x0)
|
buf.WriteByte(0x0)
|
||||||
@ -27,7 +43,7 @@ func (s *SASLPlain) Encode() string {
|
|||||||
buf.WriteByte(0x0)
|
buf.WriteByte(0x0)
|
||||||
buf.WriteString(s.Password)
|
buf.WriteString(s.Password)
|
||||||
|
|
||||||
return base64.StdEncoding.EncodeToString(buf.Bytes())
|
return base64.StdEncoding.EncodeToString(buf.Bytes()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type SASLExternal struct{}
|
type SASLExternal struct{}
|
||||||
@ -36,29 +52,124 @@ func (s *SASLExternal) Name() string {
|
|||||||
return "EXTERNAL"
|
return "EXTERNAL"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SASLExternal) Encode() string {
|
func (s *SASLExternal) Step(string) (string, error) {
|
||||||
return "+"
|
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) {
|
func (c *Client) handleSASL(msg *Message) {
|
||||||
switch msg.Command {
|
switch msg.Command {
|
||||||
case AUTHENTICATE:
|
case AUTHENTICATE:
|
||||||
auth := c.Config.SASL.Encode()
|
auth, err := c.currentSASL().Step(msg.LastParam())
|
||||||
|
if err != nil {
|
||||||
|
c.finishCAP()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for len(auth) >= 400 {
|
for len(auth) >= 400 {
|
||||||
c.write("AUTHENTICATE " + auth)
|
c.authenticate(auth)
|
||||||
auth = auth[400:]
|
auth = auth[400:]
|
||||||
}
|
}
|
||||||
if len(auth) > 0 {
|
if len(auth) > 0 {
|
||||||
c.write("AUTHENTICATE " + auth)
|
c.authenticate(auth)
|
||||||
} else {
|
} else {
|
||||||
c.write("AUTHENTICATE +")
|
c.authenticate("+")
|
||||||
}
|
}
|
||||||
|
|
||||||
case RPL_SASLSUCCESS:
|
case RPL_SASLMECHS:
|
||||||
c.write("CAP END")
|
if len(msg.Params) > 1 {
|
||||||
|
supportedMechs := strings.Split(msg.Params[1], ",")
|
||||||
|
|
||||||
case ERR_NICKLOCKED, ERR_SASLFAIL, ERR_SASLTOOLONG, ERR_SASLABORTED, RPL_SASLMECHS:
|
for i, mech := range c.saslMechanisms {
|
||||||
c.write("CAP END")
|
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,
|
Nick: server.Nick,
|
||||||
Username: server.Username,
|
Username: server.Username,
|
||||||
Realname: server.Realname,
|
Realname: server.Realname,
|
||||||
|
Account: server.Account,
|
||||||
|
Password: server.Password,
|
||||||
Version: fmt.Sprintf("Dispatch %s (git: %s)", version.Tag, version.Commit),
|
Version: fmt.Sprintf("Dispatch %s (git: %s)", version.Tag, version.Commit),
|
||||||
Source: "https://github.com/khlieng/dispatch",
|
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)
|
ircCfg.Username = hex.EncodeToString(srcIP)
|
||||||
}
|
}
|
||||||
|
|
||||||
if server.Account != "" && server.Password != "" {
|
|
||||||
ircCfg.SASL = &irc.SASLPlain{
|
|
||||||
Username: server.Account,
|
|
||||||
Password: server.Password,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if server.ServerPassword == "" &&
|
if server.ServerPassword == "" &&
|
||||||
cfg.Defaults.ServerPassword != "" &&
|
cfg.Defaults.ServerPassword != "" &&
|
||||||
server.Host == cfg.Defaults.Host {
|
server.Host == cfg.Defaults.Host {
|
||||||
ircCfg.Password = cfg.Defaults.ServerPassword
|
ircCfg.ServerPassword = cfg.Defaults.ServerPassword
|
||||||
} else {
|
} else {
|
||||||
ircCfg.Password = server.ServerPassword
|
ircCfg.ServerPassword = server.ServerPassword
|
||||||
}
|
}
|
||||||
|
|
||||||
i := irc.NewClient(&ircCfg)
|
i := irc.NewClient(&ircCfg)
|
||||||
|
@ -36,15 +36,6 @@ func NewUser(store Store) (*User, error) {
|
|||||||
return nil, err
|
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)
|
err = os.MkdirAll(Path.User(user.Username), 0700)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -54,6 +45,15 @@ func NewUser(store Store) (*User, error) {
|
|||||||
return nil, err
|
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
|
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/tinylib/msgp/msgp
|
||||||
# github.com/willf/bitset v1.1.10
|
# github.com/willf/bitset v1.1.10
|
||||||
github.com/willf/bitset
|
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
|
# go.etcd.io/bbolt v1.3.4
|
||||||
## explicit
|
## explicit
|
||||||
go.etcd.io/bbolt
|
go.etcd.io/bbolt
|
||||||
# golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
|
# golang.org/x/crypto v0.0.0-20200602180216-279210d13fed
|
||||||
## explicit
|
## explicit
|
||||||
golang.org/x/crypto/ed25519
|
golang.org/x/crypto/ed25519
|
||||||
golang.org/x/crypto/ed25519/internal/edwards25519
|
golang.org/x/crypto/ed25519/internal/edwards25519
|
||||||
|
Loading…
Reference in New Issue
Block a user