auth.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. // Go MySQL Driver - A MySQL-Driver for Go's database/sql package
  2. //
  3. // Copyright 2018 The Go-MySQL-Driver Authors. All rights reserved.
  4. //
  5. // This Source Code Form is subject to the terms of the Mozilla Public
  6. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  7. // You can obtain one at http://mozilla.org/MPL/2.0/.
  8. package mysql
  9. import (
  10. "crypto/rand"
  11. "crypto/rsa"
  12. "crypto/sha1"
  13. "crypto/sha256"
  14. "crypto/x509"
  15. "encoding/pem"
  16. )
  17. // Hash password using pre 4.1 (old password) method
  18. // https://github.com/atcurtis/mariadb/blob/master/mysys/my_rnd.c
  19. type myRnd struct {
  20. seed1, seed2 uint32
  21. }
  22. const myRndMaxVal = 0x3FFFFFFF
  23. // Pseudo random number generator
  24. func newMyRnd(seed1, seed2 uint32) *myRnd {
  25. return &myRnd{
  26. seed1: seed1 % myRndMaxVal,
  27. seed2: seed2 % myRndMaxVal,
  28. }
  29. }
  30. // Tested to be equivalent to MariaDB's floating point variant
  31. // http://play.golang.org/p/QHvhd4qved
  32. // http://play.golang.org/p/RG0q4ElWDx
  33. func (r *myRnd) NextByte() byte {
  34. r.seed1 = (r.seed1*3 + r.seed2) % myRndMaxVal
  35. r.seed2 = (r.seed1 + r.seed2 + 33) % myRndMaxVal
  36. return byte(uint64(r.seed1) * 31 / myRndMaxVal)
  37. }
  38. // Generate binary hash from byte string using insecure pre 4.1 method
  39. func pwHash(password []byte) (result [2]uint32) {
  40. var add uint32 = 7
  41. var tmp uint32
  42. result[0] = 1345345333
  43. result[1] = 0x12345671
  44. for _, c := range password {
  45. // skip spaces and tabs in password
  46. if c == ' ' || c == '\t' {
  47. continue
  48. }
  49. tmp = uint32(c)
  50. result[0] ^= (((result[0] & 63) + add) * tmp) + (result[0] << 8)
  51. result[1] += (result[1] << 8) ^ result[0]
  52. add += tmp
  53. }
  54. // Remove sign bit (1<<31)-1)
  55. result[0] &= 0x7FFFFFFF
  56. result[1] &= 0x7FFFFFFF
  57. return
  58. }
  59. // Hash password using insecure pre 4.1 method
  60. func scrambleOldPassword(scramble []byte, password string) []byte {
  61. if len(password) == 0 {
  62. return nil
  63. }
  64. scramble = scramble[:8]
  65. hashPw := pwHash([]byte(password))
  66. hashSc := pwHash(scramble)
  67. r := newMyRnd(hashPw[0]^hashSc[0], hashPw[1]^hashSc[1])
  68. var out [8]byte
  69. for i := range out {
  70. out[i] = r.NextByte() + 64
  71. }
  72. mask := r.NextByte()
  73. for i := range out {
  74. out[i] ^= mask
  75. }
  76. return out[:]
  77. }
  78. // Hash password using 4.1+ method (SHA1)
  79. func scramblePassword(scramble []byte, password string) []byte {
  80. if len(password) == 0 {
  81. return nil
  82. }
  83. // stage1Hash = SHA1(password)
  84. crypt := sha1.New()
  85. crypt.Write([]byte(password))
  86. stage1 := crypt.Sum(nil)
  87. // scrambleHash = SHA1(scramble + SHA1(stage1Hash))
  88. // inner Hash
  89. crypt.Reset()
  90. crypt.Write(stage1)
  91. hash := crypt.Sum(nil)
  92. // outer Hash
  93. crypt.Reset()
  94. crypt.Write(scramble)
  95. crypt.Write(hash)
  96. scramble = crypt.Sum(nil)
  97. // token = scrambleHash XOR stage1Hash
  98. for i := range scramble {
  99. scramble[i] ^= stage1[i]
  100. }
  101. return scramble
  102. }
  103. // Hash password using MySQL 8+ method (SHA256)
  104. func scrambleSHA256Password(scramble []byte, password string) []byte {
  105. if len(password) == 0 {
  106. return nil
  107. }
  108. // XOR(SHA256(password), SHA256(SHA256(SHA256(password)), scramble))
  109. crypt := sha256.New()
  110. crypt.Write([]byte(password))
  111. message1 := crypt.Sum(nil)
  112. crypt.Reset()
  113. crypt.Write(message1)
  114. message1Hash := crypt.Sum(nil)
  115. crypt.Reset()
  116. crypt.Write(message1Hash)
  117. crypt.Write(scramble)
  118. message2 := crypt.Sum(nil)
  119. for i := range message1 {
  120. message1[i] ^= message2[i]
  121. }
  122. return message1
  123. }
  124. func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, bool, error) {
  125. switch plugin {
  126. case "caching_sha2_password":
  127. authResp := scrambleSHA256Password(authData, mc.cfg.Passwd)
  128. return authResp, (authResp == nil), nil
  129. case "mysql_old_password":
  130. if !mc.cfg.AllowOldPasswords {
  131. return nil, false, ErrOldPassword
  132. }
  133. // Note: there are edge cases where this should work but doesn't;
  134. // this is currently "wontfix":
  135. // https://github.com/go-sql-driver/mysql/issues/184
  136. authResp := scrambleOldPassword(authData[:8], mc.cfg.Passwd)
  137. return authResp, true, nil
  138. case "mysql_clear_password":
  139. if !mc.cfg.AllowCleartextPasswords {
  140. return nil, false, ErrCleartextPassword
  141. }
  142. // http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html
  143. // http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html
  144. return []byte(mc.cfg.Passwd), true, nil
  145. case "mysql_native_password":
  146. if !mc.cfg.AllowNativePasswords {
  147. return nil, false, ErrNativePassword
  148. }
  149. // https://dev.mysql.com/doc/internals/en/secure-password-authentication.html
  150. // Native password authentication only need and will need 20-byte challenge.
  151. authResp := scramblePassword(authData[:20], mc.cfg.Passwd)
  152. return authResp, false, nil
  153. default:
  154. errLog.Print("unknown auth plugin:", plugin)
  155. return nil, false, ErrUnknownPlugin
  156. }
  157. }
  158. func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
  159. // Read Result Packet
  160. authData, newPlugin, err := mc.readAuthResult()
  161. if err != nil {
  162. return err
  163. }
  164. // handle auth plugin switch, if requested
  165. if newPlugin != "" {
  166. // If CLIENT_PLUGIN_AUTH capability is not supported, no new cipher is
  167. // sent and we have to keep using the cipher sent in the init packet.
  168. if authData == nil {
  169. authData = oldAuthData
  170. }
  171. plugin = newPlugin
  172. authResp, addNUL, err := mc.auth(authData, plugin)
  173. if err != nil {
  174. return err
  175. }
  176. if err = mc.writeAuthSwitchPacket(authResp, addNUL); err != nil {
  177. return err
  178. }
  179. // Read Result Packet
  180. authData, newPlugin, err = mc.readAuthResult()
  181. if err != nil {
  182. return err
  183. }
  184. // Do not allow to change the auth plugin more than once
  185. if newPlugin != "" {
  186. return ErrMalformPkt
  187. }
  188. }
  189. switch plugin {
  190. // https://insidemysql.com/preparing-your-community-connector-for-mysql-8-part-2-sha256/
  191. case "caching_sha2_password":
  192. switch len(authData) {
  193. case 0:
  194. return nil // auth successful
  195. case 1:
  196. switch authData[0] {
  197. case cachingSha2PasswordFastAuthSuccess:
  198. if err = mc.readResultOK(); err == nil {
  199. return nil // auth successful
  200. }
  201. case cachingSha2PasswordPerformFullAuthentication:
  202. if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
  203. // write cleartext auth packet
  204. err = mc.writeAuthSwitchPacket([]byte(mc.cfg.Passwd), true)
  205. if err != nil {
  206. return err
  207. }
  208. } else {
  209. seed := oldAuthData
  210. // TODO: allow to specify a local file with the pub key via
  211. // the DSN
  212. // request public key
  213. data := mc.buf.takeSmallBuffer(4 + 1)
  214. data[4] = cachingSha2PasswordRequestPublicKey
  215. mc.writePacket(data)
  216. // parse public key
  217. data, err := mc.readPacket()
  218. if err != nil {
  219. return err
  220. }
  221. block, _ := pem.Decode(data[1:])
  222. pub, err := x509.ParsePKIXPublicKey(block.Bytes)
  223. if err != nil {
  224. return err
  225. }
  226. // send encrypted password
  227. plain := make([]byte, len(mc.cfg.Passwd)+1)
  228. copy(plain, mc.cfg.Passwd)
  229. for i := range plain {
  230. j := i % len(seed)
  231. plain[i] ^= seed[j]
  232. }
  233. sha1 := sha1.New()
  234. enc, err := rsa.EncryptOAEP(sha1, rand.Reader, pub.(*rsa.PublicKey), plain, nil)
  235. if err != nil {
  236. return err
  237. }
  238. if err = mc.writeAuthSwitchPacket(enc, false); err != nil {
  239. return err
  240. }
  241. }
  242. if err = mc.readResultOK(); err == nil {
  243. return nil // auth successful
  244. }
  245. default:
  246. return ErrMalformPkt
  247. }
  248. default:
  249. return ErrMalformPkt
  250. }
  251. default:
  252. return nil // auth successful
  253. }
  254. return err
  255. }