package sasl

Import Path
	mellium.im/sasl (on go.dev)

Dependency Relation
	imports 13 packages, and imported by one package

Involved Source Files 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. mechanism.go negotiator.go nonce.go options.go plain.go scram.go xor.go xor_amd64.go xor_amd64.s
Code Examples package main import ( "fmt" "mellium.im/sasl" ) func main() { const ( username = "miranda" password = "pencil" ) creds := sasl.Credentials(func() ([]byte, []byte, []byte) { return []byte(username), []byte(password), []byte{} }) server := sasl.NewServer(sasl.Plain, func(n *sasl.Negotiator) bool { user, pass, ident := n.Credentials() // In a real auth system you might want to consider a constant time // comparison and this would probably involve hashing and a database lookup. if len(ident) == 0 && string(user) == username && string(pass) == password { fmt.Println("auth success!") return true } fmt.Println("auth failed!") return false }, creds) client := sasl.NewClient(sasl.Plain, sasl.Credentials(func() ([]byte, []byte, []byte) { // In a real auth system this would probably be user input. return []byte(username), []byte("password!"), []byte{} })) _, resp, err := client.Step(nil) if err != nil { fmt.Println(err) return } // Normally the response would come from the network, not from a client on the // same machine. _, _, err = server.Step(resp) if err != sasl.ErrAuthn { fmt.Println(err) return } } package main import ( "fmt" "mellium.im/sasl" ) func main() { const ( username = "miranda" password = "pencil" ) creds := sasl.Credentials(func() ([]byte, []byte, []byte) { // In a real auth system this would probably be user input. return []byte(username), []byte(password), []byte{} }) server := sasl.NewServer(sasl.Plain, func(n *sasl.Negotiator) bool { user, pass, ident := n.Credentials() // In a real auth system you might want to consider a constant time // comparison and this would probably involve hashing and a database lookup. if len(ident) == 0 && string(user) == username && string(pass) == password { fmt.Println("auth success!") return true } fmt.Println("auth failed!") return false }, creds) client := sasl.NewClient(sasl.Plain, creds) _, resp, err := client.Step(nil) if err != nil { fmt.Println(err) return } // Normally the response would come from the network, not from a client on the // same machine. _, _, err = server.Step(resp) if err != nil { fmt.Println(err) return } } 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') 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, bytes.Replace(resp, []byte{1}, []byte{' '}, -1)) }
Package-Level Type Names (total 4, all are exported)
/* sort exporteds by: | */
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. Name string Next func(n *Negotiator, challenge []byte, data interface{}) (more bool, resp []byte, cache interface{}, err error) Start func(n *Negotiator) (more bool, resp []byte, cache interface{}, err error) func NewClient(m Mechanism, opts ...Option) *Negotiator func NewServer(m Mechanism, permissions func(*Negotiator) bool, opts ...Option) *Negotiator var Plain var ScramSha1 var ScramSha1Plus var ScramSha256 var ScramSha256Plus
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. Credentials returns a username, and password for authentication and optional identity for authorization. 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. Permissions is the callback used by the server to authenticate the user. RemoteMechanisms is a list of mechanisms as advertised by the other side of a SASL negotiation. Reset resets the state machine to its initial state so that it can be reused in another SASL exchange. State returns the internal state of the SASL state machine. 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. TLSState is the state of any TLS connections being used to negotiate SASL (it can be used for channel binding). func NewClient(m Mechanism, opts ...Option) *Negotiator func NewServer(m Mechanism, permissions func(*Negotiator) bool, opts ...Option) *Negotiator
An Option represents an input to a SASL state machine. func Credentials(f func() (Username, Password, Identity []byte)) Option func RemoteMechanisms(m ...string) Option func TLSState(cs tls.ConnectionState) Option func NewClient(m Mechanism, opts ...Option) *Negotiator func NewServer(m Mechanism, permissions func(*Negotiator) bool, opts ...Option) *Negotiator func (*Negotiator).Permissions(opts ...Option) bool
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. func (*Negotiator).State() State const AuthTextSent const Errored const Initial const Receiving const RemoteCB const ResponseSent const ValidServerResponse
Package-Level Functions (total 14, in which 5 are exported)
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.
NewClient creates a new SASL Negotiator that supports creating authentication requests using the given mechanism.
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.
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.
TLSState lets the state machine negotiate channel binding with a TLS session if supported by the underlying mechanism.
Package-Level Variables (total 13, in which 9 are exported)
Define common errors used by SASL mechanisms and negotiators.
Define common errors used by SASL mechanisms and negotiators.
Define common errors used by SASL mechanisms and negotiators.
Define common errors used by SASL mechanisms and negotiators.
Plain is a Mechanism that implements the PLAIN authentication mechanism as defined by RFC 4616.
ScramSha1 is a Mechanism that implements the SCRAM-SHA-1 authentication mechanism defined in RFC 5802.
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.
ScramSha256 is a Mechanism that implements the SCRAM-SHA-256 authentication mechanism defined in RFC 7677.
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.
Package-Level Constants (total 15, in which 8 are exported)
The current step of the Server or Client (represented by the first two bits of the state byte).
Errored bit is on if the machine has errored.
The current step of the Server or Client (represented by the first two bits of the state byte).
Receiving bit is on if the machine is a server.
RemoteCB bit is on if the remote client or server supports channel binding.
The current step of the Server or Client (represented by the first two bits of the state byte).
Bitmask used for extracting the step from the state byte.
The current step of the Server or Client (represented by the first two bits of the state byte).