/* * sm-cwa14890.c: Procedures related to Secure Messaging according to the CWA-14890 * * Copyright (C) 2010 Viktor Tarasov * OpenTrust * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef HAVE_CONFIG_H #include #endif #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #include #include #include "libopensc/opensc.h" #include "libopensc/sm.h" #include "libopensc/log.h" #include "libopensc/asn1.h" #include "libopensc/iasecc.h" #include "libopensc/iasecc-sdo.h" #include "sm-module.h" int sm_cwa_get_mac(struct sc_context *ctx, unsigned char *key, DES_cblock *icv, unsigned char *in, int in_len, DES_cblock *out, int force_padding) { unsigned char padding[8] = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; unsigned char *buf; LOG_FUNC_CALLED(ctx); sc_debug(ctx, SC_LOG_DEBUG_SM, "sm_cwa_get_mac() data length %i", in_len); buf = malloc(in_len + 8); if (!buf) LOG_FUNC_RETURN(ctx, SC_ERROR_OUT_OF_MEMORY); sc_debug(ctx, SC_LOG_DEBUG_SM, "sm_cwa_get_mac() in_data(%i) %s", in_len, sc_dump_hex(in, in_len)); memcpy(buf, in, in_len); memcpy(buf + in_len, padding, 8); if (force_padding) in_len = ((in_len + 8) / 8) * 8; else in_len = ((in_len + 7) / 8) * 8; sc_debug(ctx, SC_LOG_DEBUG_SM, "sm_cwa_get_mac() data to MAC(%i) %s", in_len, sc_dump_hex(buf, in_len)); sc_debug(ctx, SC_LOG_DEBUG_SM, "sm_cwa_get_mac() ICV %s", sc_dump_hex((unsigned char *)icv, 8)); DES_cbc_cksum_3des_emv96(buf, out, in_len, key, icv); free(buf); LOG_FUNC_RETURN(ctx, SC_SUCCESS); } static int sm_cwa_encode_external_auth_data(struct sc_context *ctx, struct sm_cwa_session *session_data, unsigned char *out, size_t out_len) { if (out_len < 16) return SC_ERROR_BUFFER_TOO_SMALL; sc_debug(ctx, SC_LOG_DEBUG_SM, "IFD.RND %s", sc_dump_hex(session_data->ifd.rnd, 8)); sc_debug(ctx, SC_LOG_DEBUG_SM, "IFD.SN %s", sc_dump_hex(session_data->ifd.sn, 8)); memcpy(out + 0, session_data->icc.rnd, 8); memcpy(out + 8, session_data->icc.sn, 8); return 16; } int sm_cwa_encode_mutual_auth_data(struct sc_context *ctx, struct sm_cwa_session *session_data, unsigned char *out, size_t out_len) { if (out_len < 64) return SC_ERROR_BUFFER_TOO_SMALL; sc_debug(ctx, SC_LOG_DEBUG_SM, "IFD.RND %s", sc_dump_hex(session_data->ifd.rnd, 8)); sc_debug(ctx, SC_LOG_DEBUG_SM, "IFD.SN %s", sc_dump_hex(session_data->ifd.sn, 8)); sc_debug(ctx, SC_LOG_DEBUG_SM, "IFD.K %s", sc_dump_hex(session_data->ifd.k, 32)); sc_debug(ctx, SC_LOG_DEBUG_SM, "ICC.RND %s", sc_dump_hex(session_data->icc.rnd, 8)); sc_debug(ctx, SC_LOG_DEBUG_SM, "ICC.SN %s", sc_dump_hex(session_data->icc.sn, 8)); memcpy(out + 0, session_data->ifd.rnd, 8); memcpy(out + 8, session_data->ifd.sn, 8); memcpy(out + 16, session_data->icc.rnd, 8); memcpy(out + 24, session_data->icc.sn, 8); memcpy(out + 32, session_data->ifd.k, 32); return 64; } int sm_cwa_decode_authentication_data(struct sc_context *ctx, struct sm_cwa_keyset *keyset, struct sm_cwa_session *session_data, unsigned char *auth_data) { DES_cblock icv = {0, 0, 0, 0, 0, 0, 0, 0}; DES_cblock cblock; unsigned char *decrypted = NULL; size_t decrypted_len; int rv; LOG_FUNC_CALLED(ctx); memset(icv, 0, sizeof(icv)); rv = sm_cwa_get_mac(ctx, keyset->mac, &icv, session_data->mdata, 0x40, &cblock, 1); LOG_TEST_RET(ctx, rv, "Decode authentication data: sm_ecc_get_mac failed"); sc_debug(ctx, SC_LOG_DEBUG_SM, "MAC:%s", sc_dump_hex(cblock, sizeof(cblock))); if(memcmp(session_data->mdata + 0x40, cblock, 8)) LOG_FUNC_RETURN(ctx, SC_ERROR_SM_AUTHENTICATION_FAILED); rv = sm_decrypt_des_cbc3(ctx, keyset->enc, session_data->mdata, session_data->mdata_len, &decrypted, &decrypted_len); LOG_TEST_RET(ctx, rv, "sm_ecc_decode_auth_data() DES CBC3 decrypt error"); sc_debug(ctx, SC_LOG_DEBUG_SM, "sm_ecc_decode_auth_data() decrypted(%"SC_FORMAT_LEN_SIZE_T"u) %s", decrypted_len, sc_dump_hex(decrypted, decrypted_len)); if (memcmp(decrypted, session_data->icc.rnd, 8)) { free(decrypted); LOG_FUNC_RETURN(ctx, SC_ERROR_UNKNOWN_DATA_RECEIVED); } if (memcmp(decrypted + 8, session_data->icc.sn, 8)) { free(decrypted); LOG_FUNC_RETURN(ctx, SC_ERROR_UNKNOWN_DATA_RECEIVED); } if (memcmp(decrypted + 16, session_data->ifd.rnd, 8)) { free(decrypted); LOG_FUNC_RETURN(ctx, SC_ERROR_UNKNOWN_DATA_RECEIVED); } if (memcmp(decrypted + 24, session_data->ifd.sn, 8)) { free(decrypted); LOG_FUNC_RETURN(ctx, SC_ERROR_UNKNOWN_DATA_RECEIVED); } memcpy(session_data->icc.k, decrypted + 32, 32); free(decrypted); LOG_FUNC_RETURN(ctx, SC_SUCCESS); } int sm_cwa_init_session_keys(struct sc_context *ctx, struct sm_cwa_session *session_data, unsigned char mechanism) { unsigned char xored[36]; unsigned char buff[SHA256_DIGEST_LENGTH]; int ii; memset(xored, 0, sizeof(xored)); for (ii=0; ii<32; ii++) xored[ii] = session_data->ifd.k[ii] ^ session_data->icc.k[ii]; sc_debug(ctx, SC_LOG_DEBUG_SM, "K_IFD %s", sc_dump_hex(session_data->ifd.k, 32)); sc_debug(ctx, SC_LOG_DEBUG_SM, "K_ICC %s", sc_dump_hex(session_data->icc.k, 32)); if (mechanism == IASECC_ALGORITHM_SYMMETRIC_SHA1) { xored[35] = 0x01; sc_debug(ctx, SC_LOG_DEBUG_SM, "XOR for SkEnc %s", sc_dump_hex(xored, 36)); SHA1(xored, 36, buff); memcpy(&session_data->session_enc[0], buff, sizeof(session_data->session_enc)); xored[35] = 0x02; sc_debug(ctx, SC_LOG_DEBUG_SM, "XOR for SkMac %s", sc_dump_hex(xored, 36)); SHA1(xored, 36, buff); memcpy(&session_data->session_mac[0], buff, sizeof(session_data->session_mac)); } else if (mechanism == IASECC_ALGORITHM_SYMMETRIC_SHA256) { xored[35] = 0x01; SHA256(xored, 36, buff); memcpy(&session_data->session_enc[0], buff, sizeof(session_data->session_enc)); xored[35] = 0x02; SHA256(xored, 36, buff); memcpy(&session_data->session_mac[0], buff, sizeof(session_data->session_mac)); } else { return SC_ERROR_INVALID_ARGUMENTS; } memcpy(session_data->ssc + 0, session_data->icc.rnd + 4, 4); memcpy(session_data->ssc + 4, session_data->ifd.rnd + 4, 4); LOG_FUNC_RETURN(ctx, SC_SUCCESS); } int sm_cwa_initialize(struct sc_context *ctx, struct sm_info *sm_info, struct sc_remote_data *rdata) { struct sm_cwa_session *cwa_session = &sm_info->session.cwa; struct sm_cwa_keyset *cwa_keyset = &sm_info->session.cwa.cwa_keyset; struct sc_serial_number sn = sm_info->serialnr; size_t icc_sn_len = sizeof(cwa_session->icc.sn); struct sc_remote_apdu *new_rapdu = NULL; struct sc_apdu *apdu = NULL; unsigned char buf[0x100], *encrypted = NULL; size_t encrypted_len; DES_cblock icv = {0, 0, 0, 0, 0, 0, 0, 0}, cblock; int rv, offs; LOG_FUNC_CALLED(ctx); sc_debug(ctx, SC_LOG_DEBUG_SM, "SM IAS/ECC initialize: serial %s", sc_dump_hex(sm_info->serialnr.value, sm_info->serialnr.len)); sc_debug(ctx, SC_LOG_DEBUG_SM, "SM IAS/ECC initialize: card challenge %s", sc_dump_hex(cwa_session->card_challenge, 8)); sc_debug(ctx, SC_LOG_DEBUG_SM, "SM IAS/ECC initialize: current_df_path %s", sc_print_path(&sm_info->current_path_df)); sc_debug(ctx, SC_LOG_DEBUG_SM, "SM IAS/ECC initialize: CRT_AT reference 0x%X", cwa_session->params.crt_at.refs[0]); if (!rdata || !rdata->alloc) LOG_FUNC_RETURN(ctx, SC_ERROR_INVALID_ARGUMENTS); rv = rdata->alloc(rdata, &new_rapdu); LOG_TEST_RET(ctx, rv, "SM GP decode card answer: cannot allocate remote APDU"); apdu = &new_rapdu->apdu; memcpy(&cwa_session->icc.rnd[0], cwa_session->card_challenge, 8); if (sn.len > icc_sn_len) memcpy(&cwa_session->icc.sn[0], &sn.value[sn.len - icc_sn_len], icc_sn_len); else memcpy(&cwa_session->icc.sn[icc_sn_len - sn.len], &sn.value[0], sn.len); if (sm_info->cmd == SM_CMD_EXTERNAL_AUTH) { offs = sm_cwa_encode_external_auth_data(ctx, cwa_session, buf, sizeof(buf)); if (offs != 0x10) LOG_FUNC_RETURN(ctx, offs); } else { offs = sm_cwa_encode_mutual_auth_data(ctx, cwa_session, buf, sizeof(buf)); if (offs != 0x40) LOG_FUNC_RETURN(ctx, offs); } sc_debug(ctx, SC_LOG_DEBUG_SM, "S(%i) %s", offs, sc_dump_hex(buf, offs)); rv = sm_encrypt_des_cbc3(ctx, cwa_keyset->enc, buf, offs, &encrypted, &encrypted_len, 1); LOG_TEST_RET(ctx, rv, "_encrypt_des_cbc3() failed"); sc_debug(ctx, SC_LOG_DEBUG_SM, "ENCed(%"SC_FORMAT_LEN_SIZE_T"u) %s", encrypted_len, sc_dump_hex(encrypted, encrypted_len)); memcpy(buf, encrypted, encrypted_len); offs = encrypted_len; rv = sm_cwa_get_mac(ctx, cwa_keyset->mac, &icv, buf, offs, &cblock, 1); LOG_TEST_GOTO_ERR(ctx, rv, "sm_ecc_get_mac() failed"); sc_debug(ctx, SC_LOG_DEBUG_SM, "MACed(%"SC_FORMAT_LEN_SIZE_T"u) %s", sizeof(cblock), sc_dump_hex(cblock, sizeof(cblock))); apdu->cse = SC_APDU_CASE_4_SHORT; apdu->cla = 0x00; apdu->ins = 0x82; apdu->p1 = 0x00; apdu->p2 = 0x00; apdu->lc = encrypted_len + sizeof(cblock); apdu->le = encrypted_len + sizeof(cblock); apdu->datalen = encrypted_len + sizeof(cblock); memcpy(new_rapdu->sbuf, encrypted, encrypted_len); memcpy(new_rapdu->sbuf + encrypted_len, cblock, sizeof(cblock)); rv = SC_SUCCESS; err: free(encrypted); LOG_FUNC_RETURN(ctx, rv); } int sm_cwa_securize_apdu(struct sc_context *ctx, struct sm_info *sm_info, struct sc_remote_apdu *rapdu) { struct sm_cwa_session *session_data = &sm_info->session.cwa; struct sc_apdu *apdu = &rapdu->apdu; unsigned char sbuf[0x400]; DES_cblock cblock, icv; unsigned char *encrypted = NULL, edfb_data[0x200], mac_data[0x200]; size_t encrypted_len, edfb_len = 0, mac_len = 0, offs; int rv; LOG_FUNC_CALLED(ctx); sc_debug(ctx, SC_LOG_DEBUG_SM, "securize APDU (cla:%X,ins:%X,p1:%X,p2:%X,data(%"SC_FORMAT_LEN_SIZE_T"u):%p)", apdu->cla, apdu->ins, apdu->p1, apdu->p2, apdu->datalen, apdu->data); sm_incr_ssc(session_data->ssc, sizeof(session_data->ssc)); rv = sm_encrypt_des_cbc3(ctx, session_data->session_enc, apdu->data, apdu->datalen, &encrypted, &encrypted_len, 0); LOG_TEST_RET(ctx, rv, "securize APDU: DES CBC3 encryption failed"); sc_debug(ctx, SC_LOG_DEBUG_SM, "encrypted data (len:%"SC_FORMAT_LEN_SIZE_T"u, %s)", encrypted_len, sc_dump_hex(encrypted, encrypted_len)); offs = 0; if (apdu->ins & 0x01) { edfb_data[offs++] = IASECC_SM_DO_TAG_TCG_ODD_INS; if (encrypted_len + 1 > 0x7F) edfb_data[offs++] = 0x81; edfb_data[offs++] = encrypted_len; } else { edfb_data[offs++] = IASECC_SM_DO_TAG_TCG_EVEN_INS; if (encrypted_len + 1 > 0x7F) edfb_data[offs++] = 0x81; edfb_data[offs++] = encrypted_len + 1; edfb_data[offs++] = 0x01; } memcpy(edfb_data + offs, encrypted, encrypted_len); offs += encrypted_len; edfb_len = offs; sc_debug(ctx, SC_LOG_DEBUG_SM, "securize APDU: EDFB(len:%"SC_FORMAT_LEN_SIZE_T"u,%s)", edfb_len, sc_dump_hex(edfb_data, edfb_len)); free(encrypted); encrypted = NULL; offs = 0; memcpy(mac_data + offs, session_data->ssc, 8); offs += 8; mac_data[offs++] = apdu->cla | 0x0C; mac_data[offs++] = apdu->ins; mac_data[offs++] = apdu->p1; mac_data[offs++] = apdu->p2; mac_data[offs++] = 0x80; mac_data[offs++] = 0x00; mac_data[offs++] = 0x00; mac_data[offs++] = 0x00; memcpy(mac_data + offs, edfb_data, edfb_len); offs += edfb_len; /* if (apdu->le) { */ mac_data[offs++] = IASECC_SM_DO_TAG_TLE; mac_data[offs++] = 1; mac_data[offs++] = apdu->le; /* } */ mac_len = offs; sc_debug(ctx, SC_LOG_DEBUG_SM, "securize APDU: MAC data(len:%"SC_FORMAT_LEN_SIZE_T"u,%s)", mac_len, sc_dump_hex(mac_data, mac_len)); memset(icv, 0, sizeof(icv)); rv = sm_cwa_get_mac(ctx, session_data->session_mac, &icv, mac_data, mac_len, &cblock, 0); LOG_TEST_RET(ctx, rv, "securize APDU: MAC calculation error"); sc_debug(ctx, SC_LOG_DEBUG_SM, "securize APDU: MAC:%s", sc_dump_hex(cblock, sizeof(cblock))); offs = 0; if (edfb_len) { memcpy(sbuf + offs, edfb_data, edfb_len); offs += edfb_len; } /* if (apdu->le) { */ sbuf[offs++] = IASECC_SM_DO_TAG_TLE; sbuf[offs++] = 1; sbuf[offs++] = apdu->le; /* } */ sbuf[offs++] = IASECC_SM_DO_TAG_TCC; sbuf[offs++] = 8; memcpy(sbuf + offs, cblock, 8); offs += 8; sc_debug(ctx, SC_LOG_DEBUG_SM, "securize APDU: SM data(len:%"SC_FORMAT_LEN_SIZE_T"u,%s)", offs, sc_dump_hex(sbuf, offs)); if (offs > sizeof(rapdu->sbuf)) LOG_TEST_RET(ctx, SC_ERROR_BUFFER_TOO_SMALL, "securize APDU: buffer too small for encrypted data"); apdu->cse = SC_APDU_CASE_4_SHORT; apdu->cla |= 0x0C; apdu->lc = offs; apdu->datalen = offs; memcpy((unsigned char *)apdu->data, sbuf, offs); sm_incr_ssc(session_data->ssc, sizeof(session_data->ssc)); LOG_FUNC_RETURN(ctx, SC_SUCCESS); }