pg.ddx.io  pgsql-hackers@postgresql.org mailing list archive  
help / color / mirror / Atom feed
From: Heikki Linnakangas <hlinnaka@iki.fi>
To: Michael Paquier <michael.paquier@gmail.com>
Cc: Noah Misch <noah@leadboat.com>
Cc: Robert Haas <robertmhaas@gmail.com>
Cc: Tom Lane <tgl@sss.pgh.pa.us>
Cc: Jeff Janes <jeff.janes@gmail.com>
Cc: Joe Conway <mail@joeconway.com>
Cc: pgsql-hackers <pgsql-hackers@postgresql.org>
Subject: Re: scram and \password
Date: Wed, 26 Apr 2017 13:22:36 +0300
Message-ID: <57f8c243-2362-e7b8-4c56-e752ba6edf6c@iki.fi> (raw)
In-Reply-To: <c32a2be9-f25d-30e8-6d48-64e1801a910f@iki.fi>
References: <a8b0d5cb-35d4-d686-3fae-f743659ae346@iki.fi>
	<CAB7nPqSjSmYK7H3S8A98giR6Jb=jKKTOMMruK1Kxw5Lfxc+Baw@mail.gmail.com>
	<fe2c096e-7b06-3e8e-0804-109def6b5d98@iki.fi>
	<CAB7nPqQ3QiPMgGv5OCz1wsocx7PHdZg5ursQOpyOb96AZuvmGA@mail.gmail.com>
	<5478ab75-404f-9490-9355-dc1365ce3d7f@iki.fi>
	<CAB7nPqTLzbHp5XmREoqiL31uF8VRz4jM1VhDRMZLOJuVg8gZoQ@mail.gmail.com>
	<dead641a-1fc8-0fd7-89d6-b818ac61b31a@iki.fi>
	<CAB7nPqQmJ5t=rd67paE7DWxuSi0C4X=5_pnzZchH7qFkBHy==A@mail.gmail.com>
	<CA+TgmoYk+xfnvYC4xtQD4qFdu7xKn6bRLC-baH5MDdtyCSxg6w@mail.gmail.com>
	<f56a2116-a466-ad6c-03d5-413294bfca9f@iki.fi>
	<20170410035323.GA2846583@tornado.leadboat.com>
	<CAB7nPqSbsCBCxy8-DtwzRxYgTnbGUtY4uFEkLQhG=R=uo=g8Fw@mail.gmail.com>
	<66e776c9-e02c-3683-28df-339f7f64ea66@iki.fi>
	<CAB7nPqSWP9J9PzeC7=-WjuuaL57_d+EawmzJNVsM+m7Y4gRRBA@mail.gmail.com>
	<c32a2be9-f25d-30e8-6d48-64e1801a910f@iki.fi>
List-Unsubscribe: <mailto:majordomo@postgresql.org?body=unsub%20pgsql-hackers>

On 04/25/2017 06:26 PM, Heikki Linnakangas wrote:
> Thoughts? Unless someone has better ideas or objections, I'll go
> implement that.

This is what I came up with in the end. Some highlights and differences 
vs the plan I posted earlier:

* If algorithm is not given explicitly, PQencryptPasswordConn() queries 
"SHOW password_encryption", and uses that. That is documented, and it is 
also documented that it will *not* issue queries, and hence will not 
block, if the algorithm is given explicitly. That's an important 
property for some applications. If you want the default behavior without 
blocking, query "SHOW password_encryption" yourself, and pass the result 
as the 'algorithm'.

* In the previous draft, I envisioned an algorithm-specific 'options' 
argument. I left it out, on second thoughts (more on that below).

* The old PQencryptPassword() function is unchanged, and always uses 
md5. Per public opinion.


We talked about the alternative where PQencryptPasswordConn() would not 
look at password_encryption, but would always use the strongest possible 
algorithm supported by the server. That would avoid querying the server. 
But then I started thinking how this would work, if we make the number 
of iterations in the SCRAM verifier configurable in the future. The 
client would not know the desired number of iterations based only on the 
server version, it would need to query the server, and we would be back 
to square one. We could add an "options" argument to 
PQencryptPasswordConn() that the application could use to pass that 
information, but documenting how to fetch that information, if you don't 
want PQencryptPasswordConn() to block, gets tedious, and puts a lot of 
burden to applications. That is why I left out the "options" argument, 
after all.

I'm now thinking that if we add password hashing options like the 
iteration count in the future, they will be tacked on to 
password_encryption. For example, "password_encryption='scram-sha-256, 
iterations=10000". That way, "password_encryption" will always contain 
enough information to construct password verifiers.

What will happen to existing applications using PQencryptPasswordConn() 
if a new password_encryption algorithm (or options) is added in the 
future? With this scheme, the application doesn't need to know what 
algorithms exist. An application can pass algorithm=NULL, to use the 
server default, or do "show password_encryption" and pass that, for the 
non-blocking behavior. If you use an older libpq against a new server, 
so that libpq doesn't know about the algorithm used by the server, you 
get an error.

For reviewer convenience, I put up the patched docs at 
http://hlinnaka.iki.fi/temp/scram-wip-docs/libpq-misc.html#libpq-pqencryptpasswordconn.

Thoughts? Am I missing anything?

As an alternative, I considered making password_encryption GUC_REPORT, 
so that libpq would always know it without having to issue a query. But 
it feels wrong to send that option to the client on every connection, 
when it's only needed in the rare case that you use 
PQencryptPasswordConn(). And if we added new settings like the iteration 
count in the future, those would need to be GUC_REPORT too.

- Heikki

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments: download all (3 files, .tar.gz)

0001-Move-scram_build_verifier-to-src-common.patch (application/x-download, 9.6K) download From a656e0ab2aaed016ed5244c7bb79752acf5b71b7 Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas <heikki.linnakangas@iki.fi> Date: Wed, 26 Apr 2017 12:53:49 +0300 Subject: [PATCH 1/3] Move scram_build_verifier to src/common. This is in preparation for the next patch to add support for building SCRAM verifiers to libpq. Michael Paquier and me Discussion: https://www.postgresql.org/message-id/CAMkU%3D1wfBgFPbfAMYZQE78p%3DVhZX7nN86aWkp0QcCp%3D%2BKxZ%3Dbg%40mail.gmail.com --- src/backend/libpq/auth-scram.c | 62 ++++++++------------------------------ src/backend/libpq/crypt.c | 2 +- src/common/scram-common.c | 63 +++++++++++++++++++++++++++++++++++++++ src/include/common/scram-common.h | 7 +++-- src/include/libpq/scram.h | 4 +-- 5 files changed, 83 insertions(+), 55 deletions(-) diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c index 16bea446e3..56b8935847 100644 --- a/src/backend/libpq/auth-scram.c +++ b/src/backend/libpq/auth-scram.c @@ -207,7 +207,7 @@ pg_be_scram_init(const char *username, const char *shadow_pass) */ char *verifier; - verifier = scram_build_verifier(username, shadow_pass, 0); + verifier = pg_be_scram_build_verifier(shadow_pass); (void) parse_scram_verifier(verifier, &state->iterations, &state->salt, state->StoredKey, state->ServerKey); @@ -387,21 +387,14 @@ pg_be_scram_exchange(void *opaq, char *input, int inputlen, /* * Construct a verifier string for SCRAM, stored in pg_authid.rolpassword. * - * If iterations is 0, default number of iterations is used. The result is - * palloc'd, so caller is responsible for freeing it. + * The result is palloc'd, so caller is responsible for freeing it. */ char * -scram_build_verifier(const char *username, const char *password, - int iterations) +pg_be_scram_build_verifier(const char *password) { char *prep_password = NULL; pg_saslprep_rc rc; - char saltbuf[SCRAM_SALT_LEN]; - uint8 keybuf[SCRAM_KEY_LEN]; - char *encoded_salt; - char *encoded_storedkey; - char *encoded_serverkey; - int encoded_len; + char saltbuf[SCRAM_DEFAULT_SALT_LEN]; char *result; /* @@ -413,11 +406,8 @@ scram_build_verifier(const char *username, const char *password, if (rc == SASLPREP_SUCCESS) password = (const char *) prep_password; - if (iterations <= 0) - iterations = SCRAM_ITERATIONS_DEFAULT; - - /* Generate salt, and encode it in base64 */ - if (!pg_backend_random(saltbuf, SCRAM_SALT_LEN)) + /* Generate salt */ + if (!pg_backend_random(saltbuf, SCRAM_DEFAULT_SALT_LEN)) { ereport(LOG, (errcode(ERRCODE_INTERNAL_ERROR), @@ -425,37 +415,11 @@ scram_build_verifier(const char *username, const char *password, return NULL; } - encoded_salt = palloc(pg_b64_enc_len(SCRAM_SALT_LEN) + 1); - encoded_len = pg_b64_encode(saltbuf, SCRAM_SALT_LEN, encoded_salt); - encoded_salt[encoded_len] = '\0'; - - /* Calculate StoredKey, and encode it in base64 */ - scram_ClientOrServerKey(password, saltbuf, SCRAM_SALT_LEN, - iterations, SCRAM_CLIENT_KEY_NAME, keybuf); - scram_H(keybuf, SCRAM_KEY_LEN, keybuf); /* StoredKey */ - - encoded_storedkey = palloc(pg_b64_enc_len(SCRAM_KEY_LEN) + 1); - encoded_len = pg_b64_encode((const char *) keybuf, SCRAM_KEY_LEN, - encoded_storedkey); - encoded_storedkey[encoded_len] = '\0'; - - /* And same for ServerKey */ - scram_ClientOrServerKey(password, saltbuf, SCRAM_SALT_LEN, iterations, - SCRAM_SERVER_KEY_NAME, keybuf); - - encoded_serverkey = palloc(pg_b64_enc_len(SCRAM_KEY_LEN) + 1); - encoded_len = pg_b64_encode((const char *) keybuf, SCRAM_KEY_LEN, - encoded_serverkey); - encoded_serverkey[encoded_len] = '\0'; - - result = psprintf("SCRAM-SHA-256$%d:%s$%s:%s", iterations, encoded_salt, - encoded_storedkey, encoded_serverkey); + result = scram_build_verifier(saltbuf, SCRAM_DEFAULT_SALT_LEN, + SCRAM_DEFAULT_ITERATIONS, password); if (prep_password) pfree(prep_password); - pfree(encoded_salt); - pfree(encoded_storedkey); - pfree(encoded_serverkey); return result; } @@ -630,12 +594,12 @@ mock_scram_verifier(const char *username, int *iterations, char **salt, /* Generate deterministic salt */ raw_salt = scram_MockSalt(username); - encoded_salt = (char *) palloc(pg_b64_enc_len(SCRAM_SALT_LEN) + 1); - encoded_len = pg_b64_encode(raw_salt, SCRAM_SALT_LEN, encoded_salt); + encoded_salt = (char *) palloc(pg_b64_enc_len(SCRAM_DEFAULT_SALT_LEN) + 1); + encoded_len = pg_b64_encode(raw_salt, SCRAM_DEFAULT_SALT_LEN, encoded_salt); encoded_salt[encoded_len] = '\0'; *salt = encoded_salt; - *iterations = SCRAM_ITERATIONS_DEFAULT; + *iterations = SCRAM_DEFAULT_ITERATIONS; /* StoredKey and ServerKey are not used in a doomed authentication */ memset(stored_key, 0, SCRAM_KEY_LEN); @@ -1192,9 +1156,9 @@ scram_MockSalt(const char *username) * Generate salt using a SHA256 hash of the username and the cluster's * mock authentication nonce. (This works as long as the salt length is * not larger the SHA256 digest length. If the salt is smaller, the caller - * will just ignore the extra data)) + * will just ignore the extra data.) */ - StaticAssertStmt(PG_SHA256_DIGEST_LENGTH >= SCRAM_SALT_LEN, + StaticAssertStmt(PG_SHA256_DIGEST_LENGTH >= SCRAM_DEFAULT_SALT_LEN, "salt length greater than SHA256 digest length"); pg_sha256_init(&ctx); diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c index d0030f2b6d..9fe79b4894 100644 --- a/src/backend/libpq/crypt.c +++ b/src/backend/libpq/crypt.c @@ -156,7 +156,7 @@ encrypt_password(PasswordType target_type, const char *role, switch (guessed_type) { case PASSWORD_TYPE_PLAINTEXT: - return scram_build_verifier(role, password, 0); + return pg_be_scram_build_verifier(password); case PASSWORD_TYPE_MD5: diff --git a/src/common/scram-common.c b/src/common/scram-common.c index df9f0eaa90..38a23de73e 100644 --- a/src/common/scram-common.c +++ b/src/common/scram-common.c @@ -23,6 +23,7 @@ #include <netinet/in.h> #include <arpa/inet.h> +#include "common/base64.h" #include "common/scram-common.h" #define HMAC_IPAD 0x36 @@ -165,3 +166,65 @@ scram_ClientOrServerKey(const char *password, scram_HMAC_update(&ctx, keystr, strlen(keystr)); scram_HMAC_final(result, &ctx); } + + +/* + * Construct a verifier string for SCRAM, stored in pg_authid.rolpassword. + * + * The password should already have been processed with SASLprep, if necessary! + * + * If iterations is 0, default number of iterations is used. The result is + * palloc'd or malloc'd, so caller is responsible for freeing it. + */ +char * +scram_build_verifier(const char *salt, int saltlen, int iterations, + const char *password) +{ + uint8 storedkeybuf[SCRAM_KEY_LEN]; + uint8 serverkeybuf[SCRAM_KEY_LEN]; + char *result; + char *p; + int maxlen; + + if (iterations <= 0) + iterations = SCRAM_DEFAULT_ITERATIONS; + + /* Calculate StoredKey and ServerKey */ + scram_ClientOrServerKey(password, salt, saltlen, + iterations, SCRAM_CLIENT_KEY_NAME, storedkeybuf); + scram_H(storedkeybuf, SCRAM_KEY_LEN, storedkeybuf); + + scram_ClientOrServerKey(password, salt, saltlen, iterations, + SCRAM_SERVER_KEY_NAME, serverkeybuf); + + /* + * The format is: + * SCRAM-SHA-256$<iteration count>:<salt>$<StoredKey>:<ServerKey> + */ + maxlen = strlen("SCRAM-SHA-256") + 1 + + 10 + 1 /* iteration count */ + + pg_b64_enc_len(saltlen) + 1 /* Base64-encoded salt */ + + pg_b64_enc_len(SCRAM_KEY_LEN) + 1 /* Base64-encoded StoredKey */ + + pg_b64_enc_len(SCRAM_KEY_LEN) + 1; /* Base64-encoded ServerKey */ + +#ifdef FRONTEND + result = malloc(maxlen); + if (!result) + return NULL; +#else + result = palloc(maxlen); +#endif + + p = result + sprintf(result, "SCRAM-SHA-256$%d:", iterations); + + p += pg_b64_encode(salt, saltlen, p); + *(p++) = '$'; + p += pg_b64_encode((char *) storedkeybuf, SCRAM_KEY_LEN, p); + *(p++) = ':'; + p += pg_b64_encode((char *) serverkeybuf, SCRAM_KEY_LEN, p); + *(p++) = '\0'; + + Assert(p - result <= maxlen); + + return result; +} diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h index 6740069eee..cc69aafade 100644 --- a/src/include/common/scram-common.h +++ b/src/include/common/scram-common.h @@ -29,10 +29,10 @@ #define SCRAM_RAW_NONCE_LEN 10 /* length of salt when generating new verifiers */ -#define SCRAM_SALT_LEN 10 +#define SCRAM_DEFAULT_SALT_LEN 10 /* default number of iterations when generating verifier */ -#define SCRAM_ITERATIONS_DEFAULT 4096 +#define SCRAM_DEFAULT_ITERATIONS 4096 /* Base name of keys used for proof generation */ #define SCRAM_SERVER_KEY_NAME "Server Key" @@ -56,4 +56,7 @@ extern void scram_ClientOrServerKey(const char *password, const char *salt, int saltlen, int iterations, const char *keystr, uint8 *result); +extern char *scram_build_verifier(const char *salt, int saltlen, int iterations, + const char *password); + #endif /* SCRAM_COMMON_H */ diff --git a/src/include/libpq/scram.h b/src/include/libpq/scram.h index e373f0c07e..060b8af69e 100644 --- a/src/include/libpq/scram.h +++ b/src/include/libpq/scram.h @@ -27,9 +27,7 @@ extern int pg_be_scram_exchange(void *opaq, char *input, int inputlen, char **output, int *outputlen, char **logdetail); /* Routines to handle and check SCRAM-SHA-256 verifier */ -extern char *scram_build_verifier(const char *username, - const char *password, - int iterations); +extern char *pg_be_scram_build_verifier(const char *password); extern bool is_scram_verifier(const char *verifier); extern bool scram_verify_plain_password(const char *username, const char *password, const char *verifier); -- 2.11.0
0002-Add-PQencryptPasswordConn-function-to-libpq.patch (application/x-download, 11.8K) download From eea84c48129a613e6385facb76d5a19978906b7a Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas <heikki.linnakangas@iki.fi> Date: Wed, 26 Apr 2017 12:55:35 +0300 Subject: [PATCH 2/3] Add PQencryptPasswordConn function to libpq. The new function supports creating SCRAM verifiers, in addition to md5 hashes. The algorithm is chosen based on password_encryption, by default. Michael Paquier and me Discussion: https://www.postgresql.org/message-id/CAMkU%3D1wfBgFPbfAMYZQE78p%3DVhZX7nN86aWkp0QcCp%3D%2BKxZ%3Dbg%40mail.gmail.com --- doc/src/sgml/libpq.sgml | 66 +++++++++++++++--- src/interfaces/libpq/exports.txt | 1 + src/interfaces/libpq/fe-auth-scram.c | 32 +++++++++ src/interfaces/libpq/fe-auth.c | 125 +++++++++++++++++++++++++++++++---- src/interfaces/libpq/fe-auth.h | 1 + src/interfaces/libpq/libpq-fe.h | 1 + 6 files changed, 203 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 4bc5bf3192..2228386eea 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -5875,11 +5875,11 @@ void PQconninfoFree(PQconninfoOption *connOptions); </listitem> </varlistentry> - <varlistentry id="libpq-pqencryptpassword"> + <varlistentry id="libpq-pqencryptpasswordconn"> <term> - <function>PQencryptPassword</function> + <function>PQencryptPasswordConn</function> <indexterm> - <primary>PQencryptPassword</primary> + <primary>PQencryptPasswordConn</primary> </indexterm> </term> @@ -5887,20 +5887,64 @@ void PQconninfoFree(PQconninfoOption *connOptions); <para> Prepares the encrypted form of a <productname>PostgreSQL</> password. <synopsis> -char * PQencryptPassword(const char *passwd, const char *user); +char *PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user, const char *algorithm); </synopsis> This function is intended to be used by client applications that wish to send commands like <literal>ALTER USER joe PASSWORD 'pwd'</>. It is good practice not to send the original cleartext password in such a command, because it might be exposed in command logs, activity displays, and so on. Instead, use this function to - convert the password to encrypted form before it is sent. The - arguments are the cleartext password, and the SQL name of the user - it is for. The return value is a string allocated by - <function>malloc</function>, or <symbol>NULL</symbol> if out of - memory. The caller can assume the string doesn't contain any - special characters that would require escaping. Use - <function>PQfreemem</> to free the result when done with it. + convert the password to encrypted form before it is sent. + </para> + + <para> + The <parameter>passwd</> and <parameter>user</> arguments + are the cleartext password, and the SQL name of the user it is for. + <parameter>algorithm</> specifies the encryption algorithm + to use to encrypt the password. Currently supported algorithms are + <literal>md5</> and <literal>scram-sha-256</>. <literal>scram-sha-256</> + was introduced in <productname>PostgreSQL</> version 10, and will + not work correctly with older server versions. If <parameter>algorithm</> + is <symbol>NULL</>, this function will query the server for the current + value of the <xref linkend="guc-password-encryption"> setting. That can + block, and will fail if the current transaction is aborted, or if the + connection is busy executing another query. If you wish to use the + default algorithm for the server but want to avoid blocking, query + <varname>password_encryption</> yourself before calling + <function>PQencryptPasswordConn</>, and pass that value as the + <parameter>algorithm</>. + </para> + + <para> + The return value is a string allocated by <function>malloc</>. + The caller can assume the string doesn't contain any special characters + that would require escaping. Use <function>PQfreemem</> to free the + result when done with it. On error, returns <symbol>NULL</>, and + a suitable message is stored in the connection object. + </para> + + </listitem> + </varlistentry> + + <varlistentry id="libpq-pqencryptpassword"> + <term> + <function>PQencryptPassword</function> + <indexterm> + <primary>PQencryptPassword</primary> + </indexterm> + </term> + + <listitem> + <para> + Prepares the md5-encrypted form of a <productname>PostgreSQL</> password. + <synopsis> +char *PQencryptPassword(const char *passwd, const char *user); + </synopsis> + <function>PQencryptPassword</> is an older, deprecated version of + <function>PQencryptPasswodConn</>. The difference is that + <function>PQencryptPassword</> does not + require a connection object, and <literal>md5</> is always used as the + encryption algorithm. </para> </listitem> </varlistentry> diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt index 21dd772ca9..d6a38d0df8 100644 --- a/src/interfaces/libpq/exports.txt +++ b/src/interfaces/libpq/exports.txt @@ -171,3 +171,4 @@ PQsslAttributeNames 168 PQsslAttribute 169 PQsetErrorContextVisibility 170 PQresultVerboseErrorMessage 171 +PQencryptPasswordConn 172 diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c index c56e91e0e0..6910c1e6db 100644 --- a/src/interfaces/libpq/fe-auth-scram.c +++ b/src/interfaces/libpq/fe-auth-scram.c @@ -610,6 +610,38 @@ verify_server_proof(fe_scram_state *state) return true; } +char * +pg_fe_scram_build_verifier(const char *password) +{ + char *prep_password = NULL; + pg_saslprep_rc rc; + char saltbuf[SCRAM_DEFAULT_SALT_LEN]; + char *result; + + /* + * Normalize the password with SASLprep. If that doesn't work, because + * the password isn't valid UTF-8 or contains prohibited characters, just + * proceed with the original password. (See comments at top of file.) + */ + rc = pg_saslprep(password, &prep_password); + if (rc == SASLPREP_OOM) + return NULL; + if (rc == SASLPREP_SUCCESS) + password = (const char *) prep_password; + + /* Generate salt */ + if (!pg_frontend_random(saltbuf, SCRAM_DEFAULT_SALT_LEN)) + return NULL; + + result = scram_build_verifier(saltbuf, SCRAM_DEFAULT_SALT_LEN, + SCRAM_DEFAULT_ITERATIONS, password); + + if (prep_password) + free(prep_password); + + return result; +} + /* * Random number generator. */ diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c index d81ee4f944..517854a305 100644 --- a/src/interfaces/libpq/fe-auth.c +++ b/src/interfaces/libpq/fe-auth.c @@ -1077,7 +1077,33 @@ pg_fe_getauthname(PQExpBuffer errorMessage) /* - * PQencryptPassword -- exported routine to encrypt a password + * PQencryptPassword -- exported routine to encrypt a password with MD5 + * + * This function is equivalent to calling PQencryptPasswordConn with + * "md5" as the encryption method, except that this doesn't require + * a connection object. This function is deprecated, use + * PQencryptPasswordConn instead. + */ +char * +PQencryptPassword(const char *passwd, const char *user) +{ + char *crypt_pwd; + + crypt_pwd = malloc(MD5_PASSWD_LEN + 1); + if (!crypt_pwd) + return NULL; + + if (!pg_md5_encrypt(passwd, user, strlen(user), crypt_pwd)) + { + free(crypt_pwd); + return NULL; + } + + return crypt_pwd; +} + +/* + * PQencryptPasswordConn -- exported routine to encrypt a password * * This is intended to be used by client applications that wish to send * commands like ALTER USER joe PASSWORD 'pwd'. The password need not @@ -1087,27 +1113,102 @@ pg_fe_getauthname(PQExpBuffer errorMessage) * be dependent on low-level details like whether the encryption is MD5 * or something else. * - * Arguments are the cleartext password, and the SQL name of the user it - * is for. + * Arguments are a connection object, the cleartext password, the SQL + * name of the user it is for, and a string indicating the algorithm to + * use for encrypting the password. If algorithm is NULL, this queries + * the server for the current 'password_encryption' value. If you wish + * to avoid that, e.g. to avoid blocking, you can execute + * 'show password_encryption' yourself before calling this function, and pass + * it as the algorithm. * - * Return value is a malloc'd string, or NULL if out-of-memory. The client - * may assume the string doesn't contain any special characters that would - * require escaping. + * Return value is a malloc'd string. The client may assume the string + * doesn't contain any special characters that would require escaping. + * On error, an error message is stored in the connection object, and + * returns NULL. */ char * -PQencryptPassword(const char *passwd, const char *user) +PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user, + const char *algorithm) { - char *crypt_pwd; +#define MAX_ALGORITHM_NAME_LEN 50 + char algobuf[MAX_ALGORITHM_NAME_LEN + 1]; + char *crypt_pwd = NULL; - crypt_pwd = malloc(MD5_PASSWD_LEN + 1); - if (!crypt_pwd) + if (!conn) return NULL; - if (!pg_md5_encrypt(passwd, user, strlen(user), crypt_pwd)) + /* If no algorithm was given, ask the server. */ + if (algorithm == NULL) { - free(crypt_pwd); + PGresult *res; + char *val; + + res = PQexec(conn, "show password_encryption"); + if (res == NULL) + { + /* PQexec() should've set conn->errorMessage already */ + return NULL; + } + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + /* PQexec() should've set conn->errorMessage already */ + PQclear(res); + return NULL; + } + if (PQntuples(res) != 1 || PQnfields(res) != 1) + { + PQclear(res); + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unexpected shape of result set returned for SHOW\n")); + return NULL; + } + val = PQgetvalue(res, 0, 0); + + if (strlen(val) > MAX_ALGORITHM_NAME_LEN) + { + PQclear(res); + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("password_encryption value too long\n")); + return NULL; + } + strcpy(algobuf, val); + PQclear(res); + + algorithm = algobuf; + } + + /* Ok, now we know what algorithm to use */ + + if (strcmp(algorithm, "scram-sha-256") == 0) + { + crypt_pwd = pg_fe_scram_build_verifier(passwd); + } + else if (strcmp(algorithm, "md5") == 0) + { + crypt_pwd = malloc(MD5_PASSWD_LEN + 1); + if (crypt_pwd) + { + if (!pg_md5_encrypt(passwd, user, strlen(user), crypt_pwd)) + { + free(crypt_pwd); + crypt_pwd = NULL; + } + } + } + else if (strcmp(algorithm, "plain") == 0) + { + crypt_pwd = strdup(passwd); + } + else + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unknown password encryption algorithm\n")); return NULL; } + if (!crypt_pwd) + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("out of memory\n")); + return crypt_pwd; } diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h index a5c739f01a..9f4c2a50d8 100644 --- a/src/interfaces/libpq/fe-auth.h +++ b/src/interfaces/libpq/fe-auth.h @@ -28,5 +28,6 @@ extern void pg_fe_scram_free(void *opaq); extern void pg_fe_scram_exchange(void *opaq, char *input, int inputlen, char **output, int *outputlen, bool *done, bool *success, PQExpBuffer errorMessage); +extern char *pg_fe_scram_build_verifier(const char *password); #endif /* FE_AUTH_H */ diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index 635af5b50e..c1497f00b6 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -597,6 +597,7 @@ extern int PQenv2encoding(void); /* === in fe-auth.c === */ extern char *PQencryptPassword(const char *passwd, const char *user); +extern char *PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user, const char *method); /* === in encnames.c === */ -- 2.11.0
0003-Use-new-PQencryptPasswordConn-function-in-psql-and-c.patch (application/x-download, 1.7K) download From a335389bbe31fdf365250fdbdf1f5d01940dad86 Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas <heikki.linnakangas@iki.fi> Date: Wed, 26 Apr 2017 12:57:12 +0300 Subject: [PATCH 3/3] Use new PQencryptPasswordConn function in psql and createuser 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 --- src/bin/psql/command.c | 2 +- src/bin/scripts/createuser.c | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 859ded71f6..51bd8f9cc9 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -1878,7 +1878,7 @@ exec_command_password(PsqlScanState scan_state, bool active_branch) else user = PQuser(pset.db); - encrypted_password = PQencryptPassword(pw1, user); + encrypted_password = PQencryptPasswordConn(pset.db, pw1, user, NULL); if (!encrypted_password) { diff --git a/src/bin/scripts/createuser.c b/src/bin/scripts/createuser.c index 3d74797a8f..087a5ca05f 100644 --- a/src/bin/scripts/createuser.c +++ b/src/bin/scripts/createuser.c @@ -274,8 +274,10 @@ main(int argc, char *argv[]) { char *encrypted_password; - encrypted_password = PQencryptPassword(newpassword, - newuser); + encrypted_password = PQencryptPasswordConn(conn, + newpassword, + newuser, + NULL); if (!encrypted_password) { fprintf(stderr, _("Password encryption failed.\n")); -- 2.11.0

view thread (81+ messages)  latest in thread

Message-ID: <57f8c243-2362-e7b8-4c56-e752ba6edf6c@iki.fi>
Permalink:  ../57f8c243-2362-e7b8-4c56-e752ba6edf6c@iki.fi/
Also on:    postgresql.org/message-id/57f8c243-2362-e7b8-4c56-e752ba6edf6c@iki.fi

 ·  · 

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: pgsql-hackers@postgresql.org
  Cc: hlinnaka@iki.fi, michael.paquier@gmail.com, noah@leadboat.com, robertmhaas@gmail.com, tgl@sss.pgh.pa.us, jeff.janes@gmail.com, mail@joeconway.com
  Subject: Re: scram and \password
  In-Reply-To: <57f8c243-2362-e7b8-4c56-e752ba6edf6c@iki.fi>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox