2016-04-12 21:54:58 +03:00
/* Crypto operations using stored keys
*
* Copyright ( c ) 2016 , Intel Corporation
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation ; either version
* 2 of the License , or ( at your option ) any later version .
*/
# include <linux/slab.h>
# include <linux/uaccess.h>
2017-06-08 16:50:11 +03:00
# include <linux/scatterlist.h>
2016-08-19 21:39:09 +03:00
# include <linux/crypto.h>
# include <crypto/hash.h>
2017-06-08 16:50:11 +03:00
# include <crypto/kpp.h>
# include <crypto/dh.h>
2016-04-12 21:54:58 +03:00
# include <keys/user-type.h>
# include "internal.h"
2017-06-08 16:50:11 +03:00
static ssize_t dh_data_from_key ( key_serial_t keyid , void * * data )
2016-04-12 21:54:58 +03:00
{
struct key * key ;
key_ref_t key_ref ;
long status ;
ssize_t ret ;
key_ref = lookup_user_key ( keyid , 0 , KEY_NEED_READ ) ;
if ( IS_ERR ( key_ref ) ) {
ret = - ENOKEY ;
goto error ;
}
key = key_ref_to_ptr ( key_ref ) ;
ret = - EOPNOTSUPP ;
if ( key - > type = = & key_type_user ) {
down_read ( & key - > sem ) ;
status = key_validate ( key ) ;
if ( status = = 0 ) {
const struct user_key_payload * payload ;
2017-06-08 16:50:11 +03:00
uint8_t * duplicate ;
2016-04-12 21:54:58 +03:00
2017-03-01 18:11:23 +03:00
payload = user_key_payload_locked ( key ) ;
2016-04-12 21:54:58 +03:00
2017-06-08 16:50:11 +03:00
duplicate = kmemdup ( payload - > data , payload - > datalen ,
GFP_KERNEL ) ;
if ( duplicate ) {
* data = duplicate ;
2016-04-12 21:54:58 +03:00
ret = payload - > datalen ;
} else {
2017-06-08 16:50:11 +03:00
ret = - ENOMEM ;
2016-04-12 21:54:58 +03:00
}
}
up_read ( & key - > sem ) ;
}
key_put ( key ) ;
error :
return ret ;
}
2017-06-08 16:50:11 +03:00
static void dh_free_data ( struct dh * dh )
{
kzfree ( dh - > key ) ;
kzfree ( dh - > p ) ;
kzfree ( dh - > g ) ;
}
struct dh_completion {
struct completion completion ;
int err ;
} ;
static void dh_crypto_done ( struct crypto_async_request * req , int err )
{
struct dh_completion * compl = req - > data ;
if ( err = = - EINPROGRESS )
return ;
compl - > err = err ;
complete ( & compl - > completion ) ;
}
2016-08-19 21:39:09 +03:00
struct kdf_sdesc {
struct shash_desc shash ;
char ctx [ ] ;
} ;
static int kdf_alloc ( struct kdf_sdesc * * sdesc_ret , char * hashname )
{
struct crypto_shash * tfm ;
struct kdf_sdesc * sdesc ;
int size ;
2017-06-08 16:49:34 +03:00
int err ;
2016-08-19 21:39:09 +03:00
/* allocate synchronous hash */
tfm = crypto_alloc_shash ( hashname , 0 , 0 ) ;
if ( IS_ERR ( tfm ) ) {
pr_info ( " could not allocate digest TFM handle %s \n " , hashname ) ;
return PTR_ERR ( tfm ) ;
}
2017-06-08 16:49:34 +03:00
err = - EINVAL ;
if ( crypto_shash_digestsize ( tfm ) = = 0 )
goto out_free_tfm ;
err = - ENOMEM ;
2016-08-19 21:39:09 +03:00
size = sizeof ( struct shash_desc ) + crypto_shash_descsize ( tfm ) ;
sdesc = kmalloc ( size , GFP_KERNEL ) ;
if ( ! sdesc )
2017-06-08 16:49:34 +03:00
goto out_free_tfm ;
2016-08-19 21:39:09 +03:00
sdesc - > shash . tfm = tfm ;
sdesc - > shash . flags = 0x0 ;
* sdesc_ret = sdesc ;
return 0 ;
2017-06-08 16:49:34 +03:00
out_free_tfm :
crypto_free_shash ( tfm ) ;
return err ;
2016-08-19 21:39:09 +03:00
}
static void kdf_dealloc ( struct kdf_sdesc * sdesc )
{
if ( ! sdesc )
return ;
if ( sdesc - > shash . tfm )
crypto_free_shash ( sdesc - > shash . tfm ) ;
kzfree ( sdesc ) ;
}
/*
* Implementation of the KDF in counter mode according to SP800 - 108 section 5.1
* as well as SP800 - 56 A section 5.8 .1 ( Single - step KDF ) .
*
* SP800 - 56 A :
* The src pointer is defined as Z | | other info where Z is the shared secret
* from DH and other info is an arbitrary string ( see SP800 - 56 A section
* 5.8 .1 .2 ) .
*/
static int kdf_ctr ( struct kdf_sdesc * sdesc , const u8 * src , unsigned int slen ,
2017-06-08 16:50:11 +03:00
u8 * dst , unsigned int dlen , unsigned int zlen )
2016-08-19 21:39:09 +03:00
{
struct shash_desc * desc = & sdesc - > shash ;
unsigned int h = crypto_shash_digestsize ( desc - > tfm ) ;
int err = 0 ;
u8 * dst_orig = dst ;
2017-06-08 16:49:49 +03:00
__be32 counter = cpu_to_be32 ( 1 ) ;
2016-08-19 21:39:09 +03:00
while ( dlen ) {
err = crypto_shash_init ( desc ) ;
if ( err )
goto err ;
2017-06-08 16:49:49 +03:00
err = crypto_shash_update ( desc , ( u8 * ) & counter , sizeof ( __be32 ) ) ;
2016-08-19 21:39:09 +03:00
if ( err )
goto err ;
2017-06-08 16:50:11 +03:00
if ( zlen & & h ) {
u8 tmpbuffer [ h ] ;
size_t chunk = min_t ( size_t , zlen , h ) ;
memset ( tmpbuffer , 0 , chunk ) ;
do {
err = crypto_shash_update ( desc , tmpbuffer ,
chunk ) ;
if ( err )
goto err ;
zlen - = chunk ;
chunk = min_t ( size_t , zlen , h ) ;
} while ( zlen ) ;
}
2016-08-19 21:39:09 +03:00
if ( src & & slen ) {
err = crypto_shash_update ( desc , src , slen ) ;
if ( err )
goto err ;
}
if ( dlen < h ) {
u8 tmpbuffer [ h ] ;
err = crypto_shash_final ( desc , tmpbuffer ) ;
if ( err )
goto err ;
memcpy ( dst , tmpbuffer , dlen ) ;
memzero_explicit ( tmpbuffer , h ) ;
return 0 ;
} else {
err = crypto_shash_final ( desc , dst ) ;
if ( err )
goto err ;
dlen - = h ;
dst + = h ;
2017-06-08 16:49:49 +03:00
counter = cpu_to_be32 ( be32_to_cpu ( counter ) + 1 ) ;
2016-08-19 21:39:09 +03:00
}
}
return 0 ;
err :
memzero_explicit ( dst_orig , dlen ) ;
return err ;
}
static int keyctl_dh_compute_kdf ( struct kdf_sdesc * sdesc ,
char __user * buffer , size_t buflen ,
2017-06-08 16:50:11 +03:00
uint8_t * kbuf , size_t kbuflen , size_t lzero )
2016-08-19 21:39:09 +03:00
{
uint8_t * outbuf = NULL ;
int ret ;
outbuf = kmalloc ( buflen , GFP_KERNEL ) ;
if ( ! outbuf ) {
ret = - ENOMEM ;
goto err ;
}
2017-06-08 16:50:11 +03:00
ret = kdf_ctr ( sdesc , kbuf , kbuflen , outbuf , buflen , lzero ) ;
2016-08-19 21:39:09 +03:00
if ( ret )
goto err ;
ret = buflen ;
if ( copy_to_user ( buffer , outbuf , buflen ) ! = 0 )
ret = - EFAULT ;
err :
kzfree ( outbuf ) ;
return ret ;
}
long __keyctl_dh_compute ( struct keyctl_dh_params __user * params ,
char __user * buffer , size_t buflen ,
struct keyctl_kdf_params * kdfcopy )
2016-04-12 21:54:58 +03:00
{
long ret ;
2017-06-08 16:50:11 +03:00
ssize_t dlen ;
int secretlen ;
int outlen ;
2016-04-12 21:54:58 +03:00
struct keyctl_dh_params pcopy ;
2017-06-08 16:50:11 +03:00
struct dh dh_inputs ;
struct scatterlist outsg ;
struct dh_completion compl ;
struct crypto_kpp * tfm ;
struct kpp_request * req ;
uint8_t * secret ;
uint8_t * outbuf ;
2016-08-19 21:39:09 +03:00
struct kdf_sdesc * sdesc = NULL ;
2016-04-12 21:54:58 +03:00
if ( ! params | | ( ! buffer & & buflen ) ) {
ret = - EINVAL ;
2017-06-08 16:50:11 +03:00
goto out1 ;
2016-04-12 21:54:58 +03:00
}
if ( copy_from_user ( & pcopy , params , sizeof ( pcopy ) ) ! = 0 ) {
ret = - EFAULT ;
2017-06-08 16:50:11 +03:00
goto out1 ;
2016-04-12 21:54:58 +03:00
}
2016-08-19 21:39:09 +03:00
if ( kdfcopy ) {
char * hashname ;
2017-07-13 15:16:56 +03:00
if ( memchr_inv ( kdfcopy - > __spare , 0 , sizeof ( kdfcopy - > __spare ) ) ) {
ret = - EINVAL ;
goto out1 ;
}
2016-08-19 21:39:09 +03:00
if ( buflen > KEYCTL_KDF_MAX_OUTPUT_LEN | |
kdfcopy - > otherinfolen > KEYCTL_KDF_MAX_OI_LEN ) {
ret = - EMSGSIZE ;
2017-06-08 16:50:11 +03:00
goto out1 ;
2016-08-19 21:39:09 +03:00
}
/* get KDF name string */
hashname = strndup_user ( kdfcopy - > hashname , CRYPTO_MAX_ALG_NAME ) ;
if ( IS_ERR ( hashname ) ) {
ret = PTR_ERR ( hashname ) ;
2017-06-08 16:50:11 +03:00
goto out1 ;
2016-08-19 21:39:09 +03:00
}
/* allocate KDF from the kernel crypto API */
ret = kdf_alloc ( & sdesc , hashname ) ;
kfree ( hashname ) ;
if ( ret )
2017-06-08 16:50:11 +03:00
goto out1 ;
2016-05-27 00:38:12 +03:00
}
2017-06-08 16:50:11 +03:00
memset ( & dh_inputs , 0 , sizeof ( dh_inputs ) ) ;
dlen = dh_data_from_key ( pcopy . prime , & dh_inputs . p ) ;
if ( dlen < 0 ) {
ret = dlen ;
goto out1 ;
2016-04-12 21:54:58 +03:00
}
2017-06-08 16:50:11 +03:00
dh_inputs . p_size = dlen ;
2016-04-12 21:54:58 +03:00
2017-06-08 16:50:11 +03:00
dlen = dh_data_from_key ( pcopy . base , & dh_inputs . g ) ;
if ( dlen < 0 ) {
ret = dlen ;
goto out2 ;
}
dh_inputs . g_size = dlen ;
2016-04-12 21:54:58 +03:00
2017-06-08 16:50:11 +03:00
dlen = dh_data_from_key ( pcopy . private , & dh_inputs . key ) ;
if ( dlen < 0 ) {
ret = dlen ;
goto out2 ;
2016-04-12 21:54:58 +03:00
}
2017-06-08 16:50:11 +03:00
dh_inputs . key_size = dlen ;
2016-04-12 21:54:58 +03:00
2017-06-08 16:50:11 +03:00
secretlen = crypto_dh_key_len ( & dh_inputs ) ;
secret = kmalloc ( secretlen , GFP_KERNEL ) ;
if ( ! secret ) {
ret = - ENOMEM ;
goto out2 ;
}
ret = crypto_dh_encode_key ( secret , secretlen , & dh_inputs ) ;
if ( ret )
goto out3 ;
tfm = crypto_alloc_kpp ( " dh " , CRYPTO_ALG_TYPE_KPP , 0 ) ;
if ( IS_ERR ( tfm ) ) {
ret = PTR_ERR ( tfm ) ;
goto out3 ;
}
ret = crypto_kpp_set_secret ( tfm , secret , secretlen ) ;
if ( ret )
goto out4 ;
outlen = crypto_kpp_maxsize ( tfm ) ;
if ( ! kdfcopy ) {
/*
* When not using a KDF , buflen 0 is used to read the
* required buffer length
*/
if ( buflen = = 0 ) {
ret = outlen ;
goto out4 ;
} else if ( outlen > buflen ) {
ret = - EOVERFLOW ;
goto out4 ;
}
2016-04-12 21:54:58 +03:00
}
2017-06-08 16:50:11 +03:00
outbuf = kzalloc ( kdfcopy ? ( outlen + kdfcopy - > otherinfolen ) : outlen ,
GFP_KERNEL ) ;
if ( ! outbuf ) {
2016-04-12 21:54:58 +03:00
ret = - ENOMEM ;
2017-06-08 16:50:11 +03:00
goto out4 ;
2016-04-12 21:54:58 +03:00
}
2017-06-08 16:50:11 +03:00
sg_init_one ( & outsg , outbuf , outlen ) ;
req = kpp_request_alloc ( tfm , GFP_KERNEL ) ;
if ( ! req ) {
2016-04-12 21:54:58 +03:00
ret = - ENOMEM ;
2017-06-08 16:50:11 +03:00
goto out5 ;
2016-04-12 21:54:58 +03:00
}
2017-06-08 16:50:11 +03:00
kpp_request_set_input ( req , NULL , 0 ) ;
kpp_request_set_output ( req , & outsg , outlen ) ;
init_completion ( & compl . completion ) ;
kpp_request_set_callback ( req , CRYPTO_TFM_REQ_MAY_BACKLOG |
CRYPTO_TFM_REQ_MAY_SLEEP ,
dh_crypto_done , & compl ) ;
2016-08-19 21:39:09 +03:00
/*
2017-06-08 16:50:11 +03:00
* For DH , generate_public_key and generate_shared_secret are
* the same calculation
2016-08-19 21:39:09 +03:00
*/
2017-06-08 16:50:11 +03:00
ret = crypto_kpp_generate_public_key ( req ) ;
if ( ret = = - EINPROGRESS ) {
wait_for_completion ( & compl . completion ) ;
ret = compl . err ;
if ( ret )
goto out6 ;
2016-08-19 21:39:09 +03:00
}
if ( kdfcopy ) {
2017-06-08 16:50:11 +03:00
/*
* Concatenate SP800 - 56 A otherinfo past DH shared secret - - the
* input to the KDF is ( DH shared secret | | otherinfo )
*/
if ( copy_from_user ( outbuf + req - > dst_len , kdfcopy - > otherinfo ,
kdfcopy - > otherinfolen ) ! = 0 ) {
2016-08-19 21:39:09 +03:00
ret = - EFAULT ;
2017-06-08 16:50:11 +03:00
goto out6 ;
}
ret = keyctl_dh_compute_kdf ( sdesc , buffer , buflen , outbuf ,
req - > dst_len + kdfcopy - > otherinfolen ,
outlen - req - > dst_len ) ;
} else if ( copy_to_user ( buffer , outbuf , req - > dst_len ) = = 0 ) {
ret = req - > dst_len ;
} else {
ret = - EFAULT ;
2016-08-19 21:39:09 +03:00
}
2016-04-12 21:54:58 +03:00
2017-06-08 16:50:11 +03:00
out6 :
kpp_request_free ( req ) ;
out5 :
kzfree ( outbuf ) ;
out4 :
crypto_free_kpp ( tfm ) ;
out3 :
kzfree ( secret ) ;
out2 :
dh_free_data ( & dh_inputs ) ;
out1 :
2016-08-19 21:39:09 +03:00
kdf_dealloc ( sdesc ) ;
2016-04-12 21:54:58 +03:00
return ret ;
}
2016-08-19 21:39:09 +03:00
long keyctl_dh_compute ( struct keyctl_dh_params __user * params ,
char __user * buffer , size_t buflen ,
struct keyctl_kdf_params __user * kdf )
{
struct keyctl_kdf_params kdfcopy ;
if ( ! kdf )
return __keyctl_dh_compute ( params , buffer , buflen , NULL ) ;
if ( copy_from_user ( & kdfcopy , kdf , sizeof ( kdfcopy ) ) ! = 0 )
return - EFAULT ;
return __keyctl_dh_compute ( params , buffer , buflen , & kdfcopy ) ;
}