// SPDX-License-Identifier: GPL-2.0+ /* * Copyright IBM Corp. 2019 * Author(s): Harald Freudenberger * * Collection of EP11 misc functions used by zcrypt and pkey */ #define KMSG_COMPONENT "zcrypt" #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt #include #include #include #include #include #include #include #include "ap_bus.h" #include "zcrypt_api.h" #include "zcrypt_debug.h" #include "zcrypt_msgtype6.h" #include "zcrypt_ep11misc.h" #include "zcrypt_ccamisc.h" #define EP11_PINBLOB_V1_BYTES 56 /* default iv used here */ static const u8 def_iv[16] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff }; /* ep11 card info cache */ struct card_list_entry { struct list_head list; u16 cardnr; struct ep11_card_info info; }; static LIST_HEAD(card_list); static DEFINE_SPINLOCK(card_list_lock); static int card_cache_fetch(u16 cardnr, struct ep11_card_info *ci) { int rc = -ENOENT; struct card_list_entry *ptr; spin_lock_bh(&card_list_lock); list_for_each_entry(ptr, &card_list, list) { if (ptr->cardnr == cardnr) { memcpy(ci, &ptr->info, sizeof(*ci)); rc = 0; break; } } spin_unlock_bh(&card_list_lock); return rc; } static void card_cache_update(u16 cardnr, const struct ep11_card_info *ci) { int found = 0; struct card_list_entry *ptr; spin_lock_bh(&card_list_lock); list_for_each_entry(ptr, &card_list, list) { if (ptr->cardnr == cardnr) { memcpy(&ptr->info, ci, sizeof(*ci)); found = 1; break; } } if (!found) { ptr = kmalloc(sizeof(*ptr), GFP_ATOMIC); if (!ptr) { spin_unlock_bh(&card_list_lock); return; } ptr->cardnr = cardnr; memcpy(&ptr->info, ci, sizeof(*ci)); list_add(&ptr->list, &card_list); } spin_unlock_bh(&card_list_lock); } static void card_cache_scrub(u16 cardnr) { struct card_list_entry *ptr; spin_lock_bh(&card_list_lock); list_for_each_entry(ptr, &card_list, list) { if (ptr->cardnr == cardnr) { list_del(&ptr->list); kfree(ptr); break; } } spin_unlock_bh(&card_list_lock); } static void __exit card_cache_free(void) { struct card_list_entry *ptr, *pnext; spin_lock_bh(&card_list_lock); list_for_each_entry_safe(ptr, pnext, &card_list, list) { list_del(&ptr->list); kfree(ptr); } spin_unlock_bh(&card_list_lock); } static int ep11_kb_split(const u8 *kb, size_t kblen, u32 kbver, struct ep11kblob_header **kbhdr, size_t *kbhdrsize, u8 **kbpl, size_t *kbplsize) { struct ep11kblob_header *hdr = NULL; size_t hdrsize, plsize = 0; int rc = -EINVAL; u8 *pl = NULL; if (kblen < sizeof(struct ep11kblob_header)) goto out; hdr = (struct ep11kblob_header *)kb; switch (kbver) { case TOKVER_EP11_AES: /* header overlays the payload */ hdrsize = 0; break; case TOKVER_EP11_ECC_WITH_HEADER: case TOKVER_EP11_AES_WITH_HEADER: /* payload starts after the header */ hdrsize = sizeof(struct ep11kblob_header); break; default: goto out; } plsize = kblen - hdrsize; pl = (u8 *)kb + hdrsize; if (kbhdr) *kbhdr = hdr; if (kbhdrsize) *kbhdrsize = hdrsize; if (kbpl) *kbpl = pl; if (kbplsize) *kbplsize = plsize; rc = 0; out: return rc; } static int ep11_kb_decode(const u8 *kb, size_t kblen, struct ep11kblob_header **kbhdr, size_t *kbhdrsize, struct ep11keyblob **kbpl, size_t *kbplsize) { struct ep11kblob_header *tmph, *hdr = NULL; size_t hdrsize = 0, plsize = 0; struct ep11keyblob *pl = NULL; int rc = -EINVAL; u8 *tmpp; if (kblen < sizeof(struct ep11kblob_header)) goto out; tmph = (struct ep11kblob_header *)kb; if (tmph->type != TOKTYPE_NON_CCA && tmph->len > kblen) goto out; if (ep11_kb_split(kb, kblen, tmph->version, &hdr, &hdrsize, &tmpp, &plsize)) goto out; if (plsize < sizeof(struct ep11keyblob)) goto out; if (!is_ep11_keyblob(tmpp)) goto out; pl = (struct ep11keyblob *)tmpp; plsize = hdr->len - hdrsize; if (kbhdr) *kbhdr = hdr; if (kbhdrsize) *kbhdrsize = hdrsize; if (kbpl) *kbpl = pl; if (kbplsize) *kbplsize = plsize; rc = 0; out: return rc; } /* * For valid ep11 keyblobs, returns a reference to the wrappingkey verification * pattern. Otherwise NULL. */ const u8 *ep11_kb_wkvp(const u8 *keyblob, u32 keybloblen) { struct ep11keyblob *kb; if (ep11_kb_decode(keyblob, keybloblen, NULL, NULL, &kb, NULL)) return NULL; return kb->wkvp; } EXPORT_SYMBOL(ep11_kb_wkvp); /* * Simple check if the key blob is a valid EP11 AES key blob with header. */ int ep11_check_aes_key_with_hdr(debug_info_t *dbg, int dbflvl, const u8 *key, u32 keylen, int checkcpacfexp) { struct ep11kblob_header *hdr = (struct ep11kblob_header *)key; struct ep11keyblob *kb = (struct ep11keyblob *)(key + sizeof(*hdr)); #define DBF(...) debug_sprintf_event(dbg, dbflvl, ##__VA_ARGS__) if (keylen < sizeof(*hdr) + sizeof(*kb)) { DBF("%s key check failed, keylen %u < %zu\n", __func__, keylen, sizeof(*hdr) + sizeof(*kb)); return -EINVAL; } if (hdr->type != TOKTYPE_NON_CCA) { if (dbg) DBF("%s key check failed, type 0x%02x != 0x%02x\n", __func__, (int)hdr->type, TOKTYPE_NON_CCA); return -EINVAL; } if (hdr->hver != 0x00) { if (dbg) DBF("%s key check failed, header version 0x%02x != 0x00\n", __func__, (int)hdr->hver); return -EINVAL; } if (hdr->version != TOKVER_EP11_AES_WITH_HEADER) { if (dbg) DBF("%s key check failed, version 0x%02x != 0x%02x\n", __func__, (int)hdr->version, TOKVER_EP11_AES_WITH_HEADER); return -EINVAL; } if (hdr->len > keylen) { if (dbg) DBF("%s key check failed, header len %d keylen %u mismatch\n", __func__, (int)hdr->len, keylen); return -EINVAL; } if (hdr->len < sizeof(*hdr) + sizeof(*kb)) { if (dbg) DBF("%s key check failed, header len %d < %zu\n", __func__, (int)hdr->len, sizeof(*hdr) + sizeof(*kb)); return -EINVAL; } if (kb->version != EP11_STRUCT_MAGIC) { if (dbg) DBF("%s key check failed, blob magic 0x%04x != 0x%04x\n", __func__, (int)kb->version, EP11_STRUCT_MAGIC); return -EINVAL; } if (checkcpacfexp && !(kb->attr & EP11_BLOB_PKEY_EXTRACTABLE)) { if (dbg) DBF("%s key check failed, PKEY_EXTRACTABLE is off\n", __func__); return -EINVAL; } #undef DBF return 0; } EXPORT_SYMBOL(ep11_check_aes_key_with_hdr); /* * Simple check if the key blob is a valid EP11 ECC key blob with header. */ int ep11_check_ecc_key_with_hdr(debug_info_t *dbg, int dbflvl, const u8 *key, u32 keylen, int checkcpacfexp) { struct ep11kblob_header *hdr = (struct ep11kblob_header *)key; struct ep11keyblob *kb = (struct ep11keyblob *)(key + sizeof(*hdr)); #define DBF(...) debug_sprintf_event(dbg, dbflvl, ##__VA_ARGS__) if (keylen < sizeof(*hdr) + sizeof(*kb)) { DBF("%s key check failed, keylen %u < %zu\n", __func__, keylen, sizeof(*hdr) + sizeof(*kb)); return -EINVAL; } if (hdr->type != TOKTYPE_NON_CCA) { if (dbg) DBF("%s key check failed, type 0x%02x != 0x%02x\n", __func__, (int)hdr->type, TOKTYPE_NON_CCA); return -EINVAL; } if (hdr->hver != 0x00) { if (dbg) DBF("%s key check failed, header version 0x%02x != 0x00\n", __func__, (int)hdr->hver); return -EINVAL; } if (hdr->version != TOKVER_EP11_ECC_WITH_HEADER) { if (dbg) DBF("%s key check failed, version 0x%02x != 0x%02x\n", __func__, (int)hdr->version, TOKVER_EP11_ECC_WITH_HEADER); return -EINVAL; } if (hdr->len > keylen) { if (dbg) DBF("%s key check failed, header len %d keylen %u mismatch\n", __func__, (int)hdr->len, keylen); return -EINVAL; } if (hdr->len < sizeof(*hdr) + sizeof(*kb)) { if (dbg) DBF("%s key check failed, header len %d < %zu\n", __func__, (int)hdr->len, sizeof(*hdr) + sizeof(*kb)); return -EINVAL; } if (kb->version != EP11_STRUCT_MAGIC) { if (dbg) DBF("%s key check failed, blob magic 0x%04x != 0x%04x\n", __func__, (int)kb->version, EP11_STRUCT_MAGIC); return -EINVAL; } if (checkcpacfexp && !(kb->attr & EP11_BLOB_PKEY_EXTRACTABLE)) { if (dbg) DBF("%s key check failed, PKEY_EXTRACTABLE is off\n", __func__); return -EINVAL; } #undef DBF return 0; } EXPORT_SYMBOL(ep11_check_ecc_key_with_hdr); /* * Simple check if the key blob is a valid EP11 AES key blob with * the header in the session field (old style EP11 AES key). */ int ep11_check_aes_key(debug_info_t *dbg, int dbflvl, const u8 *key, u32 keylen, int checkcpacfexp) { struct ep11keyblob *kb = (struct ep11keyblob *)key; #define DBF(...) debug_sprintf_event(dbg, dbflvl, ##__VA_ARGS__) if (keylen < sizeof(*kb)) { DBF("%s key check failed, keylen %u < %zu\n", __func__, keylen, sizeof(*kb)); return -EINVAL; } if (kb->head.type != TOKTYPE_NON_CCA) { if (dbg) DBF("%s key check failed, type 0x%02x != 0x%02x\n", __func__, (int)kb->head.type, TOKTYPE_NON_CCA); return -EINVAL; } if (kb->head.version != TOKVER_EP11_AES) { if (dbg) DBF("%s key check failed, version 0x%02x != 0x%02x\n", __func__, (int)kb->head.version, TOKVER_EP11_AES); return -EINVAL; } if (kb->head.len > keylen) { if (dbg) DBF("%s key check failed, header len %d keylen %u mismatch\n", __func__, (int)kb->head.len, keylen); return -EINVAL; } if (kb->head.len < sizeof(*kb)) { if (dbg) DBF("%s key check failed, header len %d < %zu\n", __func__, (int)kb->head.len, sizeof(*kb)); return -EINVAL; } if (kb->version != EP11_STRUCT_MAGIC) { if (dbg) DBF("%s key check failed, blob magic 0x%04x != 0x%04x\n", __func__, (int)kb->version, EP11_STRUCT_MAGIC); return -EINVAL; } if (checkcpacfexp && !(kb->attr & EP11_BLOB_PKEY_EXTRACTABLE)) { if (dbg) DBF("%s key check failed, PKEY_EXTRACTABLE is off\n", __func__); return -EINVAL; } #undef DBF return 0; } EXPORT_SYMBOL(ep11_check_aes_key); /* * Allocate and prepare ep11 cprb plus additional payload. */ static inline struct ep11_cprb *alloc_cprb(size_t payload_len) { size_t len = sizeof(struct ep11_cprb) + payload_len; struct ep11_cprb *cprb; cprb = kzalloc(len, GFP_KERNEL); if (!cprb) return NULL; cprb->cprb_len = sizeof(struct ep11_cprb); cprb->cprb_ver_id = 0x04; memcpy(cprb->func_id, "T4", 2); cprb->ret_code = 0xFFFFFFFF; cprb->payload_len = payload_len; return cprb; } /* * Some helper functions related to ASN1 encoding. * Limited to length info <= 2 byte. */ #define ASN1TAGLEN(x) (2 + (x) + ((x) > 127 ? 1 : 0) + ((x) > 255 ? 1 : 0)) static int asn1tag_write(u8 *ptr, u8 tag, const u8 *pvalue, u16 valuelen) { ptr[0] = tag; if (valuelen > 255) { ptr[1] = 0x82; *((u16 *)(ptr + 2)) = valuelen; memcpy(ptr + 4, pvalue, valuelen); return 4 + valuelen; } if (valuelen > 127) { ptr[1] = 0x81; ptr[2] = (u8)valuelen; memcpy(ptr + 3, pvalue, valuelen); return 3 + valuelen; } ptr[1] = (u8)valuelen; memcpy(ptr + 2, pvalue, valuelen); return 2 + valuelen; } /* EP11 payload > 127 bytes starts with this struct */ struct pl_head { u8 tag; u8 lenfmt; u16 len; u8 func_tag; u8 func_len; u32 func; u8 dom_tag; u8 dom_len; u32 dom; } __packed; /* prep ep11 payload head helper function */ static inline void prep_head(struct pl_head *h, size_t pl_size, int api, int func) { h->tag = 0x30; h->lenfmt = 0x82; h->len = pl_size - 4; h->func_tag = 0x04; h->func_len = sizeof(u32); h->func = (api << 16) + func; h->dom_tag = 0x04; h->dom_len = sizeof(u32); } /* prep urb helper function */ static inline void prep_urb(struct ep11_urb *u, struct ep11_target_dev *t, int nt, struct ep11_cprb *req, size_t req_len, struct ep11_cprb *rep, size_t rep_len) { u->targets = (u8 __user *)t; u->targets_num = nt; u->req = (u8 __user *)req; u->req_len = req_len; u->resp = (u8 __user *)rep; u->resp_len = rep_len; } /* Check ep11 reply payload, return 0 or suggested errno value. */ static int check_reply_pl(const u8 *pl, const char *func) { int len; u32 ret; /* start tag */ if (*pl++ != 0x30) { ZCRYPT_DBF_ERR("%s reply start tag mismatch\n", func); return -EIO; } /* payload length format */ if (*pl < 127) { len = *pl; pl++; } else if (*pl == 0x81) { pl++; len = *pl; pl++; } else if (*pl == 0x82) { pl++; len = *((u16 *)pl); pl += 2; } else { ZCRYPT_DBF_ERR("%s reply start tag lenfmt mismatch 0x%02hhx\n", func, *pl); return -EIO; } /* len should cover at least 3 fields with 32 bit value each */ if (len < 3 * 6) { ZCRYPT_DBF_ERR("%s reply length %d too small\n", func, len); return -EIO; } /* function tag, length and value */ if (pl[0] != 0x04 || pl[1] != 0x04) { ZCRYPT_DBF_ERR("%s function tag or length mismatch\n", func); return -EIO; } pl += 6; /* dom tag, length and value */ if (pl[0] != 0x04 || pl[1] != 0x04) { ZCRYPT_DBF_ERR("%s dom tag or length mismatch\n", func); return -EIO; } pl += 6; /* return value tag, length and value */ if (pl[0] != 0x04 || pl[1] != 0x04) { ZCRYPT_DBF_ERR("%s return value tag or length mismatch\n", func); return -EIO; } pl += 2; ret = *((u32 *)pl); if (ret != 0) { ZCRYPT_DBF_ERR("%s return value 0x%08x != 0\n", func, ret); return -EIO; } return 0; } /* Check ep11 reply cprb, return 0 or suggested errno value. */ static int check_reply_cprb(const struct ep11_cprb *rep, const char *func) { /* check ep11 reply return code field */ if (rep->ret_code) { ZCRYPT_DBF_ERR("%s ep11 reply ret_code=0x%08x\n", __func__, rep->ret_code); if (rep->ret_code == 0x000c0003) return -EBUSY; else return -EIO; } return 0; } /* * Helper function which does an ep11 query with given query type. */ static int ep11_query_info(u16 cardnr, u16 domain, u32 query_type, size_t buflen, u8 *buf) { struct ep11_info_req_pl { struct pl_head head; u8 query_type_tag; u8 query_type_len; u32 query_type; u8 query_subtype_tag; u8 query_subtype_len; u32 query_subtype; } __packed * req_pl; struct ep11_info_rep_pl { struct pl_head head; u8 rc_tag; u8 rc_len; u32 rc; u8 data_tag; u8 data_lenfmt; u16 data_len; } __packed * rep_pl; struct ep11_cprb *req = NULL, *rep = NULL; struct ep11_target_dev target; struct ep11_urb *urb = NULL; int api = EP11_API_V1, rc = -ENOMEM; /* request cprb and payload */ req = alloc_cprb(sizeof(struct ep11_info_req_pl)); if (!req) goto out; req_pl = (struct ep11_info_req_pl *)(((u8 *)req) + sizeof(*req)); prep_head(&req_pl->head, sizeof(*req_pl), api, 38); /* get xcp info */ req_pl->query_type_tag = 0x04; req_pl->query_type_len = sizeof(u32); req_pl->query_type = query_type; req_pl->query_subtype_tag = 0x04; req_pl->query_subtype_len = sizeof(u32); /* reply cprb and payload */ rep = alloc_cprb(sizeof(struct ep11_info_rep_pl) + buflen); if (!rep) goto out; rep_pl = (struct ep11_info_rep_pl *)(((u8 *)rep) + sizeof(*rep)); /* urb and target */ urb = kmalloc(sizeof(*urb), GFP_KERNEL); if (!urb) goto out; target.ap_id = cardnr; target.dom_id = domain; prep_urb(urb, &target, 1, req, sizeof(*req) + sizeof(*req_pl), rep, sizeof(*rep) + sizeof(*rep_pl) + buflen); rc = zcrypt_send_ep11_cprb(urb); if (rc) { ZCRYPT_DBF_ERR("%s zcrypt_send_ep11_cprb(card=%d dom=%d) failed, rc=%d\n", __func__, (int)cardnr, (int)domain, rc); goto out; } /* check ep11 reply cprb */ rc = check_reply_cprb(rep, __func__); if (rc) goto out; /* check payload */ rc = check_reply_pl((u8 *)rep_pl, __func__); if (rc) goto out; if (rep_pl->data_tag != 0x04 || rep_pl->data_lenfmt != 0x82) { ZCRYPT_DBF_ERR("%s unknown reply data format\n", __func__); rc = -EIO; goto out; } if (rep_pl->data_len > buflen) { ZCRYPT_DBF_ERR("%s mismatch between reply data len and buffer len\n", __func__); rc = -ENOSPC; goto out; } memcpy(buf, ((u8 *)rep_pl) + sizeof(*rep_pl), rep_pl->data_len); out: kfree(req); kfree(rep); kfree(urb); return rc; } /* * Provide information about an EP11 card. */ int ep11_get_card_info(u16 card, struct ep11_card_info *info, int verify) { int rc; struct ep11_module_query_info { u32 API_ord_nr; u32 firmware_id; u8 FW_major_vers; u8 FW_minor_vers; u8 CSP_major_vers; u8 CSP_minor_vers; u8 fwid[32]; u8 xcp_config_hash[32]; u8 CSP_config_hash[32]; u8 serial[16]; u8 module_date_time[16]; u64 op_mode; u32 PKCS11_flags; u32 ext_flags; u32 domains; u32 sym_state_bytes; u32 digest_state_bytes; u32 pin_blob_bytes; u32 SPKI_bytes; u32 priv_key_blob_bytes; u32 sym_blob_bytes; u32 max_payload_bytes; u32 CP_profile_bytes; u32 max_CP_index; } __packed * pmqi = NULL; rc = card_cache_fetch(card, info); if (rc || verify) { pmqi = kmalloc(sizeof(*pmqi), GFP_KERNEL); if (!pmqi) return -ENOMEM; rc = ep11_query_info(card, AUTOSEL_DOM, 0x01 /* module info query */, sizeof(*pmqi), (u8 *)pmqi); if (rc) { if (rc == -ENODEV) card_cache_scrub(card); goto out; } memset(info, 0, sizeof(*info)); info->API_ord_nr = pmqi->API_ord_nr; info->FW_version = (pmqi->FW_major_vers << 8) + pmqi->FW_minor_vers; memcpy(info->serial, pmqi->serial, sizeof(info->serial)); info->op_mode = pmqi->op_mode; card_cache_update(card, info); } out: kfree(pmqi); return rc; } EXPORT_SYMBOL(ep11_get_card_info); /* * Provide information about a domain within an EP11 card. */ int ep11_get_domain_info(u16 card, u16 domain, struct ep11_domain_info *info) { int rc; struct ep11_domain_query_info { u32 dom_index; u8 cur_WK_VP[32]; u8 new_WK_VP[32]; u32 dom_flags; u64 op_mode; } __packed * p_dom_info; p_dom_info = kmalloc(sizeof(*p_dom_info), GFP_KERNEL); if (!p_dom_info) return -ENOMEM; rc = ep11_query_info(card, domain, 0x03 /* domain info query */, sizeof(*p_dom_info), (u8 *)p_dom_info); if (rc) goto out; memset(info, 0, sizeof(*info)); info->cur_wk_state = '0'; info->new_wk_state = '0'; if (p_dom_info->dom_flags & 0x10 /* left imprint mode */) { if (p_dom_info->dom_flags & 0x02 /* cur wk valid */) { info->cur_wk_state = '1'; memcpy(info->cur_wkvp, p_dom_info->cur_WK_VP, 32); } if (p_dom_info->dom_flags & 0x04 || /* new wk present */ p_dom_info->dom_flags & 0x08 /* new wk committed */) { info->new_wk_state = p_dom_info->dom_flags & 0x08 ? '2' : '1'; memcpy(info->new_wkvp, p_dom_info->new_WK_VP, 32); } } info->op_mode = p_dom_info->op_mode; out: kfree(p_dom_info); return rc; } EXPORT_SYMBOL(ep11_get_domain_info); /* * Default EP11 AES key generate attributes, used when no keygenflags given: * XCP_BLOB_ENCRYPT | XCP_BLOB_DECRYPT | XCP_BLOB_PROTKEY_EXTRACTABLE */ #define KEY_ATTR_DEFAULTS 0x00200c00 static int _ep11_genaeskey(u16 card, u16 domain, u32 keybitsize, u32 keygenflags, u8 *keybuf, size_t *keybufsize) { struct keygen_req_pl { struct pl_head head; u8 var_tag; u8 var_len; u32 var; u8 keybytes_tag; u8 keybytes_len; u32 keybytes; u8 mech_tag; u8 mech_len; u32 mech; u8 attr_tag; u8 attr_len; u32 attr_header; u32 attr_bool_mask; u32 attr_bool_bits; u32 attr_val_len_type; u32 attr_val_len_value; /* followed by empty pin tag or empty pinblob tag */ } __packed * req_pl; struct keygen_rep_pl { struct pl_head head; u8 rc_tag; u8 rc_len; u32 rc; u8 data_tag; u8 data_lenfmt; u16 data_len; u8 data[512]; } __packed * rep_pl; struct ep11_cprb *req = NULL, *rep = NULL; size_t req_pl_size, pinblob_size = 0; struct ep11_target_dev target; struct ep11_urb *urb = NULL; int api, rc = -ENOMEM; u8 *p; switch (keybitsize) { case 128: case 192: case 256: break; default: ZCRYPT_DBF_ERR("%s unknown/unsupported keybitsize %d\n", __func__, keybitsize); rc = -EINVAL; goto out; } /* request cprb and payload */ api = (!keygenflags || keygenflags & 0x00200000) ? EP11_API_V4 : EP11_API_V1; if (ap_is_se_guest()) { /* * genkey within SE environment requires API ordinal 6 * with empty pinblob */ api = EP11_API_V6; pinblob_size = EP11_PINBLOB_V1_BYTES; } req_pl_size = sizeof(struct keygen_req_pl) + ASN1TAGLEN(pinblob_size); req = alloc_cprb(req_pl_size); if (!req) goto out; req_pl = (struct keygen_req_pl *)(((u8 *)req) + sizeof(*req)); prep_head(&req_pl->head, req_pl_size, api, 21); /* GenerateKey */ req_pl->var_tag = 0x04; req_pl->var_len = sizeof(u32); req_pl->keybytes_tag = 0x04; req_pl->keybytes_len = sizeof(u32); req_pl->keybytes = keybitsize / 8; req_pl->mech_tag = 0x04; req_pl->mech_len = sizeof(u32); req_pl->mech = 0x00001080; /* CKM_AES_KEY_GEN */ req_pl->attr_tag = 0x04; req_pl->attr_len = 5 * sizeof(u32); req_pl->attr_header = 0x10010000; req_pl->attr_bool_mask = keygenflags ? keygenflags : KEY_ATTR_DEFAULTS; req_pl->attr_bool_bits = keygenflags ? keygenflags : KEY_ATTR_DEFAULTS; req_pl->attr_val_len_type = 0x00000161; /* CKA_VALUE_LEN */ req_pl->attr_val_len_value = keybitsize / 8; p = ((u8 *)req_pl) + sizeof(*req_pl); /* pin tag */ *p++ = 0x04; *p++ = pinblob_size; /* reply cprb and payload */ rep = alloc_cprb(sizeof(struct keygen_rep_pl)); if (!rep) goto out; rep_pl = (struct keygen_rep_pl *)(((u8 *)rep) + sizeof(*rep)); /* urb and target */ urb = kmalloc(sizeof(*urb), GFP_KERNEL); if (!urb) goto out; target.ap_id = card; target.dom_id = domain; prep_urb(urb, &target, 1, req, sizeof(*req) + req_pl_size, rep, sizeof(*rep) + sizeof(*rep_pl)); rc = zcrypt_send_ep11_cprb(urb); if (rc) { ZCRYPT_DBF_ERR("%s zcrypt_send_ep11_cprb(card=%d dom=%d) failed, rc=%d\n", __func__, (int)card, (int)domain, rc); goto out; } /* check ep11 reply cprb */ rc = check_reply_cprb(rep, __func__); if (rc) goto out; /* check payload */ rc = check_reply_pl((u8 *)rep_pl, __func__); if (rc) goto out; if (rep_pl->data_tag != 0x04 || rep_pl->data_lenfmt != 0x82) { ZCRYPT_DBF_ERR("%s unknown reply data format\n", __func__); rc = -EIO; goto out; } if (rep_pl->data_len > *keybufsize) { ZCRYPT_DBF_ERR("%s mismatch reply data len / key buffer len\n", __func__); rc = -ENOSPC; goto out; } /* copy key blob */ memcpy(keybuf, rep_pl->data, rep_pl->data_len); *keybufsize = rep_pl->data_len; out: kfree(req); kfree(rep); kfree(urb); return rc; } int ep11_genaeskey(u16 card, u16 domain, u32 keybitsize, u32 keygenflags, u8 *keybuf, u32 *keybufsize, u32 keybufver) { struct ep11kblob_header *hdr; size_t hdr_size, pl_size; u8 *pl; int rc; switch (keybufver) { case TOKVER_EP11_AES: case TOKVER_EP11_AES_WITH_HEADER: break; default: return -EINVAL; } rc = ep11_kb_split(keybuf, *keybufsize, keybufver, &hdr, &hdr_size, &pl, &pl_size); if (rc) return rc; rc = _ep11_genaeskey(card, domain, keybitsize, keygenflags, pl, &pl_size); if (rc) return rc; *keybufsize = hdr_size + pl_size; /* update header information */ hdr->type = TOKTYPE_NON_CCA; hdr->len = *keybufsize; hdr->version = keybufver; hdr->bitlen = keybitsize; return 0; } EXPORT_SYMBOL(ep11_genaeskey); static int ep11_cryptsingle(u16 card, u16 domain, u16 mode, u32 mech, const u8 *iv, const u8 *key, size_t keysize, const u8 *inbuf, size_t inbufsize, u8 *outbuf, size_t *outbufsize) { struct crypt_req_pl { struct pl_head head; u8 var_tag; u8 var_len; u32 var; u8 mech_tag; u8 mech_len; u32 mech; /* * maybe followed by iv data * followed by key tag + key blob * followed by plaintext tag + plaintext */ } __packed * req_pl; struct crypt_rep_pl { struct pl_head head; u8 rc_tag; u8 rc_len; u32 rc; u8 data_tag; u8 data_lenfmt; /* data follows */ } __packed * rep_pl; struct ep11_cprb *req = NULL, *rep = NULL; struct ep11_target_dev target; struct ep11_urb *urb = NULL; size_t req_pl_size, rep_pl_size; int n, api = EP11_API_V1, rc = -ENOMEM; u8 *p; /* the simple asn1 coding used has length limits */ if (keysize > 0xFFFF || inbufsize > 0xFFFF) return -EINVAL; /* request cprb and payload */ req_pl_size = sizeof(struct crypt_req_pl) + (iv ? 16 : 0) + ASN1TAGLEN(keysize) + ASN1TAGLEN(inbufsize); req = alloc_cprb(req_pl_size); if (!req) goto out; req_pl = (struct crypt_req_pl *)(((u8 *)req) + sizeof(*req)); prep_head(&req_pl->head, req_pl_size, api, (mode ? 20 : 19)); req_pl->var_tag = 0x04; req_pl->var_len = sizeof(u32); /* mech is mech + mech params (iv here) */ req_pl->mech_tag = 0x04; req_pl->mech_len = sizeof(u32) + (iv ? 16 : 0); req_pl->mech = (mech ? mech : 0x00001085); /* CKM_AES_CBC_PAD */ p = ((u8 *)req_pl) + sizeof(*req_pl); if (iv) { memcpy(p, iv, 16); p += 16; } /* key and input data */ p += asn1tag_write(p, 0x04, key, keysize); p += asn1tag_write(p, 0x04, inbuf, inbufsize); /* reply cprb and payload, assume out data size <= in data size + 32 */ rep_pl_size = sizeof(struct crypt_rep_pl) + ASN1TAGLEN(inbufsize + 32); rep = alloc_cprb(rep_pl_size); if (!rep) goto out; rep_pl = (struct crypt_rep_pl *)(((u8 *)rep) + sizeof(*rep)); /* urb and target */ urb = kmalloc(sizeof(*urb), GFP_KERNEL); if (!urb) goto out; target.ap_id = card; target.dom_id = domain; prep_urb(urb, &target, 1, req, sizeof(*req) + req_pl_size, rep, sizeof(*rep) + rep_pl_size); rc = zcrypt_send_ep11_cprb(urb); if (rc) { ZCRYPT_DBF_ERR("%s zcrypt_send_ep11_cprb(card=%d dom=%d) failed, rc=%d\n", __func__, (int)card, (int)domain, rc); goto out; } /* check ep11 reply cprb */ rc = check_reply_cprb(rep, __func__); if (rc) goto out; /* check payload */ rc = check_reply_pl((u8 *)rep_pl, __func__); if (rc) goto out; if (rep_pl->data_tag != 0x04) { ZCRYPT_DBF_ERR("%s unknown reply data format\n", __func__); rc = -EIO; goto out; } p = ((u8 *)rep_pl) + sizeof(*rep_pl); if (rep_pl->data_lenfmt <= 127) { n = rep_pl->data_lenfmt; } else if (rep_pl->data_lenfmt == 0x81) { n = *p++; } else if (rep_pl->data_lenfmt == 0x82) { n = *((u16 *)p); p += 2; } else { ZCRYPT_DBF_ERR("%s unknown reply data length format 0x%02hhx\n", __func__, rep_pl->data_lenfmt); rc = -EIO; goto out; } if (n > *outbufsize) { ZCRYPT_DBF_ERR("%s mismatch reply data len %d / output buffer %zu\n", __func__, n, *outbufsize); rc = -ENOSPC; goto out; } memcpy(outbuf, p, n); *outbufsize = n; out: kfree(req); kfree(rep); kfree(urb); return rc; } static int _ep11_unwrapkey(u16 card, u16 domain, const u8 *kek, size_t keksize, const u8 *enckey, size_t enckeysize, u32 mech, const u8 *iv, u32 keybitsize, u32 keygenflags, u8 *keybuf, size_t *keybufsize) { struct uw_req_pl { struct pl_head head; u8 attr_tag; u8 attr_len; u32 attr_header; u32 attr_bool_mask; u32 attr_bool_bits; u32 attr_key_type; u32 attr_key_type_value; u32 attr_val_len; u32 attr_val_len_value; u8 mech_tag; u8 mech_len; u32 mech; /* * maybe followed by iv data * followed by kek tag + kek blob * followed by empty mac tag * followed by empty pin tag or empty pinblob tag * followed by encryted key tag + bytes */ } __packed * req_pl; struct uw_rep_pl { struct pl_head head; u8 rc_tag; u8 rc_len; u32 rc; u8 data_tag; u8 data_lenfmt; u16 data_len; u8 data[512]; } __packed * rep_pl; struct ep11_cprb *req = NULL, *rep = NULL; size_t req_pl_size, pinblob_size = 0; struct ep11_target_dev target; struct ep11_urb *urb = NULL; int api, rc = -ENOMEM; u8 *p; /* request cprb and payload */ api = (!keygenflags || keygenflags & 0x00200000) ? EP11_API_V4 : EP11_API_V1; if (ap_is_se_guest()) { /* * unwrap within SE environment requires API ordinal 6 * with empty pinblob */ api = EP11_API_V6; pinblob_size = EP11_PINBLOB_V1_BYTES; } req_pl_size = sizeof(struct uw_req_pl) + (iv ? 16 : 0) + ASN1TAGLEN(keksize) + ASN1TAGLEN(0) + ASN1TAGLEN(pinblob_size) + ASN1TAGLEN(enckeysize); req = alloc_cprb(req_pl_size); if (!req) goto out; req_pl = (struct uw_req_pl *)(((u8 *)req) + sizeof(*req)); prep_head(&req_pl->head, req_pl_size, api, 34); /* UnwrapKey */ req_pl->attr_tag = 0x04; req_pl->attr_len = 7 * sizeof(u32); req_pl->attr_header = 0x10020000; req_pl->attr_bool_mask = keygenflags ? keygenflags : KEY_ATTR_DEFAULTS; req_pl->attr_bool_bits = keygenflags ? keygenflags : KEY_ATTR_DEFAULTS; req_pl->attr_key_type = 0x00000100; /* CKA_KEY_TYPE */ req_pl->attr_key_type_value = 0x0000001f; /* CKK_AES */ req_pl->attr_val_len = 0x00000161; /* CKA_VALUE_LEN */ req_pl->attr_val_len_value = keybitsize / 8; /* mech is mech + mech params (iv here) */ req_pl->mech_tag = 0x04; req_pl->mech_len = sizeof(u32) + (iv ? 16 : 0); req_pl->mech = (mech ? mech : 0x00001085); /* CKM_AES_CBC_PAD */ p = ((u8 *)req_pl) + sizeof(*req_pl); if (iv) { memcpy(p, iv, 16); p += 16; } /* kek */ p += asn1tag_write(p, 0x04, kek, keksize); /* empty mac key tag */ *p++ = 0x04; *p++ = 0; /* pin tag */ *p++ = 0x04; *p++ = pinblob_size; p += pinblob_size; /* encrypted key value tag and bytes */ p += asn1tag_write(p, 0x04, enckey, enckeysize); /* reply cprb and payload */ rep = alloc_cprb(sizeof(struct uw_rep_pl)); if (!rep) goto out; rep_pl = (struct uw_rep_pl *)(((u8 *)rep) + sizeof(*rep)); /* urb and target */ urb = kmalloc(sizeof(*urb), GFP_KERNEL); if (!urb) goto out; target.ap_id = card; target.dom_id = domain; prep_urb(urb, &target, 1, req, sizeof(*req) + req_pl_size, rep, sizeof(*rep) + sizeof(*rep_pl)); rc = zcrypt_send_ep11_cprb(urb); if (rc) { ZCRYPT_DBF_ERR("%s zcrypt_send_ep11_cprb(card=%d dom=%d) failed, rc=%d\n", __func__, (int)card, (int)domain, rc); goto out; } /* check ep11 reply cprb */ rc = check_reply_cprb(rep, __func__); if (rc) goto out; /* check payload */ rc = check_reply_pl((u8 *)rep_pl, __func__); if (rc) goto out; if (rep_pl->data_tag != 0x04 || rep_pl->data_lenfmt != 0x82) { ZCRYPT_DBF_ERR("%s unknown reply data format\n", __func__); rc = -EIO; goto out; } if (rep_pl->data_len > *keybufsize) { ZCRYPT_DBF_ERR("%s mismatch reply data len / key buffer len\n", __func__); rc = -ENOSPC; goto out; } /* copy key blob */ memcpy(keybuf, rep_pl->data, rep_pl->data_len); *keybufsize = rep_pl->data_len; out: kfree(req); kfree(rep); kfree(urb); return rc; } static int ep11_unwrapkey(u16 card, u16 domain, const u8 *kek, size_t keksize, const u8 *enckey, size_t enckeysize, u32 mech, const u8 *iv, u32 keybitsize, u32 keygenflags, u8 *keybuf, u32 *keybufsize, u8 keybufver) { struct ep11kblob_header *hdr; size_t hdr_size, pl_size; u8 *pl; int rc; rc = ep11_kb_split(keybuf, *keybufsize, keybufver, &hdr, &hdr_size, &pl, &pl_size); if (rc) return rc; rc = _ep11_unwrapkey(card, domain, kek, keksize, enckey, enckeysize, mech, iv, keybitsize, keygenflags, pl, &pl_size); if (rc) return rc; *keybufsize = hdr_size + pl_size; /* update header information */ hdr = (struct ep11kblob_header *)keybuf; hdr->type = TOKTYPE_NON_CCA; hdr->len = *keybufsize; hdr->version = keybufver; hdr->bitlen = keybitsize; return 0; } static int _ep11_wrapkey(u16 card, u16 domain, const u8 *key, size_t keysize, u32 mech, const u8 *iv, u8 *databuf, size_t *datasize) { struct wk_req_pl { struct pl_head head; u8 var_tag; u8 var_len; u32 var; u8 mech_tag; u8 mech_len; u32 mech; /* * followed by iv data * followed by key tag + key blob * followed by dummy kek param * followed by dummy mac param */ } __packed * req_pl; struct wk_rep_pl { struct pl_head head; u8 rc_tag; u8 rc_len; u32 rc; u8 data_tag; u8 data_lenfmt; u16 data_len; u8 data[1024]; } __packed * rep_pl; struct ep11_cprb *req = NULL, *rep = NULL; struct ep11_target_dev target; struct ep11_urb *urb = NULL; size_t req_pl_size; int api, rc = -ENOMEM; u8 *p; /* request cprb and payload */ req_pl_size = sizeof(struct wk_req_pl) + (iv ? 16 : 0) + ASN1TAGLEN(keysize) + 4; req = alloc_cprb(req_pl_size); if (!req) goto out; if (!mech || mech == 0x80060001) req->flags |= 0x20; /* CPACF_WRAP needs special bit */ req_pl = (struct wk_req_pl *)(((u8 *)req) + sizeof(*req)); api = (!mech || mech == 0x80060001) ? /* CKM_IBM_CPACF_WRAP */ EP11_API_V4 : EP11_API_V1; prep_head(&req_pl->head, req_pl_size, api, 33); /* WrapKey */ req_pl->var_tag = 0x04; req_pl->var_len = sizeof(u32); /* mech is mech + mech params (iv here) */ req_pl->mech_tag = 0x04; req_pl->mech_len = sizeof(u32) + (iv ? 16 : 0); req_pl->mech = (mech ? mech : 0x80060001); /* CKM_IBM_CPACF_WRAP */ p = ((u8 *)req_pl) + sizeof(*req_pl); if (iv) { memcpy(p, iv, 16); p += 16; } /* key blob */ p += asn1tag_write(p, 0x04, key, keysize); /* empty kek tag */ *p++ = 0x04; *p++ = 0; /* empty mac tag */ *p++ = 0x04; *p++ = 0; /* reply cprb and payload */ rep = alloc_cprb(sizeof(struct wk_rep_pl)); if (!rep) goto out; rep_pl = (struct wk_rep_pl *)(((u8 *)rep) + sizeof(*rep)); /* urb and target */ urb = kmalloc(sizeof(*urb), GFP_KERNEL); if (!urb) goto out; target.ap_id = card; target.dom_id = domain; prep_urb(urb, &target, 1, req, sizeof(*req) + req_pl_size, rep, sizeof(*rep) + sizeof(*rep_pl)); rc = zcrypt_send_ep11_cprb(urb); if (rc) { ZCRYPT_DBF_ERR("%s zcrypt_send_ep11_cprb(card=%d dom=%d) failed, rc=%d\n", __func__, (int)card, (int)domain, rc); goto out; } /* check ep11 reply cprb */ rc = check_reply_cprb(rep, __func__); if (rc) goto out; /* check payload */ rc = check_reply_pl((u8 *)rep_pl, __func__); if (rc) goto out; if (rep_pl->data_tag != 0x04 || rep_pl->data_lenfmt != 0x82) { ZCRYPT_DBF_ERR("%s unknown reply data format\n", __func__); rc = -EIO; goto out; } if (rep_pl->data_len > *datasize) { ZCRYPT_DBF_ERR("%s mismatch reply data len / data buffer len\n", __func__); rc = -ENOSPC; goto out; } /* copy the data from the cprb to the data buffer */ memcpy(databuf, rep_pl->data, rep_pl->data_len); *datasize = rep_pl->data_len; out: kfree(req); kfree(rep); kfree(urb); return rc; } int ep11_clr2keyblob(u16 card, u16 domain, u32 keybitsize, u32 keygenflags, const u8 *clrkey, u8 *keybuf, u32 *keybufsize, u32 keytype) { int rc; u8 encbuf[64], *kek = NULL; size_t clrkeylen, keklen, encbuflen = sizeof(encbuf); if (keybitsize == 128 || keybitsize == 192 || keybitsize == 256) { clrkeylen = keybitsize / 8; } else { ZCRYPT_DBF_ERR("%s unknown/unsupported keybitsize %d\n", __func__, keybitsize); return -EINVAL; } /* allocate memory for the temp kek */ keklen = MAXEP11AESKEYBLOBSIZE; kek = kmalloc(keklen, GFP_ATOMIC); if (!kek) { rc = -ENOMEM; goto out; } /* Step 1: generate AES 256 bit random kek key */ rc = _ep11_genaeskey(card, domain, 256, 0x00006c00, /* EN/DECRYPT, WRAP/UNWRAP */ kek, &keklen); if (rc) { ZCRYPT_DBF_ERR("%s generate kek key failed, rc=%d\n", __func__, rc); goto out; } /* Step 2: encrypt clear key value with the kek key */ rc = ep11_cryptsingle(card, domain, 0, 0, def_iv, kek, keklen, clrkey, clrkeylen, encbuf, &encbuflen); if (rc) { ZCRYPT_DBF_ERR("%s encrypting key value with kek key failed, rc=%d\n", __func__, rc); goto out; } /* Step 3: import the encrypted key value as a new key */ rc = ep11_unwrapkey(card, domain, kek, keklen, encbuf, encbuflen, 0, def_iv, keybitsize, 0, keybuf, keybufsize, keytype); if (rc) { ZCRYPT_DBF_ERR("%s importing key value as new key failed,, rc=%d\n", __func__, rc); goto out; } out: kfree(kek); return rc; } EXPORT_SYMBOL(ep11_clr2keyblob); int ep11_kblob2protkey(u16 card, u16 dom, const u8 *keyblob, u32 keybloblen, u8 *protkey, u32 *protkeylen, u32 *protkeytype) { struct ep11kblob_header *hdr; struct ep11keyblob *key; size_t wkbuflen, keylen; struct wk_info { u16 version; u8 res1[16]; u32 pkeytype; u32 pkeybitsize; u64 pkeysize; u8 res2[8]; u8 pkey[]; } __packed * wki; u8 *wkbuf = NULL; int rc = -EIO; if (ep11_kb_decode((u8 *)keyblob, keybloblen, &hdr, NULL, &key, &keylen)) return -EINVAL; if (hdr->version == TOKVER_EP11_AES) { /* wipe overlayed header */ memset(hdr, 0, sizeof(*hdr)); } /* !!! hdr is no longer a valid header !!! */ /* alloc temp working buffer */ wkbuflen = (keylen + AES_BLOCK_SIZE) & (~(AES_BLOCK_SIZE - 1)); wkbuf = kmalloc(wkbuflen, GFP_ATOMIC); if (!wkbuf) return -ENOMEM; /* ep11 secure key -> protected key + info */ rc = _ep11_wrapkey(card, dom, (u8 *)key, keylen, 0, def_iv, wkbuf, &wkbuflen); if (rc) { ZCRYPT_DBF_ERR("%s rewrapping ep11 key to pkey failed, rc=%d\n", __func__, rc); goto out; } wki = (struct wk_info *)wkbuf; /* check struct version and pkey type */ if (wki->version != 1 || wki->pkeytype < 1 || wki->pkeytype > 5) { ZCRYPT_DBF_ERR("%s wk info version %d or pkeytype %d mismatch.\n", __func__, (int)wki->version, (int)wki->pkeytype); rc = -EIO; goto out; } /* check protected key type field */ switch (wki->pkeytype) { case 1: /* AES */ switch (wki->pkeysize) { case 16 + 32: /* AES 128 protected key */ if (protkeytype) *protkeytype = PKEY_KEYTYPE_AES_128; break; case 24 + 32: /* AES 192 protected key */ if (protkeytype) *protkeytype = PKEY_KEYTYPE_AES_192; break; case 32 + 32: /* AES 256 protected key */ if (protkeytype) *protkeytype = PKEY_KEYTYPE_AES_256; break; default: ZCRYPT_DBF_ERR("%s unknown/unsupported AES pkeysize %d\n", __func__, (int)wki->pkeysize); rc = -EIO; goto out; } break; case 3: /* EC-P */ case 4: /* EC-ED */ case 5: /* EC-BP */ if (protkeytype) *protkeytype = PKEY_KEYTYPE_ECC; break; case 2: /* TDES */ default: ZCRYPT_DBF_ERR("%s unknown/unsupported key type %d\n", __func__, (int)wki->pkeytype); rc = -EIO; goto out; } /* copy the translated protected key */ if (wki->pkeysize > *protkeylen) { ZCRYPT_DBF_ERR("%s wk info pkeysize %llu > protkeysize %u\n", __func__, wki->pkeysize, *protkeylen); rc = -EINVAL; goto out; } memcpy(protkey, wki->pkey, wki->pkeysize); *protkeylen = wki->pkeysize; out: kfree(wkbuf); return rc; } EXPORT_SYMBOL(ep11_kblob2protkey); int ep11_findcard2(u32 **apqns, u32 *nr_apqns, u16 cardnr, u16 domain, int minhwtype, int minapi, const u8 *wkvp) { struct zcrypt_device_status_ext *device_status; u32 *_apqns = NULL, _nr_apqns = 0; int i, card, dom, rc = -ENOMEM; struct ep11_domain_info edi; struct ep11_card_info eci; /* fetch status of all crypto cards */ device_status = kvcalloc(MAX_ZDEV_ENTRIES_EXT, sizeof(struct zcrypt_device_status_ext), GFP_KERNEL); if (!device_status) return -ENOMEM; zcrypt_device_status_mask_ext(device_status); /* allocate 1k space for up to 256 apqns */ _apqns = kmalloc_array(256, sizeof(u32), GFP_KERNEL); if (!_apqns) { kvfree(device_status); return -ENOMEM; } /* walk through all the crypto apqnss */ for (i = 0; i < MAX_ZDEV_ENTRIES_EXT; i++) { card = AP_QID_CARD(device_status[i].qid); dom = AP_QID_QUEUE(device_status[i].qid); /* check online state */ if (!device_status[i].online) continue; /* check for ep11 functions */ if (!(device_status[i].functions & 0x01)) continue; /* check cardnr */ if (cardnr != 0xFFFF && card != cardnr) continue; /* check domain */ if (domain != 0xFFFF && dom != domain) continue; /* check min hardware type */ if (minhwtype && device_status[i].hwtype < minhwtype) continue; /* check min api version if given */ if (minapi > 0) { if (ep11_get_card_info(card, &eci, 0)) continue; if (minapi > eci.API_ord_nr) continue; } /* check wkvp if given */ if (wkvp) { if (ep11_get_domain_info(card, dom, &edi)) continue; if (edi.cur_wk_state != '1') continue; if (memcmp(wkvp, edi.cur_wkvp, 16)) continue; } /* apqn passed all filtering criterons, add to the array */ if (_nr_apqns < 256) _apqns[_nr_apqns++] = (((u16)card) << 16) | ((u16)dom); } /* nothing found ? */ if (!_nr_apqns) { kfree(_apqns); rc = -ENODEV; } else { /* no re-allocation, simple return the _apqns array */ *apqns = _apqns; *nr_apqns = _nr_apqns; rc = 0; } kvfree(device_status); return rc; } EXPORT_SYMBOL(ep11_findcard2); void __exit zcrypt_ep11misc_exit(void) { card_cache_free(); }