sqlx_postgres/message/
password.rs

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