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 124 : get_role_password(const char *role, const char **logdetail)
37 : {
38 124 : 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 124 : roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
46 124 : 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 124 : datum = SysCacheGetAttr(AUTHNAME, roleTup,
54 : Anum_pg_authid_rolpassword, &isnull);
55 124 : 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 124 : shadow_pass = TextDatumGetCString(datum);
63 :
64 124 : datum = SysCacheGetAttr(AUTHNAME, roleTup,
65 : Anum_pg_authid_rolvaliduntil, &isnull);
66 124 : if (!isnull)
67 0 : vuntil = DatumGetTimestampTz(datum);
68 :
69 124 : ReleaseSysCache(roleTup);
70 :
71 : /*
72 : * Password OK, but check to be sure we are not past rolvaliduntil
73 : */
74 124 : if (!isnull && vuntil < GetCurrentTimestamp())
75 : {
76 0 : *logdetail = psprintf(_("User \"%s\" has an expired password."),
77 : role);
78 0 : return NULL;
79 : }
80 :
81 124 : return shadow_pass;
82 : }
83 :
84 : /*
85 : * What kind of a password type is 'shadow_pass'?
86 : */
87 : PasswordType
88 528 : get_password_type(const char *shadow_pass)
89 : {
90 : char *encoded_salt;
91 : int iterations;
92 528 : 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 528 : 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 466 : if (parse_scram_secret(shadow_pass, &iterations, &hash_type, &key_length,
102 : &encoded_salt, stored_key, server_key))
103 222 : 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 146 : encrypt_password(PasswordType target_type, const char *role,
116 : const char *password)
117 : {
118 146 : PasswordType guessed_type = get_password_type(password);
119 : char *encrypted_password;
120 146 : const char *errstr = NULL;
121 :
122 146 : 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 28 : return pstrdup(password);
129 : }
130 :
131 118 : switch (target_type)
132 : {
133 22 : case PASSWORD_TYPE_MD5:
134 22 : encrypted_password = palloc(MD5_PASSWD_LEN + 1);
135 :
136 22 : if (!pg_md5_encrypt(password, role, strlen(role),
137 : encrypted_password, &errstr))
138 0 : elog(ERROR, "password encryption failed: %s", errstr);
139 22 : return encrypted_password;
140 :
141 96 : case PASSWORD_TYPE_SCRAM_SHA_256:
142 96 : return pg_be_scram_build_secret(password);
143 :
144 0 : case PASSWORD_TYPE_PLAINTEXT:
145 0 : 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 0 : 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 2 : 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 2 : const char *errstr = NULL;
176 :
177 : Assert(md5_salt_len > 0);
178 :
179 2 : if (get_password_type(shadow_pass) != PASSWORD_TYPE_MD5)
180 : {
181 : /* incompatible password hash format. */
182 0 : *logdetail = psprintf(_("User \"%s\" has a password that cannot be used with MD5 authentication."),
183 : role);
184 0 : 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 2 : if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
192 : md5_salt, md5_salt_len,
193 : crypt_pwd, &errstr))
194 : {
195 0 : *logdetail = errstr;
196 0 : return STATUS_ERROR;
197 : }
198 :
199 2 : if (strcmp(client_pass, crypt_pwd) == 0)
200 2 : retval = STATUS_OK;
201 : else
202 : {
203 0 : *logdetail = psprintf(_("Password does not match for user \"%s\"."),
204 : role);
205 0 : retval = STATUS_ERROR;
206 : }
207 :
208 2 : 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 182 : 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 182 : 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 182 : switch (get_password_type(shadow_pass))
235 : {
236 38 : case PASSWORD_TYPE_SCRAM_SHA_256:
237 38 : if (scram_verify_plain_password(role,
238 : client_pass,
239 : shadow_pass))
240 : {
241 24 : return STATUS_OK;
242 : }
243 : else
244 : {
245 14 : *logdetail = psprintf(_("Password does not match for user \"%s\"."),
246 : role);
247 14 : return STATUS_ERROR;
248 : }
249 : break;
250 :
251 26 : case PASSWORD_TYPE_MD5:
252 26 : if (!pg_md5_encrypt(client_pass,
253 : role,
254 : strlen(role),
255 : crypt_client_pass,
256 : &errstr))
257 : {
258 0 : *logdetail = errstr;
259 0 : return STATUS_ERROR;
260 : }
261 26 : if (strcmp(crypt_client_pass, shadow_pass) == 0)
262 10 : return STATUS_OK;
263 : else
264 : {
265 16 : *logdetail = psprintf(_("Password does not match for user \"%s\"."),
266 : role);
267 16 : return STATUS_ERROR;
268 : }
269 : break;
270 :
271 118 : case PASSWORD_TYPE_PLAINTEXT:
272 :
273 : /*
274 : * We never store passwords in plaintext, so this shouldn't
275 : * happen.
276 : */
277 118 : break;
278 : }
279 :
280 : /*
281 : * This shouldn't happen. Plain "password" authentication is possible
282 : * with any kind of stored password hash.
283 : */
284 118 : *logdetail = psprintf(_("Password of user \"%s\" is in unrecognized format."),
285 : role);
286 118 : return STATUS_ERROR;
287 : }
|