mirror of
https://github.com/launchbadge/sqlx.git
synced 2025-12-26 10:09:50 +00:00
Implement mysql_clear_password (#2533)
* mysql_clear_password * comment * refactor no-data edge case and add unit tests * add a connection option to explicitly enable mysql_clear_password * cargo fmt * scary comment & log warning
This commit is contained in:
parent
7e7dded8af
commit
febf9ed775
@ -27,6 +27,12 @@ impl AuthPlugin {
|
||||
|
||||
// https://mariadb.com/kb/en/sha256_password-plugin/
|
||||
AuthPlugin::Sha256Password => encrypt_rsa(stream, 0x01, password, nonce).await,
|
||||
|
||||
AuthPlugin::MySqlClearPassword => {
|
||||
let mut pw_bytes = password.as_bytes().to_owned();
|
||||
pw_bytes.push(0); // null terminate
|
||||
Ok(pw_bytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ use crate::protocol::connect::{
|
||||
AuthSwitchRequest, AuthSwitchResponse, Handshake, HandshakeResponse,
|
||||
};
|
||||
use crate::protocol::Capabilities;
|
||||
use crate::{MySqlConnectOptions, MySqlConnection};
|
||||
use crate::{MySqlConnectOptions, MySqlConnection, MySqlSslMode};
|
||||
|
||||
impl MySqlConnection {
|
||||
pub(crate) async fn establish(options: &MySqlConnectOptions) -> Result<Self, Error> {
|
||||
@ -49,6 +49,15 @@ impl<'a> DoHandshake<'a> {
|
||||
.transpose()?
|
||||
.unwrap_or_else(|| charset.default_collation());
|
||||
|
||||
if options.enable_cleartext_plugin
|
||||
&& matches!(
|
||||
options.ssl_mode,
|
||||
MySqlSslMode::Disabled | MySqlSslMode::Preferred
|
||||
)
|
||||
{
|
||||
log::warn!("Security warning: sending cleartext passwords without requiring SSL");
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
options,
|
||||
charset,
|
||||
@ -134,7 +143,8 @@ impl<'a> DoHandshake<'a> {
|
||||
}
|
||||
|
||||
0xfe => {
|
||||
let switch: AuthSwitchRequest = packet.decode()?;
|
||||
let switch: AuthSwitchRequest =
|
||||
packet.decode_with(self.options.enable_cleartext_plugin)?;
|
||||
|
||||
plugin = Some(switch.plugin);
|
||||
let nonce = switch.data.chain(Bytes::new());
|
||||
|
||||
@ -76,6 +76,7 @@ pub struct MySqlConnectOptions {
|
||||
pub(crate) collation: Option<String>,
|
||||
pub(crate) log_settings: LogSettings,
|
||||
pub(crate) pipes_as_concat: bool,
|
||||
pub(crate) enable_cleartext_plugin: bool,
|
||||
}
|
||||
|
||||
impl Default for MySqlConnectOptions {
|
||||
@ -103,6 +104,7 @@ impl MySqlConnectOptions {
|
||||
statement_cache_capacity: 100,
|
||||
log_settings: Default::default(),
|
||||
pipes_as_concat: true,
|
||||
enable_cleartext_plugin: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -266,4 +268,19 @@ impl MySqlConnectOptions {
|
||||
self.pipes_as_concat = flag_val;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables mysql_clear_password plugin support.
|
||||
///
|
||||
/// Security Note:
|
||||
/// Sending passwords as cleartext may be a security problem in some
|
||||
/// configurations. Without additional defensive configuration like
|
||||
/// ssl-mode=VERIFY_IDENTITY, an attacker can compromise a router
|
||||
/// and trick the application into divulging its credentials.
|
||||
///
|
||||
/// It is strongly recommended to set `.ssl_mode` to `Required`,
|
||||
/// `VerifyCa`, or `VerifyIdentity` when enabling cleartext plugin.
|
||||
pub fn enable_cleartext_plugin(mut self, flag_val: bool) -> Self {
|
||||
self.enable_cleartext_plugin = flag_val;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ pub enum AuthPlugin {
|
||||
MySqlNativePassword,
|
||||
CachingSha2Password,
|
||||
Sha256Password,
|
||||
MySqlClearPassword,
|
||||
}
|
||||
|
||||
impl AuthPlugin {
|
||||
@ -15,6 +16,7 @@ impl AuthPlugin {
|
||||
AuthPlugin::MySqlNativePassword => "mysql_native_password",
|
||||
AuthPlugin::CachingSha2Password => "caching_sha2_password",
|
||||
AuthPlugin::Sha256Password => "sha256_password",
|
||||
AuthPlugin::MySqlClearPassword => "mysql_clear_password",
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -27,6 +29,7 @@ impl FromStr for AuthPlugin {
|
||||
"mysql_native_password" => Ok(AuthPlugin::MySqlNativePassword),
|
||||
"caching_sha2_password" => Ok(AuthPlugin::CachingSha2Password),
|
||||
"sha256_password" => Ok(AuthPlugin::Sha256Password),
|
||||
"mysql_clear_password" => Ok(AuthPlugin::MySqlClearPassword),
|
||||
|
||||
_ => Err(err_protocol!("unknown authentication plugin: {}", s)),
|
||||
}
|
||||
|
||||
@ -14,8 +14,8 @@ pub struct AuthSwitchRequest {
|
||||
pub data: Bytes,
|
||||
}
|
||||
|
||||
impl Decode<'_> for AuthSwitchRequest {
|
||||
fn decode_with(mut buf: Bytes, _: ()) -> Result<Self, Error> {
|
||||
impl Decode<'_, bool> for AuthSwitchRequest {
|
||||
fn decode_with(mut buf: Bytes, enable_cleartext_plugin: bool) -> Result<Self, Error> {
|
||||
let header = buf.get_u8();
|
||||
if header != 0xfe {
|
||||
return Err(err_protocol!(
|
||||
@ -26,6 +26,21 @@ impl Decode<'_> for AuthSwitchRequest {
|
||||
|
||||
let plugin = buf.get_str_nul()?.parse()?;
|
||||
|
||||
if matches!(plugin, AuthPlugin::MySqlClearPassword) && !enable_cleartext_plugin {
|
||||
return Err(err_protocol!("mysql_cleartext_plugin disabled"));
|
||||
}
|
||||
|
||||
if matches!(plugin, AuthPlugin::MySqlClearPassword) && buf.is_empty() {
|
||||
// Contrary to the MySQL protocol, AWS Aurora with IAM sends
|
||||
// no data. That is fine because the mysql_clear_password says to
|
||||
// ignore any data sent.
|
||||
// See: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_authentication_methods_clear_text_password.html
|
||||
return Ok(Self {
|
||||
plugin,
|
||||
data: Bytes::new(),
|
||||
});
|
||||
}
|
||||
|
||||
// See: https://github.com/mysql/mysql-server/blob/ea7d2e2d16ac03afdd9cb72a972a95981107bf51/sql/auth/sha2_password.cc#L942
|
||||
if buf.len() != 21 {
|
||||
return Err(err_protocol!(
|
||||
@ -48,3 +63,35 @@ impl Encode<'_, Capabilities> for AuthSwitchResponse {
|
||||
buf.extend_from_slice(&self.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_auth_switch_packet_data() {
|
||||
const AUTH_SWITCH_NO_DATA: &[u8] = b"\xfecaching_sha2_password\x00abcdefghijabcdefghij\x00";
|
||||
|
||||
let p = AuthSwitchRequest::decode_with(AUTH_SWITCH_NO_DATA.into(), true).unwrap();
|
||||
|
||||
assert!(matches!(p.plugin, AuthPlugin::CachingSha2Password));
|
||||
assert_eq!(p.data, &b"abcdefghijabcdefghij"[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_auth_switch_cleartext_disabled() {
|
||||
const AUTH_SWITCH_CLEARTEXT: &[u8] = b"\xfemysql_clear_password\x00abcdefghijabcdefghij\x00";
|
||||
|
||||
let e = AuthSwitchRequest::decode_with(AUTH_SWITCH_CLEARTEXT.into(), false).unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
e.to_string(),
|
||||
"encountered unexpected or invalid data: mysql_cleartext_plugin disabled"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_auth_switch_packet_no_data() {
|
||||
const AUTH_SWITCH_NO_DATA: &[u8] = b"\xfemysql_clear_password\x00";
|
||||
|
||||
let p = AuthSwitchRequest::decode_with(AUTH_SWITCH_NO_DATA.into(), true).unwrap();
|
||||
|
||||
assert!(matches!(p.plugin, AuthPlugin::MySqlClearPassword));
|
||||
assert_eq!(p.data, Bytes::new());
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user