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