diff options
Diffstat (limited to 'lib/crypto_backend/crypto_storage.c')
-rw-r--r-- | lib/crypto_backend/crypto_storage.c | 347 |
1 files changed, 347 insertions, 0 deletions
diff --git a/lib/crypto_backend/crypto_storage.c b/lib/crypto_backend/crypto_storage.c new file mode 100644 index 0000000..13479dd --- /dev/null +++ b/lib/crypto_backend/crypto_storage.c @@ -0,0 +1,347 @@ +/* + * Generic wrapper for storage encryption modes and Initial Vectors + * (reimplementation of some functions from Linux dm-crypt kernel) + * + * Copyright (C) 2014-2023 Milan Broz + * + * 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 <stdlib.h> +#include <errno.h> +#include <strings.h> +#include "bitops.h" +#include "crypto_backend.h" + +#define SECTOR_SHIFT 9 + +/* + * Internal IV helper + * IV documentation: https://gitlab.com/cryptsetup/cryptsetup/wikis/DMCrypt + */ +struct crypt_sector_iv { + enum { IV_NONE, IV_NULL, IV_PLAIN, IV_PLAIN64, IV_ESSIV, IV_BENBI, IV_PLAIN64BE, IV_EBOIV } type; + int iv_size; + char *iv; + struct crypt_cipher *cipher; + int shift; +}; + +/* Block encryption storage context */ +struct crypt_storage { + size_t sector_size; + unsigned iv_shift; + struct crypt_cipher *cipher; + struct crypt_sector_iv cipher_iv; +}; + +static int int_log2(unsigned int x) +{ + int r = 0; + for (x >>= 1; x > 0; x >>= 1) + r++; + return r; +} + +static int crypt_sector_iv_init(struct crypt_sector_iv *ctx, + const char *cipher_name, const char *mode_name, + const char *iv_name, const void *key, size_t key_length, + size_t sector_size) +{ + int r; + + memset(ctx, 0, sizeof(*ctx)); + + ctx->iv_size = crypt_cipher_ivsize(cipher_name, mode_name); + if (ctx->iv_size < 0 || (strcmp(mode_name, "ecb") && ctx->iv_size < 8)) + return -ENOENT; + + if (!strcmp(cipher_name, "cipher_null") || + !strcmp(mode_name, "ecb")) { + if (iv_name) + return -EINVAL; + ctx->type = IV_NONE; + ctx->iv_size = 0; + return 0; + } else if (!iv_name) { + return -EINVAL; + } else if (!strcasecmp(iv_name, "null")) { + ctx->type = IV_NULL; + } else if (!strcasecmp(iv_name, "plain64")) { + ctx->type = IV_PLAIN64; + } else if (!strcasecmp(iv_name, "plain64be")) { + ctx->type = IV_PLAIN64BE; + } else if (!strcasecmp(iv_name, "plain")) { + ctx->type = IV_PLAIN; + } else if (!strncasecmp(iv_name, "essiv:", 6)) { + struct crypt_hash *h = NULL; + char *hash_name = strchr(iv_name, ':'); + int hash_size; + char tmp[256]; + + if (!hash_name) + return -EINVAL; + + hash_size = crypt_hash_size(++hash_name); + if (hash_size < 0) + return -ENOENT; + + if ((unsigned)hash_size > sizeof(tmp)) + return -EINVAL; + + if (crypt_hash_init(&h, hash_name)) + return -EINVAL; + + r = crypt_hash_write(h, key, key_length); + if (r) { + crypt_hash_destroy(h); + return r; + } + + r = crypt_hash_final(h, tmp, hash_size); + crypt_hash_destroy(h); + if (r) { + crypt_backend_memzero(tmp, sizeof(tmp)); + return r; + } + + r = crypt_cipher_init(&ctx->cipher, cipher_name, "ecb", + tmp, hash_size); + crypt_backend_memzero(tmp, sizeof(tmp)); + if (r) + return r; + + ctx->type = IV_ESSIV; + } else if (!strncasecmp(iv_name, "benbi", 5)) { + int log = int_log2(ctx->iv_size); + if (log > SECTOR_SHIFT) + return -EINVAL; + + ctx->type = IV_BENBI; + ctx->shift = SECTOR_SHIFT - log; + } else if (!strncasecmp(iv_name, "eboiv", 5)) { + r = crypt_cipher_init(&ctx->cipher, cipher_name, "ecb", + key, key_length); + if (r) + return r; + + ctx->type = IV_EBOIV; + ctx->shift = int_log2(sector_size); + } else + return -ENOENT; + + ctx->iv = malloc(ctx->iv_size); + if (!ctx->iv) + return -ENOMEM; + + return 0; +} + +static int crypt_sector_iv_generate(struct crypt_sector_iv *ctx, uint64_t sector) +{ + uint64_t val, *u64_iv; + uint32_t *u32_iv; + + switch (ctx->type) { + case IV_NONE: + break; + case IV_NULL: + memset(ctx->iv, 0, ctx->iv_size); + break; + case IV_PLAIN: + memset(ctx->iv, 0, ctx->iv_size); + u32_iv = (void *)ctx->iv; + *u32_iv = cpu_to_le32(sector & 0xffffffff); + break; + case IV_PLAIN64: + memset(ctx->iv, 0, ctx->iv_size); + u64_iv = (void *)ctx->iv; + *u64_iv = cpu_to_le64(sector); + break; + case IV_PLAIN64BE: + memset(ctx->iv, 0, ctx->iv_size); + /* iv_size is at least of size u64; usually it is 16 bytes */ + u64_iv = (void *)&ctx->iv[ctx->iv_size - sizeof(uint64_t)]; + *u64_iv = cpu_to_be64(sector); + break; + case IV_ESSIV: + memset(ctx->iv, 0, ctx->iv_size); + u64_iv = (void *)ctx->iv; + *u64_iv = cpu_to_le64(sector); + return crypt_cipher_encrypt(ctx->cipher, + ctx->iv, ctx->iv, ctx->iv_size, NULL, 0); + break; + case IV_BENBI: + memset(ctx->iv, 0, ctx->iv_size); + val = cpu_to_be64((sector << ctx->shift) + 1); + memcpy(ctx->iv + ctx->iv_size - sizeof(val), &val, sizeof(val)); + break; + case IV_EBOIV: + memset(ctx->iv, 0, ctx->iv_size); + u64_iv = (void *)ctx->iv; + *u64_iv = cpu_to_le64(sector << ctx->shift); + return crypt_cipher_encrypt(ctx->cipher, + ctx->iv, ctx->iv, ctx->iv_size, NULL, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static void crypt_sector_iv_destroy(struct crypt_sector_iv *ctx) +{ + if (ctx->type == IV_ESSIV || ctx->type == IV_EBOIV) + crypt_cipher_destroy(ctx->cipher); + + if (ctx->iv) { + memset(ctx->iv, 0, ctx->iv_size); + free(ctx->iv); + } + + memset(ctx, 0, sizeof(*ctx)); +} + +/* Block encryption storage wrappers */ + +int crypt_storage_init(struct crypt_storage **ctx, + size_t sector_size, + const char *cipher, + const char *cipher_mode, + const void *key, size_t key_length, + bool large_iv) +{ + struct crypt_storage *s; + char mode_name[64]; + char *cipher_iv = NULL; + int r = -EIO; + + if (sector_size < (1 << SECTOR_SHIFT) || + sector_size > (1 << (SECTOR_SHIFT + 3)) || + sector_size & (sector_size - 1)) + return -EINVAL; + + s = malloc(sizeof(*s)); + if (!s) + return -ENOMEM; + memset(s, 0, sizeof(*s)); + + /* Remove IV if present */ + strncpy(mode_name, cipher_mode, sizeof(mode_name)); + mode_name[sizeof(mode_name) - 1] = 0; + cipher_iv = strchr(mode_name, '-'); + if (cipher_iv) { + *cipher_iv = '\0'; + cipher_iv++; + } + + r = crypt_cipher_init(&s->cipher, cipher, mode_name, key, key_length); + if (r) { + crypt_storage_destroy(s); + return r; + } + + r = crypt_sector_iv_init(&s->cipher_iv, cipher, mode_name, cipher_iv, key, key_length, sector_size); + if (r) { + crypt_storage_destroy(s); + return r; + } + + s->sector_size = sector_size; + s->iv_shift = large_iv ? int_log2(sector_size) - SECTOR_SHIFT : 0; + + *ctx = s; + return 0; +} + +int crypt_storage_decrypt(struct crypt_storage *ctx, + uint64_t iv_offset, + uint64_t length, char *buffer) +{ + uint64_t i; + int r = 0; + + if (length & (ctx->sector_size - 1)) + return -EINVAL; + + if (iv_offset & ((ctx->sector_size >> SECTOR_SHIFT) - 1)) + return -EINVAL; + + for (i = 0; i < length; i += ctx->sector_size) { + r = crypt_sector_iv_generate(&ctx->cipher_iv, (iv_offset + (i >> SECTOR_SHIFT)) >> ctx->iv_shift); + if (r) + break; + r = crypt_cipher_decrypt(ctx->cipher, + &buffer[i], + &buffer[i], + ctx->sector_size, + ctx->cipher_iv.iv, + ctx->cipher_iv.iv_size); + if (r) + break; + } + + return r; +} + +int crypt_storage_encrypt(struct crypt_storage *ctx, + uint64_t iv_offset, + uint64_t length, char *buffer) +{ + uint64_t i; + int r = 0; + + if (length & (ctx->sector_size - 1)) + return -EINVAL; + + if (iv_offset & ((ctx->sector_size >> SECTOR_SHIFT) - 1)) + return -EINVAL; + + for (i = 0; i < length; i += ctx->sector_size) { + r = crypt_sector_iv_generate(&ctx->cipher_iv, (iv_offset + (i >> SECTOR_SHIFT)) >> ctx->iv_shift); + if (r) + break; + r = crypt_cipher_encrypt(ctx->cipher, + &buffer[i], + &buffer[i], + ctx->sector_size, + ctx->cipher_iv.iv, + ctx->cipher_iv.iv_size); + if (r) + break; + } + + return r; +} + +void crypt_storage_destroy(struct crypt_storage *ctx) +{ + if (!ctx) + return; + + crypt_sector_iv_destroy(&ctx->cipher_iv); + + if (ctx->cipher) + crypt_cipher_destroy(ctx->cipher); + + memset(ctx, 0, sizeof(*ctx)); + free(ctx); +} + +bool crypt_storage_kernel_only(struct crypt_storage *ctx) +{ + return crypt_cipher_kernel_only(ctx->cipher); +} |