fix(go.sum): update ResolveSpec dependency to v1.0.87
CI / build-and-test (push) Failing after 1s
Release / release (push) Failing after 19m26s

This commit is contained in:
Hein
2026-06-23 13:17:16 +02:00
parent 0227912325
commit 1adf50e3db
2436 changed files with 1078758 additions and 114 deletions
+6
View File
@@ -0,0 +1,6 @@
*.sw[op]
*.svg
*.xml
*.out
Gopkg.lock
vendor/
+28
View File
@@ -0,0 +1,28 @@
# Changelog
All notable changes to this project will be documented in this file.
## v0.3.1 — 2022-12-28
### Fixed
- Sometimes the nonce was not set on the SASL state machine, resulting in
authentication failing
## v0.3.0 — 2022-08-15
### Added
- Support for tls-exporter channel binding method as defined in [RFC 9266]
- Support for fast XOR using SIMD/VSX on more architectures
### Fixed
- Return an error if no tls-unique channel binding (CB) data is present in the
TLS connection state (or no connection state exists) and we use SCRAM with CB
[RFC 9266]: https://datatracker.ietf.org/doc/html/rfc9266
+37
View File
@@ -0,0 +1,37 @@
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
1 Letterman Drive
Suite D4700
San Francisco, CA, 94129
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
+23
View File
@@ -0,0 +1,23 @@
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.
+21
View File
@@ -0,0 +1,21 @@
# SASL
[![Issue Tracker][badge]](https://mellium.im/issue)
[![Docs](https://pkg.go.dev/badge/mellium.im/sasl)](https://pkg.go.dev/mellium.im/sasl)
[![Chat](https://img.shields.io/badge/XMPP-users@mellium.chat-orange.svg)](https://mellium.chat)
[![License](https://img.shields.io/badge/license-FreeBSD-blue.svg)](https://opensource.org/licenses/BSD-2-Clause)
<a href="https://opencollective.com/mellium" alt="Donate on Open Collective"><img src="https://opencollective.com/mellium/donate/button@2x.png?color=blue" width="200"/></a>
A Go library implementing the Simple Authentication and Security Layer (SASL) as
defined by [RFC 4422][rfc4422].
## 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].
[badge]: https://img.shields.io/badge/style-mellium%2fxmpp-green.svg?longCache=true&style=popout-square&label=issues
[rfc4422]: https://tools.ietf.org/html/rfc4422
[LICENSE]: https://codeberg.org/mellium/xmpp/src/branch/main/LICENSE
+15
View 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"
+61
View File
@@ -0,0 +1,61 @@
// 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 (
/* #nosec */
"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 types are tls-unique as defined in RFC
// 5929 and tls-exporter defined in RFC 9266.
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 types are tls-unique as defined in RFC
// 5929 and tls-exporter defined in RFC 9266.
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)
}
+196
View File
@@ -0,0 +1,196 @@
// 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,
}
getOpts(machine, opts...)
for _, rname := range machine.remoteMechanisms {
lname := m.Name
if lname == rname && strings.HasSuffix(lname, "-PLUS") {
machine.state |= RemoteCB
break
}
}
if len(machine.nonce) == 0 {
machine.nonce = nonce(noncerandlen, rand.Reader)
}
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,
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
break
}
}
if len(machine.nonce) == 0 {
machine.nonce = nonce(noncerandlen, rand.Reader)
}
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
View 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
}
+61
View File
@@ -0,0 +1,61 @@
// 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
}
}
// nonce overrides the nonce used for authentication attempts.
// This defaults to a random value and should not be changed.
func setNonce(v []byte) Option {
return func(n *Negotiator) {
n.nonce = v
}
}
// 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
View 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
},
}
+286
View File
@@ -0,0 +1,286 @@
// 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"
"crypto/tls"
"encoding/base64"
"errors"
"hash"
"strconv"
"strings"
"golang.org/x/crypto/pbkdf2"
)
const (
exporterLen = 32
exporterLabel = "EXPORTER-Channel-Binding"
gs2HeaderCBSupportUnique = "p=tls-unique,"
gs2HeaderCBSupportExporter = "p=tls-exporter,"
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()
tlsState := n.TLSState()
switch {
case 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
if tlsState.Version >= tls.VersionTLS13 {
gs2Header = []byte(gs2HeaderCBSupportExporter)
} else {
gs2Header = []byte(gs2HeaderCBSupportUnique)
}
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 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
remain := challenge
for {
var field []byte
field, remain = nextParam(remain)
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
}
if remain == nil {
break
}
}
switch {
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
switch plus := strings.HasSuffix(name, "-PLUS"); {
case plus && tlsState == nil:
err = errors.New("sasl: SCRAM with channel binding requires a TLS connection")
return
case bytes.Contains(gs2Header, []byte(gs2HeaderCBSupportExporter)):
keying, err := tlsState.ExportKeyingMaterial(exporterLabel, nil, exporterLen)
if err != nil {
return false, nil, nil, err
}
if len(keying) == 0 {
err = errors.New("sasl: SCRAM with channel binding requires valid TLS keying material")
return false, nil, nil, err
}
channelBinding = make([]byte, 2+base64.StdEncoding.EncodedLen(len(gs2Header)+len(keying)))
channelBinding[0] = 'c'
channelBinding[1] = '='
base64.StdEncoding.Encode(channelBinding[2:], append(gs2Header, keying...))
case bytes.Contains(gs2Header, []byte(gs2HeaderCBSupportUnique)):
//lint:ignore SA1019 TLS unique must be supported by SCRAM
if len(tlsState.TLSUnique) == 0 {
err = errors.New("sasl: SCRAM with channel binding requires valid tls-unique data")
return false, nil, nil, err
}
channelBinding = make(
[]byte,
//lint:ignore SA1019 TLS unique must be supported by SCRAM
2+base64.StdEncoding.EncodedLen(len(gs2Header)+len(tlsState.TLSUnique)),
)
channelBinding[0] = 'c'
channelBinding[1] = '='
//lint:ignore SA1019 TLS unique must be supported by SCRAM
base64.StdEncoding.Encode(channelBinding[2:], append(gs2Header, tlsState.TLSUnique...))
default:
channelBinding = make(
[]byte,
2+base64.StdEncoding.EncodedLen(len(gs2Header)),
)
channelBinding[0] = 'c'
channelBinding[1] = '='
base64.StdEncoding.Encode(channelBinding[2:], gs2Header)
}
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)
_, err = h.Write(serverKeyInput)
if err != nil {
return
}
serverKey := h.Sum(nil)
h.Reset()
_, err = h.Write(clientKeyInput)
if err != nil {
return
}
clientKey := h.Sum(nil)
h = hmac.New(fn, serverKey)
_, err = h.Write(authMessage)
if err != nil {
return
}
serverSignature := h.Sum(nil)
h = fn()
_, err = h.Write(clientKey)
if err != nil {
return
}
storedKey := h.Sum(nil)
h = hmac.New(fn, storedKey)
_, err = h.Write(authMessage)
if err != nil {
return
}
clientSignature := h.Sum(nil)
clientProof := make([]byte, len(clientKey))
goXORBytes(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
}
func nextParam(params []byte) ([]byte, []byte) {
idx := bytes.IndexByte(params, ',')
if idx == -1 {
return params, nil
}
return params[:idx], params[idx+1:]
}
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2022 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.
//go:build !go1.20
package sasl
// TODO: remove all the specialized XOR code and use "crypto/subtle".XORBytes
// when Go v1.21 comes out. For more information see:
// https://mellium.im/issue/338
func goXORBytes(dst, x, y []byte) int {
n := len(x)
if len(y) < n {
n = len(y)
}
if n == 0 {
return 0
}
if n > len(dst) {
panic("subtle.XORBytes: dst too short")
}
xorBytes(&dst[0], &x[0], &y[0], n) // arch-specific
return n
}
+10
View File
@@ -0,0 +1,10 @@
// Copyright 2018 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.
//go:build !purego
package sasl
//go:noescape
func xorBytes(dst, a, b *byte, n int)
+56
View File
@@ -0,0 +1,56 @@
// Copyright 2018 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.
//go:build !purego
#include "textflag.h"
// func xorBytes(dst, a, b *byte, n int)
TEXT ·xorBytes(SB), NOSPLIT, $0
MOVQ dst+0(FP), BX
MOVQ a+8(FP), SI
MOVQ b+16(FP), CX
MOVQ n+24(FP), DX
TESTQ $15, DX // AND 15 & len, if not zero jump to not_aligned.
JNZ not_aligned
aligned:
MOVQ $0, AX // position in slices
loop16b:
MOVOU (SI)(AX*1), X0 // XOR 16byte forwards.
MOVOU (CX)(AX*1), X1
PXOR X1, X0
MOVOU X0, (BX)(AX*1)
ADDQ $16, AX
CMPQ DX, AX
JNE loop16b
RET
loop_1b:
SUBQ $1, DX // XOR 1byte backwards.
MOVB (SI)(DX*1), DI
MOVB (CX)(DX*1), AX
XORB AX, DI
MOVB DI, (BX)(DX*1)
TESTQ $7, DX // AND 7 & len, if not zero jump to loop_1b.
JNZ loop_1b
CMPQ DX, $0 // if len is 0, ret.
JE ret
TESTQ $15, DX // AND 15 & len, if zero jump to aligned.
JZ aligned
not_aligned:
TESTQ $7, DX // AND $7 & len, if not zero jump to loop_1b.
JNE loop_1b
SUBQ $8, DX // XOR 8bytes backwards.
MOVQ (SI)(DX*1), DI
MOVQ (CX)(DX*1), AX
XORQ AX, DI
MOVQ DI, (BX)(DX*1)
CMPQ DX, $16 // if len is greater or equal 16 here, it must be aligned.
JGE aligned
ret:
RET
+10
View File
@@ -0,0 +1,10 @@
// Copyright 2020 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.
//go:build !purego
package sasl
//go:noescape
func xorBytes(dst, a, b *byte, n int)
+69
View File
@@ -0,0 +1,69 @@
// Copyright 2020 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.
//go:build !purego
#include "textflag.h"
// func xorBytes(dst, a, b *byte, n int)
TEXT ·xorBytes(SB), NOSPLIT|NOFRAME, $0
MOVD dst+0(FP), R0
MOVD a+8(FP), R1
MOVD b+16(FP), R2
MOVD n+24(FP), R3
CMP $64, R3
BLT tail
loop_64:
VLD1.P 64(R1), [V0.B16, V1.B16, V2.B16, V3.B16]
VLD1.P 64(R2), [V4.B16, V5.B16, V6.B16, V7.B16]
VEOR V0.B16, V4.B16, V4.B16
VEOR V1.B16, V5.B16, V5.B16
VEOR V2.B16, V6.B16, V6.B16
VEOR V3.B16, V7.B16, V7.B16
VST1.P [V4.B16, V5.B16, V6.B16, V7.B16], 64(R0)
SUBS $64, R3
CMP $64, R3
BGE loop_64
tail:
// quick end
CBZ R3, end
TBZ $5, R3, less_than32
VLD1.P 32(R1), [V0.B16, V1.B16]
VLD1.P 32(R2), [V2.B16, V3.B16]
VEOR V0.B16, V2.B16, V2.B16
VEOR V1.B16, V3.B16, V3.B16
VST1.P [V2.B16, V3.B16], 32(R0)
less_than32:
TBZ $4, R3, less_than16
LDP.P 16(R1), (R11, R12)
LDP.P 16(R2), (R13, R14)
EOR R11, R13, R13
EOR R12, R14, R14
STP.P (R13, R14), 16(R0)
less_than16:
TBZ $3, R3, less_than8
MOVD.P 8(R1), R11
MOVD.P 8(R2), R12
EOR R11, R12, R12
MOVD.P R12, 8(R0)
less_than8:
TBZ $2, R3, less_than4
MOVWU.P 4(R1), R13
MOVWU.P 4(R2), R14
EORW R13, R14, R14
MOVWU.P R14, 4(R0)
less_than4:
TBZ $1, R3, less_than2
MOVHU.P 2(R1), R15
MOVHU.P 2(R2), R16
EORW R15, R16, R16
MOVHU.P R16, 2(R0)
less_than2:
TBZ $0, R3, end
MOVBU (R1), R17
MOVBU (R2), R19
EORW R17, R19, R19
MOVBU R19, (R0)
end:
RET
+58
View File
@@ -0,0 +1,58 @@
// 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.
//go:build (!amd64 && !arm64 && !ppc64 && !ppc64le) || purego
package sasl
import (
"runtime"
"unsafe"
)
const wordSize = unsafe.Sizeof(uintptr(0))
const supportsUnaligned = runtime.GOARCH == "386" ||
runtime.GOARCH == "amd64" ||
runtime.GOARCH == "ppc64" ||
runtime.GOARCH == "ppc64le" ||
runtime.GOARCH == "s390x"
func xorBytes(dstb, xb, yb *byte, n int) {
// xorBytes assembly is written using pointers and n. Back to slices.
dst := unsafe.Slice(dstb, n)
x := unsafe.Slice(xb, n)
y := unsafe.Slice(yb, n)
if supportsUnaligned || aligned(dstb, xb, yb) {
xorLoop(words(dst), words(x), words(y))
if uintptr(n)%wordSize == 0 {
return
}
done := n &^ int(wordSize-1)
dst = dst[done:]
x = x[done:]
y = y[done:]
}
xorLoop(dst, x, y)
}
// aligned reports whether dst, x, and y are all word-aligned pointers.
func aligned(dst, x, y *byte) bool {
return (uintptr(unsafe.Pointer(dst))|uintptr(unsafe.Pointer(x))|uintptr(unsafe.Pointer(y)))&(wordSize-1) == 0
}
// words returns a []uintptr pointing at the same data as x,
// with any trailing partial word removed.
func words(x []byte) []uintptr {
return unsafe.Slice((*uintptr)(unsafe.Pointer(&x[0])), uintptr(len(x))/wordSize)
}
func xorLoop[T byte | uintptr](dst, x, y []T) {
x = x[:len(dst)] // remove bounds check in loop
y = y[:len(dst)] // remove bounds check in loop
for i := range dst {
dst[i] = x[i] ^ y[i]
}
}
+15
View File
@@ -0,0 +1,15 @@
// Copyright 2022 The Mellium Contributors.
// Use of this source code is governed by the BSD 2-clause
// license that can be found in the LICENSE file.
//go:build go1.20
package sasl
import (
"crypto/subtle"
)
func goXORBytes(dst, x, y []byte) int {
return subtle.XORBytes(dst, x, y)
}
+10
View File
@@ -0,0 +1,10 @@
// Copyright 2018 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.
//go:build (ppc64 || ppc64le) && !purego
package sasl
//go:noescape
func xorBytes(dst, a, b *byte, n int)
+87
View File
@@ -0,0 +1,87 @@
// Copyright 2018 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.
//go:build (ppc64 || ppc64le) && !purego
#include "textflag.h"
// func xorBytes(dst, a, b *byte, n int)
TEXT ·xorBytes(SB), NOSPLIT, $0
MOVD dst+0(FP), R3 // R3 = dst
MOVD a+8(FP), R4 // R4 = a
MOVD b+16(FP), R5 // R5 = b
MOVD n+24(FP), R6 // R6 = n
CMPU R6, $32, CR7 // Check if n 32 bytes
MOVD R0, R8 // R8 = index
CMPU R6, $8, CR6 // Check if 8 n < 32 bytes
BLT CR6, small // Smaller than 8
BLT CR7, xor16 // Case for 16 n < 32 bytes
// Case for n 32 bytes
preloop32:
SRD $5, R6, R7 // Setup loop counter
MOVD R7, CTR
MOVD $16, R10
ANDCC $31, R6, R9 // Check for tailing bytes for later
loop32:
LXVD2X (R4)(R8), VS32 // VS32 = a[i,...,i+15]
LXVD2X (R4)(R10), VS34
LXVD2X (R5)(R8), VS33 // VS33 = b[i,...,i+15]
LXVD2X (R5)(R10), VS35
XXLXOR VS32, VS33, VS32 // VS34 = a[] ^ b[]
XXLXOR VS34, VS35, VS34
STXVD2X VS32, (R3)(R8) // Store to dst
STXVD2X VS34, (R3)(R10)
ADD $32, R8 // Update index
ADD $32, R10
BC 16, 0, loop32 // bdnz loop16
BEQ CR0, done
MOVD R9, R6
CMP R6, $8
BLT small
xor16:
CMP R6, $16
BLT xor8
LXVD2X (R4)(R8), VS32
LXVD2X (R5)(R8), VS33
XXLXOR VS32, VS33, VS32
STXVD2X VS32, (R3)(R8)
ADD $16, R8
ADD $-16, R6
CMP R6, $8
BLT small
xor8:
// Case for 8 n < 16 bytes
MOVD (R4)(R8), R14 // R14 = a[i,...,i+7]
MOVD (R5)(R8), R15 // R15 = b[i,...,i+7]
XOR R14, R15, R16 // R16 = a[] ^ b[]
SUB $8, R6 // n = n - 8
MOVD R16, (R3)(R8) // Store to dst
ADD $8, R8
// Check if we're finished
CMP R6, R0
BGT small
RET
// Case for n < 8 bytes and tailing bytes from the
// previous cases.
small:
CMP R6, R0
BEQ done
MOVD R6, CTR // Setup loop counter
loop:
MOVBZ (R4)(R8), R14 // R14 = a[i]
MOVBZ (R5)(R8), R15 // R15 = b[i]
XOR R14, R15, R16 // R16 = a[i] ^ b[i]
MOVB R16, (R3)(R8) // Store to dst
ADD $1, R8
BC 16, 0, loop // bdnz loop
done:
RET