1// Package scram implements the SCRAM-SHA-* SASL authentication mechanism, RFC 7677 and RFC 5802.
2//
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. Both the client and
6// server side are implemented.
7package scram
8
9// todo: test with messages that contains extensions
10// todo: some tests for the parser
11// todo: figure out how invalid parameters etc should be handled. just abort? perhaps mostly a problem for imap.
12
13import (
14 "bytes"
15 "crypto/hmac"
16 cryptorand "crypto/rand"
17 "crypto/tls"
18 "encoding/base64"
19 "errors"
20 "fmt"
21 "hash"
22 "strings"
23
24 "golang.org/x/crypto/pbkdf2"
25 "golang.org/x/text/secure/precis"
26 "golang.org/x/text/unicode/norm"
27)
28
29// Errors at scram protocol level. Can be exchanged between client and server.
30var (
31 ErrInvalidEncoding Error = "invalid-encoding"
32 ErrExtensionsNotSupported Error = "extensions-not-supported"
33 ErrInvalidProof Error = "invalid-proof"
34 ErrChannelBindingsDontMatch Error = "channel-bindings-dont-match"
35 ErrServerDoesSupportChannelBinding Error = "server-does-support-channel-binding"
36 ErrChannelBindingNotSupported Error = "channel-binding-not-supported"
37 ErrUnsupportedChannelBindingType Error = "unsupported-channel-binding-type"
38 ErrUnknownUser Error = "unknown-user"
39 ErrNoResources Error = "no-resources"
40 ErrOtherError Error = "other-error"
41)
42
43var scramErrors = makeErrors()
44
45func makeErrors() map[string]Error {
46 l := []Error{
47 ErrInvalidEncoding,
48 ErrExtensionsNotSupported,
49 ErrInvalidProof,
50 ErrChannelBindingsDontMatch,
51 ErrServerDoesSupportChannelBinding,
52 ErrChannelBindingNotSupported,
53 ErrUnsupportedChannelBindingType,
54 ErrUnknownUser,
55 ErrNoResources,
56 ErrOtherError,
57 }
58 m := map[string]Error{}
59 for _, e := range l {
60 m[string(e)] = e
61 }
62 return m
63}
64
65var (
66 ErrNorm = errors.New("parameter not unicode normalized") // E.g. if client sends non-normalized username or authzid.
67 ErrUnsafe = errors.New("unsafe parameter") // E.g. salt, nonce too short, or too few iterations.
68 ErrProtocol = errors.New("protocol error") // E.g. server responded with a nonce not prefixed by the client nonce.
69)
70
71type Error string
72
73func (e Error) Error() string {
74 return string(e)
75}
76
77// MakeRandom returns a cryptographically random buffer for use as salt or as
78// nonce.
79func MakeRandom() []byte {
80 buf := make([]byte, 12)
81 _, err := cryptorand.Read(buf)
82 if err != nil {
83 panic("generate random")
84 }
85 return buf
86}
87
88// Cleanup password with precis, like remote should have done. If the password
89// appears invalid, we'll return the original, there is a chance the server also
90// doesn't enforce requirements and accepts it. ../rfc/8265:679
91func precisPassword(password string) string {
92 pw, err := precis.OpaqueString.String(password)
93 if err != nil {
94 return password
95 }
96 return pw
97}
98
99// SaltPassword returns a salted password.
100func SaltPassword(h func() hash.Hash, password string, salt []byte, iterations int) []byte {
101 password = precisPassword(password)
102 return pbkdf2.Key([]byte(password), salt, iterations, h().Size(), h)
103}
104
105// hmac0 returns the hmac with key over msg.
106func hmac0(h func() hash.Hash, key []byte, msg string) []byte {
107 mac := hmac.New(h, key)
108 mac.Write([]byte(msg))
109 return mac.Sum(nil)
110}
111
112func xor(a, b []byte) {
113 for i := range a {
114 a[i] ^= b[i]
115 }
116}
117
118func channelBindData(cs *tls.ConnectionState) ([]byte, error) {
119 if cs.Version <= tls.VersionTLS12 {
120 if cs.TLSUnique == nil {
121 return nil, fmt.Errorf("no channel binding data available")
122 }
123 return cs.TLSUnique, nil
124 }
125
126 // "tls-exporter", ../rfc/9266:95
127 // Since TLS 1.3, a zero-length and absent context have the same behaviour. ../rfc/8446:5385 ../rfc/8446:5405
128 // This is different from TLS 1.2 and earlier. ../rfc/5705:206 ../rfc/5705:245
129 return cs.ExportKeyingMaterial("EXPORTER-Channel-Binding", []byte{}, 32)
130}
131
132// Server represents the server-side of a SCRAM-SHA-* authentication.
133type Server struct {
134 Authentication string // Username for authentication, "authc". Always set and non-empty.
135 Authorization string // If set, role of user to assume after authentication, "authz".
136
137 h func() hash.Hash // sha1.New or sha256.New
138
139 // Messages used in hash calculations.
140 clientFirstBare string
141 serverFirst string
142 clientFinalWithoutProof string
143
144 gs2header string
145 clientNonce string // Client-part of the nonce.
146 serverNonceOverride string // If set, server does not generate random nonce, but uses this. For tests with the test vector.
147 nonce string // Full client + server nonce.
148 channelBinding []byte
149}
150
151// NewServer returns a server given the first SCRAM message from a client.
152//
153// If cs is set, the PLUS variant can be negotiated, binding the authentication
154// exchange to the TLS channel (preventing MitM attempts). If a client
155// indicates it supports the PLUS variant, but thinks the server does not, the
156// authentication attempt will fail.
157//
158// If channelBindingRequired is set, the client has indicated it will do channel
159// binding and not doing so will cause the authentication to fail.
160//
161// The sequence for data and calls on a server:
162//
163// - Read initial data from client, call NewServer (this call), then ServerFirst and write to the client.
164// - Read response from client, call Finish or FinishFinal and write the resulting string.
165func NewServer(h func() hash.Hash, clientFirst []byte, cs *tls.ConnectionState, channelBindingRequired bool) (server *Server, rerr error) {
166 p := newParser(clientFirst)
167 defer p.recover(&rerr)
168
169 server = &Server{h: h}
170
171 // ../rfc/5802:949 ../rfc/5802:910
172 gs2cbindFlag := p.xbyte()
173 switch gs2cbindFlag {
174 case 'n':
175 // Client does not support channel binding.
176 if channelBindingRequired {
177 p.xerrorf("channel binding is required when specifying scram plus: %w", ErrChannelBindingsDontMatch)
178 }
179 case 'y':
180 // Client supports channel binding but thinks we as server do not.
181 p.xerrorf("gs2 channel bind flag is y, client believes server does not support channel binding: %w", ErrServerDoesSupportChannelBinding)
182 case 'p':
183 // Use channel binding.
184 // It seems a cyrus-sasl client tells a server it is using the bare (non-PLUS)
185 // scram authentication mechanism, but then does use channel binding. It seems to
186 // use the server announcement of the plus variant only to learn the server
187 // supports channel binding.
188 p.xtake("=")
189 cbname := p.xcbname()
190 // Assume the channel binding name is case-sensitive, and lower-case as used in
191 // examples. The ABNF rule accepts both lower and upper case. But the ABNF for
192 // attribute names also allows that, while the text claims they are case
193 // sensitive... ../rfc/5802:547
194 switch cbname {
195 case "tls-unique":
196 if cs == nil {
197 p.xerrorf("no tls connection: %w", ErrChannelBindingsDontMatch)
198 } else if cs.Version >= tls.VersionTLS13 {
199 // ../rfc/9266:122
200 p.xerrorf("tls-unique not defined for tls 1.3 and later, use tls-exporter: %w", ErrChannelBindingsDontMatch)
201 } else if cs.TLSUnique == nil {
202 // As noted in the crypto/tls documentation.
203 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)
204 }
205 case "tls-exporter":
206 if cs == nil {
207 p.xerrorf("no tls connection: %w", ErrChannelBindingsDontMatch)
208 } else if cs.Version < tls.VersionTLS13 {
209 // Using tls-exporter with pre-1.3 TLS would require more precautions. Perhaps later.
210 // ../rfc/9266:201
211 p.xerrorf("tls-exporter with tls before 1.3 not implemented, use tls-unique: %w", ErrChannelBindingsDontMatch)
212 }
213 default:
214 p.xerrorf("unknown parameter p %s: %w", cbname, ErrUnsupportedChannelBindingType)
215 }
216 cb, err := channelBindData(cs)
217 if err != nil {
218 // We can pass back the error, it should never contain sensitive data, and only
219 // happen due to incorrect calling or a TLS config that is currently impossible
220 // (renegotiation enabled).
221 p.xerrorf("error fetching channel binding data: %v: %w", err, ErrOtherError)
222 }
223 server.channelBinding = cb
224 default:
225 p.xerrorf("unrecognized gs2 channel bind flag")
226 }
227 p.xtake(",")
228 if !p.take(",") {
229 server.Authorization = p.xauthzid()
230 if norm.NFC.String(server.Authorization) != server.Authorization {
231 return nil, fmt.Errorf("%w: authzid", ErrNorm)
232 }
233 p.xtake(",")
234 }
235 server.gs2header = p.s[:p.o]
236 server.clientFirstBare = p.s[p.o:]
237
238 // ../rfc/5802:632
239 // ../rfc/5802:946
240 if p.take("m=") {
241 p.xerrorf("unexpected mandatory extension: %w", ErrExtensionsNotSupported) // ../rfc/5802:973
242 }
243 server.Authentication = p.xusername()
244 if norm.NFC.String(server.Authentication) != server.Authentication {
245 return nil, fmt.Errorf("%w: username", ErrNorm)
246 }
247 p.xtake(",")
248 server.clientNonce = p.xnonce()
249 if len(server.clientNonce) < 8 {
250 return nil, fmt.Errorf("%w: client nonce too short", ErrUnsafe)
251 }
252 // Extensions, we don't recognize them.
253 for p.take(",") {
254 p.xattrval()
255 }
256 p.xempty()
257 return server, nil
258}
259
260// ServerFirst returns the string to send back to the client. To be called after NewServer.
261func (s *Server) ServerFirst(iterations int, salt []byte) (string, error) {
262 // ../rfc/5802:959
263 serverNonce := s.serverNonceOverride
264 if serverNonce == "" {
265 serverNonce = base64.StdEncoding.EncodeToString(MakeRandom())
266 }
267 s.nonce = s.clientNonce + serverNonce
268 s.serverFirst = fmt.Sprintf("r=%s,s=%s,i=%d", s.nonce, base64.StdEncoding.EncodeToString(salt), iterations)
269 return s.serverFirst, nil
270}
271
272// Finish takes the final client message, and the salted password (probably
273// from server storage), verifies the client, and returns a message to return
274// to the client. If err is nil, authentication was successful. If the
275// authorization requested is not acceptable, the server should call
276// FinishError instead.
277func (s *Server) Finish(clientFinal []byte, saltedPassword []byte) (serverFinal string, rerr error) {
278 p := newParser(clientFinal)
279 defer p.recover(&rerr)
280
281 // If there is any channel binding, and it doesn't match, this may be a
282 // MitM-attack. If the MitM would replace the channel binding, the signature
283 // calculated below would not match.
284 cbind := p.xchannelBinding()
285 cbindExp := append([]byte(s.gs2header), s.channelBinding...)
286 if !bytes.Equal(cbind, cbindExp) {
287 return "e=" + string(ErrChannelBindingsDontMatch), ErrChannelBindingsDontMatch
288 }
289 p.xtake(",")
290 nonce := p.xnonce()
291 if nonce != s.nonce {
292 return "e=" + string(ErrInvalidProof), ErrInvalidProof
293 }
294 for !p.peek(",p=") {
295 p.xtake(",")
296 p.xattrval() // Ignored.
297 }
298 s.clientFinalWithoutProof = p.s[:p.o]
299 p.xtake(",")
300 proof := p.xproof()
301 p.xempty()
302
303 authMsg := s.clientFirstBare + "," + s.serverFirst + "," + s.clientFinalWithoutProof
304
305 clientKey := hmac0(s.h, saltedPassword, "Client Key")
306 h := s.h()
307 h.Write(clientKey)
308 storedKey := h.Sum(nil)
309
310 clientSig := hmac0(s.h, storedKey, authMsg)
311 xor(clientSig, clientKey) // Now clientProof.
312 if !bytes.Equal(clientSig, proof) {
313 return "e=" + string(ErrInvalidProof), ErrInvalidProof
314 }
315
316 serverKey := hmac0(s.h, saltedPassword, "Server Key")
317 serverSig := hmac0(s.h, serverKey, authMsg)
318 return fmt.Sprintf("v=%s", base64.StdEncoding.EncodeToString(serverSig)), nil
319}
320
321// FinishError returns an error message to write to the client for the final
322// server message.
323func (s *Server) FinishError(err Error) string {
324 return "e=" + string(err)
325}
326
327// Client represents the client-side of a SCRAM-SHA-* authentication.
328type Client struct {
329 authc string
330 authz string
331
332 h func() hash.Hash // sha1.New or sha256.New
333 noServerPlus bool // Server did not announce support for PLUS-variant.
334 cs *tls.ConnectionState // If set, use PLUS-variant.
335
336 // Messages used in hash calculations.
337 clientFirstBare string
338 serverFirst string
339 clientFinalWithoutProof string
340 authMessage string
341
342 gs2header string
343 clientNonce string
344 nonce string // Full client + server nonce.
345 saltedPassword []byte
346 channelBindData []byte // For PLUS-variant.
347}
348
349// NewClient returns a client for authentication authc, optionally for
350// authorization with role authz, for the hash (sha1.New or sha256.New).
351//
352// If noServerPlus is true, the client would like to have used the PLUS-variant,
353// that binds the authentication attempt to the TLS connection, but the client did
354// not see support for the PLUS variant announced by the server. Used during
355// negotiation to detect possible MitM attempt.
356//
357// If cs is not nil, the SCRAM PLUS-variant is negotiated, with channel binding to
358// the unique TLS connection, either using "tls-exporter" for TLS 1.3 and later, or
359// "tls-unique" otherwise.
360//
361// If cs is nil, no channel binding is done. If noServerPlus is also false, the
362// client is configured to not attempt/"support" the PLUS-variant, ensuring servers
363// that do support the PLUS-variant do not abort the connection.
364//
365// The sequence for data and calls on a client:
366//
367// - ClientFirst, write result to server.
368// - Read response from server, feed to ServerFirst, write response to server.
369// - Read response from server, feed to ServerFinal.
370func NewClient(h func() hash.Hash, authc, authz string, noServerPlus bool, cs *tls.ConnectionState) *Client {
371 authc = norm.NFC.String(authc)
372 authz = norm.NFC.String(authz)
373 return &Client{authc: authc, authz: authz, h: h, noServerPlus: noServerPlus, cs: cs}
374}
375
376// ClientFirst returns the first client message to write to the server.
377// No channel binding is done/supported.
378// A random nonce is generated.
379func (c *Client) ClientFirst() (clientFirst string, rerr error) {
380 if c.noServerPlus && c.cs != nil {
381 return "", fmt.Errorf("cannot set both claim channel binding is not supported, and use channel binding")
382 }
383 // The first byte of the gs2header indicates if/how channel binding should be used.
384 // ../rfc/5802:903
385 if c.cs != nil {
386 if c.cs.Version >= tls.VersionTLS13 {
387 c.gs2header = "p=tls-exporter"
388 } else {
389 c.gs2header = "p=tls-unique"
390 }
391 cbdata, err := channelBindData(c.cs)
392 if err != nil {
393 return "", fmt.Errorf("get channel binding data: %v", err)
394 }
395 c.channelBindData = cbdata
396 } else if c.noServerPlus {
397 // We support it, but we think server does not. If server does support it, we may
398 // have been downgraded, and the server will tell us.
399 c.gs2header = "y"
400 } else {
401 // We don't want to do channel binding.
402 c.gs2header = "n"
403 }
404 c.gs2header += fmt.Sprintf(",%s,", saslname(c.authz))
405 if c.clientNonce == "" {
406 c.clientNonce = base64.StdEncoding.EncodeToString(MakeRandom())
407 }
408 c.clientFirstBare = fmt.Sprintf("n=%s,r=%s", saslname(c.authc), c.clientNonce)
409 return c.gs2header + c.clientFirstBare, nil
410}
411
412// ServerFirst processes the first response message from the server. The
413// provided nonce, salt and iterations are checked. If valid, a final client
414// message is calculated and returned. This message must be written to the
415// server. It includes proof that the client knows the password.
416func (c *Client) ServerFirst(serverFirst []byte, password string) (clientFinal string, rerr error) {
417 c.serverFirst = string(serverFirst)
418 p := newParser(serverFirst)
419 defer p.recover(&rerr)
420
421 // ../rfc/5802:632
422 // ../rfc/5802:959
423 if p.take("m=") {
424 p.xerrorf("unsupported mandatory extension: %w", ErrExtensionsNotSupported) // ../rfc/5802:973
425 }
426
427 c.nonce = p.xnonce()
428 p.xtake(",")
429 salt := p.xsalt()
430 p.xtake(",")
431 iterations := p.xiterations()
432 // We ignore extensions that we don't know about.
433 for p.take(",") {
434 p.xattrval()
435 }
436 p.xempty()
437
438 if !strings.HasPrefix(c.nonce, c.clientNonce) {
439 return "", fmt.Errorf("%w: server dropped our nonce", ErrProtocol)
440 }
441 if len(c.nonce)-len(c.clientNonce) < 8 {
442 return "", fmt.Errorf("%w: server nonce too short", ErrUnsafe)
443 }
444 if len(salt) < 8 {
445 return "", fmt.Errorf("%w: salt too short", ErrUnsafe)
446 }
447 if iterations < 2048 {
448 return "", fmt.Errorf("%w: too few iterations", ErrUnsafe)
449 }
450
451 // We send our channel binding data if present. If the server has different values,
452 // we'll get an error. If any MitM would try to modify the channel binding data,
453 // the server cannot verify our signature and will fail the attempt.
454 // ../rfc/5802:925 ../rfc/5802:1015
455 cbindInput := append([]byte(c.gs2header), c.channelBindData...)
456 c.clientFinalWithoutProof = fmt.Sprintf("c=%s,r=%s", base64.StdEncoding.EncodeToString(cbindInput), c.nonce)
457
458 c.authMessage = c.clientFirstBare + "," + c.serverFirst + "," + c.clientFinalWithoutProof
459
460 c.saltedPassword = SaltPassword(c.h, password, salt, iterations)
461 clientKey := hmac0(c.h, c.saltedPassword, "Client Key")
462 h := c.h()
463 h.Write(clientKey)
464 storedKey := h.Sum(nil)
465 clientSig := hmac0(c.h, storedKey, c.authMessage)
466 xor(clientSig, clientKey) // Now clientProof.
467 clientProof := clientSig
468
469 r := c.clientFinalWithoutProof + ",p=" + base64.StdEncoding.EncodeToString(clientProof)
470 return r, nil
471}
472
473// ServerFinal processes the final message from the server, verifying that the
474// server knows the password.
475func (c *Client) ServerFinal(serverFinal []byte) (rerr error) {
476 p := newParser(serverFinal)
477 defer p.recover(&rerr)
478
479 if p.take("e=") {
480 errstr := p.xvalue()
481 var err error = scramErrors[errstr]
482 if err == Error("") {
483 err = errors.New(errstr)
484 }
485 return fmt.Errorf("error from server: %w", err)
486 }
487 p.xtake("v=")
488 verifier := p.xbase64()
489
490 serverKey := hmac0(c.h, c.saltedPassword, "Server Key")
491 serverSig := hmac0(c.h, serverKey, c.authMessage)
492 if !bytes.Equal(verifier, serverSig) {
493 return fmt.Errorf("incorrect server signature")
494 }
495 return nil
496}
497
498// Convert "," to =2C and "=" to =3D.
499func saslname(s string) string {
500 var r string
501 for _, c := range s {
502 if c == ',' {
503 r += "=2C"
504 } else if c == '=' {
505 r += "=3D"
506 } else {
507 r += string(c)
508 }
509 }
510 return r
511}
512