Skip to content

Commit ce924a4

Browse files
twocodejulienschmidt
authored andcommitted
Support authentication switch with mysql_native_password authentication (go-sql-driver#494)
* Support authentication switch with mysql_native_password authentication. Besides, fix bug that cipher needs to refreshed from authentication switch request packet from server. * revert connection.go which was modified by accident. * Address comments. * Keep DSN params sorted * DSN: Add code to format allowNativePasswords
1 parent 2a6c607 commit ce924a4

File tree

7 files changed

+77
-16
lines changed

7 files changed

+77
-16
lines changed

AUTHORS

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Runrioter Wung <runrioter at gmail.com>
4343
Soroush Pour <me at soroushjp.com>
4444
Stan Putrya <root.vagner at gmail.com>
4545
Stanley Gunawan <gunawan.stanley at gmail.com>
46+
Xiangyu Hu <xiangyu.hu at outlook.com>
4647
Xiaobing Jiang <s7v7nislands at gmail.com>
4748
Xiuming Chen <cc at cxm.cc>
4849
Zhenye Xie <xiezhenye at gmail.com>

README.md

+9
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,15 @@ Default: false
135135

136136
`allowCleartextPasswords=true` allows using the [cleartext client side plugin](http://dev.mysql.com/doc/en/cleartext-authentication-plugin.html) if required by an account, such as one defined with the [PAM authentication plugin](http://dev.mysql.com/doc/en/pam-authentication-plugin.html). Sending passwords in clear text may be a security problem in some configurations. To avoid problems if there is any possibility that the password would be intercepted, clients should connect to MySQL Server using a method that protects the password. Possibilities include [TLS / SSL](#tls), IPsec, or a private network.
137137

138+
##### `allowNativePasswords`
139+
140+
```
141+
Type: bool
142+
Valid Values: true, false
143+
Default: false
144+
```
145+
`allowNativePasswords=true` allows the usage of the mysql native password method.
146+
138147
##### `allowOldPasswords`
139148

140149
```

driver.go

+10-5
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
101101
}
102102

103103
// Handle response to auth packet, switch methods if possible
104-
if err = handleAuthResult(mc, cipher); err != nil {
104+
if err = handleAuthResult(mc); err != nil {
105105
// Authentication failed and MySQL has already closed the connection
106106
// (https://dev.mysql.com/doc/internals/en/authentication-fails.html).
107107
// Do not send COM_QUIT, just cleanup and return the error.
@@ -134,9 +134,9 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
134134
return mc, nil
135135
}
136136

137-
func handleAuthResult(mc *mysqlConn, cipher []byte) error {
137+
func handleAuthResult(mc *mysqlConn) error {
138138
// Read Result Packet
139-
err := mc.readResultOK()
139+
cipher, err := mc.readResultOK()
140140
if err == nil {
141141
return nil // auth successful
142142
}
@@ -153,15 +153,20 @@ func handleAuthResult(mc *mysqlConn, cipher []byte) error {
153153
if err = mc.writeOldAuthPacket(cipher); err != nil {
154154
return err
155155
}
156-
err = mc.readResultOK()
156+
_, err = mc.readResultOK()
157157
} else if mc.cfg.AllowCleartextPasswords && err == ErrCleartextPassword {
158158
// Retry with clear text password for
159159
// http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html
160160
// http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html
161161
if err = mc.writeClearAuthPacket(); err != nil {
162162
return err
163163
}
164-
err = mc.readResultOK()
164+
_, err = mc.readResultOK()
165+
} else if mc.cfg.AllowNativePasswords && err == ErrNativePassword {
166+
if err = mc.writeNativeAuthPacket(cipher); err != nil {
167+
return err
168+
}
169+
_, err = mc.readResultOK()
165170
}
166171
return err
167172
}

dsn.go

+18
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ type Config struct {
4646

4747
AllowAllFiles bool // Allow all files to be used with LOAD DATA LOCAL INFILE
4848
AllowCleartextPasswords bool // Allows the cleartext client side plugin
49+
AllowNativePasswords bool // Allows the native password authentication method
4950
AllowOldPasswords bool // Allows the old insecure password method
5051
ClientFoundRows bool // Return number of matching rows instead of rows changed
5152
ColumnsWithAlias bool // Prepend table alias to column names
@@ -101,6 +102,15 @@ func (cfg *Config) FormatDSN() string {
101102
}
102103
}
103104

105+
if cfg.AllowNativePasswords {
106+
if hasParam {
107+
buf.WriteString("&allowNativePasswords=true")
108+
} else {
109+
hasParam = true
110+
buf.WriteString("?allowNativePasswords=true")
111+
}
112+
}
113+
104114
if cfg.AllowOldPasswords {
105115
if hasParam {
106116
buf.WriteString("&allowOldPasswords=true")
@@ -381,6 +391,14 @@ func parseDSNParams(cfg *Config, params string) (err error) {
381391
return errors.New("invalid bool value: " + value)
382392
}
383393

394+
// Use native password authentication
395+
case "allowNativePasswords":
396+
var isBool bool
397+
cfg.AllowNativePasswords, isBool = readBool(value)
398+
if !isBool {
399+
return errors.New("invalid bool value: " + value)
400+
}
401+
384402
// Use old authentication mode (pre MySQL 4.1)
385403
case "allowOldPasswords":
386404
var isBool bool

errors.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ var (
2222
ErrInvalidConn = errors.New("invalid connection")
2323
ErrMalformPkt = errors.New("malformed packet")
2424
ErrNoTLS = errors.New("TLS requested but server does not support TLS")
25-
ErrOldPassword = errors.New("this user requires old password authentication. If you still want to use it, please add 'allowOldPasswords=1' to your DSN. See also https://github.com/go-sql-driver/mysql/wiki/old_passwords")
2625
ErrCleartextPassword = errors.New("this user requires clear text authentication. If you still want to use it, please add 'allowCleartextPasswords=1' to your DSN")
26+
ErrNativePassword = errors.New("this user requires mysql native password authentication.")
27+
ErrOldPassword = errors.New("this user requires old password authentication. If you still want to use it, please add 'allowOldPasswords=1' to your DSN. See also https://github.com/go-sql-driver/mysql/wiki/old_passwords")
2728
ErrUnknownPlugin = errors.New("this authentication plugin is not supported")
2829
ErrOldProtocol = errors.New("MySQL server does not support required protocol 41+")
2930
ErrPktSync = errors.New("commands out of sync. You can't run this command now")

infile.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,8 @@ func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
173173

174174
// read OK packet
175175
if err == nil {
176-
return mc.readResultOK()
176+
_, err = mc.readResultOK()
177+
return err
177178
}
178179

179180
mc.readPacket()

packets.go

+35-9
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,26 @@ func (mc *mysqlConn) writeClearAuthPacket() error {
372372
return mc.writePacket(data)
373373
}
374374

375+
// Native password authentication method
376+
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
377+
func (mc *mysqlConn) writeNativeAuthPacket(cipher []byte) error {
378+
scrambleBuff := scramblePassword(cipher, []byte(mc.cfg.Passwd))
379+
380+
// Calculate the packet length and add a tailing 0
381+
pktLen := len(scrambleBuff)
382+
data := mc.buf.takeSmallBuffer(4 + pktLen)
383+
if data == nil {
384+
// can not take the buffer. Something must be wrong with the connection
385+
errLog.Print(ErrBusyBuffer)
386+
return driver.ErrBadConn
387+
}
388+
389+
// Add the scramble
390+
copy(data[4:], scrambleBuff)
391+
392+
return mc.writePacket(data)
393+
}
394+
375395
/******************************************************************************
376396
* Command Packets *
377397
******************************************************************************/
@@ -445,36 +465,42 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
445465
******************************************************************************/
446466

447467
// Returns error if Packet is not an 'Result OK'-Packet
448-
func (mc *mysqlConn) readResultOK() error {
468+
func (mc *mysqlConn) readResultOK() ([]byte, error) {
449469
data, err := mc.readPacket()
450470
if err == nil {
451471
// packet indicator
452472
switch data[0] {
453473

454474
case iOK:
455-
return mc.handleOkPacket(data)
475+
return nil, mc.handleOkPacket(data)
456476

457477
case iEOF:
458478
if len(data) > 1 {
459-
plugin := string(data[1:bytes.IndexByte(data, 0x00)])
479+
pluginEndIndex := bytes.IndexByte(data, 0x00)
480+
plugin := string(data[1:pluginEndIndex])
481+
cipher := data[pluginEndIndex+1 : len(data)-1]
482+
460483
if plugin == "mysql_old_password" {
461484
// using old_passwords
462-
return ErrOldPassword
485+
return cipher, ErrOldPassword
463486
} else if plugin == "mysql_clear_password" {
464487
// using clear text password
465-
return ErrCleartextPassword
488+
return cipher, ErrCleartextPassword
489+
} else if plugin == "mysql_native_password" {
490+
// using mysql default authentication method
491+
return cipher, ErrNativePassword
466492
} else {
467-
return ErrUnknownPlugin
493+
return cipher, ErrUnknownPlugin
468494
}
469495
} else {
470-
return ErrOldPassword
496+
return nil, ErrOldPassword
471497
}
472498

473499
default: // Error otherwise
474-
return mc.handleErrorPacket(data)
500+
return nil, mc.handleErrorPacket(data)
475501
}
476502
}
477-
return err
503+
return nil, err
478504
}
479505

480506
// Result Set Header Packet

0 commit comments

Comments
 (0)