/* * 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 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sdp_crypto.h" int __init 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 __exit sdp_crypto_exit_sha512(void) { crypto_free_shash(sha512_tfm); sha512_tfm = NULL; } void __exit 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); }