Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/libpq/auth-scram.c')
-rw-r--r--src/backend/libpq/auth-scram.c63
1 files changed, 59 insertions, 4 deletions
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index 14ddc8bd542..5077ff33b16 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -11,13 +11,43 @@
*
* - Username from the authentication exchange is not used. The client
* should send an empty string as the username.
- * - Password is not processed with the SASLprep algorithm.
+ *
+ * - If the password isn't valid UTF-8, or contains characters prohibited
+ * by the SASLprep profile, we skip the SASLprep pre-processing and use
+ * the raw bytes in calculating the hash.
+ *
* - Channel binding is not supported yet.
*
+ *
* The password stored in pg_authid consists of the salt, iteration count,
* StoredKey and ServerKey.
*
- * On error handling:
+ * SASLprep usage
+ * --------------
+ *
+ * One notable difference to the SCRAM specification is that while the
+ * specification dictates that the password is in UTF-8, and prohibits
+ * certain characters, we are more lenient. If the password isn't a valid
+ * UTF-8 string, or contains prohibited characters, the raw bytes are used
+ * to calculate the hash instead, without SASLprep processing. This is
+ * because PostgreSQL supports other encodings too, and the encoding being
+ * used during authentication is undefined (client_encoding isn't set until
+ * after authentication). In effect, we try to interpret the password as
+ * UTF-8 and apply SASLprep processing, but if it looks invalid, we assume
+ * that it's in some other encoding.
+ *
+ * In the worst case, we misinterpret a password that's in a different
+ * encoding as being Unicode, because it happens to consists entirely of
+ * valid UTF-8 bytes, and we apply Unicode normalization to it. As long
+ * as we do that consistently, that will not lead to failed logins.
+ * Fortunately, the UTF-8 byte sequences that are ignored by SASLprep
+ * don't correspond to any commonly used characters in any of the other
+ * supported encodings, so it should not lead to any significant loss in
+ * entropy, even if the normalization is incorrectly applied to a
+ * non-UTF-8 password.
+ *
+ * Error handling
+ * --------------
*
* Don't reveal user information to an unauthenticated client. We don't
* want an attacker to be able to probe whether a particular username is
@@ -37,6 +67,7 @@
* to the encoding being used, whatever that is. We cannot avoid that in
* general, after logging in, but let's do what we can here.
*
+ *
* Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
@@ -52,6 +83,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_control.h"
#include "common/base64.h"
+#include "common/saslprep.h"
#include "common/scram-common.h"
#include "common/sha2.h"
#include "libpq/auth.h"
@@ -344,6 +376,17 @@ scram_build_verifier(const char *username, const char *password,
char salt[SCRAM_SALT_LEN];
char *encoded_salt;
int encoded_len;
+ char *prep_password = NULL;
+ pg_saslprep_rc rc;
+
+ /*
+ * 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_SUCCESS)
+ password = (const char *) prep_password;
if (iterations <= 0)
iterations = SCRAM_ITERATIONS_DEFAULT;
@@ -373,6 +416,9 @@ scram_build_verifier(const char *username, const char *password,
(void) hex_encode((const char *) keybuf, SCRAM_KEY_LEN, serverkey_hex);
serverkey_hex[SCRAM_KEY_LEN * 2] = '\0';
+ if (prep_password)
+ pfree(prep_password);
+
return psprintf("scram-sha-256:%s:%d:%s:%s", encoded_salt, iterations, storedkey_hex, serverkey_hex);
}
@@ -392,13 +438,14 @@ scram_verify_plain_password(const char *username, const char *password,
uint8 stored_key[SCRAM_KEY_LEN];
uint8 server_key[SCRAM_KEY_LEN];
uint8 computed_key[SCRAM_KEY_LEN];
+ char *prep_password = NULL;
+ pg_saslprep_rc rc;
if (!parse_scram_verifier(verifier, &encoded_salt, &iterations,
stored_key, server_key))
{
/*
- * The password looked like a SCRAM verifier, but could not be
- * parsed.
+ * The password looked like a SCRAM verifier, but could not be parsed.
*/
elog(LOG, "invalid SCRAM verifier for user \"%s\"", username);
return false;
@@ -412,10 +459,18 @@ scram_verify_plain_password(const char *username, const char *password,
return false;
}
+ /* Normalize the password */
+ rc = pg_saslprep(password, &prep_password);
+ if (rc == SASLPREP_SUCCESS)
+ password = prep_password;
+
/* Compute Server key based on the user-supplied plaintext password */
scram_ClientOrServerKey(password, salt, saltlen, iterations,
SCRAM_SERVER_KEY_NAME, computed_key);
+ if (prep_password)
+ pfree(prep_password);
+
/*
* Compare the verifier's Server Key with the one computed from the
* user-supplied password.