| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * This file is part of UBIFS. |
| * |
| * Copyright (C) 2018 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de> |
| */ |
| |
| /* |
| * This file implements various helper functions for UBIFS authentication support |
| */ |
| |
| #include <linux/crypto.h> |
| #include <linux/verification.h> |
| #include <crypto/hash.h> |
| #include <crypto/algapi.h> |
| #include <keys/user-type.h> |
| #include <keys/asymmetric-type.h> |
| |
| #include "ubifs.h" |
| |
| /** |
| * ubifs_node_calc_hash - calculate the hash of a UBIFS node |
| * @c: UBIFS file-system description object |
| * @node: the node to calculate a hash for |
| * @hash: the returned hash |
| * |
| * Returns 0 for success or a negative error code otherwise. |
| */ |
| int __ubifs_node_calc_hash(const struct ubifs_info *c, const void *node, |
| u8 *hash) |
| { |
| const struct ubifs_ch *ch = node; |
| |
| return crypto_shash_tfm_digest(c->hash_tfm, node, le32_to_cpu(ch->len), |
| hash); |
| } |
| |
| /** |
| * ubifs_hash_calc_hmac - calculate a HMAC from a hash |
| * @c: UBIFS file-system description object |
| * @hash: the node to calculate a HMAC for |
| * @hmac: the returned HMAC |
| * |
| * Returns 0 for success or a negative error code otherwise. |
| */ |
| static int ubifs_hash_calc_hmac(const struct ubifs_info *c, const u8 *hash, |
| u8 *hmac) |
| { |
| return crypto_shash_tfm_digest(c->hmac_tfm, hash, c->hash_len, hmac); |
| } |
| |
| /** |
| * ubifs_prepare_auth_node - Prepare an authentication node |
| * @c: UBIFS file-system description object |
| * @node: the node to calculate a hash for |
| * @inhash: input hash of previous nodes |
| * |
| * This function prepares an authentication node for writing onto flash. |
| * It creates a HMAC from the given input hash and writes it to the node. |
| * |
| * Returns 0 for success or a negative error code otherwise. |
| */ |
| int ubifs_prepare_auth_node(struct ubifs_info *c, void *node, |
| struct shash_desc *inhash) |
| { |
| struct ubifs_auth_node *auth = node; |
| u8 hash[UBIFS_HASH_ARR_SZ]; |
| int err; |
| |
| { |
| SHASH_DESC_ON_STACK(hash_desc, c->hash_tfm); |
| |
| hash_desc->tfm = c->hash_tfm; |
| ubifs_shash_copy_state(c, inhash, hash_desc); |
| |
| err = crypto_shash_final(hash_desc, hash); |
| if (err) |
| return err; |
| } |
| |
| err = ubifs_hash_calc_hmac(c, hash, auth->hmac); |
| if (err) |
| return err; |
| |
| auth->ch.node_type = UBIFS_AUTH_NODE; |
| ubifs_prepare_node(c, auth, ubifs_auth_node_sz(c), 0); |
| return 0; |
| } |
| |
| static struct shash_desc *ubifs_get_desc(const struct ubifs_info *c, |
| struct crypto_shash *tfm) |
| { |
| struct shash_desc *desc; |
| int err; |
| |
| if (!ubifs_authenticated(c)) |
| return NULL; |
| |
| desc = kmalloc(sizeof(*desc) + crypto_shash_descsize(tfm), GFP_KERNEL); |
| if (!desc) |
| return ERR_PTR(-ENOMEM); |
| |
| desc->tfm = tfm; |
| |
| err = crypto_shash_init(desc); |
| if (err) { |
| kfree(desc); |
| return ERR_PTR(err); |
| } |
| |
| return desc; |
| } |
| |
| /** |
| * __ubifs_hash_get_desc - get a descriptor suitable for hashing a node |
| * @c: UBIFS file-system description object |
| * |
| * This function returns a descriptor suitable for hashing a node. Free after use |
| * with kfree. |
| */ |
| struct shash_desc *__ubifs_hash_get_desc(const struct ubifs_info *c) |
| { |
| return ubifs_get_desc(c, c->hash_tfm); |
| } |
| |
| /** |
| * ubifs_bad_hash - Report hash mismatches |
| * @c: UBIFS file-system description object |
| * @node: the node |
| * @hash: the expected hash |
| * @lnum: the LEB @node was read from |
| * @offs: offset in LEB @node was read from |
| * |
| * This function reports a hash mismatch when a node has a different hash than |
| * expected. |
| */ |
| void ubifs_bad_hash(const struct ubifs_info *c, const void *node, const u8 *hash, |
| int lnum, int offs) |
| { |
| int len = min(c->hash_len, 20); |
| int cropped = len != c->hash_len; |
| const char *cont = cropped ? "..." : ""; |
| |
| u8 calc[UBIFS_HASH_ARR_SZ]; |
| |
| __ubifs_node_calc_hash(c, node, calc); |
| |
| ubifs_err(c, "hash mismatch on node at LEB %d:%d", lnum, offs); |
| ubifs_err(c, "hash expected: %*ph%s", len, hash, cont); |
| ubifs_err(c, "hash calculated: %*ph%s", len, calc, cont); |
| } |
| |
| /** |
| * __ubifs_node_check_hash - check the hash of a node against given hash |
| * @c: UBIFS file-system description object |
| * @node: the node |
| * @expected: the expected hash |
| * |
| * This function calculates a hash over a node and compares it to the given hash. |
| * Returns 0 if both hashes are equal or authentication is disabled, otherwise a |
| * negative error code is returned. |
| */ |
| int __ubifs_node_check_hash(const struct ubifs_info *c, const void *node, |
| const u8 *expected) |
| { |
| u8 calc[UBIFS_HASH_ARR_SZ]; |
| int err; |
| |
| err = __ubifs_node_calc_hash(c, node, calc); |
| if (err) |
| return err; |
| |
| if (ubifs_check_hash(c, expected, calc)) |
| return -EPERM; |
| |
| return 0; |
| } |
| |
| /** |
| * ubifs_sb_verify_signature - verify the signature of a superblock |
| * @c: UBIFS file-system description object |
| * @sup: The superblock node |
| * |
| * To support offline signed images the superblock can be signed with a |
| * PKCS#7 signature. The signature is placed directly behind the superblock |
| * node in an ubifs_sig_node. |
| * |
| * Returns 0 when the signature can be successfully verified or a negative |
| * error code if not. |
| */ |
| int ubifs_sb_verify_signature(struct ubifs_info *c, |
| const struct ubifs_sb_node *sup) |
| { |
| int err; |
| struct ubifs_scan_leb *sleb; |
| struct ubifs_scan_node *snod; |
| const struct ubifs_sig_node *signode; |
| |
| sleb = ubifs_scan(c, UBIFS_SB_LNUM, UBIFS_SB_NODE_SZ, c->sbuf, 0); |
| if (IS_ERR(sleb)) { |
| err = PTR_ERR(sleb); |
| return err; |
| } |
| |
| if (sleb->nodes_cnt == 0) { |
| ubifs_err(c, "Unable to find signature node"); |
| err = -EINVAL; |
| goto out_destroy; |
| } |
| |
| snod = list_first_entry(&sleb->nodes, struct ubifs_scan_node, list); |
| |
| if (snod->type != UBIFS_SIG_NODE) { |
| ubifs_err(c, "Signature node is of wrong type"); |
| err = -EINVAL; |
| goto out_destroy; |
| } |
| |
| signode = snod->node; |
| |
| if (le32_to_cpu(signode->len) > snod->len + sizeof(struct ubifs_sig_node)) { |
| ubifs_err(c, "invalid signature len %d", le32_to_cpu(signode->len)); |
| err = -EINVAL; |
| goto out_destroy; |
| } |
| |
| if (le32_to_cpu(signode->type) != UBIFS_SIGNATURE_TYPE_PKCS7) { |
| ubifs_err(c, "Signature type %d is not supported\n", |
| le32_to_cpu(signode->type)); |
| err = -EINVAL; |
| goto out_destroy; |
| } |
| |
| err = verify_pkcs7_signature(sup, sizeof(struct ubifs_sb_node), |
| signode->sig, le32_to_cpu(signode->len), |
| NULL, VERIFYING_UNSPECIFIED_SIGNATURE, |
| NULL, NULL); |
| |
| if (err) |
| ubifs_err(c, "Failed to verify signature"); |
| else |
| ubifs_msg(c, "Successfully verified super block signature"); |
| |
| out_destroy: |
| ubifs_scan_destroy(sleb); |
| |
| return err; |
| } |
| |
| /** |
| * ubifs_init_authentication - initialize UBIFS authentication support |
| * @c: UBIFS file-system description object |
| * |
| * This function returns 0 for success or a negative error code otherwise. |
| */ |
| int ubifs_init_authentication(struct ubifs_info *c) |
| { |
| struct key *keyring_key; |
| const struct user_key_payload *ukp; |
| int err; |
| char hmac_name[CRYPTO_MAX_ALG_NAME]; |
| |
| if (!c->auth_hash_name) { |
| ubifs_err(c, "authentication hash name needed with authentication"); |
| return -EINVAL; |
| } |
| |
| c->auth_hash_algo = match_string(hash_algo_name, HASH_ALGO__LAST, |
| c->auth_hash_name); |
| if ((int)c->auth_hash_algo < 0) { |
| ubifs_err(c, "Unknown hash algo %s specified", |
| c->auth_hash_name); |
| return -EINVAL; |
| } |
| |
| snprintf(hmac_name, CRYPTO_MAX_ALG_NAME, "hmac(%s)", |
| c->auth_hash_name); |
| |
| keyring_key = request_key(&key_type_logon, c->auth_key_name, NULL); |
| |
| if (IS_ERR(keyring_key)) { |
| ubifs_err(c, "Failed to request key: %ld", |
| PTR_ERR(keyring_key)); |
| return PTR_ERR(keyring_key); |
| } |
| |
| down_read(&keyring_key->sem); |
| |
| if (keyring_key->type != &key_type_logon) { |
| ubifs_err(c, "key type must be logon"); |
| err = -ENOKEY; |
| goto out; |
| } |
| |
| ukp = user_key_payload_locked(keyring_key); |
| if (!ukp) { |
| /* key was revoked before we acquired its semaphore */ |
| err = -EKEYREVOKED; |
| goto out; |
| } |
| |
| c->hash_tfm = crypto_alloc_shash(c->auth_hash_name, 0, 0); |
| if (IS_ERR(c->hash_tfm)) { |
| err = PTR_ERR(c->hash_tfm); |
| ubifs_err(c, "Can not allocate %s: %d", |
| c->auth_hash_name, err); |
| goto out; |
| } |
| |
| c->hash_len = crypto_shash_digestsize(c->hash_tfm); |
| if (c->hash_len > UBIFS_HASH_ARR_SZ) { |
| ubifs_err(c, "hash %s is bigger than maximum allowed hash size (%d > %d)", |
| c->auth_hash_name, c->hash_len, UBIFS_HASH_ARR_SZ); |
| err = -EINVAL; |
| goto out_free_hash; |
| } |
| |
| c->hmac_tfm = crypto_alloc_shash(hmac_name, 0, 0); |
| if (IS_ERR(c->hmac_tfm)) { |
| err = PTR_ERR(c->hmac_tfm); |
| ubifs_err(c, "Can not allocate %s: %d", hmac_name, err); |
| goto out_free_hash; |
| } |
| |
| c->hmac_desc_len = crypto_shash_digestsize(c->hmac_tfm); |
| if (c->hmac_desc_len > UBIFS_HMAC_ARR_SZ) { |
| ubifs_err(c, "hmac %s is bigger than maximum allowed hmac size (%d > %d)", |
| hmac_name, c->hmac_desc_len, UBIFS_HMAC_ARR_SZ); |
| err = -EINVAL; |
| goto out_free_hash; |
| } |
| |
| err = crypto_shash_setkey(c->hmac_tfm, ukp->data, ukp->datalen); |
| if (err) |
| goto out_free_hmac; |
| |
| c->authenticated = true; |
| |
| c->log_hash = ubifs_hash_get_desc(c); |
| if (IS_ERR(c->log_hash)) { |
| err = PTR_ERR(c->log_hash); |
| goto out_free_hmac; |
| } |
| |
| err = 0; |
| |
| out_free_hmac: |
| if (err) |
| crypto_free_shash(c->hmac_tfm); |
| out_free_hash: |
| if (err) |
| crypto_free_shash(c->hash_tfm); |
| out: |
| up_read(&keyring_key->sem); |
| key_put(keyring_key); |
| |
| return err; |
| } |
| |
| /** |
| * __ubifs_exit_authentication - release resource |
| * @c: UBIFS file-system description object |
| * |
| * This function releases the authentication related resources. |
| */ |
| void __ubifs_exit_authentication(struct ubifs_info *c) |
| { |
| if (!ubifs_authenticated(c)) |
| return; |
| |
| crypto_free_shash(c->hmac_tfm); |
| crypto_free_shash(c->hash_tfm); |
| kfree(c->log_hash); |
| } |
| |
| /** |
| * ubifs_node_calc_hmac - calculate the HMAC of a UBIFS node |
| * @c: UBIFS file-system description object |
| * @node: the node to insert a HMAC into. |
| * @len: the length of the node |
| * @ofs_hmac: the offset in the node where the HMAC is inserted |
| * @hmac: returned HMAC |
| * |
| * This function calculates a HMAC of a UBIFS node. The HMAC is expected to be |
| * embedded into the node, so this area is not covered by the HMAC. Also not |
| * covered is the UBIFS_NODE_MAGIC and the CRC of the node. |
| */ |
| static int ubifs_node_calc_hmac(const struct ubifs_info *c, const void *node, |
| int len, int ofs_hmac, void *hmac) |
| { |
| SHASH_DESC_ON_STACK(shash, c->hmac_tfm); |
| int hmac_len = c->hmac_desc_len; |
| int err; |
| |
| ubifs_assert(c, ofs_hmac > 8); |
| ubifs_assert(c, ofs_hmac + hmac_len < len); |
| |
| shash->tfm = c->hmac_tfm; |
| |
| err = crypto_shash_init(shash); |
| if (err) |
| return err; |
| |
| /* behind common node header CRC up to HMAC begin */ |
| err = crypto_shash_update(shash, node + 8, ofs_hmac - 8); |
| if (err < 0) |
| return err; |
| |
| /* behind HMAC, if any */ |
| if (len - ofs_hmac - hmac_len > 0) { |
| err = crypto_shash_update(shash, node + ofs_hmac + hmac_len, |
| len - ofs_hmac - hmac_len); |
| if (err < 0) |
| return err; |
| } |
| |
| return crypto_shash_final(shash, hmac); |
| } |
| |
| /** |
| * __ubifs_node_insert_hmac - insert a HMAC into a UBIFS node |
| * @c: UBIFS file-system description object |
| * @node: the node to insert a HMAC into. |
| * @len: the length of the node |
| * @ofs_hmac: the offset in the node where the HMAC is inserted |
| * |
| * This function inserts a HMAC at offset @ofs_hmac into the node given in |
| * @node. |
| * |
| * This function returns 0 for success or a negative error code otherwise. |
| */ |
| int __ubifs_node_insert_hmac(const struct ubifs_info *c, void *node, int len, |
| int ofs_hmac) |
| { |
| return ubifs_node_calc_hmac(c, node, len, ofs_hmac, node + ofs_hmac); |
| } |
| |
| /** |
| * __ubifs_node_verify_hmac - verify the HMAC of UBIFS node |
| * @c: UBIFS file-system description object |
| * @node: the node to insert a HMAC into. |
| * @len: the length of the node |
| * @ofs_hmac: the offset in the node where the HMAC is inserted |
| * |
| * This function verifies the HMAC at offset @ofs_hmac of the node given in |
| * @node. Returns 0 if successful or a negative error code otherwise. |
| */ |
| int __ubifs_node_verify_hmac(const struct ubifs_info *c, const void *node, |
| int len, int ofs_hmac) |
| { |
| int hmac_len = c->hmac_desc_len; |
| u8 *hmac; |
| int err; |
| |
| hmac = kmalloc(hmac_len, GFP_NOFS); |
| if (!hmac) |
| return -ENOMEM; |
| |
| err = ubifs_node_calc_hmac(c, node, len, ofs_hmac, hmac); |
| if (err) { |
| kfree(hmac); |
| return err; |
| } |
| |
| err = crypto_memneq(hmac, node + ofs_hmac, hmac_len); |
| |
| kfree(hmac); |
| |
| if (!err) |
| return 0; |
| |
| return -EPERM; |
| } |
| |
| int __ubifs_shash_copy_state(const struct ubifs_info *c, struct shash_desc *src, |
| struct shash_desc *target) |
| { |
| u8 *state; |
| int err; |
| |
| state = kmalloc(crypto_shash_descsize(src->tfm), GFP_NOFS); |
| if (!state) |
| return -ENOMEM; |
| |
| err = crypto_shash_export(src, state); |
| if (err) |
| goto out; |
| |
| err = crypto_shash_import(target, state); |
| |
| out: |
| kfree(state); |
| |
| return err; |
| } |
| |
| /** |
| * ubifs_hmac_wkm - Create a HMAC of the well known message |
| * @c: UBIFS file-system description object |
| * @hmac: The HMAC of the well known message |
| * |
| * This function creates a HMAC of a well known message. This is used |
| * to check if the provided key is suitable to authenticate a UBIFS |
| * image. This is only a convenience to the user to provide a better |
| * error message when the wrong key is provided. |
| * |
| * This function returns 0 for success or a negative error code otherwise. |
| */ |
| int ubifs_hmac_wkm(struct ubifs_info *c, u8 *hmac) |
| { |
| SHASH_DESC_ON_STACK(shash, c->hmac_tfm); |
| int err; |
| const char well_known_message[] = "UBIFS"; |
| |
| if (!ubifs_authenticated(c)) |
| return 0; |
| |
| shash->tfm = c->hmac_tfm; |
| |
| err = crypto_shash_init(shash); |
| if (err) |
| return err; |
| |
| err = crypto_shash_update(shash, well_known_message, |
| sizeof(well_known_message) - 1); |
| if (err < 0) |
| return err; |
| |
| err = crypto_shash_final(shash, hmac); |
| if (err) |
| return err; |
| return 0; |
| } |
| |
| /* |
| * ubifs_hmac_zero - test if a HMAC is zero |
| * @c: UBIFS file-system description object |
| * @hmac: the HMAC to test |
| * |
| * This function tests if a HMAC is zero and returns true if it is |
| * and false otherwise. |
| */ |
| bool ubifs_hmac_zero(struct ubifs_info *c, const u8 *hmac) |
| { |
| return !memchr_inv(hmac, 0, c->hmac_desc_len); |
| } |