Skip to content

Commit

Permalink
blk-crypto: add ioctls to create and prepare hardware-wrapped keys
Browse files Browse the repository at this point in the history
Until this point, the kernel can use hardware-wrapped keys to do
encryption if userspace provides one -- specifically a key in
ephemerally-wrapped form.  However, no generic way has been provided for
userspace to get such a key in the first place.

Getting such a key is a two-step process.  First, the key needs to be
imported from a raw key or generated by the hardware, producing a key in
long-term wrapped form.  This happens once in the whole lifetime of the
key.  Second, the long-term wrapped key needs to be converted into
ephemerally-wrapped form.  This happens each time the key is "unlocked".

In Android, these operations are supported in a generic way through
KeyMint, a userspace abstraction layer.  However, that method is
Android-specific and can't be used on other Linux systems, may rely on
proprietary libraries, and also misleads people into supporting KeyMint
features like rollback resistance that make sense for other KeyMint keys
but don't make sense for hardware-wrapped inline encryption keys.

Therefore, this patch provides a generic kernel interface for these
operations by introducing new block device ioctls:

- BLKCRYPTOIMPORTKEY: convert a raw key to long-term wrapped form.

- BLKCRYPTOGENERATEKEY: have the hardware generate a new key, then
  return it in long-term wrapped form.

- BLKCRYPTOPREPAREKEY: convert a key from long-term wrapped form to
  ephemerally-wrapped form.

These ioctls are implemented using new operations in blk_crypto_ll_ops.

Signed-off-by: Eric Biggers <ebiggers@google.com>
Tested-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org> # sm8650
Link: https://lore.kernel.org/r/20250204060041.409950-4-ebiggers@kernel.org
Signed-off-by: Jens Axboe <axboe@kernel.dk>
  • Loading branch information
Eric Biggers authored and Jens Axboe committed Feb 10, 2025
1 parent e35fde4 commit 1ebd4a3
Show file tree
Hide file tree
Showing 10 changed files with 350 additions and 4 deletions.
36 changes: 36 additions & 0 deletions Documentation/block/inline-encryption.rst
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,42 @@ when hardware support is available. This works in the following way:
blk-crypto-fallback doesn't support hardware-wrapped keys. Therefore,
hardware-wrapped keys can only be used with actual inline encryption hardware.

All the above deals with hardware-wrapped keys in ephemerally-wrapped form only.
To get such keys in the first place, new block device ioctls have been added to
provide a generic interface to creating and preparing such keys:

- ``BLKCRYPTOIMPORTKEY`` converts a raw key to long-term wrapped form. It takes
in a pointer to a ``struct blk_crypto_import_key_arg``. The caller must set
``raw_key_ptr`` and ``raw_key_size`` to the pointer and size (in bytes) of the
raw key to import. On success, ``BLKCRYPTOIMPORTKEY`` returns 0 and writes
the resulting long-term wrapped key blob to the buffer pointed to by
``lt_key_ptr``, which is of maximum size ``lt_key_size``. It also updates
``lt_key_size`` to be the actual size of the key. On failure, it returns -1
and sets errno. An errno of ``EOPNOTSUPP`` indicates that the block device
does not support hardware-wrapped keys. An errno of ``EOVERFLOW`` indicates
that the output buffer did not have enough space for the key blob.

- ``BLKCRYPTOGENERATEKEY`` is like ``BLKCRYPTOIMPORTKEY``, but it has the
hardware generate the key instead of importing one. It takes in a pointer to
a ``struct blk_crypto_generate_key_arg``.

- ``BLKCRYPTOPREPAREKEY`` converts a key from long-term wrapped form to
ephemerally-wrapped form. It takes in a pointer to a ``struct
blk_crypto_prepare_key_arg``. The caller must set ``lt_key_ptr`` and
``lt_key_size`` to the pointer and size (in bytes) of the long-term wrapped
key blob to convert. On success, ``BLKCRYPTOPREPAREKEY`` returns 0 and writes
the resulting ephemerally-wrapped key blob to the buffer pointed to by
``eph_key_ptr``, which is of maximum size ``eph_key_size``. It also updates
``eph_key_size`` to be the actual size of the key. On failure, it returns -1
and sets errno. Errno values of ``EOPNOTSUPP`` and ``EOVERFLOW`` mean the
same as they do for ``BLKCRYPTOIMPORTKEY``. An errno of ``EBADMSG`` indicates
that the long-term wrapped key is invalid.

Userspace needs to use either ``BLKCRYPTOIMPORTKEY`` or ``BLKCRYPTOGENERATEKEY``
once to create a key, and then ``BLKCRYPTOPREPAREKEY`` each time the key is
unlocked and added to the kernel. Note that these ioctls have no relevance for
raw keys; they are only for hardware-wrapped keys.

Testability
-----------

Expand Down
2 changes: 2 additions & 0 deletions Documentation/userspace-api/ioctl/ioctl-number.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ Code Seq# Include File Comments
0x10 20-2F arch/s390/include/uapi/asm/hypfs.h
0x12 all linux/fs.h BLK* ioctls
linux/blkpg.h
linux/blkzoned.h
linux/blk-crypto.h
0x15 all linux/fs.h FS_IOC_* ioctls
0x1b all InfiniBand Subsystem
<http://infiniband.sourceforge.net/>
Expand Down
9 changes: 9 additions & 0 deletions block/blk-crypto-internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ int __blk_crypto_evict_key(struct blk_crypto_profile *profile,
bool __blk_crypto_cfg_supported(struct blk_crypto_profile *profile,
const struct blk_crypto_config *cfg);

int blk_crypto_ioctl(struct block_device *bdev, unsigned int cmd,
void __user *argp);

#else /* CONFIG_BLK_INLINE_ENCRYPTION */

static inline int blk_crypto_sysfs_register(struct gendisk *disk)
Expand Down Expand Up @@ -130,6 +133,12 @@ static inline bool blk_crypto_rq_has_keyslot(struct request *rq)
return false;
}

static inline int blk_crypto_ioctl(struct block_device *bdev, unsigned int cmd,
void __user *argp)
{
return -ENOTTY;
}

#endif /* CONFIG_BLK_INLINE_ENCRYPTION */

void __bio_crypt_advance(struct bio *bio, unsigned int bytes);
Expand Down
55 changes: 55 additions & 0 deletions block/blk-crypto-profile.c
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,61 @@ int blk_crypto_derive_sw_secret(struct block_device *bdev,
return err;
}

int blk_crypto_import_key(struct blk_crypto_profile *profile,
const u8 *raw_key, size_t raw_key_size,
u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE])
{
int ret;

if (!profile)
return -EOPNOTSUPP;
if (!(profile->key_types_supported & BLK_CRYPTO_KEY_TYPE_HW_WRAPPED))
return -EOPNOTSUPP;
if (!profile->ll_ops.import_key)
return -EOPNOTSUPP;
blk_crypto_hw_enter(profile);
ret = profile->ll_ops.import_key(profile, raw_key, raw_key_size,
lt_key);
blk_crypto_hw_exit(profile);
return ret;
}

int blk_crypto_generate_key(struct blk_crypto_profile *profile,
u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE])
{
int ret;

if (!profile)
return -EOPNOTSUPP;
if (!(profile->key_types_supported & BLK_CRYPTO_KEY_TYPE_HW_WRAPPED))
return -EOPNOTSUPP;
if (!profile->ll_ops.generate_key)
return -EOPNOTSUPP;
blk_crypto_hw_enter(profile);
ret = profile->ll_ops.generate_key(profile, lt_key);
blk_crypto_hw_exit(profile);
return ret;
}

int blk_crypto_prepare_key(struct blk_crypto_profile *profile,
const u8 *lt_key, size_t lt_key_size,
u8 eph_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE])
{
int ret;

if (!profile)
return -EOPNOTSUPP;
if (!(profile->key_types_supported & BLK_CRYPTO_KEY_TYPE_HW_WRAPPED))
return -EOPNOTSUPP;
if (!profile->ll_ops.prepare_key)
return -EOPNOTSUPP;
blk_crypto_hw_enter(profile);
ret = profile->ll_ops.prepare_key(profile, lt_key, lt_key_size,
eph_key);
blk_crypto_hw_exit(profile);
return ret;
}

/**
* blk_crypto_intersect_capabilities() - restrict supported crypto capabilities
* by child device
Expand Down
143 changes: 143 additions & 0 deletions block/blk-crypto.c
Original file line number Diff line number Diff line change
Expand Up @@ -469,3 +469,146 @@ void blk_crypto_evict_key(struct block_device *bdev,
pr_warn_ratelimited("%pg: error %d evicting key\n", bdev, err);
}
EXPORT_SYMBOL_GPL(blk_crypto_evict_key);

static int blk_crypto_ioctl_import_key(struct blk_crypto_profile *profile,
void __user *argp)
{
struct blk_crypto_import_key_arg arg;
u8 raw_key[BLK_CRYPTO_MAX_RAW_KEY_SIZE];
u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE];
int ret;

if (copy_from_user(&arg, argp, sizeof(arg)))
return -EFAULT;

if (memchr_inv(arg.reserved, 0, sizeof(arg.reserved)))
return -EINVAL;

if (arg.raw_key_size < 16 || arg.raw_key_size > sizeof(raw_key))
return -EINVAL;

if (copy_from_user(raw_key, u64_to_user_ptr(arg.raw_key_ptr),
arg.raw_key_size)) {
ret = -EFAULT;
goto out;
}
ret = blk_crypto_import_key(profile, raw_key, arg.raw_key_size, lt_key);
if (ret < 0)
goto out;
if (ret > arg.lt_key_size) {
ret = -EOVERFLOW;
goto out;
}
arg.lt_key_size = ret;
if (copy_to_user(u64_to_user_ptr(arg.lt_key_ptr), lt_key,
arg.lt_key_size) ||
copy_to_user(argp, &arg, sizeof(arg))) {
ret = -EFAULT;
goto out;
}
ret = 0;

out:
memzero_explicit(raw_key, sizeof(raw_key));
memzero_explicit(lt_key, sizeof(lt_key));
return ret;
}

static int blk_crypto_ioctl_generate_key(struct blk_crypto_profile *profile,
void __user *argp)
{
struct blk_crypto_generate_key_arg arg;
u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE];
int ret;

if (copy_from_user(&arg, argp, sizeof(arg)))
return -EFAULT;

if (memchr_inv(arg.reserved, 0, sizeof(arg.reserved)))
return -EINVAL;

ret = blk_crypto_generate_key(profile, lt_key);
if (ret < 0)
goto out;
if (ret > arg.lt_key_size) {
ret = -EOVERFLOW;
goto out;
}
arg.lt_key_size = ret;
if (copy_to_user(u64_to_user_ptr(arg.lt_key_ptr), lt_key,
arg.lt_key_size) ||
copy_to_user(argp, &arg, sizeof(arg))) {
ret = -EFAULT;
goto out;
}
ret = 0;

out:
memzero_explicit(lt_key, sizeof(lt_key));
return ret;
}

static int blk_crypto_ioctl_prepare_key(struct blk_crypto_profile *profile,
void __user *argp)
{
struct blk_crypto_prepare_key_arg arg;
u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE];
u8 eph_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE];
int ret;

if (copy_from_user(&arg, argp, sizeof(arg)))
return -EFAULT;

if (memchr_inv(arg.reserved, 0, sizeof(arg.reserved)))
return -EINVAL;

if (arg.lt_key_size > sizeof(lt_key))
return -EINVAL;

if (copy_from_user(lt_key, u64_to_user_ptr(arg.lt_key_ptr),
arg.lt_key_size)) {
ret = -EFAULT;
goto out;
}
ret = blk_crypto_prepare_key(profile, lt_key, arg.lt_key_size, eph_key);
if (ret < 0)
goto out;
if (ret > arg.eph_key_size) {
ret = -EOVERFLOW;
goto out;
}
arg.eph_key_size = ret;
if (copy_to_user(u64_to_user_ptr(arg.eph_key_ptr), eph_key,
arg.eph_key_size) ||
copy_to_user(argp, &arg, sizeof(arg))) {
ret = -EFAULT;
goto out;
}
ret = 0;

out:
memzero_explicit(lt_key, sizeof(lt_key));
memzero_explicit(eph_key, sizeof(eph_key));
return ret;
}

int blk_crypto_ioctl(struct block_device *bdev, unsigned int cmd,
void __user *argp)
{
struct blk_crypto_profile *profile =
bdev_get_queue(bdev)->crypto_profile;

if (!profile)
return -EOPNOTSUPP;

switch (cmd) {
case BLKCRYPTOIMPORTKEY:
return blk_crypto_ioctl_import_key(profile, argp);
case BLKCRYPTOGENERATEKEY:
return blk_crypto_ioctl_generate_key(profile, argp);
case BLKCRYPTOPREPAREKEY:
return blk_crypto_ioctl_prepare_key(profile, argp);
default:
return -ENOTTY;
}
}
5 changes: 5 additions & 0 deletions block/ioctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <linux/io_uring/cmd.h>
#include <uapi/linux/blkdev.h>
#include "blk.h"
#include "blk-crypto-internal.h"

static int blkpg_do_ioctl(struct block_device *bdev,
struct blkpg_partition __user *upart, int op)
Expand Down Expand Up @@ -620,6 +621,10 @@ static int blkdev_common_ioctl(struct block_device *bdev, blk_mode_t mode,
case BLKTRACESTOP:
case BLKTRACETEARDOWN:
return blk_trace_ioctl(bdev, cmd, argp);
case BLKCRYPTOIMPORTKEY:
case BLKCRYPTOGENERATEKEY:
case BLKCRYPTOPREPAREKEY:
return blk_crypto_ioctl(bdev, cmd, argp);
case IOC_PR_REGISTER:
return blkdev_pr_register(bdev, mode, argp);
case IOC_PR_RESERVE:
Expand Down
53 changes: 53 additions & 0 deletions include/linux/blk-crypto-profile.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,48 @@ struct blk_crypto_ll_ops {
int (*derive_sw_secret)(struct blk_crypto_profile *profile,
const u8 *eph_key, size_t eph_key_size,
u8 sw_secret[BLK_CRYPTO_SW_SECRET_SIZE]);

/**
* @import_key: Create a hardware-wrapped key by importing a raw key.
*
* This only needs to be implemented if BLK_CRYPTO_KEY_TYPE_HW_WRAPPED
* is supported.
*
* On success, must write the new key in long-term wrapped form to
* @lt_key and return its size in bytes. On failure, must return a
* -errno value.
*/
int (*import_key)(struct blk_crypto_profile *profile,
const u8 *raw_key, size_t raw_key_size,
u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE]);

/**
* @generate_key: Generate a hardware-wrapped key.
*
* This only needs to be implemented if BLK_CRYPTO_KEY_TYPE_HW_WRAPPED
* is supported.
*
* On success, must write the new key in long-term wrapped form to
* @lt_key and return its size in bytes. On failure, must return a
* -errno value.
*/
int (*generate_key)(struct blk_crypto_profile *profile,
u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE]);

/**
* @prepare_key: Prepare a hardware-wrapped key to be used.
*
* Prepare a hardware-wrapped key to be used by converting it from
* long-term wrapped form to ephemerally-wrapped form. This only needs
* to be implemented if BLK_CRYPTO_KEY_TYPE_HW_WRAPPED is supported.
*
* On success, must write the key in ephemerally-wrapped form to
* @eph_key and return its size in bytes. On failure, must return
* -EBADMSG if the key is invalid, or another -errno on other error.
*/
int (*prepare_key)(struct blk_crypto_profile *profile,
const u8 *lt_key, size_t lt_key_size,
u8 eph_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE]);
};

/**
Expand Down Expand Up @@ -163,6 +205,17 @@ void blk_crypto_reprogram_all_keys(struct blk_crypto_profile *profile);

void blk_crypto_profile_destroy(struct blk_crypto_profile *profile);

int blk_crypto_import_key(struct blk_crypto_profile *profile,
const u8 *raw_key, size_t raw_key_size,
u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE]);

int blk_crypto_generate_key(struct blk_crypto_profile *profile,
u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE]);

int blk_crypto_prepare_key(struct blk_crypto_profile *profile,
const u8 *lt_key, size_t lt_key_size,
u8 eph_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE]);

void blk_crypto_intersect_capabilities(struct blk_crypto_profile *parent,
const struct blk_crypto_profile *child);

Expand Down
1 change: 1 addition & 0 deletions include/linux/blk-crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include <linux/minmax.h>
#include <linux/types.h>
#include <uapi/linux/blk-crypto.h>

enum blk_crypto_mode_num {
BLK_ENCRYPTION_MODE_INVALID,
Expand Down
Loading

0 comments on commit 1ebd4a3

Please sign in to comment.