diff --git a/.gitignore b/.gitignore index 567609b12..1b763adf4 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ build/ +.ccls diff --git a/CHANGELOG.md b/CHANGELOG.md index 3583eab5c..4dee51a16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added - Add support for volatile keys. [#460](https://github.com/greenbone/gvm-libs/pull/460) - +- Possibility to use lcrypt with `$6$` (sha512) for authentication [484](https://github.com/greenbone/gvm-libs/pull/484) ### Changed ### Fixed ### Removed diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ed05c744..3b3a53e77 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -225,7 +225,8 @@ if (BUILD_TESTS AND NOT SKIP_SRC) add_custom_target (tests DEPENDS array-test alivedetection-test boreas_error-test boreas_io-test cli-test cvss-test ping-test sniffer-test util-test networking-test - xmlutils-test version-test osp-test nvti-test hosts-test) + passwordbasedauthentication-test xmlutils-test version-test osp-test + nvti-test hosts-test) endif (BUILD_TESTS AND NOT SKIP_SRC) diff --git a/base/cvss_tests.c b/base/cvss_tests.c index 5c6b65f48..e16734fb2 100644 --- a/base/cvss_tests.c +++ b/base/cvss_tests.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2009-2020 Greenbone Networks GmbH +/* Copyright (C) 2009-2021 Greenbone Networks GmbH * * SPDX-License-Identifier: GPL-2.0-or-later * diff --git a/util/CMakeLists.txt b/util/CMakeLists.txt index 3f10a4b81..d4b9d6ee4 100644 --- a/util/CMakeLists.txt +++ b/util/CMakeLists.txt @@ -75,6 +75,21 @@ else (NOT GPGME) endif (GPGME_VERSION VERSION_LESS GPGME_MIN_VERSION) endif (NOT GPGME) + +message (STATUS "Looking for libcrypt...") +find_library (CRYPT crypt) +message (STATUS "Looking for libcrypt... ${CRYPT}") +if (NOT CRYPT) +message (SEND_ERROR "The libcrypt library is required.") +else (NOT CRYPT) + pkg_search_module(CRYPT_M QUIET libcrypt) + if (DEFINED ${CRYPT_M_VERSION} AND ${CRYPT_M_VERSION} VERSION_GREATER "3.1.1") + message (STATUS "\t Using external crypt_gensal_r of ... ${CRYPT_M_VERSION}") + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DEXTERNAL_CRYPT_GENSALT_R=1") + endif() + set (CRYPT_LDFLAGS "-lcrypt") +endif (NOT CRYPT) + message (STATUS "Looking for libgcrypt...") find_library (GCRYPT gcrypt) message (STATUS "Looking for libgcrypt... ${GCRYPT}") @@ -140,11 +155,11 @@ endif() include_directories (${GLIB_INCLUDE_DIRS} ${GPGME_INCLUDE_DIRS} ${GCRYPT_INCLUDE_DIRS} ${LIBXML2_INCLUDE_DIRS}) -set (FILES authutils.c compressutils.c fileutils.c gpgmeutils.c kb.c ldaputils.c +set (FILES passwordbasedauthentication.c compressutils.c fileutils.c gpgmeutils.c kb.c ldaputils.c nvticache.c radiusutils.c serverutils.c sshutils.c uuidutils.c xmlutils.c) -set (HEADERS authutils.h compressutils.h fileutils.h gpgmeutils.h kb.h +set (HEADERS passwordbasedauthentication.h authutils.h compressutils.h fileutils.h gpgmeutils.h kb.h ldaputils.h nvticache.h radiusutils.h serverutils.h sshutils.h uuidutils.h xmlutils.h) @@ -168,13 +183,30 @@ if (BUILD_SHARED) ${RADIUS_LDFLAGS} ${LIBSSH_LDFLAGS} ${GNUTLS_LDFLAGS} ${GCRYPT_LDFLAGS} ${LDAP_LDFLAGS} ${REDIS_LDFLAGS} ${LIBXML2_LDFLAGS} ${UUID_LDFLAGS} - ${LINKER_HARDENING_FLAGS}) + ${LINKER_HARDENING_FLAGS} ${CRYPT_LDFLAGS}) endif (BUILD_SHARED) ## Tests if (BUILD_TESTS) + add_executable (passwordbasedauthentication-test + EXCLUDE_FROM_ALL + passwordbasedauthentication_tests.c) + + add_test (passwordbasedauthentication-test passwordbasedauthentication-test) + + target_include_directories (passwordbasedauthentication-test PRIVATE ${CGREEN_INCLUDE_DIRS}) + + target_link_libraries (passwordbasedauthentication-test ${CGREEN_LIBRARIES} + ${BSD_LDFLAGS} + ${GCRYPT_LDFLAGS} + ${CRYPT_LDFLAGS} + ${GLIB_LDFLAGS}) + + add_custom_target (tests-passwordbasedauthentication + DEPENDS passwordbasedauthentication-test) + add_executable (xmlutils-test EXCLUDE_FROM_ALL xmlutils_tests.c) diff --git a/util/passwordbasedauthentication.c b/util/passwordbasedauthentication.c new file mode 100644 index 000000000..bc9855127 --- /dev/null +++ b/util/passwordbasedauthentication.c @@ -0,0 +1,270 @@ +/* Copyright (C) 2020-2021 Greenbone Networks GmbH + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "passwordbasedauthentication.h" +// internal usage to have access to gvm_auth initialized to verify if +// initialization is needed +#include "authutils.c" + +#include +#include +#include +// UFC_crypt defines crypt_r when only when __USE_GNU is set +// this shouldn't affect other implementations +#define __USE_GNU +#include +#ifndef CRYPT_GENSALT_OUTPUT_SIZE +#define CRYPT_GENSALT_OUTPUT_SIZE 192 +#endif + +#ifndef CRYPT_OUTPUT_SIZE +#define CRYPT_OUTPUT_SIZE 384 +#endif + +int +is_prefix_supported (const char *id) +{ + return strcmp (PREFIX_DEFAULT, id) == 0; +} + +// we assume something else than libxcrypt > 3.1; like UFC-crypt +// libxcrypt sets a macro of crypt_gensalt_r to crypt_gensalt_rn +// therefore we could use that mechanism to figure out if we are on +// debian buster or newer. +#ifndef EXTERNAL_CRYPT_GENSALT_R + +// used printables within salt +const char ascii64[64] = + "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +/* Tries to get BUFLEN random bytes into BUF; returns 0 on success. */ +int +get_random (char *buf, size_t buflen) +{ + FILE *fp = fopen ("/dev/urandom", "r"); + int result = 0; + if (fp == NULL) + { + result = -1; + goto exit; + } + size_t nread = fread (buf, 1, buflen, fp); + fclose (fp); + if (nread < buflen) + { + result = -2; + } + +exit: + return result; +} +/* Generate a string suitable for use as the setting when hashing a passphrase. + * PREFIX controls which hash function will be used, + * COUNT controls the computional cost of the hash, + * RBYTES should point to NRBYTES bytes of random data. + * + * If PREFIX is a NULL pointer, the current best default is used; if RBYTES + * is a NULL pointer, random data will be retrieved from the operating system + * if possible. + * + * Teh generated setting string is written to OUTPUT, which is OUTPUT_SIZE long. + * OUTPUT_SIZE must be at least CRYPT_GENSALT_OUTPUT_SIZE. + * + * */ +char * +crypt_gensalt_r (const char *prefix, unsigned long count, const char *rbytes, + int nrbytes, char *output, int output_size) +{ + char *internal_rbytes = NULL; + unsigned int written = 0, used = 0; + unsigned long value = 0; + if ((rbytes != NULL && nrbytes < 3) || output_size < 16 + || !is_prefix_supported (prefix)) + { + output[0] = '*'; + goto exit; + } + if (rbytes == NULL) + { + internal_rbytes = malloc (16); + if (get_random (internal_rbytes, 16) != 0) + { + output[0] = '*'; + goto exit; + } + nrbytes = 16; + rbytes = internal_rbytes; + } + written = snprintf (output, output_size, "%srounds=%lu$", + prefix == NULL ? PREFIX_DEFAULT : prefix, count); + while (written + 5 < (unsigned int) output_size + && used + 3 < (unsigned int) nrbytes && (used * 4 / 3) < 16) + { + value = ((unsigned long) rbytes[used + 0] << 0) + | ((unsigned long) rbytes[used + 1] << 8) + | ((unsigned long) rbytes[used + 2] << 16); + output[written] = ascii64[value & 0x3f]; + output[written + 1] = ascii64[(value >> 6) & 0x3f]; + output[written + 2] = ascii64[(value >> 12) & 0x3f]; + output[written + 3] = ascii64[(value >> 18) & 0x3f]; + written += 4; + used += 3; + } + output[written] = '\0'; +exit: + if (internal_rbytes != NULL) + free (internal_rbytes); + return output[0] == '*' ? 0 : output; +} + +#endif + +struct PBASettings * +pba_init (const char *pepper, unsigned int pepper_size, unsigned int count, + char *prefix) +{ + unsigned int i = 0; + struct PBASettings *result = NULL; + if (pepper_size > MAX_PEPPER_SIZE) + goto exit; + if (prefix != NULL && !is_prefix_supported (prefix)) + goto exit; + result = malloc (sizeof (struct PBASettings)); + for (i = 0; i < MAX_PEPPER_SIZE; i++) + result->pepper[i] = pepper != NULL && i < pepper_size ? pepper[i] : 0; + result->count = count == 0 ? COUNT_DEFAULT : count; + result->prefix = prefix == NULL ? PREFIX_DEFAULT : prefix; +exit: + return result; +} + +void +pba_finalize (struct PBASettings *settings) +{ + free (settings); +} + +int +pba_is_phc_compliant (const char *setting) +{ + if (setting == NULL) + { + return 0; + } + return strlen (setting) > 1 && setting[0] == '$'; +} + +char * +pba_hash (struct PBASettings *setting, const char *password) +{ + char *result = NULL, *settings = NULL, *tmp, *rslt; + int i; + struct crypt_data *data = NULL; + + if (!setting || !password) + goto exit; + if (!is_prefix_supported (setting->prefix)) + goto exit; + settings = malloc (CRYPT_GENSALT_OUTPUT_SIZE); + if (crypt_gensalt_r (setting->prefix, setting->count, NULL, 0, settings, + CRYPT_GENSALT_OUTPUT_SIZE) + == NULL) + goto exit; + tmp = settings + strlen (settings) - 1; + for (i = MAX_PEPPER_SIZE - 1; i > -1; i--) + { + if (setting->pepper[i] != 0) + tmp[0] = setting->pepper[i]; + tmp--; + } + + data = calloc (1, sizeof (struct crypt_data)); + rslt = crypt_r (password, settings, data); + if (rslt == NULL) + goto exit; + result = malloc (CRYPT_OUTPUT_SIZE); + strncpy(result, rslt, CRYPT_OUTPUT_SIZE); + // remove pepper, by jumping to begin of applied pepper within result + // and overridding it. + tmp = result + (tmp - settings); + for (i = 0; i < MAX_PEPPER_SIZE; i++) + { + tmp++; + if (setting->pepper[i] != 0) + tmp[0] = '0'; + } +exit: + if (data != NULL) + free (data); + if (settings != NULL) + free (settings); + return result; +} + +enum pba_rc +pba_verify_hash (const struct PBASettings *setting, const char *hash, + const char *password) +{ + char *cmp, *tmp = NULL; + struct crypt_data *data = NULL; + int i = 0; + enum pba_rc result = ERR; + if (!setting || !hash || !password) + goto exit; + if (!is_prefix_supported (setting->prefix)) + goto exit; + if (pba_is_phc_compliant (hash) != 0) + { + data = calloc (1, sizeof (struct crypt_data)); + // manipulate hash to reapply pepper + tmp = malloc ( CRYPT_OUTPUT_SIZE); + strncpy(tmp, hash, CRYPT_OUTPUT_SIZE); + cmp = strrchr (tmp, '$'); + for (i = MAX_PEPPER_SIZE - 1; i > -1; i--) + { + cmp--; + if (setting->pepper[i] != 0) + cmp[0] = setting->pepper[i]; + } + cmp = crypt_r (password, tmp, data); + if (strcmp (tmp, cmp) == 0) + result = VALID; + else + result = INVALID; + } + else + { + // assume authutils hash handling + // initialize gvm_auth utils if not already initialized + if (initialized == FALSE && gvm_auth_init () != 0) + { + goto exit; + } + // verify result of gvm_authenticate_classic + i = gvm_authenticate_classic (NULL, password, hash); + if (i == 0) + result = UPDATE_RECOMMENDED; + else if (i == 1) + result = INVALID; + } +exit: + if (data != NULL) + free (data); + if (tmp != NULL) + free (tmp); + return result; +} diff --git a/util/passwordbasedauthentication.h b/util/passwordbasedauthentication.h new file mode 100644 index 000000000..cc912227c --- /dev/null +++ b/util/passwordbasedauthentication.h @@ -0,0 +1,81 @@ +/* Copyright (C) 2020-2021 Greenbone Networks GmbH + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef _GVM_PASSWORDBASEDAUTHENTICATION_H +#define _GVM_PASSWORDBASEDAUTHENTICATION_H + +/* max amount of applied pepper */ +#define MAX_PEPPER_SIZE 4 +/* is used when count is 0 on init*/ +#define COUNT_DEFAULT 20000 +/* sha512 */ +#define PREFIX_DEFAULT "$6$" + +/** + * + * PBASettings is used by pba_hash to control SALT, HASH function and + * computional costs. + * + * */ +struct PBASettings +{ + char pepper[MAX_PEPPER_SIZE]; /* is statically applied to the random salt */ + unsigned int count; /* controls the computational cost of the hash */ + char *prefix; /* controls which hash function will be used */ +}; +/** + * Intitializes PBASettings with given PEPPER, PREFIX, COUNT. + * + * PEPPER_SIZE must be lower or equal MAX_PEPPER_SIZE when PEPPER is set, when + * PEPPER is a NULL pointer, no pepper will be used and PEPPER_SIZE is ignored. + * + * COUNT is set to COUNT_DEFAULT when it is 0, PREFIX is set to PREFIX_DEFAULT + * when prefix is a nullpointer. + * + * Returns a pointer to PBASettings on success or NULL on failure. + * + * */ +struct PBASettings *pba_init(const char *pepper, unsigned int pepper_size, unsigned int count, char *prefix); + +/* return values for pba pba_verify_hash */ +enum pba_rc { + VALID, /* hash and password are correct */ + UPDATE_RECOMMENDED, /* password is correct but in an outdated format*/ + INVALID, /* password is incorrect */ + ERR, /* unexpected error */ +}; + +/** + * pba_hash tries to create a hash based SETTING and PASSWORD. + * Returns a hash on success or a NULL pointer on failure + */ +char *pba_hash (struct PBASettings *setting, const char *password); + +/** + * pba_verify_hash tries to create hash based on PASSWORD and settings found via + * HASH and compares that with HASH. + * + * Returns VALID if HASH and PASSWORD are correct; + * UPDATE_RECOMMENDED when the HASH and PASSWORD are correct but based on a deprecated algorithm; + * IVALID if HASH does not match PASSWORD; + * ERR if an unexpected error occurs. + */ +enum pba_rc pba_verify_hash(const struct PBASettings *settings, const char *hash, const char *password); + +void pba_finalize(struct PBASettings *settings); + +#endif diff --git a/util/passwordbasedauthentication_tests.c b/util/passwordbasedauthentication_tests.c new file mode 100644 index 000000000..2e6afc9d6 --- /dev/null +++ b/util/passwordbasedauthentication_tests.c @@ -0,0 +1,143 @@ +/* Copyright (C) 2019-2021 Greenbone Networks GmbH + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "passwordbasedauthentication.c" +#include "authutils.h" + +#include +#include +#include +Describe (PBA); +BeforeEach (PBA) +{ +} +AfterEach (PBA) +{ +} + +Ensure (PBA, returns_false_on_not_phc_compliant_setting) +{ + assert_false(pba_is_phc_compliant(NULL)); + assert_false(pba_is_phc_compliant("$")); + assert_false(pba_is_phc_compliant("password")); +} +Ensure (PBA, returns_true_on_phc_compliant_setting) +{ + assert_true(pba_is_phc_compliant("$password")); +} +Ensure (PBA, returns_NULL_on_unsupport_settings) +{ + struct PBASettings setting = { "0000", 20000, "$6$"}; + assert_false(pba_hash(NULL, "*password")); + assert_false(pba_hash(&setting, NULL)); + setting.prefix = "$1$"; + assert_false(pba_hash(&setting, "*password")); +} +Ensure (PBA, unique_hash_without_adding_used_pepper) +{ + struct PBASettings setting = { "4242", 20000, "$6$"}; + char *cmp_hash, *hash; + hash = pba_hash(&setting, "*password"); + assert_not_equal(hash, NULL); + assert_false(string_contains(hash, setting.pepper)); + cmp_hash = pba_hash(&setting, "*password"); + assert_string_not_equal(hash, cmp_hash); + free(hash); + free(cmp_hash); +} +Ensure (PBA, verify_hash) +{ + struct PBASettings setting = { "4242" , 20000, "$6$"}; + char *hash; + hash = pba_hash(&setting, "*password"); + assert_not_equal(hash, NULL); + assert_equal(pba_verify_hash(&setting, hash, "*password"), VALID); + assert_equal(pba_verify_hash(&setting, hash, "*password1"), INVALID); + free(hash); + struct PBASettings setting_wo_pepper = { "\0\0\0\0" , 20000, "$6$"}; + hash = pba_hash(&setting_wo_pepper, "*password"); + assert_equal(pba_verify_hash(&setting_wo_pepper, hash, "*password"), VALID); + free(hash); +} + +Ensure (PBA, defaults) +{ + int i; + struct PBASettings *settings = pba_init(NULL, 0, 0, NULL); + assert_equal(settings->count, 20000); + for (i = 0; i < MAX_PEPPER_SIZE; i++) + assert_equal_with_message(settings->pepper[i], 0, "init_without_pepper_should_not_have_pepper"); + assert_string_equal(settings->prefix, "$6$"); + pba_finalize(settings); + +} +Ensure (PBA, initialization) +{ + int i; + struct PBASettings *settings = pba_init("444", 3, 1, "$6$"); + assert_equal(settings->count, 1); + for (i = 0; i < MAX_PEPPER_SIZE - 1; i++) + assert_equal_with_message(settings->pepper[i], '4', "init_with_pepper_should_be_set"); + assert_equal_with_message(settings->pepper[MAX_PEPPER_SIZE -1], '\0', "last_pepper_should_be_unset_by_pepper_3"); + assert_string_equal(settings->prefix, "$6$"); + pba_finalize(settings); + settings = pba_init("444", MAX_PEPPER_SIZE + 1, 1, "$6$"); + assert_equal_with_message(settings, NULL, "should_fail_due_to_too_much_pepper"); + settings = pba_init("444", MAX_PEPPER_SIZE, 1, "$WALDFEE$"); + assert_equal_with_message(settings, NULL, "should_fail_due_to_unknown_prefix"); + +} + +Ensure (PBA, handle_md5_hash) +{ + struct PBASettings *settings = pba_init(NULL, 0, 0, NULL); + char *hash; + assert_equal(gvm_auth_init(), 0); + hash = get_password_hashes ("admin"); + assert_equal(pba_verify_hash(settings, hash, "admin"), UPDATE_RECOMMENDED); + pba_finalize(settings); +} + +int +main (int argc, char **argv) +{ + TestSuite *suite; + + suite = create_test_suite (); + + add_test_with_context (suite, PBA, + returns_false_on_not_phc_compliant_setting); + add_test_with_context (suite, PBA, + returns_true_on_phc_compliant_setting); + add_test_with_context (suite, PBA, + returns_NULL_on_unsupport_settings); + add_test_with_context (suite, PBA, + unique_hash_without_adding_used_pepper); + add_test_with_context (suite, PBA, + verify_hash); + add_test_with_context (suite, PBA, + handle_md5_hash); + add_test_with_context (suite, PBA, + defaults); + add_test_with_context (suite, PBA, + initialization); + if (argc > 1) + return run_single_test (suite, argv[1], create_text_reporter ()); + return run_test_suite (suite, create_text_reporter ()); +}