Repository navigation
Expand file tree
/
Copy pathsasl.go
More file actions
205 lines (171 loc) · 5.51 KB
/
Copy pathsasl.go
File metadata and controls
205 lines (171 loc) · 5.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
// Package SASL implements Simple Authentication and Security Layer, RFC 4422.
package sasl
import (
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"fmt"
"hash"
"strings"
"github.com/mjl-/mox/scram"
)
// Client is a SASL client
type Client interface {
// Name as used in SMTP AUTH, e.g. PLAIN, CRAM-MD5, SCRAM-SHA-256.
// cleartextCredentials indicates if credentials are exchanged in clear text, which influences whether they are logged.
Info() (name string, cleartextCredentials bool)
// Next is called for each step of the SASL communication. The first call has a nil
// fromServer and serves to get a possible "initial response" from the client. If
// the client sends its final message it indicates so with last. Returning an error
// aborts the authentication attempt.
// For the first toServer ("initial response"), a nil toServer indicates there is
// no data, which is different from a non-nil zero-length toServer.
Next(fromServer []byte) (toServer []byte, last bool, err error)
}
type clientPlain struct {
Username, Password string
step int
}
var _ Client = (*clientPlain)(nil)
// NewClientPlain returns a client for SASL PLAIN authentication.
func NewClientPlain(username, password string) Client {
return &clientPlain{username, password, 0}
}
func (a *clientPlain) Info() (name string, hasCleartextCredentials bool) {
return "PLAIN", true
}
func (a *clientPlain) Next(fromServer []byte) (toServer []byte, last bool, rerr error) {
defer func() { a.step++ }()
switch a.step {
case 0:
return []byte(fmt.Sprintf("\u0000%s\u0000%s", a.Username, a.Password)), true, nil
default:
return nil, false, fmt.Errorf("invalid step %d", a.step)
}
}
type clientLogin struct {
Username, Password string
step int
}
var _ Client = (*clientLogin)(nil)
// NewClientLogin returns a client for the obsolete SASL LOGIN authentication.
// See https://datatracker.ietf.org/doc/html/draft-murchison-sasl-login-00
func NewClientLogin(username, password string) Client {
return &clientLogin{username, password, 0}
}
func (a *clientLogin) Info() (name string, hasCleartextCredentials bool) {
return "LOGIN", true
}
func (a *clientLogin) Next(fromServer []byte) (toServer []byte, last bool, rerr error) {
defer func() { a.step++ }()
switch a.step {
case 0:
return []byte(a.Username), false, nil
case 1:
return []byte(a.Password), true, nil
default:
return nil, false, fmt.Errorf("invalid step %d", a.step)
}
}
type clientCRAMMD5 struct {
Username, Password string
step int
}
var _ Client = (*clientCRAMMD5)(nil)
// NewClientCRAMMD5 returns a client for SASL CRAM-MD5 authentication.
func NewClientCRAMMD5(username, password string) Client {
return &clientCRAMMD5{username, password, 0}
}
func (a *clientCRAMMD5) Info() (name string, hasCleartextCredentials bool) {
return "CRAM-MD5", false
}
func (a *clientCRAMMD5) Next(fromServer []byte) (toServer []byte, last bool, rerr error) {
defer func() { a.step++ }()
switch a.step {
case 0:
return nil, false, nil
case 1:
// Validate the challenge.
// ../rfc/2195:82
s := string(fromServer)
if !strings.HasPrefix(s, "<") || !strings.HasSuffix(s, ">") {
return nil, false, fmt.Errorf("invalid challenge, missing angle brackets")
}
t := strings.SplitN(s, ".", 2)
if len(t) != 2 || t[0] == "" {
return nil, false, fmt.Errorf("invalid challenge, missing dot or random digits")
}
t = strings.Split(t[1], "@")
if len(t) == 1 || t[0] == "" || t[len(t)-1] == "" {
return nil, false, fmt.Errorf("invalid challenge, empty timestamp or empty hostname")
}
// ../rfc/2195:138
key := []byte(a.Password)
if len(key) > 64 {
t := md5.Sum(key)
key = t[:]
}
ipad := make([]byte, md5.BlockSize)
opad := make([]byte, md5.BlockSize)
copy(ipad, key)
copy(opad, key)
for i := range ipad {
ipad[i] ^= 0x36
opad[i] ^= 0x5c
}
ipadh := md5.New()
ipadh.Write(ipad)
ipadh.Write([]byte(fromServer))
opadh := md5.New()
opadh.Write(opad)
opadh.Write(ipadh.Sum(nil))
// ../rfc/2195:88
return []byte(fmt.Sprintf("%s %x", a.Username, opadh.Sum(nil))), true, nil
default:
return nil, false, fmt.Errorf("invalid step %d", a.step)
}
}
type clientSCRAMSHA struct {
Username, Password string
name string
step int
scram *scram.Client
}
var _ Client = (*clientSCRAMSHA)(nil)
// NewClientSCRAMSHA1 returns a client for SASL SCRAM-SHA-1 authentication.
func NewClientSCRAMSHA1(username, password string) Client {
return &clientSCRAMSHA{username, password, "SCRAM-SHA-1", 0, nil}
}
// NewClientSCRAMSHA256 returns a client for SASL SCRAM-SHA-256 authentication.
func NewClientSCRAMSHA256(username, password string) Client {
return &clientSCRAMSHA{username, password, "SCRAM-SHA-256", 0, nil}
}
func (a *clientSCRAMSHA) Info() (name string, hasCleartextCredentials bool) {
return a.name, false
}
func (a *clientSCRAMSHA) Next(fromServer []byte) (toServer []byte, last bool, rerr error) {
defer func() { a.step++ }()
switch a.step {
case 0:
var h func() hash.Hash
switch a.name {
case "SCRAM-SHA-1":
h = sha1.New
case "SCRAM-SHA-256":
h = sha256.New
default:
return nil, false, fmt.Errorf("invalid SCRAM-SHA variant %q", a.name)
}
a.scram = scram.NewClient(h, a.Username, "")
toserver, err := a.scram.ClientFirst()
return []byte(toserver), false, err
case 1:
clientFinal, err := a.scram.ServerFirst(fromServer, a.Password)
return []byte(clientFinal), false, err
case 2:
err := a.scram.ServerFinal(fromServer)
return nil, true, err
default:
return nil, false, fmt.Errorf("invalid step %d", a.step)
}
}