2011-10-14 16:25:16 +04:00
/*
* Copyright ( C ) 2011 Nokia Corporation
* Copyright ( C ) 2011 Intel Corporation
*
* Author :
* Dmitry Kasatkin < dmitry . kasatkin @ nokia . com >
* < dmitry . kasatkin @ intel . com >
*
* 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 , version 2 of the License .
*
* File : sign . c
* implements signature ( RSA ) verification
* pkcs decoding is based on LibTomCrypt code
*/
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
# include <linux/err.h>
# include <linux/module.h>
# include <linux/slab.h>
# include <linux/key.h>
# include <linux/crypto.h>
# include <crypto/hash.h>
# include <crypto/sha.h>
# include <keys/user-type.h>
# include <linux/mpi.h>
# include <linux/digsig.h>
static struct crypto_shash * shash ;
2013-01-30 13:30:05 +04:00
static const char * pkcs_1_v1_5_decode_emsa ( const unsigned char * msg ,
unsigned long msglen ,
unsigned long modulus_bitlen ,
unsigned long * outlen )
2011-10-14 16:25:16 +04:00
{
unsigned long modulus_len , ps_len , i ;
modulus_len = ( modulus_bitlen > > 3 ) + ( modulus_bitlen & 7 ? 1 : 0 ) ;
/* test message size */
if ( ( msglen > modulus_len ) | | ( modulus_len < 11 ) )
2013-01-30 13:30:05 +04:00
return NULL ;
2011-10-14 16:25:16 +04:00
/* separate encoded message */
2013-01-30 13:30:05 +04:00
if ( msg [ 0 ] ! = 0x00 | | msg [ 1 ] ! = 0x01 )
return NULL ;
2011-10-14 16:25:16 +04:00
for ( i = 2 ; i < modulus_len - 1 ; i + + )
if ( msg [ i ] ! = 0xFF )
break ;
/* separator check */
2012-01-26 21:13:26 +04:00
if ( msg [ i ] ! = 0 )
2011-10-14 16:25:16 +04:00
/* There was no octet with hexadecimal value 0x00
to separate ps from m . */
2013-01-30 13:30:05 +04:00
return NULL ;
2011-10-14 16:25:16 +04:00
ps_len = i - 2 ;
* outlen = ( msglen - ( 2 + ps_len + 1 ) ) ;
2013-01-30 13:30:05 +04:00
return msg + 2 + ps_len + 1 ;
2011-10-14 16:25:16 +04:00
}
/*
* RSA Signature verification with public key
*/
static int digsig_verify_rsa ( struct key * key ,
const char * sig , int siglen ,
const char * h , int hlen )
{
int err = - EINVAL ;
unsigned long len ;
unsigned long mlen , mblen ;
unsigned nret , l ;
2012-01-26 21:13:26 +04:00
int head , i ;
2013-01-30 13:30:05 +04:00
unsigned char * out1 = NULL ;
const char * m ;
2011-10-14 16:25:16 +04:00
MPI in = NULL , res = NULL , pkey [ 2 ] ;
2015-10-21 16:04:48 +03:00
uint8_t * p , * datap ;
const uint8_t * endp ;
const struct user_key_payload * ukp ;
2011-10-14 16:25:16 +04:00
struct pubkey_hdr * pkh ;
down_read ( & key - > sem ) ;
2015-10-21 16:04:48 +03:00
ukp = user_key_payload ( key ) ;
2012-01-26 21:13:25 +04:00
if ( ukp - > datalen < sizeof ( * pkh ) )
goto err1 ;
2011-10-14 16:25:16 +04:00
pkh = ( struct pubkey_hdr * ) ukp - > data ;
if ( pkh - > version ! = 1 )
goto err1 ;
if ( pkh - > algo ! = PUBKEY_ALGO_RSA )
goto err1 ;
if ( pkh - > nmpi ! = 2 )
goto err1 ;
datap = pkh - > mpi ;
2012-01-26 21:13:25 +04:00
endp = ukp - > data + ukp - > datalen ;
2011-10-14 16:25:16 +04:00
2012-01-26 21:13:24 +04:00
err = - ENOMEM ;
2011-10-14 16:25:16 +04:00
for ( i = 0 ; i < pkh - > nmpi ; i + + ) {
unsigned int remaining = endp - datap ;
pkey [ i ] = mpi_read_from_buffer ( datap , & remaining ) ;
2012-01-26 21:13:24 +04:00
if ( ! pkey [ i ] )
goto err ;
2011-10-14 16:25:16 +04:00
datap + = remaining ;
}
mblen = mpi_get_nbits ( pkey [ 0 ] ) ;
2013-01-30 13:30:05 +04:00
mlen = DIV_ROUND_UP ( mblen , 8 ) ;
2011-10-14 16:25:16 +04:00
2012-01-26 21:13:25 +04:00
if ( mlen = = 0 )
goto err ;
2011-10-14 16:25:16 +04:00
out1 = kzalloc ( mlen , GFP_KERNEL ) ;
if ( ! out1 )
goto err ;
nret = siglen ;
in = mpi_read_from_buffer ( sig , & nret ) ;
if ( ! in )
goto err ;
res = mpi_alloc ( mpi_get_nlimbs ( in ) * 2 ) ;
if ( ! res )
goto err ;
err = mpi_powm ( res , in , pkey [ 1 ] , pkey [ 0 ] ) ;
if ( err )
goto err ;
if ( mpi_get_nlimbs ( res ) * BYTES_PER_MPI_LIMB > mlen ) {
err = - EINVAL ;
goto err ;
}
p = mpi_get_buffer ( res , & l , NULL ) ;
if ( ! p ) {
err = - EINVAL ;
goto err ;
}
len = mlen ;
head = len - l ;
memset ( out1 , 0 , head ) ;
memcpy ( out1 + head , p , l ) ;
2013-01-25 18:54:20 +04:00
kfree ( p ) ;
2013-01-30 13:30:05 +04:00
m = pkcs_1_v1_5_decode_emsa ( out1 , len , mblen , & len ) ;
2011-10-14 16:25:16 +04:00
2013-01-30 13:30:05 +04:00
if ( ! m | | len ! = hlen | | memcmp ( m , h , hlen ) )
2012-09-12 14:26:55 +04:00
err = - EINVAL ;
2011-10-14 16:25:16 +04:00
err :
mpi_free ( in ) ;
mpi_free ( res ) ;
kfree ( out1 ) ;
2012-01-26 21:13:24 +04:00
while ( - - i > = 0 )
mpi_free ( pkey [ i ] ) ;
2011-10-14 16:25:16 +04:00
err1 :
up_read ( & key - > sem ) ;
return err ;
}
/**
* digsig_verify ( ) - digital signature verification with public key
* @ keyring : keyring to search key in
* @ sig : digital signature
2014-06-05 03:11:57 +04:00
* @ siglen : length of the signature
2011-10-14 16:25:16 +04:00
* @ data : data
* @ datalen : length of the data
2014-06-05 03:11:57 +04:00
*
* Returns 0 on success , - EINVAL otherwise
2011-10-14 16:25:16 +04:00
*
* Verifies data integrity against digital signature .
* Currently only RSA is supported .
* Normally hash of the content is used as a data for this function .
*
*/
int digsig_verify ( struct key * keyring , const char * sig , int siglen ,
const char * data , int datalen )
{
int err = - ENOMEM ;
struct signature_hdr * sh = ( struct signature_hdr * ) sig ;
struct shash_desc * desc = NULL ;
unsigned char hash [ SHA1_DIGEST_SIZE ] ;
struct key * key ;
char name [ 20 ] ;
if ( siglen < sizeof ( * sh ) + 2 )
return - EINVAL ;
if ( sh - > algo ! = PUBKEY_ALGO_RSA )
return - ENOTSUPP ;
sprintf ( name , " %llX " , __be64_to_cpup ( ( uint64_t * ) sh - > keyid ) ) ;
if ( keyring ) {
/* search in specific keyring */
key_ref_t kref ;
kref = keyring_search ( make_key_ref ( keyring , 1UL ) ,
& key_type_user , name ) ;
if ( IS_ERR ( kref ) )
2013-11-13 03:09:51 +04:00
key = ERR_CAST ( kref ) ;
2011-10-14 16:25:16 +04:00
else
key = key_ref_to_ptr ( kref ) ;
} else {
key = request_key ( & key_type_user , name , NULL ) ;
}
if ( IS_ERR ( key ) ) {
pr_err ( " key not found, id: %s \n " , name ) ;
return PTR_ERR ( key ) ;
}
desc = kzalloc ( sizeof ( * desc ) + crypto_shash_descsize ( shash ) ,
GFP_KERNEL ) ;
if ( ! desc )
goto err ;
desc - > tfm = shash ;
desc - > flags = CRYPTO_TFM_REQ_MAY_SLEEP ;
crypto_shash_init ( desc ) ;
crypto_shash_update ( desc , data , datalen ) ;
crypto_shash_update ( desc , sig , sizeof ( * sh ) ) ;
crypto_shash_final ( desc , hash ) ;
kfree ( desc ) ;
/* pass signature mpis address */
err = digsig_verify_rsa ( key , sig + sizeof ( * sh ) , siglen - sizeof ( * sh ) ,
hash , sizeof ( hash ) ) ;
err :
key_put ( key ) ;
return err ? - EINVAL : 0 ;
}
EXPORT_SYMBOL_GPL ( digsig_verify ) ;
static int __init digsig_init ( void )
{
shash = crypto_alloc_shash ( " sha1 " , 0 , 0 ) ;
if ( IS_ERR ( shash ) ) {
pr_err ( " shash allocation failed \n " ) ;
return PTR_ERR ( shash ) ;
}
return 0 ;
}
static void __exit digsig_cleanup ( void )
{
crypto_free_shash ( shash ) ;
}
module_init ( digsig_init ) ;
module_exit ( digsig_cleanup ) ;
MODULE_LICENSE ( " GPL " ) ;