1 /*-------------------------------------------------------------------------
2 *
3 * crypt.c
4 * Functions for dealing with encrypted passwords stored in
5 * pg_authid.rolpassword.
6 *
7 * Portions Copyright (c) 1996-2021, 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 *
get_role_password(const char * role,char ** logdetail)37 get_role_password(const char *role, 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 */
88 PasswordType
get_password_type(const char * shadow_pass)89 get_password_type(const char *shadow_pass)
90 {
91 char *encoded_salt;
92 int iterations;
93 uint8 stored_key[SCRAM_KEY_LEN];
94 uint8 server_key[SCRAM_KEY_LEN];
95
96 if (strncmp(shadow_pass, "md5", 3) == 0 &&
97 strlen(shadow_pass) == MD5_PASSWD_LEN &&
98 strspn(shadow_pass + 3, MD5_PASSWD_CHARSET) == MD5_PASSWD_LEN - 3)
99 return PASSWORD_TYPE_MD5;
100 if (parse_scram_secret(shadow_pass, &iterations, &encoded_salt,
101 stored_key, server_key))
102 return PASSWORD_TYPE_SCRAM_SHA_256;
103 return PASSWORD_TYPE_PLAINTEXT;
104 }
105
106 /*
107 * Given a user-supplied password, convert it into a secret of
108 * 'target_type' kind.
109 *
110 * If the password is already in encrypted form, we cannot reverse the
111 * hash, so it is stored as it is regardless of the requested type.
112 */
113 char *
encrypt_password(PasswordType target_type,const char * role,const char * password)114 encrypt_password(PasswordType target_type, const char *role,
115 const char *password)
116 {
117 PasswordType guessed_type = get_password_type(password);
118 char *encrypted_password;
119
120 if (guessed_type != PASSWORD_TYPE_PLAINTEXT)
121 {
122 /*
123 * Cannot convert an already-encrypted password from one format to
124 * another, so return it as it is.
125 */
126 return pstrdup(password);
127 }
128
129 switch (target_type)
130 {
131 case PASSWORD_TYPE_MD5:
132 encrypted_password = palloc(MD5_PASSWD_LEN + 1);
133
134 if (!pg_md5_encrypt(password, role, strlen(role),
135 encrypted_password))
136 elog(ERROR, "password encryption failed");
137 return encrypted_password;
138
139 case PASSWORD_TYPE_SCRAM_SHA_256:
140 return pg_be_scram_build_secret(password);
141
142 case PASSWORD_TYPE_PLAINTEXT:
143 elog(ERROR, "cannot encrypt password with 'plaintext'");
144 }
145
146 /*
147 * This shouldn't happen, because the above switch statements should
148 * handle every combination of source and target password types.
149 */
150 elog(ERROR, "cannot encrypt password to requested type");
151 return NULL; /* keep compiler quiet */
152 }
153
154 /*
155 * Check MD5 authentication response, and return STATUS_OK or STATUS_ERROR.
156 *
157 * 'shadow_pass' is the user's correct password or password hash, as stored
158 * in pg_authid.rolpassword.
159 * 'client_pass' is the response given by the remote user to the MD5 challenge.
160 * 'md5_salt' is the salt used in the MD5 authentication challenge.
161 *
162 * In the error case, optionally store a palloc'd string at *logdetail
163 * that will be sent to the postmaster log (but not the client).
164 */
165 int
md5_crypt_verify(const char * role,const char * shadow_pass,const char * client_pass,const char * md5_salt,int md5_salt_len,char ** logdetail)166 md5_crypt_verify(const char *role, const char *shadow_pass,
167 const char *client_pass,
168 const char *md5_salt, int md5_salt_len,
169 char **logdetail)
170 {
171 int retval;
172 char crypt_pwd[MD5_PASSWD_LEN + 1];
173
174 Assert(md5_salt_len > 0);
175
176 if (get_password_type(shadow_pass) != PASSWORD_TYPE_MD5)
177 {
178 /* incompatible password hash format. */
179 *logdetail = psprintf(_("User \"%s\" has a password that cannot be used with MD5 authentication."),
180 role);
181 return STATUS_ERROR;
182 }
183
184 /*
185 * Compute the correct answer for the MD5 challenge.
186 *
187 * We do not bother setting logdetail for any pg_md5_encrypt failure
188 * below: the only possible error is out-of-memory, which is unlikely, and
189 * if it did happen adding a psprintf call would only make things worse.
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))
195 {
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, optionally store a palloc'd string at *logdetail
219 * that will be sent to the postmaster log (but not the client).
220 */
221 int
plain_crypt_verify(const char * role,const char * shadow_pass,const char * client_pass,char ** logdetail)222 plain_crypt_verify(const char *role, const char *shadow_pass,
223 const char *client_pass,
224 char **logdetail)
225 {
226 char crypt_client_pass[MD5_PASSWD_LEN + 1];
227
228 /*
229 * Client sent password in plaintext. If we have an MD5 hash stored, hash
230 * the password the client sent, and compare the hashes. Otherwise
231 * compare the plaintext passwords directly.
232 */
233 switch (get_password_type(shadow_pass))
234 {
235 case PASSWORD_TYPE_SCRAM_SHA_256:
236 if (scram_verify_plain_password(role,
237 client_pass,
238 shadow_pass))
239 {
240 return STATUS_OK;
241 }
242 else
243 {
244 *logdetail = psprintf(_("Password does not match for user \"%s\"."),
245 role);
246 return STATUS_ERROR;
247 }
248 break;
249
250 case PASSWORD_TYPE_MD5:
251 if (!pg_md5_encrypt(client_pass,
252 role,
253 strlen(role),
254 crypt_client_pass))
255 {
256 /*
257 * We do not bother setting logdetail for pg_md5_encrypt
258 * failure: the only possible error is out-of-memory, which is
259 * unlikely, and if it did happen adding a psprintf call would
260 * only make things worse.
261 */
262 return STATUS_ERROR;
263 }
264 if (strcmp(crypt_client_pass, shadow_pass) == 0)
265 return STATUS_OK;
266 else
267 {
268 *logdetail = psprintf(_("Password does not match for user \"%s\"."),
269 role);
270 return STATUS_ERROR;
271 }
272 break;
273
274 case PASSWORD_TYPE_PLAINTEXT:
275
276 /*
277 * We never store passwords in plaintext, so this shouldn't
278 * happen.
279 */
280 break;
281 }
282
283 /*
284 * This shouldn't happen. Plain "password" authentication is possible
285 * with any kind of stored password hash.
286 */
287 *logdetail = psprintf(_("Password of user \"%s\" is in unrecognized format."),
288 role);
289 return STATUS_ERROR;
290 }
291