2022-06-27 11:52:05 +02:00
// SPDX-License-Identifier: GPL-2.0
/*
* NVMe over Fabrics DH - HMAC - CHAP authentication .
* Copyright ( c ) 2020 Hannes Reinecke , SUSE Software Solutions .
* All rights reserved .
*/
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
# include <linux/module.h>
# include <linux/init.h>
# include <linux/slab.h>
# include <linux/err.h>
# include <crypto/hash.h>
# include <linux/crc32.h>
# include <linux/base64.h>
# include <linux/ctype.h>
# include <linux/random.h>
# include <linux/nvme-auth.h>
# include <asm/unaligned.h>
# include "nvmet.h"
int nvmet_auth_set_key ( struct nvmet_host * host , const char * secret ,
bool set_ctrl )
{
unsigned char key_hash ;
char * dhchap_secret ;
if ( sscanf ( secret , " DHHC-1:%hhd:%*s " , & key_hash ) ! = 1 )
return - EINVAL ;
if ( key_hash > 3 ) {
pr_warn ( " Invalid DH-HMAC-CHAP hash id %d \n " ,
key_hash ) ;
return - EINVAL ;
}
if ( key_hash > 0 ) {
/* Validate selected hash algorithm */
const char * hmac = nvme_auth_hmac_name ( key_hash ) ;
if ( ! crypto_has_shash ( hmac , 0 , 0 ) ) {
pr_err ( " DH-HMAC-CHAP hash %s unsupported \n " , hmac ) ;
return - ENOTSUPP ;
}
}
dhchap_secret = kstrdup ( secret , GFP_KERNEL ) ;
if ( ! dhchap_secret )
return - ENOMEM ;
if ( set_ctrl ) {
2022-11-14 08:45:02 +02:00
kfree ( host - > dhchap_ctrl_secret ) ;
2022-06-27 11:52:05 +02:00
host - > dhchap_ctrl_secret = strim ( dhchap_secret ) ;
host - > dhchap_ctrl_key_hash = key_hash ;
} else {
2022-11-14 08:45:02 +02:00
kfree ( host - > dhchap_secret ) ;
2022-06-27 11:52:05 +02:00
host - > dhchap_secret = strim ( dhchap_secret ) ;
host - > dhchap_key_hash = key_hash ;
}
return 0 ;
}
2022-06-27 11:52:06 +02:00
int nvmet_setup_dhgroup ( struct nvmet_ctrl * ctrl , u8 dhgroup_id )
{
const char * dhgroup_kpp ;
int ret = 0 ;
pr_debug ( " %s: ctrl %d selecting dhgroup %d \n " ,
__func__ , ctrl - > cntlid , dhgroup_id ) ;
if ( ctrl - > dh_tfm ) {
if ( ctrl - > dh_gid = = dhgroup_id ) {
pr_debug ( " %s: ctrl %d reuse existing DH group %d \n " ,
__func__ , ctrl - > cntlid , dhgroup_id ) ;
return 0 ;
}
crypto_free_kpp ( ctrl - > dh_tfm ) ;
ctrl - > dh_tfm = NULL ;
ctrl - > dh_gid = 0 ;
}
if ( dhgroup_id = = NVME_AUTH_DHGROUP_NULL )
return 0 ;
dhgroup_kpp = nvme_auth_dhgroup_kpp ( dhgroup_id ) ;
if ( ! dhgroup_kpp ) {
pr_debug ( " %s: ctrl %d invalid DH group %d \n " ,
__func__ , ctrl - > cntlid , dhgroup_id ) ;
return - EINVAL ;
}
ctrl - > dh_tfm = crypto_alloc_kpp ( dhgroup_kpp , 0 , 0 ) ;
if ( IS_ERR ( ctrl - > dh_tfm ) ) {
pr_debug ( " %s: ctrl %d failed to setup DH group %d, err %ld \n " ,
__func__ , ctrl - > cntlid , dhgroup_id ,
PTR_ERR ( ctrl - > dh_tfm ) ) ;
ret = PTR_ERR ( ctrl - > dh_tfm ) ;
ctrl - > dh_tfm = NULL ;
ctrl - > dh_gid = 0 ;
} else {
ctrl - > dh_gid = dhgroup_id ;
pr_debug ( " %s: ctrl %d setup DH group %d \n " ,
__func__ , ctrl - > cntlid , ctrl - > dh_gid ) ;
ret = nvme_auth_gen_privkey ( ctrl - > dh_tfm , ctrl - > dh_gid ) ;
if ( ret < 0 ) {
pr_debug ( " %s: ctrl %d failed to generate private key, err %d \n " ,
__func__ , ctrl - > cntlid , ret ) ;
kfree_sensitive ( ctrl - > dh_key ) ;
return ret ;
}
ctrl - > dh_keysize = crypto_kpp_maxsize ( ctrl - > dh_tfm ) ;
kfree_sensitive ( ctrl - > dh_key ) ;
ctrl - > dh_key = kzalloc ( ctrl - > dh_keysize , GFP_KERNEL ) ;
if ( ! ctrl - > dh_key ) {
pr_warn ( " ctrl %d failed to allocate public key \n " ,
ctrl - > cntlid ) ;
return - ENOMEM ;
}
ret = nvme_auth_gen_pubkey ( ctrl - > dh_tfm , ctrl - > dh_key ,
ctrl - > dh_keysize ) ;
if ( ret < 0 ) {
pr_warn ( " ctrl %d failed to generate public key \n " ,
ctrl - > cntlid ) ;
kfree ( ctrl - > dh_key ) ;
ctrl - > dh_key = NULL ;
}
}
return ret ;
}
2022-06-27 11:52:05 +02:00
int nvmet_setup_auth ( struct nvmet_ctrl * ctrl )
{
int ret = 0 ;
struct nvmet_host_link * p ;
struct nvmet_host * host = NULL ;
const char * hash_name ;
down_read ( & nvmet_config_sem ) ;
if ( nvmet_is_disc_subsys ( ctrl - > subsys ) )
goto out_unlock ;
if ( ctrl - > subsys - > allow_any_host )
goto out_unlock ;
list_for_each_entry ( p , & ctrl - > subsys - > hosts , entry ) {
pr_debug ( " check %s \n " , nvmet_host_name ( p - > host ) ) ;
if ( strcmp ( nvmet_host_name ( p - > host ) , ctrl - > hostnqn ) )
continue ;
host = p - > host ;
break ;
}
if ( ! host ) {
pr_debug ( " host %s not found \n " , ctrl - > hostnqn ) ;
ret = - EPERM ;
goto out_unlock ;
}
2022-06-27 11:52:06 +02:00
ret = nvmet_setup_dhgroup ( ctrl , host - > dhchap_dhgroup_id ) ;
if ( ret < 0 )
pr_warn ( " Failed to setup DH group " ) ;
2022-06-27 11:52:05 +02:00
if ( ! host - > dhchap_secret ) {
pr_debug ( " No authentication provided \n " ) ;
goto out_unlock ;
}
if ( host - > dhchap_hash_id = = ctrl - > shash_id ) {
pr_debug ( " Re-use existing hash ID %d \n " ,
ctrl - > shash_id ) ;
} else {
hash_name = nvme_auth_hmac_name ( host - > dhchap_hash_id ) ;
if ( ! hash_name ) {
pr_warn ( " Hash ID %d invalid \n " , host - > dhchap_hash_id ) ;
ret = - EINVAL ;
goto out_unlock ;
}
ctrl - > shash_id = host - > dhchap_hash_id ;
}
/* Skip the 'DHHC-1:XX:' prefix */
nvme_auth_free_key ( ctrl - > host_key ) ;
ctrl - > host_key = nvme_auth_extract_key ( host - > dhchap_secret + 10 ,
host - > dhchap_key_hash ) ;
if ( IS_ERR ( ctrl - > host_key ) ) {
ret = PTR_ERR ( ctrl - > host_key ) ;
ctrl - > host_key = NULL ;
goto out_free_hash ;
}
pr_debug ( " %s: using hash %s key %*ph \n " , __func__ ,
ctrl - > host_key - > hash > 0 ?
nvme_auth_hmac_name ( ctrl - > host_key - > hash ) : " none " ,
( int ) ctrl - > host_key - > len , ctrl - > host_key - > key ) ;
nvme_auth_free_key ( ctrl - > ctrl_key ) ;
if ( ! host - > dhchap_ctrl_secret ) {
ctrl - > ctrl_key = NULL ;
goto out_unlock ;
}
ctrl - > ctrl_key = nvme_auth_extract_key ( host - > dhchap_ctrl_secret + 10 ,
host - > dhchap_ctrl_key_hash ) ;
if ( IS_ERR ( ctrl - > ctrl_key ) ) {
ret = PTR_ERR ( ctrl - > ctrl_key ) ;
ctrl - > ctrl_key = NULL ;
2022-08-24 09:23:16 +02:00
goto out_free_hash ;
2022-06-27 11:52:05 +02:00
}
pr_debug ( " %s: using ctrl hash %s key %*ph \n " , __func__ ,
ctrl - > ctrl_key - > hash > 0 ?
nvme_auth_hmac_name ( ctrl - > ctrl_key - > hash ) : " none " ,
( int ) ctrl - > ctrl_key - > len , ctrl - > ctrl_key - > key ) ;
out_free_hash :
if ( ret ) {
if ( ctrl - > host_key ) {
nvme_auth_free_key ( ctrl - > host_key ) ;
ctrl - > host_key = NULL ;
}
ctrl - > shash_id = 0 ;
}
out_unlock :
up_read ( & nvmet_config_sem ) ;
return ret ;
}
void nvmet_auth_sq_free ( struct nvmet_sq * sq )
{
2022-06-27 11:52:07 +02:00
cancel_delayed_work ( & sq - > auth_expired_work ) ;
2022-06-27 11:52:05 +02:00
kfree ( sq - > dhchap_c1 ) ;
sq - > dhchap_c1 = NULL ;
kfree ( sq - > dhchap_c2 ) ;
sq - > dhchap_c2 = NULL ;
kfree ( sq - > dhchap_skey ) ;
sq - > dhchap_skey = NULL ;
}
void nvmet_destroy_auth ( struct nvmet_ctrl * ctrl )
{
ctrl - > shash_id = 0 ;
2022-06-27 11:52:06 +02:00
if ( ctrl - > dh_tfm ) {
crypto_free_kpp ( ctrl - > dh_tfm ) ;
ctrl - > dh_tfm = NULL ;
ctrl - > dh_gid = 0 ;
}
kfree_sensitive ( ctrl - > dh_key ) ;
ctrl - > dh_key = NULL ;
2022-06-27 11:52:05 +02:00
if ( ctrl - > host_key ) {
nvme_auth_free_key ( ctrl - > host_key ) ;
ctrl - > host_key = NULL ;
}
if ( ctrl - > ctrl_key ) {
nvme_auth_free_key ( ctrl - > ctrl_key ) ;
ctrl - > ctrl_key = NULL ;
}
}
bool nvmet_check_auth_status ( struct nvmet_req * req )
{
if ( req - > sq - > ctrl - > host_key & &
! req - > sq - > authenticated )
return false ;
return true ;
}
int nvmet_auth_host_hash ( struct nvmet_req * req , u8 * response ,
unsigned int shash_len )
{
struct crypto_shash * shash_tfm ;
struct shash_desc * shash ;
struct nvmet_ctrl * ctrl = req - > sq - > ctrl ;
const char * hash_name ;
u8 * challenge = req - > sq - > dhchap_c1 , * host_response ;
u8 buf [ 4 ] ;
int ret ;
hash_name = nvme_auth_hmac_name ( ctrl - > shash_id ) ;
if ( ! hash_name ) {
pr_warn ( " Hash ID %d invalid \n " , ctrl - > shash_id ) ;
return - EINVAL ;
}
shash_tfm = crypto_alloc_shash ( hash_name , 0 , 0 ) ;
if ( IS_ERR ( shash_tfm ) ) {
pr_err ( " failed to allocate shash %s \n " , hash_name ) ;
return PTR_ERR ( shash_tfm ) ;
}
if ( shash_len ! = crypto_shash_digestsize ( shash_tfm ) ) {
pr_debug ( " %s: hash len mismatch (len %d digest %d) \n " ,
__func__ , shash_len ,
crypto_shash_digestsize ( shash_tfm ) ) ;
ret = - EINVAL ;
goto out_free_tfm ;
}
host_response = nvme_auth_transform_key ( ctrl - > host_key , ctrl - > hostnqn ) ;
if ( IS_ERR ( host_response ) ) {
ret = PTR_ERR ( host_response ) ;
goto out_free_tfm ;
}
ret = crypto_shash_setkey ( shash_tfm , host_response ,
ctrl - > host_key - > len ) ;
if ( ret )
goto out_free_response ;
2022-06-27 11:52:06 +02:00
if ( ctrl - > dh_gid ! = NVME_AUTH_DHGROUP_NULL ) {
challenge = kmalloc ( shash_len , GFP_KERNEL ) ;
if ( ! challenge ) {
ret = - ENOMEM ;
goto out_free_response ;
}
ret = nvme_auth_augmented_challenge ( ctrl - > shash_id ,
req - > sq - > dhchap_skey ,
req - > sq - > dhchap_skey_len ,
req - > sq - > dhchap_c1 ,
challenge , shash_len ) ;
if ( ret )
goto out_free_response ;
}
2022-06-27 11:52:05 +02:00
pr_debug ( " ctrl %d qid %d host response seq %u transaction %d \n " ,
ctrl - > cntlid , req - > sq - > qid , req - > sq - > dhchap_s1 ,
req - > sq - > dhchap_tid ) ;
shash = kzalloc ( sizeof ( * shash ) + crypto_shash_descsize ( shash_tfm ) ,
GFP_KERNEL ) ;
if ( ! shash ) {
ret = - ENOMEM ;
goto out_free_response ;
}
shash - > tfm = shash_tfm ;
ret = crypto_shash_init ( shash ) ;
if ( ret )
goto out ;
ret = crypto_shash_update ( shash , challenge , shash_len ) ;
if ( ret )
goto out ;
put_unaligned_le32 ( req - > sq - > dhchap_s1 , buf ) ;
ret = crypto_shash_update ( shash , buf , 4 ) ;
if ( ret )
goto out ;
put_unaligned_le16 ( req - > sq - > dhchap_tid , buf ) ;
ret = crypto_shash_update ( shash , buf , 2 ) ;
if ( ret )
goto out ;
memset ( buf , 0 , 4 ) ;
ret = crypto_shash_update ( shash , buf , 1 ) ;
if ( ret )
goto out ;
ret = crypto_shash_update ( shash , " HostHost " , 8 ) ;
if ( ret )
goto out ;
ret = crypto_shash_update ( shash , ctrl - > hostnqn , strlen ( ctrl - > hostnqn ) ) ;
if ( ret )
goto out ;
ret = crypto_shash_update ( shash , buf , 1 ) ;
if ( ret )
goto out ;
ret = crypto_shash_update ( shash , ctrl - > subsysnqn ,
strlen ( ctrl - > subsysnqn ) ) ;
if ( ret )
goto out ;
ret = crypto_shash_final ( shash , response ) ;
out :
if ( challenge ! = req - > sq - > dhchap_c1 )
kfree ( challenge ) ;
kfree ( shash ) ;
out_free_response :
kfree_sensitive ( host_response ) ;
out_free_tfm :
crypto_free_shash ( shash_tfm ) ;
return 0 ;
}
int nvmet_auth_ctrl_hash ( struct nvmet_req * req , u8 * response ,
unsigned int shash_len )
{
struct crypto_shash * shash_tfm ;
struct shash_desc * shash ;
struct nvmet_ctrl * ctrl = req - > sq - > ctrl ;
const char * hash_name ;
u8 * challenge = req - > sq - > dhchap_c2 , * ctrl_response ;
u8 buf [ 4 ] ;
int ret ;
hash_name = nvme_auth_hmac_name ( ctrl - > shash_id ) ;
if ( ! hash_name ) {
pr_warn ( " Hash ID %d invalid \n " , ctrl - > shash_id ) ;
return - EINVAL ;
}
shash_tfm = crypto_alloc_shash ( hash_name , 0 , 0 ) ;
if ( IS_ERR ( shash_tfm ) ) {
pr_err ( " failed to allocate shash %s \n " , hash_name ) ;
return PTR_ERR ( shash_tfm ) ;
}
if ( shash_len ! = crypto_shash_digestsize ( shash_tfm ) ) {
pr_debug ( " %s: hash len mismatch (len %d digest %d) \n " ,
__func__ , shash_len ,
crypto_shash_digestsize ( shash_tfm ) ) ;
ret = - EINVAL ;
goto out_free_tfm ;
}
ctrl_response = nvme_auth_transform_key ( ctrl - > ctrl_key ,
ctrl - > subsysnqn ) ;
if ( IS_ERR ( ctrl_response ) ) {
ret = PTR_ERR ( ctrl_response ) ;
goto out_free_tfm ;
}
ret = crypto_shash_setkey ( shash_tfm , ctrl_response ,
ctrl - > ctrl_key - > len ) ;
if ( ret )
goto out_free_response ;
2022-06-27 11:52:06 +02:00
if ( ctrl - > dh_gid ! = NVME_AUTH_DHGROUP_NULL ) {
challenge = kmalloc ( shash_len , GFP_KERNEL ) ;
if ( ! challenge ) {
ret = - ENOMEM ;
goto out_free_response ;
}
ret = nvme_auth_augmented_challenge ( ctrl - > shash_id ,
req - > sq - > dhchap_skey ,
req - > sq - > dhchap_skey_len ,
req - > sq - > dhchap_c2 ,
challenge , shash_len ) ;
if ( ret )
goto out_free_response ;
}
2022-06-27 11:52:05 +02:00
shash = kzalloc ( sizeof ( * shash ) + crypto_shash_descsize ( shash_tfm ) ,
GFP_KERNEL ) ;
if ( ! shash ) {
ret = - ENOMEM ;
goto out_free_response ;
}
shash - > tfm = shash_tfm ;
ret = crypto_shash_init ( shash ) ;
if ( ret )
goto out ;
ret = crypto_shash_update ( shash , challenge , shash_len ) ;
if ( ret )
goto out ;
put_unaligned_le32 ( req - > sq - > dhchap_s2 , buf ) ;
ret = crypto_shash_update ( shash , buf , 4 ) ;
if ( ret )
goto out ;
put_unaligned_le16 ( req - > sq - > dhchap_tid , buf ) ;
ret = crypto_shash_update ( shash , buf , 2 ) ;
if ( ret )
goto out ;
memset ( buf , 0 , 4 ) ;
ret = crypto_shash_update ( shash , buf , 1 ) ;
if ( ret )
goto out ;
ret = crypto_shash_update ( shash , " Controller " , 10 ) ;
if ( ret )
goto out ;
ret = crypto_shash_update ( shash , ctrl - > subsysnqn ,
strlen ( ctrl - > subsysnqn ) ) ;
if ( ret )
goto out ;
ret = crypto_shash_update ( shash , buf , 1 ) ;
if ( ret )
goto out ;
ret = crypto_shash_update ( shash , ctrl - > hostnqn , strlen ( ctrl - > hostnqn ) ) ;
if ( ret )
goto out ;
ret = crypto_shash_final ( shash , response ) ;
out :
if ( challenge ! = req - > sq - > dhchap_c2 )
kfree ( challenge ) ;
kfree ( shash ) ;
out_free_response :
kfree_sensitive ( ctrl_response ) ;
out_free_tfm :
crypto_free_shash ( shash_tfm ) ;
return 0 ;
}
2022-06-27 11:52:06 +02:00
int nvmet_auth_ctrl_exponential ( struct nvmet_req * req ,
u8 * buf , int buf_size )
{
struct nvmet_ctrl * ctrl = req - > sq - > ctrl ;
int ret = 0 ;
if ( ! ctrl - > dh_key ) {
pr_warn ( " ctrl %d no DH public key! \n " , ctrl - > cntlid ) ;
return - ENOKEY ;
}
if ( buf_size ! = ctrl - > dh_keysize ) {
2022-07-18 07:02:29 +02:00
pr_warn ( " ctrl %d DH public key size mismatch, need %zu is %d \n " ,
2022-06-27 11:52:06 +02:00
ctrl - > cntlid , ctrl - > dh_keysize , buf_size ) ;
ret = - EINVAL ;
} else {
memcpy ( buf , ctrl - > dh_key , buf_size ) ;
pr_debug ( " %s: ctrl %d public key %*ph \n " , __func__ ,
ctrl - > cntlid , ( int ) buf_size , buf ) ;
}
return ret ;
}
int nvmet_auth_ctrl_sesskey ( struct nvmet_req * req ,
u8 * pkey , int pkey_size )
{
struct nvmet_ctrl * ctrl = req - > sq - > ctrl ;
int ret ;
req - > sq - > dhchap_skey_len = ctrl - > dh_keysize ;
req - > sq - > dhchap_skey = kzalloc ( req - > sq - > dhchap_skey_len , GFP_KERNEL ) ;
if ( ! req - > sq - > dhchap_skey )
return - ENOMEM ;
ret = nvme_auth_gen_shared_secret ( ctrl - > dh_tfm ,
pkey , pkey_size ,
req - > sq - > dhchap_skey ,
req - > sq - > dhchap_skey_len ) ;
if ( ret )
2022-07-15 14:24:13 +01:00
pr_debug ( " failed to compute shared secret, err %d \n " , ret ) ;
2022-06-27 11:52:06 +02:00
else
pr_debug ( " %s: shared secret %*ph \n " , __func__ ,
( int ) req - > sq - > dhchap_skey_len ,
req - > sq - > dhchap_skey ) ;
return ret ;
}