From ea719cd02ac1cc43473d8e5789bdcf3263b57e7e Mon Sep 17 00:00:00 2001 From: Dmitry Podgorny Date: Sat, 30 Aug 2014 15:45:10 +0300 Subject: [PATCH] SCRAM-SHA-1 authentication mechanism support --- ChangeLog | 6 +- Makefile.am | 5 +- src/auth.c | 165 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/common.h | 1 + src/sasl.c | 110 ++++++++++++++++++++++++++++++++++ src/sasl.h | 3 + src/scram.c | 146 +++++++++++++++++++++++++++++++++++++++++++++ src/scram.h | 38 ++++++++++++ 8 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 src/scram.c create mode 100644 src/scram.h diff --git a/ChangeLog b/ChangeLog index 1569633..bfedde7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,5 @@ -0.8.5 - - libtoolize to generate .so +TBA + - SCRAM-SHA-1 authentication mechanism +0.8.5 + - libtoolize to generate .so diff --git a/Makefile.am b/Makefile.am index 09f5880..207bffa 100644 --- a/Makefile.am +++ b/Makefile.am @@ -17,11 +17,12 @@ libstrophe_la_CFLAGS=$(STROPHE_FLAGS) $(PARSER_CFLAGS) libstrophe_la_LDFLAGS=$(SSL_LIBS) $(PARSER_LIBS) libstrophe_la_SOURCES = src/auth.c src/conn.c src/ctx.c \ src/event.c src/handler.c src/hash.c \ - src/jid.c src/md5.c src/sasl.c src/sha1.c \ + src/jid.c src/md5.c src/sasl.c src/scram.c src/sha1.c \ src/snprintf.c src/sock.c src/stanza.c src/thread.c \ src/tls_openssl.c src/util.c \ src/common.h src/hash.h src/md5.h src/ostypes.h src/parser.h \ - src/sasl.h src/sha1.h src/sock.h src/thread.h src/tls.h src/util.h + src/sasl.h src/scram.h src/sha1.h src/sock.h src/thread.h src/tls.h \ + src/util.h if PARSER_EXPAT libstrophe_la_SOURCES += src/parser_expat.c diff --git a/src/auth.c b/src/auth.c index 7530b27..8d1b947 100644 --- a/src/auth.c +++ b/src/auth.c @@ -19,6 +19,7 @@ #include #include #include +#include #include "strophe.h" #include "common.h" @@ -74,6 +75,10 @@ static int _handle_digestmd5_challenge(xmpp_conn_t * const conn, static int _handle_digestmd5_rspauth(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata); +static int _handle_scram_sha1_challenge(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata); +static char *_make_scram_sha1_init_msg(xmpp_conn_t * const conn); static int _handle_missing_features_sasl(xmpp_conn_t * const conn, void * const userdata); @@ -228,6 +233,8 @@ static int _handle_features(xmpp_conn_t * const conn, conn->sasl_support |= SASL_MASK_PLAIN; else if (strcasecmp(text, "DIGEST-MD5") == 0) conn->sasl_support |= SASL_MASK_DIGESTMD5; + else if (strcasecmp(text, "SCRAM-SHA-1") == 0) + conn->sasl_support |= SASL_MASK_SCRAMSHA1; else if (strcasecmp(text, "ANONYMOUS") == 0) conn->sasl_support |= SASL_MASK_ANONYMOUS; @@ -415,6 +422,118 @@ static int _handle_digestmd5_rspauth(xmpp_conn_t * const conn, return 1; } +/* handle the challenge phase of SCRAM-SHA-1 auth */ +static int _handle_scram_sha1_challenge(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + char *text; + char *response; + xmpp_stanza_t *auth, *authdata; + char *name; + char *challenge; + + name = xmpp_stanza_get_name(stanza); + xmpp_debug(conn->ctx, "xmpp", + "handle SCRAM-SHA-1 (challenge) called for %s", name); + + if (strcmp(name, "challenge") == 0) { + text = xmpp_stanza_get_text(stanza); + + challenge = (char *)base64_decode(conn->ctx, text, strlen(text)); + if (!challenge) { + disconnect_mem_error(conn); + return 0; + } + + /* userdata is scram_init allocated in _auth() */ + response = sasl_scram_sha1(conn->ctx, challenge, (char *)userdata, + conn->jid, conn->pass); + if (!response) { + xmpp_free(conn->ctx, challenge); + disconnect_mem_error(conn); + return 0; + } + + xmpp_free(conn->ctx, challenge); + + auth = xmpp_stanza_new(conn->ctx); + if (!auth) { + xmpp_free(conn->ctx, response); + disconnect_mem_error(conn); + return 0; + } + xmpp_stanza_set_name(auth, "response"); + xmpp_stanza_set_ns(auth, XMPP_NS_SASL); + + authdata = xmpp_stanza_new(conn->ctx); + if (!authdata) { + xmpp_free(conn->ctx, response); + xmpp_stanza_release(auth); + disconnect_mem_error(conn); + return 0; + } + + xmpp_stanza_set_text(authdata, response); + xmpp_free(conn->ctx, response); + + xmpp_stanza_add_child(auth, authdata); + xmpp_stanza_release(authdata); + + xmpp_send(conn, auth); + xmpp_stanza_release(auth); + + } else { + return _handle_sasl_result(conn, stanza, "SCRAM-SHA-1"); + } + + return 1; +} + +static char *_get_nonce(xmpp_ctx_t *ctx) +{ + unsigned char buffer[sizeof(clock_t) + sizeof(time_t)] = {0}; + clock_t ticks = clock(); + time_t t; + + if (ticks != (clock_t)-1) { + *(clock_t *)buffer = ticks; + } + t = time((time_t *)(buffer + sizeof(clock_t))); + if (t == (time_t)-1) { + *(time_t *)(buffer + sizeof(clock_t)) = (time_t)rand(); + } + + return base64_encode(ctx, buffer, sizeof(buffer)); +} + +static char *_make_scram_sha1_init_msg(xmpp_conn_t * const conn) +{ + size_t message_len; + char *node; + char *message; + char *nonce; + + node = xmpp_jid_node(conn->ctx, conn->jid); + if (!node) { + return NULL; + } + + nonce = _get_nonce(conn->ctx); + if (!nonce) { + return NULL; + } + message_len = strlen(node) + strlen(nonce) + 8 + 1; + message = xmpp_alloc(conn->ctx, message_len); + if (message) { + xmpp_snprintf(message, message_len, "n,,n=%s,r=%s", node, nonce); + xmpp_free(conn->ctx, node); + } + xmpp_free(conn->ctx, nonce); + + return message; +} + static xmpp_stanza_t *_make_starttls(xmpp_conn_t * const conn) { xmpp_stanza_t *starttls; @@ -454,6 +573,7 @@ static void _auth(xmpp_conn_t * const conn) { xmpp_stanza_t *auth, *authdata, *query, *child, *iq; char *str, *authid; + char *scram_init; int anonjid; /* if there is no node in conn->jid, we assume anonymous connect */ @@ -516,6 +636,51 @@ static void _auth(xmpp_conn_t * const conn) xmpp_error(conn->ctx, "auth", "No node in JID, and SASL ANONYMOUS unsupported."); xmpp_disconnect(conn); + } else if (conn->sasl_support & SASL_MASK_SCRAMSHA1) { + auth = _make_sasl_auth(conn, "SCRAM-SHA-1"); + if (!auth) { + disconnect_mem_error(conn); + return; + } + + /* don't free scram_init on success */ + scram_init = _make_scram_sha1_init_msg(conn); + if (!scram_init) { + xmpp_stanza_release(auth); + disconnect_mem_error(conn); + return; + } + + str = (char *)base64_encode(conn->ctx, (unsigned char *)scram_init, + strlen(scram_init)); + if (!str) { + xmpp_free(conn->ctx, scram_init); + xmpp_stanza_release(auth); + disconnect_mem_error(conn); + return; + } + + authdata = xmpp_stanza_new(conn->ctx); + if (!authdata) { + xmpp_free(conn->ctx, str); + xmpp_free(conn->ctx, scram_init); + xmpp_stanza_release(auth); + disconnect_mem_error(conn); + return; + } + xmpp_stanza_set_text(authdata, str); + xmpp_free(conn->ctx, str); + xmpp_stanza_add_child(auth, authdata); + xmpp_stanza_release(authdata); + + handler_add(conn, _handle_scram_sha1_challenge, + XMPP_NS_SASL, NULL, NULL, (void *)scram_init); + + xmpp_send(conn, auth); + xmpp_stanza_release(auth); + + /* SASL SCRAM-SHA-1 was tried, unset flag */ + conn->sasl_support &= ~SASL_MASK_SCRAMSHA1; } else if (conn->sasl_support & SASL_MASK_DIGESTMD5) { auth = _make_sasl_auth(conn, "DIGEST-MD5"); if (!auth) { diff --git a/src/common.h b/src/common.h index 9434e6f..822707f 100644 --- a/src/common.h +++ b/src/common.h @@ -147,6 +147,7 @@ struct _xmpp_handlist_t { #define SASL_MASK_PLAIN 0x01 #define SASL_MASK_DIGESTMD5 0x02 #define SASL_MASK_ANONYMOUS 0x04 +#define SASL_MASK_SCRAMSHA1 0x08 typedef void (*xmpp_open_handler)(xmpp_conn_t * const conn); diff --git a/src/sasl.c b/src/sasl.c index 6fe0e00..f238e1c 100644 --- a/src/sasl.c +++ b/src/sasl.c @@ -16,12 +16,15 @@ * SASL authentication. */ +#include #include #include "strophe.h" #include "common.h" #include "sasl.h" #include "md5.h" +#include "sha1.h" +#include "scram.h" /* make sure the stdint.h types are available */ #if defined(_MSC_VER) /* Microsoft Visual C++ */ @@ -360,6 +363,113 @@ char *sasl_digest_md5(xmpp_ctx_t *ctx, const char *challenge, return response; } +/** generate auth response string for the SASL SCRAM-SHA-1 mechanism */ +char *sasl_scram_sha1(xmpp_ctx_t *ctx, const char *challenge, + const char *first_bare, const char *jid, + const char *password) +{ + uint8_t key[SHA1_DIGEST_SIZE]; + uint8_t sign[SHA1_DIGEST_SIZE]; + char *r = NULL; + char *s = NULL; + char *i = NULL; + char *sval; + size_t sval_len; + long ival; + char *tmp; + char *ptr; + char *saveptr = NULL; + char *response; + char *auth; + char *response_b64; + char *sign_b64; + char *result = NULL; + size_t response_len; + size_t auth_len; + int j; + + tmp = xmpp_strdup(ctx, challenge); + if (!tmp) { + return NULL; + } + + ptr = strtok_r(tmp, ",", &saveptr); + while (ptr) { + if (strncmp(ptr, "r=", 2) == 0) { + r = ptr; + } else if (strncmp(ptr, "s=", 2) == 0) { + s = ptr + 2; + } else if (strncmp(ptr, "i=", 2) == 0) { + i = ptr + 2; + } + ptr = strtok_r(NULL, ",", &saveptr); + } + + if (!r || !s || !i) { + goto out; + } + + sval = (char *)base64_decode(ctx, s, strlen(s)); + if (!sval) { + goto out; + } + sval_len = base64_decoded_len(ctx, s, strlen(s)); + ival = strtol(i, &saveptr, 10); + + auth_len = 10 + strlen(r) + strlen(first_bare) + strlen(challenge); + auth = xmpp_alloc(ctx, auth_len); + if (!auth) { + goto out_sval; + } + + response_len = 39 + strlen(r); + response = xmpp_alloc(ctx, response_len); + if (!response) { + goto out_auth; + } + + xmpp_snprintf(response, response_len, "c=biws,%s", r); + xmpp_snprintf(auth, auth_len, "%s,%s,%s", first_bare + 3, challenge, + response); + + SCRAM_SHA1_ClientKey((uint8_t *)password, strlen(password), + (uint8_t *)sval, sval_len, (uint32_t)ival, key); + SCRAM_SHA1_ClientSignature(key, (uint8_t *)auth, strlen(auth), sign); + for (j = 0; j < SHA1_DIGEST_SIZE; j++) { + sign[j] ^= key[j]; + } + + sign_b64 = base64_encode(ctx, sign, sizeof(sign)); + if (!sign_b64) { + goto out_response; + } + + if (strlen(response) + strlen(sign_b64) + 3 + 1 > response_len) { + xmpp_free(ctx, sign_b64); + goto out_response; + } + strcat(response, ",p="); + strcat(response, sign_b64); + xmpp_free(ctx, sign_b64); + + response_b64 = base64_encode(ctx, (unsigned char *)response, + strlen(response)); + if (!response_b64) { + goto out_response; + } + result = response_b64; + +out_response: + xmpp_free(ctx, response); +out_auth: + xmpp_free(ctx, auth); +out_sval: + xmpp_free(ctx, sval); +out: + xmpp_free(ctx, tmp); + return result; +} + /** Base64 encoding routines. Implemented according to RFC 3548 */ diff --git a/src/sasl.h b/src/sasl.h index 09d5b8d..1b9dec6 100644 --- a/src/sasl.h +++ b/src/sasl.h @@ -26,6 +26,9 @@ char *sasl_plain(xmpp_ctx_t *ctx, const char *authid, const char *password); char *sasl_digest_md5(xmpp_ctx_t *ctx, const char *challenge, const char *jid, const char *password); +char *sasl_scram_sha1(xmpp_ctx_t *ctx, const char *challenge, + const char *first_bare, const char *jid, + const char *password); /** Base64 encoding routines. Implemented according to RFC 3548 */ diff --git a/src/scram.c b/src/scram.c new file mode 100644 index 0000000..f31fc77 --- /dev/null +++ b/src/scram.c @@ -0,0 +1,146 @@ +/* scram.c + * strophe XMPP client library + * + * SCRAM-SHA1 helper functions according to RFC5802 + * HMAC-SHA1 implementation according to RFC2104 + * + * Copyright (C) 2013 Dmitry Podgorny + * + * This software is provided AS-IS with no warranty, either express + * or implied. + * + * This software is distributed under license and may not be copied, + * modified or distributed except as expressly authorized under the + * terms of the license contained in the file LICENSE.txt in this + * distribution. + */ + +/** @file + * SCRAM-SHA1 helper functions. + */ + +#include +#include +#include + +#include "sha1.h" + +#include "scram.h" + +/* block size for HMAC */ +#define BLOCK_SIZE 64 +#if BLOCK_SIZE < SHA1_DIGEST_SIZE +#error BLOCK_SIZE must not be less than SHA1_DIGEST_SIZE +#endif + +static const uint8_t ipad = 0x36; +static const uint8_t opad = 0x5C; + +static inline void SHA1(const uint8_t* data, size_t len, + uint8_t digest[SHA1_DIGEST_SIZE]) +{ + SHA1_CTX ctx; + SHA1_Init(&ctx); + SHA1_Update(&ctx, data, len); + SHA1_Final(&ctx, digest); +} + +static void HMAC_SHA1(const uint8_t *key, size_t key_len, + const uint8_t *text, size_t len, + uint8_t digest[SHA1_DIGEST_SIZE]) +{ + uint8_t key_pad[BLOCK_SIZE]; + uint8_t key_ipad[BLOCK_SIZE]; + uint8_t key_opad[BLOCK_SIZE]; + uint8_t sha_digest[SHA1_DIGEST_SIZE]; + int i; + SHA1_CTX ctx; + + memset(key_pad, 0, sizeof(key_pad)); + if (key_len <= BLOCK_SIZE) { + memcpy(key_pad, key, key_len); + } else { + /* according to RFC2104 */ + SHA1(key, key_len, key_pad); + } + + for (i = 0; i < BLOCK_SIZE; i++) { + key_ipad[i] = key_pad[i] ^ ipad; + key_opad[i] = key_pad[i] ^ opad; + } + + SHA1_Init(&ctx); + SHA1_Update(&ctx, key_ipad, BLOCK_SIZE); + SHA1_Update(&ctx, text, len); + SHA1_Final(&ctx, sha_digest); + + SHA1_Init(&ctx); + SHA1_Update(&ctx, key_opad, BLOCK_SIZE); + SHA1_Update(&ctx, sha_digest, SHA1_DIGEST_SIZE); + SHA1_Final(&ctx, digest); +} + +static void SCRAM_SHA1_Hi(const uint8_t *text, size_t len, + const uint8_t *salt, size_t salt_len, uint32_t i, + uint8_t digest[SHA1_DIGEST_SIZE]) +{ + int j, k; + uint8_t tmp[128]; + + static uint8_t int1[] = {0x0, 0x0, 0x0, 0x1}; + + /* assume salt + INT(1) isn't longer than sizeof(tmp) */ + assert(salt_len <= sizeof(tmp) - sizeof(int1)); + + memset(digest, 0, SHA1_DIGEST_SIZE); + if (i == 0) { + return; + } + + memcpy(tmp, salt, salt_len); + memcpy(&tmp[salt_len], int1, sizeof(int1)); + + /* 'text' for Hi is a 'key' for HMAC */ + HMAC_SHA1(text, len, tmp, salt_len + sizeof(int1), digest); + memcpy(tmp, digest, SHA1_DIGEST_SIZE); + + for (j = 1; j < i; j++) { + HMAC_SHA1(text, len, tmp, SHA1_DIGEST_SIZE, tmp); + for (k = 0; k < SHA1_DIGEST_SIZE; k++) { + digest[k] ^= tmp[k]; + } + } +} + +void SCRAM_SHA1_ClientKey(const uint8_t *password, size_t len, + const uint8_t *salt, size_t salt_len, uint32_t i, + uint8_t key[SHA1_DIGEST_SIZE]) +{ + uint8_t salted[SHA1_DIGEST_SIZE]; + + /* XXX: Normalize(password) is omitted */ + + SCRAM_SHA1_Hi(password, len, salt, salt_len, i, salted); + HMAC_SHA1(salted, SHA1_DIGEST_SIZE, (uint8_t *)"Client Key", + strlen("Client Key"), key); +} + +void SCRAM_SHA1_ClientSignature(const uint8_t ClientKey[SHA1_DIGEST_SIZE], + const uint8_t *AuthMessage, size_t len, + uint8_t sign[SHA1_DIGEST_SIZE]) +{ + uint8_t stored[SHA1_DIGEST_SIZE]; + + SHA1(ClientKey, SHA1_DIGEST_SIZE, stored); + HMAC_SHA1(stored, SHA1_DIGEST_SIZE, AuthMessage, len, sign); +} + +void SCRAM_SHA1_ClientProof(const uint8_t ClientKey[SHA1_DIGEST_SIZE], + const uint8_t ClientSignature[SHA1_DIGEST_SIZE], + uint8_t proof[SHA1_DIGEST_SIZE]) +{ + int i; + for (i = 0; i < SHA1_DIGEST_SIZE; i++) { + proof[i] = ClientKey[i] ^ ClientSignature[i]; + } +} diff --git a/src/scram.h b/src/scram.h new file mode 100644 index 0000000..c25814d --- /dev/null +++ b/src/scram.h @@ -0,0 +1,38 @@ +/* scram.h + * strophe XMPP client library -- SCRAM-SHA1 helper functions + * + * Copyright (C) 2013 Dmitry Podgorny + * + * This software is provided AS-IS with no warranty, either express + * or implied. + * + * This software is distributed under license and may not be copied, + * modified or distributed except as expressly authorized under the + * terms of the license contained in the file LICENSE.txt in this + * distribution. + */ + +/** @file + * SCRAM-SHA1 helper functions. + */ + +#ifndef __LIBSTROPHE_SCRAM_H__ +#define __LIBSTROPHE_SCRAM_H__ + +#include + +#include "sha1.h" + +void SCRAM_SHA1_ClientKey(const uint8_t *password, size_t len, + const uint8_t *salt, size_t salt_len, uint32_t i, + uint8_t key[SHA1_DIGEST_SIZE]); + +void SCRAM_SHA1_ClientSignature(const uint8_t ClientKey[SHA1_DIGEST_SIZE], + const uint8_t *AuthMessage, size_t len, + uint8_t sign[SHA1_DIGEST_SIZE]); + +void SCRAM_SHA1_ClientProof(const uint8_t ClientKey[SHA1_DIGEST_SIZE], + const uint8_t ClientSignature[SHA1_DIGEST_SIZE], + uint8_t proof[SHA1_DIGEST_SIZE]); + +#endif /* __LIBSTROPHE_SCRAM_H__ */