mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-12-07 16:48:07 -06:00
Grand test fixup (#138)
* start fixing up tests * fix up tests + automate with drone * fiddle with linting * messing about with drone.yml * some more fiddling * hmmm * add cache * add vendor directory * verbose * ci updates * update some little things * update sig
This commit is contained in:
parent
329a5e8144
commit
98263a7de6
2677 changed files with 1090869 additions and 219 deletions
6
vendor/mellium.im/sasl/.gitignore
vendored
Normal file
6
vendor/mellium.im/sasl/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
*.sw[op]
|
||||
*.svg
|
||||
*.xml
|
||||
*.out
|
||||
Gopkg.lock
|
||||
vendor/
|
||||
25
vendor/mellium.im/sasl/LICENSE.md
vendored
Normal file
25
vendor/mellium.im/sasl/LICENSE.md
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
## The BSD 2-Clause License
|
||||
|
||||
Copyright © 2014 The Mellium Contributors.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
20
vendor/mellium.im/sasl/Makefile
vendored
Normal file
20
vendor/mellium.im/sasl/Makefile
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
PACKAGES=$$(go list ./... | grep -v '/vendor/')
|
||||
|
||||
.PHONEY: test
|
||||
test:
|
||||
go test -cover $(PACKAGES)
|
||||
|
||||
.PHONEY: bench
|
||||
bench:
|
||||
go test -cover -bench . -benchmem -run 'Benchmark.*' $(PACKAGES)
|
||||
|
||||
.PHONEY: vet
|
||||
vet:
|
||||
go vet $(PACKAGES)
|
||||
|
||||
deps.svg: *.go
|
||||
( echo "digraph G {"; \
|
||||
go list -f '{{range .Imports}}{{printf "\t%q -> %q;\n" $$.ImportPath .}}{{end}}' \
|
||||
$$(go list -f '{{join .Deps " "}}' .) .; \
|
||||
echo "}"; \
|
||||
) | dot -Tsvg -o $@
|
||||
22
vendor/mellium.im/sasl/README.md
vendored
Normal file
22
vendor/mellium.im/sasl/README.md
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# SASL
|
||||
|
||||
[](https://godoc.org/mellium.im/sasl)
|
||||
[](https://opensource.org/licenses/BSD-2-Clause)
|
||||
|
||||
[](https://www.buymeacoffee.com/samwhited)
|
||||
|
||||
A Go library implementing the Simple Authentication and Security Layer (SASL) as
|
||||
defined by [RFC 4422][rfc4422].
|
||||
|
||||
## Issues and feature requests
|
||||
|
||||
To file a bug report, please use the [issue tracker][issues].
|
||||
|
||||
## License
|
||||
|
||||
The package may be used under the terms of the BSD 2-Clause License a copy of
|
||||
which may be found in the file [LICENSE.md][LICENSE].
|
||||
|
||||
[rfc4422]: https://tools.ietf.org/html/rfc4422
|
||||
[issues]: https://bitbucket.org/mellium/sasl/issues?status=new&status=open
|
||||
[LICENSE]: ./LICENSE.md
|
||||
10
vendor/mellium.im/sasl/bitbucket-pipelines.yml
vendored
Normal file
10
vendor/mellium.im/sasl/bitbucket-pipelines.yml
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
image: golang:latest
|
||||
pipelines:
|
||||
default:
|
||||
- step:
|
||||
script:
|
||||
- go version
|
||||
- go vet ./...
|
||||
- go test -race ./...
|
||||
- go test -cover ./...
|
||||
- go test -bench . -benchmem ./...
|
||||
15
vendor/mellium.im/sasl/doc.go
vendored
Normal file
15
vendor/mellium.im/sasl/doc.go
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright 2016 The Mellium Contributors.
|
||||
// Use of this source code is governed by the BSD 2-clause license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Package sasl implements the Simple Authentication and Security Layer (SASL)
|
||||
// as defined by RFC 4422.
|
||||
//
|
||||
// Most users of this package will only need to create a Negotiator using
|
||||
// NewClient or NewServer and call its Step method repeatedly.
|
||||
// Authors implementing SASL mechanisms other than the builtin ones will want to
|
||||
// create a Mechanism struct which will likely use the other methods on the
|
||||
// Negotiator.
|
||||
//
|
||||
// Be advised: This API is still unstable and is subject to change.
|
||||
package sasl // import "mellium.im/sasl"
|
||||
3
vendor/mellium.im/sasl/go.mod
vendored
Normal file
3
vendor/mellium.im/sasl/go.mod
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
module mellium.im/sasl
|
||||
|
||||
require golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b
|
||||
2
vendor/mellium.im/sasl/go.sum
vendored
Normal file
2
vendor/mellium.im/sasl/go.sum
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b h1:2b9XGzhjiYsYPnKXoEfL7klWZQIt8IfyRCz62gCqqlQ=
|
||||
golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
58
vendor/mellium.im/sasl/mechanism.go
vendored
Normal file
58
vendor/mellium.im/sasl/mechanism.go
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright 2016 The Mellium Contributors.
|
||||
// Use of this source code is governed by the BSD 2-clause license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package sasl
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Define common errors used by SASL mechanisms and negotiators.
|
||||
var (
|
||||
ErrInvalidState = errors.New("Invalid state")
|
||||
ErrInvalidChallenge = errors.New("Invalid or missing challenge")
|
||||
ErrAuthn = errors.New("Authentication error")
|
||||
ErrTooManySteps = errors.New("Step called too many times")
|
||||
)
|
||||
|
||||
var (
|
||||
// Plain is a Mechanism that implements the PLAIN authentication mechanism
|
||||
// as defined by RFC 4616.
|
||||
Plain Mechanism = plain
|
||||
|
||||
// ScramSha256Plus is a Mechanism that implements the SCRAM-SHA-256-PLUS
|
||||
// authentication mechanism defined in RFC 7677. The only supported channel
|
||||
// binding type is tls-unique as defined in RFC 5929.
|
||||
ScramSha256Plus Mechanism = scram("SCRAM-SHA-256-PLUS", sha256.New)
|
||||
|
||||
// ScramSha256 is a Mechanism that implements the SCRAM-SHA-256
|
||||
// authentication mechanism defined in RFC 7677.
|
||||
ScramSha256 Mechanism = scram("SCRAM-SHA-256", sha256.New)
|
||||
|
||||
// ScramSha1Plus is a Mechanism that implements the SCRAM-SHA-1-PLUS
|
||||
// authentication mechanism defined in RFC 5802. The only supported channel
|
||||
// binding type is tls-unique as defined in RFC 5929.
|
||||
ScramSha1Plus Mechanism = scram("SCRAM-SHA-1-PLUS", sha1.New)
|
||||
|
||||
// ScramSha1 is a Mechanism that implements the SCRAM-SHA-1 authentication
|
||||
// mechanism defined in RFC 5802.
|
||||
ScramSha1 Mechanism = scram("SCRAM-SHA-1", sha1.New)
|
||||
)
|
||||
|
||||
// Mechanism represents a SASL mechanism that can be used by a Client or Server
|
||||
// to perform the actual negotiation. Base64 encoding the final challenges and
|
||||
// responses should not be performed by the mechanism.
|
||||
//
|
||||
// Mechanisms must be stateless and may be shared between goroutines. When a
|
||||
// mechanism needs to store state between the different steps it can return
|
||||
// anything that it needs to store and the value will be cached by the
|
||||
// negotiator and passed in as the data parameter when the next challenge is
|
||||
// received.
|
||||
type Mechanism struct {
|
||||
Name string
|
||||
Start func(n *Negotiator) (more bool, resp []byte, cache interface{}, err error)
|
||||
Next func(n *Negotiator, challenge []byte, data interface{}) (more bool, resp []byte, cache interface{}, err error)
|
||||
}
|
||||
192
vendor/mellium.im/sasl/negotiator.go
vendored
Normal file
192
vendor/mellium.im/sasl/negotiator.go
vendored
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
// Copyright 2016 The Mellium Contributors.
|
||||
// Use of this source code is governed by the BSD 2-clause license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package sasl
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// State represents the current state of a Negotiator.
|
||||
// The first two bits represent the actual state of the state machine and the
|
||||
// last 3 bits are a bitmask that define the machines behavior.
|
||||
// The remaining bits should not be used.
|
||||
type State uint8
|
||||
|
||||
// The current step of the Server or Client (represented by the first two bits
|
||||
// of the state byte).
|
||||
const (
|
||||
Initial State = iota
|
||||
AuthTextSent
|
||||
ResponseSent
|
||||
ValidServerResponse
|
||||
|
||||
// Bitmask used for extracting the step from the state byte.
|
||||
StepMask = 0x3
|
||||
)
|
||||
|
||||
const (
|
||||
// RemoteCB bit is on if the remote client or server supports channel binding.
|
||||
RemoteCB State = 1 << (iota + 3)
|
||||
|
||||
// Errored bit is on if the machine has errored.
|
||||
Errored
|
||||
|
||||
// Receiving bit is on if the machine is a server.
|
||||
Receiving
|
||||
)
|
||||
|
||||
// NewClient creates a new SASL Negotiator that supports creating authentication
|
||||
// requests using the given mechanism.
|
||||
func NewClient(m Mechanism, opts ...Option) *Negotiator {
|
||||
machine := &Negotiator{
|
||||
mechanism: m,
|
||||
nonce: nonce(noncerandlen, rand.Reader),
|
||||
}
|
||||
getOpts(machine, opts...)
|
||||
for _, rname := range machine.remoteMechanisms {
|
||||
lname := m.Name
|
||||
if lname == rname && strings.HasSuffix(lname, "-PLUS") {
|
||||
machine.state |= RemoteCB
|
||||
return machine
|
||||
}
|
||||
}
|
||||
return machine
|
||||
}
|
||||
|
||||
// NewServer creates a new SASL Negotiator that supports receiving
|
||||
// authentication requests using the given mechanism.
|
||||
// A nil permissions function is the same as a function that always returns
|
||||
// false.
|
||||
func NewServer(m Mechanism, permissions func(*Negotiator) bool, opts ...Option) *Negotiator {
|
||||
machine := &Negotiator{
|
||||
mechanism: m,
|
||||
nonce: nonce(noncerandlen, rand.Reader),
|
||||
state: AuthTextSent | Receiving,
|
||||
}
|
||||
getOpts(machine, opts...)
|
||||
if permissions != nil {
|
||||
machine.permissions = permissions
|
||||
}
|
||||
for _, rname := range machine.remoteMechanisms {
|
||||
lname := m.Name
|
||||
if lname == rname && strings.HasSuffix(lname, "-PLUS") {
|
||||
machine.state |= RemoteCB
|
||||
return machine
|
||||
}
|
||||
}
|
||||
return machine
|
||||
}
|
||||
|
||||
// A Negotiator represents a SASL client or server state machine that can
|
||||
// attempt to negotiate auth. Negotiators should not be used from multiple
|
||||
// goroutines, and must be reset between negotiation attempts.
|
||||
type Negotiator struct {
|
||||
tlsState *tls.ConnectionState
|
||||
remoteMechanisms []string
|
||||
credentials func() (Username, Password, Identity []byte)
|
||||
permissions func(*Negotiator) bool
|
||||
mechanism Mechanism
|
||||
state State
|
||||
nonce []byte
|
||||
cache interface{}
|
||||
}
|
||||
|
||||
// Nonce returns a unique nonce that is reset for each negotiation attempt. It
|
||||
// is used by SASL Mechanisms and should generally not be called directly.
|
||||
func (c *Negotiator) Nonce() []byte {
|
||||
return c.nonce
|
||||
}
|
||||
|
||||
// Step attempts to transition the state machine to its next state. If Step is
|
||||
// called after a previous invocation generates an error (and the state machine
|
||||
// has not been reset to its initial state), Step panics.
|
||||
func (c *Negotiator) Step(challenge []byte) (more bool, resp []byte, err error) {
|
||||
if c.state&Errored == Errored {
|
||||
panic("sasl: Step called on a SASL state machine that has errored")
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
c.state |= Errored
|
||||
}
|
||||
}()
|
||||
|
||||
switch c.state & StepMask {
|
||||
case Initial:
|
||||
more, resp, c.cache, err = c.mechanism.Start(c)
|
||||
c.state = c.state&^StepMask | AuthTextSent
|
||||
case AuthTextSent:
|
||||
more, resp, c.cache, err = c.mechanism.Next(c, challenge, c.cache)
|
||||
c.state = c.state&^StepMask | ResponseSent
|
||||
case ResponseSent:
|
||||
more, resp, c.cache, err = c.mechanism.Next(c, challenge, c.cache)
|
||||
c.state = c.state&^StepMask | ValidServerResponse
|
||||
case ValidServerResponse:
|
||||
more, resp, c.cache, err = c.mechanism.Next(c, challenge, c.cache)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
return more, resp, err
|
||||
}
|
||||
|
||||
// State returns the internal state of the SASL state machine.
|
||||
func (c *Negotiator) State() State {
|
||||
return c.state
|
||||
}
|
||||
|
||||
// Reset resets the state machine to its initial state so that it can be reused
|
||||
// in another SASL exchange.
|
||||
func (c *Negotiator) Reset() {
|
||||
c.state = c.state & (Receiving | RemoteCB)
|
||||
|
||||
// Skip the start step for servers
|
||||
if c.state&Receiving == Receiving {
|
||||
c.state = c.state&^StepMask | AuthTextSent
|
||||
}
|
||||
|
||||
c.nonce = nonce(noncerandlen, rand.Reader)
|
||||
c.cache = nil
|
||||
}
|
||||
|
||||
// Credentials returns a username, and password for authentication and optional
|
||||
// identity for authorization.
|
||||
func (c *Negotiator) Credentials() (username, password, identity []byte) {
|
||||
if c.credentials != nil {
|
||||
return c.credentials()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Permissions is the callback used by the server to authenticate the user.
|
||||
func (c *Negotiator) Permissions(opts ...Option) bool {
|
||||
if c.permissions != nil {
|
||||
nn := *c
|
||||
getOpts(&nn, opts...)
|
||||
return c.permissions(&nn)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TLSState is the state of any TLS connections being used to negotiate SASL
|
||||
// (it can be used for channel binding).
|
||||
func (c *Negotiator) TLSState() *tls.ConnectionState {
|
||||
if c.tlsState != nil {
|
||||
return c.tlsState
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoteMechanisms is a list of mechanisms as advertised by the other side of a
|
||||
// SASL negotiation.
|
||||
func (c *Negotiator) RemoteMechanisms() []string {
|
||||
if c.remoteMechanisms != nil {
|
||||
return c.remoteMechanisms
|
||||
}
|
||||
return nil
|
||||
}
|
||||
30
vendor/mellium.im/sasl/nonce.go
vendored
Normal file
30
vendor/mellium.im/sasl/nonce.go
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright 2016 The Mellium Contributors.
|
||||
// Use of this source code is governed by the BSD 2-clause license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package sasl
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Generates a nonce with n random bytes base64 encoded to ensure that it meets
|
||||
// the criteria for inclusion in a SCRAM message.
|
||||
func nonce(n int, r io.Reader) []byte {
|
||||
if n < 1 {
|
||||
panic("Cannot generate zero or negative length nonce")
|
||||
}
|
||||
b := make([]byte, n)
|
||||
n2, err := r.Read(b)
|
||||
switch {
|
||||
case err != nil:
|
||||
panic(err)
|
||||
case n2 != n:
|
||||
panic("Could not read enough randomness to generate nonce")
|
||||
}
|
||||
val := make([]byte, base64.RawStdEncoding.EncodedLen(n))
|
||||
base64.RawStdEncoding.Encode(val, b)
|
||||
|
||||
return val
|
||||
}
|
||||
53
vendor/mellium.im/sasl/options.go
vendored
Normal file
53
vendor/mellium.im/sasl/options.go
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright 2016 The Mellium Contributors.
|
||||
// Use of this source code is governed by the BSD 2-clause license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package sasl
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
)
|
||||
|
||||
// An Option represents an input to a SASL state machine.
|
||||
type Option func(*Negotiator)
|
||||
|
||||
func getOpts(n *Negotiator, o ...Option) {
|
||||
n.credentials = func() (username, password, identity []byte) {
|
||||
return
|
||||
}
|
||||
n.permissions = func(_ *Negotiator) bool {
|
||||
return false
|
||||
}
|
||||
for _, f := range o {
|
||||
f(n)
|
||||
}
|
||||
}
|
||||
|
||||
// TLSState lets the state machine negotiate channel binding with a TLS session
|
||||
// if supported by the underlying mechanism.
|
||||
func TLSState(cs tls.ConnectionState) Option {
|
||||
return func(n *Negotiator) {
|
||||
n.tlsState = &cs
|
||||
}
|
||||
}
|
||||
|
||||
// RemoteMechanisms sets a list of mechanisms supported by the remote client or
|
||||
// server with which the state machine will be negotiating.
|
||||
// It is used to determine if the server supports channel binding.
|
||||
func RemoteMechanisms(m ...string) Option {
|
||||
return func(n *Negotiator) {
|
||||
n.remoteMechanisms = m
|
||||
}
|
||||
}
|
||||
|
||||
// Credentials provides the negotiator with a username and password to
|
||||
// authenticate with and (optionally) an authorization identity.
|
||||
// Identity will normally be left empty to act as the username.
|
||||
// The Credentials function is called lazily and may be called multiple times by
|
||||
// the mechanism.
|
||||
// It is not memoized by the negotiator.
|
||||
func Credentials(f func() (Username, Password, Identity []byte)) Option {
|
||||
return func(n *Negotiator) {
|
||||
n.credentials = f
|
||||
}
|
||||
}
|
||||
52
vendor/mellium.im/sasl/plain.go
vendored
Normal file
52
vendor/mellium.im/sasl/plain.go
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright 2016 The Mellium Contributors.
|
||||
// Use of this source code is governed by the BSD 2-clause license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package sasl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
var plainSep = []byte{0}
|
||||
|
||||
var plain = Mechanism{
|
||||
Name: "PLAIN",
|
||||
Start: func(m *Negotiator) (more bool, resp []byte, _ interface{}, err error) {
|
||||
username, password, identity := m.credentials()
|
||||
payload := make([]byte, 0, len(identity)+len(username)+len(password)+2)
|
||||
payload = append(payload, identity...)
|
||||
payload = append(payload, '\x00')
|
||||
payload = append(payload, username...)
|
||||
payload = append(payload, '\x00')
|
||||
payload = append(payload, password...)
|
||||
return false, payload, nil, nil
|
||||
},
|
||||
Next: func(m *Negotiator, challenge []byte, _ interface{}) (more bool, resp []byte, _ interface{}, err error) {
|
||||
// If we're a client, or we're a server that's past the AuthTextSent step,
|
||||
// we should never actually hit this step.
|
||||
if m.State()&Receiving != Receiving || m.State()&StepMask != AuthTextSent {
|
||||
err = ErrTooManySteps
|
||||
return
|
||||
}
|
||||
|
||||
// If we're a server, validate that the challenge looks like:
|
||||
// "Identity\x00Username\x00Password"
|
||||
parts := bytes.Split(challenge, plainSep)
|
||||
if len(parts) != 3 {
|
||||
err = ErrInvalidChallenge
|
||||
return
|
||||
}
|
||||
|
||||
if m.Permissions(Credentials(func() (Username, Password, Identity []byte) {
|
||||
return parts[1], parts[2], parts[0]
|
||||
})) {
|
||||
// Everything checks out as far as we know and the server should continue
|
||||
// to authenticate the user.
|
||||
return
|
||||
}
|
||||
|
||||
err = ErrAuthn
|
||||
return
|
||||
},
|
||||
}
|
||||
227
vendor/mellium.im/sasl/scram.go
vendored
Normal file
227
vendor/mellium.im/sasl/scram.go
vendored
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
// Copyright 2016 The Mellium Contributors.
|
||||
// Use of this source code is governed by the BSD 2-clause license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package sasl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"hash"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
const (
|
||||
gs2HeaderCBSupport = "p=tls-unique,"
|
||||
gs2HeaderNoServerCBSupport = "y,"
|
||||
gs2HeaderNoCBSupport = "n,"
|
||||
)
|
||||
|
||||
var (
|
||||
clientKeyInput = []byte("Client Key")
|
||||
serverKeyInput = []byte("Server Key")
|
||||
)
|
||||
|
||||
// The number of random bytes to generate for a nonce.
|
||||
const noncerandlen = 16
|
||||
|
||||
func getGS2Header(name string, n *Negotiator) (gs2Header []byte) {
|
||||
_, _, identity := n.Credentials()
|
||||
switch {
|
||||
case n.TLSState() == nil || !strings.HasSuffix(name, "-PLUS"):
|
||||
// We do not support channel binding
|
||||
gs2Header = []byte(gs2HeaderNoCBSupport)
|
||||
case n.State()&RemoteCB == RemoteCB:
|
||||
// We support channel binding and the server does too
|
||||
gs2Header = []byte(gs2HeaderCBSupport)
|
||||
case n.State()&RemoteCB != RemoteCB:
|
||||
// We support channel binding but the server does not
|
||||
gs2Header = []byte(gs2HeaderNoServerCBSupport)
|
||||
}
|
||||
if len(identity) > 0 {
|
||||
gs2Header = append(gs2Header, []byte(`a=`)...)
|
||||
gs2Header = append(gs2Header, identity...)
|
||||
}
|
||||
gs2Header = append(gs2Header, ',')
|
||||
return
|
||||
}
|
||||
|
||||
func scram(name string, fn func() hash.Hash) Mechanism {
|
||||
// BUG(ssw): We need a way to cache the SCRAM client and server key
|
||||
// calculations.
|
||||
return Mechanism{
|
||||
Name: name,
|
||||
Start: func(m *Negotiator) (bool, []byte, interface{}, error) {
|
||||
user, _, _ := m.Credentials()
|
||||
|
||||
// Escape "=" and ",". This is mostly the same as bytes.Replace but
|
||||
// faster because we can do both replacements in a single pass.
|
||||
n := bytes.Count(user, []byte{'='}) + bytes.Count(user, []byte{','})
|
||||
username := make([]byte, len(user)+(n*2))
|
||||
w := 0
|
||||
start := 0
|
||||
for i := 0; i < n; i++ {
|
||||
j := start
|
||||
j += bytes.IndexAny(user[start:], "=,")
|
||||
w += copy(username[w:], user[start:j])
|
||||
switch user[j] {
|
||||
case '=':
|
||||
w += copy(username[w:], "=3D")
|
||||
case ',':
|
||||
w += copy(username[w:], "=2C")
|
||||
}
|
||||
start = j + 1
|
||||
}
|
||||
copy(username[w:], user[start:])
|
||||
|
||||
clientFirstMessage := make([]byte, 5+len(m.Nonce())+len(username))
|
||||
copy(clientFirstMessage, "n=")
|
||||
copy(clientFirstMessage[2:], username)
|
||||
copy(clientFirstMessage[2+len(username):], ",r=")
|
||||
copy(clientFirstMessage[5+len(username):], m.Nonce())
|
||||
|
||||
return true, append(getGS2Header(name, m), clientFirstMessage...), clientFirstMessage, nil
|
||||
},
|
||||
Next: func(m *Negotiator, challenge []byte, data interface{}) (more bool, resp []byte, cache interface{}, err error) {
|
||||
if challenge == nil || len(challenge) == 0 {
|
||||
return more, resp, cache, ErrInvalidChallenge
|
||||
}
|
||||
|
||||
if m.State()&Receiving == Receiving {
|
||||
panic("not yet implemented")
|
||||
}
|
||||
return scramClientNext(name, fn, m, challenge, data)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func scramClientNext(name string, fn func() hash.Hash, m *Negotiator, challenge []byte, data interface{}) (more bool, resp []byte, cache interface{}, err error) {
|
||||
_, password, _ := m.Credentials()
|
||||
state := m.State()
|
||||
|
||||
switch state & StepMask {
|
||||
case AuthTextSent:
|
||||
iter := -1
|
||||
var salt, nonce []byte
|
||||
for _, field := range bytes.Split(challenge, []byte{','}) {
|
||||
if len(field) < 3 || (len(field) >= 2 && field[1] != '=') {
|
||||
continue
|
||||
}
|
||||
switch field[0] {
|
||||
case 'i':
|
||||
ival := string(bytes.TrimRight(field[2:], "\x00"))
|
||||
|
||||
if iter, err = strconv.Atoi(ival); err != nil {
|
||||
return
|
||||
}
|
||||
case 's':
|
||||
salt = make([]byte, base64.StdEncoding.DecodedLen(len(field)-2))
|
||||
var n int
|
||||
n, err = base64.StdEncoding.Decode(salt, field[2:])
|
||||
salt = salt[:n]
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case 'r':
|
||||
nonce = field[2:]
|
||||
case 'm':
|
||||
// RFC 5802:
|
||||
// m: This attribute is reserved for future extensibility. In this
|
||||
// version of SCRAM, its presence in a client or a server message
|
||||
// MUST cause authentication failure when the attribute is parsed by
|
||||
// the other end.
|
||||
err = errors.New("Server sent reserved attribute `m'")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case iter < 0:
|
||||
err = errors.New("Iteration count is missing")
|
||||
return
|
||||
case iter < 0:
|
||||
err = errors.New("Iteration count is invalid")
|
||||
return
|
||||
case nonce == nil || !bytes.HasPrefix(nonce, m.Nonce()):
|
||||
err = errors.New("Server nonce does not match client nonce")
|
||||
return
|
||||
case salt == nil:
|
||||
err = errors.New("Server sent empty salt")
|
||||
return
|
||||
}
|
||||
|
||||
gs2Header := getGS2Header(name, m)
|
||||
tlsState := m.TLSState()
|
||||
var channelBinding []byte
|
||||
if tlsState != nil && strings.HasSuffix(name, "-PLUS") {
|
||||
channelBinding = make(
|
||||
[]byte,
|
||||
2+base64.StdEncoding.EncodedLen(len(gs2Header)+len(tlsState.TLSUnique)),
|
||||
)
|
||||
base64.StdEncoding.Encode(channelBinding[2:], append(gs2Header, tlsState.TLSUnique...))
|
||||
channelBinding[0] = 'c'
|
||||
channelBinding[1] = '='
|
||||
} else {
|
||||
channelBinding = make(
|
||||
[]byte,
|
||||
2+base64.StdEncoding.EncodedLen(len(gs2Header)),
|
||||
)
|
||||
base64.StdEncoding.Encode(channelBinding[2:], gs2Header)
|
||||
channelBinding[0] = 'c'
|
||||
channelBinding[1] = '='
|
||||
}
|
||||
clientFinalMessageWithoutProof := append(channelBinding, []byte(",r=")...)
|
||||
clientFinalMessageWithoutProof = append(clientFinalMessageWithoutProof, nonce...)
|
||||
|
||||
clientFirstMessage := data.([]byte)
|
||||
authMessage := append(clientFirstMessage, ',')
|
||||
authMessage = append(authMessage, challenge...)
|
||||
authMessage = append(authMessage, ',')
|
||||
authMessage = append(authMessage, clientFinalMessageWithoutProof...)
|
||||
|
||||
saltedPassword := pbkdf2.Key(password, salt, iter, fn().Size(), fn)
|
||||
|
||||
h := hmac.New(fn, saltedPassword)
|
||||
h.Write(serverKeyInput)
|
||||
serverKey := h.Sum(nil)
|
||||
h.Reset()
|
||||
|
||||
h.Write(clientKeyInput)
|
||||
clientKey := h.Sum(nil)
|
||||
|
||||
h = hmac.New(fn, serverKey)
|
||||
h.Write(authMessage)
|
||||
serverSignature := h.Sum(nil)
|
||||
|
||||
h = fn()
|
||||
h.Write(clientKey)
|
||||
storedKey := h.Sum(nil)
|
||||
h = hmac.New(fn, storedKey)
|
||||
h.Write(authMessage)
|
||||
clientSignature := h.Sum(nil)
|
||||
clientProof := make([]byte, len(clientKey))
|
||||
xorBytes(clientProof, clientKey, clientSignature)
|
||||
|
||||
encodedClientProof := make([]byte, base64.StdEncoding.EncodedLen(len(clientProof)))
|
||||
base64.StdEncoding.Encode(encodedClientProof, clientProof)
|
||||
clientFinalMessage := append(clientFinalMessageWithoutProof, []byte(",p=")...)
|
||||
clientFinalMessage = append(clientFinalMessage, encodedClientProof...)
|
||||
|
||||
return true, clientFinalMessage, serverSignature, nil
|
||||
case ResponseSent:
|
||||
clientCalculatedServerFinalMessage := "v=" + base64.StdEncoding.EncodeToString(data.([]byte))
|
||||
if clientCalculatedServerFinalMessage != string(challenge) {
|
||||
err = ErrAuthn
|
||||
return
|
||||
}
|
||||
// Success!
|
||||
return false, nil, nil, nil
|
||||
}
|
||||
err = ErrInvalidState
|
||||
return
|
||||
}
|
||||
24
vendor/mellium.im/sasl/xor_generic.go
vendored
Normal file
24
vendor/mellium.im/sasl/xor_generic.go
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file was split out from Go's crypto/cipher/xor.go
|
||||
|
||||
// +build !386,!amd64,!ppc64,!ppc64le,!s390x,!appengine
|
||||
|
||||
package sasl
|
||||
|
||||
func xorBytes(dst, a, b []byte) int {
|
||||
n := len(a)
|
||||
if len(b) < n {
|
||||
n = len(b)
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
dst[i] = a[i] ^ b[i]
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func xorWords(dst, a, b []byte) {
|
||||
xorBytes(dst, a, b)
|
||||
}
|
||||
52
vendor/mellium.im/sasl/xor_unaligned.go
vendored
Normal file
52
vendor/mellium.im/sasl/xor_unaligned.go
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file was split out from Go's crypto/cipher/xor.go
|
||||
|
||||
// +build 386 amd64 ppc64 ppc64le s390x appengine
|
||||
|
||||
package sasl
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const wordSize = int(unsafe.Sizeof(uintptr(0)))
|
||||
|
||||
// xorBytes xors in bulk. It only works on architectures that support unaligned
|
||||
// read/writes.
|
||||
func xorBytes(dst, a, b []byte) int {
|
||||
n := len(a)
|
||||
if len(b) < n {
|
||||
n = len(b)
|
||||
}
|
||||
|
||||
w := n / wordSize
|
||||
if w > 0 {
|
||||
dw := *(*[]uintptr)(unsafe.Pointer(&dst))
|
||||
aw := *(*[]uintptr)(unsafe.Pointer(&a))
|
||||
bw := *(*[]uintptr)(unsafe.Pointer(&b))
|
||||
for i := 0; i < w; i++ {
|
||||
dw[i] = aw[i] ^ bw[i]
|
||||
}
|
||||
}
|
||||
|
||||
for i := (n - n%wordSize); i < n; i++ {
|
||||
dst[i] = a[i] ^ b[i]
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
// xorWords XORs multiples of 4 or 8 bytes (depending on architecture.) The
|
||||
// arguments are assumed to be of equal length.
|
||||
func xorWords(dst, a, b []byte) {
|
||||
dw := *(*[]uintptr)(unsafe.Pointer(&dst))
|
||||
aw := *(*[]uintptr)(unsafe.Pointer(&a))
|
||||
bw := *(*[]uintptr)(unsafe.Pointer(&b))
|
||||
n := len(b) / wordSize
|
||||
for i := 0; i < n; i++ {
|
||||
dw[i] = aw[i] ^ bw[i]
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue