[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