orcinus 0.3.0

async-ready mysql protocol implementation / wrapper libraries
Documentation
use futures_util::{future::BoxFuture, FutureExt};
use ring::digest::{digest, SHA1_FOR_LEGACY_USE_ONLY as SHA1};
use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};

use crate::{
    protos::{write_packet, write_packet_sync, AsyncReceivePacket, GenericOKErrPacket, OKPacket, ReceivePacket},
    CommunicationError,
};

/// Generates an auth_response for `"mysql_native_password"` authentication plugin
pub fn gen_secure_password_auth_response(password: &str, salt1: &[u8], salt2: &[u8]) -> Vec<u8> {
    let password_sha1 = digest(&SHA1, password.as_bytes());
    let mut concat_data = Vec::with_capacity(40);
    concat_data.extend(salt1.iter().chain(salt2.iter()).take(20));
    concat_data.extend(digest(&SHA1, password_sha1.as_ref()).as_ref());
    let concat_data_sha1 = digest(&SHA1, &concat_data);

    password_sha1
        .as_ref()
        .into_iter()
        .zip(concat_data_sha1.as_ref().into_iter())
        .map(|(a, b)| a ^ b)
        .collect()
}

/// Protocol 4.1 Native Authentication(`"mysql_native_password"`)
pub struct Native41<'s> {
    /// first 8 bytes in handshake request
    pub server_data_1: &'s [u8],
    /// rest bytes in handshake request
    pub server_data_2: &'s [u8],
}
impl super::Authentication for Native41<'_> {
    const NAME: &'static str = "mysql_native_password";

    fn run_sync(
        &self,
        mut stream: impl std::io::Read + std::io::Write,
        con_info: &super::ConnectionInfo,
        first_sequence_id: u8,
    ) -> Result<(OKPacket, u8), CommunicationError> {
        let payload = gen_secure_password_auth_response(con_info.password, self.server_data_1, self.server_data_2);

        write_packet_sync(
            &mut stream,
            con_info.make_handshake_response(&payload, Some(Self::NAME)),
            first_sequence_id,
        )?;
        stream.flush()?;
        let (resp, sequence_id) =
            GenericOKErrPacket::read_packet(stream, con_info.client_capabilities)?.into_result()?;

        Ok((resp, sequence_id))
    }
}
impl<'s, S> super::AsyncAuthentication<'s, S> for Native41<'_>
where
    S: AsyncRead + AsyncWrite + Send + Sync + Unpin + 's,
{
    type OperationF = BoxFuture<'s, Result<(OKPacket, u8), CommunicationError>>;

    fn run(&'s self, mut stream: S, con_info: &'s super::ConnectionInfo, first_sequence_id: u8) -> Self::OperationF {
        async move {
            let payload = gen_secure_password_auth_response(con_info.password, self.server_data_1, self.server_data_2);

            write_packet(
                &mut stream,
                con_info.make_handshake_response(&payload, Some(<Self as super::Authentication>::NAME)),
                first_sequence_id,
            )
            .await?;
            stream.flush().await?;
            let (resp, sequence_id) = GenericOKErrPacket::read_packet_async(stream, con_info.client_capabilities)
                .await?
                .into_result()?;

            Ok((resp, sequence_id))
        }
        .boxed()
    }
}