From 37f1aff5f5f967d6a4440d176f3de877aab789ac Mon Sep 17 00:00:00 2001 From: Vojtech Trefny Date: Mon, 20 Sep 2021 16:38:16 +0200 Subject: [PATCH 1/3] Add support for creating and activating integrity devices This adds support for create, open and close actions for standalone integrity devices using cryptsetup. --- configure.ac | 4 +- src/lib/plugin_apis/crypto.api | 157 +++++++++++++++++ src/plugins/crypto.c | 261 +++++++++++++++++++++++++++- src/plugins/crypto.h | 41 +++++ src/python/gi/overrides/BlockDev.py | 24 +++ tests/crypto_test.py | 97 ++++++++++- 6 files changed, 576 insertions(+), 8 deletions(-) diff --git a/configure.ac b/configure.ac index abe1412..13830ae 100644 --- a/configure.ac +++ b/configure.ac @@ -210,7 +210,9 @@ AS_IF([test "x$with_crypto" != "xno"], AS_IF([$PKG_CONFIG --atleast-version=2.0.3 libcryptsetup], [AC_DEFINE([LIBCRYPTSETUP_2])], []) AS_IF([$PKG_CONFIG --atleast-version=2.3.0 libcryptsetup], - [AC_DEFINE([LIBCRYPTSETUP_BITLK])], []) + [AC_DEFINE([LIBCRYPTSETUP_23])], []) + AS_IF([$PKG_CONFIG --atleast-version=2.4.0 libcryptsetup], + [AC_DEFINE([LIBCRYPTSETUP_24])], []) AS_IF([test "x$with_escrow" != "xno"], [LIBBLOCKDEV_PKG_CHECK_MODULES([NSS], [nss >= 3.18.0]) LIBBLOCKDEV_CHECK_HEADER([volume_key/libvolume_key.h], [$GLIB_CFLAGS $NSS_CFLAGS], [libvolume_key.h not available])], diff --git a/src/lib/plugin_apis/crypto.api b/src/lib/plugin_apis/crypto.api index ef0217f..40e32c8 100644 --- a/src/lib/plugin_apis/crypto.api +++ b/src/lib/plugin_apis/crypto.api @@ -1,5 +1,6 @@ #include #include +#include #define BD_CRYPTO_LUKS_METADATA_SIZE G_GUINT64_CONSTANT (2097152ULL) // 2 MiB @@ -245,6 +246,115 @@ GType bd_crypto_luks_extra_get_type () { return type; } +#define BD_CRYPTO_TYPE_INTEGRITY_EXTRA (bd_crypto_integrity_extra_get_type ()) +GType bd_crypto_integrity_extra_get_type(); + +/** + * BDCryptoIntegrityExtra: + * @sector_size: integrity sector size + * @journal_size: size of journal in bytes + * @journal_watermark: journal flush watermark in percents; in bitmap mode sectors-per-bit + * @journal_commit_time: journal commit time (or bitmap flush time) in ms + * @interleave_sectors: number of interleave sectors (power of two) + * @tag_size: tag size per-sector in bytes + * @buffer_sectors: number of sectors in one buffer + */ +typedef struct BDCryptoIntegrityExtra { + guint32 sector_size; + guint64 journal_size; + guint journal_watermark; + guint journal_commit_time; + guint32 interleave_sectors; + guint32 tag_size; + guint32 buffer_sectors; +} BDCryptoIntegrityExtra; + +/** + * bd_crypto_integrity_extra_copy: (skip) + * @extra: (allow-none): %BDCryptoIntegrityExtra to copy + * + * Creates a new copy of @extra. + */ +BDCryptoIntegrityExtra* bd_crypto_integrity_extra_copy (BDCryptoIntegrityExtra *extra) { + if (extra == NULL) + return NULL; + + BDCryptoIntegrityExtra *new_extra = g_new0 (BDCryptoIntegrityExtra, 1); + + new_extra->sector_size = extra->sector_size; + new_extra->journal_size = extra->journal_size; + new_extra->journal_watermark = extra->journal_watermark; + new_extra->journal_commit_time = extra->journal_commit_time; + new_extra->interleave_sectors = extra->interleave_sectors; + new_extra->tag_size = extra->tag_size; + new_extra->buffer_sectors = extra->buffer_sectors; + + return new_extra; +} + +/** + * bd_crypto_integrity_extra_free: (skip) + * @extra: (allow-none): %BDCryptoIntegrityExtra to free + * + * Frees @extra. + */ +void bd_crypto_integrity_extra_free (BDCryptoIntegrityExtra *extra) { + if (extra == NULL) + return; + + g_free (extra); +} + +/** + * bd_crypto_integrity_extra_new: (constructor) + * @sector_size: integrity sector size, 0 for default (512) + * @journal_size: size of journal in bytes + * @journal_watermark: journal flush watermark in percents; in bitmap mode sectors-per-bit + * @journal_commit_time: journal commit time (or bitmap flush time) in ms + * @interleave_sectors: number of interleave sectors (power of two) + * @tag_size: tag size per-sector in bytes + * @buffer_sectors: number of sectors in one buffer + * + * Returns: (transfer full): a new Integrity extra argument + */ +BDCryptoIntegrityExtra* bd_crypto_integrity_extra_new (guint64 sector_size, guint64 journal_size, guint journal_watermark, guint journal_commit_time, guint64 interleave_sectors, guint64 tag_size, guint64 buffer_sectors) { + BDCryptoIntegrityExtra *ret = g_new0 (BDCryptoIntegrityExtra, 1); + ret->sector_size = sector_size; + ret->journal_size = journal_size; + ret->journal_watermark = journal_watermark; + ret->journal_commit_time = journal_commit_time; + ret->interleave_sectors = interleave_sectors; + ret->tag_size = tag_size; + ret->buffer_sectors = buffer_sectors; + + return ret; +} + +GType bd_crypto_integrity_extra_get_type () { + static GType type = 0; + + if (G_UNLIKELY(type == 0)) { + type = g_boxed_type_register_static("BDCryptoIntegrityExtra", + (GBoxedCopyFunc) bd_crypto_integrity_extra_copy, + (GBoxedFreeFunc) bd_crypto_integrity_extra_free); + } + + return type; +} + +typedef enum { + BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL = CRYPT_ACTIVATE_NO_JOURNAL, + BD_CRYPTO_INTEGRITY_OPEN_RECOVERY = CRYPT_ACTIVATE_RECOVERY, +#ifdef CRYPT_ACTIVATE_NO_JOURNAL_BITMAP + BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL_BITMAP = CRYPT_ACTIVATE_NO_JOURNAL_BITMAP, +#endif + BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE = CRYPT_ACTIVATE_RECALCULATE, +#ifdef CRYPT_ACTIVATE_RECALCULATE_RESET + BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE_RESET = CRYPT_ACTIVATE_RECALCULATE_RESET, +#endif + BD_CRYPTO_INTEGRITY_OPEN_ALLOW_DISCARDS = CRYPT_ACTIVATE_ALLOW_DISCARDS, +} BDCryptoIntegrityOpenFlags; + #define BD_CRYPTO_TYPE_LUKS_INFO (bd_crypto_luks_info_get_type ()) GType bd_crypto_luks_info_get_type(); @@ -857,6 +967,53 @@ BDCryptoLUKSInfo* bd_crypto_luks_info (const gchar *luks_device, GError **error) */ BDCryptoIntegrityInfo* bd_crypto_integrity_info (const gchar *device, GError **error); +/** + * bd_crypto_integrity_format: + * @device: a device to format as integrity + * @algorithm: integrity algorithm specification (e.g. "crc32c" or "sha256") or %NULL to use the default + * @wipe: whether to wipe the device after format; a device that is not initially wiped will contain invalid checksums + * @key_data: (allow-none) (array length=key_size): integrity key or %NULL if not needed + * @key_size: size the integrity key and @key_data + * @extra: (allow-none): extra arguments for integrity format creation + * @error: (out): place to store error (if any) + * + * Formats the given @device as integrity according to the other parameters given. + * + * Returns: whether the given @device was successfully formatted as integrity or not + * (the @error) contains the error in such cases) + * + * Tech category: %BD_CRYPTO_TECH_INTEGRITY-%BD_CRYPTO_TECH_MODE_CREATE + */ +gboolean bd_crypto_integrity_format (const gchar *device, const gchar *algorithm, gboolean wipe, const guint8* key_data, gsize key_size, BDCryptoIntegrityExtra *extra, GError **error); + +/** + * bd_crypto_integrity_open: + * @device: integrity device to open + * @name: name for the opened @device + * @algorithm: (allow-none): integrity algorithm specification (e.g. "crc32c" or "sha256") or %NULL to use the default + * @key_data: (allow-none) (array length=key_size): integrity key or %NULL if not needed + * @key_size: size the integrity key and @key_data + * @flags: flags for the integrity device activation + * @extra: (allow-none): extra arguments for integrity open + * @error: (out): place to store error (if any) + * + * Returns: whether the @device was successfully opened or not + * + * Tech category: %BD_CRYPTO_TECH_INTEGRITY-%BD_CRYPTO_TECH_MODE_OPEN_CLOSE + */ +gboolean bd_crypto_integrity_open (const gchar *device, const gchar *name, const gchar *algorithm, const guint8* key_data, gsize key_size, BDCryptoIntegrityOpenFlags flags, BDCryptoIntegrityExtra *extra, GError **error); + +/** + * bd_crypto_integrity_close: + * @integrity_device: integrity device to close + * @error: (out): place to store error (if any) + * + * Returns: whether the given @integrity_device was successfully closed or not + * + * Tech category: %BD_CRYPTO_TECH_INTEGRITY-%BD_CRYPTO_TECH_MODE_OPEN_CLOSE + */ +gboolean bd_crypto_integrity_close (const gchar *integrity_device, GError **error); + /** * bd_crypto_device_seems_encrypted: * @device: the queried device diff --git a/src/plugins/crypto.c b/src/plugins/crypto.c index 4fad9a8..b1b0700 100644 --- a/src/plugins/crypto.c +++ b/src/plugins/crypto.c @@ -50,6 +50,18 @@ #define SECTOR_SIZE 512 +#define DEFAULT_LUKS_KEYSIZE_BITS 256 +#define DEFAULT_LUKS_CIPHER "aes-xts-plain64" + +#ifdef LIBCRYPTSETUP_23 +/* 0 for autodetect since 2.3.0 */ +#define DEFAULT_INTEGRITY_TAG_SIZE 0 +#else +/* we need some sane default for older versions, users should specify tag size when using + other algorithms than the default crc32c */ +#define DEFAULT_INTEGRITY_TAG_SIZE 4 +#endif + #define UNUSED __attribute__((unused)) /** @@ -146,6 +158,43 @@ BDCryptoLUKSExtra* bd_crypto_luks_extra_new (guint64 data_alignment, const gchar return ret; } +BDCryptoIntegrityExtra* bd_crypto_integrity_extra_new (guint64 sector_size, guint64 journal_size, guint journal_watermark, guint journal_commit_time, guint64 interleave_sectors, guint64 tag_size, guint64 buffer_sectors) { + BDCryptoIntegrityExtra *ret = g_new0 (BDCryptoIntegrityExtra, 1); + ret->sector_size = sector_size; + ret->journal_size = journal_size; + ret->journal_watermark = journal_watermark; + ret->journal_commit_time = journal_commit_time; + ret->interleave_sectors = interleave_sectors; + ret->tag_size = tag_size; + ret->buffer_sectors = buffer_sectors; + + return ret; +} + +BDCryptoIntegrityExtra* bd_crypto_integrity_extra_copy (BDCryptoIntegrityExtra *extra) { + if (extra == NULL) + return NULL; + + BDCryptoIntegrityExtra *new_extra = g_new0 (BDCryptoIntegrityExtra, 1); + + new_extra->sector_size = extra->sector_size; + new_extra->journal_size = extra->journal_size; + new_extra->journal_watermark = extra->journal_watermark; + new_extra->journal_commit_time = extra->journal_commit_time; + new_extra->interleave_sectors = extra->interleave_sectors; + new_extra->tag_size = extra->tag_size; + new_extra->buffer_sectors = extra->buffer_sectors; + + return new_extra; +} + +void bd_crypto_integrity_extra_free (BDCryptoIntegrityExtra *extra) { + if (extra == NULL) + return; + + g_free (extra); +} + void bd_crypto_luks_info_free (BDCryptoLUKSInfo *info) { if (info == NULL) return; @@ -346,15 +395,15 @@ gboolean bd_crypto_is_tech_avail (BDCryptoTech tech, guint64 mode, GError **erro "Integrity technology requires libcryptsetup >= 2.0"); return FALSE; #endif - ret = mode & (BD_CRYPTO_TECH_MODE_QUERY); + ret = mode & (BD_CRYPTO_TECH_MODE_CREATE|BD_CRYPTO_TECH_MODE_OPEN_CLOSE|BD_CRYPTO_TECH_MODE_QUERY); if (ret != mode) { g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_TECH_UNAVAIL, - "Only 'query' supported for Integrity"); + "Only 'create', 'open' and 'query' supported for Integrity"); return FALSE; } else return TRUE; case BD_CRYPTO_TECH_BITLK: -#ifndef LIBCRYPTSETUP_BITLK +#ifndef LIBCRYPTSETUP_23 g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_TECH_UNAVAIL, "BITLK technology requires libcryptsetup >= 2.3.0"); return FALSE; @@ -2035,6 +2084,208 @@ BDCryptoIntegrityInfo* bd_crypto_integrity_info (const gchar *device, GError **e } #endif +static int _wipe_progress (guint64 size, guint64 offset, void *usrptr) { + /* "convert" the progress from 0-100 to 50-100 because wipe starts at 50 in bd_crypto_integrity_format */ + gdouble progress = 50 + (((gdouble) offset / size) * 100) / 2; + bd_utils_report_progress (*(guint64 *) usrptr, progress, "Integrity device wipe in progress"); + + return 0; +} + +/** + * bd_crypto_integrity_format: + * @device: a device to format as integrity + * @algorithm: integrity algorithm specification (e.g. "crc32c" or "sha256") + * @wipe: whether to wipe the device after format; a device that is not initially wiped will contain invalid checksums + * @key_data: (allow-none) (array length=key_size): integrity key or %NULL if not needed + * @key_size: size the integrity key and @key_data + * @extra: (allow-none): extra arguments for integrity format creation + * @error: (out): place to store error (if any) + * + * Formats the given @device as integrity according to the other parameters given. + * + * Returns: whether the given @device was successfully formatted as integrity or not + * (the @error) contains the error in such cases) + * + * Tech category: %BD_CRYPTO_TECH_INTEGRITY-%BD_CRYPTO_TECH_MODE_CREATE + */ +gboolean bd_crypto_integrity_format (const gchar *device, const gchar *algorithm, gboolean wipe, const guint8* key_data, gsize key_size, BDCryptoIntegrityExtra *extra, GError **error) { + struct crypt_device *cd = NULL; + gint ret; + guint64 progress_id = 0; + gchar *msg = NULL; + struct crypt_params_integrity params = ZERO_INIT; + g_autofree gchar *tmp_name = NULL; + g_autofree gchar *tmp_path = NULL; + g_autofree gchar *dev_name = NULL; + + msg = g_strdup_printf ("Started formatting '%s' as integrity device", device); + progress_id = bd_utils_report_started (msg); + g_free (msg); + + ret = crypt_init (&cd, device); + if (ret != 0) { + g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_DEVICE, + "Failed to initialize device: %s", strerror_l (-ret, c_locale)); + bd_utils_report_finished (progress_id, (*error)->message); + return FALSE; + } + + if (extra) { + params.sector_size = extra->sector_size; + params.journal_size = extra->journal_size; + params.journal_watermark = extra->journal_watermark; + params.journal_commit_time = extra->journal_commit_time; + params.interleave_sectors = extra->interleave_sectors; + params.tag_size = extra->tag_size; + params.buffer_sectors = extra->buffer_sectors; + } + + params.integrity_key_size = key_size; + params.integrity = algorithm; + params.tag_size = params.tag_size ? params.tag_size : DEFAULT_INTEGRITY_TAG_SIZE; + + ret = crypt_format (cd, CRYPT_INTEGRITY, NULL, NULL, NULL, NULL, 0, ¶ms); + if (ret != 0) { + g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_FORMAT_FAILED, + "Failed to format device: %s", strerror_l (-ret, c_locale)); + crypt_free (cd); + bd_utils_report_finished (progress_id, (*error)->message); + return FALSE; + } + + if (wipe) { + bd_utils_report_progress (progress_id, 50, "Format created"); + + dev_name = g_path_get_basename (device); + tmp_name = g_strdup_printf ("bd-temp-integrity-%s-%d", dev_name, g_random_int ()); + tmp_path = g_strdup_printf ("%s/%s", crypt_get_dir (), tmp_name); + + ret = crypt_activate_by_volume_key (cd, tmp_name, (const char *) key_data, key_size, + CRYPT_ACTIVATE_PRIVATE | CRYPT_ACTIVATE_NO_JOURNAL); + if (ret != 0) { + g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_DEVICE, + "Failed to activate the newly created integrity device for wiping: %s", + strerror_l (-ret, c_locale)); + crypt_free (cd); + bd_utils_report_finished (progress_id, (*error)->message); + return FALSE; + } + + bd_utils_report_progress (progress_id, 50, "Starting to wipe the newly created integrity device"); + ret = crypt_wipe (cd, tmp_path, CRYPT_WIPE_ZERO, 0, 0, 1048576, + 0, &_wipe_progress, &progress_id); + bd_utils_report_progress (progress_id, 100, "Wipe finished"); + if (ret != 0) { + g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_DEVICE, + "Failed to wipe the newly created integrity device: %s", + strerror_l (-ret, c_locale)); + + ret = crypt_deactivate (cd, tmp_name); + if (ret != 0) + g_warning ("Failed to deactivate temporary device %s", tmp_name); + + crypt_free (cd); + bd_utils_report_finished (progress_id, (*error)->message); + return FALSE; + } + + ret = crypt_deactivate (cd, tmp_name); + if (ret != 0) + g_warning ("Failed to deactivate temporary device %s", tmp_name); + + } else + bd_utils_report_finished (progress_id, "Completed"); + + crypt_free (cd); + + return TRUE; +} + +/** + * bd_crypto_integrity_open: + * @device: integrity device to open + * @name: name for the opened @device + * @algorithm: (allow-none): integrity algorithm specification (e.g. "crc32c" or "sha256") or %NULL to use the default + * @key_data: (allow-none) (array length=key_size): integrity key or %NULL if not needed + * @key_size: size the integrity key and @key_data + * @flags: flags for the integrity device activation + * @extra: (allow-none): extra arguments for integrity open + * @error: (out): place to store error (if any) + * + * Returns: whether the @device was successfully opened or not + * + * Tech category: %BD_CRYPTO_TECH_INTEGRITY-%BD_CRYPTO_TECH_MODE_OPEN_CLOSE + */ +gboolean bd_crypto_integrity_open (const gchar *device, const gchar *name, const gchar *algorithm, const guint8* key_data, gsize key_size, BDCryptoIntegrityOpenFlags flags, BDCryptoIntegrityExtra *extra, GError **error) { + struct crypt_device *cd = NULL; + gint ret = 0; + guint64 progress_id = 0; + gchar *msg = NULL; + struct crypt_params_integrity params = ZERO_INIT; + + params.integrity = algorithm; + params.integrity_key_size = key_size; + + if (extra) { + params.sector_size = extra->sector_size; + params.journal_size = extra->journal_size; + params.journal_watermark = extra->journal_watermark; + params.journal_commit_time = extra->journal_commit_time; + params.interleave_sectors = extra->interleave_sectors; + params.tag_size = extra->tag_size; + params.buffer_sectors = extra->buffer_sectors; + } + + msg = g_strdup_printf ("Started opening '%s' integrity device", device); + progress_id = bd_utils_report_started (msg); + g_free (msg); + + ret = crypt_init (&cd, device); + if (ret != 0) { + g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_DEVICE, + "Failed to initialize device: %s", strerror_l (-ret, c_locale)); + bd_utils_report_finished (progress_id, (*error)->message); + return FALSE; + } + + ret = crypt_load (cd, CRYPT_INTEGRITY, ¶ms); + if (ret != 0) { + g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_DEVICE, + "Failed to load device's parameters: %s", strerror_l (-ret, c_locale)); + crypt_free (cd); + bd_utils_report_finished (progress_id, (*error)->message); + return FALSE; + } + + ret = crypt_activate_by_volume_key (cd, name, (const char *) key_data, key_size, flags); + if (ret < 0) { + g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_DEVICE, + "Failed to activate device: %s", strerror_l (-ret, c_locale)); + + crypt_free (cd); + bd_utils_report_finished (progress_id, (*error)->message); + return FALSE; + } + + crypt_free (cd); + bd_utils_report_finished (progress_id, "Completed"); + return TRUE; +} + +/** + * bd_crypto_integrity_close: + * @integrity_device: integrity device to close + * @error: (out): place to store error (if any) + * + * Returns: whether the given @integrity_device was successfully closed or not + * + * Tech category: %BD_CRYPTO_TECH_INTEGRITY-%BD_CRYPTO_TECH_MODE_OPEN_CLOSE + */ +gboolean bd_crypto_integrity_close (const gchar *integrity_device, GError **error) { + return _crypto_close (integrity_device, "integrity", error); +} + /** * bd_crypto_device_seems_encrypted: * @device: the queried device @@ -2472,7 +2723,7 @@ gboolean bd_crypto_escrow_device (const gchar *device, const gchar *passphrase, * * Tech category: %BD_CRYPTO_TECH_BITLK-%BD_CRYPTO_TECH_MODE_OPEN_CLOSE */ -#ifndef LIBCRYPTSETUP_BITLK +#ifndef LIBCRYPTSETUP_23 gboolean bd_crypto_bitlk_open (const gchar *device UNUSED, const gchar *name UNUSED, const guint8* pass_data UNUSED, gsize data_len UNUSED, gboolean read_only UNUSED, GError **error) { /* this will return FALSE and set error, because BITLK technology is not available */ return bd_crypto_is_tech_avail (BD_CRYPTO_TECH_BITLK, BD_CRYPTO_TECH_MODE_OPEN_CLOSE, error); @@ -2542,7 +2793,7 @@ gboolean bd_crypto_bitlk_open (const gchar *device, const gchar *name, const gui * * Tech category: %BD_CRYPTO_TECH_BITLK-%BD_CRYPTO_TECH_MODE_OPEN_CLOSE */ -#ifndef LIBCRYPTSETUP_BITLK +#ifndef LIBCRYPTSETUP_23 gboolean bd_crypto_bitlk_close (const gchar *bitlk_device UNUSED, GError **error) { /* this will return FALSE and set error, because BITLK technology is not available */ return bd_crypto_is_tech_avail (BD_CRYPTO_TECH_BITLK, BD_CRYPTO_TECH_MODE_OPEN_CLOSE, error); diff --git a/src/plugins/crypto.h b/src/plugins/crypto.h index a38724d..166e558 100644 --- a/src/plugins/crypto.h +++ b/src/plugins/crypto.h @@ -116,6 +116,43 @@ void bd_crypto_luks_extra_free (BDCryptoLUKSExtra *extra); BDCryptoLUKSExtra* bd_crypto_luks_extra_copy (BDCryptoLUKSExtra *extra); BDCryptoLUKSExtra* bd_crypto_luks_extra_new (guint64 data_alignment, const gchar *data_device, const gchar *integrity, guint64 sector_size, const gchar *label, const gchar *subsystem, BDCryptoLUKSPBKDF *pbkdf); +/** + * BDCryptoIntegrityExtra: + * @sector_size: integrity sector size + * @journal_size: size of journal in bytes + * @journal_watermark: journal flush watermark in percents; in bitmap mode sectors-per-bit + * @journal_commit_time: journal commit time (or bitmap flush time) in ms + * @interleave_sectors: number of interleave sectors (power of two) + * @tag_size: tag size per-sector in bytes + * @buffer_sectors: number of sectors in one buffer + */ +typedef struct BDCryptoIntegrityExtra { + guint32 sector_size; + guint64 journal_size; + guint journal_watermark; + guint journal_commit_time; + guint32 interleave_sectors; + guint32 tag_size; + guint32 buffer_sectors; +} BDCryptoIntegrityExtra; + +void bd_crypto_integrity_extra_free (BDCryptoIntegrityExtra *extra); +BDCryptoIntegrityExtra* bd_crypto_integrity_extra_copy (BDCryptoIntegrityExtra *extra); +BDCryptoIntegrityExtra* bd_crypto_integrity_extra_new (guint64 sector_size, guint64 journal_size, guint journal_watermark, guint journal_commit_time, guint64 interleave_sectors, guint64 tag_size, guint64 buffer_sectors); + +typedef enum { + BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL = CRYPT_ACTIVATE_NO_JOURNAL, + BD_CRYPTO_INTEGRITY_OPEN_RECOVERY = CRYPT_ACTIVATE_RECOVERY, +#ifdef CRYPT_ACTIVATE_NO_JOURNAL_BITMAP + BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL_BITMAP = CRYPT_ACTIVATE_NO_JOURNAL_BITMAP, +#endif + BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE = CRYPT_ACTIVATE_RECALCULATE, +#ifdef CRYPT_ACTIVATE_RECALCULATE_RESET + BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE_RESET = CRYPT_ACTIVATE_RECALCULATE_RESET, +#endif + BD_CRYPTO_INTEGRITY_OPEN_ALLOW_DISCARDS = CRYPT_ACTIVATE_ALLOW_DISCARDS, +} BDCryptoIntegrityOpenFlags; + /** * BDCryptoLUKSInfo: * @version: LUKS version @@ -209,6 +246,10 @@ gboolean bd_crypto_luks_header_restore (const gchar *device, const gchar *backup BDCryptoLUKSInfo* bd_crypto_luks_info (const gchar *luks_device, GError **error); BDCryptoIntegrityInfo* bd_crypto_integrity_info (const gchar *device, GError **error); +gboolean bd_crypto_integrity_format (const gchar *device, const gchar *algorithm, gboolean wipe, const guint8* key_data, gsize key_size, BDCryptoIntegrityExtra *extra, GError **error); +gboolean bd_crypto_integrity_open (const gchar *device, const gchar *name, const gchar *algorithm, const guint8* key_data, gsize key_size, BDCryptoIntegrityOpenFlags flags, BDCryptoIntegrityExtra *extra, GError **error); +gboolean bd_crypto_integrity_close (const gchar *integrity_device, GError **error); + gboolean bd_crypto_device_seems_encrypted (const gchar *device, GError **error); gboolean bd_crypto_tc_open (const gchar *device, const gchar *name, const guint8* pass_data, gsize data_len, gboolean read_only, GError **error); gboolean bd_crypto_tc_open_full (const gchar *device, const gchar *name, const guint8* pass_data, gsize data_len, const gchar **keyfiles, gboolean hidden, gboolean system, gboolean veracrypt, guint32 veracrypt_pim, gboolean read_only, GError **error); diff --git a/src/python/gi/overrides/BlockDev.py b/src/python/gi/overrides/BlockDev.py index 715a262..71bcd31 100644 --- a/src/python/gi/overrides/BlockDev.py +++ b/src/python/gi/overrides/BlockDev.py @@ -276,6 +276,30 @@ def crypto_bitlk_open(device, name, passphrase, read_only=False): __all__.append("crypto_bitlk_open") +class CryptoIntegrityExtra(BlockDev.CryptoIntegrityExtra): + def __new__(cls, sector_size=0, journal_size=0, journal_watermark=0, journal_commit_time=0, interleave_sectors=0, tag_size=0, buffer_sectors=0): + ret = BlockDev.CryptoIntegrityExtra.new(sector_size, journal_size, journal_watermark, journal_commit_time, interleave_sectors, tag_size, buffer_sectors) + ret.__class__ = cls + return ret + def __init__(self, *args, **kwargs): # pylint: disable=unused-argument + super(CryptoIntegrityExtra, self).__init__() #pylint: disable=bad-super-call +CryptoIntegrityExtra = override(CryptoIntegrityExtra) +__all__.append("CryptoIntegrityExtra") + + +_crypto_integrity_format = BlockDev.crypto_integrity_format +@override(BlockDev.crypto_integrity_format) +def crypto_integrity_format(device, algorithm=None, wipe=True, key_data=None, extra=None): + return _crypto_integrity_format(device, algorithm, wipe, key_data, extra) +__all__.append("crypto_integrity_format") + +_crypto_integrity_open = BlockDev.crypto_integrity_open +@override(BlockDev.crypto_integrity_open) +def crypto_integrity_open(device, name, algorithm, key_data=None, flags=0, extra=None): + return _crypto_integrity_open(device, name, algorithm, key_data, flags, extra) +__all__.append("crypto_integrity_open") + + _dm_create_linear = BlockDev.dm_create_linear @override(BlockDev.dm_create_linear) def dm_create_linear(map_name, device, length, uuid=None): diff --git a/tests/crypto_test.py b/tests/crypto_test.py index 0aecc03..1c6832e 100644 --- a/tests/crypto_test.py +++ b/tests/crypto_test.py @@ -2,6 +2,7 @@ import unittest import os import tempfile import overrides_hack +import secrets import shutil import subprocess import six @@ -42,6 +43,8 @@ class CryptoTestCase(unittest.TestCase): requested_plugins = BlockDev.plugin_specs_from_names(("crypto", "loop")) + _dm_name = "libblockdevTestLUKS" + @classmethod def setUpClass(cls): unittest.TestCase.setUpClass() @@ -72,7 +75,7 @@ class CryptoTestCase(unittest.TestCase): def _clean_up(self): try: - BlockDev.crypto_luks_close("libblockdevTestLUKS") + BlockDev.crypto_luks_close(self._dm_name) except: pass @@ -964,7 +967,8 @@ class CryptoTestInfo(CryptoTestCase): succ = BlockDev.crypto_luks_close("libblockdevTestLUKS") self.assertTrue(succ) -class CryptoTestIntegrity(CryptoTestCase): + +class CryptoTestLUKS2Integrity(CryptoTestCase): @tag_test(TestTags.SLOW) @unittest.skipUnless(HAVE_LUKS2, "LUKS 2 not supported") def test_luks2_integrity(self): @@ -1151,3 +1155,92 @@ class CryptoTestBitlk(CryptoTestCase): succ = BlockDev.crypto_bitlk_close("libblockdevTestBitlk") self.assertTrue(succ) self.assertFalse(os.path.exists("/dev/mapper/libblockdevTestBitlk")) + + +class CryptoTestIntegrity(CryptoTestCase): + + _dm_name = "libblockdevTestIntegrity" + + @unittest.skipUnless(HAVE_LUKS2, "Integrity not supported") + def test_integrity(self): + # basic format+open+close test + succ = BlockDev.crypto_integrity_format(self.loop_dev, "sha256", False) + self.assertTrue(succ) + + succ = BlockDev.crypto_integrity_open(self.loop_dev, self._dm_name, "sha256") + self.assertTrue(succ) + self.assertTrue(os.path.exists("/dev/mapper/%s" % self._dm_name)) + + info = BlockDev.crypto_integrity_info(self._dm_name) + self.assertEqual(info.algorithm, "sha256") + + succ = BlockDev.crypto_integrity_close(self._dm_name) + self.assertTrue(succ) + self.assertFalse(os.path.exists("/dev/mapper/%s" % self._dm_name)) + + # same now with a keyed algorithm + key = list(secrets.token_bytes(64)) + + succ = BlockDev.crypto_integrity_format(self.loop_dev, "hmac(sha256)", False, key) + self.assertTrue(succ) + + succ = BlockDev.crypto_integrity_open(self.loop_dev, self._dm_name, "hmac(sha256)", key) + self.assertTrue(succ) + self.assertTrue(os.path.exists("/dev/mapper/%s" % self._dm_name)) + + info = BlockDev.crypto_integrity_info(self._dm_name) + self.assertEqual(info.algorithm, "hmac(sha256)") + + succ = BlockDev.crypto_integrity_close(self._dm_name) + self.assertTrue(succ) + self.assertFalse(os.path.exists("/dev/mapper/%s" % self._dm_name)) + + # same with some custom parameters + extra = BlockDev.CryptoIntegrityExtra(sector_size=4096, interleave_sectors=65536) + succ = BlockDev.crypto_integrity_format(self.loop_dev, "crc32c", wipe=False, extra=extra) + self.assertTrue(succ) + + succ = BlockDev.crypto_integrity_open(self.loop_dev, self._dm_name, "crc32c") + self.assertTrue(succ) + self.assertTrue(os.path.exists("/dev/mapper/%s" % self._dm_name)) + + info = BlockDev.crypto_integrity_info(self._dm_name) + self.assertEqual(info.algorithm, "crc32c") + self.assertEqual(info.sector_size, 4096) + self.assertEqual(info.interleave_sectors, 65536) + + succ = BlockDev.crypto_integrity_close(self._dm_name) + self.assertTrue(succ) + self.assertFalse(os.path.exists("/dev/mapper/%s" % self._dm_name)) + + @tag_test(TestTags.SLOW) + @unittest.skipUnless(HAVE_LUKS2, "Integrity not supported") + def test_integrity_wipe(self): + # also check that wipe progress reporting works + progress_log = [] + + def _my_progress_func(_task, _status, completion, msg): + progress_log.append((completion, msg)) + + succ = BlockDev.utils_init_prog_reporting(_my_progress_func) + self.assertTrue(succ) + self.addCleanup(BlockDev.utils_init_prog_reporting, None) + + succ = BlockDev.crypto_integrity_format(self.loop_dev, "sha256", True) + self.assertTrue(succ) + + # at least one message "Integrity device wipe in progress" should be logged + self.assertTrue(any(prog[1] == "Integrity device wipe in progress" for prog in progress_log)) + + succ = BlockDev.crypto_integrity_open(self.loop_dev, self._dm_name, "sha256") + self.assertTrue(succ) + self.assertTrue(os.path.exists("/dev/mapper/%s" % self._dm_name)) + + # check the devices was wiped and the checksums recalculated + # (mkfs reads some blocks first so without checksums it would fail) + ret, _out, err = run_command("mkfs.ext2 /dev/mapper/%s " % self._dm_name) + self.assertEqual(ret, 0, msg="Failed to create ext2 filesystem on integrity: %s" % err) + + succ = BlockDev.crypto_integrity_close(self._dm_name) + self.assertTrue(succ) + self.assertFalse(os.path.exists("/dev/mapper/%s" % self._dm_name)) -- 2.31.1 From 4dcb7a42a2cb33f7a63021d72889c9a9688adfd3 Mon Sep 17 00:00:00 2001 From: Vojtech Trefny Date: Thu, 30 Sep 2021 16:01:40 +0200 Subject: [PATCH 2/3] Create smaller test images for integrity tests We are going to overwrite the entire device in test_integrity_wipe so we need to make sure the sparse actually fits to /tmp which can be smaller than 1 GiB. --- tests/crypto_test.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/crypto_test.py b/tests/crypto_test.py index 1c6832e..b7ec251 100644 --- a/tests/crypto_test.py +++ b/tests/crypto_test.py @@ -44,6 +44,7 @@ class CryptoTestCase(unittest.TestCase): requested_plugins = BlockDev.plugin_specs_from_names(("crypto", "loop")) _dm_name = "libblockdevTestLUKS" + _sparse_size = 1024**3 @classmethod def setUpClass(cls): @@ -57,8 +58,8 @@ class CryptoTestCase(unittest.TestCase): def setUp(self): self.addCleanup(self._clean_up) - self.dev_file = create_sparse_tempfile("crypto_test", 1024**3) - self.dev_file2 = create_sparse_tempfile("crypto_test2", 1024**3) + self.dev_file = create_sparse_tempfile("crypto_test", self._sparse_size) + self.dev_file2 = create_sparse_tempfile("crypto_test2", self._sparse_size) try: self.loop_dev = create_lio_device(self.dev_file) except RuntimeError as e: @@ -1160,6 +1161,7 @@ class CryptoTestBitlk(CryptoTestCase): class CryptoTestIntegrity(CryptoTestCase): _dm_name = "libblockdevTestIntegrity" + _sparse_size = 100 * 1024**2 @unittest.skipUnless(HAVE_LUKS2, "Integrity not supported") def test_integrity(self): -- 2.31.1 From 3b82f9085c0df2e58b673716cdefd747495738e2 Mon Sep 17 00:00:00 2001 From: Vojtech Trefny Date: Wed, 20 Oct 2021 10:27:41 +0200 Subject: [PATCH 3/3] crypto: Do not use libcryptsetup flags directly in crypto.h We can "translate" our flags in the implementation instead to avoid including libcryptsetup.h in our header and API files. --- src/lib/plugin_apis/crypto.api | 17 ++++++----------- src/plugins/crypto.c | 34 +++++++++++++++++++++++++++++++++- src/plugins/crypto.h | 16 ++++++---------- tests/crypto_test.py | 14 ++++++++++++++ 4 files changed, 59 insertions(+), 22 deletions(-) diff --git a/src/lib/plugin_apis/crypto.api b/src/lib/plugin_apis/crypto.api index 40e32c8..cf87979 100644 --- a/src/lib/plugin_apis/crypto.api +++ b/src/lib/plugin_apis/crypto.api @@ -1,6 +1,5 @@ #include #include -#include #define BD_CRYPTO_LUKS_METADATA_SIZE G_GUINT64_CONSTANT (2097152ULL) // 2 MiB @@ -343,16 +342,12 @@ GType bd_crypto_integrity_extra_get_type () { } typedef enum { - BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL = CRYPT_ACTIVATE_NO_JOURNAL, - BD_CRYPTO_INTEGRITY_OPEN_RECOVERY = CRYPT_ACTIVATE_RECOVERY, -#ifdef CRYPT_ACTIVATE_NO_JOURNAL_BITMAP - BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL_BITMAP = CRYPT_ACTIVATE_NO_JOURNAL_BITMAP, -#endif - BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE = CRYPT_ACTIVATE_RECALCULATE, -#ifdef CRYPT_ACTIVATE_RECALCULATE_RESET - BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE_RESET = CRYPT_ACTIVATE_RECALCULATE_RESET, -#endif - BD_CRYPTO_INTEGRITY_OPEN_ALLOW_DISCARDS = CRYPT_ACTIVATE_ALLOW_DISCARDS, + BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL = 1 << 0, + BD_CRYPTO_INTEGRITY_OPEN_RECOVERY = 1 << 1, + BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL_BITMAP = 1 << 2, + BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE = 1 << 3, + BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE_RESET = 1 << 4, + BD_CRYPTO_INTEGRITY_OPEN_ALLOW_DISCARDS = 1 << 5, } BDCryptoIntegrityOpenFlags; #define BD_CRYPTO_TYPE_LUKS_INFO (bd_crypto_luks_info_get_type ()) diff --git a/src/plugins/crypto.c b/src/plugins/crypto.c index b1b0700..8a4d64a 100644 --- a/src/plugins/crypto.c +++ b/src/plugins/crypto.c @@ -2223,6 +2223,7 @@ gboolean bd_crypto_integrity_open (const gchar *device, const gchar *name, const guint64 progress_id = 0; gchar *msg = NULL; struct crypt_params_integrity params = ZERO_INIT; + guint32 activate_flags = 0; params.integrity = algorithm; params.integrity_key_size = key_size; @@ -2237,6 +2238,37 @@ gboolean bd_crypto_integrity_open (const gchar *device, const gchar *name, const params.buffer_sectors = extra->buffer_sectors; } + + if (flags & BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL) + activate_flags |= CRYPT_ACTIVATE_NO_JOURNAL; + if (flags & BD_CRYPTO_INTEGRITY_OPEN_RECOVERY) + activate_flags |= CRYPT_ACTIVATE_RECOVERY; + if (flags & BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE) + activate_flags |= CRYPT_ACTIVATE_RECALCULATE; + if (flags & BD_CRYPTO_INTEGRITY_OPEN_ALLOW_DISCARDS) + activate_flags |= CRYPT_ACTIVATE_ALLOW_DISCARDS; + if (flags & BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL_BITMAP) { +#ifndef CRYPT_ACTIVATE_NO_JOURNAL_BITMAP + g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_TECH_UNAVAIL, + "Cannot activate %s with bitmap, installed version of cryptsetup doesn't support this option.", device); + bd_utils_report_finished (progress_id, (*error)->message); + return FALSE; +#else + activate_flags |= CRYPT_ACTIVATE_NO_JOURNAL_BITMAP; +#endif + } + + if (flags & BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE_RESET) { +#ifndef CRYPT_ACTIVATE_RECALCULATE_RESET + g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_TECH_UNAVAIL, + "Cannot reset integrity recalculation while activating %s, installed version of cryptsetup doesn't support this option.", device); + bd_utils_report_finished (progress_id, (*error)->message); + return FALSE; +#else + activate_flags |= CRYPT_ACTIVATE_RECALCULATE_RESET; +#endif + } + msg = g_strdup_printf ("Started opening '%s' integrity device", device); progress_id = bd_utils_report_started (msg); g_free (msg); @@ -2258,7 +2290,7 @@ gboolean bd_crypto_integrity_open (const gchar *device, const gchar *name, const return FALSE; } - ret = crypt_activate_by_volume_key (cd, name, (const char *) key_data, key_size, flags); + ret = crypt_activate_by_volume_key (cd, name, (const char *) key_data, key_size, activate_flags); if (ret < 0) { g_set_error (error, BD_CRYPTO_ERROR, BD_CRYPTO_ERROR_DEVICE, "Failed to activate device: %s", strerror_l (-ret, c_locale)); diff --git a/src/plugins/crypto.h b/src/plugins/crypto.h index 166e558..b5f133c 100644 --- a/src/plugins/crypto.h +++ b/src/plugins/crypto.h @@ -141,16 +141,12 @@ BDCryptoIntegrityExtra* bd_crypto_integrity_extra_copy (BDCryptoIntegrityExtra * BDCryptoIntegrityExtra* bd_crypto_integrity_extra_new (guint64 sector_size, guint64 journal_size, guint journal_watermark, guint journal_commit_time, guint64 interleave_sectors, guint64 tag_size, guint64 buffer_sectors); typedef enum { - BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL = CRYPT_ACTIVATE_NO_JOURNAL, - BD_CRYPTO_INTEGRITY_OPEN_RECOVERY = CRYPT_ACTIVATE_RECOVERY, -#ifdef CRYPT_ACTIVATE_NO_JOURNAL_BITMAP - BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL_BITMAP = CRYPT_ACTIVATE_NO_JOURNAL_BITMAP, -#endif - BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE = CRYPT_ACTIVATE_RECALCULATE, -#ifdef CRYPT_ACTIVATE_RECALCULATE_RESET - BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE_RESET = CRYPT_ACTIVATE_RECALCULATE_RESET, -#endif - BD_CRYPTO_INTEGRITY_OPEN_ALLOW_DISCARDS = CRYPT_ACTIVATE_ALLOW_DISCARDS, + BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL = 1 << 0, + BD_CRYPTO_INTEGRITY_OPEN_RECOVERY = 1 << 1, + BD_CRYPTO_INTEGRITY_OPEN_NO_JOURNAL_BITMAP = 1 << 2, + BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE = 1 << 3, + BD_CRYPTO_INTEGRITY_OPEN_RECALCULATE_RESET = 1 << 4, + BD_CRYPTO_INTEGRITY_OPEN_ALLOW_DISCARDS = 1 << 5, } BDCryptoIntegrityOpenFlags; /** diff --git a/tests/crypto_test.py b/tests/crypto_test.py index b7ec251..673d8b8 100644 --- a/tests/crypto_test.py +++ b/tests/crypto_test.py @@ -1215,6 +1215,20 @@ class CryptoTestIntegrity(CryptoTestCase): self.assertTrue(succ) self.assertFalse(os.path.exists("/dev/mapper/%s" % self._dm_name)) + # open with flags + succ = BlockDev.crypto_integrity_open(self.loop_dev, self._dm_name, "crc32c", + flags=BlockDev.CryptoIntegrityOpenFlags.ALLOW_DISCARDS) + self.assertTrue(succ) + self.assertTrue(os.path.exists("/dev/mapper/%s" % self._dm_name)) + + # check that discard is enabled for the mapped device + _ret, out, _err = run_command("dmsetup table %s" % self._dm_name) + self.assertIn("allow_discards", out) + + succ = BlockDev.crypto_integrity_close(self._dm_name) + self.assertTrue(succ) + self.assertFalse(os.path.exists("/dev/mapper/%s" % self._dm_name)) + @tag_test(TestTags.SLOW) @unittest.skipUnless(HAVE_LUKS2, "Integrity not supported") def test_integrity_wipe(self): -- 2.31.1