-
Notifications
You must be signed in to change notification settings - Fork 12
/
handshake_response41.go
161 lines (130 loc) · 3.99 KB
/
handshake_response41.go
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
package mysqlproto
import (
"crypto/sha1"
)
func HandshakeResponse41(
capabilityFlags uint32,
characterSet byte,
username string,
password string,
authPluginData []byte,
database string,
authPluginName string,
connectAttrs map[string]string,
) []byte {
capabilityFlags |= CLIENT_PROTOCOL_41 // must be always set
// todo not supported
capabilityFlags &= ^CLIENT_SSL
capabilityFlags &= ^CLIENT_COMPRESS
capabilityFlags &= ^CLIENT_DEPRECATE_EOF
var packetSize uint32 = 0
packetSize += 4 // capability flags
packetSize += 4 // packet size
packetSize += 1 // character set
packetSize += 23 // reserved string
packetSize += uint32(len(username)) + 1 // + null character
var authResponse []byte
switch authPluginName {
case "mysql_native_password":
authResponse = nativePassword(password, authPluginData)
case "mysql_old_password":
panic(`auth method "mysql_old_password" not supported`) // todo
default:
panic(`invalid auth method "` + authPluginName + `"`)
}
packetSize += uint32(len(authResponse))
var authResponseLen []byte
// todo support all methods
if capabilityFlags&CLIENT_SECURE_CONNECTION > 0 {
authResponseLen = []byte{byte(len(authResponse))}
packetSize += uint32(len(authResponseLen))
capabilityFlags &= ^CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA
} else {
authResponse = append(authResponse, 0x00)
packetSize += 1
capabilityFlags &= ^CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA
capabilityFlags &= ^CLIENT_SECURE_CONNECTION
}
if l := len(database); l > 0 {
capabilityFlags |= CLIENT_CONNECT_WITH_DB
packetSize += uint32(l) + 1 // + null character
} else {
capabilityFlags &= ^CLIENT_CONNECT_WITH_DB
}
if l := len(authPluginName); l > 0 {
capabilityFlags |= CLIENT_PLUGIN_AUTH
packetSize += uint32(l) + 1 // + null character
} else {
capabilityFlags &= ^CLIENT_PLUGIN_AUTH
}
var attrData []byte
if len(connectAttrs) > 0 {
var data []byte
capabilityFlags |= CLIENT_CONNECT_ATTRS
for key, value := range connectAttrs {
data = append(data, lenEncStr(key)...)
data = append(data, lenEncStr(value)...)
}
total := lenEncInt(uint64(len(data)))
attrData = make([]byte, len(total)+len(data))
copy(attrData[:len(total)], total)
copy(attrData[len(total):], data)
} else {
capabilityFlags &= ^CLIENT_CONNECT_ATTRS
}
packetSize += uint32(len(attrData))
packet := make([]byte, 0, packetSize+4) // header: 3 bytes length + sequence ID
packet = append(packet,
byte(packetSize),
byte(packetSize>>8),
byte(packetSize>>16),
byte(0x01), // sequence ID is always 1 on this stage
)
packet = append(packet,
byte(capabilityFlags),
byte(capabilityFlags>>8),
byte(capabilityFlags>>16),
byte(capabilityFlags>>24),
)
packet = append(packet,
byte(packetSize),
byte(packetSize>>8),
byte(packetSize>>16),
byte(packetSize>>24),
)
packet = append(packet, characterSet)
packet = append(packet, make([]byte, 23)...)
packet = append(packet, username...)
packet = append(packet, 0x00)
packet = append(packet, authResponseLen...)
packet = append(packet, authResponse...)
if capabilityFlags&CLIENT_CONNECT_WITH_DB > 0 {
packet = append(packet, database...)
packet = append(packet, 0x00)
}
packet = append(packet, authPluginName...)
packet = append(packet, 0x00)
packet = append(packet, attrData...)
return packet
}
// https://dev.mysql.com/doc/internals/en/secure-password-authentication.html#packet-Authentication::Native41
// SHA1( password ) XOR SHA1( "20-bytes random data from server" <concat> SHA1( SHA1( password ) ) )
func nativePassword(password string, authPluginData []byte) []byte {
if len(password) == 0 {
return nil
}
hash := sha1.New()
hash.Write([]byte(password))
hashPass := hash.Sum(nil)
hash.Reset()
hash.Write(hashPass)
doubleHashPass := hash.Sum(nil)
hash.Reset()
hash.Write(authPluginData)
hash.Write(doubleHashPass)
salt := hash.Sum(nil)
for i, b := range hashPass {
hashPass[i] = b ^ salt[i]
}
return hashPass
}