2006-06-07 08:33:37 +00:00
|
|
|
/*
|
|
|
|
* muscle.c: Support for MuscleCard Applet from musclecard.com
|
|
|
|
*
|
|
|
|
* Copyright (C) 2006, Identity Alliance, Thomas Harning <support@identityalliance.com>
|
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*/
|
2010-03-04 08:14:36 +00:00
|
|
|
|
2015-04-22 21:55:33 +00:00
|
|
|
#if HAVE_CONFIG_H
|
2010-03-04 08:14:36 +00:00
|
|
|
#include "config.h"
|
2015-04-22 21:55:33 +00:00
|
|
|
#endif
|
2006-06-07 08:33:37 +00:00
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
2010-03-04 08:14:36 +00:00
|
|
|
#include "internal.h"
|
|
|
|
#include "muscle.h"
|
|
|
|
|
2006-06-07 08:33:37 +00:00
|
|
|
#define MSC_RSA_PUBLIC 0x01
|
|
|
|
#define MSC_RSA_PRIVATE 0x02
|
|
|
|
#define MSC_RSA_PRIVATE_CRT 0x03
|
|
|
|
#define MSC_DSA_PUBLIC 0x04
|
|
|
|
#define MSC_DSA_PRIVATE 0x05
|
|
|
|
|
2006-12-07 10:53:37 +00:00
|
|
|
static msc_id inputId = { { 0xFF, 0xFF, 0xFF, 0xFF } };
|
|
|
|
static msc_id outputId = { { 0xFF, 0xFF, 0xFF, 0xFE } };
|
|
|
|
|
2006-06-07 08:33:37 +00:00
|
|
|
int msc_list_objects(sc_card_t* card, u8 next, mscfs_file_t* file) {
|
|
|
|
sc_apdu_t apdu;
|
|
|
|
u8 fileData[14];
|
|
|
|
int r;
|
|
|
|
|
|
|
|
sc_format_apdu(card, &apdu, SC_APDU_CASE_2, 0x58, next, 0x00);
|
|
|
|
apdu.le = 14;
|
|
|
|
apdu.resplen = 14;
|
|
|
|
apdu.resp = fileData;
|
|
|
|
r = sc_transmit_apdu(card, &apdu);
|
|
|
|
if (r)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
if(apdu.sw1 == 0x9C && apdu.sw2 == 0x12) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
r = sc_check_sw(card, apdu.sw1, apdu.sw2);
|
|
|
|
if (r)
|
|
|
|
return r;
|
|
|
|
if(apdu.resplen == 0) /* No more left */
|
|
|
|
return 0;
|
|
|
|
if (apdu.resplen != 14) {
|
2018-11-22 08:31:29 +00:00
|
|
|
sc_log(card->ctx,
|
2017-03-14 19:02:30 +00:00
|
|
|
"expected 14 bytes, got %"SC_FORMAT_LEN_SIZE_T"u.\n",
|
|
|
|
apdu.resplen);
|
2006-06-07 08:33:37 +00:00
|
|
|
return SC_ERROR_UNKNOWN_DATA_RECEIVED;
|
|
|
|
}
|
2006-11-30 08:14:16 +00:00
|
|
|
memcpy(file->objectId.id, fileData, 4);
|
2006-06-27 17:49:19 +00:00
|
|
|
file->size = bebytes2ulong(fileData + 4);
|
|
|
|
file->read = bebytes2ushort(fileData + 8);
|
|
|
|
file->write = bebytes2ushort(fileData + 10);
|
|
|
|
file->delete = bebytes2ushort(fileData + 12);
|
|
|
|
|
2006-06-07 08:33:37 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2006-11-30 08:14:16 +00:00
|
|
|
int msc_partial_read_object(sc_card_t *card, msc_id objectId, int offset, u8 *data, size_t dataLength)
|
2006-06-07 08:33:37 +00:00
|
|
|
{
|
|
|
|
u8 buffer[9];
|
|
|
|
sc_apdu_t apdu;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT, 0x56, 0x00, 0x00);
|
|
|
|
|
2018-11-22 08:31:29 +00:00
|
|
|
sc_log(card->ctx,
|
2017-03-14 19:02:30 +00:00
|
|
|
"READ: Offset: %x\tLength: %"SC_FORMAT_LEN_SIZE_T"u\n", offset,
|
|
|
|
dataLength);
|
2006-11-30 08:14:16 +00:00
|
|
|
memcpy(buffer, objectId.id, 4);
|
2006-06-27 17:49:19 +00:00
|
|
|
ulong2bebytes(buffer + 4, offset);
|
2006-06-07 08:33:37 +00:00
|
|
|
buffer[8] = (u8)dataLength;
|
|
|
|
apdu.data = buffer;
|
|
|
|
apdu.datalen = 9;
|
|
|
|
apdu.lc = 9;
|
|
|
|
apdu.le = dataLength;
|
|
|
|
apdu.resplen = dataLength;
|
|
|
|
apdu.resp = data;
|
|
|
|
r = sc_transmit_apdu(card, &apdu);
|
2018-11-23 20:54:14 +00:00
|
|
|
LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
|
2006-06-07 08:33:37 +00:00
|
|
|
if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00)
|
|
|
|
return dataLength;
|
|
|
|
if(apdu.sw1 == 0x9C) {
|
|
|
|
if(apdu.sw2 == 0x07) {
|
2010-03-15 12:17:13 +00:00
|
|
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_FILE_NOT_FOUND);
|
2006-06-07 08:33:37 +00:00
|
|
|
} else if(apdu.sw2 == 0x06) {
|
2010-03-15 12:17:13 +00:00
|
|
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_NOT_ALLOWED);
|
2006-06-07 08:33:37 +00:00
|
|
|
} else if(apdu.sw2 == 0x0F) {
|
|
|
|
/* GUESSED */
|
2010-03-15 12:17:13 +00:00
|
|
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_INVALID_ARGUMENTS);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
|
|
|
}
|
2018-11-22 08:31:29 +00:00
|
|
|
sc_log(card->ctx,
|
2010-03-15 12:17:13 +00:00
|
|
|
"got strange SWs: 0x%02X 0x%02X\n", apdu.sw1, apdu.sw2);
|
2006-06-07 08:33:37 +00:00
|
|
|
return dataLength;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2006-11-30 08:14:16 +00:00
|
|
|
int msc_read_object(sc_card_t *card, msc_id objectId, int offset, u8 *data, size_t dataLength)
|
2006-06-07 08:33:37 +00:00
|
|
|
{
|
|
|
|
int r;
|
2006-06-17 15:07:49 +00:00
|
|
|
size_t i;
|
2006-12-18 21:58:16 +00:00
|
|
|
size_t max_read_unit = MSC_MAX_READ;
|
|
|
|
|
|
|
|
for(i = 0; i < dataLength; i += max_read_unit) {
|
|
|
|
r = msc_partial_read_object(card, objectId, offset + i, data + i, MIN(dataLength - i, max_read_unit));
|
2018-11-23 20:54:14 +00:00
|
|
|
LOG_TEST_RET(card->ctx, r, "Error in partial object read");
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
|
|
|
return dataLength;
|
|
|
|
}
|
|
|
|
|
2006-11-30 08:14:16 +00:00
|
|
|
int msc_zero_object(sc_card_t *card, msc_id objectId, size_t dataLength)
|
2006-06-07 08:33:37 +00:00
|
|
|
{
|
2006-12-18 21:58:16 +00:00
|
|
|
u8 zeroBuffer[MSC_MAX_APDU];
|
2006-06-17 15:07:49 +00:00
|
|
|
size_t i;
|
2006-12-18 21:58:16 +00:00
|
|
|
size_t max_write_unit = MSC_MAX_SEND - 9; /* - 9 for object ID+length */
|
|
|
|
|
|
|
|
memset(zeroBuffer, 0, max_write_unit);
|
|
|
|
for(i = 0; i < dataLength; i += max_write_unit) {
|
|
|
|
int r = msc_partial_update_object(card, objectId, i, zeroBuffer, MIN(dataLength - i, max_write_unit));
|
2018-11-23 20:54:14 +00:00
|
|
|
LOG_TEST_RET(card->ctx, r, "Error in zeroing file update");
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-04-02 13:49:54 +00:00
|
|
|
int msc_create_object(sc_card_t *card, msc_id objectId, size_t objectSize, unsigned short readAcl, unsigned short writeAcl, unsigned short deleteAcl)
|
2006-06-07 08:33:37 +00:00
|
|
|
{
|
|
|
|
u8 buffer[14];
|
|
|
|
sc_apdu_t apdu;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x5A, 0x00, 0x00);
|
|
|
|
apdu.lc = 14;
|
|
|
|
apdu.data = buffer,
|
|
|
|
apdu.datalen = 14;
|
|
|
|
|
2006-11-30 08:14:16 +00:00
|
|
|
memcpy(buffer, objectId.id, 4);
|
2006-06-27 17:49:19 +00:00
|
|
|
ulong2bebytes(buffer + 4, objectSize);
|
|
|
|
ushort2bebytes(buffer + 8, readAcl);
|
|
|
|
ushort2bebytes(buffer + 10, writeAcl);
|
|
|
|
ushort2bebytes(buffer + 12, deleteAcl);
|
2006-06-07 08:33:37 +00:00
|
|
|
r = sc_transmit_apdu(card, &apdu);
|
2018-11-23 20:54:14 +00:00
|
|
|
LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
|
2006-06-07 08:33:37 +00:00
|
|
|
if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00)
|
2006-06-27 17:49:19 +00:00
|
|
|
return objectSize;
|
2006-06-07 08:33:37 +00:00
|
|
|
if(apdu.sw1 == 0x9C) {
|
|
|
|
if(apdu.sw2 == 0x01) {
|
2010-03-15 12:17:13 +00:00
|
|
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_MEMORY_FAILURE);
|
2006-06-07 08:33:37 +00:00
|
|
|
} else if(apdu.sw2 == 0x08) {
|
2010-03-15 12:17:13 +00:00
|
|
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_FILE_ALREADY_EXISTS);
|
2006-06-07 08:33:37 +00:00
|
|
|
} else if(apdu.sw2 == 0x06) {
|
2010-03-15 12:17:13 +00:00
|
|
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_NOT_ALLOWED);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (card->ctx->debug >= 2) {
|
2018-11-22 08:31:29 +00:00
|
|
|
sc_log(card->ctx, "got strange SWs: 0x%02X 0x%02X\n",
|
2006-06-07 08:33:37 +00:00
|
|
|
apdu.sw1, apdu.sw2);
|
|
|
|
}
|
2006-06-27 17:49:19 +00:00
|
|
|
msc_zero_object(card, objectId, objectSize);
|
|
|
|
return objectSize;
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
|
|
|
|
2006-12-18 21:58:16 +00:00
|
|
|
/* Update up to MSC_MAX_READ - 9 bytes */
|
2006-11-30 08:14:16 +00:00
|
|
|
int msc_partial_update_object(sc_card_t *card, msc_id objectId, int offset, const u8 *data, size_t dataLength)
|
2006-06-07 08:33:37 +00:00
|
|
|
{
|
2006-12-18 21:58:16 +00:00
|
|
|
u8 buffer[MSC_MAX_APDU];
|
2006-06-07 08:33:37 +00:00
|
|
|
sc_apdu_t apdu;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x54, 0x00, 0x00);
|
|
|
|
apdu.lc = dataLength + 9;
|
|
|
|
if (card->ctx->debug >= 2)
|
2018-11-22 08:31:29 +00:00
|
|
|
sc_log(card->ctx,
|
2017-03-14 19:02:30 +00:00
|
|
|
"WRITE: Offset: %x\tLength: %"SC_FORMAT_LEN_SIZE_T"u\n",
|
|
|
|
offset, dataLength);
|
2006-11-30 08:14:16 +00:00
|
|
|
|
|
|
|
memcpy(buffer, objectId.id, 4);
|
2006-06-27 17:49:19 +00:00
|
|
|
ulong2bebytes(buffer + 4, offset);
|
2006-06-07 08:33:37 +00:00
|
|
|
buffer[8] = (u8)dataLength;
|
|
|
|
memcpy(buffer + 9, data, dataLength);
|
|
|
|
apdu.data = buffer;
|
|
|
|
apdu.datalen = apdu.lc;
|
|
|
|
r = sc_transmit_apdu(card, &apdu);
|
2018-11-23 20:54:14 +00:00
|
|
|
LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
|
2006-06-07 08:33:37 +00:00
|
|
|
if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00)
|
|
|
|
return dataLength;
|
|
|
|
if(apdu.sw1 == 0x9C) {
|
|
|
|
if(apdu.sw2 == 0x07) {
|
2010-03-15 12:17:13 +00:00
|
|
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_FILE_NOT_FOUND);
|
2006-06-07 08:33:37 +00:00
|
|
|
} else if(apdu.sw2 == 0x06) {
|
2010-03-15 12:17:13 +00:00
|
|
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_NOT_ALLOWED);
|
2006-06-07 08:33:37 +00:00
|
|
|
} else if(apdu.sw2 == 0x0F) {
|
|
|
|
/* GUESSED */
|
2010-03-15 12:17:13 +00:00
|
|
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_INVALID_ARGUMENTS);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (card->ctx->debug >= 2) {
|
2018-11-22 08:31:29 +00:00
|
|
|
sc_log(card->ctx, "got strange SWs: 0x%02X 0x%02X\n",
|
2006-06-07 08:33:37 +00:00
|
|
|
apdu.sw1, apdu.sw2);
|
|
|
|
}
|
|
|
|
return dataLength;
|
|
|
|
}
|
|
|
|
|
2006-11-30 08:14:16 +00:00
|
|
|
int msc_update_object(sc_card_t *card, msc_id objectId, int offset, const u8 *data, size_t dataLength)
|
2006-06-07 08:33:37 +00:00
|
|
|
{
|
2006-06-17 15:07:49 +00:00
|
|
|
int r;
|
|
|
|
size_t i;
|
2006-12-18 21:58:16 +00:00
|
|
|
size_t max_write_unit = MSC_MAX_SEND - 9;
|
|
|
|
for(i = 0; i < dataLength; i += max_write_unit) {
|
|
|
|
r = msc_partial_update_object(card, objectId, offset + i, data + i, MIN(dataLength - i, max_write_unit));
|
2018-11-23 20:54:14 +00:00
|
|
|
LOG_TEST_RET(card->ctx, r, "Error in partial object update");
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
|
|
|
return dataLength;
|
|
|
|
}
|
|
|
|
|
2006-11-30 08:14:16 +00:00
|
|
|
int msc_delete_object(sc_card_t *card, msc_id objectId, int zero)
|
2006-06-07 08:33:37 +00:00
|
|
|
{
|
|
|
|
sc_apdu_t apdu;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x52, 0x00, zero ? 0x01 : 0x00);
|
|
|
|
apdu.lc = 4;
|
2006-11-30 08:14:16 +00:00
|
|
|
apdu.data = objectId.id;
|
2006-06-07 08:33:37 +00:00
|
|
|
apdu.datalen = 4;
|
|
|
|
r = sc_transmit_apdu(card, &apdu);
|
2018-11-23 20:54:14 +00:00
|
|
|
LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
|
2006-06-07 08:33:37 +00:00
|
|
|
if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00)
|
|
|
|
return 0;
|
|
|
|
if(apdu.sw1 == 0x9C) {
|
|
|
|
if(apdu.sw2 == 0x07) {
|
2010-03-15 12:17:13 +00:00
|
|
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_FILE_NOT_FOUND);
|
2006-06-07 08:33:37 +00:00
|
|
|
} else if(apdu.sw2 == 0x06) {
|
2010-03-15 12:17:13 +00:00
|
|
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_NOT_ALLOWED);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (card->ctx->debug >= 2) {
|
2018-11-22 08:31:29 +00:00
|
|
|
sc_log(card->ctx, "got strange SWs: 0x%02X 0x%02X\n",
|
2006-06-07 08:33:37 +00:00
|
|
|
apdu.sw1, apdu.sw2);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int msc_select_applet(sc_card_t *card, u8 *appletId, size_t appletIdLength)
|
|
|
|
{
|
|
|
|
sc_apdu_t apdu;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0xA4, 4, 0);
|
|
|
|
apdu.lc = appletIdLength;
|
|
|
|
apdu.data = appletId;
|
|
|
|
apdu.datalen = appletIdLength;
|
|
|
|
apdu.resplen = 0;
|
|
|
|
apdu.le = 0;
|
|
|
|
|
|
|
|
r = sc_transmit_apdu(card, &apdu);
|
2018-11-23 20:54:14 +00:00
|
|
|
LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
|
2006-06-07 08:33:37 +00:00
|
|
|
if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00)
|
|
|
|
return 1;
|
|
|
|
|
2010-03-15 12:17:13 +00:00
|
|
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_CARD_CMD_FAILED);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
|
|
|
|
2006-12-18 21:58:16 +00:00
|
|
|
/* Truncate the nulls at the end of a PIN, useful in padding is unnecessarily added */
|
|
|
|
static void truncatePinNulls(const u8* pin, int *pinLength) {
|
|
|
|
for(; *pinLength > 0; (*pinLength)--) {
|
|
|
|
if(pin[*pinLength - 1]) break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-06-07 08:33:37 +00:00
|
|
|
int msc_verify_pin(sc_card_t *card, int pinNumber, const u8 *pinValue, int pinLength, int *tries)
|
|
|
|
{
|
|
|
|
sc_apdu_t apdu;
|
|
|
|
int r;
|
|
|
|
|
2006-12-18 21:58:16 +00:00
|
|
|
const int bufferLength = MSC_MAX_PIN_LENGTH;
|
2007-01-06 16:35:38 +00:00
|
|
|
u8 buffer[MSC_MAX_PIN_LENGTH];
|
2006-12-18 21:58:16 +00:00
|
|
|
assert(pinLength <= MSC_MAX_PIN_LENGTH);
|
|
|
|
|
|
|
|
msc_verify_pin_apdu(card, &apdu, buffer, bufferLength, pinNumber, pinValue, pinLength);
|
2006-06-07 08:33:37 +00:00
|
|
|
if(tries)
|
|
|
|
*tries = -1;
|
|
|
|
r = sc_transmit_apdu(card, &apdu);
|
2018-11-23 20:54:14 +00:00
|
|
|
LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
|
2006-06-07 08:33:37 +00:00
|
|
|
if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00) {
|
|
|
|
return 0;
|
|
|
|
} else if(apdu.sw1 == 0x63) { /* Invalid auth */
|
|
|
|
if(tries)
|
|
|
|
*tries = apdu.sw2 & 0x0F;
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_PIN_CODE_INCORRECT);
|
2006-06-07 08:33:37 +00:00
|
|
|
} else if(apdu.sw1 == 0x9C && apdu.sw2 == 0x02) {
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_PIN_CODE_INCORRECT);
|
2006-06-07 08:33:37 +00:00
|
|
|
} else if(apdu.sw1 == 0x69 && apdu.sw2 == 0x83) {
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_AUTH_METHOD_BLOCKED);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
|
|
|
|
2010-03-15 12:17:13 +00:00
|
|
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_PIN_CODE_INCORRECT);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* USE ISO_VERIFY due to tries return */
|
2006-12-18 21:58:16 +00:00
|
|
|
void msc_verify_pin_apdu(sc_card_t *card, sc_apdu_t *apdu, u8* buffer, size_t bufferLength, int pinNumber, const u8 *pinValue, int pinLength)
|
2006-06-07 08:33:37 +00:00
|
|
|
{
|
2006-12-18 21:58:16 +00:00
|
|
|
assert(buffer);
|
2008-05-05 13:00:01 +00:00
|
|
|
assert(bufferLength >= (size_t)pinLength);
|
2006-12-18 21:58:16 +00:00
|
|
|
assert(pinLength <= MSC_MAX_PIN_LENGTH);
|
|
|
|
|
|
|
|
truncatePinNulls(pinValue, &pinLength);
|
|
|
|
|
|
|
|
memcpy(buffer, pinValue, pinLength);
|
2006-06-07 08:33:37 +00:00
|
|
|
sc_format_apdu(card, apdu, SC_APDU_CASE_3_SHORT, 0x42, pinNumber, 0);
|
|
|
|
apdu->lc = pinLength;
|
2006-12-18 21:58:16 +00:00
|
|
|
apdu->data = buffer;
|
2006-06-07 08:33:37 +00:00
|
|
|
apdu->datalen = pinLength;
|
|
|
|
}
|
|
|
|
|
|
|
|
int msc_unblock_pin(sc_card_t *card, int pinNumber, const u8 *pukValue, int pukLength, int *tries)
|
|
|
|
{
|
|
|
|
sc_apdu_t apdu;
|
|
|
|
int r;
|
2006-12-18 21:58:16 +00:00
|
|
|
const int bufferLength = MSC_MAX_PIN_LENGTH;
|
2007-01-06 16:35:38 +00:00
|
|
|
u8 buffer[MSC_MAX_PIN_LENGTH];
|
2006-06-07 08:33:37 +00:00
|
|
|
|
2006-12-18 21:58:16 +00:00
|
|
|
assert(pukLength <= MSC_MAX_PIN_LENGTH);
|
|
|
|
|
|
|
|
msc_unblock_pin_apdu(card, &apdu, buffer, bufferLength, pinNumber, pukValue, pukLength);
|
2006-06-07 08:33:37 +00:00
|
|
|
if(tries)
|
|
|
|
*tries = -1;
|
|
|
|
r = sc_transmit_apdu(card, &apdu);
|
2018-11-23 20:54:14 +00:00
|
|
|
LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
|
2006-06-07 08:33:37 +00:00
|
|
|
if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00) {
|
|
|
|
return 0;
|
|
|
|
} else if(apdu.sw1 == 0x63) { /* Invalid auth */
|
|
|
|
if(tries)
|
|
|
|
*tries = apdu.sw2 & 0x0F;
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_PIN_CODE_INCORRECT);
|
2006-06-07 08:33:37 +00:00
|
|
|
} else if(apdu.sw1 == 0x9C && apdu.sw2 == 0x02) {
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_PIN_CODE_INCORRECT);
|
2006-06-07 08:33:37 +00:00
|
|
|
} else if(apdu.sw1 == 0x69 && apdu.sw2 == 0x83) {
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_AUTH_METHOD_BLOCKED);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
|
|
|
|
2010-03-15 12:17:13 +00:00
|
|
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_PIN_CODE_INCORRECT);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
|
|
|
|
2006-12-18 21:58:16 +00:00
|
|
|
void msc_unblock_pin_apdu(sc_card_t *card, sc_apdu_t *apdu, u8* buffer, size_t bufferLength, int pinNumber, const u8 *pukValue, int pukLength)
|
2006-06-07 08:33:37 +00:00
|
|
|
{
|
2006-12-18 21:58:16 +00:00
|
|
|
assert(buffer);
|
2008-05-05 13:00:01 +00:00
|
|
|
assert(bufferLength >= (size_t)pukLength);
|
2006-12-18 21:58:16 +00:00
|
|
|
assert(pukLength <= MSC_MAX_PIN_LENGTH);
|
|
|
|
|
|
|
|
truncatePinNulls(pukValue, &pukLength);
|
|
|
|
|
|
|
|
memcpy(buffer, pukValue, pukLength);
|
2006-06-07 08:33:37 +00:00
|
|
|
sc_format_apdu(card, apdu, SC_APDU_CASE_3_SHORT, 0x46, pinNumber, 0);
|
|
|
|
apdu->lc = pukLength;
|
2006-12-18 21:58:16 +00:00
|
|
|
apdu->data = buffer;
|
2006-06-07 08:33:37 +00:00
|
|
|
apdu->datalen = pukLength;
|
|
|
|
}
|
|
|
|
|
|
|
|
int msc_change_pin(sc_card_t *card, int pinNumber, const u8 *pinValue, int pinLength, const u8 *newPin, int newPinLength, int *tries)
|
|
|
|
{
|
|
|
|
sc_apdu_t apdu;
|
|
|
|
int r;
|
2006-12-18 21:58:16 +00:00
|
|
|
const int bufferLength = (MSC_MAX_PIN_LENGTH + 1) * 2;
|
2007-01-06 16:35:38 +00:00
|
|
|
u8 buffer[(MSC_MAX_PIN_LENGTH + 1) * 2];
|
2006-06-07 08:33:37 +00:00
|
|
|
|
2006-12-18 21:58:16 +00:00
|
|
|
msc_change_pin_apdu(card, &apdu, buffer, bufferLength, pinNumber, pinValue, pinLength, newPin, newPinLength);
|
2006-06-07 08:33:37 +00:00
|
|
|
if(tries)
|
|
|
|
*tries = -1;
|
|
|
|
r = sc_transmit_apdu(card, &apdu);
|
2018-11-23 20:54:14 +00:00
|
|
|
LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
|
2006-06-07 08:33:37 +00:00
|
|
|
if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00) {
|
|
|
|
return 0;
|
|
|
|
} else if(apdu.sw1 == 0x63) { /* Invalid auth */
|
|
|
|
if(tries)
|
|
|
|
*tries = apdu.sw2 & 0x0F;
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_PIN_CODE_INCORRECT);
|
2006-06-07 08:33:37 +00:00
|
|
|
} else if(apdu.sw1 == 0x9C && apdu.sw2 == 0x02) {
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_PIN_CODE_INCORRECT);
|
2006-06-07 08:33:37 +00:00
|
|
|
} else if(apdu.sw1 == 0x69 && apdu.sw2 == 0x83) {
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_AUTH_METHOD_BLOCKED);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
|
|
|
|
2010-03-15 12:17:13 +00:00
|
|
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_PIN_CODE_INCORRECT);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* USE ISO_VERIFY due to tries return */
|
2006-12-18 21:58:16 +00:00
|
|
|
void msc_change_pin_apdu(sc_card_t *card, sc_apdu_t *apdu, u8* buffer, size_t bufferLength, int pinNumber, const u8 *pinValue, int pinLength, const u8 *newPin, int newPinLength)
|
2006-06-07 08:33:37 +00:00
|
|
|
{
|
2006-12-18 21:58:16 +00:00
|
|
|
u8 *ptr;
|
|
|
|
assert(pinLength <= MSC_MAX_PIN_LENGTH);
|
|
|
|
assert(newPinLength <= MSC_MAX_PIN_LENGTH);
|
|
|
|
assert(buffer);
|
2008-05-05 13:00:01 +00:00
|
|
|
assert(bufferLength >= pinLength + newPinLength + 2UL);
|
2006-12-18 21:58:16 +00:00
|
|
|
|
|
|
|
truncatePinNulls(pinValue, &pinLength);
|
|
|
|
truncatePinNulls(newPin, &newPinLength);
|
|
|
|
|
|
|
|
ptr = buffer;
|
2006-06-07 08:33:37 +00:00
|
|
|
|
|
|
|
sc_format_apdu(card, apdu, SC_APDU_CASE_3_SHORT, 0x44, pinNumber, 0);
|
|
|
|
*ptr = pinLength;
|
|
|
|
ptr++;
|
|
|
|
memcpy(ptr, pinValue, pinLength);
|
|
|
|
ptr += pinLength;
|
|
|
|
*ptr = newPinLength;
|
|
|
|
ptr++;
|
|
|
|
memcpy(ptr, newPin, newPinLength);
|
|
|
|
apdu->lc = pinLength + newPinLength + 2;
|
|
|
|
apdu->datalen = apdu->lc;
|
2006-12-18 21:58:16 +00:00
|
|
|
apdu->data = buffer;
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
|
|
|
|
2009-10-22 09:18:16 +00:00
|
|
|
int msc_get_challenge(sc_card_t *card, unsigned short dataLength, unsigned short seedLength, u8 *seedData, u8 *outputData)
|
2006-06-07 08:33:37 +00:00
|
|
|
{
|
|
|
|
sc_apdu_t apdu;
|
2009-10-22 09:18:16 +00:00
|
|
|
int r, location, cse;
|
|
|
|
size_t len;
|
2006-06-07 08:33:37 +00:00
|
|
|
u8 *buffer, *ptr;
|
|
|
|
|
2006-12-18 21:58:16 +00:00
|
|
|
location = (dataLength < MSC_MAX_READ) ? 1 : 2; /* 1 == APDU, 2 == (seed in 0xFFFFFFFE, out in 0xFFFFFFFF) */
|
2006-06-07 08:33:37 +00:00
|
|
|
cse = (location == 1) ? SC_APDU_CASE_4_SHORT : SC_APDU_CASE_3_SHORT;
|
|
|
|
len = seedLength + 4;
|
2006-12-18 21:58:16 +00:00
|
|
|
|
|
|
|
assert(seedLength < MSC_MAX_SEND - 4);
|
|
|
|
assert(dataLength < MSC_MAX_READ - 9); /* Output buffer doesn't seem to operate as desired.... nobody can read/delete */
|
2006-06-07 08:33:37 +00:00
|
|
|
|
|
|
|
buffer = malloc(len);
|
2018-11-22 22:10:49 +00:00
|
|
|
if(!buffer) LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY);
|
2006-06-07 08:33:37 +00:00
|
|
|
ptr = buffer;
|
2006-06-27 17:49:19 +00:00
|
|
|
ushort2bebytes(ptr, dataLength);
|
2006-06-07 08:33:37 +00:00
|
|
|
ptr+=2;
|
2006-06-27 17:49:19 +00:00
|
|
|
ushort2bebytes(ptr, seedLength);
|
2006-06-07 08:33:37 +00:00
|
|
|
ptr+=2;
|
|
|
|
if(seedLength > 0) {
|
|
|
|
memcpy(ptr, seedData, seedLength);
|
|
|
|
}
|
2009-03-25 13:42:13 +00:00
|
|
|
sc_format_apdu(card, &apdu, cse, 0x62, 0x00, location);
|
2006-06-07 08:33:37 +00:00
|
|
|
apdu.data = buffer;
|
|
|
|
apdu.datalen = len;
|
|
|
|
apdu.lc = len;
|
|
|
|
|
|
|
|
if(location == 1) {
|
|
|
|
u8* outputBuffer = malloc(dataLength + 2);
|
2018-11-22 22:10:49 +00:00
|
|
|
if(outputBuffer == NULL) LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY);
|
2006-06-07 08:33:37 +00:00
|
|
|
apdu.le = dataLength + 2;
|
|
|
|
apdu.resp = outputBuffer;
|
|
|
|
apdu.resplen = dataLength + 2;
|
|
|
|
}
|
|
|
|
r = sc_transmit_apdu(card, &apdu);
|
|
|
|
if(location == 1) {
|
|
|
|
memcpy(outputData, apdu.resp + 2, dataLength);
|
|
|
|
free(apdu.resp);
|
|
|
|
}
|
|
|
|
free(buffer);
|
2018-11-23 20:54:14 +00:00
|
|
|
LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
|
2006-06-07 08:33:37 +00:00
|
|
|
if(location == 1) {
|
|
|
|
if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00) {
|
2009-03-25 13:47:46 +00:00
|
|
|
return SC_SUCCESS;
|
2006-06-07 08:33:37 +00:00
|
|
|
} else {
|
|
|
|
r = sc_check_sw(card, apdu.sw1, apdu.sw2);
|
|
|
|
if (r) {
|
|
|
|
if (card->ctx->debug >= 2) {
|
2018-11-22 08:31:29 +00:00
|
|
|
sc_log(card->ctx, "got strange SWs: 0x%02X 0x%02X\n",
|
2006-06-07 08:33:37 +00:00
|
|
|
apdu.sw1, apdu.sw2);
|
|
|
|
}
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, r);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_CARD_CMD_FAILED);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if(apdu.sw1 != 0x90 || apdu.sw2 != 0x00) {
|
|
|
|
r = sc_check_sw(card, apdu.sw1, apdu.sw2);
|
|
|
|
if (r) {
|
|
|
|
if (card->ctx->debug >= 2) {
|
2018-11-22 08:31:29 +00:00
|
|
|
sc_log(card->ctx, "got strange SWs: 0x%02X 0x%02X\n",
|
2006-06-07 08:33:37 +00:00
|
|
|
apdu.sw1, apdu.sw2);
|
|
|
|
}
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, r);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_CARD_CMD_FAILED);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
2006-11-30 08:14:16 +00:00
|
|
|
r = msc_read_object(card, inputId, 2, outputData, dataLength);
|
2006-06-07 08:33:37 +00:00
|
|
|
if(r < 0)
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, r);
|
2006-11-30 08:14:16 +00:00
|
|
|
msc_delete_object(card, inputId,0);
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, r);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int msc_generate_keypair(sc_card_t *card, int privateKey, int publicKey, int algorithm, int keySize, int options)
|
|
|
|
{
|
|
|
|
sc_apdu_t apdu;
|
2006-12-18 21:58:16 +00:00
|
|
|
u8 buffer[16]; /* Keypair payload length */
|
2006-06-07 08:33:37 +00:00
|
|
|
u8 *ptr = buffer;
|
|
|
|
int r;
|
|
|
|
unsigned short prRead = 0xFFFF, prWrite = 0x0002, prCompute = 0x0002,
|
|
|
|
puRead = 0x0000, puWrite = 0x0002, puCompute = 0x0000;
|
|
|
|
|
|
|
|
assert(privateKey <= 0x0F && publicKey <= 0x0F);
|
|
|
|
|
|
|
|
sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x30, privateKey, publicKey);
|
2006-06-27 17:49:19 +00:00
|
|
|
|
2006-06-07 08:33:37 +00:00
|
|
|
*ptr = algorithm; ptr++;
|
|
|
|
|
2006-06-27 17:49:19 +00:00
|
|
|
ushort2bebytes(ptr, keySize);
|
2006-06-07 08:33:37 +00:00
|
|
|
ptr+=2;
|
|
|
|
|
2006-06-27 17:49:19 +00:00
|
|
|
ushort2bebytes(ptr, prRead);
|
2006-06-07 08:33:37 +00:00
|
|
|
ptr+=2;
|
2006-06-27 17:49:19 +00:00
|
|
|
ushort2bebytes(ptr, prWrite);
|
2006-06-07 08:33:37 +00:00
|
|
|
ptr+=2;
|
2006-06-27 17:49:19 +00:00
|
|
|
ushort2bebytes(ptr, prCompute);
|
2006-06-07 08:33:37 +00:00
|
|
|
ptr+=2;
|
|
|
|
|
2006-06-27 17:49:19 +00:00
|
|
|
ushort2bebytes(ptr, puRead);
|
2006-06-07 08:33:37 +00:00
|
|
|
ptr+=2;
|
2006-06-27 17:49:19 +00:00
|
|
|
ushort2bebytes(ptr, puWrite);
|
2006-06-07 08:33:37 +00:00
|
|
|
ptr+=2;
|
2006-06-27 17:49:19 +00:00
|
|
|
ushort2bebytes(ptr, puCompute);
|
2006-06-07 08:33:37 +00:00
|
|
|
ptr+=2;
|
|
|
|
|
|
|
|
*ptr = 0; /* options; -- no options for now, they need extra data */
|
|
|
|
|
|
|
|
apdu.data = buffer;
|
|
|
|
apdu.datalen = 16;
|
|
|
|
apdu.lc = 16;
|
|
|
|
|
|
|
|
r = sc_transmit_apdu(card, &apdu);
|
2018-11-23 20:54:14 +00:00
|
|
|
LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
|
2006-06-07 08:33:37 +00:00
|
|
|
if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
r = sc_check_sw(card, apdu.sw1, apdu.sw2);
|
|
|
|
if (r) {
|
|
|
|
if (card->ctx->debug >= 2) {
|
2018-11-22 08:31:29 +00:00
|
|
|
sc_log(card->ctx, "got strange SWs: 0x%02X 0x%02X\n",
|
2006-06-07 08:33:37 +00:00
|
|
|
apdu.sw1, apdu.sw2);
|
|
|
|
}
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, r);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_CARD_CMD_FAILED);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int msc_extract_key(sc_card_t *card,
|
|
|
|
int keyLocation)
|
|
|
|
{
|
|
|
|
sc_apdu_t apdu;
|
|
|
|
u8 encoding = 0;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x34, keyLocation, 0x00);
|
|
|
|
apdu.data = &encoding;
|
|
|
|
apdu.datalen = 1;
|
|
|
|
apdu.lc = 1;
|
|
|
|
r = sc_transmit_apdu(card, &apdu);
|
2018-11-23 20:54:14 +00:00
|
|
|
LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
|
2006-06-07 08:33:37 +00:00
|
|
|
if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
r = sc_check_sw(card, apdu.sw1, apdu.sw2);
|
|
|
|
if (r) {
|
|
|
|
if (card->ctx->debug >= 2) {
|
2018-11-22 08:31:29 +00:00
|
|
|
sc_log(card->ctx, "got strange SWs: 0x%02X 0x%02X\n",
|
2006-06-07 08:33:37 +00:00
|
|
|
apdu.sw1, apdu.sw2);
|
|
|
|
}
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, r);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_CARD_CMD_FAILED);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int msc_extract_rsa_public_key(sc_card_t *card,
|
|
|
|
int keyLocation,
|
2015-10-14 20:56:53 +00:00
|
|
|
size_t* modLength,
|
2006-06-07 08:33:37 +00:00
|
|
|
u8** modulus,
|
2015-10-14 20:56:53 +00:00
|
|
|
size_t* expLength,
|
2006-06-07 08:33:37 +00:00
|
|
|
u8** exponent)
|
|
|
|
{
|
|
|
|
int r;
|
|
|
|
u8 buffer[1024]; /* Should be plenty... */
|
|
|
|
int fileLocation = 1;
|
|
|
|
|
|
|
|
r = msc_extract_key(card, keyLocation);
|
2018-11-22 22:10:49 +00:00
|
|
|
if(r < 0) LOG_FUNC_RETURN(card->ctx, r);
|
2006-06-07 08:33:37 +00:00
|
|
|
|
2006-12-18 21:58:16 +00:00
|
|
|
/* Read keyType, keySize, and what should be the modulus size */
|
2006-11-30 08:14:16 +00:00
|
|
|
r = msc_read_object(card, inputId, fileLocation, buffer, 5);
|
2006-06-07 08:33:37 +00:00
|
|
|
fileLocation += 5;
|
2018-11-22 22:10:49 +00:00
|
|
|
if(r < 0) LOG_FUNC_RETURN(card->ctx, r);
|
2006-06-07 08:33:37 +00:00
|
|
|
|
2018-11-22 22:10:49 +00:00
|
|
|
if(buffer[0] != MSC_RSA_PUBLIC) LOG_FUNC_RETURN(card->ctx, SC_ERROR_UNKNOWN_DATA_RECEIVED);
|
2006-06-07 08:33:37 +00:00
|
|
|
*modLength = (buffer[3] << 8) | buffer[4];
|
|
|
|
/* Read the modulus and the exponent length */
|
2015-09-17 18:06:19 +00:00
|
|
|
|
|
|
|
if (*modLength + 2 > sizeof buffer)
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY);
|
2006-11-30 08:14:16 +00:00
|
|
|
r = msc_read_object(card, inputId, fileLocation, buffer, *modLength + 2);
|
2006-06-07 08:33:37 +00:00
|
|
|
fileLocation += *modLength + 2;
|
2018-11-22 22:10:49 +00:00
|
|
|
if(r < 0) LOG_FUNC_RETURN(card->ctx, r);
|
2006-06-07 08:33:37 +00:00
|
|
|
|
|
|
|
*modulus = malloc(*modLength);
|
2018-11-22 22:10:49 +00:00
|
|
|
if(!*modulus) LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY);
|
2006-06-07 08:33:37 +00:00
|
|
|
memcpy(*modulus, buffer, *modLength);
|
|
|
|
*expLength = (buffer[*modLength] << 8) | buffer[*modLength + 1];
|
2018-06-21 12:47:05 +00:00
|
|
|
if (*expLength > sizeof buffer)
|
|
|
|
return SC_ERROR_OUT_OF_MEMORY;
|
2006-11-30 08:14:16 +00:00
|
|
|
r = msc_read_object(card, inputId, fileLocation, buffer, *expLength);
|
2006-06-07 08:33:37 +00:00
|
|
|
if(r < 0) {
|
|
|
|
free(*modulus); *modulus = NULL;
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, r);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
|
|
|
*exponent = malloc(*expLength);
|
|
|
|
if(!*exponent) {
|
|
|
|
free(*modulus);
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
|
|
|
memcpy(*exponent, buffer, *expLength);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* For the moment, only support streaming data to the card
|
|
|
|
in blocks, not through file IO */
|
|
|
|
int msc_compute_crypt_init(sc_card_t *card,
|
|
|
|
int keyLocation,
|
|
|
|
int cipherMode,
|
|
|
|
int cipherDirection,
|
|
|
|
const u8* initData,
|
|
|
|
u8* outputData,
|
|
|
|
size_t dataLength,
|
|
|
|
size_t* outputDataLength)
|
|
|
|
{
|
|
|
|
sc_apdu_t apdu;
|
2006-12-18 21:58:16 +00:00
|
|
|
u8 buffer[MSC_MAX_APDU];
|
2006-06-07 08:33:37 +00:00
|
|
|
u8 *ptr;
|
|
|
|
int r;
|
|
|
|
|
2012-06-11 19:16:47 +00:00
|
|
|
u8 outputBuffer[MSC_MAX_APDU + 2];
|
2006-06-07 08:33:37 +00:00
|
|
|
sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT, 0x36, keyLocation, 0x01); /* Init */
|
|
|
|
apdu.data = buffer;
|
|
|
|
apdu.datalen = dataLength + 5;
|
|
|
|
apdu.lc = dataLength + 5;
|
2012-06-11 19:16:47 +00:00
|
|
|
|
2006-06-07 08:33:37 +00:00
|
|
|
memset(outputBuffer, 0, sizeof(outputBuffer));
|
|
|
|
apdu.resp = outputBuffer;
|
|
|
|
apdu.resplen = dataLength + 2;
|
|
|
|
apdu.le = dataLength + 2;
|
|
|
|
ptr = buffer;
|
|
|
|
*ptr = cipherMode; ptr++;
|
|
|
|
*ptr = cipherDirection; ptr++;
|
|
|
|
*ptr = 0x01; ptr++; /* DATA LOCATION: APDU */
|
|
|
|
*ptr = (dataLength >> 8) & 0xFF; ptr++;
|
|
|
|
*ptr = dataLength & 0xFF; ptr++;
|
|
|
|
memcpy(ptr, initData, dataLength);
|
2012-06-11 19:16:47 +00:00
|
|
|
|
2006-06-07 08:33:37 +00:00
|
|
|
r = sc_transmit_apdu(card, &apdu);
|
2018-11-23 20:54:14 +00:00
|
|
|
LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
|
2006-06-07 08:33:37 +00:00
|
|
|
if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00) {
|
|
|
|
short receivedData = outputBuffer[0] << 8 | outputBuffer[1];
|
2015-01-28 06:07:33 +00:00
|
|
|
*outputDataLength = receivedData;
|
2012-06-11 19:16:47 +00:00
|
|
|
|
2006-12-18 21:58:16 +00:00
|
|
|
assert(receivedData <= MSC_MAX_APDU);
|
2006-06-07 08:33:37 +00:00
|
|
|
memcpy(outputData, outputBuffer + 2, receivedData);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
r = sc_check_sw(card, apdu.sw1, apdu.sw2);
|
|
|
|
if (r) {
|
|
|
|
if (card->ctx->debug >= 2) {
|
2018-11-22 08:31:29 +00:00
|
|
|
sc_log(card->ctx, "init: got strange SWs: 0x%02X 0x%02X\n",
|
2006-06-07 08:33:37 +00:00
|
|
|
apdu.sw1, apdu.sw2);
|
|
|
|
}
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, r);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_CARD_CMD_FAILED);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int msc_compute_crypt_final(
|
|
|
|
sc_card_t *card,
|
|
|
|
int keyLocation,
|
|
|
|
const u8* inputData,
|
|
|
|
u8* outputData,
|
|
|
|
size_t dataLength,
|
|
|
|
size_t* outputDataLength)
|
|
|
|
{
|
|
|
|
sc_apdu_t apdu;
|
2006-12-18 21:58:16 +00:00
|
|
|
u8 buffer[MSC_MAX_APDU];
|
2012-06-11 19:16:47 +00:00
|
|
|
u8 outputBuffer[MSC_MAX_APDU + 2];
|
2006-06-07 08:33:37 +00:00
|
|
|
u8 *ptr;
|
|
|
|
int r;
|
|
|
|
|
2009-03-25 14:31:21 +00:00
|
|
|
sc_format_apdu(card, &apdu, SC_APDU_CASE_4, 0x36, keyLocation, 0x03); /* Final */
|
2006-06-07 08:33:37 +00:00
|
|
|
|
|
|
|
apdu.data = buffer;
|
|
|
|
apdu.datalen = dataLength + 3;
|
|
|
|
apdu.lc = dataLength + 3;
|
|
|
|
|
|
|
|
memset(outputBuffer, 0, sizeof(outputBuffer));
|
|
|
|
apdu.resp = outputBuffer;
|
2009-03-25 14:31:21 +00:00
|
|
|
apdu.resplen = dataLength + 2;
|
2007-04-10 19:49:26 +00:00
|
|
|
apdu.le = dataLength +2;
|
2006-06-07 08:33:37 +00:00
|
|
|
ptr = buffer;
|
|
|
|
*ptr = 0x01; ptr++; /* DATA LOCATION: APDU */
|
|
|
|
*ptr = (dataLength >> 8) & 0xFF; ptr++;
|
|
|
|
*ptr = dataLength & 0xFF; ptr++;
|
|
|
|
memcpy(ptr, inputData, dataLength);
|
|
|
|
|
|
|
|
r = sc_transmit_apdu(card, &apdu);
|
2018-11-23 20:54:14 +00:00
|
|
|
LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
|
2006-06-07 08:33:37 +00:00
|
|
|
if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00) {
|
|
|
|
short receivedData = outputBuffer[0] << 8 | outputBuffer[1];
|
|
|
|
*outputDataLength = receivedData;
|
2006-12-18 21:58:16 +00:00
|
|
|
assert(receivedData <= MSC_MAX_APDU);
|
2006-06-07 08:33:37 +00:00
|
|
|
memcpy(outputData, outputBuffer + 2, receivedData);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
r = sc_check_sw(card, apdu.sw1, apdu.sw2);
|
|
|
|
if (r) {
|
|
|
|
if (card->ctx->debug >= 2) {
|
2018-11-22 08:31:29 +00:00
|
|
|
sc_log(card->ctx, "final: got strange SWs: 0x%02X 0x%02X\n",
|
2006-06-07 08:33:37 +00:00
|
|
|
apdu.sw1, apdu.sw2);
|
|
|
|
}
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, r);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_CARD_CMD_FAILED);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
|
|
|
|
2009-03-25 14:31:21 +00:00
|
|
|
/* Stream data to the card through file IO */
|
|
|
|
static int msc_compute_crypt_final_object(
|
|
|
|
sc_card_t *card,
|
|
|
|
int keyLocation,
|
|
|
|
const u8* inputData,
|
|
|
|
u8* outputData,
|
|
|
|
size_t dataLength,
|
|
|
|
size_t* outputDataLength)
|
|
|
|
{
|
|
|
|
sc_apdu_t apdu;
|
|
|
|
u8 buffer[MSC_MAX_APDU];
|
|
|
|
u8 *ptr;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x36, keyLocation, 0x03); /* Final */
|
|
|
|
|
|
|
|
apdu.data = buffer;
|
|
|
|
apdu.datalen = 1;
|
|
|
|
apdu.lc = 1;
|
|
|
|
|
|
|
|
ptr = buffer;
|
|
|
|
*ptr = 0x02;
|
|
|
|
ptr++; /* DATA LOCATION: OBJECT */
|
|
|
|
*ptr = (dataLength >> 8) & 0xFF;
|
|
|
|
ptr++;
|
|
|
|
*ptr = dataLength & 0xFF;
|
|
|
|
ptr++;
|
|
|
|
memcpy(ptr, inputData, dataLength);
|
|
|
|
|
|
|
|
r = msc_create_object(card, outputId, dataLength + 2, 0x02, 0x02, 0x02);
|
|
|
|
if(r < 0) {
|
|
|
|
if(r == SC_ERROR_FILE_ALREADY_EXISTS) {
|
|
|
|
r = msc_delete_object(card, outputId, 0);
|
|
|
|
if(r < 0) {
|
2010-03-15 12:17:13 +00:00
|
|
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, r);
|
2009-03-25 14:31:21 +00:00
|
|
|
}
|
|
|
|
r = msc_create_object(card, outputId, dataLength + 2, 0x02, 0x02, 0x02);
|
|
|
|
if(r < 0) {
|
2010-03-15 12:17:13 +00:00
|
|
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, r);
|
2009-03-25 14:31:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
r = msc_update_object(card, outputId, 0, buffer + 1, dataLength + 2);
|
|
|
|
if(r < 0) return r;
|
|
|
|
|
|
|
|
r = sc_transmit_apdu(card, &apdu);
|
2018-11-23 20:54:14 +00:00
|
|
|
LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
|
2009-03-25 14:31:21 +00:00
|
|
|
if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00) {
|
|
|
|
r = msc_read_object(card, inputId, 2, outputData, dataLength);
|
2017-10-31 09:12:12 +00:00
|
|
|
if (r >= 0)
|
|
|
|
*outputDataLength = r;
|
2009-03-25 14:31:21 +00:00
|
|
|
msc_delete_object(card, outputId, 0);
|
|
|
|
msc_delete_object(card, inputId, 0);
|
2017-10-31 09:12:12 +00:00
|
|
|
return r;
|
2009-03-25 14:31:21 +00:00
|
|
|
}
|
|
|
|
r = sc_check_sw(card, apdu.sw1, apdu.sw2);
|
|
|
|
if (r) {
|
|
|
|
if (card->ctx->debug >= 2) {
|
2018-11-22 08:31:29 +00:00
|
|
|
sc_log(card->ctx, "final: got strange SWs: 0x%02X 0x%02X\n",
|
2009-03-25 14:31:21 +00:00
|
|
|
apdu.sw1, apdu.sw2);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
r = SC_ERROR_CARD_CMD_FAILED;
|
|
|
|
}
|
2009-09-14 08:46:59 +00:00
|
|
|
/* this is last ditch cleanup */
|
2009-03-25 14:31:21 +00:00
|
|
|
msc_delete_object(card, outputId, 0);
|
|
|
|
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, r);
|
2009-03-25 14:31:21 +00:00
|
|
|
}
|
|
|
|
|
2006-06-07 08:33:37 +00:00
|
|
|
int msc_compute_crypt(sc_card_t *card,
|
|
|
|
int keyLocation,
|
|
|
|
int cipherMode,
|
|
|
|
int cipherDirection,
|
|
|
|
const u8* data,
|
|
|
|
u8* outputData,
|
|
|
|
size_t dataLength,
|
|
|
|
size_t outputDataLength)
|
|
|
|
{
|
2008-05-05 13:00:01 +00:00
|
|
|
size_t left = dataLength;
|
2006-06-07 08:33:37 +00:00
|
|
|
const u8* inPtr = data;
|
|
|
|
u8* outPtr = outputData;
|
|
|
|
int toSend;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
size_t received = 0;
|
|
|
|
assert(outputDataLength >= dataLength);
|
|
|
|
|
|
|
|
/* Don't send data during init... apparently current version does not support it */
|
|
|
|
toSend = 0;
|
|
|
|
r = msc_compute_crypt_init(card,
|
|
|
|
keyLocation,
|
|
|
|
cipherMode,
|
|
|
|
cipherDirection,
|
|
|
|
inPtr,
|
|
|
|
outPtr,
|
|
|
|
toSend,
|
|
|
|
&received);
|
2018-11-22 22:10:49 +00:00
|
|
|
if(r < 0) LOG_FUNC_RETURN(card->ctx, r);
|
2006-06-07 08:33:37 +00:00
|
|
|
left -= toSend;
|
|
|
|
inPtr += toSend;
|
|
|
|
outPtr += received;
|
2009-03-25 14:31:21 +00:00
|
|
|
|
|
|
|
toSend = MIN(left, MSC_MAX_APDU - 5);
|
|
|
|
/* If the card supports extended APDUs, or the data fits in
|
|
|
|
one normal APDU, use it for the data exchange */
|
|
|
|
if (left < (MSC_MAX_SEND - 4) || (card->caps & SC_CARD_CAP_APDU_EXT) != 0) {
|
|
|
|
r = msc_compute_crypt_final(card,
|
2006-06-07 08:33:37 +00:00
|
|
|
keyLocation,
|
|
|
|
inPtr,
|
|
|
|
outPtr,
|
|
|
|
toSend,
|
|
|
|
&received);
|
2018-11-22 22:10:49 +00:00
|
|
|
if(r < 0) LOG_FUNC_RETURN(card->ctx, r);
|
2009-03-25 14:31:21 +00:00
|
|
|
} else { /* Data is too big: use objects */
|
|
|
|
r = msc_compute_crypt_final_object(card,
|
|
|
|
keyLocation,
|
|
|
|
inPtr,
|
|
|
|
outPtr,
|
|
|
|
toSend,
|
|
|
|
&received);
|
2018-11-22 22:10:49 +00:00
|
|
|
if(r < 0) LOG_FUNC_RETURN(card->ctx, r);
|
2009-03-25 14:31:21 +00:00
|
|
|
}
|
2006-06-07 08:33:37 +00:00
|
|
|
outPtr += received;
|
2009-03-25 14:31:21 +00:00
|
|
|
|
2006-06-07 08:33:37 +00:00
|
|
|
return outPtr - outputData; /* Amt received */
|
|
|
|
}
|
|
|
|
|
|
|
|
/* USED IN KEY ITEM WRITING */
|
|
|
|
#define CPYVAL(valName) \
|
2006-06-27 17:49:19 +00:00
|
|
|
ushort2bebytes(p, data->valName ## Length); p+= 2; \
|
2006-06-07 08:33:37 +00:00
|
|
|
memcpy(p, data->valName ## Value, data->valName ## Length); p+= data->valName ## Length
|
|
|
|
|
|
|
|
int msc_import_key(sc_card_t *card,
|
|
|
|
int keyLocation,
|
|
|
|
sc_cardctl_muscle_key_info_t *data)
|
|
|
|
{
|
2010-04-02 13:49:54 +00:00
|
|
|
unsigned short readAcl = 0xFFFF,
|
|
|
|
writeAcl = 0x0002,
|
2006-06-07 08:33:37 +00:00
|
|
|
use = 0x0002,
|
|
|
|
keySize = data->keySize;
|
|
|
|
int bufferSize = 0;
|
|
|
|
u8 *buffer, *p;
|
|
|
|
u8 apduBuffer[6];
|
|
|
|
sc_apdu_t apdu;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(data->keyType == 0x02 || data->keyType == 0x03);
|
|
|
|
if(data->keyType == 0x02) {
|
|
|
|
if( (data->pLength == 0 || !data->pValue)
|
|
|
|
|| (data->modLength == 0 || !data->modValue))
|
2010-03-15 12:17:13 +00:00
|
|
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_INVALID_ARGUMENTS);
|
2006-06-07 08:33:37 +00:00
|
|
|
} else if(data->keyType == 0x03) {
|
|
|
|
if( (data->pLength == 0 || !data->pValue)
|
|
|
|
|| (data->qLength == 0 || !data->qValue)
|
|
|
|
|| (data->pqLength == 0 || !data->pqValue)
|
|
|
|
|| (data->dp1Length == 0 || !data->dp1Value)
|
|
|
|
|| (data->dq1Length == 0 || !data->dq1Value))
|
2010-03-15 12:17:13 +00:00
|
|
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_INVALID_ARGUMENTS);
|
2006-06-07 08:33:37 +00:00
|
|
|
} else {
|
2010-03-15 12:17:13 +00:00
|
|
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_INVALID_ARGUMENTS);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if(data->keyType == 0x02) {
|
|
|
|
bufferSize = 4 + 4 + data->pLength + data->modLength;
|
|
|
|
} else if(data->keyType == 0x03) {
|
|
|
|
bufferSize = 4 + 10
|
|
|
|
+ data->pLength + data->qLength + data->pqLength
|
|
|
|
+ data->dp1Length + data->dq1Length;
|
|
|
|
}
|
|
|
|
buffer = malloc(bufferSize);
|
2018-11-22 22:10:49 +00:00
|
|
|
if(!buffer) LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY);
|
2006-06-07 08:33:37 +00:00
|
|
|
p = buffer;
|
|
|
|
*p = 0x00; p++; /* Encoding plain */
|
|
|
|
*p = data->keyType; p++; /* RSA_PRIVATE */
|
2006-06-27 17:49:19 +00:00
|
|
|
ushort2bebytes(p, keySize); p+=2; /* key size */
|
2006-06-07 08:33:37 +00:00
|
|
|
|
|
|
|
if(data->keyType == 0x02) {
|
|
|
|
CPYVAL(mod);
|
|
|
|
CPYVAL(p);
|
|
|
|
} else if(data->keyType == 0x03) {
|
|
|
|
CPYVAL(p);
|
|
|
|
CPYVAL(q);
|
|
|
|
CPYVAL(pq);
|
|
|
|
CPYVAL(dp1);
|
|
|
|
CPYVAL(dq1);
|
|
|
|
}
|
2006-11-30 08:14:16 +00:00
|
|
|
|
|
|
|
r = msc_create_object(card, outputId, bufferSize, 0x02, 0x02, 0x02);
|
2006-06-07 08:33:37 +00:00
|
|
|
if(r < 0) {
|
|
|
|
if(r == SC_ERROR_FILE_ALREADY_EXISTS) {
|
2006-11-30 08:14:16 +00:00
|
|
|
r = msc_delete_object(card, outputId, 0);
|
2006-06-07 08:33:37 +00:00
|
|
|
if(r < 0) {
|
|
|
|
free(buffer);
|
2010-03-15 12:17:13 +00:00
|
|
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, r);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
2006-11-30 08:14:16 +00:00
|
|
|
r = msc_create_object(card, outputId, bufferSize, 0x02, 0x02, 0x02);
|
2006-06-07 08:33:37 +00:00
|
|
|
if(r < 0) {
|
|
|
|
free(buffer);
|
2010-03-15 12:17:13 +00:00
|
|
|
SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, r);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2006-11-30 08:14:16 +00:00
|
|
|
|
|
|
|
r = msc_update_object(card, outputId, 0, buffer, bufferSize);
|
2006-06-07 08:33:37 +00:00
|
|
|
free(buffer);
|
|
|
|
if(r < 0) return r;
|
|
|
|
|
|
|
|
|
|
|
|
sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x32, keyLocation, 0x00);
|
|
|
|
apdu.lc = 6;
|
|
|
|
apdu.data = apduBuffer;
|
|
|
|
apdu.datalen = 6;
|
|
|
|
p = apduBuffer;
|
2010-04-02 13:49:54 +00:00
|
|
|
ushort2bebytes(p, readAcl); p+=2;
|
|
|
|
ushort2bebytes(p, writeAcl); p+=2;
|
2011-02-05 21:18:34 +00:00
|
|
|
ushort2bebytes(p, use);
|
2006-06-07 08:33:37 +00:00
|
|
|
r = sc_transmit_apdu(card, &apdu);
|
2018-11-23 20:54:14 +00:00
|
|
|
LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
|
2006-06-07 08:33:37 +00:00
|
|
|
if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00) {
|
2006-11-30 08:14:16 +00:00
|
|
|
msc_delete_object(card, outputId, 0);
|
2006-06-07 08:33:37 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
r = sc_check_sw(card, apdu.sw1, apdu.sw2);
|
|
|
|
if (r) {
|
|
|
|
if (card->ctx->debug >= 2) {
|
2018-11-22 08:31:29 +00:00
|
|
|
sc_log(card->ctx, "keyimport: got strange SWs: 0x%02X 0x%02X\n",
|
2006-06-07 08:33:37 +00:00
|
|
|
apdu.sw1, apdu.sw2);
|
|
|
|
}
|
2009-09-14 08:46:59 +00:00
|
|
|
/* this is last ditch cleanup */
|
2006-11-30 08:14:16 +00:00
|
|
|
msc_delete_object(card, outputId, 0);
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, r);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
2009-09-14 08:46:59 +00:00
|
|
|
/* this is last ditch cleanup */
|
2006-11-30 08:14:16 +00:00
|
|
|
msc_delete_object(card, outputId, 0);
|
2006-06-07 08:33:37 +00:00
|
|
|
|
2018-11-22 22:10:49 +00:00
|
|
|
LOG_FUNC_RETURN(card->ctx, SC_ERROR_CARD_CMD_FAILED);
|
2006-06-07 08:33:37 +00:00
|
|
|
}
|
|
|
|
#undef CPYVAL
|