An implementation of the Simple Authentication and Security Layer (SASL). https://mellium.im/sasl/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
sasl/negotiator.go

196 lines
5.4 KiB

// 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
return machine
}
}
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
return machine
}
}
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
}