Skip to content

Commit 9be743a

Browse files
committed
auth: add caching_sha2 tests
1 parent d0d482d commit 9be743a

File tree

6 files changed

+280
-53
lines changed

6 files changed

+280
-53
lines changed

auth.go

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@
99
package mysql
1010

1111
import (
12+
"crypto/rand"
13+
"crypto/rsa"
1214
"crypto/sha1"
1315
"crypto/sha256"
16+
"crypto/x509"
17+
"encoding/pem"
1418
)
1519

1620
// Hash password using pre 4.1 (old password) method
@@ -242,7 +246,42 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
242246
return err
243247
}
244248
} else {
245-
if err = mc.writePublicKeyAuthPacket(oldAuthData); err != nil {
249+
seed := oldAuthData
250+
251+
// TODO: allow to specify a local file with the pub key via
252+
// the DSN
253+
254+
// request public key
255+
data := mc.buf.takeSmallBuffer(4 + 1)
256+
data[4] = cachingSha2PasswordRequestPublicKey
257+
mc.writePacket(data)
258+
259+
// parse public key
260+
data, err := mc.readPacket()
261+
if err != nil {
262+
return err
263+
}
264+
265+
block, _ := pem.Decode(data[1:])
266+
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
267+
if err != nil {
268+
return err
269+
}
270+
271+
// send encrypted password
272+
plain := make([]byte, len(mc.cfg.Passwd)+1)
273+
copy(plain, mc.cfg.Passwd)
274+
for i := range plain {
275+
j := i % len(seed)
276+
plain[i] ^= seed[j]
277+
}
278+
sha1 := sha1.New()
279+
enc, err := rsa.EncryptOAEP(sha1, rand.Reader, pub.(*rsa.PublicKey), plain, nil)
280+
if err != nil {
281+
return err
282+
}
283+
284+
if err = mc.writeAuthSwitchPacket(enc); err != nil {
246285
return err
247286
}
248287
}

auth_test.go

Lines changed: 224 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,39 @@ package mysql
1010

1111
import (
1212
"bytes"
13+
"crypto/tls"
1314
"fmt"
1415
"testing"
1516
)
1617

18+
var serverPubKey = []byte{1, 45, 45, 45, 45, 45, 66, 69, 71, 73, 78, 32, 80, 85,
19+
66, 76, 73, 67, 32, 75, 69, 89, 45, 45, 45, 45, 45, 10, 77, 73, 73, 66, 73,
20+
106, 65, 78, 66, 103, 107, 113, 104, 107, 105, 71, 57, 119, 48, 66, 65, 81,
21+
69, 70, 65, 65, 79, 67, 65, 81, 56, 65, 77, 73, 73, 66, 67, 103, 75, 67, 65,
22+
81, 69, 65, 51, 72, 115, 120, 83, 53, 80, 47, 72, 97, 88, 80, 118, 109, 51,
23+
109, 50, 65, 68, 110, 10, 98, 117, 54, 71, 81, 102, 112, 83, 71, 111, 55,
24+
104, 50, 103, 104, 56, 49, 112, 109, 97, 120, 107, 67, 110, 68, 67, 119,
25+
102, 54, 109, 109, 101, 72, 55, 76, 75, 104, 115, 110, 89, 110, 78, 52, 81,
26+
48, 99, 122, 49, 81, 69, 47, 98, 104, 100, 80, 117, 54, 106, 115, 43, 86,
27+
97, 89, 52, 10, 67, 99, 77, 117, 98, 80, 78, 49, 103, 79, 75, 97, 89, 118,
28+
78, 99, 103, 69, 87, 112, 116, 73, 67, 105, 50, 88, 84, 116, 116, 66, 55,
29+
117, 104, 43, 118, 67, 77, 106, 76, 118, 106, 65, 77, 100, 54, 47, 68, 109,
30+
120, 100, 98, 85, 66, 48, 122, 80, 71, 113, 68, 79, 103, 105, 76, 68, 10,
31+
75, 82, 79, 79, 53, 113, 100, 55, 115, 104, 98, 55, 49, 82, 47, 88, 74, 69,
32+
70, 118, 76, 120, 71, 88, 69, 70, 48, 90, 116, 104, 72, 101, 78, 111, 57,
33+
102, 69, 118, 120, 70, 81, 111, 109, 98, 49, 107, 90, 57, 74, 56, 110, 66,
34+
119, 116, 101, 53, 83, 70, 53, 89, 108, 113, 86, 50, 10, 66, 66, 53, 113,
35+
108, 97, 122, 43, 51, 81, 83, 78, 118, 109, 67, 49, 105, 87, 102, 108, 106,
36+
88, 98, 89, 53, 107, 51, 47, 97, 54, 109, 107, 77, 47, 76, 97, 87, 104, 97,
37+
117, 78, 53, 80, 82, 51, 115, 67, 120, 53, 85, 117, 49, 77, 102, 100, 115,
38+
86, 105, 107, 53, 102, 88, 77, 77, 10, 100, 120, 107, 102, 70, 43, 88, 51,
39+
99, 104, 107, 65, 110, 119, 73, 51, 70, 117, 119, 119, 50, 87, 71, 109, 87,
40+
79, 71, 98, 75, 116, 109, 73, 101, 85, 109, 51, 98, 73, 82, 109, 100, 70,
41+
85, 113, 97, 108, 81, 105, 70, 104, 113, 101, 90, 50, 105, 107, 106, 104,
42+
103, 86, 73, 57, 112, 76, 10, 119, 81, 73, 68, 65, 81, 65, 66, 10, 45, 45,
43+
45, 45, 45, 69, 78, 68, 32, 80, 85, 66, 76, 73, 67, 32, 75, 69, 89, 45, 45,
44+
45, 45, 45, 10}
45+
1746
func TestScrambleOldPass(t *testing.T) {
1847
scramble := []byte{9, 8, 7, 6, 5, 4, 3, 2}
1948
vectors := []struct {
@@ -48,7 +77,200 @@ func TestScrambleSHA256Pass(t *testing.T) {
4877
t.Errorf("Failed SHA256 password %q", tuple.pass)
4978
}
5079
}
80+
}
81+
82+
func TestAuthCachingSHA256PasswordCached(t *testing.T) {
83+
conn, mc := newRWMockConn(1)
84+
mc.cfg.User = "root"
85+
mc.cfg.Passwd = "secret"
86+
87+
authData := []byte{90, 105, 74, 126, 30, 48, 37, 56, 3, 23, 115, 127, 69,
88+
22, 41, 84, 32, 123, 43, 118}
89+
plugin := "caching_sha2_password"
90+
91+
// Send Client Authentication Packet
92+
authResp, err := mc.auth(authData, plugin)
93+
if err != nil {
94+
t.Fatal(err)
95+
}
96+
if err = mc.writeAuthPacket(authResp, plugin); err != nil {
97+
t.Fatal(err)
98+
}
99+
100+
// check written auth response
101+
authRespStart := 4 + 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1
102+
authRespEnd := authRespStart + 1 + len(authResp)
103+
writtenAuthRespLen := conn.written[authRespStart]
104+
writtenAuthResp := conn.written[authRespStart+1 : authRespEnd]
105+
expectedAuthResp := []byte{102, 32, 5, 35, 143, 161, 140, 241, 171, 232, 56,
106+
139, 43, 14, 107, 196, 249, 170, 147, 60, 220, 204, 120, 178, 214, 15,
107+
184, 150, 26, 61, 57, 235}
108+
if writtenAuthRespLen != 32 || !bytes.Equal(writtenAuthResp, expectedAuthResp) {
109+
t.Fatalf("unexpected written auth response (%d bytes): %v", writtenAuthRespLen, writtenAuthResp)
110+
}
111+
conn.written = nil
112+
113+
// auth response
114+
conn.data = []byte{
115+
2, 0, 0, 2, 1, 3, // Fast Auth Success
116+
7, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, // OK
117+
}
118+
conn.maxReads = 1
119+
120+
// Handle response to auth packet
121+
if err := mc.handleAuthResult(authData, plugin); err != nil {
122+
t.Errorf("got error: %v", err)
123+
}
124+
}
51125

126+
func TestAuthCachingSHA256PasswordEmpty(t *testing.T) {
127+
conn, mc := newRWMockConn(1)
128+
mc.cfg.User = "root"
129+
mc.cfg.Passwd = ""
130+
131+
authData := []byte{90, 105, 74, 126, 30, 48, 37, 56, 3, 23, 115, 127, 69,
132+
22, 41, 84, 32, 123, 43, 118}
133+
plugin := "caching_sha2_password"
134+
135+
// Send Client Authentication Packet
136+
authResp, err := mc.auth(authData, plugin)
137+
if err != nil {
138+
t.Fatal(err)
139+
}
140+
if err = mc.writeAuthPacket(authResp, plugin); err != nil {
141+
t.Fatal(err)
142+
}
143+
144+
// check written auth response
145+
authRespStart := 4 + 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1
146+
authRespEnd := authRespStart + 1 + len(authResp)
147+
writtenAuthRespLen := conn.written[authRespStart]
148+
writtenAuthResp := conn.written[authRespStart+1 : authRespEnd]
149+
expectedAuthResp := []byte{}
150+
if writtenAuthRespLen != 0 || !bytes.Equal(writtenAuthResp, expectedAuthResp) {
151+
t.Fatalf("unexpected written auth response (%d bytes): %v", writtenAuthRespLen, writtenAuthResp)
152+
}
153+
conn.written = nil
154+
155+
// auth response
156+
conn.data = []byte{
157+
7, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, // OK
158+
}
159+
conn.maxReads = 1
160+
161+
// Handle response to auth packet
162+
if err := mc.handleAuthResult(authData, plugin); err != nil {
163+
t.Errorf("got error: %v", err)
164+
}
165+
}
166+
167+
func TestAuthCachingSHA256PasswordFullRSA(t *testing.T) {
168+
conn, mc := newRWMockConn(1)
169+
mc.cfg.User = "root"
170+
mc.cfg.Passwd = "secret"
171+
172+
authData := []byte{6, 81, 96, 114, 14, 42, 50, 30, 76, 47, 1, 95, 126, 81,
173+
62, 94, 83, 80, 52, 85}
174+
plugin := "caching_sha2_password"
175+
176+
// Send Client Authentication Packet
177+
authResp, err := mc.auth(authData, plugin)
178+
if err != nil {
179+
t.Fatal(err)
180+
}
181+
if err = mc.writeAuthPacket(authResp, plugin); err != nil {
182+
t.Fatal(err)
183+
}
184+
185+
// check written auth response
186+
authRespStart := 4 + 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1
187+
authRespEnd := authRespStart + 1 + len(authResp)
188+
writtenAuthRespLen := conn.written[authRespStart]
189+
writtenAuthResp := conn.written[authRespStart+1 : authRespEnd]
190+
expectedAuthResp := []byte{171, 201, 138, 146, 89, 159, 11, 170, 0, 67, 165,
191+
49, 175, 94, 218, 68, 177, 109, 110, 86, 34, 33, 44, 190, 67, 240, 70,
192+
110, 40, 139, 124, 41}
193+
if writtenAuthRespLen != 32 || !bytes.Equal(writtenAuthResp, expectedAuthResp) {
194+
t.Fatalf("unexpected written auth response (%d bytes): %v", writtenAuthRespLen, writtenAuthResp)
195+
}
196+
conn.written = nil
197+
198+
// auth response
199+
conn.data = []byte{
200+
2, 0, 0, 2, 1, 4, // Perform Full Authentication
201+
}
202+
conn.queuedReplies = [][]byte{
203+
// pub key response
204+
append([]byte{byte(len(serverPubKey)), 1, 0, 4}, serverPubKey...),
205+
206+
// OK
207+
{7, 0, 0, 6, 0, 0, 0, 2, 0, 0, 0},
208+
}
209+
conn.maxReads = 3
210+
211+
// Handle response to auth packet
212+
if err := mc.handleAuthResult(authData, plugin); err != nil {
213+
t.Errorf("got error: %v", err)
214+
}
215+
216+
if !bytes.HasPrefix(conn.written, []byte{1, 0, 0, 3, 2, 0, 1, 0, 5}) {
217+
t.Errorf("unexpected written data: %v", conn.written)
218+
}
219+
}
220+
221+
func TestAuthCachingSHA256PasswordFullSecure(t *testing.T) {
222+
conn, mc := newRWMockConn(1)
223+
mc.cfg.User = "root"
224+
mc.cfg.Passwd = "secret"
225+
226+
authData := []byte{6, 81, 96, 114, 14, 42, 50, 30, 76, 47, 1, 95, 126, 81,
227+
62, 94, 83, 80, 52, 85}
228+
plugin := "caching_sha2_password"
229+
230+
// Send Client Authentication Packet
231+
authResp, err := mc.auth(authData, plugin)
232+
if err != nil {
233+
t.Fatal(err)
234+
}
235+
if err = mc.writeAuthPacket(authResp, plugin); err != nil {
236+
t.Fatal(err)
237+
}
238+
239+
// Hack to make the caching_sha2_password plugin believe that the connection
240+
// is secure
241+
mc.cfg.tls = &tls.Config{InsecureSkipVerify: true}
242+
243+
// check written auth response
244+
authRespStart := 4 + 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1
245+
authRespEnd := authRespStart + 1 + len(authResp)
246+
writtenAuthRespLen := conn.written[authRespStart]
247+
writtenAuthResp := conn.written[authRespStart+1 : authRespEnd]
248+
expectedAuthResp := []byte{171, 201, 138, 146, 89, 159, 11, 170, 0, 67, 165,
249+
49, 175, 94, 218, 68, 177, 109, 110, 86, 34, 33, 44, 190, 67, 240, 70,
250+
110, 40, 139, 124, 41}
251+
if writtenAuthRespLen != 32 || !bytes.Equal(writtenAuthResp, expectedAuthResp) {
252+
t.Fatalf("unexpected written auth response (%d bytes): %v", writtenAuthRespLen, writtenAuthResp)
253+
}
254+
conn.written = nil
255+
256+
// auth response
257+
conn.data = []byte{
258+
2, 0, 0, 2, 1, 4, // Perform Full Authentication
259+
}
260+
conn.queuedReplies = [][]byte{
261+
// OK
262+
{7, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0},
263+
}
264+
conn.maxReads = 3
265+
266+
// Handle response to auth packet
267+
if err := mc.handleAuthResult(authData, plugin); err != nil {
268+
t.Errorf("got error: %v", err)
269+
}
270+
271+
if !bytes.Equal(conn.written, []byte{6, 0, 0, 3, 115, 101, 99, 114, 101, 116}) {
272+
t.Errorf("unexpected written data: %v", conn.written)
273+
}
52274
}
53275

54276
func TestAuthSwitchCleartextPasswordNotAllowed(t *testing.T) {
@@ -161,7 +383,8 @@ func TestAuthSwitchNativePassword(t *testing.T) {
161383
t.Errorf("got error: %v", err)
162384
}
163385

164-
expectedReply := []byte{20, 0, 0, 3, 202, 41, 195, 164, 34, 226, 49, 103, 21, 211, 167, 199, 227, 116, 8, 48, 57, 71, 149, 146}
386+
expectedReply := []byte{20, 0, 0, 3, 202, 41, 195, 164, 34, 226, 49, 103,
387+
21, 211, 167, 199, 227, 116, 8, 48, 57, 71, 149, 146}
165388
if !bytes.Equal(conn.written, expectedReply) {
166389
t.Errorf("got unexpected data: %v", conn.written)
167390
}

driver.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,27 +107,31 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
107107
mc.writeTimeout = mc.cfg.WriteTimeout
108108

109109
// Reading Handshake Initialization Packet
110-
cipher, plugin, err := mc.readInitPacket()
110+
authData, plugin, err := mc.readInitPacket()
111111
if err != nil {
112112
mc.cleanup()
113113
return nil, err
114114
}
115115

116116
// Send Client Authentication Packet
117-
authData, err := mc.auth(cipher, plugin)
117+
authResp, err := mc.auth(authData, plugin)
118118
if err != nil {
119119
// try the default auth plugin, if using the requested plugin failed
120-
errLog.Print("could not use requested plugin '"+plugin+"': ", err.Error())
120+
errLog.Print("could not use requested auth plugin '"+plugin+"': ", err.Error())
121121
plugin = defaultAuthPlugin
122-
authData, _ = mc.auth(cipher, plugin)
122+
authResp, err = mc.auth(authData, plugin)
123+
if err != nil {
124+
mc.cleanup()
125+
return nil, err
126+
}
123127
}
124-
if err = mc.writeAuthPacket(authData, plugin); err != nil {
128+
if err = mc.writeAuthPacket(authResp, plugin); err != nil {
125129
mc.cleanup()
126130
return nil, err
127131
}
128132

129133
// Handle response to auth packet, switch methods if possible
130-
if err = mc.handleAuthResult(cipher, plugin); err != nil {
134+
if err = mc.handleAuthResult(authData, plugin); err != nil {
131135
// Authentication failed and MySQL has already closed the connection
132136
// (https://dev.mysql.com/doc/internals/en/authentication-fails.html).
133137
// Do not send COM_QUIT, just cleanup and return the error.

driver_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ func maybeSkip(t *testing.T, err error, skipErrno uint16) {
189189
}
190190

191191
func TestEmptyQuery(t *testing.T) {
192-
runTests(t, dsn+"&allowOldPasswords=1", func(dbt *DBTest) {
192+
runTests(t, dsn, func(dbt *DBTest) {
193193
// just a comment, no query
194194
rows := dbt.mustQuery("--")
195195
// will hang before #255

0 commit comments

Comments
 (0)