1 /*-------------------------------------------------------------------------
4 * Functions for dealing with encrypted passwords stored in
5 * pg_authid.rolpassword.
7 * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
8 * Portions Copyright (c) 1994, Regents of the University of California
10 * src/backend/libpq/crypt.c
12 *-------------------------------------------------------------------------
18 #include "catalog/pg_authid.h"
19 #include "common/md5.h"
20 #include "common/scram-common.h"
21 #include "libpq/crypt.h"
22 #include "libpq/scram.h"
23 #include "utils/builtins.h"
24 #include "utils/syscache.h"
25 #include "utils/timestamp.h"
29 * Fetch stored password for a user, for authentication.
31 * On error, returns NULL, and stores a palloc'd string describing the reason,
32 * for the postmaster log, in *logdetail. The error reason should *not* be
33 * sent to the client, to avoid giving away user information!
36 get_role_password(const char *role
, const char **logdetail
)
38 TimestampTz vuntil
= 0;
44 /* Get role info from pg_authid */
45 roleTup
= SearchSysCache1(AUTHNAME
, PointerGetDatum(role
));
46 if (!HeapTupleIsValid(roleTup
))
48 *logdetail
= psprintf(_("Role \"%s\" does not exist."),
50 return NULL
; /* no such user */
53 datum
= SysCacheGetAttr(AUTHNAME
, roleTup
,
54 Anum_pg_authid_rolpassword
, &isnull
);
57 ReleaseSysCache(roleTup
);
58 *logdetail
= psprintf(_("User \"%s\" has no password assigned."),
60 return NULL
; /* user has no password */
62 shadow_pass
= TextDatumGetCString(datum
);
64 datum
= SysCacheGetAttr(AUTHNAME
, roleTup
,
65 Anum_pg_authid_rolvaliduntil
, &isnull
);
67 vuntil
= DatumGetTimestampTz(datum
);
69 ReleaseSysCache(roleTup
);
72 * Password OK, but check to be sure we are not past rolvaliduntil
74 if (!isnull
&& vuntil
< GetCurrentTimestamp())
76 *logdetail
= psprintf(_("User \"%s\" has an expired password."),
85 * What kind of a password type is 'shadow_pass'?
88 get_password_type(const char *shadow_pass
)
93 pg_cryptohash_type hash_type
;
94 uint8 stored_key
[SCRAM_MAX_KEY_LEN
];
95 uint8 server_key
[SCRAM_MAX_KEY_LEN
];
97 if (strncmp(shadow_pass
, "md5", 3) == 0 &&
98 strlen(shadow_pass
) == MD5_PASSWD_LEN
&&
99 strspn(shadow_pass
+ 3, MD5_PASSWD_CHARSET
) == MD5_PASSWD_LEN
- 3)
100 return PASSWORD_TYPE_MD5
;
101 if (parse_scram_secret(shadow_pass
, &iterations
, &hash_type
, &key_length
,
102 &encoded_salt
, stored_key
, server_key
))
103 return PASSWORD_TYPE_SCRAM_SHA_256
;
104 return PASSWORD_TYPE_PLAINTEXT
;
108 * Given a user-supplied password, convert it into a secret of
109 * 'target_type' kind.
111 * If the password is already in encrypted form, we cannot reverse the
112 * hash, so it is stored as it is regardless of the requested type.
115 encrypt_password(PasswordType target_type
, const char *role
,
116 const char *password
)
118 PasswordType guessed_type
= get_password_type(password
);
119 char *encrypted_password
;
120 const char *errstr
= NULL
;
122 if (guessed_type
!= PASSWORD_TYPE_PLAINTEXT
)
125 * Cannot convert an already-encrypted password from one format to
126 * another, so return it as it is.
128 return pstrdup(password
);
133 case PASSWORD_TYPE_MD5
:
134 encrypted_password
= palloc(MD5_PASSWD_LEN
+ 1);
136 if (!pg_md5_encrypt(password
, role
, strlen(role
),
137 encrypted_password
, &errstr
))
138 elog(ERROR
, "password encryption failed: %s", errstr
);
139 return encrypted_password
;
141 case PASSWORD_TYPE_SCRAM_SHA_256
:
142 return pg_be_scram_build_secret(password
);
144 case PASSWORD_TYPE_PLAINTEXT
:
145 elog(ERROR
, "cannot encrypt password with 'plaintext'");
149 * This shouldn't happen, because the above switch statements should
150 * handle every combination of source and target password types.
152 elog(ERROR
, "cannot encrypt password to requested type");
153 return NULL
; /* keep compiler quiet */
157 * Check MD5 authentication response, and return STATUS_OK or STATUS_ERROR.
159 * 'shadow_pass' is the user's correct password or password hash, as stored
160 * in pg_authid.rolpassword.
161 * 'client_pass' is the response given by the remote user to the MD5 challenge.
162 * 'md5_salt' is the salt used in the MD5 authentication challenge.
164 * In the error case, save a string at *logdetail that will be sent to the
165 * postmaster log (but not the client).
168 md5_crypt_verify(const char *role
, const char *shadow_pass
,
169 const char *client_pass
,
170 const char *md5_salt
, int md5_salt_len
,
171 const char **logdetail
)
174 char crypt_pwd
[MD5_PASSWD_LEN
+ 1];
175 const char *errstr
= NULL
;
177 Assert(md5_salt_len
> 0);
179 if (get_password_type(shadow_pass
) != PASSWORD_TYPE_MD5
)
181 /* incompatible password hash format. */
182 *logdetail
= psprintf(_("User \"%s\" has a password that cannot be used with MD5 authentication."),
188 * Compute the correct answer for the MD5 challenge.
190 /* stored password already encrypted, only do salt */
191 if (!pg_md5_encrypt(shadow_pass
+ strlen("md5"),
192 md5_salt
, md5_salt_len
,
199 if (strcmp(client_pass
, crypt_pwd
) == 0)
203 *logdetail
= psprintf(_("Password does not match for user \"%s\"."),
205 retval
= STATUS_ERROR
;
212 * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
214 * 'shadow_pass' is the user's correct password hash, as stored in
215 * pg_authid.rolpassword.
216 * 'client_pass' is the password given by the remote user.
218 * In the error case, store a string at *logdetail that will be sent to the
219 * postmaster log (but not the client).
222 plain_crypt_verify(const char *role
, const char *shadow_pass
,
223 const char *client_pass
,
224 const char **logdetail
)
226 char crypt_client_pass
[MD5_PASSWD_LEN
+ 1];
227 const char *errstr
= NULL
;
230 * Client sent password in plaintext. If we have an MD5 hash stored, hash
231 * the password the client sent, and compare the hashes. Otherwise
232 * compare the plaintext passwords directly.
234 switch (get_password_type(shadow_pass
))
236 case PASSWORD_TYPE_SCRAM_SHA_256
:
237 if (scram_verify_plain_password(role
,
245 *logdetail
= psprintf(_("Password does not match for user \"%s\"."),
251 case PASSWORD_TYPE_MD5
:
252 if (!pg_md5_encrypt(client_pass
,
261 if (strcmp(crypt_client_pass
, shadow_pass
) == 0)
265 *logdetail
= psprintf(_("Password does not match for user \"%s\"."),
271 case PASSWORD_TYPE_PLAINTEXT
:
274 * We never store passwords in plaintext, so this shouldn't
281 * This shouldn't happen. Plain "password" authentication is possible
282 * with any kind of stored password hash.
284 *logdetail
= psprintf(_("Password of user \"%s\" is in unrecognized format."),