--- /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 "haslgs2header.h" + PROP_STANDARD_MECHANISM, + PROP_CHANNEL_BINDING_NAME, +static GParamSpec *properties[N_PROPERTIES] = {NULL, }; + gboolean standard_mechanism; + HaslGs2HeaderChannelBinding channel_binding; + char *channel_binding_name; +G_DEFINE_TYPE(HaslGs2Header, hasl_gs2_header, G_TYPE_OBJECT) +/****************************************************************************** + * GObject Implementation + *****************************************************************************/ +hasl_gs2_header_get_property(GObject *obj, guint param_id, GValue *value, + HaslGs2Header *header = HASL_GS2_HEADER(obj); + case PROP_STANDARD_MECHANISM: + g_value_set_boolean(value, + hasl_gs2_header_get_standard_mechanism(header)); + case PROP_CHANNEL_BINDING: + g_value_set_enum(value, hasl_gs2_header_get_channel_binding(header)); + case PROP_CHANNEL_BINDING_NAME: + g_value_set_string(value, + hasl_gs2_header_get_channel_binding_name(header)); + g_value_set_string(value, hasl_gs2_header_get_authzid(header)); + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); +hasl_gs2_header_set_property(GObject *obj, guint param_id, const GValue *value, + HaslGs2Header *header = HASL_GS2_HEADER(obj); + case PROP_STANDARD_MECHANISM: + hasl_gs2_header_set_standard_mechanism(header, + g_value_get_boolean(value)); + case PROP_CHANNEL_BINDING: + hasl_gs2_header_set_channel_binding(header, g_value_get_enum(value)); + case PROP_CHANNEL_BINDING_NAME: + hasl_gs2_header_set_channel_binding_name(header, + g_value_get_string(value)); + hasl_gs2_header_set_authzid(header, g_value_get_string(value)); + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); +hasl_gs2_header_init(HaslGs2Header *header) { + header->standard_mechanism = TRUE; + header->channel_binding = HaslGs2HeaderChannelBindingClientNotSupported; +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); +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. + properties[PROP_STANDARD_MECHANISM] = g_param_spec_boolean( + "standard-mechanism", "standard-mechanism", + "Whether or not this mechanism is a standard GSS-API mechanism.", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + * HalsGs2Header:channel-binding: + * The channel binding flag for this header. + 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 + properties[PROP_CHANNEL_BINDING_NAME] = g_param_spec_string( + "channel-binding-name", "channel-binding-name", + "The name of the channel binding data to use.", + 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. + properties[PROP_AUTHZID] = g_param_spec_string( + "The username to authenticate as.", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties(obj_class, N_PROPERTIES, properties); +/****************************************************************************** + *****************************************************************************/ +hasl_gs2_header_new(void) { + return g_object_new(HASL_TYPE_GS2_HEADER, NULL); +hasl_gs2_header_to_string(HaslGs2Header *header, GError **error) { + 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 " + g_string_free(str, TRUE); + /* 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); + 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); + 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"); + g_set_error(error, HASL_GS2_HEADER_DOMAIN, 0, + "unknown channel binding value %d", + header->channel_binding); + g_string_free(str, TRUE); + g_string_append(str, ","); + /* Add the authzid if we have it. */ + if(header->authzid != NULL && header->authzid[0] != '\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); + 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); +hasl_gs2_header_get_standard_mechanism(HaslGs2Header *header) { + g_return_val_if_fail(HASL_IS_GS2_HEADER(header), TRUE); + return header->standard_mechanism; +hasl_gs2_header_set_standard_mechanism(HaslGs2Header *header, + 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; +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]); +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; +hasl_gs2_header_set_channel_binding_name(HaslGs2Header *header, + 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]); +hasl_gs2_header_get_authzid(HaslGs2Header *header) { + g_return_val_if_fail(HASL_IS_GS2_HEADER(header), NULL); + return header->authzid; +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-object.h> + * HaslGs2HeaderChannelBinding: + * @HaslGs2HeaderChannelBindingClientNotSupported: The client doesn't support + * @HaslGs2HeaderChannelBindingClientSupported: The client supports channel + * binding and has specified a + * @HaslGs2HeaderChannelBindingClientSupportedServerUnknown: The client + * This enum tells the header about channel bindings for this connection. See + * https://datatracker.ietf.org/doc/html/rfc5801#section-4 for more + HaslGs2HeaderChannelBindingClientNotSupported, + HaslGs2HeaderChannelBindingClientSupported, + HaslGs2HeaderChannelBindingClientSupportedServerUnknown, +} HaslGs2HeaderChannelBinding; +#define HASL_GS2_HEADER_DOMAIN (g_quark_from_static_string("hash-gs2-header")) + * 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. +#define HASL_TYPE_GS2_HEADER (hasl_gs2_header_get_type()) +G_DECLARE_FINAL_TYPE(HaslGs2Header, hasl_gs2_header, HASL, GS2_HEADER, GObject) + * Creates a new gs2_header. + * Returns: (transfer full): The new gs2_header. +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. +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. +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. +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. +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. +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. +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. +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. +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 +void hasl_gs2_header_set_authzid(HaslGs2Header *header, const char *authzid); +#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 @@
'haslmechanismanonymous.c',
'haslmechanismexternal.c',
@@ -10,6 +11,7 @@
'haslmechanismanonymous.h',
'haslmechanismexternal.h',
@@ -53,6 +55,7 @@
###############################################################################
--- 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 @@
--- /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/>. +/****************************************************************************** + *****************************************************************************/ +test_hasl_gs2_header_new(void) { + HaslGs2Header *header = hasl_gs2_header_new(); + g_assert_true(HASL_IS_GS2_HEADER(header)); + g_clear_object(&header); +test_hasl_gs2_header_properties(void) { + HaslGs2Header *header = NULL; + HaslGs2HeaderChannelBinding channel_binding; + char *channel_binding_name = NULL; + gboolean standard_mechanism = FALSE; + "standard-mechanism", FALSE, + "channel-binding", HaslGs2HeaderChannelBindingClientSupportedServerUnknown, + "channel-binding-name", "weee", + g_assert_true(HASL_IS_GS2_HEADER(header)); + "standard-mechanism", &standard_mechanism, + "channel-binding", &channel_binding, + "channel-binding-name", &channel_binding_name, + 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); +/****************************************************************************** + *****************************************************************************/ +test_hasl_gs2_header_to_string_default(void) { + HaslGs2Header *header = 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); +test_hasl_gs2_header_to_string_non_standard_mechanism(void) { + HaslGs2Header *header = 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); +test_hasl_gs2_header_to_string_channel_binding_client_supported(void) { + HaslGs2Header *header = 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); +test_hasl_gs2_header_to_string_channel_binding_client_supported_name_required(void) { + HaslGs2Header *header = 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_object(&header); +test_hasl_gs2_header_to_string_channel_binding_client_supported_server_unknown(void) + HaslGs2Header *header = 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); +test_hasl_gs2_header_to_string_channel_binding_unknown(void) { + HaslGs2Header *header = 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_object(&header); +test_hasl_gs2_header_to_string_channel_binding_name_invalid(void) { + HaslGs2Header *header = 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*", + g_assert_true(matched); + g_clear_object(&header); +test_hasl_gs2_header_to_string_authzid(void) { + HaslGs2Header *header = 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 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);