Merge pull request #16981 from keszybz/use-crypt_ra

Use crypt_ra to allocate scratch area for password hashing
This commit is contained in:
Lennart Poettering 2020-09-18 19:46:08 +02:00 committed by GitHub
commit 3f440b13b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 266 additions and 112 deletions

View File

@ -882,6 +882,17 @@ libm = cc.find_library('m')
libdl = cc.find_library('dl')
libcrypt = cc.find_library('crypt')
crypt_header = conf.get('HAVE_CRYPT_H') == 1 ? \
'''#include <crypt.h>''' : '''#include <unistd.h>'''
foreach ident : [
['crypt_ra', crypt_header],
['crypt_gensalt_ra', crypt_header]]
have = cc.has_function(ident[0], prefix : ident[1], args : '-D_GNU_SOURCE',
dependencies : libcrypt)
conf.set10('HAVE_' + ident[0].to_upper(), have)
endforeach
libcap = dependency('libcap', required : false)
if not libcap.found()
# Compat with Ubuntu 14.04 which ships libcap w/o .pc file

View File

@ -802,7 +802,7 @@ static int write_root_shadow(const char *shadow_path, const char *hashed_passwor
static int process_root_args(void) {
_cleanup_close_ int lock = -1;
struct crypt_data cd = {};
_cleanup_(erase_and_freep) char *_hashed_password = NULL;
const char *password, *hashed_password;
const char *etc_passwd, *etc_shadow;
int r;
@ -866,20 +866,13 @@ static int process_root_args(void) {
password = "x";
hashed_password = arg_root_password;
} else if (arg_root_password) {
_cleanup_free_ char *salt = NULL;
/* hashed_password points inside cd after crypt_r returns so cd has function scope. */
r = hash_password(arg_root_password, &_hashed_password);
if (r < 0)
return log_error_errno(r, "Failed to hash password: %m");
password = "x";
hashed_password = _hashed_password;
r = make_salt(&salt);
if (r < 0)
return log_error_errno(r, "Failed to get salt: %m");
errno = 0;
hashed_password = crypt_r(arg_root_password, salt, &cd);
if (!hashed_password)
return log_error_errno(errno == 0 ? SYNTHETIC_ERRNO(EINVAL) : errno,
"Failed to encrypt password: %m");
} else if (arg_delete_root_password)
password = hashed_password = "";
else

View File

@ -1,7 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "dns-domain.h"
#include "errno-util.h"
#include "home-util.h"
#include "libcrypt-util.h"
#include "memory-util.h"
@ -134,35 +133,3 @@ int bus_message_append_secret(sd_bus_message *m, UserRecord *secret) {
return sd_bus_message_append(m, "s", formatted);
}
int test_password_one(const char *hashed_password, const char *password) {
struct crypt_data cc = {};
const char *k;
bool b;
errno = 0;
k = crypt_r(password, hashed_password, &cc);
if (!k) {
explicit_bzero_safe(&cc, sizeof(cc));
return errno_or_else(EINVAL);
}
b = streq(k, hashed_password);
explicit_bzero_safe(&cc, sizeof(cc));
return b;
}
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;
}

View File

@ -21,6 +21,3 @@ int bus_message_append_secret(sd_bus_message *m, UserRecord *secret);
/* Many of our operations might be slow due to crypto, fsck, recursive chown() and so on. For these
* operations permit a *very* long timeout */
#define HOME_SLOW_BUS_CALL_TIMEOUT_USEC (2*USEC_PER_MINUTE)
int test_password_one(const char *hashed_password, const char *password);
int test_password_many(char **hashed_password, const char *password);

View File

@ -70,31 +70,23 @@ static int add_fido2_salt(
size_t secret_size) {
_cleanup_(json_variant_unrefp) JsonVariant *l = NULL, *w = NULL, *e = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
_cleanup_free_ char *unix_salt = NULL;
struct crypt_data cd = {};
char *k;
_cleanup_(erase_and_freep) char *base64_encoded = NULL, *hashed = NULL;
int r;
r = make_salt(&unix_salt);
if (r < 0)
return log_error_errno(r, "Failed to generate salt: %m");
/* Before using UNIX hashing on the supplied key we base64 encode it, since crypt_r() and friends
* expect a NUL terminated string, and we use a binary key */
r = base64mem(secret, secret_size, &base64_encoded);
if (r < 0)
return log_error_errno(r, "Failed to base64 encode secret key: %m");
errno = 0;
k = crypt_r(base64_encoded, unix_salt, &cd);
if (!k)
r = hash_password(base64_encoded, &hashed);
if (r < 0)
return log_error_errno(errno_or_else(EINVAL), "Failed to UNIX hash secret key: %m");
r = json_build(&e, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("credential", JSON_BUILD_BASE64(cid, cid_size)),
JSON_BUILD_PAIR("salt", JSON_BUILD_BASE64(fido2_salt, fido2_salt_size)),
JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRING(k))));
JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRING(hashed))));
if (r < 0)
return log_error_errno(r, "Failed to build FIDO2 salt JSON key object: %m");

View File

@ -134,10 +134,7 @@ static int add_pkcs11_encrypted_key(
const void *decrypted_key, size_t decrypted_key_size) {
_cleanup_(json_variant_unrefp) JsonVariant *l = NULL, *w = NULL, *e = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
_cleanup_free_ char *salt = NULL;
struct crypt_data cd = {};
char *k;
_cleanup_(erase_and_freep) char *base64_encoded = NULL, *hashed = NULL;
int r;
assert(v);
@ -147,25 +144,20 @@ static int add_pkcs11_encrypted_key(
assert(decrypted_key);
assert(decrypted_key_size > 0);
r = make_salt(&salt);
if (r < 0)
return log_error_errno(r, "Failed to generate salt: %m");
/* Before using UNIX hashing on the supplied key we base64 encode it, since crypt_r() and friends
* expect a NUL terminated string, and we use a binary key */
r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
if (r < 0)
return log_error_errno(r, "Failed to base64 encode secret key: %m");
errno = 0;
k = crypt_r(base64_encoded, salt, &cd);
if (!k)
r = hash_password(base64_encoded, &hashed);
if (r < 0)
return log_error_errno(errno_or_else(EINVAL), "Failed to UNIX hash secret key: %m");
r = json_build(&e, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("uri", JSON_BUILD_STRING(uri)),
JSON_BUILD_PAIR("data", JSON_BUILD_BASE64(encrypted_key, encrypted_key_size)),
JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRING(k))));
JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRING(hashed))));
if (r < 0)
return log_error_errno(r, "Failed to build encrypted JSON key object: %m");

View File

@ -183,9 +183,7 @@ static int print_qr_code(const char *secret) {
}
int identity_add_recovery_key(JsonVariant **v) {
_cleanup_(erase_and_freep) char *unix_salt = NULL, *password = NULL;
struct crypt_data cd = {};
char *k;
_cleanup_(erase_and_freep) char *password = NULL, *hashed = NULL;
int r;
assert(v);
@ -196,17 +194,12 @@ int identity_add_recovery_key(JsonVariant **v) {
return r;
/* Let's UNIX hash it */
r = make_salt(&unix_salt);
r = hash_password(password, &hashed);
if (r < 0)
return log_error_errno(r, "Failed to generate salt: %m");
errno = 0;
k = crypt_r(password, unix_salt, &cd);
if (!k)
return log_error_errno(errno_or_else(EINVAL), "Failed to UNIX hash secret key: %m");
/* Let's now add the "privileged" version of the recovery key */
r = add_privileged(v, k);
r = add_privileged(v, hashed);
if (r < 0)
return r;

View File

@ -17,6 +17,7 @@
#include "homework-mount.h"
#include "homework-pkcs11.h"
#include "homework.h"
#include "libcrypt-util.h"
#include "main-func.h"
#include "memory-util.h"
#include "missing_magic.h"

View File

@ -3,6 +3,7 @@
#include "bus-common-errors.h"
#include "errno-util.h"
#include "home-util.h"
#include "libcrypt-util.h"
#include "pwquality-util.h"
#include "strv.h"
#include "user-record-pwquality.h"

View File

@ -806,20 +806,13 @@ int user_record_make_hashed_password(UserRecord *h, char **secret, bool extend)
}
STRV_FOREACH(i, secret) {
_cleanup_free_ char *salt = NULL;
struct crypt_data cd = {};
char *k;
_cleanup_(erase_and_freep) char *hashed = NULL;
r = make_salt(&salt);
r = hash_password(*i, &hashed);
if (r < 0)
return r;
errno = 0;
k = crypt_r(*i, salt, &cd);
if (!k)
return errno_or_else(EINVAL);
r = strv_extend(&np, k);
r = strv_consume(&np, TAKE_PTR(hashed));
if (r < 0)
return r;
}

View File

@ -1,12 +1,29 @@
/* 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"
@ -14,12 +31,12 @@
int make_salt(char **ret) {
#ifdef XCRYPT_VERSION_MAJOR
#if HAVE_CRYPT_GENSALT_RA
const char *e;
char *salt;
/* If we have libxcrypt we default to the "preferred method" (i.e. usually yescrypt), and generate it
* with crypt_gensalt_ra(). */
/* 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)
@ -34,8 +51,7 @@ int make_salt(char **ret) {
*ret = salt;
return 0;
#else
/* If libxcrypt is not used, we use SHA512 and generate the salt on our own since crypt_gensalt_ra()
* is not available. */
/* If crypt_gensalt_ra() is not available, we use SHA512 and generate the salt on our own. */
static const char table[] =
"abcdefghijklmnopqrstuvwxyz"
@ -53,6 +69,8 @@ int make_salt(char **ret) {
assert_cc(sizeof(table) == 64U + 1U);
log_debug("Generating fallback salt for hash prefix: $6$");
/* 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)
@ -74,6 +92,73 @@ int make_salt(char **ret) {
#endif
}
#if HAVE_CRYPT_RA
# define CRYPT_RA_NAME "crypt_ra"
#else
# define CRYPT_RA_NAME "crypt_r"
/* Provide a poor man's fallback that uses a fixed size buffer. */
static char* systemd_crypt_ra(const char *phrase, const char *setting, void **data, int *size) {
assert(data);
assert(size);
/* We allocate the buffer because crypt(3) says: struct crypt_data may be quite large (32kB in this
* implementation of libcrypt; over 128kB in some other implementations). This is large enough that
* it may be unwise to allocate it on the stack. */
if (!*data) {
*data = new0(struct crypt_data, 1);
if (!*data) {
errno = -ENOMEM;
return NULL;
}
*size = (int) (sizeof(struct crypt_data));
}
char *t = crypt_r(phrase, setting, *data);
if (!t)
return NULL;
/* crypt_r may return a pointer to an invalid hashed password on error. Our callers expect NULL on
* error, so let's just return that. */
if (t[0] == '*')
return NULL;
return t;
}
#define crypt_ra systemd_crypt_ra
#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_NAME "() 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
@ -89,3 +174,35 @@ bool looks_like_hashed_password(const char *s) {
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) {
if (errno == ENOMEM)
return -ENOMEM;
/* Unknown or unavailable hashing method or string too short */
return 0;
}
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;
}

View File

@ -1,22 +1,13 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#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>
#endif
#include <stdbool.h>
#include <stdlib.h>
int make_salt(char **ret);
int hash_password_full(const char *password, void **cd_data, int *cd_size, char **ret);
static inline int hash_password(const char *password, char **ret) {
return hash_password_full(password, NULL, NULL, ret);
}
bool looks_like_hashed_password(const char *s);
int test_password_one(const char *hashed_password, const char *password);
int test_password_many(char **hashed_password, const char *password);

View File

@ -301,6 +301,10 @@ tests += [
[],
[]],
[['src/test/test-libcrypt-util.c'],
[],
[]],
[['src/test/test-offline-passwd.c',
'src/shared/offline-passwd.c',
'src/shared/offline-passwd.h'],

View File

@ -0,0 +1,102 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#if HAVE_CRYPT_H
# include <crypt.h>
#else
# include <unistd.h>
#endif
#include "strv.h"
#include "tests.h"
#include "libcrypt-util.h"
static int test_hash_password(void) {
log_info("/* %s */", __func__);
/* As a warmup exercise, check if we can hash passwords. */
bool have_sane_hash = false;
const char *hash;
FOREACH_STRING(hash,
"ew3bU1.hoKk4o",
"$1$gc5rWpTB$wK1aul1PyBn9AX1z93stk1",
"$2b$12$BlqcGkB/7BFvNMXKGxDea.5/8D6FTny.cbNcHW/tqcrcyo6ZJd8u2",
"$5$lGhDrcrao9zb5oIK$05KlOVG3ocknx/ThreqXE/gk.XzFFBMTksc4t2CPDUD",
"$6$c7wB/3GiRk0VHf7e$zXJ7hN0aLZapE.iO4mn/oHu6.prsXTUG/5k1AxpgR85ELolyAcaIGRgzfwJs3isTChMDBjnthZyaMCfCNxo9I.",
"$y$j9T$$9cKOWsAm4m97WiYk61lPPibZpy3oaGPIbsL4koRe/XD") {
int b;
b = test_password_one(hash, "ppp");
log_info("%s: %s", hash, yes_no(b));
#if defined(XCRYPT_VERSION_MAJOR)
/* xcrypt is supposed to always implement all methods. */
assert_se(b);
#endif
if (b && IN_SET(hash[1], '6', 'y'))
have_sane_hash = true;
}
return have_sane_hash;
}
static void test_hash_password_full(void) {
log_info("/* %s */", __func__);
_cleanup_free_ void *cd_data = NULL;
const char *i;
int cd_size = 0;
log_info("sizeof(struct crypt_data): %zu bytes", sizeof(struct crypt_data));
for (unsigned c = 0; c < 2; c++)
FOREACH_STRING(i, "abc123", "h⸿sło") {
_cleanup_free_ char *hashed;
if (c == 0)
assert_se(hash_password_full(i, &cd_data, &cd_size, &hashed) == 0);
else
assert_se(hash_password_full(i, NULL, NULL, &hashed) == 0);
log_debug("\"%s\"\"%s\"", i, hashed);
log_info("crypt_r[a] buffer size: %i bytes", cd_size);
assert_se(test_password_one(hashed, i) == true);
assert_se(test_password_one(i, hashed) <= 0); /* We get an error for non-utf8 */
assert_se(test_password_one(hashed, "foobar") == false);
assert_se(test_password_many(STRV_MAKE(hashed), i) == true);
assert_se(test_password_many(STRV_MAKE(hashed), "foobar") == false);
assert_se(test_password_many(STRV_MAKE(hashed, hashed, hashed), "foobar") == false);
assert_se(test_password_many(STRV_MAKE("$y$j9T$dlCXwkX0GC5L6B8Gf.4PN/$VCyEH",
hashed,
"$y$j9T$SAayASazWZIQeJd9AS02m/$"),
i) == true);
assert_se(test_password_many(STRV_MAKE("$W$j9T$dlCXwkX0GC5L6B8Gf.4PN/$VCyEH", /* no such method exists... */
hashed,
"$y$j9T$SAayASazWZIQeJd9AS02m/$"),
i) == true);
assert_se(test_password_many(STRV_MAKE("$y$j9T$dlCXwkX0GC5L6B8Gf.4PN/$VCyEH",
hashed,
"$y$j9T$SAayASazWZIQeJd9AS02m/$"),
"") == false);
assert_se(test_password_many(STRV_MAKE("$W$j9T$dlCXwkX0GC5L6B8Gf.4PN/$VCyEH", /* no such method exists... */
hashed,
"$y$j9T$SAayASazWZIQeJd9AS02m/$"),
"") == false);
}
}
int main(int argc, char *argv[]) {
test_setup_logging(LOG_DEBUG);
#if defined(__powerpc__) && !defined(XCRYPT_VERSION_MAJOR)
return log_tests_skipped("crypt_r() causes a buffer overflow on ppc64el, see https://github.com/systemd/systemd/pull/16981#issuecomment-691203787");
#endif
if (!test_hash_password())
return log_tests_skipped("crypt doesn't support yescrypt or sha512crypt");
test_hash_password_full();
return 0;
}