[Dovecot] [PATCH] Add SCRAM-SHA-1 password scheme
Florian Zeitz
florob at babelmonkeys.de
Wed Oct 3 00:05:56 EEST 2012
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
-------------- next part --------------
# HG changeset patch
# User Florian Zeitz <florob at babelmonkeys.de>
# 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 <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 "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 <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-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);
More information about the dovecot
mailing list