Skip to content

Commit c57aeac

Browse files
committed
prevent unicode-confusion in password by applying PRECIS, and username/email address by applying unicode NFC normalization
an é (e with accent) can also be written as e+\u0301. the first form is NFC, the second NFD. when logging in, we transform usernames (email addresses) to NFC. so both forms will be accepted. if a client is using NFD, they can log in too. for passwords, we apply the PRECIS "opaquestring", which (despite the name) transforms the value too: unicode spaces are replaced with ascii spaces. the string is also normalized to NFC. PRECIS may reject confusing passwords when you set a password.
1 parent 8e6fe74 commit c57aeac

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

99 files changed

+59625
-114
lines changed

imapserver/append_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ func TestAppend(t *testing.T) {
1818
tc3 := startNoSwitchboard(t)
1919
defer tc3.close()
2020

21-
tc2.client.Login("[email protected]", "testtest")
21+
tc2.client.Login("[email protected]", password0)
2222
tc2.client.Select("inbox")
23-
tc.client.Login("[email protected]", "testtest")
23+
tc.client.Login("[email protected]", password0)
2424
tc.client.Select("inbox")
25-
tc3.client.Login("[email protected]", "testtest")
25+
tc3.client.Login("[email protected]", password0)
2626

2727
tc2.transactf("bad", "append") // Missing params.
2828
tc2.transactf("bad", `append inbox`) // Missing message.
@@ -32,13 +32,13 @@ func TestAppend(t *testing.T) {
3232
tc2.transactf("bad", "append inbox (\\Badflag) {1+}\r\nx") // Unknown flag.
3333
tc2 = startNoSwitchboard(t)
3434
defer tc2.close()
35-
tc2.client.Login("[email protected]", "testtest")
35+
tc2.client.Login("[email protected]", password0)
3636
tc2.client.Select("inbox")
3737

3838
tc2.transactf("bad", "append inbox () \"bad time\" {1+}\r\nx") // Bad time.
3939
tc2 = startNoSwitchboard(t)
4040
defer tc2.close()
41-
tc2.client.Login("[email protected]", "testtest")
41+
tc2.client.Login("[email protected]", password0)
4242
tc2.client.Select("inbox")
4343

4444
tc2.transactf("no", "append nobox (\\Seen) \" 1-Jan-2022 10:10:00 +0100\" {1}")
@@ -81,7 +81,7 @@ func TestAppend(t *testing.T) {
8181

8282
tclimit := startArgs(t, false, false, true, true, "limit")
8383
defer tclimit.close()
84-
tclimit.client.Login("[email protected]", "testtest")
84+
tclimit.client.Login("[email protected]", password0)
8585
tclimit.client.Select("inbox")
8686
// First message of 1 byte is within limits.
8787
tclimit.transactf("ok", "append inbox (\\Seen Label1 $label2) \" 1-Jan-2022 10:10:00 +0100\" {1+}\r\nx")

imapserver/authenticate_test.go

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,18 @@ import (
1212
"strings"
1313
"testing"
1414

15+
"golang.org/x/text/secure/precis"
16+
1517
"github.com/mjl-/mox/scram"
1618
)
1719

20+
func TestAuthenticateLogin(t *testing.T) {
21+
// NFD username and PRECIS-cleaned password.
22+
tc := start(t)
23+
tc.client.Login("mo\u0301[email protected]", password1)
24+
tc.close()
25+
}
26+
1827
func TestAuthenticatePlain(t *testing.T) {
1928
tc := start(t)
2029

@@ -28,21 +37,26 @@ func TestAuthenticatePlain(t *testing.T) {
2837
tc.xcode("AUTHENTICATIONFAILED")
2938
tc.transactf("no", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("\u0000[email protected]\u0000test")))
3039
tc.xcode("AUTHENTICATIONFAILED")
31-
tc.transactf("no", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("\u0000[email protected]\u0000testtesttest")))
40+
tc.transactf("no", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("\u0000[email protected]\u0000test"+password0)))
3241
tc.xcode("AUTHENTICATIONFAILED")
3342
tc.transactf("bad", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("\u0000")))
3443
tc.xcode("")
35-
tc.transactf("no", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("other\u0000[email protected]\u0000testtest")))
44+
tc.transactf("no", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("other\u0000[email protected]\u0000"+password0)))
3645
tc.xcode("AUTHORIZATIONFAILED")
37-
tc.transactf("ok", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("\u0000[email protected]\u0000testtest")))
46+
tc.transactf("ok", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("\u0000[email protected]\u0000"+password0)))
47+
tc.close()
48+
49+
tc = start(t)
50+
tc.transactf("ok", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("[email protected]\u0000[email protected]\u0000"+password0)))
3851
tc.close()
3952

53+
// NFD username and PRECIS-cleaned password.
4054
tc = start(t)
41-
tc.transactf("ok", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("mjl@mox.example\u0000mjl@mox.example\u0000testtest")))
55+
tc.transactf("ok", "authenticate plain %s", base64.StdEncoding.EncodeToString([]byte("mo\u0301x@mox.example\u0000mo\u0301x@mox.example\u0000"+password1)))
4256
tc.close()
4357

4458
tc = start(t)
45-
tc.client.AuthenticatePlain("[email protected]", "testtest")
59+
tc.client.AuthenticatePlain("[email protected]", password0)
4660
tc.close()
4761

4862
tc = start(t)
@@ -55,7 +69,7 @@ func TestAuthenticatePlain(t *testing.T) {
5569

5670
tc.cmdf("", "authenticate plain")
5771
tc.readprefixline("+ ")
58-
tc.writelinef("%s", base64.StdEncoding.EncodeToString([]byte("\u0000[email protected]\u0000testtest")))
72+
tc.writelinef("%s", base64.StdEncoding.EncodeToString([]byte("\u0000[email protected]\u0000"+password0)))
5973
tc.readstatus("ok")
6074
}
6175

@@ -77,7 +91,7 @@ func TestAuthenticateSCRAMSHA256PLUS(t *testing.T) {
7791

7892
func testAuthenticateSCRAM(t *testing.T, tls bool, method string, h func() hash.Hash) {
7993
tc := startArgs(t, true, tls, true, true, "mjl")
80-
tc.client.AuthenticateSCRAM(method, h, "[email protected]", "testtest")
94+
tc.client.AuthenticateSCRAM(method, h, "[email protected]", password0)
8195
tc.close()
8296

8397
auth := func(status string, serverFinalError error, username, password string) {
@@ -129,11 +143,15 @@ func testAuthenticateSCRAM(t *testing.T, tls bool, method string, h func() hash.
129143
auth("no", scram.ErrInvalidProof, "[email protected]", "badpass")
130144
auth("no", scram.ErrInvalidProof, "[email protected]", "")
131145
// 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.
132-
// auth("no", nil, "[email protected]", "testtest")
146+
// auth("no", nil, "[email protected]", password0)
133147

134148
tc.transactf("no", "authenticate bogus ")
135149
tc.transactf("bad", "authenticate %s not base64...", method)
136150
tc.transactf("bad", "authenticate %s %s", method, base64.StdEncoding.EncodeToString([]byte("bad data")))
151+
152+
// NFD username, with PRECIS-cleaned password.
153+
auth("ok", nil, "mo\u0301[email protected]", password1)
154+
137155
tc.close()
138156
}
139157

@@ -163,6 +181,10 @@ func TestAuthenticateCRAMMD5(t *testing.T) {
163181
}
164182

165183
chal := xreadContinuation()
184+
pw, err := precis.OpaqueString.String(password)
185+
if err == nil {
186+
password = pw
187+
}
166188
h := hmac.New(md5.New, []byte(password))
167189
h.Write([]byte(chal))
168190
resp := fmt.Sprintf("%s %x", username, h.Sum(nil))
@@ -177,9 +199,14 @@ func TestAuthenticateCRAMMD5(t *testing.T) {
177199

178200
auth("no", "[email protected]", "badpass")
179201
auth("no", "[email protected]", "")
180-
auth("no", "[email protected]", "testtest")
202+
auth("no", "[email protected]", password0)
181203

182-
auth("ok", "[email protected]", "testtest")
204+
auth("ok", "[email protected]", password0)
183205

184206
tc.close()
207+
208+
// NFD username, with PRECIS-cleaned password.
209+
tc = start(t)
210+
auth("ok", "mo\u0301[email protected]", password1)
211+
tc.close()
185212
}

imapserver/condstore_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func testCondstoreQresync(t *testing.T, qresync bool) {
3434
capability = "Qresync"
3535
}
3636

37-
tc.client.Login("[email protected]", "testtest")
37+
tc.client.Login("[email protected]", password0)
3838
tc.client.Enable(capability)
3939
tc.transactf("ok", "Select inbox")
4040
tc.xuntaggedOpt(false, imapclient.UntaggedResult{Status: imapclient.OK, RespText: imapclient.RespText{Code: "HIGHESTMODSEQ", CodeArg: imapclient.CodeHighestModSeq(1), More: "x"}})
@@ -101,13 +101,13 @@ func testCondstoreQresync(t *testing.T, qresync bool) {
101101
// tc2 is a client without condstore, so no modseq responses.
102102
tc2 := startNoSwitchboard(t)
103103
defer tc2.close()
104-
tc2.client.Login("[email protected]", "testtest")
104+
tc2.client.Login("[email protected]", password0)
105105
tc2.client.Select("inbox")
106106

107107
// tc3 is a client with condstore, so with modseq responses.
108108
tc3 := startNoSwitchboard(t)
109109
defer tc3.close()
110-
tc3.client.Login("[email protected]", "testtest")
110+
tc3.client.Login("[email protected]", password0)
111111
tc3.client.Enable(capability)
112112
tc3.client.Select("inbox")
113113

@@ -357,7 +357,7 @@ func testCondstoreQresync(t *testing.T, qresync bool) {
357357
xtc.close()
358358
store.CheckConsistencyOnClose = true
359359
}()
360-
xtc.client.Login("[email protected]", "testtest")
360+
xtc.client.Login("[email protected]", password0)
361361
fn(xtc)
362362
tagcount++
363363
label := fmt.Sprintf("l%d", tagcount)
@@ -444,13 +444,13 @@ func testCondstoreQresync(t *testing.T, qresync bool) {
444444
// tc2o is a client without condstore, so no modseq responses.
445445
tc2o := startNoSwitchboard(t)
446446
defer tc2o.close()
447-
tc2o.client.Login("[email protected]", "testtest")
447+
tc2o.client.Login("[email protected]", password0)
448448
tc2o.client.Select("otherbox")
449449

450450
// tc3o is a client with condstore, so with modseq responses.
451451
tc3o := startNoSwitchboard(t)
452452
defer tc3o.close()
453-
tc3o.client.Login("[email protected]", "testtest")
453+
tc3o.client.Login("[email protected]", password0)
454454
tc3o.client.Enable(capability)
455455
tc3o.client.Select("otherbox")
456456

@@ -529,7 +529,7 @@ func testQresync(t *testing.T, tc *testconn, clientModseq int64) {
529529

530530
// Vanished not allowed without first enabling qresync. ../rfc/7162:1697
531531
xtc := startNoSwitchboard(t)
532-
xtc.client.Login("[email protected]", "testtest")
532+
xtc.client.Login("[email protected]", password0)
533533
xtc.transactf("ok", "Select inbox (Condstore)")
534534
xtc.transactf("bad", "Uid Fetch 1:* (Flags) (Changedsince 1 Vanished)")
535535
// Prevent triggering the consistency checker, we still have modseq/createseq at 0.
@@ -553,7 +553,7 @@ func testQresync(t *testing.T, tc *testconn, clientModseq int64) {
553553

554554
// Must enable qresync explicitly before using. ../rfc/7162:1446
555555
xtc = startNoSwitchboard(t)
556-
xtc.client.Login("[email protected]", "testtest")
556+
xtc.client.Login("[email protected]", password0)
557557
xtc.transactf("bad", "Select inbox (Qresync 1 0)")
558558
// Prevent triggering the consistency checker, we still have modseq/createseq at 0.
559559
store.CheckConsistencyOnClose = false

imapserver/copy_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ func TestCopy(t *testing.T) {
1414
tc2 := startNoSwitchboard(t)
1515
defer tc2.close()
1616

17-
tc.client.Login("[email protected]", "testtest")
17+
tc.client.Login("[email protected]", password0)
1818
tc.client.Select("inbox")
1919

20-
tc2.client.Login("[email protected]", "testtest")
20+
tc2.client.Login("[email protected]", password0)
2121
tc2.client.Select("Trash")
2222

2323
tc.transactf("bad", "copy") // Missing params.
@@ -61,7 +61,7 @@ func TestCopy(t *testing.T) {
6161

6262
tclimit := startArgs(t, false, false, true, true, "limit")
6363
defer tclimit.close()
64-
tclimit.client.Login("[email protected]", "testtest")
64+
tclimit.client.Login("[email protected]", password0)
6565
tclimit.client.Select("inbox")
6666
// First message of 1 byte is within limits.
6767
tclimit.transactf("ok", "append inbox (\\Seen Label1 $label2) \" 1-Jan-2022 10:10:00 +0100\" {1+}\r\nx")

imapserver/create_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ func TestCreate(t *testing.T) {
1313
tc2 := startNoSwitchboard(t)
1414
defer tc2.close()
1515

16-
tc.client.Login("[email protected]", "testtest")
17-
tc2.client.Login("[email protected]", "testtest")
16+
tc.client.Login("[email protected]", password0)
17+
tc2.client.Login("[email protected]", password0)
1818

1919
tc.transactf("no", "create inbox") // Already exists and not allowed. ../rfc/9051:1913
2020
tc.transactf("no", "create Inbox") // Idem.

imapserver/delete_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ func TestDelete(t *testing.T) {
1616
tc3 := startNoSwitchboard(t)
1717
defer tc3.close()
1818

19-
tc.client.Login("[email protected]", "testtest")
20-
tc2.client.Login("[email protected]", "testtest")
21-
tc3.client.Login("[email protected]", "testtest")
19+
tc.client.Login("[email protected]", password0)
20+
tc2.client.Login("[email protected]", password0)
21+
tc3.client.Login("[email protected]", password0)
2222

2323
tc.transactf("bad", "delete") // Missing mailbox.
2424
tc.transactf("no", "delete inbox") // Cannot delete inbox.

imapserver/expunge_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ func TestExpunge(t *testing.T) {
1414
tc2 := startNoSwitchboard(t)
1515
defer tc2.close()
1616

17-
tc.client.Login("[email protected]", "testtest")
17+
tc.client.Login("[email protected]", password0)
1818
tc.client.Select("inbox")
1919

20-
tc2.client.Login("[email protected]", "testtest")
20+
tc2.client.Login("[email protected]", password0)
2121
tc2.client.Select("inbox")
2222

2323
tc.transactf("bad", "expunge leftover") // Leftover data.

imapserver/fetch_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ func TestFetch(t *testing.T) {
1212
tc := start(t)
1313
defer tc.close()
1414

15-
tc.client.Login("[email protected]", "testtest")
15+
tc.client.Login("[email protected]", password0)
1616
tc.client.Enable("imap4rev2")
1717
received, err := time.Parse(time.RFC3339, "2022-11-16T10:01:00+01:00")
1818
tc.check(err, "parse time")

imapserver/fuzz_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ func FuzzServer(f *testing.F) {
7070
f.Fatalf("open account: %v", err)
7171
}
7272
defer acc.Close()
73-
err = acc.SetPassword(log, "testtest")
73+
err = acc.SetPassword(log, password0)
7474
if err != nil {
7575
f.Fatalf("set password: %v", err)
7676
}

imapserver/idle_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ import (
1111
func TestIdle(t *testing.T) {
1212
tc1 := start(t)
1313
defer tc1.close()
14-
tc1.transactf("ok", "login [email protected] testtest")
14+
tc1.client.Login("[email protected]", password0)
1515

1616
tc2 := startNoSwitchboard(t)
1717
defer tc2.close()
18-
tc2.transactf("ok", "login [email protected] testtest")
18+
tc2.client.Login("[email protected]", password0)
1919

2020
tc1.transactf("ok", "select inbox")
2121
tc2.transactf("ok", "select inbox")

0 commit comments

Comments
 (0)