From 8909d83a3ed226e4a7c962261217cb2c14ff2ec9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 10:35:42 +0200 Subject: Merging upstream version 2:2.7.0. Signed-off-by: Daniel Baumann --- lib/Makemodule.am | 2 + lib/bitlk/bitlk.c | 17 +- lib/bitlk/bitlk.h | 6 +- lib/crypt_plain.c | 4 +- lib/crypto_backend/argon2/meson.build | 28 + lib/crypto_backend/argon2_generic.c | 39 +- lib/crypto_backend/base64.c | 2 +- lib/crypto_backend/cipher_check.c | 4 +- lib/crypto_backend/cipher_generic.c | 5 +- lib/crypto_backend/crc32.c | 4 +- lib/crypto_backend/crypto_backend.h | 6 +- lib/crypto_backend/crypto_backend_internal.h | 4 +- lib/crypto_backend/crypto_cipher_kernel.c | 7 +- lib/crypto_backend/crypto_gcrypt.c | 150 +- lib/crypto_backend/crypto_kernel.c | 6 +- lib/crypto_backend/crypto_nettle.c | 4 +- lib/crypto_backend/crypto_nss.c | 4 +- lib/crypto_backend/crypto_openssl.c | 90 +- lib/crypto_backend/crypto_storage.c | 2 +- lib/crypto_backend/meson.build | 40 + lib/crypto_backend/pbkdf2_generic.c | 4 +- lib/crypto_backend/pbkdf_check.c | 4 +- lib/crypto_backend/utf8.c | 2 +- lib/integrity/integrity.c | 121 +- lib/integrity/integrity.h | 5 +- lib/internal.h | 31 +- lib/keyslot_context.c | 364 +++- lib/keyslot_context.h | 52 +- lib/libcryptsetup.h | 288 ++- lib/libcryptsetup.sym | 15 + lib/libcryptsetup_macros.h | 4 +- lib/libcryptsetup_symver.h | 2 +- lib/libdevmapper.c | 39 +- lib/loopaes/loopaes.c | 4 +- lib/loopaes/loopaes.h | 4 +- lib/luks1/af.c | 2 +- lib/luks1/af.h | 2 +- lib/luks1/keyencryption.c | 4 +- lib/luks1/keymanage.c | 4 +- lib/luks1/luks.h | 2 +- lib/luks2/hw_opal/hw_opal.c | 1089 +++++++++++ lib/luks2/hw_opal/hw_opal.h | 71 + lib/luks2/luks2.h | 46 +- lib/luks2/luks2_digest.c | 18 +- lib/luks2/luks2_digest_pbkdf2.c | 16 +- lib/luks2/luks2_disk_metadata.c | 25 +- lib/luks2/luks2_internal.h | 37 +- lib/luks2/luks2_json_format.c | 227 ++- lib/luks2/luks2_json_metadata.c | 303 ++- lib/luks2/luks2_keyslot.c | 35 +- lib/luks2/luks2_keyslot_luks2.c | 40 +- lib/luks2/luks2_keyslot_reenc.c | 21 +- lib/luks2/luks2_luks1_convert.c | 43 +- lib/luks2/luks2_reencrypt.c | 428 +++-- lib/luks2/luks2_reencrypt_digest.c | 22 +- lib/luks2/luks2_segment.c | 244 ++- lib/luks2/luks2_token.c | 295 ++- lib/luks2/luks2_token_keyring.c | 13 +- lib/meson.build | 116 ++ lib/random.c | 2 +- lib/setup.c | 2621 ++++++++++++++++++++------ lib/tcrypt/tcrypt.c | 8 +- lib/tcrypt/tcrypt.h | 4 +- lib/utils.c | 69 +- lib/utils_benchmark.c | 13 +- lib/utils_blkid.c | 170 +- lib/utils_blkid.h | 2 +- lib/utils_crypt.c | 21 +- lib/utils_crypt.h | 7 +- lib/utils_device.c | 32 +- lib/utils_device_locking.c | 30 +- lib/utils_device_locking.h | 5 +- lib/utils_devpath.c | 33 +- lib/utils_dm.h | 7 +- lib/utils_io.c | 4 +- lib/utils_io.h | 4 +- lib/utils_keyring.c | 433 ++++- lib/utils_keyring.h | 39 +- lib/utils_loop.c | 6 +- lib/utils_loop.h | 4 +- lib/utils_pbkdf.c | 32 +- lib/utils_safe_memory.c | 4 +- lib/utils_storage_wrappers.c | 2 +- lib/utils_storage_wrappers.h | 2 +- lib/utils_wipe.c | 76 +- lib/verity/rs.h | 2 +- lib/verity/rs_decode_char.c | 2 +- lib/verity/rs_encode_char.c | 2 +- lib/verity/verity.c | 140 +- lib/verity/verity.h | 15 +- lib/verity/verity_fec.c | 2 +- lib/verity/verity_hash.c | 2 +- lib/volumekey.c | 4 +- 93 files changed, 6773 insertions(+), 1492 deletions(-) create mode 100644 lib/crypto_backend/argon2/meson.build create mode 100644 lib/crypto_backend/meson.build create mode 100644 lib/luks2/hw_opal/hw_opal.c create mode 100644 lib/luks2/hw_opal/hw_opal.h create mode 100644 lib/meson.build (limited to 'lib') diff --git a/lib/Makemodule.am b/lib/Makemodule.am index 2e60a90..ae5fab9 100644 --- a/lib/Makemodule.am +++ b/lib/Makemodule.am @@ -103,6 +103,8 @@ libcryptsetup_la_SOURCES = \ lib/luks2/luks2_token.c \ lib/luks2/luks2_internal.h \ lib/luks2/luks2.h \ + lib/luks2/hw_opal/hw_opal.c \ + lib/luks2/hw_opal/hw_opal.h \ lib/utils_blkid.c \ lib/utils_blkid.h \ lib/bitlk/bitlk.h \ diff --git a/lib/bitlk/bitlk.c b/lib/bitlk/bitlk.c index de7bcea..ae533e5 100644 --- a/lib/bitlk/bitlk.c +++ b/lib/bitlk/bitlk.c @@ -1,9 +1,9 @@ /* * BITLK (BitLocker-compatible) volume handling * - * Copyright (C) 2019-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2019-2023 Milan Broz - * Copyright (C) 2019-2023 Vojtech Trefny + * Copyright (C) 2019-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2019-2024 Milan Broz + * Copyright (C) 2019-2024 Vojtech Trefny * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -735,6 +735,7 @@ int BITLK_dump(struct crypt_device *cd, struct device *device, struct bitlk_meta { struct volume_key *vk_p; struct bitlk_vmk *vmk_p; + char time[32]; int next_id = 0; int i = 0; @@ -743,7 +744,8 @@ int BITLK_dump(struct crypt_device *cd, struct device *device, struct bitlk_meta log_std(cd, "GUID: \t%s\n", params->guid); log_std(cd, "Sector size: \t%u [bytes]\n", params->sector_size); log_std(cd, "Volume size: \t%" PRIu64 " [bytes]\n", params->volume_size); - log_std(cd, "Created: \t%s", ctime((time_t *)&(params->creation_time))); + if (ctime_r((time_t *)¶ms->creation_time, time)) + log_std(cd, "Created: \t%s", time); log_std(cd, "Description: \t%s\n", params->description); log_std(cd, "Cipher name: \t%s\n", params->cipher); log_std(cd, "Cipher mode: \t%s\n", params->cipher_mode); @@ -982,8 +984,7 @@ static int get_startup_key(struct crypt_device *cd, } } -static int bitlk_kdf(struct crypt_device *cd, - const char *password, +static int bitlk_kdf(const char *password, size_t passwordLen, bool recovery, const uint8_t *salt, @@ -1120,7 +1121,7 @@ int BITLK_get_volume_key(struct crypt_device *cd, next_vmk = params->vmks; while (next_vmk) { if (next_vmk->protection == BITLK_PROTECTION_PASSPHRASE) { - r = bitlk_kdf(cd, password, passwordLen, false, next_vmk->salt, &vmk_dec_key); + r = bitlk_kdf(password, passwordLen, false, next_vmk->salt, &vmk_dec_key); if (r) { /* something wrong happened, but we still want to check other key slots */ next_vmk = next_vmk->next; @@ -1140,7 +1141,7 @@ int BITLK_get_volume_key(struct crypt_device *cd, continue; } log_dbg(cd, "Trying to use given password as a recovery key."); - r = bitlk_kdf(cd, recovery_key->key, recovery_key->keylength, + r = bitlk_kdf(recovery_key->key, recovery_key->keylength, true, next_vmk->salt, &vmk_dec_key); crypt_free_volume_key(recovery_key); if (r) diff --git a/lib/bitlk/bitlk.h b/lib/bitlk/bitlk.h index 54d3dc7..7eb7321 100644 --- a/lib/bitlk/bitlk.h +++ b/lib/bitlk/bitlk.h @@ -1,9 +1,9 @@ /* * BITLK (BitLocker-compatible) header definition * - * Copyright (C) 2019-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2019-2023 Milan Broz - * Copyright (C) 2019-2023 Vojtech Trefny + * Copyright (C) 2019-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2019-2024 Milan Broz + * Copyright (C) 2019-2024 Vojtech Trefny * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/lib/crypt_plain.c b/lib/crypt_plain.c index c839b09..99155e8 100644 --- a/lib/crypt_plain.c +++ b/lib/crypt_plain.c @@ -2,8 +2,8 @@ * cryptsetup plain device helper functions * * Copyright (C) 2004 Jana Saout - * Copyright (C) 2010-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2010-2023 Milan Broz + * Copyright (C) 2010-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2010-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/lib/crypto_backend/argon2/meson.build b/lib/crypto_backend/argon2/meson.build new file mode 100644 index 0000000..bb68516 --- /dev/null +++ b/lib/crypto_backend/argon2/meson.build @@ -0,0 +1,28 @@ +libargon2_sources = files( + 'blake2/blake2b.c', + 'argon2.c', + 'core.c', + 'encoding.c', + 'thread.c', +) + +if use_internal_sse_argon2 + libargon2_sources += files( + 'opt.c', + ) +else + libargon2_sources += files( + 'ref.c', + ) +endif + +libargon2 = static_library('argon2', + libargon2_sources, + override_options : ['c_std=c89', 'optimization=3'], + build_by_default : false, + include_directories: include_directories( + 'blake2', + ), + dependencies : [ + threads, + ]) diff --git a/lib/crypto_backend/argon2_generic.c b/lib/crypto_backend/argon2_generic.c index 0ce67da..eca575b 100644 --- a/lib/crypto_backend/argon2_generic.c +++ b/lib/crypto_backend/argon2_generic.c @@ -1,8 +1,8 @@ /* * Argon2 PBKDF2 library wrapper * - * Copyright (C) 2016-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2016-2023 Milan Broz + * Copyright (C) 2016-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2016-2024 Milan Broz * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -29,14 +29,12 @@ #define CONST_CAST(x) (x)(uintptr_t) +#if USE_INTERNAL_ARGON2 || HAVE_ARGON2_H int argon2(const char *type, const char *password, size_t password_length, const char *salt, size_t salt_length, char *key, size_t key_length, uint32_t iterations, uint32_t memory, uint32_t parallel) { -#if !USE_INTERNAL_ARGON2 && !HAVE_ARGON2_H - return -EINVAL; -#else argon2_type atype; argon2_context context = { .flags = ARGON2_DEFAULT_FLAGS, @@ -54,6 +52,9 @@ int argon2(const char *type, const char *password, size_t password_length, }; int r; + /* This code must not be run if crypt backend library natively supports Argon2 */ + assert(!(crypt_backend_flags() & CRYPT_BACKEND_ARGON2)); + if (!strcmp(type, "argon2i")) atype = Argon2_i; else if(!strcmp(type, "argon2id")) @@ -75,5 +76,33 @@ int argon2(const char *type, const char *password, size_t password_length, } return r; +} + +#else /* USE_INTERNAL_ARGON2 || HAVE_ARGON2_H */ +#pragma GCC diagnostic ignored "-Wunused-parameter" + +int argon2(const char *type, const char *password, size_t password_length, + const char *salt, size_t salt_length, + char *key, size_t key_length, + uint32_t iterations, uint32_t memory, uint32_t parallel) +{ + return -EINVAL; +} + +#endif + +/* Additional string for crypt backend version */ +const char *crypt_argon2_version(void) +{ + const char *version = ""; + + if (crypt_backend_flags() & CRYPT_BACKEND_ARGON2) + return version; + +#if HAVE_ARGON2_H /* this has priority over internal argon2 */ + version = " [external libargon2]"; +#elif USE_INTERNAL_ARGON2 + version = " [cryptsetup libargon2]"; #endif + return version; } diff --git a/lib/crypto_backend/base64.c b/lib/crypto_backend/base64.c index 42f70cb..92e558a 100644 --- a/lib/crypto_backend/base64.c +++ b/lib/crypto_backend/base64.c @@ -4,7 +4,7 @@ * Copyright (C) 2010 Lennart Poettering * * cryptsetup related changes - * Copyright (C) 2021-2023 Milan Broz + * Copyright (C) 2021-2024 Milan Broz * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/lib/crypto_backend/cipher_check.c b/lib/crypto_backend/cipher_check.c index 98ec1a5..25200a4 100644 --- a/lib/crypto_backend/cipher_check.c +++ b/lib/crypto_backend/cipher_check.c @@ -1,8 +1,8 @@ /* * Cipher performance check * - * Copyright (C) 2018-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2018-2023 Milan Broz + * Copyright (C) 2018-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2018-2024 Milan Broz * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/lib/crypto_backend/cipher_generic.c b/lib/crypto_backend/cipher_generic.c index b3a4407..746cfcf 100644 --- a/lib/crypto_backend/cipher_generic.c +++ b/lib/crypto_backend/cipher_generic.c @@ -1,8 +1,8 @@ /* * Linux kernel cipher generic utilities * - * Copyright (C) 2018-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2018-2023 Milan Broz + * Copyright (C) 2018-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2018-2024 Milan Broz * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -51,6 +51,7 @@ static const struct cipher_alg cipher_algs[] = { { "xchacha12,aes", "adiantum", 32, false }, { "xchacha20,aes", "adiantum", 32, false }, { "sm4", NULL, 16, false }, + { "aria", NULL, 16, false }, { NULL, NULL, 0, false } }; diff --git a/lib/crypto_backend/crc32.c b/lib/crypto_backend/crc32.c index 9009b02..7a12a8e 100644 --- a/lib/crypto_backend/crc32.c +++ b/lib/crypto_backend/crc32.c @@ -158,7 +158,7 @@ static const uint32_t crc32c_tab[] = { * whatever they need. */ static uint32_t compute_crc32( - const uint32_t *crc32_tab, + const uint32_t *crc32_table, uint32_t seed, const unsigned char *buf, size_t len) @@ -167,7 +167,7 @@ static uint32_t compute_crc32( const unsigned char *p = buf; while(len-- > 0) - crc = crc32_tab[(crc ^ *p++) & 0xff] ^ (crc >> 8); + crc = crc32_table[(crc ^ *p++) & 0xff] ^ (crc >> 8); return crc; } diff --git a/lib/crypto_backend/crypto_backend.h b/lib/crypto_backend/crypto_backend.h index 88562e9..15ed745 100644 --- a/lib/crypto_backend/crypto_backend.h +++ b/lib/crypto_backend/crypto_backend.h @@ -1,8 +1,8 @@ /* * crypto backend implementation * - * Copyright (C) 2010-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2010-2023 Milan Broz + * Copyright (C) 2010-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2010-2024 Milan Broz * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -43,9 +43,11 @@ void crypt_backend_destroy(void); #define CRYPT_BACKEND_KERNEL (1 << 0) /* Crypto uses kernel part, for benchmark */ #define CRYPT_BACKEND_PBKDF2_INT (1 << 1) /* Iteration in PBKDF2 is signed int and can overflow */ +#define CRYPT_BACKEND_ARGON2 (1 << 2) /* Backend provides native Argon2 implementation */ uint32_t crypt_backend_flags(void); const char *crypt_backend_version(void); +const char *crypt_argon2_version(void); /* HASH */ int crypt_hash_size(const char *name); diff --git a/lib/crypto_backend/crypto_backend_internal.h b/lib/crypto_backend/crypto_backend_internal.h index 9b1cc69..539f11a 100644 --- a/lib/crypto_backend/crypto_backend_internal.h +++ b/lib/crypto_backend/crypto_backend_internal.h @@ -1,8 +1,8 @@ /* * crypto backend implementation * - * Copyright (C) 2010-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2010-2023 Milan Broz + * Copyright (C) 2010-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2010-2024 Milan Broz * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/lib/crypto_backend/crypto_cipher_kernel.c b/lib/crypto_backend/crypto_cipher_kernel.c index 3460717..77b3643 100644 --- a/lib/crypto_backend/crypto_cipher_kernel.c +++ b/lib/crypto_backend/crypto_cipher_kernel.c @@ -1,8 +1,8 @@ /* * Linux kernel userspace API crypto backend implementation (skcipher) * - * Copyright (C) 2012-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2012-2023 Milan Broz + * Copyright (C) 2012-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2012-2024 Milan Broz * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -109,6 +109,7 @@ int crypt_cipher_init_kernel(struct crypt_cipher_kernel *ctx, const char *name, } /* The in/out should be aligned to page boundary */ +/* coverity[ -taint_source : arg-3 ] */ static int _crypt_cipher_crypt(struct crypt_cipher_kernel *ctx, const char *in, size_t in_length, char *out, size_t out_length, @@ -312,6 +313,8 @@ int crypt_bitlk_decrypt_key_kernel(const void *key, size_t key_length, } #else /* ENABLE_AF_ALG */ +#pragma GCC diagnostic ignored "-Wunused-parameter" + int crypt_cipher_init_kernel(struct crypt_cipher_kernel *ctx, const char *name, const char *mode, const void *key, size_t key_length) { diff --git a/lib/crypto_backend/crypto_gcrypt.c b/lib/crypto_backend/crypto_gcrypt.c index e974aa8..8e3f14e 100644 --- a/lib/crypto_backend/crypto_gcrypt.c +++ b/lib/crypto_backend/crypto_gcrypt.c @@ -1,8 +1,8 @@ /* * GCRYPT crypto backend implementation * - * Copyright (C) 2010-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2010-2023 Milan Broz + * Copyright (C) 2010-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2010-2024 Milan Broz * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -23,6 +23,7 @@ #include #include #include +#include #include "crypto_backend_internal.h" static int crypto_backend_initialised = 0; @@ -126,10 +127,11 @@ int crypt_backend_init(bool fips __attribute__((unused))) crypto_backend_initialised = 1; crypt_hash_test_whirlpool_bug(); - r = snprintf(version, sizeof(version), "gcrypt %s%s%s", + r = snprintf(version, sizeof(version), "gcrypt %s%s%s%s", gcry_check_version(NULL), crypto_backend_secmem ? "" : ", secmem disabled", - crypto_backend_whirlpool_bug > 0 ? ", flawed whirlpool" : ""); + crypto_backend_whirlpool_bug > 0 ? ", flawed whirlpool" : "", + crypt_backend_flags() & CRYPT_BACKEND_ARGON2 ? ", argon2" : ""); if (r < 0 || (size_t)r >= sizeof(version)) return -EINVAL; @@ -151,7 +153,11 @@ const char *crypt_backend_version(void) uint32_t crypt_backend_flags(void) { - return 0; + uint32_t flags = 0; +#if HAVE_DECL_GCRY_KDF_ARGON2 && !USE_INTERNAL_ARGON2 + flags |= CRYPT_BACKEND_ARGON2; +#endif + return flags; } static const char *crypt_hash_compat_name(const char *name, unsigned int *flags) @@ -266,7 +272,6 @@ int crypt_hash_final(struct crypt_hash *ctx, char *buffer, size_t length) void crypt_hash_destroy(struct crypt_hash *ctx) { gcry_md_close(ctx->hd); - memset(ctx, 0, sizeof(*ctx)); free(ctx); } @@ -341,7 +346,6 @@ int crypt_hmac_final(struct crypt_hmac *ctx, char *buffer, size_t length) void crypt_hmac_destroy(struct crypt_hmac *ctx) { gcry_md_close(ctx->hd); - memset(ctx, 0, sizeof(*ctx)); free(ctx); } @@ -386,6 +390,130 @@ static int pbkdf2(const char *hash, #endif /* USE_INTERNAL_PBKDF2 */ } +#if HAVE_DECL_GCRY_KDF_ARGON2 && !USE_INTERNAL_ARGON2 +struct gcrypt_thread_job +{ + pthread_t thread; + struct job_thread_param { + gcry_kdf_job_fn_t job; + void *p; + } work; +}; + +struct gcrypt_threads +{ + pthread_attr_t attr; + unsigned int num_threads; + unsigned int max_threads; + struct gcrypt_thread_job *jobs_ctx; +}; + +static void *gcrypt_job_thread(void *p) +{ + struct job_thread_param *param = p; + param->job(param->p); + pthread_exit(NULL); +} + +static int gcrypt_wait_all_jobs(void *ctx) +{ + unsigned int i; + struct gcrypt_threads *threads = ctx; + + for (i = 0; i < threads->num_threads; i++) { + pthread_join(threads->jobs_ctx[i].thread, NULL); + threads->jobs_ctx[i].thread = 0; + } + + threads->num_threads = 0; + return 0; +} + +static int gcrypt_dispatch_job(void *ctx, gcry_kdf_job_fn_t job, void *p) +{ + struct gcrypt_threads *threads = ctx; + + if (threads->num_threads >= threads->max_threads) + return -1; + + threads->jobs_ctx[threads->num_threads].work.job = job; + threads->jobs_ctx[threads->num_threads].work.p = p; + + if (pthread_create(&threads->jobs_ctx[threads->num_threads].thread, &threads->attr, + gcrypt_job_thread, &threads->jobs_ctx[threads->num_threads].work)) + return -1; + + threads->num_threads++; + return 0; +} + +static int gcrypt_argon2(const char *type, + const char *password, size_t password_length, + const char *salt, size_t salt_length, + char *key, size_t key_length, + uint32_t iterations, uint32_t memory, uint32_t parallel) +{ + gcry_kdf_hd_t hd; + int atype, r = -EINVAL; + unsigned long param[4]; + struct gcrypt_threads threads = { + .max_threads = parallel, + .num_threads = 0 + }; + const gcry_kdf_thread_ops_t ops = { + .jobs_context = &threads, + .dispatch_job = gcrypt_dispatch_job, + .wait_all_jobs = gcrypt_wait_all_jobs + }; + + if (!strcmp(type, "argon2i")) + atype = GCRY_KDF_ARGON2I; + else if (!strcmp(type, "argon2id")) + atype = GCRY_KDF_ARGON2ID; + else + return -EINVAL; + + param[0] = key_length; + param[1] = iterations; + param[2] = memory; + param[3] = parallel; + + if (gcry_kdf_open(&hd, GCRY_KDF_ARGON2, atype, param, 4, + password, password_length, salt, salt_length, + NULL, 0, NULL, 0)) { + free(threads.jobs_ctx); + return -EINVAL; + } + + if (parallel == 1) { + /* Do not use threads here */ + if (gcry_kdf_compute(hd, NULL)) + goto out; + } else { + threads.jobs_ctx = calloc(threads.max_threads, + sizeof(struct gcrypt_thread_job)); + if (!threads.jobs_ctx) + goto out; + + if (pthread_attr_init(&threads.attr)) + goto out; + + if (gcry_kdf_compute(hd, &ops)) + goto out; + } + + if (gcry_kdf_final(hd, key_length, key)) + goto out; + r = 0; +out: + gcry_kdf_close(hd); + pthread_attr_destroy(&threads.attr); + free(threads.jobs_ctx); + + return r; +} +#endif + /* PBKDF */ int crypt_pbkdf(const char *kdf, const char *hash, const char *password, size_t password_length, @@ -400,8 +528,13 @@ int crypt_pbkdf(const char *kdf, const char *hash, return pbkdf2(hash, password, password_length, salt, salt_length, key, key_length, iterations); else if (!strncmp(kdf, "argon2", 6)) +#if HAVE_DECL_GCRY_KDF_ARGON2 && !USE_INTERNAL_ARGON2 + return gcrypt_argon2(kdf, password, password_length, salt, salt_length, + key, key_length, iterations, memory, parallel); +#else return argon2(kdf, password, password_length, salt, salt_length, key, key_length, iterations, memory, parallel); +#endif return -EINVAL; } @@ -565,6 +698,9 @@ bool crypt_fips_mode(void) if (fips_checked) return fips_mode; + if (crypt_backend_init(false /* ignored */)) + return false; + fips_mode = gcry_fips_mode_active(); fips_checked = true; diff --git a/lib/crypto_backend/crypto_kernel.c b/lib/crypto_backend/crypto_kernel.c index 8493c0a..be6051a 100644 --- a/lib/crypto_backend/crypto_kernel.c +++ b/lib/crypto_backend/crypto_kernel.c @@ -1,8 +1,8 @@ /* * Linux kernel userspace API crypto backend implementation * - * Copyright (C) 2010-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2010-2023 Milan Broz + * Copyright (C) 2010-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2010-2024 Milan Broz * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -245,7 +245,6 @@ void crypt_hash_destroy(struct crypt_hash *ctx) close(ctx->tfmfd); if (ctx->opfd >= 0) close(ctx->opfd); - memset(ctx, 0, sizeof(*ctx)); free(ctx); } @@ -324,7 +323,6 @@ void crypt_hmac_destroy(struct crypt_hmac *ctx) close(ctx->tfmfd); if (ctx->opfd >= 0) close(ctx->opfd); - memset(ctx, 0, sizeof(*ctx)); free(ctx); } diff --git a/lib/crypto_backend/crypto_nettle.c b/lib/crypto_backend/crypto_nettle.c index 086e4fc..f08db74 100644 --- a/lib/crypto_backend/crypto_nettle.c +++ b/lib/crypto_backend/crypto_nettle.c @@ -1,8 +1,8 @@ /* * Nettle crypto backend implementation * - * Copyright (C) 2011-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2011-2023 Milan Broz + * Copyright (C) 2011-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2011-2024 Milan Broz * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/lib/crypto_backend/crypto_nss.c b/lib/crypto_backend/crypto_nss.c index c154812..6b390a4 100644 --- a/lib/crypto_backend/crypto_nss.c +++ b/lib/crypto_backend/crypto_nss.c @@ -1,8 +1,8 @@ /* * NSS crypto backend implementation * - * Copyright (C) 2010-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2010-2023 Milan Broz + * Copyright (C) 2010-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2010-2024 Milan Broz * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/lib/crypto_backend/crypto_openssl.c b/lib/crypto_backend/crypto_openssl.c index 607ec38..4e85384 100644 --- a/lib/crypto_backend/crypto_openssl.c +++ b/lib/crypto_backend/crypto_openssl.c @@ -1,8 +1,8 @@ /* * OPENSSL crypto backend implementation * - * Copyright (C) 2010-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2010-2023 Milan Broz + * Copyright (C) 2010-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2010-2024 Milan Broz * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -44,9 +44,20 @@ static OSSL_PROVIDER *ossl_legacy = NULL; static OSSL_PROVIDER *ossl_default = NULL; static OSSL_LIB_CTX *ossl_ctx = NULL; static char backend_version[256] = "OpenSSL"; + +#define MAX_THREADS 8 +#if !HAVE_DECL_OSSL_GET_MAX_THREADS +static int OSSL_set_max_threads(OSSL_LIB_CTX *ctx __attribute__((unused)), + uint64_t max_threads __attribute__((unused))) { return 0; } +static uint64_t OSSL_get_max_threads(OSSL_LIB_CTX *ctx __attribute__((unused))) { return 0; } +#else +#include +#endif + #endif #define CONST_CAST(x) (x)(uintptr_t) +#define UNUSED(x) (void)(x) static int crypto_backend_initialised = 0; @@ -162,6 +173,7 @@ static int openssl_backend_init(bool fips) */ #if OPENSSL_VERSION_MAJOR >= 3 int r; + bool ossl_threads = false; /* * In FIPS mode we keep default OpenSSL context & global config @@ -181,16 +193,24 @@ static int openssl_backend_init(bool fips) ossl_legacy = OSSL_PROVIDER_try_load(ossl_ctx, "legacy", 0); } - r = snprintf(backend_version, sizeof(backend_version), "%s %s%s%s", + if (OSSL_set_max_threads(ossl_ctx, MAX_THREADS) == 1 && + OSSL_get_max_threads(ossl_ctx) == MAX_THREADS) + ossl_threads = true; + + r = snprintf(backend_version, sizeof(backend_version), "%s %s%s%s%s%s", OpenSSL_version(OPENSSL_VERSION), ossl_default ? "[default]" : "", ossl_legacy ? "[legacy]" : "", - fips ? "[fips]" : ""); + fips ? "[fips]" : "", + ossl_threads ? "[threads]" : "", + crypt_backend_flags() & CRYPT_BACKEND_ARGON2 ? "[argon2]" : ""); if (r < 0 || (size_t)r >= sizeof(backend_version)) { openssl_backend_exit(); return -EINVAL; } +#else + UNUSED(fips); #endif return 0; } @@ -232,11 +252,14 @@ void crypt_backend_destroy(void) uint32_t crypt_backend_flags(void) { -#if OPENSSL_VERSION_MAJOR >= 3 - return 0; -#else - return CRYPT_BACKEND_PBKDF2_INT; + uint32_t flags = 0; +#if OPENSSL_VERSION_MAJOR < 3 + flags |= CRYPT_BACKEND_PBKDF2_INT; +#endif +#if HAVE_DECL_OSSL_KDF_PARAM_ARGON2_VERSION + flags |= CRYPT_BACKEND_ARGON2; #endif + return flags; } const char *crypt_backend_version(void) @@ -281,6 +304,8 @@ static void hash_id_free(const EVP_MD *hash_id) { #if OPENSSL_VERSION_MAJOR >= 3 EVP_MD_free(CONST_CAST(EVP_MD*)hash_id); +#else + UNUSED(hash_id); #endif } @@ -297,6 +322,8 @@ static void cipher_type_free(const EVP_CIPHER *cipher_type) { #if OPENSSL_VERSION_MAJOR >= 3 EVP_CIPHER_free(CONST_CAST(EVP_CIPHER*)cipher_type); +#else + UNUSED(cipher_type); #endif } @@ -391,7 +418,6 @@ void crypt_hash_destroy(struct crypt_hash *ctx) { hash_id_free(ctx->hash_id); EVP_MD_CTX_free(ctx->md); - memset(ctx, 0, sizeof(*ctx)); free(ctx); } @@ -527,7 +553,6 @@ void crypt_hmac_destroy(struct crypt_hmac *ctx) hash_id_free(ctx->hash_id); HMAC_CTX_free(ctx->md); #endif - memset(ctx, 0, sizeof(*ctx)); free(ctx); } @@ -593,8 +618,53 @@ static int openssl_argon2(const char *type, const char *password, size_t passwor const char *salt, size_t salt_length, char *key, size_t key_length, uint32_t iterations, uint32_t memory, uint32_t parallel) { +#if HAVE_DECL_OSSL_KDF_PARAM_ARGON2_VERSION + EVP_KDF_CTX *ctx; + EVP_KDF *argon2; + unsigned int threads = parallel; + int r; + OSSL_PARAM params[] = { + OSSL_PARAM_octet_string(OSSL_KDF_PARAM_PASSWORD, + CONST_CAST(void*)password, password_length), + OSSL_PARAM_octet_string(OSSL_KDF_PARAM_SALT, + CONST_CAST(void*)salt, salt_length), + OSSL_PARAM_uint32(OSSL_KDF_PARAM_ITER, &iterations), + OSSL_PARAM_uint(OSSL_KDF_PARAM_THREADS, &threads), + OSSL_PARAM_uint32(OSSL_KDF_PARAM_ARGON2_LANES, ¶llel), + OSSL_PARAM_uint32(OSSL_KDF_PARAM_ARGON2_MEMCOST, &memory), + OSSL_PARAM_END + }; + + if (OSSL_get_max_threads(ossl_ctx) == 0) + threads = 1; + + argon2 = EVP_KDF_fetch(ossl_ctx, type, NULL); + if (!argon2) + return -EINVAL; + + ctx = EVP_KDF_CTX_new(argon2); + if (!ctx) { + EVP_KDF_free(argon2); + return -EINVAL;; + } + + if (EVP_KDF_CTX_set_params(ctx, params) != 1) { + EVP_KDF_CTX_free(ctx); + EVP_KDF_free(argon2); + return -EINVAL; + } + + r = EVP_KDF_derive(ctx, (unsigned char*)key, key_length, NULL /*params*/); + + EVP_KDF_CTX_free(ctx); + EVP_KDF_free(argon2); + + /* _derive() returns 0 or negative value on error, 1 on success */ + return r == 1 ? 0 : -EINVAL; +#else return argon2(type, password, password_length, salt, salt_length, key, key_length, iterations, memory, parallel); +#endif } /* PBKDF */ diff --git a/lib/crypto_backend/crypto_storage.c b/lib/crypto_backend/crypto_storage.c index 13479dd..6c8f991 100644 --- a/lib/crypto_backend/crypto_storage.c +++ b/lib/crypto_backend/crypto_storage.c @@ -2,7 +2,7 @@ * Generic wrapper for storage encryption modes and Initial Vectors * (reimplementation of some functions from Linux dm-crypt kernel) * - * Copyright (C) 2014-2023 Milan Broz + * Copyright (C) 2014-2024 Milan Broz * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/lib/crypto_backend/meson.build b/lib/crypto_backend/meson.build new file mode 100644 index 0000000..d6c31fd --- /dev/null +++ b/lib/crypto_backend/meson.build @@ -0,0 +1,40 @@ +if use_internal_argon2 + subdir('argon2') +endif + +libcrypto_backend_dependencies = [ + crypto_backend_library, + clock_gettime, +] +libcrypto_backend_link_with = [] + +libcrypto_backend_sources = files( + 'argon2_generic.c', + 'base64.c', + 'cipher_check.c', + 'cipher_generic.c', + 'crc32.c', + 'crypto_cipher_kernel.c', + 'crypto_storage.c', + 'pbkdf_check.c', + 'utf8.c', +) + +crypto_backend = get_option('crypto-backend') +libcrypto_backend_sources += files('crypto_@0@.c'.format(crypto_backend)) + +if use_internal_pbkdf2 + libcrypto_backend_sources += files('pbkdf2_generic.c') +endif + +if use_internal_argon2 and get_option('argon-implementation') == 'internal' + libcrypto_backend_link_with += libargon2 +elif get_option('argon-implementation') == 'libargon2' + libcrypto_backend_dependencies += libargon2_external +endif + +libcrypto_backend = static_library('crypto_backend', + libcrypto_backend_sources, + include_directories: includes_lib, + dependencies: libcrypto_backend_dependencies, + link_with: libcrypto_backend_link_with) diff --git a/lib/crypto_backend/pbkdf2_generic.c b/lib/crypto_backend/pbkdf2_generic.c index 9e87e19..f7fe5bc 100644 --- a/lib/crypto_backend/pbkdf2_generic.c +++ b/lib/crypto_backend/pbkdf2_generic.c @@ -4,8 +4,8 @@ * Copyright (C) 2004 Free Software Foundation * * cryptsetup related changes - * Copyright (C) 2012-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2012-2023 Milan Broz + * Copyright (C) 2012-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2012-2024 Milan Broz * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/lib/crypto_backend/pbkdf_check.c b/lib/crypto_backend/pbkdf_check.c index 53a2da9..54d6a34 100644 --- a/lib/crypto_backend/pbkdf_check.c +++ b/lib/crypto_backend/pbkdf_check.c @@ -1,7 +1,7 @@ /* * PBKDF performance check - * Copyright (C) 2012-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2012-2023 Milan Broz + * Copyright (C) 2012-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2012-2024 Milan Broz * Copyright (C) 2016-2020 Ondrej Mosnacek * * This file is free software; you can redistribute it and/or diff --git a/lib/crypto_backend/utf8.c b/lib/crypto_backend/utf8.c index 24e0d8d..c13e953 100644 --- a/lib/crypto_backend/utf8.c +++ b/lib/crypto_backend/utf8.c @@ -4,7 +4,7 @@ * Copyright (C) 2010 Lennart Poettering * * cryptsetup related changes - * Copyright (C) 2021-2023 Vojtech Trefny + * Copyright (C) 2021-2024 Vojtech Trefny * Parts of the original systemd implementation are based on the GLIB utf8 * validation functions. diff --git a/lib/integrity/integrity.c b/lib/integrity/integrity.c index aeadc82..ac2f0d0 100644 --- a/lib/integrity/integrity.c +++ b/lib/integrity/integrity.c @@ -1,7 +1,7 @@ /* * Integrity volume handling * - * Copyright (C) 2016-2023 Milan Broz + * Copyright (C) 2016-2024 Milan Broz * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -335,13 +335,62 @@ int INTEGRITY_activate(struct crypt_device *cd, return r; } +static int _create_reduced_device(struct crypt_device *cd, + const char *name, + uint64_t device_size_sectors, + struct device **ret_device) +{ + int r; + char path[PATH_MAX]; + struct device *dev; + + struct crypt_dm_active_device dmd = { + .size = device_size_sectors, + .flags = CRYPT_ACTIVATE_PRIVATE, + }; + + assert(cd); + assert(name); + assert(device_size_sectors); + assert(ret_device); + + r = snprintf(path, sizeof(path), "%s/%s", dm_get_dir(), name); + if (r < 0 || (size_t)r >= sizeof(path)) + return -EINVAL; + + r = device_block_adjust(cd, crypt_data_device(cd), DEV_OK, + crypt_get_data_offset(cd), &device_size_sectors, &dmd.flags); + if (r) + return r; + + log_dbg(cd, "Activating reduced helper device %s.", name); + + r = dm_linear_target_set(&dmd.segment, 0, dmd.size, crypt_data_device(cd), crypt_get_data_offset(cd)); + if (!r) + r = dm_create_device(cd, name, CRYPT_SUBDEV, &dmd); + dm_targets_free(cd, &dmd); + if (r < 0) + return r; + + r = device_alloc(cd, &dev, path); + if (!r) { + *ret_device = dev; + return 0; + } + + dm_remove_device(cd, name, CRYPT_DEACTIVATE_FORCE); + + return r; +} + int INTEGRITY_format(struct crypt_device *cd, const struct crypt_params_integrity *params, struct volume_key *journal_crypt_key, - struct volume_key *journal_mac_key) + struct volume_key *journal_mac_key, + uint64_t backing_device_sectors) { uint32_t dmi_flags; - char tmp_name[64], tmp_uuid[40]; + char reduced_device_name[70], tmp_name[64], tmp_uuid[40]; struct crypt_dm_active_device dmdi = { .size = 8, .flags = CRYPT_ACTIVATE_PRIVATE, /* We always create journal but it can be unused later */ @@ -349,6 +398,8 @@ int INTEGRITY_format(struct crypt_device *cd, struct dm_target *tgt = &dmdi.segment; int r; uuid_t tmp_uuid_bin; + uint64_t data_offset_sectors; + struct device *p_metadata_device, *p_data_device, *reduced_device = NULL; struct volume_key *vk = NULL; uuid_generate(tmp_uuid_bin); @@ -358,18 +409,42 @@ int INTEGRITY_format(struct crypt_device *cd, if (r < 0 || (size_t)r >= sizeof(tmp_name)) return -EINVAL; + p_metadata_device = INTEGRITY_metadata_device(cd); + + if (backing_device_sectors) { + r = snprintf(reduced_device_name, sizeof(reduced_device_name), + "temporary-cryptsetup-reduced-%s", tmp_uuid); + if (r < 0 || (size_t)r >= sizeof(reduced_device_name)) + return -EINVAL; + + /* + * Creates reduced dm-linear mapping over data device starting at + * crypt_data_offset(cd) and backing_device_sectors in size. + */ + r = _create_reduced_device(cd, reduced_device_name, + backing_device_sectors, &reduced_device); + if (r < 0) + return r; + + data_offset_sectors = 0; + p_data_device = reduced_device; + if (p_metadata_device == crypt_data_device(cd)) + p_metadata_device = reduced_device; + } else { + data_offset_sectors = crypt_get_data_offset(cd); + p_data_device = crypt_data_device(cd); + } + /* There is no data area, we can actually use fake zeroed key */ if (params && params->integrity_key_size) vk = crypt_alloc_volume_key(params->integrity_key_size, NULL); - r = dm_integrity_target_set(cd, tgt, 0, dmdi.size, INTEGRITY_metadata_device(cd), - crypt_data_device(cd), crypt_get_integrity_tag_size(cd), - crypt_get_data_offset(cd), crypt_get_sector_size(cd), vk, + r = dm_integrity_target_set(cd, tgt, 0, dmdi.size, p_metadata_device, + p_data_device, crypt_get_integrity_tag_size(cd), + data_offset_sectors, crypt_get_sector_size(cd), vk, journal_crypt_key, journal_mac_key, params); - if (r < 0) { - crypt_free_volume_key(vk); - return r; - } + if (r < 0) + goto err; log_dbg(cd, "Trying to format INTEGRITY device on top of %s, tmp name %s, tag size %d.", device_path(tgt->data_device), tmp_name, tgt->u.integrity.tag_size); @@ -379,24 +454,26 @@ int INTEGRITY_format(struct crypt_device *cd, log_err(cd, _("Kernel does not support dm-integrity mapping.")); r = -ENOTSUP; } - if (r) { - dm_targets_free(cd, &dmdi); - return r; - } + if (r) + goto err; if (tgt->u.integrity.meta_device) { r = device_block_adjust(cd, tgt->u.integrity.meta_device, DEV_EXCL, 0, NULL, NULL); - if (r) { - dm_targets_free(cd, &dmdi); - return r; - } + if (r) + goto err; } r = dm_create_device(cd, tmp_name, CRYPT_INTEGRITY, &dmdi); - crypt_free_volume_key(vk); - dm_targets_free(cd, &dmdi); if (r) - return r; + goto err; - return dm_remove_device(cd, tmp_name, CRYPT_DEACTIVATE_FORCE); + r = dm_remove_device(cd, tmp_name, CRYPT_DEACTIVATE_FORCE); +err: + dm_targets_free(cd, &dmdi); + crypt_free_volume_key(vk); + if (reduced_device) { + dm_remove_device(cd, reduced_device_name, CRYPT_DEACTIVATE_FORCE); + device_free(cd, reduced_device); + } + return r; } diff --git a/lib/integrity/integrity.h b/lib/integrity/integrity.h index 2883ef8..55c7148 100644 --- a/lib/integrity/integrity.h +++ b/lib/integrity/integrity.h @@ -1,7 +1,7 @@ /* * Integrity header definition * - * Copyright (C) 2016-2023 Milan Broz + * Copyright (C) 2016-2024 Milan Broz * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -75,7 +75,8 @@ int INTEGRITY_hash_tag_size(const char *integrity); int INTEGRITY_format(struct crypt_device *cd, const struct crypt_params_integrity *params, struct volume_key *journal_crypt_key, - struct volume_key *journal_mac_key); + struct volume_key *journal_mac_key, + uint64_t backing_device_sectors); int INTEGRITY_activate(struct crypt_device *cd, const char *name, diff --git a/lib/internal.h b/lib/internal.h index b5cb4e3..3a0d6e6 100644 --- a/lib/internal.h +++ b/lib/internal.h @@ -3,8 +3,8 @@ * * Copyright (C) 2004 Jana Saout * Copyright (C) 2004-2007 Clemens Fruhwirth - * Copyright (C) 2009-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2009-2023 Milan Broz + * Copyright (C) 2009-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2009-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -53,6 +53,7 @@ #define MAX_DM_DEPS 32 #define CRYPT_SUBDEV "SUBDEV" /* prefix for sublayered devices underneath public crypt types */ +#define CRYPT_LUKS2_HW_OPAL "LUKS2-OPAL" /* dm uuid prefix used for any HW OPAL enabled LUKS2 device */ #ifndef O_CLOEXEC #define O_CLOEXEC 0 @@ -89,6 +90,7 @@ int crypt_benchmark_pbkdf_internal(struct crypt_device *cd, struct crypt_pbkdf_type *pbkdf, size_t volume_key_size); const char *crypt_get_cipher_spec(struct crypt_device *cd); +uint32_t pbkdf_adjusted_phys_memory_kb(void); /* Device backend */ struct device; @@ -113,6 +115,7 @@ void device_release_excl(struct crypt_device *cd, struct device *device); void device_disable_direct_io(struct device *device); int device_is_identical(struct device *device1, struct device *device2); int device_is_rotational(struct device *device); +int device_is_dax(struct device *device); size_t device_alignment(struct device *device); int device_direct_io(const struct device *device); int device_fallocate(struct device *device, uint64_t size); @@ -153,21 +156,31 @@ int create_or_reload_device_with_integrity(struct crypt_device *cd, const char * struct device *crypt_metadata_device(struct crypt_device *cd); struct device *crypt_data_device(struct crypt_device *cd); +uint64_t crypt_get_metadata_size_bytes(struct crypt_device *cd); +uint64_t crypt_get_keyslots_size_bytes(struct crypt_device *cd); +uint64_t crypt_get_data_offset_sectors(struct crypt_device *cd); +int crypt_opal_supported(struct crypt_device *cd, struct device *opal_device); + int crypt_confirm(struct crypt_device *cd, const char *msg); char *crypt_lookup_dev(const char *dev_id); int crypt_dev_is_rotational(int major, int minor); +int crypt_dev_is_dax(int major, int minor); int crypt_dev_is_partition(const char *dev_path); char *crypt_get_partition_device(const char *dev_path, uint64_t offset, uint64_t size); +int crypt_dev_get_partition_number(const char *dev_path); char *crypt_get_base_device(const char *dev_path); uint64_t crypt_dev_partition_offset(const char *dev_path); int lookup_by_disk_id(const char *dm_uuid); int lookup_by_sysfs_uuid_field(const char *dm_uuid); int crypt_uuid_cmp(const char *dm_uuid, const char *hdr_uuid); +int crypt_uuid_type_cmp(const char *dm_uuid, const char *type); size_t crypt_getpagesize(void); unsigned crypt_cpusonline(void); uint64_t crypt_getphysmemory_kb(void); +uint64_t crypt_getphysmemoryfree_kb(void); +bool crypt_swapavailable(void); int init_crypto(struct crypt_device *ctx); @@ -202,7 +215,7 @@ void crypt_set_luks2_reencrypt(struct crypt_device *cd, struct luks2_reencrypt * struct luks2_reencrypt *crypt_get_luks2_reencrypt(struct crypt_device *cd); int onlyLUKS2(struct crypt_device *cd); -int onlyLUKS2mask(struct crypt_device *cd, uint32_t mask); +int onlyLUKS2reencrypt(struct crypt_device *cd); int crypt_wipe_device(struct crypt_device *cd, struct device *device, @@ -221,6 +234,14 @@ int crypt_get_integrity_tag_size(struct crypt_device *cd); int crypt_key_in_keyring(struct crypt_device *cd); void crypt_set_key_in_keyring(struct crypt_device *cd, unsigned key_in_keyring); int crypt_volume_key_load_in_keyring(struct crypt_device *cd, struct volume_key *vk); +int crypt_keyring_get_user_key(struct crypt_device *cd, + const char *key_description, + char **key, + size_t *key_size); +int crypt_keyring_get_key_by_name(struct crypt_device *cd, + const char *key_description, + char **key, + size_t *key_size); int crypt_use_keyring_for_vk(struct crypt_device *cd); void crypt_drop_keyring_key_by_description(struct crypt_device *cd, const char *key_description, key_type_t ktype); void crypt_drop_keyring_key(struct crypt_device *cd, struct volume_key *vks); @@ -250,4 +271,8 @@ static inline bool uint64_mult_overflow(uint64_t *u, uint64_t b, size_t size) return false; } +#define KEY_NOT_VERIFIED -2 +#define KEY_EXTERNAL_VERIFICATION -1 +#define KEY_VERIFIED 0 + #endif /* INTERNAL_H */ diff --git a/lib/keyslot_context.c b/lib/keyslot_context.c index 89bd433..5860247 100644 --- a/lib/keyslot_context.c +++ b/lib/keyslot_context.c @@ -1,8 +1,8 @@ /* * LUKS - Linux Unified Key Setup, keyslot unlock helpers * - * Copyright (C) 2022-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2022-2023 Ondrej Kozina + * Copyright (C) 2022-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2022-2024 Ondrej Kozina * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -173,7 +173,7 @@ static int get_luks1_volume_key_by_keyfile(struct crypt_device *cd, return r; } -static int get_key_by_key(struct crypt_device *cd, +static int get_key_by_key(struct crypt_device *cd __attribute__((unused)), struct crypt_keyslot_context *kc, int keyslot __attribute__((unused)), int segment __attribute__((unused)), @@ -204,19 +204,73 @@ static int get_volume_key_by_key(struct crypt_device *cd, return get_key_by_key(cd, kc, -2 /* unused */, -2 /* unused */, r_vk); } +static int get_generic_volume_key_by_key(struct crypt_device *cd, + struct crypt_keyslot_context *kc, + struct volume_key **r_vk) +{ + return get_key_by_key(cd, kc, -2 /* unused */, -2 /* unused */, r_vk); +} + +static int get_generic_signed_key_by_key(struct crypt_device *cd, + struct crypt_keyslot_context *kc, + struct volume_key **r_vk, + struct volume_key **r_signature) +{ + struct volume_key *vk, *vk_sig; + + assert(kc && ((kc->type == CRYPT_KC_TYPE_KEY) || + (kc->type == CRYPT_KC_TYPE_SIGNED_KEY))); + assert(r_vk); + assert(r_signature); + + /* return key with no signature */ + if (kc->type == CRYPT_KC_TYPE_KEY) { + *r_signature = NULL; + return get_key_by_key(cd, kc, -2 /* unused */, -2 /* unused */, r_vk); + } + + if (!kc->u.ks.volume_key || !kc->u.ks.signature) { + kc->error = -EINVAL; + return kc->error; + } + + vk = crypt_alloc_volume_key(kc->u.ks.volume_key_size, kc->u.ks.volume_key); + if (!vk) { + kc->error = -ENOMEM; + return kc->error; + } + + vk_sig = crypt_alloc_volume_key(kc->u.ks.signature_size, kc->u.ks.signature); + if (!vk_sig) { + crypt_free_volume_key(vk); + kc->error = -ENOMEM; + return kc->error; + } + + *r_vk = vk; + *r_signature = vk_sig; + + return 0; +} + static int get_luks2_key_by_token(struct crypt_device *cd, struct crypt_keyslot_context *kc, - int keyslot __attribute__((unused)), + int keyslot, int segment, struct volume_key **r_vk) { int r; + struct luks2_hdr *hdr; assert(cd); assert(kc && kc->type == CRYPT_KC_TYPE_TOKEN); assert(r_vk); - r = LUKS2_token_unlock_key(cd, crypt_get_hdr(cd, CRYPT_LUKS2), kc->u.t.id, kc->u.t.type, + hdr = crypt_get_hdr(cd, CRYPT_LUKS2); + if (!hdr) + return -EINVAL; + + r = LUKS2_token_unlock_key(cd, hdr, keyslot, kc->u.t.id, kc->u.t.type, kc->u.t.pin, kc->u.t.pin_size, segment, kc->u.t.usrptr, r_vk); if (r < 0) kc->error = r; @@ -226,10 +280,10 @@ static int get_luks2_key_by_token(struct crypt_device *cd, static int get_luks2_volume_key_by_token(struct crypt_device *cd, struct crypt_keyslot_context *kc, - int keyslot __attribute__((unused)), + int keyslot, struct volume_key **r_vk) { - return get_luks2_key_by_token(cd, kc, -2 /* unused */, CRYPT_DEFAULT_SEGMENT, r_vk); + return get_luks2_key_by_token(cd, kc, keyslot, CRYPT_DEFAULT_SEGMENT, r_vk); } static int get_passphrase_by_token(struct crypt_device *cd, @@ -261,6 +315,136 @@ static int get_passphrase_by_token(struct crypt_device *cd, return kc->u.t.id; } +static int get_passphrase_by_keyring(struct crypt_device *cd, + struct crypt_keyslot_context *kc, + const char **r_passphrase, + size_t *r_passphrase_size) +{ + int r; + + assert(cd); + assert(kc && kc->type == CRYPT_KC_TYPE_KEYRING); + assert(r_passphrase); + assert(r_passphrase_size); + + if (!kc->i_passphrase) { + r = crypt_keyring_get_user_key(cd, kc->u.kr.key_description, + &kc->i_passphrase, &kc->i_passphrase_size); + if (r < 0) { + log_err(cd, _("Failed to read passphrase from keyring.")); + kc->error = -EINVAL; + return -EINVAL; + } + } + + *r_passphrase = kc->i_passphrase; + *r_passphrase_size = kc->i_passphrase_size; + + return 0; +} + +static int get_luks2_key_by_keyring(struct crypt_device *cd, + struct crypt_keyslot_context *kc, + int keyslot, + int segment, + struct volume_key **r_vk) +{ + int r; + + assert(cd); + assert(kc && kc->type == CRYPT_KC_TYPE_KEYRING); + assert(r_vk); + + r = get_passphrase_by_keyring(cd, kc, CONST_CAST(const char **) &kc->i_passphrase, + &kc->i_passphrase_size); + if (r < 0) { + log_err(cd, _("Failed to read passphrase from keyring.")); + kc->error = -EINVAL; + return -EINVAL; + } + + r = LUKS2_keyslot_open(cd, keyslot, segment, kc->i_passphrase, kc->i_passphrase_size, r_vk); + if (r < 0) + kc->error = r; + + return 0; +} + +static int get_luks2_volume_key_by_keyring(struct crypt_device *cd, + struct crypt_keyslot_context *kc, + int keyslot, + struct volume_key **r_vk) +{ + return get_luks2_key_by_keyring(cd, kc, keyslot, CRYPT_DEFAULT_SEGMENT, r_vk); +} + +static int get_luks1_volume_key_by_keyring(struct crypt_device *cd, + struct crypt_keyslot_context *kc, + int keyslot, + struct volume_key **r_vk) +{ + int r; + + assert(cd); + assert(kc && kc->type == CRYPT_KC_TYPE_PASSPHRASE); + assert(r_vk); + + r = get_passphrase_by_keyring(cd, kc, CONST_CAST(const char **) &kc->i_passphrase, + &kc->i_passphrase_size); + if (r < 0) { + log_err(cd, _("Failed to read passphrase from keyring.")); + kc->error = -EINVAL; + return -EINVAL; + } + + r = LUKS_open_key_with_hdr(keyslot, kc->i_passphrase, kc->i_passphrase_size, + crypt_get_hdr(cd, CRYPT_LUKS1), r_vk, cd); + if (r < 0) + kc->error = r; + + return r; +} + +static int get_key_by_vk_in_keyring(struct crypt_device *cd, + struct crypt_keyslot_context *kc, + int keyslot __attribute__((unused)), + int segment __attribute__((unused)), + struct volume_key **r_vk) +{ + char *key; + size_t key_size; + int r; + + assert(cd); + assert(kc && kc->type == CRYPT_KC_TYPE_VK_KEYRING); + assert(r_vk); + + r = crypt_keyring_get_key_by_name(cd, kc->u.vk_kr.key_description, + &key, &key_size); + if (r < 0) { + log_err(cd, _("Failed to read volume key candidate from keyring.")); + kc->error = -EINVAL; + return -EINVAL; + } + + *r_vk = crypt_alloc_volume_key(key_size, key); + crypt_safe_free(key); + if (!*r_vk) { + kc->error = -ENOMEM; + return kc->error; + } + + return 0; +} + +static int get_volume_key_by_vk_in_keyring(struct crypt_device *cd, + struct crypt_keyslot_context *kc, + int keyslot __attribute__((unused)), + struct volume_key **r_vk) +{ + return get_key_by_vk_in_keyring(cd, kc, -2 /* unused */, -2 /* unused */, r_vk); +} + static void unlock_method_init_internal(struct crypt_keyslot_context *kc) { assert(kc); @@ -270,6 +454,26 @@ static void unlock_method_init_internal(struct crypt_keyslot_context *kc) kc->i_passphrase_size = 0; } +void crypt_keyslot_unlock_by_keyring_internal(struct crypt_keyslot_context *kc, + const char *key_description) +{ + assert(kc); + + kc->type = CRYPT_KC_TYPE_KEYRING; + kc->u.kr.key_description = key_description; + + kc->get_luks2_key = get_luks2_key_by_keyring; + kc->get_luks2_volume_key = get_luks2_volume_key_by_keyring; + kc->get_luks1_volume_key = get_luks1_volume_key_by_keyring; + kc->get_passphrase = get_passphrase_by_keyring; + kc->get_plain_volume_key = NULL; + kc->get_bitlk_volume_key = NULL; + kc->get_fvault2_volume_key = NULL; + kc->get_verity_volume_key = NULL; + kc->get_integrity_volume_key = NULL; + unlock_method_init_internal(kc); +} + void crypt_keyslot_unlock_by_key_init_internal(struct crypt_keyslot_context *kc, const char *volume_key, size_t volume_key_size) @@ -283,6 +487,36 @@ void crypt_keyslot_unlock_by_key_init_internal(struct crypt_keyslot_context *kc, kc->get_luks2_volume_key = get_volume_key_by_key; kc->get_luks1_volume_key = get_volume_key_by_key; kc->get_passphrase = NULL; /* keyslot key context does not provide passphrase */ + kc->get_plain_volume_key = get_generic_volume_key_by_key; + kc->get_bitlk_volume_key = get_generic_volume_key_by_key; + kc->get_fvault2_volume_key = get_generic_volume_key_by_key; + kc->get_verity_volume_key = get_generic_signed_key_by_key; + kc->get_integrity_volume_key = get_generic_volume_key_by_key; + unlock_method_init_internal(kc); +} + +void crypt_keyslot_unlock_by_signed_key_init_internal(struct crypt_keyslot_context *kc, + const char *volume_key, + size_t volume_key_size, + const char *signature, + size_t signature_size) +{ + assert(kc); + + kc->type = CRYPT_KC_TYPE_SIGNED_KEY; + kc->u.ks.volume_key = volume_key; + kc->u.ks.volume_key_size = volume_key_size; + kc->u.ks.signature = signature; + kc->u.ks.signature_size = signature_size; + kc->get_luks2_key = NULL; + kc->get_luks2_volume_key = NULL; + kc->get_luks1_volume_key = NULL; + kc->get_passphrase = NULL; + kc->get_plain_volume_key = NULL; + kc->get_bitlk_volume_key = NULL; + kc->get_fvault2_volume_key = NULL; + kc->get_verity_volume_key = get_generic_signed_key_by_key; + kc->get_integrity_volume_key = NULL; unlock_method_init_internal(kc); } @@ -299,6 +533,11 @@ void crypt_keyslot_unlock_by_passphrase_init_internal(struct crypt_keyslot_conte kc->get_luks2_volume_key = get_luks2_volume_key_by_passphrase; kc->get_luks1_volume_key = get_luks1_volume_key_by_passphrase; kc->get_passphrase = get_passphrase_by_passphrase; + kc->get_plain_volume_key = NULL; + kc->get_bitlk_volume_key = NULL; + kc->get_fvault2_volume_key = NULL; + kc->get_verity_volume_key = NULL; + kc->get_integrity_volume_key = NULL; unlock_method_init_internal(kc); } @@ -317,6 +556,11 @@ void crypt_keyslot_unlock_by_keyfile_init_internal(struct crypt_keyslot_context kc->get_luks2_volume_key = get_luks2_volume_key_by_keyfile; kc->get_luks1_volume_key = get_luks1_volume_key_by_keyfile; kc->get_passphrase = get_passphrase_by_keyfile; + kc->get_plain_volume_key = NULL; + kc->get_bitlk_volume_key = NULL; + kc->get_fvault2_volume_key = NULL; + kc->get_verity_volume_key = NULL; + kc->get_integrity_volume_key = NULL; unlock_method_init_internal(kc); } @@ -339,9 +583,35 @@ void crypt_keyslot_unlock_by_token_init_internal(struct crypt_keyslot_context *k kc->get_luks2_volume_key = get_luks2_volume_key_by_token; kc->get_luks1_volume_key = NULL; /* LUKS1 is not supported */ kc->get_passphrase = get_passphrase_by_token; + kc->get_plain_volume_key = NULL; + kc->get_bitlk_volume_key = NULL; + kc->get_fvault2_volume_key = NULL; + kc->get_verity_volume_key = NULL; + kc->get_integrity_volume_key = NULL; unlock_method_init_internal(kc); } +void crypt_keyslot_unlock_by_vk_in_keyring_internal(struct crypt_keyslot_context *kc, + const char *key_description) +{ + assert(kc); + + kc->type = CRYPT_KC_TYPE_VK_KEYRING; + kc->u.vk_kr.key_description = key_description; + + kc->get_luks2_key = get_key_by_vk_in_keyring; + kc->get_luks2_volume_key = get_volume_key_by_vk_in_keyring; + kc->get_luks1_volume_key = NULL; + kc->get_passphrase = NULL; /* keyslot key context does not provide passphrase */ + kc->get_plain_volume_key = NULL; + kc->get_bitlk_volume_key = NULL; + kc->get_fvault2_volume_key = NULL; + kc->get_verity_volume_key = NULL; + kc->get_integrity_volume_key = NULL; + unlock_method_init_internal(kc); +} + + void crypt_keyslot_context_destroy_internal(struct crypt_keyslot_context *kc) { if (!kc) @@ -358,7 +628,7 @@ void crypt_keyslot_context_free(struct crypt_keyslot_context *kc) free(kc); } -int crypt_keyslot_context_init_by_passphrase(struct crypt_device *cd, +int crypt_keyslot_context_init_by_passphrase(struct crypt_device *cd __attribute__((unused)), const char *passphrase, size_t passphrase_size, struct crypt_keyslot_context **kc) @@ -379,7 +649,7 @@ int crypt_keyslot_context_init_by_passphrase(struct crypt_device *cd, return 0; } -int crypt_keyslot_context_init_by_keyfile(struct crypt_device *cd, +int crypt_keyslot_context_init_by_keyfile(struct crypt_device *cd __attribute__((unused)), const char *keyfile, size_t keyfile_size, uint64_t keyfile_offset, @@ -401,7 +671,7 @@ int crypt_keyslot_context_init_by_keyfile(struct crypt_device *cd, return 0; } -int crypt_keyslot_context_init_by_token(struct crypt_device *cd, +int crypt_keyslot_context_init_by_token(struct crypt_device *cd __attribute__((unused)), int token, const char *type, const char *pin, size_t pin_size, @@ -424,7 +694,7 @@ int crypt_keyslot_context_init_by_token(struct crypt_device *cd, return 0; } -int crypt_keyslot_context_init_by_volume_key(struct crypt_device *cd, +int crypt_keyslot_context_init_by_volume_key(struct crypt_device *cd __attribute__((unused)), const char *volume_key, size_t volume_key_size, struct crypt_keyslot_context **kc) @@ -445,12 +715,76 @@ int crypt_keyslot_context_init_by_volume_key(struct crypt_device *cd, return 0; } +int crypt_keyslot_context_init_by_signed_key(struct crypt_device *cd __attribute__((unused)), + const char *volume_key, + size_t volume_key_size, + const char *signature, + size_t signature_size, + struct crypt_keyslot_context **kc) +{ + struct crypt_keyslot_context *tmp; + + if (!kc) + return -EINVAL; + + tmp = malloc(sizeof(*tmp)); + if (!tmp) + return -ENOMEM; + + crypt_keyslot_unlock_by_signed_key_init_internal(tmp, volume_key, volume_key_size, + signature, signature_size); + + *kc = tmp; + + return 0; +} + +int crypt_keyslot_context_init_by_keyring(struct crypt_device *cd __attribute__((unused)), + const char *key_description, + struct crypt_keyslot_context **kc) +{ + struct crypt_keyslot_context *tmp; + + if (!kc) + return -EINVAL; + + tmp = malloc(sizeof(*tmp)); + if (!tmp) + return -ENOMEM; + + crypt_keyslot_unlock_by_keyring_internal(tmp, key_description); + + *kc = tmp; + + return 0; +} + +int crypt_keyslot_context_init_by_vk_in_keyring(struct crypt_device *cd __attribute__((unused)), + const char *key_description, + struct crypt_keyslot_context **kc) +{ + struct crypt_keyslot_context *tmp; + + if (!kc) + return -EINVAL; + + tmp = malloc(sizeof(*tmp)); + if (!tmp) + return -ENOMEM; + + crypt_keyslot_unlock_by_vk_in_keyring_internal(tmp, key_description); + + *kc = tmp; + + return 0; +} + int crypt_keyslot_context_get_error(struct crypt_keyslot_context *kc) { return kc ? kc->error : -EINVAL; } -int crypt_keyslot_context_set_pin(struct crypt_device *cd, +int crypt_keyslot_context_set_pin(struct crypt_device *cd __attribute__((unused)), const char *pin, size_t pin_size, struct crypt_keyslot_context *kc) { @@ -482,6 +816,12 @@ const char *keyslot_context_type_string(const struct crypt_keyslot_context *kc) return "token"; case CRYPT_KC_TYPE_KEY: return "key"; + case CRYPT_KC_TYPE_KEYRING: + return "keyring"; + case CRYPT_KC_TYPE_VK_KEYRING: + return "volume key in keyring"; + case CRYPT_KC_TYPE_SIGNED_KEY: + return "signed key"; default: return ""; } diff --git a/lib/keyslot_context.h b/lib/keyslot_context.h index 7ca7428..fd15159 100644 --- a/lib/keyslot_context.h +++ b/lib/keyslot_context.h @@ -1,8 +1,8 @@ /* * LUKS - Linux Unified Key Setup, keyslot unlock helpers * - * Copyright (C) 2022-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2022-2023 Ondrej Kozina + * Copyright (C) 2022-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2022-2024 Ondrej Kozina * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -40,6 +40,17 @@ typedef int (*keyslot_context_get_volume_key) ( int keyslot, struct volume_key **r_vk); +typedef int (*keyslot_context_get_generic_volume_key) ( + struct crypt_device *cd, + struct crypt_keyslot_context *kc, + struct volume_key **r_vk); + +typedef int (*keyslot_context_get_generic_signed_key) ( + struct crypt_device *cd, + struct crypt_keyslot_context *kc, + struct volume_key **r_vk, + struct volume_key **r_signature); + typedef int (*keyslot_context_get_passphrase) ( struct crypt_device *cd, struct crypt_keyslot_context *kc, @@ -71,6 +82,18 @@ struct crypt_keyslot_context { const char *volume_key; size_t volume_key_size; } k; + struct { + const char *volume_key; + size_t volume_key_size; + const char *signature; + size_t signature_size; + } ks; + struct { + const char *key_description; + } kr; + struct { + const char *key_description; + } vk_kr; } u; int error; @@ -78,10 +101,15 @@ struct crypt_keyslot_context { char *i_passphrase; size_t i_passphrase_size; - keyslot_context_get_key get_luks2_key; - keyslot_context_get_volume_key get_luks1_volume_key; - keyslot_context_get_volume_key get_luks2_volume_key; - keyslot_context_get_passphrase get_passphrase; + keyslot_context_get_key get_luks2_key; + keyslot_context_get_volume_key get_luks1_volume_key; + keyslot_context_get_volume_key get_luks2_volume_key; + keyslot_context_get_generic_volume_key get_plain_volume_key; + keyslot_context_get_generic_volume_key get_bitlk_volume_key; + keyslot_context_get_generic_volume_key get_fvault2_volume_key; + keyslot_context_get_generic_signed_key get_verity_volume_key; + keyslot_context_get_generic_volume_key get_integrity_volume_key; + keyslot_context_get_passphrase get_passphrase; }; void crypt_keyslot_context_destroy_internal(struct crypt_keyslot_context *method); @@ -90,6 +118,12 @@ void crypt_keyslot_unlock_by_key_init_internal(struct crypt_keyslot_context *kc, const char *volume_key, size_t volume_key_size); +void crypt_keyslot_unlock_by_signed_key_init_internal(struct crypt_keyslot_context *kc, + const char *volume_key, + size_t volume_key_size, + const char *signature, + size_t signature_size); + void crypt_keyslot_unlock_by_passphrase_init_internal(struct crypt_keyslot_context *kc, const char *passphrase, size_t passphrase_size); @@ -106,6 +140,12 @@ void crypt_keyslot_unlock_by_token_init_internal(struct crypt_keyslot_context *k size_t pin_size, void *usrptr); +void crypt_keyslot_unlock_by_keyring_internal(struct crypt_keyslot_context *kc, + const char *key_description); + +void crypt_keyslot_unlock_by_vk_in_keyring_internal(struct crypt_keyslot_context *kc, + const char *key_description); + const char *keyslot_context_type_string(const struct crypt_keyslot_context *kc); #endif /* KEYSLOT_CONTEXT_H */ diff --git a/lib/libcryptsetup.h b/lib/libcryptsetup.h index e899829..82d042f 100644 --- a/lib/libcryptsetup.h +++ b/lib/libcryptsetup.h @@ -3,8 +3,8 @@ * * Copyright (C) 2004 Jana Saout * Copyright (C) 2004-2007 Clemens Fruhwirth - * Copyright (C) 2009-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2009-2023 Milan Broz + * Copyright (C) 2009-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2009-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -273,7 +273,7 @@ struct crypt_pbkdf_type { /** Iteration time set by crypt_set_iteration_time(), for compatibility only. */ #define CRYPT_PBKDF_ITER_TIME_SET (UINT32_C(1) << 0) -/** Never run benchmarks, use pre-set value or defaults. */ +/** Never run benchmarks or limit by system resources, use pre-set values or defaults. */ #define CRYPT_PBKDF_NO_BENCHMARK (UINT32_C(1) << 1) /** PBKDF2 according to RFC2898, LUKS1 legacy */ @@ -450,6 +450,34 @@ const char *crypt_get_type(struct crypt_device *cd); */ const char *crypt_get_default_type(void); +/** + * @defgroup crypt-hw-encryption-types HW encryption type + * @addtogroup crypt-hw-encryption-types + * @{ + */ +/** SW encryption, no OPAL encryption in place (default) */ +#define CRYPT_SW_ONLY INT16_C(0) +/** OPAL HW encryption only (no SW encryption!) */ +#define CRYPT_OPAL_HW_ONLY INT16_C(1) +/** SW encryption stacked over OPAL HW encryption */ +#define CRYPT_SW_AND_OPAL_HW INT16_C(2) +/** @} */ + +/** + * Get HW encryption type + * + * @return HW encryption type (see @link crypt-hw-encryption-types @endlink) + * or negative errno otherwise. + */ +int crypt_get_hw_encryption_type(struct crypt_device *cd); + +/** + * Get HW encryption (like OPAL) key size (in bytes) + * + * @return key size or 0 if no HW encryption is used. + */ +int crypt_get_hw_encryption_key_size(struct crypt_device *cd); + /** * * Structure used as parameter for PLAIN device type. @@ -609,6 +637,18 @@ struct crypt_params_luks2 { const char *label; /**< header label or @e NULL*/ const char *subsystem; /**< header subsystem label or @e NULL*/ }; + +/** + * Structure used as parameter for OPAL (HW encrypted) device type. + * + * @see crypt_format_luks2_opal + * + */ +struct crypt_params_hw_opal { + const char *admin_key; /**< admin key */ + size_t admin_key_size; /**< admin key size in bytes */ + size_t user_key_size; /**< user authority key size part in bytes */ +}; /** @} */ /** @@ -648,6 +688,34 @@ int crypt_format(struct crypt_device *cd, size_t volume_key_size, void *params); +/** + * Create (format) new LUKS2 crypt device over HW OPAL device but do not activate it. + * + * @pre @e cd contains initialized and not formatted device context (device type must @b not be set) + * + * @param cd crypt device handle + * @param cipher for SW encryption (e.g. "aes") or NULL for HW encryption only + * @param cipher_mode including IV specification (e.g. "xts-plain") or NULL for HW encryption only + * @param uuid requested UUID or @e NULL if it should be generated + * @param volume_keys pre-generated volume keys or @e NULL if it should be generated (only for LUKS2 SW encryption) + * @param volume_keys_size size of volume keys in bytes (only for SW encryption). + * @param params LUKS2 crypt type specific parameters (see @link crypt-type @endlink) + * @param opal_params OPAL specific parameters + * + * @returns @e 0 on success or negative errno value otherwise. + * + * @note Note that crypt_format_luks2_opal does not create LUKS keyslot. + * To create keyslot call any crypt_keyslot_add_* function. + */ +int crypt_format_luks2_opal(struct crypt_device *cd, + const char *cipher, + const char *cipher_mode, + const char *uuid, + const char *volume_keys, + size_t volume_keys_size, + struct crypt_params_luks2 *params, + struct crypt_params_hw_opal *opal_params); + /** * Set format compatibility flags. * @@ -941,6 +1009,23 @@ int crypt_resume_by_token_pin(struct crypt_device *cd, const char *pin, size_t pin_size, void *usrptr); + +/** + * Resume crypt device using keyslot context. + * + * @param cd crypt device handle + * @param name name of device to resume + * @param keyslot requested keyslot to check or @e CRYPT_ANY_SLOT, keyslot is + * ignored for unlock methods not based on passphrase + * @param kc keyslot context providing volume key or passphrase. + * + * @return unlocked key slot number for passphrase-based unlock, zero for other + * unlock methods (e.g. volume key context) or negative errno on error. + */ +int crypt_resume_by_keyslot_context(struct crypt_device *cd, + const char *name, + int keyslot, + struct crypt_keyslot_context *kc); /** @} */ /** @@ -1099,7 +1184,7 @@ int crypt_keyslot_add_by_volume_key(struct crypt_device *cd, * @warning CRYPT_VOLUME_KEY_SET flag force updates volume key. It is @b not @b reencryption! * By doing so you will most probably destroy your ciphertext data device. It's supposed * to be used only in wrapped keys scheme for key refresh process where real (inner) volume - * key stays untouched. It may be involed on active @e keyslot which makes the (previously + * key stays untouched. It may be involved on active @e keyslot which makes the (previously * unbound) keyslot new regular keyslot. */ int crypt_keyslot_add_by_key(struct crypt_device *cd, @@ -1194,6 +1279,59 @@ int crypt_keyslot_context_init_by_volume_key(struct crypt_device *cd, size_t volume_key_size, struct crypt_keyslot_context **kc); +/** + * Initialize keyslot context via signed key. + * + * @param cd crypt device handle initialized to device context + * + * @param volume_key provided volume key + * @param volume_key_size size of volume_key + * @param signature buffer with signature for the key + * @param signature_size bsize of signature buffer + * @param kc returns crypt keyslot context handle type CRYPT_KC_TYPE_SIGNED_KEY + * + * @return zero on success or negative errno otherwise. + * + * @note currently supported only with VERITY devices. + */ +int crypt_keyslot_context_init_by_signed_key(struct crypt_device *cd, + const char *volume_key, + size_t volume_key_size, + const char *signature, + size_t signature_size, + struct crypt_keyslot_context **kc); + +/** + * Initialize keyslot context via passphrase stored in a keyring. + * + * @param cd crypt device handle initialized to LUKS device context + * + * @param key_description kernel keyring key description library should look + * for passphrase in + * @param kc returns crypt keyslot context handle type CRYPT_KC_TYPE_KEYRING + * + * @return zero on success or negative errno otherwise. + */ +int crypt_keyslot_context_init_by_keyring(struct crypt_device *cd, + const char *key_description, + struct crypt_keyslot_context **kc); + +/** + * Initialize keyslot context via volume key stored in a keyring. + * + * @param cd crypt device handle initialized to LUKS device context + * + * @param key_description kernel keyring key description library should look + * for passphrase in. The key can be passed either as number in ASCII, + * or a text representation in the form "%:" + * @param kc returns crypt keyslot context handle type CRYPT_KC_TYPE_KEYRING + * + * @return zero on success or negative errno otherwise. + */ +int crypt_keyslot_context_init_by_vk_in_keyring(struct crypt_device *cd, + const char *key_description, + struct crypt_keyslot_context **kc); + /** * Get error code per keyslot context from last failed call. * @@ -1225,7 +1363,7 @@ int crypt_keyslot_context_set_pin(struct crypt_device *cd, struct crypt_keyslot_context *kc); /** - * @defgroup crypt-keyslot-context-types Crypt keyslot context + * @defgroup crypt-keyslot-context-types Crypt keyslot context types * @addtogroup crypt-keyslot-context-types * @{ */ @@ -1237,6 +1375,16 @@ int crypt_keyslot_context_set_pin(struct crypt_device *cd, #define CRYPT_KC_TYPE_TOKEN INT16_C(3) /** keyslot context initialized by volume key or unbound key (@link crypt_keyslot_context_init_by_volume_key @endlink) */ #define CRYPT_KC_TYPE_KEY INT16_C(4) +/** keyslot context initialized by description of a keyring key + * (@link crypt_keyslot_context_init_by_keyring @endlink) + */ +#define CRYPT_KC_TYPE_KEYRING INT16_C(5) +/** keyslot context initialized by description of a keyring key containing the volume key + * (@link crypt_keyslot_context_init_by_vk_in_keyring @endlink) + */ +#define CRYPT_KC_TYPE_VK_KEYRING INT16_C(6) +/** keyslot context initialized by signed key (@link crypt_keyslot_context_init_by_signed_key @endlink) */ +#define CRYPT_KC_TYPE_SIGNED_KEY INT16_C(7) /** @} */ /** @@ -1281,7 +1429,7 @@ int crypt_keyslot_context_get_type(const struct crypt_keyslot_context *kc); * @warning CRYPT_VOLUME_KEY_SET flag force updates volume key. It is @b not @b reencryption! * By doing so you will most probably destroy your ciphertext data device. It's supposed * to be used only in wrapped keys scheme for key refresh process where real (inner) volume - * key stays untouched. It may be involed on active @e keyslot which makes the (previously + * key stays untouched. It may be involved on active @e keyslot which makes the (previously * unbound) keyslot new regular keyslot. */ int crypt_keyslot_add_by_keyslot_context(struct crypt_device *cd, @@ -1420,6 +1568,8 @@ uint64_t crypt_get_active_integrity_failures(struct crypt_device *cd, #define CRYPT_REQUIREMENT_OFFLINE_REENCRYPT (UINT32_C(1) << 0) /** Online reencryption in-progress */ #define CRYPT_REQUIREMENT_ONLINE_REENCRYPT (UINT32_C(1) << 1) +/** Device configured with OPAL support */ +#define CRYPT_REQUIREMENT_OPAL (UINT32_C(1) << 2) /** unknown requirement in header (output only) */ #define CRYPT_REQUIREMENT_UNKNOWN (UINT32_C(1) << 31) @@ -1473,6 +1623,39 @@ int crypt_persistent_flags_get(struct crypt_device *cd, * @{ */ +/** + * Activate device or check using keyslot context. In some cases (device under + * reencryption), more than one keyslot context is required (e.g. one for the old + * volume key and one for the new volume key). The order of the keyslot + * contexts does not matter. When less keyslot contexts are supplied than + * required to unlock the device an -ESRCH error code is returned and you + * should call the function again with an additional keyslot context specified. + * + * NOTE: the API at the moment fully works for single keyslot context only, + * the additional keyslot context currently works only with + * @e CRYPT_KC_TYPE_VK_KEYRING or @e CRYPT_KC_TYPE_KEY contexts. + * + * @param cd crypt device handle + * @param name name of device to create, if @e NULL only check passphrase + * @param keyslot requested keyslot to check or @e CRYPT_ANY_SLOT, keyslot is + * ignored for unlock methods not based on passphrase + * @param kc keyslot context providing volume key or passphrase. + * @param additional_keyslot requested additional keyslot to check or @e CRYPT_ANY_SLOT + * @param additional_kc keyslot context providing additional volume key or + * passphrase (e.g. old volume key for device under reencryption). + * @param flags activation flags + * + * @return unlocked key slot number for passphrase-based unlock, zero for other + * unlock methods (e.g. volume key context) or negative errno on error. + */ +int crypt_activate_by_keyslot_context(struct crypt_device *cd, + const char *name, + int keyslot, + struct crypt_keyslot_context *kc, + int additional_keyslot, + struct crypt_keyslot_context *additional_kc, + uint32_t flags); + /** * Activate device or check passphrase. * @@ -1553,6 +1736,9 @@ int crypt_activate_by_keyfile(struct crypt_device *cd, * CRYPT_ACTIVATE_READONLY flag always. * @note For TCRYPT the volume key should be always NULL * the key from decrypted header is used instead. + * @note For BITLK the name cannot be @e NULL checking volume key is not + * supported for BITLK, the device will be activated even if the + * provided key is not correct. */ int crypt_activate_by_volume_key(struct crypt_device *cd, const char *name, @@ -2259,6 +2445,36 @@ int crypt_wipe(struct crypt_device *cd, /** Use direct-io */ #define CRYPT_WIPE_NO_DIRECT_IO (UINT32_C(1) << 0) + +enum { + CRYPT_LUKS2_SEGMENT = -2, + CRYPT_NO_SEGMENT = -1, +}; + +/** + * Safe erase of a partition or an entire OPAL device. WARNING: ALL DATA ON + * PARTITION/DISK WILL BE LOST. If the CRYPT_NO_SEGMENT is passed as the segment + * parameter, the entire device will be wiped, not just what is included in the + * LUKS2 device/partition. + * + * @param cd crypt device handle + * @param segment the segment number to wipe (0..8), or CRYPT_LUKS2_SEGMENT + * to wipe the segment configured in the LUKS2 header, or CRYPT_NO_SEGMENT + * to wipe the entire device via a factory reset. + * @param password admin password/PSID (for factory reset) to wipe the + * partition/device + * @param password_size length of password/PSID + * @param flags (currently unused) + * + * @return @e 0 on success or negative errno value otherwise. + */ +int crypt_wipe_hw_opal(struct crypt_device *cd, + int segment, /* 0..8, CRYPT_LUKS2_SEGMENT -2, CRYPT_NO_SEGMENT -1 */ + const char *password, /* Admin1 PIN or PSID */ + size_t password_size, + uint32_t flags /* currently unused */ +); + /** @} */ /** @@ -2566,6 +2782,17 @@ int crypt_token_register(const crypt_token_handler *handler); */ const char *crypt_token_external_path(void); +/** + * Override configured external token handlers path for the library. + * + * @param path Absolute path (starts with '/') to new external token handlers directory or @e NULL. + * + * @note if @e path is @e NULL the external token path is reset to default path. + * + * @return @e 0 on success or negative errno value otherwise. + */ +int crypt_token_set_external_path(const char *path); + /** * Disable external token handlers (plugins) support * If disabled, it cannot be enabled again. @@ -2875,6 +3102,55 @@ void crypt_safe_memzero(void *data, size_t size); /** @} */ +/** + * @defgroup crypt-keyring Kernel keyring manipulation + * @addtogroup crypt-keyring + * @{ + */ + +/** + * Link the volume key to the specified kernel keyring. + * + * The volume can have one or two keys. Normally, the device has one key. + * However if reencryption was started and not finished yet, the volume will + * have two volume keys (the new VK for the already reencrypted segment and old + * VK for the not yet reencrypted segment). + * + * The @e old_key_description argument is required only for + * devices that are in re-encryption and have two volume keys at the same time + * (old and new). You can set the @e old_key_description to NULL, + * but if you supply number of keys less than required, the function will + * return -ESRCH. In that case you need to call the function again and set + * the missing key description. When supplying just one key description, make + * sure to supply it in the @e key_description. + * + * @param cd crypt device handle + * @param key_description the key description of the volume key linked in desired keyring. + * @param old_key_description the key description of the old volume key linked in desired keyring + * (for devices in re-encryption). + * @param key_type_desc the key type used for the volume key. Currently only "user" and "logon" types are + * supported. if @e NULL is specified the default "user" type is applied. + * @param keyring_to_link_vk the keyring description of the keyring in which volume key should + * be linked, if @e NULL is specified, linking will be disabled. + * + * @note keyring_to_link_vk may be passed in various string formats: + * It can be kernel key numeric id of existing keyring written as a string, + * keyring name prefixed optionally be either "%:" or "%keyring:" substrings or keyctl + * special values for keyrings "@t", "@p", "@s" and so on. See keyctl(1) man page, + * section KEY IDENTIFIERS for more information. All other prefixes starting "%:" + * are ignored. + * + * @note key_description "%:" prefixes are ignored. Type is applied based on key_type parameter + * value. + */ +int crypt_set_keyring_to_link(struct crypt_device* cd, + const char* key_description, + const char* old_key_description, + const char* key_type_desc, + const char* keyring_to_link_vk); + +/** @} */ + #ifdef __cplusplus } #endif diff --git a/lib/libcryptsetup.sym b/lib/libcryptsetup.sym index d0f0d98..89d6468 100644 --- a/lib/libcryptsetup.sym +++ b/lib/libcryptsetup.sym @@ -165,3 +165,18 @@ CRYPTSETUP_2.6 { crypt_keyslot_add_by_keyslot_context; crypt_volume_key_get_by_keyslot_context; } CRYPTSETUP_2.5; + +CRYPTSETUP_2.7 { + global: + crypt_activate_by_keyslot_context; + crypt_format_luks2_opal; + crypt_get_hw_encryption_type; + crypt_get_hw_encryption_key_size; + crypt_keyslot_context_init_by_keyring; + crypt_keyslot_context_init_by_vk_in_keyring; + crypt_keyslot_context_init_by_signed_key; + crypt_resume_by_keyslot_context; + crypt_token_set_external_path; + crypt_set_keyring_to_link; + crypt_wipe_hw_opal; +} CRYPTSETUP_2.6; diff --git a/lib/libcryptsetup_macros.h b/lib/libcryptsetup_macros.h index 55187ab..89c1e10 100644 --- a/lib/libcryptsetup_macros.h +++ b/lib/libcryptsetup_macros.h @@ -1,8 +1,8 @@ /* * Definitions of common constant and generic macros of libcryptsetup * - * Copyright (C) 2009-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2009-2023 Milan Broz + * Copyright (C) 2009-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2009-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/lib/libcryptsetup_symver.h b/lib/libcryptsetup_symver.h index a5aa8f9..3ea31bf 100644 --- a/lib/libcryptsetup_symver.h +++ b/lib/libcryptsetup_symver.h @@ -1,7 +1,7 @@ /* * Helpers for defining versioned symbols * - * Copyright (C) 2021-2023 Red Hat, Inc. All rights reserved. + * Copyright (C) 2021-2024 Red Hat, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/lib/libdevmapper.c b/lib/libdevmapper.c index 9c5fc0c..ebee542 100644 --- a/lib/libdevmapper.c +++ b/lib/libdevmapper.c @@ -3,8 +3,8 @@ * * Copyright (C) 2004 Jana Saout * Copyright (C) 2004-2007 Clemens Fruhwirth - * Copyright (C) 2009-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2009-2023 Milan Broz + * Copyright (C) 2009-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2009-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -602,7 +602,8 @@ static char *get_dm_crypt_params(const struct dm_target *tgt, uint32_t flags) hexkey = crypt_safe_alloc(keystr_len); if (!hexkey) goto out; - r = snprintf(hexkey, keystr_len, ":%zu:logon:%s", tgt->u.crypt.vk->keylength, tgt->u.crypt.vk->key_description); + r = snprintf(hexkey, keystr_len, ":%zu:logon:%s", tgt->u.crypt.vk->keylength, + tgt->u.crypt.vk->key_description); if (r < 0 || r >= keystr_len) goto out; } else @@ -1330,7 +1331,15 @@ static int _dm_create_device(struct crypt_device *cd, const char *name, const ch goto out; if (!dm_task_run(dmt)) { - r = dm_status_device(cd, name);; + + r = -dm_task_get_errno(dmt); + if (r == -ENOKEY || r == -EKEYREVOKED || r == -EKEYEXPIRED) { + /* propagate DM errors around key management as such */ + r = -ENOKEY; + goto out; + } + + r = dm_status_device(cd, name); if (r >= 0) r = -EEXIST; if (r != -EEXIST && r != -ENODEV) @@ -1663,6 +1672,11 @@ int dm_create_device(struct crypt_device *cd, const char *name, log_err(cd, _("Requested sector_size option is not supported.")); r = -EINVAL; } + if (dmd->segment.u.crypt.sector_size > SECTOR_SIZE && + dmd->size % dmd->segment.u.crypt.sector_size) { + log_err(cd, _("The device size is not multiple of the requested sector size.")); + r = -EINVAL; + } } if (dmd->segment.type == DM_INTEGRITY && (dmd->flags & CRYPT_ACTIVATE_RECALCULATE) && @@ -2829,7 +2843,7 @@ static int _process_deps(struct crypt_device *cd, const char *prefix, struct dm_ int dm_device_deps(struct crypt_device *cd, const char *name, const char *prefix, char **names, size_t names_length) { - struct dm_task *dmt; + struct dm_task *dmt = NULL; struct dm_info dmi; struct dm_deps *deps; int r = -EINVAL; @@ -2989,7 +3003,8 @@ int dm_resume_and_reinstate_key(struct crypt_device *cd, const char *name, } if (vk->key_description) { - r = snprintf(msg, msg_size, "key set :%zu:logon:%s", vk->keylength, vk->key_description); + r = snprintf(msg, msg_size, "key set :%zu:logon:%s", vk->keylength, + vk->key_description); } else { key = crypt_bytes_to_hex(vk->keylength, vk->key); if (!key) { @@ -3026,6 +3041,18 @@ const char *dm_get_dir(void) return dm_dir(); } +int dm_get_iname(const char *name, char **iname, bool with_path) +{ + int r; + + if (with_path) + r = asprintf(iname, "%s/%s_dif", dm_get_dir(), name); + else + r = asprintf(iname, "%s_dif", name); + + return r < 0 ? -ENOMEM : 0; +} + int dm_is_dm_device(int major) { return dm_is_dm_major((uint32_t)major); diff --git a/lib/loopaes/loopaes.c b/lib/loopaes/loopaes.c index 224d3d0..4ff4fc9 100644 --- a/lib/loopaes/loopaes.c +++ b/lib/loopaes/loopaes.c @@ -1,8 +1,8 @@ /* * loop-AES compatible volume handling * - * Copyright (C) 2011-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2011-2023 Milan Broz + * Copyright (C) 2011-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2011-2024 Milan Broz * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/lib/loopaes/loopaes.h b/lib/loopaes/loopaes.h index a921694..fe9e71c 100644 --- a/lib/loopaes/loopaes.h +++ b/lib/loopaes/loopaes.h @@ -1,8 +1,8 @@ /* * loop-AES compatible volume handling * - * Copyright (C) 2011-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2011-2023 Milan Broz + * Copyright (C) 2011-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2011-2024 Milan Broz * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/lib/luks1/af.c b/lib/luks1/af.c index 76afeac..cafa468 100644 --- a/lib/luks1/af.c +++ b/lib/luks1/af.c @@ -2,7 +2,7 @@ * AFsplitter - Anti forensic information splitter * * Copyright (C) 2004 Clemens Fruhwirth - * Copyright (C) 2009-2023 Red Hat, Inc. All rights reserved. + * Copyright (C) 2009-2024 Red Hat, Inc. All rights reserved. * * AFsplitter diffuses information over a large stripe of data, * therefore supporting secure data destruction. diff --git a/lib/luks1/af.h b/lib/luks1/af.h index 8a2bceb..efc1133 100644 --- a/lib/luks1/af.h +++ b/lib/luks1/af.h @@ -2,7 +2,7 @@ * AFsplitter - Anti forensic information splitter * * Copyright (C) 2004 Clemens Fruhwirth - * Copyright (C) 2009-2023 Red Hat, Inc. All rights reserved. + * Copyright (C) 2009-2024 Red Hat, Inc. All rights reserved. * * AFsplitter diffuses information over a large stripe of data, * therefore supporting secure data destruction. diff --git a/lib/luks1/keyencryption.c b/lib/luks1/keyencryption.c index c1c8201..64fdf2d 100644 --- a/lib/luks1/keyencryption.c +++ b/lib/luks1/keyencryption.c @@ -2,8 +2,8 @@ * LUKS - Linux Unified Key Setup * * Copyright (C) 2004-2006 Clemens Fruhwirth - * Copyright (C) 2009-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2012-2023 Milan Broz + * Copyright (C) 2009-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2012-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/lib/luks1/keymanage.c b/lib/luks1/keymanage.c index fe49a00..24ab160 100644 --- a/lib/luks1/keymanage.c +++ b/lib/luks1/keymanage.c @@ -2,8 +2,8 @@ * LUKS - Linux Unified Key Setup * * Copyright (C) 2004-2006 Clemens Fruhwirth - * Copyright (C) 2009-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2013-2023 Milan Broz + * Copyright (C) 2009-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2013-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/lib/luks1/luks.h b/lib/luks1/luks.h index 9c3f386..74cb7a7 100644 --- a/lib/luks1/luks.h +++ b/lib/luks1/luks.h @@ -2,7 +2,7 @@ * LUKS - Linux Unified Key Setup * * Copyright (C) 2004-2006 Clemens Fruhwirth - * Copyright (C) 2009-2023 Red Hat, Inc. All rights reserved. + * Copyright (C) 2009-2024 Red Hat, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/lib/luks2/hw_opal/hw_opal.c b/lib/luks2/hw_opal/hw_opal.c new file mode 100644 index 0000000..31ef87e --- /dev/null +++ b/lib/luks2/hw_opal/hw_opal.c @@ -0,0 +1,1089 @@ +/* + * OPAL utilities + * + * Copyright (C) 2022-2023 Luca Boccassi + * 2023 Ondrej Kozina + * + * This file is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This file 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_SYSMACROS_H +# include /* for major, minor */ +#endif + +#include "internal.h" +#include "libcryptsetup.h" +#include "luks2/hw_opal/hw_opal.h" +#include "utils_device_locking.h" + +#if HAVE_HW_OPAL + +#include + +/* Error codes are defined in the specification: + * TCG_Storage_Architecture_Core_Spec_v2.01_r1.00 + * Section 5.1.5: Method Status Codes + * Names and values from table 166 */ +typedef enum OpalStatus { + OPAL_STATUS_SUCCESS, + OPAL_STATUS_NOT_AUTHORIZED, + OPAL_STATUS_OBSOLETE0, /* Undefined but possible return values are called 'obsolete' */ + OPAL_STATUS_SP_BUSY, + OPAL_STATUS_SP_FAILED, + OPAL_STATUS_SP_DISABLED, + OPAL_STATUS_SP_FROZEN, + OPAL_STATUS_NO_SESSIONS_AVAILABLE, + OPAL_STATUS_UNIQUENESS_CONFLICT, + OPAL_STATUS_INSUFFICIENT_SPACE, + OPAL_STATUS_INSUFFICIENT_ROWS, + OPAL_STATUS_INVALID_PARAMETER, + OPAL_STATUS_OBSOLETE1, + OPAL_STATUS_OBSOLETE2, + OPAL_STATUS_TPER_MALFUNCTION, + OPAL_STATUS_TRANSACTION_FAILURE, + OPAL_STATUS_RESPONSE_OVERFLOW, + OPAL_STATUS_AUTHORITY_LOCKED_OUT, + OPAL_STATUS_FAIL = 0x3F, /* As defined by specification */ + _OPAL_STATUS_MAX, + _OPAL_STATUS_INVALID = -EINVAL, +} OpalStatus; + +static const char* const opal_status_table[_OPAL_STATUS_MAX] = { + [OPAL_STATUS_SUCCESS] = "success", + [OPAL_STATUS_NOT_AUTHORIZED] = "not authorized", + [OPAL_STATUS_OBSOLETE0] = "obsolete", + [OPAL_STATUS_SP_BUSY] = "SP busy", + [OPAL_STATUS_SP_FAILED] = "SP failed", + [OPAL_STATUS_SP_DISABLED] = "SP disabled", + [OPAL_STATUS_SP_FROZEN] = "SP frozen", + [OPAL_STATUS_NO_SESSIONS_AVAILABLE] = "no sessions available", + [OPAL_STATUS_UNIQUENESS_CONFLICT] = "uniqueness conflict", + [OPAL_STATUS_INSUFFICIENT_SPACE] = "insufficient space", + [OPAL_STATUS_INSUFFICIENT_ROWS] = "insufficient rows", + [OPAL_STATUS_INVALID_PARAMETER] = "invalid parameter", + [OPAL_STATUS_OBSOLETE1] = "obsolete", + [OPAL_STATUS_OBSOLETE2] = "obsolete", + [OPAL_STATUS_TPER_MALFUNCTION] = "TPer malfunction", + [OPAL_STATUS_TRANSACTION_FAILURE] = "transaction failure", + [OPAL_STATUS_RESPONSE_OVERFLOW] = "response overflow", + [OPAL_STATUS_AUTHORITY_LOCKED_OUT] = "authority locked out", + [OPAL_STATUS_FAIL] = "unknown failure", +}; + +static const char *opal_status_to_string(int t) +{ + if (t < 0) + return strerror(-t); + + if (t >= _OPAL_STATUS_MAX) + return "unknown error"; + + return opal_status_table[t]; +} + +static const char *opal_ioctl_to_string(unsigned long rq) +{ + switch(rq) { + case IOC_OPAL_GET_STATUS: return "GET_STATUS"; + case IOC_OPAL_GET_GEOMETRY: return "GET_GEOMETRY"; + case IOC_OPAL_GET_LR_STATUS: return "GET_LR_STATUS"; + case IOC_OPAL_TAKE_OWNERSHIP: return "TAKE_OWNERSHIP"; + case IOC_OPAL_ACTIVATE_USR: return "ACTIVATE_USR"; + case IOC_OPAL_ACTIVATE_LSP: return "ACTIVATE_LSP"; + case IOC_OPAL_ERASE_LR: return "ERASE_LR"; + case IOC_OPAL_SECURE_ERASE_LR: return "SECURE_ERASE_LR"; + case IOC_OPAL_ADD_USR_TO_LR: return "ADD_USR_TO_LR"; + case IOC_OPAL_SET_PW: return "SET_PW"; + case IOC_OPAL_LR_SETUP: return "LR_SETUP"; + case IOC_OPAL_LOCK_UNLOCK: return "LOCK_UNLOCK"; + case IOC_OPAL_SAVE: return "SAVE"; + case IOC_OPAL_PSID_REVERT_TPR: return "PSID_REVERT_TPR"; + } + + assert(false && "unknown OPAL ioctl"); + return NULL; +} + +static void opal_ioctl_debug(struct crypt_device *cd, + unsigned long rq, + void *args, + bool post, + int ret) +{ + const char *cmd = opal_ioctl_to_string(rq); + + if (ret) { + log_dbg(cd, "OPAL %s failed: %s", cmd, opal_status_to_string(ret)); + return; + } + + if (post) switch(rq) { + case IOC_OPAL_GET_STATUS: { /* OUT */ + struct opal_status *st = args; + log_dbg(cd, "OPAL %s: flags:%" PRIu32, cmd, st->flags); + }; + break; + case IOC_OPAL_GET_GEOMETRY: { /* OUT */ + struct opal_geometry *geo = args; + log_dbg(cd, "OPAL %s: align:%" PRIu8 ", lb_size:%" PRIu32 ", gran:%" PRIu64 ", lowest_lba:%" PRIu64, + cmd, geo->align, geo->logical_block_size, geo->alignment_granularity, geo->lowest_aligned_lba); + }; + break; + case IOC_OPAL_GET_LR_STATUS: { /* OUT */ + struct opal_lr_status *lrs = args; + log_dbg(cd, "OPAL %s: sum:%" PRIu32 ", who:%" PRIu32 ", lr:%" PRIu8 + ", start:%" PRIu64 ", length:%" PRIu64 ", rle:%" PRIu32 ", rwe:%" PRIu32 ", state:%" PRIu32, + cmd, lrs->session.sum, lrs->session.who, lrs->session.opal_key.lr, + lrs->range_start, lrs->range_length, lrs->RLE, lrs->WLE, lrs->l_state); + }; + break; + } else switch (rq) { + case IOC_OPAL_TAKE_OWNERSHIP: { /* IN */ + log_dbg(cd, "OPAL %s", cmd); + }; + break; + case IOC_OPAL_ACTIVATE_USR: { /* IN */ + struct opal_session_info *ui = args; + log_dbg(cd, "OPAL %s: sum:%" PRIu32 ", who:%" PRIu32 ", lr:%" PRIu8, + cmd, ui->sum, ui->who, ui->opal_key.lr); + }; + break; + case IOC_OPAL_ACTIVATE_LSP: { /* IN */ + struct opal_lr_act *act = args; + log_dbg(cd, "OPAL %s: k.lr:%" PRIu8 ", sum:%" PRIu32 ", num_lrs:%" PRIu8 ", lr:" + "%"PRIu8"|%"PRIu8"|%"PRIu8"|%"PRIu8"|%"PRIu8"|%"PRIu8"|%"PRIu8"|%"PRIu8"|%"PRIu8, + cmd, act->key.lr, act->sum, act->num_lrs, + act->lr[0], act->lr[1], act->lr[2], act->lr[3], act->lr[4], + act->lr[5], act->lr[6], act->lr[7], act->lr[8]); + }; + break; + case IOC_OPAL_ERASE_LR: { /* IN */ + struct opal_session_info *ui = args; + log_dbg(cd, "OPAL %s: sum:%" PRIu32 ", who:%" PRIu32 ", lr:%" PRIu8, + cmd, ui->sum, ui->who, ui->opal_key.lr); + }; + break; + case IOC_OPAL_SECURE_ERASE_LR: { /* IN */ + struct opal_session_info *ui = args; + log_dbg(cd, "OPAL %s: sum:%" PRIu32 ", who:%" PRIu32 ", lr:%" PRIu8, + cmd, ui->sum, ui->who, ui->opal_key.lr); + }; + break; + case IOC_OPAL_ADD_USR_TO_LR: { /* IN */ + struct opal_lock_unlock *lu = args; + log_dbg(cd, "OPAL %s: sum:%" PRIu32 ", who:%" PRIu32 ", lr:%" PRIu8 + ", l_state:%" PRIu32 ", flags:%" PRIu16, + cmd, lu->session.sum, lu->session.who, lu->session.opal_key.lr, + lu->l_state, lu->flags); + }; + break; + case IOC_OPAL_SET_PW: { /* IN */ + struct opal_new_pw *pw = args; + log_dbg(cd, "OPAL %s: sum:%" PRIu32 ", who:%" PRIu32 ", lr:%" PRIu8, + cmd, pw->session.sum, pw->session.who, pw->session.opal_key.lr); + }; + break; + case IOC_OPAL_LR_SETUP: { /* IN */ + struct opal_user_lr_setup *lrs = args; + log_dbg(cd, "OPAL %s: sum:%" PRIu32 ", who:%" PRIu32 ", lr:%" PRIu8 + ", start:%" PRIu64 ", length:%" PRIu64 ", rle:%" PRIu32 ", rwe:%" PRIu32, + cmd, lrs->session.sum, lrs->session.who, lrs->session.opal_key.lr, + lrs->range_start, lrs->range_length, lrs->RLE, lrs->WLE); + }; + break; + case IOC_OPAL_LOCK_UNLOCK: { /* IN */ + struct opal_lock_unlock *lu = args; + log_dbg(cd, "OPAL %s: sum:%" PRIu32 ", who:%" PRIu32 ", lr:%" PRIu8 + ", l_state:%" PRIu32 ", flags:%" PRIu16, + cmd, lu->session.sum, lu->session.who, lu->session.opal_key.lr, + lu->l_state, lu->flags); + }; + break; + case IOC_OPAL_SAVE: { /* IN */ + struct opal_lock_unlock *lu = args; + log_dbg(cd, "OPAL %s: sum:%" PRIu32 ", who:%" PRIu32 ", lr:%" PRIu8 + ", l_state:%" PRIu32 ", flags:%" PRIu16, + cmd, lu->session.sum, lu->session.who, lu->session.opal_key.lr, + lu->l_state, lu->flags); + }; + break; + case IOC_OPAL_PSID_REVERT_TPR: { /* IN */ + struct opal_key *key = args; + log_dbg(cd, "OPAL %s: lr:%" PRIu8, + cmd, key->lr); + }; + break; + } +} + +static int opal_ioctl(struct crypt_device *cd, int fd, unsigned long rq, void *args) +{ + int r; + + opal_ioctl_debug(cd, rq, args, false, 0); + r = ioctl(fd, rq, args); + opal_ioctl_debug(cd, rq, args, true, r); + + return r; +} + +static int opal_geometry_fd(struct crypt_device *cd, + int fd, + bool *ret_align, + uint32_t *ret_block_size, + uint64_t *ret_alignment_granularity_blocks, + uint64_t *ret_lowest_lba_blocks) +{ + int r; + struct opal_geometry geo; + + assert(fd >= 0); + + r = opal_ioctl(cd, fd, IOC_OPAL_GET_GEOMETRY, &geo); + if (r != OPAL_STATUS_SUCCESS) + return r; + + if (ret_align) + *ret_align = (geo.align == 1); + if (ret_block_size) + *ret_block_size = geo.logical_block_size; + if (ret_alignment_granularity_blocks) + *ret_alignment_granularity_blocks = geo.alignment_granularity; + if (ret_lowest_lba_blocks) + *ret_lowest_lba_blocks = geo.lowest_aligned_lba; + + return r; +} + +static int opal_range_check_attributes_fd(struct crypt_device *cd, + int fd, + uint32_t segment_number, + const struct volume_key *vk, + const uint64_t *check_offset_sectors, + const uint64_t *check_length_sectors, + bool *check_read_locked, + bool *check_write_locked, + bool *ret_read_locked, + bool *ret_write_locked) +{ + int r; + struct opal_lr_status *lrs; + uint32_t opal_block_bytes = 0; + uint64_t offset, length; + bool read_locked, write_locked; + + assert(fd >= 0); + assert(cd); + assert(vk); + + if (check_offset_sectors || check_length_sectors) { + r = opal_geometry_fd(cd, fd, NULL, &opal_block_bytes, NULL, NULL); + if (r != OPAL_STATUS_SUCCESS) + return -EINVAL; + } + + lrs = crypt_safe_alloc(sizeof(*lrs)); + if (!lrs) + return -ENOMEM; + + *lrs = (struct opal_lr_status) { + .session = { + .who = segment_number + 1, + .opal_key = { + .key_len = vk->keylength, + .lr = segment_number + } + } + }; + memcpy(lrs->session.opal_key.key, vk->key, vk->keylength); + + r = opal_ioctl(cd, fd, IOC_OPAL_GET_LR_STATUS, lrs); + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to get locking range status on device '%s'.", + crypt_get_device_name(cd)); + r = -EINVAL; + goto out; + } + + r = 0; + + if (check_offset_sectors) { + offset = lrs->range_start * opal_block_bytes / SECTOR_SIZE; + if (offset != *check_offset_sectors) { + log_err(cd, _("OPAL range %d offset %" PRIu64 " does not match expected values %" PRIu64 "."), + segment_number, offset, *check_offset_sectors); + r = -EINVAL; + } + } + + if (check_length_sectors) { + length = lrs->range_length * opal_block_bytes / SECTOR_SIZE; + if (length != *check_length_sectors) { + log_err(cd, _("OPAL range %d length %" PRIu64" does not match device length %" PRIu64 "."), + segment_number, length, *check_length_sectors); + r = -EINVAL; + } + } + + if (!lrs->RLE || !lrs->WLE) { + log_err(cd, _("OPAL range %d locking is disabled."), segment_number); + r = -EINVAL; + } + + read_locked = (lrs->l_state == OPAL_LK); + write_locked = !!(lrs->l_state & (OPAL_RO | OPAL_LK)); + + if (check_read_locked && (read_locked != *check_read_locked)) { + log_dbg(cd, "OPAL range %d read lock is %slocked.", + segment_number, *check_read_locked ? "" : "not "); + log_err(cd, _("Unexpected OPAL range %d lock state."), segment_number); + r = -EINVAL; + } + + if (check_write_locked && (write_locked != *check_write_locked)) { + log_dbg(cd, "OPAL range %d write lock is %slocked.", + segment_number, *check_write_locked ? "" : "not "); + log_err(cd, _("Unexpected OPAL range %d lock state."), segment_number); + r = -EINVAL; + } + + if (ret_read_locked) + *ret_read_locked = read_locked; + if (ret_write_locked) + *ret_write_locked = write_locked; +out: + crypt_safe_free(lrs); + + return r; +} + +static int opal_query_status(struct crypt_device *cd, struct device *dev, unsigned expected) +{ + struct opal_status st = { }; + int fd, r; + + assert(cd); + assert(dev); + + fd = device_open(cd, dev, O_RDONLY); + if (fd < 0) + return -EIO; + + r = opal_ioctl(cd, fd, IOC_OPAL_GET_STATUS, &st); + + return r < 0 ? -EINVAL : (st.flags & expected) ? 1 : 0; +} + +static int opal_enabled(struct crypt_device *cd, struct device *dev) +{ + return opal_query_status(cd, dev, OPAL_FL_LOCKING_ENABLED); +} + +/* requires opal lock */ +int opal_setup_ranges(struct crypt_device *cd, + struct device *dev, + const struct volume_key *vk, + uint64_t range_start, + uint64_t range_length, + uint32_t segment_number, + const void *admin_key, + size_t admin_key_len) +{ + struct opal_lr_act *activate = NULL; + struct opal_session_info *user_session = NULL; + struct opal_lock_unlock *user_add_to_lr = NULL, *lock = NULL; + struct opal_new_pw *new_pw = NULL; + struct opal_user_lr_setup *setup = NULL; + int r, fd; + + assert(cd); + assert(dev); + assert(vk); + assert(admin_key); + assert(vk->keylength <= OPAL_KEY_MAX); + + if (admin_key_len > OPAL_KEY_MAX) + return -EINVAL; + + fd = device_open(cd, dev, O_RDONLY); + if (fd < 0) + return -EIO; + + r = opal_enabled(cd, dev); + if (r < 0) + return r; + + /* If OPAL has never been enabled, we need to take ownership and do basic setup first */ + if (r == 0) { + activate = crypt_safe_alloc(sizeof(struct opal_lr_act)); + if (!activate) { + r = -ENOMEM; + goto out; + } + *activate = (struct opal_lr_act) { + .key = { + .key_len = admin_key_len, + }, + .num_lrs = 8, + /* A max of 9 segments are supported, enable them all as there's no reason not to + * (0 is whole-volume) + */ + .lr = { 1, 2, 3, 4, 5, 6, 7, 8 }, + }; + memcpy(activate->key.key, admin_key, admin_key_len); + + r = opal_ioctl(cd, fd, IOC_OPAL_TAKE_OWNERSHIP, &activate->key); + if (r < 0) { + r = -ENOTSUP; + log_dbg(cd, "OPAL not supported on this kernel version, refusing."); + goto out; + } + if (r == OPAL_STATUS_NOT_AUTHORIZED) /* We'll try again with a different key. */ { + r = -EPERM; + log_dbg(cd, "Failed to take ownership of OPAL device '%s': permission denied", + crypt_get_device_name(cd)); + goto out; + } + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to take ownership of OPAL device '%s': %s", + crypt_get_device_name(cd), opal_status_to_string(r)); + r = -EINVAL; + goto out; + } + + r = opal_ioctl(cd, fd, IOC_OPAL_ACTIVATE_LSP, activate); + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to activate OPAL device '%s': %s", + crypt_get_device_name(cd), opal_status_to_string(r)); + r = -EINVAL; + goto out; + } + } else { + /* If it is already enabled, wipe the locking range first */ + user_session = crypt_safe_alloc(sizeof(struct opal_session_info)); + if (!user_session) { + r = -ENOMEM; + goto out; + } + *user_session = (struct opal_session_info) { + .who = OPAL_ADMIN1, + .opal_key = { + .lr = segment_number, + .key_len = admin_key_len, + }, + }; + memcpy(user_session->opal_key.key, admin_key, admin_key_len); + + r = opal_ioctl(cd, fd, IOC_OPAL_ERASE_LR, user_session); + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to reset (erase) OPAL locking range %u on device '%s': %s", + segment_number, crypt_get_device_name(cd), opal_status_to_string(r)); + r = opal_ioctl(cd, fd, IOC_OPAL_SECURE_ERASE_LR, user_session); + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to reset (secure erase) OPAL locking range %u on device '%s': %s", + segment_number, crypt_get_device_name(cd), opal_status_to_string(r)); + r = -EINVAL; + goto out; + } + } + } + + crypt_safe_free(user_session); + + user_session = crypt_safe_alloc(sizeof(struct opal_session_info)); + if (!user_session) { + r = -ENOMEM; + goto out; + } + *user_session = (struct opal_session_info) { + .who = segment_number + 1, + .opal_key = { + .key_len = admin_key_len, + }, + }; + memcpy(user_session->opal_key.key, admin_key, admin_key_len); + + r = opal_ioctl(cd, fd, IOC_OPAL_ACTIVATE_USR, user_session); + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to activate OPAL user on device '%s': %s", + crypt_get_device_name(cd), opal_status_to_string(r)); + r = -EINVAL; + goto out; + } + + user_add_to_lr = crypt_safe_alloc(sizeof(struct opal_lock_unlock)); + if (!user_add_to_lr) { + r = -ENOMEM; + goto out; + } + *user_add_to_lr = (struct opal_lock_unlock) { + .session = { + .who = segment_number + 1, + .opal_key = { + .lr = segment_number, + .key_len = admin_key_len, + }, + }, + .l_state = OPAL_RO, + }; + memcpy(user_add_to_lr->session.opal_key.key, admin_key, admin_key_len); + + r = opal_ioctl(cd, fd, IOC_OPAL_ADD_USR_TO_LR, user_add_to_lr); + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to add OPAL user to locking range %u (RO) on device '%s': %s", + segment_number, crypt_get_device_name(cd), opal_status_to_string(r)); + r = -EINVAL; + goto out; + } + user_add_to_lr->l_state = OPAL_RW; + r = opal_ioctl(cd, fd, IOC_OPAL_ADD_USR_TO_LR, user_add_to_lr); + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to add OPAL user to locking range %u (RW) on device '%s': %s", + segment_number, crypt_get_device_name(cd), opal_status_to_string(r)); + r = -EINVAL; + goto out; + } + + new_pw = crypt_safe_alloc(sizeof(struct opal_new_pw)); + if (!new_pw) { + r = -ENOMEM; + goto out; + } + *new_pw = (struct opal_new_pw) { + .session = { + .who = OPAL_ADMIN1, + .opal_key = { + .lr = segment_number, + .key_len = admin_key_len, + }, + }, + .new_user_pw = { + .who = segment_number + 1, + .opal_key = { + .key_len = vk->keylength, + .lr = segment_number, + }, + }, + }; + memcpy(new_pw->new_user_pw.opal_key.key, vk->key, vk->keylength); + memcpy(new_pw->session.opal_key.key, admin_key, admin_key_len); + + r = opal_ioctl(cd, fd, IOC_OPAL_SET_PW, new_pw); + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to set OPAL user password on device '%s': (%d) %s", + crypt_get_device_name(cd), r, opal_status_to_string(r)); + r = -EINVAL; + goto out; + } + + setup = crypt_safe_alloc(sizeof(struct opal_user_lr_setup)); + if (!setup) { + r = -ENOMEM; + goto out; + } + *setup = (struct opal_user_lr_setup) { + .range_start = range_start, + .range_length = range_length, + /* Some drives do not enable Locking Ranges on setup. This have some + * interesting consequences: Lock command called later below will pass, + * but locking range will _not_ be locked at all. + */ + .RLE = 1, + .WLE = 1, + .session = { + .who = OPAL_ADMIN1, + .opal_key = { + .key_len = admin_key_len, + .lr = segment_number, + }, + }, + }; + memcpy(setup->session.opal_key.key, admin_key, admin_key_len); + + r = opal_ioctl(cd, fd, IOC_OPAL_LR_SETUP, setup); + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to setup locking range of length %llu at offset %llu on OPAL device '%s': %s", + setup->range_length, setup->range_start, crypt_get_device_name(cd), opal_status_to_string(r)); + r = -EINVAL; + goto out; + } + + /* After setup an OPAL device is unlocked, but the expectation with cryptsetup is that it needs + * to be activated separately, so lock it immediately. */ + lock = crypt_safe_alloc(sizeof(struct opal_lock_unlock)); + if (!lock) { + r = -ENOMEM; + goto out; + } + *lock = (struct opal_lock_unlock) { + .l_state = OPAL_LK, + .session = { + .who = segment_number + 1, + .opal_key = { + .key_len = vk->keylength, + .lr = segment_number, + }, + } + }; + memcpy(lock->session.opal_key.key, vk->key, vk->keylength); + + r = opal_ioctl(cd, fd, IOC_OPAL_LOCK_UNLOCK, lock); + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to lock OPAL device '%s': %s", + crypt_get_device_name(cd), opal_status_to_string(r)); + r = -EINVAL; + goto out; + } + + /* Double check the locking range is locked and the ranges are set up as configured */ + r = opal_range_check_attributes_fd(cd, fd, segment_number, vk, &range_start, + &range_length, &(bool) {true}, &(bool){true}, + NULL, NULL); +out: + crypt_safe_free(activate); + crypt_safe_free(user_session); + crypt_safe_free(user_add_to_lr); + crypt_safe_free(new_pw); + crypt_safe_free(setup); + crypt_safe_free(lock); + + return r; +} + +static int opal_lock_unlock(struct crypt_device *cd, + struct device *dev, + uint32_t segment_number, + const struct volume_key *vk, + bool lock) +{ + struct opal_lock_unlock unlock = { + .l_state = lock ? OPAL_LK : OPAL_RW, + .session = { + .who = segment_number + 1, + .opal_key = { + .lr = segment_number, + }, + }, + }; + int r, fd; + + if (opal_supported(cd, dev) <= 0) + return -ENOTSUP; + if (!lock && !vk) + return -EINVAL; + + fd = device_open(cd, dev, O_RDONLY); + if (fd < 0) + return -EIO; + + if (!lock) { + assert(vk->keylength <= OPAL_KEY_MAX); + + unlock.session.opal_key.key_len = vk->keylength; + memcpy(unlock.session.opal_key.key, vk->key, vk->keylength); + } + + r = opal_ioctl(cd, fd, IOC_OPAL_LOCK_UNLOCK, &unlock); + if (r < 0) { + r = -ENOTSUP; + log_dbg(cd, "OPAL not supported on this kernel version, refusing."); + goto out; + } + if (r == OPAL_STATUS_NOT_AUTHORIZED) /* We'll try again with a different key. */ { + r = -EPERM; + log_dbg(cd, "Failed to %slock OPAL device '%s': permission denied", + lock ? "" : "un", crypt_get_device_name(cd)); + goto out; + } + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to %slock OPAL device '%s': %s", + lock ? "" : "un", crypt_get_device_name(cd), opal_status_to_string(r)); + r = -EINVAL; + goto out; + } + + /* If we are unlocking, also tell the kernel to automatically unlock when resuming + * from suspend, otherwise the drive will be locked and everything will go up in flames. + * Also set the flag to allow locking without having to pass the key again. + * But do not error out if this fails, as the device will already be unlocked. + * + * On a lock path we have to overwrite the cached key from kernel otherwise the locking range + * gets unlocked automatically after system resume even when cryptsetup previously locked it + * on purpose (crypt_deactivate* or crypt_suspend) + */ + if (!lock) + unlock.flags = OPAL_SAVE_FOR_LOCK; + + r = opal_ioctl(cd, fd, IOC_OPAL_SAVE, &unlock); + if (r != OPAL_STATUS_SUCCESS) { + if (!lock) + log_std(cd, "Failed to prepare OPAL device '%s' for sleep resume, be aware before suspending: %s", + crypt_get_device_name(cd), opal_status_to_string(r)); + else + log_std(cd, "Failed to erase OPAL key for device '%s' from kernel: %s", + crypt_get_device_name(cd), opal_status_to_string(r)); + r = 0; + } +out: + if (!lock) + crypt_safe_memzero(unlock.session.opal_key.key, unlock.session.opal_key.key_len); + + return r; +} + +/* requires opal lock */ +int opal_lock(struct crypt_device *cd, struct device *dev, uint32_t segment_number) +{ + return opal_lock_unlock(cd, dev, segment_number, NULL, /* lock= */ true); +} + +/* requires opal lock */ +int opal_unlock(struct crypt_device *cd, + struct device *dev, + uint32_t segment_number, + const struct volume_key *vk) +{ + return opal_lock_unlock(cd, dev, segment_number, vk, /* lock= */ false); +} + +/* + * It does not require opal lock. This completely destroys + * data on whole OPAL block device. Serialization does not + * make sense here. + */ +int opal_factory_reset(struct crypt_device *cd, + struct device *dev, + const char *password, + size_t password_len) +{ + struct opal_key reset = { + .key_len = password_len, + }; + int r, fd; + + assert(cd); + assert(dev); + assert(password); + + if (password_len > OPAL_KEY_MAX) + return -EINVAL; + + fd = device_open(cd, dev, O_RDONLY); + if (fd < 0) + return -EIO; + + memcpy(reset.key, password, password_len); + + r = opal_ioctl(cd, fd, IOC_OPAL_PSID_REVERT_TPR, &reset); + if (r < 0) { + r = -ENOTSUP; + log_dbg(cd, "OPAL not supported on this kernel version, refusing."); + goto out; + } + if (r == OPAL_STATUS_NOT_AUTHORIZED) /* We'll try again with a different key. */ { + r = -EPERM; + log_dbg(cd, "Failed to reset OPAL device '%s', incorrect PSID?", + crypt_get_device_name(cd)); + goto out; + } + if (r != OPAL_STATUS_SUCCESS) { + r = -EINVAL; + log_dbg(cd, "Failed to reset OPAL device '%s' with PSID: %s", + crypt_get_device_name(cd), opal_status_to_string(r)); + goto out; + } +out: + crypt_safe_memzero(reset.key, reset.key_len); + + return r; +} + +/* requires opal lock */ +int opal_reset_segment(struct crypt_device *cd, + struct device *dev, + uint32_t segment_number, + const char *password, + size_t password_len) +{ + struct opal_session_info *user_session = NULL; + struct opal_user_lr_setup *setup = NULL; + int r, fd; + + assert(cd); + assert(dev); + assert(password); + + if (password_len > OPAL_KEY_MAX) + return -EINVAL; + + if (opal_enabled(cd, dev) <= 0) + return -EINVAL; + + user_session = crypt_safe_alloc(sizeof(struct opal_session_info)); + if (!user_session) + return -ENOMEM; + *user_session = (struct opal_session_info) { + .who = OPAL_ADMIN1, + .opal_key = { + .lr = segment_number, + .key_len = password_len, + }, + }; + memcpy(user_session->opal_key.key, password, password_len); + + fd = device_open(cd, dev, O_RDONLY); + if (fd < 0) { + r = -EIO; + goto out; + } + + r = opal_ioctl(cd, fd, IOC_OPAL_ERASE_LR, user_session); + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to reset (erase) OPAL locking range %u on device '%s': %s", + segment_number, crypt_get_device_name(cd), opal_status_to_string(r)); + r = opal_ioctl(cd, fd, IOC_OPAL_SECURE_ERASE_LR, user_session); + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to reset (secure erase) OPAL locking range %u on device '%s': %s", + segment_number, crypt_get_device_name(cd), opal_status_to_string(r)); + r = -EINVAL; + goto out; + } + + /* Unlike IOC_OPAL_ERASE_LR, IOC_OPAL_SECURE_ERASE_LR does not disable the locking range, + * we have to do that by hand. + */ + setup = crypt_safe_alloc(sizeof(struct opal_user_lr_setup)); + if (!setup) { + r = -ENOMEM; + goto out; + } + *setup = (struct opal_user_lr_setup) { + .range_start = 0, + .range_length = 0, + .session = { + .who = OPAL_ADMIN1, + .opal_key = user_session->opal_key, + }, + }; + + r = opal_ioctl(cd, fd, IOC_OPAL_LR_SETUP, setup); + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to disable locking range on OPAL device '%s': %s", + crypt_get_device_name(cd), opal_status_to_string(r)); + r = -EINVAL; + goto out; + } + } +out: + crypt_safe_free(user_session); + crypt_safe_free(setup); + + return r; +} + +/* + * Does not require opal lock (immutable). + */ +int opal_supported(struct crypt_device *cd, struct device *dev) +{ + return opal_query_status(cd, dev, OPAL_FL_SUPPORTED|OPAL_FL_LOCKING_SUPPORTED); +} + +/* + * Does not require opal lock (immutable). + */ +int opal_geometry(struct crypt_device *cd, + struct device *dev, + bool *ret_align, + uint32_t *ret_block_size, + uint64_t *ret_alignment_granularity_blocks, + uint64_t *ret_lowest_lba_blocks) +{ + int fd; + + assert(cd); + assert(dev); + + fd = device_open(cd, dev, O_RDONLY); + if (fd < 0) + return -EIO; + + return opal_geometry_fd(cd, fd, ret_align, ret_block_size, + ret_alignment_granularity_blocks, ret_lowest_lba_blocks); +} + +/* requires opal lock */ +int opal_range_check_attributes_and_get_lock_state(struct crypt_device *cd, + struct device *dev, + uint32_t segment_number, + const struct volume_key *vk, + const uint64_t *check_offset_sectors, + const uint64_t *check_length_sectors, + bool *ret_read_locked, + bool *ret_write_locked) +{ + int fd; + + assert(cd); + assert(dev); + assert(vk); + + fd = device_open(cd, dev, O_RDONLY); + if (fd < 0) + return -EIO; + + return opal_range_check_attributes_fd(cd, fd, segment_number, vk, + check_offset_sectors, check_length_sectors, NULL, + NULL, ret_read_locked, ret_write_locked); +} + +static int opal_lock_internal(struct crypt_device *cd, struct device *opal_device, struct crypt_lock_handle **opal_lock) +{ + char *lock_resource; + int devfd, r; + struct stat st; + + if (!crypt_metadata_locking_enabled()) { + *opal_lock = NULL; + return 0; + } + + /* + * This also asserts we do not hold any metadata lock on the same device to + * avoid deadlock (OPAL lock must be taken first) + */ + devfd = device_open(cd, opal_device, O_RDONLY); + if (devfd < 0) + return -EINVAL; + + if (fstat(devfd, &st) || !S_ISBLK(st.st_mode)) + return -EINVAL; + + r = asprintf(&lock_resource, "OPAL_%d:%d", major(st.st_rdev), minor(st.st_rdev)); + if (r < 0) + return -ENOMEM; + + r = crypt_write_lock(cd, lock_resource, true, opal_lock); + + free(lock_resource); + + return r; +} + +int opal_exclusive_lock(struct crypt_device *cd, struct device *opal_device, struct crypt_lock_handle **opal_lock) +{ + if (!cd || !opal_device || (crypt_get_type(cd) && strcmp(crypt_get_type(cd), CRYPT_LUKS2))) + return -EINVAL; + + return opal_lock_internal(cd, opal_device, opal_lock); +} + +void opal_exclusive_unlock(struct crypt_device *cd, struct crypt_lock_handle *opal_lock) +{ + crypt_unlock_internal(cd, opal_lock); +} + +#else +#pragma GCC diagnostic ignored "-Wunused-parameter" + +int opal_setup_ranges(struct crypt_device *cd, + struct device *dev, + const struct volume_key *vk, + uint64_t range_start, + uint64_t range_length, + uint32_t segment_number, + const void *admin_key, + size_t admin_key_len) +{ + return -ENOTSUP; +} + +int opal_lock(struct crypt_device *cd, struct device *dev, uint32_t segment_number) +{ + return -ENOTSUP; +} + +int opal_unlock(struct crypt_device *cd, + struct device *dev, + uint32_t segment_number, + const struct volume_key *vk) +{ + return -ENOTSUP; +} + +int opal_supported(struct crypt_device *cd, struct device *dev) +{ + return -ENOTSUP; +} + +int opal_factory_reset(struct crypt_device *cd, + struct device *dev, + const char *password, + size_t password_len) +{ + return -ENOTSUP; +} + +int opal_reset_segment(struct crypt_device *cd, + struct device *dev, + uint32_t segment_number, + const char *password, + size_t password_len) +{ + return -ENOTSUP; +} + +int opal_geometry(struct crypt_device *cd, + struct device *dev, + bool *ret_align, + uint32_t *ret_block_size, + uint64_t *ret_alignment_granularity_blocks, + uint64_t *ret_lowest_lba_blocks) +{ + return -ENOTSUP; +} + +int opal_range_check_attributes_and_get_lock_state(struct crypt_device *cd, + struct device *dev, + uint32_t segment_number, + const struct volume_key *vk, + const uint64_t *check_offset_sectors, + const uint64_t *check_length_sectors, + bool *ret_read_locked, + bool *ret_write_locked) +{ + return -ENOTSUP; +} + +int opal_exclusive_lock(struct crypt_device *cd, struct device *opal_device, struct crypt_lock_handle **opal_lock) +{ + return -ENOTSUP; +} + +void opal_exclusive_unlock(struct crypt_device *cd, struct crypt_lock_handle *opal_lock) +{ +} + +#endif diff --git a/lib/luks2/hw_opal/hw_opal.h b/lib/luks2/hw_opal/hw_opal.h new file mode 100644 index 0000000..f1823bf --- /dev/null +++ b/lib/luks2/hw_opal/hw_opal.h @@ -0,0 +1,71 @@ +/* + * OPAL utilities + * + * Copyright (C) 2022-2023 Luca Boccassi + * 2023 Ondrej Kozina + * + * This file is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This file 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _UTILS_OPAL +#define _UTILS_OPAL + +#include "internal.h" + +struct crypt_lock_handle; + +int opal_setup_ranges(struct crypt_device *cd, + struct device *dev, + const struct volume_key *vk, + uint64_t range_start, + uint64_t range_length, + uint32_t segment_number, + const void *admin_key, + size_t admin_key_len); +int opal_lock(struct crypt_device *cd, struct device *dev, uint32_t segment_number); +int opal_unlock(struct crypt_device *cd, + struct device *dev, + uint32_t segment_number, + const struct volume_key *vk); +int opal_supported(struct crypt_device *cd, struct device *dev); +int opal_factory_reset(struct crypt_device *cd, + struct device *dev, + const char *password, + size_t password_len); +int opal_reset_segment(struct crypt_device *cd, + struct device *dev, + uint32_t segment_number, + const char *password, + size_t password_len); +int opal_geometry(struct crypt_device *cd, + struct device *dev, + bool *ret_align, + uint32_t *ret_block_size, + uint64_t *ret_alignment_granularity_blocks, + uint64_t *ret_lowest_lba_blocks); +int opal_range_check_attributes_and_get_lock_state(struct crypt_device *cd, + struct device *dev, + uint32_t segment_number, + const struct volume_key *vk, + const uint64_t *check_offset_sectors, + const uint64_t *check_length_sectors, + bool *ret_read_locked, + bool *ret_write_locked); +int opal_exclusive_lock(struct crypt_device *cd, + struct device *opal_device, + struct crypt_lock_handle **opal_lock); +void opal_exclusive_unlock(struct crypt_device *cd, struct crypt_lock_handle *opal_lock); + +#endif diff --git a/lib/luks2/luks2.h b/lib/luks2/luks2.h index dfccf02..25ae1dd 100644 --- a/lib/luks2/luks2.h +++ b/lib/luks2/luks2.h @@ -1,8 +1,8 @@ /* * LUKS - Linux Unified Key Setup v2 * - * Copyright (C) 2015-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2015-2023 Milan Broz + * Copyright (C) 2015-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2015-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -224,8 +224,7 @@ int LUKS2_keyslot_store(struct crypt_device *cd, int LUKS2_keyslot_wipe(struct crypt_device *cd, struct luks2_hdr *hdr, - int keyslot, - int wipe_area_only); + int keyslot); crypt_keyslot_priority LUKS2_keyslot_priority_get(struct luks2_hdr *hdr, int keyslot); @@ -277,6 +276,7 @@ crypt_token_info LUKS2_token_status(struct crypt_device *cd, int LUKS2_token_open_and_activate(struct crypt_device *cd, struct luks2_hdr *hdr, + int keyslot, int token, const char *name, const char *type, @@ -287,6 +287,7 @@ int LUKS2_token_open_and_activate(struct crypt_device *cd, int LUKS2_token_unlock_key(struct crypt_device *cd, struct luks2_hdr *hdr, + int keyslot, int token, const char *type, const char *pin, @@ -359,7 +360,8 @@ int LUKS2_digest_create(struct crypt_device *cd, */ int LUKS2_activate(struct crypt_device *cd, const char *name, - struct volume_key *vk, + struct volume_key *crypt_key, + struct volume_key *opal_key, uint32_t flags); int LUKS2_activate_multi(struct crypt_device *cd, @@ -378,16 +380,23 @@ int LUKS2_generate_hdr( struct crypt_device *cd, struct luks2_hdr *hdr, const struct volume_key *vk, - const char *cipherName, - const char *cipherMode, + const char *cipher_spec, const char *integrity, const char *uuid, unsigned int sector_size, uint64_t data_offset, - uint64_t align_offset, - uint64_t required_alignment, - uint64_t metadata_size, - uint64_t keyslots_size); + uint64_t metadata_size_bytes, + uint64_t keyslots_size_bytes, + uint64_t device_size_bytes, + uint32_t opal_segment_number, + uint32_t opal_key_size); + +int LUKS2_hdr_get_storage_params(struct crypt_device *cd, + uint64_t alignment_offset_bytes, + uint64_t alignment_bytes, + uint64_t *ret_metadata_size_bytes, + uint64_t *ret_keyslots_size_bytes, + uint64_t *ret_data_offset_bytes); int LUKS2_check_metadata_area_size(uint64_t metadata_size); int LUKS2_check_keyslots_area_size(uint64_t keyslots_size); @@ -414,6 +423,12 @@ int LUKS2_keyslot_area(struct luks2_hdr *hdr, uint64_t *length); int LUKS2_keyslot_pbkdf(struct luks2_hdr *hdr, int keyslot, struct crypt_pbkdf_type *pbkdf); +int LUKS2_split_crypt_and_opal_keys(struct crypt_device *cd, + struct luks2_hdr *hdr, + const struct volume_key *vk, + struct volume_key **ret_crypt_key, + struct volume_key **ret_opal_key); + /* * Permanent activation flags stored in header */ @@ -457,6 +472,9 @@ int LUKS2_reencrypt_locked_recovery_by_passphrase(struct crypt_device *cd, size_t passphrase_size, struct volume_key **vks); +int LUKS2_reencrypt_locked_recovery_by_vks(struct crypt_device *cd, + struct volume_key *vks); + void LUKS2_reencrypt_free(struct crypt_device *cd, struct luks2_reencrypt *rh); @@ -479,9 +497,13 @@ int LUKS2_reencrypt_check_device_size(struct crypt_device *cd, struct luks2_hdr *hdr, uint64_t check_size, uint64_t *dev_size, - bool activation, + bool device_exclusive_check, bool dynamic); +void LUKS2_reencrypt_lookup_key_ids(struct crypt_device *cd, + struct luks2_hdr *hdr, + struct volume_key *vk); + int LUKS2_reencrypt_digest_verify(struct crypt_device *cd, struct luks2_hdr *hdr, struct volume_key *vks); diff --git a/lib/luks2/luks2_digest.c b/lib/luks2/luks2_digest.c index 933b059..293df3e 100644 --- a/lib/luks2/luks2_digest.c +++ b/lib/luks2/luks2_digest.c @@ -1,8 +1,8 @@ /* * LUKS - Linux Unified Key Setup v2, digest handling * - * Copyright (C) 2015-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2015-2023 Milan Broz + * Copyright (C) 2015-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2015-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -157,7 +157,7 @@ int LUKS2_digest_dump(struct crypt_device *cd, int digest) } int LUKS2_digest_any_matching(struct crypt_device *cd, - struct luks2_hdr *hdr, + struct luks2_hdr *hdr __attribute__((unused)), const struct volume_key *vk) { int digest; @@ -174,6 +174,18 @@ int LUKS2_digest_verify_by_segment(struct crypt_device *cd, int segment, const struct volume_key *vk) { + int r = -EINVAL; + unsigned s; + + if (segment == CRYPT_ANY_SEGMENT) { + for (s = 0; s < json_segments_count(LUKS2_get_segments_jobj(hdr)); s++) { + if ((r = LUKS2_digest_verify_by_digest(cd, LUKS2_digest_by_segment(hdr, s), vk)) >= 0) + return r; + } + + return -EPERM; + } + return LUKS2_digest_verify_by_digest(cd, LUKS2_digest_by_segment(hdr, segment), vk); } diff --git a/lib/luks2/luks2_digest_pbkdf2.c b/lib/luks2/luks2_digest_pbkdf2.c index 1009cfb..e8fd00d 100644 --- a/lib/luks2/luks2_digest_pbkdf2.c +++ b/lib/luks2/luks2_digest_pbkdf2.c @@ -1,8 +1,8 @@ /* * LUKS - Linux Unified Key Setup v2, PBKDF2 digest handler (LUKS1 compatible) * - * Copyright (C) 2015-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2015-2023 Milan Broz + * Copyright (C) 2015-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2015-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -147,6 +147,9 @@ static int PBKDF2_digest_store(struct crypt_device *cd, json_object_object_get_ex(hdr->jobj, "digests", &jobj_digests); } + if (!jobj_digest) + return -ENOMEM; + json_object_object_add(jobj_digest, "type", json_object_new_string("pbkdf2")); json_object_object_add(jobj_digest, "keyslots", json_object_new_array()); json_object_object_add(jobj_digest, "segments", json_object_new_array()); @@ -169,8 +172,13 @@ static int PBKDF2_digest_store(struct crypt_device *cd, json_object_object_add(jobj_digest, "digest", json_object_new_string(base64_str)); free(base64_str); - if (jobj_digests) - json_object_object_add_by_uint(jobj_digests, digest, jobj_digest); + if (jobj_digests) { + r = json_object_object_add_by_uint(jobj_digests, digest, jobj_digest); + if (r < 0) { + json_object_put(jobj_digest); + return r; + } + } JSON_DBG(cd, jobj_digest, "Digest JSON:"); return 0; diff --git a/lib/luks2/luks2_disk_metadata.c b/lib/luks2/luks2_disk_metadata.c index e995959..d7f360c 100644 --- a/lib/luks2/luks2_disk_metadata.c +++ b/lib/luks2/luks2_disk_metadata.c @@ -1,8 +1,8 @@ /* * LUKS - Linux Unified Key Setup v2 * - * Copyright (C) 2015-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2015-2023 Milan Broz + * Copyright (C) 2015-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2015-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -256,6 +256,7 @@ static int hdr_read_disk(struct crypt_device *cd, if (read_lseek_blockwise(devfd, device_block_size(cd, device), device_alignment(device), hdr_disk, LUKS2_HDR_BIN_LEN, offset) != LUKS2_HDR_BIN_LEN) { + memset(hdr_disk, 0, LUKS2_HDR_BIN_LEN); return -EIO; } @@ -537,11 +538,20 @@ static int validate_luks2_json_object(struct crypt_device *cd, json_object *jobj } static json_object *parse_and_validate_json(struct crypt_device *cd, - const char *json_area, uint64_t max_length) + const char *json_area, uint64_t hdr_size) { int json_len, r; - json_object *jobj = parse_json_len(cd, json_area, max_length, &json_len); + json_object *jobj; + uint64_t max_length; + + if (hdr_size <= LUKS2_HDR_BIN_LEN || hdr_size > LUKS2_HDR_OFFSET_MAX) { + log_dbg(cd, "LUKS2 header JSON has bogus size 0x%04" PRIx64 ".", hdr_size); + return NULL; + } + + max_length = hdr_size - LUKS2_HDR_BIN_LEN; + jobj = parse_json_len(cd, json_area, max_length, &json_len); if (!jobj) return NULL; @@ -635,7 +645,7 @@ int LUKS2_disk_hdr_read(struct crypt_device *cd, struct luks2_hdr *hdr, state_hdr1 = HDR_FAIL; r = hdr_read_disk(cd, device, &hdr_disk1, &json_area1, 0, 0); if (r == 0) { - jobj_hdr1 = parse_and_validate_json(cd, json_area1, be64_to_cpu(hdr_disk1.hdr_size) - LUKS2_HDR_BIN_LEN); + jobj_hdr1 = parse_and_validate_json(cd, json_area1, be64_to_cpu(hdr_disk1.hdr_size)); state_hdr1 = jobj_hdr1 ? HDR_OK : HDR_OBSOLETE; } else if (r == -EIO) state_hdr1 = HDR_FAIL_IO; @@ -647,7 +657,7 @@ int LUKS2_disk_hdr_read(struct crypt_device *cd, struct luks2_hdr *hdr, if (state_hdr1 != HDR_FAIL && state_hdr1 != HDR_FAIL_IO) { r = hdr_read_disk(cd, device, &hdr_disk2, &json_area2, be64_to_cpu(hdr_disk1.hdr_size), 1); if (r == 0) { - jobj_hdr2 = parse_and_validate_json(cd, json_area2, be64_to_cpu(hdr_disk2.hdr_size) - LUKS2_HDR_BIN_LEN); + jobj_hdr2 = parse_and_validate_json(cd, json_area2, be64_to_cpu(hdr_disk2.hdr_size)); state_hdr2 = jobj_hdr2 ? HDR_OK : HDR_OBSOLETE; } else if (r == -EIO) state_hdr2 = HDR_FAIL_IO; @@ -655,11 +665,12 @@ int LUKS2_disk_hdr_read(struct crypt_device *cd, struct luks2_hdr *hdr, /* * No header size, check all known offsets. */ + hdr_disk2.hdr_size = 0; for (r = -EINVAL,i = 0; r < 0 && i < ARRAY_SIZE(hdr2_offsets); i++) r = hdr_read_disk(cd, device, &hdr_disk2, &json_area2, hdr2_offsets[i], 1); if (r == 0) { - jobj_hdr2 = parse_and_validate_json(cd, json_area2, be64_to_cpu(hdr_disk2.hdr_size) - LUKS2_HDR_BIN_LEN); + jobj_hdr2 = parse_and_validate_json(cd, json_area2, be64_to_cpu(hdr_disk2.hdr_size)); state_hdr2 = jobj_hdr2 ? HDR_OK : HDR_OBSOLETE; } else if (r == -EIO) state_hdr2 = HDR_FAIL_IO; diff --git a/lib/luks2/luks2_internal.h b/lib/luks2/luks2_internal.h index b564a48..aacc75e 100644 --- a/lib/luks2/luks2_internal.h +++ b/lib/luks2/luks2_internal.h @@ -1,8 +1,8 @@ /* * LUKS - Linux Unified Key Setup v2 * - * Copyright (C) 2015-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2015-2023 Milan Broz + * Copyright (C) 2015-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2015-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -62,6 +62,7 @@ uint32_t crypt_jobj_get_uint32(json_object *jobj); json_object *crypt_jobj_new_uint64(uint64_t value); int json_object_object_add_by_uint(json_object *jobj, unsigned key, json_object *jobj_val); +int json_object_object_add_by_uint_by_ref(json_object *jobj, unsigned key, json_object **jobj_val_ref); void json_object_object_del_by_uint(json_object *jobj, unsigned key); int json_object_copy(json_object *jobj_src, json_object **jobj_dst); @@ -295,13 +296,24 @@ uint64_t json_segment_get_iv_offset(json_object *jobj_segment); uint64_t json_segment_get_size(json_object *jobj_segment, unsigned blockwise); const char *json_segment_get_cipher(json_object *jobj_segment); uint32_t json_segment_get_sector_size(json_object *jobj_segment); +int json_segment_get_opal_segment_id(json_object *jobj_segment, uint32_t *ret_opal_segment_id); +int json_segment_get_opal_key_size(json_object *jobj_segment, size_t *ret_key_size); bool json_segment_is_backup(json_object *jobj_segment); json_object *json_segments_get_segment(json_object *jobj_segments, int segment); unsigned json_segments_count(json_object *jobj_segments); void json_segment_remove_flag(json_object *jobj_segment, const char *flag); uint64_t json_segments_get_minimal_offset(json_object *jobj_segments, unsigned blockwise); json_object *json_segment_create_linear(uint64_t offset, const uint64_t *length, unsigned reencryption); -json_object *json_segment_create_crypt(uint64_t offset, uint64_t iv_offset, const uint64_t *length, const char *cipher, uint32_t sector_size, unsigned reencryption); +json_object *json_segment_create_crypt(uint64_t offset, uint64_t iv_offset, const uint64_t *length, + const char *cipher, const char *integrity, + uint32_t sector_size, unsigned reencryption); +json_object *json_segment_create_opal(uint64_t offset, const uint64_t *length, + uint32_t segment_number, uint32_t key_size); +json_object *json_segment_create_opal_crypt(uint64_t offset, const uint64_t *length, + uint32_t segment_number, uint32_t key_size, + uint64_t iv_offset, const char *cipher, + const char *integrity, uint32_t sector_size, + unsigned reencryption); int json_segments_segment_in_reencrypt(json_object *jobj_segments); bool json_segment_cmp(json_object *jobj_segment_1, json_object *jobj_segment_2); bool json_segment_contains_flag(json_object *jobj_segment, const char *flag_str, size_t len); @@ -338,10 +350,26 @@ uint64_t LUKS2_segment_size(struct luks2_hdr *hdr, int segment, unsigned blockwise); +bool LUKS2_segment_set_size(struct luks2_hdr *hdr, + int segment, + const uint64_t *segment_size_bytes); + +uint64_t LUKS2_opal_segment_size(struct luks2_hdr *hdr, + int segment, + unsigned blockwise); + int LUKS2_segment_is_type(struct luks2_hdr *hdr, int segment, const char *type); +bool LUKS2_segment_is_hw_opal(struct luks2_hdr *hdr, int segment); +bool LUKS2_segment_is_hw_opal_crypt(struct luks2_hdr *hdr, int segment); +bool LUKS2_segment_is_hw_opal_only(struct luks2_hdr *hdr, int segment); + +int LUKS2_get_opal_segment_number(struct luks2_hdr *hdr, int segment, + uint32_t *ret_opal_segment_number); +int LUKS2_get_opal_key_size(struct luks2_hdr *hdr, int segment); + int LUKS2_segment_by_type(struct luks2_hdr *hdr, const char *type); @@ -350,8 +378,11 @@ int LUKS2_last_segment_by_type(struct luks2_hdr *hdr, int LUKS2_get_default_segment(struct luks2_hdr *hdr); +bool LUKS2_segments_dynamic_size(struct luks2_hdr *hdr); + int LUKS2_reencrypt_digest_new(struct luks2_hdr *hdr); int LUKS2_reencrypt_digest_old(struct luks2_hdr *hdr); +unsigned LUKS2_reencrypt_vks_count(struct luks2_hdr *hdr); int LUKS2_reencrypt_data_offset(struct luks2_hdr *hdr, bool blockwise); /* diff --git a/lib/luks2/luks2_json_format.c b/lib/luks2/luks2_json_format.c index 4456358..100e026 100644 --- a/lib/luks2/luks2_json_format.c +++ b/lib/luks2/luks2_json_format.c @@ -1,8 +1,8 @@ /* * LUKS - Linux Unified Key Setup v2, LUKS2 header format code * - * Copyright (C) 2015-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2015-2023 Milan Broz + * Copyright (C) 2015-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2015-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -204,76 +204,33 @@ int LUKS2_generate_hdr( struct crypt_device *cd, struct luks2_hdr *hdr, const struct volume_key *vk, - const char *cipherName, - const char *cipherMode, + const char *cipher_spec, const char *integrity, const char *uuid, unsigned int sector_size, /* in bytes */ uint64_t data_offset, /* in bytes */ - uint64_t align_offset, /* in bytes */ - uint64_t required_alignment, - uint64_t metadata_size, - uint64_t keyslots_size) + uint64_t metadata_size_bytes, + uint64_t keyslots_size_bytes, + uint64_t device_size_bytes, + uint32_t opal_segment_number, + uint32_t opal_key_size) { - struct json_object *jobj_segment, *jobj_integrity, *jobj_keyslots, *jobj_segments, *jobj_config; - char cipher[128]; + struct json_object *jobj_segment, *jobj_keyslots, *jobj_segments, *jobj_config; uuid_t partitionUuid; int r, digest; - uint64_t mdev_size; - if (!metadata_size) - metadata_size = LUKS2_HDR_16K_LEN; - hdr->hdr_size = metadata_size; + assert(cipher_spec || (opal_key_size > 0 && device_size_bytes)); - if (data_offset && data_offset < get_min_offset(hdr)) { - log_err(cd, _("Requested data offset is too small.")); - return -EINVAL; - } - - /* Increase keyslot size according to data offset */ - if (!keyslots_size && data_offset) - keyslots_size = data_offset - get_min_offset(hdr); - - /* keyslots size has to be 4 KiB aligned */ - keyslots_size -= (keyslots_size % 4096); - - if (keyslots_size > LUKS2_MAX_KEYSLOTS_SIZE) - keyslots_size = LUKS2_MAX_KEYSLOTS_SIZE; - - if (!keyslots_size) { - assert(LUKS2_DEFAULT_HDR_SIZE > 2 * LUKS2_HDR_OFFSET_MAX); - keyslots_size = LUKS2_DEFAULT_HDR_SIZE - get_min_offset(hdr); - /* Decrease keyslots_size due to metadata device being too small */ - if (!device_size(crypt_metadata_device(cd), &mdev_size) && - ((keyslots_size + get_min_offset(hdr)) > mdev_size) && - device_fallocate(crypt_metadata_device(cd), keyslots_size + get_min_offset(hdr)) && - (get_min_offset(hdr) <= mdev_size)) - keyslots_size = mdev_size - get_min_offset(hdr); - } - - /* Decrease keyslots_size if we have smaller data_offset */ - if (data_offset && (keyslots_size + get_min_offset(hdr)) > data_offset) { - keyslots_size = data_offset - get_min_offset(hdr); - log_dbg(cd, "Decreasing keyslot area size to %" PRIu64 - " bytes due to the requested data offset %" - PRIu64 " bytes.", keyslots_size, data_offset); - } - - /* Data offset has priority */ - if (!data_offset && required_alignment) { - data_offset = size_round_up(get_min_offset(hdr) + keyslots_size, - (size_t)required_alignment); - data_offset += align_offset; - } + hdr->hdr_size = metadata_size_bytes; log_dbg(cd, "Formatting LUKS2 with JSON metadata area %" PRIu64 " bytes and keyslots area %" PRIu64 " bytes.", - metadata_size - LUKS2_HDR_BIN_LEN, keyslots_size); + metadata_size_bytes - LUKS2_HDR_BIN_LEN, keyslots_size_bytes); - if (keyslots_size < (LUKS2_HDR_OFFSET_MAX - 2*LUKS2_HDR_16K_LEN)) + if (keyslots_size_bytes < (LUKS2_HDR_OFFSET_MAX - 2*LUKS2_HDR_16K_LEN)) log_std(cd, _("WARNING: keyslots area (%" PRIu64 " bytes) is very small," " available LUKS2 keyslot count is very limited.\n"), - keyslots_size); + keyslots_size_bytes); hdr->seqid = 1; hdr->version = 2; @@ -291,54 +248,81 @@ int LUKS2_generate_hdr( uuid_unparse(partitionUuid, hdr->uuid); - if (*cipherMode != '\0') - r = snprintf(cipher, sizeof(cipher), "%s-%s", cipherName, cipherMode); - else - r = snprintf(cipher, sizeof(cipher), "%s", cipherName); - if (r < 0 || (size_t)r >= sizeof(cipher)) - return -EINVAL; - hdr->jobj = json_object_new_object(); + if (!hdr->jobj) { + r = -ENOMEM; + goto err; + } jobj_keyslots = json_object_new_object(); + if (!jobj_keyslots) { + r = -ENOMEM; + goto err; + } + json_object_object_add(hdr->jobj, "keyslots", jobj_keyslots); json_object_object_add(hdr->jobj, "tokens", json_object_new_object()); jobj_segments = json_object_new_object(); + if (!jobj_segments) { + r = -ENOMEM; + goto err; + } + json_object_object_add(hdr->jobj, "segments", jobj_segments); json_object_object_add(hdr->jobj, "digests", json_object_new_object()); jobj_config = json_object_new_object(); + if (!jobj_config) { + r = -ENOMEM; + goto err; + } + json_object_object_add(hdr->jobj, "config", jobj_config); digest = LUKS2_digest_create(cd, "pbkdf2", hdr, vk); - if (digest < 0) + if (digest < 0) { + r = -EINVAL; goto err; + } - if (LUKS2_digest_segment_assign(cd, hdr, 0, digest, 1, 0) < 0) + if (LUKS2_digest_segment_assign(cd, hdr, 0, digest, 1, 0) < 0) { + r = -EINVAL; goto err; + } - jobj_segment = json_segment_create_crypt(data_offset, 0, NULL, cipher, sector_size, 0); - if (!jobj_segment) - goto err; + if (!opal_key_size) + jobj_segment = json_segment_create_crypt(data_offset, 0, + NULL, cipher_spec, + integrity, sector_size, + 0); + else if (opal_key_size && cipher_spec) + jobj_segment = json_segment_create_opal_crypt(data_offset, &device_size_bytes, + opal_segment_number, opal_key_size, 0, + cipher_spec, integrity, + sector_size, 0); + else + jobj_segment = json_segment_create_opal(data_offset, &device_size_bytes, + opal_segment_number, opal_key_size); - if (integrity) { - jobj_integrity = json_object_new_object(); - json_object_object_add(jobj_integrity, "type", json_object_new_string(integrity)); - json_object_object_add(jobj_integrity, "journal_encryption", json_object_new_string("none")); - json_object_object_add(jobj_integrity, "journal_integrity", json_object_new_string("none")); - json_object_object_add(jobj_segment, "integrity", jobj_integrity); + if (!jobj_segment) { + r = -EINVAL; + goto err; } - json_object_object_add_by_uint(jobj_segments, 0, jobj_segment); + if (json_object_object_add_by_uint(jobj_segments, 0, jobj_segment)) { + json_object_put(jobj_segment); + r = -ENOMEM; + goto err; + } - json_object_object_add(jobj_config, "json_size", crypt_jobj_new_uint64(metadata_size - LUKS2_HDR_BIN_LEN)); - json_object_object_add(jobj_config, "keyslots_size", crypt_jobj_new_uint64(keyslots_size)); + json_object_object_add(jobj_config, "json_size", crypt_jobj_new_uint64(metadata_size_bytes - LUKS2_HDR_BIN_LEN)); + json_object_object_add(jobj_config, "keyslots_size", crypt_jobj_new_uint64(keyslots_size_bytes)); JSON_DBG(cd, hdr->jobj, "Header JSON:"); return 0; err: json_object_put(hdr->jobj); hdr->jobj = NULL; - return -EINVAL; + return r; } int LUKS2_wipe_header_areas(struct crypt_device *cd, @@ -379,6 +363,14 @@ int LUKS2_wipe_header_areas(struct crypt_device *cd, offset = get_min_offset(hdr); length = LUKS2_keyslots_size(hdr); + /* + * Skip keyslots area wipe in case it is not defined. + * Otherwise we would wipe whole data device (length == 0) + * starting at offset get_min_offset(hdr). + */ + if (!length) + return 0; + log_dbg(cd, "Wiping keyslots area (0x%06" PRIx64 " - 0x%06" PRIx64") with random data.", offset, length + offset); @@ -409,3 +401,80 @@ int LUKS2_set_keyslots_size(struct luks2_hdr *hdr, uint64_t data_offset) json_object_object_add(jobj_config, "keyslots_size", crypt_jobj_new_uint64(keyslots_size)); return 0; } + +int LUKS2_hdr_get_storage_params(struct crypt_device *cd, + uint64_t alignment_offset_bytes, + uint64_t alignment_bytes, + uint64_t *ret_metadata_size_bytes, + uint64_t *ret_keyslots_size_bytes, + uint64_t *ret_data_offset_bytes) +{ + uint64_t data_offset_bytes, keyslots_size_bytes, metadata_size_bytes, mdev_size_bytes; + + assert(cd); + assert(ret_metadata_size_bytes); + assert(ret_keyslots_size_bytes); + assert(ret_data_offset_bytes); + + metadata_size_bytes = crypt_get_metadata_size_bytes(cd); + keyslots_size_bytes = crypt_get_keyslots_size_bytes(cd); + data_offset_bytes = crypt_get_data_offset_sectors(cd) * SECTOR_SIZE; + + if (!metadata_size_bytes) + metadata_size_bytes = LUKS2_HDR_16K_LEN; + + if (data_offset_bytes && data_offset_bytes < 2 * metadata_size_bytes) { + log_err(cd, _("Requested data offset is too small.")); + return -EINVAL; + } + + /* Increase keyslot size according to data offset */ + if (!keyslots_size_bytes && data_offset_bytes) + keyslots_size_bytes = data_offset_bytes - 2 * metadata_size_bytes; + + /* keyslots size has to be 4 KiB aligned */ + keyslots_size_bytes -= (keyslots_size_bytes % 4096); + + if (keyslots_size_bytes > LUKS2_MAX_KEYSLOTS_SIZE) + keyslots_size_bytes = LUKS2_MAX_KEYSLOTS_SIZE; + + if (!keyslots_size_bytes) { + assert(LUKS2_DEFAULT_HDR_SIZE > 2 * LUKS2_HDR_OFFSET_MAX); + keyslots_size_bytes = LUKS2_DEFAULT_HDR_SIZE - 2 * metadata_size_bytes; + /* Decrease keyslots_size due to metadata device being too small */ + if (!device_size(crypt_metadata_device(cd), &mdev_size_bytes) && + ((keyslots_size_bytes + 2 * metadata_size_bytes) > mdev_size_bytes) && + device_fallocate(crypt_metadata_device(cd), keyslots_size_bytes + 2 * metadata_size_bytes) && + ((2 * metadata_size_bytes) <= mdev_size_bytes)) + keyslots_size_bytes = mdev_size_bytes - 2 * metadata_size_bytes; + } + + /* Decrease keyslots_size if we have smaller data_offset */ + if (data_offset_bytes && (keyslots_size_bytes + 2 * metadata_size_bytes) > data_offset_bytes) { + keyslots_size_bytes = data_offset_bytes - 2 * metadata_size_bytes; + log_dbg(cd, "Decreasing keyslot area size to %" PRIu64 + " bytes due to the requested data offset %" + PRIu64 " bytes.", keyslots_size_bytes, data_offset_bytes); + } + + /* Data offset has priority */ + if (!data_offset_bytes && alignment_bytes) { + data_offset_bytes = size_round_up(2 * metadata_size_bytes + keyslots_size_bytes, + (size_t)alignment_bytes); + data_offset_bytes += alignment_offset_bytes; + } + + if (crypt_get_metadata_size_bytes(cd) && (crypt_get_metadata_size_bytes(cd) != metadata_size_bytes)) + log_std(cd, _("WARNING: LUKS2 metadata size changed to %" PRIu64 " bytes.\n"), + metadata_size_bytes); + + if (crypt_get_keyslots_size_bytes(cd) && (crypt_get_keyslots_size_bytes(cd) != keyslots_size_bytes)) + log_std(cd, _("WARNING: LUKS2 keyslots area size changed to %" PRIu64 " bytes.\n"), + keyslots_size_bytes); + + *ret_metadata_size_bytes = metadata_size_bytes; + *ret_keyslots_size_bytes = keyslots_size_bytes; + *ret_data_offset_bytes = data_offset_bytes; + + return 0; +} diff --git a/lib/luks2/luks2_json_metadata.c b/lib/luks2/luks2_json_metadata.c index 4771f04..22f3e3d 100644 --- a/lib/luks2/luks2_json_metadata.c +++ b/lib/luks2/luks2_json_metadata.c @@ -1,9 +1,9 @@ /* * LUKS - Linux Unified Key Setup v2 * - * Copyright (C) 2015-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2015-2023 Milan Broz - * Copyright (C) 2015-2023 Ondrej Kozina + * Copyright (C) 2015-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2015-2024 Milan Broz + * Copyright (C) 2015-2024 Ondrej Kozina * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -21,6 +21,7 @@ */ #include "luks2_internal.h" +#include "luks2/hw_opal/hw_opal.h" #include "../integrity/integrity.h" #include #include @@ -88,6 +89,9 @@ struct json_object *LUKS2_array_remove(struct json_object *array, const char *nu /* Create new array without jobj_removing. */ array_new = json_object_new_array(); + if (!array_new) + return NULL; + for (i = 0; i < (int) json_object_array_length(array); i++) { jobj1 = json_object_array_get_idx(array, i); if (jobj1 != jobj_removing) @@ -478,6 +482,9 @@ static int hdr_validate_json_size(struct crypt_device *cd, json_object *hdr_jobj json = json_object_to_json_string_ext(hdr_jobj, JSON_C_TO_STRING_PLAIN | JSON_C_TO_STRING_NOSLASHESCAPE); + if (!json) + return 1; + json_area_size = crypt_jobj_get_uint64(jobj1); json_size = (uint64_t)strlen(json); @@ -637,6 +644,11 @@ static int reqs_reencrypt_online(uint32_t reqs) return reqs & CRYPT_REQUIREMENT_ONLINE_REENCRYPT; } +static int reqs_opal(uint32_t reqs) +{ + return reqs & CRYPT_REQUIREMENT_OPAL; +} + /* * Config section requirements object must be valid. * Also general segments section must be validated first. @@ -697,7 +709,7 @@ static int validate_reencrypt_segments(struct crypt_device *cd, json_object *hdr static int hdr_validate_segments(struct crypt_device *cd, json_object *hdr_jobj) { json_object *jobj_segments, *jobj_digests, *jobj_offset, *jobj_size, *jobj_type, *jobj_flags, *jobj; - uint64_t offset, size; + uint64_t offset, size, opal_segment_size; int i, r, count, first_backup = -1; struct interval *intervals = NULL; @@ -777,6 +789,32 @@ static int hdr_validate_segments(struct crypt_device *cd, json_object *hdr_jobj) if (!strcmp(json_object_get_string(jobj_type), "crypt") && hdr_validate_crypt_segment(cd, val, key, jobj_digests, size)) return 1; + + /* opal */ + if (!strncmp(json_object_get_string(jobj_type), "hw-opal", 7)) { + if (!size) { + log_dbg(cd, "segment type %s does not support dynamic size.", + json_object_get_string(jobj_type)); + return 1; + } + if (!json_contains(cd, val, key, "Segment", "opal_segment_number", json_type_int) || + !json_contains(cd, val, key, "Segment", "opal_key_size", json_type_int) || + !(jobj_size = json_contains_string(cd, val, key, "Segment", "opal_segment_size"))) + return 1; + if (!numbered(cd, "opal_segment_size", json_object_get_string(jobj_size))) + return 1; + if (!json_str_to_uint64(jobj_size, &opal_segment_size) || !opal_segment_size) { + log_dbg(cd, "Illegal OPAL segment size value."); + return 1; + } + if (size > opal_segment_size) { + log_dbg(cd, "segment size overflows OPAL locking range size."); + return 1; + } + if (!strcmp(json_object_get_string(jobj_type), "hw-opal-crypt") && + hdr_validate_crypt_segment(cd, val, key, jobj_digests, size)) + return 1; + } } if (first_backup == 0) { @@ -1575,6 +1613,8 @@ int LUKS2_config_set_flags(struct crypt_device *cd, struct luks2_hdr *hdr, uint3 return 0; jobj_flags = json_object_new_array(); + if (!jobj_flags) + return -ENOMEM; for (i = 0; persistent_flags[i].description; i++) { if (flags & persistent_flags[i].flag) { @@ -1615,6 +1655,7 @@ static const struct requirement_flag requirements_flags[] = { { CRYPT_REQUIREMENT_ONLINE_REENCRYPT, 2, "online-reencrypt-v2" }, { CRYPT_REQUIREMENT_ONLINE_REENCRYPT, 3, "online-reencrypt-v3" }, { CRYPT_REQUIREMENT_ONLINE_REENCRYPT, 1, "online-reencrypt" }, + { CRYPT_REQUIREMENT_OPAL, 1, "opal" }, { 0, 0, NULL } }; @@ -1707,7 +1748,7 @@ int LUKS2_config_get_reencrypt_version(struct luks2_hdr *hdr, uint8_t *version) return -ENOENT; } -static const struct requirement_flag *stored_requirement_name_by_id(struct crypt_device *cd, struct luks2_hdr *hdr, uint32_t req_id) +static const struct requirement_flag *stored_requirement_name_by_id(struct luks2_hdr *hdr, uint32_t req_id) { json_object *jobj_mandatory, *jobj; int i, len; @@ -1786,7 +1827,7 @@ int LUKS2_config_set_requirements(struct crypt_device *cd, struct luks2_hdr *hdr req_id = reqs & requirements_flags[i].flag; if (req_id) { /* retain already stored version of requirement flag */ - req = stored_requirement_name_by_id(cd, hdr, req_id); + req = stored_requirement_name_by_id(hdr, req_id); if (req) jobj = json_object_new_string(req->description); else @@ -2090,6 +2131,8 @@ static void hdr_dump_segments(struct crypt_device *cd, json_object *hdr_jobj) if (json_object_object_get_ex(jobj_segment, "encryption", &jobj1)) log_std(cd, "\tcipher: %s\n", json_object_get_string(jobj1)); + else + log_std(cd, "\tcipher: (no SW encryption)\n"); if (json_object_object_get_ex(jobj_segment, "sector_size", &jobj1)) log_std(cd, "\tsector: %" PRIu32 " [bytes]\n", crypt_jobj_get_uint32(jobj1)); @@ -2109,6 +2152,18 @@ static void hdr_dump_segments(struct crypt_device *cd, json_object *hdr_jobj) log_std(cd, "\n"); } + json_object_object_get_ex(jobj_segment, "type", &jobj1); + if (!strncmp(json_object_get_string(jobj1), "hw-opal", 7)) { + log_std(cd, "\tHW OPAL encryption:\n"); + json_object_object_get_ex(jobj_segment, "opal_segment_number", &jobj1); + log_std(cd, "\t\tOPAL segment number: %" PRIu32 "\n", crypt_jobj_get_uint32(jobj1)); + json_object_object_get_ex(jobj_segment, "opal_key_size", &jobj1); + log_std(cd, "\t\tOPAL key: %" PRIu32 " bits\n", crypt_jobj_get_uint32(jobj1) * 8); + json_object_object_get_ex(jobj_segment, "opal_segment_size", &jobj1); + json_str_to_uint64(jobj1, &value); + log_std(cd, "\t\tOPAL segment length: %" PRIu64 " [bytes]\n", value); + } + log_std(cd, "\n"); } } @@ -2584,26 +2639,104 @@ int LUKS2_activate_multi(struct crypt_device *cd, int LUKS2_activate(struct crypt_device *cd, const char *name, - struct volume_key *vk, + struct volume_key *crypt_key, + struct volume_key *opal_key, uint32_t flags) { int r; + bool dynamic, read_lock, write_lock, opal_lock_on_error = false; + uint32_t opal_segment_number; + uint64_t range_offset_sectors, range_length_sectors, device_length_bytes; struct luks2_hdr *hdr = crypt_get_hdr(cd, CRYPT_LUKS2); struct crypt_dm_active_device dmdi = {}, dmd = { .uuid = crypt_get_uuid(cd) }; + struct crypt_lock_handle *opal_lh = NULL; /* do not allow activation when particular requirements detected */ - if ((r = LUKS2_unmet_requirements(cd, hdr, 0, 0))) + if ((r = LUKS2_unmet_requirements(cd, hdr, CRYPT_REQUIREMENT_OPAL, 0))) return r; - r = dm_crypt_target_set(&dmd.segment, 0, dmd.size, crypt_data_device(cd), - vk, crypt_get_cipher_spec(cd), crypt_get_iv_offset(cd), - crypt_get_data_offset(cd), crypt_get_integrity(cd) ?: "none", - crypt_get_integrity_tag_size(cd), crypt_get_sector_size(cd)); - if (r < 0) + /* Check that cipher is in compatible format */ + if (!crypt_get_cipher(cd)) { + log_err(cd, _("No known cipher specification pattern detected in LUKS2 header.")); + return -EINVAL; + } + + if ((r = LUKS2_get_data_size(hdr, &device_length_bytes, &dynamic))) return r; + if (dynamic && opal_key) { + log_err(cd, _("OPAL device must have static device size.")); + return -EINVAL; + } + + if (!dynamic) + dmd.size = device_length_bytes / SECTOR_SIZE; + + if (opal_key) { + r = crypt_opal_supported(cd, crypt_data_device(cd)); + if (r < 0) + return r; + + r = LUKS2_get_opal_segment_number(hdr, CRYPT_DEFAULT_SEGMENT, &opal_segment_number); + if (r < 0) + return -EINVAL; + + range_length_sectors = LUKS2_opal_segment_size(hdr, CRYPT_DEFAULT_SEGMENT, 1); + + if (crypt_get_integrity_tag_size(cd)) { + if (dmd.size >= range_length_sectors) { + log_err(cd, _("Encrypted OPAL device with integrity must be smaller than locking range.")); + return -EINVAL; + } + } else { + if (range_length_sectors != dmd.size) { + log_err(cd, _("OPAL device must have same size as locking range.")); + return -EINVAL; + } + } + + range_offset_sectors = crypt_get_data_offset(cd) + crypt_dev_partition_offset(device_path(crypt_data_device(cd))); + r = opal_exclusive_lock(cd, crypt_data_device(cd), &opal_lh); + if (r < 0) { + log_err(cd, _("Failed to acquire OPAL lock on device %s."), device_path(crypt_data_device(cd))); + return -EINVAL; + } + + r = opal_range_check_attributes_and_get_lock_state(cd, crypt_data_device(cd), opal_segment_number, + opal_key, &range_offset_sectors, &range_length_sectors, + &read_lock, &write_lock); + if (r < 0) + goto out; + + opal_lock_on_error = read_lock && write_lock; + if (!opal_lock_on_error && !(flags & CRYPT_ACTIVATE_REFRESH)) + log_std(cd, _("OPAL device is %s already unlocked.\n"), + device_path(crypt_data_device(cd))); + + r = opal_unlock(cd, crypt_data_device(cd), opal_segment_number, opal_key); + if (r < 0) + goto out; + } + + if (LUKS2_segment_is_type(hdr, CRYPT_DEFAULT_SEGMENT, "crypt") || + LUKS2_segment_is_type(hdr, CRYPT_DEFAULT_SEGMENT, "hw-opal-crypt")) { + r = dm_crypt_target_set(&dmd.segment, 0, + dmd.size, crypt_data_device(cd), + crypt_key, crypt_get_cipher_spec(cd), + crypt_get_iv_offset(cd), crypt_get_data_offset(cd), + crypt_get_integrity(cd) ?: "none", + crypt_get_integrity_tag_size(cd), + crypt_get_sector_size(cd)); + } else + r = dm_linear_target_set(&dmd.segment, 0, + dmd.size, crypt_data_device(cd), + crypt_get_data_offset(cd)); + + if (r < 0) + goto out; + /* Add persistent activation flags */ if (!(flags & CRYPT_ACTIVATE_IGNORE_PERSISTENT)) LUKS2_config_get_flags(cd, hdr, &dmd.flags); @@ -2613,29 +2746,47 @@ int LUKS2_activate(struct crypt_device *cd, if (crypt_get_integrity_tag_size(cd)) { if (!LUKS2_integrity_compatible(hdr)) { log_err(cd, _("Unsupported device integrity configuration.")); - return -EINVAL; + r = -EINVAL; + goto out; } if (dmd.flags & CRYPT_ACTIVATE_ALLOW_DISCARDS) { log_err(cd, _("Discard/TRIM is not supported.")); - return -EINVAL; + r = -EINVAL; + goto out; } r = INTEGRITY_create_dmd_device(cd, NULL, NULL, NULL, NULL, &dmdi, dmd.flags, 0); if (r) - return r; + goto out; + + if (!dynamic && dmdi.size != dmd.size) { + log_err(cd, _("Underlying dm-integrity device with unexpected provided data sectors.")); + r = -EINVAL; + goto out; + } dmdi.flags |= CRYPT_ACTIVATE_PRIVATE; dmdi.uuid = dmd.uuid; dmd.segment.u.crypt.offset = 0; - dmd.segment.size = dmdi.segment.size; + if (dynamic) + dmd.segment.size = dmdi.segment.size; - r = create_or_reload_device_with_integrity(cd, name, CRYPT_LUKS2, &dmd, &dmdi); + r = create_or_reload_device_with_integrity(cd, name, + opal_key ? CRYPT_LUKS2_HW_OPAL : CRYPT_LUKS2, + &dmd, &dmdi); } else - r = create_or_reload_device(cd, name, CRYPT_LUKS2, &dmd); + r = create_or_reload_device(cd, name, + opal_key ? CRYPT_LUKS2_HW_OPAL : CRYPT_LUKS2, + &dmd); dm_targets_free(cd, &dmd); dm_targets_free(cd, &dmdi); +out: + if (r < 0 && opal_lock_on_error) + opal_lock(cd, crypt_data_device(cd), opal_segment_number); + + opal_exclusive_unlock(cd, opal_lh); return r; } @@ -2665,13 +2816,15 @@ static bool contains_reencryption_helper(char **names) int LUKS2_deactivate(struct crypt_device *cd, const char *name, struct luks2_hdr *hdr, struct crypt_dm_active_device *dmd, uint32_t flags) { + bool dm_opal_uuid; int r, ret; struct dm_target *tgt; crypt_status_info ci; struct crypt_dm_active_device dmdc; + uint32_t opal_segment_number; char **dep, deps_uuid_prefix[40], *deps[MAX_DM_DEPS+1] = { 0 }; const char *namei = NULL; - struct crypt_lock_handle *reencrypt_lock = NULL; + struct crypt_lock_handle *reencrypt_lock = NULL, *opal_lh = NULL; if (!dmd || !dmd->uuid || strncmp(CRYPT_LUKS2, dmd->uuid, sizeof(CRYPT_LUKS2)-1)) return -EINVAL; @@ -2684,6 +2837,11 @@ int LUKS2_deactivate(struct crypt_device *cd, const char *name, struct luks2_hdr if (r < 0 || (size_t)r != (sizeof(deps_uuid_prefix) - 1)) return -EINVAL; + /* check if active device has LUKS2-OPAL dm uuid prefix */ + dm_opal_uuid = !crypt_uuid_type_cmp(dmd->uuid, CRYPT_LUKS2_HW_OPAL); + if (dm_opal_uuid && hdr && !LUKS2_segment_is_hw_opal(hdr, CRYPT_DEFAULT_SEGMENT)) + return -EINVAL; + tgt = &dmd->segment; /* TODO: We have LUKS2 dependencies now */ @@ -2726,7 +2884,8 @@ int LUKS2_deactivate(struct crypt_device *cd, const char *name, struct luks2_hdr tgt = &dmdc.segment; while (tgt) { if (tgt->type == DM_CRYPT) - crypt_drop_keyring_key_by_description(cd, tgt->u.crypt.vk->key_description, LOGON_KEY); + crypt_drop_keyring_key_by_description(cd, tgt->u.crypt.vk->key_description, + LOGON_KEY); tgt = tgt->next; } } @@ -2761,7 +2920,8 @@ int LUKS2_deactivate(struct crypt_device *cd, const char *name, struct luks2_hdr tgt = &dmdc.segment; while (tgt) { if (tgt->type == DM_CRYPT) - crypt_drop_keyring_key_by_description(cd, tgt->u.crypt.vk->key_description, LOGON_KEY); + crypt_drop_keyring_key_by_description(cd, tgt->u.crypt.vk->key_description, + LOGON_KEY); tgt = tgt->next; } } @@ -2773,7 +2933,35 @@ int LUKS2_deactivate(struct crypt_device *cd, const char *name, struct luks2_hdr r = ret; } + if (!r && dm_opal_uuid) { + if (hdr) { + if (LUKS2_get_opal_segment_number(hdr, CRYPT_DEFAULT_SEGMENT, &opal_segment_number)) { + log_err(cd, _("Device %s was deactivated but hardware OPAL device cannot be locked."), + name); + r = -EINVAL; + goto out; + } + } else { + /* Guess OPAL range number for LUKS2-OPAL device with missing header */ + opal_segment_number = 1; + ret = crypt_dev_get_partition_number(device_path(crypt_data_device(cd))); + if (ret > 0) + opal_segment_number = ret; + } + + if (crypt_data_device(cd)) { + r = opal_exclusive_lock(cd, crypt_data_device(cd), &opal_lh); + if (r < 0) { + log_err(cd, _("Failed to acquire OPAL lock on device %s."), device_path(crypt_data_device(cd))); + goto out; + } + } + + if (!crypt_data_device(cd) || opal_lock(cd, crypt_data_device(cd), opal_segment_number)) + log_err(cd, _("Device %s was deactivated but hardware OPAL device cannot be locked."), name); + } out: + opal_exclusive_unlock(cd, opal_lh); LUKS2_reencrypt_unlock(cd, reencrypt_lock); dep = deps; while (*dep) @@ -2807,6 +2995,8 @@ int LUKS2_unmet_requirements(struct crypt_device *cd, struct luks2_hdr *hdr, uin log_err(cd, _("Operation incompatible with device marked for legacy reencryption. Aborting.")); if (reqs_reencrypt_online(reqs) && !quiet) log_err(cd, _("Operation incompatible with device marked for LUKS2 reencryption. Aborting.")); + if (reqs_opal(reqs) && !quiet) + log_err(cd, _("Operation incompatible with device using OPAL. Aborting.")); /* any remaining unmasked requirement fails the check */ return reqs ? -EINVAL : 0; @@ -2859,6 +3049,20 @@ int json_object_object_add_by_uint(json_object *jobj, unsigned key, json_object #endif } +int json_object_object_add_by_uint_by_ref(json_object *jobj, unsigned key, json_object **jobj_val_ref) +{ + int r; + + assert(jobj); + assert(jobj_val_ref); + + r = json_object_object_add_by_uint(jobj, key, *jobj_val_ref); + if (!r) + *jobj_val_ref = NULL; + + return r; +} + /* jobj_dst must contain pointer initialized to NULL (see json-c json_object_deep_copy API) */ int json_object_copy(json_object *jobj_src, json_object **jobj_dst) { @@ -2872,3 +3076,58 @@ int json_object_copy(json_object *jobj_src, json_object **jobj_dst) return *jobj_dst ? 0 : -1; #endif } + +int LUKS2_split_crypt_and_opal_keys(struct crypt_device *cd __attribute__((unused)), + struct luks2_hdr *hdr, + const struct volume_key *vk, + struct volume_key **ret_crypt_key, + struct volume_key **ret_opal_key) +{ + int r; + uint32_t opal_segment_number; + size_t opal_user_key_size; + json_object *jobj_segment; + struct volume_key *opal_key, *crypt_key; + + assert(vk); + assert(ret_crypt_key); + assert(ret_opal_key); + + jobj_segment = LUKS2_get_segment_jobj(hdr, CRYPT_DEFAULT_SEGMENT); + if (!jobj_segment) + return -EINVAL; + + r = json_segment_get_opal_segment_id(jobj_segment, &opal_segment_number); + if (r < 0) + return -EINVAL; + + r = json_segment_get_opal_key_size(jobj_segment, &opal_user_key_size); + if (r < 0) + return -EINVAL; + + if (vk->keylength < opal_user_key_size) + return -EINVAL; + + /* OPAL SEGMENT only */ + if (vk->keylength == opal_user_key_size) { + *ret_crypt_key = NULL; + *ret_opal_key = NULL; + return 0; + } + + opal_key = crypt_alloc_volume_key(opal_user_key_size, vk->key); + if (!opal_key) + return -ENOMEM; + + crypt_key = crypt_alloc_volume_key(vk->keylength - opal_user_key_size, + vk->key + opal_user_key_size); + if (!crypt_key) { + crypt_free_volume_key(opal_key); + return -ENOMEM; + } + + *ret_opal_key = opal_key; + *ret_crypt_key = crypt_key; + + return 0; +} diff --git a/lib/luks2/luks2_keyslot.c b/lib/luks2/luks2_keyslot.c index 5cf4b83..40816eb 100644 --- a/lib/luks2/luks2_keyslot.c +++ b/lib/luks2/luks2_keyslot.c @@ -1,8 +1,8 @@ /* * LUKS - Linux Unified Key Setup v2, keyslot handling * - * Copyright (C) 2015-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2015-2023 Milan Broz + * Copyright (C) 2015-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2015-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -578,6 +578,8 @@ int LUKS2_keyslot_open(struct crypt_device *cd, int r_prio, r = -EINVAL; hdr = crypt_get_hdr(cd, CRYPT_LUKS2); + if (!hdr) + return -EINVAL; if (keyslot == CRYPT_ANY_SLOT) { r_prio = LUKS2_keyslot_open_priority(cd, hdr, CRYPT_SLOT_PRIORITY_PREFER, @@ -676,8 +678,7 @@ int LUKS2_keyslot_store(struct crypt_device *cd, int LUKS2_keyslot_wipe(struct crypt_device *cd, struct luks2_hdr *hdr, - int keyslot, - int wipe_area_only) + int keyslot) { struct device *device = crypt_metadata_device(cd); uint64_t area_offset, area_length; @@ -694,9 +695,6 @@ int LUKS2_keyslot_wipe(struct crypt_device *cd, if (!jobj_keyslot) return -ENOENT; - if (wipe_area_only) - log_dbg(cd, "Wiping keyslot %d area only.", keyslot); - r = LUKS2_device_write_lock(cd, hdr, device); if (r) return r; @@ -720,9 +718,6 @@ int LUKS2_keyslot_wipe(struct crypt_device *cd, } } - if (wipe_area_only) - goto out; - /* Slot specific wipe */ if (h) { r = h->wipe(cd, keyslot); @@ -803,6 +798,9 @@ int placeholder_keyslot_alloc(struct crypt_device *cd, return -EINVAL; jobj_keyslot = json_object_new_object(); + if (!jobj_keyslot) + return -ENOMEM; + json_object_object_add(jobj_keyslot, "type", json_object_new_string("placeholder")); /* * key_size = -1 makes placeholder keyslot impossible to pass validation. @@ -813,11 +811,19 @@ int placeholder_keyslot_alloc(struct crypt_device *cd, /* Area object */ jobj_area = json_object_new_object(); + if (!jobj_area) { + json_object_put(jobj_keyslot); + return -ENOMEM; + } + json_object_object_add(jobj_area, "offset", crypt_jobj_new_uint64(area_offset)); json_object_object_add(jobj_area, "size", crypt_jobj_new_uint64(area_length)); json_object_object_add(jobj_keyslot, "area", jobj_area); - json_object_object_add_by_uint(jobj_keyslots, keyslot, jobj_keyslot); + if (json_object_object_add_by_uint(jobj_keyslots, keyslot, jobj_keyslot)) { + json_object_put(jobj_keyslot); + return -EINVAL; + } return 0; } @@ -899,7 +905,7 @@ int LUKS2_keyslots_validate(struct crypt_device *cd, json_object *hdr_jobj) return 0; } -void LUKS2_keyslots_repair(struct crypt_device *cd, json_object *jobj_keyslots) +void LUKS2_keyslots_repair(struct crypt_device *cd __attribute__((unused)), json_object *jobj_keyslots) { const keyslot_handler *h; json_object *jobj_type; @@ -964,14 +970,17 @@ int LUKS2_keyslot_swap(struct crypt_device *cd, struct luks2_hdr *hdr, json_object_object_del_by_uint(jobj_keyslots, keyslot); r = json_object_object_add_by_uint(jobj_keyslots, keyslot, jobj_keyslot2); if (r < 0) { + json_object_put(jobj_keyslot2); log_dbg(cd, "Failed to swap keyslot %d.", keyslot); return r; } json_object_object_del_by_uint(jobj_keyslots, keyslot2); r = json_object_object_add_by_uint(jobj_keyslots, keyslot2, jobj_keyslot); - if (r < 0) + if (r < 0) { + json_object_put(jobj_keyslot); log_dbg(cd, "Failed to swap keyslot2 %d.", keyslot2); + } return r; } diff --git a/lib/luks2/luks2_keyslot_luks2.c b/lib/luks2/luks2_keyslot_luks2.c index 491dcad..2c1f400 100644 --- a/lib/luks2/luks2_keyslot_luks2.c +++ b/lib/luks2/luks2_keyslot_luks2.c @@ -1,8 +1,8 @@ /* * LUKS - Linux Unified Key Setup v2, LUKS2 type keyslot handler * - * Copyright (C) 2015-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2015-2023 Milan Broz + * Copyright (C) 2015-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2015-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -307,7 +307,7 @@ static int luks2_keyslot_get_key(struct crypt_device *cd, char *volume_key, size_t volume_key_len) { struct volume_key *derived_key = NULL; - struct crypt_pbkdf_type pbkdf; + struct crypt_pbkdf_type pbkdf, *cd_pbkdf; char *AfKey = NULL; size_t AFEKSize; const char *af_hash = NULL; @@ -360,6 +360,16 @@ static int luks2_keyslot_get_key(struct crypt_device *cd, goto out; } + /* + * Print warning when keyslot requires more memory than available + * (if maximum memory was adjusted - no swap, not enough memory), + * but be silent if user set keyslot memory cost above default limit intentionally. + */ + cd_pbkdf = crypt_get_pbkdf(cd); + if (cd_pbkdf->max_memory_kb && pbkdf.max_memory_kb > cd_pbkdf->max_memory_kb && + pbkdf.max_memory_kb <= DEFAULT_LUKS2_MEMORY_KB) + log_std(cd, _("Warning: keyslot operation could fail as it requires more than available memory.\n")); + /* * If requested, serialize unlocking for memory-hard KDF. Usually NOOP. */ @@ -512,23 +522,42 @@ static int luks2_keyslot_alloc(struct crypt_device *cd, } jobj_keyslot = json_object_new_object(); + if (!jobj_keyslot) { + r = -ENOMEM; + goto err; + } + json_object_object_add(jobj_keyslot, "type", json_object_new_string("luks2")); json_object_object_add(jobj_keyslot, "key_size", json_object_new_int(volume_key_len)); /* AF object */ jobj_af = json_object_new_object(); + if (!jobj_af) { + r = -ENOMEM; + goto err; + } + json_object_object_add(jobj_af, "type", json_object_new_string("luks1")); json_object_object_add(jobj_af, "stripes", json_object_new_int(params->af.luks1.stripes)); json_object_object_add(jobj_keyslot, "af", jobj_af); /* Area object */ jobj_area = json_object_new_object(); + if (!jobj_area) { + r = -ENOMEM; + goto err; + } + json_object_object_add(jobj_area, "type", json_object_new_string("raw")); json_object_object_add(jobj_area, "offset", crypt_jobj_new_uint64(area_offset)); json_object_object_add(jobj_area, "size", crypt_jobj_new_uint64(area_length)); json_object_object_add(jobj_keyslot, "area", jobj_area); - json_object_object_add_by_uint(jobj_keyslots, keyslot, jobj_keyslot); + r = json_object_object_add_by_uint(jobj_keyslots, keyslot, jobj_keyslot); + if (r) { + json_object_put(jobj_keyslot); + return r; + } r = luks2_keyslot_update_json(cd, jobj_keyslot, params); @@ -541,6 +570,9 @@ static int luks2_keyslot_alloc(struct crypt_device *cd, json_object_object_del_by_uint(jobj_keyslots, keyslot); return r; +err: + json_object_put(jobj_keyslot); + return r; } static int luks2_keyslot_open(struct crypt_device *cd, diff --git a/lib/luks2/luks2_keyslot_reenc.c b/lib/luks2/luks2_keyslot_reenc.c index 4291d0c..e847673 100644 --- a/lib/luks2/luks2_keyslot_reenc.c +++ b/lib/luks2/luks2_keyslot_reenc.c @@ -1,8 +1,8 @@ /* * LUKS - Linux Unified Key Setup v2, reencryption keyslot handler * - * Copyright (C) 2016-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2016-2023 Ondrej Kozina + * Copyright (C) 2016-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2016-2024 Ondrej Kozina * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -145,7 +145,12 @@ static int reenc_keyslot_alloc(struct crypt_device *cd, else json_object_object_add(jobj_keyslot, "direction", json_object_new_string("backward")); - json_object_object_add_by_uint(jobj_keyslots, keyslot, jobj_keyslot); + r = json_object_object_add_by_uint(jobj_keyslots, keyslot, jobj_keyslot); + if (r) { + json_object_put(jobj_keyslot); + return r; + } + if (LUKS2_check_json_size(cd, hdr)) { log_dbg(cd, "New keyslot too large to fit in free metadata space."); json_object_object_del_by_uint(jobj_keyslots, keyslot); @@ -371,8 +376,7 @@ static int reenc_keyslot_validate(struct crypt_device *cd, json_object *jobj_key return 0; } -static int reenc_keyslot_update_needed(struct crypt_device *cd, - json_object *jobj_keyslot, +static int reenc_keyslot_update_needed(json_object *jobj_keyslot, const struct crypt_params_reencrypt *params, size_t alignment) { @@ -537,8 +541,7 @@ static int reenc_keyslot_load_resilience(struct crypt_device *cd, return reenc_keyslot_load_resilience_secondary(cd, type, jobj_area, area_length, rp); } -static bool reenc_keyslot_update_is_valid(struct crypt_device *cd, - json_object *jobj_area, +static bool reenc_keyslot_update_is_valid(json_object *jobj_area, const struct crypt_params_reencrypt *params) { const char *type; @@ -589,7 +592,7 @@ static int reenc_keyslot_update(struct crypt_device *cd, if (!params || !params->resilience) jobj_area_new = reencrypt_keyslot_area_jobj_update_block_size(cd, jobj_area, alignment); else { - if (!reenc_keyslot_update_is_valid(cd, jobj_area, params)) { + if (!reenc_keyslot_update_is_valid(jobj_area, params)) { log_err(cd, _("Invalid reencryption resilience mode change requested.")); return -EINVAL; } @@ -661,7 +664,7 @@ int LUKS2_keyslot_reencrypt_update_needed(struct crypt_device *cd, strcmp(json_object_get_string(jobj_type), "reencrypt")) return -EINVAL; - r = reenc_keyslot_update_needed(cd, jobj_keyslot, params, alignment); + r = reenc_keyslot_update_needed(jobj_keyslot, params, alignment); if (!r) log_dbg(cd, "No update of reencrypt keyslot needed."); diff --git a/lib/luks2/luks2_luks1_convert.c b/lib/luks2/luks2_luks1_convert.c index 6d3fa1e..9513217 100644 --- a/lib/luks2/luks2_luks1_convert.c +++ b/lib/luks2/luks2_luks1_convert.c @@ -1,9 +1,9 @@ /* * LUKS - Linux Unified Key Setup v2, LUKS1 conversion code * - * Copyright (C) 2015-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2015-2023 Ondrej Kozina - * Copyright (C) 2015-2023 Milan Broz + * Copyright (C) 2015-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2015-2024 Ondrej Kozina + * Copyright (C) 2015-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -67,11 +67,21 @@ static int json_luks1_keyslot(const struct luks_phdr *hdr_v1, int keyslot, struc int r; keyslot_obj = json_object_new_object(); + if (!keyslot_obj) { + r = -ENOMEM; + goto err; + } + json_object_object_add(keyslot_obj, "type", json_object_new_string("luks2")); json_object_object_add(keyslot_obj, "key_size", json_object_new_int64(hdr_v1->keyBytes)); /* KDF */ jobj_kdf = json_object_new_object(); + if (!jobj_kdf) { + r = -ENOMEM; + goto err; + } + json_object_object_add(jobj_kdf, "type", json_object_new_string(CRYPT_KDF_PBKDF2)); json_object_object_add(jobj_kdf, "hash", json_object_new_string(hdr_v1->hashSpec)); json_object_object_add(jobj_kdf, "iterations", json_object_new_int64(hdr_v1->keyblock[keyslot].passwordIterations)); @@ -89,6 +99,11 @@ static int json_luks1_keyslot(const struct luks_phdr *hdr_v1, int keyslot, struc /* AF */ jobj_af = json_object_new_object(); + if (!jobj_af) { + r = -ENOMEM; + goto err; + } + json_object_object_add(jobj_af, "type", json_object_new_string("luks1")); json_object_object_add(jobj_af, "hash", json_object_new_string(hdr_v1->hashSpec)); /* stripes field ignored, fixed to LUKS_STRIPES (4000) */ @@ -97,6 +112,11 @@ static int json_luks1_keyslot(const struct luks_phdr *hdr_v1, int keyslot, struc /* Area */ jobj_area = json_object_new_object(); + if (!jobj_area) { + r = -ENOMEM; + goto err; + } + json_object_object_add(jobj_area, "type", json_object_new_string("raw")); /* encryption algorithm field */ @@ -124,6 +144,9 @@ static int json_luks1_keyslot(const struct luks_phdr *hdr_v1, int keyslot, struc *keyslot_object = keyslot_obj; return 0; +err: + json_object_put(keyslot_obj); + return r; } static int json_luks1_keyslots(const struct luks_phdr *hdr_v1, struct json_object **keyslots_object) @@ -143,7 +166,12 @@ static int json_luks1_keyslots(const struct luks_phdr *hdr_v1, struct json_objec json_object_put(keyslot_obj); return r; } - json_object_object_add_by_uint(keyslot_obj, keyslot, field); + r = json_object_object_add_by_uint(keyslot_obj, keyslot, field); + if (r) { + json_object_put(field); + json_object_put(keyslot_obj); + return r; + } } *keyslots_object = keyslot_obj; @@ -238,7 +266,12 @@ static int json_luks1_segments(const struct luks_phdr *hdr_v1, struct json_objec json_object_put(segments_obj); return r; } - json_object_object_add_by_uint(segments_obj, 0, field); + r = json_object_object_add_by_uint(segments_obj, 0, field); + if (r) { + json_object_put(field); + json_object_put(segments_obj); + return r; + } *segments_object = segments_obj; return 0; diff --git a/lib/luks2/luks2_reencrypt.c b/lib/luks2/luks2_reencrypt.c index b0dcd6d..b7af206 100644 --- a/lib/luks2/luks2_reencrypt.c +++ b/lib/luks2/luks2_reencrypt.c @@ -1,8 +1,8 @@ /* * LUKS - Linux Unified Key Setup v2, reencryption helpers * - * Copyright (C) 2015-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2015-2023 Ondrej Kozina + * Copyright (C) 2015-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2015-2024 Ondrej Kozina * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -162,6 +162,7 @@ static uint64_t reencrypt_get_data_offset_old(struct luks2_hdr *hdr) return reencrypt_data_offset(hdr, 0); } #endif + static int reencrypt_digest(struct luks2_hdr *hdr, unsigned new) { int segment = LUKS2_get_segment_id_by_flag(hdr, new ? "backup-final" : "backup-previous"); @@ -182,6 +183,21 @@ int LUKS2_reencrypt_digest_old(struct luks2_hdr *hdr) return reencrypt_digest(hdr, 0); } +unsigned LUKS2_reencrypt_vks_count(struct luks2_hdr *hdr) +{ + int digest_old, digest_new; + unsigned vks_count = 0; + + if ((digest_new = LUKS2_reencrypt_digest_new(hdr)) >= 0) + vks_count++; + if ((digest_old = LUKS2_reencrypt_digest_old(hdr)) >= 0) { + if (digest_old != digest_new) + vks_count++; + } + + return vks_count; +} + /* none, checksums, journal or shift */ static const char *reencrypt_resilience_type(struct luks2_hdr *hdr) { @@ -224,7 +240,7 @@ static const char *reencrypt_resilience_hash(struct luks2_hdr *hdr) static json_object *_enc_create_segments_shift_after(struct luks2_reencrypt *rh, uint64_t data_offset) { int reenc_seg, i = 0; - json_object *jobj_copy, *jobj_seg_new = NULL, *jobj_segs_post = json_object_new_object(); + json_object *jobj, *jobj_copy = NULL, *jobj_seg_new = NULL, *jobj_segs_post = json_object_new_object(); uint64_t tmp; if (!rh->jobj_segs_hot || !jobj_segs_post) @@ -239,17 +255,21 @@ static json_object *_enc_create_segments_shift_after(struct luks2_reencrypt *rh, while (i < reenc_seg) { jobj_copy = json_segments_get_segment(rh->jobj_segs_hot, i); - if (!jobj_copy) + if (!jobj_copy || json_object_object_add_by_uint(jobj_segs_post, i++, json_object_get(jobj_copy))) goto err; - json_object_object_add_by_uint(jobj_segs_post, i++, json_object_get(jobj_copy)); } + jobj_copy = NULL; - if (json_object_copy(json_segments_get_segment(rh->jobj_segs_hot, reenc_seg + 1), &jobj_seg_new)) { - if (json_object_copy(json_segments_get_segment(rh->jobj_segs_hot, reenc_seg), &jobj_seg_new)) + jobj = json_segments_get_segment(rh->jobj_segs_hot, reenc_seg + 1); + if (!jobj) { + jobj = json_segments_get_segment(rh->jobj_segs_hot, reenc_seg); + if (!jobj || json_object_copy(jobj, &jobj_seg_new)) goto err; json_segment_remove_flag(jobj_seg_new, "in-reencryption"); tmp = rh->length; } else { + if (json_object_copy(jobj, &jobj_seg_new)) + goto err; json_object_object_add(jobj_seg_new, "offset", crypt_jobj_new_uint64(rh->offset + data_offset)); json_object_object_add(jobj_seg_new, "iv_tweak", crypt_jobj_new_uint64(rh->offset >> SECTOR_SHIFT)); tmp = json_segment_get_size(jobj_seg_new, 0) + rh->length; @@ -257,10 +277,12 @@ static json_object *_enc_create_segments_shift_after(struct luks2_reencrypt *rh, /* alter size of new segment, reenc_seg == 0 we're finished */ json_object_object_add(jobj_seg_new, "size", reenc_seg > 0 ? crypt_jobj_new_uint64(tmp) : json_object_new_string("dynamic")); - json_object_object_add_by_uint(jobj_segs_post, reenc_seg, jobj_seg_new); + if (!json_object_object_add_by_uint(jobj_segs_post, reenc_seg, jobj_seg_new)) + return jobj_segs_post; - return jobj_segs_post; err: + json_object_put(jobj_seg_new); + json_object_put(jobj_copy); json_object_put(jobj_segs_post); return NULL; } @@ -271,7 +293,7 @@ static json_object *reencrypt_make_hot_segments_encrypt_shift(struct luks2_hdr * { int sg, crypt_seg, i = 0; uint64_t segment_size; - json_object *jobj_seg_shrunk, *jobj_seg_new, *jobj_copy, *jobj_enc_seg = NULL, + json_object *jobj_seg_shrunk = NULL, *jobj_seg_new = NULL, *jobj_copy = NULL, *jobj_enc_seg = NULL, *jobj_segs_hot = json_object_new_object(); if (!jobj_segs_hot) @@ -290,38 +312,41 @@ static json_object *reencrypt_make_hot_segments_encrypt_shift(struct luks2_hdr * rh->offset >> SECTOR_SHIFT, &rh->length, reencrypt_segment_cipher_new(hdr), + NULL, /* integrity */ reencrypt_get_sector_size_new(hdr), 1); while (i < sg) { jobj_copy = LUKS2_get_segment_jobj(hdr, i); - if (!jobj_copy) + if (!jobj_copy || json_object_object_add_by_uint(jobj_segs_hot, i++, json_object_get(jobj_copy))) goto err; - json_object_object_add_by_uint(jobj_segs_hot, i++, json_object_get(jobj_copy)); } + jobj_copy = NULL; segment_size = LUKS2_segment_size(hdr, sg, 0); if (segment_size > rh->length) { - jobj_seg_shrunk = NULL; if (json_object_copy(LUKS2_get_segment_jobj(hdr, sg), &jobj_seg_shrunk)) goto err; json_object_object_add(jobj_seg_shrunk, "size", crypt_jobj_new_uint64(segment_size - rh->length)); - json_object_object_add_by_uint(jobj_segs_hot, sg++, jobj_seg_shrunk); + if (json_object_object_add_by_uint_by_ref(jobj_segs_hot, sg++, &jobj_seg_shrunk)) + goto err; } - json_object_object_add_by_uint(jobj_segs_hot, sg++, jobj_enc_seg); - jobj_enc_seg = NULL; /* see err: label */ + if (json_object_object_add_by_uint_by_ref(jobj_segs_hot, sg++, &jobj_enc_seg)) + goto err; /* first crypt segment after encryption ? */ if (crypt_seg >= 0) { jobj_seg_new = LUKS2_get_segment_jobj(hdr, crypt_seg); - if (!jobj_seg_new) + if (!jobj_seg_new || json_object_object_add_by_uint(jobj_segs_hot, sg, json_object_get(jobj_seg_new))) goto err; - json_object_object_add_by_uint(jobj_segs_hot, sg, json_object_get(jobj_seg_new)); } return jobj_segs_hot; err: + json_object_put(jobj_copy); + json_object_put(jobj_seg_new); + json_object_put(jobj_seg_shrunk); json_object_put(jobj_enc_seg); json_object_put(jobj_segs_hot); @@ -343,6 +368,7 @@ static json_object *reencrypt_make_segment_new(struct crypt_device *cd, crypt_get_iv_offset(cd) + (iv_offset >> SECTOR_SHIFT), segment_length, reencrypt_segment_cipher_new(hdr), + NULL, /* integrity */ reencrypt_get_sector_size_new(hdr), 0); case CRYPT_REENCRYPT_DECRYPT: return json_segment_create_linear(data_offset + segment_offset, segment_length, 0); @@ -357,7 +383,7 @@ static json_object *reencrypt_make_post_segments_forward(struct crypt_device *cd uint64_t data_offset) { int reenc_seg; - json_object *jobj_new_seg_after, *jobj_old_seg, *jobj_old_seg_copy = NULL, + json_object *jobj_old_seg, *jobj_new_seg_after = NULL, *jobj_old_seg_copy = NULL, *jobj_segs_post = json_object_new_object(); uint64_t fixed_length = rh->offset + rh->length; @@ -366,7 +392,7 @@ static json_object *reencrypt_make_post_segments_forward(struct crypt_device *cd reenc_seg = json_segments_segment_in_reencrypt(rh->jobj_segs_hot); if (reenc_seg < 0) - return NULL; + goto err; jobj_old_seg = json_segments_get_segment(rh->jobj_segs_hot, reenc_seg + 1); @@ -375,24 +401,26 @@ static json_object *reencrypt_make_post_segments_forward(struct crypt_device *cd * Set size to 'dynamic' again. */ jobj_new_seg_after = reencrypt_make_segment_new(cd, hdr, rh, data_offset, 0, 0, jobj_old_seg ? &fixed_length : NULL); - if (!jobj_new_seg_after) + if (!jobj_new_seg_after || json_object_object_add_by_uint_by_ref(jobj_segs_post, 0, &jobj_new_seg_after)) goto err; - json_object_object_add_by_uint(jobj_segs_post, 0, jobj_new_seg_after); if (jobj_old_seg) { if (rh->fixed_length) { if (json_object_copy(jobj_old_seg, &jobj_old_seg_copy)) goto err; - jobj_old_seg = jobj_old_seg_copy; fixed_length = rh->device_size - fixed_length; - json_object_object_add(jobj_old_seg, "size", crypt_jobj_new_uint64(fixed_length)); + json_object_object_add(jobj_old_seg_copy, "size", crypt_jobj_new_uint64(fixed_length)); } else - json_object_get(jobj_old_seg); - json_object_object_add_by_uint(jobj_segs_post, 1, jobj_old_seg); + jobj_old_seg_copy = json_object_get(jobj_old_seg); + + if (json_object_object_add_by_uint_by_ref(jobj_segs_post, 1, &jobj_old_seg_copy)) + goto err; } return jobj_segs_post; err: + json_object_put(jobj_new_seg_after); + json_object_put(jobj_old_seg_copy); json_object_put(jobj_segs_post); return NULL; } @@ -405,7 +433,7 @@ static json_object *reencrypt_make_post_segments_backward(struct crypt_device *c int reenc_seg; uint64_t fixed_length; - json_object *jobj_new_seg_after, *jobj_old_seg, + json_object *jobj_new_seg_after = NULL, *jobj_old_seg = NULL, *jobj_segs_post = json_object_new_object(); if (!rh->jobj_segs_hot || !jobj_segs_post) @@ -413,22 +441,26 @@ static json_object *reencrypt_make_post_segments_backward(struct crypt_device *c reenc_seg = json_segments_segment_in_reencrypt(rh->jobj_segs_hot); if (reenc_seg < 0) - return NULL; + goto err; jobj_old_seg = json_segments_get_segment(rh->jobj_segs_hot, reenc_seg - 1); - if (jobj_old_seg) - json_object_object_add_by_uint(jobj_segs_post, reenc_seg - 1, json_object_get(jobj_old_seg)); + if (jobj_old_seg) { + json_object_get(jobj_old_seg); + if (json_object_object_add_by_uint_by_ref(jobj_segs_post, reenc_seg - 1, &jobj_old_seg)) + goto err; + } + if (rh->fixed_length && rh->offset) { fixed_length = rh->device_size - rh->offset; jobj_new_seg_after = reencrypt_make_segment_new(cd, hdr, rh, data_offset, rh->offset, rh->offset, &fixed_length); } else jobj_new_seg_after = reencrypt_make_segment_new(cd, hdr, rh, data_offset, rh->offset, rh->offset, NULL); - if (!jobj_new_seg_after) - goto err; - json_object_object_add_by_uint(jobj_segs_post, reenc_seg, jobj_new_seg_after); - return jobj_segs_post; + if (jobj_new_seg_after && !json_object_object_add_by_uint(jobj_segs_post, reenc_seg, jobj_new_seg_after)) + return jobj_segs_post; err: + json_object_put(jobj_new_seg_after); + json_object_put(jobj_old_seg); json_object_put(jobj_segs_post); return NULL; } @@ -448,6 +480,7 @@ static json_object *reencrypt_make_segment_reencrypt(struct crypt_device *cd, crypt_get_iv_offset(cd) + (iv_offset >> SECTOR_SHIFT), segment_length, reencrypt_segment_cipher_new(hdr), + NULL, /* integrity */ reencrypt_get_sector_size_new(hdr), 1); case CRYPT_REENCRYPT_DECRYPT: return json_segment_create_linear(data_offset + segment_offset, segment_length, 1); @@ -472,6 +505,7 @@ static json_object *reencrypt_make_segment_old(struct crypt_device *cd, crypt_get_iv_offset(cd) + (segment_offset >> SECTOR_SHIFT), segment_length, reencrypt_segment_cipher_old(hdr), + NULL, /* integrity */ reencrypt_get_sector_size_old(hdr), 0); break; @@ -488,38 +522,40 @@ static json_object *reencrypt_make_hot_segments_forward(struct crypt_device *cd, uint64_t device_size, uint64_t data_offset) { - json_object *jobj_segs_hot, *jobj_reenc_seg, *jobj_old_seg, *jobj_new_seg; uint64_t fixed_length, tmp = rh->offset + rh->length; + json_object *jobj_segs_hot = json_object_new_object(), *jobj_reenc_seg = NULL, + *jobj_old_seg = NULL, *jobj_new_seg = NULL; unsigned int sg = 0; - jobj_segs_hot = json_object_new_object(); if (!jobj_segs_hot) return NULL; if (rh->offset) { jobj_new_seg = reencrypt_make_segment_new(cd, hdr, rh, data_offset, 0, 0, &rh->offset); - if (!jobj_new_seg) + if (!jobj_new_seg || json_object_object_add_by_uint_by_ref(jobj_segs_hot, sg++, &jobj_new_seg)) goto err; - json_object_object_add_by_uint(jobj_segs_hot, sg++, jobj_new_seg); } jobj_reenc_seg = reencrypt_make_segment_reencrypt(cd, hdr, rh, data_offset, rh->offset, rh->offset, &rh->length); if (!jobj_reenc_seg) goto err; - json_object_object_add_by_uint(jobj_segs_hot, sg++, jobj_reenc_seg); + if (json_object_object_add_by_uint_by_ref(jobj_segs_hot, sg++, &jobj_reenc_seg)) + goto err; if (tmp < device_size) { fixed_length = device_size - tmp; jobj_old_seg = reencrypt_make_segment_old(cd, hdr, rh, data_offset + data_shift_value(&rh->rp), rh->offset + rh->length, rh->fixed_length ? &fixed_length : NULL); - if (!jobj_old_seg) + if (!jobj_old_seg || json_object_object_add_by_uint_by_ref(jobj_segs_hot, sg, &jobj_old_seg)) goto err; - json_object_object_add_by_uint(jobj_segs_hot, sg, jobj_old_seg); } return jobj_segs_hot; err: + json_object_put(jobj_reenc_seg); + json_object_put(jobj_old_seg); + json_object_put(jobj_new_seg); json_object_put(jobj_segs_hot); return NULL; } @@ -528,29 +564,31 @@ static json_object *reencrypt_make_hot_segments_decrypt_shift(struct crypt_devic struct luks2_hdr *hdr, struct luks2_reencrypt *rh, uint64_t device_size, uint64_t data_offset) { - json_object *jobj_segs_hot, *jobj_reenc_seg, *jobj_old_seg, *jobj_new_seg; uint64_t fixed_length, tmp = rh->offset + rh->length, linear_length = rh->progress; + json_object *jobj, *jobj_segs_hot = json_object_new_object(), *jobj_reenc_seg = NULL, + *jobj_old_seg = NULL, *jobj_new_seg = NULL; unsigned int sg = 0; - jobj_segs_hot = json_object_new_object(); if (!jobj_segs_hot) return NULL; if (rh->offset) { - jobj_new_seg = LUKS2_get_segment_jobj(hdr, 0); - if (!jobj_new_seg) + jobj = LUKS2_get_segment_jobj(hdr, 0); + if (!jobj) + goto err; + + jobj_new_seg = json_object_get(jobj); + if (json_object_object_add_by_uint_by_ref(jobj_segs_hot, sg++, &jobj_new_seg)) goto err; - json_object_object_add_by_uint(jobj_segs_hot, sg++, json_object_get(jobj_new_seg)); if (linear_length) { jobj_new_seg = reencrypt_make_segment_new(cd, hdr, rh, data_offset, - json_segment_get_size(jobj_new_seg, 0), + json_segment_get_size(jobj, 0), 0, &linear_length); - if (!jobj_new_seg) + if (!jobj_new_seg || json_object_object_add_by_uint_by_ref(jobj_segs_hot, sg++, &jobj_new_seg)) goto err; - json_object_object_add_by_uint(jobj_segs_hot, sg++, jobj_new_seg); } } @@ -558,27 +596,29 @@ static json_object *reencrypt_make_hot_segments_decrypt_shift(struct crypt_devic rh->offset, rh->offset, &rh->length); - if (!jobj_reenc_seg) + if (!jobj_reenc_seg || json_object_object_add_by_uint_by_ref(jobj_segs_hot, sg++, &jobj_reenc_seg)) goto err; - json_object_object_add_by_uint(jobj_segs_hot, sg++, jobj_reenc_seg); - - if (!rh->offset && (jobj_new_seg = LUKS2_get_segment_jobj(hdr, 1)) && - !json_segment_is_backup(jobj_new_seg)) - json_object_object_add_by_uint(jobj_segs_hot, sg++, json_object_get(jobj_new_seg)); - else if (tmp < device_size) { + if (!rh->offset && (jobj = LUKS2_get_segment_jobj(hdr, 1)) && + !json_segment_is_backup(jobj)) { + jobj_new_seg = json_object_get(jobj); + if (json_object_object_add_by_uint_by_ref(jobj_segs_hot, sg++, &jobj_new_seg)) + goto err; + } else if (tmp < device_size) { fixed_length = device_size - tmp; jobj_old_seg = reencrypt_make_segment_old(cd, hdr, rh, data_offset + data_shift_value(&rh->rp), rh->offset + rh->length, rh->fixed_length ? &fixed_length : NULL); - if (!jobj_old_seg) + if (!jobj_old_seg || json_object_object_add_by_uint_by_ref(jobj_segs_hot, sg, &jobj_old_seg)) goto err; - json_object_object_add_by_uint(jobj_segs_hot, sg, jobj_old_seg); } return jobj_segs_hot; err: + json_object_put(jobj_reenc_seg); + json_object_put(jobj_old_seg); + json_object_put(jobj_new_seg); json_object_put(jobj_segs_hot); return NULL; } @@ -589,7 +629,7 @@ static json_object *_dec_create_segments_shift_after(struct crypt_device *cd, uint64_t data_offset) { int reenc_seg, i = 0; - json_object *jobj_copy, *jobj_seg_old, *jobj_seg_new, + json_object *jobj_seg_old, *jobj_copy = NULL, *jobj_seg_old_copy = NULL, *jobj_seg_new = NULL, *jobj_segs_post = json_object_new_object(); unsigned segs; uint64_t tmp; @@ -607,9 +647,8 @@ static json_object *_dec_create_segments_shift_after(struct crypt_device *cd, if (reenc_seg == 0) { jobj_seg_new = reencrypt_make_segment_new(cd, hdr, rh, data_offset, 0, 0, NULL); - if (!jobj_seg_new) + if (!jobj_seg_new || json_object_object_add_by_uint(jobj_segs_post, 0, jobj_seg_new)) goto err; - json_object_object_add_by_uint(jobj_segs_post, 0, jobj_seg_new); return jobj_segs_post; } @@ -617,22 +656,29 @@ static json_object *_dec_create_segments_shift_after(struct crypt_device *cd, jobj_copy = json_segments_get_segment(rh->jobj_segs_hot, 0); if (!jobj_copy) goto err; - json_object_object_add_by_uint(jobj_segs_post, i++, json_object_get(jobj_copy)); + json_object_get(jobj_copy); + if (json_object_object_add_by_uint_by_ref(jobj_segs_post, i++, &jobj_copy)) + goto err; - jobj_seg_old = json_segments_get_segment(rh->jobj_segs_hot, reenc_seg + 1); + if ((jobj_seg_old = json_segments_get_segment(rh->jobj_segs_hot, reenc_seg + 1))) + jobj_seg_old_copy = json_object_get(jobj_seg_old); tmp = rh->length + rh->progress; jobj_seg_new = reencrypt_make_segment_new(cd, hdr, rh, data_offset, json_segment_get_size(rh->jobj_segment_moved, 0), data_shift_value(&rh->rp), jobj_seg_old ? &tmp : NULL); - json_object_object_add_by_uint(jobj_segs_post, i++, jobj_seg_new); + if (!jobj_seg_new || json_object_object_add_by_uint_by_ref(jobj_segs_post, i++, &jobj_seg_new)) + goto err; - if (jobj_seg_old) - json_object_object_add_by_uint(jobj_segs_post, i, json_object_get(jobj_seg_old)); + if (jobj_seg_old_copy && json_object_object_add_by_uint(jobj_segs_post, i, jobj_seg_old_copy)) + goto err; return jobj_segs_post; err: + json_object_put(jobj_copy); + json_object_put(jobj_seg_old_copy); + json_object_put(jobj_seg_new); json_object_put(jobj_segs_post); return NULL; } @@ -643,10 +689,10 @@ static json_object *reencrypt_make_hot_segments_backward(struct crypt_device *cd uint64_t device_size, uint64_t data_offset) { - json_object *jobj_reenc_seg, *jobj_new_seg, *jobj_old_seg = NULL, + uint64_t fixed_length, tmp = rh->offset + rh->length; + json_object *jobj_reenc_seg = NULL, *jobj_new_seg = NULL, *jobj_old_seg = NULL, *jobj_segs_hot = json_object_new_object(); int sg = 0; - uint64_t fixed_length, tmp = rh->offset + rh->length; if (!jobj_segs_hot) return NULL; @@ -656,26 +702,27 @@ static json_object *reencrypt_make_hot_segments_backward(struct crypt_device *cd goto err; json_object_object_add(jobj_old_seg, "size", crypt_jobj_new_uint64(rh->offset)); - json_object_object_add_by_uint(jobj_segs_hot, sg++, jobj_old_seg); + if (json_object_object_add_by_uint_by_ref(jobj_segs_hot, sg++, &jobj_old_seg)) + goto err; } jobj_reenc_seg = reencrypt_make_segment_reencrypt(cd, hdr, rh, data_offset, rh->offset, rh->offset, &rh->length); - if (!jobj_reenc_seg) + if (!jobj_reenc_seg || json_object_object_add_by_uint_by_ref(jobj_segs_hot, sg++, &jobj_reenc_seg)) goto err; - json_object_object_add_by_uint(jobj_segs_hot, sg++, jobj_reenc_seg); - if (tmp < device_size) { fixed_length = device_size - tmp; jobj_new_seg = reencrypt_make_segment_new(cd, hdr, rh, data_offset, rh->offset + rh->length, rh->offset + rh->length, rh->fixed_length ? &fixed_length : NULL); - if (!jobj_new_seg) + if (!jobj_new_seg || json_object_object_add_by_uint_by_ref(jobj_segs_hot, sg, &jobj_new_seg)) goto err; - json_object_object_add_by_uint(jobj_segs_hot, sg, jobj_new_seg); } return jobj_segs_hot; err: + json_object_put(jobj_reenc_seg); + json_object_put(jobj_new_seg); + json_object_put(jobj_old_seg); json_object_put(jobj_segs_hot); return NULL; } @@ -733,6 +780,7 @@ static int reencrypt_make_post_segments(struct crypt_device *cd, return rh->jobj_segs_post ? 0 : -EINVAL; } #endif + static uint64_t reencrypt_data_shift(struct luks2_hdr *hdr) { json_object *jobj_keyslot, *jobj_area, *jobj_data_shift; @@ -847,13 +895,13 @@ void LUKS2_reencrypt_free(struct crypt_device *cd, struct luks2_reencrypt *rh) free(rh); } -int LUKS2_reencrypt_max_hotzone_size(struct crypt_device *cd, +#if USE_LUKS2_REENCRYPTION +int LUKS2_reencrypt_max_hotzone_size(struct crypt_device *cd __attribute__((unused)), struct luks2_hdr *hdr, const struct reenc_protection *rp, int reencrypt_keyslot, uint64_t *r_length) { -#if USE_LUKS2_REENCRYPTION int r; uint64_t dummy, area_length; @@ -886,11 +934,8 @@ int LUKS2_reencrypt_max_hotzone_size(struct crypt_device *cd, } return -EINVAL; -#else - return -ENOTSUP; -#endif } -#if USE_LUKS2_REENCRYPTION + static size_t reencrypt_get_alignment(struct crypt_device *cd, struct luks2_hdr *hdr) { @@ -971,7 +1016,6 @@ static int reencrypt_offset_backward_moved(struct luks2_hdr *hdr, json_object *j } static int reencrypt_offset_forward_moved(struct luks2_hdr *hdr, - json_object *jobj_segments, uint64_t data_shift, uint64_t *offset) { @@ -1049,7 +1093,7 @@ static int reencrypt_offset(struct luks2_hdr *hdr, if (di == CRYPT_REENCRYPT_FORWARD) { if (reencrypt_mode(hdr) == CRYPT_REENCRYPT_DECRYPT && LUKS2_get_segment_id_by_flag(hdr, "backup-moved-segment") >= 0) { - r = reencrypt_offset_forward_moved(hdr, jobj_segments, data_shift, offset); + r = reencrypt_offset_forward_moved(hdr, data_shift, offset); if (!r && *offset > device_size) *offset = device_size; return r; @@ -1386,7 +1430,7 @@ static int reencrypt_init_storage_wrappers(struct crypt_device *cd, static int reencrypt_context_set_names(struct luks2_reencrypt *rh, const char *name) { - if (!rh | !name) + if (!rh || !name) return -EINVAL; if (*name == '/') { @@ -1964,9 +2008,7 @@ static int reencrypt_set_decrypt_shift_segments(struct crypt_device *cd, crypt_reencrypt_direction_info di) { int r; - uint64_t first_segment_offset, first_segment_length, - second_segment_offset, second_segment_length, - data_offset = LUKS2_get_data_offset(hdr) << SECTOR_SHIFT; + uint64_t data_offset = LUKS2_get_data_offset(hdr) << SECTOR_SHIFT; json_object *jobj_segment_first = NULL, *jobj_segment_second = NULL, *jobj_segments; if (di == CRYPT_REENCRYPT_BACKWARD) @@ -1976,47 +2018,49 @@ static int reencrypt_set_decrypt_shift_segments(struct crypt_device *cd, * future data_device layout: * [encrypted first segment (max data shift size)][gap (data shift size)][second encrypted data segment] */ - first_segment_offset = 0; - first_segment_length = moved_segment_length; - if (dev_size > moved_segment_length) { - second_segment_offset = data_offset + first_segment_length; - second_segment_length = 0; - } - jobj_segments = json_object_new_object(); if (!jobj_segments) return -ENOMEM; r = -EINVAL; - jobj_segment_first = json_segment_create_crypt(first_segment_offset, - crypt_get_iv_offset(cd), &first_segment_length, - crypt_get_cipher_spec(cd), crypt_get_sector_size(cd), 0); + jobj_segment_first = json_segment_create_crypt(0, crypt_get_iv_offset(cd), + &moved_segment_length, crypt_get_cipher_spec(cd), + NULL, crypt_get_sector_size(cd), 0); if (!jobj_segment_first) { log_dbg(cd, "Failed generate 1st segment."); - return r; + goto err; } + r = json_object_object_add_by_uint_by_ref(jobj_segments, 0, &jobj_segment_first); + if (r) + goto err; + if (dev_size > moved_segment_length) { - jobj_segment_second = json_segment_create_crypt(second_segment_offset, - crypt_get_iv_offset(cd) + (first_segment_length >> SECTOR_SHIFT), - second_segment_length ? &second_segment_length : NULL, + jobj_segment_second = json_segment_create_crypt(data_offset + moved_segment_length, + crypt_get_iv_offset(cd) + (moved_segment_length >> SECTOR_SHIFT), + NULL, crypt_get_cipher_spec(cd), + NULL, /* integrity */ crypt_get_sector_size(cd), 0); if (!jobj_segment_second) { - json_object_put(jobj_segment_first); + r = -EINVAL; log_dbg(cd, "Failed generate 2nd segment."); - return r; + goto err; } - } - - json_object_object_add(jobj_segments, "0", jobj_segment_first); - if (jobj_segment_second) - json_object_object_add(jobj_segments, "1", jobj_segment_second); - r = LUKS2_segments_set(cd, hdr, jobj_segments, 0); + r = json_object_object_add_by_uint_by_ref(jobj_segments, 1, &jobj_segment_second); + if (r) + goto err; + } - return r ?: LUKS2_digest_segment_assign(cd, hdr, CRYPT_ANY_SEGMENT, 0, 1, 0); + if (!(r = LUKS2_segments_set(cd, hdr, jobj_segments, 0))) + return LUKS2_digest_segment_assign(cd, hdr, CRYPT_ANY_SEGMENT, 0, 1, 0); +err: + json_object_put(jobj_segment_first); + json_object_put(jobj_segment_second); + json_object_put(jobj_segments); + return r; } static int reencrypt_make_targets(struct crypt_device *cd, @@ -2429,6 +2473,7 @@ static int reencrypt_make_backup_segments(struct crypt_device *cd, uint64_t data_offset, const struct crypt_params_reencrypt *params) { + const char *type; int r, segment, moved_segment = -1, digest_old = -1, digest_new = -1; json_object *jobj_tmp, *jobj_segment_new = NULL, *jobj_segment_old = NULL, *jobj_segment_bcp = NULL; uint32_t sector_size = params->luks2 ? params->luks2->sector_size : SECTOR_SIZE; @@ -2460,9 +2505,17 @@ static int reencrypt_make_backup_segments(struct crypt_device *cd, if (r) goto err; moved_segment = segment++; - json_object_object_add_by_uint(LUKS2_get_segments_jobj(hdr), moved_segment, jobj_segment_bcp); - if (!strcmp(json_segment_type(jobj_segment_bcp), "crypt")) - LUKS2_digest_segment_assign(cd, hdr, moved_segment, digest_old, 1, 0); + r = json_object_object_add_by_uint_by_ref(LUKS2_get_segments_jobj(hdr), moved_segment, &jobj_segment_bcp); + if (r) + goto err; + + if (!(type = json_segment_type(LUKS2_get_segment_jobj(hdr, moved_segment)))) { + r = -EINVAL; + goto err; + } + + if (!strcmp(type, "crypt") && ((r = LUKS2_digest_segment_assign(cd, hdr, moved_segment, digest_old, 1, 0)))) + goto err; } /* FIXME: Add detection for case (digest old == digest new && old segment == new segment) */ @@ -2478,6 +2531,7 @@ static int reencrypt_make_backup_segments(struct crypt_device *cd, json_segment_get_iv_offset(jobj_tmp), device_size ? &device_size : NULL, json_segment_get_cipher(jobj_tmp), + NULL, /* integrity */ json_segment_get_sector_size(jobj_tmp), 0); } else { @@ -2505,10 +2559,14 @@ static int reencrypt_make_backup_segments(struct crypt_device *cd, r = LUKS2_segment_set_flag(jobj_segment_old, "backup-previous"); if (r) goto err; - json_object_object_add_by_uint(LUKS2_get_segments_jobj(hdr), segment, jobj_segment_old); - jobj_segment_old = NULL; - if (digest_old >= 0) - LUKS2_digest_segment_assign(cd, hdr, segment, digest_old, 1, 0); + + r = json_object_object_add_by_uint_by_ref(LUKS2_get_segments_jobj(hdr), segment, &jobj_segment_old); + if (r) + goto err; + + if (digest_old >= 0 && (r = LUKS2_digest_segment_assign(cd, hdr, segment, digest_old, 1, 0))) + goto err; + segment++; if (digest_new >= 0) { @@ -2520,7 +2578,7 @@ static int reencrypt_make_backup_segments(struct crypt_device *cd, } jobj_segment_new = json_segment_create_crypt(segment_offset, crypt_get_iv_offset(cd), - NULL, cipher, sector_size, 0); + NULL, cipher, NULL, sector_size, 0); } else if (params->mode == CRYPT_REENCRYPT_DECRYPT) { segment_offset = data_offset; if (modify_offset(&segment_offset, data_shift, params->direction)) { @@ -2538,10 +2596,13 @@ static int reencrypt_make_backup_segments(struct crypt_device *cd, r = LUKS2_segment_set_flag(jobj_segment_new, "backup-final"); if (r) goto err; - json_object_object_add_by_uint(LUKS2_get_segments_jobj(hdr), segment, jobj_segment_new); - jobj_segment_new = NULL; - if (digest_new >= 0) - LUKS2_digest_segment_assign(cd, hdr, segment, digest_new, 1, 0); + + r = json_object_object_add_by_uint_by_ref(LUKS2_get_segments_jobj(hdr), segment, &jobj_segment_new); + if (r) + goto err; + + if (digest_new >= 0 && (r = LUKS2_digest_segment_assign(cd, hdr, segment, digest_new, 1, 0))) + goto err; /* FIXME: also check occupied space by keyslot in shrunk area */ if (params->direction == CRYPT_REENCRYPT_FORWARD && data_shift && @@ -2556,6 +2617,7 @@ static int reencrypt_make_backup_segments(struct crypt_device *cd, err: json_object_put(jobj_segment_new); json_object_put(jobj_segment_old); + json_object_put(jobj_segment_bcp); return r; } @@ -2590,7 +2652,6 @@ static int reencrypt_verify_keys(struct crypt_device *cd, } static int reencrypt_upload_single_key(struct crypt_device *cd, - struct luks2_hdr *hdr, int digest, struct volume_key *vks) { @@ -2615,11 +2676,11 @@ static int reencrypt_upload_keys(struct crypt_device *cd, return 0; if (digest_new >= 0 && !crypt_is_cipher_null(reencrypt_segment_cipher_new(hdr)) && - (r = reencrypt_upload_single_key(cd, hdr, digest_new, vks))) + (r = reencrypt_upload_single_key(cd, digest_new, vks))) return r; if (digest_old >= 0 && !crypt_is_cipher_null(reencrypt_segment_cipher_old(hdr)) && - (r = reencrypt_upload_single_key(cd, hdr, digest_old, vks))) { + (r = reencrypt_upload_single_key(cd, digest_old, vks))) { crypt_drop_keyring_key(cd, vks); return r; } @@ -3256,7 +3317,17 @@ static int reencrypt_load(struct crypt_device *cd, struct luks2_hdr *hdr, return 0; } +#else +int LUKS2_reencrypt_max_hotzone_size(struct crypt_device *cd __attribute__((unused)), + struct luks2_hdr *hdr __attribute__((unused)), + const struct reenc_protection *rp __attribute__((unused)), + int reencrypt_keyslot __attribute__((unused)), + uint64_t *r_length __attribute__((unused))) +{ + return -ENOTSUP; +} #endif + static int reencrypt_lock_internal(struct crypt_device *cd, const char *uuid, struct crypt_lock_handle **reencrypt_lock) { int r; @@ -3705,7 +3776,7 @@ out: return r; } -#endif + static int reencrypt_init_by_passphrase(struct crypt_device *cd, const char *name, const char *passphrase, @@ -3716,7 +3787,6 @@ static int reencrypt_init_by_passphrase(struct crypt_device *cd, const char *cipher_mode, const struct crypt_params_reencrypt *params) { -#if USE_LUKS2_REENCRYPTION int r; crypt_reencrypt_info ri; struct volume_key *vks = NULL; @@ -3778,11 +3848,22 @@ out: crypt_drop_keyring_key(cd, vks); crypt_free_volume_key(vks); return r < 0 ? r : LUKS2_find_keyslot(hdr, "reencrypt"); +} #else +static int reencrypt_init_by_passphrase(struct crypt_device *cd, + const char *name __attribute__((unused)), + const char *passphrase __attribute__((unused)), + size_t passphrase_size __attribute__((unused)), + int keyslot_old __attribute__((unused)), + int keyslot_new __attribute__((unused)), + const char *cipher __attribute__((unused)), + const char *cipher_mode __attribute__((unused)), + const struct crypt_params_reencrypt *params __attribute__((unused))) +{ log_err(cd, _("This operation is not supported for this device type.")); return -ENOTSUP; -#endif } +#endif int crypt_reencrypt_init_by_keyring(struct crypt_device *cd, const char *name, @@ -3797,14 +3878,20 @@ int crypt_reencrypt_init_by_keyring(struct crypt_device *cd, char *passphrase; size_t passphrase_size; - if (onlyLUKS2mask(cd, CRYPT_REQUIREMENT_ONLINE_REENCRYPT) || !passphrase_description) + if (onlyLUKS2reencrypt(cd) || !passphrase_description) return -EINVAL; if (params && (params->flags & CRYPT_REENCRYPT_INITIALIZE_ONLY) && (params->flags & CRYPT_REENCRYPT_RESUME_ONLY)) return -EINVAL; - r = keyring_get_passphrase(passphrase_description, &passphrase, &passphrase_size); + if (device_is_dax(crypt_data_device(cd)) > 0) { + log_err(cd, _("Reencryption is not supported for DAX (persistent memory) devices.")); + return -EINVAL; + } + + r = crypt_keyring_get_user_key(cd, passphrase_description, &passphrase, &passphrase_size); if (r < 0) { - log_err(cd, _("Failed to read passphrase from keyring (error %d)."), r); + log_dbg(cd, "crypt_keyring_get_user_key failed (error %d)", r); + log_err(cd, _("Failed to read passphrase from keyring.")); return -EINVAL; } @@ -3826,11 +3913,16 @@ int crypt_reencrypt_init_by_passphrase(struct crypt_device *cd, const char *cipher_mode, const struct crypt_params_reencrypt *params) { - if (onlyLUKS2mask(cd, CRYPT_REQUIREMENT_ONLINE_REENCRYPT) || !passphrase) + if (onlyLUKS2reencrypt(cd) || !passphrase) return -EINVAL; if (params && (params->flags & CRYPT_REENCRYPT_INITIALIZE_ONLY) && (params->flags & CRYPT_REENCRYPT_RESUME_ONLY)) return -EINVAL; + if (device_is_dax(crypt_data_device(cd)) > 0) { + log_err(cd, _("Reencryption is not supported for DAX (persistent memory) devices.")); + return -EINVAL; + } + return reencrypt_init_by_passphrase(cd, name, passphrase, passphrase_size, keyslot_old, keyslot_new, cipher, cipher_mode, params); } @@ -4112,14 +4204,12 @@ static int reencrypt_teardown(struct crypt_device *cd, struct luks2_hdr *hdr, return r; } -#endif int crypt_reencrypt_run( struct crypt_device *cd, int (*progress)(uint64_t size, uint64_t offset, void *usrptr), void *usrptr) { -#if USE_LUKS2_REENCRYPTION int r; crypt_reencrypt_info ri; struct luks2_hdr *hdr; @@ -4127,7 +4217,7 @@ int crypt_reencrypt_run( reenc_status_t rs; bool quit = false; - if (onlyLUKS2mask(cd, CRYPT_REQUIREMENT_ONLINE_REENCRYPT)) + if (onlyLUKS2reencrypt(cd)) return -EINVAL; hdr = crypt_get_hdr(cd, CRYPT_LUKS2); @@ -4180,19 +4270,9 @@ int crypt_reencrypt_run( r = reencrypt_teardown(cd, hdr, rh, rs, quit, progress, usrptr); return r; -#else - log_err(cd, _("This operation is not supported for this device type.")); - return -ENOTSUP; -#endif } -int crypt_reencrypt( - struct crypt_device *cd, - int (*progress)(uint64_t size, uint64_t offset, void *usrptr)) -{ - return crypt_reencrypt_run(cd, progress, NULL); -} -#if USE_LUKS2_REENCRYPTION + static int reencrypt_recovery(struct crypt_device *cd, struct luks2_hdr *hdr, uint64_t device_size, @@ -4228,7 +4308,27 @@ out: return r; } +#else /* USE_LUKS2_REENCRYPTION */ +int crypt_reencrypt_run( + struct crypt_device *cd, + int (*progress)(uint64_t size, uint64_t offset, void *usrptr), + void *usrptr) +{ + UNUSED(progress); + UNUSED(usrptr); + + log_err(cd, _("This operation is not supported for this device type.")); + return -ENOTSUP; +} #endif + +int crypt_reencrypt( + struct crypt_device *cd, + int (*progress)(uint64_t size, uint64_t offset, void *usrptr)) +{ + return crypt_reencrypt_run(cd, progress, NULL); +} + /* * use only for calculation of minimal data device size. * The real data offset is taken directly from segments! @@ -4246,7 +4346,7 @@ int LUKS2_reencrypt_data_offset(struct luks2_hdr *hdr, bool blockwise) /* internal only */ int LUKS2_reencrypt_check_device_size(struct crypt_device *cd, struct luks2_hdr *hdr, - uint64_t check_size, uint64_t *dev_size, bool activation, bool dynamic) + uint64_t check_size, uint64_t *dev_size, bool device_exclusive_check, bool dynamic) { int r; uint64_t data_offset, real_size = 0; @@ -4255,7 +4355,8 @@ int LUKS2_reencrypt_check_device_size(struct crypt_device *cd, struct luks2_hdr (LUKS2_get_segment_by_flag(hdr, "backup-moved-segment") || dynamic)) check_size += reencrypt_data_shift(hdr); - r = device_check_access(cd, crypt_data_device(cd), activation ? DEV_EXCL : DEV_OK); + r = device_check_access(cd, crypt_data_device(cd), + device_exclusive_check ? DEV_EXCL : DEV_OK); if (r) return r; @@ -4333,6 +4434,39 @@ out: return r < 0 ? r : keyslot; } + +int LUKS2_reencrypt_locked_recovery_by_vks(struct crypt_device *cd, + struct volume_key *vks) +{ + uint64_t minimal_size, device_size; + int r = -EINVAL; + struct luks2_hdr *hdr = crypt_get_hdr(cd, CRYPT_LUKS2); + struct volume_key *vk = NULL; + + log_dbg(cd, "Entering reencryption crash recovery."); + + if (LUKS2_get_data_size(hdr, &minimal_size, NULL)) + return r; + + if (crypt_use_keyring_for_vk(cd)) + vk = vks; + while (vk) { + r = LUKS2_volume_key_load_in_keyring_by_digest(cd, vk, crypt_volume_key_get_id(vk)); + if (r < 0) + goto out; + vk = crypt_volume_key_next(vk); + } + + if (LUKS2_reencrypt_check_device_size(cd, hdr, minimal_size, &device_size, true, false)) + goto out; + + r = reencrypt_recovery(cd, hdr, device_size, vks); + +out: + if (r < 0) + crypt_drop_keyring_key(cd, vks); + return r; +} #endif crypt_reencrypt_info LUKS2_reencrypt_get_params(struct luks2_hdr *hdr, struct crypt_params_reencrypt *params) diff --git a/lib/luks2/luks2_reencrypt_digest.c b/lib/luks2/luks2_reencrypt_digest.c index bc86f54..fcdad12 100644 --- a/lib/luks2/luks2_reencrypt_digest.c +++ b/lib/luks2/luks2_reencrypt_digest.c @@ -1,9 +1,9 @@ /* * LUKS - Linux Unified Key Setup v2, reencryption digest helpers * - * Copyright (C) 2022-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2022-2023 Ondrej Kozina - * Copyright (C) 2022-2023 Milan Broz + * Copyright (C) 2022-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2022-2024 Ondrej Kozina + * Copyright (C) 2022-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -375,6 +375,22 @@ int LUKS2_keyslot_reencrypt_digest_create(struct crypt_device *cd, return LUKS2_digest_assign(cd, hdr, keyslot_reencrypt, digest_reencrypt, 1, 0); } +void LUKS2_reencrypt_lookup_key_ids(struct crypt_device *cd, struct luks2_hdr *hdr, struct volume_key *vk) +{ + int digest_old, digest_new; + + digest_old = LUKS2_reencrypt_digest_old(hdr); + digest_new = LUKS2_reencrypt_digest_new(hdr); + + while (vk) { + if (digest_old >= 0 && LUKS2_digest_verify_by_digest(cd, digest_old, vk) == digest_old) + crypt_volume_key_set_id(vk, digest_old); + if (digest_new >= 0 && LUKS2_digest_verify_by_digest(cd, digest_new, vk) == digest_new) + crypt_volume_key_set_id(vk, digest_new); + vk = vk->next; + } +} + int LUKS2_reencrypt_digest_verify(struct crypt_device *cd, struct luks2_hdr *hdr, struct volume_key *vks) diff --git a/lib/luks2/luks2_segment.c b/lib/luks2/luks2_segment.c index 63e7c14..af87f4f 100644 --- a/lib/luks2/luks2_segment.c +++ b/lib/luks2/luks2_segment.c @@ -1,8 +1,8 @@ /* * LUKS - Linux Unified Key Setup v2, internal segment handling * - * Copyright (C) 2018-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2018-2023 Ondrej Kozina + * Copyright (C) 2018-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2018-2024 Ondrej Kozina * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -91,6 +91,33 @@ uint64_t json_segment_get_size(json_object *jobj_segment, unsigned blockwise) return blockwise ? crypt_jobj_get_uint64(jobj) >> SECTOR_SHIFT : crypt_jobj_get_uint64(jobj); } +static uint64_t json_segment_get_opal_size(json_object *jobj_segment, unsigned blockwise) +{ + json_object *jobj; + + if (!jobj_segment || + !json_object_object_get_ex(jobj_segment, "opal_segment_size", &jobj)) + return 0; + + return blockwise ? crypt_jobj_get_uint64(jobj) >> SECTOR_SHIFT : crypt_jobj_get_uint64(jobj); +} + +static bool json_segment_set_size(json_object *jobj_segment, const uint64_t *size_bytes) +{ + json_object *jobj; + + if (!jobj_segment) + return false; + + jobj = size_bytes ? crypt_jobj_new_uint64(*size_bytes) : json_object_new_string("dynamic"); + if (!jobj) + return false; + + json_object_object_add(jobj_segment, "size", jobj); + + return true; +} + const char *json_segment_get_cipher(json_object *jobj_segment) { json_object *jobj; @@ -116,6 +143,37 @@ uint32_t json_segment_get_sector_size(json_object *jobj_segment) return i < 0 ? SECTOR_SIZE : i; } +int json_segment_get_opal_segment_id(json_object *jobj_segment, uint32_t *ret_opal_segment_id) +{ + json_object *jobj_segment_id; + + assert(ret_opal_segment_id); + + if (!json_object_object_get_ex(jobj_segment, "opal_segment_number", &jobj_segment_id)) + return -EINVAL; + + *ret_opal_segment_id = json_object_get_int(jobj_segment_id); + + return 0; +} + +int json_segment_get_opal_key_size(json_object *jobj_segment, size_t *ret_key_size) +{ + json_object *jobj_key_size; + + assert(ret_key_size); + + if (!jobj_segment) + return -EINVAL; + + if (!json_object_object_get_ex(jobj_segment, "opal_key_size", &jobj_key_size)) + return -EINVAL; + + *ret_key_size = json_object_get_int(jobj_key_size); + + return 0; +} + static json_object *json_segment_get_flags(json_object *jobj_segment) { json_object *jobj; @@ -245,24 +303,94 @@ json_object *json_segment_create_linear(uint64_t offset, const uint64_t *length, return jobj; } +static bool json_add_crypt_fields(json_object *jobj_segment, uint64_t iv_offset, + const char *cipher, const char *integrity, + uint32_t sector_size, unsigned reencryption) +{ + json_object *jobj_integrity; + + assert(cipher); + + json_object_object_add(jobj_segment, "iv_tweak", crypt_jobj_new_uint64(iv_offset)); + json_object_object_add(jobj_segment, "encryption", json_object_new_string(cipher)); + json_object_object_add(jobj_segment, "sector_size", json_object_new_int(sector_size)); + + if (integrity) { + jobj_integrity = json_object_new_object(); + if (!jobj_integrity) + return false; + + json_object_object_add(jobj_integrity, "type", json_object_new_string(integrity)); + json_object_object_add(jobj_integrity, "journal_encryption", json_object_new_string("none")); + json_object_object_add(jobj_integrity, "journal_integrity", json_object_new_string("none")); + json_object_object_add(jobj_segment, "integrity", jobj_integrity); + } + + if (reencryption) + LUKS2_segment_set_flag(jobj_segment, "in-reencryption"); + + return true; +} + json_object *json_segment_create_crypt(uint64_t offset, uint64_t iv_offset, const uint64_t *length, - const char *cipher, uint32_t sector_size, - unsigned reencryption) + const char *cipher, const char *integrity, + uint32_t sector_size, unsigned reencryption) { json_object *jobj = _segment_create_generic("crypt", offset, length); + if (!jobj) return NULL; - json_object_object_add(jobj, "iv_tweak", crypt_jobj_new_uint64(iv_offset)); - json_object_object_add(jobj, "encryption", json_object_new_string(cipher)); - json_object_object_add(jobj, "sector_size", json_object_new_int(sector_size)); - if (reencryption) - LUKS2_segment_set_flag(jobj, "in-reencryption"); + if (json_add_crypt_fields(jobj, iv_offset, cipher, integrity, sector_size, reencryption)) + return jobj; + + json_object_put(jobj); + return NULL; +} + +static void json_add_opal_fields(json_object *jobj_segment, const uint64_t *length, + uint32_t segment_number, uint32_t key_size) +{ + assert(jobj_segment); + assert(length); + + json_object_object_add(jobj_segment, "opal_segment_number", json_object_new_int(segment_number)); + json_object_object_add(jobj_segment, "opal_key_size", json_object_new_int(key_size)); + json_object_object_add(jobj_segment, "opal_segment_size", crypt_jobj_new_uint64(*length)); +} + +json_object *json_segment_create_opal(uint64_t offset, const uint64_t *length, + uint32_t segment_number, uint32_t key_size) +{ + json_object *jobj = _segment_create_generic("hw-opal", offset, length); + if (!jobj) + return NULL; + + json_add_opal_fields(jobj, length, segment_number, key_size); return jobj; } +json_object *json_segment_create_opal_crypt(uint64_t offset, const uint64_t *length, + uint32_t segment_number, uint32_t key_size, + uint64_t iv_offset, const char *cipher, + const char *integrity, uint32_t sector_size, + unsigned reencryption) +{ + json_object *jobj = _segment_create_generic("hw-opal-crypt", offset, length); + if (!jobj) + return NULL; + + json_add_opal_fields(jobj, length, segment_number, key_size); + + if (json_add_crypt_fields(jobj, iv_offset, cipher, integrity, sector_size, reencryption)) + return jobj; + + json_object_put(jobj); + return NULL; +} + uint64_t LUKS2_segment_offset(struct luks2_hdr *hdr, int segment, unsigned blockwise) { return json_segment_get_offset(LUKS2_get_segment_jobj(hdr, segment), blockwise); @@ -288,11 +416,85 @@ uint64_t LUKS2_segment_size(struct luks2_hdr *hdr, int segment, unsigned blockwi return json_segment_get_size(LUKS2_get_segment_jobj(hdr, segment), blockwise); } +uint64_t LUKS2_opal_segment_size(struct luks2_hdr *hdr, int segment, unsigned blockwise) +{ + return json_segment_get_opal_size(LUKS2_get_segment_jobj(hdr, segment), blockwise); +} + +bool LUKS2_segment_set_size(struct luks2_hdr *hdr, int segment, const uint64_t *segment_size_bytes) +{ + return json_segment_set_size(LUKS2_get_segment_jobj(hdr, segment), segment_size_bytes); +} + int LUKS2_segment_is_type(struct luks2_hdr *hdr, int segment, const char *type) { return !strcmp(json_segment_type(LUKS2_get_segment_jobj(hdr, segment)) ?: "", type); } +static bool json_segment_is_hw_opal_only(json_object *jobj_segment) +{ + const char *type = json_segment_type(jobj_segment); + + if (!type) + return false; + + return !strcmp(type, "hw-opal"); +} + +static bool json_segment_is_hw_opal_crypt(json_object *jobj_segment) +{ + const char *type = json_segment_type(jobj_segment); + + if (!type) + return false; + + return !strcmp(type, "hw-opal-crypt"); +} + +static bool json_segment_is_hw_opal(json_object *jobj_segment) +{ + return json_segment_is_hw_opal_crypt(jobj_segment) || + json_segment_is_hw_opal_only(jobj_segment); +} + +bool LUKS2_segment_is_hw_opal_only(struct luks2_hdr *hdr, int segment) +{ + return json_segment_is_hw_opal_only(LUKS2_get_segment_jobj(hdr, segment)); +} + +bool LUKS2_segment_is_hw_opal_crypt(struct luks2_hdr *hdr, int segment) +{ + return json_segment_is_hw_opal_crypt(LUKS2_get_segment_jobj(hdr, segment)); +} + +bool LUKS2_segment_is_hw_opal(struct luks2_hdr *hdr, int segment) +{ + return json_segment_is_hw_opal(LUKS2_get_segment_jobj(hdr, segment)); +} + +int LUKS2_get_opal_segment_number(struct luks2_hdr *hdr, int segment, uint32_t *ret_opal_segment_number) +{ + json_object *jobj_segment = LUKS2_get_segment_jobj(hdr, segment); + + assert(ret_opal_segment_number); + + if (!json_segment_is_hw_opal(jobj_segment)) + return -ENOENT; + + return json_segment_get_opal_segment_id(jobj_segment, ret_opal_segment_number); +} + +int LUKS2_get_opal_key_size(struct luks2_hdr *hdr, int segment) +{ + size_t key_size = 0; + json_object *jobj_segment = LUKS2_get_segment_jobj(hdr, segment); + + if (json_segment_get_opal_key_size(jobj_segment, &key_size) < 0) + return 0; + + return key_size; +} + int LUKS2_last_segment_by_type(struct luks2_hdr *hdr, const char *type) { json_object *jobj_segments; @@ -424,3 +626,27 @@ bool json_segment_cmp(json_object *jobj_segment_1, json_object *jobj_segment_2) return true; } + +bool LUKS2_segments_dynamic_size(struct luks2_hdr *hdr) +{ + json_object *jobj_segments, *jobj_size; + + assert(hdr); + + jobj_segments = LUKS2_get_segments_jobj(hdr); + if (!jobj_segments) + return false; + + json_object_object_foreach(jobj_segments, key, val) { + UNUSED(key); + + if (json_segment_is_backup(val)) + continue; + + if (json_object_object_get_ex(val, "size", &jobj_size) && + !strcmp(json_object_get_string(jobj_size), "dynamic")) + return true; + } + + return false; +} diff --git a/lib/luks2/luks2_token.c b/lib/luks2/luks2_token.c index 5f65918..9c09be2 100644 --- a/lib/luks2/luks2_token.c +++ b/lib/luks2/luks2_token.c @@ -1,8 +1,8 @@ /* * LUKS - Linux Unified Key Setup v2, token handling * - * Copyright (C) 2016-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2016-2023 Milan Broz + * Copyright (C) 2016-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2016-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -25,7 +25,9 @@ #include "luks2_internal.h" #if USE_EXTERNAL_TOKENS +#define TOKENS_PATH_MAX PATH_MAX static bool external_tokens_enabled = true; +static char external_tokens_path[TOKENS_PATH_MAX] = EXTERNAL_LUKS2_TOKENS_PATH; #else static bool external_tokens_enabled = false; #endif @@ -51,31 +53,37 @@ void crypt_token_external_disable(void) const char *crypt_token_external_path(void) { - return external_tokens_enabled ? EXTERNAL_LUKS2_TOKENS_PATH : NULL; +#if USE_EXTERNAL_TOKENS + return external_tokens_enabled ? external_tokens_path : NULL; +#else + return NULL; +#endif } #if USE_EXTERNAL_TOKENS -static void *token_dlvsym(struct crypt_device *cd, - void *handle, - const char *symbol, - const char *version) +int crypt_token_set_external_path(const char *path) { - char *error; - void *sym; + int r; + char tokens_path[TOKENS_PATH_MAX]; -#ifdef HAVE_DLVSYM - log_dbg(cd, "Loading symbol %s@%s.", symbol, version); - sym = dlvsym(handle, symbol, version); -#else - log_dbg(cd, "Loading default version of symbol %s.", symbol); - sym = dlsym(handle, symbol); -#endif - error = dlerror(); + if (!path) + path = EXTERNAL_LUKS2_TOKENS_PATH; + else if (*path != '/') + return -EINVAL; - if (error) - log_dbg(cd, "%s", error); + r = snprintf(tokens_path, sizeof(tokens_path), "%s", path); + if (r < 0 || (size_t)r >= sizeof(tokens_path)) + return -EINVAL; - return sym; + (void)strcpy(external_tokens_path, tokens_path); + + return 0; +} +#else +#pragma GCC diagnostic ignored "-Wunused-parameter" +int crypt_token_set_external_path(const char *path) +{ + return -ENOTSUP; } #endif @@ -98,6 +106,29 @@ static bool token_validate_v1(struct crypt_device *cd, const crypt_token_handler } #if USE_EXTERNAL_TOKENS +static void *token_dlvsym(struct crypt_device *cd, + void *handle, + const char *symbol, + const char *version) +{ + char *error; + void *sym; + +#ifdef HAVE_DLVSYM + log_dbg(cd, "Loading symbol %s@%s.", symbol, version); + sym = dlvsym(handle, symbol, version); +#else + log_dbg(cd, "Loading default version of symbol %s.", symbol); + sym = dlsym(handle, symbol); +#endif + error = dlerror(); + + if (error) + log_dbg(cd, "%s", error); + + return sym; +} + static bool token_validate_v2(struct crypt_device *cd, const struct crypt_token_handler_internal *h) { if (!h) @@ -127,12 +158,10 @@ static bool external_token_name_valid(const char *name) return true; } -#endif static int crypt_token_load_external(struct crypt_device *cd, const char *name, struct crypt_token_handler_internal *ret) { -#if USE_EXTERNAL_TOKENS struct crypt_token_handler_v2 *token; void *h; char buf[PATH_MAX]; @@ -192,11 +221,40 @@ crypt_token_load_external(struct crypt_device *cd, const char *name, struct cryp ret->version = 2; return 0; -#else +} + +void crypt_token_unload_external_all(struct crypt_device *cd) +{ + int i; + + for (i = LUKS2_TOKENS_MAX - 1; i >= 0; i--) { + if (token_handlers[i].version < 2) + continue; + + log_dbg(cd, "Unloading %s token handler.", token_handlers[i].u.v2.name); + + free(CONST_CAST(void *)token_handlers[i].u.v2.name); + + if (dlclose(CONST_CAST(void *)token_handlers[i].u.v2.dlhandle)) + log_dbg(cd, "%s", dlerror()); + } +} + +#else /* USE_EXTERNAL_TOKENS */ + +static int crypt_token_load_external(struct crypt_device *cd __attribute__((unused)), + const char *name __attribute__((unused)), + struct crypt_token_handler_internal *ret __attribute__((unused))) +{ return -ENOTSUP; -#endif } +void crypt_token_unload_external_all(struct crypt_device *cd __attribute__((unused))) +{ +} + +#endif + static int is_builtin_candidate(const char *type) { return !strncmp(type, LUKS2_BUILTIN_TOKEN_PREFIX, LUKS2_BUILTIN_TOKEN_PREFIX_LEN); @@ -243,25 +301,6 @@ int crypt_token_register(const crypt_token_handler *handler) return 0; } -void crypt_token_unload_external_all(struct crypt_device *cd) -{ -#if USE_EXTERNAL_TOKENS - int i; - - for (i = LUKS2_TOKENS_MAX - 1; i >= 0; i--) { - if (token_handlers[i].version < 2) - continue; - - log_dbg(cd, "Unloading %s token handler.", token_handlers[i].u.v2.name); - - free(CONST_CAST(void *)token_handlers[i].u.v2.name); - - if (dlclose(CONST_CAST(void *)token_handlers[i].u.v2.dlhandle)) - log_dbg(cd, "%s", dlerror()); - } -#endif -} - static const void *LUKS2_token_handler_type(struct crypt_device *cd, const char *type) { @@ -423,12 +462,12 @@ static const char *token_json_to_string(json_object *jobj_token) JSON_C_TO_STRING_PLAIN | JSON_C_TO_STRING_NOSLASHESCAPE); } -static int token_is_usable(struct luks2_hdr *hdr, json_object *jobj_token, int segment, +static int token_is_usable(struct luks2_hdr *hdr, json_object *jobj_token, int keyslot, int segment, crypt_keyslot_priority minimal_priority, bool requires_keyslot) { crypt_keyslot_priority keyslot_priority; json_object *jobj_array; - int i, keyslot, len, r = -ENOENT; + int i, slot, len, r = -ENOENT; if (!jobj_token) return -EINVAL; @@ -451,16 +490,19 @@ static int token_is_usable(struct luks2_hdr *hdr, json_object *jobj_token, int s return -ENOENT; for (i = 0; i < len; i++) { - keyslot = atoi(json_object_get_string(json_object_array_get_idx(jobj_array, i))); + slot = atoi(json_object_get_string(json_object_array_get_idx(jobj_array, i))); + + if (keyslot != CRYPT_ANY_SLOT && slot != keyslot) + continue; - keyslot_priority = LUKS2_keyslot_priority_get(hdr, keyslot); + keyslot_priority = LUKS2_keyslot_priority_get(hdr, slot); if (keyslot_priority == CRYPT_SLOT_PRIORITY_INVALID) return -EINVAL; if (keyslot_priority < minimal_priority) continue; - r = LUKS2_keyslot_for_segment(hdr, keyslot, segment); + r = LUKS2_keyslot_for_segment(hdr, slot, segment); if (r != -ENOENT) return r; } @@ -480,6 +522,7 @@ static int translate_errno(struct crypt_device *cd, int ret_val, const char *typ static int token_open(struct crypt_device *cd, struct luks2_hdr *hdr, + int keyslot, int token, json_object *jobj_token, const char *type, @@ -507,7 +550,7 @@ static int token_open(struct crypt_device *cd, return -ENOENT; } - r = token_is_usable(hdr, jobj_token, segment, priority, requires_keyslot); + r = token_is_usable(hdr, jobj_token, keyslot, segment, priority, requires_keyslot); if (r < 0) { if (r == -ENOENT) log_dbg(cd, "Token %d unusable for segment %d with desired keyslot priority %d.", @@ -569,32 +612,22 @@ static void update_return_errno(int r, int *stored) *stored = r; } -static int LUKS2_keyslot_open_by_token(struct crypt_device *cd, +static int try_token_keyslot_unlock(struct crypt_device *cd, struct luks2_hdr *hdr, + const char *type, + json_object *jobj_token_keyslots, int token, int segment, crypt_keyslot_priority priority, const char *buffer, size_t buffer_len, - struct volume_key **vk) + struct volume_key **r_vk) { + json_object *jobj; crypt_keyslot_priority keyslot_priority; - json_object *jobj_token, *jobj_token_keyslots, *jobj_type, *jobj; - unsigned int num = 0; int i, r = -ENOENT, stored_retval = -ENOENT; + unsigned int num = 0; - jobj_token = LUKS2_get_token_jobj(hdr, token); - if (!jobj_token) - return -EINVAL; - - if (!json_object_object_get_ex(jobj_token, "type", &jobj_type)) - return -EINVAL; - - json_object_object_get_ex(jobj_token, "keyslots", &jobj_token_keyslots); - if (!jobj_token_keyslots) - return -EINVAL; - - /* Try to open keyslot referenced in token */ for (i = 0; i < (int) json_object_array_length(jobj_token_keyslots) && r < 0; i++) { jobj = json_object_array_get_idx(jobj_token_keyslots, i); num = atoi(json_object_get_string(jobj)); @@ -604,8 +637,8 @@ static int LUKS2_keyslot_open_by_token(struct crypt_device *cd, if (keyslot_priority < priority) continue; log_dbg(cd, "Trying to open keyslot %u with token %d (type %s).", - num, token, json_object_get_string(jobj_type)); - r = LUKS2_keyslot_open(cd, num, segment, buffer, buffer_len, vk); + num, token, type); + r = LUKS2_keyslot_open(cd, num, segment, buffer, buffer_len, r_vk); /* short circuit on fatal error */ if (r < 0 && r != -EPERM && r != -ENOENT) return r; @@ -620,6 +653,53 @@ static int LUKS2_keyslot_open_by_token(struct crypt_device *cd, return num; } +static int LUKS2_keyslot_open_by_token(struct crypt_device *cd, + struct luks2_hdr *hdr, + int keyslot, + int token, + int segment, + crypt_keyslot_priority min_priority, + const char *buffer, + size_t buffer_len, + struct volume_key **vk) +{ + json_object *jobj_token, *jobj_token_keyslots, *jobj_type; + crypt_keyslot_priority priority = CRYPT_SLOT_PRIORITY_PREFER; + int r = -ENOENT, stored_retval = -ENOENT; + + jobj_token = LUKS2_get_token_jobj(hdr, token); + if (!jobj_token) + return -EINVAL; + + if (!json_object_object_get_ex(jobj_token, "type", &jobj_type)) + return -EINVAL; + + json_object_object_get_ex(jobj_token, "keyslots", &jobj_token_keyslots); + if (!jobj_token_keyslots) + return -EINVAL; + + /* with specific keyslot just ignore priorities and unlock */ + if (keyslot != CRYPT_ANY_SLOT) { + log_dbg(cd, "Trying to open keyslot %u with token %d (type %s).", + keyslot, token, json_object_get_string(jobj_type)); + return LUKS2_keyslot_open(cd, keyslot, segment, buffer, buffer_len, vk); + } + + /* Try to open keyslot referenced in token */ + while (priority >= min_priority) { + r = try_token_keyslot_unlock(cd, hdr, json_object_get_string(jobj_type), + jobj_token_keyslots, token, segment, + priority, buffer, buffer_len, vk); + if (r == -EINVAL || r >= 0) + return r; + if (r == -EPERM) + stored_retval = r; + priority--; + } + + return stored_retval; +} + static bool token_is_blocked(int token, uint32_t *block_list) { /* it is safe now, but have assert in case LUKS2_TOKENS_MAX grows */ @@ -640,6 +720,7 @@ static int token_open_priority(struct crypt_device *cd, struct luks2_hdr *hdr, json_object *jobj_tokens, const char *type, + int keyslot, int segment, crypt_keyslot_priority priority, const char *pin, @@ -660,9 +741,10 @@ static int token_open_priority(struct crypt_device *cd, token = atoi(slot); if (token_is_blocked(token, block_list)) continue; - r = token_open(cd, hdr, token, val, type, segment, priority, pin, pin_size, &buffer, &buffer_size, usrptr, true); + r = token_open(cd, hdr, keyslot, token, val, type, segment, priority, pin, pin_size, + &buffer, &buffer_size, usrptr, true); if (!r) { - r = LUKS2_keyslot_open_by_token(cd, hdr, token, segment, priority, + r = LUKS2_keyslot_open_by_token(cd, hdr, keyslot, token, segment, priority, buffer, buffer_size, vk); LUKS2_token_buffer_free(cd, token, buffer, buffer_size); } @@ -679,8 +761,9 @@ static int token_open_priority(struct crypt_device *cd, return *stored_retval; } -static int token_open_any(struct crypt_device *cd, struct luks2_hdr *hdr, const char *type, int segment, - const char *pin, size_t pin_size, void *usrptr, struct volume_key **vk) +static int token_open_any(struct crypt_device *cd, struct luks2_hdr *hdr, const char *type, + int keyslot, int segment, const char *pin, size_t pin_size, void *usrptr, + struct volume_key **vk) { json_object *jobj_tokens; int r, retval = -ENOENT; @@ -692,17 +775,22 @@ static int token_open_any(struct crypt_device *cd, struct luks2_hdr *hdr, const if (!type) usrptr = NULL; - r = token_open_priority(cd, hdr, jobj_tokens, type, segment, CRYPT_SLOT_PRIORITY_PREFER, + if (keyslot != CRYPT_ANY_SLOT) + return token_open_priority(cd, hdr, jobj_tokens, type, keyslot, segment, CRYPT_SLOT_PRIORITY_IGNORE, + pin, pin_size, usrptr, &retval, &blocked, vk); + + r = token_open_priority(cd, hdr, jobj_tokens, type, keyslot, segment, CRYPT_SLOT_PRIORITY_PREFER, pin, pin_size, usrptr, &retval, &blocked, vk); if (break_loop_retval(r)) return r; - return token_open_priority(cd, hdr, jobj_tokens, type, segment, CRYPT_SLOT_PRIORITY_NORMAL, + return token_open_priority(cd, hdr, jobj_tokens, type, keyslot, segment, CRYPT_SLOT_PRIORITY_NORMAL, pin, pin_size, usrptr, &retval, &blocked, vk); } int LUKS2_token_unlock_key(struct crypt_device *cd, struct luks2_hdr *hdr, + int keyslot, int token, const char *type, const char *pin, @@ -714,6 +802,7 @@ int LUKS2_token_unlock_key(struct crypt_device *cd, char *buffer; size_t buffer_size; json_object *jobj_token; + crypt_keyslot_priority min_priority; int r = -ENOENT; assert(vk); @@ -724,13 +813,27 @@ int LUKS2_token_unlock_key(struct crypt_device *cd, if (segment < 0 && segment != CRYPT_ANY_SEGMENT) return -EINVAL; + if (keyslot != CRYPT_ANY_SLOT || token != CRYPT_ANY_TOKEN) + min_priority = CRYPT_SLOT_PRIORITY_IGNORE; + else + min_priority = CRYPT_SLOT_PRIORITY_NORMAL; + + if (keyslot != CRYPT_ANY_SLOT) { + r = LUKS2_keyslot_for_segment(hdr, keyslot, segment); + if (r < 0) { + if (r == -ENOENT) + log_dbg(cd, "Keyslot %d unusable for segment %d.", keyslot, segment); + return r; + } + } + if (token >= 0 && token < LUKS2_TOKENS_MAX) { if ((jobj_token = LUKS2_get_token_jobj(hdr, token))) { - r = token_open(cd, hdr, token, jobj_token, type, segment, CRYPT_SLOT_PRIORITY_IGNORE, + r = token_open(cd, hdr, keyslot, token, jobj_token, type, segment, min_priority, pin, pin_size, &buffer, &buffer_size, usrptr, true); if (!r) { - r = LUKS2_keyslot_open_by_token(cd, hdr, token, segment, CRYPT_SLOT_PRIORITY_IGNORE, - buffer, buffer_size, vk); + r = LUKS2_keyslot_open_by_token(cd, hdr, keyslot, token, segment, + min_priority, buffer, buffer_size, vk); LUKS2_token_buffer_free(cd, token, buffer, buffer_size); } } @@ -745,7 +848,7 @@ int LUKS2_token_unlock_key(struct crypt_device *cd, * success (>= 0) or any other negative errno short-circuits token activation loop * immediately */ - r = token_open_any(cd, hdr, type, segment, pin, pin_size, usrptr, vk); + r = token_open_any(cd, hdr, type, keyslot, segment, pin, pin_size, usrptr, vk); else r = -EINVAL; @@ -754,6 +857,7 @@ int LUKS2_token_unlock_key(struct crypt_device *cd, int LUKS2_token_open_and_activate(struct crypt_device *cd, struct luks2_hdr *hdr, + int keyslot, int token, const char *name, const char *type, @@ -763,15 +867,15 @@ int LUKS2_token_open_and_activate(struct crypt_device *cd, void *usrptr) { bool use_keyring; - int keyslot, r, segment; - struct volume_key *vk = NULL; + int r, segment; + struct volume_key *p_crypt, *p_opal, *crypt_key = NULL, *opal_key = NULL, *vk = NULL; if (flags & CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY) segment = CRYPT_ANY_SEGMENT; else segment = CRYPT_DEFAULT_SEGMENT; - r = LUKS2_token_unlock_key(cd, hdr, token, type, pin, pin_size, segment, usrptr, &vk); + r = LUKS2_token_unlock_key(cd, hdr, keyslot, token, type, pin, pin_size, segment, usrptr, &vk); if (r < 0) return r; @@ -779,23 +883,39 @@ int LUKS2_token_open_and_activate(struct crypt_device *cd, keyslot = r; - if (!crypt_use_keyring_for_vk(cd)) + if (LUKS2_segment_is_hw_opal(hdr, CRYPT_DEFAULT_SEGMENT)) { + r = LUKS2_split_crypt_and_opal_keys(cd, hdr, vk, &crypt_key, &opal_key); + if (r < 0) { + crypt_free_volume_key(vk); + return r; + } + + p_crypt = crypt_key; + p_opal = opal_key ?: vk; + } else { + p_crypt = vk; + p_opal = NULL; + } + + if (!crypt_use_keyring_for_vk(cd) || !p_crypt) use_keyring = false; else use_keyring = ((name && !crypt_is_cipher_null(crypt_get_cipher(cd))) || (flags & CRYPT_ACTIVATE_KEYRING_KEY)); if (use_keyring) { - if (!(r = LUKS2_volume_key_load_in_keyring_by_keyslot(cd, hdr, vk, keyslot))) + if (!(r = LUKS2_volume_key_load_in_keyring_by_keyslot(cd, hdr, p_crypt, keyslot))) flags |= CRYPT_ACTIVATE_KEYRING_KEY; } if (r >= 0 && name) - r = LUKS2_activate(cd, name, vk, flags); + r = LUKS2_activate(cd, name, p_crypt, p_opal, flags); if (r < 0) - crypt_drop_keyring_key(cd, vk); + crypt_drop_keyring_key(cd, p_crypt); crypt_free_volume_key(vk); + crypt_free_volume_key(crypt_key); + crypt_free_volume_key(opal_key); return r < 0 ? r : keyslot; } @@ -995,8 +1115,9 @@ int LUKS2_token_unlock_passphrase(struct crypt_device *cd, if (token >= 0 && token < LUKS2_TOKENS_MAX) { if ((jobj_token = LUKS2_get_token_jobj(hdr, token))) - r = token_open(cd, hdr, token, jobj_token, type, CRYPT_ANY_SEGMENT, CRYPT_SLOT_PRIORITY_IGNORE, - pin, pin_size, &buffer, &buffer_size, usrptr, false); + r = token_open(cd, hdr, CRYPT_ANY_SLOT, token, jobj_token, type, + CRYPT_ANY_SEGMENT, CRYPT_SLOT_PRIORITY_IGNORE, pin, pin_size, + &buffer, &buffer_size, usrptr, false); } else if (token == CRYPT_ANY_TOKEN) { json_object_object_get_ex(hdr->jobj, "tokens", &jobj_tokens); @@ -1005,7 +1126,7 @@ int LUKS2_token_unlock_passphrase(struct crypt_device *cd, json_object_object_foreach(jobj_tokens, slot, val) { token = atoi(slot); - r = token_open(cd, hdr, token, val, type, CRYPT_ANY_SEGMENT, CRYPT_SLOT_PRIORITY_IGNORE, + r = token_open(cd, hdr, CRYPT_ANY_SLOT, token, val, type, CRYPT_ANY_SEGMENT, CRYPT_SLOT_PRIORITY_IGNORE, pin, pin_size, &buffer, &buffer_size, usrptr, false); /* diff --git a/lib/luks2/luks2_token_keyring.c b/lib/luks2/luks2_token_keyring.c index ad18798..1d141b9 100644 --- a/lib/luks2/luks2_token_keyring.c +++ b/lib/luks2/luks2_token_keyring.c @@ -1,8 +1,8 @@ /* * LUKS - Linux Unified Key Setup v2, kernel keyring token * - * Copyright (C) 2016-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2016-2023 Ondrej Kozina + * Copyright (C) 2016-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2016-2024 Ondrej Kozina * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -40,14 +40,11 @@ int keyring_open(struct crypt_device *cd, json_object_object_get_ex(jobj_token, "key_description", &jobj_key); - r = keyring_get_passphrase(json_object_get_string(jobj_key), buffer, buffer_len); - if (r == -ENOTSUP) { - log_dbg(cd, "Kernel keyring features disabled."); + r = crypt_keyring_get_user_key(cd, json_object_get_string(jobj_key), buffer, buffer_len); + if (r == -ENOTSUP) return -ENOENT; - } else if (r < 0) { - log_dbg(cd, "keyring_get_passphrase failed (error %d)", r); + else if (r < 0) return -EPERM; - } return 0; } diff --git a/lib/meson.build b/lib/meson.build new file mode 100644 index 0000000..9f503b6 --- /dev/null +++ b/lib/meson.build @@ -0,0 +1,116 @@ +subdir('crypto_backend') +lib_build_dir = meson.current_build_dir() + +libutils_io = static_library('utils_io', + files( + 'utils_io.c', + )) + +libcryptsetup_sym_path = join_paths(meson.current_source_dir(), 'libcryptsetup.sym') + +libcryptsetup_deps = [ + uuid, + devmapper, + libargon2_external, + jsonc, + blkid, + dl, +] + +libcryptsetup_sources = files( + 'bitlk/bitlk.c', + 'fvault2/fvault2.c', + 'integrity/integrity.c', + 'loopaes/loopaes.c', + 'luks1/af.c', + 'luks1/keyencryption.c', + 'luks1/keymanage.c', + 'luks2/hw_opal/hw_opal.c', + 'luks2/luks2_digest.c', + 'luks2/luks2_digest_pbkdf2.c', + 'luks2/luks2_disk_metadata.c', + 'luks2/luks2_json_format.c', + 'luks2/luks2_json_metadata.c', + 'luks2/luks2_keyslot.c', + 'luks2/luks2_keyslot_luks2.c', + 'luks2/luks2_keyslot_reenc.c', + 'luks2/luks2_luks1_convert.c', + 'luks2/luks2_reencrypt.c', + 'luks2/luks2_reencrypt_digest.c', + 'luks2/luks2_segment.c', + 'luks2/luks2_token.c', + 'luks2/luks2_token_keyring.c', + 'tcrypt/tcrypt.c', + 'verity/rs_decode_char.c', + 'verity/rs_encode_char.c', + 'verity/verity.c', + 'verity/verity_fec.c', + 'verity/verity_hash.c', + 'crypt_plain.c', + 'keyslot_context.c', + 'libdevmapper.c', + 'random.c', + 'setup.c', + 'utils.c', + 'utils_benchmark.c', + 'utils_blkid.c', + 'utils_crypt.c', + 'utils_device.c', + 'utils_device_locking.c', + 'utils_devpath.c', + 'utils_keyring.c', + 'utils_loop.c', + 'utils_pbkdf.c', + 'utils_safe_memory.c', + 'utils_storage_wrappers.c', + 'utils_wipe.c', + 'volumekey.c', +) + +if enable_static + libcryptsetup = static_library('cryptsetup', + libcryptsetup_sources, + dependencies: libcryptsetup_deps, + link_with: [ + libcrypto_backend, + libutils_io, + ], + install: true) +else + libcryptsetup = library('cryptsetup', + libcryptsetup_sources, + dependencies: libcryptsetup_deps, + version: libcryptsetup_version, + link_args: [ + '-Wl,--version-script=' + + libcryptsetup_sym_path, + ], + link_with: [ + libcrypto_backend, + libutils_io, + ], + install: true) +endif + +lib_tools_files = files( + 'utils_blkid.c', + 'utils_crypt.c', + 'utils_io.c', + 'utils_loop.c', +) +lib_utils_crypt_files = files( + 'utils_crypt.c', +) +lib_ssh_token_files = files( + 'utils_io.c', + 'utils_loop.c', +) + +install_headers( + 'libcryptsetup.h', +) +pkgconfig.generate( + libcryptsetup, + name: 'libcryptsetup', + version: PACKAGE_VERSION, + description: 'cryptsetup library') diff --git a/lib/random.c b/lib/random.c index 0dfcff9..c86492d 100644 --- a/lib/random.c +++ b/lib/random.c @@ -1,7 +1,7 @@ /* * cryptsetup kernel RNG access functions * - * Copyright (C) 2010-2023 Red Hat, Inc. All rights reserved. + * Copyright (C) 2010-2024 Red Hat, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/lib/setup.c b/lib/setup.c index 1c9d47d..ff84292 100644 --- a/lib/setup.c +++ b/lib/setup.c @@ -3,8 +3,8 @@ * * Copyright (C) 2004 Jana Saout * Copyright (C) 2004-2007 Clemens Fruhwirth - * Copyright (C) 2009-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2009-2023 Milan Broz + * Copyright (C) 2009-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2009-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -31,6 +31,7 @@ #include "libcryptsetup.h" #include "luks1/luks.h" #include "luks2/luks2.h" +#include "luks2/luks2_internal.h" #include "loopaes/loopaes.h" #include "verity/verity.h" #include "tcrypt/tcrypt.h" @@ -40,6 +41,7 @@ #include "utils_device_locking.h" #include "internal.h" #include "keyslot_context.h" +#include "luks2/hw_opal/hw_opal.h" #define CRYPT_CD_UNRESTRICTED (1 << 0) #define CRYPT_CD_QUIET (1 << 1) @@ -58,6 +60,12 @@ struct crypt_device { /* global context scope settings */ unsigned key_in_keyring:1; + bool link_vk_to_keyring; + int32_t keyring_to_link_vk; + const char *user_key_name1; + const char *user_key_name2; + key_type_t keyring_key_type; + uint64_t data_offset; uint64_t metadata_size; /* Used in LUKS2 format */ uint64_t keyslots_size; /* Used in LUKS2 format */ @@ -122,8 +130,10 @@ struct crypt_device { /* buffers, must refresh from kernel on every query */ char cipher_spec[MAX_CIPHER_LEN*2+1]; char cipher[MAX_CIPHER_LEN]; + char integrity_spec[MAX_INTEGRITY_LEN]; const char *cipher_mode; unsigned int key_size; + uint32_t sector_size; } none; } u; @@ -221,6 +231,45 @@ struct device *crypt_data_device(struct crypt_device *cd) return cd->device; } +uint64_t crypt_get_metadata_size_bytes(struct crypt_device *cd) +{ + assert(cd); + return cd->metadata_size; +} + +uint64_t crypt_get_keyslots_size_bytes(struct crypt_device *cd) +{ + assert(cd); + return cd->keyslots_size; +} + +uint64_t crypt_get_data_offset_sectors(struct crypt_device *cd) +{ + assert(cd); + return cd->data_offset; +} + +int crypt_opal_supported(struct crypt_device *cd, struct device *opal_device) +{ + int r; + + assert(cd); + assert(opal_device); + + r = opal_supported(cd, opal_device); + if (r <= 0) { + if (r == -ENOTSUP) + log_err(cd, _("OPAL support is disabled in libcryptsetup.")); + else + log_err(cd, _("Device %s or kernel does not support OPAL encryption."), + device_path(opal_device)); + r = -EINVAL; + } else + r = 0; + + return r; +} + int init_crypto(struct crypt_device *ctx) { struct utsname uts; @@ -237,8 +286,9 @@ int init_crypto(struct crypt_device *ctx) log_err(ctx, _("Cannot initialize crypto backend.")); if (!r && !_crypto_logged) { - log_dbg(ctx, "Crypto backend (%s) initialized in cryptsetup library version %s.", - crypt_backend_version(), PACKAGE_VERSION); + log_dbg(ctx, "Crypto backend (%s%s) initialized in cryptsetup library version %s.", + crypt_backend_version(), crypt_argon2_version(), PACKAGE_VERSION); + if (!uname(&uts)) log_dbg(ctx, "Detected kernel %s %s %s.", uts.sysname, uts.release, uts.machine); @@ -333,7 +383,7 @@ static int isFVAULT2(const char *type) return (type && !strcmp(CRYPT_FVAULT2, type)); } -static int _onlyLUKS(struct crypt_device *cd, uint32_t cdflags) +static int _onlyLUKS(struct crypt_device *cd, uint32_t cdflags, uint32_t mask) { int r = 0; @@ -352,12 +402,22 @@ static int _onlyLUKS(struct crypt_device *cd, uint32_t cdflags) if (r || (cdflags & CRYPT_CD_UNRESTRICTED) || isLUKS1(cd->type)) return r; - return LUKS2_unmet_requirements(cd, &cd->u.luks2.hdr, 0, cdflags & CRYPT_CD_QUIET); + return LUKS2_unmet_requirements(cd, &cd->u.luks2.hdr, mask, cdflags & CRYPT_CD_QUIET); +} + +static int onlyLUKSunrestricted(struct crypt_device *cd) +{ + return _onlyLUKS(cd, CRYPT_CD_UNRESTRICTED, 0); +} + +static int onlyLUKSnoRequirements(struct crypt_device *cd) +{ + return _onlyLUKS(cd, 0, 0); } static int onlyLUKS(struct crypt_device *cd) { - return _onlyLUKS(cd, 0); + return _onlyLUKS(cd, 0, CRYPT_REQUIREMENT_OPAL); } static int _onlyLUKS2(struct crypt_device *cd, uint32_t cdflags, uint32_t mask) @@ -382,16 +442,21 @@ static int _onlyLUKS2(struct crypt_device *cd, uint32_t cdflags, uint32_t mask) return LUKS2_unmet_requirements(cd, &cd->u.luks2.hdr, mask, cdflags & CRYPT_CD_QUIET); } +static int onlyLUKS2unrestricted(struct crypt_device *cd) +{ + return _onlyLUKS2(cd, CRYPT_CD_UNRESTRICTED, 0); +} + /* Internal only */ int onlyLUKS2(struct crypt_device *cd) { - return _onlyLUKS2(cd, 0, 0); + return _onlyLUKS2(cd, 0, CRYPT_REQUIREMENT_OPAL); } /* Internal only */ -int onlyLUKS2mask(struct crypt_device *cd, uint32_t mask) +int onlyLUKS2reencrypt(struct crypt_device *cd) { - return _onlyLUKS2(cd, 0, mask); + return _onlyLUKS2(cd, 0, CRYPT_REQUIREMENT_ONLINE_REENCRYPT); } static void crypt_set_null_type(struct crypt_device *cd) @@ -461,6 +526,10 @@ int crypt_uuid_cmp(const char *dm_uuid, const char *hdr_uuid) if (!dm_uuid || !hdr_uuid) return -EINVAL; + /* skip beyond LUKS2_HW_OPAL prefix */ + if (!strncmp(dm_uuid, CRYPT_LUKS2_HW_OPAL, strlen(CRYPT_LUKS2_HW_OPAL))) + dm_uuid = dm_uuid + strlen(CRYPT_LUKS2_HW_OPAL); + str = strchr(dm_uuid, '-'); if (!str) return -EINVAL; @@ -481,33 +550,55 @@ int crypt_uuid_cmp(const char *dm_uuid, const char *hdr_uuid) } /* - * compares type of active device to provided string (only if there is no explicit type) + * compares two UUIDs returned by device-mapper (striped by cryptsetup) + * used for stacked LUKS2 & INTEGRITY devices */ -static int crypt_uuid_type_cmp(struct crypt_device *cd, const char *type) +static int crypt_uuid_integrity_cmp(const char *dm_uuid, const char *dmi_uuid) { - struct crypt_dm_active_device dmd; - size_t len; - int r; + int i; + char *str, *stri; - /* Must use header-on-disk if we know the type here */ - if (cd->type || !cd->u.none.active_name) + if (!dm_uuid || !dmi_uuid) return -EINVAL; - log_dbg(cd, "Checking if active device %s without header has UUID type %s.", - cd->u.none.active_name, type); + /* skip beyond LUKS2_HW_OPAL prefix */ + if (!strncmp(dm_uuid, CRYPT_LUKS2_HW_OPAL, strlen(CRYPT_LUKS2_HW_OPAL))) + dm_uuid = dm_uuid + strlen(CRYPT_LUKS2_HW_OPAL); - r = dm_query_device(cd, cd->u.none.active_name, DM_ACTIVE_UUID, &dmd); - if (r < 0) - return r; + str = strchr(dm_uuid, '-'); + if (!str) + return -EINVAL; + + stri = strchr(dmi_uuid, '-'); + if (!stri) + return -EINVAL; + + for (i = 1; str[i] && str[i] != '-'; i++) { + if (!stri[i]) + return -EINVAL; + + if (str[i] != stri[i]) + return -EINVAL; + } + + return 0; +} + +/* + * compares type of active device to provided string + */ +int crypt_uuid_type_cmp(const char *dm_uuid, const char *type) +{ + size_t len; + + assert(type); - r = -ENODEV; len = strlen(type); - if (dmd.uuid && strlen(dmd.uuid) > len && - !strncmp(dmd.uuid, type, len) && dmd.uuid[len] == '-') - r = 0; + if (dm_uuid && strlen(dm_uuid) > len && + !strncmp(dm_uuid, type, len) && dm_uuid[len] == '-') + return 0; - free(CONST_CAST(void*)dmd.uuid); - return r; + return -ENODEV; } int PLAIN_activate(struct crypt_device *cd, @@ -763,9 +854,12 @@ static int _crypt_load_luks2(struct crypt_device *cd, int reload, int repair) if (r) return r; - if (!reload && !(type = strdup(CRYPT_LUKS2))) { - r = -ENOMEM; - goto out; + if (!reload) { + type = strdup(CRYPT_LUKS2); + if (!type) { + r = -ENOMEM; + goto out; + } } if (verify_pbkdf_params(cd, &cd->pbkdf)) { @@ -1188,6 +1282,17 @@ static int _init_by_name_crypt_none(struct crypt_device *cd) } } + if (!r && tgt->u.crypt.integrity) { + r = snprintf(cd->u.none.integrity_spec, sizeof(cd->u.none.integrity_spec), + "%s", tgt->u.crypt.integrity); + if (r < 0 || (size_t)r >= sizeof(cd->u.none.integrity_spec)) + r = -EINVAL; + else + r = 0; + } + + cd->u.none.sector_size = tgt->u.crypt.sector_size; + dm_targets_free(cd, &dmd); return r; } @@ -1245,7 +1350,13 @@ static int _init_by_name_crypt(struct crypt_device *cd, const char *name) r = crypt_parse_name_and_mode(tgt->type == DM_LINEAR ? "null" : tgt->u.crypt.cipher, cipher, &key_nums, cipher_mode); if (r < 0) { - log_dbg(cd, "Cannot parse cipher and mode from active device."); + /* Allow crypt null context with unknown cipher string */ + if (tgt->type == DM_CRYPT && !tgt->u.crypt.integrity) { + crypt_set_null_type(cd); + r = 0; + goto out; + } + log_err(cd, _("No known cipher specification pattern detected for active device %s."), name); goto out; } @@ -1260,10 +1371,13 @@ static int _init_by_name_crypt(struct crypt_device *cd, const char *name) r = -EINVAL; goto out; } - if (!cd->metadata_device) { - device_free(cd, cd->device); - MOVE_REF(cd->device, tgti->data_device); - } + + /* + * Data device for crypt with integrity is not dm-integrity device, + * but always the device underlying dm-integrity. + */ + device_free(cd, cd->device); + MOVE_REF(cd->device, tgti->data_device); } /* do not try to lookup LUKS2 header in detached header mode */ @@ -1717,6 +1831,9 @@ static int _crypt_format_luks1(struct crypt_device *cd, return -ENOMEM; } + if (device_is_dax(crypt_data_device(cd)) > 0) + log_std(cd, _("WARNING: DAX device can corrupt data as it does not guarantee atomic sector updates.\n")); + if (params && cd->metadata_device) { /* For detached header the alignment is used directly as data offset */ if (!cd->data_offset) @@ -1772,6 +1889,116 @@ static int _crypt_format_luks1(struct crypt_device *cd, return 0; } +static int LUKS2_check_encryption_params(struct crypt_device *cd, + const char *cipher, + const char *cipher_mode, + const char *integrity, + size_t volume_key_size, + const struct crypt_params_luks2 *params, + const char **ret_integrity) +{ + int r, integrity_key_size = 0; + + assert(cipher); + assert(cipher_mode); + assert(ret_integrity); + + if (integrity) { + if (params->integrity_params) { + /* Standalone dm-integrity must not be used */ + if (params->integrity_params->integrity || + params->integrity_params->integrity_key_size) + return -EINVAL; + /* FIXME: journal encryption and MAC is here not yet supported */ + if (params->integrity_params->journal_crypt || + params->integrity_params->journal_integrity) + return -ENOTSUP; + } + if (!INTEGRITY_tag_size(integrity, cipher, cipher_mode)) { + /* merge "none" string into NULL to make branching logic is easier */ + if (!strcmp(integrity, "none")) + integrity = NULL; + else + return -EINVAL; + } + integrity_key_size = INTEGRITY_key_size(integrity); + if ((integrity_key_size < 0) || (integrity_key_size >= (int)volume_key_size)) { + log_err(cd, _("Volume key is too small for encryption with integrity extensions.")); + return -EINVAL; + } + } + + /* FIXME: allow this later also for normal ciphers (check AF_ALG availability. */ + if (integrity && !integrity_key_size) { + r = crypt_cipher_check_kernel(cipher, cipher_mode, integrity, volume_key_size); + if (r < 0) { + log_err(cd, _("Cipher %s-%s (key size %zd bits) is not available."), + cipher, cipher_mode, volume_key_size * 8); + return r; + } + } + + if ((!integrity || integrity_key_size) && !crypt_cipher_wrapped_key(cipher, cipher_mode) && + !INTEGRITY_tag_size(NULL, cipher, cipher_mode)) { + r = LUKS_check_cipher(cd, volume_key_size - integrity_key_size, + cipher, cipher_mode); + if (r < 0) + return r; + } + + *ret_integrity = integrity; + + return 0; +} + +static int LUKS2_check_encryption_sector(struct crypt_device *cd, uint64_t device_size_bytes, + uint64_t data_offset_bytes, uint32_t sector_size, bool modify_sector_size, + bool verify_data_area_alignment, uint32_t *ret_sector_size) +{ + uint32_t dmc_flags; + + assert(ret_sector_size); + + if (sector_size < SECTOR_SIZE || sector_size > MAX_SECTOR_SIZE || + NOTPOW2(sector_size)) { + log_err(cd, _("Unsupported encryption sector size.")); + return -EINVAL; + } + + if (sector_size != SECTOR_SIZE && !dm_flags(cd, DM_CRYPT, &dmc_flags) && + !(dmc_flags & DM_SECTOR_SIZE_SUPPORTED)) { + if (modify_sector_size) { + log_dbg(cd, "dm-crypt does not support encryption sector size option. Reverting to 512 bytes."); + sector_size = SECTOR_SIZE; + } else + log_std(cd, _("WARNING: The device activation will fail, dm-crypt is missing " + "support for requested encryption sector size.\n")); + } + + if (modify_sector_size) { + if (data_offset_bytes && MISALIGNED(data_offset_bytes, sector_size)) { + log_dbg(cd, "Data offset not aligned to sector size. Reverting to 512 bytes."); + sector_size = SECTOR_SIZE; + } else if (MISALIGNED(device_size_bytes - data_offset_bytes, sector_size)) { + /* underflow does not affect misalignment checks */ + log_dbg(cd, "Device size is not aligned to sector size. Reverting to 512 bytes."); + sector_size = SECTOR_SIZE; + } + } + + /* underflow does not affect misalignment checks */ + if (verify_data_area_alignment && + sector_size > SECTOR_SIZE && + MISALIGNED(device_size_bytes - data_offset_bytes, sector_size)) { + log_err(cd, _("Device size is not aligned to requested sector size.")); + return -EINVAL; + } + + *ret_sector_size = sector_size; + + return 0; +} + static int _crypt_format_luks2(struct crypt_device *cd, const char *cipher, const char *cipher_mode, @@ -1781,13 +2008,13 @@ static int _crypt_format_luks2(struct crypt_device *cd, struct crypt_params_luks2 *params, bool sector_size_autodetect) { - int r, integrity_key_size = 0; + int r; unsigned long required_alignment = DEFAULT_DISK_ALIGNMENT; unsigned long alignment_offset = 0; unsigned int sector_size; + char cipher_spec[2*MAX_CAPI_ONE_LEN]; const char *integrity = params ? params->integrity : NULL; - uint64_t dev_size; - uint32_t dmc_flags; + uint64_t data_offset_bytes, dev_size, metadata_size_bytes, keyslots_size_bytes; cd->u.luks2.hdr.jobj = NULL; cd->u.luks2.keyslot_cipher = NULL; @@ -1819,6 +2046,9 @@ static int _crypt_format_luks2(struct crypt_device *cd, return -ENOMEM; } + if (device_is_dax(crypt_data_device(cd)) > 0) + log_std(cd, _("WARNING: DAX device can corrupt data as it does not guarantee atomic sector updates.\n")); + if (sector_size_autodetect) { sector_size = device_optimal_encryption_sector_size(cd, crypt_data_device(cd)); log_dbg(cd, "Auto-detected optimal encryption sector size for device %s is %d bytes.", @@ -1826,45 +2056,6 @@ static int _crypt_format_luks2(struct crypt_device *cd, } else sector_size = params ? params->sector_size : SECTOR_SIZE; - if (sector_size < SECTOR_SIZE || sector_size > MAX_SECTOR_SIZE || - NOTPOW2(sector_size)) { - log_err(cd, _("Unsupported encryption sector size.")); - return -EINVAL; - } - if (sector_size != SECTOR_SIZE && !dm_flags(cd, DM_CRYPT, &dmc_flags) && - !(dmc_flags & DM_SECTOR_SIZE_SUPPORTED)) { - if (sector_size_autodetect) { - log_dbg(cd, "dm-crypt does not support encryption sector size option. Reverting to 512 bytes."); - sector_size = SECTOR_SIZE; - } else - log_std(cd, _("WARNING: The device activation will fail, dm-crypt is missing " - "support for requested encryption sector size.\n")); - } - - if (integrity) { - if (params->integrity_params) { - /* Standalone dm-integrity must not be used */ - if (params->integrity_params->integrity || - params->integrity_params->integrity_key_size) - return -EINVAL; - /* FIXME: journal encryption and MAC is here not yet supported */ - if (params->integrity_params->journal_crypt || - params->integrity_params->journal_integrity) - return -ENOTSUP; - } - if (!INTEGRITY_tag_size(integrity, cipher, cipher_mode)) { - if (!strcmp(integrity, "none")) - integrity = NULL; - else - return -EINVAL; - } - integrity_key_size = INTEGRITY_key_size(integrity); - if ((integrity_key_size < 0) || (integrity_key_size >= (int)volume_key_size)) { - log_err(cd, _("Volume key is too small for encryption with integrity extensions.")); - return -EINVAL; - } - } - r = device_check_access(cd, crypt_metadata_device(cd), DEV_EXCL); if (r < 0) return r; @@ -1901,67 +2092,45 @@ static int _crypt_format_luks2(struct crypt_device *cd, &required_alignment, &alignment_offset, DEFAULT_DISK_ALIGNMENT); + r = LUKS2_check_encryption_params(cd, cipher, cipher_mode, integrity, + volume_key_size, params, &integrity); + if (r < 0) + goto out; + r = device_size(crypt_data_device(cd), &dev_size); if (r < 0) goto out; - if (sector_size_autodetect) { - if (cd->data_offset && MISALIGNED(cd->data_offset, sector_size)) { - log_dbg(cd, "Data offset not aligned to sector size. Reverting to 512 bytes."); - sector_size = SECTOR_SIZE; - } else if (MISALIGNED(dev_size - (uint64_t)required_alignment - (uint64_t)alignment_offset, sector_size)) { - /* underflow does not affect misalignment checks */ - log_dbg(cd, "Device size is not aligned to sector size. Reverting to 512 bytes."); - sector_size = SECTOR_SIZE; - } - } + r = LUKS2_hdr_get_storage_params(cd, alignment_offset, required_alignment, + &metadata_size_bytes, &keyslots_size_bytes, &data_offset_bytes); + if (r < 0) + goto out; - /* FIXME: allow this later also for normal ciphers (check AF_ALG availability. */ - if (integrity && !integrity_key_size) { - r = crypt_cipher_check_kernel(cipher, cipher_mode, integrity, volume_key_size); - if (r < 0) { - log_err(cd, _("Cipher %s-%s (key size %zd bits) is not available."), - cipher, cipher_mode, volume_key_size * 8); - goto out; - } - } + r = LUKS2_check_encryption_sector(cd, dev_size, data_offset_bytes, sector_size, + sector_size_autodetect, integrity == NULL, + §or_size); + if (r < 0) + goto out; - if ((!integrity || integrity_key_size) && !crypt_cipher_wrapped_key(cipher, cipher_mode) && - !INTEGRITY_tag_size(NULL, cipher, cipher_mode)) { - r = LUKS_check_cipher(cd, volume_key_size - integrity_key_size, - cipher, cipher_mode); - if (r < 0) - goto out; + if (*cipher_mode != '\0') + r = snprintf(cipher_spec, sizeof(cipher_spec), "%s-%s", cipher, cipher_mode); + else + r = snprintf(cipher_spec, sizeof(cipher_spec), "%s", cipher); + if (r < 0 || (size_t)r >= sizeof(cipher_spec)) { + r = -EINVAL; + goto out; } r = LUKS2_generate_hdr(cd, &cd->u.luks2.hdr, cd->volume_key, - cipher, cipher_mode, + cipher_spec, integrity, uuid, sector_size, - cd->data_offset * SECTOR_SIZE, - alignment_offset, - required_alignment, - cd->metadata_size, cd->keyslots_size); + data_offset_bytes, + metadata_size_bytes, keyslots_size_bytes, + 0, 0, 0); if (r < 0) goto out; - if (cd->metadata_size && (cd->metadata_size != LUKS2_metadata_size(&cd->u.luks2.hdr))) - log_std(cd, _("WARNING: LUKS2 metadata size changed to %" PRIu64 " bytes.\n"), - LUKS2_metadata_size(&cd->u.luks2.hdr)); - - if (cd->keyslots_size && (cd->keyslots_size != LUKS2_keyslots_size(&cd->u.luks2.hdr))) - log_std(cd, _("WARNING: LUKS2 keyslots area size changed to %" PRIu64 " bytes.\n"), - LUKS2_keyslots_size(&cd->u.luks2.hdr)); - - if (!integrity && sector_size > SECTOR_SIZE) { - dev_size -= (crypt_get_data_offset(cd) * SECTOR_SIZE); - if (dev_size % sector_size) { - log_err(cd, _("Device size is not aligned to requested sector size.")); - r = -EINVAL; - goto out; - } - } - if (params && (params->label || params->subsystem)) { r = LUKS2_hdr_labels(cd, &cd->u.luks2.hdr, params->label, params->subsystem, 0); @@ -2000,7 +2169,7 @@ static int _crypt_format_luks2(struct crypt_device *cd, goto out; } - r = INTEGRITY_format(cd, params ? params->integrity_params : NULL, NULL, NULL); + r = INTEGRITY_format(cd, params ? params->integrity_params : NULL, NULL, NULL, 0); if (r) log_err(cd, _("Cannot format integrity for device %s."), data_device_path(cd)); @@ -2039,6 +2208,464 @@ out: return 0; } +static int opal_topology_alignment(struct crypt_device *cd, + uint64_t partition_offset_sectors, + uint64_t data_offset_sectors, + uint64_t required_alignment_sectors, + uint64_t default_alignment_bytes, + uint64_t *ret_alignment_offset_bytes, + uint64_t *ret_alignment_bytes, + uint32_t *ret_opal_block_bytes, + uint64_t *ret_opal_alignment_granularity_blocks) +{ + bool opal_align; + int r; + uint32_t opal_block_bytes; + uint64_t opal_alignment_granularity_blocks, opal_lowest_lba_blocks; + + assert(cd); + assert(ret_alignment_offset_bytes); + assert(ret_alignment_bytes); + assert(ret_opal_block_bytes); + assert(ret_opal_alignment_granularity_blocks); + + r = opal_geometry(cd, crypt_data_device(cd), &opal_align, &opal_block_bytes, + &opal_alignment_granularity_blocks, &opal_lowest_lba_blocks); + if (r) { + log_err(cd, _("Cannot get OPAL alignment parameters.")); + return -EINVAL; + } + + log_dbg(cd, "OPAL geometry: alignment: '%c', logical block size: %" PRIu32 + ", alignment granularity: %" PRIu64 ", lowest aligned LBA: %" PRIu64, + opal_align ? 'y' : 'n', opal_block_bytes, opal_alignment_granularity_blocks, opal_lowest_lba_blocks); + + if (opal_block_bytes < SECTOR_SIZE || NOTPOW2(opal_block_bytes)) { + log_err(cd, _("Bogus OPAL logical block size.")); + return -EINVAL; + } + + if (data_offset_sectors && + MISALIGNED(data_offset_sectors + partition_offset_sectors, opal_block_bytes / SECTOR_SIZE)) { + log_err(cd, _("Requested data offset is not compatible with OPAL block size.")); + return -EINVAL; + } + + /* Data offset has priority over data alignment parameter */ + if (!data_offset_sectors && + MISALIGNED(required_alignment_sectors, opal_block_bytes / SECTOR_SIZE)) { + log_err(cd, _("Requested data alignment is not compatible with OPAL alignment.")); + return -EINVAL; + } + + if (!opal_align) { + /* For detached header the alignment is used directly as data offset */ + if (required_alignment_sectors || cd->metadata_device) + *ret_alignment_bytes = required_alignment_sectors * SECTOR_SIZE; + else + *ret_alignment_bytes = default_alignment_bytes; + *ret_alignment_offset_bytes = 0; + *ret_opal_block_bytes = opal_block_bytes; + *ret_opal_alignment_granularity_blocks = 1; + return 0; + } + + if (data_offset_sectors) { + if (MISALIGNED((((data_offset_sectors + partition_offset_sectors) * SECTOR_SIZE) / opal_block_bytes) - opal_lowest_lba_blocks, + opal_alignment_granularity_blocks)) { + // FIXME: Add hint to user on how to fix it + log_err(cd, _("Data offset does not satisfy OPAL alignment requirements.")); + return -EINVAL; + } + + *ret_alignment_offset_bytes = 0; + *ret_alignment_bytes = 0; + *ret_opal_block_bytes = opal_block_bytes; + *ret_opal_alignment_granularity_blocks = opal_alignment_granularity_blocks; + + return 0; + } + + if (MISALIGNED(required_alignment_sectors * SECTOR_SIZE, opal_block_bytes * opal_alignment_granularity_blocks)) { + log_err(cd, _("Requested data alignment does not satisfy locking range alignment requirements.")); + return -EINVAL; + } + + /* For detached header the alignment is used directly as data offset */ + if (required_alignment_sectors || cd->metadata_device) + *ret_alignment_bytes = required_alignment_sectors * SECTOR_SIZE; + else + *ret_alignment_bytes = size_round_up(default_alignment_bytes, opal_block_bytes * opal_alignment_granularity_blocks); + + /* data offset is not set, calculate proper alignment */ + *ret_alignment_offset_bytes = (partition_offset_sectors * SECTOR_SIZE) % (opal_block_bytes * opal_alignment_granularity_blocks); + if (*ret_alignment_offset_bytes) + *ret_alignment_offset_bytes = opal_block_bytes * opal_alignment_granularity_blocks - *ret_alignment_offset_bytes; + + if (*ret_alignment_offset_bytes) + log_dbg(cd, "Compensating misaligned partition offset by %" PRIu64 "bytes.", + *ret_alignment_offset_bytes); + + *ret_alignment_offset_bytes += (opal_lowest_lba_blocks * opal_block_bytes); + *ret_opal_block_bytes = opal_block_bytes; + *ret_opal_alignment_granularity_blocks = opal_alignment_granularity_blocks; + + log_dbg(cd, "OPAL alignment (%" PRIu32 "/%" PRIu64 "), offset = %" PRIu64 ". Required alignment is %" PRIu64 ".", + opal_block_bytes, opal_alignment_granularity_blocks, *ret_alignment_offset_bytes, *ret_alignment_bytes); + + return 0; +} + +int crypt_format_luks2_opal(struct crypt_device *cd, + const char *cipher, + const char *cipher_mode, + const char *uuid, + const char *volume_keys, + size_t volume_keys_size, + struct crypt_params_luks2 *params, + struct crypt_params_hw_opal *opal_params) +{ + bool opal_range_reset = false, subsystem_overridden = false, sector_size_autodetect = cipher != NULL; + int r; + char cipher_spec[128]; + const char *integrity = params ? params->integrity : NULL; + uint32_t sector_size, opal_block_bytes, opal_segment_number = 1; /* We'll use the partition number if available later */ + uint64_t alignment_offset_bytes, data_offset_bytes, device_size_bytes, opal_alignment_granularity_blocks, + partition_offset_sectors, range_offset_blocks, range_size_bytes, + required_alignment_bytes, metadata_size_bytes, keyslots_size_bytes, + provided_data_sectors; + struct volume_key *user_key = NULL; + struct crypt_lock_handle *opal_lh = NULL; + + if (!cd || !params || !opal_params || + !opal_params->admin_key || !opal_params->admin_key_size || !opal_params->user_key_size) + return -EINVAL; + + if (cd->type) { + log_dbg(cd, "Context already formatted as %s.", cd->type); + return -EINVAL; + } + + log_dbg(cd, "Formatting device %s as type LUKS2 with OPAL HW encryption.", mdata_device_path(cd) ?: "(none)"); + + if (volume_keys_size < opal_params->user_key_size) + return -EINVAL; + + if (cipher && (volume_keys_size == opal_params->user_key_size)) + return -EINVAL; + + if (!crypt_metadata_device(cd)) { + log_err(cd, _("Can't format LUKS without device.")); + return -EINVAL; + } + + if (params->data_alignment && + MISALIGNED(cd->data_offset, params->data_alignment)) { + log_err(cd, _("Requested data alignment is not compatible with data offset.")); + return -EINVAL; + } + + if (params->data_device) { + if (!cd->metadata_device) + cd->metadata_device = cd->device; + else + device_free(cd, cd->device); + cd->device = NULL; + if (device_alloc(cd, &cd->device, params->data_device) < 0) + return -ENOMEM; + } + + r = crypt_opal_supported(cd, crypt_data_device(cd)); + if (r < 0) + return r; + + if (params->sector_size) + sector_size_autodetect = false; + + partition_offset_sectors = crypt_dev_partition_offset(device_path(crypt_data_device(cd))); + + r = device_check_access(cd, crypt_metadata_device(cd), DEV_EXCL); + if (r < 0) + return r; + + /* + * Check both data and metadata devices for exclusive access since + * we don't want to setup locking range on already used partition. + */ + if (crypt_metadata_device(cd) != crypt_data_device(cd)) { + r = device_check_access(cd, crypt_data_device(cd), DEV_EXCL); + if (r < 0) + return r; + } + + if (!(cd->type = strdup(CRYPT_LUKS2))) + return -ENOMEM; + + if (volume_keys) + cd->volume_key = crypt_alloc_volume_key(volume_keys_size, volume_keys); + else + cd->volume_key = crypt_generate_volume_key(cd, volume_keys_size); + + if (!cd->volume_key) { + r = -ENOMEM; + goto out; + } + + if (cipher) { + user_key = crypt_alloc_volume_key(opal_params->user_key_size, cd->volume_key->key); + if (!user_key) { + r = -ENOMEM; + goto out; + } + } + + r = 0; + if (params->pbkdf) + r = crypt_set_pbkdf_type(cd, params->pbkdf); + else if (verify_pbkdf_params(cd, &cd->pbkdf)) + r = init_pbkdf_type(cd, NULL, CRYPT_LUKS2); + + if (r < 0) + goto out; + + if (cd->metadata_device && !cd->data_offset) + /* For detached header the alignment is used directly as data offset */ + cd->data_offset = params->data_alignment; + + r = opal_topology_alignment(cd, partition_offset_sectors, + cd->data_offset, params->data_alignment, + DEFAULT_DISK_ALIGNMENT, &alignment_offset_bytes, &required_alignment_bytes, + &opal_block_bytes, &opal_alignment_granularity_blocks); + if (r < 0) + goto out; + + if (sector_size_autodetect) { + sector_size = device_optimal_encryption_sector_size(cd, crypt_data_device(cd)); + if ((opal_block_bytes * opal_alignment_granularity_blocks) > sector_size) + sector_size = opal_block_bytes * opal_alignment_granularity_blocks; + if (sector_size > MAX_SECTOR_SIZE) + sector_size = MAX_SECTOR_SIZE; + log_dbg(cd, "Auto-detected optimal encryption sector size for device %s is %d bytes.", + device_path(crypt_data_device(cd)), sector_size); + } else + sector_size = params->sector_size; + + /* To ensure it is obvious and explicit that OPAL is being used, set the + * subsystem tag if the user hasn't passed one. */ + if (!params->subsystem) { + params->subsystem = "HW-OPAL"; + subsystem_overridden = true; + } + + /* We need to give the drive a segment number - use the partition number if there is + * one, otherwise the first valid (1) number if it's a single-volume setup */ + r = crypt_dev_get_partition_number(device_path(crypt_data_device(cd))); + if (r > 0) + opal_segment_number = r; + + if (cipher) { + r = LUKS2_check_encryption_params(cd, cipher, cipher_mode, integrity, + volume_keys_size - opal_params->user_key_size, + params, &integrity); + if (r < 0) + goto out; + } + + r = device_size(crypt_data_device(cd), &device_size_bytes); + if (r < 0) + goto out; + + r = LUKS2_hdr_get_storage_params(cd, alignment_offset_bytes, required_alignment_bytes, + &metadata_size_bytes, &keyslots_size_bytes, &data_offset_bytes); + if (r < 0) + goto out; + + r = -EINVAL; + if (device_size_bytes < data_offset_bytes && !cd->metadata_device) { + log_err(cd, _("Device %s is too small."), device_path(crypt_data_device(cd))); + goto out; + } + + device_size_bytes -= data_offset_bytes; + range_size_bytes = device_size_bytes - (device_size_bytes % (opal_block_bytes * opal_alignment_granularity_blocks)); + if (!range_size_bytes) + goto out; + + if (device_size_bytes != range_size_bytes) + log_err(cd, _("Compensating device size by %" PRIu64 " sectors to align it with OPAL alignment granularity."), + (device_size_bytes - range_size_bytes) / SECTOR_SIZE); + + if (cipher) { + r = LUKS2_check_encryption_sector(cd, device_size_bytes, data_offset_bytes, sector_size, + sector_size_autodetect, integrity == NULL, + §or_size); + if (r < 0) + goto out; + + if (*cipher_mode != '\0') + r = snprintf(cipher_spec, sizeof(cipher_spec), "%s-%s", cipher, cipher_mode); + else + r = snprintf(cipher_spec, sizeof(cipher_spec), "%s", cipher); + if (r < 0 || (size_t)r >= sizeof(cipher_spec)) { + r = -EINVAL; + goto out; + } + } + + r = LUKS2_generate_hdr(cd, &cd->u.luks2.hdr, cd->volume_key, + cipher ? cipher_spec : NULL, integrity, uuid, + sector_size, + data_offset_bytes, + metadata_size_bytes, keyslots_size_bytes, + device_size_bytes, + opal_segment_number, + opal_params->user_key_size); + if (r < 0) + goto out; + + log_dbg(cd, "Adding LUKS2 OPAL requirement flag."); + r = LUKS2_config_set_requirement_version(cd, &cd->u.luks2.hdr, CRYPT_REQUIREMENT_OPAL, 1, false); + if (r < 0) + goto out; + + if (params->label || params->subsystem) { + r = LUKS2_hdr_labels(cd, &cd->u.luks2.hdr, + params->label, params->subsystem, 0); + if (r < 0) + goto out; + } + + device_set_block_size(crypt_data_device(cd), sector_size); + + r = LUKS2_wipe_header_areas(cd, &cd->u.luks2.hdr, cd->metadata_device != NULL); + if (r < 0) { + log_err(cd, _("Cannot wipe header on device %s."), + mdata_device_path(cd)); + if (device_size_bytes < LUKS2_hdr_and_areas_size(&cd->u.luks2.hdr)) + log_err(cd, _("Device %s is too small."), device_path(crypt_metadata_device(cd))); + goto out; + } + + range_offset_blocks = (data_offset_bytes + partition_offset_sectors * SECTOR_SIZE) / opal_block_bytes; + + r = opal_exclusive_lock(cd, crypt_data_device(cd), &opal_lh); + if (r < 0) { + log_err(cd, _("Failed to acquire OPAL lock on device %s."), device_path(crypt_data_device(cd))); + goto out; + } + + r = opal_setup_ranges(cd, crypt_data_device(cd), user_key ?: cd->volume_key, + range_offset_blocks, range_size_bytes / opal_block_bytes, + opal_segment_number, opal_params->admin_key, opal_params->admin_key_size); + if (r < 0) { + if (r == -EPERM) + log_err(cd, _("Incorrect OPAL Admin key.")); + else + log_err(cd, _("Cannot setup OPAL segment.")); + goto out; + } + + opal_range_reset = true; + + /* integrity metadata goes in unlocked OPAL locking range */ + if (crypt_get_integrity_tag_size(cd)) { + r = opal_unlock(cd, crypt_data_device(cd), opal_segment_number, user_key ?: cd->volume_key); + if (r < 0) + goto out; + + r = crypt_wipe_device(cd, crypt_data_device(cd), CRYPT_WIPE_ZERO, + crypt_get_data_offset(cd) * SECTOR_SIZE, + 8 * SECTOR_SIZE, 8 * SECTOR_SIZE, NULL, NULL); + if (r < 0) { + if (r == -EBUSY) + log_err(cd, _("Cannot format device %s in use."), + data_device_path(cd)); + else if (r == -EACCES) { + log_err(cd, _("Cannot format device %s, permission denied."), + data_device_path(cd)); + r = -EINVAL; + } else + log_err(cd, _("Cannot wipe header on device %s."), + data_device_path(cd)); + + goto out; + } + + r = INTEGRITY_format(cd, params->integrity_params, NULL, NULL, + /* + * Create reduced dm-integrity device only if locking range size does + * not match device size. + */ + device_size_bytes != range_size_bytes ? range_size_bytes / SECTOR_SIZE : 0); + if (r) + log_err(cd, _("Cannot format integrity for device %s."), + data_device_path(cd)); + if (r < 0) + goto out; + + r = INTEGRITY_data_sectors(cd, crypt_data_device(cd), + crypt_get_data_offset(cd) * SECTOR_SIZE, + &provided_data_sectors); + if (r < 0) + goto out; + + if (!LUKS2_segment_set_size(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT, + &(uint64_t) {provided_data_sectors * SECTOR_SIZE})) { + r = -EINVAL; + goto out; + } + + r = opal_lock(cd, crypt_data_device(cd), opal_segment_number); + if (r < 0) + goto out; + } + + /* override sequence id check with format */ + r = LUKS2_hdr_write_force(cd, &cd->u.luks2.hdr); + if (r < 0) { + if (r == -EBUSY) + log_err(cd, _("Cannot format device %s in use."), + mdata_device_path(cd)); + else if (r == -EACCES) { + log_err(cd, _("Cannot format device %s, permission denied."), + mdata_device_path(cd)); + r = -EINVAL; + } else if (r == -EIO) { + log_err(cd, _("Cannot format device %s, OPAL device seems to be fully write-protected now."), + mdata_device_path(cd)); + log_err(cd, _("This is perhaps a bug in firmware. Run OPAL PSID reset and reconnect for recovery.")); + } else + log_err(cd, _("Cannot format device %s."), + mdata_device_path(cd)); + } + +out: + crypt_free_volume_key(user_key); + + if (subsystem_overridden) + params->subsystem = NULL; + + if (r >= 0) { + opal_exclusive_unlock(cd, opal_lh); + return 0; + } + + if (opal_range_reset && + (opal_reset_segment(cd, crypt_data_device(cd), opal_segment_number, + opal_params->admin_key, opal_params->admin_key_size) < 0)) + log_err(cd, _("Locking range %d reset on device %s failed."), + opal_segment_number, device_path(crypt_data_device(cd))); + + opal_exclusive_unlock(cd, opal_lh); + LUKS2_hdr_free(cd, &cd->u.luks2.hdr); + + crypt_set_null_type(cd); + crypt_free_volume_key(cd->volume_key); + cd->volume_key = NULL; + + return r; +} + static int _crypt_format_loopaes(struct crypt_device *cd, const char *cipher, const char *uuid, @@ -2329,7 +2956,7 @@ static int _crypt_format_integrity(struct crypt_device *cd, cd->u.integrity.params.journal_integrity = journal_integrity; cd->u.integrity.params.journal_crypt = journal_crypt; - r = INTEGRITY_format(cd, params, cd->u.integrity.journal_crypt_key, cd->u.integrity.journal_mac_key); + r = INTEGRITY_format(cd, params, cd->u.integrity.journal_crypt_key, cd->u.integrity.journal_mac_key, 0); if (r) log_err(cd, _("Cannot format integrity for device %s."), mdata_device_path(cd)); @@ -2674,7 +3301,7 @@ int crypt_compare_dm_devices(struct crypt_device *cd, } static int _reload_device(struct crypt_device *cd, const char *name, - struct crypt_dm_active_device *sdmd) + struct crypt_dm_active_device *sdmd, uint32_t dmflags) { int r; struct crypt_dm_active_device tdmd; @@ -2742,7 +3369,7 @@ static int _reload_device(struct crypt_device *cd, const char *name, tdmd.flags = sdmd->flags; tgt->size = tdmd.size = sdmd->size; - r = dm_reload_device(cd, name, &tdmd, 0, 1); + r = dm_reload_device(cd, name, &tdmd, dmflags, 1); out: dm_targets_free(cd, &tdmd); free(CONST_CAST(void*)tdmd.uuid); @@ -2925,15 +3552,10 @@ int crypt_resize(struct crypt_device *cd, const char *name, uint64_t new_size) struct crypt_dm_active_device dmdq, dmd = {}; struct dm_target *tgt = &dmdq.segment; struct crypt_params_integrity params = {}; - uint32_t supported_flags = 0; + uint32_t supported_flags = 0, dmflags = 0; uint64_t old_size; int r; - /* - * FIXME: Also with LUKS2 we must not allow resize when there's - * explicit size stored in metadata (length != "dynamic") - */ - /* Device context type must be initialized */ if (!cd || !cd->type || !name) return -EINVAL; @@ -2943,7 +3565,15 @@ int crypt_resize(struct crypt_device *cd, const char *name, uint64_t new_size) return -ENOTSUP; } - log_dbg(cd, "Resizing device %s to %" PRIu64 " sectors.", name, new_size); + if (isLUKS2(cd->type) && !LUKS2_segments_dynamic_size(&cd->u.luks2.hdr)) { + log_err(cd, _("Can not resize LUKS2 device with static size.")); + return -EINVAL; + } + + if (new_size) + log_dbg(cd, "Resizing device %s to %" PRIu64 " sectors.", name, new_size); + else + log_dbg(cd, "Resizing device %s to underlying device size.", name); r = dm_query_device(cd, name, DM_ACTIVE_CRYPT_KEYSIZE | DM_ACTIVE_CRYPT_KEY | DM_ACTIVE_INTEGRITY_PARAMS | DM_ACTIVE_JOURNAL_CRYPT_KEY | @@ -3011,7 +3641,8 @@ int crypt_resize(struct crypt_device *cd, const char *name, uint64_t new_size) tgt->u.integrity.journal_integrity_key, ¶ms); if (r) goto out; - r = _reload_device(cd, name, &dmd); + /* Backend device cannot be smaller here, device_block_adjust() will fail if so. */ + r = _reload_device(cd, name, &dmd, DM_SUSPEND_SKIP_LOCKFS | DM_SUSPEND_NOFLUSH); if (r) goto out; @@ -3079,8 +3710,13 @@ int crypt_resize(struct crypt_device *cd, const char *name, uint64_t new_size) r = -ENOTSUP; else if (isLUKS2(cd->type)) r = LUKS2_unmet_requirements(cd, &cd->u.luks2.hdr, 0, 0); - if (!r) - r = _reload_device(cd, name, &dmd); + + if (!r) { + /* Skip flush and lockfs if extending device */ + if (new_size > dmdq.size) + dmflags = DM_SUSPEND_SKIP_LOCKFS | DM_SUSPEND_NOFLUSH; + r = _reload_device(cd, name, &dmd, dmflags); + } if (r && tgt->type == DM_INTEGRITY && !dm_flags(cd, tgt->type, &supported_flags) && @@ -3271,6 +3907,8 @@ void crypt_free(struct crypt_device *cd) free(CONST_CAST(void*)cd->pbkdf.type); free(CONST_CAST(void*)cd->pbkdf.hash); + free(CONST_CAST(void*)cd->user_key_name1); + free(CONST_CAST(void*)cd->user_key_name2); /* Some structures can contain keys (TCRYPT), wipe it */ crypt_safe_memzero(cd, sizeof(*cd)); @@ -3298,38 +3936,85 @@ static char *crypt_get_device_key_description(struct crypt_device *cd, const cha int crypt_suspend(struct crypt_device *cd, const char *name) { - char *key_desc; + bool dm_opal_uuid; crypt_status_info ci; int r; - uint32_t dmflags = DM_SUSPEND_WIPE_KEY; - - /* FIXME: check context uuid matches the dm-crypt device uuid (onlyLUKS branching) */ + struct crypt_dm_active_device dmd, dmdi = {}; + uint32_t opal_segment_number = 1, dmflags = DM_SUSPEND_WIPE_KEY; + struct dm_target *tgt = &dmd.segment; + char *key_desc = NULL, *iname = NULL; + struct crypt_lock_handle *opal_lh = NULL; if (!cd || !name) return -EINVAL; log_dbg(cd, "Suspending volume %s.", name); - if (cd->type) - r = onlyLUKS(cd); - else { - r = crypt_uuid_type_cmp(cd, CRYPT_LUKS1); - if (r < 0) - r = crypt_uuid_type_cmp(cd, CRYPT_LUKS2); - if (r < 0) - log_err(cd, _("This operation is supported only for LUKS device.")); - } - - if (r < 0) + if (cd->type && ((r = onlyLUKS(cd)) < 0)) return r; - ci = crypt_status(NULL, name); + ci = crypt_status(cd, name); if (ci < CRYPT_ACTIVE) { log_err(cd, _("Volume %s is not active."), name); return -EINVAL; } - dm_backend_init(cd); + r = dm_query_device(cd, name, DM_ACTIVE_UUID, &dmd); + if (r < 0) + return r; + + log_dbg(cd, "Checking if active device %s has UUID type LUKS.", name); + + r = crypt_uuid_type_cmp(dmd.uuid, CRYPT_LUKS2); + if (r < 0) + r = crypt_uuid_type_cmp(dmd.uuid, CRYPT_LUKS1); + + if (r < 0) { + log_err(cd, _("This operation is supported only for LUKS device.")); + goto out; + } + + r = -EINVAL; + + if (isLUKS2(cd->type) && crypt_uuid_type_cmp(dmd.uuid, CRYPT_LUKS2)) { + log_dbg(cd, "LUKS device header type: %s mismatches DM device type.", cd->type); + goto out; + } + + if (isLUKS1(cd->type) && crypt_uuid_type_cmp(dmd.uuid, CRYPT_LUKS1)) { + log_dbg(cd, "LUKS device header type: %s mismatches DM device type.", cd->type); + goto out; + } + + /* check if active device has LUKS2-OPAL dm uuid prefix */ + dm_opal_uuid = !crypt_uuid_type_cmp(dmd.uuid, CRYPT_LUKS2_HW_OPAL); + + if (!dm_opal_uuid && isLUKS2(cd->type) && + LUKS2_segment_is_hw_opal(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT)) + goto out; + + if (cd->type && (r = crypt_uuid_cmp(dmd.uuid, LUKS_UUID(cd))) < 0) { + log_dbg(cd, "LUKS device header uuid: %s mismatches DM returned uuid %s", + LUKS_UUID(cd), dmd.uuid); + goto out; + } + + /* check UUID of integrity device underneath crypt device */ + if (crypt_get_integrity_tag_size(cd)) { + r = dm_get_iname(name, &iname, false); + if (r) + goto out; + + r = dm_query_device(cd, iname, DM_ACTIVE_UUID, &dmdi); + if (r < 0) + goto out; + + r = crypt_uuid_integrity_cmp(dmd.uuid, dmdi.uuid); + if (r < 0) { + log_dbg(cd, "Integrity device uuid: %s mismatches crypt device uuid %s", dmdi.uuid, dmd.uuid); + goto out; + } + } r = dm_status_suspended(cd, name); if (r < 0) @@ -3343,76 +4028,311 @@ int crypt_suspend(struct crypt_device *cd, key_desc = crypt_get_device_key_description(cd, name); - /* we can't simply wipe wrapped keys */ - if (crypt_cipher_wrapped_key(crypt_get_cipher(cd), crypt_get_cipher_mode(cd))) + if (dm_opal_uuid && crypt_data_device(cd)) { + if (isLUKS2(cd->type)) { + r = LUKS2_get_opal_segment_number(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT, &opal_segment_number); + if (r < 0) + goto out; + } else { + /* Guess OPAL range number for LUKS2-OPAL device with missing header */ + r = crypt_dev_get_partition_number(device_path(crypt_data_device(cd))); + if (r > 0) + opal_segment_number = r; + } + } + + /* we can't simply wipe wrapped keys. HW OPAL only encryption does not use dm-crypt target */ + if (crypt_cipher_wrapped_key(crypt_get_cipher(cd), crypt_get_cipher_mode(cd)) || + (dm_opal_uuid && tgt->type == DM_LINEAR)) dmflags &= ~DM_SUSPEND_WIPE_KEY; r = dm_suspend_device(cd, name, dmflags); - if (r == -ENOTSUP) - log_err(cd, _("Suspend is not supported for device %s."), name); - else if (r) - log_err(cd, _("Error during suspending device %s."), name); - else - crypt_drop_keyring_key_by_description(cd, key_desc, LOGON_KEY); - free(key_desc); + if (r) { + if (r == -ENOTSUP) + log_err(cd, _("Suspend is not supported for device %s."), name); + else + log_err(cd, _("Error during suspending device %s."), name); + goto out; + } + + /* Suspend integrity device underneath; keep crypt suspended if it fails */ + if (crypt_get_integrity_tag_size(cd)) { + r = dm_suspend_device(cd, iname, 0); + if (r) + log_err(cd, _("Error during suspending device %s."), iname); + } + + crypt_drop_keyring_key_by_description(cd, key_desc, cd->keyring_key_type); + + if (dm_opal_uuid && crypt_data_device(cd)) { + r = opal_exclusive_lock(cd, crypt_data_device(cd), &opal_lh); + if (r < 0) { + log_err(cd, _("Failed to acquire OPAL lock on device %s."), device_path(crypt_data_device(cd))); + goto out; + } + } + + if (dm_opal_uuid && (!crypt_data_device(cd) || opal_lock(cd, crypt_data_device(cd), opal_segment_number))) + log_err(cd, _("Device %s was suspended but hardware OPAL device cannot be locked."), name); out: - dm_backend_exit(cd); + opal_exclusive_unlock(cd, opal_lh); + free(key_desc); + free(iname); + dm_targets_free(cd, &dmd); + dm_targets_free(cd, &dmdi); + free(CONST_CAST(void*)dmd.uuid); + free(CONST_CAST(void*)dmdi.uuid); return r; } -/* key must be properly verified */ -static int resume_by_volume_key(struct crypt_device *cd, +static int resume_luks1_by_volume_key(struct crypt_device *cd, struct volume_key *vk, const char *name) { - int digest, r; + int r; struct volume_key *zerokey = NULL; + assert(vk && crypt_volume_key_get_id(vk) == 0); + assert(name); + if (crypt_is_cipher_null(crypt_get_cipher_spec(cd))) { zerokey = crypt_alloc_volume_key(0, NULL); if (!zerokey) return -ENOMEM; vk = zerokey; - } else if (crypt_use_keyring_for_vk(cd)) { - /* LUKS2 path only */ - digest = LUKS2_digest_by_segment(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT); - if (digest < 0) + } + + r = dm_resume_and_reinstate_key(cd, name, vk); + + if (r == -ENOTSUP) + log_err(cd, _("Resume is not supported for device %s."), name); + else if (r) + log_err(cd, _("Error during resuming device %s."), name); + + crypt_free_volume_key(zerokey); + + return r; +} + +static void crypt_unlink_key_from_custom_keyring(struct crypt_device *cd, key_serial_t kid) +{ + assert(cd); + assert(cd->keyring_to_link_vk); + + log_dbg(cd, "Unlinking volume key (id: %" PRIi32 ") from kernel keyring (id: %" PRIi32 ").", + kid, cd->keyring_to_link_vk); + + if (!keyring_unlink_key_from_keyring(kid, cd->keyring_to_link_vk)) + return; + + log_dbg(cd, "keyring_unlink_key_from_keyring failed with errno %d.", errno); + log_err(cd, _("Failed to unlink volume key from user specified keyring.")); +} + +static key_serial_t crypt_single_volume_key_load_in_user_keyring(struct crypt_device *cd, struct volume_key *vk, const char *user_key_name) +{ + key_serial_t kid; + const char *type_name; + + assert(cd); + assert(cd->link_vk_to_keyring); + + if (!vk || !(type_name = key_type_name(cd->keyring_key_type))) + return -EINVAL; + + log_dbg(cd, "Linking volume key (type %s, name %s) to the specified keyring", + type_name, user_key_name); + + kid = keyring_add_key_to_custom_keyring(cd->keyring_key_type, user_key_name, vk->key, vk->keylength, cd->keyring_to_link_vk); + if (kid <= 0) { + log_dbg(cd, "The keyring_link_key_to_keyring function failed (error %d).", errno); + } + + return kid; +} + +static int crypt_volume_key_load_in_user_keyring(struct crypt_device *cd, struct volume_key *vk, key_serial_t *kid1_out, key_serial_t *kid2_out) +{ + key_serial_t kid1, kid2 = 0; + + assert(cd); + assert(cd->link_vk_to_keyring); + assert(cd->user_key_name1); + + if (!vk || !key_type_name(cd->keyring_key_type)) + return -EINVAL; + + kid1 = crypt_single_volume_key_load_in_user_keyring(cd, vk, cd->user_key_name1); + if (kid1 <= 0) + return -EINVAL; + + vk = vk->next; + if (vk) { + assert(cd->user_key_name2); + kid2 = crypt_single_volume_key_load_in_user_keyring(cd, vk, cd->user_key_name2); + if (kid2 <= 0) { + crypt_unlink_key_from_custom_keyring(cd, kid1); return -EINVAL; - r = LUKS2_volume_key_load_in_keyring_by_digest(cd, vk, digest); + } + } + + *kid2_out = kid2; + *kid1_out = kid1; + return 0; +} + +static int resume_luks2_by_volume_key(struct crypt_device *cd, + int digest, + struct volume_key *vk, + const char *name) +{ + bool use_keyring; + int r, enc_type; + uint32_t opal_segment_number; + struct volume_key *p_crypt = vk, *p_opal = NULL, *zerokey = NULL, *crypt_key = NULL, *opal_key = NULL; + char *iname = NULL; + struct crypt_lock_handle *opal_lh = NULL; + key_serial_t kid1 = 0, kid2 = 0; + + assert(digest >= 0); + assert(vk && crypt_volume_key_get_id(vk) == digest); + assert(name); + + enc_type = crypt_get_hw_encryption_type(cd); + if (enc_type < 0) + return enc_type; + + use_keyring = crypt_use_keyring_for_vk(cd); + + if (enc_type == CRYPT_OPAL_HW_ONLY || enc_type == CRYPT_SW_AND_OPAL_HW) { + r = LUKS2_get_opal_segment_number(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT, + &opal_segment_number); if (r < 0) return r; + + r = LUKS2_split_crypt_and_opal_keys(cd, &cd->u.luks2.hdr, + vk, &crypt_key, + &opal_key); + if (r < 0) + return r; + + p_crypt = crypt_key; + p_opal = opal_key ?: vk; } - r = dm_resume_and_reinstate_key(cd, name, vk); + if (enc_type != CRYPT_OPAL_HW_ONLY && crypt_is_cipher_null(crypt_get_cipher_spec(cd))) { + zerokey = crypt_alloc_volume_key(0, NULL); + if (!zerokey) { + r = -ENOMEM; + goto out; + } + p_crypt = zerokey; + use_keyring = false; + } + + if (use_keyring) { + if (p_crypt) { + r = LUKS2_volume_key_load_in_keyring_by_digest(cd, p_crypt, digest); + if (r < 0) + goto out; + } + + /* upload volume key in custom keyring if requested */ + if (cd->link_vk_to_keyring) { + r = crypt_volume_key_load_in_user_keyring(cd, vk, &kid1, &kid2); + if (r < 0) { + log_err(cd, _("Failed to link volume key in user defined keyring.")); + goto out; + } + } + } + + if (p_opal) { + r = opal_exclusive_lock(cd, crypt_data_device(cd), &opal_lh); + if (r < 0) { + log_err(cd, _("Failed to acquire OPAL lock on device %s."), device_path(crypt_data_device(cd))); + goto out; + } + + r = opal_unlock(cd, crypt_data_device(cd), opal_segment_number, p_opal); + if (r < 0) { + p_opal = NULL; /* do not lock on error path */ + goto out; + } + } + + if (crypt_get_integrity_tag_size(cd)) { + r = dm_get_iname(name, &iname, false); + if (r) + goto out; + + r = dm_resume_device(cd, iname, 0); + if (r) + log_err(cd, _("Error during resuming device %s."), iname); + } + + if (enc_type == CRYPT_OPAL_HW_ONLY) + r = dm_resume_device(cd, name, 0); + else + r = dm_resume_and_reinstate_key(cd, name, p_crypt); if (r == -ENOTSUP) log_err(cd, _("Resume is not supported for device %s."), name); else if (r) log_err(cd, _("Error during resuming device %s."), name); - if (r < 0) - crypt_drop_keyring_key(cd, vk); +out: + if (r < 0) { + crypt_drop_keyring_key(cd, p_crypt); + if (cd->link_vk_to_keyring && kid1) + crypt_unlink_key_from_custom_keyring(cd, kid1); + if (cd->link_vk_to_keyring && kid2) + crypt_unlink_key_from_custom_keyring(cd, kid2); + } + if (r < 0 && p_opal) + opal_lock(cd, crypt_data_device(cd), opal_segment_number); + + opal_exclusive_unlock(cd, opal_lh); crypt_free_volume_key(zerokey); + crypt_free_volume_key(opal_key); + crypt_free_volume_key(crypt_key); + free(iname); + + return r; +} + +/* key must be properly verified */ +static int resume_by_volume_key(struct crypt_device *cd, + struct volume_key *vk, + const char *name) +{ + assert(cd); + + if (isLUKS2(cd->type)) + return resume_luks2_by_volume_key(cd, + LUKS2_digest_by_segment(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT), + vk, name); + + if (isLUKS1(cd->type)) + return resume_luks1_by_volume_key(cd, vk, name); - return r; + return -EINVAL; } -int crypt_resume_by_passphrase(struct crypt_device *cd, +int crypt_resume_by_keyslot_context(struct crypt_device *cd, const char *name, int keyslot, - const char *passphrase, - size_t passphrase_size) + struct crypt_keyslot_context *kc) { - struct volume_key *vk = NULL; int r; + struct volume_key *vk = NULL; + int unlocked_keyslot = -EINVAL; - /* FIXME: check context uuid matches the dm-crypt device uuid */ - - if (!passphrase || !name) + if (!name) return -EINVAL; - log_dbg(cd, "Resuming volume %s.", name); + log_dbg(cd, "Resuming volume %s [keyslot %d] using %s.", name, keyslot, keyslot_context_type_string(kc)); if ((r = onlyLUKS(cd))) return r; @@ -3426,21 +4346,50 @@ int crypt_resume_by_passphrase(struct crypt_device *cd, return -EINVAL; } - if (isLUKS1(cd->type)) - r = LUKS_open_key_with_hdr(keyslot, passphrase, passphrase_size, - &cd->u.luks1.hdr, &vk, cd); + if (isLUKS1(cd->type) && kc->get_luks1_volume_key) + r = kc->get_luks1_volume_key(cd, kc, keyslot, &vk); + else if (isLUKS2(cd->type) && kc->get_luks2_volume_key) + r = kc->get_luks2_volume_key(cd, kc, keyslot, &vk); else - r = LUKS2_keyslot_open(cd, keyslot, CRYPT_DEFAULT_SEGMENT, passphrase, passphrase_size, &vk); - - if (r < 0) - return r; + r = -EINVAL; + if (r < 0) + goto out; + unlocked_keyslot = r; - keyslot = r; + if (isLUKS1(cd->type)) { + r = LUKS_verify_volume_key(&cd->u.luks1.hdr, vk); + crypt_volume_key_set_id(vk, 0); + } else if (isLUKS2(cd->type)) { + r = LUKS2_digest_verify_by_segment(cd, &cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT, vk); + crypt_volume_key_set_id(vk, r); + } else + r = -EINVAL; + if (r < 0) + goto out; r = resume_by_volume_key(cd, vk, name); crypt_free_volume_key(vk); - return r < 0 ? r : keyslot; + return r < 0 ? r : unlocked_keyslot; +out: + crypt_free_volume_key(vk); + return r; +} + +int crypt_resume_by_passphrase(struct crypt_device *cd, + const char *name, + int keyslot, + const char *passphrase, + size_t passphrase_size) +{ + int r; + struct crypt_keyslot_context kc; + + crypt_keyslot_unlock_by_passphrase_init_internal(&kc, passphrase, passphrase_size); + r = crypt_resume_by_keyslot_context(cd, name, keyslot, &kc); + crypt_keyslot_context_destroy_internal(&kc); + + return r; } int crypt_resume_by_keyfile_device_offset(struct crypt_device *cd, @@ -3450,53 +4399,14 @@ int crypt_resume_by_keyfile_device_offset(struct crypt_device *cd, size_t keyfile_size, uint64_t keyfile_offset) { - struct volume_key *vk = NULL; - char *passphrase_read = NULL; - size_t passphrase_size_read; int r; + struct crypt_keyslot_context kc; - /* FIXME: check context uuid matches the dm-crypt device uuid */ - - if (!name || !keyfile) - return -EINVAL; - - log_dbg(cd, "Resuming volume %s.", name); - - if ((r = onlyLUKS(cd))) - return r; - - r = dm_status_suspended(cd, name); - if (r < 0) - return r; - - if (!r) { - log_err(cd, _("Volume %s is not suspended."), name); - return -EINVAL; - } - - r = crypt_keyfile_device_read(cd, keyfile, - &passphrase_read, &passphrase_size_read, - keyfile_offset, keyfile_size, 0); - if (r < 0) - return r; - - if (isLUKS1(cd->type)) - r = LUKS_open_key_with_hdr(keyslot, passphrase_read, passphrase_size_read, - &cd->u.luks1.hdr, &vk, cd); - else - r = LUKS2_keyslot_open(cd, keyslot, CRYPT_DEFAULT_SEGMENT, - passphrase_read, passphrase_size_read, &vk); - - crypt_safe_free(passphrase_read); - if (r < 0) - return r; - - keyslot = r; - - r = resume_by_volume_key(cd, vk, name); + crypt_keyslot_unlock_by_keyfile_init_internal(&kc, keyfile, keyfile_size, keyfile_offset); + r = crypt_resume_by_keyslot_context(cd, name, keyslot, &kc); + crypt_keyslot_context_destroy_internal(&kc); - crypt_free_volume_key(vk); - return r < 0 ? r : keyslot; + return r; } int crypt_resume_by_keyfile(struct crypt_device *cd, @@ -3525,43 +4435,16 @@ int crypt_resume_by_volume_key(struct crypt_device *cd, const char *volume_key, size_t volume_key_size) { - struct volume_key *vk = NULL; int r; + struct crypt_keyslot_context kc; - if (!name || !volume_key) - return -EINVAL; - - log_dbg(cd, "Resuming volume %s by volume key.", name); - - if ((r = onlyLUKS(cd))) - return r; - - r = dm_status_suspended(cd, name); - if (r < 0) - return r; - - if (!r) { - log_err(cd, _("Volume %s is not suspended."), name); - return -EINVAL; - } - - vk = crypt_alloc_volume_key(volume_key_size, volume_key); - if (!vk) - return -ENOMEM; + crypt_keyslot_unlock_by_key_init_internal(&kc, volume_key, volume_key_size); + r = crypt_resume_by_keyslot_context(cd, name, CRYPT_ANY_SLOT /* unused */, &kc); + crypt_keyslot_context_destroy_internal(&kc); - if (isLUKS1(cd->type)) - r = LUKS_verify_volume_key(&cd->u.luks1.hdr, vk); - else if (isLUKS2(cd->type)) - r = LUKS2_digest_verify_by_segment(cd, &cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT, vk); - else - r = -EINVAL; if (r == -EPERM || r == -ENOENT) log_err(cd, _("Volume key does not match the volume.")); - if (r >= 0) - r = resume_by_volume_key(cd, vk, name); - - crypt_free_volume_key(vk); return r; } @@ -3569,35 +4452,14 @@ int crypt_resume_by_token_pin(struct crypt_device *cd, const char *name, const char *type, int token, const char *pin, size_t pin_size, void *usrptr) { - struct volume_key *vk = NULL; - int r, keyslot; - - if (!name) - return -EINVAL; - - log_dbg(cd, "Resuming volume %s by token (%s type) %d.", - name, type ?: "any", token); - - if ((r = _onlyLUKS2(cd, CRYPT_CD_QUIET, 0))) - return r; - - r = dm_status_suspended(cd, name); - if (r < 0) - return r; - - if (!r) { - log_err(cd, _("Volume %s is not suspended."), name); - return -EINVAL; - } + int r; + struct crypt_keyslot_context kc; - r = LUKS2_token_unlock_key(cd, &cd->u.luks2.hdr, token, type, - pin, pin_size, CRYPT_DEFAULT_SEGMENT, usrptr, &vk); - keyslot = r; - if (r >= 0) - r = resume_by_volume_key(cd, vk, name); + crypt_keyslot_unlock_by_token_init_internal(&kc, token, type, pin, pin_size, usrptr); + r = crypt_resume_by_keyslot_context(cd, name, CRYPT_ANY_SLOT, &kc); + crypt_keyslot_context_destroy_internal(&kc); - crypt_free_volume_key(vk); - return r < 0 ? r : keyslot; + return r; } /* @@ -3635,7 +4497,8 @@ int crypt_keyslot_change_by_passphrase(struct crypt_device *cd, const char *new_passphrase, size_t new_passphrase_size) { - int digest = -1, r, keyslot_new_orig = keyslot_new; + bool keyslot_swap = false; + int digest = -1, r; struct luks2_keyslot_params params; struct volume_key *vk = NULL; @@ -3670,13 +4533,21 @@ int crypt_keyslot_change_by_passphrase(struct crypt_device *cd, } keyslot_old = r; - if (keyslot_new == CRYPT_ANY_SLOT) { - if (isLUKS1(cd->type)) - keyslot_new = LUKS_keyslot_find_empty(&cd->u.luks1.hdr); - else if (isLUKS2(cd->type)) + if (isLUKS2(cd->type)) { + /* If there is a free keyslot (both id and binary area) avoid in-place keyslot area overwrite */ + if (keyslot_new == CRYPT_ANY_SLOT || keyslot_new == keyslot_old) { keyslot_new = LUKS2_keyslot_find_empty(cd, &cd->u.luks2.hdr, vk->keylength); - if (keyslot_new < 0) - keyslot_new = keyslot_old; + if (keyslot_new < 0) + keyslot_new = keyslot_old; + else + keyslot_swap = true; + } + } else if (isLUKS1(cd->type)) { + if (keyslot_new == CRYPT_ANY_SLOT) { + keyslot_new = LUKS_keyslot_find_empty(&cd->u.luks1.hdr); + if (keyslot_new < 0) + keyslot_new = keyslot_old; + } } log_dbg(cd, "Key change, old slot %d, new slot %d.", keyslot_old, keyslot_new); @@ -3699,16 +4570,8 @@ int crypt_keyslot_change_by_passphrase(struct crypt_device *cd, r = LUKS2_token_assignment_copy(cd, &cd->u.luks2.hdr, keyslot_old, keyslot_new, 0); if (r < 0) goto out; - } else { + } else log_dbg(cd, "Key slot %d is going to be overwritten.", keyslot_old); - /* FIXME: improve return code so that we can detect area is damaged */ - r = LUKS2_keyslot_wipe(cd, &cd->u.luks2.hdr, keyslot_old, 1); - if (r) { - /* (void)crypt_keyslot_destroy(cd, keyslot_old); */ - r = -EINVAL; - goto out; - } - } r = LUKS2_keyslot_store(cd, &cd->u.luks2.hdr, keyslot_new, new_passphrase, @@ -3717,7 +4580,7 @@ int crypt_keyslot_change_by_passphrase(struct crypt_device *cd, goto out; /* Swap old & new so the final keyslot number remains */ - if (keyslot_new_orig == CRYPT_ANY_SLOT && keyslot_old != keyslot_new) { + if (keyslot_swap && keyslot_old != keyslot_new) { r = LUKS2_keyslot_swap(cd, &cd->u.luks2.hdr, keyslot_old, keyslot_new); if (r < 0) goto out; @@ -3827,7 +4690,7 @@ int crypt_keyslot_destroy(struct crypt_device *cd, int keyslot) log_dbg(cd, "Destroying keyslot %d.", keyslot); - if ((r = _onlyLUKS(cd, CRYPT_CD_UNRESTRICTED))) + if ((r = onlyLUKSunrestricted(cd))) return r; ki = crypt_keyslot_status(cd, keyslot); @@ -3844,7 +4707,7 @@ int crypt_keyslot_destroy(struct crypt_device *cd, int keyslot) return LUKS_del_key(keyslot, &cd->u.luks1.hdr, cd); } - return LUKS2_keyslot_wipe(cd, &cd->u.luks2.hdr, keyslot, 0); + return LUKS2_keyslot_wipe(cd, &cd->u.luks2.hdr, keyslot); } static int _check_header_data_overlap(struct crypt_device *cd, const char *name) @@ -3960,12 +4823,14 @@ int create_or_reload_device(struct crypt_device *cd, const char *name, int r; enum devcheck device_check; struct dm_target *tgt; + uint64_t offset; + uint32_t dmflags = 0; if (!type || !name || !single_segment(dmd)) return -EINVAL; tgt = &dmd->segment; - if (tgt->type != DM_CRYPT && tgt->type != DM_INTEGRITY) + if (tgt->type != DM_CRYPT && tgt->type != DM_INTEGRITY && tgt->type != DM_LINEAR) return -EINVAL; /* drop CRYPT_ACTIVATE_REFRESH flag if any device is inactive */ @@ -3973,14 +4838,18 @@ int create_or_reload_device(struct crypt_device *cd, const char *name, if (r) return r; - if (dmd->flags & CRYPT_ACTIVATE_REFRESH) - r = _reload_device(cd, name, dmd); - else { - if (tgt->type == DM_CRYPT) { + if (dmd->flags & CRYPT_ACTIVATE_REFRESH) { + /* Refresh and recalculate means increasing dm-integrity device */ + if (tgt->type == DM_INTEGRITY && dmd->flags & CRYPT_ACTIVATE_RECALCULATE) + dmflags = DM_SUSPEND_SKIP_LOCKFS | DM_SUSPEND_NOFLUSH;; + r = _reload_device(cd, name, dmd, dmflags); + } else { + if (tgt->type == DM_CRYPT || tgt->type == DM_LINEAR) { device_check = dmd->flags & CRYPT_ACTIVATE_SHARED ? DEV_OK : DEV_EXCL; + offset = tgt->type == DM_CRYPT ? tgt->u.crypt.offset : tgt->u.linear.offset; r = device_block_adjust(cd, tgt->data_device, device_check, - tgt->u.crypt.offset, &dmd->size, &dmd->flags); + offset, &dmd->size, &dmd->flags); if (!r) { tgt->size = dmd->size; r = dm_create_device(cd, name, type, dmd); @@ -4009,15 +4878,18 @@ int create_or_reload_device_with_integrity(struct crypt_device *cd, const char * struct crypt_dm_active_device *dmdi) { int r; - const char *iname = NULL; - char *ipath = NULL; + char *iname = NULL, *ipath = NULL; if (!type || !name || !dmd || !dmdi) return -EINVAL; - if (asprintf(&ipath, "%s/%s_dif", dm_get_dir(), name) < 0) - return -ENOMEM; - iname = ipath + strlen(dm_get_dir()) + 1; + r = dm_get_iname(name, &iname, false); + if (r) + goto out; + + r = dm_get_iname(name, &ipath, true); + if (r) + goto out; /* drop CRYPT_ACTIVATE_REFRESH flag if any device is inactive */ r = check_devices(cd, name, iname, &dmd->flags); @@ -4030,6 +4902,7 @@ int create_or_reload_device_with_integrity(struct crypt_device *cd, const char * r = _create_device_with_integrity(cd, type, name, iname, ipath, dmd, dmdi); out: free(ipath); + free(iname); return r; } @@ -4043,7 +4916,8 @@ static int _open_and_activate(struct crypt_device *cd, { bool use_keyring; int r; - struct volume_key *vk = NULL; + struct volume_key *p_crypt = NULL, *p_opal = NULL, *crypt_key = NULL, *opal_key = NULL, *vk = NULL; + key_serial_t kid1 = 0, kid2 = 0; r = LUKS2_keyslot_open(cd, keyslot, (flags & CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY) ? @@ -4053,6 +4927,22 @@ static int _open_and_activate(struct crypt_device *cd, return r; keyslot = r; + /* split the key only if we do activation */ + if (name && LUKS2_segment_is_hw_opal(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT)) { + r = LUKS2_split_crypt_and_opal_keys(cd, &cd->u.luks2.hdr, + vk, &crypt_key, + &opal_key); + if (r < 0) + goto out; + + /* copy volume key digest id in crypt subkey */ + crypt_volume_key_set_id(crypt_key, crypt_volume_key_get_id(vk)); + + p_crypt = crypt_key; + p_opal = opal_key ?: vk; + } else + p_crypt = vk; + if (!crypt_use_keyring_for_vk(cd)) use_keyring = false; else @@ -4060,25 +4950,44 @@ static int _open_and_activate(struct crypt_device *cd, (flags & CRYPT_ACTIVATE_KEYRING_KEY)); if (use_keyring) { - r = LUKS2_volume_key_load_in_keyring_by_keyslot(cd, - &cd->u.luks2.hdr, vk, keyslot); - if (r < 0) - goto out; - flags |= CRYPT_ACTIVATE_KEYRING_KEY; + /* upload dm-crypt part of volume key in thread keyring if requested */ + if (p_crypt) { + r = LUKS2_volume_key_load_in_keyring_by_digest(cd, p_crypt, + crypt_volume_key_get_id(p_crypt)); + if (r < 0) + goto out; + flags |= CRYPT_ACTIVATE_KEYRING_KEY; + } + + /* upload the volume key in custom user keyring if requested */ + if (cd->link_vk_to_keyring) { + r = crypt_volume_key_load_in_user_keyring(cd, vk, &kid1, &kid2); + if (r < 0) { + log_err(cd, _("Failed to link volume key in user defined keyring.")); + goto out; + } + } } if (name) - r = LUKS2_activate(cd, name, vk, flags); + r = LUKS2_activate(cd, name, p_crypt, p_opal, flags); out: - if (r < 0) - crypt_drop_keyring_key(cd, vk); + if (r < 0) { + crypt_drop_keyring_key(cd, p_crypt); + if (cd->link_vk_to_keyring && kid1) + crypt_unlink_key_from_custom_keyring(cd, kid1); + if (cd->link_vk_to_keyring && kid2) + crypt_unlink_key_from_custom_keyring(cd, kid2); + } crypt_free_volume_key(vk); + crypt_free_volume_key(crypt_key); + crypt_free_volume_key(opal_key); return r < 0 ? r : keyslot; } #if USE_LUKS2_REENCRYPTION -static int load_all_keys(struct crypt_device *cd, struct luks2_hdr *hdr, struct volume_key *vks) +static int load_all_keys(struct crypt_device *cd, struct volume_key *vks) { int r; struct volume_key *vk = vks; @@ -4129,7 +5038,7 @@ static int _open_all_keys(struct crypt_device *cd, keyslot = r; if (r >= 0 && (flags & CRYPT_ACTIVATE_KEYRING_KEY)) - r = load_all_keys(cd, hdr, _vks); + r = load_all_keys(cd, _vks); if (r >= 0 && vks) MOVE_REF(*vks, _vks); @@ -4138,7 +5047,108 @@ static int _open_all_keys(struct crypt_device *cd, crypt_drop_keyring_key(cd, _vks); crypt_free_volume_key(_vks); - return r < 0 ? r : keyslot; + return r < 0 ? r : keyslot; +} + +static int _open_and_activate_reencrypt_device_by_vk(struct crypt_device *cd, + struct luks2_hdr *hdr, + const char *name, + struct volume_key *vks, + uint32_t flags) +{ + bool dynamic_size; + crypt_reencrypt_info ri; + uint64_t minimal_size, device_size; + int r = 0; + struct crypt_lock_handle *reencrypt_lock = NULL; + key_serial_t kid1 = 0, kid2 = 0; + struct volume_key *vk; + + if (!vks) + return -EINVAL; + + if (crypt_use_keyring_for_vk(cd)) + flags |= CRYPT_ACTIVATE_KEYRING_KEY; + + r = LUKS2_reencrypt_lock(cd, &reencrypt_lock); + if (r) { + if (r == -EBUSY) + log_err(cd, _("Reencryption in-progress. Cannot activate device.")); + else + log_err(cd, _("Failed to get reencryption lock.")); + return r; + } + + if ((r = crypt_load(cd, CRYPT_LUKS2, NULL))) + goto out; + + ri = LUKS2_reencrypt_status(hdr); + + if (ri == CRYPT_REENCRYPT_CRASH) { + r = LUKS2_reencrypt_locked_recovery_by_vks(cd, vks); + if (r < 0) { + log_err(cd, _("LUKS2 reencryption recovery using volume key(s) failed.")); + goto out; + } + + ri = LUKS2_reencrypt_status(hdr); + } + /* recovery finished reencryption or it's already finished */ + if (ri == CRYPT_REENCRYPT_NONE) { + vk = crypt_volume_key_by_id(vks, LUKS2_digest_by_segment(hdr, CRYPT_DEFAULT_SEGMENT)); + if (!vk) { + r = -EPERM; + goto out; + } + + r = LUKS2_digest_verify_by_segment(cd, &cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT, vk); + if (r == -EPERM || r == -ENOENT) + log_err(cd, _("Volume key does not match the volume.")); + if (r >= 0 && cd->link_vk_to_keyring) { + kid1 = crypt_single_volume_key_load_in_user_keyring(cd, vk, cd->user_key_name1); + if (kid1 <= 0) + r = -EINVAL; + } + if (r >= 0) + r = LUKS2_activate(cd, name, vk, NULL, flags); + goto out; + } + if (ri > CRYPT_REENCRYPT_CLEAN) { + r = -EINVAL; + goto out; + } + + if ((flags & CRYPT_ACTIVATE_KEYRING_KEY)) { + r = load_all_keys(cd, vks); + if (r < 0) + goto out; + } + + if ((r = LUKS2_get_data_size(hdr, &minimal_size, &dynamic_size))) + goto out; + + r = LUKS2_reencrypt_digest_verify(cd, hdr, vks); + if (r < 0) + goto out; + + log_dbg(cd, "Entering clean reencryption state mode."); + + r = LUKS2_reencrypt_check_device_size(cd, hdr, minimal_size, &device_size, true, dynamic_size); + if (r < 0) + goto out; + if (cd->link_vk_to_keyring) { + r = crypt_volume_key_load_in_user_keyring(cd, vks, &kid1, &kid2); + if (r < 0) { + log_err(cd, _("Failed to link volume keys in user defined keyring.")); + goto out; + } + } + r = LUKS2_activate_multi(cd, name, vks, device_size >> SECTOR_SHIFT, flags); +out: + LUKS2_reencrypt_unlock(cd, reencrypt_lock); + crypt_drop_keyring_key(cd, vks); + + return r; } static int _open_and_activate_reencrypt_device(struct crypt_device *cd, @@ -4155,6 +5165,7 @@ static int _open_and_activate_reencrypt_device(struct crypt_device *cd, struct volume_key *vks = NULL; int r = 0; struct crypt_lock_handle *reencrypt_lock = NULL; + key_serial_t kid1 = 0, kid2 = 0; if (crypt_use_keyring_for_vk(cd)) flags |= CRYPT_ACTIVATE_KEYRING_KEY; @@ -4215,15 +5226,31 @@ static int _open_and_activate_reencrypt_device(struct crypt_device *cd, log_dbg(cd, "Entering clean reencryption state mode."); + if (cd->link_vk_to_keyring) { + r = crypt_volume_key_load_in_user_keyring(cd, vks, &kid1, &kid2); + if (r < 0) { + log_err(cd, _("Failed to link volume keys in user defined keyring.")); + goto out; + } + } + if (r >= 0) - r = LUKS2_reencrypt_check_device_size(cd, hdr, minimal_size, &device_size, true, dynamic_size); + r = LUKS2_reencrypt_check_device_size(cd, hdr, minimal_size, &device_size, + !(flags & CRYPT_ACTIVATE_SHARED), + dynamic_size); if (r >= 0) r = LUKS2_activate_multi(cd, name, vks, device_size >> SECTOR_SHIFT, flags); out: LUKS2_reencrypt_unlock(cd, reencrypt_lock); - if (r < 0) + if (r < 0) { crypt_drop_keyring_key(cd, vks); + if (cd->link_vk_to_keyring && kid1) + crypt_unlink_key_from_custom_keyring(cd, kid1); + if (cd->link_vk_to_keyring && kid2) + crypt_unlink_key_from_custom_keyring(cd, kid2); + } + crypt_free_volume_key(vks); return r < 0 ? r : keyslot; @@ -4269,6 +5296,43 @@ static int _open_and_activate_luks2(struct crypt_device *cd, return r; } + +static int _activate_luks2_by_volume_key(struct crypt_device *cd, + const char *name, + struct volume_key *vk, + struct volume_key *external_key, + uint32_t flags) +{ + int r; + crypt_reencrypt_info ri; + int digest_new, digest_old; + struct volume_key *vk_old = NULL, *vk_new = NULL; + ri = LUKS2_reencrypt_status(&cd->u.luks2.hdr); + if (ri == CRYPT_REENCRYPT_INVALID) + return -EINVAL; + + if (ri > CRYPT_REENCRYPT_NONE) { + digest_new = LUKS2_reencrypt_digest_new(&cd->u.luks2.hdr); + digest_old = LUKS2_reencrypt_digest_old(&cd->u.luks2.hdr); + + if (digest_new >= 0) { + vk_new = crypt_volume_key_by_id(vk, digest_new); + assert(vk_new); + assert(crypt_volume_key_get_id(vk_new) == digest_new); + } + if (digest_old >= 0) { + vk_old = crypt_volume_key_by_id(vk, digest_old); + assert(vk_old); + assert(crypt_volume_key_get_id(vk_old) == digest_old); + } + r = _open_and_activate_reencrypt_device_by_vk(cd, &cd->u.luks2.hdr, name, vk, flags); + } else { + assert(crypt_volume_key_get_id(vk) == LUKS2_digest_by_segment(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT)); + r = LUKS2_activate(cd, name, vk, external_key, flags); + } + + return r; +} #else static int _open_and_activate_luks2(struct crypt_device *cd, int keyslot, @@ -4290,6 +5354,29 @@ static int _open_and_activate_luks2(struct crypt_device *cd, return _open_and_activate(cd, keyslot, name, passphrase, passphrase_size, flags); } + +static int _activate_luks2_by_volume_key(struct crypt_device *cd, + const char *name, + struct volume_key *vk, + struct volume_key *external_key, + uint32_t flags) +{ + int r; + crypt_reencrypt_info ri; + ri = LUKS2_reencrypt_status(&cd->u.luks2.hdr); + if (ri == CRYPT_REENCRYPT_INVALID) + return -EINVAL; + + if (ri > CRYPT_REENCRYPT_NONE) { + log_err(cd, _("This operation is not supported for this device type.")); + r = -ENOTSUP; + } else { + assert(crypt_volume_key_get_id(vk) == LUKS2_digest_by_segment(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT)); + r = LUKS2_activate(cd, name, vk, external_key, flags); + } + + return r; +} #endif static int _activate_by_passphrase(struct crypt_device *cd, @@ -4364,16 +5451,23 @@ out: static int _activate_loopaes(struct crypt_device *cd, const char *name, - char *buffer, + const char *buffer, size_t buffer_size, uint32_t flags) { int r; unsigned int key_count = 0; struct volume_key *vk = NULL; + char *buffer_copy; + + buffer_copy = crypt_safe_alloc(buffer_size); + if (!buffer_copy) + return -ENOMEM; + memcpy(buffer_copy, buffer, buffer_size); r = LOOPAES_parse_keyfile(cd, &vk, cd->u.loopaes.hdr.hash, &key_count, - buffer, buffer_size); + buffer_copy, buffer_size); + crypt_safe_free(buffer_copy); if (!r && name) r = LOOPAES_activate(cd, name, cd->u.loopaes.cipher, key_count, @@ -4408,66 +5502,352 @@ static int _activate_check_status(struct crypt_device *cd, const char *name, uns return r; } -// activation/deactivation of device mapping -int crypt_activate_by_passphrase(struct crypt_device *cd, +static int _verify_key(struct crypt_device *cd, + int segment, + struct volume_key *vk) +{ + int r = -EINVAL; + crypt_reencrypt_info ri; + struct luks2_hdr *hdr = &cd->u.luks2.hdr; + + assert(cd); + + if (isPLAIN(cd->type)) { + if (vk && vk->keylength == cd->u.plain.key_size) { + r = KEY_VERIFIED; + } else + log_err(cd, _("Incorrect volume key specified for plain device.")); + } else if (isLUKS1(cd->type)) { + r = LUKS_verify_volume_key(&cd->u.luks1.hdr, vk); + if (r == -EPERM) + log_err(cd, _("Volume key does not match the volume.")); + } else if (isLUKS2(cd->type)) { + ri = LUKS2_reencrypt_status(hdr); + if (ri == CRYPT_REENCRYPT_INVALID) + return -EINVAL; + + if (ri > CRYPT_REENCRYPT_NONE) { + LUKS2_reencrypt_lookup_key_ids(cd, hdr, vk); + r = LUKS2_reencrypt_digest_verify(cd, hdr, vk); + if (r == -EPERM || r == -ENOENT || r == -EINVAL) + log_err(cd, _("Reencryption volume keys do not match the volume.")); + return r; + } + + if (segment == CRYPT_ANY_SEGMENT) + r = LUKS2_digest_any_matching(cd, &cd->u.luks2.hdr, vk); + else { + r = LUKS2_digest_verify_by_segment(cd, &cd->u.luks2.hdr, segment, vk); + if (r == -EPERM || r == -ENOENT) + log_err(cd, _("Volume key does not match the volume.")); + } + } else if (isVERITY(cd->type)) + r = KEY_VERIFIED; + else if (isTCRYPT(cd->type)) + r = KEY_VERIFIED; + else if (isINTEGRITY(cd->type)) + r = KEY_VERIFIED; + else if (isBITLK(cd->type)) + r = KEY_VERIFIED; + else + log_err(cd, _("Device type is not properly initialized.")); + + if (r >= KEY_VERIFIED) + crypt_volume_key_set_id(vk, r); + + return r > 0 ? 0 : r; +} + +/* activation/deactivation of device mapping */ +static int _activate_by_volume_key(struct crypt_device *cd, const char *name, - int keyslot, - const char *passphrase, - size_t passphrase_size, + struct volume_key *vk, + struct volume_key *external_key, uint32_t flags) { int r; - if (!cd || !passphrase || (!name && (flags & CRYPT_ACTIVATE_REFRESH))) - return -EINVAL; - - log_dbg(cd, "%s volume %s [keyslot %d] using passphrase.", - name ? "Activating" : "Checking", name ?: "passphrase", - keyslot); + assert(cd); + assert(name); - r = _activate_check_status(cd, name, flags & CRYPT_ACTIVATE_REFRESH); + r = _check_header_data_overlap(cd, name); if (r < 0) return r; - return _activate_by_passphrase(cd, name, keyslot, passphrase, passphrase_size, flags); + /* use key directly, no hash */ + if (isPLAIN(cd->type)) { + assert(!external_key); + assert(crypt_volume_key_get_id(vk) == KEY_VERIFIED); + + r = PLAIN_activate(cd, name, vk, cd->u.plain.hdr.size, flags); + } else if (isLUKS1(cd->type)) { + assert(!external_key); + assert(crypt_volume_key_get_id(vk) == KEY_VERIFIED); + + r = LUKS1_activate(cd, name, vk, flags); + } else if (isLUKS2(cd->type)) { + r = _activate_luks2_by_volume_key(cd, name, vk, external_key, flags); + } else if (isVERITY(cd->type)) { + assert(crypt_volume_key_get_id(vk) == KEY_VERIFIED); + r = VERITY_activate(cd, name, vk, external_key, cd->u.verity.fec_device, + &cd->u.verity.hdr, flags); + } else if (isTCRYPT(cd->type)) { + assert(!external_key); + r = TCRYPT_activate(cd, name, &cd->u.tcrypt.hdr, + &cd->u.tcrypt.params, flags); + } else if (isINTEGRITY(cd->type)) { + assert(!external_key); + assert(!vk || crypt_volume_key_get_id(vk) == KEY_VERIFIED); + r = INTEGRITY_activate(cd, name, &cd->u.integrity.params, vk, + cd->u.integrity.journal_crypt_key, + cd->u.integrity.journal_mac_key, flags, + cd->u.integrity.sb_flags); + } else if (isBITLK(cd->type)) { + assert(!external_key); + r = BITLK_activate_by_volume_key(cd, name, vk->key, vk->keylength, + &cd->u.bitlk.params, flags); + } else { + log_err(cd, _("Device type is not properly initialized.")); + r = -EINVAL; + } + + return r; } -int crypt_activate_by_keyfile_device_offset(struct crypt_device *cd, - const char *name, +int crypt_activate_by_keyslot_context(struct crypt_device *cd, +const char *name, int keyslot, - const char *keyfile, - size_t keyfile_size, - uint64_t keyfile_offset, + struct crypt_keyslot_context *kc, + int additional_keyslot, + struct crypt_keyslot_context *additional_kc, uint32_t flags) { - char *passphrase_read = NULL; - size_t passphrase_size_read; - int r; + bool use_keyring; + struct volume_key *p_ext_key, *crypt_key = NULL, *opal_key = NULL, *vk = NULL, + *vk_sign = NULL, *p_crypt = NULL; + size_t passphrase_size; + const char *passphrase = NULL; + int unlocked_keyslot, required_keys, unlocked_keys = 0, r = -EINVAL; + key_serial_t kid1 = 0, kid2 = 0; + struct luks2_hdr *hdr = &cd->u.luks2.hdr; - if (!cd || !keyfile || - ((flags & CRYPT_ACTIVATE_KEYRING_KEY) && !crypt_use_keyring_for_vk(cd))) + if (!cd || !kc) return -EINVAL; - log_dbg(cd, "%s volume %s [keyslot %d] using keyfile %s.", - name ? "Activating" : "Checking", name ?: "passphrase", keyslot, keyfile); - + log_dbg(cd, "%s volume %s [keyslot %d] using %s.", + name ? "Activating" : "Checking", name ?: "passphrase", keyslot, keyslot_context_type_string(kc)); + if (!name && (flags & CRYPT_ACTIVATE_REFRESH)) + return -EINVAL; + if ((flags & CRYPT_ACTIVATE_KEYRING_KEY) && !crypt_use_keyring_for_vk(cd)) + return -EINVAL; + if ((flags & CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY) && name) + return -EINVAL; + if ((kc->type == CRYPT_KC_TYPE_KEYRING) && !kernel_keyring_support()) { + log_err(cd, _("Kernel keyring is not supported by the kernel.")); + return -EINVAL; + } + if ((kc->type == CRYPT_KC_TYPE_SIGNED_KEY) && !kernel_keyring_support()) { + log_err(cd, _("Kernel keyring missing: required for passing signature to kernel.")); + return -EINVAL; + } + r = _check_header_data_overlap(cd, name); + if (r < 0) + return r; r = _activate_check_status(cd, name, flags & CRYPT_ACTIVATE_REFRESH); if (r < 0) return r; - r = crypt_keyfile_device_read(cd, keyfile, - &passphrase_read, &passphrase_size_read, - keyfile_offset, keyfile_size, 0); + /* for TCRYPT and token skip passphrase activation */ + if (kc->get_passphrase && kc->type != CRYPT_KC_TYPE_TOKEN && !isTCRYPT(cd->type)) { + r = kc->get_passphrase(cd, kc, &passphrase, &passphrase_size); + if (r < 0) + return r; + /* TODO: Only loopaes should by activated by passphrase method */ + if (passphrase) { + if (isLOOPAES(cd->type)) + return _activate_loopaes(cd, name, passphrase, passphrase_size, flags); + else + return _activate_by_passphrase(cd, name, keyslot, passphrase, passphrase_size, flags); + } + } + /* only passphrase unlock is supported with loopaes */ + if (isLOOPAES(cd->type)) + return -EINVAL; + + /* activate by volume key */ + r = -EINVAL; + if (isLUKS1(cd->type)) { + if (kc->get_luks1_volume_key) + r = kc->get_luks1_volume_key(cd, kc, keyslot, &vk); + } else if (isLUKS2(cd->type)) { + required_keys = LUKS2_reencrypt_vks_count(hdr); + + if (flags & CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY && kc->get_luks2_key) + r = kc->get_luks2_key(cd, kc, keyslot, CRYPT_ANY_SEGMENT, &vk); + else if (kc->get_luks2_volume_key) + r = kc->get_luks2_volume_key(cd, kc, keyslot, &vk); + if (r >= 0) { + unlocked_keys++; + + if (required_keys > 1 && vk && additional_kc) { + if (flags & CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY && additional_kc->get_luks2_key) + r = additional_kc->get_luks2_key(cd, additional_kc, additional_keyslot, CRYPT_ANY_SEGMENT, &vk->next); + else if (additional_kc->get_luks2_volume_key) + r = additional_kc->get_luks2_volume_key(cd, additional_kc, additional_keyslot, &vk->next); + if (r >= 0) + unlocked_keys++; + } + + if (unlocked_keys < required_keys) + r = -ESRCH; + } + } else if (isTCRYPT(cd->type)) { + r = 0; + } else if (name && isPLAIN(cd->type)) { + if (kc->get_plain_volume_key) + r = kc->get_plain_volume_key(cd, kc, &vk); + } else if (name && isBITLK(cd->type)) { + if (kc->get_bitlk_volume_key) + r = kc->get_bitlk_volume_key(cd, kc, &vk); + } else if (isFVAULT2(cd->type)) { + if (kc->get_fvault2_volume_key) + r = kc->get_fvault2_volume_key(cd, kc, &vk); + } else if (isVERITY(cd->type) && (name || kc->type != CRYPT_KC_TYPE_SIGNED_KEY)) { + if (kc->get_verity_volume_key) + r = kc->get_verity_volume_key(cd, kc, &vk, &vk_sign); + if (r >= 0) + r = VERITY_verify_params(cd, &cd->u.verity.hdr, vk_sign != NULL, + cd->u.verity.fec_device, vk); + + free(CONST_CAST(void*)cd->u.verity.root_hash); + cd->u.verity.root_hash = NULL; + flags |= CRYPT_ACTIVATE_READONLY; + } else if (isINTEGRITY(cd->type)) { + if (kc->get_integrity_volume_key) + r = kc->get_integrity_volume_key(cd, kc, &vk); + } + if (r < 0 && (r != -ENOENT || kc->type == CRYPT_KC_TYPE_TOKEN)) + goto out; + unlocked_keyslot = r; + + if (r == -ENOENT && isLUKS(cd->type) && cd->volume_key) { + vk = crypt_alloc_volume_key(cd->volume_key->keylength, cd->volume_key->key); + r = vk ? 0 : -ENOMEM; + } + if (r == -ENOENT && isINTEGRITY(cd->type)) + r = 0; + if (r < 0) goto out; - if (isLOOPAES(cd->type)) - r = _activate_loopaes(cd, name, passphrase_read, passphrase_size_read, flags); - else - r = _activate_by_passphrase(cd, name, keyslot, passphrase_read, passphrase_size_read, flags); + r = _verify_key(cd, + flags & CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY ? CRYPT_ANY_SEGMENT : CRYPT_DEFAULT_SEGMENT, + vk); + if (r < 0) + goto out; + + if (isLUKS2(cd->type)) { + /* split the key only if we do activation */ + if (name && LUKS2_segment_is_hw_opal(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT)) { + r = LUKS2_split_crypt_and_opal_keys(cd, &cd->u.luks2.hdr, + vk, &crypt_key, + &opal_key); + if (r < 0) + goto out; + + /* copy volume key digest id in crypt subkey */ + crypt_volume_key_set_id(crypt_key, crypt_volume_key_get_id(vk)); + + p_crypt = crypt_key; + p_ext_key = opal_key ?: vk; + } else { + p_crypt = vk; + p_ext_key = NULL; + } + + if (!crypt_use_keyring_for_vk(cd)) + use_keyring = false; + else + use_keyring = (name && !crypt_is_cipher_null(crypt_get_cipher(cd))) || + (flags & CRYPT_ACTIVATE_KEYRING_KEY); + + if (use_keyring) { + /* upload dm-crypt part of volume key in thread keyring if requested */ + if (p_crypt) { + r = LUKS2_volume_key_load_in_keyring_by_digest(cd, p_crypt, crypt_volume_key_get_id(p_crypt)); + if (r < 0) + goto out; + flags |= CRYPT_ACTIVATE_KEYRING_KEY; + } + + /* upload the volume key in custom user keyring if requested */ + if (cd->link_vk_to_keyring) { + r = crypt_volume_key_load_in_user_keyring(cd, vk, &kid1, &kid2); + if (r < 0) { + log_err(cd, _("Failed to link volume key in user defined keyring.")); + goto out; + } + } + } + } else { + p_crypt = vk; + p_ext_key = vk_sign; + } + + if (name) + r = _activate_by_volume_key(cd, name, p_crypt, p_ext_key, flags); + if (r >= 0 && unlocked_keyslot >= 0) + r = unlocked_keyslot; out: - crypt_safe_free(passphrase_read); + if (r < 0) { + crypt_drop_keyring_key(cd, vk); + crypt_drop_keyring_key(cd, p_crypt); + if (cd->link_vk_to_keyring && kid1) + crypt_unlink_key_from_custom_keyring(cd, kid1); + if (cd->link_vk_to_keyring && kid2) + crypt_unlink_key_from_custom_keyring(cd, kid2); + } + + crypt_free_volume_key(vk); + crypt_free_volume_key(crypt_key); + crypt_free_volume_key(opal_key); + crypt_free_volume_key(vk_sign); + return r; +} + +int crypt_activate_by_passphrase(struct crypt_device *cd, + const char *name, + int keyslot, + const char *passphrase, + size_t passphrase_size, + uint32_t flags) +{ + int r; + struct crypt_keyslot_context kc; + + crypt_keyslot_unlock_by_passphrase_init_internal(&kc, passphrase, passphrase_size); + r = crypt_activate_by_keyslot_context(cd, name, keyslot, &kc, CRYPT_ANY_SLOT, NULL, flags); + crypt_keyslot_context_destroy_internal(&kc); + + return r; +} + +int crypt_activate_by_keyfile_device_offset(struct crypt_device *cd, + const char *name, + int keyslot, + const char *keyfile, + size_t keyfile_size, + uint64_t keyfile_offset, + uint32_t flags) +{ + int r; + struct crypt_keyslot_context kc; + + crypt_keyslot_unlock_by_keyfile_init_internal(&kc, keyfile, keyfile_size, keyfile_offset); + r = crypt_activate_by_keyslot_context(cd, name, keyslot, &kc, CRYPT_ANY_SLOT, NULL, flags); + crypt_keyslot_context_destroy_internal(&kc); + return r; } @@ -4493,135 +5873,19 @@ int crypt_activate_by_keyfile_offset(struct crypt_device *cd, return crypt_activate_by_keyfile_device_offset(cd, name, keyslot, keyfile, keyfile_size, keyfile_offset, flags); } + int crypt_activate_by_volume_key(struct crypt_device *cd, const char *name, const char *volume_key, size_t volume_key_size, uint32_t flags) { - bool use_keyring; - struct volume_key *vk = NULL; int r; + struct crypt_keyslot_context kc; - if (!cd || - ((flags & CRYPT_ACTIVATE_KEYRING_KEY) && !crypt_use_keyring_for_vk(cd))) - return -EINVAL; - - log_dbg(cd, "%s volume %s by volume key.", name ? "Activating" : "Checking", - name ?: ""); - - r = _activate_check_status(cd, name, flags & CRYPT_ACTIVATE_REFRESH); - if (r < 0) - return r; - - r = _check_header_data_overlap(cd, name); - if (r < 0) - return r; - - /* use key directly, no hash */ - if (isPLAIN(cd->type)) { - if (!name) - return -EINVAL; - - if (!volume_key || !volume_key_size || volume_key_size != cd->u.plain.key_size) { - log_err(cd, _("Incorrect volume key specified for plain device.")); - return -EINVAL; - } - - vk = crypt_alloc_volume_key(volume_key_size, volume_key); - if (!vk) - return -ENOMEM; - - r = PLAIN_activate(cd, name, vk, cd->u.plain.hdr.size, flags); - } else if (isLUKS1(cd->type)) { - /* If key is not provided, try to use internal key */ - if (!volume_key) { - if (!cd->volume_key) { - log_err(cd, _("Volume key does not match the volume.")); - return -EINVAL; - } - volume_key_size = cd->volume_key->keylength; - volume_key = cd->volume_key->key; - } - - vk = crypt_alloc_volume_key(volume_key_size, volume_key); - if (!vk) - return -ENOMEM; - r = LUKS_verify_volume_key(&cd->u.luks1.hdr, vk); - - if (r == -EPERM) - log_err(cd, _("Volume key does not match the volume.")); - - if (!r && name) - r = LUKS1_activate(cd, name, vk, flags); - } else if (isLUKS2(cd->type)) { - /* If key is not provided, try to use internal key */ - if (!volume_key) { - if (!cd->volume_key) { - log_err(cd, _("Volume key does not match the volume.")); - return -EINVAL; - } - volume_key_size = cd->volume_key->keylength; - volume_key = cd->volume_key->key; - } - - vk = crypt_alloc_volume_key(volume_key_size, volume_key); - if (!vk) - return -ENOMEM; - - r = LUKS2_digest_verify_by_segment(cd, &cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT, vk); - if (r == -EPERM || r == -ENOENT) - log_err(cd, _("Volume key does not match the volume.")); - if (r > 0) - r = 0; - - if (!crypt_use_keyring_for_vk(cd)) - use_keyring = false; - else - use_keyring = (name && !crypt_is_cipher_null(crypt_get_cipher(cd))) || - (flags & CRYPT_ACTIVATE_KEYRING_KEY); - - if (!r && use_keyring) { - r = LUKS2_key_description_by_segment(cd, - &cd->u.luks2.hdr, vk, CRYPT_DEFAULT_SEGMENT); - if (!r) - r = crypt_volume_key_load_in_keyring(cd, vk); - if (!r) - flags |= CRYPT_ACTIVATE_KEYRING_KEY; - } - - if (!r && name) - r = LUKS2_activate(cd, name, vk, flags); - } else if (isVERITY(cd->type)) { - r = crypt_activate_by_signed_key(cd, name, volume_key, volume_key_size, NULL, 0, flags); - } else if (isTCRYPT(cd->type)) { - if (!name) - return 0; - r = TCRYPT_activate(cd, name, &cd->u.tcrypt.hdr, - &cd->u.tcrypt.params, flags); - } else if (isINTEGRITY(cd->type)) { - if (!name) - return 0; - if (volume_key) { - vk = crypt_alloc_volume_key(volume_key_size, volume_key); - if (!vk) - return -ENOMEM; - } - r = INTEGRITY_activate(cd, name, &cd->u.integrity.params, vk, - cd->u.integrity.journal_crypt_key, - cd->u.integrity.journal_mac_key, flags, - cd->u.integrity.sb_flags); - } else if (isBITLK(cd->type)) { - r = BITLK_activate_by_volume_key(cd, name, volume_key, volume_key_size, - &cd->u.bitlk.params, flags); - } else { - log_err(cd, _("Device type is not properly initialized.")); - r = -EINVAL; - } - - if (r < 0) - crypt_drop_keyring_key(cd, vk); - crypt_free_volume_key(vk); + crypt_keyslot_unlock_by_key_init_internal(&kc, volume_key, volume_key_size); + r = crypt_activate_by_keyslot_context(cd, name, CRYPT_ANY_SLOT /* unused */, &kc, CRYPT_ANY_SLOT, NULL, flags); + crypt_keyslot_context_destroy_internal(&kc); return r; } @@ -4634,8 +5898,8 @@ int crypt_activate_by_signed_key(struct crypt_device *cd, size_t signature_size, uint32_t flags) { - char description[512]; int r; + struct crypt_keyslot_context kc; if (!cd || !isVERITY(cd->type)) return -EINVAL; @@ -4645,57 +5909,13 @@ int crypt_activate_by_signed_key(struct crypt_device *cd, return -EINVAL; } - if (name) - log_dbg(cd, "Activating volume %s by %skey.", name, signature ? "signed " : ""); - else - log_dbg(cd, "Checking volume by key."); - - if (cd->u.verity.hdr.flags & CRYPT_VERITY_ROOT_HASH_SIGNATURE && !signature) { - log_err(cd, _("Root hash signature required.")); - return -EINVAL; - } - - r = _activate_check_status(cd, name, flags & CRYPT_ACTIVATE_REFRESH); - if (r < 0) - return r; - - if (signature && !kernel_keyring_support()) { - log_err(cd, _("Kernel keyring missing: required for passing signature to kernel.")); - return -EINVAL; - } - - /* volume_key == root hash */ - free(CONST_CAST(void*)cd->u.verity.root_hash); - cd->u.verity.root_hash = NULL; - - if (signature) { - r = snprintf(description, sizeof(description)-1, "cryptsetup:%s%s%s", - crypt_get_uuid(cd) ?: "", crypt_get_uuid(cd) ? "-" : "", name); - if (r < 0) - return -EINVAL; - - log_dbg(cd, "Adding signature into keyring %s", description); - r = keyring_add_key_in_thread_keyring(USER_KEY, description, signature, signature_size); - if (r) { - log_err(cd, _("Failed to load key in kernel keyring.")); - return r; - } - } - - r = VERITY_activate(cd, name, volume_key, volume_key_size, - signature ? description : NULL, - cd->u.verity.fec_device, - &cd->u.verity.hdr, flags | CRYPT_ACTIVATE_READONLY); - - if (!r) { - cd->u.verity.root_hash_size = volume_key_size; - cd->u.verity.root_hash = malloc(volume_key_size); - if (cd->u.verity.root_hash) - memcpy(CONST_CAST(void*)cd->u.verity.root_hash, volume_key, volume_key_size); - } - if (signature) - crypt_drop_keyring_key_by_description(cd, description, USER_KEY); + crypt_keyslot_unlock_by_signed_key_init_internal(&kc, volume_key, volume_key_size, + signature, signature_size); + else + crypt_keyslot_unlock_by_key_init_internal(&kc, volume_key, volume_key_size); + r = crypt_activate_by_keyslot_context(cd, name, -2 /* unused */, &kc, CRYPT_ANY_SLOT, NULL, flags); + crypt_keyslot_context_destroy_internal(&kc); return r; } @@ -4723,6 +5943,17 @@ int crypt_deactivate_by_name(struct crypt_device *cd, const char *name, uint32_t cd = fake_cd; } + if (flags & (CRYPT_DEACTIVATE_DEFERRED | CRYPT_DEACTIVATE_DEFERRED_CANCEL)) { + struct luks2_hdr *hdr = crypt_get_hdr(cd, CRYPT_LUKS2); + if (hdr) { + json_object *jobj = json_segments_get_segment(LUKS2_get_segments_jobj(hdr), 0); + if (jobj && !strcmp(json_segment_type(jobj), "hw-opal")) { + log_err(cd, _("OPAL does not support deferred deactivation.")); + return -EINVAL; + } + } + } + /* skip holders detection and early abort when some flags raised */ if (flags & (CRYPT_DEACTIVATE_FORCE | CRYPT_DEACTIVATE_DEFERRED | CRYPT_DEACTIVATE_DEFERRED_CANCEL)) get_flags &= ~DM_ACTIVE_HOLDERS; @@ -4986,7 +6217,7 @@ int crypt_volume_key_verify(struct crypt_device *cd, struct volume_key *vk; int r; - if ((r = _onlyLUKS(cd, CRYPT_CD_UNRESTRICTED))) + if ((r = onlyLUKSunrestricted(cd))) return r; vk = crypt_alloc_volume_key(volume_key_size, volume_key); @@ -5031,6 +6262,9 @@ int crypt_get_rng_type(struct crypt_device *cd) int crypt_memory_lock(struct crypt_device *cd, int lock) { + UNUSED(cd); + UNUSED(lock); + return 0; } @@ -5264,6 +6498,9 @@ const char *crypt_get_integrity(struct crypt_device *cd) if (isLUKS2(cd->type)) return LUKS2_get_integrity(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT); + if (!cd->type && *cd->u.none.integrity_spec) + return cd->u.none.integrity_spec; + return NULL; } @@ -5272,10 +6509,7 @@ int crypt_get_integrity_key_size(struct crypt_device *cd) { int key_size = 0; - if (isINTEGRITY(cd->type)) - key_size = INTEGRITY_key_size(crypt_get_integrity(cd)); - - if (isLUKS2(cd->type)) + if (isINTEGRITY(cd->type) || isLUKS2(cd->type) || !cd->type) key_size = INTEGRITY_key_size(crypt_get_integrity(cd)); return key_size > 0 ? key_size : 0; @@ -5287,7 +6521,7 @@ int crypt_get_integrity_tag_size(struct crypt_device *cd) if (isINTEGRITY(cd->type)) return cd->u.integrity.params.tag_size; - if (isLUKS2(cd->type)) + if (isLUKS2(cd->type) || !cd->type) return INTEGRITY_tag_size(crypt_get_integrity(cd), crypt_get_cipher(cd), crypt_get_cipher_mode(cd)); @@ -5308,6 +6542,9 @@ int crypt_get_sector_size(struct crypt_device *cd) if (isLUKS2(cd->type)) return LUKS2_get_sector_size(&cd->u.luks2.hdr); + if (!cd->type && cd->u.none.sector_size) + return cd->u.none.sector_size; + return SECTOR_SIZE; } @@ -5403,6 +6640,14 @@ int crypt_get_volume_key_size(struct crypt_device *cd) return 0; } +int crypt_get_hw_encryption_key_size(struct crypt_device *cd) +{ + if (!cd || !isLUKS2(cd->type)) + return 0; + + return LUKS2_get_opal_key_size(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT); +} + int crypt_keyslot_get_key_size(struct crypt_device *cd, int keyslot) { if (!cd || !isLUKS(cd->type)) @@ -5466,6 +6711,12 @@ const char *crypt_keyslot_get_encryption(struct crypt_device *cd, int keyslot, s return cd->u.luks2.keyslot_cipher; } + if (LUKS2_segment_is_hw_opal(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT)) { + /* Fallback to default LUKS2 keyslot encryption */ + *key_size = DEFAULT_LUKS2_KEYSLOT_KEYBITS / 8; + return DEFAULT_LUKS2_KEYSLOT_CIPHER; + } + /* Try to reuse volume encryption parameters */ cipher = LUKS2_get_cipher(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT); if (!LUKS2_keyslot_cipher_incompatible(cd, cipher)) { @@ -5606,7 +6857,7 @@ uint64_t crypt_get_iv_offset(struct crypt_device *cd) crypt_keyslot_info crypt_keyslot_status(struct crypt_device *cd, int keyslot) { - if (_onlyLUKS(cd, CRYPT_CD_QUIET | CRYPT_CD_UNRESTRICTED) < 0) + if (_onlyLUKS(cd, CRYPT_CD_QUIET | CRYPT_CD_UNRESTRICTED, 0) < 0) return CRYPT_SLOT_INVALID; if (isLUKS1(cd->type)) @@ -5633,7 +6884,7 @@ int crypt_keyslot_area(struct crypt_device *cd, uint64_t *offset, uint64_t *length) { - if (_onlyLUKS(cd, CRYPT_CD_QUIET | CRYPT_CD_UNRESTRICTED) || !offset || !length) + if (_onlyLUKS(cd, CRYPT_CD_QUIET | CRYPT_CD_UNRESTRICTED, 0) || !offset || !length) return -EINVAL; if (isLUKS2(cd->type)) @@ -5644,7 +6895,7 @@ int crypt_keyslot_area(struct crypt_device *cd, crypt_keyslot_priority crypt_keyslot_get_priority(struct crypt_device *cd, int keyslot) { - if (_onlyLUKS(cd, CRYPT_CD_QUIET | CRYPT_CD_UNRESTRICTED)) + if (_onlyLUKS(cd, CRYPT_CD_QUIET | CRYPT_CD_UNRESTRICTED, 0)) return CRYPT_SLOT_PRIORITY_INVALID; if (keyslot < 0 || keyslot >= crypt_keyslot_max(cd->type)) @@ -5684,6 +6935,21 @@ const char *crypt_get_default_type(void) return DEFAULT_LUKS_FORMAT; } +int crypt_get_hw_encryption_type(struct crypt_device *cd) +{ + if (!cd) + return -EINVAL; + + if (isLUKS2(cd->type)) { + if (LUKS2_segment_is_hw_opal_crypt(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT)) + return CRYPT_SW_AND_OPAL_HW; + else if (LUKS2_segment_is_hw_opal_only(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT)) + return CRYPT_OPAL_HW_ONLY; + } + + return CRYPT_SW_ONLY; +} + int crypt_get_verity_info(struct crypt_device *cd, struct crypt_params_verity *vp) { @@ -5753,6 +7019,11 @@ int crypt_get_integrity_info(struct crypt_device *cd, ip->journal_crypt_key_size = 0; ip->journal_crypt_key = NULL; return 0; + } else if (!cd->type) { + memset(ip, 0, sizeof(*ip)); + ip->integrity = crypt_get_integrity(cd); + ip->integrity_key_size = crypt_get_integrity_key_size(cd); + ip->tag_size = crypt_get_integrity_tag_size(cd); } return -ENOTSUP; @@ -5771,7 +7042,7 @@ int crypt_convert(struct crypt_device *cd, log_dbg(cd, "Converting LUKS device to type %s", type); - if ((r = onlyLUKS(cd))) + if ((r = onlyLUKSnoRequirements(cd))) return r; if (isLUKS1(cd->type) && isLUKS2(type)) @@ -5797,6 +7068,10 @@ int crypt_convert(struct crypt_device *cd, /* Internal access function to header pointer */ void *crypt_get_hdr(struct crypt_device *cd, const char *type) { + /* One type can be OPAL */ + if (isLUKS2(type) && isLUKS2(cd->type)) + return &cd->u.luks2.hdr; + /* If requested type differs, ignore it */ if (strcmp(cd->type, type)) return NULL; @@ -5807,9 +7082,6 @@ void *crypt_get_hdr(struct crypt_device *cd, const char *type) if (isLUKS1(cd->type)) return &cd->u.luks1.hdr; - if (isLUKS2(cd->type)) - return &cd->u.luks2.hdr; - if (isLOOPAES(cd->type)) return &cd->u.loopaes; @@ -5842,26 +7114,13 @@ int crypt_activate_by_token_pin(struct crypt_device *cd, const char *name, void *usrptr, uint32_t flags) { int r; + struct crypt_keyslot_context kc; - log_dbg(cd, "%s volume %s using token (%s type) %d.", - name ? "Activating" : "Checking", name ?: "passphrase", - type ?: "any", token); - - if ((r = _onlyLUKS2(cd, CRYPT_CD_QUIET | CRYPT_CD_UNRESTRICTED, 0))) - return r; - - if ((flags & CRYPT_ACTIVATE_KEYRING_KEY) && !crypt_use_keyring_for_vk(cd)) - return -EINVAL; - - if ((flags & CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY) && name) - return -EINVAL; - - r = _activate_check_status(cd, name, flags & CRYPT_ACTIVATE_REFRESH); - if (r < 0) - return r; + crypt_keyslot_unlock_by_token_init_internal(&kc, token, type, pin, pin_size, usrptr); + r = crypt_activate_by_keyslot_context(cd, name, CRYPT_ANY_SLOT, &kc, CRYPT_ANY_SLOT, NULL, flags); + crypt_keyslot_context_destroy_internal(&kc); - return LUKS2_token_open_and_activate(cd, &cd->u.luks2.hdr, token, name, type, - pin, pin_size, flags, usrptr); + return r; } int crypt_activate_by_token(struct crypt_device *cd, @@ -5879,7 +7138,7 @@ int crypt_token_json_get(struct crypt_device *cd, int token, const char **json) log_dbg(cd, "Requesting JSON for token %d.", token); - if ((r = _onlyLUKS2(cd, CRYPT_CD_UNRESTRICTED, 0))) + if ((r = onlyLUKS2unrestricted(cd))) return r; return LUKS2_token_json_get(&cd->u.luks2.hdr, token, json) ?: token; @@ -5926,7 +7185,7 @@ int crypt_token_luks2_keyring_get(struct crypt_device *cd, log_dbg(cd, "Requesting LUKS2 keyring token %d.", token); - if ((r = _onlyLUKS2(cd, CRYPT_CD_UNRESTRICTED, 0))) + if ((r = onlyLUKS2unrestricted(cd))) return r; token_info = LUKS2_token_status(cd, &cd->u.luks2.hdr, token, &type); @@ -6041,7 +7300,7 @@ int crypt_persistent_flags_get(struct crypt_device *cd, crypt_flags_type type, u if (!flags) return -EINVAL; - if ((r = _onlyLUKS2(cd, CRYPT_CD_UNRESTRICTED, 0))) + if ((r = onlyLUKS2unrestricted(cd))) return r; if (type == CRYPT_FLAGS_ACTIVATION) @@ -6404,10 +7663,9 @@ int crypt_volume_key_keyring(struct crypt_device *cd __attribute__((unused)), in /* internal only */ int crypt_volume_key_load_in_keyring(struct crypt_device *cd, struct volume_key *vk) { - int r; - const char *type_name = key_type_name(LOGON_KEY); + key_serial_t kid; - if (!vk || !cd || !type_name) + if (!vk || !cd) return -EINVAL; if (!vk->key_description) { @@ -6415,15 +7673,83 @@ int crypt_volume_key_load_in_keyring(struct crypt_device *cd, struct volume_key return -EINVAL; } - log_dbg(cd, "Loading key (%zu bytes, type %s) in thread keyring.", vk->keylength, type_name); + log_dbg(cd, "Loading key (type logon, name %s) in thread keyring.", vk->key_description); - r = keyring_add_key_in_thread_keyring(LOGON_KEY, vk->key_description, vk->key, vk->keylength); - if (r) { - log_dbg(cd, "keyring_add_key_in_thread_keyring failed (error %d)", r); + kid = keyring_add_key_in_thread_keyring(LOGON_KEY, vk->key_description, vk->key, vk->keylength); + if (kid < 0) { + log_dbg(cd, "keyring_add_key_in_thread_keyring failed (error %d)", errno); log_err(cd, _("Failed to load key in kernel keyring.")); } else crypt_set_key_in_keyring(cd, 1); + return kid < 0 ? -EINVAL : 0; +} + +/* internal only */ +int crypt_keyring_get_user_key(struct crypt_device *cd, + const char *key_description, + char **key, + size_t *key_size) +{ + int r; + key_serial_t kid; + + if (!key_description || !key || !key_size) + return -EINVAL; + + log_dbg(cd, "Requesting key %s (user type)", key_description); + + kid = keyring_request_key_id(USER_KEY, key_description); + if (kid == -ENOTSUP) { + log_dbg(cd, "Kernel keyring features disabled."); + return -ENOTSUP; + } else if (kid < 0) { + log_dbg(cd, "keyring_request_key_id failed with errno %d.", errno); + return -EINVAL; + } + + log_dbg(cd, "Reading content of kernel key (id %" PRIi32 ").", kid); + + r = keyring_read_key(kid, key, key_size); + if (r < 0) + log_dbg(cd, "keyring_read_key failed with errno %d.", errno); + + return r; +} + +/* internal only */ +int crypt_keyring_get_key_by_name(struct crypt_device *cd, + const char *key_description, + char **key, + size_t *key_size) +{ + int r; + key_serial_t kid; + + if (!key_description || !key || !key_size) + return -EINVAL; + + log_dbg(cd, "Searching for key by name %s.", key_description); + + kid = keyring_find_key_id_by_name(key_description); + if (kid == -ENOTSUP) { + log_dbg(cd, "Kernel keyring features disabled."); + return -ENOTSUP; + } else if (kid < 0) { + log_dbg(cd, "keyring_find_key_id_by_name failed with errno %d.", errno); + return -EINVAL; + } + else if (kid == 0) { + log_dbg(cd, "keyring_find_key_id_by_name failed with errno %d.", ENOENT); + return -ENOENT; + } + + log_dbg(cd, "Reading content of kernel key (id %" PRIi32 ").", kid); + + r = keyring_read_key(kid, key, key_size); + if (r < 0) + log_dbg(cd, "keyring_read_key failed with errno %d.", errno); + return r; } @@ -6445,18 +7771,96 @@ void crypt_set_key_in_keyring(struct crypt_device *cd, unsigned key_in_keyring) /* internal only */ void crypt_drop_keyring_key_by_description(struct crypt_device *cd, const char *key_description, key_type_t ktype) { - int r; + key_serial_t kid; const char *type_name = key_type_name(ktype); if (!key_description || !type_name) return; - log_dbg(cd, "Requesting keyring %s key for revoke and unlink.", type_name); + log_dbg(cd, "Requesting kernel key %s (type %s) for unlink from thread keyring.", key_description, type_name); - r = keyring_revoke_and_unlink_key(ktype, key_description); - if (r) - log_dbg(cd, "keyring_revoke_and_unlink_key failed (error %d)", r); crypt_set_key_in_keyring(cd, 0); + + kid = keyring_request_key_id(ktype, key_description); + if (kid == -ENOTSUP) { + log_dbg(cd, "Kernel keyring features disabled."); + return; + } else if (kid < 0) { + log_dbg(cd, "keyring_request_key_id failed with errno %d.", errno); + return; + } + + log_dbg(cd, "Unlinking volume key (id: %" PRIi32 ") from thread keyring.", kid); + + if (!keyring_unlink_key_from_thread_keyring(kid)) + return; + + log_dbg(cd, "keyring_unlink_key_from_thread_keyring failed with errno %d.", errno); + log_err(cd, _("Failed to unlink volume key from thread keyring.")); + +} + +int crypt_set_keyring_to_link(struct crypt_device *cd, const char *key_description, + const char *old_key_description, + const char *key_type_desc, const char *keyring_to_link_vk) +{ + key_type_t key_type = USER_KEY; + const char *name1 = NULL, *name2 = NULL; + int32_t id = 0; + int r, ri; + struct luks2_hdr *hdr; + unsigned user_descriptions_count, vks_count = 1; + + if (!cd || ((!key_description && !old_key_description) && (keyring_to_link_vk || key_type_desc)) || + ((key_description || old_key_description) && !keyring_to_link_vk)) + return -EINVAL; + + hdr = crypt_get_hdr(cd, CRYPT_LUKS2); + + /* if only one key description is supplied, force it to be the first one */ + if (!key_description && old_key_description) + return -EINVAL; + + if ((r = _onlyLUKS2(cd, 0, CRYPT_REQUIREMENT_OPAL | CRYPT_REQUIREMENT_ONLINE_REENCRYPT))) + return r; + + if (key_type_desc) + key_type = key_type_by_name(key_type_desc); + if (key_type != LOGON_KEY && key_type != USER_KEY) + return -EINVAL; + + ri = crypt_reencrypt_status(cd, NULL); + if (ri > CRYPT_REENCRYPT_NONE && ri < CRYPT_REENCRYPT_INVALID) + vks_count = LUKS2_reencrypt_vks_count(hdr); + + user_descriptions_count = (key_description ? 1 : 0) + (old_key_description ? 1 : 0); + if (user_descriptions_count != 0 && vks_count > user_descriptions_count) + return -ESRCH; + + if (keyring_to_link_vk) { + id = keyring_find_keyring_id_by_name(keyring_to_link_vk); + if (id == 0) { + log_err(cd, _("Could not find keyring described by \"%s\"."), keyring_to_link_vk); + return -EINVAL; + } + if (key_description && !(name1 = strdup(key_description))) + return -ENOMEM; + if (old_key_description && !(name2 = strdup(old_key_description))) { + free(CONST_CAST(void*)name1); + return -ENOMEM; + } + } + + cd->keyring_key_type = key_type; + + free(CONST_CAST(void*)cd->user_key_name1); + free(CONST_CAST(void*)cd->user_key_name2); + cd->user_key_name1 = name1; + cd->user_key_name2 = name2; + cd->keyring_to_link_vk = id; + cd->link_vk_to_keyring = id != 0; + + return 0; } /* internal only */ @@ -6476,34 +7880,15 @@ int crypt_activate_by_keyring(struct crypt_device *cd, int keyslot, uint32_t flags) { - char *passphrase; - size_t passphrase_size; int r; + struct crypt_keyslot_context kc; if (!cd || !key_description) return -EINVAL; - log_dbg(cd, "%s volume %s [keyslot %d] using passphrase in keyring.", - name ? "Activating" : "Checking", name ?: "passphrase", keyslot); - - if (!kernel_keyring_support()) { - log_err(cd, _("Kernel keyring is not supported by the kernel.")); - return -EINVAL; - } - - r = _activate_check_status(cd, name, flags & CRYPT_ACTIVATE_REFRESH); - if (r < 0) - return r; - - r = keyring_get_passphrase(key_description, &passphrase, &passphrase_size); - if (r < 0) { - log_err(cd, _("Failed to read passphrase from keyring (error %d)."), r); - return -EINVAL; - } - - r = _activate_by_passphrase(cd, name, keyslot, passphrase, passphrase_size, flags); - - crypt_safe_free(passphrase); + crypt_keyslot_unlock_by_keyring_internal(&kc, key_description); + r = crypt_activate_by_keyslot_context(cd, name, keyslot, &kc, CRYPT_ANY_SLOT, NULL, flags); + crypt_keyslot_context_destroy_internal(&kc); return r; } diff --git a/lib/tcrypt/tcrypt.c b/lib/tcrypt/tcrypt.c index 60e4966..9ae7aaa 100644 --- a/lib/tcrypt/tcrypt.c +++ b/lib/tcrypt/tcrypt.c @@ -1,8 +1,8 @@ /* * TCRYPT (TrueCrypt-compatible) and VeraCrypt volume handling * - * Copyright (C) 2012-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2012-2023 Milan Broz + * Copyright (C) 2012-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2012-2024 Milan Broz * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -47,6 +47,8 @@ static const struct { { 0, 1, "pbkdf2", "whirlpool", 500000, 15000, 1000 }, { 0, 1, "pbkdf2", "sha256", 500000, 15000, 1000 }, // VeraCrypt 1.0f { 0, 1, "pbkdf2", "sha256", 200000, 0, 2048 }, // boot only + { 0, 1, "pbkdf2", "blake2s-256", 500000, 15000, 1000 }, // VeraCrypt 1.26.2 + { 0, 1, "pbkdf2", "blake2s-256", 200000, 0, 2048 }, // boot only { 0, 1, "pbkdf2", "ripemd160", 655331, 15000, 1000 }, { 0, 1, "pbkdf2", "ripemd160", 327661, 0, 2048 }, // boot only { 0, 1, "pbkdf2", "stribog512",500000, 15000, 1000 }, @@ -572,7 +574,7 @@ static int TCRYPT_init_hdr(struct crypt_device *cd, pwd[i] += params->passphrase[i]; for (i = 0; tcrypt_kdf[i].name; i++) { - if (params->hash_name && strcmp(params->hash_name, tcrypt_kdf[i].hash)) + if (params->hash_name && !strstr(tcrypt_kdf[i].hash, params->hash_name)) continue; if (!(params->flags & CRYPT_TCRYPT_LEGACY_MODES) && tcrypt_kdf[i].legacy) continue; diff --git a/lib/tcrypt/tcrypt.h b/lib/tcrypt/tcrypt.h index b95d74d..1e8765a 100644 --- a/lib/tcrypt/tcrypt.h +++ b/lib/tcrypt/tcrypt.h @@ -1,8 +1,8 @@ /* * TCRYPT (TrueCrypt-compatible) header definition * - * Copyright (C) 2012-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2012-2023 Milan Broz + * Copyright (C) 2012-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2012-2024 Milan Broz * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/lib/utils.c b/lib/utils.c index bfcf60d..cf86816 100644 --- a/lib/utils.c +++ b/lib/utils.c @@ -3,8 +3,8 @@ * * Copyright (C) 2004 Jana Saout * Copyright (C) 2004-2007 Clemens Fruhwirth - * Copyright (C) 2009-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2009-2023 Milan Broz + * Copyright (C) 2009-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2009-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -45,20 +45,77 @@ unsigned crypt_cpusonline(void) uint64_t crypt_getphysmemory_kb(void) { long pagesize, phys_pages; - uint64_t phys_memory_kb; + uint64_t phys_memory_kb, page_size_kb; pagesize = sysconf(_SC_PAGESIZE); phys_pages = sysconf(_SC_PHYS_PAGES); - if (pagesize < 0 || phys_pages < 0) + if (pagesize <= 0 || phys_pages <= 0) return 0; - phys_memory_kb = pagesize / 1024; - phys_memory_kb *= phys_pages; + page_size_kb = pagesize / 1024; + phys_memory_kb = page_size_kb * phys_pages; + /* sanity check for overflow */ + if (phys_memory_kb / phys_pages != page_size_kb) + return 0; + + /* coverity[return_overflow:FALSE] */ return phys_memory_kb; } +uint64_t crypt_getphysmemoryfree_kb(void) +{ + long pagesize, phys_pages; + uint64_t phys_memoryfree_kb, page_size_kb; + + pagesize = sysconf(_SC_PAGESIZE); + phys_pages = sysconf(_SC_AVPHYS_PAGES); + + if (pagesize <= 0 || phys_pages <= 0) + return 0; + + page_size_kb = pagesize / 1024; + phys_memoryfree_kb = page_size_kb * phys_pages; + + /* sanity check for overflow */ + if (phys_memoryfree_kb / phys_pages != page_size_kb) + return 0; + + /* coverity[return_overflow:FALSE] */ + return phys_memoryfree_kb; +} + +bool crypt_swapavailable(void) +{ + int fd; + ssize_t size; + char buf[4096], *p; + uint64_t total; + + if ((fd = open("/proc/meminfo", O_RDONLY)) < 0) + return true; + + size = read(fd, buf, sizeof(buf)); + close(fd); + if (size < 1) + return true; + + if (size < (ssize_t)sizeof(buf)) + buf[size] = 0; + else + buf[sizeof(buf) - 1] = 0; + + p = strstr(buf, "SwapTotal:"); + if (!p) + return true; + + if (sscanf(p, "SwapTotal: %" PRIu64 " kB", &total) != 1) + return true; + + return total > 0; +} + void crypt_process_priority(struct crypt_device *cd, int *priority, bool raise) { int _priority, new_priority; diff --git a/lib/utils_benchmark.c b/lib/utils_benchmark.c index 728e4df..6f2077c 100644 --- a/lib/utils_benchmark.c +++ b/lib/utils_benchmark.c @@ -1,8 +1,8 @@ /* * libcryptsetup - cryptsetup library, cipher benchmark * - * Copyright (C) 2012-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2012-2023 Milan Broz + * Copyright (C) 2012-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2012-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -101,6 +101,7 @@ int crypt_benchmark_pbkdf(struct crypt_device *cd, { int r, priority; const char *kdf_opt; + uint32_t memory_kb; if (!pbkdf || (!password && password_size)) return -EINVAL; @@ -113,6 +114,14 @@ int crypt_benchmark_pbkdf(struct crypt_device *cd, log_dbg(cd, "Running %s(%s) benchmark.", pbkdf->type, kdf_opt); + memory_kb = pbkdf_adjusted_phys_memory_kb(); + if (memory_kb < pbkdf->max_memory_kb) { + log_dbg(cd, "Not enough physical memory detected, " + "PBKDF max memory decreased from %dkB to %dkB.", + pbkdf->max_memory_kb, memory_kb); + pbkdf->max_memory_kb = memory_kb; + } + crypt_process_priority(cd, &priority, true); r = crypt_pbkdf_perf(pbkdf->type, pbkdf->hash, password, password_size, salt, salt_size, volume_key_size, pbkdf->time_ms, diff --git a/lib/utils_blkid.c b/lib/utils_blkid.c index 5a848a1..230dcab 100644 --- a/lib/utils_blkid.c +++ b/lib/utils_blkid.c @@ -1,7 +1,7 @@ /* * blkid probe utilities * - * Copyright (C) 2018-2023 Red Hat, Inc. All rights reserved. + * Copyright (C) 2018-2024 Red Hat, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -29,6 +29,7 @@ #include "utils_io.h" #ifdef HAVE_BLKID + #include /* make bad checksums flag optional */ #ifndef BLKID_SUBLKS_BADCSUM @@ -45,11 +46,9 @@ static size_t crypt_getpagesize(void) return r <= 0 ? 4096 : (size_t)r; } #endif -#endif void blk_set_chains_for_wipes(struct blkid_handle *h) { -#ifdef HAVE_BLKID blkid_probe_enable_partitions(h->pr, 1); blkid_probe_set_partitions_flags(h->pr, 0 #ifdef HAVE_BLKID_WIPE @@ -65,7 +64,6 @@ void blk_set_chains_for_wipes(struct blkid_handle *h) BLKID_SUBLKS_VERSION | BLKID_SUBLKS_MAGIC | BLKID_SUBLKS_BADCSUM); -#endif } void blk_set_chains_for_full_print(struct blkid_handle *h) @@ -75,25 +73,19 @@ void blk_set_chains_for_full_print(struct blkid_handle *h) void blk_set_chains_for_superblocks(struct blkid_handle *h) { -#ifdef HAVE_BLKID blkid_probe_enable_superblocks(h->pr, 1); blkid_probe_set_superblocks_flags(h->pr, BLKID_SUBLKS_TYPE); -#endif } void blk_set_chains_for_fast_detection(struct blkid_handle *h) { -#ifdef HAVE_BLKID blkid_probe_enable_partitions(h->pr, 1); blkid_probe_set_partitions_flags(h->pr, 0); blk_set_chains_for_superblocks(h); -#endif } int blk_init_by_path(struct blkid_handle **h, const char *path) { - int r = -ENOTSUP; -#ifdef HAVE_BLKID struct blkid_handle *tmp = malloc(sizeof(*tmp)); if (!tmp) return -ENOMEM; @@ -107,16 +99,11 @@ int blk_init_by_path(struct blkid_handle **h, const char *path) } *h = tmp; - - r = 0; -#endif - return r; + return 0; } int blk_init_by_fd(struct blkid_handle **h, int fd) { - int r = -ENOTSUP; -#ifdef HAVE_BLKID struct blkid_handle *tmp = malloc(sizeof(*tmp)); if (!tmp) return -ENOMEM; @@ -136,13 +123,9 @@ int blk_init_by_fd(struct blkid_handle **h, int fd) tmp->fd = fd; *h = tmp; - - r = 0; -#endif - return r; + return 0; } -#ifdef HAVE_BLKID static int blk_superblocks_luks(struct blkid_handle *h, bool enable) { char luks[] = "crypto_LUKS"; @@ -154,47 +137,34 @@ static int blk_superblocks_luks(struct blkid_handle *h, bool enable) enable ? BLKID_FLTR_ONLYIN : BLKID_FLTR_NOTIN, luks_filter); } -#endif int blk_superblocks_filter_luks(struct blkid_handle *h) { - int r = -ENOTSUP; -#ifdef HAVE_BLKID - r = blk_superblocks_luks(h, false); -#endif - return r; + return blk_superblocks_luks(h, false); } int blk_superblocks_only_luks(struct blkid_handle *h) { - int r = -ENOTSUP; -#ifdef HAVE_BLKID - r = blk_superblocks_luks(h, true); -#endif - return r; + return blk_superblocks_luks(h, true); } blk_probe_status blk_probe(struct blkid_handle *h) { blk_probe_status pr = PRB_FAIL; -#ifdef HAVE_BLKID + int r = blkid_do_probe(h->pr); if (r == 0) pr = PRB_OK; else if (r == 1) pr = PRB_EMPTY; -#endif + return pr; } blk_probe_status blk_safeprobe(struct blkid_handle *h) { - int r = -1; -#ifdef HAVE_BLKID - r = blkid_do_safeprobe(h->pr); -#endif - switch (r) { + switch (blkid_do_safeprobe(h->pr)) { case -2: return PRB_AMBIGUOUS; case 1: @@ -208,43 +178,30 @@ blk_probe_status blk_safeprobe(struct blkid_handle *h) int blk_is_partition(struct blkid_handle *h) { - int r = 0; -#ifdef HAVE_BLKID - r = blkid_probe_has_value(h->pr, "PTTYPE"); -#endif - return r; + return blkid_probe_has_value(h->pr, "PTTYPE"); } int blk_is_superblock(struct blkid_handle *h) { - int r = 0; -#ifdef HAVE_BLKID - r = blkid_probe_has_value(h->pr, "TYPE"); -#endif - return r; + return blkid_probe_has_value(h->pr, "TYPE");; } const char *blk_get_partition_type(struct blkid_handle *h) { const char *value = NULL; -#ifdef HAVE_BLKID (void) blkid_probe_lookup_value(h->pr, "PTTYPE", &value, NULL); -#endif return value; } const char *blk_get_superblock_type(struct blkid_handle *h) { const char *value = NULL; -#ifdef HAVE_BLKID (void) blkid_probe_lookup_value(h->pr, "TYPE", &value, NULL); -#endif return value; } void blk_free(struct blkid_handle *h) { -#ifdef HAVE_BLKID if (!h) return; @@ -252,10 +209,8 @@ void blk_free(struct blkid_handle *h) blkid_free_probe(h->pr); free(h); -#endif } -#ifdef HAVE_BLKID #ifndef HAVE_BLKID_WIPE static int blk_step_back(struct blkid_handle *h) { @@ -268,11 +223,9 @@ static int blk_step_back(struct blkid_handle *h) #endif } #endif /* not HAVE_BLKID_WIPE */ -#endif /* HAVE_BLKID */ int blk_do_wipe(struct blkid_handle *h) { -#ifdef HAVE_BLKID #ifdef HAVE_BLKID_WIPE return blkid_do_wipe(h->pr, 0); #else @@ -319,29 +272,110 @@ int blk_do_wipe(struct blkid_handle *h) return -EIO; #endif -#else /* HAVE_BLKID */ - return -ENOTSUP; -#endif } int blk_supported(void) { - int r = 0; -#ifdef HAVE_BLKID - r = 1; -#endif - return r; + return 1; } unsigned blk_get_block_size(struct blkid_handle *h) { unsigned block_size = 0; -#ifdef HAVE_BLKID const char *data; if (!blk_is_superblock(h) || !blkid_probe_has_value(h->pr, "BLOCK_SIZE") || blkid_probe_lookup_value(h->pr, "BLOCK_SIZE", &data, NULL) || sscanf(data, "%u", &block_size) != 1) block_size = 0; -#endif + return block_size; } + +#else /* HAVE_BLKID */ +#pragma GCC diagnostic ignored "-Wunused-parameter" + +void blk_set_chains_for_wipes(struct blkid_handle *h) +{ +} + +void blk_set_chains_for_full_print(struct blkid_handle *h) +{ +} + +void blk_set_chains_for_superblocks(struct blkid_handle *h) +{ +} + +void blk_set_chains_for_fast_detection(struct blkid_handle *h) +{ +} + +int blk_init_by_path(struct blkid_handle **h, const char *path) +{ + return -ENOTSUP; +} + +int blk_init_by_fd(struct blkid_handle **h, int fd) +{ + return -ENOTSUP; +} + +int blk_superblocks_filter_luks(struct blkid_handle *h) +{ + return -ENOTSUP; +} + +int blk_superblocks_only_luks(struct blkid_handle *h) +{ + return -ENOTSUP; +} + +blk_probe_status blk_probe(struct blkid_handle *h) +{ + return PRB_FAIL; +} + +blk_probe_status blk_safeprobe(struct blkid_handle *h) +{ + return PRB_FAIL; +} + +int blk_is_partition(struct blkid_handle *h) +{ + return 0; +} + +int blk_is_superblock(struct blkid_handle *h) +{ + return 0; +} + +const char *blk_get_partition_type(struct blkid_handle *h) +{ + return NULL; +} + +const char *blk_get_superblock_type(struct blkid_handle *h) +{ + return NULL; +} + +void blk_free(struct blkid_handle *h) +{ +} + +int blk_do_wipe(struct blkid_handle *h) +{ + return -ENOTSUP; +} + +int blk_supported(void) +{ + return 0; +} + +unsigned blk_get_block_size(struct blkid_handle *h) +{ + return 0; +} +#endif diff --git a/lib/utils_blkid.h b/lib/utils_blkid.h index 3ee1434..7e005f0 100644 --- a/lib/utils_blkid.h +++ b/lib/utils_blkid.h @@ -1,7 +1,7 @@ /* * blkid probe utilities * - * Copyright (C) 2018-2023 Red Hat, Inc. All rights reserved. + * Copyright (C) 2018-2024 Red Hat, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/lib/utils_crypt.c b/lib/utils_crypt.c index 0b7dc37..1e97610 100644 --- a/lib/utils_crypt.c +++ b/lib/utils_crypt.c @@ -2,8 +2,8 @@ * utils_crypt - cipher utilities for cryptsetup * * Copyright (C) 2004-2007 Clemens Fruhwirth - * Copyright (C) 2009-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2009-2023 Milan Broz + * Copyright (C) 2009-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2009-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -43,7 +43,13 @@ int crypt_parse_name_and_mode(const char *s, char *cipher, int *key_nums, cipher, cipher_mode) == 2) { if (!strcmp(cipher_mode, "plain")) strcpy(cipher_mode, "cbc-plain"); - if (key_nums) { + if (!strncmp(cipher, "capi:", 5)) { + /* CAPI must not use internal cipher driver names with dash */ + if (strchr(cipher_mode, ')')) + return -EINVAL; + if (key_nums) + *key_nums = 1; + } else if (key_nums) { char *tmp = strchr(cipher, ':'); *key_nums = tmp ? atoi(++tmp) : 1; if (!*key_nums) @@ -300,6 +306,15 @@ int crypt_capi_to_cipher(char **org_c, char **org_i, const char *c_dm, const cha if (i != 2) return -EINVAL; + /* non-cryptsetup compatible mode (generic driver with dash?) */ + if (strrchr(iv, ')')) { + if (i_dm) + return -EINVAL; + if (!(*org_c = strdup(c_dm))) + return -ENOMEM; + return 0; + } + len = strlen(tmp); if (len < 2) return -EINVAL; diff --git a/lib/utils_crypt.h b/lib/utils_crypt.h index 92e0705..0a4b5d6 100644 --- a/lib/utils_crypt.h +++ b/lib/utils_crypt.h @@ -2,8 +2,8 @@ * utils_crypt - cipher utilities for cryptsetup * * Copyright (C) 2004-2007 Clemens Fruhwirth - * Copyright (C) 2009-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2009-2023 Milan Broz + * Copyright (C) 2009-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2009-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -30,9 +30,12 @@ struct crypt_device; #define MAX_CIPHER_LEN 32 #define MAX_CIPHER_LEN_STR "31" #define MAX_KEYFILES 32 +#define MAX_KEYRING_LINKS 2 +#define MAX_VK_IN_KEYRING 2 #define MAX_CAPI_ONE_LEN 2 * MAX_CIPHER_LEN #define MAX_CAPI_ONE_LEN_STR "63" /* for sscanf length + '\0' */ #define MAX_CAPI_LEN 144 /* should be enough to fit whole capi string */ +#define MAX_INTEGRITY_LEN 64 int crypt_parse_name_and_mode(const char *s, char *cipher, int *key_nums, char *cipher_mode); diff --git a/lib/utils_device.c b/lib/utils_device.c index d80ea62..8bc329d 100644 --- a/lib/utils_device.c +++ b/lib/utils_device.c @@ -3,8 +3,8 @@ * * Copyright (C) 2004 Jana Saout * Copyright (C) 2004-2007 Clemens Fruhwirth - * Copyright (C) 2009-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2009-2023 Milan Broz + * Copyright (C) 2009-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2009-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -178,6 +178,7 @@ static int device_ready(struct crypt_device *cd, struct device *device) int devfd = -1, r = 0; struct stat st; size_t tmp_size; + const char *dm_name; if (!device) return -EINVAL; @@ -188,7 +189,12 @@ static int device_ready(struct crypt_device *cd, struct device *device) device->o_direct = 0; devfd = open(device_path(device), O_RDONLY | O_DIRECT); if (devfd >= 0) { - if (device_read_test(devfd) == 0) { + /* skip check for suspended DM devices */ + dm_name = device_dm_name(device); + if (dm_name && dm_status_suspended(cd, dm_name)) { + close(devfd); + devfd = -1; + } else if (device_read_test(devfd) == 0) { device->o_direct = 1; } else { close(devfd); @@ -470,7 +476,7 @@ void device_free(struct crypt_device *cd, struct device *device) /* Get block device path */ const char *device_block_path(const struct device *device) { - if (!device || !device->init_done) + if (!device) return NULL; return device->path; @@ -482,7 +488,7 @@ const char *device_dm_name(const struct device *device) const char *dmdir = dm_get_dir(); size_t dmdir_len = strlen(dmdir); - if (!device || !device->init_done) + if (!device) return NULL; if (strncmp(device->path, dmdir, dmdir_len)) @@ -985,6 +991,22 @@ int device_is_rotational(struct device *device) return crypt_dev_is_rotational(major(st.st_rdev), minor(st.st_rdev)); } +int device_is_dax(struct device *device) +{ + struct stat st; + + if (!device) + return -EINVAL; + + if (stat(device_path(device), &st) < 0) + return -EINVAL; + + if (!S_ISBLK(st.st_mode)) + return 0; + + return crypt_dev_is_dax(major(st.st_rdev), minor(st.st_rdev)); +} + size_t device_alignment(struct device *device) { int devfd; diff --git a/lib/utils_device_locking.c b/lib/utils_device_locking.c index e18ea77..ef3f6b4 100644 --- a/lib/utils_device_locking.c +++ b/lib/utils_device_locking.c @@ -1,8 +1,8 @@ /* * Metadata on-disk locking for processes serialization * - * Copyright (C) 2016-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2016-2023 Ondrej Kozina + * Copyright (C) 2016-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2016-2024 Ondrej Kozina * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -134,7 +134,7 @@ static int open_resource(struct crypt_device *cd, const char *res) return -EINVAL; log_dbg(cd, "Opening lock resource file %s/%s", DEFAULT_LUKS2_LOCK_PATH, res); - r = openat(lockdir_fd, res, O_CREAT | O_NOFOLLOW | O_RDWR | O_CLOEXEC, 0777); + r = openat(lockdir_fd, res, O_CREAT|O_NOFOLLOW|O_RDWR|O_CLOEXEC, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); err = errno; close(lockdir_fd); @@ -405,30 +405,6 @@ int device_write_lock_internal(struct crypt_device *cd, struct device *device) return 1; } -int crypt_read_lock(struct crypt_device *cd, const char *resource, bool blocking, struct crypt_lock_handle **lock) -{ - int r; - struct crypt_lock_handle *h; - - if (!resource) - return -EINVAL; - - log_dbg(cd, "Acquiring %sblocking read lock for resource %s.", blocking ? "" : "non", resource); - - r = acquire_and_verify(cd, NULL, resource, LOCK_SH | (blocking ? 0 : LOCK_NB), &h); - if (r < 0) - return r; - - h->type = DEV_LOCK_READ; - h->refcnt = 1; - - log_dbg(cd, "READ lock for resource %s taken.", resource); - - *lock = h; - - return 0; -} - int crypt_write_lock(struct crypt_device *cd, const char *resource, bool blocking, struct crypt_lock_handle **lock) { int r; diff --git a/lib/utils_device_locking.h b/lib/utils_device_locking.h index b73f15d..3fa09a5 100644 --- a/lib/utils_device_locking.h +++ b/lib/utils_device_locking.h @@ -1,8 +1,8 @@ /* * Metadata on-disk locking for processes serialization * - * Copyright (C) 2016-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2016-2023 Ondrej Kozina + * Copyright (C) 2016-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2016-2024 Ondrej Kozina * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -37,7 +37,6 @@ void device_unlock_internal(struct crypt_device *cd, struct device *device); int device_locked_verify(struct crypt_device *cd, int fd, struct crypt_lock_handle *h); -int crypt_read_lock(struct crypt_device *cd, const char *name, bool blocking, struct crypt_lock_handle **lock); int crypt_write_lock(struct crypt_device *cd, const char *name, bool blocking, struct crypt_lock_handle **lock); void crypt_unlock_internal(struct crypt_device *cd, struct crypt_lock_handle *h); diff --git a/lib/utils_devpath.c b/lib/utils_devpath.c index dc5a5bb..5e7e13e 100644 --- a/lib/utils_devpath.c +++ b/lib/utils_devpath.c @@ -3,8 +3,8 @@ * * Copyright (C) 2004 Jana Saout * Copyright (C) 2004-2007 Clemens Fruhwirth - * Copyright (C) 2009-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2009-2023 Milan Broz + * Copyright (C) 2009-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2009-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -210,6 +210,24 @@ static int _path_get_uint64(const char *sysfs_path, uint64_t *value, const char return _read_uint64(path, value); } +int crypt_dev_get_partition_number(const char *dev_path) +{ + uint64_t partno; + struct stat st; + + if (stat(dev_path, &st) < 0) + return 0; + + if (!S_ISBLK(st.st_mode)) + return 0; + + if (!_sysfs_get_uint64(major(st.st_rdev), minor(st.st_rdev), + &partno, "partition")) + return -EINVAL; + + return (int)partno; +} + int crypt_dev_is_rotational(int major, int minor) { uint64_t val; @@ -220,6 +238,16 @@ int crypt_dev_is_rotational(int major, int minor) return val ? 1 : 0; } +int crypt_dev_is_dax(int major, int minor) +{ + uint64_t val; + + if (!_sysfs_get_uint64(major, minor, &val, "queue/dax")) + return 0; /* if failed, expect non-DAX device */ + + return val ? 1 : 0; +} + int crypt_dev_is_partition(const char *dev_path) { uint64_t val; @@ -253,6 +281,7 @@ uint64_t crypt_dev_partition_offset(const char *dev_path) &val, "start")) return 0; + /* coverity[tainted_data_return:FALSE] */ return val; } diff --git a/lib/utils_dm.h b/lib/utils_dm.h index 79212a2..dbbd470 100644 --- a/lib/utils_dm.h +++ b/lib/utils_dm.h @@ -3,8 +3,8 @@ * * Copyright (C) 2004 Jana Saout * Copyright (C) 2004-2007 Clemens Fruhwirth - * Copyright (C) 2009-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2009-2023 Milan Broz + * Copyright (C) 2009-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2009-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -72,7 +72,7 @@ static inline uint32_t act2dmflags(uint32_t act_flags) #define DM_INTEGRITY_DISCARDS_SUPPORTED (1 << 23) /* dm-integrity discards/TRIM option is supported */ #define DM_INTEGRITY_RESIZE_SUPPORTED (1 << 23) /* dm-integrity resize of the integrity device supported (introduced in the same version as discards)*/ #define DM_VERITY_PANIC_CORRUPTION_SUPPORTED (1 << 24) /* dm-verity panic on corruption */ -#define DM_CRYPT_NO_WORKQUEUE_SUPPORTED (1 << 25) /* dm-crypt suppot for bypassing workqueues */ +#define DM_CRYPT_NO_WORKQUEUE_SUPPORTED (1 << 25) /* dm-crypt support for bypassing workqueues */ #define DM_INTEGRITY_FIX_HMAC_SUPPORTED (1 << 26) /* hmac covers also superblock */ #define DM_INTEGRITY_RESET_RECALC_SUPPORTED (1 << 27) /* dm-integrity automatic recalculation supported */ #define DM_VERITY_TASKLETS_SUPPORTED (1 << 28) /* dm-verity tasklets supported */ @@ -234,6 +234,7 @@ int dm_clear_device(struct crypt_device *cd, const char *name); int dm_cancel_deferred_removal(const char *name); const char *dm_get_dir(void); +int dm_get_iname(const char *name, char **iname, bool with_path); int lookup_dm_dev_by_uuid(struct crypt_device *cd, const char *uuid, const char *type); diff --git a/lib/utils_io.c b/lib/utils_io.c index a5bc501..1c6b456 100644 --- a/lib/utils_io.c +++ b/lib/utils_io.c @@ -3,8 +3,8 @@ * * Copyright (C) 2004 Jana Saout * Copyright (C) 2004-2007 Clemens Fruhwirth - * Copyright (C) 2009-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2009-2023 Milan Broz + * Copyright (C) 2009-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2009-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/lib/utils_io.h b/lib/utils_io.h index f8b3f00..ce6a6ed 100644 --- a/lib/utils_io.h +++ b/lib/utils_io.h @@ -3,8 +3,8 @@ * * Copyright (C) 2004 Jana Saout * Copyright (C) 2004-2007 Clemens Fruhwirth - * Copyright (C) 2009-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2009-2023 Milan Broz + * Copyright (C) 2009-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2009-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/lib/utils_keyring.c b/lib/utils_keyring.c index a0c4db1..6bd3c48 100644 --- a/lib/utils_keyring.c +++ b/lib/utils_keyring.c @@ -1,8 +1,8 @@ /* * kernel keyring utilities * - * Copyright (C) 2016-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2016-2023 Ondrej Kozina + * Copyright (C) 2016-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2016-2024 Ondrej Kozina * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -19,9 +19,14 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include +#include #include +#include #include +#include #include +#include #include #include @@ -29,11 +34,6 @@ #include "libcryptsetup_macros.h" #include "utils_keyring.h" -#ifndef HAVE_KEY_SERIAL_T -#define HAVE_KEY_SERIAL_T -typedef int32_t key_serial_t; -#endif - #ifdef KERNEL_KEYRING static const struct { @@ -42,6 +42,9 @@ static const struct { } key_types[] = { { LOGON_KEY, "logon" }, { USER_KEY, "user" }, + { BIG_KEY, "big_key" }, + { TRUSTED_KEY, "trusted" }, + { ENCRYPTED_KEY, "encrypted" }, }; #include @@ -65,16 +68,22 @@ static key_serial_t add_key(const char *type, return syscall(__NR_add_key, type, description, payload, plen, keyring); } +/* keyctl_describe */ +static long keyctl_describe(key_serial_t id, char *buffer, size_t buflen) +{ + return syscall(__NR_keyctl, KEYCTL_DESCRIBE, id, buffer, buflen); +} + /* keyctl_read */ static long keyctl_read(key_serial_t key, char *buffer, size_t buflen) { return syscall(__NR_keyctl, KEYCTL_READ, key, buffer, buflen); } -/* keyctl_revoke */ -static long keyctl_revoke(key_serial_t key) +/* keyctl_link */ +static long keyctl_link(key_serial_t key, key_serial_t keyring) { - return syscall(__NR_keyctl, KEYCTL_REVOKE, key); + return syscall(__NR_keyctl, KEYCTL_LINK, key, keyring); } /* keyctl_unlink */ @@ -82,156 +91,380 @@ static long keyctl_unlink(key_serial_t key, key_serial_t keyring) { return syscall(__NR_keyctl, KEYCTL_UNLINK, key, keyring); } -#endif -int keyring_check(void) +/* inspired by keyutils written by David Howells (dhowells@redhat.com) */ +static key_serial_t keyring_process_proc_keys_line(char *line, const char *type, const char *desc, + key_serial_t destringid) { -#ifdef KERNEL_KEYRING - /* logon type key descriptions must be in format "prefix:description" */ - return syscall(__NR_request_key, "logon", "dummy", NULL, 0) == -1l && errno != ENOSYS; -#else + char typebuf[41], rdesc[1024], *kdesc, *cp; + int ndesc, n; + key_serial_t id; + int dlen; + + assert(desc); + dlen = strlen(desc); + cp = line + strlen(line); + + ndesc = 0; + n = sscanf(line, "%x %*s %*u %*s %*x %*d %*d %40s %n", + &id, typebuf, &ndesc); + if (n == 2 && ndesc > 0 && ndesc <= cp - line) { + if (strcmp(typebuf, type) != 0) + return 0; + kdesc = line + ndesc; + if (memcmp(kdesc, desc, dlen) != 0) + return 0; + if (kdesc[dlen] != ':' && + kdesc[dlen] != '\0' && + kdesc[dlen] != ' ') + return 0; + kdesc[dlen] = '\0'; + + /* The key type appends extra stuff to the end of the + * description after a colon in /proc/keys. Colons, + * however, are allowed in descriptions, so we need to + * make a further check. */ + n = keyctl_describe(id, rdesc, sizeof(rdesc) - 1); + if (n < 0) + return 0; + if ((size_t)n >= sizeof(rdesc) - 1) + return 0; + rdesc[n] = '\0'; + + cp = strrchr(rdesc, ';'); + if (!cp) + return 0; + cp++; + if (strcmp(cp, desc) != 0) + return 0; + + + if (destringid && keyctl_link(id, destringid) == -1) + return 0; + + return id; + } + return 0; -#endif } -int keyring_add_key_in_thread_keyring(key_type_t ktype, const char *key_desc, const void *key, size_t key_size) -{ -#ifdef KERNEL_KEYRING - key_serial_t kid; - const char *type_name = key_type_name(ktype); +/* inspired by keyutils written by David Howells (dhowells@redhat.com), returns 0 ID on failure */ - if (!type_name || !key_desc) - return -EINVAL; +static key_serial_t find_key_by_type_and_desc(const char *type, const char *desc, key_serial_t destringid) +{ + key_serial_t id; + int f; + char buf[1024]; + char *newline; + size_t buffer_len = 0; + + int n; + + do { + id = request_key(type, desc, NULL, 0); + } while (id < 0 && errno == EINTR); + if (id >= 0 || errno == ENOMEM) + return id; + + f = open("/proc/keys", O_RDONLY); + if (f < 0) + return 0; - kid = add_key(type_name, key_desc, key, key_size, KEY_SPEC_THREAD_KEYRING); - if (kid < 0) - return -errno; + while ((n = read(f, buf + buffer_len, sizeof(buf) - buffer_len - 1)) > 0) { + buffer_len += n; + buf[buffer_len] = '\0'; + newline = strchr(buf, '\n'); + while (newline != NULL && buffer_len != 0) { + *newline = '\0'; + + if ((id = keyring_process_proc_keys_line(buf, type, desc, destringid))) { + close(f); + return id; + } + + buffer_len -= newline - buf + 1; + assert(buffer_len <= sizeof(buf) - 1); + memmove(buf, newline + 1, buffer_len); + buf[buffer_len] = '\0'; + newline = strchr(buf, '\n'); + } + } + close(f); return 0; -#else - return -ENOTSUP; -#endif } -/* currently used in client utilities only */ -int keyring_add_key_in_user_keyring(key_type_t ktype, const char *key_desc, const void *key, size_t key_size) +int keyring_check(void) +{ + /* logon type key descriptions must be in format "prefix:description" */ + return syscall(__NR_request_key, "logon", "dummy", NULL, 0) == -1l && errno != ENOSYS; +} + +static key_serial_t keyring_add_key_in_keyring(key_type_t ktype, + const char *key_desc, + const void *key, + size_t key_size, + key_serial_t keyring) { -#ifdef KERNEL_KEYRING const char *type_name = key_type_name(ktype); - key_serial_t kid; if (!type_name || !key_desc) return -EINVAL; - kid = add_key(type_name, key_desc, key, key_size, KEY_SPEC_USER_KEYRING); - if (kid < 0) - return -errno; - - return 0; -#else - return -ENOTSUP; -#endif + return add_key(type_name, key_desc, key, key_size, keyring); } -/* alias for the same code */ -int keyring_get_key(const char *key_desc, - char **key, - size_t *key_size) +key_serial_t keyring_add_key_in_thread_keyring(key_type_t ktype, const char *key_desc, const void *key, size_t key_size) { - return keyring_get_passphrase(key_desc, key, key_size); + return keyring_add_key_in_keyring(ktype, key_desc, key, key_size, KEY_SPEC_THREAD_KEYRING); } -int keyring_get_passphrase(const char *key_desc, - char **passphrase, - size_t *passphrase_len) +key_serial_t keyring_request_key_id(key_type_t key_type, + const char *key_description) { -#ifdef KERNEL_KEYRING - int err; key_serial_t kid; - long ret; + + do { + kid = request_key(key_type_name(key_type), key_description, NULL, 0); + } while (kid < 0 && errno == EINTR); + + return kid; +} + +int keyring_read_key(key_serial_t kid, + char **key, + size_t *key_size) +{ + long r; char *buf = NULL; size_t len = 0; - do - kid = request_key(key_type_name(USER_KEY), key_desc, NULL, 0); - while (kid < 0 && errno == EINTR); - - if (kid < 0) - return -errno; + assert(key); + assert(key_size); /* just get payload size */ - ret = keyctl_read(kid, NULL, 0); - if (ret > 0) { - len = ret; + r = keyctl_read(kid, NULL, 0); + if (r > 0) { + len = r; buf = crypt_safe_alloc(len); if (!buf) return -ENOMEM; /* retrieve actual payload data */ - ret = keyctl_read(kid, buf, len); + r = keyctl_read(kid, buf, len); } - if (ret < 0) { - err = errno; + if (r < 0) { crypt_safe_free(buf); - return -err; + return -EINVAL; } - *passphrase = buf; - *passphrase_len = len; + *key = buf; + *key_size = len; return 0; -#else - return -ENOTSUP; -#endif } -static int keyring_revoke_and_unlink_key_type(const char *type_name, const char *key_desc) +int keyring_unlink_key_from_keyring(key_serial_t kid, key_serial_t keyring_id) { -#ifdef KERNEL_KEYRING - key_serial_t kid; + return keyctl_unlink(kid, keyring_id) < 0 ? -EINVAL : 0; +} - if (!type_name || !key_desc) - return -EINVAL; +int keyring_unlink_key_from_thread_keyring(key_serial_t kid) +{ + return keyctl_unlink(kid, KEY_SPEC_THREAD_KEYRING) < 0 ? -EINVAL : 0; +} + +const char *key_type_name(key_type_t type) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(key_types); i++) + if (type == key_types[i].type) + return key_types[i].type_name; + + return NULL; +} + +key_serial_t keyring_find_key_id_by_name(const char *key_name) +{ + key_serial_t id = 0; + char *end; + char *name_copy, *name_copy_p; - do - kid = request_key(type_name, key_desc, NULL, 0); - while (kid < 0 && errno == EINTR); + assert(key_name); + + if (key_name[0] == '@') { + if (strcmp(key_name, "@t" ) == 0) return KEY_SPEC_THREAD_KEYRING; + if (strcmp(key_name, "@p" ) == 0) return KEY_SPEC_PROCESS_KEYRING; + if (strcmp(key_name, "@s" ) == 0) return KEY_SPEC_SESSION_KEYRING; + if (strcmp(key_name, "@u" ) == 0) return KEY_SPEC_USER_KEYRING; + if (strcmp(key_name, "@us") == 0) return KEY_SPEC_USER_SESSION_KEYRING; + if (strcmp(key_name, "@g" ) == 0) return KEY_SPEC_GROUP_KEYRING; + if (strcmp(key_name, "@a" ) == 0) return KEY_SPEC_REQKEY_AUTH_KEY; - if (kid < 0) return 0; + } - if (keyctl_revoke(kid)) - return -errno; + /* handle a lookup-by-name request "%:", eg: "%keyring:_ses" */ + name_copy = strdup(key_name); + if (!name_copy) + goto out; + name_copy_p = name_copy; + + if (name_copy_p[0] == '%') { + const char *type; + + name_copy_p++; + if (!*name_copy_p) + goto out; + + if (*name_copy_p == ':') { + type = "keyring"; + name_copy_p++; + } else { + type = name_copy_p; + name_copy_p = strchr(name_copy_p, ':'); + if (!name_copy_p) + goto out; + *(name_copy_p++) = '\0'; + } + + if (!*name_copy_p) + goto out; + + id = find_key_by_type_and_desc(type, name_copy_p, 0); + goto out; + } + + id = strtoul(key_name, &end, 0); + if (*end) + id = 0; - /* - * best effort only. the key could have been linked - * in some other keyring and its payload is now - * revoked anyway. - */ - keyctl_unlink(kid, KEY_SPEC_THREAD_KEYRING); - keyctl_unlink(kid, KEY_SPEC_PROCESS_KEYRING); - keyctl_unlink(kid, KEY_SPEC_USER_KEYRING); +out: + if (name_copy) + free(name_copy); + + return id; +} + +static bool numbered(const char *str) +{ + char *endp; + + errno = 0; + (void) strtol(str, &endp, 0); + if (errno == ERANGE) + return false; + + return *endp == '\0' ? true : false; +} + +key_serial_t keyring_find_keyring_id_by_name(const char *keyring_name) +{ + assert(keyring_name); + + /* "%:" is abbreviation for the type keyring */ + if ((keyring_name[0] == '@' && keyring_name[1] != 'a') || + strstr(keyring_name, "%:") || strstr(keyring_name, "%keyring:") || + numbered(keyring_name)) + return keyring_find_key_id_by_name(keyring_name); return 0; -#else - return -ENOTSUP; -#endif } -const char *key_type_name(key_type_t type) +key_type_t key_type_by_name(const char *name) { -#ifdef KERNEL_KEYRING unsigned int i; for (i = 0; i < ARRAY_SIZE(key_types); i++) - if (type == key_types[i].type) - return key_types[i].type_name; -#endif + if (!strcmp(key_types[i].type_name, name)) + return key_types[i].type; + + return INVALID_KEY; +} + +key_serial_t keyring_add_key_to_custom_keyring(key_type_t ktype, + const char *key_desc, + const void *key, + size_t key_size, + key_serial_t keyring_to_link) +{ + const char *type_name = key_type_name(ktype); + + if (!type_name || !key_desc) + return -EINVAL; + + return add_key(type_name, key_desc, key, key_size, keyring_to_link); +} + +#else /* KERNEL_KEYRING */ +#pragma GCC diagnostic ignored "-Wunused-parameter" + +int keyring_check(void) +{ + return 0; +} + +key_serial_t keyring_add_key_in_thread_keyring(key_type_t ktype, const char *key_desc, const void *key, size_t key_size) +{ + return -ENOTSUP; +} + +key_serial_t keyring_request_key_id(key_type_t key_type, + const char *key_description) +{ + return -ENOTSUP; +} + +int keyring_read_key(key_serial_t kid, + char **key, + size_t *key_size) +{ + return -ENOTSUP; +} + +int keyring_read_by_id(const char *key_desc, char **passphrase, size_t *passphrase_len) +{ + return -ENOTSUP; +} + +const char *key_type_name(key_type_t type) +{ return NULL; } -int keyring_revoke_and_unlink_key(key_type_t ktype, const char *key_desc) +key_serial_t keyring_find_key_id_by_name(const char *key_name) { - return keyring_revoke_and_unlink_key_type(key_type_name(ktype), key_desc); + return 0; } + +key_serial_t keyring_find_keyring_id_by_name(const char *keyring_name) +{ + return 0; +} + +key_type_t key_type_by_name(const char *name) +{ + return INVALID_KEY; +} + +key_serial_t keyring_add_key_to_custom_keyring(key_type_t ktype, + const char *key_desc, + const void *key, + size_t key_size, + key_serial_t keyring_to_link) +{ + return -ENOTSUP; +} + +int keyring_unlink_key_from_keyring(key_serial_t kid, key_serial_t keyring_id) +{ + return -ENOTSUP; +} + +int keyring_unlink_key_from_thread_keyring(key_serial_t kid) +{ + return -ENOTSUP; +} +#endif diff --git a/lib/utils_keyring.h b/lib/utils_keyring.h index 0248862..896f8d8 100644 --- a/lib/utils_keyring.h +++ b/lib/utils_keyring.h @@ -1,8 +1,8 @@ /* * kernel keyring syscall wrappers * - * Copyright (C) 2016-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2016-2023 Ondrej Kozina + * Copyright (C) 2016-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2016-2024 Ondrej Kozina * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -23,33 +23,38 @@ #define _UTILS_KEYRING #include +#include -typedef enum { LOGON_KEY = 0, USER_KEY } key_type_t; +#ifndef HAVE_KEY_SERIAL_T +#define HAVE_KEY_SERIAL_T +typedef int32_t key_serial_t; +#endif + +typedef enum { LOGON_KEY = 0, USER_KEY, BIG_KEY, TRUSTED_KEY, ENCRYPTED_KEY, INVALID_KEY } key_type_t; const char *key_type_name(key_type_t ktype); +key_type_t key_type_by_name(const char *name); +key_serial_t keyring_find_key_id_by_name(const char *key_name); +key_serial_t keyring_find_keyring_id_by_name(const char *keyring_name); int keyring_check(void); -int keyring_get_key(const char *key_desc, - char **key, - size_t *key_size); +key_serial_t keyring_request_key_id(key_type_t key_type, + const char *key_description); -int keyring_get_passphrase(const char *key_desc, - char **passphrase, - size_t *passphrase_len); - -int keyring_add_key_in_thread_keyring( - key_type_t ktype, - const char *key_desc, - const void *key, - size_t key_size); +int keyring_read_key(key_serial_t kid, + char **key, + size_t *key_size); -int keyring_add_key_in_user_keyring( +key_serial_t keyring_add_key_in_thread_keyring( key_type_t ktype, const char *key_desc, const void *key, size_t key_size); -int keyring_revoke_and_unlink_key(key_type_t ktype, const char *key_desc); +key_serial_t keyring_add_key_to_custom_keyring(key_type_t ktype, const char *key_desc, const void *key, + size_t key_size, key_serial_t keyring_to_link); +int keyring_unlink_key_from_keyring(key_serial_t kid, key_serial_t keyring_id); +int keyring_unlink_key_from_thread_keyring(key_serial_t kid); #endif diff --git a/lib/utils_loop.c b/lib/utils_loop.c index 9b31603..092ebfc 100644 --- a/lib/utils_loop.c +++ b/lib/utils_loop.c @@ -1,8 +1,8 @@ /* * loopback block device utilities * - * Copyright (C) 2009-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2009-2023 Milan Broz + * Copyright (C) 2009-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2009-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -282,7 +282,7 @@ static char *_sysfs_backing_file(const char *loop) { struct stat st; char buf[PATH_MAX]; - size_t len; + ssize_t len; int fd; if (stat(loop, &st) || !S_ISBLK(st.st_mode)) diff --git a/lib/utils_loop.h b/lib/utils_loop.h index c1f6356..17a78aa 100644 --- a/lib/utils_loop.h +++ b/lib/utils_loop.h @@ -1,8 +1,8 @@ /* * loopback block device utilities * - * Copyright (C) 2009-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2009-2023 Milan Broz + * Copyright (C) 2009-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2009-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/lib/utils_pbkdf.c b/lib/utils_pbkdf.c index 4d7e18d..4341e91 100644 --- a/lib/utils_pbkdf.c +++ b/lib/utils_pbkdf.c @@ -1,8 +1,8 @@ /* * utils_pbkdf - PBKDF settings for libcryptsetup * - * Copyright (C) 2009-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2009-2023 Milan Broz + * Copyright (C) 2009-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2009-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -61,9 +61,9 @@ const struct crypt_pbkdf_type *crypt_get_pbkdf_type_params(const char *pbkdf_typ return NULL; } -static uint32_t adjusted_phys_memory(void) +uint32_t pbkdf_adjusted_phys_memory_kb(void) { - uint64_t memory_kb = crypt_getphysmemory_kb(); + uint64_t free_kb, memory_kb = crypt_getphysmemory_kb(); /* Ignore bogus value */ if (memory_kb < (128 * 1024) || memory_kb > UINT32_MAX) @@ -75,6 +75,22 @@ static uint32_t adjusted_phys_memory(void) */ memory_kb /= 2; + /* + * Never use more that half of available free memory on system without swap. + */ + if (!crypt_swapavailable()) { + free_kb = crypt_getphysmemoryfree_kb(); + + /* + * Using exactly free memory causes OOM too, use only half of the value. + * Ignore small values (< 64MB), user should use PBKDF2 in such environment. + */ + free_kb /= 2; + + if (free_kb > (64 * 1024) && free_kb < memory_kb) + return free_kb; + } + return memory_kb; } @@ -238,7 +254,8 @@ int init_pbkdf_type(struct crypt_device *cd, cd_pbkdf->parallel_threads = pbkdf_limits.max_parallel; } - if (cd_pbkdf->parallel_threads) { + /* Do not limit threads by online CPUs if user forced values (no benchmark). */ + if (cd_pbkdf->parallel_threads && !(cd_pbkdf->flags & CRYPT_PBKDF_NO_BENCHMARK)) { cpus = crypt_cpusonline(); if (cd_pbkdf->parallel_threads > cpus) { log_dbg(cd, "Only %u active CPUs detected, " @@ -248,8 +265,9 @@ int init_pbkdf_type(struct crypt_device *cd, } } - if (cd_pbkdf->max_memory_kb) { - memory_kb = adjusted_phys_memory(); + /* Do not limit by available physical memory if user forced values (no benchmark). */ + if (cd_pbkdf->max_memory_kb && !(cd_pbkdf->flags & CRYPT_PBKDF_NO_BENCHMARK)) { + memory_kb = pbkdf_adjusted_phys_memory_kb(); if (cd_pbkdf->max_memory_kb > memory_kb) { log_dbg(cd, "Not enough physical memory detected, " "PBKDF max memory decreased from %dkB to %dkB.", diff --git a/lib/utils_safe_memory.c b/lib/utils_safe_memory.c index b161369..753842d 100644 --- a/lib/utils_safe_memory.c +++ b/lib/utils_safe_memory.c @@ -1,8 +1,8 @@ /* * utils_safe_memory - safe memory helpers * - * Copyright (C) 2009-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2009-2023 Milan Broz + * Copyright (C) 2009-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2009-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/lib/utils_storage_wrappers.c b/lib/utils_storage_wrappers.c index 6ff5afa..4a3aae3 100644 --- a/lib/utils_storage_wrappers.c +++ b/lib/utils_storage_wrappers.c @@ -2,7 +2,7 @@ * Generic wrapper for storage functions * (experimental only) * - * Copyright (C) 2018-2023 Ondrej Kozina + * Copyright (C) 2018-2024 Ondrej Kozina * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/lib/utils_storage_wrappers.h b/lib/utils_storage_wrappers.h index f7781e8..272c5c1 100644 --- a/lib/utils_storage_wrappers.h +++ b/lib/utils_storage_wrappers.h @@ -2,7 +2,7 @@ * Generic wrapper for storage functions * (experimental only) * - * Copyright (C) 2018-2023 Ondrej Kozina + * Copyright (C) 2018-2024 Ondrej Kozina * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/lib/utils_wipe.c b/lib/utils_wipe.c index 1df46c1..368e6dc 100644 --- a/lib/utils_wipe.c +++ b/lib/utils_wipe.c @@ -2,8 +2,8 @@ * utils_wipe - wipe a device * * Copyright (C) 2004-2007 Clemens Fruhwirth - * Copyright (C) 2009-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2009-2023 Milan Broz + * Copyright (C) 2009-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2009-2024 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -26,6 +26,8 @@ #include #include #include "internal.h" +#include "luks2/luks2_internal.h" +#include "luks2/hw_opal/hw_opal.h" /* block device zeroout ioctls, introduced in Linux kernel 3.7 */ #ifndef BLKZEROOUT @@ -309,3 +311,73 @@ int crypt_wipe(struct crypt_device *cd, return r; } + +int crypt_wipe_hw_opal(struct crypt_device *cd, + int segment, + const char *password, + size_t password_size, + uint32_t flags) +{ + int r; + struct luks2_hdr *hdr; + uint32_t opal_segment_number; + struct crypt_lock_handle *opal_lh = NULL; + + UNUSED(flags); + + if (!cd) + return -EINVAL; + + if (!password) + return -EINVAL; + + if (segment < CRYPT_LUKS2_SEGMENT || segment > 8) + return -EINVAL; + + r = crypt_opal_supported(cd, crypt_data_device(cd)); + if (r < 0) + return r; + + if (segment == CRYPT_NO_SEGMENT) { + r = opal_factory_reset(cd, crypt_data_device(cd), password, password_size); + if (r == -EPERM) + log_err(cd, _("Incorrect OPAL PSID.")); + else if (r < 0) + log_err(cd, _("Cannot erase OPAL device.")); + return r; + } + + if (onlyLUKS2(cd) < 0) + return -EINVAL; + + hdr = crypt_get_hdr(cd, CRYPT_LUKS2); + if (!hdr) + return -EINVAL; + + if (segment == CRYPT_LUKS2_SEGMENT) { + r = LUKS2_get_opal_segment_number(hdr, CRYPT_DEFAULT_SEGMENT, &opal_segment_number); + if (r < 0) { + log_dbg(cd, "Can not get OPAL segment number."); + return r; + } + } else + opal_segment_number = segment; + + r = opal_exclusive_lock(cd, crypt_data_device(cd), &opal_lh); + if (r < 0) { + log_err(cd, _("Failed to acquire OPAL lock on device %s."), device_path(crypt_data_device(cd))); + return -EINVAL; + } + + r = opal_reset_segment(cd, + crypt_data_device(cd), + opal_segment_number, + password, + password_size); + + opal_exclusive_unlock(cd, opal_lh); + if (r < 0) + return r; + + return LUKS2_wipe_header_areas(cd, hdr, crypt_header_is_detached(cd)); +} diff --git a/lib/verity/rs.h b/lib/verity/rs.h index 7638924..34785aa 100644 --- a/lib/verity/rs.h +++ b/lib/verity/rs.h @@ -3,7 +3,7 @@ * * Copyright (C) 2004 Phil Karn, KA9Q * libcryptsetup modifications - * Copyright (C) 2017-2023 Red Hat, Inc. All rights reserved. + * Copyright (C) 2017-2024 Red Hat, Inc. All rights reserved. * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/lib/verity/rs_decode_char.c b/lib/verity/rs_decode_char.c index 4473202..94c8523 100644 --- a/lib/verity/rs_decode_char.c +++ b/lib/verity/rs_decode_char.c @@ -3,7 +3,7 @@ * * Copyright (C) 2002, Phil Karn, KA9Q * libcryptsetup modifications - * Copyright (C) 2017-2023 Red Hat, Inc. All rights reserved. + * Copyright (C) 2017-2024 Red Hat, Inc. All rights reserved. * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/lib/verity/rs_encode_char.c b/lib/verity/rs_encode_char.c index 55b502a..a520562 100644 --- a/lib/verity/rs_encode_char.c +++ b/lib/verity/rs_encode_char.c @@ -3,7 +3,7 @@ * * Copyright (C) 2002, Phil Karn, KA9Q * libcryptsetup modifications - * Copyright (C) 2017-2023 Red Hat, Inc. All rights reserved. + * Copyright (C) 2017-2024 Red Hat, Inc. All rights reserved. * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/lib/verity/verity.c b/lib/verity/verity.c index 0d7a8f5..b3dd1b3 100644 --- a/lib/verity/verity.c +++ b/lib/verity/verity.c @@ -1,7 +1,7 @@ /* * dm-verity volume handling * - * Copyright (C) 2012-2023 Red Hat, Inc. All rights reserved. + * Copyright (C) 2012-2024 Red Hat, Inc. All rights reserved. * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -251,91 +251,133 @@ int VERITY_UUID_generate(char **uuid_string) return 0; } +int VERITY_verify_params(struct crypt_device *cd, + struct crypt_params_verity *hdr, + bool signed_root_hash, + struct device *fec_device, + struct volume_key *root_hash) +{ + bool userspace_verification; + int v, r; + unsigned int fec_errors = 0; + + assert(cd); + assert(hdr); + assert(root_hash); + + log_dbg(cd, "Verifying VERITY device using hash %s.", + hdr->hash_name); + + userspace_verification = hdr->flags & CRYPT_VERITY_CHECK_HASH; + + if (userspace_verification && signed_root_hash) { + log_err(cd, _("Root hash signature verification is not supported.")); + return -EINVAL; + } + + if ((hdr->flags & CRYPT_VERITY_ROOT_HASH_SIGNATURE) && !signed_root_hash) { + log_err(cd, _("Root hash signature required.")); + return -EINVAL; + } + + if (!userspace_verification) + return 0; + + log_dbg(cd, "Verification of VERITY data in userspace required."); + r = VERITY_verify(cd, hdr, root_hash->key, root_hash->keylength); + + if ((r == -EPERM || r == -EFAULT) && fec_device) { + v = r; + log_dbg(cd, "Verification failed, trying to repair with FEC device."); + r = VERITY_FEC_process(cd, hdr, fec_device, 1, &fec_errors); + if (r < 0) + log_err(cd, _("Errors cannot be repaired with FEC device.")); + else if (fec_errors) { + log_err(cd, _("Found %u repairable errors with FEC device."), + fec_errors); + /* If root hash failed, we cannot be sure it was properly repaired */ + } + if (v == -EFAULT) + r = -EPERM; + } + + return r; +} + /* Activate verity device in kernel device-mapper */ int VERITY_activate(struct crypt_device *cd, const char *name, - const char *root_hash, - size_t root_hash_size, - const char *signature_description, + struct volume_key *root_hash, + struct volume_key *signature, struct device *fec_device, struct crypt_params_verity *verity_hdr, uint32_t activation_flags) { uint32_t dmv_flags; - unsigned int fec_errors = 0; - int r, v; - struct crypt_dm_active_device dmd = { - .size = verity_hdr->data_size * verity_hdr->data_block_size / 512, - .flags = activation_flags, - .uuid = crypt_get_uuid(cd), - }; - - log_dbg(cd, "Trying to activate VERITY device %s using hash %s.", - name ?: "[none]", verity_hdr->hash_name); - - if (verity_hdr->flags & CRYPT_VERITY_CHECK_HASH) { - if (signature_description) { - log_err(cd, _("Root hash signature verification is not supported.")); - return -EINVAL; - } + int r; + key_serial_t kid; + char *description = NULL; + struct crypt_dm_active_device dmd = { 0 }; - log_dbg(cd, "Verification of data in userspace required."); - r = VERITY_verify(cd, verity_hdr, root_hash, root_hash_size); - - if ((r == -EPERM || r == -EFAULT) && fec_device) { - v = r; - log_dbg(cd, "Verification failed, trying to repair with FEC device."); - r = VERITY_FEC_process(cd, verity_hdr, fec_device, 1, &fec_errors); - if (r < 0) - log_err(cd, _("Errors cannot be repaired with FEC device.")); - else if (fec_errors) { - log_err(cd, _("Found %u repairable errors with FEC device."), - fec_errors); - /* If root hash failed, we cannot be sure it was properly repaired */ - } - if (v == -EFAULT) - r = -EPERM; - } + assert(name); + assert(root_hash); + assert(verity_hdr); + + dmd.size = verity_hdr->data_size * verity_hdr->data_block_size / 512; + dmd.flags = activation_flags; + dmd.uuid = crypt_get_uuid(cd); + + log_dbg(cd, "Activating VERITY device %s using hash %s.", + name, verity_hdr->hash_name); + if (signature) { + r = asprintf(&description, "cryptsetup:%s%s%s", + crypt_get_uuid(cd) ?: "", crypt_get_uuid(cd) ? "-" : "", name); if (r < 0) - return r; - } + return -EINVAL; - if (!name) - return 0; + log_dbg(cd, "Adding signature %s (type user) into thread keyring.", description); + kid = keyring_add_key_in_thread_keyring(USER_KEY, description, signature->key, signature->keylength); + if (kid < 0) { + log_dbg(cd, "keyring_add_key_in_thread_keyring failed with errno %d.", errno); + log_err(cd, _("Failed to load key in kernel keyring.")); + free(description); + return -EINVAL; + } + } r = device_block_adjust(cd, crypt_metadata_device(cd), DEV_OK, 0, NULL, NULL); if (r) - return r; + goto out; r = device_block_adjust(cd, crypt_data_device(cd), DEV_EXCL, 0, &dmd.size, &dmd.flags); if (r) - return r; + goto out; if (fec_device) { r = device_block_adjust(cd, fec_device, DEV_OK, 0, NULL, NULL); if (r) - return r; + goto out; } r = dm_verity_target_set(&dmd.segment, 0, dmd.size, crypt_data_device(cd), - crypt_metadata_device(cd), fec_device, root_hash, - root_hash_size, signature_description, + crypt_metadata_device(cd), fec_device, root_hash->key, + root_hash->keylength, description, VERITY_hash_offset_block(verity_hdr), VERITY_FEC_blocks(cd, fec_device, verity_hdr), verity_hdr); if (r) - return r; + goto out; r = dm_create_device(cd, name, CRYPT_VERITY, &dmd); if (r < 0 && (dm_flags(cd, DM_VERITY, &dmv_flags) || !(dmv_flags & DM_VERITY_SUPPORTED))) { log_err(cd, _("Kernel does not support dm-verity mapping.")); r = -ENOTSUP; } - if (r < 0 && signature_description && !(dmv_flags & DM_VERITY_SIGNATURE_SUPPORTED)) { + if (r < 0 && signature && !(dmv_flags & DM_VERITY_SIGNATURE_SUPPORTED)) { log_err(cd, _("Kernel does not support dm-verity signature option.")); r = -ENOTSUP; } @@ -351,6 +393,8 @@ int VERITY_activate(struct crypt_device *cd, r = 0; out: + crypt_drop_keyring_key_by_description(cd, description, USER_KEY); + free(description); dm_targets_free(cd, &dmd); return r; } diff --git a/lib/verity/verity.h b/lib/verity/verity.h index afc411e..00e9867 100644 --- a/lib/verity/verity.h +++ b/lib/verity/verity.h @@ -1,7 +1,7 @@ /* * dm-verity volume handling * - * Copyright (C) 2012-2023 Red Hat, Inc. All rights reserved. + * Copyright (C) 2012-2024 Red Hat, Inc. All rights reserved. * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -23,6 +23,7 @@ #include #include +#include #define VERITY_MAX_HASH_TYPE 1 #define VERITY_BLOCK_SIZE_OK(x) ((x) % 512 || (x) < 512 || \ @@ -31,6 +32,7 @@ struct crypt_device; struct crypt_params_verity; struct device; +struct volume_key; int VERITY_read_sb(struct crypt_device *cd, uint64_t sb_offset, @@ -44,13 +46,18 @@ int VERITY_write_sb(struct crypt_device *cd, int VERITY_activate(struct crypt_device *cd, const char *name, - const char *root_hash, - size_t root_hash_size, - const char *signature_description, + struct volume_key *root_hash, + struct volume_key *signature, struct device *fec_device, struct crypt_params_verity *verity_hdr, uint32_t activation_flags); +int VERITY_verify_params(struct crypt_device *cd, + struct crypt_params_verity *hdr, + bool signed_root_hash, + struct device *fec_device, + struct volume_key *root_hash); + int VERITY_verify(struct crypt_device *cd, struct crypt_params_verity *verity_hdr, const char *root_hash, diff --git a/lib/verity/verity_fec.c b/lib/verity/verity_fec.c index 2dbf59e..15608fd 100644 --- a/lib/verity/verity_fec.c +++ b/lib/verity/verity_fec.c @@ -2,7 +2,7 @@ * dm-verity Forward Error Correction (FEC) support * * Copyright (C) 2015 Google, Inc. All rights reserved. - * Copyright (C) 2017-2023 Red Hat, Inc. All rights reserved. + * Copyright (C) 2017-2024 Red Hat, Inc. All rights reserved. * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/lib/verity/verity_hash.c b/lib/verity/verity_hash.c index f33b737..0e351aa 100644 --- a/lib/verity/verity_hash.c +++ b/lib/verity/verity_hash.c @@ -1,7 +1,7 @@ /* * dm-verity volume handling * - * Copyright (C) 2012-2023 Red Hat, Inc. All rights reserved. + * Copyright (C) 2012-2024 Red Hat, Inc. All rights reserved. * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/lib/volumekey.c b/lib/volumekey.c index 00791ac..3de7f76 100644 --- a/lib/volumekey.c +++ b/lib/volumekey.c @@ -2,7 +2,7 @@ * cryptsetup volume key implementation * * Copyright (C) 2004-2006 Clemens Fruhwirth - * Copyright (C) 2010-2023 Red Hat, Inc. All rights reserved. + * Copyright (C) 2010-2024 Red Hat, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -39,7 +39,7 @@ struct volume_key *crypt_alloc_volume_key(size_t keylength, const char *key) vk->key_description = NULL; vk->keylength = keylength; - vk->id = -1; + vk->id = KEY_NOT_VERIFIED; vk->next = NULL; /* keylength 0 is valid => no key */ -- cgit v1.2.3