summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorruff <me@ruff.mobi>2020-10-05 18:53:29 +0200
committerGitHub <noreply@github.com>2020-10-05 18:53:29 +0200
commitba6f20086999f2d7d7dafa75c77552a06f5e7d5b (patch)
tree0d20f94f46e269e80dd83f2a5ecfb50311a5f643
parent67edd727512749faa0bcb4c5202cd4146c0e389e (diff)
parentfa4145eb9c300eab9f002675de56e8b9021730a4 (diff)
Merge pull request #13 from rufferson/binding
SASL SCRAM SHA2 and TLS channel binding
-rw-r--r--tests/wocky-connector-test.c129
-rw-r--r--tests/wocky-sasl-utils-test.c3
-rw-r--r--tests/wocky-scram-sha1-test.c2
-rw-r--r--tests/wocky-test-sasl-auth-server.c223
-rw-r--r--tests/wocky-test-sasl-auth-server.h3
-rw-r--r--tests/wocky-test-sasl-auth.c11
-rw-r--r--wocky/wocky-auth-registry.c96
-rw-r--r--wocky/wocky-auth-registry.h29
-rw-r--r--wocky/wocky-sasl-auth.c125
-rw-r--r--wocky/wocky-sasl-scram.c502
-rw-r--r--wocky/wocky-sasl-scram.h27
-rw-r--r--wocky/wocky-sasl-utils.c62
-rw-r--r--wocky/wocky-sasl-utils.h3
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);