feat(postgres): add support for clear and md5 passwords

This commit is contained in:
Ryan Leckey
2021-03-20 00:50:27 -07:00
parent 9cc80af5e4
commit db1896444d
5 changed files with 124 additions and 4 deletions

View File

@@ -33,12 +33,14 @@ log = "0.4.11"
either = "1.6.1"
bytestring = "1.0.0"
url = "2.2.0"
hex = "0.4.3"
percent-encoding = "2.1.0"
futures-io = { version = "0.3", optional = true }
bytes = "1.0"
memchr = "2.3"
bitflags = "1.2"
base64 = "0.13.0"
md-5 = "0.9.1"
[dev-dependencies]
sqlx-core = { version = "0.6.0-pre", path = "../sqlx-core", features = ["_mock"] }

View File

@@ -16,7 +16,7 @@ use sqlx_core::net::Stream as NetStream;
use sqlx_core::{Error, Result, Runtime};
use crate::protocol::backend::{Authentication, BackendMessage, BackendMessageType};
use crate::protocol::frontend::Startup;
use crate::protocol::frontend::{Password, PasswordMd5, Startup};
use crate::{PgClientError, PgConnectOptions, PgConnection};
impl<Rt: Runtime> PgConnection<Rt> {
@@ -50,12 +50,17 @@ impl<Rt: Runtime> PgConnection<Rt> {
return Ok(true);
}
Authentication::Md5Password(_) => {
todo!("md5")
Authentication::Md5Password(data) => {
self.stream.write_message(&PasswordMd5 {
password: options.get_password().unwrap_or_default(),
username: options.get_username().unwrap_or_default(),
salt: data.salt,
})?;
}
Authentication::CleartextPassword => {
todo!("cleartext")
self.stream
.write_message(&Password(options.get_password().unwrap_or_default()))?;
}
Authentication::Sasl(_) => todo!("sasl"),
@@ -70,6 +75,8 @@ impl<Rt: Runtime> PgConnection<Rt> {
}));
}
}
Ok(false)
}
}

View File

@@ -1,5 +1,7 @@
mod password;
mod startup;
mod terminate;
pub(crate) use password::{Password, PasswordMd5};
pub(crate) use startup::Startup;
pub(crate) use terminate::Terminate;

View File

@@ -0,0 +1,90 @@
use crate::io::PgWriteExt;
use md5::{Digest, Md5};
use sqlx_core::io::Serialize;
use sqlx_core::Result;
#[derive(Debug)]
pub(crate) struct Password<'a>(pub(crate) &'a str);
#[derive(Debug)]
pub(crate) struct PasswordMd5<'a> {
pub(crate) password: &'a str,
pub(crate) username: &'a str,
pub(crate) salt: [u8; 4],
}
impl Serialize<'_> for Password<'_> {
fn serialize_with(&self, buf: &mut Vec<u8>, _: ()) -> Result<()> {
buf.reserve(1 + 4 + self.0.len() + 1);
buf.push(b'p');
buf.write_len_prefixed(|buf| {
buf.extend(self.0.as_bytes());
buf.push(b'\0');
Ok(())
})
}
}
impl Serialize<'_> for PasswordMd5<'_> {
fn serialize_with(&self, buf: &mut Vec<u8>, _: ()) -> Result<()> {
buf.reserve(1 + 4 + 3 + 32 + 1);
buf.push(b'p');
buf.write_len_prefixed(|buf| {
// the actual `PasswordMessage` can be computed in SQL as:
// concat('md5', md5(concat(md5(concat(password, username)), random-salt)))
// keep in mind the md5() function returns its result as a hex string
let mut hasher = Md5::new();
hasher.update(self.password);
hasher.update(self.username);
let offset = buf.len();
buf.resize(offset + 32 + 3, 0);
let hash = hasher.finalize_reset();
let _ = hex::encode_to_slice(hash.as_slice(), &mut buf[offset..offset + 32]);
hasher.update(&buf[offset..offset + 32]);
hasher.update(self.salt);
buf[offset..offset + 3].copy_from_slice(&b"md5"[..]);
let hash = hasher.finalize();
let _ = hex::encode_to_slice(hash.as_slice(), &mut buf[offset + 3..offset + 32 + 3]);
buf.push(b'\0');
Ok(())
})
}
}
#[cfg(test)]
mod tests {
use super::{Password, PasswordMd5, Serialize};
#[test]
fn should_serialize() {
let mut buf = Vec::new();
let m = Password("password");
m.serialize(&mut buf).unwrap();
assert_eq!(buf, b"p\0\0\0\rpassword\0");
}
#[test]
fn should_serialize_md5() {
let mut buf = Vec::new();
let m = PasswordMd5 { password: "password", username: "root", salt: [147, 24, 57, 152] };
m.serialize(&mut buf).unwrap();
assert_eq!(buf, b"p\0\0\0(md53e2c9d99d49b201ef867a36f3f9ed62c\0");
}
}