2016-04-12 19:54:58 +01: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/mpi.h>
# include <linux/slab.h>
# include <linux/uaccess.h>
2016-08-19 20:39:09 +02:00
# include <linux/crypto.h>
# include <crypto/hash.h>
2016-04-12 19:54:58 +01:00
# include <keys/user-type.h>
# include "internal.h"
/*
* Public key or shared secret generation function [ RFC2631 sec 2.1 .1 ]
*
* ya = g ^ xa mod p ;
* or
* ZZ = yb ^ xa mod p ;
*
* where xa is the local private key , ya is the local public key , g is
* the generator , p is the prime , yb is the remote public key , and ZZ
* is the shared secret .
*
* Both are the same calculation , so g or yb are the " base " and ya or
* ZZ are the " result " .
*/
static int do_dh ( MPI result , MPI base , MPI xa , MPI p )
{
return mpi_powm ( result , base , xa , p ) ;
}
static ssize_t mpi_from_key ( key_serial_t keyid , size_t maxlen , MPI * mpi )
{
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-03-01 15:11:23 +00:00
payload = user_key_payload_locked ( key ) ;
2016-04-12 19:54:58 +01:00
if ( maxlen = = 0 ) {
* mpi = NULL ;
ret = payload - > datalen ;
} else if ( payload - > datalen < = maxlen ) {
* mpi = mpi_read_raw_data ( payload - > data ,
payload - > datalen ) ;
if ( * mpi )
ret = payload - > datalen ;
} else {
ret = - EINVAL ;
}
}
up_read ( & key - > sem ) ;
}
key_put ( key ) ;
error :
return ret ;
}
2016-08-19 20:39:09 +02: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 ;
/* 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 ) ;
}
size = sizeof ( struct shash_desc ) + crypto_shash_descsize ( tfm ) ;
sdesc = kmalloc ( size , GFP_KERNEL ) ;
if ( ! sdesc )
return - ENOMEM ;
sdesc - > shash . tfm = tfm ;
sdesc - > shash . flags = 0x0 ;
* sdesc_ret = sdesc ;
return 0 ;
}
static void kdf_dealloc ( struct kdf_sdesc * sdesc )
{
if ( ! sdesc )
return ;
if ( sdesc - > shash . tfm )
crypto_free_shash ( sdesc - > shash . tfm ) ;
kzfree ( sdesc ) ;
}
/* convert 32 bit integer into its string representation */
static inline void crypto_kw_cpu_to_be32 ( u32 val , u8 * buf )
{
__be32 * a = ( __be32 * ) buf ;
* a = cpu_to_be32 ( val ) ;
}
/*
* 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 ,
u8 * dst , unsigned int dlen )
{
struct shash_desc * desc = & sdesc - > shash ;
unsigned int h = crypto_shash_digestsize ( desc - > tfm ) ;
int err = 0 ;
u8 * dst_orig = dst ;
u32 i = 1 ;
u8 iteration [ sizeof ( u32 ) ] ;
while ( dlen ) {
err = crypto_shash_init ( desc ) ;
if ( err )
goto err ;
crypto_kw_cpu_to_be32 ( i , iteration ) ;
err = crypto_shash_update ( desc , iteration , sizeof ( u32 ) ) ;
if ( err )
goto err ;
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 ;
i + + ;
}
}
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 ,
uint8_t * kbuf , size_t kbuflen )
{
uint8_t * outbuf = NULL ;
int ret ;
outbuf = kmalloc ( buflen , GFP_KERNEL ) ;
if ( ! outbuf ) {
ret = - ENOMEM ;
goto err ;
}
ret = kdf_ctr ( sdesc , kbuf , kbuflen , outbuf , buflen ) ;
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 19:54:58 +01:00
{
long ret ;
MPI base , private , prime , result ;
unsigned nbytes ;
struct keyctl_dh_params pcopy ;
uint8_t * kbuf ;
ssize_t keylen ;
size_t resultlen ;
2016-08-19 20:39:09 +02:00
struct kdf_sdesc * sdesc = NULL ;
2016-04-12 19:54:58 +01:00
if ( ! params | | ( ! buffer & & buflen ) ) {
ret = - EINVAL ;
goto out ;
}
if ( copy_from_user ( & pcopy , params , sizeof ( pcopy ) ) ! = 0 ) {
ret = - EFAULT ;
goto out ;
}
2016-08-19 20:39:09 +02:00
if ( kdfcopy ) {
char * hashname ;
if ( buflen > KEYCTL_KDF_MAX_OUTPUT_LEN | |
kdfcopy - > otherinfolen > KEYCTL_KDF_MAX_OI_LEN ) {
ret = - EMSGSIZE ;
goto out ;
}
/* get KDF name string */
hashname = strndup_user ( kdfcopy - > hashname , CRYPTO_MAX_ALG_NAME ) ;
if ( IS_ERR ( hashname ) ) {
ret = PTR_ERR ( hashname ) ;
goto out ;
}
/* allocate KDF from the kernel crypto API */
ret = kdf_alloc ( & sdesc , hashname ) ;
kfree ( hashname ) ;
if ( ret )
goto out ;
2016-05-26 23:38:12 +02:00
}
2016-08-19 20:39:09 +02:00
/*
* If the caller requests postprocessing with a KDF , allow an
* arbitrary output buffer size since the KDF ensures proper truncation .
*/
keylen = mpi_from_key ( pcopy . prime , kdfcopy ? SIZE_MAX : buflen , & prime ) ;
2016-04-12 19:54:58 +01:00
if ( keylen < 0 | | ! prime ) {
/* buflen == 0 may be used to query the required buffer size,
* which is the prime key length .
*/
ret = keylen ;
goto out ;
}
/* The result is never longer than the prime */
resultlen = keylen ;
keylen = mpi_from_key ( pcopy . base , SIZE_MAX , & base ) ;
if ( keylen < 0 | | ! base ) {
ret = keylen ;
goto error1 ;
}
keylen = mpi_from_key ( pcopy . private , SIZE_MAX , & private ) ;
if ( keylen < 0 | | ! private ) {
ret = keylen ;
goto error2 ;
}
result = mpi_alloc ( 0 ) ;
if ( ! result ) {
ret = - ENOMEM ;
goto error3 ;
}
2016-08-19 20:39:09 +02:00
/* allocate space for DH shared secret and SP800-56A otherinfo */
kbuf = kmalloc ( kdfcopy ? ( resultlen + kdfcopy - > otherinfolen ) : resultlen ,
GFP_KERNEL ) ;
2016-04-12 19:54:58 +01:00
if ( ! kbuf ) {
ret = - ENOMEM ;
goto error4 ;
}
2016-08-19 20:39:09 +02:00
/*
* Concatenate SP800 - 56 A otherinfo past DH shared secret - - the
* input to the KDF is ( DH shared secret | | otherinfo )
*/
if ( kdfcopy & & kdfcopy - > otherinfo & &
copy_from_user ( kbuf + resultlen , kdfcopy - > otherinfo ,
kdfcopy - > otherinfolen ) ! = 0 ) {
ret = - EFAULT ;
goto error5 ;
}
2016-04-12 19:54:58 +01:00
ret = do_dh ( result , base , private , prime ) ;
if ( ret )
goto error5 ;
ret = mpi_read_buffer ( result , kbuf , resultlen , & nbytes , NULL ) ;
if ( ret ! = 0 )
goto error5 ;
2016-08-19 20:39:09 +02:00
if ( kdfcopy ) {
ret = keyctl_dh_compute_kdf ( sdesc , buffer , buflen , kbuf ,
resultlen + kdfcopy - > otherinfolen ) ;
} else {
ret = nbytes ;
if ( copy_to_user ( buffer , kbuf , nbytes ) ! = 0 )
ret = - EFAULT ;
}
2016-04-12 19:54:58 +01:00
error5 :
2016-08-19 20:39:09 +02:00
kzfree ( kbuf ) ;
2016-04-12 19:54:58 +01:00
error4 :
mpi_free ( result ) ;
error3 :
mpi_free ( private ) ;
error2 :
mpi_free ( base ) ;
error1 :
mpi_free ( prime ) ;
out :
2016-08-19 20:39:09 +02:00
kdf_dealloc ( sdesc ) ;
2016-04-12 19:54:58 +01:00
return ret ;
}
2016-08-19 20:39:09 +02: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 ) ;
}