vitess.io/vitess@v0.16.2/go/mysql/auth_server.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package mysql 18 19 import ( 20 "crypto/rand" 21 "crypto/rsa" 22 "crypto/sha1" 23 "crypto/sha256" 24 "crypto/subtle" 25 "encoding/hex" 26 "net" 27 "sync" 28 29 "vitess.io/vitess/go/vt/log" 30 "vitess.io/vitess/go/vt/proto/vtrpc" 31 "vitess.io/vitess/go/vt/vterrors" 32 ) 33 34 // AuthServer is the interface that servers must implement to validate 35 // users and passwords. It needs to be able to return a list of AuthMethod 36 // interfaces which implement the supported auth methods for the server. 37 type AuthServer interface { 38 // AuthMethods returns a list of auth methods that are part of this 39 // interface. Building an authentication server usually means 40 // creating AuthMethod instances with the known helpers for the 41 // currently supported AuthMethod implementations. 42 // 43 // When a client connects, the server checks the list of auth methods 44 // available. If an auth method for the requested auth mechanism by the 45 // client is available, it will be used immediately. 46 // 47 // If there is no overlap between the provided auth methods and 48 // the one the client requests, the server will send back an 49 // auth switch request using the first provided AuthMethod in this list. 50 AuthMethods() []AuthMethod 51 52 // DefaultAuthMethodDescription returns the auth method that the auth server 53 // sends during the initial server handshake. This needs to be either 54 // `mysql_native_password` or `caching_sha2_password` as those are the only 55 // supported auth methods during the initial handshake. 56 // 57 // It's not needed to also support those methods in the AuthMethods(), 58 // in fact, if you want to only support for example clear text passwords, 59 // you must still return `mysql_native_password` or `caching_sha2_password` 60 // here and the auth switch protocol will be used to switch to clear text. 61 DefaultAuthMethodDescription() AuthMethodDescription 62 } 63 64 // AuthMethod interface for concrete auth method implementations. 65 // When building an auth server, you usually don't implement these yourself 66 // but the helper methods to build AuthMethod instances should be used. 67 type AuthMethod interface { 68 // Name returns the auth method description for this implementation. 69 // This is the name that is sent as the auth plugin name during the 70 // Mysql authentication protocol handshake. 71 Name() AuthMethodDescription 72 73 // HandleUser verifies if the current auth method can authenticate 74 // the given user with the current auth method. This can be useful 75 // for example if you only have a plain text of hashed password 76 // for specific users and not all users and auth method support 77 // depends on what you have. 78 HandleUser(conn *Conn, user string) bool 79 80 // AllowClearTextWithoutTLS identifies if an auth method is allowed 81 // on a plain text connection. This check is only enforced 82 // if the listener has AllowClearTextWithoutTLS() disabled. 83 AllowClearTextWithoutTLS() bool 84 85 // AuthPluginData generates the information for the auth plugin. 86 // This is included in for example the auth switch request. This 87 // is auth plugin specific and opaque to the Mysql handshake 88 // protocol. 89 AuthPluginData() ([]byte, error) 90 91 // HandleAuthPluginData handles the returned auth plugin data from 92 // the client. The original data the server sent is also included 93 // which can include things like the salt for `mysql_native_password`. 94 // 95 // The remote address is provided for plugins that also want to 96 // do additional checks like IP based restrictions. 97 HandleAuthPluginData(conn *Conn, user string, serverAuthPluginData []byte, clientAuthPluginData []byte, remoteAddr net.Addr) (Getter, error) 98 } 99 100 // UserValidator is an interface that allows checking if a specific 101 // user will work for an auth method. This interface is called by 102 // all the default helpers that create AuthMethod instances for 103 // the various supported Mysql authentication methods. 104 type UserValidator interface { 105 HandleUser(user string) bool 106 } 107 108 // CacheState is a state that is returned by the UserEntryWithCacheHash 109 // method from the CachingStorage interface. This state is needed to indicate 110 // whether the authentication is accepted, rejected by the cache itself 111 // or if the cache can't fullfill the request. In that case it indicates 112 // that with AuthNeedMoreData. 113 type CacheState int 114 115 const ( 116 // AuthRejected is used when the cache knows the request can be rejected. 117 AuthRejected CacheState = iota 118 // AuthAccepted is used when the cache knows the request can be accepted. 119 AuthAccepted 120 // AuthNeedMoreData is used when the cache doesn't know the answer and more data is needed. 121 AuthNeedMoreData 122 ) 123 124 // HashStorage describes an object that is suitable to retrieve user information 125 // based on the hashed authentication response for mysql_native_password. 126 // 127 // In general, an implementation of this would use an internally stored password 128 // that is hashed twice with SHA1. 129 // 130 // The VerifyHashedMysqlNativePassword helper method can be used to verify 131 // such a hash based on the salt and auth response provided here after retrieving 132 // the hashed password from the storage. 133 type HashStorage interface { 134 UserEntryWithHash(conn *Conn, salt []byte, user string, authResponse []byte, remoteAddr net.Addr) (Getter, error) 135 } 136 137 // PlainTextStorage describes an object that is suitable to retrieve user information 138 // based on the plain text password of a user. This can be obtained through various 139 // Mysql authentication methods, such as `mysql_clear_passwrd`, `dialog` or 140 // `caching_sha2_password` in the full authentication handshake case of the latter. 141 // 142 // This mechanism also would allow for picking your own password storage in the backend, 143 // such as BCrypt, SCrypt, PBKDF2 or Argon2 once the plain text is obtained. 144 // 145 // When comparing plain text passwords directly, please ensure to use `subtle.ConstantTimeCompare` 146 // to prevent timing based attacks on the password. 147 type PlainTextStorage interface { 148 UserEntryWithPassword(conn *Conn, user string, password string, remoteAddr net.Addr) (Getter, error) 149 } 150 151 // CachingStorage describes an object that is suitable to retrieve user information 152 // based on a hashed value of the password. This applies to the `caching_sha2_password` 153 // authentication method. 154 // 155 // The cache would hash the password internally as `SHA256(SHA256(password))`. 156 // 157 // The VerifyHashedCachingSha2Password helper method can be used to verify 158 // such a hash based on the salt and auth response provided here after retrieving 159 // the hashed password from the cache. 160 type CachingStorage interface { 161 UserEntryWithCacheHash(conn *Conn, salt []byte, user string, authResponse []byte, remoteAddr net.Addr) (Getter, CacheState, error) 162 } 163 164 // NewMysqlNativeAuthMethod will create a new AuthMethod that implements the 165 // `mysql_native_password` handshake. The caller will need to provide a storage 166 // object and validator that will be called during the handshake phase. 167 func NewMysqlNativeAuthMethod(layer HashStorage, validator UserValidator) AuthMethod { 168 authMethod := mysqlNativePasswordAuthMethod{ 169 storage: layer, 170 validator: validator, 171 } 172 return &authMethod 173 } 174 175 // NewMysqlClearAuthMethod will create a new AuthMethod that implements the 176 // `mysql_clear_password` handshake. The caller will need to provide a storage 177 // object for plain text passwords and validator that will be called during the 178 // handshake phase. 179 func NewMysqlClearAuthMethod(layer PlainTextStorage, validator UserValidator) AuthMethod { 180 authMethod := mysqlClearAuthMethod{ 181 storage: layer, 182 validator: validator, 183 } 184 return &authMethod 185 } 186 187 // Constants for the dialog plugin. 188 const ( 189 // Default message if no custom message 190 // is configured. This is used when the message 191 // is the empty string. 192 mysqlDialogDefaultMessage = "Enter password: " 193 194 // Dialog plugin is similar to clear text, but can respond to multiple 195 // prompts in a row. This is not yet implemented. 196 // Follow questions should be prepended with a `cmd` byte: 197 // 0x02 - ordinary question 198 // 0x03 - last question 199 // 0x04 - password question 200 // 0x05 - last password 201 mysqlDialogAskPassword = 0x04 202 ) 203 204 // NewMysqlDialogAuthMethod will create a new AuthMethod that implements the 205 // `dialog` handshake. The caller will need to provide a storage object for plain 206 // text passwords and validator that will be called during the handshake phase. 207 // The message given will be sent as part of the dialog. If the empty string is 208 // provided, the default message of "Enter password: " will be used. 209 func NewMysqlDialogAuthMethod(layer PlainTextStorage, validator UserValidator, msg string) AuthMethod { 210 if msg == "" { 211 msg = mysqlDialogDefaultMessage 212 } 213 214 authMethod := mysqlDialogAuthMethod{ 215 storage: layer, 216 validator: validator, 217 msg: msg, 218 } 219 return &authMethod 220 } 221 222 // NewSha2CachingAuthMethod will create a new AuthMethod that implements the 223 // `caching_sha2_password` handshake. The caller will need to provide a cache 224 // object for the fast auth path and a plain text storage object that will 225 // be called if the return of the first layer indicates the full auth dance is 226 // needed. 227 // 228 // Right now we only support caching_sha2_password over TLS or a Unix socket. 229 // 230 // If TLS is not enabled, the client needs to encrypt it with the public 231 // key of the server. In that case, Vitess is already configured with 232 // a certificate anyway, so we recommend to use TLS if you want to use 233 // caching_sha2_password in that case instead of allowing the plain 234 // text fallback path here. 235 // 236 // This might change in the future if there's a good argument and implementation 237 // for allowing the plain text path here as well. 238 func NewSha2CachingAuthMethod(layer1 CachingStorage, layer2 PlainTextStorage, validator UserValidator) AuthMethod { 239 authMethod := mysqlCachingSha2AuthMethod{ 240 cache: layer1, 241 storage: layer2, 242 validator: validator, 243 } 244 return &authMethod 245 } 246 247 // ScrambleMysqlNativePassword computes the hash of the password using 4.1+ method. 248 // 249 // This can be used for example inside a `mysql_native_password` plugin implementation 250 // if the backend storage implements storage of plain text passwords. 251 func ScrambleMysqlNativePassword(salt, password []byte) []byte { 252 if len(password) == 0 { 253 return nil 254 } 255 256 // stage1Hash = SHA1(password) 257 crypt := sha1.New() 258 crypt.Write(password) 259 stage1 := crypt.Sum(nil) 260 261 // scrambleHash = SHA1(salt + SHA1(stage1Hash)) 262 // inner Hash 263 crypt.Reset() 264 crypt.Write(stage1) 265 hash := crypt.Sum(nil) 266 // outer Hash 267 crypt.Reset() 268 crypt.Write(salt) 269 crypt.Write(hash) 270 scramble := crypt.Sum(nil) 271 272 // token = scrambleHash XOR stage1Hash 273 for i := range scramble { 274 scramble[i] ^= stage1[i] 275 } 276 return scramble 277 } 278 279 // DecodeMysqlNativePasswordHex decodes the standard format used by MySQL 280 // for 4.1 style password hashes. It drops the optionally leading * before 281 // decoding the rest as a hex encoded string. 282 func DecodeMysqlNativePasswordHex(hexEncodedPassword string) ([]byte, error) { 283 if hexEncodedPassword[0] == '*' { 284 hexEncodedPassword = hexEncodedPassword[1:] 285 } 286 return hex.DecodeString(hexEncodedPassword) 287 } 288 289 // VerifyHashedMysqlNativePassword verifies a client reply against a stored hash. 290 // 291 // This can be used for example inside a `mysql_native_password` plugin implementation 292 // if the backend storage where the stored password is a SHA1(SHA1(password)). 293 // 294 // All values here are non encoded byte slices, so if you store for example the double 295 // SHA1 of the password as hex encoded characters, you need to decode that first. 296 // See DecodeMysqlNativePasswordHex for a decoding helper for the standard encoding 297 // format of this hash used by MySQL. 298 func VerifyHashedMysqlNativePassword(reply, salt, hashedNativePassword []byte) bool { 299 if len(reply) == 0 || len(hashedNativePassword) == 0 { 300 return false 301 } 302 303 // scramble = SHA1(salt+hash) 304 crypt := sha1.New() 305 crypt.Write(salt) 306 crypt.Write(hashedNativePassword) 307 scramble := crypt.Sum(nil) 308 309 for i := range scramble { 310 scramble[i] ^= reply[i] 311 } 312 hashStage1 := scramble 313 314 crypt.Reset() 315 crypt.Write(hashStage1) 316 candidateHash2 := crypt.Sum(nil) 317 318 return subtle.ConstantTimeCompare(candidateHash2, hashedNativePassword) == 1 319 } 320 321 // VerifyHashedCachingSha2Password verifies a client reply against a stored hash. 322 // 323 // This can be used for example inside a `caching_sha2_password` plugin implementation 324 // if the cache storage uses password keys with SHA256(SHA256(password)). 325 // 326 // All values here are non encoded byte slices, so if you store for example the double 327 // SHA256 of the password as hex encoded characters, you need to decode that first. 328 func VerifyHashedCachingSha2Password(reply, salt, hashedCachingSha2Password []byte) bool { 329 if len(reply) == 0 || len(hashedCachingSha2Password) == 0 { 330 return false 331 } 332 333 crypt := sha256.New() 334 crypt.Write(hashedCachingSha2Password) 335 crypt.Write(salt) 336 scramble := crypt.Sum(nil) 337 338 // token = scramble XOR stage1Hash 339 for i := range scramble { 340 scramble[i] ^= reply[i] 341 } 342 hashStage1 := scramble 343 344 crypt.Reset() 345 crypt.Write(hashStage1) 346 candidateHash2 := crypt.Sum(nil) 347 348 return subtle.ConstantTimeCompare(candidateHash2, hashedCachingSha2Password) == 1 349 } 350 351 // ScrambleCachingSha2Password computes the hash of the password using SHA256 as required by 352 // caching_sha2_password plugin for "fast" authentication 353 func ScrambleCachingSha2Password(salt []byte, password []byte) []byte { 354 if len(password) == 0 { 355 return nil 356 } 357 358 // stage1Hash = SHA256(password) 359 crypt := sha256.New() 360 crypt.Write(password) 361 stage1 := crypt.Sum(nil) 362 363 // scrambleHash = SHA256(SHA256(stage1Hash) + salt) 364 crypt.Reset() 365 crypt.Write(stage1) 366 innerHash := crypt.Sum(nil) 367 368 crypt.Reset() 369 crypt.Write(innerHash) 370 crypt.Write(salt) 371 scramble := crypt.Sum(nil) 372 373 // token = stage1Hash XOR scrambleHash 374 for i := range stage1 { 375 stage1[i] ^= scramble[i] 376 } 377 378 return stage1 379 } 380 381 // EncryptPasswordWithPublicKey obfuscates the password and encrypts it with server's public key as required by 382 // caching_sha2_password plugin for "full" authentication 383 func EncryptPasswordWithPublicKey(salt []byte, password []byte, pub *rsa.PublicKey) ([]byte, error) { 384 if len(password) == 0 { 385 return nil, nil 386 } 387 388 buffer := make([]byte, len(password)+1) 389 copy(buffer, password) 390 for i := range buffer { 391 buffer[i] ^= salt[i%len(salt)] 392 } 393 394 sha1Hash := sha1.New() 395 enc, err := rsa.EncryptOAEP(sha1Hash, rand.Reader, pub, buffer, nil) 396 if err != nil { 397 return nil, err 398 } 399 400 return enc, nil 401 } 402 403 type mysqlNativePasswordAuthMethod struct { 404 storage HashStorage 405 validator UserValidator 406 } 407 408 func (n *mysqlNativePasswordAuthMethod) Name() AuthMethodDescription { 409 return MysqlNativePassword 410 } 411 412 func (n *mysqlNativePasswordAuthMethod) HandleUser(conn *Conn, user string) bool { 413 return n.validator.HandleUser(user) 414 } 415 416 func (n *mysqlNativePasswordAuthMethod) AuthPluginData() ([]byte, error) { 417 salt, err := newSalt() 418 if err != nil { 419 return nil, err 420 } 421 return append(salt, 0), nil 422 } 423 424 func (n *mysqlNativePasswordAuthMethod) AllowClearTextWithoutTLS() bool { 425 return true 426 } 427 428 func (n *mysqlNativePasswordAuthMethod) HandleAuthPluginData(conn *Conn, user string, serverAuthPluginData []byte, clientAuthPluginData []byte, remoteAddr net.Addr) (Getter, error) { 429 if serverAuthPluginData[len(serverAuthPluginData)-1] != 0x00 { 430 return nil, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user) 431 } 432 433 salt := serverAuthPluginData[:len(serverAuthPluginData)-1] 434 return n.storage.UserEntryWithHash(conn, salt, user, clientAuthPluginData, remoteAddr) 435 } 436 437 type mysqlClearAuthMethod struct { 438 storage PlainTextStorage 439 validator UserValidator 440 } 441 442 func (n *mysqlClearAuthMethod) Name() AuthMethodDescription { 443 return MysqlClearPassword 444 } 445 446 func (n *mysqlClearAuthMethod) HandleUser(conn *Conn, user string) bool { 447 return n.validator.HandleUser(user) 448 } 449 450 func (n *mysqlClearAuthMethod) AuthPluginData() ([]byte, error) { 451 return nil, nil 452 } 453 454 func (n *mysqlClearAuthMethod) AllowClearTextWithoutTLS() bool { 455 return false 456 } 457 458 func (n *mysqlClearAuthMethod) HandleAuthPluginData(conn *Conn, user string, serverAuthPluginData []byte, clientAuthPluginData []byte, remoteAddr net.Addr) (Getter, error) { 459 return n.storage.UserEntryWithPassword(conn, user, string(clientAuthPluginData[:len(clientAuthPluginData)-1]), remoteAddr) 460 } 461 462 type mysqlDialogAuthMethod struct { 463 storage PlainTextStorage 464 validator UserValidator 465 msg string 466 } 467 468 func (n *mysqlDialogAuthMethod) Name() AuthMethodDescription { 469 return MysqlDialog 470 } 471 472 func (n *mysqlDialogAuthMethod) HandleUser(conn *Conn, user string) bool { 473 return n.validator.HandleUser(user) 474 } 475 476 func (n *mysqlDialogAuthMethod) AuthPluginData() ([]byte, error) { 477 result := make([]byte, len(n.msg)+2) 478 result[0] = mysqlDialogAskPassword 479 writeNullString(result, 1, n.msg) 480 return result, nil 481 } 482 483 func (n *mysqlDialogAuthMethod) HandleAuthPluginData(conn *Conn, user string, serverAuthPluginData []byte, clientAuthPluginData []byte, remoteAddr net.Addr) (Getter, error) { 484 return n.storage.UserEntryWithPassword(conn, user, string(clientAuthPluginData[:len(clientAuthPluginData)-1]), remoteAddr) 485 } 486 487 func (n *mysqlDialogAuthMethod) AllowClearTextWithoutTLS() bool { 488 return false 489 } 490 491 type mysqlCachingSha2AuthMethod struct { 492 cache CachingStorage 493 storage PlainTextStorage 494 validator UserValidator 495 } 496 497 func (n *mysqlCachingSha2AuthMethod) Name() AuthMethodDescription { 498 return CachingSha2Password 499 } 500 501 func (n *mysqlCachingSha2AuthMethod) HandleUser(conn *Conn, user string) bool { 502 if !conn.TLSEnabled() && !conn.IsUnixSocket() { 503 return false 504 } 505 return n.validator.HandleUser(user) 506 } 507 508 func (n *mysqlCachingSha2AuthMethod) AuthPluginData() ([]byte, error) { 509 salt, err := newSalt() 510 if err != nil { 511 return nil, err 512 } 513 return append(salt, 0), nil 514 } 515 516 func (n *mysqlCachingSha2AuthMethod) AllowClearTextWithoutTLS() bool { 517 return true 518 } 519 520 func (n *mysqlCachingSha2AuthMethod) HandleAuthPluginData(c *Conn, user string, serverAuthPluginData []byte, clientAuthPluginData []byte, remoteAddr net.Addr) (Getter, error) { 521 if serverAuthPluginData[len(serverAuthPluginData)-1] != 0x00 { 522 return nil, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user) 523 } 524 525 salt := serverAuthPluginData[:len(serverAuthPluginData)-1] 526 result, cacheState, err := n.cache.UserEntryWithCacheHash(c, salt, user, clientAuthPluginData, remoteAddr) 527 528 if err != nil { 529 return nil, err 530 } 531 532 switch cacheState { 533 case AuthRejected: 534 return nil, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user) 535 case AuthAccepted: 536 // We need to write a more data packet to indicate the 537 // handshake completed properly. This will be followed 538 // by a regular OK packet which the caller of this method will send. 539 data, pos := c.startEphemeralPacketWithHeader(2) 540 pos = writeByte(data, pos, AuthMoreDataPacket) 541 _ = writeByte(data, pos, CachingSha2FastAuth) 542 err = c.writeEphemeralPacket() 543 if err != nil { 544 return nil, err 545 } 546 return result, nil 547 case AuthNeedMoreData: 548 if !c.TLSEnabled() && !c.IsUnixSocket() { 549 return nil, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user) 550 } 551 552 data, pos := c.startEphemeralPacketWithHeader(2) 553 pos = writeByte(data, pos, AuthMoreDataPacket) 554 writeByte(data, pos, CachingSha2FullAuth) 555 c.writeEphemeralPacket() 556 557 password, err := readPacketPasswordString(c) 558 if err != nil { 559 return nil, err 560 } 561 562 return n.storage.UserEntryWithPassword(c, user, password, remoteAddr) 563 default: 564 // Somehow someone returned an unknown state, let's error with access denied. 565 return nil, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user) 566 } 567 } 568 569 // authServers is a registry of AuthServer implementations. 570 var authServers = make(map[string]AuthServer) 571 572 // mu is used to lock access to authServers 573 var mu sync.Mutex 574 575 // RegisterAuthServer registers an implementations of AuthServer. 576 func RegisterAuthServer(name string, authServer AuthServer) { 577 mu.Lock() 578 defer mu.Unlock() 579 if _, ok := authServers[name]; ok { 580 log.Fatalf("AuthServer named %v already exists", name) 581 } 582 authServers[name] = authServer 583 } 584 585 // GetAuthServer returns an AuthServer by name, or log.Exitf. 586 func GetAuthServer(name string) AuthServer { 587 mu.Lock() 588 defer mu.Unlock() 589 authServer, ok := authServers[name] 590 if !ok { 591 log.Exitf("no AuthServer name %v registered", name) 592 } 593 return authServer 594 } 595 596 func newSalt() ([]byte, error) { 597 salt := make([]byte, 20) 598 if _, err := rand.Read(salt); err != nil { 599 return nil, err 600 } 601 602 // Salt must be a legal UTF8 string. 603 for i := 0; i < len(salt); i++ { 604 salt[i] &= 0x7f 605 if salt[i] == '\x00' || salt[i] == '$' { 606 salt[i]++ 607 } 608 } 609 610 return salt, nil 611 } 612 613 func negotiateAuthMethod(conn *Conn, as AuthServer, user string, requestedAuth AuthMethodDescription) (AuthMethod, error) { 614 for _, m := range as.AuthMethods() { 615 if m.Name() == requestedAuth && m.HandleUser(conn, user) { 616 return m, nil 617 } 618 } 619 return nil, vterrors.Errorf(vtrpc.Code_INTERNAL, "unknown auth method requested: %s", string(requestedAuth)) 620 } 621 622 func readPacketPasswordString(c *Conn) (string, error) { 623 // Read a packet, the password is the payload, as a 624 // zero terminated string. 625 data, err := c.ReadPacket() 626 if err != nil { 627 return "", err 628 } 629 if len(data) == 0 || data[len(data)-1] != 0 { 630 return "", vterrors.Errorf(vtrpc.Code_INTERNAL, "received invalid response packet, datalen=%v", len(data)) 631 } 632 return string(data[:len(data)-1]), nil 633 }