diff options
author | Robert Relyea <rrelyea@redhat.com> | 2010-11-28 16:36:38 +0200 |
---|---|---|
committer | Anthony Liguori <aliguori@us.ibm.com> | 2011-04-01 19:07:48 -0500 |
commit | 111a38b018c86e6651750c5a548ad534f80b5bb5 (patch) | |
tree | e9397c478ead6463da198dd141c61cfd7cba189e /libcacard/cac.c | |
parent | edbb21363fbfe40e050f583df921484cbc31c79d (diff) |
libcacard: initial commit
libcacard emulates a Common Access Card (CAC) which is a standard
for smartcards. It is used by the emulated ccid card introduced in
a following patch. Docs are available in docs/libcacard.txt
Signed-off-by: Alon Levy <alevy@redhat.com>
---
changes from v24->v25:
* Fix out of tree builds.
* Fix build with linux-user targets.
changes from v23->v24: (Jes Sorensen review 2)
* Makefile.target: use obj-$(CONFIG_*) +=
* remove unrequired includes, include qemu-common before qemu-thread
* required adding #define NO_NSPR_10_SUPPORT (harmless)
changes from v22->v23:
* configure fixes: (reported by Stefan Hajnoczi)
* test a = b, not a == b (second isn't portable)
* quote $source_path in case it contains spaces
- this doesn't really help since there are many other places
that need similar fixes, not introduced by this patch.
changes from v21->v22:
* fix configure to not link libcacard if nss not found
(reported by Stefan Hajnoczi)
* fix vscclient linkage with simpletrace backend
(reported by Stefan Hajnoczi)
* card_7816.c: add missing break in ERROR_DATA_NOT_FOUND
(reported by William van de Velde)
changes from v20->v21: (Jes Sorensen review)
* use qemu infrastructure: qemu-thread, qemu-common (qemu_malloc
and qemu_free), error_report
* assert instead of ASSERT
* cosmetic fixes
* use strpbrk and isspace
* add --disable-nss --enable-nss here, instead of in the final patch.
* split vscclient, passthru and docs to following patches.
changes from v19->v20:
* checkpatch.pl
changes from v15->v16:
Build:
* don't erase self with distclean
* fix make clean after make distclean
* Makefile: make vscclient link quiet
Behavioral:
* vcard_emul_nss: load coolkey in more situations
* vscclient:
* use hton,ntoh
* send init on connect, only start vevent thread on response
* read payload after header check, before type switch
* remove Reconnect
* update for vscard_common changes, empty Flush implementation
Style/Whitespace:
* fix wrong variable usage
* remove unused variable
* use only C style comments
* add copyright header
* fix tabulation
Signed-off-by: Alon Levy <alevy@redhat.com>
libcacard: fix out of tree builds
Diffstat (limited to 'libcacard/cac.c')
-rw-r--r-- | libcacard/cac.c | 403 |
1 files changed, 403 insertions, 0 deletions
diff --git a/libcacard/cac.c b/libcacard/cac.c new file mode 100644 index 0000000000..f34f63ac78 --- /dev/null +++ b/libcacard/cac.c @@ -0,0 +1,403 @@ +/* + * implement the applets for the CAC card. + * + * This code is licensed under the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + */ + +#include "qemu-common.h" + +#include "cac.h" +#include "vcard.h" +#include "vcard_emul.h" +#include "card_7816.h" + +#define CAC_GET_PROPERTIES 0x56 +#define CAC_GET_ACR 0x4c +#define CAC_READ_BUFFER 0x52 +#define CAC_UPDATE_BUFFER 0x58 +#define CAC_SIGN_DECRYPT 0x42 +#define CAC_GET_CERTIFICATE 0x36 + +/* private data for PKI applets */ +typedef struct CACPKIAppletDataStruct { + unsigned char *cert; + int cert_len; + unsigned char *cert_buffer; + int cert_buffer_len; + unsigned char *sign_buffer; + int sign_buffer_len; + VCardKey *key; +} CACPKIAppletData; + +/* + * CAC applet private data + */ +struct VCardAppletPrivateStruct { + union { + CACPKIAppletData pki_data; + void *reserved; + } u; +}; + +/* + * handle all the APDU's that are common to all CAC applets + */ +static VCardStatus +cac_common_process_apdu(VCard *card, VCardAPDU *apdu, VCardResponse **response) +{ + int ef; + + switch (apdu->a_ins) { + case VCARD7816_INS_SELECT_FILE: + if (apdu->a_p1 != 0x02) { + /* let the 7816 code handle applet switches */ + return VCARD_NEXT; + } + /* handle file id setting */ + if (apdu->a_Lc != 2) { + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_DATA_INVALID); + return VCARD_DONE; + } + /* CAC 1.0 only supports ef = 0 */ + ef = apdu->a_body[0] | (apdu->a_body[1] << 8); + if (ef != 0) { + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_FILE_NOT_FOUND); + return VCARD_DONE; + } + *response = vcard_make_response(VCARD7816_STATUS_SUCCESS); + return VCARD_DONE; + case VCARD7816_INS_GET_RESPONSE: + case VCARD7816_INS_VERIFY: + /* let the 7816 code handle these */ + return VCARD_NEXT; + case CAC_GET_PROPERTIES: + case CAC_GET_ACR: + /* skip these for now, this will probably be needed */ + *response = vcard_make_response(VCARD7816_STATUS_ERROR_P1_P2_INCORRECT); + return VCARD_DONE; + } + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_COMMAND_NOT_SUPPORTED); + return VCARD_DONE; +} + +/* + * reset the inter call state between applet selects + */ +static VCardStatus +cac_applet_pki_reset(VCard *card, int channel) +{ + VCardAppletPrivate *applet_private = NULL; + CACPKIAppletData *pki_applet = NULL; + applet_private = vcard_get_current_applet_private(card, channel); + assert(applet_private); + pki_applet = &(applet_private->u.pki_data); + + pki_applet->cert_buffer = NULL; + if (pki_applet->sign_buffer) { + qemu_free(pki_applet->sign_buffer); + pki_applet->sign_buffer = NULL; + } + pki_applet->cert_buffer_len = 0; + pki_applet->sign_buffer_len = 0; + return VCARD_DONE; +} + +static VCardStatus +cac_applet_pki_process_apdu(VCard *card, VCardAPDU *apdu, + VCardResponse **response) +{ + CACPKIAppletData *pki_applet = NULL; + VCardAppletPrivate *applet_private = NULL; + int size, next; + unsigned char *sign_buffer; + vcard_7816_status_t status; + + applet_private = vcard_get_current_applet_private(card, apdu->a_channel); + assert(applet_private); + pki_applet = &(applet_private->u.pki_data); + + switch (apdu->a_ins) { + case CAC_UPDATE_BUFFER: + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_CONDITION_NOT_SATISFIED); + return VCARD_DONE; + case CAC_GET_CERTIFICATE: + if ((apdu->a_p2 != 0) || (apdu->a_p1 != 0)) { + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_P1_P2_INCORRECT); + break; + } + assert(pki_applet->cert != NULL); + size = apdu->a_Le; + if (pki_applet->cert_buffer == NULL) { + pki_applet->cert_buffer = pki_applet->cert; + pki_applet->cert_buffer_len = pki_applet->cert_len; + } + size = MIN(size, pki_applet->cert_buffer_len); + next = MIN(255, pki_applet->cert_buffer_len - size); + *response = vcard_response_new_bytes( + card, pki_applet->cert_buffer, size, + apdu->a_Le, next ? + VCARD7816_SW1_WARNING_CHANGE : + VCARD7816_SW1_SUCCESS, + next); + pki_applet->cert_buffer += size; + pki_applet->cert_buffer_len -= size; + if ((*response == NULL) || (next == 0)) { + pki_applet->cert_buffer = NULL; + } + if (*response == NULL) { + *response = vcard_make_response( + VCARD7816_STATUS_EXC_ERROR_MEMORY_FAILURE); + } + return VCARD_DONE; + case CAC_SIGN_DECRYPT: + if (apdu->a_p2 != 0) { + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_P1_P2_INCORRECT); + break; + } + size = apdu->a_Lc; + + sign_buffer = realloc(pki_applet->sign_buffer, + pki_applet->sign_buffer_len+size); + if (sign_buffer == NULL) { + qemu_free(pki_applet->sign_buffer); + pki_applet->sign_buffer = NULL; + pki_applet->sign_buffer_len = 0; + *response = vcard_make_response( + VCARD7816_STATUS_EXC_ERROR_MEMORY_FAILURE); + return VCARD_DONE; + } + memcpy(sign_buffer+pki_applet->sign_buffer_len, apdu->a_body, size); + size += pki_applet->sign_buffer_len; + switch (apdu->a_p1) { + case 0x80: + /* p1 == 0x80 means we haven't yet sent the whole buffer, wait for + * the rest */ + pki_applet->sign_buffer = sign_buffer; + pki_applet->sign_buffer_len = size; + *response = vcard_make_response(VCARD7816_STATUS_SUCCESS); + return VCARD_DONE; + case 0x00: + /* we now have the whole buffer, do the operation, result will be + * in the sign_buffer */ + status = vcard_emul_rsa_op(card, pki_applet->key, + sign_buffer, size); + if (status != VCARD7816_STATUS_SUCCESS) { + *response = vcard_make_response(status); + break; + } + *response = vcard_response_new(card, sign_buffer, size, apdu->a_Le, + VCARD7816_STATUS_SUCCESS); + if (*response == NULL) { + *response = vcard_make_response( + VCARD7816_STATUS_EXC_ERROR_MEMORY_FAILURE); + } + break; + default: + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_P1_P2_INCORRECT); + break; + } + qemu_free(sign_buffer); + pki_applet->sign_buffer = NULL; + pki_applet->sign_buffer_len = 0; + return VCARD_DONE; + case CAC_READ_BUFFER: + /* new CAC call, go ahead and use the old version for now */ + /* TODO: implement */ + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_COMMAND_NOT_SUPPORTED); + return VCARD_DONE; + } + return cac_common_process_apdu(card, apdu, response); +} + + +static VCardStatus +cac_applet_id_process_apdu(VCard *card, VCardAPDU *apdu, + VCardResponse **response) +{ + switch (apdu->a_ins) { + case CAC_UPDATE_BUFFER: + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_CONDITION_NOT_SATISFIED); + return VCARD_DONE; + case CAC_READ_BUFFER: + /* new CAC call, go ahead and use the old version for now */ + /* TODO: implement */ + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_COMMAND_NOT_SUPPORTED); + return VCARD_DONE; + } + return cac_common_process_apdu(card, apdu, response); +} + + +/* + * TODO: if we ever want to support general CAC middleware, we will need to + * implement the various containers. + */ +static VCardStatus +cac_applet_container_process_apdu(VCard *card, VCardAPDU *apdu, + VCardResponse **response) +{ + switch (apdu->a_ins) { + case CAC_READ_BUFFER: + case CAC_UPDATE_BUFFER: + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_COMMAND_NOT_SUPPORTED); + return VCARD_DONE; + default: + break; + } + return cac_common_process_apdu(card, apdu, response); +} + +/* + * utilities for creating and destroying the private applet data + */ +static void +cac_delete_pki_applet_private(VCardAppletPrivate *applet_private) +{ + CACPKIAppletData *pki_applet_data = NULL; + if (pki_applet_data == NULL) { + return; + } + pki_applet_data = &(applet_private->u.pki_data); + if (pki_applet_data->cert != NULL) { + qemu_free(pki_applet_data->cert); + } + if (pki_applet_data->sign_buffer != NULL) { + qemu_free(pki_applet_data->sign_buffer); + } + if (pki_applet_data->key != NULL) { + vcard_emul_delete_key(pki_applet_data->key); + } + qemu_free(applet_private); +} + +static VCardAppletPrivate * +cac_new_pki_applet_private(const unsigned char *cert, + int cert_len, VCardKey *key) +{ + CACPKIAppletData *pki_applet_data = NULL; + VCardAppletPrivate *applet_private = NULL; + applet_private = (VCardAppletPrivate *)qemu_malloc(sizeof(VCardAppletPrivate)); + + pki_applet_data = &(applet_private->u.pki_data); + pki_applet_data->cert_buffer = NULL; + pki_applet_data->cert_buffer_len = 0; + pki_applet_data->sign_buffer = NULL; + pki_applet_data->sign_buffer_len = 0; + pki_applet_data->key = NULL; + pki_applet_data->cert = (unsigned char *)qemu_malloc(cert_len+1); + /* + * if we want to support compression, then we simply change the 0 to a 1 + * and compress the cert data with libz + */ + pki_applet_data->cert[0] = 0; /* not compressed */ + memcpy(&pki_applet_data->cert[1], cert, cert_len); + pki_applet_data->cert_len = cert_len+1; + + pki_applet_data->key = key; + return applet_private; +} + + +/* + * create a new cac applet which links to a given cert + */ +static VCardApplet * +cac_new_pki_applet(int i, const unsigned char *cert, + int cert_len, VCardKey *key) +{ + VCardAppletPrivate *applet_private = NULL; + VCardApplet *applet = NULL; + unsigned char pki_aid[] = { 0xa0, 0x00, 0x00, 0x00, 0x79, 0x01, 0x00 }; + int pki_aid_len = sizeof(pki_aid); + + pki_aid[pki_aid_len-1] = i; + + applet_private = cac_new_pki_applet_private(cert, cert_len, key); + if (applet_private == NULL) { + goto failure; + } + applet = vcard_new_applet(cac_applet_pki_process_apdu, cac_applet_pki_reset, + pki_aid, pki_aid_len); + if (applet == NULL) { + goto failure; + } + vcard_set_applet_private(applet, applet_private, + cac_delete_pki_applet_private); + applet_private = NULL; + + return applet; + +failure: + if (applet_private != NULL) { + cac_delete_pki_applet_private(applet_private); + } + return NULL; +} + + +static unsigned char cac_default_container_aid[] = { + 0xa0, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00 }; +static unsigned char cac_id_aid[] = { + 0xa0, 0x00, 0x00, 0x00, 0x79, 0x03, 0x00 }; +/* + * Initialize the cac card. This is the only public function in this file. All + * the rest are connected through function pointers. + */ +VCardStatus +cac_card_init(VReader *reader, VCard *card, + const char *params, + unsigned char * const *cert, + int cert_len[], + VCardKey *key[] /* adopt the keys*/, + int cert_count) +{ + int i; + VCardApplet *applet; + + /* CAC Cards are VM Cards */ + vcard_set_type(card, VCARD_VM); + + /* create one PKI applet for each cert */ + for (i = 0; i < cert_count; i++) { + applet = cac_new_pki_applet(i, cert[i], cert_len[i], key[i]); + if (applet == NULL) { + goto failure; + } + vcard_add_applet(card, applet); + } + + /* create a default blank container applet */ + applet = vcard_new_applet(cac_applet_container_process_apdu, + NULL, cac_default_container_aid, + sizeof(cac_default_container_aid)); + if (applet == NULL) { + goto failure; + } + vcard_add_applet(card, applet); + + /* create a default blank container applet */ + applet = vcard_new_applet(cac_applet_id_process_apdu, + NULL, cac_id_aid, + sizeof(cac_id_aid)); + if (applet == NULL) { + goto failure; + } + vcard_add_applet(card, applet); + return VCARD_DONE; + +failure: + return VCARD_FAIL; +} + |