Line data Source code
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 126 : get_role_password(const char *role, const char **logdetail)
37 : {
38 126 : 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 126 : roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
46 126 : if (!HeapTupleIsValid(roleTup))
47 : {
48 0 : *logdetail = psprintf(_("Role \"%s\" does not exist."),
49 : role);
50 0 : return NULL; /* no such user */
51 : }
52 :
53 126 : datum = SysCacheGetAttr(AUTHNAME, roleTup,
54 : Anum_pg_authid_rolpassword, &isnull);
55 126 : if (isnull)
56 : {
57 0 : ReleaseSysCache(roleTup);
58 0 : *logdetail = psprintf(_("User \"%s\" has no password assigned."),
59 : role);
60 0 : return NULL; /* user has no password */
61 : }
62 126 : shadow_pass = TextDatumGetCString(datum);
63 :
64 126 : datum = SysCacheGetAttr(AUTHNAME, roleTup,
65 : Anum_pg_authid_rolvaliduntil, &isnull);
66 126 : if (!isnull)
67 0 : vuntil = DatumGetTimestampTz(datum);
68 :
69 126 : ReleaseSysCache(roleTup);
70 :
71 : /*
72 : * Password OK, but check to be sure we are not past rolvaliduntil
73 : */
74 126 : if (!isnull && vuntil < GetCurrentTimestamp())
75 : {
76 0 : *logdetail = psprintf(_("User \"%s\" has an expired password."),
77 : role);
78 0 : return NULL;
79 : }
80 :
81 126 : return shadow_pass;
82 : }
83 :
84 : /*
85 : * What kind of a password type is 'shadow_pass'?
86 : */
87 : PasswordType
88 556 : get_password_type(const char *shadow_pass)
89 : {
90 : char *encoded_salt;
91 : int iterations;
92 556 : 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 556 : if (strncmp(shadow_pass, "md5", 3) == 0 &&
98 86 : strlen(shadow_pass) == MD5_PASSWD_LEN &&
99 74 : strspn(shadow_pass + 3, MD5_PASSWD_CHARSET) == MD5_PASSWD_LEN - 3)
100 62 : return PASSWORD_TYPE_MD5;
101 494 : if (parse_scram_secret(shadow_pass, &iterations, &hash_type, &key_length,
102 : &encoded_salt, stored_key, server_key))
103 250 : return PASSWORD_TYPE_SCRAM_SHA_256;
104 244 : return PASSWORD_TYPE_PLAINTEXT;
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 158 : encrypt_password(PasswordType target_type, const char *role,
116 : const char *password)
117 : {
118 158 : PasswordType guessed_type = get_password_type(password);
119 158 : char *encrypted_password = NULL;
120 158 : const char *errstr = NULL;
121 :
122 158 : 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 40 : encrypted_password = pstrdup(password);
129 : }
130 : else
131 : {
132 118 : switch (target_type)
133 : {
134 22 : case PASSWORD_TYPE_MD5:
135 22 : encrypted_password = palloc(MD5_PASSWD_LEN + 1);
136 :
137 22 : if (!pg_md5_encrypt(password, role, strlen(role),
138 : encrypted_password, &errstr))
139 0 : elog(ERROR, "password encryption failed: %s", errstr);
140 22 : break;
141 :
142 96 : case PASSWORD_TYPE_SCRAM_SHA_256:
143 96 : encrypted_password = pg_be_scram_build_secret(password);
144 96 : break;
145 :
146 0 : case PASSWORD_TYPE_PLAINTEXT:
147 0 : elog(ERROR, "cannot encrypt password with 'plaintext'");
148 : break;
149 : }
150 158 : }
151 :
152 : Assert(encrypted_password);
153 :
154 : /*
155 : * Valid password hashes may be very long, but we don't want to store
156 : * anything that might need out-of-line storage, since de-TOASTing won't
157 : * work during authentication because we haven't selected a database yet
158 : * and cannot read pg_class. 512 bytes should be more than enough for all
159 : * practical use, so fail for anything longer.
160 : */
161 158 : if (encrypted_password && /* keep compiler quiet */
162 158 : strlen(encrypted_password) > MAX_ENCRYPTED_PASSWORD_LEN)
163 : {
164 : /*
165 : * We don't expect any of our own hashing routines to produce hashes
166 : * that are too long.
167 : */
168 : Assert(guessed_type != PASSWORD_TYPE_PLAINTEXT);
169 :
170 12 : ereport(ERROR,
171 : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
172 : errmsg("encrypted password is too long"),
173 : errdetail("Encrypted passwords must be no longer than %d bytes.",
174 : MAX_ENCRYPTED_PASSWORD_LEN)));
175 : }
176 :
177 146 : return encrypted_password;
178 : }
179 :
180 : /*
181 : * Check MD5 authentication response, and return STATUS_OK or STATUS_ERROR.
182 : *
183 : * 'shadow_pass' is the user's correct password or password hash, as stored
184 : * in pg_authid.rolpassword.
185 : * 'client_pass' is the response given by the remote user to the MD5 challenge.
186 : * 'md5_salt' is the salt used in the MD5 authentication challenge.
187 : *
188 : * In the error case, save a string at *logdetail that will be sent to the
189 : * postmaster log (but not the client).
190 : */
191 : int
192 2 : md5_crypt_verify(const char *role, const char *shadow_pass,
193 : const char *client_pass,
194 : const char *md5_salt, int md5_salt_len,
195 : const char **logdetail)
196 : {
197 : int retval;
198 : char crypt_pwd[MD5_PASSWD_LEN + 1];
199 2 : const char *errstr = NULL;
200 :
201 : Assert(md5_salt_len > 0);
202 :
203 2 : if (get_password_type(shadow_pass) != PASSWORD_TYPE_MD5)
204 : {
205 : /* incompatible password hash format. */
206 0 : *logdetail = psprintf(_("User \"%s\" has a password that cannot be used with MD5 authentication."),
207 : role);
208 0 : return STATUS_ERROR;
209 : }
210 :
211 : /*
212 : * Compute the correct answer for the MD5 challenge.
213 : */
214 : /* stored password already encrypted, only do salt */
215 2 : if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
216 : md5_salt, md5_salt_len,
217 : crypt_pwd, &errstr))
218 : {
219 0 : *logdetail = errstr;
220 0 : return STATUS_ERROR;
221 : }
222 :
223 2 : if (strcmp(client_pass, crypt_pwd) == 0)
224 2 : retval = STATUS_OK;
225 : else
226 : {
227 0 : *logdetail = psprintf(_("Password does not match for user \"%s\"."),
228 : role);
229 0 : retval = STATUS_ERROR;
230 : }
231 :
232 2 : return retval;
233 : }
234 :
235 : /*
236 : * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
237 : *
238 : * 'shadow_pass' is the user's correct password hash, as stored in
239 : * pg_authid.rolpassword.
240 : * 'client_pass' is the password given by the remote user.
241 : *
242 : * In the error case, store a string at *logdetail that will be sent to the
243 : * postmaster log (but not the client).
244 : */
245 : int
246 194 : plain_crypt_verify(const char *role, const char *shadow_pass,
247 : const char *client_pass,
248 : const char **logdetail)
249 : {
250 : char crypt_client_pass[MD5_PASSWD_LEN + 1];
251 194 : const char *errstr = NULL;
252 :
253 : /*
254 : * Client sent password in plaintext. If we have an MD5 hash stored, hash
255 : * the password the client sent, and compare the hashes. Otherwise
256 : * compare the plaintext passwords directly.
257 : */
258 194 : switch (get_password_type(shadow_pass))
259 : {
260 50 : case PASSWORD_TYPE_SCRAM_SHA_256:
261 50 : if (scram_verify_plain_password(role,
262 : client_pass,
263 : shadow_pass))
264 : {
265 24 : return STATUS_OK;
266 : }
267 : else
268 : {
269 26 : *logdetail = psprintf(_("Password does not match for user \"%s\"."),
270 : role);
271 26 : return STATUS_ERROR;
272 : }
273 : break;
274 :
275 26 : case PASSWORD_TYPE_MD5:
276 26 : if (!pg_md5_encrypt(client_pass,
277 : role,
278 : strlen(role),
279 : crypt_client_pass,
280 : &errstr))
281 : {
282 0 : *logdetail = errstr;
283 0 : return STATUS_ERROR;
284 : }
285 26 : if (strcmp(crypt_client_pass, shadow_pass) == 0)
286 10 : return STATUS_OK;
287 : else
288 : {
289 16 : *logdetail = psprintf(_("Password does not match for user \"%s\"."),
290 : role);
291 16 : return STATUS_ERROR;
292 : }
293 : break;
294 :
295 118 : case PASSWORD_TYPE_PLAINTEXT:
296 :
297 : /*
298 : * We never store passwords in plaintext, so this shouldn't
299 : * happen.
300 : */
301 118 : break;
302 : }
303 :
304 : /*
305 : * This shouldn't happen. Plain "password" authentication is possible
306 : * with any kind of stored password hash.
307 : */
308 118 : *logdetail = psprintf(_("Password of user \"%s\" is in unrecognized format."),
309 : role);
310 118 : return STATUS_ERROR;
311 : }
|