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