From: Markus Valentin Date: Thu, 2 Apr 2020 15:21:22 +0000 (+0200) Subject: auth: Add tests for some auth-mechanisms X-Git-Tag: 2.3.11.2~170 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=72d000b2f3088a620e8c4cf0d4b5149469a65b47;p=thirdparty%2Fdovecot%2Fcore.git auth: Add tests for some auth-mechanisms This adds tests for many auth-mechanisms and checks for empty inputs as well as for some broken input combinations. --- diff --git a/src/auth/Makefile.am b/src/auth/Makefile.am index cf46e430a0..8026a2637c 100644 --- a/src/auth/Makefile.am +++ b/src/auth/Makefile.am @@ -251,7 +251,8 @@ libstats_auth_la_SOURCES = auth-stats.c test_programs = \ test-libpassword \ test-auth-cache \ - test-auth + test-auth \ + test-mech noinst_PROGRAMS = $(test_programs) @@ -291,6 +292,13 @@ test_auth_SOURCES = \ test_auth_LDADD = $(test_libs) $(auth_libs) $(AUTH_LIBS) test_auth_DEPENDENCIES = $(pkglibexec_PROGRAMS) $(test_libs) +test_mech_SOURCES = \ + test-mock.c \ + test-mech.c + +test_mech_LDADD = $(test_libs) $(auth_libs) $(AUTH_LIBS) +test_mech_DEPENDENCIES = $(pkglibexec_PROGRAMS) $(test_libs) + check-local: for bin in $(test_programs); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ diff --git a/src/auth/test-mech.c b/src/auth/test-mech.c new file mode 100644 index 0000000000..531a994773 --- /dev/null +++ b/src/auth/test-mech.c @@ -0,0 +1,326 @@ +/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "auth.h" +#include "str.h" +#include "auth-common.h" +#include "auth-request.h" +#include "auth-request-handler-private.h" +#include "auth-settings.h" +#include "mech-digest-md5-private.h" +#include "otp.h" +#include "mech-otp-skey-common.h" +#include "settings-parser.h" +#include "test-common.h" + +#include +#include + +#define UCHAR_LEN(str) (const unsigned char *)(str), sizeof(str)-1 + +extern const struct mech_module mech_anonymous; +extern const struct mech_module mech_apop; +extern const struct mech_module mech_cram_md5; +extern const struct mech_module mech_digest_md5; +extern const struct mech_module mech_dovecot_token; +extern const struct mech_module mech_external; +extern const struct mech_module mech_login; +extern const struct mech_module mech_oauthbearer; +extern const struct mech_module mech_otp; +extern const struct mech_module mech_plain; +extern const struct mech_module mech_scram_sha1; +extern const struct mech_module mech_scram_sha256; +extern const struct mech_module mech_xoauth2; + +static struct auth_settings set; +static const char *challenge = NULL; + +static void +verify_plain_continue_mock_callback(struct auth_request *request ATTR_UNUSED, + verify_plain_callback_t *callback ATTR_UNUSED) +{ + request->failed = FALSE; +} + +static void +request_handler_reply_mock_callback(struct auth_request *request ATTR_UNUSED, + enum auth_client_result result ATTR_UNUSED, + const void *auth_reply ATTR_UNUSED, + size_t reply_size ATTR_UNUSED) +{ + if (request->user != NULL && request->mech != &mech_external) { + request->failed = FALSE; + } +}; + +static void +request_handler_reply_continue_mock_callback(struct auth_request *request ATTR_UNUSED, + const void *reply ATTR_UNUSED, + size_t reply_size ATTR_UNUSED) +{ + challenge = p_strndup(request->pool, reply, reply_size); +} + +static void +auth_client_request_mock_callback(const char *reply ATTR_UNUSED, + struct auth_client_connection *conn ATTR_UNUSED) +{ +} + +static void test_mechs_init(void) +{ + process_start_time = time(NULL); + + /* Copy default settings */ + set = *(struct auth_settings *) auth_setting_parser_info.defaults; + global_auth_settings = &set; + memset((&set)->username_chars_map, 1, sizeof((&set)->username_chars_map)); + set.username_format = ""; + + /* Disable stats */ + set.stats = FALSE; + + /* For tests of digest-md5. */ + set.realms_arr = t_strsplit_spaces("example.com ", " "); + /* For tests of mech-anonymous. */ + set.anonymous_username = "anonuser"; + + i_array_init(&auths, 1); + struct auth *auth = t_new(struct auth, 1); + array_push_back(&auths, &auth); +} + +static void test_mech_prepare_request(struct auth_request *request, + struct auth_request_handler *handler, + unsigned int running_test, + const char *username) +{ + request->handler = handler; + request->id = running_test+1; + request->mech_password = NULL; + request->state = AUTH_REQUEST_STATE_MECH_CONTINUE; + request->set = global_auth_settings; + request->connect_uid = running_test; + handler->refcount = 1; + + auth_fields_add(request->extra_fields, "nodelay", "", 0); + auth_request_ref(request); + auth_request_state_count[AUTH_REQUEST_STATE_MECH_CONTINUE] = 1; + challenge = NULL; + + if (username != NULL) + request->user = p_strdup(request->pool, username); +} + +static void test_mech_handle_challenge(struct auth_request *request, + const unsigned char *in, + size_t in_len, + unsigned int running_test, + bool expected_success) +{ + if (request->mech == &mech_login) { + /* We do not care about any specific password just give + * the username input as password also in case it's wanted. */ + if (expected_success) + test_assert_strcmp_idx(challenge, "Password:", running_test); + else + test_assert_strcmp_idx(challenge, "Username:", running_test); + } else if (request->mech == &mech_digest_md5) { + struct digest_auth_request *digest_request = + (struct digest_auth_request *) request; + digest_request->nonce = "OA6MG9tEQGm2hh"; + } + request->mech->auth_continue(request, in, in_len); +} + +static inline const unsigned char * +test_mech_construct_apop_challenge(unsigned int connect_uid, unsigned long *len_r) +{ + string_t *apop_challenge = t_str_new(128); + + str_printfa(apop_challenge,"<%lx.%u.%"PRIdTIME_T"", (unsigned long) getpid(), + connect_uid, process_start_time+10); + str_append_data(apop_challenge, "\0testuser\0responseoflen16-", 26); + *len_r = apop_challenge->used; + return apop_challenge->data; +} + +static void test_mechs(void) +{ + static struct auth_request_handler handler = { + .callback = auth_client_request_mock_callback, + .reply_callback = request_handler_reply_mock_callback, + .reply_continue_callback = request_handler_reply_continue_mock_callback, + .verify_plain_continue_callback = verify_plain_continue_mock_callback, + }; + + static struct { + const struct mech_module *mech; + const unsigned char *in; + size_t len; + const char *username; + const char *expect_error; + bool success; + bool set_username; + } tests[] = { + /* Expected to be successful */ + {&mech_anonymous, UCHAR_LEN("\0any \0 bad \0 content"), "anonuser", NULL, TRUE, FALSE}, + {&mech_apop, NULL, 0, "testuser", "All password databases were skipped", TRUE, FALSE}, + {&mech_cram_md5, UCHAR_LEN("testuser mockresponse"), "testuser", "All password databases were skipped", TRUE, FALSE}, + {&mech_digest_md5, UCHAR_LEN("username=\"testuser@example.com\",realm=\"example.com\",nonce=\"OA6MG9tEQGm2hh\",cnonce=\"OA6MHXh6VqTrRk\",nc=00000001,digest-uriresponse=d388dad90d4bbd760a152321f2143af7,qop=\"auth\""), "testuser@example.com", "All password databases were skipped",TRUE, FALSE}, + {&mech_digest_md5, UCHAR_LEN("username=\"testuser@example.com\",realm=\"example.com\",nonce=\"OA6MG9tEQGm2hh\",cnonce=\"OA6MHXh6VqTrRk\",nc=00000001,digest-uriresponse=d388dad90d4bbd760a152321f2143af7,qop=\"auth\",authzid=\"masteruser\""), "testuser@example.com", NULL, TRUE, FALSE}, + {&mech_digest_md5, UCHAR_LEN("username=\"test\xc3\xbaser@example.com\",realm=\"example.com\",nonce=\"OA6MG9tEQGm2hh\",cnonce=\"OA6MHXh6VqTrRk\",nc=00000001,digest-uriresponse=d388dad90d4bbd760a152321f2143af7,qop=\"auth\",authzid=\"masteruser\""), "test\xc3\xbaser@example.com", NULL, TRUE, FALSE}, + {&mech_digest_md5, UCHAR_LEN("username=\"test\xc3\xbaser@example.com\",realm=\"example.com\",nonce=\"OA6MG9tEQGm2hh\",cnonce=\"OA6MHXh6VqTrRk\",charset=\"utf-8\",cipher=unsupported,nc=00000001,digest-uri=imap/server.com,response=d388dad90d4bbd760a152321f2143af7,qop=\"auth\",authzid=\"masteruser\""), "test\xc3\xbaser@example.com", NULL, TRUE, FALSE}, + {&mech_digest_md5, UCHAR_LEN("username=\"testuser\",realm=\"example.com\",nonce=\"OA6MG9tEQGm2hh\",cnonce=\"OA6MHXh6VqTrRk\",charset=\"utf-8\",cipher=unsupported,nc=00000001,digest-uri=imap/server.com,response=d388dad90d4bbd760a152321f2143af7,qop=\"auth\",authzid=\"masteruser\""), "testuser@example.com", NULL, TRUE, FALSE}, + {&mech_external, UCHAR_LEN(""), "testuser", NULL, TRUE, TRUE}, + {&mech_dovecot_token, UCHAR_LEN("service\0pid\0testuser\0session_id\0deadbeef"), "testuser", NULL, TRUE, FALSE}, + {&mech_login, UCHAR_LEN("testuser"), "testuser", NULL, TRUE, FALSE}, + {&mech_plain, UCHAR_LEN("\0testuser\0testpass"), "testuser", NULL, TRUE, FALSE}, + {&mech_plain, UCHAR_LEN("normaluser\0masteruser\0masterpass"), "masteruser", NULL, TRUE, FALSE}, + {&mech_plain, UCHAR_LEN("normaluser\0normaluser\0masterpass"), "normaluser", NULL, TRUE, FALSE}, + {&mech_otp, UCHAR_LEN("somebody\0testuser"), "testuser", "All password databases were skipped", TRUE, FALSE}, + {&mech_otp, UCHAR_LEN("hex:5Bf0 75d9 959d 036f"), "otp_phase_2", NULL, TRUE, TRUE}, + {&mech_otp, UCHAR_LEN("word:BOND FOGY DRAB NE RISE MART"), "otp_phase_2", NULL, TRUE, TRUE}, + {&mech_otp, UCHAR_LEN("init-hex:f6bd 6b33 89b8 7203:md5 499 ke6118:23d1 b253 5ae0 2b7e"), "otp_phase_2", NULL, TRUE, TRUE}, + {&mech_otp, UCHAR_LEN("init-word:END KERN BALM NICK EROS WAVY:md5 499 ke1235:BABY FAIN OILY NIL TIDY DADE"), "otp_phase2", NULL , TRUE, TRUE}, + {&mech_oauthbearer, UCHAR_LEN("n,a=testuser,p=cHJvb2Y=,f=nonstandart\x01host=server\x01port=143\x01""auth=Bearer vF9dft4qmTc2Nvb3RlckBhbHRhdmlzdGEuY29tCg==\x01\x01"), "testuser", NULL, TRUE, FALSE}, + {&mech_scram_sha1, UCHAR_LEN("n,,n=testuser,r=rOprNGfwEbeRWgbNEkqO"), "testuser", "All password databases were skipped", TRUE, FALSE}, + {&mech_scram_sha256, UCHAR_LEN("n,,n=testuser,r=rOprNGfwEbeRWgbNEkqO"), "testuser", "All password databases were skipped", TRUE, FALSE}, + {&mech_xoauth2, UCHAR_LEN("user=testuser\x01""auth=Bearer vF9dft4qmTc2Nvb3RlckBhdHRhdmlzdGEuY29tCg==\x01\x01"), "testuser", NULL, TRUE, FALSE}, + + /* Below tests are expected to fail */ + /* Empty input tests*/ + {&mech_apop, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE}, + {&mech_cram_md5, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE}, + {&mech_digest_md5, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE}, + {&mech_dovecot_token, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE}, + {&mech_external, UCHAR_LEN(""), "testuser", NULL, FALSE, TRUE}, + {&mech_external, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE}, + {&mech_login, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE}, + {&mech_otp, UCHAR_LEN(""), NULL, "invalid input", FALSE, FALSE}, + {&mech_otp, UCHAR_LEN(""), "testuser", "unsupported response type", FALSE, TRUE}, + {&mech_plain, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE}, + {&mech_oauthbearer, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE}, + {&mech_xoauth2, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE}, + {&mech_scram_sha1, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE}, + {&mech_scram_sha256, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE}, + + /* Bad input tests*/ + {&mech_apop, UCHAR_LEN("1.1.1\0test\0user\0response"), NULL, NULL, FALSE, FALSE}, + {&mech_apop, UCHAR_LEN("1.1.1\0testuser\0tooshort"), NULL, NULL, FALSE, FALSE}, + {&mech_apop, UCHAR_LEN("1.1.1\0testuser\0responseoflen16-"), NULL, NULL, FALSE, FALSE}, + {&mech_apop, UCHAR_LEN("1.1.1"), NULL, NULL, FALSE, FALSE}, + {&mech_cram_md5, UCHAR_LEN("testuser\0response"), NULL, NULL, FALSE, FALSE}, + + /* Covering most of the digest md5 parsing */ + {&mech_digest_md5, UCHAR_LEN("username=\"testuser@example.com\",realm=\"example.com\",cnonce=\"OA6MHXh6VqTrRk\",response=d388dad90d4bbd760a152321f2143af7,qop=\"auth\""), NULL, NULL, FALSE, FALSE}, + {&mech_digest_md5, UCHAR_LEN("realm=\"example.com\",cnonce=\"OA6MHXh6VqTrRk\",nonce=\"OA6MG9tEQGm2hh\""), NULL, NULL, FALSE, FALSE}, + {&mech_digest_md5, UCHAR_LEN("username=\"testuser@example.com\",realm=\"example.com\", nonce=\"OA6MG9tEQGm2hh\""), NULL, NULL, FALSE, FALSE}, + {&mech_digest_md5, UCHAR_LEN("qop=\"auth-int\""), NULL, NULL, FALSE, FALSE}, + {&mech_digest_md5, UCHAR_LEN("qop=\"auth-int\""), NULL, NULL, FALSE, FALSE}, + {&mech_digest_md5, UCHAR_LEN("qop=\"auth-conf\",\"cipher=rc4\""), NULL, NULL, FALSE, FALSE}, + {&mech_digest_md5, UCHAR_LEN("cnonce=\"OA6MHXh6VqTrRk\",cnonce=\"OA6MHXh6VqTrRk\""), NULL, NULL, FALSE, FALSE}, + {&mech_digest_md5, UCHAR_LEN("cnonce=\"\""), NULL, NULL, FALSE, FALSE}, + {&mech_digest_md5, UCHAR_LEN("nonce=\"not matching\""), NULL, NULL, FALSE, FALSE}, + {&mech_digest_md5, UCHAR_LEN("nc=00000001,nc=00000002"), NULL, NULL, FALSE, FALSE}, + {&mech_digest_md5, UCHAR_LEN("nc=NAN"), NULL, NULL, FALSE, FALSE}, + {&mech_digest_md5, UCHAR_LEN("nc=00000002"), NULL, NULL, FALSE, FALSE}, + {&mech_digest_md5, UCHAR_LEN("cipher=unsupported"), NULL, NULL, FALSE, FALSE}, + {&mech_digest_md5, UCHAR_LEN("digest-uri="), NULL, NULL, FALSE, FALSE}, + {&mech_digest_md5, UCHAR_LEN("username=\"\""), NULL, NULL, FALSE, FALSE}, + {&mech_digest_md5, UCHAR_LEN("username=\"a\",username=\"b\""), NULL, NULL, FALSE, FALSE}, + {&mech_digest_md5, UCHAR_LEN("response=broken"), NULL, NULL, FALSE, FALSE}, + {&mech_digest_md5, UCHAR_LEN("maxbuf=32,maxbuf=1024"), NULL, NULL, FALSE, FALSE}, + {&mech_digest_md5, UCHAR_LEN("maxbuf=broken"), NULL, NULL, FALSE, FALSE}, + {&mech_digest_md5, UCHAR_LEN("authzid=\"somebody\",authzid=\"else\""), NULL, NULL, FALSE, FALSE}, + {&mech_digest_md5, UCHAR_LEN("authzid=\"\""), NULL, NULL, FALSE, FALSE}, + {&mech_digest_md5, UCHAR_LEN("charset=unsupported"), NULL, NULL, FALSE, FALSE}, + {&mech_digest_md5, UCHAR_LEN("qop=unsupported"), NULL, NULL, FALSE, FALSE}, + + /* Too much nuls */ + {&mech_dovecot_token, UCHAR_LEN("service\0pid\0fail\0se\0ssion_id\0deadbeef"), NULL , NULL, FALSE, FALSE}, + {&mech_login, UCHAR_LEN("test user\0user"), NULL, NULL, TRUE, FALSE}, + {&mech_oauthbearer, UCHAR_LEN("n,a==testuser,\x01""auth=Bearer token\x01\x01"), NULL, NULL, FALSE, FALSE}, + {&mech_oauthbearer, UCHAR_LEN("n,a=testuser,f=non-standard\x01""auth=Bearer token\x01\x01"), "testuser", NULL, FALSE, FALSE}, + {&mech_oauthbearer, UCHAR_LEN("n,a=testuser\x01""auth=token\x01\x01"), "testuser", NULL, FALSE, FALSE}, + {&mech_xoauth2, UCHAR_LEN("testuser\x01auth=Bearer token\x01\x01"), NULL, NULL, FALSE, FALSE}, + /* does not start with [B|b]earer */ + {&mech_xoauth2, UCHAR_LEN("user=testuser\x01""auth=token\x01\x01"), "testuser", NULL, FALSE, FALSE}, + /* Too much nuls */ + {&mech_plain, UCHAR_LEN("\0fa\0il\0ing\0withthis"), NULL, NULL, FALSE, FALSE}, + {&mech_plain, UCHAR_LEN("failingwiththis"), NULL, NULL, FALSE, FALSE}, + {&mech_plain, UCHAR_LEN("failing\0withthis"), NULL, NULL, FALSE, FALSE}, + {&mech_otp, UCHAR_LEN("someb\0ody\0testuser"), NULL, "invalid input", FALSE, FALSE}, + /* phase 2 */ + {&mech_otp, UCHAR_LEN("someb\0ody\0testuser"), "testuser", NULL, FALSE, TRUE}, + {&mech_scram_sha1, UCHAR_LEN("c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts="), NULL, NULL, FALSE, FALSE}, + {&mech_scram_sha1, UCHAR_LEN("iws0X8v3Bz2T0CJGbJQyF0X+HI4Ts=,,,,"), NULL, NULL, FALSE, FALSE}, + {&mech_scram_sha1, UCHAR_LEN("n,a=masteruser,,"), NULL, NULL, FALSE, FALSE}, + {&mech_scram_sha1, UCHAR_LEN("n,a==masteruser,,"), NULL, NULL, FALSE, FALSE}, + {&mech_scram_sha1, UCHAR_LEN("n,,m=testuser,,"), NULL, NULL, FALSE, FALSE}, + {&mech_scram_sha1, UCHAR_LEN("broken\0input"), NULL, NULL, FALSE, FALSE}, + {&mech_scram_sha256, UCHAR_LEN("broken\0input"), NULL, NULL, FALSE, FALSE}, + }; + + test_mechs_init(); + + for (unsigned int running_test = 0; running_test < N_ELEMENTS(tests); + running_test++) T_BEGIN { + const struct mech_module *mech = tests[running_test].mech; + struct auth_request *request; + const char *testname = t_strdup_printf("auth mech %s %d/%lu", + mech->mech_name, + running_test+1, + N_ELEMENTS(tests)); + test_begin(testname); + + request = auth_request_new(mech, NULL); + test_mech_prepare_request(request, &handler, running_test, + tests[running_test].set_username ? + tests[running_test].username : NULL); + + if (mech == &mech_apop && tests[running_test].in == NULL) + tests[running_test].in = + test_mech_construct_apop_challenge(request->connect_uid, + &tests[running_test].len); + + if (tests[running_test].expect_error != NULL) + test_expect_error_string(tests[running_test].expect_error); + + mech->auth_initial(request, tests[running_test].in, + tests[running_test].len); + + if (challenge != NULL) { + test_mech_handle_challenge(request, tests[running_test].in, + tests[running_test].len, + running_test, + tests[running_test].success); + } + + if (!tests[running_test].set_username) + test_assert_strcmp_idx(tests[running_test].username, request->user, + running_test); + else if (!tests[running_test].set_username && !tests[running_test].success) + test_assert_idx(request->user == NULL, running_test); + else + test_assert_idx(!request->failed, running_test); + + event_unref(&request->event); + event_unref(&request->mech_event); + mech->auth_free(request); + + test_end(); + } T_END; + mech_otp_deinit(); + array_free(&auths); +} + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_mechs, + NULL + }; + + return test_run(test_functions); +}