Am 16.09.2011 14:40, schrieb Timo Sirainen:
> On Fri, 2011-09-16 at 02:47 +0200, Florian Zeitz wrote:
> 
> Looks pretty good. Below are a few things I noticed. I could fix these
> myself next week also, or you can do them during weekend if you want
> to. :)
> 
I decided to do it myself, hope this fixes all issues.

>  - Could be nicer if client->proof was stored base64-decoded, so its
> validity could be checked and also later there wouldn't be need to
> base64-encode signature when testing it.
> 
>  - Doesn't verify_credentials() need to check the credentials in any way
> that it contains expected (sized) data? Anything is allowed?
> 
I don't think it needs to. The password read from the database can
legitimately have any length and from the client it just takes a base64
encoded SHA-1 hash. The correct size of that was previously implicitly
checked when comparing the base64 encoded data (strings of different
length don't compare equal). It's now explicitly checked after base64
decoding the client proof.
# HG changeset patch
# User Florian Zeitz <[email protected]>
# Date 1316132569 -7200
# Node ID 9d3cb2ef74d8a235ff86b4f8d644a28a47c17a70
# Parent  61d3544f8fdf03558055d240f5815dc1eac7fb66
lib: Add hmac-sha1 adapted from hmac-md5

diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -43,6 +43,7 @@
        hex-binary.c \
        hex-dec.c \
        hmac-md5.c \
+       hmac-sha1.c \
        home-expand.c \
        hostpid.c \
        imem.c \
diff --git a/src/lib/hmac-sha1.c b/src/lib/hmac-sha1.c
new file mode 100644
--- /dev/null
+++ b/src/lib/hmac-sha1.c
@@ -0,0 +1,52 @@
+/*
+ * HMAC-SHA1 (RFC-2104) implementation.
+ *
+ * Copyright (c) 2004 Andrey Panin <[email protected]>
+ * Copyright (c) 2011 Florian Zeitz <[email protected]>
+ *
+ * This software is released under the MIT license.
+ */
+
+#include "lib.h"
+#include "hmac-sha1.h"
+#include "safe-memset.h"
+
+void hmac_sha1_init(struct hmac_sha1_context *ctx,
+                  const unsigned char *key, size_t key_len)
+{
+       int i;
+       unsigned char sha1key[20];
+       unsigned char k_ipad[64];
+       unsigned char k_opad[64];
+
+       if (key_len > 64) {
+               sha1_get_digest(key, key_len, sha1key);
+               key = sha1key;
+               key_len = 20;
+       }
+
+       memcpy(k_ipad, key, key_len);
+       memset(k_ipad + key_len, 0, 64 - key_len);
+       memcpy(k_opad, k_ipad, 64);
+
+       for (i = 0; i < 64; i++) {
+               k_ipad[i] ^= 0x36;
+               k_opad[i] ^= 0x5c;
+       }
+
+       sha1_init(&ctx->ctx);
+       sha1_loop(&ctx->ctx, k_ipad, 64);
+       sha1_init(&ctx->ctxo);
+       sha1_loop(&ctx->ctxo, k_opad, 64);
+
+       safe_memset(k_ipad, 0, 64);
+       safe_memset(k_opad, 0, 64);
+}
+
+void hmac_sha1_final(struct hmac_sha1_context *ctx, unsigned char *digest)
+{
+       sha1_result(&ctx->ctx, digest);
+
+       sha1_loop(&ctx->ctxo, digest, 20);
+       sha1_result(&ctx->ctxo, digest);
+}
diff --git a/src/lib/hmac-sha1.h b/src/lib/hmac-sha1.h
new file mode 100644
--- /dev/null
+++ b/src/lib/hmac-sha1.h
@@ -0,0 +1,22 @@
+#ifndef HMAC_SHA1_H
+#define HMAC_SHA1_H
+
+#include "sha1.h"
+
+struct hmac_sha1_context {
+       struct sha1_ctxt ctx, ctxo;
+};
+
+void hmac_sha1_init(struct hmac_sha1_context *ctx,
+                  const unsigned char *key, size_t key_len);
+void hmac_sha1_final(struct hmac_sha1_context *ctx,
+                   unsigned char digest[SHA1_RESULTLEN]);
+
+
+static inline void
+hmac_sha1_update(struct hmac_sha1_context *ctx, const void *data, size_t size)
+{
+       sha1_loop(&ctx->ctx, data, size);
+}
+
+#endif
# HG changeset patch
# User Florian Zeitz <[email protected]>
# Date 1316132640 -7200
# Node ID fea09a51a98b789cba58a0ca4f0e220f2ae1f646
# Parent  9d3cb2ef74d8a235ff86b4f8d644a28a47c17a70
auth: Implement the SCRAM-SHA-1 SASL mechanism

diff --git a/src/auth/Makefile.am b/src/auth/Makefile.am
--- a/src/auth/Makefile.am
+++ b/src/auth/Makefile.am
@@ -81,6 +81,7 @@
        mech-gssapi.c \
        mech-ntlm.c \
        mech-otp.c \
+       mech-scram-sha1.c \
        mech-skey.c \
        mech-rpa.c \
        mech-apop.c \
diff --git a/src/auth/mech-scram-sha1.c b/src/auth/mech-scram-sha1.c
new file mode 100644
--- /dev/null
+++ b/src/auth/mech-scram-sha1.c
@@ -0,0 +1,405 @@
+/*
+ * SCRAM-SHA-1 SASL authentication, see RFC-5802
+ *
+ * Copyright (c) 2011 Florian Zeitz <[email protected]>
+ *
+ * This software is released under the MIT license.
+ */
+
+#include "auth-common.h"
+#include "base64.h"
+#include "buffer.h"
+#include "hmac-sha1.h"
+#include "randgen.h"
+#include "safe-memset.h"
+#include "str.h"
+#include "strfuncs.h"
+#include "mech.h"
+
+struct scram_auth_request {
+       struct auth_request auth_request;
+
+       pool_t pool;
+       unsigned int authenticated:1;
+
+       /* sent: */
+       char *server_first_message;
+       unsigned char salt[16];
+       unsigned char salted_password[SHA1_RESULTLEN];
+
+       /* received: */
+       char *gs2_cbind_flag;
+       char *cnonce;
+       char *snonce;
+       char *client_first_message_bare;
+       char *client_final_message_without_proof;
+       buffer_t *proof;
+};
+
+static void Hi(const unsigned char *str, size_t str_size,
+              const unsigned char *salt, size_t salt_size, unsigned int i,
+              unsigned char result[SHA1_RESULTLEN])
+{
+       struct hmac_sha1_context ctx;
+       unsigned char U[SHA1_RESULTLEN];
+       size_t j, k;
+
+       /* Calculate U1 */
+       hmac_sha1_init(&ctx, str, str_size);
+       hmac_sha1_update(&ctx, salt, salt_size);
+       hmac_sha1_update(&ctx, "\0\0\0\1", 4);
+       hmac_sha1_final(&ctx, U);
+
+       memcpy(result, U, SHA1_RESULTLEN);
+
+       /* Calculate U2 to Ui and Hi*/
+       for (j = 2; j <= i; j++) {
+               hmac_sha1_init(&ctx, str, str_size);
+               hmac_sha1_update(&ctx, U, sizeof(U));
+               hmac_sha1_final(&ctx, U);
+               for (k = 0; k < SHA1_RESULTLEN; k++)
+                       result[k] ^= U[k];
+       }
+}
+
+static const char *get_scram_server_first(struct scram_auth_request *request)
+{
+       unsigned char snonce[65];
+       string_t *str;
+       size_t i;
+
+       random_fill(snonce, sizeof(snonce)-1);
+
+       /* make sure snonce is printable and does not contain ',' */
+       for (i = 0; i < sizeof(snonce)-1; i++) {
+               snonce[i] = (snonce[i] % ('~' - '!')) + '!';
+               if (snonce[i] == ',')
+                       snonce[i] = '~';
+       }
+       snonce[sizeof(snonce)-1] = '\0';
+
+       request->snonce = p_strndup(request->pool, snonce, sizeof(snonce));
+
+       random_fill(request->salt, sizeof(request->salt));
+
+       str = t_str_new(MAX_BASE64_ENCODED_SIZE(sizeof(request->salt)));
+       base64_encode(request->salt, sizeof(request->salt), str);
+
+       return t_strdup_printf("r=%s%s,s=%s,i=%i", request->cnonce,
+                       request->snonce, str_c(str), 4096);
+}
+
+static const char *get_scram_server_final(struct scram_auth_request *request)
+{
+       struct hmac_sha1_context ctx;
+       const char *auth_message;
+       unsigned char server_key[SHA1_RESULTLEN];
+       unsigned char server_signature[SHA1_RESULTLEN];
+       string_t *str;
+
+       auth_message = t_strconcat(request->client_first_message_bare, ",",
+                       request->server_first_message, ",",
+                       request->client_final_message_without_proof, NULL);
+
+       hmac_sha1_init(&ctx, request->salted_password,
+                       sizeof(request->salted_password));
+       hmac_sha1_update(&ctx, "Server Key", 10);
+       hmac_sha1_final(&ctx, server_key);
+
+       safe_memset(request->salted_password, 0,
+                       sizeof(request->salted_password));
+
+       hmac_sha1_init(&ctx, server_key, sizeof(server_key));
+       hmac_sha1_update(&ctx, auth_message, strlen(auth_message));
+       hmac_sha1_final(&ctx, server_signature);
+
+       str = t_str_new(MAX_BASE64_ENCODED_SIZE(sizeof(server_signature)));
+       base64_encode(server_signature, sizeof(server_signature), str);
+
+       return t_strdup_printf("v=%s", str_c(str));
+}
+
+static bool parse_scram_client_first(struct scram_auth_request *request,
+                                    const unsigned char *data, size_t size,
+                                    const char **error)
+{
+       const char *const *fields;
+       const char *p;
+       string_t *username;
+
+       fields = t_strsplit(t_strndup(data, size), ",");
+
+       if (str_array_length(fields) < 4) {
+               *error = "Invalid initial client message";
+               return FALSE;
+       }
+
+       switch (fields[0][0]) {
+       case 'p':
+               *error = "Channel binding not supported";
+               return FALSE;
+       case 'y':
+       case 'n':
+               request->gs2_cbind_flag = p_strdup(request->pool, fields[0]);
+               break;
+       default:
+               *error = "Invalid GS2 header";
+               return FALSE;
+       }
+
+       if (fields[1][0] != '\0') {
+               *error = "authzid not supported";
+               return FALSE;
+       }
+
+       if (fields[2][0] == 'm') {
+               *error = "Mandatory extension(s) not supported";
+               return FALSE;
+       }
+
+       if (fields[2][0] == 'n') {
+               /* Unescape username */
+               username = t_str_new(0);
+
+               for (p = fields[2] + 2; *p != '\0'; p++) {
+                       if (p[0] == '=') {
+                               if (p[1] == '2' && p[2] == 'C') {
+                                       str_append_c(username, ',');
+                               } else if (p[1] == '3' && p[2] == 'D') {
+                                       str_append_c(username, '=');
+                               } else {
+                                       *error = "Username contains "
+                                                "forbidden character(s)";
+                                       return FALSE;
+                               }
+                               p += 2;
+                       } else if (p[0] == ',') {
+                               *error = "Username contains "
+                                        "forbidden character(s)";
+                               return FALSE;
+                       } else {
+                               str_append_c(username, *p);
+                       }
+               }
+               if (!auth_request_set_username(&request->auth_request,
+                                       str_c(username), error))
+                               return FALSE;
+       } else {
+               *error = "Invalid username";
+               return FALSE;
+       }
+
+       if (fields[3][0] == 'r')
+               request->cnonce = p_strdup(request->pool, fields[3]+2);
+       else {
+               *error = "Invalid client nonce";
+               return FALSE;
+       }
+
+       /* This works only without channel binding support,
+          otherwise the GS2 header doesn't have a fixed length */
+       request->client_first_message_bare =
+               p_strndup(request->pool, data + 3, size - 3);
+
+       return TRUE;
+}
+
+static bool verify_credentials(struct scram_auth_request *request,
+                              const unsigned char *credentials, size_t size)
+{
+       struct hmac_sha1_context ctx;
+       const char *auth_message;
+       unsigned char client_key[SHA1_RESULTLEN];
+       unsigned char client_signature[SHA1_RESULTLEN];
+       unsigned char stored_key[SHA1_RESULTLEN];
+       size_t i;
+
+       /* FIXME: credentials should be SASLprepped UTF8 data here */
+       Hi(credentials, size, request->salt, sizeof(request->salt), 4096,
+                       request->salted_password);
+
+       hmac_sha1_init(&ctx, request->salted_password,
+                       sizeof(request->salted_password));
+       hmac_sha1_update(&ctx, "Client Key", 10);
+       hmac_sha1_final(&ctx, client_key);
+
+       sha1_get_digest(client_key, sizeof(client_key), stored_key);
+
+       auth_message = t_strconcat(request->client_first_message_bare, ",",
+                       request->server_first_message, ",",
+                       request->client_final_message_without_proof, NULL);
+
+       hmac_sha1_init(&ctx, stored_key, sizeof(stored_key));
+       hmac_sha1_update(&ctx, auth_message, strlen(auth_message));
+       hmac_sha1_final(&ctx, client_signature);
+
+       for (i = 0; i < sizeof(client_signature); i++)
+               client_signature[i] ^= client_key[i];
+
+       safe_memset(client_key, 0, sizeof(client_key));
+       safe_memset(stored_key, 0, sizeof(stored_key));
+
+       if (!memcmp(client_signature, request->proof->data,
+                               request->proof->used))
+               return TRUE;
+
+       return FALSE;
+}
+
+static void credentials_callback(enum passdb_result result,
+                                const unsigned char *credentials, size_t size,
+                                struct auth_request *auth_request)
+{
+       struct scram_auth_request *request =
+               (struct scram_auth_request *)auth_request;
+       const char *server_final_message;
+
+       switch (result) {
+       case PASSDB_RESULT_OK:
+               if (!verify_credentials(request, credentials, size)) {
+                       auth_request_log_info(auth_request, "scram-sha-1",
+                                       "password mismatch");
+                       auth_request_fail(auth_request);
+               } else {
+                       request->authenticated = TRUE;
+                       server_final_message = get_scram_server_final(request);
+                       auth_request_handler_reply_continue(auth_request,
+                                       server_final_message,
+                                       strlen(server_final_message));
+               }
+               break;
+       case PASSDB_RESULT_INTERNAL_FAILURE:
+               auth_request_internal_failure(auth_request);
+               break;
+       default:
+               auth_request_fail(auth_request);
+               break;
+       }
+}
+
+static bool parse_scram_client_final(struct scram_auth_request *request,
+                                    const unsigned char *data,
+                                    size_t size ATTR_UNUSED,
+                                    const char **error)
+{
+       const char **fields;
+       unsigned int field_count;
+       const char *cbind_input;
+       string_t *str;
+
+       fields = t_strsplit((const char*)data, ",");
+       field_count = str_array_length(fields);
+
+       if (field_count < 3) {
+               *error = "Invalid final client message";
+               return FALSE;
+       }
+
+       cbind_input = t_strconcat(request->gs2_cbind_flag, ",,", NULL);
+       str = t_str_new(MAX_BASE64_ENCODED_SIZE(strlen(cbind_input)));
+       base64_encode(cbind_input, strlen(cbind_input), str);
+
+       if (strcmp(fields[0], t_strconcat("c=", str_c(str), NULL))) {
+               *error = "Invalid channel binding data";
+               return FALSE;
+       }
+
+       if (strcmp(fields[1], t_strconcat("r=", request->cnonce,
+                                       request->snonce, NULL))) {
+               *error = "Wrong nonce";
+               return FALSE;
+       }
+
+       if (fields[field_count-1][0] == 'p') {
+               size_t len = strlen(&fields[field_count-1][2]);
+
+               request->proof = buffer_create_dynamic(request->pool,
+                               MAX_BASE64_DECODED_SIZE(len));
+
+               if ((base64_decode(&fields[field_count-1][2], len, NULL,
+                                               request->proof) < 0)
+                               || (request->proof->used != SHA1_RESULTLEN)) {
+                       *error = "Invalid base64 encoding "
+                               "or length for ClientProof";
+                       return FALSE;
+               }
+       } else {
+               *error = "Invalid ClientProof";
+               return FALSE;
+       }
+
+       str_array_remove(fields, fields[field_count-1]);
+       request->client_final_message_without_proof = p_strdup(request->pool,
+                       t_strarray_join(fields, ","));
+
+       auth_request_lookup_credentials(&request->auth_request, "PLAIN",
+                       credentials_callback);
+
+       return TRUE;
+}
+
+static void mech_scram_sha1_auth_continue(struct auth_request *auth_request,
+                                         const unsigned char *data,
+                                         size_t data_size)
+{
+       struct scram_auth_request *request =
+               (struct scram_auth_request *)auth_request;
+       const char *error = NULL;
+
+       if (request->authenticated) {
+               /* authentication is done, we were just waiting the last (empty)
+                  client response */
+               auth_request_success(auth_request, NULL, 0);
+               return;
+       }
+
+       if (!request->client_first_message_bare) {
+               /* Received client-first-message */
+               if (parse_scram_client_first(request, data,
+                                       data_size, &error)) {
+                       request->server_first_message = p_strdup(request->pool,
+                                       get_scram_server_first(request));
+                       auth_request_handler_reply_continue(auth_request,
+                                       request->server_first_message,
+                                       strlen(request->server_first_message));
+                       return;
+               }
+       } else {
+               /* Received client-final-message */
+               if (parse_scram_client_final(request, data, data_size, &error))
+                       return;
+       }
+
+       if (error == NULL)
+               error = "authentication failed";
+
+        auth_request_log_info(auth_request, "scram-sha-1", "%s", error);
+       auth_request_fail(auth_request);
+}
+
+static struct auth_request *mech_scram_sha1_auth_new(void)
+{
+       struct scram_auth_request *request;
+       pool_t pool;
+
+       pool = pool_alloconly_create("scram_sha1_auth_request", 2048);
+       request = p_new(pool, struct scram_auth_request, 1);
+       request->pool = pool;
+
+       request->client_first_message_bare = NULL;
+
+       request->auth_request.pool = pool;
+       return &request->auth_request;
+}
+
+const struct mech_module mech_scram_sha1 = {
+       "SCRAM-SHA-1",
+
+       .flags = MECH_SEC_MUTUAL_AUTH,
+       .passdb_need = MECH_PASSDB_NEED_LOOKUP_CREDENTIALS,
+
+       mech_scram_sha1_auth_new,
+       mech_generic_auth_initial,
+       mech_scram_sha1_auth_continue,
+       mech_generic_auth_free
+};
diff --git a/src/auth/mech.c b/src/auth/mech.c
--- a/src/auth/mech.c
+++ b/src/auth/mech.c
@@ -70,6 +70,7 @@
 extern const struct mech_module mech_external;
 extern const struct mech_module mech_ntlm;
 extern const struct mech_module mech_otp;
+extern const struct mech_module mech_scram_sha1;
 extern const struct mech_module mech_skey;
 extern const struct mech_module mech_rpa;
 extern const struct mech_module mech_anonymous;
@@ -172,6 +173,7 @@
 #endif
        }
        mech_register_module(&mech_otp);
+       mech_register_module(&mech_scram_sha1);
        mech_register_module(&mech_skey);
        mech_register_module(&mech_rpa);
        mech_register_module(&mech_anonymous);

Reply via email to