mbox series

[00/10] Add wrapped key support for Qualcomm ICE

Message ID 20211206225725.77512-1-quic_gaurkash@quicinc.com
Headers show
Series Add wrapped key support for Qualcomm ICE | expand

Message

Gaurav Kashyap (QUIC) Dec. 6, 2021, 10:57 p.m. UTC
These patches add support to Qualcomm ICE for hardware
wrapped keys and are made on top of Eric Bigger's set of changes to
support wrapped keys.
Found here: https://lore.kernel.org/linux-block/20210916174928.65529-1-ebiggers@kernel.org).
Explanation and use of hardware-wrapped-keys can be found here:
Documentation/block/inline-encryption.rst

The first patch however is not related to wrapped keys. It is
for moving ICE functionality into a shared library which both ufs and
sdhci can use. The patch for sdhc-msm will be uploaded later.

Wrapped keys are supported in Qualcomm's ICE engine using
a proprietary hardware module known as Hardware Key Manager (HWKM).
The patches are revolved around that and testing for them
can be done only on a HWKM supported Qualcomm device.

Testing:
Test platform: SM8350 HDK/MTP
Engineering trustzone image (based on sm8350) is required to test
this feature. This is because of version changes of HWKM.
HWKM version 2 and moving forward has a lot of restrictions on the
key management due to which the launched SM8350 solution (based on v1)
cannot be used and some modifications are required in trustzone.

The ideal scenario is to test by mounting initramfs on an upstream
kernel and then using the fscrypt tool to setup directories with
encryption policies. Testing and development for that is still under way.

All the SCM calls however were individually tested from UFS by invoking
a test API on bootup which is not part of these patchsets.

Gaurav Kashyap (10):
  soc: qcom: new common library for ICE functionality
  scsi: ufs: qcom: move ICE functionality to common library
  qcom_scm: scm call for deriving a software secret
  soc: qcom: add HWKM library for storage encryption
  scsi: ufs: prepare to support wrapped keys
  soc: qcom: add wrapped key support for ICE
  qcom_scm: scm call for create, prepare and import keys
  scsi: ufs: add support for generate, import and prepare keys
  soc: qcom: support for generate, import and prepare key
  arm64: dts: qcom: sm8350: add ice and hwkm mappings

 arch/arm64/boot/dts/qcom/sm8350.dtsi |   5 +-
 drivers/firmware/qcom_scm.c          | 286 +++++++++++++++++++
 drivers/firmware/qcom_scm.h          |   4 +
 drivers/scsi/ufs/Kconfig             |   1 +
 drivers/scsi/ufs/ufs-qcom-ice.c      | 227 +++++----------
 drivers/scsi/ufs/ufs-qcom.c          |   4 +
 drivers/scsi/ufs/ufs-qcom.h          |  22 +-
 drivers/scsi/ufs/ufshcd-crypto.c     |  96 ++++++-
 drivers/scsi/ufs/ufshcd.h            |  20 +-
 drivers/soc/qcom/Kconfig             |   7 +
 drivers/soc/qcom/Makefile            |   1 +
 drivers/soc/qcom/qti-ice-common.c    | 402 +++++++++++++++++++++++++++
 drivers/soc/qcom/qti-ice-hwkm.c      | 111 ++++++++
 drivers/soc/qcom/qti-ice-regs.h      | 264 ++++++++++++++++++
 include/linux/qcom_scm.h             |  30 +-
 include/linux/qti-ice-common.h       |  40 +++
 16 files changed, 1345 insertions(+), 175 deletions(-)
 create mode 100644 drivers/soc/qcom/qti-ice-common.c
 create mode 100644 drivers/soc/qcom/qti-ice-hwkm.c
 create mode 100644 drivers/soc/qcom/qti-ice-regs.h
 create mode 100644 include/linux/qti-ice-common.h

Comments

Eric Biggers Dec. 14, 2021, 12:20 a.m. UTC | #1
On Mon, Dec 06, 2021 at 02:57:16PM -0800, Gaurav Kashyap wrote:
> Add a new library which congregates all the ICE
> functionality so that all storage controllers containing
> ICE can utilize it.

In commit messages and comments, please spell out "Inline Crypto Engine (ICE)"
the first time it appears, so that people know what ICE means.

> diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
> index 79b568f82a1c..a900f5ab6263 100644
> --- a/drivers/soc/qcom/Kconfig
> +++ b/drivers/soc/qcom/Kconfig
> @@ -209,4 +209,11 @@ config QCOM_APR
>  	  application processor and QDSP6. APR is
>  	  used by audio driver to configure QDSP6
>  	  ASM, ADM and AFE modules.
> +
> +config QTI_ICE_COMMON
> +	tristate "QTI common ICE functionality"

Since this is a library, it shouldn't be user-selectable, but rather just be
selected by the other options that need it.  Putting a string after "tristate"
makes it user-selectable; the string is the prompt string.  The line should just
be "tristate", without a string afterwards.

> diff --git a/drivers/soc/qcom/qti-ice-common.c b/drivers/soc/qcom/qti-ice-common.c
> new file mode 100644
> index 000000000000..0c5b529201c5
> --- /dev/null
> +++ b/drivers/soc/qcom/qti-ice-common.c
> @@ -0,0 +1,199 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Common ICE library for storage encryption.
> + *
> + * Copyright (c) 2021, Qualcomm Innovation Center. All rights reserved.
> + */
> +
> +#include <linux/qti-ice-common.h>
> +#include <linux/module.h>
> +#include <linux/qcom_scm.h>
> +#include <linux/delay.h>
> +#include "qti-ice-regs.h"
> +
> +#define QTI_ICE_MAX_BIST_CHECK_COUNT    100
> +#define QTI_AES_256_XTS_KEY_RAW_SIZE	64
> +
> +static bool qti_ice_supported(const struct ice_mmio_data *mmio)
> +{
> +	u32 regval = qti_ice_readl(mmio->ice_mmio, QTI_ICE_REGS_VERSION);
> +	int major = regval >> 24;
> +	int minor = (regval >> 16) & 0xFF;
> +	int step = regval & 0xFFFF;
> +
> +	/* For now this driver only supports ICE version 3 and higher. */
> +	if (major < 3) {
> +		pr_warn("Unsupported ICE version: v%d.%d.%d\n",
> +			 major, minor, step);
> +		return false;
> +	}

For log messages associated with a device, the dev_*() functions should be used
instead of pr_*().  How about including the relevant 'struct device *' in the
struct ice_mmio_data?

This comment applies to everywhere in qti-ice-common that is logging anything.

> +/**
> + * qti_ice_init() - Initialize ICE functionality
> + * @ice_mmio_data: contains ICE register mapping for i/o
> + *
> + * Initialize ICE by checking the version for ICE support and
> + * also checking the fuses blown.
> + *
> + * Return: 0 on success; -EINVAL on failure.
> + */
> +int qti_ice_init(const struct ice_mmio_data *mmio)
> +{
> +	if (!qti_ice_supported(mmio))
> +		return -EINVAL;
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(qti_ice_init);

Be more specific about what "failure" means here.  It means that the driver
doesn't support the ICE hardware, right?  -ENODEV would be a more appropriate
error code for this.  -EINVAL is a very generic error.

> +static void qti_ice_low_power_and_optimization_enable(
> +				const struct ice_mmio_data *mmio)
> +{
> +	u32 regval = qti_ice_readl(mmio->ice_mmio,
> +				   QTI_ICE_REGS_ADVANCED_CONTROL);
> +
> +	/* Enable low power mode sequence
> +	 * [0]-0,[1]-0,[2]-0,[3]-7,[4]-0,[5]-0,[6]-0,[7]-0,
> +	 * Enable CONFIG_CLK_GATING, STREAM2_CLK_GATING and STREAM1_CLK_GATING
> +	 */
> +	regval |= 0x7000;
> +	/* Optimization enable sequence*/
> +	regval |= 0xD807100;
> +	qti_ice_writel(mmio->ice_mmio, regval, QTI_ICE_REGS_ADVANCED_CONTROL);
> +	/* Memory barrier - to ensure write completion before next transaction */
> +	wmb();
> +}

This part changed slightly from the original code in
drivers/scsi/ufs/ufs-qcom-ice.c and drivers/mmc/host/sdhci-msm.c.  What is the
reason for these changes?  To be fair, I can't properly explain this part of the
original code; I just did what some existing ICE code was doing.  But if it
wasn't the correct or best way, it would be super useful to understand *why*
this different version is really the correct/best way.

Also note that the line "regval |= 0x7000;" is redundant.

> +static int qti_ice_wait_bist_status(const struct ice_mmio_data *mmio)
> +{
> +	int count;
> +	u32 regval;
> +
> +	for (count = 0; count < QTI_ICE_MAX_BIST_CHECK_COUNT; count++) {
> +		regval = qti_ice_readl(mmio->ice_mmio,
> +				       QTI_ICE_REGS_BIST_STATUS);
> +		if (!(regval & QTI_ICE_BIST_STATUS_MASK))
> +			break;
> +		udelay(50);
> +	}
> +
> +	if (regval) {
> +		pr_err("%s: wait bist status failed, reg %d\n",
> +			__func__, regval);
> +		return -ETIMEDOUT;
> +	}
> +
> +	return 0;
> +}

The copy of this in drivers/mmc/host/sdhci-msm.c is a bit nicer in that it uses
the readl_poll_timeout() helper function, and it has a longer comment explaining
it.  So I think you should use that version rather than the UFS version.

> +/**
> + * qti_ice_keyslot_program() - Program a key to an ICE slot
> + * @ice_mmio_data: contains ICE register mapping for i/o
> + * @crypto_key: key to be program, this can be wrapped or raw
> + * @crypto_key_size: size of the key to be programmed
> + * @slot: the keyslot at which the key should be programmed.
> + * @data_unit_mask: mask for the dun which is part of the
> + *                  crypto configuration.
> + * @capid: capability index indicating the algorithm for the
> + *         crypto configuration
> + *
> + * Program the passed in key to a slot in ICE.
> + * The key that is passed in can either be a raw key or wrapped.
> + * In both cases, due to access control of ICE for Qualcomm chipsets,
> + * a scm call is used to program the key into ICE from trustzone.
> + * Trustzone can differentiate between raw and wrapped keys.

How does TrustZone differentiate between raw and wrapped keys?  I thought you
had mentioned that only one is supported at a time on a particular SoC.

> +int qti_ice_keyslot_program(const struct ice_mmio_data *mmio,
> +			    const u8 *crypto_key, unsigned int crypto_key_size,
> +			    unsigned int slot, u8 data_unit_mask, int capid)
> +{
> +	int err = 0;
> +	int i = 0;
> +	union {
> +		u8 bytes[QTI_AES_256_XTS_KEY_RAW_SIZE];
> +		u32 words[QTI_AES_256_XTS_KEY_RAW_SIZE / sizeof(u32)];
> +	} key;
> +
> +	memcpy(key.bytes, crypto_key, crypto_key_size);

This is assuming that wrapped keys aren't longer than raw AES-256-XTS keys,
which isn't necessarily true.

> +#define qti_ice_writel(mmio, val, reg)		\
> +	writel_relaxed((val), mmio + (reg))
> +#define qti_ice_readl(mmio, reg)		\
> +	readl_relaxed(mmio + (reg))

Previously, writel() and readl() were used instead of writel_relaxed() and
readl_relaxed().  What is the reason for this change?  My understanding is that
the *_relaxed() functions are harder to use correctly, so they shouldn't be used
unless it's been carefully thought through and the extra performance is needed.

- Eric
Eric Biggers Dec. 14, 2021, 12:53 a.m. UTC | #2
On Mon, Dec 06, 2021 at 02:57:18PM -0800, Gaurav Kashyap wrote:
> Storage encryption requires fscrypt deriving a sw secret from

As I mentioned on the previous version of this patchset, I think mentions of
"fscrypt" should generally be avoided at the driver level.  Drivers don't know
or care who is using block layer functionality.  It's better to think of the
sw_secret support as simply one of the requirements for hardware-wrapped keys,
alongside the other ones such as support for import_key, prepare_key, etc.

> +/**
> + * qcom_scm_derive_sw_secret() - Derive SW secret from wrapped encryption key
> + * @wrapped_key: the wrapped key used for inline encryption
> + * @wrapped_key_size: size of the wrapped key
> + * @sw_secret: the secret to be derived which is at most the secret size
> + * @secret_size: maximum size of the secret that is derived
> + *
> + * Derive a SW secret to be used for inline encryption using Qualcomm ICE.

The SW secret isn't used for inline encryption.  It's actually the opposite; the
SW secret is used only for tasks that can't use inline encryption.

> + * For wrapped keys, the key needs to be unwrapped, in order to derive a
> + * SW secret, which can be done only by the secure EE. So, it makes sense
> + * for the secure EE to derive the sw secret and return to the kernel when
> + * wrapped keys are used.

The second sentence above seems to be saying the same as the first.

> + * Return: 0 on success; -errno on failure.
> + */
> +int qcom_scm_derive_sw_secret(const u8 *wrapped_key, u32 wrapped_key_size,
> +			      u8 *sw_secret, u32 secret_size)

Is @secret_size really the "maximum size of the secret that is derived"?  That
would imply that a shorter secret might be derived.  But if the return value is
0 on success, then there is no way for callers to know what length was derived.

Can't the semantics be "derive exactly secret_size bytes"?  That would make much
more sense.

> diff --git a/include/linux/qcom_scm.h b/include/linux/qcom_scm.h
> index c0475d1c9885..ccd764bdc357 100644
> --- a/include/linux/qcom_scm.h
> +++ b/include/linux/qcom_scm.h
> @@ -103,6 +103,9 @@ extern int qcom_scm_ice_invalidate_key(u32 index);
>  extern int qcom_scm_ice_set_key(u32 index, const u8 *key, u32 key_size,
>  				enum qcom_scm_ice_cipher cipher,
>  				u32 data_unit_size);
> +extern int qcom_scm_derive_sw_secret(const u8 *wrapped_key,
> +				     u32 wrapped_key_size, u8 *sw_secret,
> +				     u32 secret_size);
>  
>  extern bool qcom_scm_hdcp_available(void);
>  extern int qcom_scm_hdcp_req(struct qcom_scm_hdcp_req *req, u32 req_cnt,
> @@ -169,6 +172,9 @@ static inline int qcom_scm_ice_invalidate_key(u32 index) { return -ENODEV; }
>  static inline int qcom_scm_ice_set_key(u32 index, const u8 *key, u32 key_size,
>  				       enum qcom_scm_ice_cipher cipher,
>  				       u32 data_unit_size) { return -ENODEV; }
> +static inline int qcom_scm_derive_sw_secret(const u8 *wrapped_key,
> +					u32 wrapped_key_size, u8 *sw_secret,
> +					u32 secret_size) { return -ENODEV; }
>  
>  static inline bool qcom_scm_hdcp_available(void) { return false; }
>  static inline int qcom_scm_hdcp_req(struct qcom_scm_hdcp_req *req, u32 req_cnt,

These "return -ENODEV" stubs don't exist in the latest kernel.  Can you make
sure that you've developing on top of the latest kernel?  It looks like you
based this patchset on top of my patchset
https://git.kernel.org/pub/scm/fs/fscrypt/fscrypt.git/log/?h=wrapped-keys-v2.
But I had already sent out a newer version of it, based on v5.16-rc1:
https://git.kernel.org/pub/scm/fs/fscrypt/fscrypt.git/log/?h=wrapped-keys-v4.
So please make sure you're using the most recent version.

- Eric
Eric Biggers Dec. 14, 2021, 1:26 a.m. UTC | #3
On Mon, Dec 06, 2021 at 02:57:20PM -0800, Gaurav Kashyap wrote:
> Adds support in ufshcd-core for wrapped keys.
> 1. Change program key vop to support wrapped key sizes by
>    using blk_crypto_key directly instead of using ufs_crypto_cfg
>    which is not suitable for wrapped keys.
> 2. Add derive_sw_secret vop and derive_sw_secret crypto_profile op.
> 
> Signed-off-by: Gaurav Kashyap <quic_gaurkash@quicinc.com>
> ---
>  drivers/scsi/ufs/ufshcd-crypto.c | 52 +++++++++++++++++++++++++-------
>  drivers/scsi/ufs/ufshcd.h        |  9 +++++-
>  2 files changed, 49 insertions(+), 12 deletions(-)

There will be a build error if the patch series is applied just up to here,
since this patch changes the prototype of ufs_hba_variant_ops::program_key but
doesn't update ufs_qcom which implements it.

Every intermediate step needs to be buildable, and that's more important than
keeping changes to different drivers separate.

So I recommend having one patch that does the program_key change, in both
ufshcd-crypto.c and ufs-qcom-ice.c.

Adding derive_sw_secret should be a separate patch, and maybe should be combined
with the other new methods.

> diff --git a/drivers/scsi/ufs/ufshcd-crypto.c b/drivers/scsi/ufs/ufshcd-crypto.c
> index 0ed82741f981..9d68621a0eb4 100644
> --- a/drivers/scsi/ufs/ufshcd-crypto.c
> +++ b/drivers/scsi/ufs/ufshcd-crypto.c
> @@ -18,16 +18,23 @@ static const struct ufs_crypto_alg_entry {
>  };
>  
>  static int ufshcd_program_key(struct ufs_hba *hba,
> +			      const struct blk_crypto_key *key,
>  			      const union ufs_crypto_cfg_entry *cfg, int slot)
>  {
>  	int i;
>  	u32 slot_offset = hba->crypto_cfg_register + slot * sizeof(*cfg);
>  	int err = 0;
> +	bool evict = false;
>  
>  	ufshcd_hold(hba, false);
>  
>  	if (hba->vops && hba->vops->program_key) {
> -		err = hba->vops->program_key(hba, cfg, slot);
> +		if (!(cfg->config_enable & UFS_CRYPTO_CONFIGURATION_ENABLE))
> +			evict = true;
> +		err = hba->vops->program_key(hba, key, slot,
> +					     cfg->data_unit_size,
> +					     cfg->crypto_cap_idx,
> +					     evict);
>  		goto out;
>  	}

This is a little weird because here we've already gone through the trouble of
creating a 'union ufs_crypto_cfg_entry', only to throw it away in the
->program_key case and just use the original blk_crypto_key instead.

I think that this should be refactored a bit to make it so that a
'ufs_crypto_cfg_entry' is only be initialized if program_key is not implemented.

Also, note that 'struct blk_crypto_key' includes the data unit size.  So there's
no need to pass the data unit size as a separate argument to program_key.

> +static int ufshcd_crypto_derive_sw_secret(struct blk_crypto_profile *profile,
> +				const u8 *wrapped_key,
> +				unsigned int wrapped_key_size,
> +				u8 sw_secret[BLK_CRYPTO_SW_SECRET_SIZE])
> +{
> +	struct ufs_hba *hba =
> +		container_of(profile, struct ufs_hba, crypto_profile);
> +
> +	if (hba->vops && hba->vops->derive_secret)
> +		return  hba->vops->derive_secret(hba, wrapped_key,
> +						 wrapped_key_size, sw_secret);

There's a weird double space here.

> @@ -190,7 +215,12 @@ int ufshcd_hba_init_crypto_capabilities(struct ufs_hba *hba)
>  	hba->crypto_profile.ll_ops = ufshcd_crypto_ops;
>  	/* UFS only supports 8 bytes for any DUN */
>  	hba->crypto_profile.max_dun_bytes_supported = 8;
> -	hba->crypto_profile.key_types_supported = BLK_CRYPTO_KEY_TYPE_STANDARD;
> +	if (hba->hw_wrapped_keys_supported)
> +		hba->crypto_profile.key_types_supported =
> +				BLK_CRYPTO_KEY_TYPE_HW_WRAPPED;
> +	else
> +		hba->crypto_profile.key_types_supported =
> +				BLK_CRYPTO_KEY_TYPE_STANDARD;

"hw_wrapped_keys_supported" is confusing because it doesn't just mean that
wrapped keys are supported, but also that standard keys are *not* supported.
"use_hw_wrapped_keys" would be clearer.

However, given that wrapped keys aren't specified by the UFS standard, I think
this better belongs as a bit in hba->quirks, like
UFSHCD_QUIRK_USES_WRAPPED_CRYPTO_KEYS.

> +	int	(*derive_secret)(struct ufs_hba *hba, const u8 *wrapped_key,
> +				 unsigned int wrapped_key_size,
> +				 u8 sw_secret[BLK_CRYPTO_SW_SECRET_SIZE]);

This probably should be called derive_sw_secret, not just derive_secret.

- Eric
Eric Biggers Dec. 14, 2021, 1:50 a.m. UTC | #4
On Mon, Dec 06, 2021 at 02:57:22PM -0800, Gaurav Kashyap wrote:
> +/**
> + * qcom_scm_generate_ice_key() - Generate a wrapped key for encryption.
> + * @longterm_wrapped_key: the wrapped key returned after key generation
> + * @longterm_wrapped_key_size: size of the wrapped key to be returned.
> + *
> + * Qualcomm wrapped keys need to be generated in a trusted environment.
> + * A generate key  IOCTL call is used to achieve this. These are longterm
> + * in nature as they need to be generated and wrapped only once per
> + * requirement.
> + *
> + * This SCM calls adds support for the generate key IOCTL to interface
> + * with the secure environment to generate and return a wrapped key..
> + *
> + * Return: 0 on success; -errno on failure.
> + */
> +int qcom_scm_generate_ice_key(u8 *longterm_wrapped_key,
> +			    u32 longterm_wrapped_key_size)

Isn't longterm_wrapped_key_size really a maximum size?  How does this function
indicate the size of the resulting key?

> +/**
> + * qcom_scm_prepare_ice_key() - Get per boot ephemeral wrapped key
> + * @longterm_wrapped_key: the wrapped key
> + * @longterm_wrapped_key_size: size of the wrapped key
> + * @ephemeral_wrapped_key: ephemeral wrapped key to be returned
> + * @ephemeral_wrapped_key_size: size of the ephemeral wrapped key
> + *
> + * Qualcomm wrapped keys (longterm keys) are rewrapped with a per-boot
> + * ephemeral key for added protection. These are ephemeral in nature as
> + * they are valid only for that boot. A create key IOCTL is used to
> + * achieve this. These are the keys that are installed into the kernel
> + * to be then unwrapped and programmed into ICE.
> + *
> + * This SCM call adds support for the create key IOCTL to interface
> + * with the secure environment to rewrap the wrapped key with an
> + * ephemeral wrapping key.
> + *
> + * Return: 0 on success; -errno on failure.
> + */
> +int qcom_scm_prepare_ice_key(const u8 *longterm_wrapped_key,
> +			     u32 longterm_wrapped_key_size,
> +			     u8 *ephemeral_wrapped_key,
> +			     u32 ephemeral_wrapped_key_size)

Similarly here.  Isn't ephemeral_wrapped_key_size really a maximum size?  How
does this function indicate the size of the resulting ephemeral wrapped key?

> +/**
> + * qcom_scm_import_ice_key() - Import a wrapped key for encryption
> + * @imported_key: the raw key that is imported
> + * @imported_key_size: size of the key to be imported

imported_key and imported_key_size should be called raw_key and raw_key_size.

> + * @longterm_wrapped_key: the wrapped key to be returned
> + * @longterm_wrapped_key_size: size of the wrapped key
> + *
> + * Conceptually, this is very similar to generate, the difference being,
> + * here we want to import a raw key and return a longterm wrapped key
> + * from it. THe same create key IOCTL is used to achieve this.
> + *
> + * This SCM call adds support for the create key IOCTL to interface with
> + * the secure environment to import a raw key and generate a longterm
> + * wrapped key.
> + *
> + * Return: 0 on success; -errno on failure.
> + */
> +int qcom_scm_import_ice_key(const u8 *imported_key, u32 imported_key_size,
> +			    u8 *longterm_wrapped_key,
> +			    u32 longterm_wrapped_key_size)

And likewise, isn't longterm_wrapped_key_size really a maximum size?  How does
this function indicate the size of the resulting key?

> diff --git a/drivers/firmware/qcom_scm.h b/drivers/firmware/qcom_scm.h
> index 08bb2a4c80db..efd0ede1fb37 100644
> --- a/drivers/firmware/qcom_scm.h
> +++ b/drivers/firmware/qcom_scm.h
> @@ -111,6 +111,9 @@ extern int scm_legacy_call(struct device *dev, const struct qcom_scm_desc *desc,
>  #define QCOM_SCM_ES_INVALIDATE_ICE_KEY	0x03
>  #define QCOM_SCM_ES_CONFIG_SET_ICE_KEY	0x04
>  #define QCOM_SCM_ES_DERIVE_SW_SECRET	0x07
> +#define QCOM_SCM_ES_GENERATE_ICE_KEY	0x08
> +#define QCOM_SCM_ES_PREPARE_ICE_KEY	0x09
> +#define QCOM_SCM_ES_IMPORT_ICE_KEY	0xA

Writing "0xA" here looks weird.  It should be "0x0A" to match the others.

- Eric
Gaurav Kashyap Jan. 6, 2022, 9:14 p.m. UTC | #5
Hey Eric

Have you tested that QCOM_SCM_ES_DERIVE_SW_SECRET is working properly?

- You will need updated trustzone build for that (as I was missing a minor detail in the original one pertaining to SW secret) , please request again on the same ticket for the updated build.
- I have reminded the people in Qualcomm too to provide you the build.
- Note that with the new build you should be using the correct directions, i.e QCOM_SCM_RO where intended

Warm Regards
Gaurav Kashyap

-----Original Message-----
From: Eric Biggers <ebiggers@kernel.org> 
Sent: Thursday, January 6, 2022 11:48 AM
To: Gaurav Kashyap (QUIC) <quic_gaurkash@quicinc.com>
Cc: linux-scsi@vger.kernel.org; linux-arm-msm@vger.kernel.org; linux-mmc@vger.kernel.org; linux-block@vger.kernel.org; linux-fscrypt@vger.kernel.org; thara.gopinath@linaro.org; Neeraj Soni (QUIC) <quic_neersoni@quicinc.com>; Dinesh Garg <dineshg@quicinc.com>
Subject: Re: [PATCH 00/10] Add wrapped key support for Qualcomm ICE

WARNING: This email originated from outside of Qualcomm. Please be wary of any links or attachments, and do not enable macros.

Hi Gaurav,

On Mon, Dec 06, 2021 at 02:57:15PM -0800, Gaurav Kashyap wrote:
> Testing:
> Test platform: SM8350 HDK/MTP
> Engineering trustzone image (based on sm8350) is required to test this 
> feature. This is because of version changes of HWKM.
> HWKM version 2 and moving forward has a lot of restrictions on the key 
> management due to which the launched SM8350 solution (based on v1) 
> cannot be used and some modifications are required in trustzone.

I've been trying to test this patchset on a SM8350 HDK using the TrustZone image you provided, but it's not completely working yet.

This is the kernel branch I'm using:
https://git.kernel.org/pub/scm/fs/fscrypt/fscrypt.git/log/?h=wip-wrapped-keys.
It has my v4 patchset with your patchset rebased on top of it, some qcom_scm.c fixes I had to make (see below), and some extra logging messages.

This is how I'm building and booting a kernel on the board:
https://github.com/ebiggers/fscryptctl/blob/wip-wrapped-keys/scripts/sm8350-buildkernel.sh

And this is the test script I'm running:
https://github.com/ebiggers/fscryptctl/blob/wip-wrapped-keys/scripts/wrappedkey-test.sh.
It imports or generates a hardware-wrapped key, then tries to set up a directory
on an ext4 filesystem that is encrypted with that key.   This uses new
'fscryptctl' commands to access the new blk-crypto ioctls; the version of 'fscryptctl' on the branch the scripts are on has all the needed changes.

QCOM_SCM_ES_IMPORT_ICE_KEY, QCOM_SCM_ES_GENERATE_ICE_KEY, QCOM_SCM_ES_PREPARE_ICE_KEY, all seem to work.  However, QCOM_SCM_ES_DERIVE_SW_SECRET doesn't work; it always returns -EINVAL.

For example:

Importing hardware-wrapped key
[  187.606109] blk-crypto: entering BLKCRYPTOCREATEKEY [  187.611648] calling QCOM_SCM_ES_IMPORT_ICE_KEY; raw_key=5858585858585858585858585858585858585858585858585858585858585858
[  187.628180] QCOM_SCM_ES_IMPORT_ICE_KEY succeeded; longterm_wrapped_key=fab51aa07fb6c2bf2fea60a8120e8d35a9e53865b594e0fb6279e7951a34864591f1c1c4e26f9421039377c1ac311ff9241a0152030000000000000000000000
[  187.646433] blk-crypto: exiting BLKCRYPTOCREATEKEY; ret=0 Preparing hardware-wrapped key [  187.653129] blk-crypto: entering BLKCRYPTOPREPAREKEY [  187.660356] calling QCOM_SCM_ES_PREPARE_ICE_KEY; longterm_wrapped_key=fab51aa07fb6c2bf2fea60a8120e8d35a9e53865b594e0fb6279e7951a34864591f1c1c4e26f9421039377c1ac311ff9241a0152030000000000000000000000
[  187.680420] QCOM_SCM_ES_PREPARE_ICE_KEY succeeded; ephemeral_wrapped_key=1fbf5d501854858d6faaf52c9d22bebc576012e40485ba75e7d19e88f74b3400eb1a8836e28232939e990df6007659b1241a0152030000000000000000000000
[  187.698791] blk-crypto: exiting BLKCRYPTOPREPAREKEY; ret=0 Adding hardware-wrapped key [  187.705515] calling blk_crypto_derive_sw_secret(); wrapped_key_size=68 [  187.714075] in qti_ice_derive_sw_secret() [  187.718212] calling qti_ice_hwkm_init() [  187.722157] calling qti_ice_hwkm_init_sequence(version=1)
[  187.727715] setting standard mode
[  187.731134] checking BIST status
[  187.734464] configuring ICE registers [  187.738230] disabling CRC check [  187.741479] setting RSP_FIFO_FULL bit [  187.745247] calling qcom_scm_derive_sw_secret() [  187.749920] calling QCOM_SCM_ES_DERIVE_SW_SECRET; wrapped_key=1fbf5d501854858d6faaf52c9d22bebc576012e40485ba75e7d19e88f74b3400eb1a8836e28232939e990df6007659b1241a0152030000000000000000000000, secret_size=32 [  187.768834] QCOM_SCM_ES_DERIVE_SW_SECRET failed with error -22 [  187.774838] blk_crypto_derive_sw_secret() returned -22
error: adding key to /mnt: Invalid argument


You can see that the wrapped_key being passed to QCOM_SCM_ES_DERIVE_SW_SECRET matches the ephemeral_wrapped_key that was returned earlier by QCOM_SCM_ES_PREPARE_ICE_KEY, and that secret_size is 32.  So the arguments are as expected.  However, QCOM_SCM_ES_DERIVE_SW_SECRET still fails.

This still occurs if QCOM_SCM_ES_GENERATE_ICE_KEY is used instead of QCOM_SCM_ES_IMPORT_ICE_KEY.

Have you tested that QCOM_SCM_ES_DERIVE_SW_SECRET is working properly?

For reference, these are the fixes I had to apply to qcom_scm.c to get things working until that point.  This included fixing the direction of the first arguments to the SCM calls, and fixing the return values.  Note, I also tested leaving QCOM_SCM_ES_DERIVE_SW_SECRET using QCOM_SCM_RO instead of QCOM_SCM_RW, but the result was still the same --- it still returned -EINVAL.

diff --git a/drivers/firmware/qcom_scm.c b/drivers/firmware/qcom_scm.c index d57f52015640..002b57a1473d 100644
--- a/drivers/firmware/qcom_scm.c
+++ b/drivers/firmware/qcom_scm.c
@@ -1087,7 +1087,7 @@ int qcom_scm_derive_sw_secret(const u8 *wrapped_key, u32 wrapped_key_size,
        struct qcom_scm_desc desc = {
                .svc = QCOM_SCM_SVC_ES,
                .cmd =  QCOM_SCM_ES_DERIVE_SW_SECRET,
-               .arginfo = QCOM_SCM_ARGS(4, QCOM_SCM_RO,
+               .arginfo = QCOM_SCM_ARGS(4, QCOM_SCM_RW,
                                         QCOM_SCM_VAL, QCOM_SCM_RW,
                                         QCOM_SCM_VAL),
                .args[1] = wrapped_key_size, @@ -1148,7 +1148,7 @@ EXPORT_SYMBOL(qcom_scm_derive_sw_secret);
  * This SCM calls adds support for the generate key IOCTL to interface
  * with the secure environment to generate and return a wrapped key..
  *
- * Return: 0 on success; -errno on failure.
+ * Return: size of the resulting key on success; -errno on failure.
  */
 int qcom_scm_generate_ice_key(u8 *longterm_wrapped_key,
                            u32 longterm_wrapped_key_size) @@ -1188,7 +1188,7 @@ int qcom_scm_generate_ice_key(u8 *longterm_wrapped_key,
        dma_free_coherent(__scm->dev, longterm_wrapped_key_size,
                          longterm_wrapped_keybuf, longterm_wrapped_key_phys);

-       return ret;
+       return ret ?: longterm_wrapped_key_size;
 }
 EXPORT_SYMBOL(qcom_scm_generate_ice_key);

@@ -1209,7 +1209,7 @@ EXPORT_SYMBOL(qcom_scm_generate_ice_key);
  * with the secure environment to rewrap the wrapped key with an
  * ephemeral wrapping key.
  *
- * Return: 0 on success; -errno on failure.
+ * Return: size of the resulting key on success; -errno on failure.
  */
 int qcom_scm_prepare_ice_key(const u8 *longterm_wrapped_key,
                             u32 longterm_wrapped_key_size, @@ -1219,7 +1219,7 @@ int qcom_scm_prepare_ice_key(const u8 *longterm_wrapped_key,
        struct qcom_scm_desc desc = {
                .svc = QCOM_SCM_SVC_ES,
                .cmd =  QCOM_SCM_ES_PREPARE_ICE_KEY,
-               .arginfo = QCOM_SCM_ARGS(4, QCOM_SCM_RO,
+               .arginfo = QCOM_SCM_ARGS(4, QCOM_SCM_RW,
                                         QCOM_SCM_VAL, QCOM_SCM_RW,
                                         QCOM_SCM_VAL),
                .args[1] = longterm_wrapped_key_size, @@ -1270,7 +1270,7 @@ int qcom_scm_prepare_ice_key(const u8 *longterm_wrapped_key,
        dma_free_coherent(__scm->dev, longterm_wrapped_key_size,
                          longterm_wrapped_keybuf, longterm_wrapped_key_phys);

-       return ret;
+       return ret ?: ephemeral_wrapped_key_size;
 }
 EXPORT_SYMBOL(qcom_scm_prepare_ice_key);

@@ -1289,7 +1289,7 @@ EXPORT_SYMBOL(qcom_scm_prepare_ice_key);
  * the secure environment to import a raw key and generate a longterm
  * wrapped key.
  *
- * Return: 0 on success; -errno on failure.
+ * Return: size of the resulting key on success; -errno on failure.
  */
 int qcom_scm_import_ice_key(const u8 *imported_key, u32 imported_key_size,
                            u8 *longterm_wrapped_key, @@ -1298,7 +1298,7 @@ int qcom_scm_import_ice_key(const u8 *imported_key, u32 imported_key_size,
        struct qcom_scm_desc desc = {
                .svc = QCOM_SCM_SVC_ES,
                .cmd =  QCOM_SCM_ES_IMPORT_ICE_KEY,
-               .arginfo = QCOM_SCM_ARGS(4, QCOM_SCM_RO,
+               .arginfo = QCOM_SCM_ARGS(4, QCOM_SCM_RW,
                                         QCOM_SCM_VAL, QCOM_SCM_RW,
                                         QCOM_SCM_VAL),
                .args[1] = imported_key_size, @@ -1344,7 +1344,7 @@ int qcom_scm_import_ice_key(const u8 *imported_key, u32 imported_key_size,
        dma_free_coherent(__scm->dev, imported_key_size, imported_keybuf,
                          imported_key_phys);

-       return ret;
+       return ret ?: longterm_wrapped_key_size;
 }
 EXPORT_SYMBOL(qcom_scm_import_ice_key);
Eric Biggers Jan. 27, 2022, 12:51 a.m. UTC | #6
Hi Gaurav,

On Thu, Jan 06, 2022 at 09:14:22PM +0000, Gaurav Kashyap wrote:
> Hey Eric
> 
> > Have you tested that QCOM_SCM_ES_DERIVE_SW_SECRET is working properly?
> 
> - You will need updated trustzone build for that (as I was missing a minor detail in the original one pertaining to SW secret) , please request again on the same ticket for the updated build.
> - I have reminded the people in Qualcomm too to provide you the build.
> - Note that with the new build you should be using the correct directions, i.e QCOM_SCM_RO where intended
> 
> Warm Regards
> Gaurav Kashyap
> 

I verified that the latest TrustZone build is working; thanks for the help!

Note, these are the branches I'm using for now:

  * Kernel patches: https://git.kernel.org/pub/scm/fs/fscrypt/fscrypt.git/log/?h=wip-wrapped-keys
  * fscryptctl tool and test scripts: https://github.com/ebiggers/fscryptctl/tree/wip-wrapped-keys

The kernel branch is based on v5.17-rc1.  I haven't changed your patches from
your latest series other than rebasing them and adding a fix
"qcom_scm: fix return values" on top.

Note that v5.16-rc5 and later have a bug where the UFS controller on SM8350
isn't recognized.  Therefore, my branch contains a fix from Bjorn Andersson for
that bug, which I applied from the mailing list.

One oddity I noticed is that if I import the same raw key twice, the long-term
wrapped key blob is the same.  This implies that the key encryption algorithm
used by the Qualcomm hardware is deterministic, which is unexpected.  I would
expect the wrapped key to contain a random nonce.  Do you know why deterministic
encryption is used?  This probably isn't much of a problem, but it's unexpected.

Besides that, I think the next steps are for you to address the comments I've
left on this series, and for me to get started on adding ciphertext verification
tests for this to xfstests (alongside the other fscrypt ciphertext verification
tests that are already there) so that we can prove this feature is actually
encrypting the data as intended.

- Eric