mysql: if in a TLS stream, sha2 auth is just "send a clear text password"

This commit is contained in:
Ryan Leckey 2020-01-14 10:32:59 -08:00
parent d99b87b5c5
commit cb1dbff544
3 changed files with 91 additions and 21 deletions

View file

@ -3,6 +3,7 @@ use async_std::io::{
Read, Write,
};
use std::io;
use std::ops::{Deref, DerefMut};
const RBUF_SIZE: usize = 8 * 1024;
@ -124,6 +125,20 @@ where
}
}
impl<S> Deref for BufStream<S> {
type Target = S;
fn deref(&self) -> &Self::Target {
&self.stream
}
}
impl<S> DerefMut for BufStream<S> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.stream
}
}
// TODO: Find a nicer way to do this
// Return `Ok(None)` immediately from a function if the wrapped value is `None`
#[allow(unused)]

View file

@ -24,11 +24,27 @@ enum Inner {
impl MaybeTlsStream {
pub async fn connect(url: &Url, default_port: u16) -> crate::Result<Self> {
let conn = TcpStream::connect((url.host(), url.port(default_port))).await?;
Ok(Self { inner: Inner::NotTls(conn) })
Ok(Self {
inner: Inner::NotTls(conn),
})
}
pub fn is_tls(&self) -> bool {
match self.inner {
Inner::NotTls(_) => false,
#[cfg(feature = "tls")]
Inner::Tls(_) => true,
#[cfg(feature = "tls")]
Inner::Upgrading => false,
}
}
#[cfg(feature = "tls")]
pub async fn upgrade(&mut self, url: &Url, connector: async_native_tls::TlsConnector) -> crate::Result<()> {
pub async fn upgrade(
&mut self,
url: &Url,
connector: async_native_tls::TlsConnector,
) -> crate::Result<()> {
let conn = match std::mem::replace(&mut self.inner, Upgrading) {
NotTls(conn) => conn,
Tls(_) => return Err(tls_err!("connection already upgraded").into()),

View file

@ -10,7 +10,10 @@ use crate::cache::StatementCache;
use crate::connection::Connection;
use crate::io::{Buf, BufMut, BufStream, MaybeTlsStream};
use crate::mysql::error::MySqlError;
use crate::mysql::protocol::{AuthPlugin, AuthSwitch, Capabilities, Decode, Encode, EofPacket, ErrPacket, Handshake, HandshakeResponse, OkPacket, SslRequest};
use crate::mysql::protocol::{
AuthPlugin, AuthSwitch, Capabilities, Decode, Encode, EofPacket, ErrPacket, Handshake,
HandshakeResponse, OkPacket, SslRequest,
};
use crate::mysql::rsa;
use crate::mysql::util::xor_eq;
use crate::url::Url;
@ -59,7 +62,7 @@ const COLLATE_UTF8MB4_UNICODE_CI: u8 = 224;
/// `ssl-ca` implies `ssl-mode=VERIFY_CA` so you only actually need to specify the former
/// but you may prefer having both to be more explicit.
///
/// If `sslmode=VERIFY_IDENTITY` is specified, in addition to checking the certificate as with
/// If `ssl-mode=VERIFY_IDENTITY` is specified, in addition to checking the certificate as with
/// `ssl-mode=VERIFY_CA`, the hostname in the connection string will be verified
/// against the hostname in the server certificate, so they must be the same for the TLS
/// upgrade to succeed. `ssl-ca` must still be specified.
@ -338,6 +341,15 @@ impl MySqlConnection {
) -> crate::Result<Box<[u8]>> {
// https://mariadb.com/kb/en/caching_sha2_password-authentication-plugin/
if self.stream.is_tls() {
// If in a TLS stream, send the password directly in clear text
let mut clear_text = String::with_capacity(password.len() + 1);
clear_text.push_str(password);
clear_text.push('\0');
return Ok(clear_text.into_boxed_bytes());
}
// client sends a public key request
self.send(&[public_key_request_id][..]).await?;
@ -415,7 +427,12 @@ impl MySqlConnection {
}
#[cfg(feature = "tls")]
async fn try_ssl(&mut self, url: &Url, ca_file: Option<&str>, invalid_hostnames: bool) -> crate::Result<()> {
async fn try_ssl(
&mut self,
url: &Url,
ca_file: Option<&str>,
invalid_hostnames: bool,
) -> crate::Result<()> {
use async_native_tls::{Certificate, TlsConnector};
use async_std::fs;
@ -431,8 +448,9 @@ impl MySqlConnection {
// send upgrade request and then immediately try TLS handshake
self.send(SslRequest {
client_collation: COLLATE_UTF8MB4_UNICODE_CI,
max_packet_size: MAX_PACKET_SIZE
}).await?;
max_packet_size: MAX_PACKET_SIZE,
})
.await?;
self.stream.stream.upgrade(url, connector).await
}
@ -451,8 +469,14 @@ impl MySqlConnection {
let ca_file = url.get_param("ssl-ca");
let ssl_mode = url.get_param("ssl-mode")
.unwrap_or(if ca_file.is_some() { "VERIFY_CA" } else { "PREFERRED" }.into());
let ssl_mode = url.get_param("ssl-mode").unwrap_or(
if ca_file.is_some() {
"VERIFY_CA"
} else {
"PREFERRED"
}
.into(),
);
let supports_ssl = handshake.server_capabilities.contains(Capabilities::SSL);
@ -461,35 +485,50 @@ impl MySqlConnection {
// don't try upgrade
#[cfg(feature = "tls")]
"PREFERRED" if !supports_ssl => log::info!("server does not support TLS; using unencrypted connection"),
"PREFERRED" if !supports_ssl => {
log::info!("server does not support TLS; using unencrypted connection")
}
// try to upgrade
#[cfg(feature = "tls")]
"PREFERRED" => if let Err(e) = self_.try_ssl(&url, None, true).await {
log::warn!("TLS handshake failed, falling back to insecure: {}", e);
// fallback, redo connection
self_ = Self::new(&url).await?;
handshake = self_.receive_handshake(&url).await?;
},
"PREFERRED" => {
if let Err(e) = self_.try_ssl(&url, None, true).await {
log::warn!("TLS handshake failed, falling back to insecure: {}", e);
// fallback, redo connection
self_ = Self::new(&url).await?;
handshake = self_.receive_handshake(&url).await?;
}
}
#[cfg(not(feature = "tls"))]
"PREFERRED" => log::info!("compiled without TLS, skipping upgrade"),
#[cfg(feature = "tls")]
"REQUIRED" if !supports_ssl => return Err(tls_err!("server does not support TLS").into()),
"REQUIRED" if !supports_ssl => {
return Err(tls_err!("server does not support TLS").into())
}
#[cfg(feature = "tls")]
"REQUIRED" => self_.try_ssl(&url, None, true).await?,
#[cfg(feature = "tls")]
"VERIFY_CA" | "VERIFY_FULL" if ca_file.is_none() =>
return Err(tls_err!("`ssl-mode` of {:?} requires `ssl-ca` to be set", ssl_mode).into()),
"VERIFY_CA" | "VERIFY_FULL" if ca_file.is_none() => {
return Err(
tls_err!("`ssl-mode` of {:?} requires `ssl-ca` to be set", ssl_mode).into(),
)
}
#[cfg(feature = "tls")]
"VERIFY_CA" | "VERIFY_FULL" => self_.try_ssl(&url, ca_file.as_deref(), ssl_mode != "VERIFY_FULL").await?,
"VERIFY_CA" | "VERIFY_FULL" => {
self_
.try_ssl(&url, ca_file.as_deref(), ssl_mode != "VERIFY_FULL")
.await?
}
#[cfg(not(feature = "tls"))]
"REQUIRED" | "VERIFY_CA" | "VERIFY_FULL" => return Err(tls_err!("compiled without TLS").into()),
"REQUIRED" | "VERIFY_CA" | "VERIFY_FULL" => {
return Err(tls_err!("compiled without TLS").into())
}
_ => return Err(tls_err!("unknown `ssl-mode` value: {:?}", ssl_mode).into()),
}