Skip to main content

pg_proto/
credentials.rs

1//! Credential transforms shared by client and proxy-side authentication policy.
2
3use subtle::ConstantTimeEq as _;
4
5/// Computes the wire value for `AuthenticationMD5Password`.
6#[must_use]
7pub fn md5_response(username: &[u8], password: &[u8], salt: [u8; 4]) -> String {
8    postgres_protocol::authentication::md5_hash(username, password, salt)
9}
10
11/// Verifies an MD5 password response without a data-dependent comparison.
12#[must_use]
13pub fn verify_md5_response(
14    response: &[u8],
15    username: &[u8],
16    password: &[u8],
17    salt: [u8; 4],
18) -> bool {
19    response
20        .ct_eq(md5_response(username, password, salt).as_bytes())
21        .into()
22}
23
24/// Verifies a cleartext password response without a data-dependent comparison.
25#[must_use]
26pub fn verify_cleartext(response: &[u8], expected: &[u8]) -> bool {
27    response.ct_eq(expected).into()
28}
29
30#[cfg(test)]
31mod tests {
32    use super::*;
33
34    #[test]
35    fn computes_and_verifies_postgres_md5_response() {
36        let salt = [0x2a, 0x3d, 0x8f, 0xe0];
37        let response = md5_response(b"md5_user", b"password", salt);
38        assert_eq!(response, "md562af4dd09bbb41884907a838a3233294");
39        assert!(verify_md5_response(
40            response.as_bytes(),
41            b"md5_user",
42            b"password",
43            salt
44        ));
45        assert!(!verify_md5_response(
46            b"md500000000000000000000000000000000",
47            b"md5_user",
48            b"password",
49            salt
50        ));
51    }
52
53    #[test]
54    fn verifies_cleartext_credentials() {
55        assert!(verify_cleartext(b"secret", b"secret"));
56        assert!(!verify_cleartext(b"secret", b"different"));
57    }
58}