diff options
Diffstat (limited to 'protocols/jabber')
-rw-r--r-- | protocols/jabber/jabber.c | 4 | ||||
-rw-r--r-- | protocols/jabber/jabber.h | 1 | ||||
-rw-r--r-- | protocols/jabber/sasl.c | 105 |
3 files changed, 87 insertions, 23 deletions
diff --git a/protocols/jabber/jabber.c b/protocols/jabber/jabber.c index 2fa19c0f..fcd90598 100644 --- a/protocols/jabber/jabber.c +++ b/protocols/jabber/jabber.c @@ -116,6 +116,9 @@ static void jabber_init(account_t *acc) s = set_add(&acc->set, "carbons", "true", set_eval_bool, acc); s->flags |= ACC_SET_OFFLINE_ONLY; + s = set_add(&acc->set, "disable_scram", "false", set_eval_bool, acc); + s->flags |= SET_HIDDEN_DEFAULT; + acc->flags |= ACC_FLAG_AWAY_MESSAGE | ACC_FLAG_STATUS_MESSAGE | ACC_FLAG_HANDLE_DOMAINS; } @@ -379,6 +382,7 @@ static void jabber_logout(struct im_connection *ic) g_free(jd->me); g_free(jd->challenge.cnonce); g_free(jd->challenge.server_signature); + g_free(jd->challenge.cb_header); g_free(jd); jabber_connections = g_slist_remove(jabber_connections, ic); diff --git a/protocols/jabber/jabber.h b/protocols/jabber/jabber.h index 3445e4d7..c97c5b71 100644 --- a/protocols/jabber/jabber.h +++ b/protocols/jabber/jabber.h @@ -109,6 +109,7 @@ struct jabber_data { int scram_algo; char *cnonce; char *server_signature; + char *cb_header; } challenge; const struct oauth2_service *oauth2_service; diff --git a/protocols/jabber/sasl.c b/protocols/jabber/sasl.c index 10fbe270..e3467d86 100644 --- a/protocols/jabber/sasl.c +++ b/protocols/jabber/sasl.c @@ -23,6 +23,8 @@ #include <ctype.h> #include <gcrypt.h> +#include <gnutls/gnutls.h> +#include <stringprep.h> #include "jabber.h" #include "base64.h" @@ -40,7 +42,7 @@ const struct oauth2_service oauth2_service_google = "6C-Zgf7Tr7gEQTPlBhMUgo7R", }; -static int is_ascii(const char *str) +/*static int is_ascii(const char *str) { if (!str) { return 0; @@ -52,7 +54,7 @@ static int is_ascii(const char *str) } return 1; -} +}*/ xt_status sasl_pkt_mechanisms(struct xt_node *node, gpointer data) { @@ -60,7 +62,7 @@ xt_status sasl_pkt_mechanisms(struct xt_node *node, gpointer data) struct jabber_data *jd = ic->proto_data; struct xt_node *c, *reply; char *s; - int sup_plain = 0, sup_digest = 0, sup_gtalk = 0, sup_anonymous = 0, sup_scram = 0; + int sup_plain = 0, sup_digest = 0, sup_gtalk = 0, sup_anonymous = 0, sup_scram = 0, sup_scram_cb = 0; int want_oauth = FALSE, want_hipchat = FALSE, want_anonymous = FALSE; GString *mechs; @@ -97,9 +99,15 @@ xt_status sasl_pkt_mechanisms(struct xt_node *node, gpointer data) } else if (g_strcasecmp(c->text, "X-OAUTH2") == 0) { sup_gtalk = 1; } else if (g_strcasecmp(c->text, "SCRAM-SHA-1") == 0) { - sup_scram = JSCRAM_SHA1; + sup_scram |= JSCRAM_SHA1; + } else if (g_strcasecmp(c->text, "SCRAM-SHA-1-PLUS") == 0) { + sup_scram |= JSCRAM_SHA1; + sup_scram_cb |= JSCRAM_SHA1; } else if (g_strcasecmp(c->text, "SCRAM-SHA-256") == 0) { - sup_scram = JSCRAM_SHA256; + sup_scram |= JSCRAM_SHA256; + } else if (g_strcasecmp(c->text, "SCRAM-SHA-256-PLUS") == 0) { + sup_scram |= JSCRAM_SHA256; + sup_scram_cb |= JSCRAM_SHA256; } g_string_append_printf(mechs, " %s", c->text); @@ -108,9 +116,14 @@ xt_status sasl_pkt_mechanisms(struct xt_node *node, gpointer data) c = c->next; } - if (sup_scram && (!is_ascii(jd->username) || !is_ascii(ic->acc->pass))) { + /*if (sup_scram && (!is_ascii(jd->username) || !is_ascii(ic->acc->pass))) { imcb_log(ic, "Username/password contains non-ascii characters, SCRAM authentication disabled"); sup_scram = 0; + }*/ + + if (sup_scram_cb && !jd->ssl) { + imcb_log(ic, "Not connected over TLS, SCRAM Channel bindings disabled"); + sup_scram_cb = 0; } if (!want_oauth && !want_anonymous && !sup_plain && !sup_digest && !sup_scram) { @@ -164,20 +177,28 @@ xt_status sasl_pkt_mechanisms(struct xt_node *node, gpointer data) imc_logout(ic, FALSE); xt_free_node(reply); return XT_ABORT; - } else if (sup_scram) { + } else if (sup_scram && !set_getbool(&ic->acc->set, "disable_scram")) { GString *gs; - int len; + int len, r; unsigned char cnonce_bin[30]; - char *cnonce; + char *cnonce, *puser = NULL; - if (sup_scram & JSCRAM_SHA256) { + if (sup_scram & JSCRAM_SHA256 && (!sup_scram_cb || (sup_scram_cb & JSCRAM_SHA256))) { jd->challenge.type = JCHALLENGE_SCRAM; jd->challenge.scram_algo = GCRY_MD_SHA256; - xt_add_attr(reply, "mechanism", "SCRAM-SHA-256"); - } else if (sup_scram & JSCRAM_SHA1) { + if (sup_scram_cb) { + xt_add_attr(reply, "mechanism", "SCRAM-SHA-256-PLUS"); + } else { + xt_add_attr(reply, "mechanism", "SCRAM-SHA-256"); + } + } else if (sup_scram & JSCRAM_SHA1 && (!sup_scram_cb || (sup_scram_cb & JSCRAM_SHA1))) { jd->challenge.type = JCHALLENGE_SCRAM; jd->challenge.scram_algo = GCRY_MD_SHA1; - xt_add_attr(reply, "mechanism", "SCRAM-SHA-1"); + if (sup_scram_cb) { + xt_add_attr(reply, "mechanism", "SCRAM-SHA-1-PLUS"); + } else { + xt_add_attr(reply, "mechanism", "SCRAM-SHA-1"); + } } else { imcb_error(ic, "Unknown scram method"); /* Just in case, but we should not get here */ return XT_ABORT; @@ -190,14 +211,22 @@ xt_status sasl_pkt_mechanisms(struct xt_node *node, gpointer data) jd->challenge.cnonce = cnonce; - /* XXX: Saslprep username */ - g_string_append_printf(gs, "n,,n=%s,r=%s", jd->username, cnonce); + r = stringprep_profile(jd->username, &puser, "SASLprep", 0); + if (r != STRINGPREP_OK) { + imcb_error(ic, "SASLprep failed: %s", stringprep_strerror(r)); + return XT_ABORT; + } + + //jd->challenge.cb_header = g_strdup_printf("%s,,", sup_scram_cb ? "y" : "p=tls-unique"); + jd->challenge.cb_header = g_strdup_printf("%s,,", "n"); + g_string_append_printf(gs, "%sn=%s,r=%s", jd->challenge.cb_header, puser, cnonce); len = gs->len; s = g_string_free(gs, FALSE); reply->text = base64_encode((unsigned char *)s , len); reply->text_len = strlen(reply->text); + g_free(puser); g_free(s); } else if (sup_digest && !(jd->ssl && sup_plain)) { /* Only try DIGEST-MD5 if there's no SSL/TLS or if PLAIN isn't supported. @@ -468,12 +497,13 @@ static xt_status sasl_pkt_challenge_scram(struct xt_node *node, gpointer data) struct xt_node *reply_pkt = NULL; xt_status ret = XT_ABORT; char *random = NULL, *salt64 = NULL, *iter_str = NULL, *encoded_proof = NULL, - *reply = NULL, *client_first_bare = NULL, *server_first = NULL, - *client_final_noproof = NULL, *auth_message = NULL, *client_final = NULL; + *reply = NULL, *client_first_bare = NULL, *server_first = NULL, *cb_header64 = NULL, + *client_final_noproof = NULL, *auth_message = NULL, *client_final = NULL, *puser = NULL, *ppass = NULL; + unsigned char *salt = NULL; size_t salt_len, iter_count, i; - int algo = jd->challenge.scram_algo; + int algo = jd->challenge.scram_algo, r; size_t md_len = gcry_md_get_algo_dlen(algo); unsigned char client_key[md_len]; @@ -500,15 +530,24 @@ static xt_status sasl_pkt_challenge_scram(struct xt_node *node, gpointer data) goto error; } - // XXX: Normalize password with saslprep + r = stringprep_profile(jd->username, &puser, "SASLprep", 0); + if (r != STRINGPREP_OK) { + imcb_error(ic, "SASLprep failed: %s", stringprep_strerror(r)); + goto error; + } + r = stringprep_profile(ic->acc->pass, &ppass, "SASLprep", 0); + if (r != STRINGPREP_OK) { + imcb_error(ic, "SASLprep failed: %s", stringprep_strerror(r)); + goto error; + } salt_len = base64_decode(salt64, &salt); - if (gcry_kdf_derive(ic->acc->pass, strlen(ic->acc->pass), + if (gcry_kdf_derive(ppass, strlen(ppass), GCRY_KDF_PBKDF2, algo, salt, salt_len, iter_count, sizeof(salted_password), salted_password) != 0) { - imcb_error(ic, "gcrypt went booboo :("); + imcb_error(ic, "PBKDF failed"); goto error; } @@ -516,8 +555,23 @@ static xt_status sasl_pkt_challenge_scram(struct xt_node *node, gpointer data) gcry_md_hash_buffer(algo, stored_key, client_key, sizeof(client_key)); - client_first_bare = g_strdup_printf("n=%s,r=%s", jd->username, jd->challenge.cnonce); - client_final_noproof = g_strdup_printf("c=biws,r=%s", random); + /*if (jd->challenge.cb_header[0] == 'p') { + gnutls_datum_t cb; + int rc; + + rc = gnutls_session_channel_binding(jd->ssl, GNUTLS_CB_TLS_UNIQUE, &cb); + if (rc) { + imcb_log(ic, "Chanel binding error: %s", gnutls_strerror(rc)); + return XT_ABORT; + } + + for (size_t i = 0; i < cb.size; i++) + fprintf(stderr, "%02x", cb.data[i]); + fprintf(stderr, "\n"); + }*/ + cb_header64 = tobase64(jd->challenge.cb_header); + client_first_bare = g_strdup_printf("n=%s,r=%s", puser, jd->challenge.cnonce); + client_final_noproof = g_strdup_printf("c=%s,r=%s", cb_header64, random); auth_message = g_strdup_printf("%s,%s,%s", client_first_bare, server_first, client_final_noproof); @@ -551,11 +605,14 @@ error: imc_logout(ic, FALSE); silent_error: + g_free(puser); + g_free(ppass); g_free(random); g_free(salt64); g_free(salt); g_free(iter_str); g_free(encoded_proof); + g_free(cb_header64); g_free(client_first_bare); g_free(client_final_noproof); g_free(client_final); @@ -563,6 +620,8 @@ silent_error: g_free(reply); g_free(jd->challenge.cnonce); jd->challenge.cnonce = NULL; + g_free(jd->challenge.cb_header); + jd->challenge.cb_header = NULL; xt_free_node(reply_pkt); return ret; } |