Documentation ¶
Overview ¶
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.
Example (XOAUTH2) ¶
package main import ( "bytes" "fmt" "mellium.im/sasl" ) // A custom SASL Mechanism that implements XOAUTH2: // https://developers.google.com/gmail/xoauth2_protocol var xoauth2 = sasl.Mechanism{ Name: "XOAUTH2", Start: func(m *sasl.Negotiator) (bool, []byte, interface{}, error) { // Start is called only by clients and returns the client first message. username, password, _ := m.Credentials() payload := []byte(`user=`) payload = append(payload, username...) payload = append(payload, '\x01') payload = append(payload, []byte(`auth=Bearer `)...) payload = append(payload, password...) payload = append(payload, '\x01', '\x01') // We do not need to Base64 encode the payload; the sasl.Negotiator will do // that for us. return false, payload, nil, nil }, Next: func(m *sasl.Negotiator, challenge []byte, _ interface{}) (bool, []byte, interface{}, error) { // Next is called by both clients and servers and must be able to generate // and handle every challenge except for the client first message which is // generated (but not handled by) by Start. state := m.State() // If we're a client or a server that's past the AuthTextSent step, we // should never actually hit this step for the XOAUTH2 mechanism so return // an error. if state&sasl.Receiving != sasl.Receiving || state&sasl.StepMask != sasl.AuthTextSent { return false, nil, nil, sasl.ErrTooManySteps } parts := bytes.Split(challenge, []byte{1}) if len(parts) != 3 { return false, nil, nil, sasl.ErrInvalidChallenge } user := bytes.TrimPrefix([]byte("user="), parts[0]) if len(user) == len(parts[0]) { return false, nil, nil, sasl.ErrInvalidChallenge } pass := bytes.TrimPrefix([]byte("Auth=Bearer "), parts[1]) if len(pass) == len(parts[1]) { return false, nil, nil, sasl.ErrInvalidChallenge } if len(parts[2]) > 0 { return false, nil, nil, sasl.ErrInvalidChallenge } if m.Permissions(sasl.Credentials(func() ([]byte, []byte, []byte) { return user, pass, nil })) { return false, nil, nil, nil } return false, nil, nil, sasl.ErrAuthn }, } func main() { c := sasl.NewClient( xoauth2, sasl.Credentials(func() ([]byte, []byte, []byte) { return []byte("someuser@example.com"), []byte("vF9dft4qmTc2Nvb3RlckBhdHRhdmlzdGEuY29tCg=="), []byte{} }), ) // This is the first step and we haven't received any challenge from the // server yet. more, resp, _ := c.Step(nil) fmt.Printf("%v %s", more, resp) }
Output: false dXNlcj1zb21ldXNlckBleGFtcGxlLmNvbQFhdXRoPUJlYXJlciB2RjlkZnQ0cW1UYzJOdmIzUmxja0JoZEhSaGRtbHpkR0V1WTI5dENnPT0BAQ==
Index ¶
- Variables
- type Mechanism
- type Negotiator
- func (c *Negotiator) Credentials() (username, password, identity []byte)
- func (c *Negotiator) Nonce() []byte
- func (c *Negotiator) Permissions(opts ...Option) bool
- func (c *Negotiator) RemoteMechanisms() []string
- func (c *Negotiator) Reset()
- func (c *Negotiator) State() State
- func (c *Negotiator) Step(challenge []byte) (more bool, resp []byte, err error)
- func (c *Negotiator) TLSState() *tls.ConnectionState
- type Option
- type State
- Bugs
Examples ¶
Constants ¶
This section is empty.
Variables ¶
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") )
Define common errors used by SASL mechanisms and negotiators.
Functions ¶
This section is empty.
Types ¶
type Mechanism ¶
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) }
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.
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) )
type Negotiator ¶
type Negotiator struct {
// contains filtered or unexported fields
}
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.
func NewClient ¶
func NewClient(m Mechanism, opts ...Option) *Negotiator
NewClient creates a new SASL Negotiator that supports creating authentication requests using the given mechanism.
func NewServer ¶ added in v0.1.0
func NewServer(m Mechanism, permissions func(*Negotiator) bool, opts ...Option) *Negotiator
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 (*Negotiator) Credentials ¶ added in v0.1.0
func (c *Negotiator) Credentials() (username, password, identity []byte)
Returns a username, and password for authentication and optional identity for authorization.
func (*Negotiator) Nonce ¶
func (c *Negotiator) Nonce() []byte
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 (*Negotiator) Permissions ¶ added in v0.1.0
func (c *Negotiator) Permissions(opts ...Option) bool
A function used by the server to authenticate the user.
func (*Negotiator) RemoteMechanisms ¶ added in v0.1.0
func (c *Negotiator) RemoteMechanisms() []string
A list of mechanisms as advertised by the other side of a SASL negotiation.
func (*Negotiator) Reset ¶
func (c *Negotiator) Reset()
Reset resets the state machine to its initial state so that it can be reused in another SASL exchange.
func (*Negotiator) State ¶
func (c *Negotiator) State() State
State returns the internal state of the SASL state machine.
func (*Negotiator) Step ¶
func (c *Negotiator) Step(challenge []byte) (more bool, resp []byte, err error)
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 (*Negotiator) TLSState ¶ added in v0.1.0
func (c *Negotiator) TLSState() *tls.ConnectionState
The state of any TLS connections being used to negotiate SASL (for channel binding).
type Option ¶
type Option func(*Negotiator)
An Option represents an input to a SASL state machine.
func Credentials ¶
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 RemoteMechanisms ¶
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 TLSState ¶ added in v0.1.0
func TLSState(cs tls.ConnectionState) Option
TLSState lets the state machine negotiate channel binding with a TLS session if supported by the underlying mechanism.
type State ¶
type State uint8
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.
Notes ¶
Bugs ¶
We need a way to cache the SCRAM client and server key calculations.
The server side of SCRAM is not yet implemented.