diff options
author | ruff <me@ruff.mobi> | 2020-10-05 18:53:29 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-10-05 18:53:29 +0200 |
commit | ba6f20086999f2d7d7dafa75c77552a06f5e7d5b (patch) | |
tree | 0d20f94f46e269e80dd83f2a5ecfb50311a5f643 | |
parent | 67edd727512749faa0bcb4c5202cd4146c0e389e (diff) | |
parent | fa4145eb9c300eab9f002675de56e8b9021730a4 (diff) |
Merge pull request #13 from rufferson/binding
SASL SCRAM SHA2 and TLS channel binding
-rw-r--r-- | tests/wocky-connector-test.c | 129 | ||||
-rw-r--r-- | tests/wocky-sasl-utils-test.c | 3 | ||||
-rw-r--r-- | tests/wocky-scram-sha1-test.c | 2 | ||||
-rw-r--r-- | tests/wocky-test-sasl-auth-server.c | 223 | ||||
-rw-r--r-- | tests/wocky-test-sasl-auth-server.h | 3 | ||||
-rw-r--r-- | tests/wocky-test-sasl-auth.c | 11 | ||||
-rw-r--r-- | wocky/wocky-auth-registry.c | 96 | ||||
-rw-r--r-- | wocky/wocky-auth-registry.h | 29 | ||||
-rw-r--r-- | wocky/wocky-sasl-auth.c | 125 | ||||
-rw-r--r-- | wocky/wocky-sasl-scram.c | 502 | ||||
-rw-r--r-- | wocky/wocky-sasl-scram.h | 27 | ||||
-rw-r--r-- | wocky/wocky-sasl-utils.c | 62 | ||||
-rw-r--r-- | wocky/wocky-sasl-utils.h | 3 |
13 files changed, 1080 insertions, 135 deletions
diff --git a/tests/wocky-connector-test.c b/tests/wocky-connector-test.c index e08710e..5e77438 100644 --- a/tests/wocky-connector-test.c +++ b/tests/wocky-connector-test.c @@ -63,6 +63,17 @@ #define PLAIN FALSE #define DIGEST TRUE +#ifdef HAVE_LIBSASL2 +#include <sasl/sasl.h> +#if SASL_VERSION_FULL >= 0x0002011B +#define DEFAULT_SASL_MECH "SCRAM-SHA-256" +#endif /* SASL_VERSION_FULL */ +#endif /* HAVE_LIBSASL2 */ + +#ifndef DEFAULT_SASL_MECH +#define DEFAULT_SASL_MECH "SCRAM-SHA-1" +#endif + #define PORT_XMPP 5222 #define PORT_NONE 0 @@ -584,7 +595,7 @@ test_t tests[] = { "/connector/auth/secure/no-tlsplain/notls/digest", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { NOTLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, CONNECTOR_OK }, { "moose", "something" }, @@ -608,7 +619,7 @@ test_t tests[] = { "/connector/auth/insecure/no-tlsplain/notls/digest", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { NOTLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, CONNECTOR_OK }, { "moose", "something" }, @@ -770,7 +781,7 @@ test_t tests[] = * these should all be digest auth successes */ { "/connector/auth/secure/no-tlsplain/tls/digest", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { TLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, CONNECTOR_OK }, { "moose", "something" }, @@ -782,7 +793,7 @@ test_t tests[] = { "/connector/auth/secure/tlsplain/tls/digest", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { TLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, CONNECTOR_OK }, { "moose", "something" }, @@ -794,7 +805,7 @@ test_t tests[] = { "/connector/auth/insecure/no-tlsplain/tls/digest", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { TLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, CONNECTOR_OK }, { "moose", "something" }, @@ -806,7 +817,7 @@ test_t tests[] = { "/connector/auth/insecure/tlsplain/tls/digest", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { TLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, CONNECTOR_OK }, { "moose", "something" }, @@ -818,7 +829,7 @@ test_t tests[] = { "/connector/tls+auth/secure/no-tlsplain/tls/digest", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { TLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, CONNECTOR_OK }, { "moose", "something" }, @@ -830,7 +841,7 @@ test_t tests[] = { "/connector/tls+auth/secure/tlsplain/tls/digest", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { TLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, CONNECTOR_OK }, { "moose", "something" }, @@ -842,7 +853,7 @@ test_t tests[] = { "/connector/tls+auth/insecure/no-tlsplain/tls/digest", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { TLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, CONNECTOR_OK }, { "moose", "something" }, @@ -854,7 +865,7 @@ test_t tests[] = { "/connector/tls+auth/insecure/tlsplain/tls/digest", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { TLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, CONNECTOR_OK }, { "moose", "something" }, @@ -927,6 +938,58 @@ test_t tests[] = { NULL, 0 } } }, /* ********************************************************************* */ + /* SASL SCRAM TLS channel binding tests and error conditions */ +#if G_ENCODE_VERSION (GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION) > G_ENCODE_VERSION(2,66) + { "/connector/auth/sasl/binding", + NOISY, + { S_NO_ERROR }, + { { TLS, "SCRAM-SHA-512-PLUS" }, + { SERVER_PROBLEM_NO_PROBLEM, CONNECTOR_OK }, + { "moose", "something" }, + PORT_XMPP }, + { "weasel-juice.org", PORT_XMPP, "thud.org", REACHABLE, UNREACHABLE }, + { TLS_REQUIRED, + { "moose@weasel-juice.org", "something", DIGEST, TLS }, + { NULL, 0 } } }, + + { "/connector/auth/sasl/bad-binding-data", + NOISY, + { S_WOCKY_AUTH_ERROR, WOCKY_AUTH_ERROR_FAILURE, -1 }, + { { TLS, "SCRAM-SHA-512-PLUS" }, + { SERVER_PROBLEM_MANGLED_BINDING_DATA, CONNECTOR_OK }, + { "moose", "something" }, + PORT_XMPP }, + { "weasel-juice.org", PORT_XMPP, "thud.org", REACHABLE, UNREACHABLE }, + { TLS_REQUIRED, + { "moose@weasel-juice.org", "something", DIGEST, TLS }, + { NULL, 0 } } }, + + { "/connector/auth/sasl/bad-binding-flag", + NOISY, + { S_WOCKY_AUTH_ERROR, WOCKY_AUTH_ERROR_FAILURE, -1 }, + { { TLS, "SCRAM-SHA-512-PLUS" }, + { SERVER_PROBLEM_MANGLED_BINDING_FLAG, CONNECTOR_OK }, + { "moose", "something" }, + PORT_XMPP }, + { "weasel-juice.org", PORT_XMPP, "thud.org", REACHABLE, UNREACHABLE }, + { TLS_REQUIRED, + { "moose@weasel-juice.org", "something", DIGEST, TLS }, + { NULL, 0 } } }, + + { "/connector/auth/sasl/scrambled-binding", + NOISY, + { S_WOCKY_AUTH_ERROR, WOCKY_AUTH_ERROR_INVALID_REPLY, -1 }, + { { TLS, "SCRAM-SHA-512-PLUS" }, + { SERVER_PROBLEM_SCRAMBLED_BINDING, CONNECTOR_OK }, + { "moose", "something" }, + PORT_XMPP }, + { "weasel-juice.org", PORT_XMPP, "thud.org", REACHABLE, UNREACHABLE }, + { TLS_REQUIRED, + { "moose@weasel-juice.org", "something", DIGEST, TLS }, + { NULL, 0 } } }, +#endif /* GLIB_VERSION_2_66 */ + + /* ********************************************************************* */ /* TLS error conditions */ { "/connector/problem/tls/refused", NOISY, @@ -985,7 +1048,7 @@ test_t tests[] = /* we actually tolerate > 1.0 versions */ { "/connector/problem/xmpp/version/1.x", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { TLS, NULL, "1.1" }, { SERVER_PROBLEM_NO_PROBLEM, CONNECTOR_OK }, { "moose", "something" }, @@ -1162,7 +1225,7 @@ test_t tests[] = { "/connector/problem/xmpp/bind/no-jid", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { TLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, { OK, BIND_PROBLEM_NO_JID, OK, OK, OK } }, @@ -1175,7 +1238,7 @@ test_t tests[] = { "/connector/problem/xmpp/session/none", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { TLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, { XMPP_PROBLEM_NO_SESSION, OK, OK, OK, OK } }, @@ -1323,7 +1386,7 @@ test_t tests[] = /* quirks */ { "/connector/google/domain-discovery/require", QUIET, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { TLS, NULL }, { SERVER_PROBLEM_REQUIRE_GOOGLE_JDD, CONNECTOR_OK }, { "moose", "something" }, @@ -1422,7 +1485,7 @@ test_t tests[] = { "/connector/xep77/register/email-arg-ok", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { TLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, { OK, OK, OK, OK, OK, XEP77_PROBLEM_EMAIL_ARG } }, @@ -1499,7 +1562,7 @@ test_t tests[] = { "/connector/xep77/register/already/get", NOISY, - { S_NO_ERROR, 0 , 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0 , 0, DEFAULT_SASL_MECH }, { { TLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, { OK, OK, OK, OK, OK, XEP77_PROBLEM_QUERY_ALREADY } }, @@ -1513,7 +1576,7 @@ test_t tests[] = { "/connector/xep77/register/already/set", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { TLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, { OK, OK, OK, OK, OK, XEP77_PROBLEM_ALREADY } }, @@ -1772,7 +1835,7 @@ test_t tests[] = { "/connector/jabber/no-ssl/auth/old+sasl", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { TLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, { XMPP_PROBLEM_OLD_AUTH_FEATURE, OK, OK, OK, OK } }, @@ -1964,7 +2027,7 @@ test_t tests[] = { "/connector/jabber/ssl/auth/old+sasl", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { TLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, { XMPP_PROBLEM_OLD_AUTH_FEATURE|XMPP_PROBLEM_OLD_SSL, @@ -2006,7 +2069,7 @@ test_t tests[] = { "/connector+ssl/auth/secure/no-tlsplain/notls/digest", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { NOTLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, { XMPP_PROBLEM_OLD_SSL, OK, OK, OK, OK } }, { "moose", "something" }, @@ -2030,7 +2093,7 @@ test_t tests[] = { "/connector+ssl/auth/insecure/no-tlsplain/notls/digest", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { NOTLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, { XMPP_PROBLEM_OLD_SSL, OK, OK, OK, OK } }, { "moose", "something" }, @@ -2150,7 +2213,7 @@ test_t tests[] = * these should all be digest auth successes */ { "/connector+ssl/auth/secure/no-tlsplain/tls/digest", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { TLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, { XMPP_PROBLEM_OLD_SSL, OK, OK, OK, OK } }, { "moose", "something" }, @@ -2162,7 +2225,7 @@ test_t tests[] = { "/connector+ssl/auth/secure/tlsplain/tls/digest", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { TLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, { XMPP_PROBLEM_OLD_SSL, OK, OK, OK, OK } }, { "moose", "something" }, @@ -2174,7 +2237,7 @@ test_t tests[] = { "/connector+ssl/auth/insecure/no-tlsplain/tls/digest", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { TLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, { XMPP_PROBLEM_OLD_SSL, OK, OK, OK, OK } }, { "moose", "something" }, @@ -2186,7 +2249,7 @@ test_t tests[] = { "/connector+ssl/auth/insecure/tlsplain/tls/digest", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { TLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, { XMPP_PROBLEM_OLD_SSL, OK, OK, OK, OK } }, { "moose", "something" }, @@ -2198,7 +2261,7 @@ test_t tests[] = { "/connector+ssl/tls+auth/secure/no-tlsplain/tls/digest", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { TLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, { XMPP_PROBLEM_OLD_SSL, OK, OK, OK, OK } }, { "moose", "something" }, @@ -2210,7 +2273,7 @@ test_t tests[] = { "/connector+ssl/tls+auth/secure/tlsplain/tls/digest", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { TLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, { XMPP_PROBLEM_OLD_SSL, OK, OK, OK, OK } }, { "moose", "something" }, @@ -2222,7 +2285,7 @@ test_t tests[] = { "/connector+ssl/tls+auth/insecure/no-tlsplain/tls/digest", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { TLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, { XMPP_PROBLEM_OLD_SSL, OK, OK, OK, OK } }, { "moose", "something" }, @@ -2234,7 +2297,7 @@ test_t tests[] = { "/connector+ssl/tls+auth/insecure/tlsplain/tls/digest", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { TLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, { XMPP_PROBLEM_OLD_SSL, OK, OK, OK, OK } }, { "moose", "something" }, @@ -2325,7 +2388,7 @@ test_t tests[] = /* we actually tolerate > 1.0 versions */ { "/connector+ssl/problem/xmpp/version/1.x", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { TLS, NULL, "1.1" }, { SERVER_PROBLEM_NO_PROBLEM, { XMPP_PROBLEM_OLD_SSL, OK, OK, OK, OK } }, { "moose", "something" }, @@ -2487,7 +2550,7 @@ test_t tests[] = { "/connector+ssl/problem/xmpp/bind/no-jid", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { TLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, { XMPP_PROBLEM_OLD_SSL, BIND_PROBLEM_NO_JID, OK, OK, OK } }, @@ -2500,7 +2563,7 @@ test_t tests[] = { "/connector+ssl/problem/xmpp/session/none", NOISY, - { S_NO_ERROR, 0, 0, "SCRAM-SHA-1" }, + { S_NO_ERROR, 0, 0, DEFAULT_SASL_MECH }, { { TLS, NULL }, { SERVER_PROBLEM_NO_PROBLEM, { XMPP_PROBLEM_NO_SESSION|XMPP_PROBLEM_OLD_SSL, OK, OK, OK, OK } }, @@ -3771,7 +3834,7 @@ main (int argc, g_message ("libsasl2 not found: skipping SCRAM SASL tests"); for (i = 0; tests[i].desc != NULL; i++) { - if (!wocky_strdiff (tests[i].result.mech, "SCRAM-SHA-1")) + if (!wocky_strdiff (tests[i].result.mech, DEFAULT_SASL_MECH)) continue; g_test_add_data_func (tests[i].desc, &tests[i], (test_func)run_test); } diff --git a/tests/wocky-sasl-utils-test.c b/tests/wocky-sasl-utils-test.c index e384811..48ac3b1 100644 --- a/tests/wocky-sasl-utils-test.c +++ b/tests/wocky-sasl-utils-test.c @@ -135,7 +135,8 @@ digest_test hmac_sha1_tests[] = { static void test_sasl_utils_hmac_sha1 (digest_test *t) { - GByteArray *result = sasl_calculate_hmac_sha1 (t->key, t->key_len, + GByteArray *result = sasl_calculate_hmac (G_CHECKSUM_SHA1, + t->key, t->key_len, t->data, t->data_len); int i; diff --git a/tests/wocky-scram-sha1-test.c b/tests/wocky-scram-sha1-test.c index 54081cd..128fddf 100644 --- a/tests/wocky-scram-sha1-test.c +++ b/tests/wocky-scram-sha1-test.c @@ -49,6 +49,8 @@ test_scram_sha1 (testcase *test) scram = WOCKY_AUTH_HANDLER (wocky_sasl_scram_new ( test->server, test->user, test->password)); + g_object_set (scram, "hash-algo", G_CHECKSUM_SHA1, NULL); + g_assert (wocky_auth_handler_get_initial_response (scram, &out, &error)); diff --git a/tests/wocky-test-sasl-auth-server.c b/tests/wocky-test-sasl-auth-server.c index b0f2308..f66736a 100644 --- a/tests/wocky-test-sasl-auth-server.c +++ b/tests/wocky-test-sasl-auth-server.c @@ -101,6 +101,7 @@ struct _TestSaslAuthServerPrivate ServerProblem problem; GTask *task; GCancellable *cancellable; + WockySaslScram *scram; }; G_DEFINE_TYPE_WITH_CODE (TestSaslAuthServer, test_sasl_auth_server, G_TYPE_OBJECT, @@ -398,12 +399,30 @@ success_sent (GObject *source, { GError *error = NULL; gboolean ok; + TestSaslAuthServer *tsas = TEST_SASL_AUTH_SERVER (user_data); + TestSaslAuthServerPrivate *priv = test_sasl_auth_server_get_instance_private (tsas); ok = wocky_xmpp_connection_send_stanza_finish ( WOCKY_XMPP_CONNECTION (source), result, &error); g_assert_no_error (error); g_assert (ok); + if (priv->problem == SERVER_PROBLEM_SCRAMBLED_BINDING && priv->task) + { + GTask *t = priv->task; + + priv->task = NULL; + + if (priv->cancellable != NULL) + g_object_unref (priv->cancellable); + + priv->cancellable = NULL; + + g_task_return_boolean (t, TRUE); + g_object_unref (t); + return; + } + wocky_xmpp_connection_reset (WOCKY_XMPP_CONNECTION (source)); wocky_xmpp_connection_recv_open_async (WOCKY_XMPP_CONNECTION (source), @@ -490,7 +509,9 @@ check_sasl_return (TestSaslAuthServer *self, int ret) { case SASL_BADAUTH: /* Bad password provided */ - g_assert_cmpint (priv->problem, ==, SERVER_PROBLEM_INVALID_PASSWORD); + g_assert_true (priv->problem == SERVER_PROBLEM_MANGLED_BINDING_FLAG + || priv->problem == SERVER_PROBLEM_MANGLED_BINDING_DATA + || priv->problem == SERVER_PROBLEM_INVALID_PASSWORD); not_authorized (self); return FALSE; #if SASL_VERSION_FULL <= 0x02011B @@ -648,6 +669,51 @@ static gchar * slash_challenge (const gchar *challenge, unsigned *len) return g_string_free (slashed, FALSE); } +typedef struct { + TestSaslAuthServer *srv; + gchar *challenge; + gboolean complete; +} ScramRes; + +static void +handle_auth_cb (GObject *source, + GAsyncResult *result, + gpointer data) +{ + WockySaslScram *scram = WOCKY_SASL_SCRAM (source); + GError *error = NULL; + ScramRes *res = data; + TestSaslAuthServerPrivate *priv = test_sasl_auth_server_get_instance_private (res->srv); + gchar *username = NULL; + + g_object_get (source, "username", &username, NULL); + if (priv->problem == SERVER_PROBLEM_INVALID_USERNAME) + { + g_assert_cmpstr (username, !=, priv->username); + } + else + { + g_assert_cmpstr (username, ==, priv->username); + g_object_set (source, "password", priv->password, NULL); + } + g_free (username); + + res->challenge = wocky_sasl_scram_server_start_finish (scram, result, &error); + + if (priv->problem == SERVER_PROBLEM_INVALID_USERNAME) + { + g_assert_error (error, WOCKY_AUTH_ERROR, WOCKY_AUTH_ERROR_NO_CREDENTIALS); + g_assert_null (res->challenge); + } + else + { + g_assert_no_error (error); + g_assert_nonnull (res->challenge); + } + + res->complete = TRUE; +} + static void handle_auth (TestSaslAuthServer *self, WockyStanza *stanza) { @@ -702,6 +768,71 @@ handle_auth (TestSaslAuthServer *self, WockyStanza *stanza) ret = wocky_strdiff ((gchar *) response, priv->password) ? SASL_BADAUTH : SASL_OK; } + else if (!wocky_strdiff ("SCRAM-SHA-512-PLUS", priv->selected_mech)) + { + ScramRes res = { self, NULL, FALSE }; + GIOStream *ios = NULL; + + g_assert_nonnull (priv->conn); + g_assert_nonnull (priv->scram); + +#if G_ENCODE_VERSION (GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION) > G_ENCODE_VERSION(2,66) + g_object_get (G_OBJECT (priv->conn), "base-stream", &ios, NULL); + if (G_IS_TLS_CONNECTION (ios)) + { + GError *err = NULL; + gchar *cb64 = NULL; + GByteArray *cb = g_byte_array_new (); + + if (!g_tls_connection_get_channel_binding_data ((GTlsConnection *) ios, + G_TLS_CHANNEL_BINDING_TLS_UNIQUE, cb, &err)) + { + g_error ("Error getting binding data: %s", err->message); + g_assert_not_reached (); + } + + if (priv->problem == SERVER_PROBLEM_MANGLED_BINDING_DATA) + cb->data[0] ^= cb->data[1]; + else if (priv->problem == SERVER_PROBLEM_MANGLED_BINDING_FLAG + || priv->problem == SERVER_PROBLEM_SCRAMBLED_BINDING) + { + gchar *r; + g_assert_true (g_str_has_prefix ((gchar *) response, "p=tls-unique,")); + r = g_strdup_printf ("n%s", response + 12); + g_free (response); + response = (guchar *) r; + } + + cb64 = g_base64_encode (cb->data, cb->len); + g_byte_array_unref (cb); + g_object_set (G_OBJECT (priv->scram), + "cb-type", WOCKY_TLS_BINDING_TLS_UNIQUE, + "cb-data", cb64, + NULL); + g_debug ("TLS binding: %s", cb64); + g_free (cb64); + } + else + g_debug ("No TLS"); +#else + (void)(ios); +#endif /* GLIB_VERSION_2_66 */ + + wocky_sasl_scram_server_start_async (priv->scram, (gchar *) response, + handle_auth_cb, priv->cancellable, &res); + + while (!res.complete) + g_main_context_iteration (NULL, FALSE); + + if (res.challenge) + { + ret = 1; /* SASL_CONTINUE */ + challenge = res.challenge; + challenge_len = strlen (challenge); + } + else + ret = SASL_NOUSER; + } else { #if HAVE_LIBSASL2 @@ -782,6 +913,52 @@ out: } static void +handle_response_cb (GObject *source, + GAsyncResult *result, + gpointer data) +{ + WockySaslScram *scram = WOCKY_SASL_SCRAM (source); + GError *error = NULL; + ScramRes *res = data; + TestSaslAuthServerPrivate *priv = test_sasl_auth_server_get_instance_private (res->srv); + + + res->challenge = wocky_sasl_scram_server_step_finish (scram, result, &error); + + if (priv->problem == SERVER_PROBLEM_INVALID_PASSWORD) + { + g_assert_error (error, WOCKY_AUTH_ERROR, WOCKY_AUTH_ERROR_NOT_AUTHORIZED); + g_assert_null (res->challenge); + } + else if (priv->problem == SERVER_PROBLEM_MANGLED_BINDING_DATA) + { + g_assert_error (error, WOCKY_AUTH_ERROR, WOCKY_AUTH_ERROR_FAILURE); + g_assert_null (res->challenge); + } + else if (priv->problem == SERVER_PROBLEM_MANGLED_BINDING_FLAG) + { + g_assert_error (error, WOCKY_AUTH_ERROR, WOCKY_AUTH_ERROR_FAILURE); + g_assert_null (res->challenge); + } + else if (priv->problem == SERVER_PROBLEM_SCRAMBLED_BINDING) + { + g_assert_error (error, WOCKY_AUTH_ERROR, WOCKY_AUTH_ERROR_NOT_AUTHORIZED); + g_assert_null (res->challenge); + + /* Now pretend it didn't happen and provide phony server verification */ + res->challenge = g_strdup_printf ("v=%s", + "QRx7t5sNW98H5eXodJPJKiQKZbEJBb1XKRnK9hb1ON+WU5/D4z/9wBtG1J2OKGt/gHdDzblO7BYbBhHVb6CXtg=="); + } + else + { + g_assert_no_error (error); + g_assert_nonnull (res->challenge); + } + + res->complete = TRUE; +} + +static void handle_response (TestSaslAuthServer *self, WockyStanza *stanza) { TestSaslAuthServerPrivate * priv = self->priv; @@ -806,6 +983,40 @@ handle_response (TestSaslAuthServer *self, WockyStanza *stanza) &response_len); } + if (!wocky_strdiff ("SCRAM-SHA-512-PLUS", priv->selected_mech)) + { + ScramRes res = { self, NULL, FALSE }; + + g_assert_nonnull (priv->scram); + + if (priv->problem == SERVER_PROBLEM_SCRAMBLED_BINDING) + { + gchar *r, *rd = g_strstr_len ((gchar *) response, -1, ","); + + g_assert_true (response[0] == 'c' && response[1] == '=' && rd != NULL); + + r = g_strdup_printf ("c=biws,%s", rd + 1); + g_free (response); + response = (guchar *) r; + } + + wocky_sasl_scram_server_step_async (priv->scram, (gchar *) response, + handle_response_cb, priv->cancellable, &res); + + while (!res.complete) + g_main_context_iteration (NULL, FALSE); + + if (res.challenge) + { + ret = SASL_OK; + challenge = res.challenge; + challenge_len = strlen (challenge); + } + else + ret = SASL_BADAUTH; + } + else + { #ifdef HAVE_LIBSASL2 ret = sasl_server_step (priv->sasl_conn, (gchar *) response, (unsigned) response_len, &challenge, &challenge_len); @@ -814,6 +1025,7 @@ handle_response (TestSaslAuthServer *self, WockyStanza *stanza) challenge_len = 0; challenge = ""; #endif + } if (!check_sasl_return (self, ret)) goto out; @@ -848,7 +1060,8 @@ handle_response (TestSaslAuthServer *self, WockyStanza *stanza) } if (priv->state == AUTH_STATE_FINAL_CHALLENGE && - priv->problem == SERVER_PROBLEM_FINAL_DATA_IN_SUCCESS) + (priv->problem == SERVER_PROBLEM_FINAL_DATA_IN_SUCCESS + || priv->problem == SERVER_PROBLEM_SCRAMBLED_BINDING)) { auth_succeeded (self, challenge64); } @@ -1018,6 +1231,12 @@ test_sasl_auth_server_new (GIOStream *stream, gchar *mech, priv->mech = g_strdup (mech); priv->problem = problem; + if (!wocky_strdiff ("SCRAM-SHA-512-PLUS", mech)) + { + priv->scram = g_object_new (WOCKY_TYPE_SASL_SCRAM, "server", servername, + "hash-algo", G_CHECKSUM_SHA512, NULL); + } + if (start) { priv->stream = g_object_ref (stream); diff --git a/tests/wocky-test-sasl-auth-server.h b/tests/wocky-test-sasl-auth-server.h index d24a9fa..0c89c6f 100644 --- a/tests/wocky-test-sasl-auth-server.h +++ b/tests/wocky-test-sasl-auth-server.h @@ -39,6 +39,9 @@ typedef enum { SERVER_PROBLEM_DISLIKE_GOOGLE_JDD, SERVER_PROBLEM_SPACE_CHALLENGE, SERVER_PROBLEM_SLASH_CHALLENGE, + SERVER_PROBLEM_MANGLED_BINDING_DATA, + SERVER_PROBLEM_MANGLED_BINDING_FLAG, + SERVER_PROBLEM_SCRAMBLED_BINDING, /* Not actually a problem, but let the server choose to put * ``additional data with success'' in a success stanza. */ SERVER_PROBLEM_FINAL_DATA_IN_SUCCESS, diff --git a/tests/wocky-test-sasl-auth.c b/tests/wocky-test-sasl-auth.c index 166f232..83e4aab 100644 --- a/tests/wocky-test-sasl-auth.c +++ b/tests/wocky-test-sasl-auth.c @@ -280,6 +280,11 @@ main (int argc, 0, 0, SERVER_PROBLEM_FINAL_DATA_IN_SUCCESS, FALSE, FALSE, "test", "test123", NULL }, + SUCCESS("/xmpp-sasl/scram-sha2-plus-multistep", "SCRAM-SHA-512-PLUS", TRUE), + { "/xmpp-sasl/scram-sha2-final-data-in-success", "SCRAM-SHA-512-PLUS", TRUE, + 0, 0, SERVER_PROBLEM_FINAL_DATA_IN_SUCCESS, FALSE, FALSE, + "test", "test123", NULL }, + FAIL("/xmpp-sasl/no-supported-mechs", "NONSENSE", TRUE, WOCKY_AUTH_ERROR, WOCKY_AUTH_ERROR_NO_SUPPORTED_MECHANISMS, SERVER_PROBLEM_NO_PROBLEM), @@ -296,6 +301,9 @@ main (int argc, { "/xmpp-sasl/wrong-username-md5", "DIGEST-MD5", TRUE, WOCKY_AUTH_ERROR, WOCKY_AUTH_ERROR_FAILURE, SERVER_PROBLEM_INVALID_USERNAME, TRUE, FALSE, "test", "test123" }, + { "/xmpp-sasl/wrong-username-sha2", "SCRAM-SHA-512-PLUS", TRUE, + WOCKY_AUTH_ERROR, WOCKY_AUTH_ERROR_FAILURE, + SERVER_PROBLEM_INVALID_USERNAME, TRUE, FALSE, "test", "test123" }, { "/xmpp-sasl/wrong-password-plain", "PLAIN", TRUE, WOCKY_AUTH_ERROR, WOCKY_AUTH_ERROR_FAILURE, @@ -303,6 +311,9 @@ main (int argc, { "/xmpp-sasl/wrong-password-md5", "DIGEST-MD5", TRUE, WOCKY_AUTH_ERROR, WOCKY_AUTH_ERROR_FAILURE, SERVER_PROBLEM_INVALID_PASSWORD, FALSE, TRUE, "test", "test123" }, + { "/xmpp-sasl/wrong-password-sha2", "SCRAM-SHA-512-PLUS", TRUE, + WOCKY_AUTH_ERROR, WOCKY_AUTH_ERROR_FAILURE, + SERVER_PROBLEM_INVALID_PASSWORD, FALSE, TRUE, "test", "test123" }, /* Redo the MD5-DIGEST test with a username, password and realm that * happens to generate a \0 byte in the md5 hash of diff --git a/wocky/wocky-auth-registry.c b/wocky/wocky-auth-registry.c index 4e3f483..721dd64 100644 --- a/wocky/wocky-auth-registry.c +++ b/wocky/wocky-auth-registry.c @@ -17,10 +17,18 @@ #include "wocky-debug-internal.h" +enum +{ + PROP_CB_TYPE = 1, + PROP_CB_DATA, +}; + /* private structure */ struct _WockyAuthRegistryPrivate { gboolean dispose_has_run; + WockyTLSBindingType cb_type; + gchar *cb_data; WockyAuthHandler *handler; GSList *handlers; @@ -87,8 +95,19 @@ wocky_auth_registry_get_property (GObject *object, GValue *value, GParamSpec *pspec) { + WockyAuthRegistry *self = WOCKY_AUTH_REGISTRY (object); + WockyAuthRegistryPrivate *priv = self->priv; + switch (property_id) { + case PROP_CB_TYPE: + g_value_set_enum (value, priv->cb_type); + break; + + case PROP_CB_DATA: + g_value_set_string (value, priv->cb_data); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -100,8 +119,20 @@ wocky_auth_registry_set_property (GObject *object, const GValue *value, GParamSpec *pspec) { + WockyAuthRegistry *self = WOCKY_AUTH_REGISTRY (object); + WockyAuthRegistryPrivate *priv = self->priv; + switch (property_id) { + case PROP_CB_TYPE: + priv->cb_type = g_value_get_enum (value); + break; + + case PROP_CB_DATA: + g_free (priv->cb_data); + priv->cb_data = g_value_dup_string (value); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -118,6 +149,7 @@ wocky_auth_registry_dispose (GObject *object) priv->dispose_has_run = TRUE; + g_free (priv->cb_data); /* release any references held by the object here */ if (priv->handler != NULL) { @@ -147,6 +179,17 @@ wocky_auth_registry_class_init (WockyAuthRegistryClass *klass) object_class->constructed = wocky_auth_registry_constructed; object_class->get_property = wocky_auth_registry_get_property; object_class->set_property = wocky_auth_registry_set_property; + g_object_class_install_property (object_class, PROP_CB_TYPE, + g_param_spec_enum ("tls-binding-type", "tls channel binding type", + "The type of the TLS Channel Binding to use in SASL negotiation", + WOCKY_TYPE_TLS_BINDING_TYPE, WOCKY_TLS_BINDING_DISABLED, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_CB_DATA, + g_param_spec_string ("tls-binding-data", "tls channel binding data", + "Base64 encoded TLS Channel binding data for the set type", NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + object_class->dispose = wocky_auth_registry_dispose; object_class->finalize = wocky_auth_registry_finalize; @@ -226,6 +269,25 @@ wocky_auth_registry_select_handler (WockyAuthRegistry *self, { WockyAuthRegistryPrivate *priv = self->priv; GSList *k; + /* Define order of SCRAM hashing algorithm preferences according to ... * + * ... various recommendations */ + struct { + gchar *mech; + gboolean is_plus; + GChecksumType algo; + } scram_handlers[] = { + { WOCKY_AUTH_MECH_SASL_SCRAM_SHA_512_PLUS, TRUE, G_CHECKSUM_SHA512 }, + { WOCKY_AUTH_MECH_SASL_SCRAM_SHA_512, FALSE, G_CHECKSUM_SHA512 }, +#ifdef WOCKY_AUTH_MECH_SASL_SCRAM_SHA_384 + { WOCKY_AUTH_MECH_SASL_SCRAM_SHA_384_PLUS, TRUE, G_CHECKSUM_SHA384 }, + { WOCKY_AUTH_MECH_SASL_SCRAM_SHA_384, FALSE, G_CHECKSUM_SHA384 }, +#endif + { WOCKY_AUTH_MECH_SASL_SCRAM_SHA_256_PLUS, TRUE, G_CHECKSUM_SHA256 }, + { WOCKY_AUTH_MECH_SASL_SCRAM_SHA_256, FALSE, G_CHECKSUM_SHA256 }, + { WOCKY_AUTH_MECH_SASL_SCRAM_SHA_1_PLUS, TRUE, G_CHECKSUM_SHA1 }, + { WOCKY_AUTH_MECH_SASL_SCRAM_SHA_1, FALSE, G_CHECKSUM_SHA1 }, + { NULL, FALSE, G_CHECKSUM_SHA1 } + }; for (k = priv->handlers; k != NULL; k = k->next) { @@ -244,17 +306,35 @@ wocky_auth_registry_select_handler (WockyAuthRegistry *self, } } - if (wocky_auth_registry_has_mechanism (mechanisms, - WOCKY_AUTH_MECH_SASL_SCRAM_SHA_1)) + /* All the below mechanisms require password so if we have none + * let's just stop here */ + g_return_val_if_fail (out_handler == NULL || password != NULL, FALSE); + + for (int i = 0; scram_handlers[i].mech != NULL ; i++) { - if (out_handler != NULL) + if (wocky_auth_registry_has_mechanism (mechanisms, + scram_handlers[i].mech)) { - /* XXX: check for username and password here? */ - DEBUG ("Choosing SCRAM-SHA-1 as auth mechanism"); - *out_handler = WOCKY_AUTH_HANDLER (wocky_sasl_scram_new ( - server, username, password)); + if (out_handler != NULL && username != NULL) + { + /* For PLUS it's whatever we found/support, otherwise NONE or * + * DISABLED. NONE is when we support some but server doesn't. */ + WockyTLSBindingType cb_type = (scram_handlers[i].is_plus ? + priv->cb_type + : MIN (priv->cb_type, WOCKY_TLS_BINDING_NONE)); + DEBUG ("Choosing %s as auth mechanism", scram_handlers[i].mech); + *out_handler = WOCKY_AUTH_HANDLER (wocky_sasl_scram_new ( + server, username, password)); + WOCKY_AUTH_HANDLER_GET_IFACE (*out_handler)->mechanism = + scram_handlers[i].mech; + g_object_set (G_OBJECT (*out_handler), + "hash-algo", scram_handlers[i].algo, + "cb-type", cb_type, + "cb-data", priv->cb_data, + NULL); + } + return TRUE; } - return TRUE; } if (wocky_auth_registry_has_mechanism (mechanisms, diff --git a/wocky/wocky-auth-registry.h b/wocky/wocky-auth-registry.h index f5e3f45..28e89a5 100644 --- a/wocky/wocky-auth-registry.h +++ b/wocky/wocky-auth-registry.h @@ -59,6 +59,35 @@ typedef enum #define WOCKY_AUTH_MECH_SASL_DIGEST_MD5 "DIGEST-MD5" #define WOCKY_AUTH_MECH_SASL_PLAIN "PLAIN" #define WOCKY_AUTH_MECH_SASL_SCRAM_SHA_1 "SCRAM-SHA-1" +#define WOCKY_AUTH_MECH_SASL_SCRAM_SHA_1_PLUS "SCRAM-SHA-1-PLUS" +#define WOCKY_AUTH_MECH_SASL_SCRAM_SHA_256 "SCRAM-SHA-256" +#define WOCKY_AUTH_MECH_SASL_SCRAM_SHA_256_PLUS "SCRAM-SHA-256-PLUS" +#define WOCKY_AUTH_MECH_SASL_SCRAM_SHA_512 "SCRAM-SHA-512" +#define WOCKY_AUTH_MECH_SASL_SCRAM_SHA_512_PLUS "SCRAM-SHA-512-PLUS" +#if GLIB_VERSION_CUR_STABLE >= GLIB_VERSION_2_52 +#define WOCKY_AUTH_MECH_SASL_SCRAM_SHA_384 "SCRAM-SHA-384" +#define WOCKY_AUTH_MECH_SASL_SCRAM_SHA_384_PLUS "SCRAM-SHA-384-PLUS" +#endif + +/** + * WockyTLSBindingType + * @WOCKY_TLS_BINDING_DISABLED : binding is not supported by the client + * @WOCKY_TLS_BINDING_NONE : binding is not supported by the server + * @WOCKY_TLS_BINDING_TLS_UNIQUE : tls-unique binding type + * @WOCKY_TLS_BINDING_TLS_SERVER_END_POINT : tls-server-end-point type + * @WOCKY_TLS_BINDING_TLS_EXPORTER : tls-exporter type + * + * Possible TLS Channel Binding states + */ +typedef enum +{ + WOCKY_TLS_BINDING_DISABLED, + WOCKY_TLS_BINDING_NONE, + WOCKY_TLS_BINDING_TLS_UNIQUE, + WOCKY_TLS_BINDING_TLS_SERVER_END_POINT, + WOCKY_TLS_BINDING_TLS_EXPORTER, + WOCKY_TLS_BINDING_INVALID_TYPE +} WockyTLSBindingType; /** * WockyAuthRegistryStartData: diff --git a/wocky/wocky-sasl-auth.c b/wocky/wocky-sasl-auth.c index 31da74d..ce7d294 100644 --- a/wocky/wocky-sasl-auth.c +++ b/wocky/wocky-sasl-auth.c @@ -137,11 +137,25 @@ wocky_sasl_auth_get_property (GObject *object, } } +static WockyTLSBindingType default_cb_type = WOCKY_TLS_BINDING_TLS_UNIQUE; + static void wocky_sasl_auth_class_init (WockySaslAuthClass *wocky_sasl_auth_class) { GObjectClass *object_class = G_OBJECT_CLASS (wocky_sasl_auth_class); GParamSpec *spec; + /* Initialize default binding type once */ + const gchar *cb_str = g_getenv ("WOCKY_CHANNEL_BINDING_TYPE"); + + if (cb_str != NULL) + { + GEnumClass *gec = g_type_class_ref (WOCKY_TYPE_TLS_BINDING_TYPE); + GEnumValue *gev = g_enum_get_value_by_nick (gec, cb_str); + + if (gev) + default_cb_type = gev->value; + g_type_class_unref (gec); + } object_class->set_property = wocky_sasl_auth_set_property; object_class->get_property = wocky_sasl_auth_get_property; @@ -657,6 +671,103 @@ wocky_sasl_auth_start_cb (GObject *source_object, g_object_unref (stanza); } +/** + * wocky_tls_get_cb_data: + * @conn: a #WockyXmppConnection wrapping WockyTLSSession aka GTlsConnection + * @type: a #WockyTLSBindingType to return bidning data + */ +static gchar * +wocky_tls_get_cb_data (WockyXmppConnection *conn, WockyTLSBindingType type) +{ + GIOStream *ios = NULL; + GTlsConnection *tc = NULL; + gchar *cb_data = NULL; + int g_tls_cb_t = type; + + g_assert (conn != NULL); + g_object_get (conn, "base-stream", &ios, NULL); + g_return_val_if_fail (ios != NULL, NULL); + tc = G_TLS_CONNECTION (ios); + g_object_unref (ios); + + /* Unfortunatelly backend didn'make it into 2.66 so we need next minor */ +#if G_ENCODE_VERSION (GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION) > G_ENCODE_VERSION(2,66) + /* We need this conversion and cast until Exporter is adopted by IETF and + * gets officially into public API. So far it is hidden experimental type. + * Once adopted we can simpy typedef WockyTLSBindingType to + * GTlsChannelBindingType */ + switch (type) + { + case WOCKY_TLS_BINDING_TLS_UNIQUE: + g_tls_cb_t = G_TLS_CHANNEL_BINDING_TLS_UNIQUE; + break; + case WOCKY_TLS_BINDING_TLS_SERVER_END_POINT: + g_tls_cb_t = G_TLS_CHANNEL_BINDING_TLS_SERVER_END_POINT; + break; + case WOCKY_TLS_BINDING_TLS_EXPORTER: + g_tls_cb_t = 100500; + break; + default: + DEBUG ("TLS channel binding is disabled or not supported[%d]", type); + return NULL; + } + + if (g_tls_connection_get_channel_binding_data (tc, + (GTlsChannelBindingType)g_tls_cb_t, NULL, NULL)) + { + GByteArray *cb = g_byte_array_new (); + GError *err = NULL; + + if (g_tls_connection_get_channel_binding_data (tc, + (GTlsChannelBindingType)g_tls_cb_t, cb, &err)) + { + DEBUG ("Got %d bytes of cb data", cb->len); + cb_data = g_base64_encode (cb->data, cb->len); + } + else + { + DEBUG ("Failed to get binding data: %s", err->message); + g_clear_error (&err); + } + g_byte_array_unref (cb); + } +#else + /* The only thing we can do here is to generate SHA256 certificate digest + * and throw it to the server. 0.01% probability the server accepts it. + * Which is why default is tls-unique which this implementation does not + * support and therefore we skip SCRAM-*-PLUS versions by default. + * If you know your server supports tls-server-end-point and uses SHA256 + * certificate signature algorithm - feel free to enable it via ENV + * variable WOCKY_CHANNEL_BINDING_TYPE=tls-server-end-point */ + if (g_tls_cb_t == WOCKY_TLS_BINDING_TLS_SERVER_END_POINT) + { + GTlsCertificate *ps = g_tls_connection_get_peer_certificate (tc); + + if (ps != NULL) + { + GByteArray *der = NULL; + GChecksum *cs = g_checksum_new (G_CHECKSUM_SHA256); + guint8 sha[32]; // 32 bytes, 64 hex, 44 b64 + gsize sl = 32; + + g_object_get (ps, "certificate", &der, NULL); + g_assert (der != NULL); + + g_checksum_update (cs, der->data, der->len); + g_checksum_get_digest (cs, sha, &sl); + cb_data = g_base64_encode (sha, sl); + + g_checksum_free (cs); + g_byte_array_unref (der); + } + } +#endif /* GLIB_VERSION_CUR_STABLE >= 2.66 */ + else + DEBUG ("Requested binding type[%d] is not supported", type); + return cb_data; +} + + /* Initiate sasl auth. features should contain the stream features stanza as * receiver from the server */ void @@ -690,6 +801,20 @@ wocky_sasl_auth_authenticate_async (WockySaslAuth *sasl, goto out; } + if (is_secure) + { + WockyTLSBindingType cb_type = default_cb_type; + gchar *cb_data = wocky_tls_get_cb_data (priv->connection, cb_type); + if (cb_data != NULL) + { + DEBUG ("Using TLS Channel Binding Data: %s", cb_data); + g_object_set (priv->auth_registry, + "tls-binding-type", cb_type, + "tls-binding-data", cb_data, + NULL); + g_free (cb_data); + } + } priv->task = g_task_new (G_OBJECT (sasl), cancellable, callback, user_data); diff --git a/wocky/wocky-sasl-scram.c b/wocky/wocky-sasl-scram.c index fe2eddc..b1ef244 100644 --- a/wocky/wocky-sasl-scram.c +++ b/wocky/wocky-sasl-scram.c @@ -1,6 +1,7 @@ /* - * wocky-sasl-scram.c - SCRAM-SHA1 implementation (to be RFC 5802) + * wocky-sasl-scram.c - SCRAM-SHA-* implementation (RFC 5802, 7677) * Copyright (C) 2010 Sjoerd Simons <sjoerd@luon.net> + * Copyright (C) 2020 Ruslan N. Marchenko <me@ruff.mobi> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -21,6 +22,8 @@ #include "config.h" #endif +#include <gio/gio.h> + #include "wocky-sasl-scram.h" #include "wocky-sasl-auth.h" #include "wocky-sasl-utils.h" @@ -44,6 +47,9 @@ sasl_handler_iface_init (gpointer g_iface); enum { PROP_SERVER = 1, + PROP_CB_TYPE, + PROP_CB_DATA, + PROP_HASH_ALGO, PROP_USERNAME, PROP_PASSWORD }; @@ -51,6 +57,10 @@ enum struct _WockySaslScramPrivate { WockySaslScramState state; + WockyTLSBindingType cb_type; + GChecksumType hash_algo; + gchar *gs2_flag; + gchar *cb_data; gchar *username; gchar *password; gchar *server; @@ -67,6 +77,8 @@ struct _WockySaslScramPrivate guint64 iterations; GByteArray *salted_password; + GByteArray *server_key; + GByteArray *stored_key; }; G_DEFINE_TYPE_WITH_CODE (WockySaslScram, wocky_sasl_scram, @@ -82,6 +94,18 @@ wocky_sasl_scram_get_property ( switch (property_id) { + case PROP_HASH_ALGO: + g_value_set_int (value, priv->hash_algo); + break; + + case PROP_CB_TYPE: + g_value_set_enum (value, priv->cb_type); + break; + + case PROP_CB_DATA: + g_value_set_string (value, priv->cb_data); + break; + case PROP_USERNAME: g_value_set_string (value, priv->username); break; @@ -108,6 +132,19 @@ wocky_sasl_scram_set_property ( switch (property_id) { + case PROP_HASH_ALGO: + priv->hash_algo = g_value_get_int (value); + break; + + case PROP_CB_TYPE: + priv->cb_type = g_value_get_enum (value); + break; + + case PROP_CB_DATA: + g_free (priv->cb_data); + priv->cb_data = g_value_dup_string (value); + break; + case PROP_SERVER: g_free (priv->server); priv->server = g_value_dup_string (value); @@ -137,6 +174,8 @@ wocky_sasl_scram_dispose (GObject *object) g_free (priv->server); g_free (priv->username); g_free (priv->password); + g_free (priv->cb_data); + g_free (priv->gs2_flag); g_free (priv->client_nonce); g_free (priv->nonce); @@ -147,8 +186,9 @@ wocky_sasl_scram_dispose (GObject *object) g_free (priv->auth_message); - if (priv->salted_password != NULL) - g_byte_array_unref (priv->salted_password); + g_clear_pointer (&(priv->salted_password), g_byte_array_unref); + g_clear_pointer (&(priv->server_key), g_byte_array_unref); + g_clear_pointer (&(priv->stored_key), g_byte_array_unref); G_OBJECT_CLASS (wocky_sasl_scram_parent_class)->dispose (object); } @@ -163,6 +203,23 @@ wocky_sasl_scram_class_init ( object_class->set_property = wocky_sasl_scram_set_property; object_class->get_property = wocky_sasl_scram_get_property; + g_object_class_install_property (object_class, PROP_HASH_ALGO, + g_param_spec_int ("hash-algo", "hash algorithm", + "The type of the Hash Algorithm to use for HMAC from GChecksumType", + G_CHECKSUM_SHA1, G_CHECKSUM_SHA512, G_CHECKSUM_SHA256, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_CB_TYPE, + g_param_spec_enum ("cb-type", "binding type", + "The type of the TLS Channel Binding to use in SASL negotiation", + WOCKY_TYPE_TLS_BINDING_TYPE, WOCKY_TLS_BINDING_DISABLED, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_CB_DATA, + g_param_spec_string ("cb-data", "binding data", + "Base64 encoded TLS Channel binding data for the set type", NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, PROP_SERVER, g_param_spec_string ("server", "server", "The name of the server we're authenticating to", NULL, @@ -197,7 +254,7 @@ sasl_handler_iface_init (gpointer g_iface) { WockyAuthHandlerIface *iface = g_iface; - iface->mechanism = "SCRAM-SHA-1"; + iface->mechanism = WOCKY_AUTH_MECH_SASL_SCRAM_SHA_256; iface->plain = FALSE; iface->initial_response_func = scram_initial_response; iface->auth_data_func = scram_handle_auth_data; @@ -241,14 +298,46 @@ scram_initial_response (WockyAuthHandler *handler, return FALSE; } + switch (priv->cb_type) + { + /* no client cb support, make sure we don't stuff cb_data in */ + case WOCKY_TLS_BINDING_DISABLED: + priv->gs2_flag = g_strdup ("n,,"); + g_free (priv->cb_data); + priv->cb_data = NULL; + break; + /* we support channel binding, let's inform the other side */ + case WOCKY_TLS_BINDING_NONE: + /* no server support, wipe cb data, just in case */ + priv->gs2_flag = g_strdup ("y,,"); + g_free (priv->cb_data); + priv->cb_data = NULL; + break; + case WOCKY_TLS_BINDING_TLS_UNIQUE: + priv->gs2_flag = g_strdup ("p=tls-unique,,"); + g_assert (priv->cb_data != NULL); + break; + case WOCKY_TLS_BINDING_TLS_SERVER_END_POINT: + priv->gs2_flag = g_strdup ("p=tls-server-end-point,,"); + g_assert (priv->cb_data != NULL); + break; + case WOCKY_TLS_BINDING_TLS_EXPORTER: + priv->gs2_flag = g_strdup ("p=tls-exporter,,"); + g_assert (priv->cb_data != NULL); + break; + default: + g_assert_not_reached (); + } + g_assert (priv->client_nonce == NULL); priv->client_nonce = sasl_generate_base64_nonce (); - priv->client_first_bare = g_strdup_printf ("n,,n=%s,r=%s", + priv->client_first_bare = g_strdup_printf ("n=%s,r=%s", priv->username, priv->client_nonce); *response = g_string_new (priv->client_first_bare); + g_string_prepend (*response, priv->gs2_flag); priv->state = WOCKY_SASL_SCRAM_STATE_SERVER_FIRST_MESSAGE; @@ -308,6 +397,9 @@ scram_calculate_salted_password (WockySaslScram *self) gsize len; gsize pass_len = strlen (priv->password); + if (priv->salted_password != NULL) + return; + /* salt for U1 */ salt = g_byte_array_new (); /* Make sure we have enough data for the decoding base 64 and add 4 extra @@ -319,7 +411,8 @@ scram_calculate_salted_password (WockySaslScram *self) g_byte_array_append (salt, one, sizeof (one)); /* Calculate U1 */ - result = sasl_calculate_hmac_sha1 ((guint8 *) priv->password, pass_len, + result = sasl_calculate_hmac (priv->hash_algo, + (guint8 *) priv->password, pass_len, salt->data, salt->len); prev = g_byte_array_sized_new (result->len); @@ -328,7 +421,8 @@ scram_calculate_salted_password (WockySaslScram *self) /* Calculate U2 and onwards, while keeping a rolling result */ for (i = 1; i < priv->iterations; i++) { - GByteArray *U = sasl_calculate_hmac_sha1 ((guint8 *) priv->password, + GByteArray *U = sasl_calculate_hmac (priv->hash_algo, + (guint8 *) priv->password, pass_len, prev->data, prev->len); g_byte_array_unref (prev); @@ -342,9 +436,6 @@ scram_calculate_salted_password (WockySaslScram *self) priv->salted_password = result; } -static gchar * -scram_make_client_proof (WockySaslScram *self) -{ /* As per RFC * ClientProof := ClientKey XOR ClientSignature * ClientSignature := HMAC(StoredKey, AuthMessage) @@ -353,30 +444,57 @@ scram_make_client_proof (WockySaslScram *self) * SaltedPassword := Hi(Normalize(password), salt, i) */ #define CLIENT_KEY_STR "Client Key" - WockySaslScramPrivate *priv = self->priv; - gchar *proof = NULL; - GByteArray *client_key, *client_signature; - gsize len = WOCKY_SHA1_DIGEST_SIZE; - guint8 stored_key[WOCKY_SHA1_DIGEST_SIZE]; +static void +scram_calculate_stored_key (WockySaslScram *self, + GByteArray **ckey) +{ + WockySaslScramPrivate *priv = wocky_sasl_scram_get_instance_private (self); + GByteArray *client_key; GChecksum *checksum; + if (priv->stored_key != NULL && ckey == NULL) + return; + g_clear_pointer (&(priv->stored_key), g_byte_array_unref); + /* Calculate the salted password and save it for later as we need it to * verify the servers reply */ scram_calculate_salted_password (self); - client_key = sasl_calculate_hmac_sha1 (priv->salted_password->data, + priv->stored_key = g_byte_array_new (); + g_byte_array_set_size (priv->stored_key, + g_checksum_type_get_length (priv->hash_algo)); + + client_key = sasl_calculate_hmac (priv->hash_algo, + priv->salted_password->data, priv->salted_password->len, (guint8 *) CLIENT_KEY_STR, strlen (CLIENT_KEY_STR)); - checksum = g_checksum_new (G_CHECKSUM_SHA1); + checksum = g_checksum_new (priv->hash_algo); g_checksum_update (checksum, client_key->data, client_key->len); - g_checksum_get_digest (checksum, stored_key, &len); + g_checksum_get_digest (checksum, + priv->stored_key->data, (gsize *)&(priv->stored_key->len)); g_checksum_free (checksum); + if (ckey) + *ckey = client_key; + else + g_byte_array_unref (client_key); +} +#undef CLIENT_KEY_STR + +static gchar * +scram_make_client_proof (WockySaslScram *self) +{ + WockySaslScramPrivate *priv = self->priv; + gchar *proof = NULL; + GByteArray *client_key, *client_signature; + + scram_calculate_stored_key (self, &client_key); + DEBUG ("auth message: %s", priv->auth_message); - client_signature = sasl_calculate_hmac_sha1 (stored_key, - WOCKY_SHA1_DIGEST_SIZE, + client_signature = sasl_calculate_hmac (priv->hash_algo, + priv->stored_key->data, priv->stored_key->len, (guint8 *) priv->auth_message, strlen (priv->auth_message)); /* xor signature and key, overwriting key */ @@ -388,7 +506,6 @@ scram_make_client_proof (WockySaslScram *self) g_byte_array_unref (client_signature); return proof; -#undef CLIENT_KEY_STR } static gboolean @@ -400,6 +517,8 @@ scram_handle_server_first_message (WockySaslScram *self, WockySaslScramPrivate *priv = self->priv; gchar attr, *value = NULL; gchar *proof = NULL; + GByteArray *cb = NULL; + gchar *cb_b64 = NULL; GString *client_reply; if (!scram_get_next_attr_value (&message, &attr, &value)) @@ -435,11 +554,23 @@ scram_handle_server_first_message (WockySaslScram *self, /* We got everything we needed for our response without proof * base64("n,,") => biws */ client_reply = g_string_new (NULL); - g_string_append_printf (client_reply, "c=biws,r=%s", priv->nonce); + if (priv->cb_data) + { + gsize len = 0; + guchar *buf = g_base64_decode (priv->cb_data, &len); + cb = g_byte_array_new_take (buf, len); + } + else + cb = g_byte_array_new (); + cb = g_byte_array_prepend (cb, (const guint8 *)priv->gs2_flag, strlen (priv->gs2_flag)); + cb_b64 = g_base64_encode (cb->data, cb->len); + g_byte_array_unref (cb); + g_string_append_printf (client_reply, "c=%s,r=%s", cb_b64, priv->nonce); + g_free (cb_b64); /* So we can make the auth message */ priv->auth_message = g_strdup_printf ("%s,%s,%s", - priv->client_first_bare + 3, + priv->client_first_bare, priv->server_first_bare, client_reply->str); @@ -479,28 +610,37 @@ unknown_extension: return FALSE; } -static gboolean -scram_check_server_verification (WockySaslScram *self, - gchar *verification) -{ /* * ServerSignature := HMAC(ServerKey, AuthMessage) * ServerKey := HMAC(SaltedPassword, "Server Key") */ #define SERVER_KEY_STR "Server Key" +inline static void +scram_calculate_server_key (WockySaslScram *self) +{ + WockySaslScramPrivate *priv = wocky_sasl_scram_get_instance_private (self); + + if (priv->server_key == NULL) + priv->server_key = sasl_calculate_hmac (priv->hash_algo, + priv->salted_password->data, priv->salted_password->len, + (guint8 *) SERVER_KEY_STR, strlen (SERVER_KEY_STR)); +} +#undef SERVER_KEY_STR + +static gboolean +scram_check_server_verification (WockySaslScram *self, + gchar *verification) +{ WockySaslScramPrivate *priv = self->priv; - GByteArray *server_key; GByteArray *server_signature; gchar *v; gboolean ret; - server_key = sasl_calculate_hmac_sha1 ( - priv->salted_password->data, priv->salted_password->len, - (guint8 *) SERVER_KEY_STR, strlen (SERVER_KEY_STR)); + scram_calculate_server_key (self); - server_signature = sasl_calculate_hmac_sha1 (server_key->data, - server_key->len, (guint8 *) priv->auth_message, - strlen (priv->auth_message)); + server_signature = sasl_calculate_hmac (priv->hash_algo, + priv->server_key->data, priv->server_key->len, + (guint8 *) priv->auth_message, strlen (priv->auth_message)); v = g_base64_encode (server_signature->data, server_signature->len); @@ -511,12 +651,10 @@ scram_check_server_verification (WockySaslScram *self, verification, v); - g_byte_array_unref (server_key); g_byte_array_unref (server_signature); g_free (v); return ret; -#undef SERVER_KEY_STR } @@ -603,3 +741,295 @@ scram_handle_success (WockyAuthHandler *handler, "Server sent success before finishing authentication"); return FALSE; } + +/** + * SASL Server implementation + */ + +/* p=tls-server-end-point,, */ +#define GS2_LEN 25 + +void +wocky_sasl_scram_server_start_async (WockySaslScram *self, + gchar *message, + GAsyncReadyCallback cb, + GCancellable *cancel, + gpointer data) +{ + WockySaslScramPrivate *priv = wocky_sasl_scram_get_instance_private (self); + gchar *msg, atn, *atv; + GTask *task = g_task_new (G_OBJECT (self), cancel, cb, data); + + /* We assert the object is not reused as we don't have reset mechanism */ + g_assert (message); + g_assert (priv->gs2_flag == NULL); + + if (message[0] == 'p' && priv->cb_type > WOCKY_TLS_BINDING_NONE) + { + int i; + priv->gs2_flag = g_malloc0 (GS2_LEN); + for (i = 0; i < (GS2_LEN - 1) && message[i] > 0; i++) + { + priv->gs2_flag[i] = message[i]; + if (message[i] == ',' && message[i-1] == ',') + break; + } + if (i >= (GS2_LEN - 1) || message[i+1] == '\0') + { + g_task_return_new_error (task, WOCKY_AUTH_ERROR, + WOCKY_AUTH_ERROR_INVALID_REPLY, + "Malformed message: missing gs2_flag"); + g_object_unref (task); + return; + } + msg = message + i + 1; + } + else if (message[0] == 'y' && priv->cb_type == WOCKY_TLS_BINDING_DISABLED) + { + priv->gs2_flag = g_strdup ("y,,"); + msg = message + 3; + } + else if (message[0] == 'n') + { + priv->gs2_flag = g_strdup ("n,,"); + msg = message + 3; + priv->cb_type = WOCKY_TLS_BINDING_DISABLED; + g_clear_pointer (&(priv->cb_data), g_free); + } + else + { + g_task_return_new_error (task, WOCKY_AUTH_ERROR, + WOCKY_AUTH_ERROR_INVALID_REPLY, + "Malformed message: unexpected gs2_flag"); + g_object_unref (task); + return; + } + + g_assert (priv->client_first_bare == NULL); + priv->client_first_bare = g_strdup (msg); + + if (!scram_get_next_attr_value (&msg, &atn, &atv) || atn != 'n') + { + g_task_return_new_error (task, WOCKY_AUTH_ERROR, + WOCKY_AUTH_ERROR_INVALID_REPLY, + "Malformed message: missing identity name"); + g_object_unref (task); + return; + } + + g_assert (priv->username == NULL); + g_assert (priv->password == NULL); + priv->username = g_strdup (atv); + + if (!scram_get_next_attr_value (&msg, &atn, &atv) || atn != 'r') + { + g_task_return_new_error (task, WOCKY_AUTH_ERROR, + WOCKY_AUTH_ERROR_INVALID_REPLY, + "Malformed message: missing client nonce"); + g_object_unref (task); + return; + } + + g_assert (priv->client_nonce == NULL); + priv->client_nonce = g_strdup (atv); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +gchar * +wocky_sasl_scram_server_start_finish (WockySaslScram *self, + GAsyncResult *res, + GError **error) +{ + WockySaslScramPrivate *priv = wocky_sasl_scram_get_instance_private (self); + + g_return_val_if_fail (g_task_is_valid (res, self), NULL); + + g_assert (priv->server_first_bare == NULL); + + if (g_task_propagate_boolean (G_TASK (res), error)) + { + /* We expect caller to check IdP for `username` existance and + * set either `password` and we'll send random salt+iter, or + * alternatively set salt, iter, server_key - and we go from + * there. + */ + if (priv->password) + { + /* We shall not have both (password and keys) pre-set */ + g_assert (priv->salted_password == NULL); + g_assert (priv->server_key == NULL); + g_assert (priv->stored_key == NULL); + g_assert (priv->salt == NULL); + + /* Let's calculate all the keys and wipe clear-text password */ + priv->iterations = 8192; + priv->salt = sasl_generate_base64_nonce (); + scram_calculate_salted_password (self); + scram_calculate_server_key (self); + scram_calculate_stored_key (self, NULL); + } + else if (priv->server_key == NULL || priv->stored_key == NULL + || priv->salt == NULL || priv->iterations == 0) + { + *error = g_error_new_literal (WOCKY_AUTH_ERROR, + WOCKY_AUTH_ERROR_NO_CREDENTIALS, + "Cannot generate challenge without clear or salted password"); + return NULL; + } + + g_assert (priv->nonce == NULL); + priv->nonce = sasl_generate_base64_nonce (); + + priv->server_first_bare = g_strdup_printf ("r=%s%s,s=%s,i=%lu", priv->client_nonce, + priv->nonce, priv->salt, priv->iterations); + priv->state = WOCKY_SASL_SCRAM_STATE_SERVER_FIRST_MESSAGE; + } + return priv->server_first_bare; +} + +void +wocky_sasl_scram_server_step_async (WockySaslScram *self, + gchar *message, + GAsyncReadyCallback cb, + GCancellable *cancel, + gpointer data) +{ + WockySaslScramPrivate *priv = wocky_sasl_scram_get_instance_private (self); + gchar *msg, atn, *atv, *c, *r, *p, *proof; + GTask *task = g_task_new (G_OBJECT (self), cancel, cb, data); + GByteArray *buf; + + g_assert (message); + g_assert (priv->state == WOCKY_SASL_SCRAM_STATE_SERVER_FIRST_MESSAGE); + + msg = message; + if (!scram_get_next_attr_value (&msg, &atn, &atv) || atn != 'c') + { + g_task_return_new_error (task, WOCKY_AUTH_ERROR, + WOCKY_AUTH_ERROR_INVALID_REPLY, + "Malformed message: missing client bindings"); + g_object_unref (task); + return; + } + c = atv; + + if (!scram_get_next_attr_value (&msg, &atn, &atv) || atn != 'r') + { + g_task_return_new_error (task, WOCKY_AUTH_ERROR, + WOCKY_AUTH_ERROR_INVALID_REPLY, + "Malformed message: missing client nonce"); + g_object_unref (task); + return; + } + r = atv; + + if (!scram_get_next_attr_value (&msg, &atn, &atv) || atn != 'p') + { + g_task_return_new_error (task, WOCKY_AUTH_ERROR, + WOCKY_AUTH_ERROR_INVALID_REPLY, + "Malformed message: missing client proof"); + g_object_unref (task); + return; + } + p = atv; + + /* Data is collected, let's verify it */ + if (priv->cb_data) + { + gsize len = 0; + guchar *cbd = g_base64_decode (priv->cb_data, &len); + buf = g_byte_array_new_take (cbd, len); + } + else + buf = g_byte_array_new (); + buf = g_byte_array_prepend (buf, (const guint8 *)priv->gs2_flag, strlen (priv->gs2_flag)); + proof = g_base64_encode (buf->data, buf->len); + g_byte_array_unref (buf); + if (g_strcmp0 (c, proof)) + { + g_task_return_new_error (task, WOCKY_AUTH_ERROR, WOCKY_AUTH_ERROR_FAILURE, + "Malformed message: wrong binding"); + g_object_unref (task); + return; + } + g_free (proof); + + proof = g_strdup_printf ("%s%s", priv->client_nonce, priv->nonce); + if (g_strcmp0 (r, proof)) + { + g_task_return_new_error (task, WOCKY_AUTH_ERROR, WOCKY_AUTH_ERROR_FAILURE, + "Malformed message: wrong nonce"); + g_object_unref (task); + return; + } + g_free (proof); + + /* Let's make the auth message now and do the rest in finish */ + priv->auth_message = g_strdup_printf ("%s,%s,c=%s,r=%s", + priv->client_first_bare, priv->server_first_bare, c, r); + + DEBUG ("auth message: %s", priv->auth_message); + + g_task_return_pointer (task, g_strdup (p), g_free); +} + +gchar * +wocky_sasl_scram_server_step_finish (WockySaslScram *self, + GAsyncResult *res, + GError **error) +{ + WockySaslScramPrivate *priv = wocky_sasl_scram_get_instance_private (self); + gchar *proof = NULL; + gchar *challenge = NULL; + + g_return_val_if_fail (g_task_is_valid (res, self), NULL); + + proof = g_task_propagate_pointer (G_TASK (res), error); + if (proof) + { + gsize len = 0; + guchar *p = g_base64_decode (proof, &len); + GByteArray *client_proof = g_byte_array_new_take (p, len); + GByteArray *client_signature = sasl_calculate_hmac (priv->hash_algo, + priv->stored_key->data, priv->stored_key->len, + (guint8 *) priv->auth_message, strlen (priv->auth_message)); + GChecksum *checksum = g_checksum_new (priv->hash_algo); + + g_free (proof); + + /* xor signature and proof, overwriting proof with key */ + scram_xor_array (client_proof, client_signature); + g_byte_array_unref (client_signature); + + /* Re-calculate stored key from recovered client key and compare */ + len = g_checksum_type_get_length (priv->hash_algo); + p = g_malloc (len); + g_checksum_update (checksum, client_proof->data, client_proof->len); + g_checksum_get_digest (checksum, p, &len); + g_checksum_free (checksum); + + if (len == priv->stored_key->len && !memcmp (p, priv->stored_key->data, len)) + { + GByteArray *server_signature = sasl_calculate_hmac (priv->hash_algo, + priv->server_key->data, priv->server_key->len, + (guint8 *) priv->auth_message, strlen (priv->auth_message)); + gchar *v = g_base64_encode (server_signature->data, server_signature->len); + + challenge = g_strdup_printf ("v=%s", v); + + g_byte_array_unref (server_signature); + g_free (v); + } + else if (error != NULL) + { + *error = g_error_new_literal (WOCKY_AUTH_ERROR, + WOCKY_AUTH_ERROR_NOT_AUTHORIZED, + "Invalid password"); + } + g_byte_array_unref (client_proof); + g_free (p); + } + return challenge; +} diff --git a/wocky/wocky-sasl-scram.h b/wocky/wocky-sasl-scram.h index 204053b..211a8cd 100644 --- a/wocky/wocky-sasl-scram.h +++ b/wocky/wocky-sasl-scram.h @@ -1,6 +1,7 @@ /* - * wocky-sasl-scram.h - SCRAM-SHA1 implementation (to be RFC 5802) + * wocky-sasl-scram.h - SCRAM-SHA1 implementation (RFC 5802, 7677) * Copyright (C) 2010 Sjoerd Simons <sjoerd@luon.net> + * Copyright (C) 2020 Ruslan N. Marchenko <me@ruff.mobi> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -70,6 +71,30 @@ WockySaslScram * wocky_sasl_scram_new ( const gchar *server, const gchar *username, const gchar *password); +void +wocky_sasl_scram_server_start_async (WockySaslScram *self, + gchar *message, + GAsyncReadyCallback cb, + GCancellable *cancel, + gpointer data); + +gchar * +wocky_sasl_scram_server_start_finish (WockySaslScram *self, + GAsyncResult *res, + GError **error); + +void +wocky_sasl_scram_server_step_async (WockySaslScram *self, + gchar *message, + GAsyncReadyCallback cb, + GCancellable *cancel, + gpointer data); + +gchar * +wocky_sasl_scram_server_step_finish (WockySaslScram *self, + GAsyncResult *res, + GError **error); + G_END_DECLS #endif /* _WOCKY_SASL_SCRAM_H */ diff --git a/wocky/wocky-sasl-utils.c b/wocky/wocky-sasl-utils.c index 5dbad28..56f643b 100644 --- a/wocky/wocky-sasl-utils.c +++ b/wocky/wocky-sasl-utils.c @@ -44,64 +44,20 @@ sasl_generate_base64_nonce (void) } GByteArray * -sasl_calculate_hmac_sha1 (guint8 *key, +sasl_calculate_hmac (GChecksumType digest_type, + guint8 *key, gsize key_len, guint8 *text, gsize text_len) { -/* Calculate the HMAC keyed hash algorithm as defined in RFC2104, using - * SHA-1 as the hash algorithm */ - GChecksum *checksum; - guint8 k_ipad[WOCKY_SHA1_BLOCK_SIZE]; - guint8 k_opad[WOCKY_SHA1_BLOCK_SIZE]; - guint8 inner_checksum[WOCKY_SHA1_DIGEST_SIZE]; - GByteArray *result; - gsize len = WOCKY_SHA1_DIGEST_SIZE, i; + GHmac *hmac = g_hmac_new (digest_type, key, key_len); + gsize len = g_checksum_type_get_length (digest_type); + guint8 *digest = g_new (guint8, len); - memset (k_ipad, 0x36, WOCKY_SHA1_BLOCK_SIZE); - memset (k_opad, 0x5c, WOCKY_SHA1_BLOCK_SIZE); + g_hmac_update (hmac, text, text_len); + g_hmac_get_digest (hmac, digest, &len); - if (key_len > WOCKY_SHA1_BLOCK_SIZE) - { - guchar k[WOCKY_SHA1_DIGEST_SIZE]; - - checksum = g_checksum_new (G_CHECKSUM_SHA1); - g_checksum_update (checksum, key, key_len); - g_checksum_get_digest (checksum, k, &len); - g_checksum_free (checksum); - - for (i = 0; i < WOCKY_SHA1_DIGEST_SIZE; i++) - { - k_ipad[i] ^= k[i]; - k_opad[i] ^= k[i]; - } - } - else - { - for (i = 0; i < key_len; i++) - { - k_ipad[i] ^= key[i]; - k_opad[i] ^= key[i]; - } - } - - /* inner checksum */ - checksum = g_checksum_new (G_CHECKSUM_SHA1); - g_checksum_update (checksum, k_ipad, WOCKY_SHA1_BLOCK_SIZE); - g_checksum_update (checksum, text, text_len); - g_checksum_get_digest (checksum, inner_checksum, &len); - g_checksum_free (checksum); - - /* outer checksum */ - result = g_byte_array_new (); - g_byte_array_set_size (result, WOCKY_SHA1_DIGEST_SIZE); - - checksum = g_checksum_new (G_CHECKSUM_SHA1); - g_checksum_update (checksum, k_opad, WOCKY_SHA1_BLOCK_SIZE); - g_checksum_update (checksum, inner_checksum, WOCKY_SHA1_DIGEST_SIZE); - g_checksum_get_digest (checksum, result->data, &len); - g_checksum_free (checksum); - - return result; + g_hmac_unref (hmac); + return g_byte_array_new_take (digest, len); } diff --git a/wocky/wocky-sasl-utils.h b/wocky/wocky-sasl-utils.h index 0858b39..dfd5626 100644 --- a/wocky/wocky-sasl-utils.h +++ b/wocky/wocky-sasl-utils.h @@ -28,7 +28,8 @@ #define WOCKY_SHA1_DIGEST_SIZE 20 gchar * sasl_generate_base64_nonce (void); -GByteArray * sasl_calculate_hmac_sha1 (guint8 *key, +GByteArray * sasl_calculate_hmac (GChecksumType digest_type, + guint8 *key, gsize key_len, guint8 *text, gsize text_len); |