1 /*-------------------------------------------------------------------------
2  *
3  * crypt.c
4  *	  Functions for dealing with encrypted passwords stored in
5  *	  pg_authid.rolpassword.
6  *
7  * Portions Copyright (c) 1996-2017, 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 #ifdef HAVE_CRYPT_H
18 #include <crypt.h>
19 #endif
20 
21 #include "catalog/pg_authid.h"
22 #include "common/md5.h"
23 #include "common/scram-common.h"
24 #include "libpq/crypt.h"
25 #include "libpq/scram.h"
26 #include "miscadmin.h"
27 #include "utils/builtins.h"
28 #include "utils/syscache.h"
29 #include "utils/timestamp.h"
30 
31 
32 /*
33  * Fetch stored password for a user, for authentication.
34  *
35  * On error, returns NULL, and stores a palloc'd string describing the reason,
36  * for the postmaster log, in *logdetail.  The error reason should *not* be
37  * sent to the client, to avoid giving away user information!
38  */
39 char *
get_role_password(const char * role,char ** logdetail)40 get_role_password(const char *role, char **logdetail)
41 {
42 	TimestampTz vuntil = 0;
43 	HeapTuple	roleTup;
44 	Datum		datum;
45 	bool		isnull;
46 	char	   *shadow_pass;
47 
48 	/* Get role info from pg_authid */
49 	roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
50 	if (!HeapTupleIsValid(roleTup))
51 	{
52 		*logdetail = psprintf(_("Role \"%s\" does not exist."),
53 							  role);
54 		return NULL;			/* no such user */
55 	}
56 
57 	datum = SysCacheGetAttr(AUTHNAME, roleTup,
58 							Anum_pg_authid_rolpassword, &isnull);
59 	if (isnull)
60 	{
61 		ReleaseSysCache(roleTup);
62 		*logdetail = psprintf(_("User \"%s\" has no password assigned."),
63 							  role);
64 		return NULL;			/* user has no password */
65 	}
66 	shadow_pass = TextDatumGetCString(datum);
67 
68 	datum = SysCacheGetAttr(AUTHNAME, roleTup,
69 							Anum_pg_authid_rolvaliduntil, &isnull);
70 	if (!isnull)
71 		vuntil = DatumGetTimestampTz(datum);
72 
73 	ReleaseSysCache(roleTup);
74 
75 	/*
76 	 * Password OK, but check to be sure we are not past rolvaliduntil
77 	 */
78 	if (!isnull && vuntil < GetCurrentTimestamp())
79 	{
80 		*logdetail = psprintf(_("User \"%s\" has an expired password."),
81 							  role);
82 		return NULL;
83 	}
84 
85 	return shadow_pass;
86 }
87 
88 /*
89  * What kind of a password verifier is 'shadow_pass'?
90  */
91 PasswordType
get_password_type(const char * shadow_pass)92 get_password_type(const char *shadow_pass)
93 {
94 	char	   *encoded_salt;
95 	int			iterations;
96 	uint8		stored_key[SCRAM_KEY_LEN];
97 	uint8		server_key[SCRAM_KEY_LEN];
98 
99 	if (strncmp(shadow_pass, "md5", 3) == 0 &&
100 		strlen(shadow_pass) == MD5_PASSWD_LEN &&
101 		strspn(shadow_pass + 3, MD5_PASSWD_CHARSET) == MD5_PASSWD_LEN - 3)
102 		return PASSWORD_TYPE_MD5;
103 	if (parse_scram_verifier(shadow_pass, &iterations, &encoded_salt,
104 							 stored_key, server_key))
105 		return PASSWORD_TYPE_SCRAM_SHA_256;
106 	return PASSWORD_TYPE_PLAINTEXT;
107 }
108 
109 /*
110  * Given a user-supplied password, convert it into a verifier of
111  * 'target_type' kind.
112  *
113  * If the password is already in encrypted form, we cannot reverse the
114  * hash, so it is stored as it is regardless of the requested type.
115  */
116 char *
encrypt_password(PasswordType target_type,const char * role,const char * password)117 encrypt_password(PasswordType target_type, const char *role,
118 				 const char *password)
119 {
120 	PasswordType guessed_type = get_password_type(password);
121 	char	   *encrypted_password;
122 
123 	if (guessed_type != PASSWORD_TYPE_PLAINTEXT)
124 	{
125 		/*
126 		 * Cannot convert an already-encrypted password from one format to
127 		 * another, so return it as it is.
128 		 */
129 		return pstrdup(password);
130 	}
131 
132 	switch (target_type)
133 	{
134 		case PASSWORD_TYPE_MD5:
135 			encrypted_password = palloc(MD5_PASSWD_LEN + 1);
136 
137 			if (!pg_md5_encrypt(password, role, strlen(role),
138 								encrypted_password))
139 				elog(ERROR, "password encryption failed");
140 			return encrypted_password;
141 
142 		case PASSWORD_TYPE_SCRAM_SHA_256:
143 			return pg_be_scram_build_verifier(password);
144 
145 		case PASSWORD_TYPE_PLAINTEXT:
146 			elog(ERROR, "cannot encrypt password with 'plaintext'");
147 	}
148 
149 	/*
150 	 * This shouldn't happen, because the above switch statements should
151 	 * handle every combination of source and target password types.
152 	 */
153 	elog(ERROR, "cannot encrypt password to requested type");
154 	return NULL;				/* keep compiler quiet */
155 }
156 
157 /*
158  * Check MD5 authentication response, and return STATUS_OK or STATUS_ERROR.
159  *
160  * 'shadow_pass' is the user's correct password or password hash, as stored
161  * in pg_authid.rolpassword.
162  * 'client_pass' is the response given by the remote user to the MD5 challenge.
163  * 'md5_salt' is the salt used in the MD5 authentication challenge.
164  *
165  * In the error case, optionally store a palloc'd string at *logdetail
166  * that will be sent to the postmaster log (but not the client).
167  */
168 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)169 md5_crypt_verify(const char *role, const char *shadow_pass,
170 				 const char *client_pass,
171 				 const char *md5_salt, int md5_salt_len,
172 				 char **logdetail)
173 {
174 	int			retval;
175 	char		crypt_pwd[MD5_PASSWD_LEN + 1];
176 
177 	Assert(md5_salt_len > 0);
178 
179 	if (get_password_type(shadow_pass) != PASSWORD_TYPE_MD5)
180 	{
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;
185 	}
186 
187 	/*
188 	 * Compute the correct answer for the MD5 challenge.
189 	 *
190 	 * We do not bother setting logdetail for any pg_md5_encrypt failure
191 	 * below: the only possible error is out-of-memory, which is unlikely, and
192 	 * if it did happen adding a psprintf call would only make things worse.
193 	 */
194 	/* stored password already encrypted, only do salt */
195 	if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
196 						md5_salt, md5_salt_len,
197 						crypt_pwd))
198 	{
199 		return STATUS_ERROR;
200 	}
201 
202 	if (strcmp(client_pass, crypt_pwd) == 0)
203 		retval = STATUS_OK;
204 	else
205 	{
206 		*logdetail = psprintf(_("Password does not match for user \"%s\"."),
207 							  role);
208 		retval = STATUS_ERROR;
209 	}
210 
211 	return retval;
212 }
213 
214 /*
215  * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
216  *
217  * 'shadow_pass' is the user's correct password hash, as stored in
218  * pg_authid.rolpassword.
219  * 'client_pass' is the password given by the remote user.
220  *
221  * In the error case, optionally store a palloc'd string at *logdetail
222  * that will be sent to the postmaster log (but not the client).
223  */
224 int
plain_crypt_verify(const char * role,const char * shadow_pass,const char * client_pass,char ** logdetail)225 plain_crypt_verify(const char *role, const char *shadow_pass,
226 				   const char *client_pass,
227 				   char **logdetail)
228 {
229 	char		crypt_client_pass[MD5_PASSWD_LEN + 1];
230 
231 	/*
232 	 * Client sent password in plaintext.  If we have an MD5 hash stored, hash
233 	 * the password the client sent, and compare the hashes.  Otherwise
234 	 * compare the plaintext passwords directly.
235 	 */
236 	switch (get_password_type(shadow_pass))
237 	{
238 		case PASSWORD_TYPE_SCRAM_SHA_256:
239 			if (scram_verify_plain_password(role,
240 											client_pass,
241 											shadow_pass))
242 			{
243 				return STATUS_OK;
244 			}
245 			else
246 			{
247 				*logdetail = psprintf(_("Password does not match for user \"%s\"."),
248 									  role);
249 				return STATUS_ERROR;
250 			}
251 			break;
252 
253 		case PASSWORD_TYPE_MD5:
254 			if (!pg_md5_encrypt(client_pass,
255 								role,
256 								strlen(role),
257 								crypt_client_pass))
258 			{
259 				/*
260 				 * We do not bother setting logdetail for pg_md5_encrypt
261 				 * failure: the only possible error is out-of-memory, which is
262 				 * unlikely, and if it did happen adding a psprintf call would
263 				 * only make things worse.
264 				 */
265 				return STATUS_ERROR;
266 			}
267 			if (strcmp(crypt_client_pass, shadow_pass) == 0)
268 				return STATUS_OK;
269 			else
270 			{
271 				*logdetail = psprintf(_("Password does not match for user \"%s\"."),
272 									  role);
273 				return STATUS_ERROR;
274 			}
275 			break;
276 
277 		case PASSWORD_TYPE_PLAINTEXT:
278 
279 			/*
280 			 * We never store passwords in plaintext, so this shouldn't
281 			 * happen.
282 			 */
283 			break;
284 	}
285 
286 	/*
287 	 * This shouldn't happen.  Plain "password" authentication is possible
288 	 * with any kind of stored password hash.
289 	 */
290 	*logdetail = psprintf(_("Password of user \"%s\" is in unrecognized format."),
291 						  role);
292 	return STATUS_ERROR;
293 }
294