-
Notifications
You must be signed in to change notification settings - Fork 1k
mysql8 auth compatibility #781
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
cc2445c
136935a
6cff231
0e9bf02
dd87a68
ea490bf
2591f26
19e421f
687a2b9
963974e
7df3e00
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -26,7 +26,11 @@ func authPluginAllowed(pluginName string) bool { | |||||
return false | ||||||
} | ||||||
|
||||||
// See: http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::Handshake | ||||||
// See: | ||||||
// - https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_handshake_v10.html | ||||||
// - https://github.com/alibaba/canal/blob/0ec46991499a22870dde4ae736b2586cbcbfea94/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/packets/server/HandshakeInitializationPacket.java#L89 | ||||||
// - https://github.com/vapor/mysql-nio/blob/main/Sources/MySQLNIO/Protocol/MySQLProtocol%2BHandshakeV10.swift | ||||||
// - https://github.com/github/vitess-gh/blob/70ae1a2b3a116ff6411b0f40852d6e71382f6e07/go/mysql/client.go | ||||||
func (c *Conn) readInitialHandshake() error { | ||||||
data, err := c.ReadPacket() | ||||||
if err != nil { | ||||||
|
@@ -40,24 +44,28 @@ func (c *Conn) readInitialHandshake() error { | |||||
if data[0] < MinProtocolVersion { | ||||||
return errors.Errorf("invalid protocol version %d, must >= 10", data[0]) | ||||||
} | ||||||
pos := 1 | ||||||
|
||||||
// skip mysql version | ||||||
// mysql version end with 0x00 | ||||||
version := data[1 : bytes.IndexByte(data[1:], 0x00)+1] | ||||||
version := data[pos : bytes.IndexByte(data[pos:], 0x00)+1] | ||||||
c.serverVersion = string(version) | ||||||
pos := 1 + len(version) | ||||||
pos += len(version) + 1 /*trailing zero byte*/ | ||||||
|
||||||
// connection id length is 4 | ||||||
c.connectionID = binary.LittleEndian.Uint32(data[pos : pos+4]) | ||||||
pos += 4 | ||||||
|
||||||
c.salt = []byte{} | ||||||
c.salt = append(c.salt, data[pos:pos+8]...) | ||||||
// first 8 bytes of the plugin provided data (scramble) | ||||||
c.salt = append(c.salt[:0], data[pos:pos+8]...) | ||||||
pos += 8 | ||||||
|
||||||
// skip filter | ||||||
pos += 8 + 1 | ||||||
if data[pos] != 0 { // 0x00 byte, terminating the first part of a scramble | ||||||
return errors.Errorf("expect 0x00 after scramble, got %q", rune(data[pos])) | ||||||
} | ||||||
pos++ | ||||||
|
||||||
// capability lower 2 bytes | ||||||
// The lower 2 bytes of the Capabilities Flags | ||||||
c.capability = uint32(binary.LittleEndian.Uint16(data[pos : pos+2])) | ||||||
// check protocol | ||||||
if c.capability&CLIENT_PROTOCOL_41 == 0 { | ||||||
|
@@ -69,35 +77,53 @@ func (c *Conn) readInitialHandshake() error { | |||||
pos += 2 | ||||||
|
||||||
if len(data) > pos { | ||||||
// skip server charset | ||||||
// default server a_protocol_character_set, only the lower 8-bits | ||||||
// c.charset = data[pos] | ||||||
pos += 1 | ||||||
|
||||||
c.status = binary.LittleEndian.Uint16(data[pos : pos+2]) | ||||||
pos += 2 | ||||||
// capability flags (upper 2 bytes) | ||||||
|
||||||
// The upper 2 bytes of the Capabilities Flags | ||||||
c.capability = uint32(binary.LittleEndian.Uint16(data[pos:pos+2]))<<16 | c.capability | ||||||
pos += 2 | ||||||
|
||||||
// auth_data is end with 0x00, min data length is 13 + 8 = 21 | ||||||
// ref to https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::Handshake | ||||||
maxAuthDataLen := 21 | ||||||
if c.capability&CLIENT_PLUGIN_AUTH != 0 && int(data[pos]) > maxAuthDataLen { | ||||||
maxAuthDataLen = int(data[pos]) | ||||||
// length of the combined auth_plugin_data (scramble), if auth_plugin_data_len is > 0 | ||||||
authPluginDataLen := data[pos] | ||||||
if (c.capability&CLIENT_PLUGIN_AUTH == 0) && (authPluginDataLen > 0) { | ||||||
return errors.Errorf("invalid auth plugin data filler %d", authPluginDataLen) | ||||||
} | ||||||
pos++ | ||||||
|
||||||
// skip reserved (all [00]) | ||||||
pos += 10 + 1 | ||||||
// skip reserved (all [00] ?) | ||||||
pos += 10 | ||||||
|
||||||
// auth_data is end with 0x00, so we need to trim 0x00 | ||||||
resetOfAuthDataEndPos := pos + maxAuthDataLen - 8 - 1 | ||||||
c.salt = append(c.salt, data[pos:resetOfAuthDataEndPos]...) | ||||||
if c.capability&CLIENT_SECURE_CONNECTION != 0 { | ||||||
// Rest of the plugin provided data (scramble) | ||||||
|
||||||
// skip reset of end pos | ||||||
pos = resetOfAuthDataEndPos + 1 | ||||||
// $len=MAX(13, length of auth-plugin-data - 8) | ||||||
rest := int(authPluginDataLen) - 8 | ||||||
if max := 13; rest > max { | ||||||
atercattus marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
rest = max | ||||||
} | ||||||
if data[pos+rest-1] != 0 { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
BTW, im not familiar with this part so double check that why there's NULL after scramble? In the MySQL protocol doc I think it's a fixed length string, rather than a NULL-terminated string. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I found the origin of it:
And sha256_password auth implementation follows this rule:
Also, alibaba channel contains the same description:
I used a translator for this. I can remove this check by \0, but it looks like a historic standard convention. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's about 20 bytes len I found this:
Right now I don't see places where it would be more than 20 bytes, but let it be for the future. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I checked this logic for mysql 8.0 and 5.7. Unfortunately, compatibility with 5.6- was broken since we added |
||||||
return errors.Errorf("expect 0x00 after scramble, got %q", rune(data[pos])) | ||||||
} | ||||||
|
||||||
authPluginDataPart2 := data[pos : pos+rest-1] | ||||||
pos += rest | ||||||
|
||||||
c.salt = append(c.salt, authPluginDataPart2...) | ||||||
} | ||||||
|
||||||
if c.capability&CLIENT_PLUGIN_AUTH != 0 { | ||||||
c.authPluginName = string(data[pos : len(data)-1]) | ||||||
c.authPluginName = string(data[pos : pos+bytes.IndexByte(data[pos:], 0x00)]) | ||||||
pos += len(c.authPluginName) | ||||||
|
||||||
if data[pos] != 0 { | ||||||
return errors.Errorf("expect 0x00 after authPluginName, got %q", rune(data[pos])) | ||||||
} | ||||||
// pos++ // ineffectual | ||||||
} | ||||||
} | ||||||
|
||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just remind that this is a deprecated flag https://dev.mysql.com/doc/dev/mysql-server/latest/group__group__cs__capabilities__flags.html#ga8be684cc38eeca913698414efec06933 , we can skip this if-check or leave it because no test is broken.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it seems always set.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about merging this PR as is? I added an issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm