scram

package
v0.0.10 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 9, 2024 License: MIT Imports: 13 Imported by: 5

Documentation

Overview

Package scram implements the SCRAM-SHA-* SASL authentication mechanism, RFC 7677 and RFC 5802.

SCRAM-SHA-256 and SCRAM-SHA-1 allow a client to authenticate to a server using a password without handing plaintext password over to the server. The client also verifies the server knows (a derivative of) the password. Both the client and server side are implemented.

Example
package main

import (
	"crypto/sha256"
	"fmt"
	"log"

	"github.com/mjl-/mox/scram"
)

func main() {
	// Prepare credentials.
	//
	// The client normally remembers the password and uses it during authentication.
	//
	// The server sets the iteration count, generates a salt and uses the password once
	// to generate salted password hash. The salted password hash is used to
	// authenticate the client during authentication.
	iterations := 4096
	salt := scram.MakeRandom()
	password := "test1234"
	saltedPassword := scram.SaltPassword(sha256.New, password, salt, iterations)

	check := func(err error, msg string) {
		if err != nil {
			log.Fatalf("%s: %s", msg, err)
		}
	}

	// Make a new client for authenticating user mjl with SCRAM-SHA-256.
	username := "mjl"
	authz := ""
	client := scram.NewClient(sha256.New, username, authz, false, nil)
	clientFirst, err := client.ClientFirst()
	check(err, "client.ClientFirst")

	// Instantiate a new server with the initial message from the client.
	server, err := scram.NewServer(sha256.New, []byte(clientFirst), nil, false)
	check(err, "NewServer")

	// Generate first message from server to client, with a challenge.
	serverFirst, err := server.ServerFirst(iterations, salt)
	check(err, "server.ServerFirst")

	// Continue at client with first message from server, resulting in message from
	// client to server.
	clientFinal, err := client.ServerFirst([]byte(serverFirst), password)
	check(err, "client.ServerFirst")

	// Continue at server with message from client.
	// The server authenticates the client in this step.
	serverFinal, err := server.Finish([]byte(clientFinal), saltedPassword)
	if err != nil {
		fmt.Println("server does not accept client credentials")
	} else {
		fmt.Println("server has accepted client credentials")
	}

	// Finally, the client verifies that the server knows the salted password hash.
	err = client.ServerFinal([]byte(serverFinal))
	if err != nil {
		fmt.Println("client does not accept server")
	} else {
		fmt.Println("client has accepted server")
	}

}
Output:

server has accepted client credentials
client has accepted server

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrNorm     = errors.New("parameter not unicode normalized") // E.g. if client sends non-normalized username or authzid.
	ErrUnsafe   = errors.New("unsafe parameter")                 // E.g. salt, nonce too short, or too few iterations.
	ErrProtocol = errors.New("protocol error")                   // E.g. server responded with a nonce not prefixed by the client nonce.
)

Functions

func MakeRandom

func MakeRandom() []byte

MakeRandom returns a cryptographically random buffer for use as salt or as nonce.

func SaltPassword

func SaltPassword(h func() hash.Hash, password string, salt []byte, iterations int) []byte

SaltPassword returns a salted password.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client represents the client-side of a SCRAM-SHA-* authentication.

func NewClient

func NewClient(h func() hash.Hash, authc, authz string, noServerPlus bool, cs *tls.ConnectionState) *Client

NewClient returns a client for authentication authc, optionally for authorization with role authz, for the hash (sha1.New or sha256.New).

If noServerPlus is true, the client would like to have used the PLUS-variant, that binds the authentication attempt to the TLS connection, but the client did not see support for the PLUS variant announced by the server. Used during negotiation to detect possible MitM attempt.

If cs is not nil, the SCRAM PLUS-variant is negotiated, with channel binding to the unique TLS connection, either using "tls-exporter" for TLS 1.3 and later, or "tls-unique" otherwise.

If cs is nil, no channel binding is done. If noServerPlus is also false, the client is configured to not attempt/"support" the PLUS-variant, ensuring servers that do support the PLUS-variant do not abort the connection.

The sequence for data and calls on a client:

  • ClientFirst, write result to server.
  • Read response from server, feed to ServerFirst, write response to server.
  • Read response from server, feed to ServerFinal.

func (*Client) ClientFirst

func (c *Client) ClientFirst() (clientFirst string, rerr error)

ClientFirst returns the first client message to write to the server. No channel binding is done/supported. A random nonce is generated.

func (*Client) ServerFinal

func (c *Client) ServerFinal(serverFinal []byte) (rerr error)

ServerFinal processes the final message from the server, verifying that the server knows the password.

func (*Client) ServerFirst

func (c *Client) ServerFirst(serverFirst []byte, password string) (clientFinal string, rerr error)

ServerFirst processes the first response message from the server. The provided nonce, salt and iterations are checked. If valid, a final client message is calculated and returned. This message must be written to the server. It includes proof that the client knows the password.

type Error

type Error string
var (
	ErrInvalidEncoding                 Error = "invalid-encoding"
	ErrExtensionsNotSupported          Error = "extensions-not-supported"
	ErrInvalidProof                    Error = "invalid-proof"
	ErrChannelBindingsDontMatch        Error = "channel-bindings-dont-match"
	ErrServerDoesSupportChannelBinding Error = "server-does-support-channel-binding"
	ErrChannelBindingNotSupported      Error = "channel-binding-not-supported"
	ErrUnsupportedChannelBindingType   Error = "unsupported-channel-binding-type"
	ErrUnknownUser                     Error = "unknown-user"
	ErrNoResources                     Error = "no-resources"
	ErrOtherError                      Error = "other-error"
)

Errors at scram protocol level. Can be exchanged between client and server.

func (Error) Error

func (e Error) Error() string

type Server

type Server struct {
	Authentication string // Username for authentication, "authc". Always set and non-empty.
	Authorization  string // If set, role of user to assume after authentication, "authz".
	// contains filtered or unexported fields
}

Server represents the server-side of a SCRAM-SHA-* authentication.

func NewServer

func NewServer(h func() hash.Hash, clientFirst []byte, cs *tls.ConnectionState, channelBindingRequired bool) (server *Server, rerr error)

NewServer returns a server given the first SCRAM message from a client.

If cs is set, the PLUS variant can be negotiated, binding the authentication exchange to the TLS channel (preventing MitM attempts). If a client indicates it supports the PLUS variant, but thinks the server does not, the authentication attempt will fail.

If channelBindingRequired is set, the client has indicated it will do channel binding and not doing so will cause the authentication to fail.

The sequence for data and calls on a server:

  • Read initial data from client, call NewServer (this call), then ServerFirst and write to the client.
  • Read response from client, call Finish or FinishFinal and write the resulting string.

func (*Server) Finish

func (s *Server) Finish(clientFinal []byte, saltedPassword []byte) (serverFinal string, rerr error)

Finish takes the final client message, and the salted password (probably from server storage), verifies the client, and returns a message to return to the client. If err is nil, authentication was successful. If the authorization requested is not acceptable, the server should call FinishError instead.

func (*Server) FinishError

func (s *Server) FinishError(err Error) string

FinishError returns an error message to write to the client for the final server message.

func (*Server) ServerFirst

func (s *Server) ServerFirst(iterations int, salt []byte) (string, error)

ServerFirst returns the string to send back to the client. To be called after NewServer.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL