WL#10992: xplugin: sha256 challenge response against in-memory sha256-storage
Motivation
WL#9591 introduces
- a cache_sha2_password storage in mysql.user
- intentionally expensive, salted, iterated SHA256 hashes in storage
- a in-memory cache which stores unsalted SHA256(SHA256(password)) which is stored after successful authentication
- a challenge-response protocol with a NONCE against that cache
As the password is never exchanged in cleartext no TLS is needed to authenticate.
Goal
- allow clients to authenticate using a 'MYSQL41'-like SASL method (using SHA256 instead of SHA1) over a cleartext channel
- authentication goes against a in-memory dictionary which stores a unsalted sha256(sha256(password)) same as in WL#9591
- the cache gets filled if account has "cache_sha2_password" as auth-plugin and user logs in via PLAIN-over-TLS
Bonus Goals
- the cache MAY also work for the expensive, existing sha256_password entries.
Caching of Auth Credentials
- F1
- authentication with SHA256_MEMORY MUST fail when cache invalidation doesn't work
- F2
- authentication with PLAIN-over-secure-connections MUST store user-account, SHA256(SHA256(password)) in a in-memory sha256_cache
- F3
- authentication with PLAIN-over-secure-connections WILL succeed if user-account already exists in the cache with the same SHA256(SHA256(password))
- F4
- authentication with SHA256_MEMORY MUST succeed if the user-account, sha256(password) matches the entry in the sha256_cache
- F5
- authentication with SHA256_MEMORY MUST work over insecure and secure connections
- F6
- authentication with SHA256_MEMORY MUST fail if connection is insecure and account has any tls_option set
Protocol Implementation Requirements
- PROTO1
- server MUST not close connection after authentication failure
- PROTO2
- server MUST close connection after multiple authentication failures
- PROTO3
- server MUST support resetting the failed authentication at Connection::Reset
Edge Cases
- NF1
- there MUST be NO way to access the contents of the cache through SQL
- NF2
- the contents of the cache MUST NOT be persisted
Cache Invalidation
- CI1
- DROP USER MUST invalidate the cached entry
- CI2
- SET PASSWORD MUST invalidate the user entry in the cache
- CI3
- ALTER USER MUST invalidate the user entry in the cache
- CI4
- RENAME USER MUST invalidate the old entry and a possible new entry in the cache
- CI5
- GRANT with IDENTIFIED MUST invalidate the user entry in the cache
- CI6
- FLUSH PRIVILEGES invalidates all entries in the cache
libmysqlxclient
- CL1
- libmysqlxclient MUST support SHA256_MEMORY authentication method
General
The worklog introduces an authentication that uses cached account informations. The cache is going to contain password hashes of MySQL accounts that already authenticated using clear-text password:
- client send clear-text password
- server authenticates the user
the hash is calculated:
HASH=sha256(sha256(PASSWORD))
server makes an association between the MySQL account and the HASH (caches the user)
Authentication for already cached user, look like:
- server sends back a nonce which consist of 20 bytes (randomly generated, utf8 compatible).
client calculates the hash using:
CLIENT_HASH=SHA256(SHA256(SHA256(PASSWORD)),NONCE) XOR SHA256(PASSWORD)
client sends the CLIENT_HASH to the server
- server gets cached hash using MySQL account data (the cached hash is going to be refereed as SERVER_SHA_SHA_PASSWORD_CACHE)
server calculates following:
CALCULATED_SHA_SHA_PASSWORD = SHA256(CLIENT_HASH XOR SHA256(SERVER_SHA_SHA_PASSWORD_CACHE, NONCE))
authentication is successful when CALCULATED_SHA_SHA_PASSWORD equals SERVER_SHA_SHA_PASSWORD_CACHE
If the MySQL Server account is modified the cached entries for that account must be removed.
Protocol
When the client connects, the X Session goes through following stages:
- negotiation
- authentication
- ready
Capabilities
In negotiation stage the client is be able to check which authentication methods are supported by X Plugin. It is done by probing X Plugin capabilities, using following flow:
client -> server: Mysqlx.Connection.CapabilitiesGet
client <- server: Mysqlx.Connection.Capabilities(...)
The worklog introduces a new authentication mechanism "SHA256_MEMORY" which must be returned in "authentication.mechanisms" capability:
Mysqlx.Connection.Capabilities {
capabilities {
name: "authentication.mechanisms"
value {
type: ARRAY
array {
value {
type: SCALAR
scalar {
type: V_STRING
v_string {
value: "MYSQL41"
}
}
}
value {
type: SCALAR
scalar {
type: V_STRING
v_string {
value: "SHA256_MEMORY"
}
}
}
}
}
}
Authentication
"SHA256_MEMORY" authentication is going to be performed by flow that is already used for "MYSQL41" authentication method:
client -> server: Mysqlx.Session.AuthenticateStart(mech_name="SHA256_MEMORY")
client <- server: Mysqlx.Session.AuthenticateContinue(auth_data=NONCE)
client -> server: Mysqlx.Session.AuthenticateContinue(auth_data="SCHEMA\0USER\0CLIENT_HASH")
client <- server: Mysqlx.Session.AuthenticateOk()|Mysqlx.Error()
After clients receives the NONCE, it must calculate the CLIENT_HASH and send it back inside "auth_data" field of AuthenticateContinue message. It is transfered with default schema for the session and user name that is going to be authenticated. Schema, user and CLIENT_HASH are going to be concatenated into a single string, separated with '\0' character. Binary version of CLIENT_HASH may contain '\0' characters. To make it compatible with encoding of "auth_data", it must to be converted to hex-string.
Most authentication attempts are going to interact with the cache, table below shows actions on cache that must be performed when different accounts and methods are used:
X Method | TLS | Server account type | Cache |
---|---|---|---|
PLAIN | Yes | native_password | Add hash and account to cache |
PLAIN | Yes | sha256_password | Add hash and account to cache |
PLAIN | Yes | caching_sha2_password | Add hash and account to cache |
MYSQL41 | No | native_password | - |
MYSQL41 | Yes | native_password | - |
MEMORY_SHA256 | No | Any | Use cache for authentication |
MEMORY_SHA256 | Yes | Any | Use cache for authentication |
Multiple authentications
Currently X Plugin closes the connection after first failed authentication:
...Connection setup...
alt PLAIN
client -> server : Mysqlx.Session.AuthenticateStart(mech_name="PLAIN", auth_data="schema\0user\0password")
client <- server : Mysqlx.Error
else MYSQL41 or SHA256_MEMORY
client -> server : Mysqlx.Session.AuthenticateStart(mech_name="MYSQL41|SHA256_MEMORY")
client <- server : Mysqlx.Session.AuthenticateContinue(auth_data=NONCE)
client -> server : Mysqlx.Session.AuthenticateContinue(auth_data="SCHEMA\0USER\0CLIENT_HASH")
client <- server : Mysqlx.Error
else UNKNOWN
client -> server : Mysqlx.Session.AuthenticateStart(mech_name="UNKNOWN",...)
client <- server : Mysqlx.Error
end
...Server closes the connection...
Client doesn't know if a given MySQL account was cached in X Plugin, he can only try to authenticate using SHA256_MEMORY method. After authentication failure the connection must not be dropped to allow a sequence of authentications with different mechanisms (no need for reconnection).
Example 1:
title Most expected scenario. Account is not cached, SHA256_MEMORY fails. To fill the cache client does PLAIN auth.
...Connection setup...
client -> server : Mysqlx.Connection.CapabilitiesSet([tls=true])
client <- server : Mysqlx.Ok()
...TLS ENABLED...
client -> server : Mysqlx.Session.AuthenticateStart(mech_name="SHA256_MEMORY")
client <- server : Mysqlx.Session.AuthenticateContinue(auth_data=NONCE)
client -> server : Mysqlx.Session.AuthenticateContinue(auth_data="SCHEMA\0USER\0CLIENT_HASH")
client <- server : Mysqlx.Error
client -> server : Mysqlx.Session.AuthenticateStart(mech_name="PLAIN", auth_data="schema\0user\0password")
client <- server : Mysqlx.Session.AuthenticateOk()
...Session ready...
Example 2:
...Connection setup...
client -> server : Mysqlx.Session.AuthenticateStart(mech_name="SHA256_MEMORY")
client <- server : Mysqlx.Session.AuthenticateContinue(auth_data=NONCE)
client -> server : Mysqlx.Session.AuthenticateContinue(auth_data="SCHEMA\0USER\0CLIENT_HASH")
client <- server : Mysqlx.Error
client -> server : Mysqlx.Connection.CapabilitiesSet([tls=true])
client <- server : Mysqlx.Ok()
...TLS ENABLED...
client -> server : Mysqlx.Session.AuthenticateStart(mech_name="PLAIN", auth_data="schema\0user\0password")
client <- server : Mysqlx.Session.AuthenticateOk()
...Session ready...
Example 3:
...Connection setup...
client -> server : Mysqlx.Session.AuthenticateStart(mech_name="SHA256_MEMORY")
client <- server : Mysqlx.Session.AuthenticateContinue(auth_data=NONCE)
client -> server : Mysqlx.Session.AuthenticateContinue(auth_data="SCHEMA\0USER\0CLIENT_HASH")
client <- server : Mysqlx.Error
client -> server : Mysqlx.Session.AuthenticateStart(mech_name="MYSQL41")
client <- server : Mysqlx.Session.AuthenticateContinue(auth_data=NONCE)
client -> server : Mysqlx.Session.AuthenticateContinue(auth_data="SCHEMA\0USER\0CLIENT_HASH")
client <- server : Mysqlx.Error
client -> server : Mysqlx.Connection.CapabilitiesSet([tls=true])
client <- server : Mysqlx.Ok()
...TLS ENABLED...
client -> server : Mysqlx.Session.AuthenticateStart(mech_name="PLAIN", auth_data="schema\0user\0password")
client <- server : Mysqlx.Session.AuthenticateOk()
...Session ready...
Number of authentication retries should be limited to three (number of all authentication mechanisms supported by X Plugin).
Error and Warnings
Authentication failure must not introduce any new error codes.
SQL
New audit plugin is going to be added. It is going to maintain the cache and remove all entires for accounts that were modified. To installe the plugin user must execute:
INSTALL PLUGIN mysqlx_cache_cleaner SONAME 'mysqlx.so';
Compatibility
New authentication method is not compatible with old MySQL server, except that its not supported there, old server do not allow authentication sequencing the first tried auth-method there should be either PLAIN or MYSQL41:
Authentication mech | MySQL 5.7 | before MySQL 8.0.4 | MySQL 8.0.4 and after |
---|---|---|---|
MYSQL41 | OK | OK | OK |
PLAIN | OK | OK | OK |
SHA256_MEMORY | NO | NO | OK |
If an new connector is going to send SHA256_MEMORY to old plugin, it must expect following behaviour:
client->server: Mysqlx.Session.AuthenticateStart(mech="SHA256_MEMORY")
client<-server: Mysqlx.Error(ER_NOT_SUPPORTED_AUTH_MODE)
client<-server: TCP:RST
Mysqlxclient must have a configuration option in which is going to use PLAIN or MYSQL41 when its in AUTO mode.
Client
MYSQLXCLIENT
"xcl::Mysqlx_option::Authentication_method" configuration option select which authentication mechanism is going to be used during session setup. Still it accepts single string. To be able to support a authentication sequence the option needs to accept an array of strings, which would represent multiple steps of authentication. Th X Client API needs to be modified:
namespace xcl {
...
class XSession {
public:
/**
Modify mysqlx options.
This method may only be called before calling `XSession::connect`
method.
@param option option to set or modify
@param value assign list of string values to the option
@return Error code with description
@retval != true OK
@retval == true error occurred
*/
virtual XError set_mysql_option(const Mysqlx_option option,
const std::vector<std::string> &values_list) = 0;
New values ("SHA256_MEMORY") needs to be accepted by the configuration option to use new authentication mechanism. Full list of possible values:
"AUTO" - let the library select authentication method/methods (default)
"SHA256_MEMORY - authentication based on memory-stored credentials
"MYSQL41" - do not use plain password send through network
"PLAIN" - use plain password for authentication
The behavior of "AUTO" setting needs to check connection properties and use different authentication sequences:
Connection properties | Authentication sequence |
---|---|
TCP, TLS | SHA256_MEMORY, PLAIN, MYSQL41 |
TCP | SHA256_MEMORY, MYSQL41 |
UNIX socket | SHA256_MEMORY, PLAIN, MYSQL41 |
MYSQLXTEST
Authentication sequence should be configurable through command line arguments. Sequence of following arguments should trigger the same authentication sequence:
plain-auth Use PLAIN text authentication mechanism (already exists)
cached-auth Use SHA256_MEMORY authentication mechanism
mysql41-auth Use MYSQL41 authentication mechanism
In test language, user must be able to trigger a single authentication using selected mechanism. To execute a sequence of authentication, user must call multiple of following commands:
- login
] Performs authentication steps (use with --no-auth)
New connection commands with specified authentication:
* newsession
newsession_mysql41
Create a new connection which is going to be authenticate using MYSQL41 mechanism. newsession_memory
Create a new connection which is going to be authenticate using SHA256_MEMORY mechanism. newsession_plain
Create a new connection which is going to be authenticate using PLAIN mechanism.
Notes
The caches maintained by X Plugin and caching_sha2_password are independent. MySQL Server does not provide a mechanism to share the data between them.
SHA256_MEMORY authentication uses the same flow as MYSQL41 which is implemented by Sasl_mysql41_auth class. Its possible to extract some base class for both.
NONCE that it utf8 compatible is already generated in caching_sha2_password using call "generate_user_salt(scramble, SCRAMBLE_LENGTH + 1);"