Revert commit 66c0185a3 and follow-on patches.
[pgsql.git] / src / backend / libpq / crypt.c
blob629e51e00be9e53524aee2432239690edab31627
1 /*-------------------------------------------------------------------------
3 * crypt.c
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 *-------------------------------------------------------------------------
14 #include "postgres.h"
16 #include <unistd.h>
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!
35 char *
36 get_role_password(const char *role, const char **logdetail)
38 TimestampTz vuntil = 0;
39 HeapTuple roleTup;
40 Datum datum;
41 bool isnull;
42 char *shadow_pass;
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."),
49 role);
50 return NULL; /* no such user */
53 datum = SysCacheGetAttr(AUTHNAME, roleTup,
54 Anum_pg_authid_rolpassword, &isnull);
55 if (isnull)
57 ReleaseSysCache(roleTup);
58 *logdetail = psprintf(_("User \"%s\" has no password assigned."),
59 role);
60 return NULL; /* user has no password */
62 shadow_pass = TextDatumGetCString(datum);
64 datum = SysCacheGetAttr(AUTHNAME, roleTup,
65 Anum_pg_authid_rolvaliduntil, &isnull);
66 if (!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."),
77 role);
78 return NULL;
81 return shadow_pass;
85 * What kind of a password type is 'shadow_pass'?
87 PasswordType
88 get_password_type(const char *shadow_pass)
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];
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.
114 char *
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);
131 switch (target_type)
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)
173 int retval;
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."),
183 role);
184 return STATUS_ERROR;
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,
193 crypt_pwd, &errstr))
195 *logdetail = errstr;
196 return STATUS_ERROR;
199 if (strcmp(client_pass, crypt_pwd) == 0)
200 retval = STATUS_OK;
201 else
203 *logdetail = psprintf(_("Password does not match for user \"%s\"."),
204 role);
205 retval = STATUS_ERROR;
208 return retval;
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,
238 client_pass,
239 shadow_pass))
241 return STATUS_OK;
243 else
245 *logdetail = psprintf(_("Password does not match for user \"%s\"."),
246 role);
247 return STATUS_ERROR;
249 break;
251 case PASSWORD_TYPE_MD5:
252 if (!pg_md5_encrypt(client_pass,
253 role,
254 strlen(role),
255 crypt_client_pass,
256 &errstr))
258 *logdetail = errstr;
259 return STATUS_ERROR;
261 if (strcmp(crypt_client_pass, shadow_pass) == 0)
262 return STATUS_OK;
263 else
265 *logdetail = psprintf(_("Password does not match for user \"%s\"."),
266 role);
267 return STATUS_ERROR;
269 break;
271 case PASSWORD_TYPE_PLAINTEXT:
274 * We never store passwords in plaintext, so this shouldn't
275 * happen.
277 break;
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."),
285 role);
286 return STATUS_ERROR;