From db92d029a4049a939146c78da9b0731fd6b3cee4 Mon Sep 17 00:00:00 2001 From: followmsi Date: Mon, 27 Feb 2023 23:07:30 +0100 Subject: [PATCH] Import new HW_RANDOM_MSM Change-Id: If9f43dd61d7e07f8e695455d8705ba881c672cbd --- drivers/char/hw_random/Kconfig | 2 + drivers/char/hw_random/Makefile | 2 +- drivers/char/hw_random/ctr_drbg.c | 938 +++++++++++++++++++++ drivers/char/hw_random/ctr_drbg.h | 114 +++ drivers/char/hw_random/fips_drbg.c | 290 +++++++ drivers/char/hw_random/fips_drbg.h | 58 ++ drivers/char/hw_random/msm_fips_selftest.c | 346 ++++++++ drivers/char/hw_random/msm_fips_selftest.h | 31 + drivers/char/hw_random/msm_rng.c | 363 +++++++- drivers/char/hw_random/msm_rng.h | 47 ++ 10 files changed, 2162 insertions(+), 29 deletions(-) create mode 100644 drivers/char/hw_random/ctr_drbg.c create mode 100644 drivers/char/hw_random/ctr_drbg.h create mode 100644 drivers/char/hw_random/fips_drbg.c create mode 100644 drivers/char/hw_random/fips_drbg.h create mode 100644 drivers/char/hw_random/msm_fips_selftest.c create mode 100644 drivers/char/hw_random/msm_fips_selftest.h create mode 100644 drivers/char/hw_random/msm_rng.h diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig index 68616b8ad180..87938e00ad07 100644 --- a/drivers/char/hw_random/Kconfig +++ b/drivers/char/hw_random/Kconfig @@ -254,6 +254,8 @@ config UML_RANDOM config HW_RANDOM_MSM tristate "Qualcomm MSM Random Number Generator support" depends on HW_RANDOM && ARCH_MSM + select CRYPTO_AES + select CRYPTO_ECB default n ---help--- This driver provides kernel-side support for the Random Number diff --git a/drivers/char/hw_random/Makefile b/drivers/char/hw_random/Makefile index c24305dedd10..d0369fdeec47 100644 --- a/drivers/char/hw_random/Makefile +++ b/drivers/char/hw_random/Makefile @@ -22,4 +22,4 @@ obj-$(CONFIG_HW_RANDOM_OCTEON) += octeon-rng.o obj-$(CONFIG_HW_RANDOM_NOMADIK) += nomadik-rng.o obj-$(CONFIG_HW_RANDOM_PICOXCELL) += picoxcell-rng.o obj-$(CONFIG_HW_RANDOM_PPC4XX) += ppc4xx-rng.o -obj-$(CONFIG_HW_RANDOM_MSM) += msm_rng.o +obj-$(CONFIG_HW_RANDOM_MSM) += msm_rng.o fips_drbg.o ctr_drbg.o msm_fips_selftest.o diff --git a/drivers/char/hw_random/ctr_drbg.c b/drivers/char/hw_random/ctr_drbg.c new file mode 100644 index 000000000000..d8da08e18ba0 --- /dev/null +++ b/drivers/char/hw_random/ctr_drbg.c @@ -0,0 +1,938 @@ +/* + * Copyright (c) 2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "ctr_drbg.h" +#include "fips_drbg.h" + +#define E_FAILURE 0Xffff +#define E_SUCCESS 0 + +#define AES128_KEY_SIZE (16) +#define AES128_BLOCK_SIZE (16) + +#define AES_TEXT_LENGTH (64) +#define MAX_TEXT_LENGTH (2048) + +uint8_t df_initial_k[16] = "\x0\x1\x2\x3\x4\x5\x6\x7\x8\x9\xa\xb\xc\xd\xe\xf"; + +static void _crypto_cipher_test_complete(struct crypto_async_request *req, + int err) +{ + struct msm_ctr_tcrypt_result_s *res = NULL; + + if (!req) + return; + + res = req->data; + if (!res) + return; + + if (err == -EINPROGRESS) + return; + res->err = err; + complete(&res->completion); +} + +static int ctr_aes_init(struct ctr_drbg_ctx_s *ctx) +{ + int status = 0; + + ctx->aes_ctx.tfm = crypto_alloc_ablkcipher("qcom-ecb(aes)", 0, 0); + if (IS_ERR(ctx->aes_ctx.tfm) || (NULL == ctx->aes_ctx.tfm)) { + pr_info("%s: qcom-ecb(aes) failed", __func__); + ctx->aes_ctx.tfm = crypto_alloc_ablkcipher("ecb(aes)", 0, 0); + pr_info("ctx->aes_ctx.tfm = %p\n", ctx->aes_ctx.tfm); + if (IS_ERR(ctx->aes_ctx.tfm) || (NULL == ctx->aes_ctx.tfm)) { + pr_err("%s: qcom-ecb(aes) failed\n", __func__); + status = -E_FAILURE; + goto out; + } + } + + ctx->aes_ctx.req = ablkcipher_request_alloc(ctx->aes_ctx.tfm, + GFP_KERNEL); + if (IS_ERR(ctx->aes_ctx.req) || (NULL == ctx->aes_ctx.req)) { + pr_info("%s: Failed to allocate request.\n", __func__); + status = -E_FAILURE; + goto clr_tfm; + } + + ablkcipher_request_set_callback(ctx->aes_ctx.req, + CRYPTO_TFM_REQ_MAY_BACKLOG, + _crypto_cipher_test_complete, + &ctx->aes_ctx.result); + + memset(&ctx->aes_ctx.input, 0, sizeof(struct msm_ctr_buffer_s)); + memset(&ctx->aes_ctx.output, 0, sizeof(struct msm_ctr_buffer_s)); + + /* Allocate memory. */ + ctx->aes_ctx.input.virt_addr = kmalloc(AES128_BLOCK_SIZE, + GFP_KERNEL | __GFP_DMA); + if (NULL == ctx->aes_ctx.input.virt_addr) { + pr_debug("%s: Failed to input memory.\n", __func__); + status = -E_FAILURE; + goto clr_req; + } + ctx->aes_ctx.output.virt_addr = kmalloc(AES128_BLOCK_SIZE, + GFP_KERNEL | __GFP_DMA); + if (NULL == ctx->aes_ctx.output.virt_addr) { + pr_debug("%s: Failed to output memory.\n", __func__); + status = -E_FAILURE; + goto clr_input; + } + + /*-------------------------------------------------------------------- + Set DF AES mode + ----------------------------------------------------------------------*/ + ctx->df_aes_ctx.tfm = crypto_alloc_ablkcipher("qcom-ecb(aes)", 0, 0); + if ((NULL == ctx->df_aes_ctx.tfm) || IS_ERR(ctx->df_aes_ctx.tfm)) { + pr_info("%s: qcom-ecb(aes) failed", __func__); + ctx->df_aes_ctx.tfm = crypto_alloc_ablkcipher("ecb(aes)", 0, 0); + if (IS_ERR(ctx->df_aes_ctx.tfm) || + (NULL == ctx->df_aes_ctx.tfm)) { + pr_err("%s: ecb(aes) failed", __func__); + status = -E_FAILURE; + goto clr_output; + } + } + + ctx->df_aes_ctx.req = ablkcipher_request_alloc(ctx->df_aes_ctx.tfm, + GFP_KERNEL); + if (IS_ERR(ctx->df_aes_ctx.req) || (NULL == ctx->df_aes_ctx.req)) { + pr_debug(": Failed to allocate request.\n"); + status = -E_FAILURE; + goto clr_df_tfm; + } + + ablkcipher_request_set_callback(ctx->df_aes_ctx.req, + CRYPTO_TFM_REQ_MAY_BACKLOG, + _crypto_cipher_test_complete, + &ctx->df_aes_ctx.result); + + memset(&ctx->df_aes_ctx.input, 0, sizeof(struct msm_ctr_buffer_s)); + memset(&ctx->df_aes_ctx.output, 0, sizeof(struct msm_ctr_buffer_s)); + + ctx->df_aes_ctx.input.virt_addr = kmalloc(AES128_BLOCK_SIZE, + GFP_KERNEL | __GFP_DMA); + if (NULL == ctx->df_aes_ctx.input.virt_addr) { + pr_debug(": Failed to input memory.\n"); + status = -E_FAILURE; + goto clr_df_req; + } + + ctx->df_aes_ctx.output.virt_addr = kmalloc(AES128_BLOCK_SIZE, + GFP_KERNEL | __GFP_DMA); + if (NULL == ctx->df_aes_ctx.output.virt_addr) { + pr_debug(": Failed to output memory.\n"); + status = -E_FAILURE; + goto clr_df_input; + } + + goto out; + +clr_df_input: + if (ctx->df_aes_ctx.input.virt_addr) { + kzfree(ctx->df_aes_ctx.input.virt_addr); + ctx->df_aes_ctx.input.virt_addr = NULL; + } +clr_df_req: + if (ctx->df_aes_ctx.req) { + ablkcipher_request_free(ctx->df_aes_ctx.req); + ctx->df_aes_ctx.req = NULL; + } +clr_df_tfm: + if (ctx->df_aes_ctx.tfm) { + crypto_free_ablkcipher(ctx->df_aes_ctx.tfm); + ctx->df_aes_ctx.tfm = NULL; + } +clr_output: + if (ctx->aes_ctx.output.virt_addr) { + kzfree(ctx->aes_ctx.output.virt_addr); + ctx->aes_ctx.output.virt_addr = NULL; + } +clr_input: + if (ctx->aes_ctx.input.virt_addr) { + kzfree(ctx->aes_ctx.input.virt_addr); + ctx->aes_ctx.input.virt_addr = NULL; + } +clr_req: + if (ctx->aes_ctx.req) { + ablkcipher_request_free(ctx->aes_ctx.req); + ctx->aes_ctx.req = NULL; + } +clr_tfm: + if (ctx->aes_ctx.tfm) { + crypto_free_ablkcipher(ctx->aes_ctx.tfm); + ctx->aes_ctx.tfm = NULL; + } +out: + return status; +} + +/* + * Increments the V field in *ctx + */ +static void increment_V(struct ctr_drbg_ctx_s *ctx) +{ + unsigned sum = 1; + int i; + uint8_t *p = &ctx->seed.key_V.V[0]; + + /* + * To make known answer tests work, this has to be done big_endian. + * So we just do it by bytes. + * since we are using AES-128, the key size is 16 bytes. + */ + for (i = 15; sum != 0 && i >= 0; --i) { + sum += p[i]; + p[i] = (sum & 0xff); + sum >>= 8; + } + + return; +} + +/* + * The NIST update function. It updates the key and V to new values + * (to prevent backtracking) and optionally stirs in data. data may + * be null, otherwise *data is from 0 to 256 bits long. + * keysched is an optional keyschedule to use as an optimization. It + * must be consistent with the key in *ctx. No changes are made to + * *ctx until it is assured that there will be no failures. Note that + * data_len is in bytes. (That may not be offical NIST + * recommendation, but I do it anyway; they say "or equivalent" and + * this is equivalent enough.) + */ +static enum ctr_drbg_status_t +update(struct ctr_drbg_ctx_s *ctx, const uint8_t *data, size_t data_len) +{ + uint8_t temp[32]; + unsigned int i; + int rc; + struct scatterlist sg_in, sg_out; + + for (i = 0; i < 2; ++i) { + increment_V(ctx); + init_completion(&ctx->aes_ctx.result.completion); + + /* + * Note: personalize these called routines for + * specific testing. + */ + memcpy(ctx->aes_ctx.input.virt_addr, + ctx->seed.key_V.V, + CTR_DRBG_BLOCK_LEN_BYTES); + + crypto_ablkcipher_clear_flags(ctx->aes_ctx.tfm, ~0); + + /* Encrypt some clear text! */ + + sg_init_one(&sg_in, + ctx->aes_ctx.input.virt_addr, + AES128_BLOCK_SIZE); + sg_init_one(&sg_out, + ctx->aes_ctx.output.virt_addr, + AES128_BLOCK_SIZE); + ablkcipher_request_set_crypt(ctx->aes_ctx.req, + &sg_in, + &sg_out, + CTR_DRBG_BLOCK_LEN_BYTES, + NULL); + + rc = crypto_ablkcipher_encrypt(ctx->aes_ctx.req); + + switch (rc) { + case 0: + break; + case -EINPROGRESS: + case -EBUSY: + rc = wait_for_completion_interruptible( + &ctx->aes_ctx.result.completion); + if (!rc && !ctx->aes_ctx.result.err) { + INIT_COMPLETION(ctx->aes_ctx.result.completion); + break; + } + /* fall through */ + default: + pr_debug("crypto_ablkcipher_encrypt returned"); + pr_debug(" with %d result %d on iteration\n", + rc, + ctx->aes_ctx.result.err); + break; + } + + init_completion(&ctx->aes_ctx.result.completion); + + memcpy(temp + AES128_BLOCK_SIZE * i, + ctx->aes_ctx.output.virt_addr, + AES128_BLOCK_SIZE); + } + + if (data_len > 0) + pr_debug("in upadte, data_len = %zu\n", data_len); + + for (i = 0; i < data_len; ++i) + ctx->seed.as_bytes[i] = temp[i] ^ data[i]; + + /* now copy the rest of temp to key and V */ + if (32 > data_len) { + memcpy(ctx->seed.as_bytes + data_len, + temp + data_len, + 32 - data_len); + } + + memset(temp, 0, 32); + return CTR_DRBG_SUCCESS; +} + +/* + * Reseeds the CTR_DRBG instance with entropy. entropy_len_bits must + * be exactly 256. + */ +enum ctr_drbg_status_t ctr_drbg_reseed(struct ctr_drbg_ctx_s *ctx, + const void *entropy, + size_t entropy_len_bits) +{ + enum ctr_drbg_status_t update_rv; + uint8_t seed_material[32]; + int rc; + + if (ctx == NULL || entropy == NULL) + return CTR_DRBG_INVALID_ARG; + + update_rv = block_cipher_df(ctx, + (uint8_t *)entropy, + (entropy_len_bits / 8), + seed_material, + 32 + ); + if (CTR_DRBG_SUCCESS != update_rv) { + memset(seed_material, 0, 32); + return CTR_DRBG_GENERAL_ERROR; + } + + rc = crypto_ablkcipher_setkey(ctx->aes_ctx.tfm, + ctx->seed.key_V.key, + AES128_KEY_SIZE + ); + if (rc) { + memset(seed_material, 0, 32); + pr_debug("set-key in Instantiate failed, returns with %d", rc); + return CTR_DRBG_GENERAL_ERROR; + } + + pr_debug("ctr_drbg_reseed, to call update\n"); + update_rv = update(ctx, (const uint8_t *)seed_material, 32); + pr_debug("ctr_drbg_reseed, after called update\n"); + if (update_rv != CTR_DRBG_SUCCESS) { + memset(seed_material, 0, 32); + return update_rv; + } + ctx->reseed_counter = 1; /* think 0 but SP 800-90 says 1 */ + + memset(seed_material, 0, 32); + + return CTR_DRBG_SUCCESS; + +} + +/* + * The NIST instantiate function. entropy_len_bits must be exactly + * 256. After reseed_interval generate requests, generated requests + * will fail until the CTR_DRBG instance is reseeded. As per NIST SP + * 800-90, an error is returned if reseed_interval > 2^48. + */ + +enum ctr_drbg_status_t +ctr_drbg_instantiate(struct ctr_drbg_ctx_s *ctx, + const uint8_t *entropy, + size_t entropy_len_bits, + const uint8_t *nonce, + size_t nonce_len_bits, + unsigned long long reseed_interval) +{ + + enum ctr_drbg_status_t update_rv; + uint8_t seed_material[32]; + uint8_t df_input[32]; + int rc; + + if (ctx == NULL || entropy == NULL || nonce == NULL) + return CTR_DRBG_INVALID_ARG; + if (((nonce_len_bits / 8) + (entropy_len_bits / 8)) > 32) { + pr_info("\nentropy_len_bits + nonce_len_bits is too long!"); + pr_info("\nnonce len: %zu, entropy: %zu\n", + nonce_len_bits, entropy_len_bits); + return CTR_DRBG_INVALID_ARG + 1; + } + + if (reseed_interval > (1ULL << 48)) + return CTR_DRBG_INVALID_ARG + 2; + + ctr_aes_init(ctx); + + memset(ctx->seed.as_bytes, 0, sizeof(ctx->seed.as_bytes)); + memcpy(df_input, (uint8_t *)entropy, entropy_len_bits / 8); + memcpy(df_input + (entropy_len_bits / 8), nonce, nonce_len_bits / 8); + + update_rv = block_cipher_df(ctx, df_input, 24, seed_material, 32); + memset(df_input, 0, 32); + + if (CTR_DRBG_SUCCESS != update_rv) { + pr_debug("block_cipher_df failed, returns %d", update_rv); + memset(seed_material, 0, 32); + return CTR_DRBG_GENERAL_ERROR; + } + + rc = crypto_ablkcipher_setkey(ctx->aes_ctx.tfm, + ctx->seed.key_V.key, + AES128_KEY_SIZE); + if (rc) { + pr_debug("crypto_ablkcipher_setkey API failed: %d", rc); + memset(seed_material, 0, 32); + return CTR_DRBG_GENERAL_ERROR; + } + update_rv = update(ctx, (const uint8_t *)seed_material, 32); + if (update_rv != CTR_DRBG_SUCCESS) { + memset(seed_material, 0, 32); + return update_rv; + } + + ctx->reseed_counter = 1; /* think 0 but SP 800-90 says 1 */ + ctx->reseed_interval = reseed_interval; + + memset(seed_material, 0, 32); + + pr_debug(" return from ctr_drbg_instantiate\n"); + + return CTR_DRBG_SUCCESS; +} + +/* + * Generate random bits. len_bits is specified in bits, as required by + * NIST SP800-90. It fails with CTR_DRBG_NEEDS_RESEED if the number + * of generates since instantiation or the last reseed >= the + * reseed_interval supplied at instantiation. len_bits must be a + * multiple of 8. len_bits must not exceed 2^19, as per NIST SP + * 800-90. Optionally stirs in additional_input which is + * additional_input_len_bits long, and is silently rounded up to a + * multiple of 8. CTR_DRBG_INVALID_ARG is returned if any pointer arg + * is null and the corresponding length is non-zero or if + * additioanl_input_len_bits > 256. + */ +enum ctr_drbg_status_t +ctr_drbg_generate_w_data(struct ctr_drbg_ctx_s *ctx, + void *additional_input, + size_t additional_input_len_bits, + void *buffer, + size_t len_bits) +{ + size_t total_blocks = (len_bits + 127) / 128; + enum ctr_drbg_status_t update_rv; + int rv = 0; + size_t i; + int rc; + struct scatterlist sg_in, sg_out; + + if (ctx == NULL) + return CTR_DRBG_INVALID_ARG; + if (buffer == NULL && len_bits > 0) + return CTR_DRBG_INVALID_ARG; + if (len_bits % 8 != 0) + return CTR_DRBG_INVALID_ARG; + if (len_bits > (1<<19)) + return CTR_DRBG_INVALID_ARG; + + if ((additional_input == NULL && additional_input_len_bits > 0) || + additional_input_len_bits > CTR_DRBG_SEED_LEN_BITS) + return CTR_DRBG_INVALID_ARG; + if (ctx->reseed_counter > ctx->reseed_interval) + return CTR_DRBG_NEEDS_RESEED; + + rc = crypto_ablkcipher_setkey(ctx->aes_ctx.tfm, + ctx->seed.key_V.key, + AES128_KEY_SIZE); + if (rc) { + pr_debug("crypto_ablkcipher_setkey API failed: %d", rc); + return CTR_DRBG_GENERAL_ERROR; + } + if (rv < 0) + return CTR_DRBG_GENERAL_ERROR; + + if (!ctx->continuous_test_started) { + increment_V(ctx); + init_completion(&ctx->aes_ctx.result.completion); + crypto_ablkcipher_clear_flags(ctx->aes_ctx.tfm, ~0); + memcpy(ctx->aes_ctx.input.virt_addr, ctx->seed.key_V.V, 16); + sg_init_one(&sg_in, ctx->aes_ctx.input.virt_addr, 16); + sg_init_one(&sg_out, ctx->aes_ctx.output.virt_addr, 16); + ablkcipher_request_set_crypt(ctx->aes_ctx.req, &sg_in, &sg_out, + CTR_DRBG_BLOCK_LEN_BYTES, NULL); + rc = crypto_ablkcipher_encrypt(ctx->aes_ctx.req); + switch (rc) { + case 0: + break; + case -EINPROGRESS: + case -EBUSY: + rc = wait_for_completion_interruptible( + &ctx->aes_ctx.result.completion); + if (!rc && !ctx->aes_ctx.result.err) { + INIT_COMPLETION(ctx->aes_ctx.result.completion); + break; + } + /* fall through */ + default: + pr_debug(":crypto_ablkcipher_encrypt returned with %d result %d on iteration\n", + rc, + ctx->aes_ctx.result.err); + break; + } + init_completion(&ctx->aes_ctx.result.completion); + + memcpy(ctx->prev_drn, ctx->aes_ctx.output.virt_addr, 16); + ctx->continuous_test_started = 1; + } + + /* Generate the output */ + for (i = 0; i < total_blocks; ++i) { + /* Increment the counter */ + increment_V(ctx); + if (((len_bits % 128) != 0) && (i == (total_blocks - 1))) { + /* last block and it's a fragment */ + init_completion(&ctx->aes_ctx.result.completion); + + /* + * Note: personalize these called routines for + * specific testing. + */ + + crypto_ablkcipher_clear_flags(ctx->aes_ctx.tfm, ~0); + + /* Encrypt some clear text! */ + + memcpy(ctx->aes_ctx.input.virt_addr, + ctx->seed.key_V.V, + 16); + sg_init_one(&sg_in, + ctx->aes_ctx.input.virt_addr, + 16); + sg_init_one(&sg_out, + ctx->aes_ctx.output.virt_addr, + 16); + ablkcipher_request_set_crypt(ctx->aes_ctx.req, + &sg_in, + &sg_out, + CTR_DRBG_BLOCK_LEN_BYTES, + NULL); + + rc = crypto_ablkcipher_encrypt(ctx->aes_ctx.req); + + switch (rc) { + case 0: + break; + case -EINPROGRESS: + case -EBUSY: + rc = wait_for_completion_interruptible( + &ctx->aes_ctx.result.completion); + if (!rc && !ctx->aes_ctx.result.err) { + INIT_COMPLETION( + ctx->aes_ctx.result.completion); + break; + } + /* fall through */ + default: + break; + } + + init_completion(&ctx->aes_ctx.result.completion); + + if (!memcmp(ctx->prev_drn, + ctx->aes_ctx.output.virt_addr, + 16)) + return CTR_DRBG_GENERAL_ERROR; + else + memcpy(ctx->prev_drn, + ctx->aes_ctx.output.virt_addr, + 16); + rv = 0; + memcpy((uint8_t *)buffer + 16*i, + ctx->aes_ctx.output.virt_addr, + (len_bits % 128)/8); + } else { + /* normal case: encrypt direct to target buffer */ + + init_completion(&ctx->aes_ctx.result.completion); + + /* + * Note: personalize these called routines for + * specific testing. + */ + + crypto_ablkcipher_clear_flags(ctx->aes_ctx.tfm, ~0); + + /* Encrypt some clear text! */ + + memcpy(ctx->aes_ctx.input.virt_addr, + ctx->seed.key_V.V, + 16); + sg_init_one(&sg_in, + ctx->aes_ctx.input.virt_addr, + 16); + sg_init_one(&sg_out, + ctx->aes_ctx.output.virt_addr, + 16); + ablkcipher_request_set_crypt(ctx->aes_ctx.req, + &sg_in, + &sg_out, + CTR_DRBG_BLOCK_LEN_BYTES, + NULL); + + rc = crypto_ablkcipher_encrypt(ctx->aes_ctx.req); + + switch (rc) { + case 0: + break; + case -EINPROGRESS: + case -EBUSY: + rc = wait_for_completion_interruptible( + &ctx->aes_ctx.result.completion); + if (!rc && !ctx->aes_ctx.result.err) { + INIT_COMPLETION( + ctx->aes_ctx.result.completion); + break; + } + /* fall through */ + default: + break; + } + + if (!memcmp(ctx->prev_drn, + ctx->aes_ctx.output.virt_addr, + 16)) + return CTR_DRBG_GENERAL_ERROR; + else + memcpy(ctx->prev_drn, + ctx->aes_ctx.output.virt_addr, + 16); + + memcpy((uint8_t *)buffer + 16*i, + ctx->aes_ctx.output.virt_addr, + 16); + rv = 0; + } + } + + update_rv = update(ctx, + additional_input, + (additional_input_len_bits + 7) / 8); /* round up */ + if (update_rv != CTR_DRBG_SUCCESS) + return update_rv; + + ctx->reseed_counter += 1; + + return CTR_DRBG_SUCCESS; +} + +/* + * Generate random bits, but with no provided data. See notes on + * ctr_drbg_generate_w_data() + */ +enum ctr_drbg_status_t +ctr_drbg_generate(struct ctr_drbg_ctx_s *ctx, + void *buffer, + size_t len_bits) + +{ + return ctr_drbg_generate_w_data(ctx, NULL, 0, buffer, len_bits); +} + +void ctr_aes_deinit(struct ctr_drbg_ctx_s *ctx) +{ + if (ctx->aes_ctx.req) { + ablkcipher_request_free(ctx->aes_ctx.req); + ctx->aes_ctx.req = NULL; + } + if (ctx->aes_ctx.tfm) { + crypto_free_ablkcipher(ctx->aes_ctx.tfm); + ctx->aes_ctx.tfm = NULL; + } + if (ctx->aes_ctx.input.virt_addr) { + kzfree(ctx->aes_ctx.input.virt_addr); + ctx->aes_ctx.input.virt_addr = NULL; + } + if (ctx->aes_ctx.output.virt_addr) { + kzfree(ctx->aes_ctx.output.virt_addr); + ctx->aes_ctx.output.virt_addr = NULL; + } + if (ctx->df_aes_ctx.req) { + ablkcipher_request_free(ctx->df_aes_ctx.req); + ctx->df_aes_ctx.req = NULL; + } + if (ctx->df_aes_ctx.tfm) { + crypto_free_ablkcipher(ctx->df_aes_ctx.tfm); + ctx->df_aes_ctx.tfm = NULL; + } + if (ctx->df_aes_ctx.input.virt_addr) { + kzfree(ctx->df_aes_ctx.input.virt_addr); + ctx->df_aes_ctx.input.virt_addr = NULL; + } + if (ctx->df_aes_ctx.output.virt_addr) { + kzfree(ctx->df_aes_ctx.output.virt_addr); + ctx->df_aes_ctx.output.virt_addr = NULL; + } + +} + +/* + * Zeroizes the context structure. In some future implemenation it + * could also free resources. So do call it. + */ +void +ctr_drbg_uninstantiate(struct ctr_drbg_ctx_s *ctx) +{ + ctr_aes_deinit(ctx); + memset(ctx, 0, sizeof(*ctx)); +} + +/* + * the derivation functions to handle biased entropy input. + */ +enum ctr_drbg_status_t df_bcc_func(struct ctr_drbg_ctx_s *ctx, + uint8_t *key, + uint8_t *input, + uint32_t input_size, + uint8_t *output) +{ + enum ctr_drbg_status_t ret_val = CTR_DRBG_SUCCESS; + uint8_t *p; + int rc; + int i; + int n; + struct scatterlist sg_in, sg_out; + + if (0 != (input_size % CTR_DRBG_BLOCK_LEN_BYTES)) + return CTR_DRBG_INVALID_ARG; + + n = input_size / CTR_DRBG_BLOCK_LEN_BYTES; + + for (i = 0; i < CTR_DRBG_BLOCK_LEN_BYTES; i++) + ctx->df_aes_ctx.output.virt_addr[i] = 0; + + rc = crypto_ablkcipher_setkey(ctx->df_aes_ctx.tfm, + key, + AES128_KEY_SIZE); + if (rc) { + pr_debug("crypto_ablkcipher_setkey API failed: %d\n", rc); + return CTR_DRBG_GENERAL_ERROR; + } + + p = input; + while (n > 0) { + for (i = 0; i < CTR_DRBG_BLOCK_LEN_BYTES; i++, p++) + ctx->df_aes_ctx.input.virt_addr[i] = + ctx->df_aes_ctx.output.virt_addr[i] ^ (*p); + + init_completion(&ctx->df_aes_ctx.result.completion); + + /* + * Note: personalize these called routines for + * specific testing. + */ + + crypto_ablkcipher_clear_flags(ctx->df_aes_ctx.tfm, ~0); + + /* Encrypt some clear text! */ + + sg_init_one(&sg_in, ctx->df_aes_ctx.input.virt_addr, 16); + sg_init_one(&sg_out, ctx->df_aes_ctx.output.virt_addr, 16); + + ablkcipher_request_set_crypt(ctx->df_aes_ctx.req, + &sg_in, + &sg_out, + CTR_DRBG_BLOCK_LEN_BYTES, + NULL); + + rc = crypto_ablkcipher_encrypt(ctx->df_aes_ctx.req); + + switch (rc) { + case 0: + break; + case -EINPROGRESS: + case -EBUSY: + rc = wait_for_completion_interruptible( + &ctx->df_aes_ctx.result.completion); + if (!rc && !ctx->df_aes_ctx.result.err) { + INIT_COMPLETION( + ctx->df_aes_ctx.result.completion); + break; + } + /* fall through */ + default: + break; + } + + init_completion(&ctx->df_aes_ctx.result.completion); + n--; + } + + for (i = 0; i < CTR_DRBG_BLOCK_LEN_BYTES; i++) + output[i] = ctx->df_aes_ctx.output.virt_addr[i]; + + return ret_val; +} + +/* output_size must <= 512 bits (<= 64) */ +enum ctr_drbg_status_t +block_cipher_df(struct ctr_drbg_ctx_s *ctx, + const uint8_t *input, + uint32_t input_size, + uint8_t *output, + uint32_t output_size) +{ + enum ctr_drbg_status_t ret_val = CTR_DRBG_SUCCESS; + uint32_t s_len = 0; + uint32_t s_pad_len = 0; + uint8_t temp[32]; + uint32_t out_len = 0; + uint8_t siv_string[64]; + uint8_t *p_s_string = NULL; + int rc; + struct scatterlist sg_in, sg_out; + + if (output_size > 64) + return CTR_DRBG_INVALID_ARG; + + s_len = input_size + 9; + + s_pad_len = s_len % 16; + + if (0 != s_pad_len) + s_len += (16 - s_pad_len); + + /* add the length of IV */ + s_len += 16; + + if (s_len > 64) + pr_debug("error! s_len is too big!!!!!!!!!!!!\n"); + + memset(siv_string, 0, 64); + + p_s_string = siv_string + 16; + + p_s_string[3] = input_size; + p_s_string[7] = output_size; + memcpy(p_s_string + 8, input, input_size); + p_s_string[8 + input_size] = 0x80; + if (0 < s_pad_len) + memset(p_s_string + 9 + input_size, '\0', s_pad_len); + + ret_val = df_bcc_func(ctx, df_initial_k, siv_string, s_len, temp); + + if (CTR_DRBG_SUCCESS != ret_val) { + pr_debug("df_bcc_func failed, returned %d", ret_val); + goto out; + } + + siv_string[3] = 0x1; + ret_val = df_bcc_func(ctx, df_initial_k, siv_string, s_len, temp + 16); + + if (CTR_DRBG_SUCCESS != ret_val) + goto out; + + out_len = 0; + rc = crypto_ablkcipher_setkey(ctx->df_aes_ctx.tfm, + temp, + AES128_KEY_SIZE); + if (rc) { + pr_debug("crypto_ablkcipher_setkey API failed: %d", rc); + goto out; + } + memcpy(ctx->df_aes_ctx.input.virt_addr, temp + 16, 16); + + while (out_len < output_size) { + + init_completion(&ctx->df_aes_ctx.result.completion); + + /* + * Note: personalize these called routines for + * specific testing. + */ + + crypto_ablkcipher_clear_flags(ctx->df_aes_ctx.tfm, ~0); + + /* Encrypt some clear text! */ + + sg_init_one(&sg_in, ctx->df_aes_ctx.input.virt_addr, 16); + sg_init_one(&sg_out, ctx->df_aes_ctx.output.virt_addr, 16); + ablkcipher_request_set_crypt(ctx->df_aes_ctx.req, + &sg_in, + &sg_out, + CTR_DRBG_BLOCK_LEN_BYTES, + NULL); + + rc = crypto_ablkcipher_encrypt(ctx->df_aes_ctx.req); + + switch (rc) { + case 0: + break; + case -EINPROGRESS: + case -EBUSY: + rc = wait_for_completion_interruptible( + &ctx->df_aes_ctx.result.completion); + if (!rc && !ctx->df_aes_ctx.result.err) { + INIT_COMPLETION( + ctx->df_aes_ctx.result.completion); + break; + } + /* fall through */ + default: + break; + } + + + init_completion(&ctx->df_aes_ctx.result.completion); + + memcpy(output + out_len, ctx->df_aes_ctx.output.virt_addr, 16); + memcpy(ctx->df_aes_ctx.input.virt_addr, output + out_len, 16); + out_len += 16; + } + +out: + memset(siv_string, 0, 64); + memset(temp, 0, 32); + return ret_val; +} + diff --git a/drivers/char/hw_random/ctr_drbg.h b/drivers/char/hw_random/ctr_drbg.h new file mode 100644 index 000000000000..55a9988577f3 --- /dev/null +++ b/drivers/char/hw_random/ctr_drbg.h @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + * + */ + +#ifndef __MSM_CTR_DRBG_H__ +#define __MSM_CTR_DRBG_H__ + +/* This is the module that is actually follows the details of NIST SP + * 800-90 so it can claim to use a FIPS-approved algorithm. + */ + +/* Added ctr_drbg_generate_w_data which supplies + * additional input to the generate operation. + */ + + +#define CTR_DRBG_MAX_REQ_LEN_BITS (1 << 19) +#define CTR_DRBG_SEED_LEN_BITS 256 +#define CTR_DRBG_BLOCK_LEN_BITS 128 +#define CTR_DRBG_BLOCK_LEN_BYTES (CTR_DRBG_BLOCK_LEN_BITS/8) +#define CTR_DRBG_MAX_RESEED_INTERVAL (1ULL << 48) + +#define MSM_AES128_BLOCK_SIZE (16) +#define MSM_ENTROPY_BUFFER_SIZE (16) +#define MSM_NONCE_BUFFER_SIZE (8) + +enum ctr_drbg_status_t { + CTR_DRBG_SUCCESS = 0, + CTR_DRBG_NEEDS_RESEED, + CTR_DRBG_INVALID_ARG, + CTR_DRBG_GENERAL_ERROR = 0xFF, +}; + +union ctr_drbg_seed_t { + uint8_t as_bytes[32]; + uint32_t as_words[8]; + uint64_t as_64[4]; + struct { + uint8_t key[16]; + uint8_t V[16]; + } key_V; +}; + +struct msm_ctr_tcrypt_result_s { + struct completion completion; + int err; +}; + +struct msm_ctr_buffer_s { + unsigned char *virt_addr; +}; + +struct aes_struct_s { + struct crypto_ablkcipher *tfm; + struct ablkcipher_request *req; + struct msm_ctr_buffer_s input; + struct msm_ctr_buffer_s output; + struct msm_ctr_tcrypt_result_s result; +}; + +struct ctr_drbg_ctx_s { + unsigned long long reseed_counter; /* starts at 1 as per SP + * 800-90 + */ + unsigned long long reseed_interval; + union ctr_drbg_seed_t seed; + struct aes_struct_s aes_ctx; + struct aes_struct_s df_aes_ctx; + uint8_t prev_drn[MSM_AES128_BLOCK_SIZE]; + uint8_t continuous_test_started; +}; + +enum ctr_drbg_status_t ctr_drbg_instantiate(struct ctr_drbg_ctx_s *ctx, + const uint8_t *entropy, + size_t entropy_len_bits, + const uint8_t *nonce, + size_t nonce_len_bits, + unsigned long long reseed_interval); + +enum ctr_drbg_status_t ctr_drbg_reseed(struct ctr_drbg_ctx_s *ctx, + const void *entropy, + size_t entropy_len); + +enum ctr_drbg_status_t ctr_drbg_generate_w_data(struct ctr_drbg_ctx_s *ctx, + void *additional_input, + size_t additional_input_len_bits, + void *buffer, + size_t len_bits); + +enum ctr_drbg_status_t ctr_drbg_generate(struct ctr_drbg_ctx_s *ctx, + void *buffer, + size_t len); + +void ctr_drbg_uninstantiate(struct ctr_drbg_ctx_s *ctx); + +enum ctr_drbg_status_t block_cipher_df(struct ctr_drbg_ctx_s *ctx, + const uint8_t *input, + uint32_t input_size, + uint8_t *output, + uint32_t output_size + ); +void ctr_aes_deinit(struct ctr_drbg_ctx_s *ctx); + +#endif /* __MSM_CTR_DRBG_H__ */ diff --git a/drivers/char/hw_random/fips_drbg.c b/drivers/char/hw_random/fips_drbg.c new file mode 100644 index 000000000000..7b4225e341cf --- /dev/null +++ b/drivers/char/hw_random/fips_drbg.c @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "msm_rng.h" +#include "fips_drbg.h" + +/* The fips-140 random number generator is a wrapper around the CTR_DRBG + * random number generator, which is built according to the + * specifications in NIST SP 800-90 using AES-128. + * + * This wrapper has the following functionality + * a. Entropy collection is via a callback. + * b. A failure of CTR_DRBG because reseeding is needed invisibly + * causes the underlying CTR_DRBG instance to be reseeded with + * new random data and then the generate request is retried. + * c. Limitations in CTR_DRBG (like not allowed more than 65536 bytes + * to be genrated in one request) are worked around. At this level + * it just works. + * d. On success the return value is zero. If the callback was invoked + * and returned a non-zero value, that value is returned. On all other + * errors -1 is returned. + */ + +#ifndef NULL + #define NULL 0 +#endif + +/* 32 bytes = 256 bits = seed length */ +#define MAGIC 0xab10d161 + +#define RESEED_INTERVAL (1 << 31) + +int get_entropy_callback(void *ctx, void *buf) +{ + struct msm_rng_device *msm_rng_dev = (struct msm_rng_device *)ctx; + int ret_val = -1; + + if (NULL == ctx) + return FIPS140_PRNG_ERR; + + if (NULL == buf) + return FIPS140_PRNG_ERR; + + ret_val = msm_rng_direct_read(msm_rng_dev, buf); + if ((size_t)ret_val != Q_HW_DRBG_BLOCK_BYTES) + return ret_val; + + return 0; +} + +/* Initialize *ctx. Automatically reseed after reseed_interval calls + * to fips_drbg_gen. The underlying CTR_DRBG will automatically be + * reseeded every reseed_interval requests. Values over + * CTR_DRBG_MAX_RESEED_INTERVAL (2^48) or that are zero are silently + * converted to CTR_DRBG_MAX_RESEED_INTERVAL. (It is easy to justify + * lowering values that are too large to CTR_DRBG_MAX_RESEED_INTERVAL + * (the NIST SP800-90 limit): just silently enforcing the rules. + * Silently converted 0 to to CTR_DRBG_MAX_RESEED_INTERVAL is harder. + * The alternative is to return an error. But since + * CTR_DRBG_MAX_RESEED is safe, we relieve the caller of one more + * error to worry about.) + */ +static int +do_fips_drbg_init(struct fips_drbg_ctx_s *ctx, + get_entropy_callback_t callback, + void *callback_ctx, + unsigned long long reseed_interval) +{ + uint8_t entropy_pool[Q_HW_DRBG_BLOCK_BYTES]; + enum ctr_drbg_status_t init_rv; + int rv = -1; + + if (ctx == NULL) + return FIPS140_PRNG_ERR; + if (callback == NULL) + return FIPS140_PRNG_ERR; + if (reseed_interval == 0 || + reseed_interval > CTR_DRBG_MAX_RESEED_INTERVAL) + reseed_interval = CTR_DRBG_MAX_RESEED_INTERVAL; + + /* fill in callback related fields in ctx */ + ctx->get_entropy_callback = callback; + ctx->get_entropy_callback_ctx = callback_ctx; + + if (!ctx->fips_drbg_started) { + rv = (*ctx->get_entropy_callback)(ctx->get_entropy_callback_ctx, + ctx->prev_hw_drbg_block + ); + if (rv != 0) + return FIPS140_PRNG_ERR; + ctx->fips_drbg_started = 1; + } + + rv = (*ctx->get_entropy_callback)(ctx->get_entropy_callback_ctx, + entropy_pool + ); + if (rv != 0) { + memset(entropy_pool, 0, Q_HW_DRBG_BLOCK_BYTES); + return FIPS140_PRNG_ERR; + } + + if (!memcmp(entropy_pool, + ctx->prev_hw_drbg_block, + Q_HW_DRBG_BLOCK_BYTES)) { + memset(entropy_pool, 0, Q_HW_DRBG_BLOCK_BYTES); + return FIPS140_PRNG_ERR; + } else + memcpy(ctx->prev_hw_drbg_block, + entropy_pool, + Q_HW_DRBG_BLOCK_BYTES); + + + init_rv = ctr_drbg_instantiate(&ctx->ctr_drbg_ctx, + entropy_pool, + 8 * MSM_ENTROPY_BUFFER_SIZE, + entropy_pool + MSM_ENTROPY_BUFFER_SIZE, + 8 * 8, + reseed_interval); + + memset(entropy_pool, 0, Q_HW_DRBG_BLOCK_BYTES); + + if (init_rv == 0) + ctx->magic = MAGIC; + + return 0; +} + +int fips_drbg_init(struct msm_rng_device *msm_rng_ctx) +{ + uint32_t ret_val = 0; + + ret_val = do_fips_drbg_init(msm_rng_ctx->drbg_ctx, + get_entropy_callback, + msm_rng_ctx, + RESEED_INTERVAL + ); + if (ret_val != 0) + ret_val = FIPS140_PRNG_ERR; + + return ret_val; +} + +/* Push new entropy into the CTR_DRBG instance in ctx, combining + * it with the entropy already there. On success, 0 is returned. If + * the callback returns a non-zero value, that value is returned. + * Other errors return -1. + */ +static int +fips_drbg_reseed(struct fips_drbg_ctx_s *ctx) +{ + uint8_t entropy_pool[Q_HW_DRBG_BLOCK_BYTES]; + int rv; + enum ctr_drbg_status_t init_rv; + + if (ctx == NULL) + return FIPS140_PRNG_ERR; + + if (!ctx->fips_drbg_started) { + rv = (*ctx->get_entropy_callback)(ctx->get_entropy_callback_ctx, + ctx->prev_hw_drbg_block + ); + if (rv != 0) + return FIPS140_PRNG_ERR; + ctx->fips_drbg_started = 1; + } + + rv = (*ctx->get_entropy_callback)(ctx->get_entropy_callback_ctx, + entropy_pool + ); + if (rv != 0) { + memset(entropy_pool, 0, Q_HW_DRBG_BLOCK_BYTES); + return FIPS140_PRNG_ERR; + } + + if (!memcmp(entropy_pool, + ctx->prev_hw_drbg_block, + Q_HW_DRBG_BLOCK_BYTES)) { + memset(entropy_pool, 0, Q_HW_DRBG_BLOCK_BYTES); + return FIPS140_PRNG_ERR; + } else + memcpy(ctx->prev_hw_drbg_block, + entropy_pool, + Q_HW_DRBG_BLOCK_BYTES); + + init_rv = ctr_drbg_reseed(&ctx->ctr_drbg_ctx, + entropy_pool, + 8 * MSM_ENTROPY_BUFFER_SIZE); + + /* Zeroize the buffer for security. */ + memset(entropy_pool, 0, Q_HW_DRBG_BLOCK_BYTES); + + return (init_rv == CTR_DRBG_SUCCESS ? + FIPS140_PRNG_OK : + FIPS140_PRNG_ERR); +} + +/* generate random bytes. len is in bytes On success returns 0. If + * the callback returns a non-zero value, that is returned. Other + * errors return -1. */ +int +fips_drbg_gen(struct fips_drbg_ctx_s *ctx, void *tgt, size_t len) +{ + + /* The contorted flow in this function is so that the CTR_DRBG + stuff can follow NIST SP 800-90, which has the generate function + fail and return a special code if a reseed is needed. We also work + around the CTR_DRBG limitation of the maximum request sized being + 2^19 bits. */ + + enum ctr_drbg_status_t gen_rv; + int rv; + + if (ctx == NULL || ctx->magic != MAGIC) + return FIPS140_PRNG_ERR; + if (tgt == NULL && len > 0) + return FIPS140_PRNG_ERR; + while (len > 0) { + size_t req_len; + + if (len < (CTR_DRBG_MAX_REQ_LEN_BITS / 8)) + req_len = len; + else + req_len = CTR_DRBG_MAX_REQ_LEN_BITS / 8; + + gen_rv = ctr_drbg_generate(&ctx->ctr_drbg_ctx, + tgt, + 8*req_len); + switch (gen_rv) { + case CTR_DRBG_SUCCESS: + tgt = (uint8_t *)tgt + req_len; + len -= req_len; + break; + case CTR_DRBG_NEEDS_RESEED: + rv = fips_drbg_reseed(ctx); + if (rv != 0) + return rv; + break; + default: + return FIPS140_PRNG_ERR; + } + } + + return 0; +} + +/* free resources and zeroize state */ +void +fips_drbg_final(struct fips_drbg_ctx_s *ctx) +{ + ctr_drbg_uninstantiate(&ctx->ctr_drbg_ctx); + ctx->get_entropy_callback = 0; + ctx->get_entropy_callback_ctx = 0; + ctx->fips_drbg_started = 0; + memset(ctx->prev_hw_drbg_block, 0, Q_HW_DRBG_BLOCK_BYTES); + ctx->magic = 0; +} + diff --git a/drivers/char/hw_random/fips_drbg.h b/drivers/char/hw_random/fips_drbg.h new file mode 100644 index 000000000000..06da362e7390 --- /dev/null +++ b/drivers/char/hw_random/fips_drbg.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + * + */ + +#ifndef __MSM_FIPS_DRBG_H__ +#define __MSM_FIPS_DRBG_H__ + +#include "ctr_drbg.h" +#include "msm_rng.h" + +#define FIPS140_PRNG_OK (0) +#define FIPS140_PRNG_ERR (-1) + +typedef int (*get_entropy_callback_t)(void *ctx, void *buf); + +struct fips_drbg_ctx_s { + uint32_t magic; /* for checking that ctx is likely valid */ + get_entropy_callback_t get_entropy_callback; + void *get_entropy_callback_ctx; + struct ctr_drbg_ctx_s ctr_drbg_ctx; + uint8_t fips_drbg_started; + uint8_t prev_hw_drbg_block[Q_HW_DRBG_BLOCK_BYTES]; +}; + +/* + * initialize *ctx, requesting automatic reseed after reseed_interval + * calls to qpsi_rng_gen. callback is a function to get entropy. + * callback_ctx is a pointer to any context structure that function + * may need. (Pass NULL if no context structure is needed.) callback + * must return zero or a positive number on success, and a + * negative number on an error. + */ +int fips_drbg_init(struct msm_rng_device *msm_rng_ctx); + +/* generated random data. Returns 0 on success, -1 on failures */ +int fips_drbg_gen(struct fips_drbg_ctx_s *ctx, void *tgt, size_t len); + + +/* free resources and zeroize state */ +/* Failure to call fips_drbg_final is not a security issue, since + CTR_DRBG provides backtracking resistance by updating Key and V + immediately after the data has been generated but before the + generate function returns. But it is a resource issue (except at + program termination), as it abandons a FILE structure and a file + descriptor. */ +void fips_drbg_final(struct fips_drbg_ctx_s *ctx); + +#endif /* __MSM_FIPS_DRBG_H__ */ diff --git a/drivers/char/hw_random/msm_fips_selftest.c b/drivers/char/hw_random/msm_fips_selftest.c new file mode 100644 index 000000000000..3c236058d5e3 --- /dev/null +++ b/drivers/char/hw_random/msm_fips_selftest.c @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + */ +#include +#include +#include +#include +#include "fips_drbg.h" +#include "ctr_drbg.h" +#include "msm_rng.h" +#include "msm_fips_selftest.h" + +#define CTRAES128_ENTROPY_BYTES (16) +#define CTRAES128_NONCE_BYTES (8) +#define CTRAES128_MAX_OUTPUT_BYTES (64) + +struct ctr_drbg_testcase_s { + char *name; + char *entropy_string; + char *nonce_string; + char *reseed_entropy_string; + char *expected_string; +}; + +static struct ctr_drbg_testcase_s t0 = { + .name = "use_pr_0", + .entropy_string = "\x8f\xb9\x57\x3a\x54\x62\x53\xcd" + "\xbf\x62\x15\xa1\x80\x5a\x41\x38", + .nonce_string = "\x7c\x2c\xe6\x54\x02\xbc\xa6\x83", + .reseed_entropy_string = "\xbc\x5a\xd8\x9a\xe1\x8c\x49\x1f" + "\x90\xa2\xae\x9e\x7e\x2c\xf9\x9d", + .expected_string = "\x07\x62\x82\xe8\x0e\x65\xd7\x70" + "\x1a\x35\xb3\x44\x63\x68\xb6\x16" + "\xf8\xd9\x62\x23\xb9\xb5\x11\x64" + "\x23\xa3\xa2\x32\xc7\x2c\xea\xbf" + "\x4a\xcc\xc4\x0a\xc6\x19\xd6\xaa" + "\x68\xae\xdb\x8b\x26\x70\xb8\x07" + "\xcc\xe9\x9f\xc2\x1b\x8f\xa5\x16" + "\xef\x75\xb6\x8f\xc0\x6c\x87\xc7", +}; + +static struct ctr_drbg_testcase_s t1 = { + .name = "use_pr_1", + .entropy_string = "\xa3\x56\xf3\x9a\xce\x48\x59\xb1" + "\xe1\x99\x49\x40\x22\x8e\xa4\xeb", + .nonce_string = "\xff\x33\xe9\x51\x39\xf7\x67\xf1", + .reseed_entropy_string = "\x66\x8f\x0f\xe2\xd8\xa9\xa9\x29" + "\x20\xfc\xb9\xf3\x55\xd6\xc3\x4c", + .expected_string = "\xa1\x06\x61\x65\x7b\x98\x0f\xac" + "\xce\x77\x91\xde\x7f\x6f\xe6\x1e" + "\x88\x15\xe5\xe2\x4c\xce\xb8\xa6" + "\x63\xf2\xe8\x2f\x5b\xfb\x16\x92" + "\x06\x2a\xf3\xa8\x59\x05\xe0\x5a" + "\x92\x9a\x07\x65\xc7\x41\x29\x3a" + "\x4b\x1d\x15\x3e\x02\x14\x7b\xdd" + "\x74\x5e\xbd\x70\x07\x4d\x6c\x08", +}; + +static struct ctr_drbg_testcase_s *testlist[] = { + &t0, &t1 +}; + +static int allzeroP(void *p, size_t len) +{ + size_t i; + + for (i = 0; i < len; ++i) + if (((uint8_t *)p)[i] != 0) + return 0; + + return 1; +} + +/* + * basic test. return value is error count. + */ +int fips_ctraes128_df_known_answer_test(struct ctr_debg_test_inputs_s *tcase) +{ + struct ctr_drbg_ctx_s ctx; + enum ctr_drbg_status_t rv; + + if (tcase->observed_string_len > CTRAES128_MAX_OUTPUT_BYTES) { + pr_debug("known answer test output is bigger than 64!\n"); + return 1; + } + + memset(&ctx, 0, sizeof(ctx)); + + ctx.continuous_test_started = 1; + + rv = ctr_drbg_instantiate(&ctx, + tcase->entropy_string, + 8 * CTRAES128_ENTROPY_BYTES, + tcase->nonce_string, + 8 * CTRAES128_NONCE_BYTES, + 1<<19); + if (rv != CTR_DRBG_SUCCESS) { + pr_err("test instantiate failed with code %d\n", rv); + return 1; + } + + rv = ctr_drbg_reseed(&ctx, + tcase->reseed_entropy_string, + 8 * CTRAES128_ENTROPY_BYTES); + if (rv != CTR_DRBG_SUCCESS) { + pr_err("test reseed failed with code %d\n", rv); + return 1; + } + + rv = ctr_drbg_generate(&ctx, + tcase->observed_string, + tcase->observed_string_len * 8); + if (rv != CTR_DRBG_SUCCESS) { + pr_err("test generate (2) failed with code %d\n", rv); + return 1; + } + + rv = ctr_drbg_generate(&ctx, + tcase->observed_string, + tcase->observed_string_len * 8); + if (rv != CTR_DRBG_SUCCESS) { + pr_err("test generate (2) failed with code %d\n", rv); + return 1; + } + + ctr_drbg_uninstantiate(&ctx); + + if (!allzeroP(&ctx.seed, sizeof(ctx.seed))) { + pr_err("test Final failed to zeroize the context\n"); + return 1; + } + + pr_info("\n DRBG counter test done"); + return 0; + +} + +static int fips_drbg_healthcheck_sanitytest(void) +{ + struct ctr_drbg_ctx_s *p_ctx = NULL; + enum ctr_drbg_status_t rv = CTR_DRBG_SUCCESS; + char entropy_string[MSM_ENTROPY_BUFFER_SIZE]; + char nonce[MSM_NONCE_BUFFER_SIZE]; + char buffer[32]; + + pr_info("start DRBG health check sanity test.\n"); + p_ctx = kzalloc(sizeof(struct ctr_drbg_ctx_s), GFP_KERNEL); + if (NULL == p_ctx) { + rv = CTR_DRBG_GENERAL_ERROR; + pr_err("p_ctx kzalloc fail\n"); + goto outbuf; + } + + /* + * test DRGB Instantiaion function error handling. + * Sends a NULL pointer as DTR-DRBG context. + */ + rv = ctr_drbg_instantiate(NULL, + entropy_string, + 8 * CTRAES128_ENTROPY_BYTES, + nonce, + 8 * CTRAES128_NONCE_BYTES, + 1<<19); + if (CTR_DRBG_SUCCESS == rv) { + rv = CTR_DRBG_INVALID_ARG; + pr_err("failed to handle NULL pointer of CTR context\n"); + goto outbuf; + } + + /* + * test DRGB Instantiaion function error handling. + * Sends a NULL pointer as entropy input. + */ + rv = ctr_drbg_instantiate(p_ctx, + NULL, + 8 * CTRAES128_ENTROPY_BYTES, + nonce, + 8 * CTRAES128_NONCE_BYTES, + 1<<19); + if (CTR_DRBG_SUCCESS == rv) { + rv = CTR_DRBG_INVALID_ARG; + pr_err("failed to handle NULL pointer of entropy string\n"); + goto outbuf; + } + + rv = ctr_drbg_instantiate(p_ctx, + entropy_string, + 8 * CTRAES128_ENTROPY_BYTES, + NULL, + 8 * CTRAES128_NONCE_BYTES, + 1<<19); + if (CTR_DRBG_SUCCESS == rv) { + rv = CTR_DRBG_INVALID_ARG; + pr_err("failed to handle NULL pointer of nonce string\n"); + goto outbuf; + } + + /* + * test DRGB Instantiaion function error handling. + * Sends very long seed length. + */ + rv = ctr_drbg_instantiate(p_ctx, + entropy_string, + 8 * CTRAES128_ENTROPY_BYTES, + nonce, + 32 * CTRAES128_NONCE_BYTES, + 1<<19); + if (CTR_DRBG_SUCCESS == rv) { + rv = CTR_DRBG_INVALID_ARG; + pr_err("failed to handle incorrect seed size\n"); + goto outbuf; + } + + + rv = ctr_drbg_instantiate(p_ctx, + entropy_string, + 8 * CTRAES128_ENTROPY_BYTES, + nonce, + 8 * CTRAES128_NONCE_BYTES, + 1<<19); + if (CTR_DRBG_SUCCESS != rv) { + pr_err("Instantiation failed to handle CTR-DRBG instance\n"); + goto outbuf; + } + + /* + * test DRGB generator function error handling. + * set output string as NULL. + */ + rv = ctr_drbg_generate(p_ctx, NULL, 256); + if (CTR_DRBG_SUCCESS == rv) { + pr_err("failed to handle incorrect buffer pointer\n"); + rv = CTR_DRBG_INVALID_ARG; + goto outdrbg; + } + + rv = ctr_drbg_generate(p_ctx, &buffer, 1 << 20); + if (CTR_DRBG_SUCCESS == rv) { + pr_err("failed to handle too long output length\n"); + rv = CTR_DRBG_INVALID_ARG; + goto outdrbg; + } + + rv = ctr_drbg_generate(p_ctx, &buffer, 177); + if (CTR_DRBG_SUCCESS == rv) { + pr_err("failed to handle incorrect output length\n"); + rv = CTR_DRBG_INVALID_ARG; + goto outdrbg; + } + + pr_info("DRBG health check sanity test passed.\n"); + rv = CTR_DRBG_SUCCESS; + +outdrbg: + ctr_drbg_uninstantiate(p_ctx); + +outbuf: + if (p_ctx) + kzfree(p_ctx); + p_ctx = NULL; + + memset(buffer, 0, 32); + memset(nonce, 0, MSM_NONCE_BUFFER_SIZE); + memset(entropy_string, 0, MSM_ENTROPY_BUFFER_SIZE); + + return rv; +} + +int fips_self_test(void) +{ + struct ctr_debg_test_inputs_s cavs_input; + uint8_t entropy[CTRAES128_ENTROPY_BYTES]; + uint8_t nonce[CTRAES128_NONCE_BYTES]; + uint8_t reseed_entropy[CTRAES128_ENTROPY_BYTES]; + uint8_t expected[CTRAES128_MAX_OUTPUT_BYTES]; + uint8_t observed[CTRAES128_MAX_OUTPUT_BYTES]; + unsigned int i; + int errors = 0; + int ret; + + cavs_input.entropy_string = entropy; + cavs_input.nonce_string = nonce; + cavs_input.reseed_entropy_string = reseed_entropy; + cavs_input.observed_string = observed; + cavs_input.observed_string_len = CTRAES128_MAX_OUTPUT_BYTES; + + + ret = fips_drbg_healthcheck_sanitytest(); + if (CTR_DRBG_SUCCESS != ret) { + pr_err("DRBG health check fail\n"); + errors++; + return errors; + } + + for (i = 0; + i < sizeof(testlist)/sizeof(struct ctr_drbg_testcase_s *); + ++i) { + memcpy(entropy, + testlist[i]->entropy_string, + CTRAES128_ENTROPY_BYTES); + memcpy(nonce, + testlist[i]->nonce_string, + CTRAES128_NONCE_BYTES); + memcpy(reseed_entropy, + testlist[i]->reseed_entropy_string, + CTRAES128_ENTROPY_BYTES); + memcpy(expected, + testlist[i]->expected_string, + CTRAES128_MAX_OUTPUT_BYTES); + + pr_debug("starting test %s\n", testlist[i]->name); + ret = fips_ctraes128_df_known_answer_test(&cavs_input); + pr_debug("completed test %s\n\n", testlist[i]->name); + if (0 != ret) { + pr_debug("got error from drbg known answer test!\n"); + return 1; + } + + if (memcmp(expected, + cavs_input.observed_string, + CTRAES128_MAX_OUTPUT_BYTES) != 0) { + errors++; + pr_info("%s: generate failed\n", testlist[i]->name); + return 1; + } else + pr_info("%s: generate PASSED!\n", testlist[i]->name); + } + + if (errors == 0) + pr_debug("All tests passed\n"); + else + pr_debug("%d tests failed\n", errors); + + return errors; + +} + diff --git a/drivers/char/hw_random/msm_fips_selftest.h b/drivers/char/hw_random/msm_fips_selftest.h new file mode 100644 index 000000000000..090ae01100e2 --- /dev/null +++ b/drivers/char/hw_random/msm_fips_selftest.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + * + */ + +#ifndef __MSM_FIPS_SELFTEST_H__ +#define __MSM_FIPS_SELFTEST_H__ + +struct ctr_debg_test_inputs_s { + char *entropy_string; /* must by 16 bytes */ + char *nonce_string; /* must be 8 bytes */ + char *reseed_entropy_string; /* must be 16 bytes */ + char *observed_string; /* lenth is defined + in observed_string_len */ + int observed_string_len; +}; + +int fips_ctraes128_df_known_answer_test(struct ctr_debg_test_inputs_s *tcase); + +int fips_self_test(void); + +#endif /* __MSM_FIPS_SELFTEST_H__ */ diff --git a/drivers/char/hw_random/msm_rng.c b/drivers/char/hw_random/msm_rng.c index dd89e9b388e7..a3bde2d7ed69 100644 --- a/drivers/char/hw_random/msm_rng.c +++ b/drivers/char/hw_random/msm_rng.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, The Linux Foundation. All rights reserved. + * Copyright (c) 2011-2014, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -24,6 +24,17 @@ #include #include #include +#include +#include +#include +#include + +#include + +#include "msm_rng.h" +#include "ctr_drbg.h" +#include "fips_drbg.h" +#include "msm_fips_selftest.h" #define DRIVER_NAME "msm_rng" @@ -42,42 +53,79 @@ #define MAX_HW_FIFO_DEPTH 16 /* FIFO is 16 words deep */ #define MAX_HW_FIFO_SIZE (MAX_HW_FIFO_DEPTH * 4) /* FIFO is 32 bits wide */ +/* Global FIPS status */ +#ifdef CONFIG_FIPS_ENABLE +enum fips_status g_fips140_status = FIPS140_STATUS_FAIL; +EXPORT_SYMBOL(g_fips140_status); -struct msm_rng_device { - struct platform_device *pdev; - void __iomem *base; - struct clk *prng_clk; +#else +enum fips_status g_fips140_status = FIPS140_STATUS_NA; +EXPORT_SYMBOL(g_fips140_status); + +#endif + +/*FIPS140-2 call back for DRBG self test */ +void *drbg_call_back; +EXPORT_SYMBOL(drbg_call_back); + + + +enum { + FIPS_NOT_STARTED = 0, + DRBG_FIPS_STARTED }; -static int msm_rng_read(struct hwrng *rng, void *data, size_t max, bool wait) +struct msm_rng_device msm_rng_device_info; + +#ifdef CONFIG_FIPS_ENABLE +static int fips_mode_enabled = FIPS_NOT_STARTED; +#endif + +static long msm_rng_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + long ret = 0; + + pr_debug("ioctl: cmd = %d\n", cmd); + switch (cmd) { + case QRNG_IOCTL_RESET_BUS_BANDWIDTH: + pr_info("calling msm_rng_bus_scale(LOW)\n"); + ret = msm_bus_scale_client_update_request( + msm_rng_device_info.qrng_perf_client, 0); + if (ret) + pr_err("failed qrng_reset_bus_bw, ret = %ld\n", ret); + break; + default: + pr_err("Unsupported IOCTL call"); + break; + } + return ret; +} + +/* + * + * This function calls hardware random bit generator directory and retuns it + * back to caller + * + */ +int msm_rng_direct_read(struct msm_rng_device *msm_rng_dev, void *data) { - struct msm_rng_device *msm_rng_dev; struct platform_device *pdev; void __iomem *base; - size_t maxsize; size_t currsize = 0; unsigned long val; unsigned long *retdata = data; int ret; - msm_rng_dev = (struct msm_rng_device *)rng->priv; pdev = msm_rng_dev->pdev; base = msm_rng_dev->base; - /* calculate max size bytes to transfer back to caller */ - maxsize = min_t(size_t, MAX_HW_FIFO_SIZE, max); - - /* no room for word data */ - if (maxsize < 4) - return 0; - /* enable PRNG clock */ ret = clk_prepare_enable(msm_rng_dev->prng_clk); if (ret) { dev_err(&pdev->dev, "failed to enable clock in callback\n"); return 0; } - /* read random data from h/w */ do { /* check status bit if data is available */ @@ -93,17 +141,187 @@ static int msm_rng_read(struct hwrng *rng, void *data, size_t max, bool wait) *(retdata++) = val; currsize += 4; - /* make sure we stay on 32bit boundary */ - if ((maxsize - currsize) < 4) - break; - } while (currsize < maxsize); + } while (currsize < Q_HW_DRBG_BLOCK_BYTES); /* vote to turn off clock */ clk_disable_unprepare(msm_rng_dev->prng_clk); + val = 0L; + return currsize; + +} + +static int msm_rng_drbg_read(struct hwrng *rng, + void *data, size_t max, bool wait) +{ + struct msm_rng_device *msm_rng_dev; + struct platform_device *pdev; + void __iomem *base; + size_t maxsize; + size_t currsize = 0; + unsigned long val; + unsigned long *retdata = data; + int ret, ret1; + + msm_rng_dev = (struct msm_rng_device *)rng->priv; + pdev = msm_rng_dev->pdev; + base = msm_rng_dev->base; + + + down(&msm_rng_dev->drbg_sem); + + /* calculate max size bytes to transfer back to caller */ + maxsize = min_t(size_t, MAX_HW_FIFO_SIZE, max); + + /* no room for word data */ + if (maxsize < 4) + return 0; + + /* read random data from CTR-AES based DRBG */ + if (FIPS140_DRBG_ENABLED == msm_rng_dev->fips140_drbg_enabled) { + ret1 = fips_drbg_gen(msm_rng_dev->drbg_ctx, data, maxsize); + if (FIPS140_PRNG_ERR == ret1) + panic("random number generator generator error.\n"); + } else + ret1 = 1; + + /* read random data from h/w */ + /* enable PRNG clock */ + ret = clk_prepare_enable(msm_rng_dev->prng_clk); + if (ret) { + dev_err(&pdev->dev, "failed to enable clock in callback\n"); + up(&msm_rng_dev->drbg_sem); + return 0; + } + /* read random data from h/w */ + do { + /* check status bit if data is available */ + if (!(readl_relaxed(base + PRNG_STATUS_OFFSET) & 0x00000001)) + break; /* no data to read so just bail */ + + /* read FIFO */ + val = readl_relaxed(base + PRNG_DATA_OUT_OFFSET); + if (!val) + break; /* no data to read so just bail */ + + /* write data back to callers pointer */ + if (0 != ret1) + *(retdata++) = val; + currsize += 4; + + /* make sure we stay on 32bit boundary */ + if ((maxsize - currsize) < 4) + break; + } while (currsize < maxsize); + /* vote to turn off clock */ + clk_disable_unprepare(msm_rng_dev->prng_clk); + + up(&msm_rng_dev->drbg_sem); + return currsize; } +#ifdef CONFIG_FIPS_ENABLE +static void _fips_drbg_init_error(struct msm_rng_device *msm_rng_dev) +{ + unregister_chrdev(QRNG_IOC_MAGIC, DRIVER_NAME); + clk_put(msm_rng_dev->prng_clk); + iounmap(msm_rng_dev->base); + kzfree(msm_rng_dev->drbg_ctx); + kzfree(msm_rng_dev); + panic("software random number generator initialization error.\n"); +} +#else +static inline void _fips_drbg_init_error(struct msm_rng_device *msm_rng_dev) +{ + return; +} + +#endif + +#ifdef CONFIG_FIPS_ENABLE +int _do_msm_fips_drbg_init(void *rng_dev) +{ + struct msm_rng_device *msm_rng_dev = (struct msm_rng_device *) rng_dev; + + int ret; + + if (NULL == msm_rng_dev) + return 1; + + ret = fips_drbg_init(msm_rng_dev); + if (0 == ret) { + pr_debug("start fips self test\n"); + ret = fips_self_test(); + if (ret) { + msm_rng_dev->fips140_drbg_enabled = + FIPS140_DRBG_DISABLED; + _fips_drbg_init_error(msm_rng_dev); + } else { + msm_rng_dev->fips140_drbg_enabled = + FIPS140_DRBG_ENABLED; + } + } else { + msm_rng_dev->fips140_drbg_enabled = FIPS140_DRBG_DISABLED; + _fips_drbg_init_error(msm_rng_dev); + } + + return ret; +} +#else +int _do_msm_fips_drbg_init(void *rng_dev) +{ + return 0; +} +#endif + +#ifdef CONFIG_FIPS_ENABLE +static int msm_rng_read(struct hwrng *rng, void *data, size_t max, bool wait) +{ + struct msm_rng_device *msm_rng_dev = (struct msm_rng_device *)rng->priv; + unsigned char a[Q_HW_DRBG_BLOCK_BYTES]; + int read_size; + unsigned char *p = data; + + switch (fips_mode_enabled) { + case DRBG_FIPS_STARTED: + return msm_rng_drbg_read(rng, data, max, wait); + break; + case FIPS_NOT_STARTED: + if (g_fips140_status != FIPS140_STATUS_PASS) { + do { + read_size = msm_rng_direct_read(msm_rng_dev, a); + if (read_size <= 0) + break; + if ((max - read_size > 0)) { + memcpy(p, a, read_size); + p += read_size; + max -= read_size; + } else { + memcpy(p, a, max); + break; + } + } while (1); + return p - (unsigned char *)data; + } else { + fips_mode_enabled = DRBG_FIPS_STARTED; + return msm_rng_drbg_read(rng, data, max, wait); + } + break; + default: + return 0; + break; + } + + return 0; +} +#else +static int msm_rng_read(struct hwrng *rng, void *data, size_t max, bool wait) +{ + return msm_rng_drbg_read(rng, data, max, wait); +} +#endif + static struct hwrng msm_rng = { .name = DRIVER_NAME, .read = msm_rng_read, @@ -115,6 +333,12 @@ static int __devinit msm_rng_enable_hw(struct msm_rng_device *msm_rng_dev) unsigned long reg_val = 0; int ret = 0; + if (msm_rng_dev->qrng_perf_client) { + ret = msm_bus_scale_client_update_request( + msm_rng_dev->qrng_perf_client, 1); + if (ret) + pr_err("bus_scale_client_update_req failed!\n"); + } /* Enable the PRNG CLK */ ret = clk_prepare_enable(msm_rng_dev->prng_clk); if (ret) { @@ -145,18 +369,40 @@ static int __devinit msm_rng_enable_hw(struct msm_rng_device *msm_rng_dev) */ mb(); } - clk_disable_unprepare(msm_rng_dev->prng_clk); - return 0; } +static const struct file_operations msm_rng_fops = { + .unlocked_ioctl = msm_rng_ioctl, +}; +static struct class *msm_rng_class; +static struct cdev msm_rng_cdev; + +#ifdef CONFIG_FIPS_ENABLE + +static void _first_msm_drbg_init(struct msm_rng_device *msm_rng_dev) +{ + fips_reg_drbg_callback((void *)msm_rng_dev); + return; +} +#else +static void _first_msm_drbg_init(struct msm_rng_device *msm_rng_dev) +{ + _do_msm_fips_drbg_init(msm_rng_dev); +} +#endif + static int __devinit msm_rng_probe(struct platform_device *pdev) { struct resource *res; struct msm_rng_device *msm_rng_dev = NULL; void __iomem *base = NULL; int error = 0; + int ret = 0; + struct device *dev; + + struct msm_bus_scale_pdata *qrng_platform_support = NULL; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (res == NULL) { @@ -165,7 +411,7 @@ static int __devinit msm_rng_probe(struct platform_device *pdev) goto err_exit; } - msm_rng_dev = kzalloc(sizeof(msm_rng_dev), GFP_KERNEL); + msm_rng_dev = kzalloc(sizeof(struct msm_rng_device), GFP_KERNEL); if (!msm_rng_dev) { dev_err(&pdev->dev, "cannot allocate memory\n"); error = -ENOMEM; @@ -180,8 +426,21 @@ static int __devinit msm_rng_probe(struct platform_device *pdev) } msm_rng_dev->base = base; + msm_rng_dev->drbg_ctx = kzalloc(sizeof(struct fips_drbg_ctx_s), + GFP_KERNEL); + if (!msm_rng_dev->drbg_ctx) { + dev_err(&pdev->dev, "cannot allocate memory\n"); + error = -ENOMEM; + goto err_clk_get; + } + /* create a handle for clock control */ - msm_rng_dev->prng_clk = clk_get(&pdev->dev, "core_clk"); + if ((pdev->dev.of_node) && (of_property_read_bool(pdev->dev.of_node, + "qcom,msm-rng-iface-clk"))) + msm_rng_dev->prng_clk = clk_get(&pdev->dev, + "iface_clk"); + else + msm_rng_dev->prng_clk = clk_get(&pdev->dev, "core_clk"); if (IS_ERR(msm_rng_dev->prng_clk)) { dev_err(&pdev->dev, "failed to register clock source\n"); error = -EPERM; @@ -192,6 +451,17 @@ static int __devinit msm_rng_probe(struct platform_device *pdev) msm_rng_dev->pdev = pdev; platform_set_drvdata(pdev, msm_rng_dev); + if (pdev->dev.of_node) { + /* Register bus client */ + qrng_platform_support = msm_bus_cl_get_pdata(pdev); + msm_rng_dev->qrng_perf_client = msm_bus_scale_register_client( + qrng_platform_support); + msm_rng_device_info.qrng_perf_client = + msm_rng_dev->qrng_perf_client; + if (!msm_rng_dev->qrng_perf_client) + pr_err("Unable to register bus client\n"); + } + /* Enable rng h/w */ error = msm_rng_enable_hw(msm_rng_dev); @@ -206,15 +476,39 @@ static int __devinit msm_rng_probe(struct platform_device *pdev) error = -EPERM; goto rollback_clk; } + ret = register_chrdev(QRNG_IOC_MAGIC, DRIVER_NAME, &msm_rng_fops); - return 0; + msm_rng_class = class_create(THIS_MODULE, "msm-rng"); + if (IS_ERR(msm_rng_class)) { + pr_err("class_create failed\n"); + return PTR_ERR(msm_rng_class); + } + dev = device_create(msm_rng_class, NULL, MKDEV(QRNG_IOC_MAGIC, 0), + NULL, "msm-rng"); + if (IS_ERR(dev)) { + pr_err("Device create failed\n"); + error = PTR_ERR(dev); + goto unregister_chrdev; + } + cdev_init(&msm_rng_cdev, &msm_rng_fops); + + sema_init(&msm_rng_dev->drbg_sem, 1); + + _first_msm_drbg_init(msm_rng_dev); + + return error; + +unregister_chrdev: + unregister_chrdev(QRNG_IOC_MAGIC, DRIVER_NAME); + class_destroy(msm_rng_class); rollback_clk: clk_put(msm_rng_dev->prng_clk); err_clk_get: iounmap(msm_rng_dev->base); err_iomap: - kfree(msm_rng_dev); + kzfree(msm_rng_dev->drbg_ctx); + kzfree(msm_rng_dev); err_exit: return error; } @@ -223,11 +517,20 @@ static int __devexit msm_rng_remove(struct platform_device *pdev) { struct msm_rng_device *msm_rng_dev = platform_get_drvdata(pdev); + unregister_chrdev(QRNG_IOC_MAGIC, DRIVER_NAME); hwrng_unregister(&msm_rng); clk_put(msm_rng_dev->prng_clk); iounmap(msm_rng_dev->base); platform_set_drvdata(pdev, NULL); - kfree(msm_rng_dev); + if (msm_rng_dev->qrng_perf_client) + msm_bus_scale_unregister_client(msm_rng_dev->qrng_perf_client); + + if (msm_rng_dev->drbg_ctx) { + fips_drbg_final(msm_rng_dev->drbg_ctx); + kzfree(msm_rng_dev->drbg_ctx); + msm_rng_dev->drbg_ctx = NULL; + } + kzfree(msm_rng_dev); return 0; } @@ -260,6 +563,10 @@ static void __exit msm_rng_exit(void) } module_exit(msm_rng_exit); +#ifdef CONFIG_FIPS_ENABLE +EXPORT_SYMBOL(fips_ctraes128_df_known_answer_test); +#endif +EXPORT_SYMBOL(_do_msm_fips_drbg_init); MODULE_AUTHOR("The Linux Foundation"); MODULE_DESCRIPTION("Qualcomm MSM Random Number Driver"); diff --git a/drivers/char/hw_random/msm_rng.h b/drivers/char/hw_random/msm_rng.h new file mode 100644 index 000000000000..b79ba46e77df --- /dev/null +++ b/drivers/char/hw_random/msm_rng.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + * + */ +#ifndef __MSM_RNG_HEADER__ +#define __MSM_RNG_HEADER__ + +#include +#include + +struct _fips_drbg_ctx; + +#define FIPS140_DRBG_ENABLED (1) +#define FIPS140_DRBG_DISABLED (0) + +#define Q_HW_DRBG_BLOCK_BYTES (32) + +extern void fips_reg_drbg_callback(void *src); + +struct msm_rng_device { + struct platform_device *pdev; + void __iomem *base; + struct clk *prng_clk; + uint32_t qrng_perf_client; + struct semaphore drbg_sem; + struct fips_drbg_ctx_s *drbg_ctx; + int fips140_drbg_enabled; +}; + +/* + * + * This function calls hardware random bit generator + * directory and retuns it back to caller. + * + */ +int msm_rng_direct_read(struct msm_rng_device *msm_rng_dev, void *data); + +#endif