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