hasl/hasl

cd8b2d2ccaa7
Parents 1bc73f415181
Children 68a02c62a011
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 "haslcontext.h"
+#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 <stringprep.h>
+
+#include "haslmechanismanonymous.h"
+
+#include "haslcore.h"
+
+struct _HaslMechanismAnonymous {
+ HaslMechanism parent;
+};
+
+G_DEFINE_TYPE(HaslMechanismAnonymous, hasl_mechanism_anonymous,
+ HASL_TYPE_MECHANISM)
+
+/******************************************************************************
+ * HaslMechanism Implementation
+ *****************************************************************************/
+static gboolean
+hasl_mechanism_anonymous_possible(G_GNUC_UNUSED HaslMechanism *mechanism,
+ HaslContext *context,
+ GError **error)
+{
+ 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"));
+
+ return FALSE;
+ }
+
+ 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"));
+
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static HaslMechanismResult
+hasl_mechanism_anonymous_step(G_GNUC_UNUSED HaslMechanism *mechanism,
+ HaslContext *ctx,
+ G_GNUC_UNUSED const guint8 *server_in,
+ G_GNUC_UNUSED gsize server_in_length,
+ guint8 **client_out,
+ gsize *client_out_length,
+ GError **error)
+{
+ GByteArray *data = NULL;
+ const char *username = NULL;
+ char buffer[1024]; /* Up to 1020 bytes is allowed per the RFC. */
+ int res = 0;
+
+ 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
+ *****************************************************************************/
+static void
+hasl_mechanism_anonymous_init(G_GNUC_UNUSED HaslMechanismAnonymous *anonymous)
+{
+}
+
+static void
+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.h>
+#include <glib-object.h>
+
+#include <hasl/haslcontext.h>
+#include <hasl/haslmechanism.h>
+
+G_BEGIN_DECLS
+
+#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
+ * %FALSE.
+ *
+ * Since: 0.2.0
+ */
+
+#define HASL_TYPE_MECHANISM_ANONYMOUS (hasl_mechanism_anonymous_get_type())
+G_DECLARE_FINAL_TYPE(HaslMechanismAnonymous, hasl_mechanism_anonymous, HASL,
+ MECHANISM_ANONYMOUS, HaslMechanism)
+
+G_END_DECLS
+
+#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 @@
HASL_SOURCES = [
'haslcontext.c',
'haslmechanism.c',
+ 'haslmechanismanonymous.c',
'haslmechanismexternal.c',
'haslmechanismplain.c',
]
@@ -9,6 +10,7 @@
'haslcontext.h',
'haslcore.h',
'haslmechanism.h',
+ 'haslmechanismanonymous.h',
'haslmechanismexternal.h',
'haslmechanismplain.h',
]
@@ -60,7 +62,7 @@
HASL_BUILT_HEADERS,
c_args : ['-DHASL_COMPILATION', '-DG_LOG_USE_STRUCTURED',
'-DG_LOG_DOMAIN="Hasl"'],
- dependencies: [glib, gobject],
+ dependencies: [glib, gobject, libidn],
include_directories : toplevel_inc,
install : true,
version: HASL_LIB_VERSION)
@@ -102,7 +104,7 @@
include_directories : [toplevel_inc, include_directories('.')],
link_with : libhasl,
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 @@
'context',
'full',
'mechanism',
+ 'mechanism-anonymous',
'mechanism-external',
'mechanism-plain',
]
--- 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);
g_clear_object(&ctx);
@@ -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);
g_clear_object(&ctx);
--- /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/>.
+ */
+
+#include <glib.h>
+
+#include <hasl.h>
+
+/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static void
+test_hasl_mechanism_anonymous_error_test(HaslContext *context) {
+ HaslMechanism *mechanism = NULL;
+ HaslMechanismResult result = 0;
+ GError *error = NULL;
+ 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_clear_error(&error);
+
+ g_assert_cmpint(result, ==, HASL_MECHANISM_RESULT_ERROR);
+
+ g_assert_null(client_out);
+ g_assert_cmpint(client_out_length, ==, 0);
+
+ g_clear_object(&mechanism);
+}
+
+static void
+test_hasl_mechanism_anonymous_possible(HaslContext *context, gboolean expected,
+ gboolean should_error)
+{
+ HaslMechanism *mechanism = NULL;
+ GError *error = NULL;
+ gboolean ret = FALSE;
+
+ mechanism = g_object_new(HASL_TYPE_MECHANISM_ANONYMOUS, NULL);
+
+ ret = hasl_mechanism_possible(mechanism, context, &error);
+
+ if(should_error) {
+ g_assert_error(error, HASL_MECHANISM_ANONYMOUS_DOMAIN, 0);
+ g_clear_error(&error);
+ } else {
+ g_assert_no_error(error);
+ }
+
+ g_assert_true(ret == expected);
+
+ g_clear_object(&mechanism);
+}
+
+/******************************************************************************
+ * Tests
+ *****************************************************************************/
+static void
+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);
+}
+
+static void
+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);
+}
+
+static void
+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);
+}
+
+static void
+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);
+}
+
+static void
+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);
+}
+
+static void
+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);
+}
+
+static void
+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);
+}
+
+static void
+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
+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);
+
+ return g_test_run();
+}
--- 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')
###############################################################################
# NLS
--- 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-full.c
hasl/tests/test-mechanism.c
+hasl/tests/test-mechanism-anonymous.c
hasl/tests/test-mechanism-external.c
hasl/tests/test-mechanism-plain.c