Implement the ANONYMOUS mechanism from RFC 4505
Testing Done:
Ran the unit tests.
Bugs closed: HASL-6
Reviewed at https://reviews.imfreedom.org/r/2505/
--- a/hasl/haslcontext.c Sun Jul 09 14:48:55 2023 -0500
+++ b/hasl/haslcontext.c Fri Jul 14 01:51:47 2023 -0500
@@ -19,6 +19,7 @@
+#include "haslmechanismanonymous.h" #include "haslmechanismexternal.h"
#include "haslmechanismplain.h"
@@ -133,6 +134,8 @@
ctx->current_mechanism_index = -1;
+ hasl_context_add_mechanism(ctx, "ANONYMOUS", + HASL_TYPE_MECHANISM_ANONYMOUS); hasl_context_add_mechanism(ctx, "EXTERNAL", HASL_TYPE_MECHANISM_EXTERNAL);
hasl_context_add_mechanism(ctx, "PLAIN", HASL_TYPE_MECHANISM_PLAIN);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hasl/haslmechanismanonymous.c Fri Jul 14 01:51:47 2023 -0500
@@ -0,0 +1,125 @@
+ * Copyright (C) 2023 Hasl Developers + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <https://www.gnu.org/licenses/>. +#include <glib/gi18n-lib.h> +#include "haslmechanismanonymous.h" +struct _HaslMechanismAnonymous { +G_DEFINE_TYPE(HaslMechanismAnonymous, hasl_mechanism_anonymous, +/****************************************************************************** + * HaslMechanism Implementation + *****************************************************************************/ +hasl_mechanism_anonymous_possible(G_GNUC_UNUSED HaslMechanism *mechanism, + const char *value = NULL; + value = hasl_context_get_username(context); + if(value == NULL || value[0] == '\0') { + g_set_error_literal(error, HASL_MECHANISM_ANONYMOUS_DOMAIN, 0, + _("missing username")); + if(!hasl_context_get_allow_clear_text(context)) { + if(!hasl_context_get_tls(context)) { + g_set_error_literal(error, HASL_MECHANISM_ANONYMOUS_DOMAIN, 0, + _("plain text is not allowed without TLS")); +static HaslMechanismResult +hasl_mechanism_anonymous_step(G_GNUC_UNUSED HaslMechanism *mechanism, + G_GNUC_UNUSED const guint8 *server_in, + G_GNUC_UNUSED gsize server_in_length, + gsize *client_out_length, + GByteArray *data = NULL; + const char *username = NULL; + char buffer[1024]; /* Up to 1020 bytes is allowed per the RFC. */ + username = hasl_context_get_username(ctx); + if(username == NULL || username[0] == '\0') { + g_set_error_literal(error, HASL_MECHANISM_ANONYMOUS_DOMAIN, 0, + _("no username provided")); + return HASL_MECHANISM_RESULT_ERROR; + /* Copy the username into the buffer. */ + g_strlcpy(buffer, username, sizeof(buffer)); + /* Use the stringprep trace profile per the RFC. */ + res = stringprep(buffer, sizeof(buffer), 0, stringprep_trace); + if(res != STRINGPREP_OK) { + g_set_error(error, HASL_MECHANISM_ANONYMOUS_DOMAIN, 0, + _("stringprep failed: %s"), stringprep_strerror(res)); + return HASL_MECHANISM_RESULT_ERROR; + /* We have a saslpreped username, so we can build our payload. */ + data = g_byte_array_new(); + g_byte_array_append(data, (guint8 *)buffer, strlen(buffer)); + /* Set our out variables and return. */ + if(client_out_length != NULL) { + *client_out_length = data->len; + *client_out = g_byte_array_free(data, FALSE); + return HASL_MECHANISM_RESULT_SUCCESS; +/****************************************************************************** + * GObject Implementation + *****************************************************************************/ +hasl_mechanism_anonymous_init(G_GNUC_UNUSED HaslMechanismAnonymous *anonymous) +hasl_mechanism_anonymous_class_init(HaslMechanismAnonymousClass *klass) { + HaslMechanismClass *mechanism_class = HASL_MECHANISM_CLASS(klass); + mechanism_class->possible = hasl_mechanism_anonymous_possible; + mechanism_class->step = hasl_mechanism_anonymous_step; --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hasl/haslmechanismanonymous.h Fri Jul 14 01:51:47 2023 -0500
@@ -0,0 +1,51 @@
+ * Copyright (C) 2023 Hasl Developers + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <https://www.gnu.org/licenses/>. +#ifndef HASL_MECHANISM_ANONYMOUS_H +#define HASL_MECHANISM_ANONYMOUS_H +#include <glib-object.h> +#include <hasl/haslcontext.h> +#include <hasl/haslmechanism.h> +#define HASL_MECHANISM_ANONYMOUS_DOMAIN (g_quark_from_static_string("hasl-mechanism-anonymous")) + * HaslMechanismAnonymous: + * This mechanism implements the ANONYMOUS method described in + * [RFC 4505](https://www.rfc-editor.org/rfc/rfc4505). It uses the value of + * [property@Context:username] as the token. + * This mechanism is not secure, so you must set + * [property@Context:allow-clear-text] to %TRUE if [property@Context:tls] is +#define HASL_TYPE_MECHANISM_ANONYMOUS (hasl_mechanism_anonymous_get_type()) +G_DECLARE_FINAL_TYPE(HaslMechanismAnonymous, hasl_mechanism_anonymous, HASL, + MECHANISM_ANONYMOUS, HaslMechanism) +#endif /* HASL_MECHANISM_ANONYMOUS_H */ --- a/hasl/meson.build Sun Jul 09 14:48:55 2023 -0500
+++ b/hasl/meson.build Fri Jul 14 01:51:47 2023 -0500
@@ -1,6 +1,7 @@
+ 'haslmechanismanonymous.c', 'haslmechanismexternal.c',
@@ -9,6 +10,7 @@
+ 'haslmechanismanonymous.h', 'haslmechanismexternal.h',
@@ -60,7 +62,7 @@
c_args : ['-DHASL_COMPILATION', '-DG_LOG_USE_STRUCTURED',
'-DG_LOG_DOMAIN="Hasl"'],
- dependencies: [glib, gobject],
+ dependencies: [glib, gobject, libidn], include_directories : toplevel_inc,
version: HASL_LIB_VERSION)
@@ -102,7 +104,7 @@
include_directories : [toplevel_inc, include_directories('.')],
sources : [HASL_GENERATED_TARGETS, HASL_BUILT_HEADERS],
- dependencies : [glib, gobject]
+ dependencies : [glib, gobject, libidn] meson.override_dependency('hasl', hasl_dep)
--- a/hasl/tests/meson.build Sun Jul 09 14:48:55 2023 -0500
+++ b/hasl/tests/meson.build Fri Jul 14 01:51:47 2023 -0500
@@ -2,6 +2,7 @@
--- a/hasl/tests/test-context.c Sun Jul 09 14:48:55 2023 -0500
+++ b/hasl/tests/test-context.c Fri Jul 14 01:51:47 2023 -0500
@@ -122,7 +122,7 @@
ctx = hasl_context_new();
supported_mechanisms = hasl_context_get_supported_mechanisms(ctx);
- g_assert_cmpstr(supported_mechanisms, ==, "EXTERNAL PLAIN");
+ g_assert_cmpstr(supported_mechanisms, ==, "ANONYMOUS EXTERNAL PLAIN"); g_free(supported_mechanisms);
@@ -146,7 +146,8 @@
/* Now get the list and check it. */
supported_mechanisms = hasl_context_get_supported_mechanisms(ctx);
- g_assert_cmpstr(supported_mechanisms, ==, "DEBUG EXTERNAL PLAIN TEST");
+ g_assert_cmpstr(supported_mechanisms, ==, + "ANONYMOUS DEBUG EXTERNAL PLAIN TEST"); g_free(supported_mechanisms);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hasl/tests/test-mechanism-anonymous.c Fri Jul 14 01:51:47 2023 -0500
@@ -0,0 +1,198 @@
+ * Copyright (C) 2023 Hasl Developers + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <https://www.gnu.org/licenses/>. +/****************************************************************************** + *****************************************************************************/ +test_hasl_mechanism_anonymous_error_test(HaslContext *context) { + HaslMechanism *mechanism = NULL; + HaslMechanismResult result = 0; + guint8 *client_out = NULL; + gsize client_out_length = 0; + mechanism = g_object_new(HASL_TYPE_MECHANISM_ANONYMOUS, NULL); + result = hasl_mechanism_step(mechanism, context, NULL, 0, &client_out, + &client_out_length, &error); + g_assert_error(error, HASL_MECHANISM_ANONYMOUS_DOMAIN, 0); + g_assert_cmpint(result, ==, HASL_MECHANISM_RESULT_ERROR); + g_assert_null(client_out); + g_assert_cmpint(client_out_length, ==, 0); + g_clear_object(&mechanism); +test_hasl_mechanism_anonymous_possible(HaslContext *context, gboolean expected, + HaslMechanism *mechanism = NULL; + mechanism = g_object_new(HASL_TYPE_MECHANISM_ANONYMOUS, NULL); + ret = hasl_mechanism_possible(mechanism, context, &error); + g_assert_error(error, HASL_MECHANISM_ANONYMOUS_DOMAIN, 0); + g_assert_no_error(error); + g_assert_true(ret == expected); + g_clear_object(&mechanism); +/****************************************************************************** + *****************************************************************************/ +test_hasl_mechanism_anonymous_new(void) { + HaslMechanism *mechanism = NULL; + mechanism = g_object_new(HASL_TYPE_MECHANISM_ANONYMOUS, NULL); + g_assert_true(HASL_IS_MECHANISM_ANONYMOUS(mechanism)); + g_clear_object(&mechanism); +test_hasl_mechanism_anonymous_username_required_null(void) { + HaslContext *context = NULL; + context = hasl_context_new(); + hasl_context_set_password(context, "hunter2"); + test_hasl_mechanism_anonymous_error_test(context); + g_clear_object(&context); +test_hasl_mechanism_anonymous_username_required_empty(void) { + HaslContext *context = NULL; + context = hasl_context_new(); + hasl_context_set_username(context, ""); + hasl_context_set_password(context, "hunter2"); + test_hasl_mechanism_anonymous_error_test(context); + g_clear_object(&context); +test_hasl_mechanism_anonymous_possible_empty_context(void) { + HaslContext *context = hasl_context_new(); + test_hasl_mechanism_anonymous_possible(context, FALSE, TRUE); + g_clear_object(&context); +test_hasl_mechanism_anonymous_possible_username(void) { + HaslContext *context = hasl_context_new(); + hasl_context_set_tls(context, TRUE); + hasl_context_set_username(context, "alice"); + test_hasl_mechanism_anonymous_possible(context, TRUE, FALSE); + g_clear_object(&context); +test_hasl_mechanism_anonymous_possible_allow_plain_in_clear_without_tls(void) { + HaslContext *context = hasl_context_new(); + hasl_context_set_username(context, "alice"); + hasl_context_set_allow_clear_text(context, TRUE); + test_hasl_mechanism_anonymous_possible(context, TRUE, FALSE); + g_clear_object(&context); +test_hasl_mechanism_anonymous_possible_allow_plain_in_clear_with_tls(void) { + HaslContext *context = hasl_context_new(); + hasl_context_set_username(context, "alice"); + hasl_context_set_allow_clear_text(context, TRUE); + hasl_context_set_tls(context, TRUE); + test_hasl_mechanism_anonymous_possible(context, TRUE, FALSE); + g_clear_object(&context); +test_hasl_mechanism_anonymous_possible_allow_plain_in_clear_required(void) { + HaslContext *context = hasl_context_new(); + hasl_context_set_username(context, "alice"); + hasl_context_set_tls(context, FALSE); + test_hasl_mechanism_anonymous_possible(context, FALSE, TRUE); + g_clear_object(&context); +/****************************************************************************** + *****************************************************************************/ +main(int argc, char *argv[]) { + g_test_init(&argc, &argv, NULL); + g_test_add_func("/hasl/mechanism-anonymous/new", + test_hasl_mechanism_anonymous_new); + g_test_add_func("/hasl/mechanism-anonymous/username-required/null", + test_hasl_mechanism_anonymous_username_required_null); + g_test_add_func("/hasl/mechanism-anonymous/username-required/empty", + test_hasl_mechanism_anonymous_username_required_empty); + g_test_add_func("/hasl/mechanism-anonymous/possible/empty-context", + test_hasl_mechanism_anonymous_possible_empty_context); + g_test_add_func("/hasl/mechanism-anonymous/possible/username", + test_hasl_mechanism_anonymous_possible_username); + g_test_add_func("/hasl/mechanism-anonymous/possible/allow-plain-in-clear-without-tls", + test_hasl_mechanism_anonymous_possible_allow_plain_in_clear_without_tls); + g_test_add_func("/hasl/mechanism-anonymous/possible/allow-plain-in-clear-with-tls", + test_hasl_mechanism_anonymous_possible_allow_plain_in_clear_with_tls); + g_test_add_func("/hasl/mechanism-anonymous/possible/allow-plain-in-clear-required", + test_hasl_mechanism_anonymous_possible_allow_plain_in_clear_required); --- a/meson.build Sun Jul 09 14:48:55 2023 -0500
+++ b/meson.build Fri Jul 14 01:51:47 2023 -0500
@@ -37,6 +37,7 @@
###############################################################################
glib = dependency('glib-2.0', version : '>= 2.70')
gobject = dependency('gobject-2.0', version : '>= 2.70')
+libidn = dependency('libidn', version : '>= 1.41') ###############################################################################
--- a/po/POTFILES Sun Jul 09 14:48:55 2023 -0500
+++ b/po/POTFILES Fri Jul 14 01:51:47 2023 -0500
@@ -5,5 +5,6 @@
hasl/tests/test-context.c
hasl/tests/test-mechanism.c
+hasl/tests/test-mechanism-anonymous.c hasl/tests/test-mechanism-external.c
hasl/tests/test-mechanism-plain.c