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
No known key found for this signature in database
GPG Key ID: F8AA68C235AB08C9
5 changed files with 124 additions and 4 deletions

19
Cargo.lock generated
View File

@ -555,6 +555,12 @@ dependencies = [
"libc",
]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "idna"
version = "0.2.2"
@ -639,6 +645,17 @@ version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
[[package]]
name = "md-5"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15"
dependencies = [
"block-buffer",
"digest",
"opaque-debug",
]
[[package]]
name = "memchr"
version = "2.3.4"
@ -1132,7 +1149,9 @@ dependencies = [
"futures-executor",
"futures-io",
"futures-util",
"hex",
"log",
"md-5",
"memchr",
"percent-encoding",
"sqlx-core",

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");
}
}