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 
28 /*
29  * Fetch stored password for a user, for authentication.
30  *
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!
34  */
35 char *
36 get_role_password(const char *role, const char **logdetail)
37 {
38  TimestampTz vuntil = 0;
39  HeapTuple roleTup;
40  Datum datum;
41  bool isnull;
42  char *shadow_pass;
43 
44  /* Get role info from pg_authid */
45  roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
46  if (!HeapTupleIsValid(roleTup))
47  {
48  *logdetail = psprintf(_("Role \"%s\" does not exist."),
49  role);
50  return NULL; /* no such user */
51  }
52 
53  datum = SysCacheGetAttr(AUTHNAME, roleTup,
54  Anum_pg_authid_rolpassword, &isnull);
55  if (isnull)
56  {
57  ReleaseSysCache(roleTup);
58  *logdetail = psprintf(_("User \"%s\" has no password assigned."),
59  role);
60  return NULL; /* user has no password */
61  }
62  shadow_pass = TextDatumGetCString(datum);
63 
64  datum = SysCacheGetAttr(AUTHNAME, roleTup,
65  Anum_pg_authid_rolvaliduntil, &isnull);
66  if (!isnull)
67  vuntil = DatumGetTimestampTz(datum);
68 
69  ReleaseSysCache(roleTup);
70 
71  /*
72  * Password OK, but check to be sure we are not past rolvaliduntil
73  */
74  if (!isnull && vuntil < GetCurrentTimestamp())
75  {
76  *logdetail = psprintf(_("User \"%s\" has an expired password."),
77  role);
78  return NULL;
79  }
80 
81  return shadow_pass;
82 }
83 
84 /*
85  * What kind of a password type is 'shadow_pass'?
86  */
88 get_password_type(const char *shadow_pass)
89 {
90  char *encoded_salt;
91  int iterations;
92  int key_length = 0;
93  pg_cryptohash_type hash_type;
94  uint8 stored_key[SCRAM_MAX_KEY_LEN];
95  uint8 server_key[SCRAM_MAX_KEY_LEN];
96 
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))
105 }
106 
107 /*
108  * Given a user-supplied password, convert it into a secret of
109  * 'target_type' kind.
110  *
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.
113  */
114 char *
115 encrypt_password(PasswordType target_type, const char *role,
116  const char *password)
117 {
118  PasswordType guessed_type = get_password_type(password);
119  char *encrypted_password;
120  const char *errstr = NULL;
121 
122  if (guessed_type != PASSWORD_TYPE_PLAINTEXT)
123  {
124  /*
125  * Cannot convert an already-encrypted password from one format to
126  * another, so return it as it is.
127  */
128  return pstrdup(password);
129  }
130 
131  switch (target_type)
132  {
133  case PASSWORD_TYPE_MD5:
134  encrypted_password = palloc(MD5_PASSWD_LEN + 1);
135 
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;
140 
143 
145  elog(ERROR, "cannot encrypt password with 'plaintext'");
146  }
147 
148  /*
149  * This shouldn't happen, because the above switch statements should
150  * handle every combination of source and target password types.
151  */
152  elog(ERROR, "cannot encrypt password to requested type");
153  return NULL; /* keep compiler quiet */
154 }
155 
156 /*
157  * Check MD5 authentication response, and return STATUS_OK or STATUS_ERROR.
158  *
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.
163  *
164  * In the error case, save a string at *logdetail that will be sent to the
165  * postmaster log (but not the client).
166  */
167 int
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)
172 {
173  int retval;
174  char crypt_pwd[MD5_PASSWD_LEN + 1];
175  const char *errstr = NULL;
176 
177  Assert(md5_salt_len > 0);
178 
179  if (get_password_type(shadow_pass) != PASSWORD_TYPE_MD5)
180  {
181  /* incompatible password hash format. */
182  *logdetail = psprintf(_("User \"%s\" has a password that cannot be used with MD5 authentication."),
183  role);
184  return STATUS_ERROR;
185  }
186 
187  /*
188  * Compute the correct answer for the MD5 challenge.
189  */
190  /* stored password already encrypted, only do salt */
191  if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
192  md5_salt, md5_salt_len,
193  crypt_pwd, &errstr))
194  {
195  *logdetail = errstr;
196  return STATUS_ERROR;
197  }
198 
199  if (strcmp(client_pass, crypt_pwd) == 0)
200  retval = STATUS_OK;
201  else
202  {
203  *logdetail = psprintf(_("Password does not match for user \"%s\"."),
204  role);
205  retval = STATUS_ERROR;
206  }
207 
208  return retval;
209 }
210 
211 /*
212  * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
213  *
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.
217  *
218  * In the error case, store a string at *logdetail that will be sent to the
219  * postmaster log (but not the client).
220  */
221 int
222 plain_crypt_verify(const char *role, const char *shadow_pass,
223  const char *client_pass,
224  const char **logdetail)
225 {
226  char crypt_client_pass[MD5_PASSWD_LEN + 1];
227  const char *errstr = NULL;
228 
229  /*
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.
233  */
234  switch (get_password_type(shadow_pass))
235  {
238  client_pass,
239  shadow_pass))
240  {
241  return STATUS_OK;
242  }
243  else
244  {
245  *logdetail = psprintf(_("Password does not match for user \"%s\"."),
246  role);
247  return STATUS_ERROR;
248  }
249  break;
250 
251  case PASSWORD_TYPE_MD5:
252  if (!pg_md5_encrypt(client_pass,
253  role,
254  strlen(role),
255  crypt_client_pass,
256  &errstr))
257  {
258  *logdetail = errstr;
259  return STATUS_ERROR;
260  }
261  if (strcmp(crypt_client_pass, shadow_pass) == 0)
262  return STATUS_OK;
263  else
264  {
265  *logdetail = psprintf(_("Password does not match for user \"%s\"."),
266  role);
267  return STATUS_ERROR;
268  }
269  break;
270 
272 
273  /*
274  * We never store passwords in plaintext, so this shouldn't
275  * happen.
276  */
277  break;
278  }
279 
280  /*
281  * This shouldn't happen. Plain "password" authentication is possible
282  * with any kind of stored password hash.
283  */
284  *logdetail = psprintf(_("Password of user \"%s\" is in unrecognized format."),
285  role);
286  return STATUS_ERROR;
287 }
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:589
char * pg_be_scram_build_secret(const char *password)
Definition: auth-scram.c:472
bool scram_verify_plain_password(const char *username, const char *password, const char *secret)
Definition: auth-scram.c:512
TimestampTz GetCurrentTimestamp(void)
Definition: timestamp.c:1654
#define TextDatumGetCString(d)
Definition: builtins.h:98
#define STATUS_OK
Definition: c.h:1169
#define Assert(condition)
Definition: c.h:858
unsigned char uint8
Definition: c.h:504
#define STATUS_ERROR
Definition: c.h:1170
int plain_crypt_verify(const char *role, const char *shadow_pass, const char *client_pass, const char **logdetail)
Definition: crypt.c:222
char * get_role_password(const char *role, const char **logdetail)
Definition: crypt.c:36
PasswordType get_password_type(const char *shadow_pass)
Definition: crypt.c:88
char * encrypt_password(PasswordType target_type, const char *role, const char *password)
Definition: crypt.c:115
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:168
PasswordType
Definition: crypt.h:28
@ PASSWORD_TYPE_PLAINTEXT
Definition: crypt.h:29
@ PASSWORD_TYPE_SCRAM_SHA_256
Definition: crypt.h:31
@ PASSWORD_TYPE_MD5
Definition: crypt.h:30
pg_cryptohash_type
Definition: cryptohash.h:20
int64 TimestampTz
Definition: timestamp.h:39
#define _(x)
Definition: elog.c:90
#define ERROR
Definition: elog.h:39
#define elog(elevel,...)
Definition: elog.h:224
#define HeapTupleIsValid(tuple)
Definition: htup.h:78
char * pstrdup(const char *in)
Definition: mcxt.c:1695
void * palloc(Size size)
Definition: mcxt.c:1316
#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:46
#define SCRAM_MAX_KEY_LEN
Definition: scram-common.h:30
static char * password
Definition: streamutil.c:54
void ReleaseSysCache(HeapTuple tuple)
Definition: syscache.c:266
HeapTuple SearchSysCache1(int cacheId, Datum key1)
Definition: syscache.c:218
Datum SysCacheGetAttr(int cacheId, HeapTuple tup, AttrNumber attributeNumber, bool *isNull)
Definition: syscache.c:479
int iterations
Definition: thread-thread.c:39
static TimestampTz DatumGetTimestampTz(Datum X)
Definition: timestamp.h:34