diff options
Diffstat (limited to 'lib/utils_pbkdf.c')
-rw-r--r-- | lib/utils_pbkdf.c | 333 |
1 files changed, 333 insertions, 0 deletions
diff --git a/lib/utils_pbkdf.c b/lib/utils_pbkdf.c new file mode 100644 index 0000000..4d7e18d --- /dev/null +++ b/lib/utils_pbkdf.c @@ -0,0 +1,333 @@ +/* + * utils_pbkdf - PBKDF settings for libcryptsetup + * + * Copyright (C) 2009-2023 Red Hat, Inc. All rights reserved. + * Copyright (C) 2009-2023 Milan Broz + * + * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdlib.h> +#include <errno.h> + +#include "internal.h" + +const struct crypt_pbkdf_type default_pbkdf2 = { + .type = CRYPT_KDF_PBKDF2, + .hash = DEFAULT_LUKS1_HASH, + .time_ms = DEFAULT_LUKS1_ITER_TIME +}; + +const struct crypt_pbkdf_type default_argon2i = { + .type = CRYPT_KDF_ARGON2I, + .hash = DEFAULT_LUKS1_HASH, + .time_ms = DEFAULT_LUKS2_ITER_TIME, + .max_memory_kb = DEFAULT_LUKS2_MEMORY_KB, + .parallel_threads = DEFAULT_LUKS2_PARALLEL_THREADS +}; + +const struct crypt_pbkdf_type default_argon2id = { + .type = CRYPT_KDF_ARGON2ID, + .hash = DEFAULT_LUKS1_HASH, + .time_ms = DEFAULT_LUKS2_ITER_TIME, + .max_memory_kb = DEFAULT_LUKS2_MEMORY_KB, + .parallel_threads = DEFAULT_LUKS2_PARALLEL_THREADS +}; + +const struct crypt_pbkdf_type *crypt_get_pbkdf_type_params(const char *pbkdf_type) +{ + if (!pbkdf_type) + return NULL; + + if (!strcmp(pbkdf_type, CRYPT_KDF_PBKDF2)) + return &default_pbkdf2; + else if (!strcmp(pbkdf_type, CRYPT_KDF_ARGON2I)) + return &default_argon2i; + else if (!strcmp(pbkdf_type, CRYPT_KDF_ARGON2ID)) + return &default_argon2id; + + return NULL; +} + +static uint32_t adjusted_phys_memory(void) +{ + uint64_t memory_kb = crypt_getphysmemory_kb(); + + /* Ignore bogus value */ + if (memory_kb < (128 * 1024) || memory_kb > UINT32_MAX) + return DEFAULT_LUKS2_MEMORY_KB; + + /* + * Never use more than half of physical memory. + * OOM killer is too clever... + */ + memory_kb /= 2; + + return memory_kb; +} + +/* + * PBKDF configuration interface + */ +int verify_pbkdf_params(struct crypt_device *cd, + const struct crypt_pbkdf_type *pbkdf) +{ + struct crypt_pbkdf_limits pbkdf_limits; + const char *pbkdf_type; + int r; + + r = init_crypto(cd); + if (r < 0) + return r; + + if (!pbkdf || !pbkdf->type || + (!pbkdf->hash && !strcmp(pbkdf->type, "pbkdf2"))) + return -EINVAL; + + if (!pbkdf->time_ms && !(pbkdf->flags & CRYPT_PBKDF_NO_BENCHMARK)) { + log_err(cd, _("Requested PBKDF target time cannot be zero.")); + return -EINVAL; + } + + r = crypt_parse_pbkdf(pbkdf->type, &pbkdf_type); + if (r < 0) { + log_err(cd, _("Unknown PBKDF type %s."), pbkdf->type); + return r; + } + + if (pbkdf->hash && crypt_hash_size(pbkdf->hash) < 0) { + log_err(cd, _("Requested hash %s is not supported."), pbkdf->hash); + return -EINVAL; + } + + r = crypt_pbkdf_get_limits(pbkdf->type, &pbkdf_limits); + if (r < 0) + return r; + + if (crypt_get_type(cd) && + !strcmp(crypt_get_type(cd), CRYPT_LUKS1) && + strcmp(pbkdf_type, CRYPT_KDF_PBKDF2)) { + log_err(cd, _("Requested PBKDF type is not supported for LUKS1.")); + return -EINVAL; + } + + if (!strcmp(pbkdf_type, CRYPT_KDF_PBKDF2)) { + if (pbkdf->max_memory_kb || pbkdf->parallel_threads) { + log_err(cd, _("PBKDF max memory or parallel threads must not be set with pbkdf2.")); + return -EINVAL; + } + if (pbkdf->flags & CRYPT_PBKDF_NO_BENCHMARK && + pbkdf->iterations < pbkdf_limits.min_iterations) { + log_err(cd, _("Forced iteration count is too low for %s (minimum is %u)."), + pbkdf_type, pbkdf_limits.min_iterations); + return -EINVAL; + } + return 0; + } + + /* TODO: properly define minimal iterations and also minimal memory values */ + if (pbkdf->flags & CRYPT_PBKDF_NO_BENCHMARK) { + if (pbkdf->iterations < pbkdf_limits.min_iterations) { + log_err(cd, _("Forced iteration count is too low for %s (minimum is %u)."), + pbkdf_type, pbkdf_limits.min_iterations); + r = -EINVAL; + } + if (pbkdf->max_memory_kb < pbkdf_limits.min_memory) { + log_err(cd, _("Forced memory cost is too low for %s (minimum is %u kilobytes)."), + pbkdf_type, pbkdf_limits.min_memory); + r = -EINVAL; + } + } + + if (pbkdf->max_memory_kb > pbkdf_limits.max_memory) { + log_err(cd, _("Requested maximum PBKDF memory cost is too high (maximum is %d kilobytes)."), + pbkdf_limits.max_memory); + r = -EINVAL; + } + if (!pbkdf->max_memory_kb) { + log_err(cd, _("Requested maximum PBKDF memory cannot be zero.")); + r = -EINVAL; + } + if (!pbkdf->parallel_threads) { + log_err(cd, _("Requested PBKDF parallel threads cannot be zero.")); + r = -EINVAL; + } + + return r; +} + +int init_pbkdf_type(struct crypt_device *cd, + const struct crypt_pbkdf_type *pbkdf, + const char *dev_type) +{ + struct crypt_pbkdf_type *cd_pbkdf = crypt_get_pbkdf(cd); + struct crypt_pbkdf_limits pbkdf_limits; + const char *hash, *type; + unsigned cpus; + uint32_t old_flags, memory_kb; + int r; + + if (crypt_fips_mode()) { + if (pbkdf && strcmp(pbkdf->type, CRYPT_KDF_PBKDF2)) { + log_err(cd, _("Only PBKDF2 is supported in FIPS mode.")); + return -EINVAL; + } + if (!pbkdf) + pbkdf = crypt_get_pbkdf_type_params(CRYPT_KDF_PBKDF2); + } + + if (!pbkdf && dev_type && !strcmp(dev_type, CRYPT_LUKS2)) + pbkdf = crypt_get_pbkdf_type_params(DEFAULT_LUKS2_PBKDF); + else if (!pbkdf) + pbkdf = crypt_get_pbkdf_type_params(CRYPT_KDF_PBKDF2); + + r = verify_pbkdf_params(cd, pbkdf); + if (r) + return r; + + r = crypt_pbkdf_get_limits(pbkdf->type, &pbkdf_limits); + if (r < 0) + return r; + + type = strdup(pbkdf->type); + hash = pbkdf->hash ? strdup(pbkdf->hash) : NULL; + + if (!type || (!hash && pbkdf->hash)) { + free(CONST_CAST(void*)type); + free(CONST_CAST(void*)hash); + return -ENOMEM; + } + + free(CONST_CAST(void*)cd_pbkdf->type); + free(CONST_CAST(void*)cd_pbkdf->hash); + cd_pbkdf->type = type; + cd_pbkdf->hash = hash; + + old_flags = cd_pbkdf->flags; + cd_pbkdf->flags = pbkdf->flags; + + /* Reset iteration count so benchmark must run again. */ + if (cd_pbkdf->flags & CRYPT_PBKDF_NO_BENCHMARK) + cd_pbkdf->iterations = pbkdf->iterations; + else + cd_pbkdf->iterations = 0; + + if (old_flags & CRYPT_PBKDF_ITER_TIME_SET) + cd_pbkdf->flags |= CRYPT_PBKDF_ITER_TIME_SET; + else + cd_pbkdf->time_ms = pbkdf->time_ms; + + cd_pbkdf->max_memory_kb = pbkdf->max_memory_kb; + cd_pbkdf->parallel_threads = pbkdf->parallel_threads; + + if (cd_pbkdf->parallel_threads > pbkdf_limits.max_parallel) { + log_dbg(cd, "Maximum PBKDF threads is %d (requested %d).", + pbkdf_limits.max_parallel, cd_pbkdf->parallel_threads); + cd_pbkdf->parallel_threads = pbkdf_limits.max_parallel; + } + + if (cd_pbkdf->parallel_threads) { + cpus = crypt_cpusonline(); + if (cd_pbkdf->parallel_threads > cpus) { + log_dbg(cd, "Only %u active CPUs detected, " + "PBKDF threads decreased from %d to %d.", + cpus, cd_pbkdf->parallel_threads, cpus); + cd_pbkdf->parallel_threads = cpus; + } + } + + if (cd_pbkdf->max_memory_kb) { + memory_kb = adjusted_phys_memory(); + if (cd_pbkdf->max_memory_kb > memory_kb) { + log_dbg(cd, "Not enough physical memory detected, " + "PBKDF max memory decreased from %dkB to %dkB.", + cd_pbkdf->max_memory_kb, memory_kb); + cd_pbkdf->max_memory_kb = memory_kb; + } + } + + if (!strcmp(pbkdf->type, CRYPT_KDF_PBKDF2)) + log_dbg(cd, "PBKDF %s-%s, time_ms %u (iterations %u).", + cd_pbkdf->type, cd_pbkdf->hash, cd_pbkdf->time_ms, cd_pbkdf->iterations); + else + log_dbg(cd, "PBKDF %s, time_ms %u (iterations %u), max_memory_kb %u, parallel_threads %u.", + cd_pbkdf->type, cd_pbkdf->time_ms, cd_pbkdf->iterations, + cd_pbkdf->max_memory_kb, cd_pbkdf->parallel_threads); + + return 0; +} + +/* Libcryptsetup API */ + +int crypt_set_pbkdf_type(struct crypt_device *cd, const struct crypt_pbkdf_type *pbkdf) +{ + if (!cd) + return -EINVAL; + + if (!pbkdf) + log_dbg(cd, "Resetting pbkdf type to default"); + + crypt_get_pbkdf(cd)->flags = 0; + + return init_pbkdf_type(cd, pbkdf, crypt_get_type(cd)); +} + +const struct crypt_pbkdf_type *crypt_get_pbkdf_type(struct crypt_device *cd) +{ + if (!cd) + return NULL; + + return crypt_get_pbkdf(cd)->type ? crypt_get_pbkdf(cd) : NULL; +} + +const struct crypt_pbkdf_type *crypt_get_pbkdf_default(const char *type) +{ + if (!type) + return NULL; + + if (!strcmp(type, CRYPT_LUKS1) || crypt_fips_mode()) + return crypt_get_pbkdf_type_params(CRYPT_KDF_PBKDF2); + else if (!strcmp(type, CRYPT_LUKS2)) + return crypt_get_pbkdf_type_params(DEFAULT_LUKS2_PBKDF); + + return NULL; +} + +void crypt_set_iteration_time(struct crypt_device *cd, uint64_t iteration_time_ms) +{ + struct crypt_pbkdf_type *pbkdf; + uint32_t old_time_ms; + + if (!cd || iteration_time_ms > UINT32_MAX) + return; + + pbkdf = crypt_get_pbkdf(cd); + old_time_ms = pbkdf->time_ms; + pbkdf->time_ms = (uint32_t)iteration_time_ms; + + if (pbkdf->type && verify_pbkdf_params(cd, pbkdf)) { + pbkdf->time_ms = old_time_ms; + log_dbg(cd, "Invalid iteration time."); + return; + } + + pbkdf->flags |= CRYPT_PBKDF_ITER_TIME_SET; + + /* iterations must be benchmarked now */ + pbkdf->flags &= ~(CRYPT_PBKDF_NO_BENCHMARK); + pbkdf->iterations = 0; + + log_dbg(cd, "Iteration time set to %" PRIu64 " milliseconds.", iteration_time_ms); +} |