Message ID | 20240213171334.30479-16-James.Bottomley@HansenPartnership.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | add integrity and security to TPM2 transactions | expand |
On Tue Feb 13, 2024 at 7:13 PM EET, 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 immediatly 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 > --- > 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 9eb420d8e530..9d6da0c9652f 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" > @@ -110,7 +122,23 @@ struct tpm2_auth { > /* scratch for key + IV */ > u8 scratch[AES_KEYBYTES + 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 passphraselen; > + struct crypto_aes_ctx aes_ctx; > + /* saved session attributes */ nit: "Saved session attributes:" This makes just dead obvious that what follows are what we are referring to. > + u8 attrs; > + __be32 ordinal; > /* 3 names of handles: name_h is handle, name is name of handle */ > u32 name_h[AUTH_MAX_NAMES]; > u8 name[AUTH_MAX_NAMES][2 + SHA512_DIGEST_SIZE]; > @@ -306,6 +334,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 ~ A At least in the documentation these start with capital letter. > + * @chip: the TPM chip structure > + * @buf: The buffer to be appended > + * @attributes: The session attributes > + * @passphrase: The session authority (NULL if none) > + * @passphraselen: The length of the session authority (0 if none) passphrase_len > + * > + * 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 passphraselen) > +{ > + u8 nonce[SHA256_DIGEST_SIZE]; > + u32 len; > + struct tpm2_auth *auth = chip->auth; re-order > + > + /* > + * The Architecture Guide requires us to strip trailing zeros > + * before computing the HMAC > + */ > + while (passphrase && passphraselen > 0 > + && passphrase[passphraselen - 1] == '\0') > + passphraselen--; > + > + auth->attrs = attributes; > + auth->passphraselen = passphraselen; > + if (passphraselen) > + memcpy(auth->passphrase, passphrase, passphraselen); > + > + 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; definitely re-order > + > + /* 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 */ > + KDFa(auth->session_key, SHA256_DIGEST_SIZE > + + auth->passphraselen, "CFB", auth->our_nonce, > + auth->tpm_nonce, AES_KEYBYTES + AES_BLOCK_SIZE, > + auth->scratch); > + > + len = tpm_buf_read_u16(buf, &offset_p); > + aes_expandkey(&auth->aes_ctx, auth->scratch, AES_KEYBYTES); > + aescfb_encrypt(&auth->aes_ctx, &buf->data[offset_p], > + &buf->data[offset_p], len, > + auth->scratch + AES_KEYBYTES); > + /* 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 */ > + hmac_init(&sctx, auth->session_key, sizeof(auth->session_key) > + + auth->passphraselen); > + 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); > + hmac_final(&sctx, auth->session_key, sizeof(auth->session_key) > + + auth->passphraselen, 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; > @@ -403,6 +655,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; ditto > + > + 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 */ > + hmac_init(&sctx, auth->session_key, sizeof(auth->session_key) > + + auth->passphraselen); > + 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 */ > + hmac_final(&sctx, auth->session_key, sizeof(auth->session_key) > + + auth->passphraselen, 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 */ > + KDFa(auth->session_key, SHA256_DIGEST_SIZE > + + auth->passphraselen, "CFB", auth->tpm_nonce, > + auth->our_nonce, AES_KEYBYTES + AES_BLOCK_SIZE, > + auth->scratch); OK, since there is multiple patches with KDF* dep the KDF functions should be their own separate patch not glued into any patch that uses them. > + > + len = tpm_buf_read_u16(buf, &offset_p); > + aes_expandkey(&auth->aes_ctx, auth->scratch, AES_KEYBYTES); > + aescfb_decrypt(&auth->aes_ctx, &buf->data[offset_p], > + &buf->data[offset_p], len, > + auth->scratch + AES_KEYBYTES); > + } > + > + 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 9b0b0d3c0d62..9c608fac8935 100644 > --- a/include/linux/tpm.h > +++ b/include/linux/tpm.h > @@ -490,8 +490,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; > @@ -507,6 +524,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 BR, Jarkko
> OK, since there is multiple patches with KDF* dep the KDF functions > should be their own separate patch not glued into any patch that uses > them. Well, I can do that if you insist, but it can't go into the kernel like that because we'll then have a static function with no consumers which will generate an unused function warning and break the build on - Werror. James
diff --git a/drivers/char/tpm/tpm2-sessions.c b/drivers/char/tpm/tpm2-sessions.c index 9eb420d8e530..9d6da0c9652f 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" @@ -110,7 +122,23 @@ struct tpm2_auth { /* scratch for key + IV */ u8 scratch[AES_KEYBYTES + 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 passphraselen; + struct crypto_aes_ctx aes_ctx; + /* saved session attributes */ + u8 attrs; + __be32 ordinal; /* 3 names of handles: name_h is handle, name is name of handle */ u32 name_h[AUTH_MAX_NAMES]; u8 name[AUTH_MAX_NAMES][2 + SHA512_DIGEST_SIZE]; @@ -306,6 +334,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) + * @passphraselen: 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 passphraselen) +{ + 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 && passphraselen > 0 + && passphrase[passphraselen - 1] == '\0') + passphraselen--; + + auth->attrs = attributes; + auth->passphraselen = passphraselen; + if (passphraselen) + memcpy(auth->passphrase, passphrase, passphraselen); + + 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 */ + KDFa(auth->session_key, SHA256_DIGEST_SIZE + + auth->passphraselen, "CFB", auth->our_nonce, + auth->tpm_nonce, AES_KEYBYTES + AES_BLOCK_SIZE, + auth->scratch); + + len = tpm_buf_read_u16(buf, &offset_p); + aes_expandkey(&auth->aes_ctx, auth->scratch, AES_KEYBYTES); + aescfb_encrypt(&auth->aes_ctx, &buf->data[offset_p], + &buf->data[offset_p], len, + auth->scratch + AES_KEYBYTES); + /* 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 */ + hmac_init(&sctx, auth->session_key, sizeof(auth->session_key) + + auth->passphraselen); + 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); + hmac_final(&sctx, auth->session_key, sizeof(auth->session_key) + + auth->passphraselen, 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; @@ -403,6 +655,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 */ + hmac_init(&sctx, auth->session_key, sizeof(auth->session_key) + + auth->passphraselen); + 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 */ + hmac_final(&sctx, auth->session_key, sizeof(auth->session_key) + + auth->passphraselen, 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 */ + KDFa(auth->session_key, SHA256_DIGEST_SIZE + + auth->passphraselen, "CFB", auth->tpm_nonce, + auth->our_nonce, AES_KEYBYTES + AES_BLOCK_SIZE, + auth->scratch); + + len = tpm_buf_read_u16(buf, &offset_p); + aes_expandkey(&auth->aes_ctx, auth->scratch, AES_KEYBYTES); + aescfb_decrypt(&auth->aes_ctx, &buf->data[offset_p], + &buf->data[offset_p], len, + auth->scratch + AES_KEYBYTES); + } + + 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 9b0b0d3c0d62..9c608fac8935 100644 --- a/include/linux/tpm.h +++ b/include/linux/tpm.h @@ -490,8 +490,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; @@ -507,6 +524,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