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-2024, 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  */
37 char *
38 get_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  */
90 get_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  */
116 char *
117 encrypt_password(PasswordType target_type, const char *role,
118  const char *password)
119 {
120  PasswordType guessed_type = get_password_type(password);
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  {
136  case PASSWORD_TYPE_MD5:
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 
172  ereport(ERROR,
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 
179  if (md5_password_warnings &&
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  */
201 int
202 md5_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  */
255 int
256 plain_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 
285  case PASSWORD_TYPE_MD5:
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:591
char * pg_be_scram_build_secret(const char *password)
Definition: auth-scram.c:474
bool scram_verify_plain_password(const char *username, const char *password, const char *secret)
Definition: auth-scram.c:514
TimestampTz GetCurrentTimestamp(void)
Definition: timestamp.c:1644
#define TextDatumGetCString(d)
Definition: builtins.h:98
#define STATUS_OK
Definition: c.h:1123
uint8_t uint8
Definition: c.h:483
#define Assert(condition)
Definition: c.h:812
#define STATUS_ERROR
Definition: c.h:1124
int plain_crypt_verify(const char *role, const char *shadow_pass, const char *client_pass, const char **logdetail)
Definition: crypt.c:256
bool md5_password_warnings
Definition: crypt.c:28
char * get_role_password(const char *role, const char **logdetail)
Definition: crypt.c:38
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
#define HeapTupleIsValid(tuple)
Definition: htup.h:78
char * pstrdup(const char *in)
Definition: mcxt.c:1696
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:322
uintptr_t Datum
Definition: postgres.h:64
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:52
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