PostgreSQL Source Code git master
crypt.c
Go to the documentation of this file.
1/*-------------------------------------------------------------------------
2 *
3 * crypt.c
4 * Functions for dealing with encrypted passwords stored in
5 * pg_authid.rolpassword.
6 *
7 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
8 * Portions Copyright (c) 1994, Regents of the University of California
9 *
10 * src/backend/libpq/crypt.c
11 *
12 *-------------------------------------------------------------------------
13 */
14#include "postgres.h"
15
16#include <unistd.h>
17
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"
26
27/* Enables deprecation warnings for MD5 passwords. */
29
30/*
31 * Fetch stored password for a user, for authentication.
32 *
33 * On error, returns NULL, and stores a palloc'd string describing the reason,
34 * for the postmaster log, in *logdetail. The error reason should *not* be
35 * sent to the client, to avoid giving away user information!
36 */
37char *
38get_role_password(const char *role, const char **logdetail)
39{
40 TimestampTz vuntil = 0;
41 HeapTuple roleTup;
42 Datum datum;
43 bool isnull;
44 char *shadow_pass;
45
46 /* Get role info from pg_authid */
47 roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
48 if (!HeapTupleIsValid(roleTup))
49 {
50 *logdetail = psprintf(_("Role \"%s\" does not exist."),
51 role);
52 return NULL; /* no such user */
53 }
54
55 datum = SysCacheGetAttr(AUTHNAME, roleTup,
56 Anum_pg_authid_rolpassword, &isnull);
57 if (isnull)
58 {
59 ReleaseSysCache(roleTup);
60 *logdetail = psprintf(_("User \"%s\" has no password assigned."),
61 role);
62 return NULL; /* user has no password */
63 }
64 shadow_pass = TextDatumGetCString(datum);
65
66 datum = SysCacheGetAttr(AUTHNAME, roleTup,
67 Anum_pg_authid_rolvaliduntil, &isnull);
68 if (!isnull)
69 vuntil = DatumGetTimestampTz(datum);
70
71 ReleaseSysCache(roleTup);
72
73 /*
74 * Password OK, but check to be sure we are not past rolvaliduntil
75 */
76 if (!isnull && vuntil < GetCurrentTimestamp())
77 {
78 *logdetail = psprintf(_("User \"%s\" has an expired password."),
79 role);
80 return NULL;
81 }
82
83 return shadow_pass;
84}
85
86/*
87 * What kind of a password type is 'shadow_pass'?
88 */
90get_password_type(const char *shadow_pass)
91{
92 char *encoded_salt;
93 int iterations;
94 int key_length = 0;
95 pg_cryptohash_type hash_type;
96 uint8 stored_key[SCRAM_MAX_KEY_LEN];
97 uint8 server_key[SCRAM_MAX_KEY_LEN];
98
99 if (strncmp(shadow_pass, "md5", 3) == 0 &&
100 strlen(shadow_pass) == MD5_PASSWD_LEN &&
101 strspn(shadow_pass + 3, MD5_PASSWD_CHARSET) == MD5_PASSWD_LEN - 3)
102 return PASSWORD_TYPE_MD5;
103 if (parse_scram_secret(shadow_pass, &iterations, &hash_type, &key_length,
104 &encoded_salt, stored_key, server_key))
107}
108
109/*
110 * Given a user-supplied password, convert it into a secret of
111 * 'target_type' kind.
112 *
113 * If the password is already in encrypted form, we cannot reverse the
114 * hash, so it is stored as it is regardless of the requested type.
115 */
116char *
117encrypt_password(PasswordType target_type, const char *role,
118 const char *password)
119{
121 char *encrypted_password = NULL;
122 const char *errstr = NULL;
123
124 if (guessed_type != PASSWORD_TYPE_PLAINTEXT)
125 {
126 /*
127 * Cannot convert an already-encrypted password from one format to
128 * another, so return it as it is.
129 */
130 encrypted_password = pstrdup(password);
131 }
132 else
133 {
134 switch (target_type)
135 {
137 encrypted_password = palloc(MD5_PASSWD_LEN + 1);
138
139 if (!pg_md5_encrypt(password, role, strlen(role),
140 encrypted_password, &errstr))
141 elog(ERROR, "password encryption failed: %s", errstr);
142 break;
143
145 encrypted_password = pg_be_scram_build_secret(password);
146 break;
147
149 elog(ERROR, "cannot encrypt password with 'plaintext'");
150 break;
151 }
152 }
153
154 Assert(encrypted_password);
155
156 /*
157 * Valid password hashes may be very long, but we don't want to store
158 * anything that might need out-of-line storage, since de-TOASTing won't
159 * work during authentication because we haven't selected a database yet
160 * and cannot read pg_class. 512 bytes should be more than enough for all
161 * practical use, so fail for anything longer.
162 */
163 if (encrypted_password && /* keep compiler quiet */
164 strlen(encrypted_password) > MAX_ENCRYPTED_PASSWORD_LEN)
165 {
166 /*
167 * We don't expect any of our own hashing routines to produce hashes
168 * that are too long.
169 */
170 Assert(guessed_type != PASSWORD_TYPE_PLAINTEXT);
171
173 (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
174 errmsg("encrypted password is too long"),
175 errdetail("Encrypted passwords must be no longer than %d bytes.",
177 }
178
180 get_password_type(encrypted_password) == PASSWORD_TYPE_MD5)
182 (errcode(ERRCODE_WARNING_DEPRECATED_FEATURE),
183 errmsg("setting an MD5-encrypted password"),
184 errdetail("MD5 password support is deprecated and will be removed in a future release of PostgreSQL."),
185 errhint("Refer to the PostgreSQL documentation for details about migrating to another password type.")));
186
187 return encrypted_password;
188}
189
190/*
191 * Check MD5 authentication response, and return STATUS_OK or STATUS_ERROR.
192 *
193 * 'shadow_pass' is the user's correct password or password hash, as stored
194 * in pg_authid.rolpassword.
195 * 'client_pass' is the response given by the remote user to the MD5 challenge.
196 * 'md5_salt' is the salt used in the MD5 authentication challenge.
197 *
198 * In the error case, save a string at *logdetail that will be sent to the
199 * postmaster log (but not the client).
200 */
201int
202md5_crypt_verify(const char *role, const char *shadow_pass,
203 const char *client_pass,
204 const char *md5_salt, int md5_salt_len,
205 const char **logdetail)
206{
207 int retval;
208 char crypt_pwd[MD5_PASSWD_LEN + 1];
209 const char *errstr = NULL;
210
211 Assert(md5_salt_len > 0);
212
213 if (get_password_type(shadow_pass) != PASSWORD_TYPE_MD5)
214 {
215 /* incompatible password hash format. */
216 *logdetail = psprintf(_("User \"%s\" has a password that cannot be used with MD5 authentication."),
217 role);
218 return STATUS_ERROR;
219 }
220
221 /*
222 * Compute the correct answer for the MD5 challenge.
223 */
224 /* stored password already encrypted, only do salt */
225 if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
226 md5_salt, md5_salt_len,
227 crypt_pwd, &errstr))
228 {
229 *logdetail = errstr;
230 return STATUS_ERROR;
231 }
232
233 if (strcmp(client_pass, crypt_pwd) == 0)
234 retval = STATUS_OK;
235 else
236 {
237 *logdetail = psprintf(_("Password does not match for user \"%s\"."),
238 role);
239 retval = STATUS_ERROR;
240 }
241
242 return retval;
243}
244
245/*
246 * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
247 *
248 * 'shadow_pass' is the user's correct password hash, as stored in
249 * pg_authid.rolpassword.
250 * 'client_pass' is the password given by the remote user.
251 *
252 * In the error case, store a string at *logdetail that will be sent to the
253 * postmaster log (but not the client).
254 */
255int
256plain_crypt_verify(const char *role, const char *shadow_pass,
257 const char *client_pass,
258 const char **logdetail)
259{
260 char crypt_client_pass[MD5_PASSWD_LEN + 1];
261 const char *errstr = NULL;
262
263 /*
264 * Client sent password in plaintext. If we have an MD5 hash stored, hash
265 * the password the client sent, and compare the hashes. Otherwise
266 * compare the plaintext passwords directly.
267 */
268 switch (get_password_type(shadow_pass))
269 {
272 client_pass,
273 shadow_pass))
274 {
275 return STATUS_OK;
276 }
277 else
278 {
279 *logdetail = psprintf(_("Password does not match for user \"%s\"."),
280 role);
281 return STATUS_ERROR;
282 }
283 break;
284
286 if (!pg_md5_encrypt(client_pass,
287 role,
288 strlen(role),
289 crypt_client_pass,
290 &errstr))
291 {
292 *logdetail = errstr;
293 return STATUS_ERROR;
294 }
295 if (strcmp(crypt_client_pass, shadow_pass) == 0)
296 return STATUS_OK;
297 else
298 {
299 *logdetail = psprintf(_("Password does not match for user \"%s\"."),
300 role);
301 return STATUS_ERROR;
302 }
303 break;
304
306
307 /*
308 * We never store passwords in plaintext, so this shouldn't
309 * happen.
310 */
311 break;
312 }
313
314 /*
315 * This shouldn't happen. Plain "password" authentication is possible
316 * with any kind of stored password hash.
317 */
318 *logdetail = psprintf(_("Password of user \"%s\" is in unrecognized format."),
319 role);
320 return STATUS_ERROR;
321}
bool parse_scram_secret(const char *secret, int *iterations, pg_cryptohash_type *hash_type, int *key_length, char **salt, uint8 *stored_key, uint8 *server_key)
Definition: auth-scram.c:600
char * pg_be_scram_build_secret(const char *password)
Definition: auth-scram.c:483
bool scram_verify_plain_password(const char *username, const char *password, const char *secret)
Definition: auth-scram.c:523
TimestampTz GetCurrentTimestamp(void)
Definition: timestamp.c:1644
#define TextDatumGetCString(d)
Definition: builtins.h:98
#define STATUS_OK
Definition: c.h:1140
uint8_t uint8
Definition: c.h:500
#define STATUS_ERROR
Definition: c.h:1141
int plain_crypt_verify(const char *role, const char *shadow_pass, const char *client_pass, const char **logdetail)
Definition: crypt.c:256
char * get_role_password(const char *role, const char **logdetail)
Definition: crypt.c:38
bool md5_password_warnings
Definition: crypt.c:28
PasswordType get_password_type(const char *shadow_pass)
Definition: crypt.c:90
char * encrypt_password(PasswordType target_type, const char *role, const char *password)
Definition: crypt.c:117
int md5_crypt_verify(const char *role, const char *shadow_pass, const char *client_pass, const char *md5_salt, int md5_salt_len, const char **logdetail)
Definition: crypt.c:202
PasswordType
Definition: crypt.h:41
@ PASSWORD_TYPE_PLAINTEXT
Definition: crypt.h:42
@ PASSWORD_TYPE_SCRAM_SHA_256
Definition: crypt.h:44
@ PASSWORD_TYPE_MD5
Definition: crypt.h:43
#define MAX_ENCRYPTED_PASSWORD_LEN
Definition: crypt.h:26
pg_cryptohash_type
Definition: cryptohash.h:20
int64 TimestampTz
Definition: timestamp.h:39
int errdetail(const char *fmt,...)
Definition: elog.c:1203
int errhint(const char *fmt,...)
Definition: elog.c:1317
int errcode(int sqlerrcode)
Definition: elog.c:853
int errmsg(const char *fmt,...)
Definition: elog.c:1070
#define _(x)
Definition: elog.c:90
#define WARNING
Definition: elog.h:36
#define ERROR
Definition: elog.h:39
#define elog(elevel,...)
Definition: elog.h:225
#define ereport(elevel,...)
Definition: elog.h:149
Assert(PointerIsAligned(start, uint64))
#define HeapTupleIsValid(tuple)
Definition: htup.h:78
char * pstrdup(const char *in)
Definition: mcxt.c:1699
void * palloc(Size size)
Definition: mcxt.c:1317
#define MD5_PASSWD_CHARSET
Definition: md5.h:25
#define MD5_PASSWD_LEN
Definition: md5.h:26
bool pg_md5_encrypt(const char *passwd, const char *salt, size_t salt_len, char *buf, const char **errstr)
Definition: md5_common.c:145
static Datum PointerGetDatum(const void *X)
Definition: postgres.h:327
uintptr_t Datum
Definition: postgres.h:69
char * psprintf(const char *fmt,...)
Definition: psprintf.c:43
#define SCRAM_MAX_KEY_LEN
Definition: scram-common.h:30
static char * password
Definition: streamutil.c:51
void ReleaseSysCache(HeapTuple tuple)
Definition: syscache.c:269
HeapTuple SearchSysCache1(int cacheId, Datum key1)
Definition: syscache.c:221
Datum SysCacheGetAttr(int cacheId, HeapTuple tup, AttrNumber attributeNumber, bool *isNull)
Definition: syscache.c:600
int iterations
Definition: thread-thread.c:39
static TimestampTz DatumGetTimestampTz(Datum X)
Definition: timestamp.h:34