# HG changeset patch # User Florian Zeitz # 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 + * Copyright (c) 2011-2012 Florian Zeitz * * This software is released under the MIT license. */ +#include + #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 + * + * This software is released under the MIT license. + */ + +#include + +#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);