summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRuslan N. Marchenko <me@ruff.mobi>2020-10-04 20:34:41 +0200
committerRuslan N. Marchenko <me@ruff.mobi>2020-10-05 15:40:53 +0200
commit43ea7cd17b98622b03495c90815266126cb8a89b (patch)
treef0c4fbe93ce6e0d35b70c0275f07dc48536e0bec
parent0d6cda99149d470c4968b1993a204b1b4f894339 (diff)
Add SASL server-part implementation to wocky-sasl-scram
-rw-r--r--wocky/wocky-sasl-scram.c396
-rw-r--r--wocky/wocky-sasl-scram.h24
2 files changed, 386 insertions, 34 deletions
diff --git a/wocky/wocky-sasl-scram.c b/wocky/wocky-sasl-scram.c
index 9ed2859..b1ef244 100644
--- a/wocky/wocky-sasl-scram.c
+++ b/wocky/wocky-sasl-scram.c
@@ -22,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"
@@ -57,7 +59,7 @@ struct _WockySaslScramPrivate
WockySaslScramState state;
WockyTLSBindingType cb_type;
GChecksumType hash_algo;
- const gchar *gs2_flag;
+ gchar *gs2_flag;
gchar *cb_data;
gchar *username;
gchar *password;
@@ -75,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,
@@ -171,6 +175,7 @@ wocky_sasl_scram_dispose (GObject *object)
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);
@@ -181,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);
}
@@ -296,27 +302,27 @@ scram_initial_response (WockyAuthHandler *handler,
{
/* no client cb support, make sure we don't stuff cb_data in */
case WOCKY_TLS_BINDING_DISABLED:
- priv->gs2_flag = "n,,";
+ 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 = "y,,";
+ 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 = "p=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 = "p=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 = "p=tls-exporter,,";
+ priv->gs2_flag = g_strdup ("p=tls-exporter,,");
g_assert (priv->cb_data != NULL);
break;
default:
@@ -391,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
@@ -427,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)
@@ -438,18 +444,26 @@ 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 = g_checksum_type_get_length (priv->hash_algo);
- guint8 *stored_key;
+static void
+scram_calculate_stored_key (WockySaslScram *self,
+ GByteArray **ckey)
+{
+ WockySaslScramPrivate *priv = wocky_sasl_scram_get_instance_private (self);
+ GByteArray *client_key;
GChecksum *checksum;
- stored_key = g_new (guint8, len);
+ 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);
+ 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,
@@ -457,13 +471,30 @@ scram_make_client_proof (WockySaslScram *self)
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 (priv->hash_algo,
- stored_key, len,
+ priv->stored_key->data, priv->stored_key->len,
(guint8 *) priv->auth_message, strlen (priv->auth_message));
/* xor signature and key, overwriting key */
@@ -471,12 +502,10 @@ scram_make_client_proof (WockySaslScram *self)
proof = g_base64_encode (client_key->data, client_key->len);
- g_free (stored_key);
g_byte_array_unref (client_key);
g_byte_array_unref (client_signature);
return proof;
-#undef CLIENT_KEY_STR
}
static gboolean
@@ -581,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 (priv->hash_algo,
- 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 (priv->hash_algo, 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);
@@ -613,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
}
@@ -705,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 65b5ca9..211a8cd 100644
--- a/wocky/wocky-sasl-scram.h
+++ b/wocky/wocky-sasl-scram.h
@@ -71,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 */