diff options
author | Ruslan N. Marchenko <me@ruff.mobi> | 2020-10-04 20:34:41 +0200 |
---|---|---|
committer | Ruslan N. Marchenko <me@ruff.mobi> | 2020-10-05 15:40:53 +0200 |
commit | 43ea7cd17b98622b03495c90815266126cb8a89b (patch) | |
tree | f0c4fbe93ce6e0d35b70c0275f07dc48536e0bec | |
parent | 0d6cda99149d470c4968b1993a204b1b4f894339 (diff) |
Add SASL server-part implementation to wocky-sasl-scram
-rw-r--r-- | wocky/wocky-sasl-scram.c | 396 | ||||
-rw-r--r-- | wocky/wocky-sasl-scram.h | 24 |
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 */ |