Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 8f8b9be

Browse files
committed
Add PQencryptPasswordConn function to libpq, use it in psql and createuser.
The new function supports creating SCRAM verifiers, in addition to md5 hashes. The algorithm is chosen based on password_encryption, by default. This fixes the issue reported by Jeff Janes, that there was previously no way to create a SCRAM verifier with "\password". Michael Paquier and me Discussion: https://www.postgresql.org/message-id/CAMkU%3D1wfBgFPbfAMYZQE78p%3DVhZX7nN86aWkp0QcCp%3D%2BKxZ%3Dbg%40mail.gmail.com
1 parent af2c5aa commit 8f8b9be

File tree

13 files changed

+291
-76
lines changed

13 files changed

+291
-76
lines changed

doc/src/sgml/libpq.sgml

+56-11
Original file line numberDiff line numberDiff line change
@@ -5875,32 +5875,77 @@ void PQconninfoFree(PQconninfoOption *connOptions);
58755875
</listitem>
58765876
</varlistentry>
58775877

5878-
<varlistentry id="libpq-pqencryptpassword">
5878+
<varlistentry id="libpq-pqencryptpasswordconn">
58795879
<term>
5880-
<function>PQencryptPassword</function>
5880+
<function>PQencryptPasswordConn</function>
58815881
<indexterm>
5882-
<primary>PQencryptPassword</primary>
5882+
<primary>PQencryptPasswordConn</primary>
58835883
</indexterm>
58845884
</term>
58855885

58865886
<listitem>
58875887
<para>
58885888
Prepares the encrypted form of a <productname>PostgreSQL</> password.
58895889
<synopsis>
5890-
char * PQencryptPassword(const char *passwd, const char *user);
5890+
char *PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user, const char *algorithm);
58915891
</synopsis>
58925892
This function is intended to be used by client applications that
58935893
wish to send commands like <literal>ALTER USER joe PASSWORD
58945894
'pwd'</>. It is good practice not to send the original cleartext
58955895
password in such a command, because it might be exposed in command
58965896
logs, activity displays, and so on. Instead, use this function to
5897-
convert the password to encrypted form before it is sent. The
5898-
arguments are the cleartext password, and the SQL name of the user
5899-
it is for. The return value is a string allocated by
5900-
<function>malloc</function>, or <symbol>NULL</symbol> if out of
5901-
memory. The caller can assume the string doesn't contain any
5902-
special characters that would require escaping. Use
5903-
<function>PQfreemem</> to free the result when done with it.
5897+
convert the password to encrypted form before it is sent.
5898+
</para>
5899+
5900+
<para>
5901+
The <parameter>passwd</> and <parameter>user</> arguments
5902+
are the cleartext password, and the SQL name of the user it is for.
5903+
<parameter>algorithm</> specifies the encryption algorithm
5904+
to use to encrypt the password. Currently supported algorithms are
5905+
<literal>md5</>, <literal>scram-sha-256</> and <literal>plain</>.
5906+
<literal>scram-sha-256</> was introduced in <productname>PostgreSQL</>
5907+
version 10, and will not work correctly with older server versions. If
5908+
<parameter>algorithm</> is <symbol>NULL</>, this function will query
5909+
the server for the current value of the
5910+
<xref linkend="guc-password-encryption"> setting. That can block, and
5911+
will fail if the current transaction is aborted, or if the connection
5912+
is busy executing another query. If you wish to use the default
5913+
algorithm for the server but want to avoid blocking, query
5914+
<varname>password_encryption</> yourself before calling
5915+
<function>PQencryptPasswordConn</>, and pass that value as the
5916+
<parameter>algorithm</>.
5917+
</para>
5918+
5919+
<para>
5920+
The return value is a string allocated by <function>malloc</>.
5921+
The caller can assume the string doesn't contain any special characters
5922+
that would require escaping. Use <function>PQfreemem</> to free the
5923+
result when done with it. On error, returns <symbol>NULL</>, and
5924+
a suitable message is stored in the connection object.
5925+
</para>
5926+
5927+
</listitem>
5928+
</varlistentry>
5929+
5930+
<varlistentry id="libpq-pqencryptpassword">
5931+
<term>
5932+
<function>PQencryptPassword</function>
5933+
<indexterm>
5934+
<primary>PQencryptPassword</primary>
5935+
</indexterm>
5936+
</term>
5937+
5938+
<listitem>
5939+
<para>
5940+
Prepares the md5-encrypted form of a <productname>PostgreSQL</> password.
5941+
<synopsis>
5942+
char *PQencryptPassword(const char *passwd, const char *user);
5943+
</synopsis>
5944+
<function>PQencryptPassword</> is an older, deprecated version of
5945+
<function>PQencryptPasswodConn</>. The difference is that
5946+
<function>PQencryptPassword</> does not
5947+
require a connection object, and <literal>md5</> is always used as the
5948+
encryption algorithm.
59045949
</para>
59055950
</listitem>
59065951
</varlistentry>

src/backend/libpq/auth-scram.c

+7-44
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ pg_be_scram_init(const char *username, const char *shadow_pass)
207207
*/
208208
char *verifier;
209209

210-
verifier = scram_build_verifier(username, shadow_pass, 0);
210+
verifier = pg_be_scram_build_verifier(shadow_pass);
211211

212212
(void) parse_scram_verifier(verifier, &state->iterations, &state->salt,
213213
state->StoredKey, state->ServerKey);
@@ -387,22 +387,14 @@ pg_be_scram_exchange(void *opaq, char *input, int inputlen,
387387
/*
388388
* Construct a verifier string for SCRAM, stored in pg_authid.rolpassword.
389389
*
390-
* If iterations is 0, default number of iterations is used. The result is
391-
* palloc'd, so caller is responsible for freeing it.
390+
* The result is palloc'd, so caller is responsible for freeing it.
392391
*/
393392
char *
394-
scram_build_verifier(const char *username, const char *password,
395-
int iterations)
393+
pg_be_scram_build_verifier(const char *password)
396394
{
397395
char *prep_password = NULL;
398396
pg_saslprep_rc rc;
399397
char saltbuf[SCRAM_DEFAULT_SALT_LEN];
400-
uint8 salted_password[SCRAM_KEY_LEN];
401-
uint8 keybuf[SCRAM_KEY_LEN];
402-
char *encoded_salt;
403-
char *encoded_storedkey;
404-
char *encoded_serverkey;
405-
int encoded_len;
406398
char *result;
407399

408400
/*
@@ -414,10 +406,7 @@ scram_build_verifier(const char *username, const char *password,
414406
if (rc == SASLPREP_SUCCESS)
415407
password = (const char *) prep_password;
416408

417-
if (iterations <= 0)
418-
iterations = SCRAM_DEFAULT_ITERATIONS;
419-
420-
/* Generate salt, and encode it in base64 */
409+
/* Generate random salt */
421410
if (!pg_backend_random(saltbuf, SCRAM_DEFAULT_SALT_LEN))
422411
{
423412
ereport(LOG,
@@ -426,37 +415,11 @@ scram_build_verifier(const char *username, const char *password,
426415
return NULL;
427416
}
428417

429-
encoded_salt = palloc(pg_b64_enc_len(SCRAM_DEFAULT_SALT_LEN) + 1);
430-
encoded_len = pg_b64_encode(saltbuf, SCRAM_DEFAULT_SALT_LEN, encoded_salt);
431-
encoded_salt[encoded_len] = '\0';
432-
433-
/* Calculate StoredKey, and encode it in base64 */
434-
scram_SaltedPassword(password, saltbuf, SCRAM_DEFAULT_SALT_LEN,
435-
iterations, salted_password);
436-
scram_ClientKey(salted_password, keybuf);
437-
scram_H(keybuf, SCRAM_KEY_LEN, keybuf); /* StoredKey */
438-
439-
encoded_storedkey = palloc(pg_b64_enc_len(SCRAM_KEY_LEN) + 1);
440-
encoded_len = pg_b64_encode((const char *) keybuf, SCRAM_KEY_LEN,
441-
encoded_storedkey);
442-
encoded_storedkey[encoded_len] = '\0';
443-
444-
/* And same for ServerKey */
445-
scram_ServerKey(salted_password, keybuf);
446-
447-
encoded_serverkey = palloc(pg_b64_enc_len(SCRAM_KEY_LEN) + 1);
448-
encoded_len = pg_b64_encode((const char *) keybuf, SCRAM_KEY_LEN,
449-
encoded_serverkey);
450-
encoded_serverkey[encoded_len] = '\0';
451-
452-
result = psprintf("SCRAM-SHA-256$%d:%s$%s:%s", iterations, encoded_salt,
453-
encoded_storedkey, encoded_serverkey);
418+
result = scram_build_verifier(saltbuf, SCRAM_DEFAULT_SALT_LEN,
419+
SCRAM_DEFAULT_ITERATIONS, password);
454420

455421
if (prep_password)
456422
pfree(prep_password);
457-
pfree(encoded_salt);
458-
pfree(encoded_storedkey);
459-
pfree(encoded_serverkey);
460423

461424
return result;
462425
}
@@ -1194,7 +1157,7 @@ scram_MockSalt(const char *username)
11941157
* Generate salt using a SHA256 hash of the username and the cluster's
11951158
* mock authentication nonce. (This works as long as the salt length is
11961159
* not larger the SHA256 digest length. If the salt is smaller, the caller
1197-
* will just ignore the extra data))
1160+
* will just ignore the extra data.)
11981161
*/
11991162
StaticAssertStmt(PG_SHA256_DIGEST_LENGTH >= SCRAM_DEFAULT_SALT_LEN,
12001163
"salt length greater than SHA256 digest length");

src/backend/libpq/crypt.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ encrypt_password(PasswordType target_type, const char *role,
156156
switch (guessed_type)
157157
{
158158
case PASSWORD_TYPE_PLAINTEXT:
159-
return scram_build_verifier(role, password, 0);
159+
return pg_be_scram_build_verifier(password);
160160

161161
case PASSWORD_TYPE_MD5:
162162

src/bin/psql/command.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -1878,11 +1878,11 @@ exec_command_password(PsqlScanState scan_state, bool active_branch)
18781878
else
18791879
user = PQuser(pset.db);
18801880

1881-
encrypted_password = PQencryptPassword(pw1, user);
1881+
encrypted_password = PQencryptPasswordConn(pset.db, pw1, user, NULL);
18821882

18831883
if (!encrypted_password)
18841884
{
1885-
psql_error("Password encryption failed.\n");
1885+
psql_error("%s", PQerrorMessage(pset.db));
18861886
success = false;
18871887
}
18881888
else

src/bin/scripts/createuser.c

+6-3
Original file line numberDiff line numberDiff line change
@@ -274,11 +274,14 @@ main(int argc, char *argv[])
274274
{
275275
char *encrypted_password;
276276

277-
encrypted_password = PQencryptPassword(newpassword,
278-
newuser);
277+
encrypted_password = PQencryptPasswordConn(conn,
278+
newpassword,
279+
newuser,
280+
NULL);
279281
if (!encrypted_password)
280282
{
281-
fprintf(stderr, _("Password encryption failed.\n"));
283+
fprintf(stderr, _("%s: password encryption failed: %s"),
284+
progname, PQerrorMessage(conn));
282285
exit(1);
283286
}
284287
appendStringLiteralConn(&sql, encrypted_password, conn);

src/common/scram-common.c

+64
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <netinet/in.h>
2424
#include <arpa/inet.h>
2525

26+
#include "common/base64.h"
2627
#include "common/scram-common.h"
2728

2829
#define HMAC_IPAD 0x36
@@ -180,3 +181,66 @@ scram_ServerKey(const uint8 *salted_password, uint8 *result)
180181
scram_HMAC_update(&ctx, "Server Key", strlen("Server Key"));
181182
scram_HMAC_final(result, &ctx);
182183
}
184+
185+
186+
/*
187+
* Construct a verifier string for SCRAM, stored in pg_authid.rolpassword.
188+
*
189+
* The password should already have been processed with SASLprep, if necessary!
190+
*
191+
* If iterations is 0, default number of iterations is used. The result is
192+
* palloc'd or malloc'd, so caller is responsible for freeing it.
193+
*/
194+
char *
195+
scram_build_verifier(const char *salt, int saltlen, int iterations,
196+
const char *password)
197+
{
198+
uint8 salted_password[SCRAM_KEY_LEN];
199+
uint8 stored_key[SCRAM_KEY_LEN];
200+
uint8 server_key[SCRAM_KEY_LEN];
201+
char *result;
202+
char *p;
203+
int maxlen;
204+
205+
if (iterations <= 0)
206+
iterations = SCRAM_DEFAULT_ITERATIONS;
207+
208+
/* Calculate StoredKey and ServerKey */
209+
scram_SaltedPassword(password, salt, saltlen, iterations,
210+
salted_password);
211+
scram_ClientKey(salted_password, stored_key);
212+
scram_H(stored_key, SCRAM_KEY_LEN, stored_key);
213+
214+
scram_ServerKey(salted_password, server_key);
215+
216+
/*
217+
* The format is:
218+
* SCRAM-SHA-256$<iteration count>:<salt>$<StoredKey>:<ServerKey>
219+
*/
220+
maxlen = strlen("SCRAM-SHA-256") + 1
221+
+ 10 + 1 /* iteration count */
222+
+ pg_b64_enc_len(saltlen) + 1 /* Base64-encoded salt */
223+
+ pg_b64_enc_len(SCRAM_KEY_LEN) + 1 /* Base64-encoded StoredKey */
224+
+ pg_b64_enc_len(SCRAM_KEY_LEN) + 1; /* Base64-encoded ServerKey */
225+
226+
#ifdef FRONTEND
227+
result = malloc(maxlen);
228+
if (!result)
229+
return NULL;
230+
#else
231+
result = palloc(maxlen);
232+
#endif
233+
234+
p = result + sprintf(result, "SCRAM-SHA-256$%d:", iterations);
235+
236+
p += pg_b64_encode(salt, saltlen, p);
237+
*(p++) = '$';
238+
p += pg_b64_encode((char *) stored_key, SCRAM_KEY_LEN, p);
239+
*(p++) = ':';
240+
p += pg_b64_encode((char *) server_key, SCRAM_KEY_LEN, p);
241+
*(p++) = '\0';
242+
243+
Assert(p - result <= maxlen);
244+
245+
return result;
246+
}

src/include/common/scram-common.h

+3
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,7 @@ extern void scram_H(const uint8 *str, int len, uint8 *result);
5353
extern void scram_ClientKey(const uint8 *salted_password, uint8 *result);
5454
extern void scram_ServerKey(const uint8 *salted_password, uint8 *result);
5555

56+
extern char *scram_build_verifier(const char *salt, int saltlen, int iterations,
57+
const char *password);
58+
5659
#endif /* SCRAM_COMMON_H */

src/include/libpq/scram.h

+1-3
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,7 @@ extern int pg_be_scram_exchange(void *opaq, char *input, int inputlen,
2727
char **output, int *outputlen, char **logdetail);
2828

2929
/* Routines to handle and check SCRAM-SHA-256 verifier */
30-
extern char *scram_build_verifier(const char *username,
31-
const char *password,
32-
int iterations);
30+
extern char *pg_be_scram_build_verifier(const char *password);
3331
extern bool is_scram_verifier(const char *verifier);
3432
extern bool scram_verify_plain_password(const char *username,
3533
const char *password, const char *verifier);

src/interfaces/libpq/exports.txt

+1
Original file line numberDiff line numberDiff line change
@@ -171,3 +171,4 @@ PQsslAttributeNames 168
171171
PQsslAttribute 169
172172
PQsetErrorContextVisibility 170
173173
PQresultVerboseErrorMessage 171
174+
PQencryptPasswordConn 172

src/interfaces/libpq/fe-auth-scram.c

+35
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,41 @@ verify_server_signature(fe_scram_state *state)
614614
return true;
615615
}
616616

617+
/*
618+
* Build a new SCRAM verifier.
619+
*/
620+
char *
621+
pg_fe_scram_build_verifier(const char *password)
622+
{
623+
char *prep_password = NULL;
624+
pg_saslprep_rc rc;
625+
char saltbuf[SCRAM_DEFAULT_SALT_LEN];
626+
char *result;
627+
628+
/*
629+
* Normalize the password with SASLprep. If that doesn't work, because
630+
* the password isn't valid UTF-8 or contains prohibited characters, just
631+
* proceed with the original password. (See comments at top of file.)
632+
*/
633+
rc = pg_saslprep(password, &prep_password);
634+
if (rc == SASLPREP_OOM)
635+
return NULL;
636+
if (rc == SASLPREP_SUCCESS)
637+
password = (const char *) prep_password;
638+
639+
/* Generate a random salt */
640+
if (!pg_frontend_random(saltbuf, SCRAM_DEFAULT_SALT_LEN))
641+
return NULL;
642+
643+
result = scram_build_verifier(saltbuf, SCRAM_DEFAULT_SALT_LEN,
644+
SCRAM_DEFAULT_ITERATIONS, password);
645+
646+
if (prep_password)
647+
free(prep_password);
648+
649+
return result;
650+
}
651+
617652
/*
618653
* Random number generator.
619654
*/

0 commit comments

Comments
 (0)