blob: e7ee04c70b27294598429f8b37099c00469f4f5a [file] [log] [blame]
/*
* Copyright (C) 2018 Samsung Electronics Co., Ltd.
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include <crypto/hash.h>
#include <crypto/rng.h>
#include <crypto/sha.h>
#include <keys/encrypted-type.h>
#include <keys/user-type.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/kern_levels.h>
#include <linux/random.h>
#include <linux/scatterlist.h>
#include <linux/string.h>
#include <linux/types.h>
#include <uapi/linux/keyctl.h>
#include "sdp_crypto.h"
int sdp_crypto_init(void)
{
// Defer effective initialization until the time /dev/random is ready.
printk(KERN_INFO "sdp_crypto: initialized!\n");
return 0;
}
/* Codes are extracted from fs/crypto/crypto_sec.c */
#ifdef CONFIG_CRYPTO_FIPS
static struct crypto_rng *sdp_crypto_rng = NULL;
#endif
static struct crypto_shash *sha512_tfm = NULL;
#ifdef CONFIG_CRYPTO_FIPS
static int sdp_crypto_init_rng(void)
{
struct crypto_rng *rng = NULL;
struct file *filp = NULL;
char *rng_seed = NULL;
mm_segment_t fs_type;
int trial = 10;
int read = 0;
int res = 0;
/* already initialized */
if (sdp_crypto_rng) {
printk(KERN_ERR "sdp_crypto: sdp_crypto_rng already initialized\n");
return 0;
}
rng_seed = kmalloc(SDP_CRYPTO_RNG_SEED_SIZE, GFP_KERNEL);
if (!rng_seed) {
printk(KERN_ERR "sdp_crypto: failed to allocate rng_seed memory\n");
res = -ENOMEM;
goto out;
}
memset((void *)rng_seed, 0, SDP_CRYPTO_RNG_SEED_SIZE);
// open random device for drbg seed number
filp = filp_open("/dev/random", O_RDONLY, 0);
if (IS_ERR(filp)) {
res = PTR_ERR(filp);
printk(KERN_ERR "sdp_crypto: failed to open random(err:%d)\n", res);
filp = NULL;
goto out;
}
fs_type = get_fs();
set_fs(KERNEL_DS);
while (trial > 0) {
int get_bytes = (int)filp->f_op->read(filp, &(rng_seed[read]),
SDP_CRYPTO_RNG_SEED_SIZE-read, &filp->f_pos);
if (likely(get_bytes > 0))
read += get_bytes;
if (likely(read == SDP_CRYPTO_RNG_SEED_SIZE))
break;
trial--;
}
set_fs(fs_type);
if (read != SDP_CRYPTO_RNG_SEED_SIZE) {
printk(KERN_ERR "sdp_crypto: failed to get enough random bytes "
"(read=%d / request=%d)\n", read, SDP_CRYPTO_RNG_SEED_SIZE);
res = -EINVAL;
goto out;
}
// create drbg for random number generation
rng = crypto_alloc_rng("stdrng", 0, 0);
if (IS_ERR(rng)) {
printk(KERN_ERR "sdp_crypto: failed to allocate rng, "
"not available (%ld)\n", PTR_ERR(rng));
res = PTR_ERR(rng);
rng = NULL;
goto out;
}
// push seed to drbg
res = crypto_rng_reset(rng, rng_seed, SDP_CRYPTO_RNG_SEED_SIZE);
if (res < 0)
printk(KERN_ERR "sdp_crypto: rng reset fail (%d)\n", res);
out:
if (res && rng) {
crypto_free_rng(rng);
rng = NULL;
}
if (filp)
filp_close(filp, NULL);
kfree(rng_seed);
// save rng on global variable
sdp_crypto_rng = rng;
return res;
}
#endif
int sdp_crypto_generate_key(void *raw_key, int nbytes)
{
#ifdef CONFIG_CRYPTO_FIPS
int res;
int trial = 10;
if (likely(sdp_crypto_rng)) {
again:
BUG_ON(!sdp_crypto_rng);
return crypto_rng_get_bytes(sdp_crypto_rng,
raw_key, nbytes);
}
do {
res = sdp_crypto_init_rng();
if (!res)
goto again;
printk(KERN_DEBUG "sdp_crypto: try to sdp_crypto_init_rng(%d)\n", trial);
msleep(500);
trial--;
} while(trial > 0);
printk(KERN_ERR "sdp_crypto: failed to initialize "
"sdp crypto rng handler again (err:%d)\n", res);
return res;
#else
get_random_bytes(raw_key, nbytes);
return 0;
#endif
}
static void sdp_crypto_exit_rng(void)
{
#ifdef CONFIG_CRYPTO_FIPS
if (!sdp_crypto_rng)
return;
printk(KERN_DEBUG "sdp_crypto: release sdp crypto rng!\n");
crypto_free_rng(sdp_crypto_rng);
sdp_crypto_rng = NULL;
#else
return;
#endif
}
static void sdp_crypto_exit_sha512(void)
{
crypto_free_shash(sha512_tfm);
sha512_tfm = NULL;
}
void sdp_crypto_exit(void)
{
sdp_crypto_exit_rng();
sdp_crypto_exit_sha512();
}
int sdp_crypto_hash_sha512(const u8 *data, u32 data_len, u8 *hashed)
{
struct crypto_shash *tfm = READ_ONCE(sha512_tfm);
/* init hash transform on demand */
if (unlikely(!tfm)) {
struct crypto_shash *prev_tfm;
tfm = crypto_alloc_shash("sha512", 0, 0);
if (IS_ERR(tfm)) {
printk(KERN_ERR
"sdp_crypto_sha512: error allocating SHA-512 transform: %ld",
PTR_ERR(tfm));
return PTR_ERR(tfm);
}
prev_tfm = cmpxchg(&sha512_tfm, NULL, tfm);
if (prev_tfm) {
crypto_free_shash(tfm);
tfm = prev_tfm;
}
}
{
SHASH_DESC_ON_STACK(desc, tfm);
desc->tfm = tfm;
desc->flags = 0;
return crypto_shash_digest(desc, data, data_len, hashed);
}
}
int sdp_crypto_aes_gcm_encrypt(struct crypto_aead *tfm,
u8 *data, size_t data_len, u8 *auth, u8 *iv)
{
struct scatterlist sg[3];
struct aead_request *aead_req;
int reqsize;
int err;
u8 *__aad;
if (tfm == NULL || iv == NULL || data == NULL || auth == NULL) {
printk(KERN_ERR
"sdp_crypto_aes_gcm_encrypt: failed due to invalid input\n");
return -EINVAL;
}
reqsize = sizeof(*aead_req) + crypto_aead_reqsize(tfm);
aead_req = kzalloc(reqsize + SDP_CRYPTO_GCM_AAD_LEN, GFP_ATOMIC);
if (!aead_req)
return -ENOMEM;
__aad = (u8 *)aead_req + reqsize;
memcpy(__aad, SDP_CRYPTO_GCM_DEFAULT_AAD, SDP_CRYPTO_GCM_AAD_LEN);
sg_init_table(sg, 3);
sg_set_buf(&sg[0], __aad, SDP_CRYPTO_GCM_AAD_LEN);
sg_set_buf(&sg[1], data, data_len);
sg_set_buf(&sg[2], auth, SDP_CRYPTO_GCM_AUTH_LEN);
aead_request_set_tfm(aead_req, tfm);
aead_request_set_crypt(aead_req, sg, sg, data_len, iv);
aead_request_set_ad(aead_req, SDP_CRYPTO_GCM_AAD_LEN);
err = crypto_aead_encrypt(aead_req);
kzfree(aead_req);
return err;
}
int sdp_crypto_aes_gcm_encrypt_pack(struct crypto_aead *tfm, gcm_pack *pack)
{
struct scatterlist sg[3];
struct aead_request *aead_req;
int reqsize;
int err;
u8 *__aad;
u8 *__data;
u8 *__auth;
size_t __aad_len = SDP_CRYPTO_GCM_AAD_LEN;
size_t __auth_len = SDP_CRYPTO_GCM_AUTH_LEN;
size_t __data_len = CONV_TYPE_TO_DLEN(pack->type);
if (unlikely(tfm == NULL || pack == NULL || __data_len == 0)) {
printk(KERN_ERR
"sdp_crypto_aes_gcm_encrypt_pack: failed due to invalid input\n");
return -EINVAL;
}
reqsize = sizeof(*aead_req) + crypto_aead_reqsize(tfm);
aead_req = kzalloc(reqsize + __aad_len + __data_len + __auth_len, GFP_ATOMIC);
if (!aead_req)
return -ENOMEM;
__aad = (u8 *)aead_req + reqsize;
__data = __aad + __aad_len;
__auth = __data + __data_len;
memcpy(__aad, SDP_CRYPTO_GCM_DEFAULT_AAD, __aad_len);
memcpy(__data, pack->data, __data_len);
memcpy(__auth, pack->auth, __auth_len);
sg_init_table(sg, 3);
sg_set_buf(&sg[0], __aad, __aad_len);
sg_set_buf(&sg[1], __data, __data_len);
sg_set_buf(&sg[2], __auth, __auth_len);
aead_request_set_tfm(aead_req, tfm);
aead_request_set_crypt(aead_req, sg, sg, __data_len, pack->iv);
aead_request_set_ad(aead_req, __aad_len);
err = crypto_aead_encrypt(aead_req);
if (!err) {
memcpy(pack->data, __data, __data_len + __auth_len);
}
kzfree(aead_req);
return err;
}
int sdp_crypto_aes_gcm_decrypt(struct crypto_aead *tfm,
u8 *data, size_t data_len, u8 *auth, u8 *iv)
{
struct scatterlist sg[3];
struct aead_request *aead_req;
int reqsize;
int err;
u8 *__aad;
if (tfm == NULL || iv == NULL || data == NULL || auth == NULL) {
printk(KERN_ERR
"sdp_crypto_aes_gcm_decrypt: failed due to invalid input\n");
return -EINVAL;
}
reqsize = sizeof(*aead_req) + crypto_aead_reqsize(tfm);
aead_req = kzalloc(reqsize + SDP_CRYPTO_GCM_AAD_LEN, GFP_ATOMIC);
if (!aead_req)
return -ENOMEM;
__aad = (u8 *)aead_req + reqsize;
memcpy(__aad, SDP_CRYPTO_GCM_DEFAULT_AAD, SDP_CRYPTO_GCM_AAD_LEN);
sg_init_table(sg, 3);
sg_set_buf(&sg[0], __aad, SDP_CRYPTO_GCM_AAD_LEN);
sg_set_buf(&sg[1], data, data_len);
sg_set_buf(&sg[2], auth, SDP_CRYPTO_GCM_AUTH_LEN);
aead_request_set_tfm(aead_req, tfm);
aead_request_set_crypt(aead_req, sg, sg, data_len + SDP_CRYPTO_GCM_AUTH_LEN, iv);
aead_request_set_ad(aead_req, SDP_CRYPTO_GCM_AAD_LEN);
err = crypto_aead_decrypt(aead_req);
kzfree(aead_req);
return err;
}
int sdp_crypto_aes_gcm_decrypt_pack(struct crypto_aead *tfm, gcm_pack *pack)
{
struct scatterlist sg[3];
struct aead_request *aead_req;
int reqsize;
int err;
u8 *__aad;
u8 *__data;
u8 *__auth;
size_t __aad_len = SDP_CRYPTO_GCM_AAD_LEN;
size_t __auth_len = SDP_CRYPTO_GCM_AUTH_LEN;
size_t __data_len = CONV_TYPE_TO_DLEN(pack->type);
if (unlikely(tfm == NULL || pack == NULL || __data_len == 0)) {
printk(KERN_ERR
"sdp_crypto_aes_gcm_decrypt_pack: failed due to invalid input\n");
return -EINVAL;
}
reqsize = sizeof(*aead_req) + crypto_aead_reqsize(tfm);
aead_req = kzalloc(reqsize + __aad_len + __data_len + __auth_len, GFP_ATOMIC);
if (!aead_req)
return -ENOMEM;
__aad = (u8 *)aead_req + reqsize;
__data = __aad + __aad_len;
__auth = __data + __data_len;
memcpy(__aad, SDP_CRYPTO_GCM_DEFAULT_AAD, __aad_len);
memcpy(__data, pack->data, __data_len);
memcpy(__auth, pack->auth, __auth_len);
sg_init_table(sg, 3);
sg_set_buf(&sg[0], __aad, __aad_len);
sg_set_buf(&sg[1], __data, __data_len);
sg_set_buf(&sg[2], __auth, __auth_len);
aead_request_set_tfm(aead_req, tfm);
aead_request_set_crypt(aead_req, sg, sg, __data_len + __auth_len, pack->iv);
aead_request_set_ad(aead_req, __aad_len);
err = crypto_aead_decrypt(aead_req);
if (!err) {
memcpy(pack->data, __data, __data_len);
}
kzfree(aead_req);
return err;
}
struct crypto_aead *sdp_crypto_aes_gcm_key_setup(const u8 key[], size_t key_len)
{
struct crypto_aead *tfm;
int err;
tfm = crypto_alloc_aead("gcm(aes)", 0, CRYPTO_ALG_ASYNC);
if (IS_ERR(tfm)) {
printk(KERN_ERR "sdp_crypto: failed to allocate aead handle\n");
return tfm;
}
err = crypto_aead_setkey(tfm, key, key_len);
if (err) {
printk(KERN_ERR "sdp_crypto: failed to set key for aead (err:%d)\n", err);
goto free_aead;
}
err = crypto_aead_setauthsize(tfm, SDP_CRYPTO_GCM_AUTH_LEN);
if (err) {
printk(KERN_ERR "sdp_crypto: failed to set auth size for aead (err:%d)\n", err);
goto free_aead;
}
return tfm;
free_aead:
crypto_free_aead(tfm);
return ERR_PTR(err);
}
void sdp_crypto_aes_gcm_key_free(struct crypto_aead *tfm)
{
crypto_free_aead(tfm);
}