15 "golang.org/x/text/secure/precis"
17 "github.com/mjl-/mox/scram"
20func TestAuthenticateLogin(t *testing.T) {
21 // NFD username and PRECIS-cleaned password.
23 tc.client.Login("mo\u0301x@mox.example", password1)
27func TestAuthenticatePlain(t *testing.T) {
30 tc.transactf("no", "authenticate bogus ")
31 tc.transactf("bad", "authenticate plain not base64...")
32 tc.transactf("no", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("\u0000baduser\u0000badpass")))
33 tc.xcode("AUTHENTICATIONFAILED")
34 tc.transactf("no", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("\u0000mjl@mox.example\u0000badpass")))
35 tc.xcode("AUTHENTICATIONFAILED")
36 tc.transactf("no", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("\u0000mjl\u0000badpass"))) // Need email, not account.
37 tc.xcode("AUTHENTICATIONFAILED")
38 tc.transactf("no", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("\u0000mjl@mox.example\u0000test")))
39 tc.xcode("AUTHENTICATIONFAILED")
40 tc.transactf("no", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("\u0000mjl@mox.example\u0000test"+password0)))
41 tc.xcode("AUTHENTICATIONFAILED")
42 tc.transactf("bad", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("\u0000")))
44 tc.transactf("no", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("other\u0000mjl@mox.example\u0000"+password0)))
45 tc.xcode("AUTHORIZATIONFAILED")
46 tc.transactf("ok", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("\u0000mjl@mox.example\u0000"+password0)))
50 tc.transactf("ok", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("mjl@mox.example\u0000mjl@mox.example\u0000"+password0)))
53 // NFD username and PRECIS-cleaned password.
55 tc.transactf("ok", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("mo\u0301x@mox.example\u0000mo\u0301x@mox.example\u0000"+password1)))
59 tc.client.AuthenticatePlain("mjl@mox.example", password0)
65 tc.cmdf("", "authenticate plain")
66 tc.readprefixline("+ ")
67 tc.writelinef("*") // Aborts.
70 tc.cmdf("", "authenticate plain")
71 tc.readprefixline("+ ")
72 tc.writelinef("%s", base64.StdEncoding.EncodeToString([]byte("\u0000mjl@mox.example\u0000"+password0)))
76func TestAuthenticateSCRAMSHA1(t *testing.T) {
77 testAuthenticateSCRAM(t, false, "SCRAM-SHA-1", sha1.New)
80func TestAuthenticateSCRAMSHA256(t *testing.T) {
81 testAuthenticateSCRAM(t, false, "SCRAM-SHA-256", sha256.New)
84func TestAuthenticateSCRAMSHA1PLUS(t *testing.T) {
85 testAuthenticateSCRAM(t, true, "SCRAM-SHA-1-PLUS", sha1.New)
88func TestAuthenticateSCRAMSHA256PLUS(t *testing.T) {
89 testAuthenticateSCRAM(t, true, "SCRAM-SHA-256-PLUS", sha256.New)
92func testAuthenticateSCRAM(t *testing.T, tls bool, method string, h func() hash.Hash) {
93 tc := startArgs(t, true, tls, true, true, "mjl")
94 tc.client.AuthenticateSCRAM(method, h, "mjl@mox.example", password0)
97 auth := func(status string, serverFinalError error, username, password string) {
100 noServerPlus := false
101 sc := scram.NewClient(h, username, "", noServerPlus, tc.client.TLSConnectionState())
102 clientFirst, err := sc.ClientFirst()
103 tc.check(err, "scram clientFirst")
104 tc.client.LastTag = "x001"
105 tc.writelinef("%s authenticate %s %s", tc.client.LastTag, method, base64.StdEncoding.EncodeToString([]byte(clientFirst)))
107 xreadContinuation := func() []byte {
108 line, _, result, rerr := tc.client.ReadContinuation()
109 tc.check(rerr, "read continuation")
110 if result.Status != "" {
111 tc.t.Fatalf("expected continuation")
113 buf, err := base64.StdEncoding.DecodeString(line)
114 tc.check(err, "parsing base64 from remote")
118 serverFirst := xreadContinuation()
119 clientFinal, err := sc.ServerFirst(serverFirst, password)
120 tc.check(err, "scram clientFinal")
121 tc.writelinef("%s", base64.StdEncoding.EncodeToString([]byte(clientFinal)))
123 serverFinal := xreadContinuation()
124 err = sc.ServerFinal(serverFinal)
125 if serverFinalError == nil {
126 tc.check(err, "scram serverFinal")
127 } else if err == nil || !errors.Is(err, serverFinalError) {
128 t.Fatalf("server final, got err %#v, expected %#v", err, serverFinalError)
130 if serverFinalError != nil {
135 _, result, err := tc.client.Response()
136 tc.check(err, "read response")
137 if string(result.Status) != strings.ToUpper(status) {
138 tc.t.Fatalf("got status %q, expected %q", result.Status, strings.ToUpper(status))
142 tc = startArgs(t, true, tls, true, true, "mjl")
143 auth("no", scram.ErrInvalidProof, "mjl@mox.example", "badpass")
144 auth("no", scram.ErrInvalidProof, "mjl@mox.example", "")
145 // 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.
146 // auth("no", nil, "other@mox.example", password0)
148 tc.transactf("no", "authenticate bogus ")
149 tc.transactf("bad", "authenticate %s not base64...", method)
150 tc.transactf("bad", "authenticate %s %s", method, base64.StdEncoding.EncodeToString([]byte("bad data")))
152 // NFD username, with PRECIS-cleaned password.
153 auth("ok", nil, "mo\u0301x@mox.example", password1)
158func TestAuthenticateCRAMMD5(t *testing.T) {
161 tc.transactf("no", "authenticate bogus ")
162 tc.transactf("bad", "authenticate CRAM-MD5 not base64...")
163 tc.transactf("bad", "authenticate CRAM-MD5 %s", base64.StdEncoding.EncodeToString([]byte("baddata")))
164 tc.transactf("bad", "authenticate CRAM-MD5 %s", base64.StdEncoding.EncodeToString([]byte("bad data")))
166 auth := func(status string, username, password string) {
169 tc.client.LastTag = "x001"
170 tc.writelinef("%s authenticate CRAM-MD5", tc.client.LastTag)
172 xreadContinuation := func() []byte {
173 line, _, result, rerr := tc.client.ReadContinuation()
174 tc.check(rerr, "read continuation")
175 if result.Status != "" {
176 tc.t.Fatalf("expected continuation")
178 buf, err := base64.StdEncoding.DecodeString(line)
179 tc.check(err, "parsing base64 from remote")
183 chal := xreadContinuation()
184 pw, err := precis.OpaqueString.String(password)
188 h := hmac.New(md5.New, []byte(password))
189 h.Write([]byte(chal))
190 resp := fmt.Sprintf("%s %x", username, h.Sum(nil))
191 tc.writelinef("%s", base64.StdEncoding.EncodeToString([]byte(resp)))
193 _, result, err := tc.client.Response()
194 tc.check(err, "read response")
195 if string(result.Status) != strings.ToUpper(status) {
196 tc.t.Fatalf("got status %q, expected %q", result.Status, strings.ToUpper(status))
200 auth("no", "mjl@mox.example", "badpass")
201 auth("no", "mjl@mox.example", "")
202 auth("no", "other@mox.example", password0)
204 auth("ok", "mjl@mox.example", password0)
208 // NFD username, with PRECIS-cleaned password.
210 auth("ok", "mo\u0301x@mox.example", password1)