From 2a1afd8fd2bbd4f1f1e2438f53f372278074b185 Mon Sep 17 00:00:00 2001 From: frankmorgner Date: Mon, 29 Mar 2010 19:53:11 +0000 Subject: [PATCH] - Added an untested SM implementation, currently only with encryption - Added utils from OpenPACE git-svn-id: https://vsmartcard.svn.sourceforge.net/svnroot/vsmartcard@51 96b47cad-a561-4643-ad3b-153ac7d7599c --- ccid/Makefile | 4 +- ccid/sm.c | 293 ++++++++++++++++++++++++++++++++++++++++++++++++++ ccid/sm.h | 25 +++++ ccid/utils.c | 279 +++++++++++++++++++++++++++++++++++++++++++++++ ccid/utils.h | 119 ++++++++++++++++++++ 5 files changed, 718 insertions(+), 2 deletions(-) create mode 100644 ccid/sm.c create mode 100644 ccid/sm.h create mode 100644 ccid/utils.c create mode 100644 ccid/utils.h diff --git a/ccid/Makefile b/ccid/Makefile index 192eaa9..9cca3c7 100644 --- a/ccid/Makefile +++ b/ccid/Makefile @@ -25,10 +25,10 @@ TARGETS = ccid all: $(TARGETS) -ccid: ccid.h ccid.c pace.h pace.c apdu.h apdu.c usbstring.c usbstring.h usb.c +ccid: ccid.h ccid.c pace.h pace.c sm.c sm.h utils.h utils.c apdu.h apdu.c usbstring.c usbstring.h usb.c $(CC) $(LIBPCSCLITE_CFLAGS) $(OPENSC_CFLAGS) $(OPENSSL_CFLAGS) \ $(PTHREAD_CFLAGS) $(CFLAGS) $(EXTRA_CFLAGS) \ - usbstring.c ccid.c pace.c apdu.c usb.c -o $@ + usbstring.c ccid.c pace.c sm.c utils.c apdu.c usb.c -o $@ install: $(TARGETS) installdirs diff --git a/ccid/sm.c b/ccid/sm.c new file mode 100644 index 0000000..e9d32b0 --- /dev/null +++ b/ccid/sm.c @@ -0,0 +1,293 @@ +#include "sm.h" +#include "utils.h" +#include +#include +#include + +static const struct sc_asn1_entry c_sm_capdu[] = { + { "Padding-content indicator followed by cryptogram", + SC_ASN1_INTEGER , 0x87, SC_ASN1_OPTIONAL|SC_ASN1_UNSIGNED, NULL, NULL }, + { "Protected Le", + SC_ASN1_INTEGER , 0x97, SC_ASN1_OPTIONAL|SC_ASN1_UNSIGNED, NULL, NULL }, + { "Cryptographic Checksum", + SC_ASN1_OCTET_STRING, 0x8E, 0, NULL, NULL }, + { NULL , 0 , 0 , 0 , NULL , NULL } +}; + +static const struct sc_asn1_entry c_sm_rapdu[] = { + { "Padding-content indicator followed by cryptogram" , + SC_ASN1_INTEGER , 0x87, SC_ASN1_OPTIONAL|SC_ASN1_UNSIGNED, NULL, NULL }, + { "Processing Status", + SC_ASN1_INTEGER , 0x99, SC_ASN1_OPTIONAL|SC_ASN1_UNSIGNED, NULL, NULL }, + { "Cryptographic Checksum", + SC_ASN1_OCTET_STRING, 0x8E, 0, NULL, NULL }, + { NULL, 0, 0, 0, NULL, NULL } +}; + +BUF_MEM *add_padding(const struct sm_ctx *ctx, const char *data, size_t datalen) +{ + BUF_MEM *tmp = BUF_MEM_create_init(data, datalen); + BUF_MEM *padded = NULL; + + switch (ctx->padding_indicator) { + case SM_NO_PADDING: + padded = tmp; + tmp = NULL; + break; + case SM_ISO_PADDING: + padded = add_iso_pad(tmp, EVP_CIPHER_block_size(ctx->cipher)); + if (tmp) { + OPENSSL_cleanse(tmp->data, tmp->max); + BUF_MEM_free(tmp); + } + break; + default: + return NULL; + } + + return padded; +} + +u8 * format_le(size_t le_len, size_t le, struct sc_asn1_entry *le_entry) +{ + u8 * lebuf = malloc(le_len); + if (lebuf) { + switch (le_len) { + case 1: + lebuf[0] = le; + break; + case 2: + lebuf[0] = htons(le) >> 8; + lebuf[1] = htons(le) & 0xff; + break; + case 3: + lebuf[0] = 0x00; + lebuf[1] = htons(le) >> 8; + lebuf[2] = htons(le) & 0xff; + break; + default: + free(lebuf); + return NULL; + } + + sc_format_asn1_entry(le_entry, lebuf, &le_len, SC_ASN1_PRESENT); + } + + return lebuf; +} + +BUF_MEM * prefix_buf(u8 prefix, BUF_MEM *buf) +{ + if (!buf) return NULL; + BUF_MEM *cat = BUF_MEM_create(buf->length + 1); + if (cat) { + cat->data[0] = prefix; + memcpy(cat->data + 1, buf->data, buf->length); + cat->length = buf->length + 1; + } + return cat; +} + +BUF_MEM * format_data(const struct sm_ctx *ctx, const u8 *data, size_t datalen, + struct sc_asn1_entry *formatted_encrypted_data_entry) +{ + BUF_MEM *pad_data = add_padding(ctx, (char *) data, datalen); + BUF_MEM *enc_data = cipher(ctx->cipher_ctx, ctx->cipher, + ctx->cipher_engine, ctx->key_enc, ctx->iv, 1, pad_data, 1); + + if (pad_data) { + OPENSSL_cleanse(pad_data->data, pad_data->max); + BUF_MEM_free(pad_data); + } + + BUF_MEM *indicator_encdata = prefix_buf(ctx->padding_indicator, enc_data); + + if (enc_data) { + BUF_MEM_free(enc_data); + } + + if (indicator_encdata) + sc_format_asn1_entry(formatted_encrypted_data_entry, + indicator_encdata->data, &(indicator_encdata->length), + SC_ASN1_PRESENT); + return indicator_encdata; +} + +int sm_encrypt(const struct sm_ctx *ctx, sc_card_t *card, sc_apdu_t *apdu, + sc_apdu_t *sm_apdu) +{ + struct sc_asn1_entry sm_capdu[4]; + u8 *le = NULL; + size_t oldlen; + BUF_MEM *formatted_data = NULL, *sm_data = NULL, + *mac_data = NULL, *mac = NULL, *tmp = NULL; + char head[4]; + int r; + + if (!apdu || !ctx || !card || !card->slot || !sm_apdu) { + r = SC_ERROR_INVALID_ARGUMENTS; + goto err; + } + + + sc_copy_asn1_entry(c_sm_capdu, sm_capdu); + + head[0] = apdu->cla; + head[1] = apdu->ins; + head[2] = apdu->p1; + head[3] = apdu->p2; + mac_data = add_padding(ctx, head, sizeof head); + if (!mac_data) { + r = SC_ERROR_WRONG_PADDING; + goto err; + } + + + /* get asn1 encoding of le and padding indicator depending on the case of + * the unsecure command */ + switch (apdu->cse) { + case SC_APDU_CASE_1: + break; + case SC_APDU_CASE_2_SHORT: + le = format_le(1, apdu->le, sm_capdu + 1); + if (!le) { + r = SC_ERROR_WRONG_LENGTH; + goto err; + } + break; + case SC_APDU_CASE_2_EXT: + if (card->slot->active_protocol == SC_PROTO_T0) { + /* T0 extended APDUs look just like short APDUs */ + le = format_le(1, apdu->le, sm_capdu + 1); + if (!le) { + r = SC_ERROR_WRONG_LENGTH; + goto err; + } + } else { + /* in case of T1 always use 3 bytes for length */ + le = format_le(3, apdu->le, sm_capdu + 1); + if (!le) { + r = SC_ERROR_WRONG_LENGTH; + goto err; + } + } + break; + case SC_APDU_CASE_3_SHORT: + case SC_APDU_CASE_3_EXT: + formatted_data = format_data(ctx, apdu->data, apdu->datalen, + sm_capdu + 0); + if (!formatted_data) { + r = SC_ERROR_OUT_OF_MEMORY; + goto err; + } + break; + case SC_APDU_CASE_4_SHORT: + /* in case of T0 no Le byte is added */ + if (card->slot->active_protocol != SC_PROTO_T0) { + le = format_le(1, apdu->le, sm_capdu + 1); + if (!le) { + r = SC_ERROR_WRONG_LENGTH; + goto err; + } + } + + formatted_data = format_data(ctx, apdu->data, apdu->datalen, + sm_capdu + 0); + if (!formatted_data) { + r = SC_ERROR_OUT_OF_MEMORY; + goto err; + } + break; + case SC_APDU_CASE_4_EXT: + if (card->slot->active_protocol == SC_PROTO_T0) { + /* again a T0 extended case 4 APDU looks just + * like a short APDU, the additional data is + * transferred using ENVELOPE and GET RESPONSE */ + } else { + /* only 2 bytes are use to specify the length of the + * expected data */ + le = format_le(2, apdu->le, sm_capdu + 1); + if (!le) { + r = SC_ERROR_WRONG_LENGTH; + goto err; + } + } + + formatted_data = format_data(ctx, apdu->data, apdu->datalen, + sm_capdu + 0); + if (!formatted_data) { + r = SC_ERROR_OUT_OF_MEMORY; + goto err; + } + break; + default: + r = SC_ERROR_INVALID_DATA; + goto err; + } + + + tmp = BUF_MEM_new(); + if (!tmp) { + r = SC_ERROR_OUT_OF_MEMORY; + goto err; + } + r = sc_asn1_encode(card->ctx, sm_capdu, (u8 **) &tmp->data, &tmp->length); + if (r < 0) { + goto err; + } + oldlen = mac_data->length; + BUF_MEM_grow(mac_data, oldlen + tmp->length); + memcpy(mac_data->data + oldlen, tmp->data, tmp->length); + BUF_MEM_free(tmp); + tmp = add_padding(ctx, mac_data->data, mac_data->length); + BUF_MEM_free(mac_data); + mac_data = hash(ctx->md, ctx->md_ctx, ctx->md_engine, tmp); + mac = cipher(ctx->cipher_ctx, ctx->cipher, ctx->cipher_engine, + ctx->key_mac, ctx->iv, 1, mac_data, 1); + if (!mac) { + r = SC_ERROR_INTERNAL; + goto err; + } + sc_format_asn1_entry(sm_capdu + 2, mac->data, &(mac->length), + SC_ASN1_PRESENT); + + + /* format SM apdu */ + BUF_MEM_free(sm_data); + sm_data = BUF_MEM_new(); + if (!sm_data) { + r = SC_ERROR_OUT_OF_MEMORY; + goto err; + } + r = sc_asn1_encode(card->ctx, sm_capdu, (u8 **) &sm_data->data, &sm_data->length); + if (r < 0) + goto err; + memset(sm_apdu, 0, sizeof *sm_apdu); + sm_apdu->cla = apdu->cla; + sm_apdu->ins = apdu->ins; + sm_apdu->p1 = apdu->p1; + sm_apdu->p2 = apdu->p2; + sm_apdu->data = (u8 *) sm_data->data; + sm_apdu->lc = sm_data->length; + sm_apdu->le = 0; + sm_apdu->cse = SC_APDU_CASE_4; + +err: + if (formatted_data) { + BUF_MEM_free(formatted_data); + } + if (tmp) { + BUF_MEM_free(tmp); + } + if (mac_data) { + BUF_MEM_free(mac_data); + } + if (mac) { + BUF_MEM_free(mac); + } + if (le) { + free(le); + } + + SC_FUNC_RETURN(card->ctx, SC_LOG_TYPE_ERROR, r); +} diff --git a/ccid/sm.h b/ccid/sm.h new file mode 100644 index 0000000..5823edf --- /dev/null +++ b/ccid/sm.h @@ -0,0 +1,25 @@ +#include +#include + +#define SM_ISO_PADDING 0x01 +#define SM_NO_PADDING 0x02 + +struct sm_ctx { + u8 padding_indicator; + u8 *key_mac; + size_t key_mac_len; + u8 *key_enc; + size_t key_enc_len; + + const EVP_CIPHER *cipher; + EVP_CIPHER_CTX *cipher_ctx; + ENGINE *cipher_engine; + unsigned char *iv; + + const EVP_MD * md; + EVP_MD_CTX * md_ctx; + ENGINE *md_engine; +}; + +int sm_encrypt(const struct sm_ctx *ctx, sc_card_t *card, sc_apdu_t *apdu, + sc_apdu_t *sm_apdu); diff --git a/ccid/utils.c b/ccid/utils.c new file mode 100644 index 0000000..a4efa89 --- /dev/null +++ b/ccid/utils.c @@ -0,0 +1,279 @@ +/** + * @date 2009-11-30 + * @version 0.1 + * @author Frank Morgner + * @author Dominik Oepen + */ + +#include +#include +#include +#include +#include +#include +#include "utils.h" + +BUF_MEM * +hash(const EVP_MD * md, EVP_MD_CTX * ctx, ENGINE * impl, const BUF_MEM * in) +{ + BUF_MEM * out = NULL; + EVP_MD_CTX * tmp_ctx = NULL; + unsigned int tmp_len; + + if (!md || !in) + goto err; + + if (ctx) + tmp_ctx = ctx; + else { + tmp_ctx = EVP_MD_CTX_create(); + if (!tmp_ctx) + goto err; + } + + tmp_len = EVP_MD_size(md); + out = BUF_MEM_create(tmp_len); + if (!out || !EVP_DigestInit_ex(tmp_ctx, md, impl) || + !EVP_DigestUpdate(tmp_ctx, in->data, in->length) || + !EVP_DigestFinal_ex(tmp_ctx, (unsigned char *) out->data, + &tmp_len)) + goto err; + out->length = tmp_len; + + if (!ctx) + EVP_MD_CTX_destroy(tmp_ctx); + + return out; + +err: + if (out) + BUF_MEM_free(out); + if (tmp_ctx && !ctx) + EVP_MD_CTX_destroy(tmp_ctx); + + return NULL; +} + +BUF_MEM * +cipher(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, ENGINE *impl, + unsigned char *key, unsigned char *iv, int enc, const BUF_MEM * in, + int init) +{ + BUF_MEM * out = NULL; + EVP_CIPHER_CTX * tmp_ctx = NULL; + int i; + + if (!in) + goto err; + + if (ctx) + tmp_ctx = ctx; + else { + tmp_ctx = EVP_CIPHER_CTX_new(); + if (!tmp_ctx) + goto err; + EVP_CIPHER_CTX_init(tmp_ctx); + } + + if (ctx->flags & EVP_CIPH_NO_PADDING) { + i = in->length; + if (in->length % EVP_CIPHER_block_size(type) != 0) { + printf("Input length is not a multiple of block length\n"); + goto err; + } + } else + i = in->length + EVP_CIPHER_block_size(type); + + out = BUF_MEM_create(i); + if (!out) + goto err; + + /* get cipher */ + if (ctx && init) + if (!EVP_CipherInit_ex(tmp_ctx, type, impl, key, iv, enc)) + goto err; + + if (!EVP_CipherUpdate(ctx, (unsigned char *) out->data, &i, + (unsigned char *) in->data, in->length)) + goto err; + out->length = i; + + if (!EVP_CipherFinal_ex(ctx, (unsigned char *) (out->data + out->length), + &i)) + goto err; + + if (!(ctx->flags & EVP_CIPH_NO_PADDING)) + out->length += i; + + if (!ctx) { + EVP_CIPHER_CTX_cleanup(tmp_ctx); + EVP_CIPHER_CTX_free(tmp_ctx); + } + + return out; + +err: + + if (out) + BUF_MEM_free(out); + if (!ctx && tmp_ctx) { + EVP_CIPHER_CTX_cleanup(tmp_ctx); + EVP_CIPHER_CTX_free(tmp_ctx); + } + + return NULL; +} + + +BUF_MEM * +add_iso_pad(const BUF_MEM * m, int block_size) +{ + BUF_MEM * out = NULL; + int p_len; + + if (!m) + goto err; + + /* calculate length of padded message */ + p_len = (m->length / block_size) * block_size + block_size; + + out = BUF_MEM_create(p_len); + if (!out) + goto err; + + memcpy(out->data, m->data, m->length); + + /* now add iso padding */ + memset(out->data + m->length, 0x80, 1); + memset(out->data + m->length + 1, 0, p_len - m->length - 1); + + return out; + +err: + if (out) + BUF_MEM_free(out); + + return NULL; +} + +BUF_MEM * +encoded_ssc(const uint16_t ssc, const PACE_CTX *ctx) +{ + BUF_MEM * out = NULL; + size_t len; + + if (!ctx) + goto err; + + len = EVP_CIPHER_block_size(ctx->cipher); + out = BUF_MEM_create(len); + if (!out || len < sizeof ssc) + goto err; + + /* Copy SSC to the end of buffer and fill the rest with 0 */ + memset(out->data, 0, len); + memcpy(out->data + len - sizeof ssc, &ssc, sizeof ssc); + + return out; + +err: + if (out) + BUF_MEM_free(out); + + return NULL; +} + +BUF_MEM * +BUF_MEM_create(size_t len) +{ + BUF_MEM *out = BUF_MEM_new(); + if (!out) + return NULL; + + if (!BUF_MEM_grow(out, len)) { + BUF_MEM_free(out); + return NULL; + } + + return out; +} + +BUF_MEM * +BUF_MEM_create_init(const void *buf, size_t len) +{ + BUF_MEM *out; + + if (!buf) + return NULL; + + out = BUF_MEM_create(len); + if (!out) + return NULL; + + memcpy(out->data, buf, len); + + return out; +} + +BUF_MEM * +point2buf(const EC_KEY * ecdh, BN_CTX * bn_ctx, const EC_POINT * ecp) +{ + size_t len; + BUF_MEM * out; + + len = EC_POINT_point2oct(EC_KEY_get0_group(ecdh), ecp, + EC_KEY_get_conv_form(ecdh), NULL, 0, bn_ctx); + if (len == 0) + return NULL; + + out = BUF_MEM_create(len); + if (!out) + return NULL; + + out->length = EC_POINT_point2oct(EC_KEY_get0_group(ecdh), ecp, + EC_KEY_get_conv_form(ecdh), (unsigned char *) out->data, out->max, + bn_ctx); + + return out; +} + +BUF_MEM * +bn2buf(const BIGNUM *bn) +{ + BUF_MEM * out; + + if (!bn) + return NULL; + + out = BUF_MEM_create(BN_num_bytes(bn)); + if (!out) + return NULL; + + out->length = BN_bn2bin(bn, (unsigned char *) out->data); + + return out; +} + +BUF_MEM * +BUF_MEM_dup(const BUF_MEM * in) +{ + BUF_MEM * out = NULL; + + if (!in) + return NULL; + + out = BUF_MEM_create(in->length); + if (!out) + goto err; + + memcpy(out->data, in->data, in->length); + out->max = in->max; + + return out; + +err: + if (out) + BUF_MEM_free(out); + + return NULL; +} diff --git a/ccid/utils.h b/ccid/utils.h new file mode 100644 index 0000000..552bbcb --- /dev/null +++ b/ccid/utils.h @@ -0,0 +1,119 @@ +#ifndef _UTIL_H_ +#define _UTIL_H_ +/** + * @date 2009-11-30 + * @version 0.1 + * @author Frank Morgner + * @author Dominik Oepen + */ + +#include +#include +#include +#include +#include + +/** + * @brief Wrapper for the OpenSSL hash functions. + * + * @param md + * @param ctx (optional) + * @param impl (optional) + * @param in + * + * @return message digest or NULL if an error occurred + */ +BUF_MEM * +hash(const EVP_MD * md, EVP_MD_CTX * ctx, ENGINE * impl, const BUF_MEM * in); +/** + * @brief Wrapper to the OpenSSL encryption functions. + * + * @param ctx (optional) + * @param type + * @param impl only evaluated if init is 1. (optional) + * @param key only evaluated if init is 1. + * @param iv only evaluated if init is 1. (optional) + * @param enc only evaluated if init is 1. + * @param in + * @param init whether to initialize (1) the given ctx or not (0). + * + * @return cipher of in or NULL if an error occurred + */ +BUF_MEM * +cipher(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, ENGINE *impl, + unsigned char *key, unsigned char *iv, int enc, const BUF_MEM * in, + const int init); +/** + * @brief Padds a buffer using ISO/IEC 9797-1 padding method 2. + * + * @param m buffer to padd + * @param block_size pad to this block size + * + * @return new buffer with the padded input or NULL if an error occurred + */ +BUF_MEM * +add_iso_pad(const BUF_MEM * m, int block_size); +/** + * @brief Encodes a send sequence counter according to TR-3110 F.3 + * + * @param ssc Send sequence counter to encode (Formatted in big endian) + * @param ctx PACE_CTX object + * + * @return BUF_MEM object containing the send sequence counter or NULL if an error occurred + * + * @note This function is automatically called during PACE, normally you should not need to use it. + */ +BUF_MEM * encoded_ssc(const uint16_t ssc, const PACE_CTX *ctx); + +#include +#include + +/** + * @brief Creates a BUF_MEM object + * + * @param len required length of the buffer + * + * @return Initialized BUF_MEM object or NULL if an error occurred + */ +BUF_MEM * +BUF_MEM_create(size_t len); +/** + * @brief Creates and initializes a BUF_MEM object + * + * @param buf Initial data + * @param len Length of buf + * + * @return Initialized BUF_MEM object or NULL if an error occurred + */ +BUF_MEM * +BUF_MEM_create_init(const void *buf, size_t len); +/** + * @brief converts an EC_POINT object to a BUF_MEM object + * + * @param ecdh EC_KEY object + * @param bn_ctx object (optional) + * @param ecp elliptic curve point to convert + * + * @return converted elliptic curve point or NULL if an error occurred + */ +BUF_MEM * +point2buf(const EC_KEY * ecdh, BN_CTX * bn_ctx, const EC_POINT * ecp); +/** + * @brief converts an BIGNUM object to a BUF_MEM object + * + * @param bn bignumber to convert + * + * @return converted bignumber or NULL if an error occurred + */ +BUF_MEM * +bn2buf(const BIGNUM *bn); +/** + * @brief duplicates a BUF_MEM structure + * + * @param in BUF_MEM to duplicate + * + * @return pointer to the new BUF_MEM or NULL in case of error + */ +BUF_MEM * +BUF_MEM_dup(const BUF_MEM * in); +#endif