hasl/hasl

Add an object to help create gs2 headers

9 months ago, Gary Kramlich
4103bc84bfaa
Parents e677e224dcf7
Children 2822ac53f984
Add an object to help create gs2 headers

This isn't used yet, but is necessary for the SCRAM mechanisms and probably
some others that I just haven't seen yet.

Testing Done:
Ran the unit tests.

Reviewed at https://reviews.imfreedom.org/r/2548/
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hasl/haslgs2header.c Tue Aug 08 01:40:11 2023 -0500
@@ -0,0 +1,366 @@
+/*
+ * 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 "haslgs2header.h"
+
+#include "haslenums.h"
+
+enum {
+ PROP_0,
+ PROP_STANDARD_MECHANISM,
+ PROP_CHANNEL_BINDING,
+ PROP_CHANNEL_BINDING_NAME,
+ PROP_AUTHZID,
+ N_PROPERTIES,
+};
+static GParamSpec *properties[N_PROPERTIES] = {NULL, };
+
+struct _HaslGs2Header {
+ GObject parent;
+
+ gboolean standard_mechanism;
+
+ HaslGs2HeaderChannelBinding channel_binding;
+ char *channel_binding_name;
+
+ char *authzid;
+};
+
+G_DEFINE_TYPE(HaslGs2Header, hasl_gs2_header, G_TYPE_OBJECT)
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+static void
+hasl_gs2_header_get_property(GObject *obj, guint param_id, GValue *value,
+ GParamSpec *pspec)
+{
+ HaslGs2Header *header = HASL_GS2_HEADER(obj);
+
+ switch(param_id) {
+ case PROP_STANDARD_MECHANISM:
+ g_value_set_boolean(value,
+ hasl_gs2_header_get_standard_mechanism(header));
+ break;
+ case PROP_CHANNEL_BINDING:
+ g_value_set_enum(value, hasl_gs2_header_get_channel_binding(header));
+ break;
+ case PROP_CHANNEL_BINDING_NAME:
+ g_value_set_string(value,
+ hasl_gs2_header_get_channel_binding_name(header));
+ break;
+ case PROP_AUTHZID:
+ g_value_set_string(value, hasl_gs2_header_get_authzid(header));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+ break;
+ }
+}
+
+static void
+hasl_gs2_header_set_property(GObject *obj, guint param_id, const GValue *value,
+ GParamSpec *pspec)
+{
+ HaslGs2Header *header = HASL_GS2_HEADER(obj);
+
+ switch(param_id) {
+ case PROP_STANDARD_MECHANISM:
+ hasl_gs2_header_set_standard_mechanism(header,
+ g_value_get_boolean(value));
+ break;
+ case PROP_CHANNEL_BINDING:
+ hasl_gs2_header_set_channel_binding(header, g_value_get_enum(value));
+ break;
+ case PROP_CHANNEL_BINDING_NAME:
+ hasl_gs2_header_set_channel_binding_name(header,
+ g_value_get_string(value));
+ break;
+ case PROP_AUTHZID:
+ hasl_gs2_header_set_authzid(header, g_value_get_string(value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+ break;
+ }
+}
+
+static void
+hasl_gs2_header_init(HaslGs2Header *header) {
+ header->standard_mechanism = TRUE;
+ header->channel_binding = HaslGs2HeaderChannelBindingClientNotSupported;
+}
+
+static void
+hasl_gs2_header_finalize(GObject *obj) {
+ HaslGs2Header *header = HASL_GS2_HEADER(obj);
+
+ g_clear_pointer(&header->channel_binding_name, g_free);
+ g_clear_pointer(&header->authzid, g_free);
+
+ G_OBJECT_CLASS(hasl_gs2_header_parent_class)->finalize(obj);
+}
+
+static void
+hasl_gs2_header_class_init(HaslGs2HeaderClass *klass) {
+ GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+
+ obj_class->finalize = hasl_gs2_header_finalize;
+ obj_class->get_property = hasl_gs2_header_get_property;
+ obj_class->set_property = hasl_gs2_header_set_property;
+
+ /**
+ * HaslGs2Header:standard-mechanism:
+ *
+ * Whether or not the mechanism is a standard GSS-API mechanism.
+ *
+ * Since: 0.3.0
+ */
+ properties[PROP_STANDARD_MECHANISM] = g_param_spec_boolean(
+ "standard-mechanism", "standard-mechanism",
+ "Whether or not this mechanism is a standard GSS-API mechanism.",
+ TRUE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * HalsGs2Header:channel-binding:
+ *
+ * The channel binding flag for this header.
+ *
+ * Since: 0.3.0
+ */
+ properties[PROP_CHANNEL_BINDING] = g_param_spec_enum(
+ "channel-binding", "channel-binding",
+ "The channel binding flag for this header.",
+ HASL_TYPE_GS2_HEADER_CHANNEL_BINDING,
+ HaslGs2HeaderChannelBindingClientNotSupported,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * HaslGs2Header:channel-binding-name:
+ *
+ * The name of the channel binding data to use.
+ *
+ * If [property@Gs2Header:channel-binding] is set to ClientSupported, this
+ * must not be %NULL.
+ *
+ * Since: 0.3.0
+ */
+ properties[PROP_CHANNEL_BINDING_NAME] = g_param_spec_string(
+ "channel-binding-name", "channel-binding-name",
+ "The name of the channel binding data to use.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+
+ /**
+ * HaslGs2Header:authzid:
+ *
+ * An optional authzid to use. This is the username to authenticate as when
+ * using admin/moderator credentials.
+ *
+ * Since: 0.3.0
+ */
+ properties[PROP_AUTHZID] = g_param_spec_string(
+ "authzid", "authzid",
+ "The username to authenticate as.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+HaslGs2Header *
+hasl_gs2_header_new(void) {
+ return g_object_new(HASL_TYPE_GS2_HEADER, NULL);
+}
+
+char *
+hasl_gs2_header_to_string(HaslGs2Header *header, GError **error) {
+ GString *str = NULL;
+
+ g_return_val_if_fail(HASL_IS_GS2_HEADER(header), NULL);
+
+ str = g_string_new("");
+
+ if(!header->standard_mechanism) {
+ g_string_append(str, "F,");
+ }
+
+ /* Add our channel binding information. */
+ if(header->channel_binding == HaslGs2HeaderChannelBindingClientSupported) {
+ gboolean invalid = FALSE;
+
+ if(header->channel_binding_name == NULL ||
+ header->channel_binding_name[0] == '\0')
+ {
+ g_set_error_literal(error, HASL_GS2_HEADER_DOMAIN, 0,
+ "channel binding was set to supported but no "
+ "name was given.");
+
+ g_string_free(str, TRUE);
+
+ return NULL;
+ }
+
+ /* Look to see if we have any invalid characters in the name. */
+ invalid = g_regex_match_simple("[^A-Za-z0-9.-]",
+ header->channel_binding_name,
+ G_REGEX_DEFAULT, G_REGEX_MATCH_DEFAULT);
+ if(invalid) {
+ g_set_error(error, HASL_GS2_HEADER_DOMAIN, 0,
+ "channel binding name \"%s\" contains invalid "
+ "characters. Only alphanumeric . and - are supported",
+ header->channel_binding_name);
+
+ g_string_free(str, TRUE);
+
+ return NULL;
+ }
+
+ g_string_append_printf(str, "p=%s", header->channel_binding_name);
+ } else if(header->channel_binding == HaslGs2HeaderChannelBindingClientNotSupported)
+ {
+ g_string_append(str, "n");
+ } else if(header->channel_binding == HaslGs2HeaderChannelBindingClientSupportedServerUnknown)
+ {
+ g_string_append(str, "y");
+ } else {
+ g_set_error(error, HASL_GS2_HEADER_DOMAIN, 0,
+ "unknown channel binding value %d",
+ header->channel_binding);
+
+ g_string_free(str, TRUE);
+
+ return NULL;
+ }
+ g_string_append(str, ",");
+
+ /* Add the authzid if we have it. */
+ if(header->authzid != NULL && header->authzid[0] != '\0') {
+ char buffer[1024];
+ int res = 0;
+
+ g_strlcpy(buffer, header->authzid, sizeof(buffer));
+ res = stringprep(buffer, sizeof(buffer), 0, stringprep_saslprep);
+ if(res != STRINGPREP_OK) {
+ g_set_error(error, HASL_GS2_HEADER_DOMAIN, 0,
+ _("stringprep failed: %s"), stringprep_strerror(res));
+
+ g_string_free(str, TRUE);
+
+ return NULL;
+ }
+
+ g_string_append_printf(str, "a=%s", header->authzid);
+ }
+ g_string_append(str, ",");
+
+ /* Free the string but return the non-freed segment. */
+ return g_string_free(str, FALSE);
+}
+
+gboolean
+hasl_gs2_header_get_standard_mechanism(HaslGs2Header *header) {
+ g_return_val_if_fail(HASL_IS_GS2_HEADER(header), TRUE);
+
+ return header->standard_mechanism;
+}
+
+void
+hasl_gs2_header_set_standard_mechanism(HaslGs2Header *header,
+ gboolean standard)
+{
+ g_return_if_fail(HASL_IS_GS2_HEADER(header));
+
+ if(header->standard_mechanism != standard) {
+ header->standard_mechanism = standard;
+
+ g_object_notify_by_pspec(G_OBJECT(header),
+ properties[PROP_STANDARD_MECHANISM]);
+ }
+}
+
+HaslGs2HeaderChannelBinding
+hasl_gs2_header_get_channel_binding(HaslGs2Header *header) {
+ g_return_val_if_fail(HASL_IS_GS2_HEADER(header),
+ HaslGs2HeaderChannelBindingClientNotSupported);
+
+ return header->channel_binding;
+}
+
+void
+hasl_gs2_header_set_channel_binding(HaslGs2Header *header,
+ HaslGs2HeaderChannelBinding channel_binding)
+{
+ g_return_if_fail(HASL_IS_GS2_HEADER(header));
+
+ if(header->channel_binding != channel_binding) {
+ header->channel_binding = channel_binding;
+
+ g_object_notify_by_pspec(G_OBJECT(header),
+ properties[PROP_CHANNEL_BINDING]);
+ }
+}
+
+const char *
+hasl_gs2_header_get_channel_binding_name(HaslGs2Header *header) {
+ g_return_val_if_fail(HASL_IS_GS2_HEADER(header), NULL);
+
+ return header->channel_binding_name;
+}
+
+void
+hasl_gs2_header_set_channel_binding_name(HaslGs2Header *header,
+ const char *name)
+{
+ g_return_if_fail(HASL_IS_GS2_HEADER(header));
+
+ if(g_strcmp0(header->channel_binding_name, name) != 0) {
+ g_free(header->channel_binding_name);
+ header->channel_binding_name = g_strdup(name);
+
+ g_object_notify_by_pspec(G_OBJECT(header),
+ properties[PROP_CHANNEL_BINDING_NAME]);
+ }
+}
+
+const char *
+hasl_gs2_header_get_authzid(HaslGs2Header *header) {
+ g_return_val_if_fail(HASL_IS_GS2_HEADER(header), NULL);
+
+ return header->authzid;
+}
+
+void
+hasl_gs2_header_set_authzid(HaslGs2Header *header, const char *authzid) {
+ g_return_if_fail(HASL_IS_GS2_HEADER(header));
+
+ if(g_strcmp0(header->authzid, authzid) != 0) {
+ g_free(header->authzid);
+ header->authzid = g_strdup(authzid);
+
+ g_object_notify_by_pspec(G_OBJECT(header), properties[PROP_AUTHZID]);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hasl/haslgs2header.h Tue Aug 08 01:40:11 2023 -0500
@@ -0,0 +1,187 @@
+/*
+ * 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_GS2_HEADER_H
+#define HASL_GS2_HEADER_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/**
+ * HaslGs2HeaderChannelBinding:
+ * @HaslGs2HeaderChannelBindingClientNotSupported: The client doesn't support
+ * channel binding.
+ * @HaslGs2HeaderChannelBindingClientSupported: The client supports channel
+ * binding and has specified a
+ * name.
+ * @HaslGs2HeaderChannelBindingClientSupportedServerUnknown: The client
+ * supports
+ * channel binding,
+ * doesn't know if
+ * the server does.
+ *
+ * This enum tells the header about channel bindings for this connection. See
+ * https://datatracker.ietf.org/doc/html/rfc5801#section-4 for more
+ * information.
+ *
+ * Since: 0.3.0
+ */
+typedef enum {
+ HaslGs2HeaderChannelBindingClientNotSupported,
+ HaslGs2HeaderChannelBindingClientSupported,
+ HaslGs2HeaderChannelBindingClientSupportedServerUnknown,
+} HaslGs2HeaderChannelBinding;
+
+#define HASL_GS2_HEADER_DOMAIN (g_quark_from_static_string("hash-gs2-header"))
+
+/**
+ * HaslGs2Header:
+ *
+ * This is an object to make it easier to generate a GS2 Header. See
+ * https://www.rfc-editor.org/rfc/rfc5801#section-4 for the formal definition.
+ *
+ * This is primarily meant to be used by mechanism implementations and should
+ * not be needed for normal use cases.
+ *
+ * Since: 0.3.0
+ */
+#define HASL_TYPE_GS2_HEADER (hasl_gs2_header_get_type())
+G_DECLARE_FINAL_TYPE(HaslGs2Header, hasl_gs2_header, HASL, GS2_HEADER, GObject)
+
+/**
+ * hasl_gs2_header_new:
+ *
+ * Creates a new gs2_header.
+ *
+ * Returns: (transfer full): The new gs2_header.
+ *
+ * Since: 0.3.0
+ */
+HaslGs2Header *hasl_gs2_header_new(void);
+
+/**
+ * hasl_gs2_header_to_string:
+ * @header: The instance.
+ * @error: (out): A return address for an error.
+ *
+ * Serializes @header to a string in the correct format.
+ *
+ * Returns: (transfer full): The serialized header.
+ *
+ * Since: 0.3.0
+ */
+char *hasl_gs2_header_to_string(HaslGs2Header *header, GError **error);
+
+/**
+ * hasl_gs2_header_get_standard_mechanism:
+ * @header: The instance.
+ *
+ * Gets whether or not this header is for a standard GSS-API mechanism.
+ *
+ * Returns: %TRUE if this mechanism is standard, %FALSE otherwise.
+ *
+ * Since: 0.3.0
+ */
+gboolean hasl_gs2_header_get_standard_mechanism(HaslGs2Header *header);
+
+/**
+ * hasl_gs2_header_set_standard_mechanism:
+ * @header: The instance.
+ * @standard: The new value.
+ *
+ * Sets whether or not the mechanism is a standard GSS-API mechanism.
+ *
+ * Since: 0.3.0
+ */
+void hasl_gs2_header_set_standard_mechanism(HaslGs2Header *header, gboolean standard);
+
+/**
+ * hasl_gs2_header_get_channel_binding:
+ * @header: The instance.
+ *
+ * Gets the channel binding value for this header.
+ *
+ * Returns: The channel binding value.
+ *
+ * Since: 0.3.0
+ */
+HaslGs2HeaderChannelBinding hasl_gs2_header_get_channel_binding(HaslGs2Header *header);
+
+/**
+ * hasl_gs2_header_set_channel_binding:
+ * @header: The instance.
+ * @channel_binding: The channel binding flag.
+ *
+ * Sets the channel binding flag for @header.
+ *
+ * Since: 0.3.0
+ */
+void hasl_gs2_header_set_channel_binding(HaslGs2Header *header, HaslGs2HeaderChannelBinding channel_binding);
+
+/**
+ * hasl_gs2_header_get_channel_binding_name:
+ * @header: The instance.
+ *
+ * Gets the name of the channel binding that is in use.
+ *
+ * Returns: (nullable): The channel binding name.
+ *
+ * Since: 0.3.0
+ */
+const char *hasl_gs2_header_get_channel_binding_name(HaslGs2Header *header);
+
+/**
+ * hasl_gs2_header_set_channel_binding_name:
+ * @header: The instance.
+ * @name: (nullable): The name of the channel binding data.
+ *
+ * Sets the name of the channel binding data that should be used.
+ *
+ * Since: 0.3.0
+ */
+void hasl_gs2_header_set_channel_binding_name(HaslGs2Header *header, const char *name);
+
+/**
+ * hasl_gs2_header_get_authzid:
+ * @header: The instance.
+ *
+ * Gets the authzid to use for this header.
+ *
+ * Returns: (nullable): The authzid for this header.
+ *
+ * Since: 0.3.0
+ */
+const char *hasl_gs2_header_get_authzid(HaslGs2Header *header);
+
+/**
+ * hasl_gs2_header_set_authzid:
+ * @header: The instance.
+ * @authzid: (nullable): The username to authenticate for.
+ *
+ * Sets the username to authenticate for. This is used when an administrator
+ * or moderator needs to authenticate as a specific user using their own
+ * credentials.
+ *
+ * Since: 0.3.0
+ */
+void hasl_gs2_header_set_authzid(HaslGs2Header *header, const char *authzid);
+
+G_END_DECLS
+
+#endif /* HASL_GS2_HEADER_H */
--- a/hasl/meson.build Mon Aug 07 23:11:41 2023 -0500
+++ b/hasl/meson.build Tue Aug 08 01:40:11 2023 -0500
@@ -1,5 +1,6 @@
HASL_SOURCES = [
'haslcontext.c',
+ 'haslgs2header.c',
'haslmechanism.c',
'haslmechanismanonymous.c',
'haslmechanismexternal.c',
@@ -10,6 +11,7 @@
HASL_HEADERS = [
'haslcontext.h',
'haslcore.h',
+ 'haslgs2header.h',
'haslmechanism.h',
'haslmechanismanonymous.h',
'haslmechanismexternal.h',
@@ -53,6 +55,7 @@
# Enums
###############################################################################
HASL_ENUM_HEADERS = [
+ 'haslgs2header.h',
'haslmechanism.h',
]
--- a/hasl/tests/meson.build Mon Aug 07 23:11:41 2023 -0500
+++ b/hasl/tests/meson.build Tue Aug 08 01:40:11 2023 -0500
@@ -1,6 +1,7 @@
TESTS = [
'context',
'full',
+ 'gs2-header',
'mechanism',
'mechanism-anonymous',
'mechanism-external',
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hasl/tests/test-gs2-header.c Tue Aug 08 01:40:11 2023 -0500
@@ -0,0 +1,254 @@
+/*
+ * 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>
+
+/******************************************************************************
+ * Tests
+ *****************************************************************************/
+static void
+test_hasl_gs2_header_new(void) {
+ HaslGs2Header *header = hasl_gs2_header_new();
+
+ g_assert_true(HASL_IS_GS2_HEADER(header));
+
+ g_clear_object(&header);
+}
+
+static void
+test_hasl_gs2_header_properties(void) {
+ HaslGs2Header *header = NULL;
+ HaslGs2HeaderChannelBinding channel_binding;
+ char *channel_binding_name = NULL;
+ char *authzid = NULL;
+ gboolean standard_mechanism = FALSE;
+
+ header = g_object_new(
+ HASL_TYPE_GS2_HEADER,
+ "standard-mechanism", FALSE,
+ "channel-binding", HaslGs2HeaderChannelBindingClientSupportedServerUnknown,
+ "channel-binding-name", "weee",
+ "authzid", "zerocool",
+ NULL);
+
+ g_assert_true(HASL_IS_GS2_HEADER(header));
+
+ g_object_get(
+ G_OBJECT(header),
+ "standard-mechanism", &standard_mechanism,
+ "channel-binding", &channel_binding,
+ "channel-binding-name", &channel_binding_name,
+ "authzid", &authzid,
+ NULL);
+
+ g_assert_false(standard_mechanism);
+ g_assert_cmpuint(channel_binding, ==,
+ HaslGs2HeaderChannelBindingClientSupportedServerUnknown);
+
+ g_assert_cmpstr(channel_binding_name, ==, "weee");
+ g_clear_pointer(&channel_binding_name, g_free);
+
+ g_assert_cmpstr(authzid, ==, "zerocool");
+ g_clear_pointer(&authzid, g_free);
+
+ g_clear_object(&header);
+}
+
+/******************************************************************************
+ * to_string tests
+ *****************************************************************************/
+static void
+test_hasl_gs2_header_to_string_default(void) {
+ HaslGs2Header *header = NULL;
+ GError *error = NULL;
+ char *str = NULL;
+
+ header = hasl_gs2_header_new();
+
+ str = hasl_gs2_header_to_string(header, &error);
+ g_assert_no_error(error);
+ g_assert_cmpstr(str, ==, "n,,");
+
+ g_clear_pointer(&str, g_free);
+ g_clear_object(&header);
+}
+
+static void
+test_hasl_gs2_header_to_string_non_standard_mechanism(void) {
+ HaslGs2Header *header = NULL;
+ GError *error = NULL;
+ char *str = NULL;
+
+ header = hasl_gs2_header_new();
+ hasl_gs2_header_set_standard_mechanism(header, FALSE);
+
+ str = hasl_gs2_header_to_string(header, &error);
+ g_assert_no_error(error);
+ g_assert_cmpstr(str, ==, "F,n,,");
+
+ g_clear_pointer(&str, g_free);
+ g_clear_object(&header);
+}
+
+static void
+test_hasl_gs2_header_to_string_channel_binding_client_supported(void) {
+ HaslGs2Header *header = NULL;
+ GError *error = NULL;
+ char *str = NULL;
+
+ header = hasl_gs2_header_new();
+ hasl_gs2_header_set_channel_binding(header,
+ HaslGs2HeaderChannelBindingClientSupported);
+ hasl_gs2_header_set_channel_binding_name(header, "weee");
+
+ str = hasl_gs2_header_to_string(header, &error);
+ g_assert_no_error(error);
+ g_assert_cmpstr(str, ==, "p=weee,,");
+
+ g_clear_pointer(&str, g_free);
+ g_clear_object(&header);
+}
+
+static void
+test_hasl_gs2_header_to_string_channel_binding_client_supported_name_required(void) {
+ HaslGs2Header *header = NULL;
+ GError *error = NULL;
+ char *str = NULL;
+
+ header = hasl_gs2_header_new();
+ hasl_gs2_header_set_channel_binding(header,
+ HaslGs2HeaderChannelBindingClientSupported);
+
+ str = hasl_gs2_header_to_string(header, &error);
+ g_assert_error(error, HASL_GS2_HEADER_DOMAIN, 0);
+ g_clear_error(&error);
+ g_assert_null(str);
+
+ g_clear_object(&header);
+}
+
+static void
+test_hasl_gs2_header_to_string_channel_binding_client_supported_server_unknown(void)
+{
+ HaslGs2Header *header = NULL;
+ GError *error = NULL;
+ char *str = NULL;
+
+ header = hasl_gs2_header_new();
+ hasl_gs2_header_set_channel_binding(header,
+ HaslGs2HeaderChannelBindingClientSupportedServerUnknown);
+ hasl_gs2_header_set_channel_binding_name(header, "weee");
+
+ str = hasl_gs2_header_to_string(header, &error);
+ g_assert_no_error(error);
+ g_assert_cmpstr(str, ==, "y,,");
+
+ g_clear_pointer(&str, g_free);
+ g_clear_object(&header);
+}
+
+static void
+test_hasl_gs2_header_to_string_channel_binding_unknown(void) {
+ HaslGs2Header *header = NULL;
+ GError *error = NULL;
+ char *str = NULL;
+
+ header = hasl_gs2_header_new();
+ hasl_gs2_header_set_channel_binding(header, 0xFFFF);
+
+ str = hasl_gs2_header_to_string(header, &error);
+ g_assert_error(error, HASL_GS2_HEADER_DOMAIN, 0);
+ g_clear_error(&error);
+ g_assert_null(str);
+
+ g_clear_object(&header);
+}
+
+static void
+test_hasl_gs2_header_to_string_channel_binding_name_invalid(void) {
+ HaslGs2Header *header = NULL;
+ GError *error = NULL;
+ char *str = NULL;
+ gboolean matched = FALSE;
+
+ header = hasl_gs2_header_new();
+ hasl_gs2_header_set_channel_binding(header,
+ HaslGs2HeaderChannelBindingClientSupported);
+ hasl_gs2_header_set_channel_binding_name(header, "🪦");
+
+ str = hasl_gs2_header_to_string(header, &error);
+ g_assert_error(error, HASL_GS2_HEADER_DOMAIN, 0);
+
+ matched = g_pattern_match_simple("channel binding name*contains invalid*",
+ error->message);
+ g_assert_true(matched);
+
+ g_clear_error(&error);
+ g_assert_null(str);
+
+ g_clear_object(&header);
+}
+
+static void
+test_hasl_gs2_header_to_string_authzid(void) {
+ HaslGs2Header *header = NULL;
+ GError *error = NULL;
+ char *str = NULL;
+
+ header = hasl_gs2_header_new();
+ hasl_gs2_header_set_authzid(header, "herman");
+
+ str = hasl_gs2_header_to_string(header, &error);
+ g_assert_no_error(error);
+ g_assert_cmpstr(str, ==, "n,a=herman,");
+
+ g_clear_pointer(&str, g_free);
+ g_clear_object(&header);
+}
+
+/******************************************************************************
+ * Main
+ *****************************************************************************/
+int
+main(int argc, char *argv[]) {
+ g_test_init(&argc, &argv, NULL);
+
+ g_test_add_func("/hasl/gs2-header/new", test_hasl_gs2_header_new);
+ g_test_add_func("/hasl/gs2-header/properties",
+ test_hasl_gs2_header_properties);
+
+ g_test_add_func("/hasl/gs2-header/to-string/default",
+ test_hasl_gs2_header_to_string_default);
+ g_test_add_func("/hasl/gs2-header/to-string/non-standard-mechanism",
+ test_hasl_gs2_header_to_string_non_standard_mechanism);
+ g_test_add_func("/hasl/gs2-header/to-string/channel-binding-client-supported",
+ test_hasl_gs2_header_to_string_channel_binding_client_supported);
+ g_test_add_func("/hasl/gs2-header/to-string/channel-binding-client-supported-name-required",
+ test_hasl_gs2_header_to_string_channel_binding_client_supported_name_required);
+ g_test_add_func("/hasl/gs2-header/to-string/channel-binding-client-supported-server-unknown",
+ test_hasl_gs2_header_to_string_channel_binding_client_supported_server_unknown);
+ g_test_add_func("/hasl/gs2-header/to-string/channel-binding-unknown",
+ test_hasl_gs2_header_to_string_channel_binding_unknown);
+ g_test_add_func("/hasl/gs2-header/to-string/channel-binding-name-invalid",
+ test_hasl_gs2_header_to_string_channel_binding_name_invalid);
+ g_test_add_func("/hasl/gs2-header/to-string/authzid",
+ test_hasl_gs2_header_to_string_authzid);
+
+ return g_test_run();
+}