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 (PlainFailure) ¶
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 } }
Output: auth failed!
Example (PlainSuccess) ¶
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 } }
Output: auth success!
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') 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)) }
Output: false user=someuser@example.com auth=Bearer vF9dft4qmTc2Nvb3RlckBhdHRhdmlzdGEuY29tCg==
Index ¶
- Variables
- func SCRAMSaltPassword(fn func() hash.Hash, password []byte, salt []byte, iter int) []byte
- 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) SaltedCredentials(username, identity []byte) (salt []byte, saltedPassword []byte, iterations int64, err error)
- 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 SaltedCredentialsFetcher
- 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 ¶
func SCRAMSaltPassword ¶ added in v0.3.2
SCRAMSaltPassword calculates salted version of the raw password using fn as hash function, salt and iter as number of iterations.
See RFC 5802, section 3. SCRAM Algorithm Overview, for implementation details.
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 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) // Anonymous is a Mechanism that implements the ANONYMOUS // authentication mechanism as defined by RFC 4505. Anonymous Mechanism = anonymous )
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)
Credentials returns a username, and password for authentication and optional identity for authorization. Used in client negotiator.
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
Permissions is the callback used by the server to authenticate the user.
func (*Negotiator) RemoteMechanisms ¶ added in v0.1.0
func (c *Negotiator) RemoteMechanisms() []string
RemoteMechanisms is 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) SaltedCredentials ¶ added in v0.3.2
func (c *Negotiator) SaltedCredentials(username, identity []byte) (salt []byte, saltedPassword []byte, iterations int64, err error)
SaltedCredentials returns a salt, saltedPassword and iteration count for a given username and optional identity for client authorization. Refer to SaltedCredentialsFetcher documentation for details. Used in server negotiator.
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
TLSState is the state of any TLS connections being used to negotiate SASL (it can be used 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 SaltedCredentials ¶ added in v0.3.2
func SaltedCredentials(f SaltedCredentialsFetcher) Option
SaltedCredentials provides the negotiator with a SaltedCredentialsFetcher that used to fetch user information from storage.
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 SaltedCredentialsFetcher ¶ added in v0.3.2
type SaltedCredentialsFetcher func(Username, Identity []byte, MechanismName string) (salt []byte, saltedPassword []byte, iterations int64, err error)
SaltedCredentialsFetcher defines function that fetches user information using Username and optional Identity from storage.
Function should return saltedPassword as well as salt and iterations used to generate saltedPassword for specified SCRAM MechanismName. If there is no such Username or Username is not authorized to take Identity function should return ErrAuthn as an error.
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.