Re: [PATCH v8 16/22] tpm: Add the rest of the session HMAC API

Jarkko Sakkinen
2024-04-29T22:39:22Z

On Mon Apr 29, 2024 at 11:28 PM EEST, James Bottomley wrote:
> The final pieces of the HMAC API are for manipulating the session area
> of the command. To add an authentication HMAC session
> tpm_buf_append_hmac_session() is called where tpm2_append_auth() would
> go. If a non empty password is passed in, this is correctly added to
> the HMAC to prove knowledge of it without revealing it. Note that if
> the session is only used to encrypt or decrypt parameters (no
> authentication) then tpm_buf_append_hmac_session_opt() must be used
> instead. This functions identically to tpm_buf_append_hmac_session()
> when TPM_BUS_SECURITY is enabled, but differently when it isn't,
> because effectively nothing is appended to the session area.
>
> Next the parameters should be filled in for the command and finally
> tpm_buf_fill_hmac_session() is called immediately prior to transmitting
> the command which computes the correct HMAC and places it in the
> command at the session location in the tpm buffer
>
> Finally, after tpm_transmit_cmd() is called,
> tpm_buf_check_hmac_response() is called to check that the returned
> HMAC matched and collect the new state for the next use of the
> session, if any.
>
> The features of the session are controlled by the session attributes
> set in tpm_buf_append_hmac_session(). If TPM2_SA_CONTINUE_SESSION is
> not specified, the session will be flushed and the tpm2_auth structure
> freed in tpm_buf_check_hmac_response(); otherwise the session may be
> used again. Parameter encryption is specified by or'ing the flag
> TPM2_SA_DECRYPT and response encryption by or'ing the flag
> TPM2_SA_ENCRYPT. the various encryptions will be taken care of by
> tpm_buf_fill_hmac_session() and tpm_buf_check_hmac_response()
> respectively.
>
> Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
> Reviewed-by: Ard Biesheuvel <ard.biesheuvel@linaro.org> # crypto API parts
>
> ---
>
> v6: split into new patch, update config variable
> v7: add memzero_explicit
> v8: minor updates
> ---
> drivers/char/tpm/tpm2-sessions.c | 400 +++++++++++++++++++++++++++++++
> include/linux/tpm.h | 69 ++++++
> 2 files changed, 469 insertions(+)
>
> diff --git a/drivers/char/tpm/tpm2-sessions.c b/drivers/char/tpm/tpm2-sessions.c
> index 99eb048f18c8..3c97d3d5e00e 100644
> --- a/drivers/char/tpm/tpm2-sessions.c
> +++ b/drivers/char/tpm/tpm2-sessions.c
> @@ -54,6 +54,18 @@
> * handles because handles have to be processed specially when
> * calculating the HMAC. In particular, for NV, volatile and
> * permanent objects you now need to provide the name.
> + * tpm_buf_append_hmac_session() which appends the hmac session to the
> + * buf in the same way tpm_buf_append_auth does().
> + * tpm_buf_fill_hmac_session() This calculates the correct hash and
> + * places it in the buffer. It must be called after the complete
> + * command buffer is finalized so it can fill in the correct HMAC
> + * based on the parameters.
> + * tpm_buf_check_hmac_response() which checks the session response in
> + * the buffer and calculates what it should be. If there's a
> + * mismatch it will log a warning and return an error. If
> + * tpm_buf_append_hmac_session() did not specify
> + * TPM_SA_CONTINUE_SESSION then the session will be closed (if it
> + * hasn't been consumed) and the auth structure freed.
> */
>
> #include "tpm.h"
> @@ -103,7 +115,23 @@ struct tpm2_auth {
> /* scratch for key + IV */
> u8 scratch[AES_KEY_BYTES + AES_BLOCK_SIZE];
> };
> + /*
> + * the session key and passphrase are the same size as the
> + * name digest (sha256 again). The session key is constant
> + * for the use of the session and the passphrase can change
> + * with every invocation.
> + *
> + * Note: these fields must be adjacent and in this order
> + * because several HMAC/KDF schemes use the combination of the
> + * session_key and passphrase.
> + */
> u8 session_key[SHA256_DIGEST_SIZE];
> + u8 passphrase[SHA256_DIGEST_SIZE];
> + int passphrase_len;
> + struct crypto_aes_ctx aes_ctx;
> + /* saved session attributes: */
> + u8 attrs;
> + __be32 ordinal;
>
> /*
> * memory for three authorization handles. We know them by
> @@ -309,6 +337,230 @@ static void tpm_buf_append_salt(struct tpm_buf *buf, struct tpm_chip *chip)
> crypto_free_kpp(kpp);
> }
>
> +/**
> + * tpm_buf_append_hmac_session() - Append a TPM session element
> + * @chip: the TPM chip structure
> + * @buf: The buffer to be appended
> + * @attributes: The session attributes
> + * @passphrase: The session authority (NULL if none)
> + * @passphrase_len: The length of the session authority (0 if none)
> + *
> + * This fills in a session structure in the TPM command buffer, except
> + * for the HMAC which cannot be computed until the command buffer is
> + * complete. The type of session is controlled by the @attributes,
> + * the main ones of which are TPM2_SA_CONTINUE_SESSION which means the
> + * session won't terminate after tpm_buf_check_hmac_response(),
> + * TPM2_SA_DECRYPT which means this buffers first parameter should be
> + * encrypted with a session key and TPM2_SA_ENCRYPT, which means the
> + * response buffer's first parameter needs to be decrypted (confusing,
> + * but the defines are written from the point of view of the TPM).
> + *
> + * Any session appended by this command must be finalized by calling
> + * tpm_buf_fill_hmac_session() otherwise the HMAC will be incorrect
> + * and the TPM will reject the command.
> + *
> + * As with most tpm_buf operations, success is assumed because failure
> + * will be caused by an incorrect programming model and indicated by a
> + * kernel message.
> + */
> +void tpm_buf_append_hmac_session(struct tpm_chip *chip, struct tpm_buf *buf,
> + u8 attributes, u8 *passphrase,
> + int passphrase_len)
> +{
> + u8 nonce[SHA256_DIGEST_SIZE];
> + u32 len;
> + struct tpm2_auth *auth = chip->auth;
> +
> + /*
> + * The Architecture Guide requires us to strip trailing zeros
> + * before computing the HMAC
> + */
> + while (passphrase && passphrase_len > 0
> + && passphrase[passphrase_len - 1] == '\0')
> + passphrase_len--;
> +
> + auth->attrs = attributes;
> + auth->passphrase_len = passphrase_len;
> + if (passphrase_len)
> + memcpy(auth->passphrase, passphrase, passphrase_len);
> +
> + if (auth->session != tpm_buf_length(buf)) {
> + /* we're not the first session */
> + len = get_unaligned_be32(&buf->data[auth->session]);
> + if (4 + len + auth->session != tpm_buf_length(buf)) {
> + WARN(1, "session length mismatch, cannot append");
> + return;
> + }
> +
> + /* add our new session */
> + len += 9 + 2 * SHA256_DIGEST_SIZE;
> + put_unaligned_be32(len, &buf->data[auth->session]);
> + } else {
> + tpm_buf_append_u32(buf, 9 + 2 * SHA256_DIGEST_SIZE);
> + }
> +
> + /* random number for our nonce */
> + get_random_bytes(nonce, sizeof(nonce));
> + memcpy(auth->our_nonce, nonce, sizeof(nonce));
> + tpm_buf_append_u32(buf, auth->handle);
> + /* our new nonce */
> + tpm_buf_append_u16(buf, SHA256_DIGEST_SIZE);
> + tpm_buf_append(buf, nonce, SHA256_DIGEST_SIZE);
> + tpm_buf_append_u8(buf, auth->attrs);
> + /* and put a placeholder for the hmac */
> + tpm_buf_append_u16(buf, SHA256_DIGEST_SIZE);
> + tpm_buf_append(buf, nonce, SHA256_DIGEST_SIZE);
> +}
> +EXPORT_SYMBOL(tpm_buf_append_hmac_session);
> +
> +/**
> + * tpm_buf_fill_hmac_session() - finalize the session HMAC
> + * @chip: the TPM chip structure
> + * @buf: The buffer to be appended
> + *
> + * This command must not be called until all of the parameters have
> + * been appended to @buf otherwise the computed HMAC will be
> + * incorrect.
> + *
> + * This function computes and fills in the session HMAC using the
> + * session key and, if TPM2_SA_DECRYPT was specified, computes the
> + * encryption key and encrypts the first parameter of the command
> + * buffer with it.
> + *
> + * As with most tpm_buf operations, success is assumed because failure
> + * will be caused by an incorrect programming model and indicated by a
> + * kernel message.
> + */
> +void tpm_buf_fill_hmac_session(struct tpm_chip *chip, struct tpm_buf *buf)
> +{
> + u32 cc, handles, val;
> + struct tpm2_auth *auth = chip->auth;
> + int i;
> + struct tpm_header *head = (struct tpm_header *)buf->data;
> + off_t offset_s = TPM_HEADER_SIZE, offset_p;
> + u8 *hmac = NULL;
> + u32 attrs;
> + u8 cphash[SHA256_DIGEST_SIZE];
> + struct sha256_state sctx;
> +
> + /* save the command code in BE format */
> + auth->ordinal = head->ordinal;
> +
> + cc = be32_to_cpu(head->ordinal);
> +
> + i = tpm2_find_cc(chip, cc);
> + if (i < 0) {
> + dev_err(&chip->dev, "Command 0x%x not found in TPM\n", cc);
> + return;
> + }
> + attrs = chip->cc_attrs_tbl[i];
> +
> + handles = (attrs >> TPM2_CC_ATTR_CHANDLES) & GENMASK(2, 0);
> +
> + /*
> + * just check the names, it's easy to make mistakes. This
> + * would happen if someone added a handle via
> + * tpm_buf_append_u32() instead of tpm_buf_append_name()
> + */
> + for (i = 0; i < handles; i++) {
> + u32 handle = tpm_buf_read_u32(buf, &offset_s);
> +
> + if (auth->name_h[i] != handle) {
> + dev_err(&chip->dev, "TPM: handle %d wrong for name\n",
> + i);
> + return;
> + }
> + }
> + /* point offset_s to the start of the sessions */
> + val = tpm_buf_read_u32(buf, &offset_s);
> + /* point offset_p to the start of the parameters */
> + offset_p = offset_s + val;
> + for (i = 1; offset_s < offset_p; i++) {
> + u32 handle = tpm_buf_read_u32(buf, &offset_s);
> + u16 len;
> + u8 a;
> +
> + /* nonce (already in auth) */
> + len = tpm_buf_read_u16(buf, &offset_s);
> + offset_s += len;
> +
> + a = tpm_buf_read_u8(buf, &offset_s);
> +
> + len = tpm_buf_read_u16(buf, &offset_s);
> + if (handle == auth->handle && auth->attrs == a) {
> + hmac = &buf->data[offset_s];
> + /*
> + * save our session number so we know which
> + * session in the response belongs to us
> + */
> + auth->session = i;
> + }
> +
> + offset_s += len;
> + }
> + if (offset_s != offset_p) {
> + dev_err(&chip->dev, "TPM session length is incorrect\n");
> + return;
> + }
> + if (!hmac) {
> + dev_err(&chip->dev, "TPM could not find HMAC session\n");
> + return;
> + }
> +
> + /* encrypt before HMAC */
> + if (auth->attrs & TPM2_SA_DECRYPT) {
> + u16 len;
> +
> + /* need key and IV */
> + tpm2_KDFa(auth->session_key, SHA256_DIGEST_SIZE
> + + auth->passphrase_len, "CFB", auth->our_nonce,
> + auth->tpm_nonce, AES_KEY_BYTES + AES_BLOCK_SIZE,
> + auth->scratch);
> +
> + len = tpm_buf_read_u16(buf, &offset_p);
> + aes_expandkey(&auth->aes_ctx, auth->scratch, AES_KEY_BYTES);
> + aescfb_encrypt(&auth->aes_ctx, &buf->data[offset_p],
> + &buf->data[offset_p], len,
> + auth->scratch + AES_KEY_BYTES);
> + /* reset p to beginning of parameters for HMAC */
> + offset_p -= 2;
> + }
> +
> + sha256_init(&sctx);
> + /* ordinal is already BE */
> + sha256_update(&sctx, (u8 *)&head->ordinal, sizeof(head->ordinal));
> + /* add the handle names */
> + for (i = 0; i < handles; i++) {
> + enum tpm2_mso_type mso = tpm2_handle_mso(auth->name_h[i]);
> +
> + if (mso == TPM2_MSO_PERSISTENT ||
> + mso == TPM2_MSO_VOLATILE ||
> + mso == TPM2_MSO_NVRAM) {
> + sha256_update(&sctx, auth->name[i],
> + name_size(auth->name[i]));
> + } else {
> + __be32 h = cpu_to_be32(auth->name_h[i]);
> +
> + sha256_update(&sctx, (u8 *)&h, 4);
> + }
> + }
> + if (offset_s != tpm_buf_length(buf))
> + sha256_update(&sctx, &buf->data[offset_s],
> + tpm_buf_length(buf) - offset_s);
> + sha256_final(&sctx, cphash);
> +
> + /* now calculate the hmac */
> + tpm2_hmac_init(&sctx, auth->session_key, sizeof(auth->session_key)
> + + auth->passphrase_len);
> + sha256_update(&sctx, cphash, sizeof(cphash));
> + sha256_update(&sctx, auth->our_nonce, sizeof(auth->our_nonce));
> + sha256_update(&sctx, auth->tpm_nonce, sizeof(auth->tpm_nonce));
> + sha256_update(&sctx, &auth->attrs, 1);
> + tpm2_hmac_final(&sctx, auth->session_key, sizeof(auth->session_key)
> + + auth->passphrase_len, hmac);
> +}
> +EXPORT_SYMBOL(tpm_buf_fill_hmac_session);
> +
> static int tpm2_parse_read_public(char *name, struct tpm_buf *buf)
> {
> struct tpm_header *head = (struct tpm_header *)buf->data;
> @@ -406,6 +658,154 @@ void tpm_buf_append_name(struct tpm_chip *chip, struct tpm_buf *buf,
> memcpy(auth->name[slot], name, name_size(name));
> }
> EXPORT_SYMBOL(tpm_buf_append_name);
> +
> +/**
> + * tpm_buf_check_hmac_response() - check the TPM return HMAC for correctness
> + * @chip: the TPM chip structure
> + * @buf: the original command buffer (which now contains the response)
> + * @rc: the return code from tpm_transmit_cmd
> + *
> + * If @rc is non zero, @buf may not contain an actual return, so @rc
> + * is passed through as the return and the session cleaned up and
> + * de-allocated if required (this is required if
> + * TPM2_SA_CONTINUE_SESSION was not specified as a session flag).
> + *
> + * If @rc is zero, the response HMAC is computed against the returned
> + * @buf and matched to the TPM one in the session area. If there is a
> + * mismatch, an error is logged and -EINVAL returned.
> + *
> + * The reason for this is that the command issue and HMAC check
> + * sequence should look like:
> + *
> + * rc = tpm_transmit_cmd(...);
> + * rc = tpm_buf_check_hmac_response(&buf, auth, rc);
> + * if (rc)
> + * ...
> + *
> + * Which is easily layered into the current contrl flow.
> + *
> + * Returns: 0 on success or an error.
> + */
> +int tpm_buf_check_hmac_response(struct tpm_chip *chip, struct tpm_buf *buf,
> + int rc)
> +{
> + struct tpm_header *head = (struct tpm_header *)buf->data;
> + struct tpm2_auth *auth = chip->auth;
> + off_t offset_s, offset_p;
> + u8 rphash[SHA256_DIGEST_SIZE];
> + u32 attrs;
> + struct sha256_state sctx;
> + u16 tag = be16_to_cpu(head->tag);
> + u32 cc = be32_to_cpu(auth->ordinal);
> + int parm_len, len, i, handles;
> +
> + if (auth->session >= TPM_HEADER_SIZE) {
> + WARN(1, "tpm session not filled correctly\n");
> + goto out;
> + }
> +
> + if (rc != 0)
> + /* pass non success rc through and close the session */
> + goto out;
> +
> + rc = -EINVAL;
> + if (tag != TPM2_ST_SESSIONS) {
> + dev_err(&chip->dev, "TPM: HMAC response check has no sessions tag\n");
> + goto out;
> + }
> +
> + i = tpm2_find_cc(chip, cc);
> + if (i < 0)
> + goto out;
> + attrs = chip->cc_attrs_tbl[i];
> + handles = (attrs >> TPM2_CC_ATTR_RHANDLE) & 1;
> +
> + /* point to area beyond handles */
> + offset_s = TPM_HEADER_SIZE + handles * 4;
> + parm_len = tpm_buf_read_u32(buf, &offset_s);
> + offset_p = offset_s;
> + offset_s += parm_len;
> + /* skip over any sessions before ours */
> + for (i = 0; i < auth->session - 1; i++) {
> + len = tpm_buf_read_u16(buf, &offset_s);
> + offset_s += len + 1;
> + len = tpm_buf_read_u16(buf, &offset_s);
> + offset_s += len;
> + }
> + /* TPM nonce */
> + len = tpm_buf_read_u16(buf, &offset_s);
> + if (offset_s + len > tpm_buf_length(buf))
> + goto out;
> + if (len != SHA256_DIGEST_SIZE)
> + goto out;
> + memcpy(auth->tpm_nonce, &buf->data[offset_s], len);
> + offset_s += len;
> + attrs = tpm_buf_read_u8(buf, &offset_s);
> + len = tpm_buf_read_u16(buf, &offset_s);
> + if (offset_s + len != tpm_buf_length(buf))
> + goto out;
> + if (len != SHA256_DIGEST_SIZE)
> + goto out;
> + /*
> + * offset_s points to the HMAC. now calculate comparison, beginning
> + * with rphash
> + */
> + sha256_init(&sctx);
> + /* yes, I know this is now zero, but it's what the standard says */
> + sha256_update(&sctx, (u8 *)&head->return_code,
> + sizeof(head->return_code));
> + /* ordinal is already BE */
> + sha256_update(&sctx, (u8 *)&auth->ordinal, sizeof(auth->ordinal));
> + sha256_update(&sctx, &buf->data[offset_p], parm_len);
> + sha256_final(&sctx, rphash);
> +
> + /* now calculate the hmac */
> + tpm2_hmac_init(&sctx, auth->session_key, sizeof(auth->session_key)
> + + auth->passphrase_len);
> + sha256_update(&sctx, rphash, sizeof(rphash));
> + sha256_update(&sctx, auth->tpm_nonce, sizeof(auth->tpm_nonce));
> + sha256_update(&sctx, auth->our_nonce, sizeof(auth->our_nonce));
> + sha256_update(&sctx, &auth->attrs, 1);
> + /* we're done with the rphash, so put our idea of the hmac there */
> + tpm2_hmac_final(&sctx, auth->session_key, sizeof(auth->session_key)
> + + auth->passphrase_len, rphash);
> + if (memcmp(rphash, &buf->data[offset_s], SHA256_DIGEST_SIZE) == 0) {
> + rc = 0;
> + } else {
> + dev_err(&chip->dev, "TPM: HMAC check failed\n");
> + goto out;
> + }
> +
> + /* now do response decryption */
> + if (auth->attrs & TPM2_SA_ENCRYPT) {
> + /* need key and IV */
> + tpm2_KDFa(auth->session_key, SHA256_DIGEST_SIZE
> + + auth->passphrase_len, "CFB", auth->tpm_nonce,
> + auth->our_nonce, AES_KEY_BYTES + AES_BLOCK_SIZE,
> + auth->scratch);
> +
> + len = tpm_buf_read_u16(buf, &offset_p);
> + aes_expandkey(&auth->aes_ctx, auth->scratch, AES_KEY_BYTES);
> + aescfb_decrypt(&auth->aes_ctx, &buf->data[offset_p],
> + &buf->data[offset_p], len,
> + auth->scratch + AES_KEY_BYTES);
> + }
> +
> + out:
> + if ((auth->attrs & TPM2_SA_CONTINUE_SESSION) == 0) {
> + if (rc)
> + /* manually close the session if it wasn't consumed */
> + tpm2_flush_context(chip, auth->handle);
> + memzero_explicit(auth, sizeof(*auth));
> + } else {
> + /* reset for next use */
> + auth->session = TPM_HEADER_SIZE;
> + }
> +
> + return rc;
> +}
> +EXPORT_SYMBOL(tpm_buf_check_hmac_response);
> +
> /**
> * tpm2_end_auth_session() - kill the allocated auth session
> * @chip: the TPM chip structure
> diff --git a/include/linux/tpm.h b/include/linux/tpm.h
> index 31c2065fcd35..dd4d6a6158c4 100644
> --- a/include/linux/tpm.h
> +++ b/include/linux/tpm.h
> @@ -510,8 +510,25 @@ static inline void tpm_buf_append_empty_auth(struct tpm_buf *buf, u32 handle)
> int tpm2_start_auth_session(struct tpm_chip *chip);
> void tpm_buf_append_name(struct tpm_chip *chip, struct tpm_buf *buf,
> u32 handle, u8 *name);
> +void tpm_buf_append_hmac_session(struct tpm_chip *chip, struct tpm_buf *buf,
> + u8 attributes, u8 *passphrase,
> + int passphraselen);
> +static inline void tpm_buf_append_hmac_session_opt(struct tpm_chip *chip,
> + struct tpm_buf *buf,
> + u8 attributes,
> + u8 *passphrase,
> + int passphraselen)
> +{
> + tpm_buf_append_hmac_session(chip, buf, attributes, passphrase,
> + passphraselen);
> +}
> +void tpm_buf_fill_hmac_session(struct tpm_chip *chip, struct tpm_buf *buf);
> +int tpm_buf_check_hmac_response(struct tpm_chip *chip, struct tpm_buf *buf,
> + int rc);
> void tpm2_end_auth_session(struct tpm_chip *chip);
> #else
> +#include <asm/unaligned.h>
> +
> static inline int tpm2_start_auth_session(struct tpm_chip *chip)
> {
> return 0;
> @@ -527,6 +544,58 @@ static inline void tpm_buf_append_name(struct tpm_chip *chip,
> /* count the number of handles in the upper bits of flags */
> buf->handles++;
> }
> +static inline void tpm_buf_append_hmac_session(struct tpm_chip *chip,
> + struct tpm_buf *buf,
> + u8 attributes, u8 *passphrase,
> + int passphraselen)
> +{
> + /* offset tells us where the sessions area begins */
> + int offset = buf->handles * 4 + TPM_HEADER_SIZE;
> + u32 len = 9 + passphraselen;
> +
> + if (tpm_buf_length(buf) != offset) {
> + /* not the first session so update the existing length */
> + len += get_unaligned_be32(&buf->data[offset]);
> + put_unaligned_be32(len, &buf->data[offset]);
> + } else {
> + tpm_buf_append_u32(buf, len);
> + }
> + /* auth handle */
> + tpm_buf_append_u32(buf, TPM2_RS_PW);
> + /* nonce */
> + tpm_buf_append_u16(buf, 0);
> + /* attributes */
> + tpm_buf_append_u8(buf, 0);
> + /* passphrase */
> + tpm_buf_append_u16(buf, passphraselen);
> + tpm_buf_append(buf, passphrase, passphraselen);
> +}
> +static inline void tpm_buf_append_hmac_session_opt(struct tpm_chip *chip,
> + struct tpm_buf *buf,
> + u8 attributes,
> + u8 *passphrase,
> + int passphraselen)
> +{
> + int offset = buf->handles * 4 + TPM_HEADER_SIZE;
> + struct tpm_header *head = (struct tpm_header *) buf->data;
> +
> + /*
> + * if the only sessions are optional, the command tag
> + * must change to TPM2_ST_NO_SESSIONS
> + */
> + if (tpm_buf_length(buf) == offset)
> + head->tag = cpu_to_be16(TPM2_ST_NO_SESSIONS);
> +}
> +static inline void tpm_buf_fill_hmac_session(struct tpm_chip *chip,
> + struct tpm_buf *buf)
> +{
> +}
> +static inline int tpm_buf_check_hmac_response(struct tpm_chip *chip,
> + struct tpm_buf *buf,
> + int rc)
> +{
> + return rc;
> +}
> #endif /* CONFIG_TCG_TPM2_HMAC */
>
> #endif

Reviewed-by: Jarkko Sakkinen <jarkko@kernel.org>

BR, Jarkko