1// Package scram implements the SCRAM-SHA-* SASL authentication mechanisms, including the PLUS variants, RFC 7677 and RFC 5802.
 
3// SCRAM-SHA-256 and SCRAM-SHA-1 allow a client to authenticate to a server using a
 
4// password without handing plaintext password over to the server. The client also
 
5// verifies the server knows (a derivative of) the password. The *-PLUS variants
 
6// bind the authentication exchange to the TLS session, preventing MitM attempts.
 
7// Both the client and server side are implemented.
 
10// todo: test with messages that contains extensions
 
11// todo: some tests for the parser
 
12// todo: figure out how invalid parameters etc should be handled. just abort? perhaps mostly a problem for imap.
 
17	cryptorand "crypto/rand"
 
25	"golang.org/x/crypto/pbkdf2"
 
26	"golang.org/x/text/secure/precis"
 
27	"golang.org/x/text/unicode/norm"
 
30// Errors at scram protocol level. Can be exchanged between client and server.
 
32	ErrInvalidEncoding                 Error = "invalid-encoding"
 
33	ErrExtensionsNotSupported          Error = "extensions-not-supported"
 
34	ErrInvalidProof                    Error = "invalid-proof"
 
35	ErrChannelBindingsDontMatch        Error = "channel-bindings-dont-match"
 
36	ErrServerDoesSupportChannelBinding Error = "server-does-support-channel-binding"
 
37	ErrChannelBindingNotSupported      Error = "channel-binding-not-supported"
 
38	ErrUnsupportedChannelBindingType   Error = "unsupported-channel-binding-type"
 
39	ErrUnknownUser                     Error = "unknown-user"
 
40	ErrNoResources                     Error = "no-resources"
 
41	ErrOtherError                      Error = "other-error"
 
44var scramErrors = makeErrors()
 
46func makeErrors() map[string]Error {
 
49		ErrExtensionsNotSupported,
 
51		ErrChannelBindingsDontMatch,
 
52		ErrServerDoesSupportChannelBinding,
 
53		ErrChannelBindingNotSupported,
 
54		ErrUnsupportedChannelBindingType,
 
59	m := map[string]Error{}
 
67	ErrNorm     = errors.New("parameter not unicode normalized") // E.g. if client sends non-normalized username or authzid.
 
68	ErrUnsafe   = errors.New("unsafe parameter")                 // E.g. salt, nonce too short, or too few iterations.
 
69	ErrProtocol = errors.New("protocol error")                   // E.g. server responded with a nonce not prefixed by the client nonce.
 
74func (e Error) Error() string {
 
78// MakeRandom returns a cryptographically random buffer for use as salt or as
 
80func MakeRandom() []byte {
 
81	buf := make([]byte, 12)
 
82	_, err := cryptorand.Read(buf)
 
84		panic("generate random")
 
89// Cleanup password with precis, like remote should have done. If the password
 
90// appears invalid, we'll return the original, there is a chance the server also
 
92func precisPassword(password string) string {
 
93	pw, err := precis.OpaqueString.String(password)
 
100// SaltPassword returns a salted password.
 
101func SaltPassword(h func() hash.Hash, password string, salt []byte, iterations int) []byte {
 
102	password = precisPassword(password)
 
103	return pbkdf2.Key([]byte(password), salt, iterations, h().Size(), h)
 
106// hmac0 returns the hmac with key over msg.
 
107func hmac0(h func() hash.Hash, key []byte, msg string) []byte {
 
108	mac := hmac.New(h, key)
 
109	mac.Write([]byte(msg))
 
113func xor(a, b []byte) {
 
119func channelBindData(cs *tls.ConnectionState) ([]byte, error) {
 
120	if cs.Version <= tls.VersionTLS12 {
 
121		if cs.TLSUnique == nil {
 
122			return nil, fmt.Errorf("no channel binding data available")
 
124		return cs.TLSUnique, nil
 
130	return cs.ExportKeyingMaterial("EXPORTER-Channel-Binding", []byte{}, 32)
 
133// Server represents the server-side of a SCRAM-SHA-* authentication.
 
135	Authentication string // Username for authentication, "authc". Always set and non-empty.
 
136	Authorization  string // If set, role of user to assume after authentication, "authz".
 
138	h func() hash.Hash // sha1.New or sha256.New
 
140	// Messages used in hash calculations.
 
141	clientFirstBare         string
 
143	clientFinalWithoutProof string
 
146	clientNonce         string // Client-part of the nonce.
 
147	serverNonceOverride string // If set, server does not generate random nonce, but uses this. For tests with the test vector.
 
148	nonce               string // Full client + server nonce.
 
149	channelBinding      []byte
 
152// NewServer returns a server given the first SCRAM message from a client.
 
154// If cs is set, the PLUS variant can be negotiated, binding the authentication
 
155// exchange to the TLS channel (preventing MitM attempts). If a client
 
156// indicates it supports the PLUS variant, but thinks the server does not, the
 
157// authentication attempt will fail.
 
159// If channelBindingRequired is set, the client has indicated it will do channel
 
160// binding and not doing so will cause the authentication to fail.
 
162// The sequence for data and calls on a server:
 
164//   - Read initial data from client, call NewServer (this call), then ServerFirst and write to the client.
 
165//   - Read response from client, call Finish or FinishFinal and write the resulting string.
 
166func NewServer(h func() hash.Hash, clientFirst []byte, cs *tls.ConnectionState, channelBindingRequired bool) (server *Server, rerr error) {
 
167	p := newParser(clientFirst)
 
168	defer p.recover(&rerr)
 
170	server = &Server{h: h}
 
173	gs2cbindFlag := p.xbyte()
 
174	switch gs2cbindFlag {
 
176		// Client does not support channel binding.
 
177		if channelBindingRequired {
 
178			p.xerrorf("channel binding is required when specifying scram plus: %w", ErrChannelBindingsDontMatch)
 
181		// Client supports channel binding but thinks we as server do not.
 
182		p.xerrorf("gs2 channel bind flag is y, client believes server does not support channel binding: %w", ErrServerDoesSupportChannelBinding)
 
184		// Use channel binding.
 
185		// It seems a cyrus-sasl client tells a server it is using the bare (non-PLUS)
 
186		// scram authentication mechanism, but then does use channel binding. It seems to
 
187		// use the server announcement of the plus variant only to learn the server
 
188		// supports channel binding.
 
190		cbname := p.xcbname()
 
191		// Assume the channel binding name is case-sensitive, and lower-case as used in
 
192		// examples. The ABNF rule accepts both lower and upper case. But the ABNF for
 
193		// attribute names also allows that, while the text claims they are case
 
198				p.xerrorf("no tls connection: %w", ErrChannelBindingsDontMatch)
 
199			} else if cs.Version >= tls.VersionTLS13 {
 
201				p.xerrorf("tls-unique not defined for tls 1.3 and later, use tls-exporter: %w", ErrChannelBindingsDontMatch)
 
202			} else if cs.TLSUnique == nil {
 
203				// As noted in the crypto/tls documentation.
 
204				p.xerrorf("no tls-unique channel binding value for this tls connection, possibly due to missing extended master key support and/or resumed connection: %w", ErrChannelBindingsDontMatch)
 
208				p.xerrorf("no tls connection: %w", ErrChannelBindingsDontMatch)
 
209			} else if cs.Version < tls.VersionTLS13 {
 
210				// Using tls-exporter with pre-1.3 TLS would require more precautions. Perhaps later.
 
212				p.xerrorf("tls-exporter with tls before 1.3 not implemented, use tls-unique: %w", ErrChannelBindingsDontMatch)
 
215			p.xerrorf("unknown parameter p %s: %w", cbname, ErrUnsupportedChannelBindingType)
 
217		cb, err := channelBindData(cs)
 
219			// We can pass back the error, it should never contain sensitive data, and only
 
220			// happen due to incorrect calling or a TLS config that is currently impossible
 
221			// (renegotiation enabled).
 
222			p.xerrorf("error fetching channel binding data: %v: %w", err, ErrOtherError)
 
224		server.channelBinding = cb
 
226		p.xerrorf("unrecognized gs2 channel bind flag")
 
230		server.Authorization = p.xauthzid()
 
231		if norm.NFC.String(server.Authorization) != server.Authorization {
 
232			return nil, fmt.Errorf("%w: authzid", ErrNorm)
 
236	server.gs2header = p.s[:p.o]
 
237	server.clientFirstBare = p.s[p.o:]
 
242		p.xerrorf("unexpected mandatory extension: %w", ErrExtensionsNotSupported) // 
../rfc/5802:973 
244	server.Authentication = p.xusername()
 
245	if norm.NFC.String(server.Authentication) != server.Authentication {
 
246		return nil, fmt.Errorf("%w: username", ErrNorm)
 
249	server.clientNonce = p.xnonce()
 
250	if len(server.clientNonce) < 8 {
 
251		return nil, fmt.Errorf("%w: client nonce too short", ErrUnsafe)
 
253	// Extensions, we don't recognize them.
 
261// ServerFirst returns the string to send back to the client. To be called after NewServer.
 
262func (s *Server) ServerFirst(iterations int, salt []byte) (string, error) {
 
264	serverNonce := s.serverNonceOverride
 
265	if serverNonce == "" {
 
266		serverNonce = base64.StdEncoding.EncodeToString(MakeRandom())
 
268	s.nonce = s.clientNonce + serverNonce
 
269	s.serverFirst = fmt.Sprintf("r=%s,s=%s,i=%d", s.nonce, base64.StdEncoding.EncodeToString(salt), iterations)
 
270	return s.serverFirst, nil
 
273// Finish takes the final client message, and the salted password (probably
 
274// from server storage), verifies the client, and returns a message to return
 
275// to the client. If err is nil, authentication was successful. If the
 
276// authorization requested is not acceptable, the server should call
 
277// FinishError instead.
 
278func (s *Server) Finish(clientFinal []byte, saltedPassword []byte) (serverFinal string, rerr error) {
 
279	p := newParser(clientFinal)
 
280	defer p.recover(&rerr)
 
282	// If there is any channel binding, and it doesn't match, this may be a
 
283	// MitM-attack. If the MitM would replace the channel binding, the signature
 
284	// calculated below would not match.
 
285	cbind := p.xchannelBinding()
 
286	cbindExp := append([]byte(s.gs2header), s.channelBinding...)
 
287	if !bytes.Equal(cbind, cbindExp) {
 
288		return "e=" + string(ErrChannelBindingsDontMatch), ErrChannelBindingsDontMatch
 
292	if nonce != s.nonce {
 
293		return "e=" + string(ErrInvalidProof), ErrInvalidProof
 
297		p.xattrval() // Ignored.
 
299	s.clientFinalWithoutProof = p.s[:p.o]
 
304	authMsg := s.clientFirstBare + "," + s.serverFirst + "," + s.clientFinalWithoutProof
 
306	clientKey := hmac0(s.h, saltedPassword, "Client Key")
 
309	storedKey := h.Sum(nil)
 
311	clientSig := hmac0(s.h, storedKey, authMsg)
 
312	xor(clientSig, clientKey) // Now clientProof.
 
313	if !bytes.Equal(clientSig, proof) {
 
314		return "e=" + string(ErrInvalidProof), ErrInvalidProof
 
317	serverKey := hmac0(s.h, saltedPassword, "Server Key")
 
318	serverSig := hmac0(s.h, serverKey, authMsg)
 
319	return fmt.Sprintf("v=%s", base64.StdEncoding.EncodeToString(serverSig)), nil
 
322// FinishError returns an error message to write to the client for the final
 
324func (s *Server) FinishError(err Error) string {
 
325	return "e=" + string(err)
 
328// Client represents the client-side of a SCRAM-SHA-* authentication.
 
333	h            func() hash.Hash     // sha1.New or sha256.New
 
334	noServerPlus bool                 // Server did not announce support for PLUS-variant.
 
335	cs           *tls.ConnectionState // If set, use PLUS-variant.
 
337	// Messages used in hash calculations.
 
338	clientFirstBare         string
 
340	clientFinalWithoutProof string
 
345	nonce           string // Full client + server nonce.
 
346	saltedPassword  []byte
 
347	channelBindData []byte // For PLUS-variant.
 
350// NewClient returns a client for authentication authc, optionally for
 
351// authorization with role authz, for the hash (sha1.New or sha256.New).
 
353// If noServerPlus is true, the client would like to have used the PLUS-variant,
 
354// that binds the authentication attempt to the TLS connection, but the client did
 
355// not see support for the PLUS variant announced by the server. Used during
 
356// negotiation to detect possible MitM attempt.
 
358// If cs is not nil, the SCRAM PLUS-variant is negotiated, with channel binding to
 
359// the unique TLS connection, either using "tls-exporter" for TLS 1.3 and later, or
 
360// "tls-unique" otherwise.
 
362// If cs is nil, no channel binding is done. If noServerPlus is also false, the
 
363// client is configured to not attempt/"support" the PLUS-variant, ensuring servers
 
364// that do support the PLUS-variant do not abort the connection.
 
366// The sequence for data and calls on a client:
 
368//   - ClientFirst, write result to server.
 
369//   - Read response from server, feed to ServerFirst, write response to server.
 
370//   - Read response from server, feed to ServerFinal.
 
371func NewClient(h func() hash.Hash, authc, authz string, noServerPlus bool, cs *tls.ConnectionState) *Client {
 
372	authc = norm.NFC.String(authc)
 
373	authz = norm.NFC.String(authz)
 
374	return &Client{authc: authc, authz: authz, h: h, noServerPlus: noServerPlus, cs: cs}
 
377// ClientFirst returns the first client message to write to the server.
 
378// No channel binding is done/supported.
 
379// A random nonce is generated.
 
380func (c *Client) ClientFirst() (clientFirst string, rerr error) {
 
381	if c.noServerPlus && c.cs != nil {
 
382		return "", fmt.Errorf("cannot set both claim channel binding is not supported, and use channel binding")
 
384	// The first byte of the gs2header indicates if/how channel binding should be used.
 
387		if c.cs.Version >= tls.VersionTLS13 {
 
388			c.gs2header = "p=tls-exporter"
 
390			c.gs2header = "p=tls-unique"
 
392		cbdata, err := channelBindData(c.cs)
 
394			return "", fmt.Errorf("get channel binding data: %v", err)
 
396		c.channelBindData = cbdata
 
397	} else if c.noServerPlus {
 
398		// We support it, but we think server does not. If server does support it, we may
 
399		// have been downgraded, and the server will tell us.
 
402		// We don't want to do channel binding.
 
405	c.gs2header += fmt.Sprintf(",%s,", saslname(c.authz))
 
406	if c.clientNonce == "" {
 
407		c.clientNonce = base64.StdEncoding.EncodeToString(MakeRandom())
 
409	c.clientFirstBare = fmt.Sprintf("n=%s,r=%s", saslname(c.authc), c.clientNonce)
 
410	return c.gs2header + c.clientFirstBare, nil
 
413// ServerFirst processes the first response message from the server. The
 
414// provided nonce, salt and iterations are checked. If valid, a final client
 
415// message is calculated and returned. This message must be written to the
 
416// server. It includes proof that the client knows the password.
 
417func (c *Client) ServerFirst(serverFirst []byte, password string) (clientFinal string, rerr error) {
 
418	c.serverFirst = string(serverFirst)
 
419	p := newParser(serverFirst)
 
420	defer p.recover(&rerr)
 
425		p.xerrorf("unsupported mandatory extension: %w", ErrExtensionsNotSupported) // 
../rfc/5802:973 
432	iterations := p.xiterations()
 
433	// We ignore extensions that we don't know about.
 
439	if !strings.HasPrefix(c.nonce, c.clientNonce) {
 
440		return "", fmt.Errorf("%w: server dropped our nonce", ErrProtocol)
 
442	if len(c.nonce)-len(c.clientNonce) < 8 {
 
443		return "", fmt.Errorf("%w: server nonce too short", ErrUnsafe)
 
446		return "", fmt.Errorf("%w: salt too short", ErrUnsafe)
 
448	if iterations < 2048 {
 
449		return "", fmt.Errorf("%w: too few iterations", ErrUnsafe)
 
452	// We send our channel binding data if present. If the server has different values,
 
453	// we'll get an error. If any MitM would try to modify the channel binding data,
 
454	// the server cannot verify our signature and will fail the attempt.
 
456	cbindInput := append([]byte(c.gs2header), c.channelBindData...)
 
457	c.clientFinalWithoutProof = fmt.Sprintf("c=%s,r=%s", base64.StdEncoding.EncodeToString(cbindInput), c.nonce)
 
459	c.authMessage = c.clientFirstBare + "," + c.serverFirst + "," + c.clientFinalWithoutProof
 
461	c.saltedPassword = SaltPassword(c.h, password, salt, iterations)
 
462	clientKey := hmac0(c.h, c.saltedPassword, "Client Key")
 
465	storedKey := h.Sum(nil)
 
466	clientSig := hmac0(c.h, storedKey, c.authMessage)
 
467	xor(clientSig, clientKey) // Now clientProof.
 
468	clientProof := clientSig
 
470	r := c.clientFinalWithoutProof + ",p=" + base64.StdEncoding.EncodeToString(clientProof)
 
474// ServerFinal processes the final message from the server, verifying that the
 
475// server knows the password.
 
476func (c *Client) ServerFinal(serverFinal []byte) (rerr error) {
 
477	p := newParser(serverFinal)
 
478	defer p.recover(&rerr)
 
482		var err error = scramErrors[errstr]
 
483		if err == Error("") {
 
484			err = errors.New(errstr)
 
486		return fmt.Errorf("error from server: %w", err)
 
489	verifier := p.xbase64()
 
491	serverKey := hmac0(c.h, c.saltedPassword, "Server Key")
 
492	serverSig := hmac0(c.h, serverKey, c.authMessage)
 
493	if !bytes.Equal(verifier, serverSig) {
 
494		return fmt.Errorf("incorrect server signature")
 
499// Convert "," to =2C and "=" to =3D.
 
500func saslname(s string) string {
 
502	for _, c := range s {