Documentation ¶
Overview ¶
Package sasler contains client-side and server-side implementations for the following SASL mechanisms: ANONYMOUS, ECDSA-NIST256P-CHALLENGE, EXTERNAL, OAUTHBEARER, PLAIN, SCRAM-SHA-1, and SCRAM-SHA-256.
Client-side usage ¶
- Call one of the functions that returns a ClientMech implementation.
- Send a message to the server to signal you want to start SASL authentication, passing the mechanism name returned by calling Mech().
- If the server acknowledges starting SASL authentication, relay messages between the CientMech and the server, using appropriate encoding for the protocol used with the server. Note that you can call Mech() to detect whether the first message must be sent by the client, or will be sent by the server.
- When the server indicates authentication has finished, you're done. However, when Data() returns an error, the authentication process has failed and must be aborted.
The ClientMech documentation contains an example that demonstrates the process described above.
Server-side usage ¶
- Implement the authenticator matching the desired mechanism, which is by the ServerMech to hook up with any systems you have in place for user and/or credential storage.
- When a client request SASL authentication, call the appropriate function that returns the ServerMech implementation requested by the client.
- Send a message to the client to acknowledge SASL authentication has been started.
- Relay messages between the ServerMech and the client, using appropriate encoding for the protocol used with the client. Note that you can call Mech() to detect whether the first message must be sent by the server, or will be sent by the client.
- Whenever Data() returns an error, the authentication process has failed and must be aborted. Send a message to the client notifying it about the abortion.
- After each call to Data(), call HasCompleted() to check whether the authentication process has been completed. If it returned true, it also returned the authorized identity.
The ServerMech documentation contains an example that demonstrates the process described above. The documentation for each authenticator interface contains an example implementation.
Index ¶
- Variables
- type AnonymousAuthenticator
- type ClientMech
- func AnonymousClient(trace string) (ClientMech, error)
- func EcdsaNist256pChallengeClient(authz, authn string, key *ecdsa.PrivateKey) (ClientMech, error)
- func ExternalClient(authz string) ClientMech
- func OAuthBearerClient(authz string, token []byte, host string, port int) ClientMech
- func PlainClient(authz, authn string, passwd []byte) ClientMech
- func ScramSha1Client(authz, authn string, passwd []byte) (ClientMech, error)
- func ScramSha256Client(authz, authn string, passwd []byte) (ClientMech, error)
- type EcdsaAuthenticator
- type ExternalAuthenticator
- type OAuthBearerAuthenticator
- type PlainAuthenticator
- type ScramAuthenticator
- type ServerMech
- func AnonymousServer(authz string, auth AnonymousAuthenticator) ServerMech
- func EcdsaNist256pChallengeServer(auth EcdsaAuthenticator) ServerMech
- func ExternalServer(auth ExternalAuthenticator) ServerMech
- func OAuthBearerServer(auth OAuthBearerAuthenticator) ServerMech
- func PlainServer(auth PlainAuthenticator) ServerMech
- func ScramSha1Server(auth ScramAuthenticator) (ServerMech, error)
- func ScramSha256Server(auth ScramAuthenticator) (ServerMech, error)
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrInvalidState is returned by a mechanism implementation if the function // is called at an inappropriate moment in the authentication process. ErrInvalidState = errors.New("sasler: mechanism in invalid state") // ErrInvalidMessage is returend by Date if the function is supplied with a // message that is syntactially invalid. ErrInvalidMessage = errors.New("sasler: invalid message") // ErrAuthenticationFailed can be returned by an implementation to indicate // that authentication has failed. ErrAuthenticationFailed = errors.New("sasler: authentication failed") // that the authenticated authn is not authorized to use the requested authz. ErrUnauthorized = errors.New("sasler: unauthorized") )
var ErrWrongCurve = errors.New("sasler: wrong curve")
ErrWrongCurve is returned by a function is called with an ECDSA private or public key that references a curve the function doesn't accept.
Functions ¶
This section is empty.
Types ¶
type AnonymousAuthenticator ¶
type AnonymousAuthenticator interface { // StoreTrace is called to store trace information provided by the client. // Will not be called if the client didn't provide trace information. StoreTrace(trace string) }
AnonymousAuthenticator is supplied to AnonymousServer to implement storing received trace values.
Example ¶
package main import ( "fmt" "github.com/phedny/sasler" ) func main() { auth := myAnonymousAuthenticator{} mech := sasler.AnonymousServer("anonymous-user", &auth) // ANONYMOUS expects one message from the client _, err := mech.Data([]byte("user@example.com")) if err != nil { fmt.Println(err) } // Retrieve authorized identity completed, authz := mech.HasCompleted() if completed { fmt.Println("Authorized identity:", authz) fmt.Println("Stored trace:", auth.trace) } } // myAnonymousAuthenciator is an example AnonymousAuthenticator that just // stores the latest retrieve trace information in a field. type myAnonymousAuthenticator struct { trace string } // StoreTrace stores the received trace information in a field of a. func (a *myAnonymousAuthenticator) StoreTrace(trace string) { a.trace = trace }
Output: Authorized identity: anonymous-user Stored trace: user@example.com
type ClientMech ¶
type ClientMech interface { // Mech returns the full name of the mechanism as described in its // specification, and a bool that is true when this is a client-first // mechanism. Mech() (string, bool) // Data must be called each time SASL data arrived from the other party, // providing the bytes of the message. It returns the bytes of the message // that must be returned to the other party, or an error when authentication // failed and must be aborted. If the returned []byte is nil and no error is // returned, authentication has finished successfully. // // On a client-first mechanism, the first call to Data must be done with nil // or zero length slice. On a server-first mechanism, the first call to Data // must be done with the initial challenge received from the server. Data(data []byte) ([]byte, error) }
ClientMech describes the functions that are implemented by the client-side implementation of a SASL mechanism.
Example ¶
package main import ( "fmt" "github.com/phedny/sasler" ) func main() { // SelectClientMech() represents a function that returns a ClientMech mech := SelectClientMech() mechName, clientFirst := mech.Mech() // AuthenticateToServer() and all calls to Read() and Write() represent a // protocol-specific communication channel. server := AuthenticateToServer(mechName) // For client-first mechanisms, send the first message if clientFirst { data, err := mech.Data(nil) if err != nil { fmt.Println("Authentication aborted.", err) return } server.Write(data) } // Relay authentication data until error, Success or Failure for { command, data := server.Read() switch command { case Success: fmt.Println("Authentication succeeded.") return case Failure: fmt.Println("Authentication failed.") return case Data: data, err := mech.Data(data) if err != nil { fmt.Println("Authentication aborted.", err) return } server.Write(data) } } } func SelectClientMech() sasler.ClientMech { return sasler.ExternalClient("") } type exampleServer struct{} func AuthenticateToServer(mechName string) exampleServer { return exampleServer{} } func (*exampleServer) Write(data []byte) {} const ( Data = iota Success Failure ) func (*exampleServer) Read() (int, []byte) { return Success, nil }
Output: Authentication succeeded.
func AnonymousClient ¶
func AnonymousClient(trace string) (ClientMech, error)
AnonymousClient returns a ClientMech implementation for the ANONYMOUS mechanism, as specified in RFC 4505. Return an error for values of trace that are invalid, i.e. when it contains prohibited characters, or fails BiDi rules. For details, see section 3 of the RFC.
func EcdsaNist256pChallengeClient ¶
func EcdsaNist256pChallengeClient(authz, authn string, key *ecdsa.PrivateKey) (ClientMech, error)
EcdsaNist256pChallengeClient returns a SaslMech implementation for the ECDSA-NIST256P-CHALLENGE mechanism, as specified at ecdsatool. Returns ErrWrongCurve if the public key doesn't use the curve returned by elliptic.P256().
func ExternalClient ¶
func ExternalClient(authz string) ClientMech
ExternalClient returns a ClientMech implementation for the EXTERNAL mechanism, as specified in RFC 4422, appendix A.
func OAuthBearerClient ¶
func OAuthBearerClient(authz string, token []byte, host string, port int) ClientMech
OAuthBearerClient returns a ClientMech implementation for the OAUTHBEARER mechanism, as specified in RFC 7628.
func PlainClient ¶
func PlainClient(authz, authn string, passwd []byte) ClientMech
PlainClient returns a ClientMech implementation for the PLAIN mechanism, as specified in RFC 4616.
func ScramSha1Client ¶
func ScramSha1Client(authz, authn string, passwd []byte) (ClientMech, error)
ScramSha1Client returns a ClientMech implementation for the SCRAM-SHA-1 mechanism, as specified in RFC 5802. Returns an error if SASLprep on authn, or passwd fails, as described in RFC 5802, section 5.1. Also returns an error when generating a random client nonce failed.
func ScramSha256Client ¶
func ScramSha256Client(authz, authn string, passwd []byte) (ClientMech, error)
ScramSha256Client returns a ClientMech implementation for the SCRAM-SHA-256 mechanism, as specified in RFC 7677. Returns an error if SASLprep on authn, or passwd fails, as described in RFC 5802, section 5.1. Also returns an error when generating a random client nonce failed.
type EcdsaAuthenticator ¶
type EcdsaAuthenticator interface { // GetPublicKey returns the public key for an authn, or an error if the // public key could not be retrieved. GetPublicKey(authn string) (*ecdsa.PublicKey, error) // DeriveAuthz derives an authz from an authn. It is only called when no // authz has been requested by the client. Return the empty string if no // authz can be derived from the supplied authn. DeriveAuthz(authn string) string // Authorize verifies whether an authn is authorized to use the requested or // derived authz. Return false to fail authorization. Authorize(authz, authn string) bool }
EcdsaAuthenticator is supplied to EcdsaNist256pChallengeServer to implement retrieving the public key for an authn, authz derivation, and authorization checking.
Example ¶
package main import ( "crypto/ecdsa" "errors" "fmt" "github.com/phedny/sasler" ) func main() { // EcdsaClientConn() represents a function that returns a client that has // requested authentication using the ECDSA-NIST256P-CHALLENGE mechanism. conn := EcdsaClientConn() auth := myEcdsaAuthenticator{} mech := sasler.EcdsaNist256pChallengeServer(&auth) // ECDSA-NIST256P-CHALLENGE requires a couple of messages to be exchanged // between client and server. for { data, err := mech.Data(conn.Read()) if err != nil { // Abort() represents a function that uses a protocol-specific way to // signal to the client that authentication has failed. conn.Abort() fmt.Println(err) return } // Relay data from mech to client if data != nil { conn.Write(data) } // Test if the conversation has completed and show the authz completed, authz := mech.HasCompleted() if completed { // Success() represents a function that uses a protocol-specific way to // signal to the client that authentication has succeeded. conn.Success() fmt.Println("Authorized identity:", authz) return } } } // myEcdsaAuthenticator is an example EcdsaAuthenticator that accepts only a // single username. type myEcdsaAuthenticator struct{} // GetPublicKey retrieves the public key, if authn is "user". func (a *myEcdsaAuthenticator) GetPublicKey(authn string) (*ecdsa.PublicKey, error) { if authn != "user" { return nil, errors.New("unknown authn") } return RetrievePublicKey(authn), nil } // DeriveAuthz derives an authz from an authn. func (a *myEcdsaAuthenticator) DeriveAuthz(authn string) string { return authn } // Authorize checks whether the authn is authorized to act on behalf of the // authz. This implementation also allows anyone authenticated as Admin to be // authorized for any identity. func (a *myEcdsaAuthenticator) Authorize(authz, authn string) bool { return authz == authn || authn == "Admin" }
Output: Authorized identity: user
type ExternalAuthenticator ¶
type ExternalAuthenticator interface { // DeriveAuthz derives an authz from external sources. It is only called when // no authz has been requested by the client. Return the empty string if no // authz can be derived from external sources. DeriveAuthz() string // Authorize verifies whether an externally derived identity is authorized to // use the requested or derived authz. Return false to fail authorization. Authorize(authz string) bool }
ExternalAuthenticator is supplied to ExternalServer to implement authz derivation and authorization checking.
Example ¶
package main import ( "crypto/x509" "fmt" "github.com/phedny/sasler" ) func main() { // TLSConn() represents a function that returns a *tls.Conn with a client // that has presented a client certificate and has requested authentication // using the EXTERNAL mechanism. certs := TLSConn().ConnectionState().PeerCertificates if len(certs) == 0 { fmt.Println("No client TLS certificate.") return } auth := myExternalAuthenticator{certs[0]} mech := sasler.ExternalServer(&auth) // EXTERNAL expects one message from the client _, err := mech.Data([]byte("")) if err != nil { fmt.Println(err) } // Retrieve authorized identity completed, authz := mech.HasCompleted() if completed { fmt.Println("Authorized identity:", authz) } } // myExternalAuthenticator is an example ExternalAuthenticator that accepts // any authentication request when a certificate is present, because it assumes // that connections to clients that present invalid or untrusted certificates // have been denied. type myExternalAuthenticator struct { cert *x509.Certificate } // DeriveAuthz derives an authz from the common name of the certificate. func (a *myExternalAuthenticator) DeriveAuthz() string { return "cn=" + a.cert.Subject.CommonName } // Authorize checks whether the authz matches the common name of the // certificate. This implementation also allows anyone authenticated as // admin@example.com to be authorized for any identity. func (a *myExternalAuthenticator) Authorize(authz string) bool { cn := a.cert.Subject.CommonName return authz == "cn="+cn || authz == "cn=admin@example.com" }
Output: Authorized identity: cn=user@example.com
type OAuthBearerAuthenticator ¶
type OAuthBearerAuthenticator interface { // VerifyToken verifies whether the supplied token is valid. The host and/or // port values default to "" and 0 respectively, if not provided by the // client. Return false to fail authentication. VerifyToken(token []byte, host string, port int) bool // DeriveAuthz derives an authz from a token. It is only called when no authz // has been requested by the client. Return the empty string if no authz can // be derived from the supplied token. DeriveAuthz(token []byte) string // Authorize verifies whether the provided token is authorized to use the // requested or derived authz. Return false to fail authorization. Authorize(authz string, token []byte) bool }
OAuthBearerAuthenticator is supplied to OAuthBearerServer to implement token verification, authz derivation and authorization checking.
Example ¶
package main import ( "bytes" "fmt" "github.com/phedny/sasler" ) func main() { auth := myOAuthBearerAuthenticator{ host: "example.com", port: 143, signature: []byte("SiGNeD_By_auTHoRiTy"), } mech := sasler.OAuthBearerServer(&auth) // OAUTHBEARER expects one message from the client _, err := mech.Data([]byte("n,\x01host=example.com\x01port=143\x01auth=Bearer username,SiGNeD_By_auTHoRiTy\x01\x01")) if err != nil { fmt.Println(err) } // Retrieve authorized identity completed, authz := mech.HasCompleted() if completed { fmt.Println("Authorized identity:", authz) } } // myOAuthBearerAuthentication is an example OAuthBearerAuthentication that // accepts only authentication requets for a specific host and port, signed by // a static signature. type myOAuthBearerAuthenticator struct { host string port int signature []byte } // VerifyToken verifies the host, port and signature of the provided token. func (a *myOAuthBearerAuthenticator) VerifyToken(token []byte, host string, port int) bool { signature := bytes.Split(token, []byte(","))[1] return host == a.host && port == a.port && bytes.Equal(signature, a.signature) } // DeriveAuthz derives an authz from the token. func (a *myOAuthBearerAuthenticator) DeriveAuthz(token []byte) string { return string(bytes.Split(token, []byte(","))[0]) } // Authorize checks whether the authn in the token matches the authz. This // implementation also allows anyone authenticated as Admin to be authorized // for any identity. func (a *myOAuthBearerAuthenticator) Authorize(authz string, token []byte) bool { authn := string(bytes.Split(token, []byte(","))[0]) return authz == authn || authn == "Admin" }
Output: Authorized identity: username
type PlainAuthenticator ¶
type PlainAuthenticator interface { // VerifyPasswd verifies whether the supplied combination of authn and passwd // is valid. Return false to fail authentication. VerifyPasswd(authn string, passwd []byte) bool // DerivceAuthz derives an authz from an authn. It is only called when no // authz has been requested by the client. Return the empty string if no // authz can be derived from the supplied authn. DeriveAuthz(authn string) string // Authorize verifies whether an authn is authorized to use the requested or // derived authz. Return false to fail authorization. Authorize(authz, authn string) bool }
PlainAuthenticator is supplied to PlainServer to implement password verification, authz derivation and authorization checking.
Example ¶
package main import ( "bytes" "fmt" "github.com/phedny/sasler" ) func main() { auth := myPlainAuthenticator{ user: "user", passwd: []byte("pencil"), } mech := sasler.PlainServer(&auth) // PLAIN expects one message from the client _, err := mech.Data([]byte("\x00user\x00pencil")) if err != nil { fmt.Println(err) } // Retrieve authorized identity completed, authz := mech.HasCompleted() if completed { fmt.Println("Authorized identity:", authz) } } // myPlainAuthenticator is an example PlainAuthenticator that accepts only a // single username/password combination. type myPlainAuthenticator struct { user string passwd []byte } // VerifyPasswd verifies the username and password. func (a *myPlainAuthenticator) VerifyPasswd(authn string, passwd []byte) bool { return authn == a.user && bytes.Equal(passwd, a.passwd) } // DeriveAuthz derives an authz from an authn. func (a *myPlainAuthenticator) DeriveAuthz(authn string) string { return authn } // Authorize checks whether the authn is authorized to act on behalf of the // authz. This implementation also allows anyone authenticatd as Admin to be // authorized for any identity. func (a *myPlainAuthenticator) Authorize(authz, authn string) bool { return authz == authn || authn == "Admin" }
Output: Authorized identity: user
type ScramAuthenticator ¶
type ScramAuthenticator interface { // GetCredentials returns the credentials for an authn, or an error if the // credentials could not be retrieved. The salt and iCount are parameters for // the SCRAM algorithm. If isSalted is true, passwd is salted using the salt // and iCount parameters. If isSalted is false, passwd is return plaintext. // It is advised to store salted passwords and therefore implementations // should always return a salted password and isSalted = true. GetCredentials(authn string) (passwd []byte, isSalted bool, salt []byte, iCount int, err error) // DeriveAuthz derives an authz from an authn. It is only called when no // authz has been requested by the client. Return the empty string if no // authz can be derived from the supplied authn. DeriveAuthz(authn string) string // Authorize verifies whether an authn is authorized to use the requested or // derived authz. Return false to fail authorization. Authorize(authz, authn string) bool }
ScramAuthenticator is passed to ScramSha1Server or ScramSha256Server to implement credential retrieval, authz derivation and authorization checking.
Example ¶
package main import ( "errors" "fmt" "github.com/phedny/sasler" ) func main() { // ScramClientConn() represents a function that returns a client that has // requested authentication using the SCRAM-SHA-1 mechanism. conn := ScramClientConn() auth := myScramAuthenticator{} mech, err := sasler.ScramSha1Server(&auth) if err != nil { fmt.Println(err) return } // SCRAM-* requires a couple of messages to be exchanged between client and // server. for { data, err := mech.Data(conn.Read()) if err != nil { // Abort() represents a function that uses a protocol-specific way to // signal to the client that authentication has failed. conn.Abort() fmt.Println(err) return } // Relay data from mech to client if data != nil { conn.Write(data) } // Test if the conversation has completed and show the authz completed, authz := mech.HasCompleted() if completed { // Success() represents a function that uses a protocol-specific way to // signal to the client that authentication has succeeded. conn.Success() fmt.Println("Authorized identity:", authz) return } } } // myScramAuthenticator is an example ScramAuthenticator that accepts only a // single username/password combination. type myScramAuthenticator struct{} // GetCredentials returns the credentials that belong to the requested authn. func (a *myScramAuthenticator) GetCredentials(authn string) (passwd []byte, isSalted bool, salt []byte, iCount int, err error) { if authn != "user" { return nil, false, nil, 0, errors.New("unknown authn") } // passwd is stored in a database in salted form, which is recommended. passwd = []byte("\x1d\x96\xee:R\x9bZ_\x9eG\xc0\x1f\"\x9a,\xb8\xa6\xe1_}") // If a plaintext password is returned, return isSalted = false. isSalted = true // Both salt and iCount must be retrieved from database if isSalted = true, // otherwise they might be random. If salts are stored in the database, it is // recommended to use a different salt for every entry. salt = []byte("A%\xc2G\xe4:\xb1\xe9<m\xffv") iCount = 4096 return } // DeriveAuthz derives an authz from an authn. func (a *myScramAuthenticator) DeriveAuthz(authn string) string { return authn } // Authorize checks whether the authn is authorized to act on behalf of the // authz. This implementation also allows anyone authenticated as Admin to be // authorized for any identity. func (a *myScramAuthenticator) Authorize(authz, authn string) bool { return authz == authn || authn == "Admin" }
Output: Authorized identity: user
type ServerMech ¶
type ServerMech interface { // Mech returns the full name of the mechanism as described in its // specification, and a bool that is true when this is a client-first // mechanism. Mech() (string, bool) // Data must be called each time SASL data arrived from the other party, // providing the bytes of the message. It returns the bytes of the message // that must be returned to the other party, or an error when authentication // failed and must be aborted. If the returned []byte is nil and no error is // returned, authentication has finished successfully. // // On a client-first mechanism, the first call to Data must be done with the // initial response received from the client. On a server-first mechanism, // the first call to Data must be done with nil or a zero length slice. Data(data []byte) ([]byte, error) // HasCompleted returns (true, authz) if the authentication proccess has // completed successfully, or (true, "") if it has failed, or (false, "") if // it's still in progress. HasCompleted() (bool, string) }
ServerMech describes the functions that are implemented by the server-side implementation of a SASL mechanism.
Example ¶
// ClientConn() represents retrieving a connection with a client that // initiated SASL authentication, including the mechanism name it requested. mechName, conn := ClientConn() // CreateMechanism() represents a function that returns a ServerMech based on // the requested mechanism name. mech := CreateMechanism(mechName) _, clientFirst := mech.Mech() // For server-first mechanisms, send the first message if !clientFirst { data, err := mech.Data(nil) if err != nil { fmt.Println("Authentication aborted.") return } conn.Write(Data, data) } // Relay authentication data until error or completion for { data := conn.Read() data, err := mech.Data(data) if err != nil { conn.Write(Failure, nil) switch err { case sasler.ErrAuthenticationFailed: fmt.Println("Authentication failed.") case sasler.ErrUnauthorized: fmt.Println("Unauthorized.") default: fmt.Println("Authentication aborted.", err) } return } if data != nil { conn.Write(Data, data) } completed, authz := mech.HasCompleted() if completed { conn.Write(Success, nil) fmt.Println("Authentication succeeded, authorised id:", authz) return } }
Output: Authentication succeeded, authorised id: username
func AnonymousServer ¶
func AnonymousServer(authz string, auth AnonymousAuthenticator) ServerMech
AnonymousServer returns a ServerMech implementation for the ANONYMOUS mechanism, as specified in RFC 4505.
func EcdsaNist256pChallengeServer ¶
func EcdsaNist256pChallengeServer(auth EcdsaAuthenticator) ServerMech
EcdsaNist256pChallengeServer returns a SaslMech implementation for the ECDSA-NIST256P-CHALLENGE mechanism, as specified in ecdsatool.
func ExternalServer ¶
func ExternalServer(auth ExternalAuthenticator) ServerMech
ExternalServer returns a ServerMech implementation for the EXTERNAL mechanism, as specified in RFC 4422, appendix A.
func OAuthBearerServer ¶
func OAuthBearerServer(auth OAuthBearerAuthenticator) ServerMech
OAuthBearerServer returns a ServerMech implementation for the OAUTHBEARER mechanism, as specified in RFC 7628.
func PlainServer ¶
func PlainServer(auth PlainAuthenticator) ServerMech
PlainServer returns a ServerMech implementation for the PLAIN mechanism, as specified in RFC 4616.
func ScramSha1Server ¶
func ScramSha1Server(auth ScramAuthenticator) (ServerMech, error)
ScramSha1Server returns a server-side SaslMech implementation for the SCRAM-SHA-1 mechanism, as specified in RFC 5802. Returns an error when generating a random server nonce failed.
func ScramSha256Server ¶
func ScramSha256Server(auth ScramAuthenticator) (ServerMech, error)
ScramSha256Server returns a server-side SaslMech implementation for the SCRAM-SHA-256 mechanism, as specified in RFC 7677. Returns an error when generating a random server nonce failed.