| /* |
| * Copyright (c) 2015 Samsung Electronics Co., Ltd. |
| * |
| * Sensitive Data Protection |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License 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. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with this program; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| */ |
| |
| #include <crypto/internal/hash.h> |
| #include <crypto/scatterwalk.h> |
| #include <linux/init.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/string.h> |
| #include <linux/interrupt.h> |
| #include <linux/wakelock.h> |
| #include <linux/sched.h> |
| #include <linux/netlink.h> |
| #include <linux/net.h> |
| #include <net/netlink.h> |
| #include <net/sock.h> |
| #include <net/net_namespace.h> |
| #include <linux/types.h> |
| #include <linux/wait.h> |
| #include <linux/slab.h> |
| #include <linux/spinlock.h> |
| #include <linux/audit.h> |
| #include <linux/jiffies.h> |
| |
| #include <linux/version.h> |
| #include <sdp/pub_crypto_emul.h> |
| |
| #define NETLINK_FIPS_CRYPTO 29 |
| #define PUB_CRYPTO_PID_SET 3001 |
| #define PUB_CRYPTO_RESULT 3002 |
| |
| #define RESULT_ARRAY_MAX_LEN 100 |
| |
| #define CRYPTO_MAX_TIMEOUT HZ/5 |
| |
| #define PUB_CRYPTO_REQ_TIMEOUT 3000 |
| |
| pub_crypto_control_t g_pub_crypto_control; |
| |
| DEFINE_MUTEX(crypto_send_mutex); |
| static int user_fipscryptod_pid = 0; |
| static struct sock* crypto_sock = NULL; |
| |
| static int pub_crypto_request_get_msg(pub_crypto_request_t *req, char **msg); |
| static void request_send(pub_crypto_control_t *con, |
| pub_crypto_request_t *req); |
| static void request_wait_answer(pub_crypto_control_t *con, |
| pub_crypto_request_t *req); |
| static pub_crypto_request_t *request_find(pub_crypto_control_t *con, |
| u32 request_id); |
| static pub_crypto_request_t *request_alloc(u32 opcode); |
| static void request_free(pub_crypto_control_t *con, pub_crypto_request_t *req); |
| static void req_dump(pub_crypto_request_t *req, const char *msg); |
| static void dump(unsigned char *buf, int len, const char *msg); |
| |
| /* Debug */ |
| #define PUB_CRYPTO_DEBUG 0 |
| |
| #if PUB_CRYPTO_DEBUG |
| #define PUB_CRYPTO_LOGD(FMT, ...) printk("SDP_PUB_CRYPTO[%d] : %s " FMT , current->pid, __func__, ##__VA_ARGS__) |
| #else |
| #define PUB_CRYPTO_LOGD(FMT, ...) |
| #endif /* PUB_CRYPTO_DEBUG */ |
| #define PUB_CRYPTO_LOGE(FMT, ...) printk("SDP_PUB_CRYPTO[%d] : %s " FMT , current->pid, __func__, ##__VA_ARGS__) |
| #define PUB_CRYPTO_LOGI(FMT, ...) printk("SDP_PUB_CRYPTO[%d] : %s " FMT , current->pid, __func__, ##__VA_ARGS__) |
| |
| //static char* process_crypto_request(u8 opcode, char* send_msg, |
| // int send_msg_size, int* result_len, int* ret) { |
| static int __do_dek_crypt(pub_crypto_request_t *req, char *ret) { |
| int rc = 0; |
| |
| struct sk_buff *skb_in = NULL; |
| struct sk_buff *skb_out = NULL; |
| struct nlmsghdr *nlh = NULL; |
| |
| char *nl_msg = NULL; |
| int nl_msg_size = 0; |
| |
| PUB_CRYPTO_LOGD("====================== \t entred\n"); |
| |
| if(req == NULL) { |
| PUB_CRYPTO_LOGE("invalid request\n"); |
| return -1; |
| } |
| |
| request_send(&g_pub_crypto_control, req); |
| |
| nl_msg_size = pub_crypto_request_get_msg(req, &nl_msg); |
| if(nl_msg_size <= 0) { |
| PUB_CRYPTO_LOGE("invalid opcode %d\n", req->opcode); |
| return -1; |
| } |
| |
| // sending netlink message |
| skb_in = nlmsg_new(nl_msg_size, 0); |
| if (!skb_in) { |
| PUB_CRYPTO_LOGE("Failed to allocate new skb: \n"); |
| return -1; |
| } |
| |
| nlh = nlmsg_put(skb_in, 0, 0, NLMSG_DONE, nl_msg_size, 0); |
| NETLINK_CB(skb_in).dst_group = 0; |
| memcpy(nlmsg_data(nlh), nl_msg, nl_msg_size); |
| |
| mutex_lock(&crypto_send_mutex); |
| rc = nlmsg_unicast(crypto_sock, skb_in, user_fipscryptod_pid); |
| mutex_unlock(&crypto_send_mutex); |
| |
| if (rc < 0) { |
| PUB_CRYPTO_LOGE("Error while sending bak to user, err id: %d\n", rc); |
| return -1; |
| } |
| |
| /* |
| * In a very rare case, response comes before request gets into pending list. |
| */ |
| if(req->state != PUB_CRYPTO_REQ_FINISHED) |
| request_wait_answer(&g_pub_crypto_control, req); |
| else |
| PUB_CRYPTO_LOGE("request already finished, skip waiting\n"); |
| |
| skb_out = skb_dequeue(&crypto_sock->sk_receive_queue); |
| |
| if(req->state != PUB_CRYPTO_REQ_FINISHED) { |
| PUB_CRYPTO_LOGE("FIPS_CRYPTO_ERROR!!!\n"); |
| /* |
| * TODO : |
| * Request not finished by an interrupt or abort. |
| */ |
| rc = -EINTR; |
| goto out; |
| } |
| |
| if(req->aborted) { |
| PUB_CRYPTO_LOGE("Request aborted!!!\n"); |
| rc = -ETIMEDOUT; |
| goto out; |
| } |
| |
| if(req->result.ret < 0) { |
| PUB_CRYPTO_LOGE("failed to opcode(%d)!!!\n", req->opcode); |
| rc = req->result.ret; |
| goto out; |
| } |
| |
| switch(req->opcode) { |
| case OP_DH_ENC: |
| case OP_DH_DEC: |
| case OP_ECDH_ENC: |
| case OP_ECDH_DEC: |
| #ifdef CONFIG_SDP_ENHANCED |
| case OP_ECDH_REQ_SS: |
| #endif |
| dump(req->result.dek.buf, req->result.dek.len, "req->result.dek"); |
| memcpy(ret, &(req->result.dek), sizeof(dek_t)); |
| //dump(req->result.dek.buf, req->result.dek.len, "req->result.dek"); |
| rc = 0; |
| break; |
| case OP_RSA_ENC: |
| case OP_RSA_DEC: |
| memcpy(ret, &(req->result.dek), sizeof(dek_t)); |
| rc = 0; |
| break; |
| default: |
| PUB_CRYPTO_LOGE("Not supported opcode(%d)!!!\n", req->opcode); |
| rc = -EOPNOTSUPP; |
| break; |
| } |
| |
| out: |
| if(skb_out) { |
| kfree_skb(skb_out); |
| } |
| if(rc != 0) |
| req_dump(req, "failed"); |
| |
| return rc; |
| } |
| |
| |
| static int pub_crypto_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) |
| { |
| void *data; |
| u16 msg_type = nlh->nlmsg_type; |
| u32 err = 0; |
| struct audit_status *status_get = NULL; |
| u16 len = 0; |
| |
| data = NLMSG_DATA(nlh); |
| len = ntohs(*(uint16_t*) (data+1)); |
| switch (msg_type) { |
| case PUB_CRYPTO_PID_SET: |
| status_get = (struct audit_status *)data; |
| user_fipscryptod_pid = status_get->pid; |
| PUB_CRYPTO_LOGE("crypto_receive_msg: pid = %d\n", user_fipscryptod_pid); |
| break; |
| case PUB_CRYPTO_RESULT: |
| { |
| result_t *result = (result_t *)data; |
| pub_crypto_request_t *req = NULL; |
| |
| req = request_find(&g_pub_crypto_control, result->request_id); |
| |
| if(req) { |
| memcpy(&req->result, result, sizeof(result_t)); |
| req->state = PUB_CRYPTO_REQ_FINISHED; |
| wake_up(&req->waitq); |
| |
| memset(result, 0, sizeof(result_t)); |
| } |
| break; |
| } |
| default: |
| PUB_CRYPTO_LOGE("unknown message type : %d\n", msg_type); |
| break; |
| } |
| |
| return err; |
| } |
| |
| /* Receive messages from netlink socket. */ |
| static void crypto_recver(struct sk_buff *skb) |
| { |
| struct nlmsghdr *nlh; |
| int len; |
| int err; |
| |
| nlh = nlmsg_hdr(skb); |
| len = skb->len; |
| |
| err = pub_crypto_recv_msg(skb, nlh); |
| } |
| |
| static void dump(unsigned char *buf, int len, const char *msg) { |
| #if PUB_CRYPTO_DEBUG |
| int i; |
| |
| printk("%s len=%d: ", msg, len); |
| for(i=0;i<len;++i) { |
| printk("%02x ", (unsigned char)buf[i]); |
| } |
| printk("\n"); |
| #else |
| printk("%s len=%d: ", msg, len); |
| printk("\n"); |
| #endif |
| } |
| |
| int do_dek_crypt(int opcode, dek_t *in, dek_t *out, kek_t *key){ |
| pub_crypto_request_t *req = request_alloc(opcode); |
| int ret = -1; |
| |
| if(req) { |
| switch(req->opcode) { |
| case OP_RSA_ENC: |
| case OP_RSA_DEC: |
| case OP_DH_ENC: |
| case OP_DH_DEC: |
| case OP_ECDH_ENC: |
| case OP_ECDH_DEC: |
| #ifdef CONFIG_SDP_ENHANCED |
| case OP_ECDH_REQ_SS: |
| #endif |
| req->cipher_param.request_id = req->id; |
| req->cipher_param.opcode = req->opcode; |
| memcpy(&req->cipher_param.in, (void *) in, sizeof(dek_t)); |
| memcpy(&req->cipher_param.key, (void *) key, sizeof(kek_t)); |
| break; |
| default: |
| PUB_CRYPTO_LOGE("opcode[%d] failed, not supported\n", opcode); |
| goto error; |
| break; |
| } |
| |
| ret = __do_dek_crypt(req, (char *)out); |
| |
| if(ret != 0) { |
| PUB_CRYPTO_LOGE("opcode[%d] failed\n", opcode); |
| goto error; |
| } |
| } else { |
| PUB_CRYPTO_LOGE("request allocation failed\n"); |
| return -ENOMEM; |
| } |
| |
| request_free(&g_pub_crypto_control, req); |
| return 0; |
| error: |
| request_free(&g_pub_crypto_control, req); |
| return -1; |
| } |
| |
| int rsa_encryptByPub(dek_t *dek, dek_t *edek, kek_t *key){ |
| return do_dek_crypt(OP_RSA_ENC, dek, edek, key); |
| } |
| |
| int rsa_decryptByPair(dek_t *edek, dek_t *dek, kek_t *key){ |
| return do_dek_crypt(OP_RSA_DEC, edek, dek, key); |
| } |
| |
| int dh_encryptDEK(dek_t *dek, dek_t *edek, kek_t *key){ |
| return do_dek_crypt(OP_DH_ENC, dek, edek, key); |
| } |
| |
| int dh_decryptEDEK(dek_t *edek, dek_t *dek, kek_t *key){ |
| return do_dek_crypt(OP_DH_DEC, edek, dek, key); |
| } |
| |
| int ecdh_encryptDEK(dek_t *dek, dek_t *edek, kek_t *key){ |
| return do_dek_crypt(OP_ECDH_ENC, dek, edek, key); |
| } |
| |
| int ecdh_decryptEDEK(dek_t *edek, dek_t *dek, kek_t *key){ |
| return do_dek_crypt(OP_ECDH_DEC, edek, dek, key); |
| } |
| |
| #ifdef CONFIG_SDP_ENHANCED |
| int ecdh_deriveSS(dek_t *in, dek_t *out, kek_t *drv_key){ |
| return do_dek_crypt(OP_ECDH_REQ_SS, in, out, drv_key); |
| } |
| #endif |
| |
| static int pub_crypto_request_get_msg(pub_crypto_request_t *req, char **msg) |
| { |
| int msg_len = -1; |
| |
| switch(req->opcode) { |
| case OP_RSA_ENC: |
| case OP_RSA_DEC: |
| case OP_DH_DEC: |
| case OP_DH_ENC: |
| case OP_ECDH_DEC: |
| case OP_ECDH_ENC: |
| #ifdef CONFIG_SDP_ENHANCED |
| case OP_ECDH_REQ_SS: |
| #endif |
| *msg = (char *)&req->cipher_param; |
| msg_len = (int) sizeof(cipher_param_t); |
| break; |
| default: |
| *msg = NULL; |
| msg_len = -1; |
| break; |
| } |
| return msg_len; |
| } |
| |
| static u32 pub_crypto_get_unique_id(pub_crypto_control_t *control) |
| { |
| u32 local_reqctr; |
| |
| spin_lock(&control->lock); |
| |
| ++(control->reqctr); |
| /* zero is special */ |
| if (control->reqctr == 0) |
| control->reqctr = 1; |
| local_reqctr = control->reqctr; |
| |
| spin_unlock(&control->lock); |
| |
| return local_reqctr; |
| } |
| static void req_dump(pub_crypto_request_t *req, const char *msg) { |
| PUB_CRYPTO_LOGI("req %s {id:%d op:%d state:%d}\n", msg, req->id, req->opcode, req->state); |
| } |
| |
| static void request_send(pub_crypto_control_t *con, |
| pub_crypto_request_t *req) { |
| spin_lock(&con->lock); |
| |
| list_add_tail(&req->list, &con->pending_list); |
| req->state = PUB_CRYPTO_REQ_PENDING; |
| req_dump(req, "added"); |
| |
| spin_unlock(&con->lock); |
| } |
| |
| static void request_wait_answer(pub_crypto_control_t *con, |
| pub_crypto_request_t *req) { |
| int intr; |
| |
| while (req->state != PUB_CRYPTO_REQ_FINISHED) { |
| /* |
| * TODO : can anyone answer what happens when current process gets killed here? |
| */ |
| intr = wait_event_interruptible_timeout(req->waitq, |
| req->state == PUB_CRYPTO_REQ_FINISHED, |
| msecs_to_jiffies(PUB_CRYPTO_REQ_TIMEOUT)); |
| if(req->state == PUB_CRYPTO_REQ_FINISHED) |
| break; |
| |
| if(intr == 0) { |
| PUB_CRYPTO_LOGE("timeout! %d [ID:%d] \n", intr, req->id); |
| req->state = PUB_CRYPTO_REQ_FINISHED; |
| req->aborted = 1; |
| break; |
| } |
| |
| if(intr == -ERESTARTSYS) { |
| PUB_CRYPTO_LOGE("wait interrupted : intr %d(-ERESTARTSYS) \n", intr); |
| break; |
| } |
| } |
| } |
| |
| static pub_crypto_request_t *request_find(pub_crypto_control_t *con, |
| u32 request_id) { |
| struct list_head *entry; |
| |
| spin_lock(&con->lock); |
| |
| list_for_each(entry, &con->pending_list) { |
| pub_crypto_request_t *req; |
| req = list_entry(entry, pub_crypto_request_t, list); |
| if (req->id == request_id) { |
| req_dump(req, "found"); |
| |
| spin_unlock(&con->lock); |
| return req; |
| } |
| } |
| |
| spin_unlock(&con->lock); |
| |
| PUB_CRYPTO_LOGE("Can't find request %d\n", request_id); |
| return NULL; |
| } |
| |
| static struct kmem_cache *pub_crypto_req_cachep; |
| |
| static void pub_crypto_request_init(pub_crypto_request_t *req, u32 opcode) { |
| memset(req, 0, sizeof(pub_crypto_request_t)); |
| |
| req->state = PUB_CRYPTO_REQ_INIT; |
| req->id = pub_crypto_get_unique_id(&g_pub_crypto_control); |
| |
| INIT_LIST_HEAD(&req->list); |
| init_waitqueue_head(&req->waitq); |
| atomic_set(&req->count, 1); |
| req->aborted = 0; |
| req->opcode = opcode; |
| } |
| |
| static pub_crypto_request_t *request_alloc(u32 opcode) { |
| pub_crypto_request_t *req = kmem_cache_alloc(pub_crypto_req_cachep, GFP_KERNEL); |
| |
| if(req) |
| pub_crypto_request_init(req, opcode); |
| return req; |
| } |
| |
| static void request_free(pub_crypto_control_t *con, pub_crypto_request_t *req) { |
| if(req) { |
| req_dump(req, "freed"); |
| spin_lock(&con->lock); |
| |
| list_del(&req->list); |
| memset(req, 0, sizeof(pub_crypto_request_t)); |
| kmem_cache_free(pub_crypto_req_cachep, req); |
| |
| spin_unlock(&con->lock); |
| } else { |
| PUB_CRYPTO_LOGE("req is NULL, skip free\n"); |
| } |
| } |
| |
| void pub_crypto_control_init(pub_crypto_control_t *con) { |
| PUB_CRYPTO_LOGD("pub_crypto_control_init"); |
| spin_lock_init(&con->lock); |
| INIT_LIST_HEAD(&con->pending_list); |
| |
| spin_lock(&con->lock); |
| con->reqctr = 0; |
| spin_unlock(&con->lock); |
| } |
| |
| static int __init pub_crypto_mod_init(void) { |
| #if (LINUX_VERSION_CODE > KERNEL_VERSION(3,4,0)) |
| struct netlink_kernel_cfg cfg = { |
| .input = crypto_recver, |
| }; |
| |
| crypto_sock = netlink_kernel_create(&init_net, NETLINK_FIPS_CRYPTO, &cfg); |
| #else |
| crypto_sock = netlink_kernel_create(&init_net, NETLINK_FIPS_CRYPTO, 0, crypto_recver, NULL, THIS_MODULE); |
| #endif |
| |
| if (!crypto_sock) { |
| PUB_CRYPTO_LOGE("Failed to create Crypto Netlink Socket .. Exiting \n"); |
| return -ENOMEM; |
| } |
| PUB_CRYPTO_LOGE("netlink socket is created successfully! \n"); |
| |
| pub_crypto_control_init(&g_pub_crypto_control); |
| pub_crypto_req_cachep = kmem_cache_create("pub_crypto_requst", |
| sizeof(pub_crypto_request_t), |
| 0, 0, NULL); |
| if (!pub_crypto_req_cachep) { |
| netlink_kernel_release(crypto_sock); |
| PUB_CRYPTO_LOGE("Failed to create pub_crypto_requst cache mem.. Exiting \n"); |
| return -ENOMEM; |
| } |
| |
| return 0; |
| } |
| |
| static void __exit pub_crypto_mod_exit(void) { |
| |
| /* |
| if (crypto_sock && crypto_sock->sk_socket) { |
| sock_release(crypto_sock->sk_socket); |
| } |
| */ |
| netlink_kernel_release(crypto_sock); |
| kmem_cache_destroy(pub_crypto_req_cachep); |
| } |
| |
| module_init(pub_crypto_mod_init); |
| module_exit(pub_crypto_mod_exit); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_DESCRIPTION("SDP pub crypto"); |
| |