aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
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 === */