Skip to main content

sqlx_postgres/message/
password.rs

1use crate::io::BufMutExt;
2use crate::message::{FrontendMessage, FrontendMessageFormat};
3use md5::{Digest, Md5};
4use sqlx_core::Error;
5use std::num::Saturating;
6
7#[derive(Debug)]
8pub enum Password<'a> {
9    Cleartext(&'a str),
10
11    Md5 {
12        password: &'a str,
13        username: &'a str,
14        salt: [u8; 4],
15    },
16}
17
18impl FrontendMessage for Password<'_> {
19    const FORMAT: FrontendMessageFormat = FrontendMessageFormat::PasswordPolymorphic;
20
21    #[inline(always)]
22    fn body_size_hint(&self) -> Saturating<usize> {
23        let mut size = Saturating(0);
24
25        match self {
26            Password::Cleartext(password) => {
27                // To avoid reporting the exact password length anywhere,
28                // we deliberately give a bad estimate.
29                //
30                // This shouldn't affect performance in the long run.
31                size += password
32                    .len()
33                    .saturating_add(1) // NUL terminator
34                    .checked_next_power_of_two()
35                    .unwrap_or(usize::MAX);
36            }
37            Password::Md5 { .. } => {
38                // "md5<32 hex chars>\0"
39                size += 36;
40            }
41        }
42
43        size
44    }
45
46    fn encode_body(&self, buf: &mut Vec<u8>) -> Result<(), Error> {
47        match self {
48            Password::Cleartext(password) => {
49                buf.put_str_nul(password);
50            }
51
52            Password::Md5 {
53                username,
54                password,
55                salt,
56            } => {
57                // The actual `PasswordMessage` can be computed in SQL as
58                // `concat('md5', md5(concat(md5(concat(password, username)), random-salt)))`.
59
60                // Keep in mind the md5() function returns its result as a hex string.
61
62                let mut hasher = Md5::new();
63
64                hasher.update(password);
65                hasher.update(username);
66
67                let mut output = hex::encode(hasher.finalize_reset());
68
69                hasher.update(&output);
70                hasher.update(salt);
71
72                // This is not really optimal but hopefully this is an uncommon code path.
73                // MD5 password hashing should really be phased out anyway.
74                output = hex::encode(hasher.finalize());
75                output.insert_str(0, "md5");
76
77                buf.put_str_nul(&output);
78            }
79        }
80
81        Ok(())
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use crate::message::FrontendMessage;
88
89    use super::Password;
90
91    #[test]
92    fn test_encode_clear_password() {
93        const EXPECTED: &[u8] = b"p\0\0\0\rpassword\0";
94
95        let mut buf = Vec::new();
96        let m = Password::Cleartext("password");
97
98        m.encode_msg(&mut buf).unwrap();
99
100        assert_eq!(buf, EXPECTED);
101    }
102
103    #[test]
104    fn test_encode_md5_password() {
105        const EXPECTED: &[u8] = b"p\0\0\0(md53e2c9d99d49b201ef867a36f3f9ed62c\0";
106
107        let mut buf = Vec::new();
108        let m = Password::Md5 {
109            password: "password",
110            username: "root",
111            salt: [147, 24, 57, 152],
112        };
113
114        m.encode_msg(&mut buf).unwrap();
115
116        assert_eq!(buf, EXPECTED);
117    }
118
119    #[cfg(all(test, not(debug_assertions)))]
120    #[bench]
121    fn bench_encode_clear_password(b: &mut test::Bencher) {
122        use test::black_box;
123
124        let mut buf = Vec::with_capacity(128);
125
126        b.iter(|| {
127            buf.clear();
128
129            black_box(Password::Cleartext("password")).encode_msg(&mut buf);
130        });
131    }
132
133    #[cfg(all(test, not(debug_assertions)))]
134    #[bench]
135    fn bench_encode_md5_password(b: &mut test::Bencher) {
136        use test::black_box;
137
138        let mut buf = Vec::with_capacity(128);
139
140        b.iter(|| {
141            buf.clear();
142
143            black_box(Password::Md5 {
144                password: "password",
145                username: "root",
146                salt: [147, 24, 57, 152],
147            })
148            .encode_msg(&mut buf);
149        });
150    }
151}