[Dovecot] [PATCH] Add SCRAM-SHA-1 password scheme

Florian Zeitz florob at babelmonkeys.de
Wed Oct 3 03:10:41 EEST 2012


Am 03.10.2012 01:58, schrieb Timo Sirainen:
> On 3.10.2012, at 2.54, Florian Zeitz wrote:
> 
>> Am 03.10.2012 01:42, schrieb Timo Sirainen:
>>> On 3.10.2012, at 0.05, Florian Zeitz wrote:
>>>
>>>> attached is an hg export on top of the current dovecot-2.2 branch, which
>>>> adds support for a SCRAM-SHA-1 password scheme.
>>>
>>> Oh, and SCRAM-SHA1 or SCRAM-SHA-1? I'd think SCRAM-SHA1 as the scheme is now called, but elsewhere in the code (including user-visible strings) it says SCRAM-SHA-1.
>>>
>> Well, I usually prefer SCRAM-SHA-1, as that is how it is called in the
>> RFC, and SHA-1 is the hash name registered with IANA [1].
>> I did call the password scheme SCRAM-SHA1 to be consistent with other
>> current password schemes. I'm not 100% sure which one to use, or whether
>> a mix might even be the way to go ("correct" messages, but minimum user
>> confusion for password schemes).
> 
> Hmm. Probably not worth it to have both SCRAM-SHA1 and SCRAM-SHA-1. And now I see that the user-visible strings are about SCRAM-SHA-1 mechanism, not the hash. So yeah, I guess the best way to avoid confusion is to call it SCRAM-SHA-1 everywhere.
> 
Seems sensible.

Attached is a new export incorporating your feedback.
The iteration count is now limited to [4096, INT_MAX]. The lower bound
is a recommendation of the RFC.
-------------- next part --------------
# HG changeset patch
# User Florian Zeitz <florob at babelmonkeys.de>
# Date 1348017219 -7200
# Node ID a0b0eece12335905500631477ec1d6ab31014469
# Parent  99843f74422ac68bfde86e9cee6920164eae4d5d
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,14 @@
 /*
  * SCRAM-SHA-1 SASL authentication, see RFC-5802
  *
- * Copyright (c) 2011 Florian Zeitz <florob at babelmonkeys.de>
+ * Copyright (c) 2011-2012 Florian Zeitz <florob at babelmonkeys.de>
  *
  * This software is released under the MIT license.
  */
 
+#include <stdlib.h>
+#include <limits.h>
+
 #include "auth-common.h"
 #include "base64.h"
 #include "buffer.h"
@@ -15,6 +18,7 @@
 #include "safe-memset.h"
 #include "str.h"
 #include "strfuncs.h"
+#include "strnum.h"
 #include "mech.h"
 
 /* SCRAM hash iteration count. RFC says it SHOULD be at least 4096 */
@@ -29,45 +33,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 +65,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 +83,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 +182,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 +191,90 @@
 	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;
+	unsigned 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), ",");
+
+		if (str_array_length(fields) != 4) {
 			auth_request_log_info(auth_request, "scram-sha-1",
-					      "password mismatch");
+					      "Invalid passdb entry");
 			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;
 		}
+
+		if (str_to_uint(fields[0], &iter) || (iter < 4096) ||
+		    (iter > INT_MAX)) {
+			auth_request_log_info(auth_request, "scram-sha-1",
+					      "Invalid iteration count");
+			auth_request_fail(auth_request);
+			break;
+		}
+
+		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",
+					      "Invalid base64 encoding"
+					      "of StoredKey in passdb");
+			auth_request_fail(auth_request);
+			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 +339,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 +349,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-SHA-1",
+							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 <florob at babelmonkeys.de>
+ *
+ * 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-SHA-1", 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);


More information about the dovecot mailing list