Systemd/src/shared/libcrypt-util.c
Zbigniew Jędrzejewski-Szmek 2b49f0ca83 Check for crypt_gensalt_ra() instead of relying on libxcrypt presence
Since the loop to check various xcrypt functions is already in place,
adding one more is cheap. And it is nicer to check for the function
directly. People like to backport things, so we might get lucky even
without having libxcrypt.
2020-09-15 11:52:30 +02:00

162 lines
4.8 KiB
C

/* SPDX-License-Identifier: LGPL-2.1+ */
#if HAVE_CRYPT_H
/* libxcrypt is a replacement for glibc's libcrypt, and libcrypt might be
* removed from glibc at some point. As part of the removal, defines for
* crypt(3) are dropped from unistd.h, and we must include crypt.h instead.
*
* Newer versions of glibc (v2.0+) already ship crypt.h with a definition
* of crypt(3) as well, so we simply include it if it is present. MariaDB,
* MySQL, PostgreSQL, Perl and some other wide-spread packages do it the
* same way since ages without any problems.
*/
# include <crypt.h>
#else
# include <unistd.h>
#endif
#include <errno.h>
#include <stdlib.h>
#include "alloc-util.h"
#include "errno-util.h"
#include "libcrypt-util.h"
#include "log.h"
#include "macro.h"
#include "memory-util.h"
#include "missing_stdlib.h"
#include "random-util.h"
#include "string-util.h"
#include "strv.h"
int make_salt(char **ret) {
#if HAVE_CRYPT_GENSALT_RA
const char *e;
char *salt;
/* If we have crypt_gensalt_ra() we default to the "preferred method" (i.e. usually yescrypt).
* crypt_gensalt_ra() is usually provided by libxcrypt. */
e = secure_getenv("SYSTEMD_CRYPT_PREFIX");
if (!e)
e = crypt_preferred_method();
log_debug("Generating salt for hash prefix: %s", e);
salt = crypt_gensalt_ra(e, 0, NULL, 0);
if (!salt)
return -errno;
*ret = salt;
return 0;
#else
/* If crypt_gensalt_ra() is not available, we use SHA512 and generate the salt on our own. */
static const char table[] =
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789"
"./";
uint8_t raw[16];
char *salt, *j;
size_t i;
int r;
/* This is a bit like crypt_gensalt_ra(), but doesn't require libcrypt, and doesn't do anything but
* SHA512, i.e. is legacy-free and minimizes our deps. */
assert_cc(sizeof(table) == 64U + 1U);
/* Insist on the best randomness by setting RANDOM_BLOCK, this is about keeping passwords secret after all. */
r = genuine_random_bytes(raw, sizeof(raw), RANDOM_BLOCK);
if (r < 0)
return r;
salt = new(char, 3+sizeof(raw)+1+1);
if (!salt)
return -ENOMEM;
/* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */
j = stpcpy(salt, "$6$");
for (i = 0; i < sizeof(raw); i++)
j[i] = table[raw[i] & 63];
j[i++] = '$';
j[i] = 0;
*ret = salt;
return 0;
#endif
}
int hash_password_full(const char *password, void **cd_data, int *cd_size, char **ret) {
_cleanup_free_ char *salt = NULL;
_cleanup_(erase_and_freep) void *_cd_data = NULL;
char *p;
int r, _cd_size = 0;
assert(!!cd_data == !!cd_size);
r = make_salt(&salt);
if (r < 0)
return log_debug_errno(r, "Failed to generate salt: %m");
errno = 0;
p = crypt_ra(password, salt, cd_data ?: &_cd_data, cd_size ?: &_cd_size);
if (!p)
return log_debug_errno(errno_or_else(SYNTHETIC_ERRNO(EINVAL)),
"crypt_ra() failed: %m");
p = strdup(p);
if (!p)
return -ENOMEM;
*ret = p;
return 0;
}
bool looks_like_hashed_password(const char *s) {
/* Returns false if the specified string is certainly not a hashed UNIX password. crypt(5) lists
* various hashing methods. We only reject (return false) strings which are documented to have
* different meanings.
*
* In particular, we allow locked passwords, i.e. strings starting with "!", including just "!",
* i.e. the locked empty password. See also fc58c0c7bf7e4f525b916e3e5be0de2307fef04e.
*/
if (!s)
return false;
s += strspn(s, "!"); /* Skip (possibly duplicated) locking prefix */
return !STR_IN_SET(s, "x", "*");
}
int test_password_one(const char *hashed_password, const char *password) {
_cleanup_(erase_and_freep) void *cd_data = NULL;
int cd_size = 0;
const char *k;
errno = 0;
k = crypt_ra(password, hashed_password, &cd_data, &cd_size);
if (!k)
return errno_or_else(EINVAL);
return streq(k, hashed_password);
}
int test_password_many(char **hashed_password, const char *password) {
char **hpw;
int r;
STRV_FOREACH(hpw, hashed_password) {
r = test_password_one(*hpw, password);
if (r < 0)
return r;
if (r > 0)
return true;
}
return false;
}