Hello,

attached is an hg export on top of the current dovecot-2.2 branch, which
adds support for a SCRAM-SHA-1 password scheme.

Ideally I'd want doveadm pw's rounds flag to apply to this, but that's
currently specific to the crypt password scheme, so I left it out for now.

Regards,
Florian Zeitz
# HG changeset patch
# User Florian Zeitz <[email protected]>
# Date 1348017219 -7200
# Node ID 21a0d1b4daa7bb924f1666f0bb7c7e697a19c950
# Parent  8802322d72573ee17c52ce5e972e77e6f8ad69d1
auth: Add and use SCRAM-SHA-1 password scheme

diff --git a/src/auth/Makefile.am b/src/auth/Makefile.am
--- a/src/auth/Makefile.am
+++ b/src/auth/Makefile.am
@@ -44,6 +44,7 @@
        password-scheme.c \
        password-scheme-crypt.c \
        password-scheme-md5crypt.c \
+       password-scheme-scram.c \
        password-scheme-otp.c \
        password-scheme-rpa.c
 
diff --git a/src/auth/mech-scram-sha1.c b/src/auth/mech-scram-sha1.c
--- a/src/auth/mech-scram-sha1.c
+++ b/src/auth/mech-scram-sha1.c
@@ -1,11 +1,13 @@
 /*
  * SCRAM-SHA-1 SASL authentication, see RFC-5802
  *
- * Copyright (c) 2011 Florian Zeitz <[email protected]>
+ * Copyright (c) 2011-2012 Florian Zeitz <[email protected]>
  *
  * This software is released under the MIT license.
  */
 
+#include <stdlib.h>
+
 #include "auth-common.h"
 #include "base64.h"
 #include "buffer.h"
@@ -29,45 +31,22 @@
 
        /* sent: */
        const char *server_first_message;
-       unsigned char salt[16];
-       unsigned char salted_password[SHA1_RESULTLEN];
+       const char *snonce;
 
        /* received: */
        const char *gs2_cbind_flag;
        const char *cnonce;
-       const char *snonce;
        const char *client_first_message_bare;
        const char *client_final_message_without_proof;
        buffer_t *proof;
+
+       /* stored */
+       buffer_t *stored_key;
+       buffer_t *server_key;
 };
 
-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_context ctx;
-       unsigned char U[SHA1_RESULTLEN];
-       unsigned int j, k;
-
-       /* Calculate U1 */
-       hmac_init(&ctx, str, str_size, &hash_method_sha1);
-       hmac_update(&ctx, salt, salt_size);
-       hmac_update(&ctx, "\0\0\0\1", 4);
-       hmac_final(&ctx, U);
-
-       memcpy(result, U, SHA1_RESULTLEN);
-
-       /* Calculate U2 to Ui and Hi */
-       for (j = 2; j <= i; j++) {
-               hmac_init(&ctx, str, str_size, &hash_method_sha1);
-               hmac_update(&ctx, U, sizeof(U));
-               hmac_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)
+static const char *get_scram_server_first(struct scram_auth_request *request,
+                                         int iter, const char *salt)
 {
        unsigned char snonce[SCRAM_SERVER_NONCE_LEN+1];
        string_t *str;
@@ -84,12 +63,9 @@
        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)));
-       str_printfa(str, "r=%s%s,s=", request->cnonce, request->snonce);
-       base64_encode(request->salt, sizeof(request->salt), str);
-       str_printfa(str, ",i=%d", SCRAM_ITERATE_COUNT);
+       str = t_str_new(sizeof(snonce));
+       str_printfa(str, "r=%s%s,s=%s,i=%d", request->cnonce, request->snonce,
+                   salt, iter);
        return str_c(str);
 }
 
@@ -105,15 +81,8 @@
                        request->server_first_message, ",",
                        request->client_final_message_without_proof, NULL);
 
-       hmac_init(&ctx, request->salted_password,
-                 sizeof(request->salted_password), &hash_method_sha1);
-       hmac_update(&ctx, "Server Key", 10);
-       hmac_final(&ctx, server_key);
-
-       safe_memset(request->salted_password, 0,
-                   sizeof(request->salted_password));
-
-       hmac_init(&ctx, server_key, sizeof(server_key), &hash_method_sha1);
+       hmac_init(&ctx, request->server_key->data, request->server_key->used,
+                 &hash_method_sha1);
        hmac_update(&ctx, auth_message, strlen(auth_message));
        hmac_final(&ctx, server_signature);
 
@@ -211,8 +180,7 @@
        return TRUE;
 }
 
-static bool verify_credentials(struct scram_auth_request *request,
-                              const unsigned char *credentials, size_t size)
+static bool verify_credentials(struct scram_auth_request *request)
 {
        struct hmac_context ctx;
        const char *auth_message;
@@ -221,54 +189,76 @@
        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),
-          SCRAM_ITERATE_COUNT, request->salted_password);
-
-       hmac_init(&ctx, request->salted_password,
-                 sizeof(request->salted_password), &hash_method_sha1);
-       hmac_update(&ctx, "Client Key", 10);
-       hmac_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_init(&ctx, stored_key, sizeof(stored_key), &hash_method_sha1);
+       hmac_init(&ctx, request->stored_key->data, request->stored_key->used,
+                 &hash_method_sha1);
        hmac_update(&ctx, auth_message, strlen(auth_message));
        hmac_final(&ctx, client_signature);
 
        for (i = 0; i < sizeof(client_signature); i++)
-               client_signature[i] ^= client_key[i];
+               client_key[i] =
+                       ((char*)request->proof->data)[i] ^ client_signature[i];
+
+       sha1_get_digest(client_key, sizeof(client_key), stored_key);
 
        safe_memset(client_key, 0, sizeof(client_key));
-       safe_memset(stored_key, 0, sizeof(stored_key));
+       safe_memset(client_signature, 0, sizeof(client_signature));
 
-       return memcmp(client_signature, request->proof->data,
-                     request->proof->used) == 0;
+       return memcmp(stored_key, request->stored_key->data,
+                     request->stored_key->used) == 0;
 }
 
 static void credentials_callback(enum passdb_result result,
                                 const unsigned char *credentials, size_t size,
                                 struct auth_request *auth_request)
 {
+       const char *const *fields;
+       size_t len;
+       int iter;
+       const char *salt;
        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)) {
+               fields = t_strsplit(t_strndup(credentials, size), ",");
+
+               iter = atoi(fields[0]);
+               salt = fields[1];
+
+               len = strlen(fields[2]);
+               request->stored_key = buffer_create_dynamic(request->pool,
+                                       MAX_BASE64_DECODED_SIZE(len));
+               if (base64_decode(fields[2], len, NULL,
+                                 request->stored_key) < 0) {
                        auth_request_log_info(auth_request, "scram-sha-1",
-                                             "password mismatch");
+                                             "Invalid base64 encoding"
+                                             "of StoredKey in passdb");
                        auth_request_fail(auth_request);
-               } else {
-                       server_final_message = get_scram_server_final(request);
-                       auth_request_success(auth_request, server_final_message,
-                                            strlen(server_final_message));
+                       break;
                }
+
+               len = strlen(fields[3]);
+               request->server_key = buffer_create_dynamic(request->pool,
+                                       MAX_BASE64_DECODED_SIZE(len));
+               if (base64_decode(fields[3], len, NULL,
+                                 request->server_key) < 0) {
+                       auth_request_log_info(auth_request, "scram-sha-1",
+                                             "Invalid base64 encoding"
+                                             "of ServerKey in passdb");
+                       auth_request_fail(auth_request);
+                       break;
+               }
+
+               request->server_first_message = p_strdup(request->pool,
+                       get_scram_server_first(request, iter, salt));
+
+               auth_request_handler_reply_continue(auth_request,
+                                       request->server_first_message,
+                                       strlen(request->server_first_message));
                break;
        case PASSDB_RESULT_INTERNAL_FAILURE:
                auth_request_internal_failure(auth_request);
@@ -333,8 +323,6 @@
        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;
 }
 
@@ -345,22 +333,35 @@
        struct scram_auth_request *request =
                (struct scram_auth_request *)auth_request;
        const char *error = NULL;
+       const char *server_final_message;
+       int len;
 
        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));
+                       auth_request_lookup_credentials(&request->auth_request,
+                                                       "SCRAM-SHA1",
+                                                       credentials_callback);
                        return;
                }
        } else {
                /* Received client-final-message */
-               if (parse_scram_client_final(request, data, data_size, &error))
-                       return;
+               if (parse_scram_client_final(request, data, data_size,
+                                            &error)) {
+                       if (!verify_credentials(request)) {
+                               auth_request_log_info(auth_request,
+                                                     "scram-sha-1",
+                                                     "password mismatch");
+                       } else {
+                               server_final_message =
+                                       get_scram_server_final(request);
+                               len = strlen(server_final_message);
+                               auth_request_success(auth_request,
+                                                    server_final_message, len);
+                               return;
+                       }
+               }
        }
 
        if (error != NULL)
diff --git a/src/auth/password-scheme-scram.c b/src/auth/password-scheme-scram.c
new file mode 100644
--- /dev/null
+++ b/src/auth/password-scheme-scram.c
@@ -0,0 +1,139 @@
+/*
+ * SCRAM-SHA-1 SASL authentication, see RFC-5802
+ *
+ * Copyright (c) 2012 Florian Zeitz <[email protected]>
+ *
+ * This software is released under the MIT license.
+ */
+
+#include <stdlib.h>
+
+#include "lib.h"
+#include "safe-memset.h"
+#include "base64.h"
+#include "buffer.h"
+#include "hmac.h"
+#include "randgen.h"
+#include "sha1.h"
+#include "str.h"
+#include "password-scheme.h"
+
+/* SCRAM hash iteration count. RFC says it SHOULD be at least 4096 */
+#define SCRAM_ITERATE_COUNT 4096
+
+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_context ctx;
+       unsigned char U[SHA1_RESULTLEN];
+       unsigned int j, k;
+
+       /* Calculate U1 */
+       hmac_init(&ctx, str, str_size, &hash_method_sha1);
+       hmac_update(&ctx, salt, salt_size);
+       hmac_update(&ctx, "\0\0\0\1", 4);
+       hmac_final(&ctx, U);
+
+       memcpy(result, U, SHA1_RESULTLEN);
+
+       /* Calculate U2 to Ui and Hi */
+       for (j = 2; j <= i; j++) {
+               hmac_init(&ctx, str, str_size, &hash_method_sha1);
+               hmac_update(&ctx, U, sizeof(U));
+               hmac_final(&ctx, U);
+               for (k = 0; k < SHA1_RESULTLEN; k++)
+                       result[k] ^= U[k];
+       }
+}
+
+/* password string format: iter,salt,stored_key,server_key */
+
+int scram_sha1_verify(const char *plaintext, const char *user ATTR_UNUSED,
+                     const unsigned char *raw_password, size_t size,
+                     const char **error_r ATTR_UNUSED)
+{
+       struct hmac_context ctx;
+       string_t *str;
+       const char *const *fields;
+       int iter;
+       const unsigned char *salt;
+       size_t salt_len;
+       unsigned char salted_password[SHA1_RESULTLEN];
+       unsigned char client_key[SHA1_RESULTLEN];
+       unsigned char stored_key[SHA1_RESULTLEN];
+
+       fields = t_strsplit(t_strndup(raw_password, size), ",");
+       iter = atoi(fields[0]);
+       salt = buffer_get_data(t_base64_decode_str(fields[1]), &salt_len);
+       str = t_str_new(strlen(fields[2]));
+
+       /* FIXME: credentials should be SASLprepped UTF8 data here */
+       Hi((const unsigned char *)plaintext, strlen(plaintext), salt, salt_len,
+          iter, salted_password);
+
+       /* Calculate ClientKey */
+       hmac_init(&ctx, salted_password, sizeof(salted_password),
+                 &hash_method_sha1);
+       hmac_update(&ctx, "Client Key", 10);
+       hmac_final(&ctx, client_key);
+
+       /* Calculate StoredKey */
+       sha1_get_digest(client_key, sizeof(client_key), stored_key);
+       base64_encode(stored_key, sizeof(stored_key), str);
+
+       safe_memset(salted_password, 0, sizeof(salted_password));
+       safe_memset(client_key, 0, sizeof(client_key));
+       safe_memset(stored_key, 0, sizeof(stored_key));
+
+       return strcmp(fields[2], str_c(str)) == 0 ? 1 : 0;
+}
+
+void scram_sha1_generate(const char *plaintext, const char *user ATTR_UNUSED,
+                        const unsigned char **raw_password_r, size_t *size_r)
+{
+       string_t *str;
+       struct hmac_context ctx;
+       unsigned char salt[16];
+       unsigned char salted_password[SHA1_RESULTLEN];
+       unsigned char client_key[SHA1_RESULTLEN];
+       unsigned char server_key[SHA1_RESULTLEN];
+       unsigned char stored_key[SHA1_RESULTLEN];
+
+       random_fill(salt, sizeof(salt));
+
+       str = t_str_new(MAX_BASE64_ENCODED_SIZE(sizeof(salt)));
+       str_printfa(str, "%i,", SCRAM_ITERATE_COUNT);
+       base64_encode(salt, sizeof(salt), str);
+
+       /* FIXME: credentials should be SASLprepped UTF8 data here */
+       Hi((const unsigned char *)plaintext, strlen(plaintext), salt,
+          sizeof(salt), SCRAM_ITERATE_COUNT, salted_password);
+
+       /* Calculate ClientKey */
+       hmac_init(&ctx, salted_password, sizeof(salted_password),
+                 &hash_method_sha1);
+       hmac_update(&ctx, "Client Key", 10);
+       hmac_final(&ctx, client_key);
+
+       /* Calculate StoredKey */
+       sha1_get_digest(client_key, sizeof(client_key), stored_key);
+       str_append_c(str, ',');
+       base64_encode(stored_key, sizeof(stored_key), str);
+
+       /* Calculate ServerKey */
+       hmac_init(&ctx, salted_password, sizeof(salted_password),
+                 &hash_method_sha1);
+       hmac_update(&ctx, "Server Key", 10);
+       hmac_final(&ctx, server_key);
+       str_append_c(str, ',');
+       base64_encode(server_key, sizeof(server_key), str);
+
+       safe_memset(salted_password, 0, sizeof(salted_password));
+       safe_memset(client_key, 0, sizeof(client_key));
+       safe_memset(server_key, 0, sizeof(server_key));
+       safe_memset(stored_key, 0, sizeof(stored_key));
+
+       *raw_password_r = (const unsigned char *)str_c(str);
+       *size_r = str_len(str);
+}
diff --git a/src/auth/password-scheme.c b/src/auth/password-scheme.c
--- a/src/auth/password-scheme.c
+++ b/src/auth/password-scheme.c
@@ -822,6 +822,8 @@
        { "PLAIN-TRUNC", PW_ENCODING_NONE, 0, plain_trunc_verify, 
plain_generate },
        { "CRAM-MD5", PW_ENCODING_HEX, CRAM_MD5_CONTEXTLEN,
          NULL, cram_md5_generate },
+       { "SCRAM-SHA1", PW_ENCODING_NONE, 0, scram_sha1_verify,
+         scram_sha1_generate},
        { "HMAC-MD5", PW_ENCODING_HEX, CRAM_MD5_CONTEXTLEN,
          NULL, cram_md5_generate },
        { "DIGEST-MD5", PW_ENCODING_HEX, MD5_RESULTLEN,
diff --git a/src/auth/password-scheme.h b/src/auth/password-scheme.h
--- a/src/auth/password-scheme.h
+++ b/src/auth/password-scheme.h
@@ -85,6 +85,12 @@
                 const unsigned char *raw_password, size_t size,
                 const char **error_r);
 
+int scram_sha1_verify(const char *plaintext, const char *user ATTR_UNUSED,
+                     const unsigned char *raw_password, size_t size,
+                     const char **error_r ATTR_UNUSED);
+void scram_sha1_generate(const char *plaintext, const char *user ATTR_UNUSED,
+                        const unsigned char **raw_password_r, size_t *size_r);
+
 /* check wich of the algorithms Blowfisch, SHA-256 and SHA-512 are
    supported by the used libc's/glibc's crypt() */
 void password_scheme_register_crypt(void);

Reply via email to