aboutsummaryrefslogtreecommitdiff
path: root/src/common/scram-common.c
diff options
context:
space:
mode:
authorHeikki Linnakangas <heikki.linnakangas@iki.fi>2017-03-07 14:25:40 +0200
committerHeikki Linnakangas <heikki.linnakangas@iki.fi>2017-03-07 14:25:40 +0200
commit818fd4a67d610991757b610755e3065fb99d80a5 (patch)
tree6902ced6e8316044e9b706f93586c84bc24e2010 /src/common/scram-common.c
parent273c458a2b3a0fb73968020ea5e9e35eb6928967 (diff)
downloadpostgresql-818fd4a67d610991757b610755e3065fb99d80a5.tar.gz
postgresql-818fd4a67d610991757b610755e3065fb99d80a5.zip
Support SCRAM-SHA-256 authentication (RFC 5802 and 7677).
This introduces a new generic SASL authentication method, similar to the GSS and SSPI methods. The server first tells the client which SASL authentication mechanism to use, and then the mechanism-specific SASL messages are exchanged in AuthenticationSASLcontinue and PasswordMessage messages. Only SCRAM-SHA-256 is supported at the moment, but this allows adding more SASL mechanisms in the future, without changing the overall protocol. Support for channel binding, aka SCRAM-SHA-256-PLUS is left for later. The SASLPrep algorithm, for pre-processing the password, is not yet implemented. That could cause trouble, if you use a password with non-ASCII characters, and a client library that does implement SASLprep. That will hopefully be added later. Authorization identities, as specified in the SCRAM-SHA-256 specification, are ignored. SET SESSION AUTHORIZATION provides more or less the same functionality, anyway. If a user doesn't exist, perform a "mock" authentication, by constructing an authentic-looking challenge on the fly. The challenge is derived from a new system-wide random value, "mock authentication nonce", which is created at initdb, and stored in the control file. We go through these motions, in order to not give away the information on whether the user exists, to unauthenticated users. Bumps PG_CONTROL_VERSION, because of the new field in control file. Patch by Michael Paquier and Heikki Linnakangas, reviewed at different stages by Robert Haas, Stephen Frost, David Steele, Aleksander Alekseev, and many others. Discussion: https://www.postgresql.org/message-id/CAB7nPqRbR3GmFYdedCAhzukfKrgBLTLtMvENOmPrVWREsZkF8g%40mail.gmail.com Discussion: https://www.postgresql.org/message-id/CAB7nPqSMXU35g%3DW9X74HVeQp0uvgJxvYOuA4A-A3M%2B0wfEBv-w%40mail.gmail.com Discussion: https://www.postgresql.org/message-id/55192AFE.6080106@iki.fi
Diffstat (limited to 'src/common/scram-common.c')
-rw-r--r--src/common/scram-common.c196
1 files changed, 196 insertions, 0 deletions
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
new file mode 100644
index 00000000000..4a1c3809cfe
--- /dev/null
+++ b/src/common/scram-common.c
@@ -0,0 +1,196 @@
+/*-------------------------------------------------------------------------
+ * scram-common.c
+ * Shared frontend/backend code for SCRAM authentication
+ *
+ * This contains the common low-level functions needed in both frontend and
+ * backend, for implement the Salted Challenge Response Authentication
+ * Mechanism (SCRAM), per IETF's RFC 5802.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/scram-common.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FRONTEND
+#include "postgres.h"
+#include "utils/memutils.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/scram-common.h"
+
+#define HMAC_IPAD 0x36
+#define HMAC_OPAD 0x5C
+
+/*
+ * Calculate HMAC per RFC2104.
+ *
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_init(scram_HMAC_ctx *ctx, const uint8 *key, int keylen)
+{
+ uint8 k_ipad[SHA256_HMAC_B];
+ int i;
+ uint8 keybuf[SCRAM_KEY_LEN];
+
+ /*
+ * If the key is longer than the block size (64 bytes for SHA-256), pass
+ * it through SHA-256 once to shrink it down.
+ */
+ if (keylen > SHA256_HMAC_B)
+ {
+ pg_sha256_ctx sha256_ctx;
+
+ pg_sha256_init(&sha256_ctx);
+ pg_sha256_update(&sha256_ctx, key, keylen);
+ pg_sha256_final(&sha256_ctx, keybuf);
+ key = keybuf;
+ keylen = SCRAM_KEY_LEN;
+ }
+
+ memset(k_ipad, HMAC_IPAD, SHA256_HMAC_B);
+ memset(ctx->k_opad, HMAC_OPAD, SHA256_HMAC_B);
+
+ for (i = 0; i < keylen; i++)
+ {
+ k_ipad[i] ^= key[i];
+ ctx->k_opad[i] ^= key[i];
+ }
+
+ /* tmp = H(K XOR ipad, text) */
+ pg_sha256_init(&ctx->sha256ctx);
+ pg_sha256_update(&ctx->sha256ctx, k_ipad, SHA256_HMAC_B);
+}
+
+/*
+ * Update HMAC calculation
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_update(scram_HMAC_ctx *ctx, const char *str, int slen)
+{
+ pg_sha256_update(&ctx->sha256ctx, (const uint8 *) str, slen);
+}
+
+/*
+ * Finalize HMAC calculation.
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_final(uint8 *result, scram_HMAC_ctx *ctx)
+{
+ uint8 h[SCRAM_KEY_LEN];
+
+ pg_sha256_final(&ctx->sha256ctx, h);
+
+ /* H(K XOR opad, tmp) */
+ pg_sha256_init(&ctx->sha256ctx);
+ pg_sha256_update(&ctx->sha256ctx, ctx->k_opad, SHA256_HMAC_B);
+ pg_sha256_update(&ctx->sha256ctx, h, SCRAM_KEY_LEN);
+ pg_sha256_final(&ctx->sha256ctx, result);
+}
+
+/*
+ * Iterate hash calculation of HMAC entry using given salt.
+ * scram_Hi() is essentially PBKDF2 (see RFC2898) with HMAC() as the
+ * pseudorandom function.
+ */
+static void
+scram_Hi(const char *str, const char *salt, int saltlen, int iterations, uint8 *result)
+{
+ int str_len = strlen(str);
+ uint32 one = htonl(1);
+ int i,
+ j;
+ uint8 Ui[SCRAM_KEY_LEN];
+ uint8 Ui_prev[SCRAM_KEY_LEN];
+ scram_HMAC_ctx hmac_ctx;
+
+ /* First iteration */
+ scram_HMAC_init(&hmac_ctx, (uint8 *) str, str_len);
+ scram_HMAC_update(&hmac_ctx, salt, saltlen);
+ scram_HMAC_update(&hmac_ctx, (char *) &one, sizeof(uint32));
+ scram_HMAC_final(Ui_prev, &hmac_ctx);
+ memcpy(result, Ui_prev, SCRAM_KEY_LEN);
+
+ /* Subsequent iterations */
+ for (i = 2; i <= iterations; i++)
+ {
+ scram_HMAC_init(&hmac_ctx, (uint8 *) str, str_len);
+ scram_HMAC_update(&hmac_ctx, (const char *) Ui_prev, SCRAM_KEY_LEN);
+ scram_HMAC_final(Ui, &hmac_ctx);
+ for (j = 0; j < SCRAM_KEY_LEN; j++)
+ result[j] ^= Ui[j];
+ memcpy(Ui_prev, Ui, SCRAM_KEY_LEN);
+ }
+}
+
+
+/*
+ * Calculate SHA-256 hash for a NULL-terminated string. (The NULL terminator is
+ * not included in the hash).
+ */
+void
+scram_H(const uint8 *input, int len, uint8 *result)
+{
+ pg_sha256_ctx ctx;
+
+ pg_sha256_init(&ctx);
+ pg_sha256_update(&ctx, input, len);
+ pg_sha256_final(&ctx, result);
+}
+
+/*
+ * Normalize a password for SCRAM authentication.
+ */
+static void
+scram_Normalize(const char *password, char *result)
+{
+ /*
+ * XXX: Here SASLprep should be applied on password. However, per RFC5802,
+ * it is required that the password is encoded in UTF-8, something that is
+ * not guaranteed in this protocol. We may want to revisit this
+ * normalization function once encoding functions are available as well in
+ * the frontend in order to be able to encode properly this string, and
+ * then apply SASLprep on it.
+ */
+ memcpy(result, password, strlen(password) + 1);
+}
+
+/*
+ * Encrypt password for SCRAM authentication. This basically applies the
+ * normalization of the password and a hash calculation using the salt
+ * value given by caller.
+ */
+static void
+scram_SaltedPassword(const char *password, const char *salt, int saltlen, int iterations,
+ uint8 *result)
+{
+ char *pwbuf;
+
+ pwbuf = (char *) malloc(strlen(password) + 1);
+ scram_Normalize(password, pwbuf);
+ scram_Hi(pwbuf, salt, saltlen, iterations, result);
+ free(pwbuf);
+}
+
+/*
+ * Calculate ClientKey or ServerKey.
+ */
+void
+scram_ClientOrServerKey(const char *password,
+ const char *salt, int saltlen, int iterations,
+ const char *keystr, uint8 *result)
+{
+ uint8 keybuf[SCRAM_KEY_LEN];
+ scram_HMAC_ctx ctx;
+
+ scram_SaltedPassword(password, salt, saltlen, iterations, keybuf);
+ scram_HMAC_init(&ctx, keybuf, SCRAM_KEY_LEN);
+ scram_HMAC_update(&ctx, keystr, strlen(keystr));
+ scram_HMAC_final(result, &ctx);
+}