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 ());
+}