/*
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#if HAVE_CONFIG_H
#include "config.h"
#endif

#include <string.h>

#include "internal.h"
#include "muscle.h"

#define MSC_RSA_PUBLIC		0x01
#define MSC_RSA_PRIVATE 	0x02
#define MSC_RSA_PRIVATE_CRT	0x03

static msc_id inputId = { { 0xFF, 0xFF, 0xFF, 0xFF } };
static msc_id outputId = { { 0xFF, 0xFF, 0xFF, 0xFE } };

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) {
		sc_log(card->ctx,
			 "expected 14 bytes, got %"SC_FORMAT_LEN_SIZE_T"u.\n",
			 apdu.resplen);
		return SC_ERROR_UNKNOWN_DATA_RECEIVED;
	}
	memcpy(file->objectId.id, fileData, 4);
	file->size = bebytes2ulong(fileData + 4);
	file->read = bebytes2ushort(fileData + 8);
	file->write = bebytes2ushort(fileData + 10);
	file->delete = bebytes2ushort(fileData + 12);

	return 1;
}

int msc_partial_read_object(sc_card_t *card, msc_id objectId, int offset, u8 *data, size_t dataLength)
{
	u8 buffer[9];
	sc_apdu_t apdu;
	int r;

	sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT, 0x56, 0x00, 0x00);

	sc_log(card->ctx,
		"READ: Offset: %x\tLength: %"SC_FORMAT_LEN_SIZE_T"u\n", offset,
		 dataLength);
	memcpy(buffer, objectId.id, 4);
	ulong2bebytes(buffer + 4, offset);
	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);
	LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
	if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00)
		return (int)dataLength;
	if(apdu.sw1 == 0x9C) {
		if(apdu.sw2 == 0x07) {
			SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_FILE_NOT_FOUND);
		} else if(apdu.sw2 == 0x06) {
			SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_NOT_ALLOWED);
		} else if(apdu.sw2 == 0x0F) {
			/* GUESSED */
			SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_INVALID_ARGUMENTS);
		}
	}
	sc_log(card->ctx,
		"got strange SWs: 0x%02X 0x%02X\n", apdu.sw1, apdu.sw2);
	return (int)dataLength;

}

int msc_read_object(sc_card_t *card, msc_id objectId, int offset, u8 *data, size_t dataLength)
{
	int r;
	unsigned int i;
	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));
		LOG_TEST_RET(card->ctx, r, "Error in partial object read");
	}
	return (int)dataLength;
}

int msc_zero_object(sc_card_t *card, msc_id objectId, size_t dataLength)
{
	u8 zeroBuffer[MSC_MAX_APDU];
	size_t i;
	size_t max_write_unit = MIN(MSC_MAX_APDU, 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));
		LOG_TEST_RET(card->ctx, r, "Error in zeroing file update");
	}
	return 0;
}

int msc_create_object(sc_card_t *card, msc_id objectId, size_t objectSize, unsigned short readAcl, unsigned short writeAcl, unsigned short deleteAcl)
{
	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;

	memcpy(buffer, objectId.id, 4);
	ulong2bebytes(buffer + 4, objectSize);
	ushort2bebytes(buffer + 8, readAcl);
	ushort2bebytes(buffer + 10, writeAcl);
	ushort2bebytes(buffer + 12, deleteAcl);
	r = sc_transmit_apdu(card, &apdu);
	LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
	if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00)
		return (int)objectSize;
	if(apdu.sw1 == 0x9C) {
		if(apdu.sw2 == 0x01) {
			SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_MEMORY_FAILURE);
		} else if(apdu.sw2 == 0x08) {
			SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_FILE_ALREADY_EXISTS);
		} else if(apdu.sw2 == 0x06) {
			SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_NOT_ALLOWED);
		}
	}
	if (card->ctx->debug >= 2) {
		sc_log(card->ctx,  "got strange SWs: 0x%02X 0x%02X\n",
		     apdu.sw1, apdu.sw2);
	}
	msc_zero_object(card, objectId, objectSize);
	return (int)objectSize;
}

/* Update up to MSC_MAX_READ - 9 bytes */
int msc_partial_update_object(sc_card_t *card, msc_id objectId, size_t offset, const u8 *data, size_t dataLength)
{
	u8 buffer[MSC_MAX_APDU];
	sc_apdu_t apdu;
	int r;

	if (dataLength + 9 > MSC_MAX_APDU)
		return SC_ERROR_INVALID_ARGUMENTS;

	sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x54, 0x00, 0x00);
	apdu.lc = dataLength + 9;
	if (card->ctx->debug >= 2)
		sc_log(card->ctx,
			 "WRITE: Offset: %zx\tLength: %"SC_FORMAT_LEN_SIZE_T"u\n",
			 offset, dataLength);

	memcpy(buffer, objectId.id, 4);
	ulong2bebytes(buffer + 4, offset);
	buffer[8] = (u8)dataLength;
	memcpy(buffer + 9, data, dataLength);
	apdu.data = buffer;
	apdu.datalen = apdu.lc;
	r = sc_transmit_apdu(card, &apdu);
	LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
	if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00)
		return (int)dataLength;
	if(apdu.sw1 == 0x9C) {
		if(apdu.sw2 == 0x07) {
			SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_FILE_NOT_FOUND);
		} else if(apdu.sw2 == 0x06) {
			SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_NOT_ALLOWED);
		} else if(apdu.sw2 == 0x0F) {
			/* GUESSED */
			SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_INVALID_ARGUMENTS);
		}
	}
	if (card->ctx->debug >= 2) {
		sc_log(card->ctx,  "got strange SWs: 0x%02X 0x%02X\n",
		     apdu.sw1, apdu.sw2);
	}
	return (int)dataLength;
}

int msc_update_object(sc_card_t *card, msc_id objectId, int offset, const u8 *data, size_t dataLength)
{
	int r;
	size_t i;
	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));
		LOG_TEST_RET(card->ctx, r, "Error in partial object update");
	}
	return (int)dataLength;
}

int msc_delete_object(sc_card_t *card, msc_id objectId, int zero)
{
	sc_apdu_t apdu;
	int r;

	sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x52, 0x00, zero ? 0x01 : 0x00);
	apdu.lc = 4;
	apdu.data = objectId.id;
	apdu.datalen = 4;
	r = sc_transmit_apdu(card, &apdu);
	LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
	if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00)
		return 0;
	if(apdu.sw1 == 0x9C) {
		if(apdu.sw2 == 0x07) {
			SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_FILE_NOT_FOUND);
		} else if(apdu.sw2 == 0x06) {
			SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_NOT_ALLOWED);
		}
	}
	if (card->ctx->debug >= 2) {
		sc_log(card->ctx,  "got strange SWs: 0x%02X 0x%02X\n",
		     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);
	LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
	if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00)
		return 1;

	SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE,  SC_ERROR_CARD_CMD_FAILED);
}

/* Truncate the nulls at the end of a PIN, useful in padding is unnecessarily added */
static void truncatePinNulls(const u8* pin, size_t *pinLength) {
	for(; *pinLength > 0; (*pinLength)--) {
		if(pin[*pinLength - 1]) break;
	}
}

int msc_verify_pin(sc_card_t *card, int pinNumber, const u8 *pinValue, int pinLength, int *tries)
{
	sc_apdu_t apdu;
	int r;

	const int bufferLength = MSC_MAX_PIN_LENGTH;
	u8 buffer[MSC_MAX_PIN_LENGTH];

	if (pinLength > MSC_MAX_PIN_LENGTH)
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS);

	r = msc_verify_pin_apdu(card, &apdu, buffer, bufferLength, pinNumber, pinValue, pinLength);
	LOG_TEST_RET(card->ctx, r, "APDU verification failed");

	if(tries)
		*tries = -1;
	r = sc_transmit_apdu(card, &apdu);
	LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
	if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00) {
		return 0;
	} else if(apdu.sw1 == 0x63) { /* Invalid auth */
		if(tries)
			*tries = apdu.sw2 & 0x0F;
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_PIN_CODE_INCORRECT);
	} else if(apdu.sw1 == 0x9C && apdu.sw2 == 0x02) {
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_PIN_CODE_INCORRECT);
	} else if(apdu.sw1 == 0x69 && apdu.sw2 == 0x83) {
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_AUTH_METHOD_BLOCKED);
	}

	SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE,  SC_ERROR_PIN_CODE_INCORRECT);
}

/* USE ISO_VERIFY due to tries return */
int msc_verify_pin_apdu(sc_card_t *card, sc_apdu_t *apdu, u8* buffer, size_t bufferLength, int pinNumber, const u8 *pinValue, size_t pinLength)
{
	if (!buffer || bufferLength < (size_t)pinLength || pinLength > MSC_MAX_PIN_LENGTH)
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS);

	truncatePinNulls(pinValue, &pinLength);

	memcpy(buffer, pinValue, pinLength);
	sc_format_apdu(card, apdu, SC_APDU_CASE_3_SHORT, 0x42, pinNumber, 0);
	apdu->lc = pinLength;
	apdu->data = buffer;
	apdu->datalen = pinLength;
	LOG_FUNC_RETURN(card->ctx, SC_SUCCESS);
}

int msc_unblock_pin(sc_card_t *card, int pinNumber, const u8 *pukValue, int pukLength, int *tries)
{
	sc_apdu_t apdu;
	int r;
	const int bufferLength = MSC_MAX_PIN_LENGTH;
	u8 buffer[MSC_MAX_PIN_LENGTH];

	if (pukLength > MSC_MAX_PIN_LENGTH)
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS);

	r = msc_unblock_pin_apdu(card, &apdu, buffer, bufferLength, pinNumber, pukValue, pukLength);
	LOG_TEST_RET(card->ctx, r, "APDU unblock failed");

	if(tries)
		*tries = -1;
	r = sc_transmit_apdu(card, &apdu);
	LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
	if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00) {
		return 0;
	} else if(apdu.sw1 == 0x63) { /* Invalid auth */
		if(tries)
			*tries = apdu.sw2 & 0x0F;
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_PIN_CODE_INCORRECT);
	} else if(apdu.sw1 == 0x9C && apdu.sw2 == 0x02) {
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_PIN_CODE_INCORRECT);
	} else if(apdu.sw1 == 0x69 && apdu.sw2 == 0x83) {
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_AUTH_METHOD_BLOCKED);
	}

	SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE,  SC_ERROR_PIN_CODE_INCORRECT);
}

int msc_unblock_pin_apdu(sc_card_t *card, sc_apdu_t *apdu, u8* buffer, size_t bufferLength, int pinNumber, const u8 *pukValue, size_t pukLength)
{
	if (!buffer || bufferLength < (size_t)pukLength || pukLength > MSC_MAX_PIN_LENGTH)
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS);

	truncatePinNulls(pukValue, &pukLength);

	memcpy(buffer, pukValue, pukLength);
	sc_format_apdu(card, apdu, SC_APDU_CASE_3_SHORT, 0x46, pinNumber, 0);
	apdu->lc = pukLength;
	apdu->data = buffer;
	apdu->datalen = pukLength;
	LOG_FUNC_RETURN(card->ctx, SC_SUCCESS);
}

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;
	const int bufferLength = (MSC_MAX_PIN_LENGTH + 1) * 2;
	u8 buffer[(MSC_MAX_PIN_LENGTH + 1) * 2];

	r = msc_change_pin_apdu(card, &apdu, buffer, bufferLength, pinNumber, pinValue, pinLength, newPin, newPinLength);
	LOG_TEST_RET(card->ctx, r, "APDU change failed");
	if(tries)
		*tries = -1;
	r = sc_transmit_apdu(card, &apdu);
	LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
	if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00) {
		return 0;
	} else if(apdu.sw1 == 0x63) { /* Invalid auth */
		if(tries)
			*tries = apdu.sw2 & 0x0F;
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_PIN_CODE_INCORRECT);
	} else if(apdu.sw1 == 0x9C && apdu.sw2 == 0x02) {
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_PIN_CODE_INCORRECT);
	} else if(apdu.sw1 == 0x69 && apdu.sw2 == 0x83) {
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_AUTH_METHOD_BLOCKED);
	}

	SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE,  SC_ERROR_PIN_CODE_INCORRECT);
}

/* USE ISO_VERIFY due to tries return */
int msc_change_pin_apdu(sc_card_t *card, sc_apdu_t *apdu, u8* buffer, size_t bufferLength, int pinNumber, const u8 *pinValue, size_t pinLength, const u8 *newPin, size_t newPinLength)
{
	u8 *ptr;
	if (pinLength > MSC_MAX_PIN_LENGTH || newPinLength > MSC_MAX_PIN_LENGTH
		|| !buffer || bufferLength < pinLength + newPinLength + 2UL)
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS);

	truncatePinNulls(pinValue, &pinLength);
	truncatePinNulls(newPin, &newPinLength);

	ptr = buffer;

	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;
	apdu->data = buffer;
	LOG_FUNC_RETURN(card->ctx, SC_SUCCESS);
}

int msc_get_challenge(sc_card_t *card, unsigned short dataLength, unsigned short seedLength, u8 *seedData, u8 *outputData)
{
	sc_apdu_t apdu;
	int r, location, cse;
	size_t len;
	u8 *buffer, *ptr;

	location = (dataLength < MSC_MAX_READ) ? 1 : 2; /* 1 == APDU, 2 == (seed in 0xFFFFFFFE, out in 0xFFFFFFFF) */
	cse = (location == 1) ? SC_APDU_CASE_4_SHORT : SC_APDU_CASE_3_SHORT;
	len = seedLength + 4;

	if (seedLength >= MSC_MAX_SEND - 4 || dataLength >= MSC_MAX_READ - 9)/* Output buffer doesn't seem to operate as desired.... nobody can read/delete */
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS);

	buffer = malloc(len);
	if(!buffer) LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY);
	ptr = buffer;
	ushort2bebytes(ptr, dataLength);
	ptr+=2;
	ushort2bebytes(ptr, seedLength);
	ptr+=2;
	if(seedLength > 0) {
		memcpy(ptr, seedData, seedLength);
	}
	sc_format_apdu(card, &apdu, cse, 0x62, 0x00, location);
	apdu.data = buffer;
	apdu.datalen = len;
	apdu.lc = len;

	if(location == 1) {
		u8* outputBuffer = malloc(dataLength + 2);
		if(outputBuffer == NULL) LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY);
		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);
	LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
	if(location == 1) {
		if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00) {
			return SC_SUCCESS;
		} else {
			r = sc_check_sw(card, apdu.sw1, apdu.sw2);
			if (r) {
				if (card->ctx->debug >= 2) {
					sc_log(card->ctx,  "got strange SWs: 0x%02X 0x%02X\n",
					     apdu.sw1, apdu.sw2);
				}
				LOG_FUNC_RETURN(card->ctx, r);
			}
			LOG_FUNC_RETURN(card->ctx, SC_ERROR_CARD_CMD_FAILED);
		}
	} else {
		if(apdu.sw1 != 0x90 || apdu.sw2 != 0x00) {
			r = sc_check_sw(card, apdu.sw1, apdu.sw2);
			if (r) {
				if (card->ctx->debug >= 2) {
					sc_log(card->ctx,  "got strange SWs: 0x%02X 0x%02X\n",
					     apdu.sw1, apdu.sw2);
				}
				LOG_FUNC_RETURN(card->ctx, r);
			}
			LOG_FUNC_RETURN(card->ctx, SC_ERROR_CARD_CMD_FAILED);
		}
		r = msc_read_object(card, inputId, 2, outputData, dataLength);
		if(r < 0)
			LOG_FUNC_RETURN(card->ctx, r);
		msc_delete_object(card, inputId,0);
		LOG_FUNC_RETURN(card->ctx, r);
	}
}

int msc_generate_keypair(sc_card_t *card, int privateKey, int publicKey, int algorithm, size_t keySize, int options)
{
	sc_apdu_t apdu;
	u8 buffer[16]; /* Key pair payload length */
	u8 *ptr = buffer;
	int r;
	unsigned short prRead = 0xFFFF, prWrite = 0x0002, prCompute = 0x0002,
		puRead = 0x0000, puWrite = 0x0002, puCompute = 0x0000;

	if (privateKey > 0x0F || publicKey > 0x0F)
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS);

	sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x30, privateKey, publicKey);

	*ptr = algorithm; ptr++;

	ushort2bebytes(ptr, keySize);
	ptr+=2;

	ushort2bebytes(ptr, prRead);
	ptr+=2;
	ushort2bebytes(ptr, prWrite);
	ptr+=2;
	ushort2bebytes(ptr, prCompute);
	ptr+=2;

	ushort2bebytes(ptr, puRead);
	ptr+=2;
	ushort2bebytes(ptr, puWrite);
	ptr+=2;
	ushort2bebytes(ptr, puCompute);
	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);
	LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
	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) {
			sc_log(card->ctx,  "got strange SWs: 0x%02X 0x%02X\n",
			     apdu.sw1, apdu.sw2);
		}
		LOG_FUNC_RETURN(card->ctx, r);
	}
	LOG_FUNC_RETURN(card->ctx, SC_ERROR_CARD_CMD_FAILED);
}

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);
	LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
	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) {
			sc_log(card->ctx,  "got strange SWs: 0x%02X 0x%02X\n",
			     apdu.sw1, apdu.sw2);
		}
		LOG_FUNC_RETURN(card->ctx, r);
	}
	LOG_FUNC_RETURN(card->ctx, SC_ERROR_CARD_CMD_FAILED);
}

int msc_extract_rsa_public_key(sc_card_t *card,
			int keyLocation,
			size_t* modLength,
			u8** modulus,
			size_t* expLength,
			u8** exponent)
{
	int r;
	u8 buffer[1024]; /* Should be plenty... */
	int fileLocation = 1;

	r = msc_extract_key(card, keyLocation);
	if(r < 0) LOG_FUNC_RETURN(card->ctx, r);

	/* Read keyType, keySize, and what should be the modulus size */
	r = msc_read_object(card, inputId, fileLocation, buffer, 5);
	fileLocation += 5;
	if(r < 0) LOG_FUNC_RETURN(card->ctx, r);

	if(buffer[0] != MSC_RSA_PUBLIC) LOG_FUNC_RETURN(card->ctx, SC_ERROR_UNKNOWN_DATA_RECEIVED);
	*modLength = (buffer[3] << 8) | buffer[4];
	/* Read the modulus and the exponent length */

	if (*modLength + 2 > sizeof buffer)
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY);
	r = msc_read_object(card, inputId, fileLocation, buffer, *modLength + 2);
	fileLocation += *modLength + 2;
	if(r < 0) LOG_FUNC_RETURN(card->ctx, r);

	*modulus = malloc(*modLength);
	if(!*modulus) LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY);
	memcpy(*modulus, buffer, *modLength);
	*expLength = (buffer[*modLength] << 8) | buffer[*modLength + 1];
	if (*expLength > sizeof buffer) {
		free(*modulus); *modulus = NULL;
		return SC_ERROR_OUT_OF_MEMORY;
	}
	r = msc_read_object(card, inputId, fileLocation, buffer, *expLength);
	if(r < 0) {
		free(*modulus); *modulus = NULL;
		LOG_FUNC_RETURN(card->ctx, r);
	}
	*exponent = malloc(*expLength);
	if(!*exponent) {
		free(*modulus);
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY);
	}
	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;
	u8 buffer[MSC_MAX_APDU];
	u8 *ptr;
	int r;

	u8 outputBuffer[MSC_MAX_APDU + 2];
	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;

	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);

	r = sc_transmit_apdu(card, &apdu);
	LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
	if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00) {
		short receivedData = outputBuffer[0] << 8 | outputBuffer[1];
		*outputDataLength = receivedData;

		if (receivedData > MSC_MAX_APDU)
			LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_DATA);
		memcpy(outputData, outputBuffer + 2, receivedData);
		return 0;
	}
	r = sc_check_sw(card, apdu.sw1, apdu.sw2);
	if (r) {
		if (card->ctx->debug >= 2) {
			sc_log(card->ctx,  "init: got strange SWs: 0x%02X 0x%02X\n",
			     apdu.sw1, apdu.sw2);
		}
		LOG_FUNC_RETURN(card->ctx, r);
	}
	LOG_FUNC_RETURN(card->ctx, SC_ERROR_CARD_CMD_FAILED);
}

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;
	u8 buffer[MSC_MAX_APDU];
	u8 outputBuffer[MSC_MAX_APDU + 2];
	u8 *ptr;
	int r;

	sc_format_apdu(card, &apdu, SC_APDU_CASE_4, 0x36, keyLocation, 0x03); /* Final */

	apdu.data = buffer;
	apdu.datalen = dataLength + 3;
	apdu.lc = dataLength + 3;

	memset(outputBuffer, 0, sizeof(outputBuffer));
	apdu.resp = outputBuffer;
	apdu.resplen = dataLength + 2;
	apdu.le = dataLength +2;
	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);
	LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
	if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00) {
		short receivedData = outputBuffer[0] << 8 | outputBuffer[1];
		*outputDataLength = receivedData;

		if (receivedData > MSC_MAX_APDU)
			LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_DATA);
		memcpy(outputData, outputBuffer + 2, receivedData);
		return 0;
	}
	r = sc_check_sw(card, apdu.sw1, apdu.sw2);
	if (r) {
		if (card->ctx->debug >= 2) {
			sc_log(card->ctx,  "final: got strange SWs: 0x%02X 0x%02X\n",
			     apdu.sw1, apdu.sw2);
		}
		LOG_FUNC_RETURN(card->ctx, r);
	}
	LOG_FUNC_RETURN(card->ctx, SC_ERROR_CARD_CMD_FAILED);
}

/* 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) {
				SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, r);
			}
			r = msc_create_object(card, outputId, dataLength + 2, 0x02, 0x02, 0x02);
			if(r < 0) {
				SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, r);
			}
		}
	}

	r = msc_update_object(card, outputId, 0, buffer + 1, dataLength + 2);
	if(r < 0) return r;

	r = sc_transmit_apdu(card, &apdu);
	LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
	if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00) {
		r = msc_read_object(card, inputId, 2, outputData, dataLength);
		if (r >= 0)
			*outputDataLength = r;
		msc_delete_object(card, outputId, 0);
		msc_delete_object(card, inputId, 0);
		return r;
	}
	r = sc_check_sw(card, apdu.sw1, apdu.sw2);
	if (r) {
		if (card->ctx->debug >= 2) {
			sc_log(card->ctx,  "final: got strange SWs: 0x%02X 0x%02X\n",
			     apdu.sw1, apdu.sw2);
		}
	} else {
		r = SC_ERROR_CARD_CMD_FAILED;
	}
	/* this is last ditch cleanup */
	msc_delete_object(card, outputId, 0);

	LOG_FUNC_RETURN(card->ctx, r);
}

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)
{
	size_t left = dataLength;
	const u8* inPtr = data;
	u8* outPtr = outputData;
	int toSend;
	int r;

	size_t received = 0;

	if (outputDataLength < dataLength)
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS);

	/* 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);
	if(r < 0) LOG_FUNC_RETURN(card->ctx, r);
	left -= toSend;
	inPtr += toSend;
	outPtr += received;

	toSend = MIN((int)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,
			keyLocation,
			inPtr,
			outPtr,
			toSend,
			&received);
		if(r < 0) LOG_FUNC_RETURN(card->ctx, r);
	} else { /* Data is too big: use objects */
		r = msc_compute_crypt_final_object(card,
			keyLocation,
			inPtr,
			outPtr,
			toSend,
			&received);
		if(r < 0) LOG_FUNC_RETURN(card->ctx, r);
	}
	outPtr += received;

	return (int)(outPtr - outputData); /* Amt received */
}

/* USED IN KEY ITEM WRITING */
#define CPYVAL(valName) \
	ushort2bebytes(p, data->valName ## Length); p+= 2; \
	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)
{
	unsigned short readAcl = 0xFFFF,
		writeAcl = 0x0002,
		use = 0x0002,
		keySize = data->keySize;
	size_t bufferSize = 0;
	u8 *buffer, *p;
	u8 apduBuffer[6];
	sc_apdu_t apdu;
	int r;

	if (data->keyType != 0x02 && data->keyType != 0x03)
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS);

	if(data->keyType == 0x02) {
		if( (data->pLength == 0 || !data->pValue)
		|| (data->modLength == 0 || !data->modValue))
			SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_INVALID_ARGUMENTS);
	} 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))
			SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_INVALID_ARGUMENTS);
	} else {
		SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_INVALID_ARGUMENTS);
	}

	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);
	if(!buffer) LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY);
	p = buffer;
	*p = 0x00; p++; /* Encoding plain */
	*p = data->keyType; p++; /* RSA_PRIVATE */
	ushort2bebytes(p, keySize); p+=2; /* key size */

	if(data->keyType == 0x02) {
		CPYVAL(mod);
		CPYVAL(p);
	} else if(data->keyType == 0x03) {
		CPYVAL(p);
		CPYVAL(q);
		CPYVAL(pq);
		CPYVAL(dp1);
		CPYVAL(dq1);
	}

	r = msc_create_object(card, outputId, bufferSize, 0x02, 0x02, 0x02);
	if(r < 0) {
		if(r == SC_ERROR_FILE_ALREADY_EXISTS) {
			r = msc_delete_object(card, outputId, 0);
			if(r < 0) {
				free(buffer);
				SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, r);
			}
			r = msc_create_object(card, outputId, bufferSize, 0x02, 0x02, 0x02);
			if(r < 0) {
				free(buffer);
				SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, r);
			}
		}
	}

	r = msc_update_object(card, outputId, 0, buffer, bufferSize);
	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;
	ushort2bebytes(p, readAcl); p+=2;
	ushort2bebytes(p, writeAcl); p+=2;
	ushort2bebytes(p, use);
	r = sc_transmit_apdu(card, &apdu);
	LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
	if(apdu.sw1 == 0x90 && apdu.sw2 == 0x00) {
		msc_delete_object(card, outputId, 0);
		return 0;
	}
	r = sc_check_sw(card, apdu.sw1, apdu.sw2);
	if (r) {
		if (card->ctx->debug >= 2) {
			sc_log(card->ctx,  "keyimport: got strange SWs: 0x%02X 0x%02X\n",
			     apdu.sw1, apdu.sw2);
		}
		/* this is last ditch cleanup */
		msc_delete_object(card, outputId, 0);
		LOG_FUNC_RETURN(card->ctx, r);
	}
	/* this is last ditch cleanup */
	msc_delete_object(card, outputId, 0);

	LOG_FUNC_RETURN(card->ctx, SC_ERROR_CARD_CMD_FAILED);
}
#undef CPYVAL
