]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-sasl: Add CRAM-MD5 client support
authorStephan Bosch <stephan.bosch@open-xchange.com>
Sat, 5 Oct 2024 13:01:09 +0000 (15:01 +0200)
committertimo.sirainen <timo.sirainen@open-xchange.com>
Thu, 9 Oct 2025 08:41:22 +0000 (08:41 +0000)
src/lib-sasl/Makefile.am
src/lib-sasl/dsasl-client-mech-cram-md5.c [new file with mode: 0644]
src/lib-sasl/dsasl-client-private.h
src/lib-sasl/dsasl-client.c
src/lib-sasl/fuzz-sasl-authentication.c
src/lib-sasl/test-sasl-authentication.c

index b903a04d5a54acc5fd48f6c815ecfda0b8965dd6..525f6b0590ab87562c1119d156d65410ef497786 100644 (file)
@@ -17,6 +17,7 @@ AM_CPPFLAGS = \
 
 client_mechanisms = \
        dsasl-client-mech-anonymous.c \
+       dsasl-client-mech-cram-md5.c \
        dsasl-client-mech-external.c \
        dsasl-client-mech-login.c \
        dsasl-client-mech-oauthbearer.c \
diff --git a/src/lib-sasl/dsasl-client-mech-cram-md5.c b/src/lib-sasl/dsasl-client-mech-cram-md5.c
new file mode 100644 (file)
index 0000000..3d87169
--- /dev/null
@@ -0,0 +1,108 @@
+/* Copyright (c) 2024 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "hex-binary.h"
+#include "hmac.h"
+#include "md5.h"
+
+#include "dsasl-client-private.h"
+
+struct cram_md5_dsasl_client {
+       struct dsasl_client client;
+
+       const char *challenge;
+};
+
+static enum dsasl_client_result
+mech_cram_md5_input(struct dsasl_client *client,
+                   const unsigned char *input, size_t input_len,
+                   const char **error_r)
+{
+       struct cram_md5_dsasl_client *cclient =
+               container_of(client, struct cram_md5_dsasl_client, client);
+
+       const unsigned char *p = input, *pend = input + input_len;
+
+       if (p >= pend) {
+               *error_r = "Server sent empty challenge";
+               return DSASL_CLIENT_RESULT_ERR_PROTOCOL;
+       }
+       if (*p != '<') {
+               *error_r = "Server sent invalid challenge begin";
+               return DSASL_CLIENT_RESULT_ERR_PROTOCOL;
+       }
+       p++;
+
+       for (; (p + 1) < pend; p++) {
+               if (*p <= 32 || *p == 127 || *p == '>') {
+                       *error_r = "Server sent invalid challenge";
+                       return DSASL_CLIENT_RESULT_ERR_PROTOCOL;
+               }
+       }
+
+       if (p >= pend || *p != '>') {
+               *error_r = "Server sent invalid challenge end";
+               return DSASL_CLIENT_RESULT_ERR_PROTOCOL;
+       }
+       p++;
+       i_assert(p == pend);
+
+       if (input_len < 5) {
+               *error_r = "Server sent invalid challenge";
+               return DSASL_CLIENT_RESULT_ERR_PROTOCOL;
+       }
+
+       cclient->challenge = p_strndup(client->pool, input, input_len);
+       return DSASL_CLIENT_RESULT_OK;
+}
+
+static enum dsasl_client_result
+mech_cram_md5_output(struct dsasl_client *client,
+                    const unsigned char **output_r, size_t *output_len_r,
+                    const char **error_r)
+{
+       struct cram_md5_dsasl_client *cclient =
+               container_of(client, struct cram_md5_dsasl_client, client);
+       string_t *str;
+
+       if (client->set.authid == NULL) {
+               *error_r = "authid not set";
+               return DSASL_CLIENT_RESULT_ERR_INTERNAL;
+       }
+       if (client->password == NULL) {
+               *error_r = "password not set";
+               return DSASL_CLIENT_RESULT_ERR_INTERNAL;
+       }
+
+       if (cclient->challenge == NULL) {
+               *output_r = uchar_empty_ptr;
+               *output_len_r = 0;
+               return DSASL_CLIENT_RESULT_OK;
+       }
+
+       struct hmac_context ctx;
+       unsigned char digest[MD5_RESULTLEN];
+
+       hmac_init(&ctx, (const unsigned char *)client->password,
+                 strlen(client->password), &hash_method_md5);
+       hmac_update(&ctx, cclient->challenge, strlen(cclient->challenge));
+       hmac_final(&ctx, digest);
+
+       str = str_new(client->pool, 256);
+       str_append(str, client->set.authid);
+       str_append_c(str, ' ');
+       binary_to_hex_append(str, digest, sizeof(digest));
+
+       *output_r = str_data(str);
+       *output_len_r = str_len(str);
+       return DSASL_CLIENT_RESULT_OK;
+}
+
+const struct dsasl_client_mech dsasl_client_mech_cram_md5 = {
+       .name = SASL_MECH_NAME_CRAM_MD5,
+       .struct_size = sizeof(struct cram_md5_dsasl_client),
+
+       .input = mech_cram_md5_input,
+       .output = mech_cram_md5_output,
+};
index 4bdc16cedbf89ef7f284698292a628174fad2fad..6837fd01ce7aa8a98e96688db1579cf28609e42a 100644 (file)
@@ -43,6 +43,7 @@ struct dsasl_client_mech {
 };
 
 extern const struct dsasl_client_mech dsasl_client_mech_anonymous;
+extern const struct dsasl_client_mech dsasl_client_mech_cram_md5;
 extern const struct dsasl_client_mech dsasl_client_mech_external;
 extern const struct dsasl_client_mech dsasl_client_mech_login;
 extern const struct dsasl_client_mech dsasl_client_mech_oauthbearer;
index 983abd1e65111e17ed0708401bc88b9ecd60137b..548ff32fbbe86455daebe6978ae2115ec8d86f11 100644 (file)
@@ -158,6 +158,7 @@ void dsasl_clients_init(void)
        dsasl_client_mech_register(&dsasl_client_mech_external);
        dsasl_client_mech_register(&dsasl_client_mech_plain);
        dsasl_client_mech_register(&dsasl_client_mech_login);
+       dsasl_client_mech_register(&dsasl_client_mech_cram_md5);
        dsasl_client_mech_register(&dsasl_client_mech_oauthbearer);
        dsasl_client_mech_register(&dsasl_client_mech_xoauth2);
        dsasl_client_mech_register(&dsasl_client_mech_scram_sha_1);
index 3471eb21e65f62d727c3e4893fee3eeb438d6aa7..220f45e58b52fbaa9f526ee420b7353fc893e83d 100644 (file)
@@ -580,6 +580,7 @@ static void fuzz_sasl_run(struct istream *input)
        server_inst = sasl_server_instance_create(server, &server_set);
 
        sasl_server_mech_register_anonymous(server_inst);
+       sasl_server_mech_register_cram_md5(server_inst);
        sasl_server_mech_register_login(server_inst);
        sasl_server_mech_register_plain(server_inst);
        sasl_server_mech_register_scram_sha1(server_inst);
index 69d6becaf9a90540bbcdc612d7774574d4b4ebaf..5a84afbb866f06b23050c8b45abd19badb5f950a 100644 (file)
@@ -422,6 +422,7 @@ test_sasl_run(const struct test_sasl *test, const char *label,
        server_inst = sasl_server_instance_create(server, &server_set);
 
        sasl_server_mech_register_anonymous(server_inst);
+       sasl_server_mech_register_cram_md5(server_inst);
        sasl_server_mech_register_external(server_inst);
        sasl_server_mech_register_login(server_inst);
        sasl_server_mech_register_plain(server_inst);
@@ -484,6 +485,15 @@ static const struct test_sasl success_tests[] = {
                        .password = "pass",
                },
        },
+       /* CRAM-MD5 */
+       {
+               .mech = "CRAM-MD5",
+               .authid_type = SASL_SERVER_AUTHID_TYPE_USERNAME,
+               .server = {
+                       .authid = "user",
+                       .password = "pass",
+               },
+       },
        /* SCRAM-SHA-1 */
        {
                .mech = "SCRAM-SHA-1",
@@ -727,6 +737,31 @@ static const struct test_sasl bad_creds_tests[] = {
                },
                .failure = TRUE,
        },
+       /* CRAM-MD5 */
+       {
+               .mech = "CRAM-MD5",
+               .authid_type = SASL_SERVER_AUTHID_TYPE_USERNAME,
+               .server = {
+                       .authid = "user",
+                       .password = "pass",
+               },
+               .client = {
+                       .authid = "userb",
+               },
+               .failure = TRUE,
+       },
+       {
+               .mech = "CRAM-MD5",
+               .authid_type = SASL_SERVER_AUTHID_TYPE_USERNAME,
+               .server = {
+                       .authid = "user",
+                       .password = "pass",
+               },
+               .client = {
+                       .password= "florp",
+               },
+               .failure = TRUE,
+       },
        /* SCRAM-SHA-1 */
        {
                .mech = "SCRAM-SHA-1",