aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHeikki Linnakangas <heikki.linnakangas@iki.fi>2017-05-03 11:19:07 +0300
committerHeikki Linnakangas <heikki.linnakangas@iki.fi>2017-05-03 11:19:07 +0300
commit8f8b9be51fd788bb11276df89606bc653163524e (patch)
tree4d2daef287c2adb74da34bd6fcdbd47febbb47df /src
parentaf2c5aa88d38573724e40fa029499b4db20b0eb2 (diff)
downloadpostgresql-8f8b9be51fd788bb11276df89606bc653163524e.tar.gz
postgresql-8f8b9be51fd788bb11276df89606bc653163524e.zip
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
Diffstat (limited to 'src')
-rw-r--r--src/backend/libpq/auth-scram.c51
-rw-r--r--src/backend/libpq/crypt.c2
-rw-r--r--src/bin/psql/command.c4
-rw-r--r--src/bin/scripts/createuser.c9
-rw-r--r--src/common/scram-common.c64
-rw-r--r--src/include/common/scram-common.h3
-rw-r--r--src/include/libpq/scram.h4
-rw-r--r--src/interfaces/libpq/exports.txt1
-rw-r--r--src/interfaces/libpq/fe-auth-scram.c35
-rw-r--r--src/interfaces/libpq/fe-auth.c125
-rw-r--r--src/interfaces/libpq/fe-auth.h1
-rw-r--r--src/interfaces/libpq/libpq-fe.h1
12 files changed, 235 insertions, 65 deletions
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index 5c85af943cd..6e7a1405826 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,22 +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_DEFAULT_SALT_LEN];
- uint8 salted_password[SCRAM_KEY_LEN];
- uint8 keybuf[SCRAM_KEY_LEN];
- char *encoded_salt;
- char *encoded_storedkey;
- char *encoded_serverkey;
- int encoded_len;
char *result;
/*
@@ -414,10 +406,7 @@ scram_build_verifier(const char *username, const char *password,
if (rc == SASLPREP_SUCCESS)
password = (const char *) prep_password;
- if (iterations <= 0)
- iterations = SCRAM_DEFAULT_ITERATIONS;
-
- /* Generate salt, and encode it in base64 */
+ /* Generate random salt */
if (!pg_backend_random(saltbuf, SCRAM_DEFAULT_SALT_LEN))
{
ereport(LOG,
@@ -426,37 +415,11 @@ scram_build_verifier(const char *username, const char *password,
return NULL;
}
- encoded_salt = palloc(pg_b64_enc_len(SCRAM_DEFAULT_SALT_LEN) + 1);
- encoded_len = pg_b64_encode(saltbuf, SCRAM_DEFAULT_SALT_LEN, encoded_salt);
- encoded_salt[encoded_len] = '\0';
-
- /* Calculate StoredKey, and encode it in base64 */
- scram_SaltedPassword(password, saltbuf, SCRAM_DEFAULT_SALT_LEN,
- iterations, salted_password);
- scram_ClientKey(salted_password, 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_ServerKey(salted_password, 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;
}
@@ -1194,7 +1157,7 @@ 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_DEFAULT_SALT_LEN,
"salt length greater than SHA256 digest length");
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index d0030f2b6d8..9fe79b48946 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/bin/psql/command.c b/src/bin/psql/command.c
index 859ded71f61..b3263a9570a 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -1878,11 +1878,11 @@ 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)
{
- psql_error("Password encryption failed.\n");
+ psql_error("%s", PQerrorMessage(pset.db));
success = false;
}
else
diff --git a/src/bin/scripts/createuser.c b/src/bin/scripts/createuser.c
index 3d74797a8f5..35a53bf2064 100644
--- a/src/bin/scripts/createuser.c
+++ b/src/bin/scripts/createuser.c
@@ -274,11 +274,14 @@ 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"));
+ fprintf(stderr, _("%s: password encryption failed: %s"),
+ progname, PQerrorMessage(conn));
exit(1);
}
appendStringLiteralConn(&sql, encrypted_password, conn);
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
index a8ea44944c4..77b54c8a5e7 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
@@ -180,3 +181,66 @@ scram_ServerKey(const uint8 *salted_password, uint8 *result)
scram_HMAC_update(&ctx, "Server Key", strlen("Server Key"));
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 salted_password[SCRAM_KEY_LEN];
+ uint8 stored_key[SCRAM_KEY_LEN];
+ uint8 server_key[SCRAM_KEY_LEN];
+ char *result;
+ char *p;
+ int maxlen;
+
+ if (iterations <= 0)
+ iterations = SCRAM_DEFAULT_ITERATIONS;
+
+ /* Calculate StoredKey and ServerKey */
+ scram_SaltedPassword(password, salt, saltlen, iterations,
+ salted_password);
+ scram_ClientKey(salted_password, stored_key);
+ scram_H(stored_key, SCRAM_KEY_LEN, stored_key);
+
+ scram_ServerKey(salted_password, server_key);
+
+ /*
+ * 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 *) stored_key, SCRAM_KEY_LEN, p);
+ *(p++) = ':';
+ p += pg_b64_encode((char *) server_key, 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 656d9e1e6b1..307f92b54a4 100644
--- a/src/include/common/scram-common.h
+++ b/src/include/common/scram-common.h
@@ -53,4 +53,7 @@ extern void scram_H(const uint8 *str, int len, uint8 *result);
extern void scram_ClientKey(const uint8 *salted_password, uint8 *result);
extern void scram_ServerKey(const uint8 *salted_password, 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 e373f0c07e8..060b8af69e3 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);
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index 21dd772ca91..d6a38d0df85 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 be271ce8ac0..52dae49abf6 100644
--- a/src/interfaces/libpq/fe-auth-scram.c
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -615,6 +615,41 @@ verify_server_signature(fe_scram_state *state)
}
/*
+ * Build a new SCRAM verifier.
+ */
+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 a random 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.
*/
static bool
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index d81ee4f9447..daa7cc95858 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 a5c739f01a3..9f4c2a50d8d 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 635af5b50e3..093c4986d8c 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 *algorithm);
/* === in encnames.c === */