21 "golang.org/x/text/secure/precis"
23 "github.com/mjl-/mox/imapclient"
24 "github.com/mjl-/mox/mox-"
25 "github.com/mjl-/mox/scram"
26 "github.com/mjl-/mox/store"
29func TestAuthenticateLogin(t *testing.T) {
30 // NFD username and PRECIS-cleaned password.
32 tc.client.Login("mo\u0301x@mox.example", password1)
36func TestAuthenticatePlain(t *testing.T) {
39 tc.transactf("no", "authenticate bogus ")
40 tc.transactf("bad", "authenticate plain not base64...")
41 tc.transactf("no", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("\u0000baduser\u0000badpass")))
42 tc.xcodeWord("AUTHENTICATIONFAILED")
43 tc.transactf("no", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("\u0000mjl@mox.example\u0000badpass")))
44 tc.xcodeWord("AUTHENTICATIONFAILED")
45 tc.transactf("no", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("\u0000mjl\u0000badpass"))) // Need email, not account.
46 tc.xcodeWord("AUTHENTICATIONFAILED")
47 tc.transactf("no", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("\u0000mjl@mox.example\u0000test")))
48 tc.xcodeWord("AUTHENTICATIONFAILED")
49 tc.transactf("no", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("\u0000mjl@mox.example\u0000test"+password0)))
50 tc.xcodeWord("AUTHENTICATIONFAILED")
51 tc.transactf("bad", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("\u0000")))
53 tc.transactf("no", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("other\u0000mjl@mox.example\u0000"+password0)))
54 tc.xcodeWord("AUTHORIZATIONFAILED")
55 tc.transactf("ok", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("\u0000mjl@mox.example\u0000"+password0)))
59 tc.transactf("ok", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("mjl@mox.example\u0000mjl@mox.example\u0000"+password0)))
62 // NFD username and PRECIS-cleaned password.
64 tc.transactf("ok", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("mo\u0301x@mox.example\u0000mo\u0301x@mox.example\u0000"+password1)))
68 tc.client.AuthenticatePlain("mjl@mox.example", password0)
74 tc.cmdf("", "authenticate plain")
75 tc.readprefixline("+ ")
76 tc.writelinef("*") // Aborts.
79 tc.cmdf("", "authenticate plain")
80 tc.readprefixline("+ ")
81 tc.writelinef("%s", base64.StdEncoding.EncodeToString([]byte("\u0000mjl@mox.example\u0000"+password0)))
85func TestLoginDisabled(t *testing.T) {
89 acc, err := store.OpenAccount(pkglog, "disabled", false)
90 tcheck(t, err, "open account")
91 err = acc.SetPassword(pkglog, "test1234")
92 tcheck(t, err, "set password")
94 tcheck(t, err, "close account")
96 tc.transactf("no", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("\u0000disabled@mox.example\u0000test1234")))
98 tc.transactf("no", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("\u0000disabled@mox.example\u0000bogus")))
99 tc.xcodeWord("AUTHENTICATIONFAILED")
101 tc.transactf("no", "login disabled@mox.example test1234")
103 tc.transactf("no", "login disabled@mox.example bogus")
104 tc.xcodeWord("AUTHENTICATIONFAILED")
107func TestAuthenticateSCRAMSHA1(t *testing.T) {
108 testAuthenticateSCRAM(t, false, "SCRAM-SHA-1", sha1.New)
111func TestAuthenticateSCRAMSHA256(t *testing.T) {
112 testAuthenticateSCRAM(t, false, "SCRAM-SHA-256", sha256.New)
115func TestAuthenticateSCRAMSHA1PLUS(t *testing.T) {
116 testAuthenticateSCRAM(t, true, "SCRAM-SHA-1-PLUS", sha1.New)
119func TestAuthenticateSCRAMSHA256PLUS(t *testing.T) {
120 testAuthenticateSCRAM(t, true, "SCRAM-SHA-256-PLUS", sha256.New)
123func testAuthenticateSCRAM(t *testing.T, tls bool, method string, h func() hash.Hash) {
124 tc := startArgs(t, false, true, tls, true, true, "mjl")
125 tc.client.AuthenticateSCRAM(method, h, "mjl@mox.example", password0)
128 auth := func(status string, serverFinalError error, username, password string) {
131 noServerPlus := false
132 sc := scram.NewClient(h, username, "", noServerPlus, tc.client.TLSConnectionState())
133 clientFirst, err := sc.ClientFirst()
134 tc.check(err, "scram clientFirst")
135 tc.client.WriteCommandf("", "authenticate %s %s", method, base64.StdEncoding.EncodeToString([]byte(clientFirst)))
137 xreadContinuation := func() []byte {
138 line, err := tc.client.ReadContinuation()
139 tcheck(t, err, "read continuation")
140 buf, err := base64.StdEncoding.DecodeString(line)
141 tc.check(err, "parsing base64 from remote")
145 serverFirst := xreadContinuation()
146 clientFinal, err := sc.ServerFirst(serverFirst, password)
147 tc.check(err, "scram clientFinal")
148 tc.writelinef("%s", base64.StdEncoding.EncodeToString([]byte(clientFinal)))
150 serverFinal := xreadContinuation()
151 err = sc.ServerFinal(serverFinal)
152 if serverFinalError == nil {
153 tc.check(err, "scram serverFinal")
154 } else if err == nil || !errors.Is(err, serverFinalError) {
155 t.Fatalf("server final, got err %#v, expected %#v", err, serverFinalError)
157 if serverFinalError != nil {
162 resp, err := tc.client.ReadResponse()
163 tc.check(err, "read response")
164 if string(resp.Status) != strings.ToUpper(status) {
165 tc.t.Fatalf("got status %q, expected %q", resp.Status, strings.ToUpper(status))
169 tc = startArgs(t, false, true, tls, true, true, "mjl")
170 auth("no", scram.ErrInvalidProof, "mjl@mox.example", "badpass")
171 auth("no", scram.ErrInvalidProof, "mjl@mox.example", "")
172 // todo: server aborts due to invalid username. we should probably make client continue with fake determinisitically generated salt and result in error in the end.
173 // auth("no", nil, "other@mox.example", password0)
175 tc.transactf("no", "authenticate bogus ")
176 tc.transactf("bad", "authenticate %s not base64...", method)
177 tc.transactf("no", "authenticate %s %s", method, base64.StdEncoding.EncodeToString([]byte("bad data")))
179 // NFD username, with PRECIS-cleaned password.
180 auth("ok", nil, "mo\u0301x@mox.example", password1)
185func TestAuthenticateCRAMMD5(t *testing.T) {
186 tc := start(t, false)
188 tc.transactf("no", "authenticate bogus ")
189 tc.transactf("bad", "authenticate CRAM-MD5 not base64...")
190 tc.transactf("bad", "authenticate CRAM-MD5 %s", base64.StdEncoding.EncodeToString([]byte("baddata")))
191 tc.transactf("bad", "authenticate CRAM-MD5 %s", base64.StdEncoding.EncodeToString([]byte("bad data")))
193 auth := func(status string, username, password string) {
196 tc.client.WriteCommandf("", "authenticate CRAM-MD5")
198 xreadContinuation := func() []byte {
199 line, err := tc.client.ReadContinuation()
200 tcheck(t, err, "read continuation")
201 buf, err := base64.StdEncoding.DecodeString(line)
202 tc.check(err, "parsing base64 from remote")
206 chal := xreadContinuation()
207 pw, err := precis.OpaqueString.String(password)
211 h := hmac.New(md5.New, []byte(password))
212 h.Write([]byte(chal))
213 data := fmt.Sprintf("%s %x", username, h.Sum(nil))
214 tc.writelinef("%s", base64.StdEncoding.EncodeToString([]byte(data)))
216 resp, err := tc.client.ReadResponse()
217 tc.check(err, "read response")
218 if string(resp.Status) != strings.ToUpper(status) {
219 tc.t.Fatalf("got status %q, expected %q", resp.Status, strings.ToUpper(status))
223 auth("no", "mjl@mox.example", "badpass")
224 auth("no", "mjl@mox.example", "")
225 auth("no", "other@mox.example", password0)
227 auth("ok", "mjl@mox.example", password0)
231 // NFD username, with PRECIS-cleaned password.
233 auth("ok", "mo\u0301x@mox.example", password1)
237func TestAuthenticateTLSClientCert(t *testing.T) {
238 tc := startArgsMore(t, false, true, true, nil, nil, true, true, "mjl", nil)
239 tc.transactf("no", "authenticate external ") // No TLS auth.
242 // Create a certificate, register its public key with account, and make a tls
243 // client config that sends the certificate.
244 clientCert0 := fakeCert(t, true)
245 clientConfig := tls.Config{
246 InsecureSkipVerify: true,
247 Certificates: []tls.Certificate{clientCert0},
250 tlspubkey, err := store.ParseTLSPublicKeyCert(clientCert0.Certificate[0])
251 tcheck(t, err, "parse certificate")
252 tlspubkey.Account = "mjl"
253 tlspubkey.LoginAddress = "mjl@mox.example"
254 tlspubkey.NoIMAPPreauth = true
256 addClientCert := func() error {
257 return store.TLSPublicKeyAdd(ctxbg, &tlspubkey)
260 // No preauth, explicit authenticate with TLS.
261 tc = startArgsMore(t, false, true, true, nil, &clientConfig, false, true, "mjl", addClientCert)
262 if tc.client.Preauth {
263 t.Fatalf("preauthentication while not configured for tls public key")
265 tc.transactf("ok", "authenticate external ")
268 // External with explicit username.
269 tc = startArgsMore(t, false, true, true, nil, &clientConfig, false, true, "mjl", addClientCert)
270 if tc.client.Preauth {
271 t.Fatalf("preauthentication while not configured for tls public key")
273 tc.transactf("ok", "authenticate external %s", base64.StdEncoding.EncodeToString([]byte("mjl@mox.example")))
276 // No preauth, also allow other mechanisms.
277 tc = startArgsMore(t, false, true, true, nil, &clientConfig, false, true, "mjl", addClientCert)
278 tc.transactf("ok", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("\u0000mjl@mox.example\u0000"+password0)))
281 // No preauth, also allow other username for same account.
282 tc = startArgsMore(t, false, true, true, nil, &clientConfig, false, true, "mjl", addClientCert)
283 tc.transactf("ok", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("\u0000móx@mox.example\u0000"+password0)))
286 // No preauth, other mechanism must be for same account.
287 acc, err := store.OpenAccount(pkglog, "other", false)
288 tcheck(t, err, "open account")
289 err = acc.SetPassword(pkglog, "test1234")
290 tcheck(t, err, "set password")
292 tcheck(t, err, "close account")
293 tc = startArgsMore(t, false, true, true, nil, &clientConfig, false, true, "mjl", addClientCert)
294 tc.transactf("no", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("\u0000other@mox.example\u0000test1234")))
297 // Starttls and external auth.
298 tc = startArgsMore(t, false, true, false, nil, &clientConfig, false, true, "mjl", addClientCert)
299 tc.client.StartTLS(&clientConfig)
300 tc.transactf("ok", "authenticate external =")
303 tlspubkey.NoIMAPPreauth = false
304 err = store.TLSPublicKeyUpdate(ctxbg, &tlspubkey)
305 tcheck(t, err, "update tls public key")
307 // With preauth, no authenticate command needed/allowed.
308 // Already set up tls session ticket cache, for next test.
309 serverConfig := tls.Config{
310 Certificates: []tls.Certificate{fakeCert(t, false)},
312 ctx, cancel := context.WithCancel(ctxbg)
314 mox.StartTLSSessionTicketKeyRefresher(ctx, pkglog, &serverConfig)
315 clientConfig.ClientSessionCache = tls.NewLRUClientSessionCache(10)
316 tc = startArgsMore(t, false, true, true, &serverConfig, &clientConfig, false, true, "mjl", addClientCert)
317 if !tc.client.Preauth {
318 t.Fatalf("not preauthentication while configured for tls public key")
320 cs := tc.conn.(*tls.Conn).ConnectionState()
322 t.Fatalf("tls connection was resumed")
324 tc.transactf("no", "authenticate external ") // Not allowed, already in authenticated state.
327 // Authentication works with TLS resumption.
328 tc = startArgsMore(t, false, true, true, &serverConfig, &clientConfig, false, true, "mjl", addClientCert)
329 if !tc.client.Preauth {
330 t.Fatalf("not preauthentication while configured for tls public key")
332 cs = tc.conn.(*tls.Conn).ConnectionState()
334 t.Fatalf("tls connection was not resumed")
336 // Check that operations that require an account work.
337 tc.client.Enable(imapclient.CapIMAP4rev2)
338 received, err := time.Parse(time.RFC3339, "2022-11-16T10:01:00+01:00")
339 tc.check(err, "parse time")
340 tc.client.Append("inbox", makeAppendTime(exampleMsg, received))
341 tc.client.Select("inbox")
344 // Authentication with unknown key should fail.
345 // todo: less duplication, change startArgs so this can be merged into it.
347 tcheck(t, err, "store close")
348 os.RemoveAll("../testdata/imap/data")
349 err = store.Init(ctxbg)
350 tcheck(t, err, "store init")
351 mox.ConfigStaticPath = filepath.FromSlash("../testdata/imap/mox.conf")
352 mox.MustLoadConfig(true, false)
353 switchStop := store.Switchboard()
356 serverConn, clientConn := net.Pipe()
357 defer clientConn.Close()
359 done := make(chan struct{})
360 defer func() { <-done }()
364 defer serverConn.Close()
365 serve("test", cid, &serverConfig, serverConn, true, false, false, "")
369 clientConfig.ClientSessionCache = nil
370 clientConn = tls.Client(clientConn, &clientConfig)
371 // note: It's not enough to do a handshake and check if that was successful. If the
372 // client cert is not acceptable, we only learn after the handshake, when the first
373 // data messages are exchanged.
374 buf := make([]byte, 100)
375 _, err = clientConn.Read(buf)
377 t.Fatalf("tls handshake with unknown client certificate succeeded")
379 if alert, ok := mox.AsTLSAlert(err); !ok || alert != 42 {
380 t.Fatalf("got err %#v, expected tls 'bad certificate' alert", err)