2018-09-07 15:36:32 +03:00
// 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 <crypto/hash.h>
# include <crypto/sha.h>
# include <crypto/algapi.h>
# include <keys/user-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 ;
SHASH_DESC_ON_STACK ( shash , c - > hash_tfm ) ;
int err ;
shash - > tfm = c - > hash_tfm ;
err = crypto_shash_digest ( shash , node , le32_to_cpu ( ch - > len ) , hash ) ;
if ( err < 0 )
return err ;
return 0 ;
}
/**
* 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 )
{
SHASH_DESC_ON_STACK ( shash , c - > hmac_tfm ) ;
int err ;
shash - > tfm = c - > hmac_tfm ;
err = crypto_shash_digest ( shash , hash , c - > hash_len , hmac ) ;
if ( err < 0 )
return err ;
return 0 ;
}
/**
* ubifs_prepare_auth_node - Prepare an authentication node
* @ c : UBIFS file - system description object
* @ node : the node to calculate a hash for
* @ hash : 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 ;
int err ;
hash = kmalloc ( crypto_shash_descsize ( c - > hash_tfm ) , GFP_NOFS ) ;
if ( ! hash )
return - ENOMEM ;
2019-03-24 22:44:51 +03:00
{
SHASH_DESC_ON_STACK ( hash_desc , c - > hash_tfm ) ;
2018-09-07 15:36:32 +03:00
2019-03-24 22:44:51 +03:00
hash_desc - > tfm = c - > hash_tfm ;
ubifs_shash_copy_state ( c , inhash , hash_desc ) ;
err = crypto_shash_final ( hash_desc , hash ) ;
if ( err )
goto out ;
}
2018-09-07 15:36:32 +03:00
err = ubifs_hash_calc_hmac ( c , hash , auth - > hmac ) ;
if ( err )
goto out ;
auth - > ch . node_type = UBIFS_AUTH_NODE ;
ubifs_prepare_node ( c , auth , ubifs_auth_node_sz ( c ) , 0 ) ;
err = 0 ;
out :
kfree ( hash ) ;
return err ;
}
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_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 ;
}
2018-11-14 23:21:11 +03:00
c - > hash_tfm = crypto_alloc_shash ( c - > auth_hash_name , 0 , 0 ) ;
2018-09-07 15:36:32 +03:00
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 ;
}
2018-11-14 23:21:11 +03:00
c - > hmac_tfm = crypto_alloc_shash ( hmac_name , 0 , 0 ) ;
2018-09-07 15:36:32 +03:00
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 ) )
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 )
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 ;
}