/*
 *  Copyright (c) 2005-2026, Karsten Ohme
 *  This file is part of GlobalPlatform.
 *
 *  GlobalPlatform 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 3 of the License, or
 *  (at your option) any later version.
 *
 *  GlobalPlatform 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 GlobalPlatform.  If not, see <https://www.gnu.org/licenses/>.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations
 * including the two.
 * You must obey the GNU Lesser General Public License in all respects
 * for all of the code used other than OpenSSL.  If you modify
 * file(s) with this exception, you may extend this exception to your
 * version of the file(s), but you are not obligated to do so.  If you
 * do not wish to do so, delete this exception statement from your
 * version.
 */

/*! \mainpage GlobalPlatform Library
 *
 * \author Karsten Ohme
 * \section intro_sec Introduction
 *
 * This library provides C APIs for managing OpenPlatform 2.0.1' and GlobalPlatform smart cards.
 * It implements GlobalPlatform 2.1.1 APIs and supports SCP01, SCP02, SCP03, and SCP11a secure
 * channels, delegated management, DAP verification, and PC/SC connection plugins.
 *
 * <h2>Note</h2>
 * <p>
 * Before you call a card related command make sure that the Issuer Security Domain
 * (CardManager for Open Platform 2.0.1') or Security Domain you
 * want to use for the command is selected by OPGP_select_application().
 * </p>
 * <h2>Unicode support</h2>
 * <p>
 * Obey that this library supports Unicode. If you develop an application you should use Unicode
 * strings. OPGP_STRING and OPGP_CSTRING are wrapping this for you. Use the <code>LPTSTR</code>, <code>TCHAR</code> and the <code>_T()</code> macro,
 * use Unicode functions and compile your application with the switches UNICODE and _UNICODE in your own code. Under Unixes
 * only ASCII is supported for now but to be portable use the mappings in globalplatform/unicode.h
 * </p>
 * @file
 * @brief This implements all Open- and GlobalPlatform functions.
 */
#include <stdbool.h>
#ifdef WIN32
#include "stdafx.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include "globalplatform/globalplatform.h"
#ifndef WIN32
#include <string.h>
#endif
#include <errno.h>
#include "globalplatform/debug.h"
#include "unzip/unzip.h"
#include "unzip/zip.h"
#include "util.h"
#include "crypto.h"
#include "scp.h"
#include "loadfile.h"

#define MAX_APDU_DATA_SIZE(secInfo) (get_max_apdu_data_size(secInfo))
#define GP211_SCP11_DEFAULT_CA_KLOC_KEY_IDENTIFIER 0x10

#ifndef MAX_PATH
#define MAX_PATH 257
#endif

static BYTE C_MACDerivationConstant[2] = {0x01, 0x01}; //!< Constant for C-MAC session key calculation.
static BYTE ENCDerivationConstant[2] = {0x01, 0x82};//!< Constant for encryption session key calculation.
static BYTE DEKDerivationConstant[2] = {0x01, 0x81};//!< Constant for data encryption session key calculation.
static BYTE R_MACDerivationConstant[2] = {0x01, 0x02};//!< Constant for R-MAC session key calculation.

#define CARD_DATA_APPLICATION_TAG_8 0x68
#define CARD_DATA_APPLICATION_TAG_7 0x67
#define CARD_DATA_APPLICATION_TAG_6 0x66
#define CARD_DATA_APPLICATION_TAG_5 0x65
#define CARD_DATA_APPLICATION_TAG_4 0x64
#define CARD_DATA_APPLICATION_TAG_3 0x63
#define CARD_DATA_APPLICATION_TAG_0 0x60
#define OID_TAG 0x06

static DWORD get_max_apdu_data_size(GP211_SECURITY_INFO *secInfo);

static void extract_oid_version(const char *oid, char *out, size_t outLength) {
	size_t len;
	size_t i;
	int dotCount = 0;
	const char *start = oid;
	size_t copyLength;

	if (out == NULL || outLength == 0) {
		return;
	}
	out[0] = '\0';
	if (oid == NULL || oid[0] == '\0') {
		return;
	}

	len = strlen(oid);
	for (i = len; i > 0; i--) {
		if (oid[i - 1] == '.') {
			dotCount++;
			if (dotCount == 3) {
				start = &oid[i];
				break;
			}
		}
	}

	copyLength = strlen(start);
	if (copyLength >= outLength) {
		copyLength = outLength - 1;
	}
	memcpy(out, start, copyLength);
	out[copyLength] = '\0';
}

static LONG parse_optional_oid_and_data(const TLV *sourceTlv,
					char *oidBuffer, DWORD oidBufferLength, PDWORD oidLength,
					PBYTE dataBuffer, DWORD dataBufferLength, PDWORD dataLength) {
	DWORD unknownOffset = 0;
	DWORD unknownLength = 0;
	LONG innerResult = -1;
	LONG oidStringLength = -1;
	TLV innerTlv;
	PBYTE allocated = NULL;
	PBYTE oidValue = NULL;

	if (sourceTlv == NULL || oidBuffer == NULL || oidLength == NULL || dataBuffer == NULL || dataLength == NULL) {
		return -1;
	}

	*oidLength = 0;
	if (oidBufferLength > 0) {
		oidBuffer[0] = '\0';
	}
	*dataLength = 0;

	if (sourceTlv->length == 0) {
		return 0;
	}

	if (sourceTlv->length > sizeof(innerTlv.value)) {
		allocated = (PBYTE)malloc(sourceTlv->length);
		if (allocated != NULL) {
			innerTlv.extendedValue = allocated;
			innerResult = read_TLV(sourceTlv->value, sourceTlv->length, &innerTlv);
		}
	} else {
		innerTlv.extendedValue = NULL;
		innerResult = read_TLV(sourceTlv->value, sourceTlv->length, &innerTlv);
	}

	if (innerResult != -1 && innerTlv.tag == OID_TAG) {
		oidValue = (innerTlv.length > sizeof(innerTlv.value)) ? innerTlv.extendedValue : innerTlv.value;
		oidStringLength = parse_OID_numeric_string(oidValue, innerTlv.length, oidBuffer, oidBufferLength);
		if (oidStringLength == -1) {
			if (allocated != NULL) {
				free(allocated);
			}
			return -1;
		}
		*oidLength = (DWORD)oidStringLength;
		unknownOffset = (DWORD)innerResult;
	}

	if (allocated != NULL) {
		free(allocated);
	}

	unknownLength = (sourceTlv->length > unknownOffset) ? (sourceTlv->length - unknownOffset) : 0;
	memcpy(dataBuffer, sourceTlv->value + unknownOffset, min(unknownLength, dataBufferLength));
	*dataLength = min(unknownLength, dataBufferLength);
	return 0;
}

static int parse_oid_subidentifier(const BYTE *oid, DWORD oidLength, DWORD *offset,
		DWORD *value, DWORD *encodedStart, DWORD *encodedLength) {
	DWORD idx;
	DWORD v = 0;
	DWORD start;

	if (oid == NULL || offset == NULL || value == NULL || *offset >= oidLength) {
		return -1;
	}

	start = *offset;
	idx = *offset;
	while (idx < oidLength) {
		BYTE b = oid[idx++];
		if (v > (0xFFFFFFFFu >> 7)) {
			return -1;
		}
		v = (v << 7) | (DWORD)(b & 0x7F);
		if ((b & 0x80) == 0) {
			*offset = idx;
			*value = v;
			if (encodedStart != NULL) {
				*encodedStart = start;
			}
			if (encodedLength != NULL) {
				*encodedLength = idx - start;
			}
			return 0;
		}
	}

	return -1;
}

static int parse_card_data_scp_oid(const BYTE *oid, DWORD oidLength,
		BYTE *scp, BYTE *scpImpl, DWORD *scpImplValue,
		BYTE *scpImplOidBytes, BYTE *scpImplOidBytesLength) {
	DWORD offset = 0;
	DWORD value = 0;
	DWORD implStart = 0;
	DWORD implLength = 0;
	DWORD copyLength = 0;

	if (oid == NULL || scp == NULL || scpImpl == NULL || scpImplValue == NULL
			|| scpImplOidBytes == NULL || scpImplOidBytesLength == NULL || oidLength == 0) {
		return -1;
	}

	/* First byte encodes arcs 1.2 and must be 0x2A for GlobalPlatform OIDs used here. */
	if (oid[0] != 0x2A) {
		return -1;
	}
	offset = 1;

	if (parse_oid_subidentifier(oid, oidLength, &offset, &value, NULL, NULL) != 0 || value != 840) {
		return -1;
	}
	if (parse_oid_subidentifier(oid, oidLength, &offset, &value, NULL, NULL) != 0 || value != 114283) {
		return -1;
	}
	if (parse_oid_subidentifier(oid, oidLength, &offset, &value, NULL, NULL) != 0 || value != 4) {
		return -1;
	}

	/* SCP identifier subidentifier. */
	if (parse_oid_subidentifier(oid, oidLength, &offset, &value, NULL, NULL) != 0 || value > 0xFF) {
		return -1;
	}
	*scp = (BYTE)value;

	/* SCP implementation option subidentifier (can be multi-byte in BER). */
	if (parse_oid_subidentifier(oid, oidLength, &offset, &value, &implStart, &implLength) != 0 || implLength == 0) {
		return -1;
	}
	if (offset != oidLength) {
		return -1;
	}

	*scpImplValue = value;
	*scpImpl = oid[implStart + implLength - 1];

	copyLength = min(implLength, (DWORD)GP211_MAX_CARD_RECOGNITION_SCP_IMPL_OID_BYTES);
	memcpy(scpImplOidBytes, oid + implStart, copyLength);
	*scpImplOidBytesLength = (BYTE)copyLength;
	return 0;
}

static int parse_card_capability_scp_info(const TLV *sourceTlv,
		GP211_CARD_CAPABILITY_INFORMATION *cardCapabilityInfo) {
	DWORD offset = 0;
	LONG result;
	TLV innerTlv;
	GP211_SCP_INFORMATION *scpInfo;

	if (sourceTlv == NULL || cardCapabilityInfo == NULL) {
		return -1;
	}

	if (cardCapabilityInfo->scpInformationLength >= GP211_MAX_CARD_CAPABILITY_SCP_INFOS) {
		return 0;
	}

	scpInfo = &cardCapabilityInfo->scpInformation[cardCapabilityInfo->scpInformationLength];
	memset(scpInfo, 0, sizeof(*scpInfo));

	while (offset < sourceTlv->length) {
		result = read_TLV(sourceTlv->value + offset, sourceTlv->length - offset, &innerTlv);
		if (result == -1) {
			return -1;
		}
		switch (innerTlv.tag) {
			case 0x80:
				if (innerTlv.length > 0) {
					scpInfo->scpIdentifier = innerTlv.value[0];
				}
				break;
			case 0x81:
				scpInfo->scpOptionsLength = min(innerTlv.length, (DWORD)sizeof(scpInfo->scpOptions));
				memcpy(scpInfo->scpOptions, innerTlv.value, scpInfo->scpOptionsLength);
				break;
			case 0x91:
				scpInfo->scpOptionsMaskLength = min(innerTlv.length, (DWORD)sizeof(scpInfo->scpOptionsMask));
				memcpy(scpInfo->scpOptionsMask, innerTlv.value, scpInfo->scpOptionsMaskLength);
				break;
			case 0x82:
				if (innerTlv.length > 0) {
					scpInfo->supportedKeySizes = innerTlv.value[0];
				}
				break;
			case 0x83:
				scpInfo->tlsCipherSuitesLength = min(innerTlv.length, (DWORD)sizeof(scpInfo->tlsCipherSuites));
				memcpy(scpInfo->tlsCipherSuites, innerTlv.value, scpInfo->tlsCipherSuitesLength);
				break;
			case 0x84:
				if (innerTlv.length > 0) {
					scpInfo->maxPskLength = innerTlv.value[0];
				}
				break;
		}
		offset += innerTlv.tlvLength;
	}

	cardCapabilityInfo->scpInformationLength++;
	return 0;
}

OPGP_NO_API
OPGP_ERROR_STATUS calculate_install_token(BYTE P1, PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength,
							 PBYTE executableModuleAID,
							 DWORD executableModuleAIDLength, PBYTE applicationAID, DWORD applicationAIDLength,
							 DWORD applicationPrivileges, DWORD volatileDataSpaceLimit, DWORD nonVolatileDataSpaceLimit,
							 PBYTE installParameters, DWORD installParametersLength,
							 PBYTE sdParameters, DWORD sdParametersLength,
							 PBYTE uiccSystemSpecParams, DWORD uiccSystemSpecParamsLength,
							 PBYTE simSpecParams, DWORD simSpecParamsLength,
							 PBYTE installToken, PDWORD installTokenLength,
							 OPGP_STRING PEMKeyFileName, char *passPhrase);


OPGP_NO_API
OPGP_ERROR_STATUS put_asymmetric_keys(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
									  BYTE keySetVersion, BYTE keyIndex, BYTE newKeySetVersion,
									  OPGP_STRING PEMKeyFileName, char *passPhrase,
									  BYTE keyType, BOOL includeEccDomainParameters);

OPGP_NO_API
OPGP_ERROR_STATUS put_delegated_management_receipt_keys(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
                                                        BYTE keySetVersion, BYTE newKeySetVersion,
                                                        BYTE receiptKey[32], DWORD keyLength, BYTE receiptKeyType);

OPGP_NO_API
OPGP_ERROR_STATUS put_3des_key(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
				  BYTE keySetVersion, BYTE keyIndex, BYTE newKeySetVersion, BYTE _3DESKey[16]);

OPGP_NO_API
OPGP_ERROR_STATUS put_secure_channel_keys(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
							 BYTE keySetVersion,
							 BYTE newKeySetVersion,
							 PBYTE newBaseKey,
							 PBYTE newS_ENC,
							 PBYTE newS_MAC, PBYTE newDEK, DWORD keyLength, BYTE keyType);

OPGP_NO_API
OPGP_ERROR_STATUS delete_key(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo, BYTE keySetVersion, BYTE keyIndex);

OPGP_NO_API
OPGP_ERROR_STATUS delete_application(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
				   OPGP_AID *AIDs, DWORD AIDsLength, GP211_RECEIPT_DATA *receiptData, PDWORD receiptDataAvailable,
				   PBYTE deleteToken, DWORD deleteTokenLength, DWORD mode);

OPGP_NO_API
OPGP_ERROR_STATUS get_data(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
			  BYTE identifier[2], PBYTE recvBuffer, PDWORD recvBufferLength);

OPGP_NO_API
OPGP_ERROR_STATUS get_data_with_data(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
			  BYTE identifier[2], PBYTE dataObject, DWORD dataObjectLength,
			  PBYTE recvBuffer, PDWORD recvBufferLength);

OPGP_NO_API
OPGP_ERROR_STATUS put_data(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo, BYTE identifier[2], PBYTE dataObject, DWORD dataObjectLength);

OPGP_NO_API
OPGP_ERROR_STATUS get_key_information_templates(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
								   BYTE keyInformationTemplate,
								   GP211_KEY_INFORMATION *keyInformation, PDWORD keyInformationLength);

OPGP_NO_API
OPGP_ERROR_STATUS get_extended_card_resources_information(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
		OPGP_EXTENDED_CARD_RESOURCE_INFORMATION *extendedCardResourceInformation);

OPGP_NO_API
OPGP_ERROR_STATUS set_status(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo, BYTE statusType, PBYTE AID, DWORD AIDLength, BYTE lifeCycleState);

OPGP_NO_API
OPGP_ERROR_STATUS load(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
				 GP211_DAP_BLOCK *loadFileDataBlockSignature, DWORD loadFileDataBlockSignatureLength,
				 OPGP_STRING executableLoadFileName,
				 GP211_RECEIPT_DATA *receiptData, PDWORD receiptDataAvailable, OPGP_PROGRESS_CALLBACK *callback);


OPGP_NO_API
OPGP_ERROR_STATUS install_for_install_and_make_selectable(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
						 PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength, PBYTE executableModuleAID,
						 DWORD executableModuleAIDLength, PBYTE applicationAID,
						 DWORD applicationAIDLength, DWORD applicationPrivileges,
						 DWORD volatileDataSpaceLimit, DWORD nonVolatileDataSpaceLimit,
						 PBYTE installParameters, DWORD installParametersLength,
						 PBYTE sdParameters, DWORD sdParametersLength,
						 PBYTE uiccSystemSpecParams, DWORD uiccSystemSpecParamsLength,
						 PBYTE simSpecParams, DWORD simSpecParamsLength,
						 PBYTE installToken, DWORD installTokenLength,
						 GP211_RECEIPT_DATA *receiptData,
						 PDWORD receiptDataAvailable);

OPGP_NO_API
OPGP_ERROR_STATUS install_for_load(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
					  PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength, PBYTE securityDomainAID,
					  DWORD securityDomainAIDLength, PBYTE loadFileDataBlockHash, DWORD loadFileDataBlockHashLength,
					  PBYTE loadToken, DWORD loadTokenLength,
					  DWORD nonVolatileCodeSpaceLimit, DWORD volatileDataSpaceLimit,
					  DWORD nonVolatileDataSpaceLimit);

OPGP_NO_API
OPGP_ERROR_STATUS install_for_install(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
						 PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength,
						 PBYTE executableModuleAID,
						 DWORD executableModuleAIDLength, PBYTE applicationAID,
						 DWORD applicationAIDLength, DWORD applicationPrivileges,
						 DWORD volatileDataSpaceLimit, DWORD nonVolatileDataSpaceLimit,
						 PBYTE installParameters, DWORD installParametersLength,
						 PBYTE sdParameters, DWORD sdParametersLength,
						 PBYTE uiccSystemSpecParams, DWORD uiccSystemSpecParamsLength,
						 PBYTE simSpecParams, DWORD simSpecParamsLength,
						 PBYTE installToken, DWORD installTokenLength,
						 GP211_RECEIPT_DATA *receiptData, PDWORD receiptDataAvailable);

OPGP_NO_API
OPGP_ERROR_STATUS install_for_make_selectable(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
								 PBYTE applicationAID,
								 DWORD applicationAIDLength, DWORD applicationPrivileges,
								 PBYTE installToken, DWORD installTokenLength,
								 GP211_RECEIPT_DATA *receiptData,
								 PDWORD receiptDataAvailable);


OPGP_NO_API
OPGP_ERROR_STATUS pin_change(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo, BYTE tryLimit,
				PBYTE newPIN, DWORD newPINLength);

OPGP_NO_API
OPGP_ERROR_STATUS mutual_authentication(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, PBYTE baseKeyOrStaticOcePrivateKey,
					   PBYTE S_ENC, PBYTE S_MAC,
					   PBYTE DEK, DWORD keyLength, BYTE keySetVersion,
					   BYTE keyIndex, BYTE secureChannelProtocol,
					   BYTE secureChannelProtocolImpl, BYTE securityLevel,
					   BYTE derivationMethod,
					   PBYTE certOceEcka, DWORD certOceEckaLength,
					   PBYTE sdPublicKeyOverride,
					   DWORD sdPublicKeyOverrideLength,
					   OPGP_STRING sdPublicKeyFileName,
					   GP211_SECURITY_INFO *secInfo);

static OPGP_ERROR_STATUS mutual_authentication_scp11a(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo,
						   PBYTE staticOcePrivateKey,
						   DWORD keyLength,
					   BYTE keySetVersion,
					   BYTE keyIndex,
					   BYTE scpParameters,
					   BYTE securityLevel,
					   PBYTE certOceEcka,
						   DWORD certOceEckaLength,
					   PBYTE sdPublicKeyOverride,
						   DWORD sdPublicKeyOverrideLength,
						   OPGP_STRING sdPublicKeyFileName,
						   GP211_SECURITY_INFO *secInfo);

static OPGP_ERROR_STATUS send_chained_data(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo,
								  GP211_SECURITY_INFO *secInfo, BYTE *sendBuffer, DWORD offset,
								  BYTE *data, DWORD dataLength,
								  BYTE *recvBuffer, PDWORD recvBufferLength,
								  BOOL invertedChaining, BOOL incrementBlockNumber,
								  BOOL responseDataExpected, BOOL allowExtendedApdu);

OPGP_NO_API
OPGP_ERROR_STATUS get_install_data(BYTE P1, PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength, PBYTE executableModuleAID,
										  DWORD executableModuleAIDLength, PBYTE applicationAID,
										  DWORD applicationAIDLength, DWORD applicationPrivileges,
									  DWORD volatileDataSpaceLimit, DWORD nonVolatileDataSpaceLimit,
									  PBYTE installParameters, DWORD installParametersLength,
									  PBYTE sdParameters, DWORD sdParametersLength,
										  PBYTE uiccSystemSpecParams, DWORD uiccSystemSpecParamsLength,
										  PBYTE simSpecParams, DWORD simSpecParamsLength,
										  PBYTE installData, PDWORD installDataLength);

OPGP_NO_API
OPGP_ERROR_STATUS load_from_buffer(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
					 GP211_DAP_BLOCK *loadFileDataBlockSignature, DWORD loadFileDataBlockSignatureLength,
					 PBYTE loadFileBuf, DWORD loadFileBufSize,
				 GP211_RECEIPT_DATA *receiptData, PDWORD receiptDataAvailable, OPGP_PROGRESS_CALLBACK *callback);

OPGP_NO_API
OPGP_ERROR_STATUS VISA2_derive_keys_get_data(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo, PBYTE AID, DWORD AIDLength, PBYTE masterKey,
							PBYTE S_ENC, PBYTE S_MAC, PBYTE DEK);

OPGP_NO_API
OPGP_ERROR_STATUS VISA1_derive_keys_get_data(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo, PBYTE masterKey,
							PBYTE S_ENC, PBYTE S_MAC, PBYTE DEK);

OPGP_NO_API
OPGP_ERROR_STATUS EMV_CPS11_derive_keys_get_data(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo, PBYTE masterKey,
							PBYTE S_ENC, PBYTE S_MAC, PBYTE DEK);


/**
 * Macro to check for the status word 9000, otherwise the status is set to the error and a jump to the end mark takes place.
 */
#define CHECK_SW_9000(recvBuffer, recvBufferLength, status) if (recvBuffer[recvBufferLength-2] != 0x90 || recvBuffer[recvBufferLength-1] != 0x00) {\
	OPGP_ERROR_CREATE_ERROR(status, status.errorCode, OPGP_stringify_error(status.errorCode)); \
	goto end; \
}

OPGP_NO_API
void mapOP201ToGP211SecurityInfo(OP201_SECURITY_INFO op201secInfo,
										GP211_SECURITY_INFO *gp211secInfo) {
	if (gp211secInfo == NULL)
		return;
	memcpy(gp211secInfo->C_MACSessionKey, op201secInfo.sessionMacKey, 16);
	memcpy(gp211secInfo->lastC_MAC, op201secInfo.lastMac, 8);
	memcpy(gp211secInfo->encryptionSessionKey, op201secInfo.sessionEncKey, 16);
	switch (op201secInfo.securityLevel) {
		case OP201_SECURITY_LEVEL_ENC_MAC:
			gp211secInfo->securityLevel = GP211_SCP01_SECURITY_LEVEL_C_DEC_C_MAC;
			break;
		case OP201_SECURITY_LEVEL_PLAIN:
			gp211secInfo->securityLevel = GP211_SCP01_SECURITY_LEVEL_NO_SECURE_MESSAGING;
			break;
		case OP201_SECURITY_LEVEL_MAC:
			gp211secInfo->securityLevel = GP211_SCP01_SECURITY_LEVEL_C_MAC;
			break;
	}
	gp211secInfo->secureChannelProtocol = GP211_SCP01;
	gp211secInfo->secureChannelProtocolImpl = GP211_SCP01_IMPL_i05;
	/* Augusto: added two attributes for key information */
	gp211secInfo->keySetVersion = op201secInfo.keySetVersion;
	gp211secInfo->keyIndex = op201secInfo.keyIndex;
	/* end */

}

OPGP_NO_API
void mapGP211ToOP201SecurityInfo(GP211_SECURITY_INFO gp211secInfo,
										OP201_SECURITY_INFO *op201secInfo) {
	if (op201secInfo == NULL)
		return;
	memcpy(op201secInfo->sessionMacKey, gp211secInfo.C_MACSessionKey, 16);
	memcpy(op201secInfo->lastMac, gp211secInfo.lastC_MAC, 8);
	memcpy(op201secInfo->sessionEncKey, gp211secInfo.encryptionSessionKey, 16);
	switch (gp211secInfo.securityLevel) {
		case GP211_SCP01_SECURITY_LEVEL_C_DEC_C_MAC:
			op201secInfo->securityLevel = OP201_SECURITY_LEVEL_ENC_MAC;
			break;
		case GP211_SCP01_SECURITY_LEVEL_NO_SECURE_MESSAGING:
			op201secInfo->securityLevel = OP201_SECURITY_LEVEL_PLAIN;
			break;
		case GP211_SCP01_SECURITY_LEVEL_C_MAC:
			op201secInfo->securityLevel = OP201_SECURITY_LEVEL_MAC;
			break;
	}

	/* Augusto: added two attributes for key information */
	op201secInfo->keySetVersion = gp211secInfo.keySetVersion;
	op201secInfo->keyIndex = gp211secInfo.keyIndex;
	/* end */
}

OPGP_NO_API
void mapOP201ToGP211DAPBlock(OP201_DAP_BLOCK op201dapBlock,
										GP211_DAP_BLOCK *gp211dapBlock) {
	if (gp211dapBlock == NULL)
		return;
	gp211dapBlock->securityDomainAIDLength = op201dapBlock.securityDomainAIDLength;
	memcpy(gp211dapBlock->securityDomainAID, op201dapBlock.securityDomainAID, op201dapBlock.securityDomainAIDLength);
	gp211dapBlock->signatureLength = op201dapBlock.signatureLength;
	memcpy(gp211dapBlock->signature, op201dapBlock.signature, op201dapBlock.signatureLength);
}

OPGP_NO_API
void mapOP201ToGP211ReceiptData(OP201_RECEIPT_DATA op201receiptData,
									   GP211_RECEIPT_DATA *gp211receiptData) {
	DWORD copyLength;
	if (gp211receiptData == NULL)
		return;
	memset(gp211receiptData, 0, sizeof(*gp211receiptData));

	copyLength = op201receiptData.cardUniqueDataLength;
	if (copyLength > sizeof(gp211receiptData->cardUniqueData)) {
		copyLength = sizeof(gp211receiptData->cardUniqueData);
	}
	gp211receiptData->cardUniqueDataLength = (BYTE)copyLength;
	memcpy(gp211receiptData->cardUniqueData, op201receiptData.cardUniqueData, copyLength);

	copyLength = op201receiptData.confirmationCounterLength;
	if (copyLength > sizeof(gp211receiptData->confirmationCounter)) {
		copyLength = sizeof(gp211receiptData->confirmationCounter);
	}
	gp211receiptData->confirmationCounterLength = (BYTE)copyLength;
	memcpy(gp211receiptData->confirmationCounter, op201receiptData.confirmationCounter, copyLength);

	copyLength = op201receiptData.receiptLength;
	if (copyLength > sizeof(gp211receiptData->receipt)) {
		copyLength = sizeof(gp211receiptData->receipt);
	}
	gp211receiptData->receiptLength = (BYTE)copyLength;
	memcpy(gp211receiptData->receipt, op201receiptData.receipt, copyLength);
}

OPGP_NO_API
void mapGP211ToOP201ReceiptData(GP211_RECEIPT_DATA gp211receiptData,
										OP201_RECEIPT_DATA *op201receiptData) {
	DWORD copyLength;
	if (op201receiptData == NULL)
		return;
	memset(op201receiptData, 0, sizeof(*op201receiptData));

	copyLength = gp211receiptData.cardUniqueDataLength;
	if (copyLength > sizeof(op201receiptData->cardUniqueData)) {
		copyLength = sizeof(op201receiptData->cardUniqueData);
	}
	op201receiptData->cardUniqueDataLength = (BYTE)copyLength;
	memcpy(op201receiptData->cardUniqueData, gp211receiptData.cardUniqueData, copyLength);

	copyLength = gp211receiptData.confirmationCounterLength;
	if (copyLength > sizeof(op201receiptData->confirmationCounter)) {
		copyLength = sizeof(op201receiptData->confirmationCounter);
	}
	op201receiptData->confirmationCounterLength = (BYTE)copyLength;
	memcpy(op201receiptData->confirmationCounter, gp211receiptData.confirmationCounter, copyLength);

	copyLength = gp211receiptData.receiptLength;
	if (copyLength > sizeof(op201receiptData->receipt)) {
		copyLength = sizeof(op201receiptData->receipt);
	}
	op201receiptData->receiptLength = (BYTE)copyLength;
	memcpy(op201receiptData->receipt, gp211receiptData.receipt, copyLength);
}

OPGP_NO_API
void mapGP211ToOP201KeyInformation(GP211_KEY_INFORMATION gp211keyInformation,
										  OP201_KEY_INFORMATION *op201keyInformation) {
	BYTE k;
	if (op201keyInformation == NULL)
		return;
	op201keyInformation->keyIndex = gp211keyInformation.keyIndex;
	op201keyInformation->keySetVersion = gp211keyInformation.keySetVersion;
	op201keyInformation->numKeyComponents = gp211keyInformation.numKeyComponents;
	for (k = 0; k < gp211keyInformation.numKeyComponents; k++) {
		op201keyInformation->keyComponents[k].keyLength = (BYTE)gp211keyInformation.keyComponents[k].keyLength;
		op201keyInformation->keyComponents[k].keyType = gp211keyInformation.keyComponents[k].keyType;
	}
}

OPGP_NO_API
void mapGP211ToOP201ApplicationData(GP211_APPLICATION_DATA gp211applData,
										   OP201_APPLICATION_DATA *op201applData) {
	if (op201applData == NULL)
		return;
	op201applData->aid.AIDLength = gp211applData.aid.AIDLength;
	memcpy(op201applData->aid.AID, gp211applData.aid.AID, gp211applData.aid.AIDLength);
	op201applData->lifeCycleState = gp211applData.lifeCycleState;
	op201applData->privileges = gp211applData.privileges >> 16;
}


/**
 * Reads a DAP block and parses it to the buffer buf.
 * \param buf [out] The buffer.
 * \param bufLength [in, out] The length of the buffer and the returned data.
 * \param dapBlock [in] The Load File Data Block DAP block.
 * \return OPGP_ERROR_SUCCESS if no error, error code else
 */
OPGP_NO_API
OPGP_ERROR_STATUS readDAPBlock(PBYTE buf, PDWORD bufLength, OP201_DAP_BLOCK dapBlock) {
	OPGP_ERROR_STATUS status;
	GP211_DAP_BLOCK gp211dapBlock;
	mapOP201ToGP211DAPBlock(dapBlock, &gp211dapBlock);
	status = read_load_file_data_block_signature(buf, bufLength, gp211dapBlock);
	return status;
}

OPGP_NO_API
OPGP_ERROR_STATUS parse_application_data(PBYTE data, DWORD dataLength,
		BYTE cardElement, BYTE format, GP211_APPLICATION_DATA *applData, PDWORD dataRead) {
	OPGP_ERROR_STATUS status;
	DWORD offset = 0;
	if (format == GP211_STATUS_FORMAT_DEPRECATED) {
		if (dataLength < 1) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			goto end;
		}
		applData->aid.AIDLength = data[offset++];
		// check also buffer overrun for AID
		if ((applData->aid.AIDLength > dataLength - offset) || applData->aid.AIDLength > sizeof(applData->aid.AID)) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			goto end;
		}
		memcpy(applData->aid.AID, data+offset, applData->aid.AIDLength);
		offset+=applData->aid.AIDLength;

		if (dataLength - offset < 1){
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			goto end;
		}
		applData->lifeCycleState = data[offset++];
		if (cardElement != GP211_STATUS_LOAD_FILES) {
			if (dataLength - offset < 1){
				OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
				goto end;
			}
			applData->privileges = data[offset++] << 16;
		}
		else {
			applData->privileges = 0x00;
			offset++;
		}
		applData->associatedSecurityDomainAID.AIDLength = 0;
	}
	else {
		DWORD result;
		TLV tlv1, tlv2;
		// read outer tag, must be 0xE3
		result = read_TLV(data, dataLength, &tlv1);
		if (result == -1 || tlv1.tag != 0xE3) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			goto end;
		}

		applData->associatedSecurityDomainAID.AIDLength = 0;
		memset(applData->versionNumber, 0, 2);
		// undocumented tags seen on some cards, use a loop to skip unknown tags
		while (offset < tlv1.length) {
			DWORD versionNumberSize;
			BYTE privBytes[4];
			result = read_TLV(tlv1.value+offset, tlv1.length, &tlv2);
			if (result == -1) {
				OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
				goto end;
			}
			switch (tlv2.tag) {
				case 0x4F:
					// check also buffer overrun for AID
					if (tlv2.length > sizeof(applData->aid.AID)) {
						OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
						goto end;
					}
					applData->aid.AIDLength = tlv2.length;
					memcpy(applData->aid.AID, tlv2.value, applData->aid.AIDLength);
					break;
				case 0x9f70:
					applData->lifeCycleState = tlv2.value[0];
					break;
				case 0xC5:
					// C5 privileges
					if (tlv2.length != 3 && tlv2.length != 1) {
						OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
						goto end;
					}
					if (tlv2.length == 3) {
						privBytes[0] = 0;
						memcpy(privBytes+1, tlv2.value, 3);
						applData->privileges = get_int(privBytes, 0);
					}
					else {
						applData->privileges = tlv2.value[0] << 16;
					}
					// +tag + length + data length
					break;
				case 0xCC:
					memcpy(applData->associatedSecurityDomainAID.AID, tlv2.value, tlv2.length);
					applData->associatedSecurityDomainAID.AIDLength = tlv2.length;
					break;
				case 0xCE:
					versionNumberSize = tlv2.length;
					memset(applData->versionNumber, 0, sizeof(applData->versionNumber));
					if (sizeof(applData->versionNumber) < versionNumberSize) {
						versionNumberSize = sizeof(applData->versionNumber);
					}
					memcpy(applData->versionNumber +
							(sizeof(applData->versionNumber) - versionNumberSize),
							tlv2.value, versionNumberSize);
					break;

				// remaining tags not supported
			}
			offset += tlv2.tlvLength;
		}
		offset = tlv1.tlvLength;
	}
	*dataRead = offset;
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	return status;
}

OPGP_NO_API
OPGP_ERROR_STATUS parse_executable_load_file_data(PBYTE data, DWORD dataLength,
		BYTE format, GP211_EXECUTABLE_MODULES_DATA *modulesData, PDWORD dataRead) {
	OPGP_ERROR_STATUS status;
	DWORD offset = 0;
	if (format == GP211_STATUS_FORMAT_DEPRECATED) {
		if (dataLength < 1) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			goto end;
		}
		modulesData->aid.AIDLength = data[offset++];
		// check also buffer overrun for AID
		if ((modulesData->aid.AIDLength > dataLength - offset) || modulesData->aid.AIDLength > sizeof(modulesData->aid.AID)) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			goto end;
		}
		memcpy(modulesData->aid.AID, data+offset, modulesData->aid.AIDLength);
		offset+=modulesData->aid.AIDLength;

		if (dataLength - offset < 1){
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			goto end;
		}
		modulesData->lifeCycleState = data[offset++];
		/* Ignore Application Privileges */
		if (dataLength - offset < 1){
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			goto end;
		}
		offset++;

		/* Number of associated Executable Modules */
		if (dataLength - offset < 1){
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			goto end;
		}
		modulesData->numExecutableModules = data[offset++];
		for (int k=0; k<modulesData->numExecutableModules && (offset<dataLength) && k < (sizeof(modulesData->executableModules) / sizeof(OPGP_AID)); k++) {
			/* Length of Executable Module AID */
			modulesData->executableModules[k].AIDLength = data[offset++];
			// check also buffer overrun for AID
			if ((modulesData->executableModules[k].AIDLength > dataLength - offset) ||
					modulesData->executableModules[k].AIDLength > sizeof(modulesData->executableModules[k].AID)) {
				OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
				goto end;
			}
			memcpy(modulesData->executableModules[k].AID, data+offset, modulesData->executableModules[k].AIDLength);
			offset+=modulesData->executableModules[k].AIDLength;
		}
		modulesData->associatedSecurityDomainAID.AIDLength = 0;
	}
	else {
		int result;
		DWORD exLoadFileAidCount = 0;
		TLV tlv1, tlv2;
		DWORD numSupportedExLoadFileAid = (sizeof(modulesData->executableModules) / sizeof(OPGP_AID));
		// read outer tag, must be 0xE3
		result = read_TLV(data, dataLength, &tlv1);
		if (result == -1 || tlv1.tag != 0xE3) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			goto end;
		}

		modulesData->associatedSecurityDomainAID.AIDLength = 0;
		memset(modulesData->versionNumber, 0, 2);
		while (offset < tlv1.length) {
			DWORD versionNumberSize;
			result = read_TLV(tlv1.value+offset, tlv1.length, &tlv2);
			if (result == -1) {
				OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
				goto end;
			}
			switch (tlv2.tag) {
				case 0x4F:
					if (tlv2.length > sizeof(modulesData->aid.AID)) {
						OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
						goto end;
					}
					modulesData->aid.AIDLength = tlv2.length;
					memcpy(modulesData->aid.AID, tlv2.value, modulesData->aid.AIDLength);
					break;
				case 0x9F70:
					modulesData->lifeCycleState = tlv2.value[0];
					break;
				case 0xCC:
					memcpy(modulesData->associatedSecurityDomainAID.AID, tlv2.value, tlv2.length);
					modulesData->associatedSecurityDomainAID.AIDLength = tlv2.length;
					break;
			// CE - Executable Load File Version Number only 2 bytes supported
				case 0xCE:
					memset(modulesData->versionNumber, 0, sizeof(modulesData->versionNumber));
					versionNumberSize = tlv2.length;
					if (sizeof(modulesData->versionNumber) < versionNumberSize) {
						versionNumberSize = sizeof(modulesData->versionNumber);
					}
					memcpy(modulesData->versionNumber +
							(sizeof(modulesData->versionNumber) - versionNumberSize),
							tlv2.value, versionNumberSize);
					break;
				case 0x84:
					if (tlv2.length > sizeof(modulesData->executableModules[exLoadFileAidCount].AID)) {
						OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
						goto end;
					}
					if (exLoadFileAidCount < numSupportedExLoadFileAid) {
						modulesData->executableModules[exLoadFileAidCount].AIDLength = tlv2.length;
						memcpy(modulesData->executableModules[exLoadFileAidCount].AID, tlv2.value, modulesData->executableModules[exLoadFileAidCount].AIDLength);
					}
					exLoadFileAidCount++;
					break;
			}
			offset += tlv2.tlvLength;
		}
		modulesData->numExecutableModules = exLoadFileAidCount;
		// remaining tags not supported
		offset = tlv1.tlvLength;
	}
	*dataRead = offset;
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	return status;
}

/**
 * Reads a valid buffer containing a (delete, load, install) receipt and parses it in a GP211_RECEIPT_DATA.
 * \param buf [in] The buffer to parse.
 * \param bufLength [in] The length of the buffer.
 * \param receiptData [out] The receipt data.
 * \return The number of bytes which were consumed while parsing the buffer.
 */
static DWORD parse_receipt_length_ber(PBYTE buf, DWORD bufLength, PDWORD receiptLength) {
	if (bufLength < 1 || receiptLength == NULL) {
		return 0;
	}
	if (buf[0] <= 0x7F) {
		*receiptLength = buf[0];
		return 1;
	}
	if (buf[0] == 0x81) {
		if (bufLength < 2 || buf[1] < 0x80) {
			return 0;
		}
		*receiptLength = buf[1];
		return 2;
	}
	if (buf[0] == 0x82) {
		if (bufLength < 3) {
			return 0;
		}
		*receiptLength = (((DWORD)buf[1]) << 8) | buf[2];
		return 3;
	}
	return 0;
}

OPGP_NO_API
DWORD fillReceipt(PBYTE buf, DWORD bufLength, GP211_RECEIPT_DATA *receiptData) {
	DWORD offset = 0;
	DWORD receiptLength = 0;
	DWORD copyLength;
	DWORD lengthFieldSize;

	if (buf == NULL || receiptData == NULL) {
		return 0;
	}
	memset(receiptData, 0, sizeof(*receiptData));

	lengthFieldSize = parse_receipt_length_ber(buf, bufLength, &receiptLength);
	if (lengthFieldSize == 0) {
		return 0;
	}
	offset += lengthFieldSize;
	if (bufLength - offset < receiptLength) {
		return 0;
	}

	copyLength = receiptLength;
	if (copyLength > sizeof(receiptData->receipt)) {
		copyLength = sizeof(receiptData->receipt);
	}
	receiptData->receiptLength = (BYTE)copyLength;
	if (copyLength > 0) {
		memcpy(receiptData->receipt, buf + offset, copyLength);
	}
	offset += receiptLength;

	if (bufLength - offset < 1) {
		return 0;
	}
	copyLength = buf[offset++];
	if (copyLength == 0 || bufLength - offset < copyLength) {
		return 0;
	}
	receiptData->confirmationCounterLength = (BYTE)copyLength;
	if (receiptData->confirmationCounterLength > sizeof(receiptData->confirmationCounter)) {
		receiptData->confirmationCounterLength = sizeof(receiptData->confirmationCounter);
	}
	memcpy(receiptData->confirmationCounter, buf + offset, receiptData->confirmationCounterLength);
	offset += copyLength;

	if (bufLength - offset < 1) {
		return 0;
	}
	copyLength = buf[offset++];
	if (bufLength - offset < copyLength) {
		return 0;
	}
	receiptData->cardUniqueDataLength = (BYTE)copyLength;
	if (receiptData->cardUniqueDataLength > sizeof(receiptData->cardUniqueData)) {
		receiptData->cardUniqueDataLength = sizeof(receiptData->cardUniqueData);
	}
	if (receiptData->cardUniqueDataLength > 0) {
		memcpy(receiptData->cardUniqueData, buf + offset, receiptData->cardUniqueDataLength);
	}
	offset += copyLength;

	if (bufLength > offset) {
		receiptData->tokenIdentifierPresent = 1;
		copyLength = buf[offset++];
		if (bufLength - offset < copyLength) {
			return offset - 1;
		}
		receiptData->tokenIdentifierLength = (BYTE)copyLength;
		if (receiptData->tokenIdentifierLength > sizeof(receiptData->tokenIdentifier)) {
			receiptData->tokenIdentifierLength = sizeof(receiptData->tokenIdentifier);
		}
		if (receiptData->tokenIdentifierLength > 0) {
			memcpy(receiptData->tokenIdentifier, buf + offset, receiptData->tokenIdentifierLength);
		}
		offset += copyLength;
	}

	if (bufLength > offset) {
		receiptData->tokenDataDigestPresent = 1;
		copyLength = buf[offset++];
		if (bufLength - offset < copyLength) {
			return offset - 1;
		}
		receiptData->tokenDataDigestLength = (BYTE)copyLength;
		if (receiptData->tokenDataDigestLength > sizeof(receiptData->tokenDataDigest)) {
			receiptData->tokenDataDigestLength = sizeof(receiptData->tokenDataDigest);
		}
		if (receiptData->tokenDataDigestLength > 0) {
			memcpy(receiptData->tokenDataDigest, buf + offset, receiptData->tokenDataDigestLength);
		}
		offset += copyLength;
	}

	return offset;
}

OPGP_NO_API
OPGP_ERROR_STATUS parse_receipt_from_response(PBYTE recvBuffer, DWORD recvBufferLength,
											  GP211_RECEIPT_DATA *receiptData, PDWORD receiptDataAvailable) {
	OPGP_ERROR_STATUS status;
	DWORD consumed;
	DWORD receiptPayloadLength;
	DWORD outerPayloadLength = 0;
	DWORD outerPayloadLengthFieldSize;
	PBYTE receiptPayload;

	if (receiptDataAvailable != NULL) {
		*receiptDataAvailable = 0;
	}
	if (recvBuffer == NULL || recvBufferLength < 2) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		goto end;
	}
	/* If response starts with 00, no receipt data is available. */
	if (recvBuffer[0] == 0x00) {
		OPGP_ERROR_CREATE_NO_ERROR(status);
		goto end;
	}
	receiptPayloadLength = recvBufferLength - 2;
	if (receiptPayloadLength == 0) {
		OPGP_ERROR_CREATE_NO_ERROR(status);
		goto end;
	}
	receiptPayload = recvBuffer;
	/*
	 * Some cards prepend confirmation data with an outer BER length.
	 * Example: 15 || 10 || <16-byte receipt> || 02 || <counter> || 00
	 * where 0x15 is the outer confirmation-data length.
	 */
	outerPayloadLengthFieldSize = parse_receipt_length_ber(receiptPayload, receiptPayloadLength, &outerPayloadLength);
	if (outerPayloadLengthFieldSize > 0 &&
		outerPayloadLengthFieldSize + outerPayloadLength == receiptPayloadLength) {
		receiptPayload += outerPayloadLengthFieldSize;
		receiptPayloadLength = outerPayloadLength;
		if (receiptPayloadLength == 0) {
			OPGP_ERROR_CREATE_NO_ERROR(status);
			goto end;
		}
	}
	if (receiptData == NULL) {
		if (receiptDataAvailable != NULL) {
			*receiptDataAvailable = 1;
		}
		OPGP_ERROR_CREATE_NO_ERROR(status);
		goto end;
	}

	consumed = fillReceipt(receiptPayload, receiptPayloadLength, receiptData);
	if (consumed == 0) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		goto end;
	}
	if (receiptDataAvailable != NULL) {
		*receiptDataAvailable = 1;
	}
	OPGP_ERROR_CREATE_NO_ERROR(status);
end:
	return status;
}

/**
 * The secInfo pointer can also be null and so this function can be used for arbitrary cards.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param capdu [in] The command APDU.
 * \param capduLength [in] The length of the command APDU.
 * \param rapdu [out] The response APDU.
 * \param rapduLength [in, out] The length of the response APDU.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_send_APDU(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo, PBYTE capdu, DWORD capduLength, PBYTE rapdu, PDWORD rapduLength) {
	return OPGP_send_APDU(cardContext, cardInfo, secInfo, capdu, capduLength, rapdu, rapduLength);
}

/**
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure. If non-null, the secure channel state is reset after a successful select.
 * \param AID [in] The AID.
 * \param AIDLength [in] The length of the AID.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OPGP_select_application(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo, PBYTE AID, DWORD AIDLength) {
	OPGP_ERROR_STATUS status;
	DWORD recvBufferLength=256;
	BYTE recvBuffer[256];
	BYTE sendBuffer[256];
	DWORD sendBufferLength=256;
	DWORD i=0;
	OPGP_LOG_START(_T("select_application"));
	sendBuffer[i++] = 0x00;
	sendBuffer[i++] = 0xA4;
	sendBuffer[i++] = 0x04;
	sendBuffer[i++] = 0x00;
	sendBuffer[i++] = (BYTE)AIDLength;
	memcpy(sendBuffer+i, AID, AIDLength);
	i+=AIDLength;
	sendBufferLength = i;
	/* Le */
	sendBuffer[i++] = 0x00;
	status = OPGP_send_APDU(cardContext, cardInfo, NULL, sendBuffer, sendBufferLength, recvBuffer, &recvBufferLength);
	if ( OPGP_ERROR_CHECK(status) ) {
		goto end;
	}
	switch (status.errorCode) {
		case OPGP_ISO7816_ERROR_CONDITIONS_NOT_SATISFIED:
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ISO7816_ERROR_NOT_MULTI_SELECTABLE, OPGP_stringify_error(OPGP_ISO7816_ERROR_NOT_MULTI_SELECTABLE)); goto end; }
		case OPGP_ISO7816_ERROR_6999:
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ISO7816_ERROR_SELECTION_REJECTED, OPGP_stringify_error(OPGP_ISO7816_ERROR_SELECTION_REJECTED)); goto end; }
		case OPGP_ISO7816_ERROR_FUNC_NOT_SUPPORTED:
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ISO7816_ERROR_APPLET_NOT_SELECTABLE, OPGP_stringify_error(OPGP_ISO7816_ERROR_APPLET_NOT_SELECTABLE)); goto end; }
		case OPGP_ISO7816_ERROR_FILE_NOT_FOUND:
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ISO7816_ERROR_APPLET_NOT_FOUND, OPGP_stringify_error(OPGP_ISO7816_ERROR_APPLET_NOT_FOUND)); goto end; }
		case OPGP_ISO7816_ERROR_FILE_INVALIDATED:
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ISO7816_WARNING_CM_LOCKED, OPGP_stringify_error(OPGP_ISO7816_WARNING_CM_LOCKED)); goto end; }
	}
	CHECK_SW_9000(recvBuffer, recvBufferLength, status);

	if (secInfo != NULL) {
		memset(secInfo, 0, sizeof(*secInfo));
	}

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("select_application"), status);
	return status;
}

/**
 * A keySetVersion value of 0x00 adds a new key.
 * Any other value between 0x01 and 0x7f must match an existing key set version.
 * The new key set version defines the key set version a new key belongs to.
 * This can be the same key version or a new not existing key set version.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param keySetVersion [in] An existing key set version.
 * \param keyIndex [in] The position of the key in the key set version.
 * \param newKeySetVersion [in] The new key set version.
 * \param PEMKeyFileName [in] A PEM file name with the public asymmetric key.
 * \param *passPhrase [in] The passphrase. Must be an ASCII string.
 * \param keyType [in] The asymmetric key type (RSA or ECC).
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_put_asymmetric_keys(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
				 BYTE keySetVersion, BYTE keyIndex, BYTE newKeySetVersion,
				 OPGP_STRING PEMKeyFileName, char *passPhrase, BYTE keyType) {
	OPGP_ERROR_STATUS status;
	status = put_asymmetric_keys(cardContext, cardInfo, secInfo, keySetVersion, keyIndex,
								 newKeySetVersion, PEMKeyFileName, passPhrase, keyType, true);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	return status;
}

/**
 * Adds or replaces an RSA public key in a key set.
 * A keySetVersion value of 0x00 adds a new key. Any other value between 0x01
 * and 0x7f must match an existing key set version.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context().
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param keySetVersion [in] Existing key set version, or 0x00 to add a new key.
 * \param keyIndex [in] The position of the key in the key set version.
 * \param newKeySetVersion [in] The key set version assigned to the new key.
 * \param PEMKeyFileName [in] A PEM file name with the public RSA key.
 * \param passPhrase [in] The passphrase, or NULL if not needed.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS GP211_put_rsa_key(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
				 BYTE keySetVersion, BYTE keyIndex, BYTE newKeySetVersion,
				 OPGP_STRING PEMKeyFileName, char *passPhrase) {
	OPGP_ERROR_STATUS status;
	status = GP211_put_asymmetric_keys(cardContext, cardInfo, secInfo, keySetVersion, keyIndex,
									   newKeySetVersion, PEMKeyFileName, passPhrase, GP211_KEY_TYPE_RSA);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	return status;
}

/**
 * A keySetVersion value of 0x00 adds a new key.
 * Any other value between 0x01 and 0x7f must match an existing key set version.
 * The new key set version defines the key set version a new key belongs to.
 * This can be the same key version or a new not existing key set version.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param keySetVersion [in] An existing key set version.
 * \param keyIndex [in] The position of the key in the key set version.
 * \param newKeySetVersion [in] The new key set version.
 * \param PEMKeyFileName [in] A PEM file name with the public ECC key.
 * \param *passPhrase [in] The passphrase. Must be an ASCII string.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_put_ecc_key(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
				 BYTE keySetVersion, BYTE keyIndex, BYTE newKeySetVersion,
				 OPGP_STRING PEMKeyFileName, char *passPhrase) {
	OPGP_ERROR_STATUS status;
	status = GP211_put_asymmetric_keys(cardContext, cardInfo, secInfo, keySetVersion, keyIndex,
									   newKeySetVersion, PEMKeyFileName, passPhrase, GP211_KEY_TYPE_ECC);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	return status;
}

/**
 * Adds or replaces an ECC public key using a curve parameter reference instead of embedding the curve parameters.
 */
OPGP_ERROR_STATUS GP211_put_ecc_key_with_curve_parameter_reference(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo,
				 GP211_SECURITY_INFO *secInfo, BYTE keySetVersion, BYTE keyIndex, BYTE newKeySetVersion,
				 OPGP_STRING PEMKeyFileName, char *passPhrase) {
	OPGP_ERROR_STATUS status;
	status = put_asymmetric_keys(cardContext, cardInfo, secInfo, keySetVersion, keyIndex,
								 newKeySetVersion, PEMKeyFileName, passPhrase, GP211_KEY_TYPE_ECC, false);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	return status;
}

/**
 * Adds or replaces an ECC private key using a curve parameter reference.
 */
OPGP_ERROR_STATUS GP211_put_ecc_private_key_with_curve_parameter_reference(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo,
				 GP211_SECURITY_INFO *secInfo, BYTE keySetVersion, BYTE keyIndex, BYTE newKeySetVersion,
				 BYTE privateKey[32], DWORD privateKeyLength) {
	OPGP_ERROR_STATUS status;
	BYTE sendBuffer[64];
	BYTE recvBuffer[64];
	DWORD recvBufferLength = sizeof(recvBuffer);
	BYTE keyCheckValue[3];
	BYTE keyDataField[128];
	DWORD keyDataFieldLength = 0;
	BYTE keyParameterReference = GP211_KEY_TYPE_ECC_KEY_PARAMETER_REFERENCE_P256;
	DWORD i = 0;

	OPGP_LOG_START(_T("GP211_put_ecc_private_key_with_curve_parameter_reference"));

	if (privateKey == NULL || privateKeyLength != 32) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_WRONG_KEY_TYPE, OPGP_stringify_error(OPGP_ERROR_WRONG_KEY_TYPE));
		goto end;
	}

	sendBuffer[i++] = 0x80;
	sendBuffer[i++] = 0xD8;
	sendBuffer[i++] = keySetVersion;
	sendBuffer[i++] = keyIndex;
	sendBuffer[i++] = 0x00;
	sendBuffer[i++] = newKeySetVersion;

	keyDataFieldLength = sizeof(keyDataField);
	status = get_key_data_field(secInfo, privateKey, privateKeyLength,
			GP211_KEY_TYPE_ECC_PRIVATE, keyDataField, &keyDataFieldLength, keyCheckValue, false);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	{
		DWORD keyParameterDataFieldLength = sizeof(keyDataField) - keyDataFieldLength;
		status = get_key_data_field(secInfo, &keyParameterReference, sizeof(keyParameterReference),
				GP211_KEY_TYPE_ECC_KEY_PARAMETER_REFERENCE, keyDataField + keyDataFieldLength,
				&keyParameterDataFieldLength, keyCheckValue, true);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
		keyDataFieldLength += keyParameterDataFieldLength;
	}

	status = send_chained_data(cardContext, cardInfo, secInfo, sendBuffer, i,
							  keyDataField, keyDataFieldLength, recvBuffer, &recvBufferLength,
							  false, false, true, false);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	OPGP_ERROR_CREATE_NO_ERROR(status);

end:
	OPGP_LOG_END(_T("GP211_put_ecc_private_key_with_curve_parameter_reference"), status);
	return status;
}

static OPGP_ERROR_STATUS add_rsa_key_data(GP211_SECURITY_INFO *secInfo,
										   BYTE *rsa_modulus, DWORD rsa_modulus_length,
										   LONG rsa_exponent,
										   BYTE *sendBuffer, DWORD *sendBufferIndex,
										   BYTE *keyCheckValue) {
	OPGP_ERROR_STATUS status;
	BYTE rsa3[] = {3};
	BYTE rsa65537[] = {1,0,1};
	BYTE keyDataField[600];
	DWORD keyDataFieldLength = 600;

	// modulus

	status = get_key_data_field(secInfo, rsa_modulus, rsa_modulus_length,
		GP211_KEY_TYPE_RSA_PUB_N, keyDataField, &keyDataFieldLength, keyCheckValue, false);
	if (OPGP_ERROR_CHECK(status)) {
		return status;
	}
	memcpy(sendBuffer + *sendBufferIndex, keyDataField, keyDataFieldLength);
	*sendBufferIndex += keyDataFieldLength;

	// exponent
	keyDataFieldLength = 600;
	if (rsa_exponent == 3) {
		status = get_key_data_field(secInfo, rsa3, 1,
			   GP211_KEY_TYPE_RSA_PUB_E, keyDataField, &keyDataFieldLength, keyCheckValue, true);
		if (OPGP_ERROR_CHECK(status)) {
			return status;
		}
	} else if (rsa_exponent == 65537) {
		status = get_key_data_field(secInfo, rsa65537, 3,
			   GP211_KEY_TYPE_RSA_PUB_E, keyDataField, &keyDataFieldLength, keyCheckValue, true);
		if (OPGP_ERROR_CHECK(status)) {
			return status;
		}
	} else {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_WRONG_EXPONENT, OPGP_stringify_error(OPGP_ERROR_WRONG_EXPONENT));
		return status;
	}
	memcpy(sendBuffer + *sendBufferIndex, keyDataField, keyDataFieldLength);
	*sendBufferIndex += keyDataFieldLength;

	OPGP_ERROR_CREATE_NO_ERROR(status);
	return status;
}

static OPGP_ERROR_STATUS add_ecc_key_data(GP211_SECURITY_INFO *secInfo,
										   BYTE *ecc_public_point, DWORD ecc_public_point_length,
										   BYTE ecc_key_component_type, BYTE key_parameter_reference,
										   GP211_ECC_DOMAIN_PARAMETERS *domainParameters,
										   BOOL withEccParams,
										   BYTE *sendBuffer, DWORD *sendBufferIndex,
										   BYTE *keyCheckValue) {
	OPGP_ERROR_STATUS status;
	BYTE keyDataField[600];
	BYTE keyParameterReference[] = {key_parameter_reference};
	DWORD keyDataFieldLength = sizeof(keyDataField);

	if (ecc_key_component_type != GP211_KEY_TYPE_ECC_PUBLIC_OR_PRIVATE &&
			ecc_key_component_type != GP211_KEY_TYPE_ECC_SM2_PUBLIC_OR_PRIVATE) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_WRONG_KEY_TYPE, OPGP_stringify_error(OPGP_ERROR_WRONG_KEY_TYPE));
		return status;
	}

	status = get_key_data_field(secInfo, ecc_public_point, ecc_public_point_length,
		ecc_key_component_type, keyDataField, &keyDataFieldLength, keyCheckValue, false);
	if (OPGP_ERROR_CHECK(status)) {
		return status;
	}
	memcpy(sendBuffer + *sendBufferIndex, keyDataField, keyDataFieldLength);
	*sendBufferIndex += keyDataFieldLength;

	if (withEccParams) {
		BOOL withCofactor;
		if (domainParameters == NULL || domainParameters->fieldPLength == 0 || domainParameters->fieldALength == 0
				|| domainParameters->fieldBLength == 0 || domainParameters->generatorLength == 0 || domainParameters->orderLength == 0) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, _T("ECC domain parameters are missing"));
			return status;
		}
			withCofactor = domainParameters->cofactorLength > 0
				&& !(domainParameters->cofactorLength == 1 && domainParameters->cofactor[0] == 0x01);

		keyDataFieldLength = sizeof(keyDataField);
		status = get_key_data_field(secInfo, domainParameters->fieldP, domainParameters->fieldPLength,
			0xB2, keyDataField, &keyDataFieldLength, keyCheckValue, false);
		if (OPGP_ERROR_CHECK(status)) {
			return status;
		}
		memcpy(sendBuffer + *sendBufferIndex, keyDataField, keyDataFieldLength);
		*sendBufferIndex += keyDataFieldLength;

		keyDataFieldLength = sizeof(keyDataField);
		status = get_key_data_field(secInfo, domainParameters->fieldA, domainParameters->fieldALength,
			0xB3, keyDataField, &keyDataFieldLength, keyCheckValue, false);
		if (OPGP_ERROR_CHECK(status)) {
			return status;
		}
		memcpy(sendBuffer + *sendBufferIndex, keyDataField, keyDataFieldLength);
		*sendBufferIndex += keyDataFieldLength;

		keyDataFieldLength = sizeof(keyDataField);
		status = get_key_data_field(secInfo, domainParameters->fieldB, domainParameters->fieldBLength,
			0xB4, keyDataField, &keyDataFieldLength, keyCheckValue, false);
		if (OPGP_ERROR_CHECK(status)) {
			return status;
		}
		memcpy(sendBuffer + *sendBufferIndex, keyDataField, keyDataFieldLength);
		*sendBufferIndex += keyDataFieldLength;

		keyDataFieldLength = sizeof(keyDataField);
		status = get_key_data_field(secInfo, domainParameters->generator, domainParameters->generatorLength,
			0xB5, keyDataField, &keyDataFieldLength, keyCheckValue, false);
		if (OPGP_ERROR_CHECK(status)) {
			return status;
		}
		memcpy(sendBuffer + *sendBufferIndex, keyDataField, keyDataFieldLength);
		*sendBufferIndex += keyDataFieldLength;

		keyDataFieldLength = sizeof(keyDataField);
		status = get_key_data_field(secInfo, domainParameters->order, domainParameters->orderLength,
			0xB6, keyDataField, &keyDataFieldLength, keyCheckValue, !withCofactor);
		if (OPGP_ERROR_CHECK(status)) {
			return status;
		}
		memcpy(sendBuffer + *sendBufferIndex, keyDataField, keyDataFieldLength);
		*sendBufferIndex += keyDataFieldLength;

		if (withCofactor) {
			keyDataFieldLength = sizeof(keyDataField);
			status = get_key_data_field(secInfo, domainParameters->cofactor, domainParameters->cofactorLength,
				0xB7, keyDataField, &keyDataFieldLength, keyCheckValue, true);
			if (OPGP_ERROR_CHECK(status)) {
				return status;
			}
			memcpy(sendBuffer + *sendBufferIndex, keyDataField, keyDataFieldLength);
			*sendBufferIndex += keyDataFieldLength;
		}
	} else {
		keyDataFieldLength = sizeof(keyDataField);
		status = get_key_data_field(secInfo, keyParameterReference, sizeof(keyParameterReference),
			GP211_KEY_TYPE_ECC_KEY_PARAMETER_REFERENCE, keyDataField, &keyDataFieldLength, keyCheckValue, true);
		if (OPGP_ERROR_CHECK(status)) {
			return status;
		}
		memcpy(sendBuffer + *sendBufferIndex, keyDataField, keyDataFieldLength);
		*sendBufferIndex += keyDataFieldLength;
	}

	OPGP_ERROR_CREATE_NO_ERROR(status);
	return status;
}

static DWORD get_max_apdu_data_size(GP211_SECURITY_INFO *secInfo) {
	DWORD macSize = 8;
	DWORD blockSize = 8;
	BOOL encryption = 0;

	if (secInfo == NULL) {
		return 255;
	}

	if (secInfo->secureChannelProtocol == GP211_SCP03) {
		blockSize = 16;
		if (secInfo->securityLevel == GP211_SCP03_SECURITY_LEVEL_NO_SECURE_MESSAGING) {
			return 255;
		}
		if (secInfo->securityLevel & 0x02) {
			encryption = 1;
		}
	} else if (secInfo->secureChannelProtocol == GP211_SCP02) {
		if (secInfo->securityLevel == GP211_SCP02_SECURITY_LEVEL_NO_SECURE_MESSAGING) {
			return 255;
		}
		if (secInfo->securityLevel & 0x02) {
			encryption = 1;
		}
	} else if (secInfo->secureChannelProtocol == GP211_SCP01) {
		if (secInfo->securityLevel == GP211_SCP01_SECURITY_LEVEL_NO_SECURE_MESSAGING) {
			return 255;
		}
		if (secInfo->securityLevel & 0x02) {
			encryption = 1;
		}
	} else {
		return 255;
	}

	if (encryption) {
		/* Maximum data size is such that when adding padding and MAC, the total is <= 255.
		   paddingSize = ((blockSize - ((lc + 1) % blockSize)) % blockSize) + 1;
		   wrappedLength = lc + paddingSize + 8;
		   For blockSize=8 (SCP01/02): lc <= 239.
		   For blockSize=16 (SCP03): lc <= 231.
		*/
		return 255 - macSize - blockSize;
	} else {
		return 255 - macSize;
	}
}

static OPGP_ERROR_STATUS extract_case3_data(PBYTE apduData, DWORD apduDataLength,
									  PBYTE p1, PBYTE p2, PBYTE *payload, PDWORD payloadLength) {
	OPGP_ERROR_STATUS status;
	DWORD offset;
	DWORD lc;

	if (apduData == NULL || p1 == NULL || p2 == NULL || payload == NULL || payloadLength == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_UNRECOGNIZED_APDU_COMMAND, OPGP_stringify_error(OPGP_ERROR_UNRECOGNIZED_APDU_COMMAND));
		return status;
	}
	if (apduDataLength < 3) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_UNRECOGNIZED_APDU_COMMAND, OPGP_stringify_error(OPGP_ERROR_UNRECOGNIZED_APDU_COMMAND));
		return status;
	}

	*p1 = apduData[0];
	*p2 = apduData[1];
	if (apduData[2] == 0x00) {
		if (apduDataLength < 5) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_UNRECOGNIZED_APDU_COMMAND, OPGP_stringify_error(OPGP_ERROR_UNRECOGNIZED_APDU_COMMAND));
			return status;
		}
		offset = 5;
		lc = ((DWORD)apduData[3] << 8) | apduData[4];
	}
	else {
		offset = 3;
		lc = apduData[2];
	}

	if (apduDataLength != offset + lc) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_UNRECOGNIZED_APDU_COMMAND, OPGP_stringify_error(OPGP_ERROR_UNRECOGNIZED_APDU_COMMAND));
		return status;
	}
	*payload = apduData + offset;
	*payloadLength = lc;

	OPGP_ERROR_CREATE_NO_ERROR(status);
	return status;
}

static OPGP_ERROR_STATUS send_chained_data(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo,
								  GP211_SECURITY_INFO *secInfo, BYTE *sendBuffer, DWORD offset,
								  BYTE *data, DWORD dataLength,
								  BYTE *recvBuffer, PDWORD recvBufferLength,
								  BOOL invertedChaining, BOOL incrementBlockNumber,
								  BOOL responseDataExpected, BOOL allowExtendedApdu) {
	OPGP_ERROR_STATUS status;
	DWORD preambleLen = (offset > 5) ? (offset - 5) : 0;
	DWORD lastMaxFirst = MAX_APDU_DATA_SIZE(secInfo) - preambleLen;
	DWORD lastMaxOther = MAX_APDU_DATA_SIZE(secInfo);
	DWORD remaining = dataLength;
	DWORD count = 0;
	DWORD i;
	PBYTE *chunks = NULL;
	DWORD *chunkLens = NULL;
	BYTE baseCLA = sendBuffer[0];
	BYTE baseINS = sendBuffer[1];
	BYTE baseP1  = sendBuffer[2];
	BYTE baseP2  = sendBuffer[3];
	BYTE blockNumber = 0x00;

	// First pass: determine number of chunks
	{
		DWORD rem = remaining;
		DWORD idx = 0;
		DWORD curPreamble = preambleLen;
		for (;;) {
			DWORD nonLastCap = (idx == 0) ? (255 - curPreamble) : 255;
			DWORD lastCap = (idx == 0) ? lastMaxFirst : lastMaxOther;
			count++;
			if (rem <= lastCap) break;

			if (rem <= nonLastCap) {
				rem = 0;
			} else {
				rem -= nonLastCap;
			}
			idx++;
			curPreamble = 0;
		}
	}

	chunks = (PBYTE*)malloc(sizeof(PBYTE) * count);
	chunkLens = (DWORD*)malloc(sizeof(DWORD) * count);
	if (!chunks || !chunkLens) {
		if (chunks) free(chunks);
		if (chunkLens) free(chunkLens);
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
		return status;
	}

	// Second pass: build chunks
	{
		DWORD pos = 0;
		DWORD curPreamble = preambleLen;
		for (i = 0; i < count; ++i) {
			BOOL last;
			DWORD nonLastCap = (i == 0) ? (255 - curPreamble) : 255;
			DWORD lastCap = (i == 0) ? lastMaxFirst : lastMaxOther;
			DWORD dataChunkLen;
			last = (remaining <= lastCap);
			if (last) {
				dataChunkLen = remaining;
			} else {
				dataChunkLen = nonLastCap;
				if (dataChunkLen >= remaining) {
					dataChunkLen = remaining - 1;
				}
			}
			DWORD apduLen = 5 + curPreamble + dataChunkLen + ((last && responseDataExpected) ? 1 : 0);
			PBYTE apdu = (PBYTE)malloc(apduLen);
			if (!apdu) {
				for (DWORD j = 0; j < i; ++j) free(chunks[j]);
				free(chunks); free(chunkLens);
				OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
				return status;
			}
			// Header
			apdu[0] = baseCLA;
			apdu[1] = baseINS;
			{
				BYTE p1 = baseP1;
				if (invertedChaining) {
					if (!last) p1 &= 0x7F; else p1 |= 0x80;
				} else {
					if (!last) p1 |= 0x80; else p1 &= 0x7F;
				}
				apdu[2] = p1;
			}
			apdu[3] = incrementBlockNumber ? blockNumber++ : baseP2;
			apdu[4] = (BYTE)(curPreamble + dataChunkLen);
			// Payload
			if (curPreamble) {
				memcpy(apdu + 5, sendBuffer + 5, curPreamble);
			}
			memcpy(apdu + 5 + curPreamble, data + pos, dataChunkLen);
			pos += dataChunkLen;
			if (last && responseDataExpected) {
				apdu[5 + curPreamble + dataChunkLen] = 0x00;
			}
			chunks[i] = apdu;
			chunkLens[i] = apduLen;
			remaining -= dataChunkLen;
			curPreamble = 0; // only for first chunk
		}
	}

	// Use connection-level chained sender that wraps once and splits again after wrapping
	status = OPGP_send_chained_APDU_extended(cardContext, cardInfo, secInfo, chunks, chunkLens, count, recvBuffer, recvBufferLength, allowExtendedApdu);
	if ( OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	CHECK_SW_9000(recvBuffer, *recvBufferLength, status);
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	for (i = 0; i < count; ++i) free(chunks[i]);
	free(chunks);
	free(chunkLens);
	return status;
}

/**
 * A keySetVersion value of 0x00 adds a new key.
 * Any other value between 0x01 and 0x7f must match an existing key set version.
 * The new key set version defines the key set version a new key belongs to.
 * This can be the same key version or a new not existing key set version.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param keySetVersion [in] An existing key set version.
 * \param keyIndex [in] The position of the key in the key set version.
 * \param newKeySetVersion [in] The new key set version.
 * \param _3DESKey [in] The new 3DES key.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_put_3des_key(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
				  BYTE keySetVersion, BYTE keyIndex, BYTE newKeySetVersion, BYTE _3DESKey[16]) {
	OPGP_ERROR_STATUS status;
	status = put_3des_key(cardContext, cardInfo, secInfo, keySetVersion, keyIndex, newKeySetVersion, _3DESKey);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	return status;
}

OPGP_ERROR_STATUS put_3des_key(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
				  BYTE keySetVersion, BYTE keyIndex, BYTE newKeySetVersion, BYTE _3DESKey[16]) {
	BYTE keyType;
	OPGP_ERROR_STATUS status;
	OPGP_LOG_START(_T("put_3des_key"));
	if (cardInfo.specVersion == OP_201) {
		keyType = OP201_KEY_TYPE_DES_ECB;
	}
	else {
		keyType = GP211_KEY_TYPE_DES;
	}
	status = GP211_put_symmetric_key(cardContext, cardInfo, secInfo, keySetVersion, keyIndex, newKeySetVersion, _3DESKey, 16, keyType);
	OPGP_LOG_END(_T("put_3des_key"), status);
	return status;
}

/**
 * A keySetVersion value of 0x00 adds a new key.
 * Any other value between 0x01 and 0x7f must match an existing key set version.
 * The new key set version defines the key set version a new key belongs to.
 * This can be the same key version or a new not existing key set version.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param keySetVersion [in] An existing key set version.
 * \param keyIndex [in] The position of the key in the key set version.
 * \param newKeySetVersion [in] The new key set version.
 * \param aesKey [in] The new AES key.
 * \param keyLength [in] The key length. 16, 24 or 32 bytes.
 */
OPGP_ERROR_STATUS GP211_put_aes_key(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
				  BYTE keySetVersion, BYTE keyIndex, BYTE newKeySetVersion, BYTE aesKey[32], DWORD keyLength) {
	OPGP_LOG_START(_T("GP211_put_aes_key"));
	OPGP_ERROR_STATUS status;
	status = GP211_put_symmetric_key(cardContext, cardInfo, secInfo, keySetVersion, keyIndex, newKeySetVersion, aesKey,
			keyLength, GP211_KEY_TYPE_AES);
	OPGP_LOG_END(_T("GP211_put_aes_key"), status);
	return status;
}

/**
 * Adds or replaces a symmetric key in a key set.
 * A keySetVersion value of 0x00 adds a new key. Any other value between 0x01
 * and 0x7f must match an existing key set version.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context().
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param keySetVersion [in] Existing key set version, or 0x00 to add a new key.
 * \param keyIndex [in] The position of the key in the key set version.
 * \param newKeySetVersion [in] The key set version assigned to the new key.
 * \param key [in] The new symmetric key.
 * \param keyLength [in] The key length in bytes.
 * \param keyType [in] The key type. See GP211_KEY_TYPE_AES and GP211_KEY_TYPE_3DES.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS GP211_put_symmetric_key(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
				  BYTE keySetVersion, BYTE keyIndex, BYTE newKeySetVersion, BYTE key[32], DWORD keyLength, BYTE keyType) {
	OPGP_ERROR_STATUS status;
	BYTE sendBuffer[64];
	BYTE recvBuffer[64];
	DWORD recvBufferLength = sizeof(recvBuffer);
	BYTE keyCheckValue[3];
	BYTE keyDataField[64];
	DWORD keyDataFieldLength=64;
	DWORD i=0;
	OPGP_LOG_START(_T("put_symmetric_key"));

	sendBuffer[i++] = 0x80;
	sendBuffer[i++] = 0xD8;
	sendBuffer[i++] = keySetVersion;
	sendBuffer[i++] = keyIndex;
	// Lc
	i++;
	sendBuffer[i++] = newKeySetVersion;

	status = get_key_data_field(secInfo, key, keyLength, keyType, keyDataField, &keyDataFieldLength, keyCheckValue, true);
	if ( OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	if (i + keyDataFieldLength + 1 > sizeof(sendBuffer)) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
		goto end;
	}
	memcpy(sendBuffer+i, keyDataField, keyDataFieldLength); // key
	i+=keyDataFieldLength;

	// Lc
	sendBuffer[4] = i-5;
	sendBuffer[i++] = 0x00; // Le

	status = OPGP_send_APDU(cardContext, cardInfo, secInfo, sendBuffer, i, recvBuffer, &recvBufferLength);
	if ( OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	CHECK_SW_9000(recvBuffer, recvBufferLength, status);
	if (memcmp(keyCheckValue, recvBuffer+1, 3) != 0)
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_KEY_CHECK_VALUE, OPGP_stringify_error(OPGP_ERROR_KEY_CHECK_VALUE)); goto end; }
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:

	OPGP_LOG_END(_T("put_symmetric_key"), status);
	return status;
}

/**
 * A keySetVersion value of 0x00 adds a new secure channel key set.
 * Any other value between 0x01 and 0x7f must match an existing key set version.
 * The new key set version defines the key set version the new secure channel keys belongs to.
 * This can be the same key version or a new not existing key set version.
 * It depends on the supported protocol implementation by the card what keys must be passed as parameters.
 * baseKey must be NULL if the protocol uses 3 Secure Channel Keys
 * (Secure Channel Encryption Key, Secure Channel Message Authentication Code Key and
 * Data Encryption Key) and vice versa.
 * Details about the supported Secure Channel Protocol and its implementation can be
 * obtained by a call to the function GP211_get_secure_channel_protocol_details().
 * Sometimes a key derivation of the put keys might be necessary so it is necessary to call
 * GP211_EMV_CPS11_derive_keys() or any other derivation function. If this is the newBaseKey
 * must be NULL and the derived keys are passed as the 3 Secure Channel Keys.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param keySetVersion [in] An existing key set version.
 * \param newKeySetVersion [in] The new key set version.
 * \param newBaseKey [in] The new Secure Channel base key.
 * \param newS_ENC [in] The new S-ENC key.
 * \param newS_MAC [in] The new S-MAC key.
 * \param newDEK [in] The new DEK.
 * \param keyLength [in] The key length. 16, 24 or 32 bytes.
 * \param keyType [in] The key type or 0 for implicit selection based on the SCP. See GP211_KEY_TYPE_AES.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_put_secure_channel_keys(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
							 BYTE keySetVersion,
							 BYTE newKeySetVersion, PBYTE newBaseKey,
							 PBYTE newS_ENC,
							 PBYTE newS_MAC, PBYTE newDEK, DWORD keyLength, BYTE keyType) {
	return put_secure_channel_keys(cardContext, cardInfo, secInfo,
							 keySetVersion,
							 newKeySetVersion, newBaseKey, newS_ENC,
							 newS_MAC, newDEK, keyLength, keyType);
}

OPGP_ERROR_STATUS put_secure_channel_keys(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
							 BYTE keySetVersion,
							 BYTE newKeySetVersion, PBYTE newBaseKey,
							 PBYTE newS_ENC,
							 PBYTE newS_MAC, PBYTE newDEK, DWORD keyLength, BYTE keyType) {
	OPGP_ERROR_STATUS status;
	BYTE sendBuffer[255];
	DWORD sendBufferLength = 255;
	DWORD recvBufferLength = APDU_RESPONSE_LEN;
	BYTE recvBuffer[APDU_RESPONSE_LEN];
	BYTE keyDataField[255];
	DWORD keyDataFieldLength = 255;
	BYTE keyCheckValue1[3];
	BYTE keyCheckValue2[3];
	BYTE keyCheckValue3[3];
	DWORD i=0;
	OPGP_LOG_START(_T("put_secure_channel_keys"));
	sendBuffer[i++] = 0x80;
	sendBuffer[i++] = 0xD8;
	sendBuffer[i++] = keySetVersion;
	sendBuffer[i++] = 0x81;
	// Lc field set later
	i++;

	sendBuffer[i++] = newKeySetVersion;
	if (keyType == 0) {
		if (cardInfo.specVersion == OP_201) {
			keyType = OP201_KEY_TYPE_DES_ECB;
		}
		else {
			keyType = GP211_KEY_TYPE_DES;
		}
		if (secInfo->secureChannelProtocol == GP211_SCP03) {
			keyType = GP211_KEY_TYPE_AES;
		}
	}
	/* only Secure Channel base key */
	if (newBaseKey != NULL && secInfo->secureChannelProtocol == GP211_SCP02 &&
			(secInfo->secureChannelProtocolImpl & 0x01) == 0) {
            status = get_key_data_field(secInfo, newBaseKey, 16, keyType, keyDataField, &keyDataFieldLength, keyCheckValue1, true);
            if ( OPGP_ERROR_CHECK(status) ) {
                    goto end;
            }
            memcpy(sendBuffer+i, keyDataField, keyDataFieldLength); // key
            i+=keyDataFieldLength;
	}
	else {
		// S-ENC key
		status = get_key_data_field(secInfo, newS_ENC, keyLength, keyType, keyDataField, &keyDataFieldLength, keyCheckValue1, true);
		if ( OPGP_ERROR_CHECK(status) ) {
			goto end;
		}
		memcpy(sendBuffer+i, keyDataField, keyDataFieldLength); // key
		i+=keyDataFieldLength;

		// S-MAC key
		status = get_key_data_field(secInfo, newS_MAC, keyLength, keyType, keyDataField, &keyDataFieldLength, keyCheckValue2, true);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
		memcpy(sendBuffer+i, keyDataField, keyDataFieldLength); // key
		i+=keyDataFieldLength;

		// DEK
		status = get_key_data_field(secInfo, newDEK, keyLength, keyType, keyDataField, &keyDataFieldLength, keyCheckValue3, true);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
		memcpy(sendBuffer+i, keyDataField, keyDataFieldLength); // key
		i+=keyDataFieldLength;
	}
	sendBuffer[4] = i - 5;
	// send the stuff

	sendBuffer[i++] = 0x00; // Le
	sendBufferLength = i;

	status = OPGP_send_APDU(cardContext, cardInfo, secInfo, sendBuffer, sendBufferLength, recvBuffer, &recvBufferLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	CHECK_SW_9000(recvBuffer, recvBufferLength, status);

	/* only Secure Channel base key, so check only first key check value */
	if (secInfo->secureChannelProtocol == GP211_SCP02 &&
		(secInfo->secureChannelProtocolImpl & 0x01) == 0) {
		if (memcmp(keyCheckValue1, recvBuffer+1, 3) != 0)
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_KEY_CHECK_VALUE, OPGP_stringify_error(OPGP_ERROR_KEY_CHECK_VALUE)); goto end; }
	}
	else {
		if (memcmp(keyCheckValue1, recvBuffer+1, 3) != 0)
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_KEY_CHECK_VALUE, OPGP_stringify_error(OPGP_ERROR_KEY_CHECK_VALUE)); goto end; }
		if (memcmp(keyCheckValue2, recvBuffer+1+3, 3) != 0)
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_KEY_CHECK_VALUE, OPGP_stringify_error(OPGP_ERROR_KEY_CHECK_VALUE)); goto end; }
		if (memcmp(keyCheckValue3, recvBuffer+1+6, 3) != 0)
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_KEY_CHECK_VALUE, OPGP_stringify_error(OPGP_ERROR_KEY_CHECK_VALUE)); goto end; }
	}
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("put_secure_channel_keys"), status);
	return status;
}


OPGP_ERROR_STATUS put_asymmetric_keys(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
									  BYTE keySetVersion, BYTE keyIndex, BYTE newKeySetVersion,
									  OPGP_STRING PEMKeyFileName, char *passPhrase,
									  BYTE keyType, BOOL includeEccDomainParameters) {
	OPGP_ERROR_STATUS status;
	BYTE sendBuffer[1000];
	DWORD recvBufferLength=APDU_RESPONSE_LEN;
	BYTE recvBuffer[APDU_RESPONSE_LEN];
	BYTE keyCheckValue[8];

	BYTE keyDataField[800];
	DWORD keyDataFieldLength=0;

	DWORD i=0;
	BYTE token_verification_rsa_modulus[512];
	DWORD rsa_modulus_length = 512;
	LONG token_verification_rsa_exponent;
	BYTE token_verification_ecc_public_point[512];
	DWORD ecc_public_point_length = 512;
	BYTE token_verification_ecc_key_component_type;
	BYTE token_verification_ecc_key_parameter_reference;
	GP211_ECC_DOMAIN_PARAMETERS token_verification_ecc_domain_parameters;

	OPGP_LOG_START(_T("put_asymmetric_keys"));

	if (PEMKeyFileName == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_FILENAME, _T("PEMKeyFileName must be provided"));
		goto end;
	}

	sendBuffer[i++] = 0x80;
	sendBuffer[i++] = 0xD8;
	sendBuffer[i++] = keySetVersion;
	sendBuffer[i++] = keyIndex;
	sendBuffer[i++] = 0x00; // Lc later calculated

	sendBuffer[i++] = newKeySetVersion;

	switch (keyType) {
		case GP211_KEY_TYPE_RSA:
			// read public key
			status = read_public_rsa_key(PEMKeyFileName, passPhrase, token_verification_rsa_modulus, &rsa_modulus_length, &token_verification_rsa_exponent);
			if (OPGP_ERROR_CHECK(status)) {
				goto end;
			}
			status = add_rsa_key_data(secInfo, token_verification_rsa_modulus, rsa_modulus_length,
				token_verification_rsa_exponent, keyDataField, &keyDataFieldLength, keyCheckValue);
			if (OPGP_ERROR_CHECK(status)) {
				goto end;
			}
			break;
		case GP211_KEY_TYPE_ECC:
			status = read_public_ecc_key(PEMKeyFileName, passPhrase,
				token_verification_ecc_public_point, &ecc_public_point_length,
				&token_verification_ecc_key_component_type, &token_verification_ecc_key_parameter_reference,
				includeEccDomainParameters ? &token_verification_ecc_domain_parameters : NULL);
			if (OPGP_ERROR_CHECK(status)) {
				goto end;
			}
			status = add_ecc_key_data(secInfo, token_verification_ecc_public_point, ecc_public_point_length,
				token_verification_ecc_key_component_type, token_verification_ecc_key_parameter_reference,
				includeEccDomainParameters ? &token_verification_ecc_domain_parameters : NULL,
				includeEccDomainParameters,
				keyDataField, &keyDataFieldLength, keyCheckValue);
			if (OPGP_ERROR_CHECK(status)) {
				goto end;
			}
			break;
		default:
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_WRONG_KEY_TYPE, OPGP_stringify_error(OPGP_ERROR_WRONG_KEY_TYPE)); goto end;
	}

	status = send_chained_data(cardContext, cardInfo, secInfo, sendBuffer, i,
							  keyDataField, keyDataFieldLength, recvBuffer, &recvBufferLength,
							  false, false, true, false);
	if ( OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:

	OPGP_LOG_END(_T("put_asymmetric_keys"), status);
	return status;
}

OPGP_ERROR_STATUS put_delegated_management_receipt_keys(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
                                                        BYTE keySetVersion, BYTE newKeySetVersion,
                                                        BYTE receiptKey[32], DWORD keyLength, BYTE receiptKeyType) {
	OPGP_ERROR_STATUS status;
	BYTE sendBuffer[64];
	BYTE recvBuffer[64];
	DWORD recvBufferLength=sizeof(recvBuffer);
	BYTE keyCheckValue[8];

	BYTE keyDataField[1024];
	DWORD currentKeyDataFieldLength=1024;

	DWORD i=0;
	BOOL withKeyCheck = false;

	OPGP_LOG_START(_T("put_delegated_management_receipt_keys"));

	if (receiptKeyType == 0) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_WRONG_KEY_TYPE, _T("receiptKeyType must be provided"));
		goto end;
	}

	sendBuffer[i++] = 0x80;
	sendBuffer[i++] = 0xD8;
	sendBuffer[i++] = keySetVersion;
	sendBuffer[i++] = 0x01;
	sendBuffer[i++] = 0x00; // Lc later calculated

	sendBuffer[i++] = newKeySetVersion;

	switch (receiptKeyType) {
		case GP211_KEY_TYPE_DES:
		case GP211_KEY_TYPE_AES:
		case GP211_KEY_TYPE_3DES:
		case GP211_KEY_TYPE_SM4:
		case GP211_KEY_TYPE_3DES_CBC:
		case GP211_KEY_TYPE_DES_CBC:
		case GP211_KEY_TYPE_DES_ECB:
			withKeyCheck = true;
			status = get_key_data_field(secInfo, receiptKey, keyLength, receiptKeyType, keyDataField, &currentKeyDataFieldLength, keyCheckValue, true);
			if ( OPGP_ERROR_CHECK(status)) {
				goto end;
			}
			break;
		default:
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_WRONG_KEY_TYPE, OPGP_stringify_error(OPGP_ERROR_WRONG_KEY_TYPE)); goto end;
	}

	// send the stuff
	sendBuffer[4] = (BYTE)i - 5;

	sendBuffer[i] = 0x00; // Le

	status = send_chained_data(cardContext, cardInfo, secInfo, sendBuffer, i,
							  keyDataField, currentKeyDataFieldLength, recvBuffer, &recvBufferLength,
							  false, false, true, false);
	if ( OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	if (withKeyCheck && memcmp(keyCheckValue, recvBuffer+1, 3) != 0)
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_KEY_CHECK_VALUE, OPGP_stringify_error(OPGP_ERROR_KEY_CHECK_VALUE)); goto end; }
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:

	OPGP_LOG_END(_T("put_delegated_management_receipt_keys"), status);
	return status;
}

/**
 * Adds or replaces a Delegated Management Token Verification Key.
 * RSA and ECC token keys are loaded as asymmetric public keys at key index 0x01.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context().
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param keySetVersion [in] Existing key set version, or 0x00 to add a new key.
 * \param newKeySetVersion [in] The key set version assigned to the new key.
 * \param PEMKeyFileName [in] A PEM file name with the public token key.
 * \param passPhrase [in] The passphrase, or NULL if not needed.
 * \param tokenKeyType [in] The token key type. See GP211_KEY_TYPE_RSA and GP211_KEY_TYPE_ECC.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS GP211_put_delegated_management_token_keys(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
                                                            BYTE keySetVersion, BYTE newKeySetVersion,
                                                            OPGP_STRING PEMKeyFileName, char *passPhrase,
                                                            BYTE tokenKeyType) {
	return put_asymmetric_keys(cardContext, cardInfo, secInfo,
							   keySetVersion, 0x01, newKeySetVersion,
							   PEMKeyFileName, passPhrase, tokenKeyType, true);
}

/**
 * Adds or replaces a DAP Verification Key.
 * RSA and ECC DAP keys are loaded as asymmetric public keys at key index 0x01;
 * AES and 3DES DAP keys are loaded as symmetric keys.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context().
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param keySetVersion [in] Existing key set version, or 0x00 to add a new key.
 * \param newKeySetVersion [in] The key set version assigned to the new key.
 * \param PEMKeyFileName [in] A PEM file name with the public asymmetric DAP key.
 * \param passPhrase [in] The passphrase, or NULL if not needed.
 * \param keyType [in] The DAP key type. See GP211_KEY_TYPE_RSA, GP211_KEY_TYPE_ECC, GP211_KEY_TYPE_AES and GP211_KEY_TYPE_3DES.
 * \param key [in] The symmetric DAP key when keyType is AES or 3DES.
 * \param keyLength [in] The symmetric DAP key length in bytes.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS GP211_put_dap_keys(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
									 BYTE keySetVersion, BYTE newKeySetVersion,
									 OPGP_STRING PEMKeyFileName, char *passPhrase,
									 BYTE keyType, BYTE key[32], DWORD keyLength) {
	OPGP_ERROR_STATUS status;
	switch (keyType) {
		case GP211_KEY_TYPE_RSA:
		case GP211_KEY_TYPE_ECC:
			status = GP211_put_asymmetric_keys(cardContext, cardInfo, secInfo,
											   keySetVersion, 0x01, newKeySetVersion,
											   PEMKeyFileName, passPhrase, keyType);
			if (OPGP_ERROR_CHECK(status)) {
				goto end;
			}
			break;
		case GP211_KEY_TYPE_AES:
		case GP211_KEY_TYPE_3DES:
			if (key == NULL || keyLength == 0) {
				OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, _T("key must be provided for symmetric DAP keys"));
				goto end;
			}
			if (keyLength > 32) {
				OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, _T("symmetric DAP key length must be <= 32 bytes"));
				goto end;
			}
			status = GP211_put_symmetric_key(cardContext, cardInfo, secInfo,
											 keySetVersion, 0x01, newKeySetVersion,
											 key, keyLength, keyType);
			if (OPGP_ERROR_CHECK(status)) {
				goto end;
			}
			break;
		default:
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_WRONG_KEY_TYPE, OPGP_stringify_error(OPGP_ERROR_WRONG_KEY_TYPE));
			goto end;
	}

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	return status;
}

/**
 * Adds or replaces a Delegated Management Receipt Key.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context().
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param keySetVersion [in] Existing key set version, or 0x00 to add a new key.
 * \param newKeySetVersion [in] The key set version assigned to the new key.
 * \param receiptKey [in] The receipt key.
 * \param keyLength [in] The receipt key length in bytes.
 * \param receiptKeyType [in] The receipt key type. See GP211_KEY_TYPE_AES and GP211_KEY_TYPE_3DES.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS GP211_put_delegated_management_receipt_keys(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
                                                              BYTE keySetVersion, BYTE newKeySetVersion,
                                                              BYTE receiptKey[32], DWORD keyLength, BYTE receiptKeyType) {
	return put_delegated_management_receipt_keys(cardContext, cardInfo, secInfo,
	                                             keySetVersion, newKeySetVersion,
	                                             receiptKey, keyLength, receiptKeyType);
}

/**
 * If keyIndex is 0xFF all keys within a keySetVersion are deleted.
 * If keySetVersion is 0x00 all keys with the specified keyIndex are deleted.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param keySetVersion [in] An existing key set version.
 * \param keyIndex [in] An existing key index.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_delete_key(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo, BYTE keySetVersion, BYTE keyIndex) {
	return delete_key(cardContext, cardInfo, secInfo, keySetVersion, keyIndex);
}

OPGP_ERROR_STATUS delete_key(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo, BYTE keySetVersion, BYTE keyIndex) {
	OPGP_ERROR_STATUS status;
	BYTE sendBuffer[255];
	DWORD sendBufferLength;
	DWORD recvBufferLength=3;
	BYTE recvBuffer[3];
	DWORD i=0;
	OPGP_LOG_START(_T("delete_key"));
	if ((keySetVersion == 0x00) && (keyIndex == 0xFF))
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_COMBINATION_KEY_SET_VERSION_KEY_INDEX, OPGP_stringify_error(OPGP_ERROR_INVALID_COMBINATION_KEY_SET_VERSION_KEY_INDEX)); goto end; }
	sendBuffer[i++] = 0x80;
	sendBuffer[i++] = 0xE4;
	sendBuffer[i++] = 0x00;
	sendBuffer[i++] = 0x80;
	if (keySetVersion == 0x00) {
		sendBuffer[i++] = 0x03;
		sendBuffer[i++] = 0xD0;
		sendBuffer[i++] = 0x01;
		sendBuffer[i++] = keyIndex;
	}
	else if (keyIndex == 0xFF) {
		sendBuffer[i++] = 0x03;
		sendBuffer[i++] = 0xD2;
		sendBuffer[i++] = 0x01;
		sendBuffer[i++] = keySetVersion;
	}
	else {
		sendBuffer[i++] = 0x06;
		sendBuffer[i++] = 0xD0;
		sendBuffer[i++] = 0x01;
		sendBuffer[i++] = keyIndex;
		sendBuffer[i++] = 0xD2;
		sendBuffer[i++] = 0x01;
		sendBuffer[i++] = keySetVersion;
	}
	sendBuffer[i++] = 0x00; // Le
	sendBufferLength = i;

	status = OPGP_send_APDU(cardContext, cardInfo, secInfo, sendBuffer, sendBufferLength, recvBuffer, &recvBufferLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	CHECK_SW_9000(recvBuffer, recvBufferLength, status);

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("delete_key"), status);
	return status;
}

/**
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param AIDs [in] A pointer to the an array of OPGP_AID structures describing the applications and load files to delete.
 * \param AIDsLength [in] The number of OPGP_AID structures.
 * \param *receiptData [out] A GP211_RECEIPT_DATA structure. If the deletion is performed by a
 * security domain with delegated management privilege this structure contains the according data.
 * \param receiptDataAvailable [out] 0 if no receiptData is available.
 * \param deleteToken [in] Optional delete token value. If present, it is encoded with tag '9F'.
 * \param deleteTokenLength [in] The length of the deleteToken buffer.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_delete_application(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
						OPGP_AID *AIDs, DWORD AIDsLength, GP211_RECEIPT_DATA *receiptData, PDWORD receiptDataAvailable,
						PBYTE deleteToken, DWORD deleteTokenLength) {
	return delete_application(cardContext, cardInfo, secInfo, AIDs, AIDsLength, receiptData, receiptDataAvailable,
		deleteToken, deleteTokenLength, GP_211);
}

/**
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param AIDs [in] A pointer to the an array of OPGP_AID structures describing the applications and load files to delete.
 * \param AIDsLength [in] The number of OPGP_AID structures.
 * \param *receiptData [out] A GP211_RECEIPT_DATA structure. If the deletion is performed by a
 * security domain with delegated management privilege this structure contains the according data.
 * \param receiptDataAvailable [out] 0 if no receiptData is available.
 * \param deleteToken [in] Optional delete token value. If present, it is encoded with tag '9F'.
 * \param deleteTokenLength [in] The length of the deleteToken buffer.
 * \param mode OpenPlatform 2.0.1' or GlobalPlatform 2.1.1 delete command.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS delete_application(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
				   OPGP_AID *AIDs, DWORD AIDsLength, GP211_RECEIPT_DATA *receiptData, PDWORD receiptDataAvailable,
				   PBYTE deleteToken, DWORD deleteTokenLength, DWORD mode) {
	OPGP_ERROR_STATUS status;
	BYTE deleteTokenSignatureData[1000] = {0};
	DWORD deleteTokenSignatureDataLength = sizeof(deleteTokenSignatureData);
	BYTE commandHeader[5] = {0x80, 0xE4, 0x00, 0x00, 0x00};
	BYTE sendBuffer[1000] = {0};
	DWORD recvBufferLength = APDU_RESPONSE_LEN;
	BYTE recvBuffer[APDU_RESPONSE_LEN] = {0};
	DWORD j,i=0;
	LONG lenLen;
	PBYTE parsedData = NULL;
	DWORD parsedDataLength = 0;
	OPGP_LOG_START(_T("delete_application"));
	if (receiptDataAvailable != NULL) {
		*receiptDataAvailable = 0;
	}
	if (mode == GP_211) {
		status = GP211_get_delete_token_signature_data(AIDs, AIDsLength,
			deleteTokenSignatureData, &deleteTokenSignatureDataLength);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
		status = extract_case3_data(deleteTokenSignatureData, deleteTokenSignatureDataLength,
			&commandHeader[2], &commandHeader[3], &parsedData, &parsedDataLength);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
		if (parsedDataLength > sizeof(sendBuffer)) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_COMMAND_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_COMMAND_TOO_LARGE)); goto end; }
		}
		memcpy(sendBuffer, parsedData, parsedDataLength);
		i += parsedDataLength;
	} else {
		for (j=0; j< AIDsLength; j++) {
			if (i + AIDs[j].AIDLength + 2 > sizeof(sendBuffer)) {
				{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_COMMAND_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_COMMAND_TOO_LARGE)); goto end; }
			}
			OPGP_LOG_HEX(_T("delete_application: AID to delete: "), AIDs[j].AID, AIDs[j].AIDLength);
			sendBuffer[i++] = 0x4F;
			sendBuffer[i++] = AIDs[j].AIDLength;
			memcpy(sendBuffer+i, AIDs[j].AID, AIDs[j].AIDLength);
			i+=AIDs[j].AIDLength;
		}
	}
	if (mode == GP_211 && deleteToken != NULL && deleteTokenLength > 0) {
		if (deleteTokenLength > 0xFFFF) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_COMMAND_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_COMMAND_TOO_LARGE)); goto end; }
		}
		if (i + 1 > sizeof(sendBuffer)) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
		}
		sendBuffer[i++] = 0x9E;
		lenLen = write_TLV_length(sendBuffer, i, sizeof(sendBuffer) - i, (USHORT)deleteTokenLength);
		if (lenLen < 0) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
		}
		if (i + (DWORD)lenLen + deleteTokenLength > sizeof(sendBuffer)) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
		}
		i += (DWORD)lenLen;
		OPGP_LOG_HEX(_T("delete_application: Delete Token: "), deleteToken, deleteTokenLength);
		memcpy(sendBuffer+i, deleteToken, deleteTokenLength);
		i+=deleteTokenLength;
	}

	status = send_chained_data(cardContext, cardInfo, secInfo, commandHeader, 5, sendBuffer, i,
		recvBuffer, &recvBufferLength, false, false, true, false);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	status = parse_receipt_from_response(recvBuffer, recvBufferLength, receiptData, receiptDataAvailable);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("delete_application"), status);
	return status;
}

/**
 * Puts a single card data object identified by identifier.
 * Some cards do not provide some data objects. Some possible identifiers are predefined.
 * See GP211_GET_DATA_CPLC_WHOLE_CPLC. For details about the coding of the dataObject see the programmer's manual
 * of your card.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param identifier [in] Two byte buffer with high and low order tag value for identifying card data object.
 * \param dataObject [in] The coded data object.
 * \param dataObjectLength [in] The length of the data object.
  * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_put_data(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo, BYTE identifier[2], PBYTE dataObject, DWORD dataObjectLength) {
	return put_data(cardContext, cardInfo, secInfo, identifier, dataObject, dataObjectLength);
}

OPGP_ERROR_STATUS put_data(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo, BYTE identifier[2], PBYTE dataObject, DWORD dataObjectLength) {
	OPGP_ERROR_STATUS status;
	BYTE sendBuffer[255];
	DWORD sendBufferLength;
	DWORD recvBufferLength=2;
	BYTE recvBuffer[2];
	DWORD i=0;
	OPGP_LOG_START(_T("put_data"));
	sendBuffer[i++] = 0x80;
	sendBuffer[i++] = 0xDA;
	sendBuffer[i++] = identifier[0];
	sendBuffer[i++] = identifier[1];
	sendBuffer[i++] = (BYTE)dataObjectLength;
	memcpy(sendBuffer+i, dataObject, dataObjectLength);
	i+=dataObjectLength;
	sendBufferLength = i;

	status = OPGP_send_APDU(cardContext, cardInfo, secInfo, sendBuffer, sendBufferLength, recvBuffer, &recvBufferLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	CHECK_SW_9000(recvBuffer, recvBufferLength, status);

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("put_data"), status);
	return status;
}

/**
 * Retrieves a single card data object from the card identified by identifier.
 * Some cards do not provide some data objects. Some possible identifiers are predefined.
 * See GP211_GET_DATA_CPLC_WHOLE_CPLC and so on. For details about the coding of the response see the programmer's manual
 * of your card.
 * There is a convenience method get_key_information_templates() to get the key information template(s)
 * containing key set version, key index, key type and key length of the keys.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param identifier [in] Two byte buffer with high and low order tag value for identifying card data object.
 * \param recvBuffer [out] The buffer for the card data object.
 * \param recvBufferLength [in, out] The length of the received card data object.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_get_data(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
			  BYTE identifier[2], PBYTE recvBuffer, PDWORD recvBufferLength) {
				  return get_data(cardContext, cardInfo, secInfo, identifier, recvBuffer, recvBufferLength);
}

/**
 * Retrieves the SCP11 ECKA certificate store with GET DATA tag BF21.
 * The command data contains a Control Reference Template (A6) with the key
 * identifier and key version number. The returned data is copied without the
 * status word and may contain the complete certificate store TLV.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context().
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication(), or NULL if the command is sent without secure messaging.
 * \param keyVersionNumber [in] Key Version Number (KVN) of the ECKA certificate store to retrieve.
 * \param keyIdentifier [in] Key Identifier (KID) of the ECKA certificate store to retrieve.
 * \param recvBuffer [out] The buffer for the ECKA certificate store response data.
 * \param recvBufferLength [in, out] The length of the receive buffer and, on success, the number of bytes copied to recvBuffer.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS GP211_get_ecka_certificate(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
			  BYTE keyVersionNumber, BYTE keyIdentifier, PBYTE recvBuffer, PDWORD recvBufferLength) {
	OPGP_ERROR_STATUS status;
	const BYTE crt[] = {0xA6, 0x04, 0x83, 0x02, keyIdentifier, keyVersionNumber};

	if (recvBuffer == NULL || recvBufferLength == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}

	OPGP_LOG_START(_T("GP211_get_ecka_certificate"));
	status = get_data_with_data(cardContext, cardInfo, secInfo, (BYTE *)GP211_GET_DATA_ECKA_CERTIFICATE,
			(PBYTE)crt, sizeof(crt), recvBuffer, recvBufferLength);
	OPGP_LOG_END(_T("GP211_get_ecka_certificate"), status);
	return status;
}

/**
 * Resolves the SCP11 CA-KLOC Identifier to a Key Identifier and Key Version Number.
 * The command sends GET DATA tag 0083 with the CA-KLOC Identifier in tag 42 and
 * expects a tag 83 response containing KID || KVN.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context().
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication(), or NULL if the command is sent without secure messaging.
 * \param caKlocIdentifier [in] The CA-KLOC Identifier value.
 * \param caKlocIdentifierLength [in] The length of caKlocIdentifier.
 * \param keyIdentifier [out] The resolved Key Identifier (KID).
 * \param keyVersionNumber [out] The resolved Key Version Number (KVN).
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS GP211_ca_kloc_kid_kvn(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
			  PBYTE caKlocIdentifier, DWORD caKlocIdentifierLength, BYTE *keyIdentifier, BYTE *keyVersionNumber) {
	OPGP_ERROR_STATUS status;
	BYTE response[APDU_RESPONSE_LEN * 2];
	DWORD responseLength = sizeof(response);
	BYTE crt[260];
	DWORD crtLength = 0;
	GP_SIMPLE_TLV responseTlv;

	if (caKlocIdentifier == NULL || caKlocIdentifierLength == 0 || keyIdentifier == NULL || keyVersionNumber == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}

	OPGP_LOG_START(_T("GP211_ca_kloc_kid_kvn"));

	status = append_tlv(crt, sizeof(crt), &crtLength, 0x42, caKlocIdentifier, caKlocIdentifierLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	{
		BYTE wrappedCrt[270];
		DWORD wrappedCrtLength = 0;
		status = append_tlv(wrappedCrt, sizeof(wrappedCrt), &wrappedCrtLength, 0xA6, crt, crtLength);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
		status = get_data_with_data(cardContext, cardInfo, secInfo, (BYTE *)GP211_GET_DATA_CA_KLOC_KID_KVN,
				wrappedCrt, wrappedCrtLength, response, &responseLength);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
	}

	if (parse_simple_tlv(response, responseLength, &responseTlv) < 0
			|| responseTlv.tag != 0x83 || responseTlv.length != 2) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		goto end;
	}

	*keyIdentifier = responseTlv.value[0];
	*keyVersionNumber = responseTlv.value[1];
	OPGP_ERROR_CREATE_NO_ERROR(status);

end:
	OPGP_LOG_END(_T("GP211_ca_kloc_kid_kvn"), status);
	return status;
}

/**
 * Retrieves SCP11 supported CA identifiers with GET DATA tags FF33 (CA-KLOC)
 * and FF34 (CA-KLCC).
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context().
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication(), or NULL if the command is sent without secure messaging.
 * \param returnCaKlccIdentifiers [in] FALSE for FF33 (CA-KLOC), TRUE for FF34 (CA-KLCC).
 * \param recvBuffer [out] The buffer for the response data.
 * \param recvBufferLength [in, out] The length of recvBuffer and, on success, the number of bytes copied.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS GP211_get_supported_ca_identifiers(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
			  BOOL returnCaKlccIdentifiers, PBYTE recvBuffer, PDWORD recvBufferLength) {
	const BYTE *identifier = returnCaKlccIdentifiers
			? GP211_GET_DATA_SUPPORTED_CA_KLCC_IDENTIFIERS
			: GP211_GET_DATA_SUPPORTED_CA_KLOC_IDENTIFIERS;
	return get_data(cardContext, cardInfo, secInfo, (BYTE *)identifier, recvBuffer, recvBufferLength);
}

static OPGP_ERROR_STATUS resolve_scp11_ca_kloc_from_supported_identifiers(OPGP_CARD_CONTEXT cardContext,
			  OPGP_CARD_INFO cardInfo, PBYTE caKlocIdentifier, DWORD caKlocIdentifierLength,
			  BYTE *keyIdentifier, BYTE *keyVersionNumber) {
	OPGP_ERROR_STATUS status;
	BYTE response[APDU_RESPONSE_LEN * 2];
	DWORD responseLength = sizeof(response);
	GP_SIMPLE_TLV outer;
	DWORD offset = 0;

	if (caKlocIdentifier == NULL || caKlocIdentifierLength == 0 || keyIdentifier == NULL || keyVersionNumber == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}

	status = GP211_get_supported_ca_identifiers(cardContext, cardInfo, NULL, 0, response, &responseLength);
	if (OPGP_ERROR_CHECK(status)) {
		return status;
	}

	if (parse_simple_tlv(response, responseLength, &outer) < 0
			|| outer.tag != 0xFF33 || outer.tlvLength != responseLength) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}

	while (offset < outer.length) {
		GP_SIMPLE_TLV identifierTlv;
		GP_SIMPLE_TLV keyReferenceTlv;

		if (parse_simple_tlv(outer.value + offset, outer.length - offset, &identifierTlv) < 0
				|| identifierTlv.tag != 0x42) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			return status;
		}
		offset += identifierTlv.tlvLength;
		if (parse_simple_tlv(outer.value + offset, outer.length - offset, &keyReferenceTlv) < 0
				|| keyReferenceTlv.tag != 0x83 || keyReferenceTlv.length != 2) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			return status;
		}
		offset += keyReferenceTlv.tlvLength;

		if (identifierTlv.length == caKlocIdentifierLength
				&& memcmp(identifierTlv.value, caKlocIdentifier, caKlocIdentifierLength) == 0) {
			*keyIdentifier = keyReferenceTlv.value[0];
			*keyVersionNumber = keyReferenceTlv.value[1];
			OPGP_ERROR_CREATE_NO_ERROR(status);
			return status;
		}
	}

	OPGP_ERROR_CREATE_ERROR(status, OPGP_ISO7816_ERROR_DATA_NOT_FOUND, OPGP_stringify_error(OPGP_ISO7816_ERROR_DATA_NOT_FOUND));
	return status;
}

static OPGP_ERROR_STATUS perform_security_operation_single_certificate(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo,
			  GP211_SECURITY_INFO *secInfo,
			  BYTE caKlocKeyVersionNumber, BYTE caKlocKeyIdentifier, PBYTE certificateData, DWORD certificateDataLength,
			  BOOL moreCertificatesExpected) {
	OPGP_ERROR_STATUS status;
	BYTE commandHeader[5];
	BYTE recvBuffer[APDU_RESPONSE_LEN];
	DWORD recvBufferLength = sizeof(recvBuffer);

	if (certificateData == NULL || certificateDataLength == 0) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}

	OPGP_LOG_START(_T("GP211_perform_security_operation"));
	commandHeader[0] = 0x80;
	commandHeader[1] = 0x2A;
	commandHeader[2] = (BYTE)(caKlocKeyVersionNumber & 0x7F);
	commandHeader[3] = (BYTE)(caKlocKeyIdentifier & 0x7F);
	if (moreCertificatesExpected) {
		commandHeader[3] |= 0x80;
	}
	commandHeader[4] = 0x00;

	status = send_chained_data(cardContext, cardInfo, secInfo, commandHeader, 5, certificateData, certificateDataLength,
			recvBuffer, &recvBufferLength, false, false, false, true);
	OPGP_LOG_END(_T("GP211_perform_security_operation"), status);
	return status;
}

/**
 * Submits SCP11 certificate data with PERFORM SECURITY OPERATION.
 * The input may contain a single certificate, consecutive certificates, or a
 * BF21 wrapper containing those certificates. For certificate lists, P2.b8 is
 * derived from each certificate's position in the list. For a single
 * certificate, \p moreCertificatesExpected controls P2.b8.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context().
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication(), or NULL if the command is sent without secure messaging.
 * \param caKlocKeyVersionNumber [in] CA-KLOC Key Version Number (KVN).
 * \param caKlocKeyIdentifier [in] CA-KLOC Key Identifier (KID).
 * \param certificateData [in] One or more 7F21 or 30 certificates, optionally wrapped in BF21.
 * \param certificateDataLength [in] The length of certificateData.
 * \param moreCertificatesExpected [in] TRUE if another certificate follows this single-certificate input, otherwise FALSE. Ignored for multi-certificate inputs.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS GP211_perform_security_operation(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
			  BYTE caKlocKeyVersionNumber, BYTE caKlocKeyIdentifier, PBYTE certificateData, DWORD certificateDataLength,
			  BOOL moreCertificatesExpected) {
	OPGP_ERROR_STATUS status;
	GP_SIMPLE_TLV outer;
	GP_SIMPLE_TLV certificateTlv;
	const BYTE *certificateList;
	DWORD certificateListLength;
	PBYTE preparedCertificateList = NULL;
	DWORD preparedCertificateListLength = 0;
	DWORD offset = 0;
	USHORT certificateTag = 0;
	BOOL inputIsCertificateList = 0;

	OPGP_LOG_START(_T("GP211_perform_security_operation"));

	if (certificateData == NULL || certificateDataLength == 0) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		goto end;
	}

	if (parse_simple_tlv(certificateData, certificateDataLength, &outer) > 0
			&& outer.tag == 0xBF21 && outer.tlvLength == certificateDataLength) {
		certificateList = outer.value;
		certificateListLength = outer.length;
		inputIsCertificateList = 1;
	} else {
		certificateList = certificateData;
		certificateListLength = certificateDataLength;
	}

	if (parse_simple_tlv(certificateList, certificateListLength, &certificateTlv) > 0
			&& certificateTlv.tag == 0x30) {
		if (certificateTlv.tlvLength < certificateListLength) {
			inputIsCertificateList = 1;
		}
		preparedCertificateList = (PBYTE)malloc(certificateListLength);
		if (preparedCertificateList == NULL) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
			goto end;
		}
		preparedCertificateListLength = certificateListLength;
		status = prepare_scp11_der_x509_certificate_chain(certificateList, certificateListLength,
				preparedCertificateList, &preparedCertificateListLength);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
		certificateList = preparedCertificateList;
		certificateListLength = preparedCertificateListLength;
		if (parse_simple_tlv(certificateList, certificateListLength, &certificateTlv) > 0
				&& certificateTlv.tlvLength < certificateListLength) {
			inputIsCertificateList = 1;
		}
	}

	while (offset < certificateListLength) {
		BOOL listHasMoreCertificates;
		BOOL p2MoreCertificatesExpected;
		LONG result = parse_simple_tlv(certificateList + offset, certificateListLength - offset, &certificateTlv);
		if (result < 0) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			goto end;
		}
		if (certificateTag == 0) {
			if (certificateTlv.tag != 0x7F21 && certificateTlv.tag != 0x30) {
				OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
				goto end;
			}
			certificateTag = certificateTlv.tag;
		} else if (certificateTlv.tag != certificateTag) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			goto end;
		}

		listHasMoreCertificates = (offset + certificateTlv.tlvLength < certificateListLength);
		if (listHasMoreCertificates) {
			inputIsCertificateList = 1;
		}
		p2MoreCertificatesExpected = listHasMoreCertificates
				|| (!inputIsCertificateList && moreCertificatesExpected);
		if (certificateTag == 0x7F21) {
			GP211_SCP11_CERTIFICATE parsedCertificate;
			status = GP211_parse_scp11_certificate((PBYTE)(certificateList + offset), certificateTlv.tlvLength, &parsedCertificate);
			if (OPGP_ERROR_CHECK(status)) {
				goto end;
			}
			if ((p2MoreCertificatesExpected
						&& (parsedCertificate.keyUsageLength != sizeof(GP211_SCP11_KEY_USAGE_DIGITAL_SIGNATURE_VERIFICATION)
							|| memcmp(parsedCertificate.keyUsage, GP211_SCP11_KEY_USAGE_DIGITAL_SIGNATURE_VERIFICATION,
								sizeof(GP211_SCP11_KEY_USAGE_DIGITAL_SIGNATURE_VERIFICATION)) != 0))
					|| (!p2MoreCertificatesExpected
						&& (parsedCertificate.keyUsageLength != sizeof(GP211_SCP11_KEY_USAGE_KEY_AGREEMENT)
							|| memcmp(parsedCertificate.keyUsage, GP211_SCP11_KEY_USAGE_KEY_AGREEMENT,
								sizeof(GP211_SCP11_KEY_USAGE_KEY_AGREEMENT)) != 0))) {
				OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
				goto end;
			}
		}
		status = perform_security_operation_single_certificate(cardContext, cardInfo, secInfo,
				caKlocKeyVersionNumber, caKlocKeyIdentifier,
				(PBYTE)(certificateList + offset), certificateTlv.tlvLength,
				p2MoreCertificatesExpected);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
		offset += certificateTlv.tlvLength;
	}
	if (offset == 0) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		goto end;
	}

	OPGP_ERROR_CREATE_NO_ERROR(status);
end:
	if (preparedCertificateList != NULL) {
		free(preparedCertificateList);
	}
	OPGP_LOG_END(_T("GP211_perform_security_operation"), status);
	return status;
}

/**
 * Submits an SCP11 certificate chain with PERFORM SECURITY OPERATION.
 * The input must contain either one or more consecutive certificates, or a BF21
 * wrapper containing those certificates. Supported certificate formats are
 * legacy GP (tag 7F21) and X.509 DER (tag 30). The chain must contain exactly
 * one format; mixed chains are rejected. For legacy GP certificates, key usage
 * is validated locally (intermediate certificates must be digital signature
 * verification; the final certificate must be key agreement). X.509 chains are
 * ordered from issuer to subject and a self-issued trust anchor is omitted.
 * Local X.509 key usage parsing is not performed.
 * Each certificate is submitted separately; P2.b8 is set for every intermediate
 * certificate and cleared for the final certificate.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context().
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication(), or NULL if the command is sent without secure messaging.
 * \param caKlocKeyVersionNumber [in] CA-KLOC Key Version Number (KVN) used for verifying the first certificate.
 * \param caKlocKeyIdentifier [in] CA-KLOC Key Identifier (KID) used for verifying the first certificate.
 * \param certificateChainData [in] One or more 7F21 or 30 certificates, optionally wrapped in BF21.
 * \param certificateChainDataLength [in] The length of certificateChainData.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS GP211_perform_security_operation_certificate_chain(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo,
			  GP211_SECURITY_INFO *secInfo,
			  BYTE caKlocKeyVersionNumber, BYTE caKlocKeyIdentifier,
			  PBYTE certificateChainData, DWORD certificateChainDataLength) {
	OPGP_ERROR_STATUS status;

	OPGP_LOG_START(_T("GP211_perform_security_operation_certificate_chain"));
	status = GP211_perform_security_operation(cardContext, cardInfo, secInfo,
			caKlocKeyVersionNumber, caKlocKeyIdentifier,
			certificateChainData, certificateChainDataLength, 0);
	OPGP_LOG_END(_T("GP211_perform_security_operation_certificate_chain"), status);
	return status;
}

/**
 * Retrieves the GlobalPlatform diversification data object with GET DATA tag CF.
 * The returned buffer receives the value of the CF TLV, without the outer tag
 * and length bytes.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context().
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication(), or NULL if the command is sent without secure messaging.
 * \param recvBuffer [out] The buffer for the diversification data value.
 * \param recvBufferLength [in, out] The length of the receive buffer and, on success, the number of bytes copied to recvBuffer.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS GP211_get_diversification_data(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
			  PBYTE recvBuffer, PDWORD recvBufferLength) {
	OPGP_ERROR_STATUS status;
	BYTE tmpBuffer[256];
	DWORD tmpLen = sizeof(tmpBuffer);
	TLV tlv;
	LONG result;

	status = get_data(cardContext, cardInfo, secInfo, (PBYTE)GP211_GET_DATA_DIVERSIFICATION_DATA, tmpBuffer, &tmpLen);
	if (OPGP_ERROR_CHECK(status)) {
		return status;
	}
	result = read_TLV(tmpBuffer, tmpLen, &tlv);
	if (result == -1 || tlv.tag != 0xCF) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}
	if (tlv.length > *recvBufferLength) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
		return status;
	}
	memcpy(recvBuffer, tlv.value, tlv.length);
	*recvBufferLength = tlv.length;
	{ OPGP_ERROR_CREATE_NO_ERROR(status); return status; }
}

/**
 * Parses Card Production Life Cycle data.
 * The input may be either the raw 42 byte CPLC value or a GET DATA response
 * wrapped in tag 9F7F.
 * \param data [in] The raw CPLC value or the tag 9F7F wrapped CPLC data.
 * \param dataLength [in] The length of data.
 * \param cplc [out] The parsed CPLC structure.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS OPGP_parse_cplc(const BYTE *data, DWORD dataLength, OPGP_CPLC *cplc) {
	OPGP_ERROR_STATUS status;
	const BYTE *cplcData = data;
	DWORD cplcLength = dataLength;
	TLV tlv;
	LONG result;

	if (data == NULL || cplc == NULL || dataLength == 0) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}

	memset(cplc, 0, sizeof(*cplc));
	result = read_TLV((const BYTE *)data, dataLength, &tlv);
	if (result != -1 && tlv.tag == 0x9F7F) {
		cplcData = tlv.value;
		cplcLength = tlv.length;
	}

	if (cplcLength < 42) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}

	cplc->icFabricator = (USHORT)((cplcData[0] << 8) | cplcData[1]);
	cplc->icType = (USHORT)((cplcData[2] << 8) | cplcData[3]);
	cplc->operatingSystemId = (USHORT)((cplcData[4] << 8) | cplcData[5]);
	cplc->operatingSystemReleaseDate = (USHORT)((cplcData[6] << 8) | cplcData[7]);
	cplc->operatingSystemReleaseLevel = (USHORT)((cplcData[8] << 8) | cplcData[9]);
	cplc->icFabricationDate = (USHORT)((cplcData[10] << 8) | cplcData[11]);
	cplc->icSerialNumberHigh = (USHORT)((cplcData[12] << 8) | cplcData[13]);
	cplc->icSerialNumberLow = (USHORT)((cplcData[14] << 8) | cplcData[15]);
	cplc->icBatchIdentifier = (USHORT)((cplcData[16] << 8) | cplcData[17]);
	cplc->icModuleFabricator = (USHORT)((cplcData[18] << 8) | cplcData[19]);
	cplc->icModulePackagingDate = (USHORT)((cplcData[20] << 8) | cplcData[21]);
	cplc->iccManufacturer = (USHORT)((cplcData[22] << 8) | cplcData[23]);
	cplc->icEmbeddingDate = (USHORT)((cplcData[24] << 8) | cplcData[25]);
	cplc->icPrePersonalizer = (USHORT)((cplcData[26] << 8) | cplcData[27]);
	cplc->icPrePersonalizationEquipmentDate = (USHORT)((cplcData[28] << 8) | cplcData[29]);
	cplc->icPrePersonalizationEquipmentId = (DWORD)((cplcData[30] << 24) | (cplcData[31] << 16) |
		(cplcData[32] << 8) | cplcData[33]) & 0xFFFFFFFF;
	cplc->icPersonalizer = (USHORT)((cplcData[34] << 8) | cplcData[35]);
	cplc->icPersonalizationDate = (USHORT)((cplcData[36] << 8) | cplcData[37]);
	cplc->icPersonalizationEquipmentId = (DWORD)((cplcData[38] << 24) | (cplcData[39] << 16) |
		(cplcData[40] << 8) | cplcData[41]) & 0xFFFFFFFF;

	OPGP_ERROR_CREATE_NO_ERROR(status);
	return status;
}

/**
 * Retrieves and parses the Card Production Life Cycle data object.
 * The CPLC data is read with GET DATA tag 9F7F and parsed into OPGP_CPLC.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context().
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication(), or NULL if the command is sent without secure messaging.
 * \param cplc [out] The parsed CPLC structure.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS OPGP_get_cplc(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
		OPGP_CPLC *cplc) {
	OPGP_ERROR_STATUS status;
	BYTE cplcData[APDU_RESPONSE_LEN];
	DWORD cplcDataLength = APDU_RESPONSE_LEN;

	OPGP_LOG_START(_T("OPGP_get_cplc"));
	status = GP211_get_data(cardContext, cardInfo, secInfo, (PBYTE)GP211_GET_DATA_CPLC_WHOLE_CPLC, cplcData, &cplcDataLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	status = OPGP_parse_cplc(cplcData, cplcDataLength, cplc);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	OPGP_ERROR_CREATE_NO_ERROR(status);
end:
	OPGP_LOG_END(_T("OPGP_get_cplc"), status);
	return status;
}


/**
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param securityLevel Level of security for all subsequent commands
 * <ul>
 * <li>GP211_SCP02_SECURITY_LEVEL_R_MAC - Each APDU response contains a R-MAC during the session.</li>
 * <li>GP211_SCP02_SECURITY_LEVEL_NO_SECURE_MESSAGING - Only the END R-MAC SESSION response message will contain a R-MAC.</li>
 * <li>GP211_SCP03_SECURITY_LEVEL_R_MAC - Each APDU response contains a R-MAC during the session.</li>
 * <li>GP211_SCP03_SECURITY_LEVEL_R_ENC_R_MAC - Each APDU response contains a R-MAC and R-encryption during the session.</li>
 * </ul>
 * \param data [in] Data for the BEGIN R-MAC SESSION command, e.g. extra challenge.
 * \param dataLength [in] Length of data.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_begin_R_MAC(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo, BYTE securityLevel, PBYTE data, DWORD dataLength)
{
	OPGP_ERROR_STATUS status;
	BYTE sendBuffer[30];
	DWORD sendBufferLength = 30;
	DWORD i=0;
	DWORD recvBufferLength=2;
	BYTE recvBuffer[2];
	OPGP_LOG_START(_T("GP211_begin_R_MAC"));
	sendBuffer[i++] = 0x80;
	sendBuffer[i++] = 0x7A;
	sendBuffer[i++] = securityLevel;
	sendBuffer[i++] = 1;
	sendBuffer[i++] = (BYTE)(1+dataLength);
	sendBuffer[i++] = (BYTE)dataLength;
	if (dataLength > 24) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_COMMAND_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_COMMAND_TOO_LARGE));
		goto end;
	}
	memcpy(sendBuffer+i, data, dataLength);
	i+=dataLength;
	sendBufferLength=i;

	status = OPGP_send_APDU(cardContext, cardInfo, secInfo, sendBuffer, sendBufferLength, recvBuffer, &recvBufferLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	CHECK_SW_9000(recvBuffer, recvBufferLength, status);
	secInfo->securityLevel |= securityLevel;
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("GP211_begin_R_MAC"), status);
	return status;
}

/**
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param secureChannelProtocol [in] The security channel protocol.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_end_R_MAC(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo, BYTE secureChannelProtocol)
{
	OPGP_ERROR_STATUS status;
	BYTE sendBuffer[6];
	DWORD sendBufferLength = 6;
	DWORD i=0;
	DWORD recvBufferLength=2;
	BYTE recvBuffer[2];
	OPGP_LOG_START(_T("GP211_end_R_MAC"));
	sendBuffer[i++] = 0x80;
	sendBuffer[i++] = 0x78;
	sendBuffer[i++] = 0;
	sendBuffer[i++] = 3;
	sendBuffer[i++] = 0; // Le
	if (secureChannelProtocol == GP211_SCP02) {
		/* Switch on, if R-MAC is only applied to last command of session. */
		secInfo->securityLevel |= GP211_SCP02_SECURITY_LEVEL_R_MAC;
	}
	if (secureChannelProtocol == GP211_SCP03) {
		/* Switch on, if R-MAC is only applied to last command of session. */
		secInfo->securityLevel |= GP211_SCP03_SECURITY_LEVEL_R_MAC;
	}

	status = OPGP_send_APDU(cardContext, cardInfo, secInfo, sendBuffer, sendBufferLength, recvBuffer, &recvBufferLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	CHECK_SW_9000(recvBuffer, recvBufferLength, status);
	// invert, this is the higher nibble
	secInfo->securityLevel &= ~GP211_SCP03_SECURITY_LEVEL_R_ENC_R_MAC;
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("GP211_end_R_MAC"), status);
	return status;
}

OPGP_ERROR_STATUS get_data(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
			  BYTE identifier[2], PBYTE recvBuffer, PDWORD recvBufferLength) {
	return get_data_with_data(cardContext, cardInfo, secInfo, identifier, NULL, 0, recvBuffer, recvBufferLength);
}

OPGP_ERROR_STATUS get_data_with_data(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
			  BYTE identifier[2], PBYTE dataObject, DWORD dataObjectLength,
			  PBYTE recvBuffer, PDWORD recvBufferLength) {
	OPGP_ERROR_STATUS status;
	BYTE sendBuffer[128];
	DWORD sendBufferLength = 0;
	DWORD cardDataLength = 0;
	DWORD recvBufferCapacity = 0;
	DWORD i=0;

	OPGP_LOG_START(_T("get_data"));

	if (identifier == NULL || recvBuffer == NULL || recvBufferLength == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		goto end;
	}
	if (dataObjectLength > sizeof(sendBuffer) - 6) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_COMMAND_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_COMMAND_TOO_LARGE));
		goto end;
	}
	if (dataObjectLength > 0 && dataObject == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		goto end;
	}

	recvBufferCapacity = *recvBufferLength;
	*recvBufferLength = 0;
	if (recvBufferCapacity == 0) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
		goto end;
	}

	sendBuffer[i++] = 0x80;
	sendBuffer[i++] = 0xCA;
	sendBuffer[i++] = identifier[0];
	sendBuffer[i++] = identifier[1];
	if (dataObjectLength > 0) {
		sendBuffer[i++] = (BYTE)dataObjectLength;
		memcpy(sendBuffer + i, dataObject, dataObjectLength);
		i += dataObjectLength;
	}
	sendBuffer[i++] = 0x00;
	sendBufferLength = i;

	cardDataLength = recvBufferCapacity;
	status = OPGP_send_APDU(cardContext, cardInfo, secInfo, sendBuffer, sendBufferLength, recvBuffer, &cardDataLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	if (cardDataLength < 2) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		goto end;
	}
	if (recvBuffer[cardDataLength-2] != 0x90 || recvBuffer[cardDataLength-1] != 0x00) {
		OPGP_ERROR_CREATE_ERROR(status, status.errorCode, OPGP_stringify_error(status.errorCode));
		goto end;
	}

	*recvBufferLength = cardDataLength - 2;
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	if (OPGP_ERROR_CHECK(status) && recvBufferLength != NULL) {
		*recvBufferLength = 0;
	}
	OPGP_LOG_END(_T("get_data"), status);
	return status;
}

/**
 * This command is useful to return the Card Data with identifier 0x0066 containing the
 * Card Recognition Data with tag 0x73 containing among others
 * the Secure Channel Protocol and the eventual implementations.
 * For getting the Secure Channel Protocol and Secure Channel Protocol implementation there is the
 * convenience function get_secure_channel_protocol_details().
 * See also data objects identified in ISO 7816-6.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param identifier [in] Two byte buffer with high and low order tag value for identifying card data.
 * \param recvBuffer [out] The buffer for the card data.
 * \param recvBufferLength [in, out] The length of the received card data.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_get_data_iso7816_4(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, BYTE identifier[2], PBYTE recvBuffer,
						PDWORD recvBufferLength) {
	OPGP_ERROR_STATUS status;
	BYTE sendBuffer[5];
	DWORD sendBufferLength = 5;
	BYTE cardData[256];
	DWORD cardDataLength = 256;
	DWORD i=0;
	OPGP_LOG_START(_T("get_data_iso7816-4"));
	sendBuffer[i++] = 0x00;
	sendBuffer[i++] = 0xCA;
	sendBuffer[i++] = identifier[0];
	sendBuffer[i++] = identifier[1];
	sendBuffer[i] = 0x00;

	status = OPGP_send_APDU(cardContext, cardInfo, NULL, sendBuffer, sendBufferLength, cardData, &cardDataLength);
	if (OPGP_ERROR_CHECK(status)) {
		*recvBufferLength = 0;
		goto end;
	}
	CHECK_SW_9000(cardData, cardDataLength, status);

	if (cardDataLength-2 > *recvBufferLength) {
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
	}
	memcpy(recvBuffer, cardData, cardDataLength-2);
	*recvBufferLength = cardDataLength-2;
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("get_data_iso7816-4"), status);
	return status;
}

/**
 * Can only be executed before a secure channel is created.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param *cardData [out] A pointer to the card recognition data.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_get_card_recognition_data(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
		GP211_CARD_RECOGNITION_DATA *cardData) {
	OPGP_ERROR_STATUS status;
	BYTE recvBuffer[256];
	DWORD recvBufferLength = sizeof(recvBuffer);

	OPGP_LOG_START(_T("GP211_get_card_data"));
	status = GP211_get_data(cardContext, cardInfo, secInfo, (PBYTE)GP211_GET_DATA_CARD_DATA, recvBuffer, &recvBufferLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	status = GP211_parse_card_recognition_data(recvBuffer, recvBufferLength, cardData);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	OPGP_ERROR_CREATE_NO_ERROR(status);
	goto end;
end:
	OPGP_LOG_END(_T("GP211_get_card_data"), status);
	return status;
}

/**
 * Parses a GlobalPlatform Card Recognition Data response.
 * The parser accepts the GET DATA card data object and walks to the tag 73 Card
 * Recognition Data template, then extracts the card management version, supported
 * Secure Channel Protocol OIDs and optional card details.
 * \param data [in] The card recognition data response.
 * \param dataLength [in] The length of data.
 * \param cardData [out] The parsed card recognition data.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS GP211_parse_card_recognition_data(const BYTE *data, DWORD dataLength,
		GP211_CARD_RECOGNITION_DATA *cardData) {
	OPGP_ERROR_STATUS status;
	LONG result;
	DWORD offset = 0, nestedOffset = 0;
	BYTE numSCPs = 0;
	TLV tlv1, tlv2, _73tlv;

	if (data == NULL || cardData == NULL || dataLength == 0) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}

	memset(cardData, 0, sizeof(*cardData));
	// read outer tag, should be one 0x66
	result = read_TLV((const BYTE *)data, dataLength, &tlv1);
	if (result == -1) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}
	// while tag 0x73 not found look into inner tlv objects
	while (tlv1.tag != 0x73) {
		result = read_TLV(tlv1.value, tlv1.length, &tlv2);
		if (result == -1) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			return status;
		}
		tlv1 = tlv2;
	}
	// 0x73 must be found, we parse in expected order
	_73tlv = tlv1;

	/* 0x06 Universal tag for Object Identifier (OID) and Length */
	result = read_TLV(_73tlv.value+offset, _73tlv.length-offset, &tlv1);
	if (result == -1) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}
	offset += result;

	/* {globalPlatform 1} OID for Card Recognition Data */
	OPGP_LOG_HEX(_T("GP211_get_card_data: OIDCardRecognitionData: "), tlv1.value, tlv1.length);

	/* Application tag 0 and length */
	result = read_TLV(_73tlv.value+offset, _73tlv.length-offset, &tlv1);
	if (result == -1 || tlv1.tag != CARD_DATA_APPLICATION_TAG_0) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}
	offset += result;
	/* inner tag: 0x06 Universal tag for Object Identifier (OID) and Length */
	result = read_TLV(tlv1.value, tlv1.length, &tlv2);
	if (result == -1) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}
	/* {globalPlatform 2 v} OID for Card Management Type and Version */
	{
		char oidBuffer[128];
		LONG oidLength = parse_OID_numeric_string(tlv2.value, tlv2.length, oidBuffer, sizeof(oidBuffer));
		if (oidLength != -1) {
			extract_oid_version(oidBuffer, cardData->version, sizeof(cardData->version));
		}
	}
	OPGP_LOG_HEX(_T("GP211_get_card_data: OIDCardManagementTypeAndVersion: "), tlv2.value, tlv2.length);

	/* Application tag 3 and length */
	result = read_TLV(_73tlv.value+offset, _73tlv.length-offset, &tlv1);
	if (result == -1 || tlv1.tag != CARD_DATA_APPLICATION_TAG_3) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}
	offset += result;
	/* inner tag: 0x06 Universal tag for Object Identifier (OID) and Length */
	result = read_TLV(tlv1.value, tlv1.length, &tlv2);
	if (result == -1) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}
	/* {globalPlatform 3} OID for Card Identification Scheme */
	OPGP_LOG_HEX(_T("GP211_get_card_data: OIDCardIdentificationScheme: "), tlv2.value, tlv2.length);

	/* Application tag 4 (=0x64) and length */
	// this can be multiple SCP information in Application tag 4 or a sequence of OIDs in 0x06
	do {
		result = read_TLV(_73tlv.value+offset, _73tlv.length-offset, &tlv1);
		if (result == -1 || tlv1.tag != CARD_DATA_APPLICATION_TAG_4) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			return status;
		}
		offset += result;
		nestedOffset = 0;
		do {
			/* inner tag: 0x06 Universal tag for Object Identifier (OID) and Length */
			result = read_TLV(tlv1.value+nestedOffset, tlv1.length, &tlv2);
			if (result == -1) {
				OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
				return status;
			}
			nestedOffset += result;
			/* {globalPlatform 4 scp i} OID for Secure Channel Protocol of
			* the Issuer Security Domain and its implementation options
			*/
			if (numSCPs < sizeof(cardData->scp)) {
				if (parse_card_data_scp_oid(tlv2.value, tlv2.length,
						&cardData->scp[numSCPs],
						&cardData->scpImpl[numSCPs],
						&cardData->scpImplValue[numSCPs],
						cardData->scpImplOidBytes[numSCPs],
						&cardData->scpImplOidBytesLength[numSCPs]) != 0) {
					OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
					return status;
				}
				numSCPs++;
			}
			OPGP_LOG_HEX(_T("GP211_get_card_data: OIDSecureChannelProtocol: "), tlv2.value, tlv2.length);
		}
		while (tlv1.length > nestedOffset && tlv1.value[nestedOffset] == OID_TAG);
		cardData->scpLength = numSCPs;
	}
	while (_73tlv.length > offset && _73tlv.value[offset] == CARD_DATA_APPLICATION_TAG_4);
	/* optional part */

	while (_73tlv.length > offset) {
		/* Application tag 5 and length */
		result = read_TLV(_73tlv.value+offset, _73tlv.length-offset, &tlv1);
		if (result == -1) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			return status;
		}
		offset += result;
		switch (tlv1.tag) {
			case CARD_DATA_APPLICATION_TAG_5:
				/* Card configuration details */
				if (parse_optional_oid_and_data(&tlv1,
						cardData->cardConfigurationDetailsOid,
						sizeof(cardData->cardConfigurationDetailsOid),
						&cardData->cardConfigurationDetailsOidLength,
						cardData->cardConfigurationDetails,
						sizeof(cardData->cardConfigurationDetails),
						&cardData->cardConfigurationDetailsLength) == -1) {
					OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
					return status;
				}
				OPGP_LOG_HEX(_T("GP211_get_card_data: CardConfigurationDetails: "),
					cardData->cardConfigurationDetails, cardData->cardConfigurationDetailsLength);
				break;
			case CARD_DATA_APPLICATION_TAG_6:
				/* Card / chip details */
				if (parse_optional_oid_and_data(&tlv1,
						cardData->cardChipDetailsOid,
						sizeof(cardData->cardChipDetailsOid),
						&cardData->cardChipDetailsOidLength,
						cardData->cardChipDetails,
						sizeof(cardData->cardChipDetails),
						&cardData->cardChipDetailsLength) == -1) {
					OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
					return status;
				}
				OPGP_LOG_HEX(_T("GP211_get_card_data: CardChipDetails: "),
					cardData->cardChipDetails, cardData->cardChipDetailsLength);
				break;
			case CARD_DATA_APPLICATION_TAG_7:
				/* Issuer Security Domain’s Trust Point certificate information */
				if (parse_optional_oid_and_data(&tlv1,
						cardData->issuerSecurityDomainsTrustPointCertificateInformationOid,
						sizeof(cardData->issuerSecurityDomainsTrustPointCertificateInformationOid),
						&cardData->issuerSecurityDomainsTrustPointCertificateInformationOidLength,
						cardData->issuerSecurityDomainsTrustPointCertificateInformation,
						sizeof(cardData->issuerSecurityDomainsTrustPointCertificateInformation),
						&cardData->issuerSecurityDomainsTrustPointCertificateInformationLength) == -1) {
					OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
					return status;
				}
				OPGP_LOG_HEX(_T("GP211_get_card_data: Issuer Security Domain’s Trust Point certificate information: "),
					cardData->issuerSecurityDomainsTrustPointCertificateInformation,
					cardData->issuerSecurityDomainsTrustPointCertificateInformationLength);
				break;
			case CARD_DATA_APPLICATION_TAG_8:
				/* Issuer Security Domain certificate information */
				if (parse_optional_oid_and_data(&tlv1,
						cardData->issuerSecurityDomainCertificateInformationOid,
						sizeof(cardData->issuerSecurityDomainCertificateInformationOid),
						&cardData->issuerSecurityDomainCertificateInformationOidLength,
						cardData->issuerSecurityDomainCertificateInformation,
						sizeof(cardData->issuerSecurityDomainCertificateInformation),
						&cardData->issuerSecurityDomainCertificateInformationLength) == -1) {
					OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
					return status;
				}
				OPGP_LOG_HEX(_T("GP211_get_card_data: Issuer Security Domain certificate information: "),
					cardData->issuerSecurityDomainCertificateInformation,
					cardData->issuerSecurityDomainCertificateInformationLength);
				break;
		}

	}

	OPGP_ERROR_CREATE_NO_ERROR(status);
	return status;
}

static OPGP_ERROR_STATUS copy_scp11_certificate_field(const GP_SIMPLE_TLV *tlv, PBYTE destination,
		DWORD destinationSize, PDWORD destinationLength, DWORD minimumLength, DWORD maximumLength) {
	OPGP_ERROR_STATUS status;
	if (tlv == NULL || destination == NULL || destinationLength == NULL
			|| tlv->length < minimumLength || tlv->length > maximumLength || tlv->length > destinationSize) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}
	memcpy(destination, tlv->value, tlv->length);
	*destinationLength = tlv->length;
	OPGP_ERROR_CREATE_NO_ERROR(status);
	return status;
}

static BOOL scp11_certificate_key_usage_valid(const GP211_SCP11_CERTIFICATE *certificate) {
	return certificate != NULL
		&& ((certificate->keyUsageLength == sizeof(GP211_SCP11_KEY_USAGE_DIGITAL_SIGNATURE_VERIFICATION)
				&& memcmp(certificate->keyUsage, GP211_SCP11_KEY_USAGE_DIGITAL_SIGNATURE_VERIFICATION,
					sizeof(GP211_SCP11_KEY_USAGE_DIGITAL_SIGNATURE_VERIFICATION)) == 0)
			|| (certificate->keyUsageLength == sizeof(GP211_SCP11_KEY_USAGE_KEY_AGREEMENT)
				&& memcmp(certificate->keyUsage, GP211_SCP11_KEY_USAGE_KEY_AGREEMENT,
					sizeof(GP211_SCP11_KEY_USAGE_KEY_AGREEMENT)) == 0));
}

static OPGP_ERROR_STATUS parse_scp11_certificate_public_key(const GP_SIMPLE_TLV *publicKeyTlv,
		GP211_SCP11_CERTIFICATE *certificate) {
	OPGP_ERROR_STATUS status;
	DWORD offset = 0;
	GP_SIMPLE_TLV tlv;

	if (publicKeyTlv == NULL || certificate == NULL || publicKeyTlv->tag != 0x7F49) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}

	while (offset < publicKeyTlv->length) {
		LONG result = parse_simple_tlv(publicKeyTlv->value + offset, publicKeyTlv->length - offset, &tlv);
		if (result < 0) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			return status;
		}
		switch (tlv.tag) {
			case 0xB0:
				status = copy_scp11_certificate_field(&tlv, certificate->publicKey, sizeof(certificate->publicKey),
						&certificate->publicKeyLength, 1, sizeof(certificate->publicKey));
				if (OPGP_ERROR_CHECK(status)) {
					return status;
				}
				break;
			case 0xF0:
				status = copy_scp11_certificate_field(&tlv, certificate->keyParameterReference, sizeof(certificate->keyParameterReference),
						&certificate->keyParameterReferenceLength, 1, sizeof(certificate->keyParameterReference));
				if (OPGP_ERROR_CHECK(status)) {
					return status;
				}
				break;
			default:
				break;
		}
		offset += tlv.tlvLength;
	}

	if (certificate->publicKeyLength == 0 || certificate->keyParameterReferenceLength == 0) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}

	OPGP_ERROR_CREATE_NO_ERROR(status);
	return status;
}

/**
 * Parses an SCP11 certificate encoded according to table 6-12.
 * The input must be a complete CERT.KA-KLOC.ECDSA or CERT.OCE.ECKA object
 * encoded as tag 7F21.
 * \param certificateData [in] The encoded SCP11 certificate.
 * \param certificateDataLength [in] The length of certificateData.
 * \param certificate [out] The parsed certificate fields.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS GP211_parse_scp11_certificate(PBYTE certificateData, DWORD certificateDataLength,
			  GP211_SCP11_CERTIFICATE *certificate) {
	OPGP_ERROR_STATUS status;
	GP_SIMPLE_TLV outer;
	GP_SIMPLE_TLV tlv;
	DWORD offset = 0;

	if (certificateData == NULL || certificateDataLength == 0 || certificate == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}
	memset(certificate, 0, sizeof(*certificate));

	if (parse_simple_tlv(certificateData, certificateDataLength, &outer) < 0
			|| outer.tag != 0x7F21 || outer.tlvLength != certificateDataLength) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}

	while (offset < outer.length) {
		LONG result = parse_simple_tlv(outer.value + offset, outer.length - offset, &tlv);
		if (result < 0) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			return status;
		}
		switch (tlv.tag) {
			case 0x93:
				if (certificate->certificateSerialNumberLength != 0) {
					OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
					return status;
				}
				status = copy_scp11_certificate_field(&tlv, certificate->certificateSerialNumber,
						sizeof(certificate->certificateSerialNumber), &certificate->certificateSerialNumberLength,
						1, sizeof(certificate->certificateSerialNumber));
				if (OPGP_ERROR_CHECK(status)) {
					return status;
				}
				break;
			case 0x42:
				if (certificate->authorityIdentifierLength != 0) {
					OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
					return status;
				}
				status = copy_scp11_certificate_field(&tlv, certificate->authorityIdentifier,
						sizeof(certificate->authorityIdentifier), &certificate->authorityIdentifierLength,
						1, sizeof(certificate->authorityIdentifier));
				if (OPGP_ERROR_CHECK(status)) {
					return status;
				}
				break;
			case 0x5F20:
				if (certificate->subjectIdentifierLength != 0) {
					OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
					return status;
				}
				status = copy_scp11_certificate_field(&tlv, certificate->subjectIdentifier,
						sizeof(certificate->subjectIdentifier), &certificate->subjectIdentifierLength,
						1, sizeof(certificate->subjectIdentifier));
				if (OPGP_ERROR_CHECK(status)) {
					return status;
				}
				break;
			case 0x95:
				if (certificate->keyUsageLength != 0) {
					OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
					return status;
				}
				status = copy_scp11_certificate_field(&tlv, certificate->keyUsage, sizeof(certificate->keyUsage),
						&certificate->keyUsageLength, 1, sizeof(certificate->keyUsage));
				if (OPGP_ERROR_CHECK(status)) {
					return status;
				}
				break;
			case 0x5F25:
				if (certificate->effectiveDatePresent || tlv.length != GP211_SCP11_CERTIFICATE_MAX_DATE_LENGTH) {
					OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
					return status;
				}
				memcpy(certificate->effectiveDate, tlv.value, tlv.length);
				certificate->effectiveDatePresent = 1;
				break;
			case 0x5F24:
				if (certificate->expirationDateLength != 0) {
					OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
					return status;
				}
				status = copy_scp11_certificate_field(&tlv, certificate->expirationDate,
						sizeof(certificate->expirationDate), &certificate->expirationDateLength,
						GP211_SCP11_CERTIFICATE_MAX_DATE_LENGTH, GP211_SCP11_CERTIFICATE_MAX_DATE_LENGTH);
				if (OPGP_ERROR_CHECK(status)) {
					return status;
				}
				break;
			case 0x53:
			case 0x73:
				if (certificate->discretionaryDataTag != 0) {
					OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
					return status;
				}
				certificate->discretionaryDataTag = tlv.tag;
				status = copy_scp11_certificate_field(&tlv, certificate->discretionaryData,
						sizeof(certificate->discretionaryData), &certificate->discretionaryDataLength,
						1, sizeof(certificate->discretionaryData));
				if (OPGP_ERROR_CHECK(status)) {
					return status;
				}
				break;
			case 0xBF20:
				if (certificate->authorizationsPresent) {
					OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
					return status;
				}
				status = copy_scp11_certificate_field(&tlv, certificate->authorizations,
						sizeof(certificate->authorizations), &certificate->authorizationsLength,
						1, sizeof(certificate->authorizations));
				if (OPGP_ERROR_CHECK(status)) {
					return status;
				}
				certificate->authorizationsPresent = 1;
				break;
			case 0x7F49:
				if (certificate->publicKeyLength != 0 || certificate->keyParameterReferenceLength != 0) {
					OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
					return status;
				}
				status = parse_scp11_certificate_public_key(&tlv, certificate);
				if (OPGP_ERROR_CHECK(status)) {
					return status;
				}
				break;
			case 0x5F37:
				if (certificate->signatureLength != 0) {
					OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
					return status;
				}
				status = copy_scp11_certificate_field(&tlv, certificate->signature,
						sizeof(certificate->signature), &certificate->signatureLength,
						1, sizeof(certificate->signature));
				if (OPGP_ERROR_CHECK(status)) {
					return status;
				}
				break;
			default:
				OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
				return status;
		}
		offset += tlv.tlvLength;
	}

	if (certificate->certificateSerialNumberLength == 0 || certificate->authorityIdentifierLength == 0
			|| certificate->subjectIdentifierLength == 0 || !scp11_certificate_key_usage_valid(certificate)
			|| certificate->expirationDateLength != GP211_SCP11_CERTIFICATE_MAX_DATE_LENGTH
			|| certificate->publicKeyLength == 0 || certificate->keyParameterReferenceLength == 0
			|| certificate->signatureLength == 0) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}

	OPGP_ERROR_CREATE_NO_ERROR(status);
	return status;
}

OPGP_ERROR_STATUS GP211_parse_certificate_store(const BYTE *certificateStoreData, DWORD certificateStoreDataLength,
			  PBYTE parsedCertificateStore, PDWORD parsedCertificateStoreLength) {
	OPGP_ERROR_STATUS status;
	GP_SIMPLE_TLV certificateStoreTlv;
	const BYTE *certificateList;
	DWORD certificateListLength;
	DWORD offset = 0;
	DWORD outputOffset = 0;
	USHORT certificateTag = 0;

	if (certificateStoreData == NULL || certificateStoreDataLength == 0
			|| parsedCertificateStore == NULL || parsedCertificateStoreLength == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}

	if (parse_simple_tlv(certificateStoreData, certificateStoreDataLength, &certificateStoreTlv) < 0
			|| certificateStoreTlv.tag != 0xBF21 || certificateStoreTlv.tlvLength != certificateStoreDataLength) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}

	certificateList = certificateStoreTlv.value;
	certificateListLength = certificateStoreTlv.length;

	while (offset < certificateListLength) {
		GP_SIMPLE_TLV certificateTlv;
		LONG parseResult = parse_simple_tlv(certificateList + offset, certificateListLength - offset, &certificateTlv);

		if (parseResult < 0 || certificateTlv.tlvLength == 0) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			return status;
		}

		if (certificateTag == 0) {
			certificateTag = certificateTlv.tag;
			if (certificateTag != 0x7F21 && certificateTag != 0x30) {
				OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
				return status;
			}
		} else if (certificateTlv.tag != certificateTag) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			return status;
		}

		if (certificateTag == 0x7F21) {
			GP211_SCP11_CERTIFICATE certificate;
			status = GP211_parse_scp11_certificate((PBYTE)(certificateList + offset), certificateTlv.tlvLength, &certificate);
			if (OPGP_ERROR_CHECK(status)) {
				return status;
			}
			if (*parsedCertificateStoreLength - outputOffset < sizeof(certificate)) {
				OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
				return status;
			}
			memcpy(parsedCertificateStore + outputOffset, &certificate, sizeof(certificate));
			outputOffset += sizeof(certificate);
		}

		offset += certificateTlv.tlvLength;
	}

	if (certificateTag == 0 || offset != certificateListLength) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}

	if (certificateTag == 0x30) {
		DWORD pemLength = *parsedCertificateStoreLength;
		status = convert_der_certificate_list_to_pem(certificateList, certificateListLength,
				parsedCertificateStore, &pemLength);
		if (OPGP_ERROR_CHECK(status)) {
			return status;
		}
		outputOffset = pemLength;
	}

	*parsedCertificateStoreLength = outputOffset;
	OPGP_ERROR_CREATE_NO_ERROR(status);
	return status;
}

static OPGP_ERROR_STATUS build_scp11_certificate_signed_data(GP211_SCP11_CERTIFICATE *certificate,
		PBYTE signedData, DWORD signedDataSize, PDWORD signedDataLength) {
	OPGP_ERROR_STATUS status;
	BYTE publicKeyData[256];
	DWORD publicKeyDataLength = 0;

	if (certificate == NULL || signedData == NULL || signedDataSize == 0 || signedDataLength == NULL
			|| certificate->certificateSerialNumberLength == 0 || certificate->authorityIdentifierLength == 0
			|| certificate->subjectIdentifierLength == 0 || !scp11_certificate_key_usage_valid(certificate)
			|| certificate->expirationDateLength != GP211_SCP11_CERTIFICATE_MAX_DATE_LENGTH
			|| certificate->publicKeyLength == 0 || certificate->keyParameterReferenceLength == 0) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}
	if (certificate->discretionaryDataLength > 0
			&& certificate->discretionaryDataTag != 0x53 && certificate->discretionaryDataTag != 0x73) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}

	*signedDataLength = 0;
	status = append_tlv(signedData, signedDataSize, signedDataLength,
			0x93, certificate->certificateSerialNumber, certificate->certificateSerialNumberLength);
	if (OPGP_ERROR_CHECK(status)) {
		return status;
	}
	status = append_tlv(signedData, signedDataSize, signedDataLength,
			0x42, certificate->authorityIdentifier, certificate->authorityIdentifierLength);
	if (OPGP_ERROR_CHECK(status)) {
		return status;
	}
	status = append_tlv(signedData, signedDataSize, signedDataLength,
			0x5F20, certificate->subjectIdentifier, certificate->subjectIdentifierLength);
	if (OPGP_ERROR_CHECK(status)) {
		return status;
	}
	status = append_tlv(signedData, signedDataSize, signedDataLength,
			0x95, certificate->keyUsage, certificate->keyUsageLength);
	if (OPGP_ERROR_CHECK(status)) {
		return status;
	}
	if (certificate->effectiveDatePresent) {
		status = append_tlv(signedData, signedDataSize, signedDataLength,
				0x5F25, certificate->effectiveDate, GP211_SCP11_CERTIFICATE_MAX_DATE_LENGTH);
		if (OPGP_ERROR_CHECK(status)) {
			return status;
		}
	}
	status = append_tlv(signedData, signedDataSize, signedDataLength,
			0x5F24, certificate->expirationDate, certificate->expirationDateLength);
	if (OPGP_ERROR_CHECK(status)) {
		return status;
	}
	if (certificate->discretionaryDataLength > 0) {
		status = append_tlv(signedData, signedDataSize, signedDataLength,
				certificate->discretionaryDataTag, certificate->discretionaryData, certificate->discretionaryDataLength);
		if (OPGP_ERROR_CHECK(status)) {
			return status;
		}
	}
	if (certificate->authorizationsPresent) {
		status = append_tlv(signedData, signedDataSize, signedDataLength,
				0xBF20, certificate->authorizations, certificate->authorizationsLength);
		if (OPGP_ERROR_CHECK(status)) {
			return status;
		}
	}
	status = append_tlv(publicKeyData, sizeof(publicKeyData), &publicKeyDataLength,
			0xB0, certificate->publicKey, certificate->publicKeyLength);
	if (OPGP_ERROR_CHECK(status)) {
		return status;
	}
	status = append_tlv(publicKeyData, sizeof(publicKeyData), &publicKeyDataLength,
			0xF0, certificate->keyParameterReference, certificate->keyParameterReferenceLength);
	if (OPGP_ERROR_CHECK(status)) {
		return status;
	}
	status = append_tlv(signedData, signedDataSize, signedDataLength,
			0x7F49, publicKeyData, publicKeyDataLength);
	return status;
}

/**
 * Builds and signs an SCP11 certificate encoded according to table 6-12.
 * The signed data is generated according to table 6-13. If subjectPublicKeyFileName
 * is provided, the public key Q and key parameter reference are extracted from the
 * OpenSSL EC public or private key file before signing.
 * \param certificate [in, out] The certificate fields. The public key and signature fields are updated.
 * \param subjectPublicKeyFileName [in] OpenSSL EC public or private key file for the certificate subject, or NULL to use certificate->publicKey.
 * \param subjectPublicKeyPassPhrase [in] Passphrase for subjectPublicKeyFileName, or NULL if not needed.
 * \param signingPrivateKeyFileName [in] OpenSSL EC private key file used to sign the certificate.
 * \param signingPrivateKeyPassPhrase [in] Passphrase for signingPrivateKeyFileName, or NULL if not needed.
 * \param certificateData [out] The encoded 7F21 certificate.
 * \param certificateDataLength [in, out] The length of certificateData and, on success, the number of encoded bytes.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS GP211_build_scp11_certificate(GP211_SCP11_CERTIFICATE *certificate,
			  OPGP_STRING subjectPublicKeyFileName, char *subjectPublicKeyPassPhrase,
			  OPGP_STRING signingPrivateKeyFileName, char *signingPrivateKeyPassPhrase,
			  PBYTE certificateData, PDWORD certificateDataLength) {
	OPGP_ERROR_STATUS status;
	BYTE signedData[GP211_SCP11_CERTIFICATE_MAX_AUTHORIZATIONS_LENGTH + 1024];
	DWORD signedDataLength = 0;
	BYTE certificateValue[GP211_SCP11_CERTIFICATE_MAX_AUTHORIZATIONS_LENGTH + 1300];
	DWORD certificateValueLength = 0;
	DWORD outputSize;
	BYTE eccKeyComponentType;
	BYTE keyParameterReference;
	DWORD signatureLength;

	if (certificate == NULL || signingPrivateKeyFileName == NULL || _tcslen(signingPrivateKeyFileName) == 0
			|| certificateData == NULL || certificateDataLength == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}

	if (subjectPublicKeyFileName != NULL && _tcslen(subjectPublicKeyFileName) > 0) {
		certificate->publicKeyLength = sizeof(certificate->publicKey);
		status = read_public_ecc_key(subjectPublicKeyFileName, subjectPublicKeyPassPhrase,
				certificate->publicKey, &certificate->publicKeyLength,
				&eccKeyComponentType, &keyParameterReference, NULL);
		if (OPGP_ERROR_CHECK(status)) {
			return status;
		}
		certificate->keyParameterReference[0] = keyParameterReference;
		certificate->keyParameterReferenceLength = 1;
	}

	status = build_scp11_certificate_signed_data(certificate, signedData, sizeof(signedData), &signedDataLength);
	if (OPGP_ERROR_CHECK(status)) {
		return status;
	}

	signatureLength = sizeof(certificate->signature);
	status = calculate_ecc_signature(signedData, signedDataLength, signingPrivateKeyFileName,
			signingPrivateKeyPassPhrase, certificate->signature, &signatureLength);
	if (OPGP_ERROR_CHECK(status)) {
		return status;
	}
	certificate->signatureLength = signatureLength;

	memcpy(certificateValue, signedData, signedDataLength);
	certificateValueLength = signedDataLength;
	status = append_tlv(certificateValue, sizeof(certificateValue), &certificateValueLength,
			0x5F37, certificate->signature, certificate->signatureLength);
	if (OPGP_ERROR_CHECK(status)) {
		return status;
	}

	outputSize = *certificateDataLength;
	if (outputSize < certificateValueLength + 5) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
		return status;
	}
	*certificateDataLength = 0;
	status = append_tlv(certificateData, outputSize, certificateDataLength,
			0x7F21, certificateValue, certificateValueLength);
	return status;
}

static BOOL is_scp11_certificate_list(PBYTE certificateData, DWORD certificateDataLength) {
	GP_SIMPLE_TLV certificateTlv;
	DWORD offset = 0;
	USHORT certificateTag = 0;

	if (certificateData == NULL || certificateDataLength == 0) {
		return 0;
	}
	while (offset < certificateDataLength) {
		LONG result = parse_simple_tlv(certificateData + offset, certificateDataLength - offset, &certificateTlv);
		if (result < 0) {
			return 0;
		}
		if (certificateTag == 0) {
			if (certificateTlv.tag != 0x7F21 && certificateTlv.tag != 0x30) {
				return 0;
			}
			certificateTag = certificateTlv.tag;
		} else if (certificateTlv.tag != certificateTag) {
			return 0;
		}
		offset += certificateTlv.tlvLength;
	}
	return offset == certificateDataLength;
}

static OPGP_ERROR_STATUS read_binary_file(OPGP_STRING fileName, PBYTE data, PDWORD dataLength) {
	OPGP_ERROR_STATUS status;
	FILE *file = NULL;
	long fileLength;
	size_t readLength;

	if (fileName == NULL || _tcslen(fileName) == 0 || data == NULL || dataLength == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_FILENAME, OPGP_stringify_error(OPGP_ERROR_INVALID_FILENAME));
		return status;
	}

	file = _tfopen(fileName, _T("rb"));
	if (file == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, errno, OPGP_stringify_error(errno));
		return status;
	}
	if (fseek(file, 0, SEEK_END) != 0) {
		OPGP_ERROR_CREATE_ERROR(status, errno, OPGP_stringify_error(errno));
		goto end;
	}
	fileLength = ftell(file);
	if (fileLength < 0) {
		OPGP_ERROR_CREATE_ERROR(status, errno, OPGP_stringify_error(errno));
		goto end;
	}
	if ((DWORD)fileLength > *dataLength) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
		goto end;
	}
	if (fseek(file, 0, SEEK_SET) != 0) {
		OPGP_ERROR_CREATE_ERROR(status, errno, OPGP_stringify_error(errno));
		goto end;
	}
	readLength = fread(data, 1, (size_t)fileLength, file);
	if ((long)readLength != fileLength) {
		OPGP_ERROR_CREATE_ERROR(status, errno, OPGP_stringify_error(errno));
		goto end;
	}
	*dataLength = (DWORD)readLength;
	OPGP_ERROR_CREATE_NO_ERROR(status);

end:
	if (file != NULL) {
		fclose(file);
	}
	return status;
}

static LONG build_OID_numeric_string(const char *oid, PBYTE out, DWORD outLength) {
	const char *start = oid;
	const char *end;
	unsigned long long val;
	DWORD offset = 0;
	unsigned long long first = 0, second = 0;

	if (oid == NULL || out == NULL) return -1;

	// Parse first component
	first = strtoull(start, (char **)&end, 10);
	if (start == end || *end != '.') return -1;
	start = end + 1;

	// Parse second component
	second = strtoull(start, (char **)&end, 10);
	if (start == end) return -1;
	if (outLength < 1) return -1;
	out[offset++] = (BYTE)(first * 40 + second);
	if (*end == '.') start = end + 1;
	else start = end;

	while (*start != '\0') {
		val = strtoull(start, (char **)&end, 10);
		if (start == end) return -1;

		// Encode base 128
		BYTE tmp[10];
		int n = 0;
		tmp[n++] = (BYTE)(val & 0x7F);
		val >>= 7;
		while (val > 0) {
			tmp[n++] = (BYTE)((val & 0x7F) | 0x80);
			val >>= 7;
		}
		if (offset + n > outLength) return -1;
		while (n > 0) {
			out[offset++] = tmp[--n];
		}

		if (*end == '.') start = end + 1;
		else start = end;
	}
	return (LONG)offset;
}

static LONG write_tlv(BYTE tag, const BYTE *value, DWORD length, PBYTE out, DWORD outLength, DWORD offset) {
	LONG lenBytes;
	if (offset + 1 > outLength) return -1;
	out[offset++] = tag;
	lenBytes = write_TLV_length(out, offset, outLength - offset, (USHORT)length);
	if (lenBytes == -1) return -1;
	offset += lenBytes;
	if (offset + length > outLength) return -1;
	memcpy(out + offset, value, length);
	return (LONG)(offset + length);
}

/**
 * Encodes an ASCII decimal string as packed BCD.
 * Digits are written high nibble first; odd length strings are padded with 0xF
 * in the low nibble of the last byte.
 * \param numericString [in] Null terminated string containing only decimal digits.
 * \param bcdData [out] The output buffer for the BCD encoded data.
 * \param bcdDataLength [in, out] The length of bcdData and, on success, the number of encoded bytes.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS OPGP_build_bcd_encoding(const char *numericString, PBYTE bcdData, PDWORD bcdDataLength) {
	OPGP_ERROR_STATUS status;
	size_t len;
	size_t i;
	DWORD bcdLen;

	if (numericString == NULL || bcdData == NULL || bcdDataLength == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_VALIDATION_FAILED, OPGP_stringify_error(OPGP_ERROR_VALIDATION_FAILED));
		return status;
	}

	len = strlen(numericString);
	bcdLen = (DWORD)((len + 1) / 2);
	if (*bcdDataLength < bcdLen) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
		return status;
	}

	memset(bcdData, 0, bcdLen);
	for (i = 0; i < len; i++) {
		BYTE digit;
		if (numericString[i] < '0' || numericString[i] > '9') {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_VALIDATION_FAILED, OPGP_stringify_error(OPGP_ERROR_VALIDATION_FAILED));
			return status;
		}
		digit = numericString[i] - '0';
		if (i % 2 == 0) {
			bcdData[i / 2] |= (digit << 4);
		} else {
			bcdData[i / 2] |= digit;
		}
	}
	// If odd length, pad with 0xF in the lower nibble of the last byte
	if (len % 2 != 0) {
		bcdData[len / 2] |= 0x0F;
	}

	*bcdDataLength = bcdLen;
	OPGP_ERROR_CREATE_NO_ERROR(status);
	return status;
}

/**
 * Builds a GlobalPlatform Card Recognition Data response.
 * The output is encoded as tag 66 containing tag 73 with the card recognition
 * OID, card management version OID, card identification scheme OID, SCP OIDs
 * and optional card detail templates from GP211_CARD_RECOGNITION_DATA.
 * \param cardData [in] The card recognition data to encode.
 * \param data [out] The output buffer for the encoded response.
 * \param dataLength [in, out] The length of data and, on success, the number of encoded bytes.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS GP211_build_card_recognition_data(const GP211_CARD_RECOGNITION_DATA *cardData,
							 PBYTE data, PDWORD dataLength) {
	OPGP_ERROR_STATUS status;
	BYTE buf[512];
	DWORD offset = 0;
	LONG res;
	BYTE oidBuf[128];
	LONG oidLen;
	BYTE innerBuf[256];
	DWORD innerOffset = 0;
	DWORD i;

	if (cardData == NULL || data == NULL || dataLength == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_VALIDATION_FAILED, OPGP_stringify_error(OPGP_ERROR_VALIDATION_FAILED));
		return status;
	}

	// Tag 0x60: OID for Card Management Type and Version
	// 1. OID for Card Recognition Data (GlobalPlatform 1) - Usually 1.2.840.114283.1
	oidLen = build_OID_numeric_string("1.2.840.114283.1", oidBuf, sizeof(oidBuf));
	res = write_tlv(0x06, oidBuf, oidLen, buf, sizeof(buf), offset);
	if (res == -1) goto err_buf;
	offset = (DWORD)res;

	// 2. Tag 0x60: OID for Card Management Type and Version
	// If cardData->version is "2.1.1", OID is 1.2.840.114283.2.1.1
	char fullOid[128];
	snprintf(fullOid, sizeof(fullOid), "1.2.840.114283.2.%s", cardData->version);
	oidLen = build_OID_numeric_string(fullOid, oidBuf, sizeof(oidBuf));
	innerOffset = 0;
	res = write_tlv(0x06, oidBuf, oidLen, innerBuf, sizeof(innerBuf), innerOffset);
	if (res == -1) goto err_buf;
	innerOffset = (DWORD)res;
	res = write_tlv(CARD_DATA_APPLICATION_TAG_0, innerBuf, innerOffset, buf, sizeof(buf), offset);
	if (res == -1) goto err_buf;
	offset = (DWORD)res;

	// 3. Tag 0x63: OID for Card Identification Scheme
	oidLen = build_OID_numeric_string("1.2.840.114283.3", oidBuf, sizeof(oidBuf));
	innerOffset = 0;
	res = write_tlv(0x06, oidBuf, oidLen, innerBuf, sizeof(innerBuf), innerOffset);
	if (res == -1) goto err_buf;
	innerOffset = (DWORD)res;
	res = write_tlv(CARD_DATA_APPLICATION_TAG_3, innerBuf, innerOffset, buf, sizeof(buf), offset);
	if (res == -1) goto err_buf;
	offset = (DWORD)res;

	// 4. Tag 0x64: Secure Channel Protocol OIDs
	innerOffset = 0;
	for (i = 0; i < cardData->scpLength; i++) {
		DWORD scpImplValue = cardData->scpImplValue[i];
		if (scpImplValue == 0 && cardData->scpImplOidBytesLength[i] == 0) {
			scpImplValue = cardData->scpImpl[i];
		}
		snprintf(fullOid, sizeof(fullOid), "1.2.840.114283.4.%u.%lu",
			(unsigned int)cardData->scp[i], (unsigned long)scpImplValue);
		oidLen = build_OID_numeric_string(fullOid, oidBuf, sizeof(oidBuf));
		res = write_tlv(0x06, oidBuf, oidLen, innerBuf, sizeof(innerBuf), innerOffset);
		if (res == -1) goto err_buf;
		innerOffset = (DWORD)res;
	}
	res = write_tlv(CARD_DATA_APPLICATION_TAG_4, innerBuf, innerOffset, buf, sizeof(buf), offset);
	if (res == -1) goto err_buf;
	offset = (DWORD)res;

	// Optional tags 5, 6, 7, 8
	struct {
		BYTE tag;
		const char *oid;
		const BYTE *data;
		DWORD dataLen;
	} optional[] = {
		{ CARD_DATA_APPLICATION_TAG_5, cardData->cardConfigurationDetailsOid, cardData->cardConfigurationDetails, cardData->cardConfigurationDetailsLength },
		{ CARD_DATA_APPLICATION_TAG_6, cardData->cardChipDetailsOid, cardData->cardChipDetails, cardData->cardChipDetailsLength },
		{ CARD_DATA_APPLICATION_TAG_7, cardData->issuerSecurityDomainsTrustPointCertificateInformationOid, cardData->issuerSecurityDomainsTrustPointCertificateInformation, cardData->issuerSecurityDomainsTrustPointCertificateInformationLength },
		{ CARD_DATA_APPLICATION_TAG_8, cardData->issuerSecurityDomainCertificateInformationOid, cardData->issuerSecurityDomainCertificateInformation, cardData->issuerSecurityDomainCertificateInformationLength }
	};

	for (i = 0; i < 4; i++) {
		if (optional[i].oid[0] != '\0' || optional[i].dataLen > 0) {
			innerOffset = 0;
			if (optional[i].oid[0] != '\0') {
				oidLen = build_OID_numeric_string(optional[i].oid, oidBuf, sizeof(oidBuf));
				res = write_tlv(0x06, oidBuf, oidLen, innerBuf, sizeof(innerBuf), innerOffset);
				if (res == -1) goto err_buf;
				innerOffset = (DWORD)res;
			}
			if (optional[i].dataLen > 0) {
				if (innerOffset + optional[i].dataLen > sizeof(innerBuf)) goto err_buf;
				memcpy(innerBuf + innerOffset, optional[i].data, optional[i].dataLen);
				innerOffset += optional[i].dataLen;
			}
			res = write_tlv(optional[i].tag, innerBuf, innerOffset, buf, sizeof(buf), offset);
			if (res == -1) goto err_buf;
			offset = (DWORD)res;
		}
	}

	// Now wrap in 0x73
	BYTE finalBuf[1024];
	DWORD finalOffset = 0;
	res = write_tlv(0x73, buf, offset, finalBuf, sizeof(finalBuf), finalOffset);
	if (res == -1) goto err_buf;
	finalOffset = (DWORD)res;

	// Wrap in 0x66
	BYTE finalBuf2[1024];
	DWORD finalOffset2 = 0;
	res = write_tlv(0x66, finalBuf, finalOffset, finalBuf2, sizeof(finalBuf2), finalOffset2);
	if (res == -1) goto err_buf;
	finalOffset2 = (DWORD)res;

	if (*dataLength < finalOffset2) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
		return status;
	}
	memcpy(data, finalBuf2, finalOffset2);
	*dataLength = finalOffset2;

	OPGP_ERROR_CREATE_NO_ERROR(status);
	return status;

err_buf:
	OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
	return status;
}

/**
 * Can only be executed before a secure channel is created.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param *cardCapabilityInfo [out] A pointer to the card capability information.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_get_card_capability_information(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
		GP211_CARD_CAPABILITY_INFORMATION *cardCapabilityInfo) {
	OPGP_ERROR_STATUS status;
	BYTE recvBuffer[256];
	DWORD recvBufferLength = sizeof(recvBuffer);

	OPGP_LOG_START(_T("GP211_get_card_capability_information"));

	status = GP211_get_data(cardContext, cardInfo, secInfo, (PBYTE)GP211_GET_DATA_CARD_CAPABILITY_INFORMATION, recvBuffer, &recvBufferLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	status = GP211_parse_card_capability_information(recvBuffer, recvBufferLength, cardCapabilityInfo);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	OPGP_ERROR_CREATE_NO_ERROR(status);
	goto end;
end:
	OPGP_LOG_END(_T("GP211_get_card_capability_information"), status);
	return status;
}

/**
 * Parses a GlobalPlatform Card Capability Information response.
 * The input must contain tag 67 and may include supported SCP information,
 * privilege masks, algorithm identifiers, key parameter references and policy
 * flags.
 * \param data [in] The card capability information response.
 * \param dataLength [in] The length of data.
 * \param cardCapabilityInfo [out] The parsed card capability information.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS GP211_parse_card_capability_information(const BYTE *data, DWORD dataLength,
		GP211_CARD_CAPABILITY_INFORMATION *cardCapabilityInfo) {
	OPGP_ERROR_STATUS status;
	LONG result;
	DWORD offset = 0;
	TLV tlv1, tlv2;

	if (data == NULL || cardCapabilityInfo == NULL || dataLength == 0) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}

	memset(cardCapabilityInfo, 0, sizeof(*cardCapabilityInfo));
	result = read_TLV((const BYTE *)data, dataLength, &tlv1);
	if (result == -1 || tlv1.tag != 0x67) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}

	offset = 0;
	while (offset < tlv1.length) {
		result = read_TLV(tlv1.value + offset, tlv1.length - offset, &tlv2);
		if (result == -1) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			return status;
		}
		switch (tlv2.tag) {
			case 0xA0:
				if (parse_card_capability_scp_info(&tlv2, cardCapabilityInfo) == -1) {
					OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
					return status;
				}
				break;
			case 0x81:
				memcpy(cardCapabilityInfo->ssdPrivileges, tlv2.value,
					min(tlv2.length, (DWORD)sizeof(cardCapabilityInfo->ssdPrivileges)));
				break;
			case 0x82:
				memcpy(cardCapabilityInfo->appPrivileges, tlv2.value,
					min(tlv2.length, (DWORD)sizeof(cardCapabilityInfo->appPrivileges)));
				break;
			case 0x83:
				cardCapabilityInfo->lfdbhAlgorithmsLength = min(tlv2.length, (DWORD)sizeof(cardCapabilityInfo->lfdbhAlgorithms));
				memcpy(cardCapabilityInfo->lfdbhAlgorithms, tlv2.value, cardCapabilityInfo->lfdbhAlgorithmsLength);
				break;
			case 0x84:
				if (tlv2.length > 0) {
					cardCapabilityInfo->lfdbencryptionCipherSuites = tlv2.value[0];
				}
				break;
			case 0x85:
				if (tlv2.length == 1) {
					cardCapabilityInfo->tokenCipherSuites = (USHORT)(tlv2.value[0] << 8);
				} else if (tlv2.length > 1) {
					cardCapabilityInfo->tokenCipherSuites = (USHORT)((tlv2.value[0] << 8) | tlv2.value[1]);
				}
				break;
			case 0x86:
				if (tlv2.length == 1) {
					cardCapabilityInfo->receiptCipherSuites = (USHORT)(tlv2.value[0] << 8);
				} else if (tlv2.length > 1) {
					cardCapabilityInfo->receiptCipherSuites = (USHORT)((tlv2.value[0] << 8) | tlv2.value[1]);
				}
				break;
			case 0x87:
				if (tlv2.length == 1) {
					cardCapabilityInfo->dapCipherSuites = (USHORT)(tlv2.value[0] << 8);
				} else if (tlv2.length > 1) {
					cardCapabilityInfo->dapCipherSuites = (USHORT)((tlv2.value[0] << 8) | tlv2.value[1]);
				}
				break;
			case 0x88:
				cardCapabilityInfo->keyParameterReferenceListLength = min(tlv2.length, (DWORD)sizeof(cardCapabilityInfo->keyParameterReferenceList));
				memcpy(cardCapabilityInfo->keyParameterReferenceList, tlv2.value, cardCapabilityInfo->keyParameterReferenceListLength);
				break;
			case 0x89:
				if (tlv2.length > 0) {
					cardCapabilityInfo->elfUpgrade = tlv2.value[0];
				}
				break;
			case 0x8A:
				cardCapabilityInfo->tokenIdentifierDenyList = (tlv2.length > 0 && tlv2.value[0] != 0);
				break;
			case 0x8B:
				cardCapabilityInfo->securityDomainSelfRemoval = (tlv2.length > 0 && tlv2.value[0] != 0);
				break;
		}
		offset += tlv2.tlvLength;
	}

	OPGP_ERROR_CREATE_NO_ERROR(status);
	return status;
}

/**
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param *secureChannelProtocol [out] A pointer to the Secure Channel Protocol to use.
 * \param *secureChannelProtocolImpl [out] A pointer to the implementation of the Secure Channel Protocol.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_get_secure_channel_protocol_details(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
										 BYTE *secureChannelProtocol, BYTE *secureChannelProtocolImpl) {
	OPGP_ERROR_STATUS status;
	BOOL found = 0;
	GP211_CARD_RECOGNITION_DATA cardData;

	OPGP_LOG_START(_T("GP211_get_secure_channel_protocol_details"));

	status = GP211_get_card_recognition_data(cardContext, cardInfo, secInfo, &cardData);
	if (!OPGP_ERROR_CHECK(status)) {
		for (int i = 0; i < cardData.scpLength; i++) {
			// only supporting SCP01 - SCP03
			if (cardData.scp[i] == GP211_SCP01 || cardData.scp[i] == GP211_SCP02 || cardData.scp[i] == GP211_SCP03) {
				*secureChannelProtocol = cardData.scp[i];
				*secureChannelProtocolImpl = cardData.scpImpl[i];
				OPGP_log_Msg(_T("Using Secure Channel Protocol 0x%02x with Secure Channel Protocol Impl: 0x%02x\n"),
					*secureChannelProtocol,
					*secureChannelProtocolImpl);
				found = 1;
				break;
			}
		}

		if (!found) {
			OPGP_ERROR_CREATE_ERROR(status,
				OPGP_ERROR_NO_SUPPORTED_SCP_FOUND,
				OPGP_stringify_error(OPGP_ERROR_NO_SUPPORTED_SCP_FOUND));
		} else {
			OPGP_ERROR_CREATE_NO_ERROR(status);
		}
	}
	OPGP_LOG_END(_T("GP211_get_secure_channel_protocol_details"), status);
	return status;
}

/**
 * The card must support the optional report of key information templates.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param keyInformationTemplate [in] The number of the key information template.
 * \param *keyInformation [out] A pointer to an array of GP211_KEY_INFORMATION structures.
 * \param keyInformationLength [in, out] The number of GP211_KEY_INFORMATION structures.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_get_key_information_templates(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
								   BYTE keyInformationTemplate,
								   GP211_KEY_INFORMATION *keyInformation, PDWORD keyInformationLength) {
	return get_key_information_templates(cardContext, cardInfo, secInfo,
								   keyInformationTemplate,
								   keyInformation, keyInformationLength);
}

OPGP_ERROR_STATUS get_key_information_templates(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
								   BYTE keyInformationTemplate,
								   GP211_KEY_INFORMATION *keyInformation, PDWORD keyInformationLength) {
	OPGP_ERROR_STATUS status;
	BYTE sendBuffer[5];
	DWORD sendBufferLength = 5;
	BYTE cardData[APDU_RESPONSE_LEN];
	DWORD cardDataLength = APDU_RESPONSE_LEN;
	DWORD offset = 0;
	DWORD i = 0;
	DWORD result;
	TLV tlv1, tlv2;
	OPGP_LOG_START(_T("get_key_information_templates"));
	sendBuffer[i++] = 0x80;
	sendBuffer[i++] = 0xCA;
	sendBuffer[i++] = keyInformationTemplate;
	sendBuffer[i++] = 0xE0;
	sendBuffer[i] = 0x00;

	status = OPGP_send_APDU(cardContext, cardInfo, secInfo, sendBuffer, sendBufferLength, cardData, &cardDataLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	CHECK_SW_9000(cardData, cardDataLength, status);

	i=0;
	// example: E006C00401FF80109000
	// parse E0
	result = read_TLV(cardData, cardDataLength, &tlv1);
	if (result == -1 || tlv1.tag != 0xE0) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		goto end;
	}
	offset = 0;
	while (offset < tlv1.length) {
		DWORD j = 0;
		BYTE k = 0;
		// parse C0
		result = read_TLV(tlv1.value + offset, tlv1.length - offset, &tlv2);
		if (result == -1 || tlv2.tag != 0xC0) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			goto end;
		}
		if (*keyInformationLength <= i) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_MORE_KEY_INFORMATION_TEMPLATES, OPGP_stringify_error(OPGP_ERROR_MORE_KEY_INFORMATION_TEMPLATES)); goto end; };
		}
		keyInformation[i].keyIndex = tlv2.value[j++];
		keyInformation[i].keySetVersion = tlv2.value[j++];

		while (j < tlv2.length && k < 8) {
			if (tlv2.value[j] == 0xFF) {
				keyInformation[i].keyComponents[k].extended = 1;
				j++; // skip 0xFF
				keyInformation[i].keyComponents[k].keyType = tlv2.value[j++];
				keyInformation[i].keyComponents[k].keyLength = get_short(tlv2.value, j);
				j += 2;
				// extended has key usage and key access at the end
				if (j < tlv2.length && tlv2.value[j++]) {
					if (tlv2.value[j - 1] == 1) {
						keyInformation[i].keyComponents[k].keyUsage = tlv2.value[j++] << 8;
					}
					else {
						keyInformation[i].keyComponents[k].keyUsage = tlv2.value[j++] << 8;
						keyInformation[i].keyComponents[k].keyUsage |= tlv2.value[j++];
					}
				}
				if (j < tlv2.length && tlv2.value[j++]) {
					keyInformation[i].keyComponents[k].keyAccess = tlv2.value[j++];
				}
			}
			else {
				keyInformation[i].keyComponents[k].extended = 0;
				keyInformation[i].keyComponents[k].keyType = tlv2.value[j++];
				keyInformation[i].keyComponents[k].keyLength = tlv2.value[j++];
				if (keyInformation[i].keyComponents[k].keyLength == 0) {
					keyInformation[i].keyComponents[k].keyLength = 256;
				}
			}
			k++;
		}
		keyInformation[i].numKeyComponents = k;

		i++;
		// increment by TLV
		offset += tlv2.tlvLength;
	}

	*keyInformationLength = i;
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("get_key_information_templates"), status);
	return status;
}

/**
 * Parses an Extended Card Resources Information response.
 * The input must contain tag FF21 and may include the number of installed
 * applications, free non-volatile memory and free volatile memory.
 * \param data [in] The extended card resources information response.
 * \param dataLength [in] The length of data.
 * \param extendedCardResourceInformation [out] The parsed extended card resources information.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS OPGP_parse_extended_card_resources_information(const BYTE *data, DWORD dataLength,
		OPGP_EXTENDED_CARD_RESOURCE_INFORMATION *extendedCardResourceInformation) {
	OPGP_ERROR_STATUS status;
	DWORD offset = 0;
	LONG result;
	TLV tlv1, tlv2;

	if (data == NULL || extendedCardResourceInformation == NULL || dataLength == 0) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}

	memset(extendedCardResourceInformation, 0, sizeof(*extendedCardResourceInformation));
	result = read_TLV((const BYTE *)data, dataLength, &tlv1);
	if (result == -1 || tlv1.tag != 0xFF21) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}

	offset = 0;
	while (offset < tlv1.length) {
		result = read_TLV(tlv1.value + offset, tlv1.length - offset, &tlv2);
		if (result == -1) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			return status;
		}
		switch (tlv2.tag) {
			case 0x81:
				extendedCardResourceInformation->numInstalledApplications = get_number(tlv2.value, 0, tlv2.length);
				break;
			case 0x82:
				extendedCardResourceInformation->freeNonVolatileMemory = get_number(tlv2.value, 0, tlv2.length);
				break;
			case 0x83:
				extendedCardResourceInformation->freeVolatileMemory = get_number(tlv2.value, 0, tlv2.length);
				break;
		}
		offset += tlv2.tlvLength;
	}

	OPGP_ERROR_CREATE_NO_ERROR(status);
	return status;
}

/**
 * The ISD must support the optional report of extended card resources information.
 * The format is defined in ETSI TS 102 226, sect. 8.2.1.7.2.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param *extendedCardResourceInformation [out] A pointer to an array of OPGP_EXTENDED_CARD_RESOURCE_INFORMATION structures.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OPGP_get_extended_card_resources_information(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
								   OPGP_EXTENDED_CARD_RESOURCE_INFORMATION *extendedCardResourceInformation) {
	return get_extended_card_resources_information(cardContext, cardInfo, secInfo, extendedCardResourceInformation);
}

OPGP_ERROR_STATUS get_extended_card_resources_information(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
		OPGP_EXTENDED_CARD_RESOURCE_INFORMATION *extendedCardResourceInformation) {
	OPGP_ERROR_STATUS status;
	BYTE sendBuffer[5];
	DWORD sendBufferLength = 5;
	BYTE cardData[APDU_RESPONSE_LEN];
	DWORD cardDataLength = APDU_RESPONSE_LEN;
	int i=0;
	OPGP_LOG_START(_T("get_extended_card_resources_information"));
	sendBuffer[i++] = 0x80;
	sendBuffer[i++] = 0xCA;
	sendBuffer[i++] = 0xFF;
	sendBuffer[i++] = 0x21;
	sendBuffer[i] = 0x00;

	status = OPGP_send_APDU(cardContext, cardInfo, secInfo, sendBuffer, sendBufferLength, cardData, &cardDataLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	CHECK_SW_9000(cardData, cardDataLength, status);

	status = OPGP_parse_extended_card_resources_information(cardData, cardDataLength, extendedCardResourceInformation);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	OPGP_ERROR_CREATE_NO_ERROR(status);
	goto end;
end:
	OPGP_LOG_END(_T("get_extended_card_resources_information"), status);
	return status;
}

/**
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param statusType [in] Status type. See GP211_STATUS_TYPE_APPLICATIONS.
 * \param AID [in] The AID.
 * \param AIDLength [in] The length of the AID.
 * \param lifeCycleState [in] The new life cycle state.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_set_status(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo, BYTE statusType, PBYTE AID, DWORD AIDLength, BYTE lifeCycleState) {
	return set_status(cardContext, cardInfo, secInfo, statusType, AID, AIDLength, lifeCycleState);
}

OPGP_ERROR_STATUS set_status(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo, BYTE statusType, PBYTE AID, DWORD AIDLength, BYTE lifeCycleState) {
	OPGP_ERROR_STATUS status;
	DWORD sendBufferLength=5+AIDLength;
	DWORD recvBufferLength=2;
	BYTE recvBuffer[2];
	BYTE sendBuffer[5+16];
	DWORD i=0;
	OPGP_LOG_START(_T("set_status"));
	sendBuffer[i++] = 0x80;
	sendBuffer[i++] = 0xF0;
	sendBuffer[i++] = statusType;
	sendBuffer[i++] = lifeCycleState;
	sendBuffer[i++] = (BYTE)AIDLength;
	memcpy(sendBuffer+i, AID, AIDLength);
	i+=AIDLength;

	status = OPGP_send_APDU(cardContext, cardInfo, secInfo,sendBuffer, sendBufferLength, recvBuffer, &recvBufferLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	CHECK_SW_9000(recvBuffer, recvBufferLength, status);

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("set_status"), status);
	return status;
}

/**
 * It depends on the card element to retrieve if an array of GP211_APPLICATION_DATA structures
 * or an array of GP211_EXECUTABLE_MODULES_DATA structures must be passed to this function.
 * For the card element GP211_EXECUTABLE_MODULES_DATA executableData must not
 * be NULL, else applData must not be NULL.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param cardElement [in] Identifier to retrieve data for Load Files, Applications or the Card Manager.
 * \param format [in] The GET STATUS output format. Newer cards might not support the legacy format.
 * See GP211_STATUS_APPLICATIONS and related.
 * \param *applData [out] The GP211_APPLICATION_DATA structure.
 * \param *executableData [out] The GP211_APPLICATION_DATA structure.
 * \param dataLength [in, out] The number of GP211_APPLICATION_DATA or GP211_EXECUTABLE_MODULES_DATA passed and returned.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_get_status(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
		BYTE cardElement, BYTE format, GP211_APPLICATION_DATA *applData, GP211_EXECUTABLE_MODULES_DATA *executableData, PDWORD dataLength) {
	OPGP_ERROR_STATUS status;
	DWORD sendBufferLength = 8;
	DWORD recvBufferLength = APDU_RESPONSE_LEN;
	BYTE recvBuffer[APDU_RESPONSE_LEN];
	BYTE sendBuffer[8];
	DWORD j=0, i=0;
	DWORD apduErrorCode;
	PBYTE wholeData = NULL;
	DWORD wholeDataSize = 2000;
	DWORD totalRead = 0;
	OPGP_LOG_START(_T("get_status"));
	wholeData = malloc(wholeDataSize);
	sendBuffer[i++] = 0x80;
	sendBuffer[i++] = 0xF2;
	sendBuffer[i++] = cardElement;
	sendBuffer[i++] = format;
	sendBuffer[i++] = 2;
	sendBuffer[i++] = 0x4F;
	sendBuffer[i++] = 0x00;
	sendBuffer[i] = 0x00;
	i=0;
	do {
		recvBufferLength = APDU_RESPONSE_LEN;
		status = OPGP_send_APDU(cardContext, cardInfo, secInfo,sendBuffer, sendBufferLength, recvBuffer, &recvBufferLength);
		if ( OPGP_ERROR_CHECK(status)) {
			goto end;
		}
		if (status.errorCode != OPGP_ISO7816_ERROR_MORE_DATA_AVAILABLE) {
			CHECK_SW_9000(recvBuffer, recvBufferLength, status);
		}
		apduErrorCode = status.errorCode;
		if (wholeDataSize < totalRead+recvBufferLength-2) {
			wholeDataSize *= 2;
			wholeData = realloc(wholeData, wholeDataSize);
		}
		memcpy(wholeData+totalRead, recvBuffer, recvBufferLength-2);
		totalRead += recvBufferLength-2;
		sendBuffer[3] |= 0x01;
	} while (apduErrorCode == OPGP_ISO7816_ERROR_MORE_DATA_AVAILABLE);

	for (j=0; j<totalRead; ) {
		DWORD dataRead;
		if (*dataLength <= i ) {
			{ OPGP_ERROR_CREATE_ERROR(status, GP211_ERROR_MORE_APPLICATION_DATA, OPGP_stringify_error(GP211_ERROR_MORE_APPLICATION_DATA)); goto end; }
		}
		if (cardElement == GP211_STATUS_LOAD_FILES_AND_EXECUTABLE_MODULES) {
			status = parse_executable_load_file_data(wholeData+j, totalRead-j, format, &executableData[i], &dataRead);
			if ( OPGP_ERROR_CHECK(status)) {
				goto end;
			}
		}
		else {
			status = parse_application_data(wholeData+j, totalRead-j, cardElement, format, &applData[i], &dataRead);
			if ( OPGP_ERROR_CHECK(status)) {
				goto end;
			}
		}
		j += dataRead;
		i++;
	}

	*dataLength = i;
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	if (wholeData != NULL) {
		free(wholeData);
	}
	OPGP_LOG_END(_T("get_status"), status);
	return status;
}

/**
 * If loadFileBuf is NULL the loadFileBufSize is ignored and the necessary buffer size
 * is returned in loadFileBufSize and the functions returns.
 * \param fileName [in] The name of the CAP file.
 * \param loadFileBuf [out] The destination buffer with the Executable Load File contents.
 * \param loadFileBufSize [in, out] The size of the loadFileBuf.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OPGP_extract_cap_file(OPGP_CSTRING fileName, PBYTE loadFileBuf, PDWORD loadFileBufSize) {
	return extract_cap_file(fileName, loadFileBuf, loadFileBufSize);
}

/**
 * \param capFileName [in] The name of the CAP file.
 * \param ijcFileName [in] The name of the destination IJC file.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OPGP_cap_to_ijc(OPGP_CSTRING capFileName, OPGP_STRING ijcFileName) {
	return cap_to_ijc(capFileName, ijcFileName);
}

/**
 * \param loadFileName [in] The load file name to parse.
 * \param *loadFileParams [out] The parsed parameters.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OPGP_read_executable_load_file_parameters(OPGP_STRING loadFileName, OPGP_LOAD_FILE_PARAMETERS *loadFileParams)  {
	return read_executable_load_file_parameters(loadFileName, loadFileParams);
}

/**
 * \param loadFileBuf [in] The load file buffer.
 * \param loadFileBufSize [in] The size of the load file buffer.
 * \param *loadFileParams [out] The parsed parameters.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OPGP_read_executable_load_file_parameters_from_buffer(PBYTE loadFileBuf, DWORD loadFileBufSize, OPGP_LOAD_FILE_PARAMETERS *loadFileParams) {
	return read_executable_load_file_parameters_from_buffer(loadFileBuf, loadFileBufSize, loadFileParams);
}

/**
 * An GP211_install_for_load() must precede.
 * The Load File Data Block Signature(s) must be the same block(s) and in the same order like in GP211_calculate_load_file_data_block_hash().
 * If no Load File Data Block Signatures are necessary the loadFileDataBlockSignature must be NULL and the loadFileDataBlockSignatureLength 0.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param *loadFileDataBlockSignature [in] A pointer to GP211_DAP_BLOCK structure(s).
 * \param loadFileDataBlockSignatureLength [in] The number of GP211_DAP_BLOCK structure(s).
 * \param executableLoadFileName [in] The name of the CAP or IJC file (Executable Load File) to load.
 * \param *receiptData [out] If the deletion is performed by a security domain with delegated management privilege
 * this structure contains the according data.
 * Can be validated with validate_load_receipt().
 * \param receiptDataAvailable [out] 0 if no receiptData is available.
 * \param *callback [in] An optional callback for measuring the progress. Can be NULL if not needed.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_load(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
				 GP211_DAP_BLOCK *loadFileDataBlockSignature, DWORD loadFileDataBlockSignatureLength,
				 OPGP_STRING executableLoadFileName,
				 GP211_RECEIPT_DATA *receiptData, PDWORD receiptDataAvailable, OPGP_PROGRESS_CALLBACK *callback) {
	return load(cardContext, cardInfo, secInfo,
				 loadFileDataBlockSignature, loadFileDataBlockSignatureLength,
				 executableLoadFileName,
				 receiptData, receiptDataAvailable, callback);
}

/**
 * An GP211_install_for_load() must precede.
 * The Load File Data Block Signature(s) must be the same block(s) and in the same order like in calculate_load_file_data_block_hash().
 * If no Load File Data Block Signatures are necessary the loadFileDataBlockSignature must be NULL and the loadFileDataBlockSignatureLength 0.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param *loadFileDataBlockSignature [in] A pointer to GP211_DAP_BLOCK structure(s).
 * \param loadFileDataBlockSignatureLength [in] The number of GP211_DAP_BLOCK structure(s).
 * \param loadFileBuf [in] buffer with the contents of a Executable Load File.
 * \param loadFileBufSize [in] size of loadFileBuf.
 * \param *receiptData [out] If the deletion is performed by a security domain with delegated management privilege
 * this structure contains the according data.
 * Can be validated with validate_load_receipt().
 * \param receiptDataAvailable [out] 0 if no receiptData is available.
 * \param *callback [in] An optional callback for measuring the progress. Can be NULL if not needed.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_load_from_buffer(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
				 GP211_DAP_BLOCK *loadFileDataBlockSignature, DWORD loadFileDataBlockSignatureLength,
				 PBYTE loadFileBuf, DWORD loadFileBufSize,
				 GP211_RECEIPT_DATA *receiptData, PDWORD receiptDataAvailable, OPGP_PROGRESS_CALLBACK *callback) {
	return load_from_buffer(cardContext, cardInfo, secInfo,
				 loadFileDataBlockSignature, loadFileDataBlockSignatureLength,
				 loadFileBuf, loadFileBufSize,
				 receiptData, receiptDataAvailable, callback);
}

OPGP_ERROR_STATUS load_from_buffer(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
				 GP211_DAP_BLOCK *loadFileDataBlockSignature, DWORD loadFileDataBlockSignatureLength,
				 PBYTE loadFileBuf, DWORD loadFileBufSize,
				 GP211_RECEIPT_DATA *receiptData, PDWORD receiptDataAvailable, OPGP_PROGRESS_CALLBACK *callback) {
	OPGP_ERROR_STATUS status;
	DWORD sendBufferLength = 0;
	DWORD recvBufferLength = APDU_RESPONSE_LEN;
	BYTE recvBuffer[APDU_RESPONSE_LEN];
	BYTE sendBuffer[APDU_COMMAND_LEN];
	BYTE dapBuf[256];
	DWORD dapBufSize=sizeof(dapBuf);

	DWORD total=0;
	DWORD fileSizeSize;
	DWORD j,k,i=0,count;
	OPGP_PROGRESS_CALLBACK_PARAMETERS callbackParameters;
	BYTE sequenceNumber=0x00;
    INIT_PROGRESS_CALLBACK_PARAMETERS(callbackParameters, callback);
	OPGP_LOG_START(_T("load_from_buffer"));

	*receiptDataAvailable = 0;
	sendBuffer[0] = 0x80;
	sendBuffer[1] = 0xE8;
	j=0;
	for (i=0; i<loadFileDataBlockSignatureLength; i++) {
		k = dapBufSize;
		status = read_load_file_data_block_signature(dapBuf, &k, loadFileDataBlockSignature[i]);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
		if (k > MAX_APDU_DATA_SIZE(secInfo)) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_COMMAND_SECURE_MESSAGING_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_COMMAND_SECURE_MESSAGING_TOO_LARGE)); goto end;
			goto end;
		}
		if (j+k <= MAX_APDU_DATA_SIZE(secInfo)) {
			memcpy(sendBuffer+5+j, dapBuf, k);
			j+=k;
		}
		else {
			sendBufferLength=5+j;
			sendBuffer[2] = 0x00;
			sendBuffer[3] = sequenceNumber++;
			sendBuffer[4]=(BYTE)j;
			// CFlex behavior strange if Le is set, so commented out
			//sendBufferLength++;
			//sendBuffer[sendBufferLength-1] = 0x00;

			status = OPGP_send_APDU(cardContext, cardInfo, secInfo, sendBuffer, sendBufferLength, recvBuffer, &recvBufferLength);
			if (OPGP_ERROR_CHECK(status)) {
				goto end;
			}
			CHECK_SW_9000(recvBuffer, recvBufferLength, status);

			/* Start with new APDU */
			j=0;
			// The current data block i is not sent so must be handled again
			i--;
		}
	}
	// send load file data block

	if (loadFileBufSize < 128L) {
		fileSizeSize=1;
	}
	else if (loadFileBufSize < 256L) {
		fileSizeSize=2;
	}
	else if (loadFileBufSize < 65536L) {
		fileSizeSize=3;
	}
	else {
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_APPLICATION_TOO_BIG, OPGP_stringify_error(OPGP_ERROR_APPLICATION_TOO_BIG)); goto end; }
	}
	// load file can only have 256 blocks (minus the already sent blocks)
	// times the maximum APDU size minus the tag and length and the current position in the APDU
	if (((256-sequenceNumber) * MAX_APDU_DATA_SIZE(secInfo) - j - 1 - fileSizeSize) < loadFileBufSize) {
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_APPLICATION_TOO_BIG, OPGP_stringify_error(OPGP_ERROR_APPLICATION_TOO_BIG)); goto end; }
	}

	// Enough space left to start load file data block

	if ((MAX_APDU_DATA_SIZE(secInfo)-j) > fileSizeSize+1+1) { // At least one byte of the load file data block must be sent.
		sendBuffer[5+j++] = 0xC4;
		switch(fileSizeSize) {
			case 1: {
				sendBuffer[5+j++] = (BYTE)loadFileBufSize;
				break;
					}
			case 2: {
				sendBuffer[5+j++] = 0x81;
				sendBuffer[5+j++] = (BYTE)loadFileBufSize;
				break;
					}
			case 3: {
				sendBuffer[5+j++] = 0x82;
				sendBuffer[5+j++] = (BYTE)(loadFileBufSize >> 8);
				sendBuffer[5+j] = (BYTE)(loadFileBufSize - (sendBuffer[5+j-1] << 8));
				j++;
					}
		}
		if (loadFileBufSize > MAX_APDU_DATA_SIZE(secInfo)-j) {
			count=MAX_APDU_DATA_SIZE(secInfo)-j;
		}
		else {
			count=loadFileBufSize;
		}

		memcpy(sendBuffer+5+j, loadFileBuf, count);
		j+=count;
		total+=count;
		sendBufferLength=5+j;
		sendBuffer[2] = 0x00;
		sendBuffer[3] = (BYTE)sequenceNumber++;
		sendBuffer[4]=(BYTE)j;
		if (count == loadFileBufSize) {
			sendBuffer[2]=0x80;
			sendBufferLength++;
			sendBuffer[sendBufferLength-1] = 0x00;
		}
		else {
			sendBuffer[2]=0x00;
			/* CyberFlex e-gate 32k cards do not behave standard conform and accept the Le field (?) */
			//sendBufferLength++;
			//sendBuffer[sendBufferLength-1] = 0x00;
		}

		recvBufferLength=APDU_RESPONSE_LEN;

		status = OPGP_send_APDU(cardContext, cardInfo, secInfo, sendBuffer, sendBufferLength, recvBuffer, &recvBufferLength);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
		CHECK_SW_9000(recvBuffer, recvBufferLength, status);

		if(callback != NULL) {
			callbackParameters.currentWork = total;
			callbackParameters.totalWork = loadFileBufSize;
			callback->callback(callbackParameters);
		}
	}
	// Not enough space to start load file data block. First send data then start load file data block.

	else {
		sendBufferLength=5+j;
		sendBuffer[2] = 0x00;
		sendBuffer[3] = sequenceNumber++;
		sendBuffer[4]=(BYTE)j;
		recvBufferLength=APDU_RESPONSE_LEN;

		status = OPGP_send_APDU(cardContext, cardInfo, secInfo, sendBuffer, sendBufferLength, recvBuffer, &recvBufferLength);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
		CHECK_SW_9000(recvBuffer, recvBufferLength, status);

		j=0;
		sendBuffer[5+j++] = 0xC4;
		switch(fileSizeSize) {
			case 1: {
				sendBuffer[5+j++] = (BYTE)loadFileBufSize;
				break;
				}
			case 2: {
				sendBuffer[5+j++] = 0x81;
				sendBuffer[5+j++] = (BYTE)loadFileBufSize;
				break;
				}
			case 3: {
				sendBuffer[5+j++] = 0x82;
				sendBuffer[5+j++] = (BYTE)(loadFileBufSize >> 8);
				sendBuffer[5+j] = (BYTE)(loadFileBufSize - (sendBuffer[5+j-1] << 8));
				j++;
				}
		}
		if (loadFileBufSize > MAX_APDU_DATA_SIZE(secInfo)-1-fileSizeSize) {
			count=MAX_APDU_DATA_SIZE(secInfo)-1-fileSizeSize;
		}
		else {
			count=loadFileBufSize;
		}

		memcpy(sendBuffer+5+j, loadFileBuf, count);
		j+=count;
		total+=count;

		sendBufferLength=5+j;
		sendBuffer[2] = 0x00;
		sendBuffer[3] = sequenceNumber++;
		sendBuffer[4]=(BYTE)j;
		if (total == loadFileBufSize) {
			sendBuffer[2]=0x80;
			sendBufferLength++;
			sendBuffer[sendBufferLength-1] = 0x00;
		}
		else {
			sendBuffer[2]=0x00;
			/* CyberFlex e-gate 32k cards do not behave standard conform and accept the Le field (?) */
			//sendBufferLength++;
			//sendBuffer[sendBufferLength-1] = 0x00;
		}

		recvBufferLength=APDU_RESPONSE_LEN;

		status = OPGP_send_APDU(cardContext, cardInfo, secInfo, sendBuffer, sendBufferLength, recvBuffer, &recvBufferLength);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
		CHECK_SW_9000(recvBuffer, recvBufferLength, status);

		if(callback != NULL) {
			callbackParameters.currentWork = total;
			callbackParameters.totalWork = loadFileBufSize;
			callback->callback(callbackParameters);
		}
	}
	// The rest of the load file data block

	while(!(total == loadFileBufSize)) {
		j = 0;
		OPGP_LOG_MSG(_T("load_from_buffer: left: %d"), loadFileBufSize-total);
		if (loadFileBufSize-total > MAX_APDU_DATA_SIZE(secInfo)) {
			count=MAX_APDU_DATA_SIZE(secInfo);
		}
		else {
			count=loadFileBufSize-total;
		}

		memcpy(sendBuffer+5+j, loadFileBuf+total, count);
		j+=count;
		total+=count;

		sendBufferLength=5+j;
		sendBuffer[3] = sequenceNumber++;
		sendBuffer[4] = (BYTE)j;
		if (loadFileBufSize == total) {
			sendBuffer[2]=0x80;
			sendBufferLength++;
			sendBuffer[sendBufferLength-1] = 0x00;
		}
		else {
			sendBuffer[2]=0x00;
			/* CyberFlex e-gate 32k cards do not behave standard conform and accept the Le field (?) */
			//sendBufferLength++;
			//sendBuffer[sendBufferLength-1] = 0x00;
		}

		recvBufferLength=APDU_RESPONSE_LEN;
		status = OPGP_send_APDU(cardContext, cardInfo, secInfo, sendBuffer, sendBufferLength, recvBuffer, &recvBufferLength);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
		CHECK_SW_9000(recvBuffer, recvBufferLength, status);

		if(callback != NULL) {
			callbackParameters.currentWork = total;
			callbackParameters.totalWork = loadFileBufSize;
			callback->callback(callbackParameters);
		}
	}
	status = parse_receipt_from_response(recvBuffer, recvBufferLength, receiptData, receiptDataAvailable);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	if(callback != NULL) {
		callbackParameters.currentWork = total;
		callbackParameters.totalWork = loadFileBufSize;
		callbackParameters.finished = OPGP_TASK_FINISHED;
		callback->callback(callbackParameters);
	}
	OPGP_LOG_END(_T("load_from_buffer"), status);
	return status;

}

OPGP_ERROR_STATUS load(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
				 GP211_DAP_BLOCK *loadFileDataBlockSignature, DWORD loadFileDataBlockSignatureLength,
				 OPGP_STRING executableLoadFileName,
				 GP211_RECEIPT_DATA *receiptData, PDWORD receiptDataAvailable, OPGP_PROGRESS_CALLBACK *callback) {
	OPGP_ERROR_STATUS status;
	PBYTE loadFileBuf = NULL;
	DWORD loadFileBufSize;

	OPGP_LOG_START(_T("load"));

	if ((executableLoadFileName == NULL) || (_tcslen(executableLoadFileName) == 0))
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_FILENAME, OPGP_stringify_error(OPGP_ERROR_INVALID_FILENAME)); goto end; }


	status = handle_load_file((OPGP_CSTRING)executableLoadFileName, loadFileBuf, &loadFileBufSize);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	loadFileBuf = (PBYTE)malloc(sizeof(BYTE) * loadFileBufSize);
	if (loadFileBuf == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, ENOMEM, OPGP_stringify_error(ENOMEM));
		goto end;
	}
	status = handle_load_file((OPGP_CSTRING)executableLoadFileName, loadFileBuf, &loadFileBufSize);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	status = load_from_buffer(cardContext, cardInfo, secInfo, loadFileDataBlockSignature,
			loadFileDataBlockSignatureLength, loadFileBuf, loadFileBufSize, receiptData, receiptDataAvailable, callback);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	if (loadFileBuf) {
		free(loadFileBuf);
	}
	OPGP_LOG_END(_T("load"), status);
	return status;
}

/**
 * The function assumes that the Issuer Security Domain or Security Domain
 * uses an optional Load File Data Block Hash using the SHA-1 message digest algorithm.
 * The loadFileDataBlockHash can be calculated using GP211_calculate_load_file_data_block_hash() or must be NULL, if the card does not
 * need or support a Load File DAP in this situation, e.g. if you want to load a Executable Load File to the Card
 * Manager Security Domain.
 * In the case of delegated management a Load Token authorizing the INSTALL [for load] must be included.
 * Otherwise loadToken must be NULL. See GP211_calculate_load_token().
 * The term Executable Load File is equivalent to the GlobalPlatform term Load File Data Block.
 * volatileDataSpaceLimit and nonVolatileDataSpaceLimit can be 0, if the card does not need or support this tags.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param executableLoadFileAID [in] A buffer with AID of the Executable Load File to INSTALL [for load].
 * \param executableLoadFileAIDLength [in] The length of the Executable Load File AID.
 * \param securityDomainAID [in] A buffer containing the AID of the intended associated Security Domain.
 * \param securityDomainAIDLength [in] The length of the Security Domain AID.
 * \param loadFileDataBlockHash [in] The Load File Data Block Hash of the Executable Load File to INSTALL [for load].
 * \param loadFileDataBlockHashLength [in] The length of the loadFileDataBlockHash buffer.
 * \param loadToken [in] The Load Token. This is an assymetric (e.g., ECC or RSA) Signature.
 * \param loadTokenLength [in] The length of the loadToken buffer.
 * \param nonVolatileCodeSpaceLimit [in] The minimum amount of space that must be available to store the package.
 * \param volatileDataSpaceLimit [in] The minimum amount of RAM space that must be available.
 * \param nonVolatileDataSpaceLimit [in] The minimum amount of space for objects of the application, i.e. the data allocated in its lifetime.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_install_for_load(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
					  PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength, PBYTE securityDomainAID,
					  DWORD securityDomainAIDLength, PBYTE loadFileDataBlockHash, DWORD loadFileDataBlockHashLength,
					  PBYTE loadToken, DWORD loadTokenLength,
					  DWORD nonVolatileCodeSpaceLimit, DWORD volatileDataSpaceLimit,
					  DWORD nonVolatileDataSpaceLimit) {
	return install_for_load(cardContext, cardInfo, secInfo,
					  executableLoadFileAID, executableLoadFileAIDLength, securityDomainAID,
					  securityDomainAIDLength, loadFileDataBlockHash, loadFileDataBlockHashLength,
					  loadToken, loadTokenLength,
					  nonVolatileCodeSpaceLimit, volatileDataSpaceLimit,
					  nonVolatileDataSpaceLimit);
}

OPGP_ERROR_STATUS install_for_load(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
					  PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength, PBYTE securityDomainAID,
					  DWORD securityDomainAIDLength, PBYTE loadFileDataBlockHash, DWORD loadFileDataBlockHashLength,
					  PBYTE loadToken, DWORD loadTokenLength,
					  DWORD nonVolatileCodeSpaceLimit, DWORD volatileDataSpaceLimit,
					  DWORD nonVolatileDataSpaceLimit)
{
	OPGP_ERROR_STATUS status;
	BYTE commandHeader[5] = {0x80, 0xE6, 0x00, 0x00, 0x00};
	DWORD recvBufferLength = APDU_RESPONSE_LEN;
	BYTE recvBuffer[APDU_RESPONSE_LEN];
	BYTE sendBuffer[1000];
	DWORD i=0;
	BYTE buf[1000];
	DWORD bufLength = sizeof(buf);
	PBYTE parsedData = NULL;
	DWORD parsedDataLength = 0;
	LONG lenLen;
	OPGP_LOG_START(_T("install_for_load"));
	status = get_load_data(executableLoadFileAID, executableLoadFileAIDLength, securityDomainAID,
		securityDomainAIDLength, loadFileDataBlockHash, loadFileDataBlockHashLength,
		0, nonVolatileCodeSpaceLimit, volatileDataSpaceLimit,
		nonVolatileDataSpaceLimit, buf, &bufLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	status = extract_case3_data(buf, bufLength, &commandHeader[2], &commandHeader[3], &parsedData, &parsedDataLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	if (parsedDataLength > sizeof(sendBuffer)) {
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_COMMAND_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_COMMAND_TOO_LARGE)); goto end; }
	}
	memcpy(sendBuffer, parsedData, parsedDataLength);
	i += parsedDataLength;

	if (loadToken != NULL && loadTokenLength > 0) {
		if (loadTokenLength > 0xFFFF) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_COMMAND_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_COMMAND_TOO_LARGE)); goto end; }
		}
		lenLen = write_TLV_length(sendBuffer, i, sizeof(sendBuffer) - i, (USHORT)loadTokenLength);
		if (lenLen < 0) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
		}
		if (i + (DWORD)lenLen + loadTokenLength > sizeof(sendBuffer)) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
		}
		i += (DWORD)lenLen;
		memcpy(sendBuffer+i, loadToken, loadTokenLength);
		i+=loadTokenLength;
	}
	else {
		if (i + 1 > sizeof(sendBuffer)) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
		}
		sendBuffer[i++] = 0x00; // Length of load token
	}

	status = send_chained_data(cardContext, cardInfo, secInfo, commandHeader, 5, sendBuffer, i,
		recvBuffer, &recvBufferLength, false, false, true, false);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("install_for_load"), status);
	return status;
}


/**
 * In the case of delegated management an Install Token authorizing the INSTALL [for install] must be included.
 * Otherwise installToken must be NULL. See GP211_calculate_install_token().
 * volatileDataSpaceLimit and nonVolatileDataSpaceLimit can be 0, if the card does not need or support this tag.
 * For Security domains look in your manual what parameters are necessary.
 * If the tag for application install parameters is mandatory for your card, but you have no install parameters
 * for the install() method of the application anyway you have to use at least a dummy parameter.
 * If executableModuleAID is NULL and executableModuleAIDLength is 0 applicationAID is assumed for executableModuleAID.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param executableLoadFileAID [in] A buffer with AID of the Executable Load File to INSTALL [for install].
 * \param executableLoadFileAIDLength [in] The length of the Executable Load File AID.
 * \param executableModuleAID [in] The AID of the application class in the package.
 * \param executableModuleAIDLength [in] The length of the executableModuleAID buffer.
 * \param applicationAID [in] The AID of the installed application.
 * \param applicationAIDLength [in] The length of the application instance AID.
 * \param applicationPrivileges [in] The application privileges. Can be an OR of multiple privileges. See GP211_APPLICATION_PRIVILEGE_SECURITY_DOMAIN.
 * \param volatileDataSpaceLimit [in] The minimum amount of RAM space that must be available.
 * \param nonVolatileDataSpaceLimit [in] The minimum amount of space for objects of the application, i.e. the data allocated in its lifetime.
 * \param installParameters [in] Applet install parameters for the install() method of the application.
 * \param installParametersLength [in] The length of the installParameters buffer.
 * \param sdParameters [in] Security Domain install parameters encoded for tag C9 (e.g., tag 'C1' with tags '81','82','83','84','86','87').
 * \param sdParametersLength [in] The length of the sdParameters buffer.
 * \param uiccSystemSpecParams [in] UICC System Specific Parameters according to ETSI TS 102 226, sect. 8.2.1.3.2.2.
 * \param uiccSystemSpecParamsLength [in] The length of the uiccSystemSpecParams buffer.
 * \param simSpecParams [in] SIM File Access and Toolkit Application Specific Parameters according to ETSI TS 102 226, sect. 8.2.1.3.2.1.
 * \param simSpecParamsLength [in] The length of the simSpecParams buffer.
 * \param installToken [in] The Install Token. This is an assymetric (e.g., ECC or RSA) Signature.
 * \param installTokenLength [in] The length of the installToken buffer.
 * \param *receiptData [out] If the deletion is performed by a security domain with delegated management privilege
 * this structure contains the according data.
 * \param receiptDataAvailable [out] 0 if no receiptData is available.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_install_for_install(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
						 PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength,
						 PBYTE executableModuleAID,
						 DWORD executableModuleAIDLength, PBYTE applicationAID,
						 DWORD applicationAIDLength, DWORD applicationPrivileges,
						 DWORD volatileDataSpaceLimit, DWORD nonVolatileDataSpaceLimit,
						 PBYTE installParameters, DWORD installParametersLength,
						 PBYTE sdParameters, DWORD sdParametersLength,
						 PBYTE uiccSystemSpecParams, DWORD uiccSystemSpecParamsLength,
						 PBYTE simSpecParams, DWORD simSpecParamsLength,
						 PBYTE installToken, DWORD installTokenLength,
						 GP211_RECEIPT_DATA *receiptData, PDWORD receiptDataAvailable) {
	return install_for_install(cardContext, cardInfo, secInfo,
						 executableLoadFileAID, executableLoadFileAIDLength,
						 executableModuleAID,
						 executableModuleAIDLength, applicationAID,
						 applicationAIDLength, applicationPrivileges,
						 volatileDataSpaceLimit, nonVolatileDataSpaceLimit,
						 installParameters, installParametersLength,
						 sdParameters, sdParametersLength,
						 uiccSystemSpecParams, uiccSystemSpecParamsLength,
						 simSpecParams, simSpecParamsLength,
						 installToken, installTokenLength,
						 receiptData, receiptDataAvailable);
}

OPGP_ERROR_STATUS install_for_install(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
						 PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength,
						 PBYTE executableModuleAID,
						 DWORD executableModuleAIDLength, PBYTE applicationAID,
						 DWORD applicationAIDLength, DWORD applicationPrivileges,
						 DWORD volatileDataSpaceLimit, DWORD nonVolatileDataSpaceLimit,
						 PBYTE installParameters, DWORD installParametersLength,
						 PBYTE sdParameters, DWORD sdParametersLength,
						 PBYTE uiccSystemSpecParams, DWORD uiccSystemSpecParamsLength,
						 PBYTE simSpecParams, DWORD simSpecParamsLength,
						 PBYTE installToken, DWORD installTokenLength,
						 GP211_RECEIPT_DATA *receiptData, PDWORD receiptDataAvailable) {
	OPGP_ERROR_STATUS status;
	BYTE commandHeader[5] = {0x80, 0xE6, 0x00, 0x00, 0x00};
	DWORD recvBufferLength = APDU_RESPONSE_LEN;
	BYTE recvBuffer[APDU_RESPONSE_LEN];
	BYTE sendBuffer[1000];
	DWORD i=0;
	BYTE buf[1000];
	DWORD bufLength = sizeof(buf);
	PBYTE parsedData = NULL;
	DWORD parsedDataLength = 0;
	LONG lenLen;
	OPGP_LOG_START(_T("install_for_install"));
	*receiptDataAvailable = 0;

	status = get_install_data(0x04, executableLoadFileAID, executableLoadFileAIDLength, executableModuleAID,
		executableModuleAIDLength, applicationAID, applicationAIDLength, applicationPrivileges,
		volatileDataSpaceLimit,	nonVolatileDataSpaceLimit, installParameters,
		installParametersLength, sdParameters, sdParametersLength,
		uiccSystemSpecParams, uiccSystemSpecParamsLength,
		simSpecParams, simSpecParamsLength, buf, &bufLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	status = extract_case3_data(buf, bufLength, &commandHeader[2], &commandHeader[3], &parsedData, &parsedDataLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	if (parsedDataLength > sizeof(sendBuffer)) {
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_COMMAND_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_COMMAND_TOO_LARGE)); goto end; }
	}
	memcpy(sendBuffer, parsedData, parsedDataLength);
	i += parsedDataLength;

	if (installToken != NULL && installTokenLength > 0) {
		if (installTokenLength > 0xFFFF) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_COMMAND_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_COMMAND_TOO_LARGE)); goto end; }
		}
		lenLen = write_TLV_length(sendBuffer, i, sizeof(sendBuffer) - i, (USHORT)installTokenLength);
		if (lenLen < 0) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
		}
		if (i + (DWORD)lenLen + installTokenLength > sizeof(sendBuffer)) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
		}
		i += (DWORD)lenLen;
		memcpy(sendBuffer+i, installToken, installTokenLength);
		i+=installTokenLength;
	}
	else {
		if (i + 1 > sizeof(sendBuffer)) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
		}
		sendBuffer[i++] = 0x00; // Length of install token
	}

	status = send_chained_data(cardContext, cardInfo, secInfo, commandHeader, 5, sendBuffer, i,
		recvBuffer, &recvBufferLength, false, false, true, false);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	status = parse_receipt_from_response(recvBuffer, recvBufferLength, receiptData, receiptDataAvailable);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("install_for_install"), status);
	return status;
}

/**
 * In the case of delegated management an Install Token authorizing the INSTALL [for install and make selectable] must be included.
 * Otherwise installToken must be NULL. See GP211_calculate_install_token().
 * volatileDataSpaceLimit and nonVolatileDataSpaceLimit can be 0, if the card does not need or support this tag.
 * For Security domains look in your manual what parameters are necessary.
 * If the tag for application install parameters is mandatory for your card, but you have no install parameters
 * for the install() method of the application anyway you have to use at least a dummy parameter.
 * If executableModuleAID is NULL and executableModuleAIDLength is 0 applicationAID is assumed for executableModuleAID.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param executableLoadFileAID [in] A buffer with AID of the Executable Load File to INSTALL [for install].
 * \param executableLoadFileAIDLength [in] The length of the Executable Load File AID.
 * \param executableModuleAID [in] The AID of the application class in the package.
 * \param executableModuleAIDLength [in] The length of the executableModuleAID buffer.
 * \param applicationAID [in] The AID of the installed application.
 * \param applicationAIDLength [in] The length of the application instance AID.
 * \param applicationPrivileges [in] The application privileges. Can be an OR of multiple privileges. See GP211_APPLICATION_PRIVILEGE_SECURITY_DOMAIN.
 * \param volatileDataSpaceLimit [in] The minimum amount of RAM space that must be available.
 * \param nonVolatileDataSpaceLimit [in] The minimum amount of space for objects of the application, i.e. the data allocated in its lifetime.
 * \param installParameters [in] Applet install parameters for the install() method of the application.
 * \param installParametersLength [in] The length of the installParameters buffer.
 * \param sdParameters [in] Security Domain install parameters encoded for tag C9 (e.g., tag 'C1' with tags '81','82','83','84','86','87').
 * \param sdParametersLength [in] The length of the sdParameters buffer.
 * \param uiccSystemSpecParams [in] UICC System Specific Parameters according to ETSI TS 102 226, sect. 8.2.1.3.2.2.
 * \param uiccSystemSpecParamsLength [in] The length of the uiccSystemSpecParams buffer.
 * \param simSpecParams [in] SIM File Access and Toolkit Application Specific Parameters according to ETSI TS 102 226, sect. 8.2.1.3.2.1.
 * \param simSpecParamsLength [in] The length of the simSpecParams buffer.
 * \param installToken [in] The Install Token. This is an assymetric (e.g., ECC or RSA) Signature.
 * \param installTokenLength [in] The length of the installToken buffer.
 * \param *receiptData [out] If the deletion is performed by a security domain with delegated management privilege
 * this structure contains the according data.
 * \param receiptDataAvailable [out] 0 if no receiptData is available.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_install_for_install_and_make_selectable(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
						 PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength, PBYTE executableModuleAID,
						 DWORD executableModuleAIDLength, PBYTE applicationAID,
						 DWORD applicationAIDLength, DWORD applicationPrivileges,
						 DWORD volatileDataSpaceLimit, DWORD nonVolatileDataSpaceLimit,
						 PBYTE installParameters, DWORD installParametersLength,
						 PBYTE sdParameters, DWORD sdParametersLength,
						 PBYTE uiccSystemSpecParams, DWORD uiccSystemSpecParamsLength,
						 PBYTE simSpecParams, DWORD simSpecParamsLength,
						 PBYTE installToken, DWORD installTokenLength,
						 GP211_RECEIPT_DATA *receiptData,
						 PDWORD receiptDataAvailable) {
	return install_for_install_and_make_selectable(cardContext, cardInfo, secInfo,
						 executableLoadFileAID, executableLoadFileAIDLength, executableModuleAID,
						 executableModuleAIDLength, applicationAID,
						 applicationAIDLength, applicationPrivileges,
						 volatileDataSpaceLimit, nonVolatileDataSpaceLimit,
						 installParameters, installParametersLength,
						 sdParameters, sdParametersLength,
						 uiccSystemSpecParams, uiccSystemSpecParamsLength,
						 simSpecParams, simSpecParamsLength,
						 installToken, installTokenLength,
						 receiptData,
						 receiptDataAvailable);
}

OPGP_ERROR_STATUS install_for_install_and_make_selectable(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
						 PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength, PBYTE executableModuleAID,
						 DWORD executableModuleAIDLength, PBYTE applicationAID,
						 DWORD applicationAIDLength, DWORD applicationPrivileges,
						 DWORD volatileDataSpaceLimit, DWORD nonVolatileDataSpaceLimit,
						 PBYTE installParameters, DWORD installParametersLength,
						 PBYTE sdParameters, DWORD sdParametersLength,
						 PBYTE uiccSystemSpecParams, DWORD uiccSystemSpecParamsLength,
						 PBYTE simSpecParams, DWORD simSpecParamsLength,
						 PBYTE installToken, DWORD installTokenLength,
						 GP211_RECEIPT_DATA *receiptData,
						 PDWORD receiptDataAvailable) {
	OPGP_ERROR_STATUS status;
	BYTE commandHeader[5] = {0x80, 0xE6, 0x00, 0x00, 0x00};
	BYTE recvBuffer[2048];
	DWORD recvBufferLength = sizeof(recvBuffer);
	BYTE sendBuffer[1000];
	DWORD i=0;
	BYTE buf[1000];
	DWORD bufLength = sizeof(buf);
	PBYTE parsedData = NULL;
	DWORD parsedDataLength = 0;
	LONG lenLen;
	OPGP_LOG_START(_T("install_for_install_and_make_selectable"));
	*receiptDataAvailable = 0;
  	status = get_install_data(0x0C, executableLoadFileAID, executableLoadFileAIDLength, executableModuleAID,
		executableModuleAIDLength, applicationAID, applicationAIDLength, applicationPrivileges,
		volatileDataSpaceLimit,	nonVolatileDataSpaceLimit, installParameters,
		installParametersLength, sdParameters, sdParametersLength,
		uiccSystemSpecParams, uiccSystemSpecParamsLength,
		simSpecParams, simSpecParamsLength, buf, &bufLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	status = extract_case3_data(buf, bufLength, &commandHeader[2], &commandHeader[3], &parsedData, &parsedDataLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	if (parsedDataLength > sizeof(sendBuffer)) {
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_COMMAND_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_COMMAND_TOO_LARGE)); goto end; }
	}
	memcpy(sendBuffer, parsedData, parsedDataLength);
	i += parsedDataLength;

	if (installToken != NULL && installTokenLength > 0) {
		if (installTokenLength > 0xFFFF) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_COMMAND_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_COMMAND_TOO_LARGE)); goto end; }
		}
		lenLen = write_TLV_length(sendBuffer, i, sizeof(sendBuffer) - i, (USHORT)installTokenLength);
		if (lenLen < 0) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
		}
		if (i + (DWORD)lenLen + installTokenLength > sizeof(sendBuffer)) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
		}
		i += (DWORD)lenLen;
		memcpy(sendBuffer+i, installToken, installTokenLength);
		i+=installTokenLength;
	}
	else {
		if (i + 1 > sizeof(sendBuffer)) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
		}
		sendBuffer[i++] = 0x00; // Length of install token
	}

 	status = send_chained_data(cardContext, cardInfo, secInfo, commandHeader, 5, sendBuffer, i,
		recvBuffer, &recvBufferLength, false, false, true, false);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	status = parse_receipt_from_response(recvBuffer, recvBufferLength, receiptData, receiptDataAvailable);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("install_for_install_and_make_selectable"), status);
	return status;
}

/**
 * In the case of delegated management an Extradition Token authorizing the
 * INSTALL [for extradition] must be included.
 * Otherwise extraditionToken must be NULL. See GP211_calculate_extradition_token().
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param securityDomainAID [in] A buffer containing the target Security Domain AID.
 * \param securityDomainAIDLength [in] The length of the target Security Domain AID.
 * \param applicationAID [in] The AID of the installed application to be moved.
 * \param applicationAIDLength [in] The length of the application instance AID to be moved.
 * \param extraditionToken [in] The Extradition Token. This is an assymetric (e.g., ECC or RSA) Signature.
 * \param extraditionTokenLength [in] The length of the extraditionToken buffer.
 * \param *receiptData [out] If the deletion is performed by a security domain with delegated management privilege
 * this structure contains the according data.
 * \param receiptDataAvailable [out] 0 if no receiptData is available.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_install_for_extradition(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
							  PBYTE securityDomainAID,
						 DWORD securityDomainAIDLength, PBYTE applicationAID,
						 DWORD applicationAIDLength,
						 PBYTE extraditionToken, DWORD extraditionTokenLength, GP211_RECEIPT_DATA *receiptData,
						 PDWORD receiptDataAvailable) {
	OPGP_ERROR_STATUS status;
	BYTE commandHeader[5] = {0x80, 0xE6, 0x00, 0x00, 0x00};
	DWORD recvBufferLength = APDU_RESPONSE_LEN;
	BYTE recvBuffer[APDU_RESPONSE_LEN];
	BYTE sendBuffer[1000];
	DWORD i=0;
	BYTE buf[1000];
	DWORD bufLength = sizeof(buf);
	PBYTE parsedData = NULL;
	DWORD parsedDataLength = 0;
	LONG lenLen;
	OPGP_LOG_START(_T("install_for_extradition"));
	*receiptDataAvailable = 0;
	status = GP211_get_extradition_token_signature_data(securityDomainAID, securityDomainAIDLength,
		applicationAID, applicationAIDLength, buf, &bufLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	status = extract_case3_data(buf, bufLength, &commandHeader[2], &commandHeader[3], &parsedData, &parsedDataLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	if (parsedDataLength > sizeof(sendBuffer)) {
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_COMMAND_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_COMMAND_TOO_LARGE)); goto end; }
	}
	memcpy(sendBuffer, parsedData, parsedDataLength);
	i += parsedDataLength;

	if (extraditionToken != NULL && extraditionTokenLength > 0) {
		if (extraditionTokenLength > 0xFFFF) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_COMMAND_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_COMMAND_TOO_LARGE)); goto end; }
		}
		lenLen = write_TLV_length(sendBuffer, i, sizeof(sendBuffer) - i, (USHORT)extraditionTokenLength);
		if (lenLen < 0) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
		}
		if (i + (DWORD)lenLen + extraditionTokenLength > sizeof(sendBuffer)) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
		}
		i += (DWORD)lenLen;
		memcpy(sendBuffer+i, extraditionToken, extraditionTokenLength);
		i+=extraditionTokenLength;
	}
	else {
		if (i + 1 > sizeof(sendBuffer)) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
		}
		sendBuffer[i++] = 0x00; // Length of install token
	}

	status = send_chained_data(cardContext, cardInfo, secInfo, commandHeader, 5, sendBuffer, i,
		recvBuffer, &recvBufferLength, false, false, true, false);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	status = parse_receipt_from_response(recvBuffer, recvBufferLength, receiptData, receiptDataAvailable);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("install_for_extradition"), status);
	return status;
}

/**
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param securityDomainAID [in] A buffer containing the target Security Domain AID.
 * \param securityDomainAIDLength [in] The length of the target Security Domain AID.
 * \param applicationAID [in] The AID of the application.
 * \param applicationAIDLength [in] The length of the application instance AID.
 * \param applicationPrivileges [in] The application privileges.
 * \param registryUpdateParameters [in] The Registry Update Parameters.
 * \param registryUpdateParametersLength [in] The length of the Registry Update Parameters.
 * \param registryUpdateToken [in] The Registry Update Token. This is an assymetric (e.g., ECC or RSA) Signature.
 * \param registryUpdateTokenLength [in] The length of the Registry Update Token.
 * \param *receiptData [out] If the deletion is performed by a security domain with delegated management privilege
 * this structure contains the according data.
 * \param receiptDataAvailable [out] 0 if no receiptData is available.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_install_for_registry_update(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
						 PBYTE securityDomainAID, DWORD securityDomainAIDLength,
						 PBYTE applicationAID, DWORD applicationAIDLength,
						 DWORD applicationPrivileges,
						 PBYTE registryUpdateParameters, DWORD registryUpdateParametersLength,
						 PBYTE registryUpdateToken, DWORD registryUpdateTokenLength,
						 GP211_RECEIPT_DATA *receiptData, PDWORD receiptDataAvailable) {
	OPGP_ERROR_STATUS status;
	BYTE commandHeader[5] = {0x80, 0xE6, 0x00, 0x00, 0x00};
	DWORD recvBufferLength = APDU_RESPONSE_LEN;
	BYTE recvBuffer[APDU_RESPONSE_LEN];
	BYTE sendBuffer[1000];
	DWORD i=0;
	BYTE buf[512];
	DWORD bufLength = sizeof(buf);
	PBYTE parsedData = NULL;
	DWORD parsedDataLength = 0;
	LONG lenLen;
	OPGP_LOG_START(_T("install_for_registry_update"));
	*receiptDataAvailable = 0;

	status = GP211_get_registry_update_token_signature_data(securityDomainAID, securityDomainAIDLength,
		applicationAID, applicationAIDLength, applicationPrivileges,
		registryUpdateParameters, registryUpdateParametersLength, buf, &bufLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	status = extract_case3_data(buf, bufLength, &commandHeader[2], &commandHeader[3], &parsedData, &parsedDataLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	if (parsedDataLength > sizeof(sendBuffer)) {
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_COMMAND_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_COMMAND_TOO_LARGE)); goto end; }
	}
	memcpy(sendBuffer, parsedData, parsedDataLength);
	i += parsedDataLength;

	if (registryUpdateToken != NULL && registryUpdateTokenLength > 0) {
		if (registryUpdateTokenLength > 0xFFFF) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_COMMAND_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_COMMAND_TOO_LARGE)); goto end;
		}
		lenLen = write_TLV_length(sendBuffer, i, sizeof(sendBuffer) - i, (USHORT)registryUpdateTokenLength);
		if (lenLen < 0) { OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
		if (i + (DWORD)lenLen + registryUpdateTokenLength > sizeof(sendBuffer)) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end;
		}
		i += (DWORD)lenLen;
		memcpy(sendBuffer+i, registryUpdateToken, registryUpdateTokenLength);
		i+=registryUpdateTokenLength;
	}
	else {
		if (i + 1 > sizeof(sendBuffer)) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
		}
		sendBuffer[i++] = 0x00; // Length of token
	}

	status = send_chained_data(cardContext, cardInfo, secInfo, commandHeader, 5, sendBuffer, i,
		recvBuffer, &recvBufferLength, false, false, true, false);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	status = parse_receipt_from_response(recvBuffer, recvBufferLength, receiptData, receiptDataAvailable);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("install_for_registry_update"), status);
	return status;
}

/**
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param applicationAID [in] The AID of the installed application.
 * \param applicationAIDLength [in] The length of the application instance AID.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_install_for_personalization(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
						 PBYTE applicationAID,
						 DWORD applicationAIDLength) {
	OPGP_ERROR_STATUS status;
	DWORD sendBufferLength=0;
	BYTE recvBuffer[16];
	DWORD recvBufferLength=sizeof(recvBuffer);
	BYTE sendBuffer[32];
	DWORD i=0;

	OPGP_LOG_START(_T("install_for_personalization"));
	if (applicationAIDLength > sizeof(sendBuffer) - 12) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_COMMAND_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_COMMAND_TOO_LARGE));
		goto end;
	}
	sendBuffer[i++] = 0x80;
	sendBuffer[i++] = 0xE6;
	sendBuffer[i++] = 0x20;
	sendBuffer[i++] = 0x00;
	sendBuffer[i++] = 0x00; // Lc dummy
	sendBuffer[i++] = 0x00;
	sendBuffer[i++] = 0x00;

	sendBuffer[i++] = (BYTE)applicationAIDLength;
	memcpy(sendBuffer+i, applicationAID, applicationAIDLength);
	i+=applicationAIDLength;

	sendBuffer[i++] = 0x00;
	sendBuffer[i++] = 0x00;
	sendBuffer[i++] = 0x00;

	sendBuffer[4] = (BYTE)i-5; // Lc
	sendBuffer[i++] = 0x00; // Le
	sendBufferLength = i;

	status = OPGP_send_APDU(cardContext, cardInfo, secInfo,sendBuffer, sendBufferLength, recvBuffer, &recvBufferLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	CHECK_SW_9000(recvBuffer, recvBufferLength, status);

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("install_for_personalization"), status);
	return status;
}

/**
 * In the case of delegated management an Install Token authorizing the INSTALL [for make selectable] must be included.
 * Otherwise installToken must be NULL.
 * For Security domains look in your manual what parameters are necessary.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param applicationAID [in] The AID of the installed application or security domain.
 * \param applicationAIDLength [in] The length of the application instance AID.
 * \param applicationPrivileges [in] The application privileges. Can be an OR of multiple privileges. See GP211_APPLICATION_PRIVILEGE_SECURITY_DOMAIN.
 * \param installToken [in] The Install Token. This is an assymetric (e.g., ECC or RSA) Signature.
 * \param installTokenLength [in] The length of the installToken buffer.
 * \param *receiptData [out] If the deletion is performed by a security domain with delegated management privilege
 * this structure contains the according data.
 * \param receiptDataAvailable [out] 0 if no receiptData is available.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_install_for_make_selectable(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
								 PBYTE applicationAID,
								 DWORD applicationAIDLength, DWORD applicationPrivileges,
								 PBYTE installToken, DWORD installTokenLength, GP211_RECEIPT_DATA *receiptData,
								 PDWORD receiptDataAvailable) {
	return install_for_make_selectable(cardContext, cardInfo, secInfo,
								 applicationAID,
								 applicationAIDLength, applicationPrivileges,
								 installToken, installTokenLength, receiptData,
								 receiptDataAvailable);
}

OPGP_ERROR_STATUS install_for_make_selectable(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
								 PBYTE applicationAID,
								 DWORD applicationAIDLength, DWORD applicationPrivileges,
								 PBYTE installToken, DWORD installTokenLength,
								 GP211_RECEIPT_DATA *receiptData,
								 PDWORD receiptDataAvailable) {
	OPGP_ERROR_STATUS status;
	BYTE commandHeader[5] = {0x80, 0xE6, 0x08, 0x00, 0x00};
	DWORD recvBufferLength = APDU_RESPONSE_LEN;
	BYTE recvBuffer[APDU_RESPONSE_LEN];
	BYTE sendBuffer[1000];
	DWORD i=0;
	LONG lenLen;
	OPGP_LOG_START(_T("install_for_make_selectable"));
	*receiptDataAvailable = 0;
	sendBuffer[i++] = 0x00; //  Executable Load File AID
	sendBuffer[i++] = 0x00; // application class AID

	if (i + 1 + applicationAIDLength > sizeof(sendBuffer)) {
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_COMMAND_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_COMMAND_TOO_LARGE)); goto end; }
	}
	sendBuffer[i++] = (BYTE)applicationAIDLength; // application instance AID
	memcpy(sendBuffer+i, applicationAID, applicationAIDLength);
	i+=applicationAIDLength;

	sendBuffer[i++] = 0x01;
	sendBuffer[i++] = applicationPrivileges; // application privileges

	sendBuffer[i++] = 0x00; // install parameter field length

	if (installToken != NULL && installTokenLength > 0) {
		if (installTokenLength > 0xFFFF) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_COMMAND_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_COMMAND_TOO_LARGE)); goto end; }
		}
		lenLen = write_TLV_length(sendBuffer, i, sizeof(sendBuffer) - i, (USHORT)installTokenLength);
		if (lenLen < 0) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
		}
		if (i + (DWORD)lenLen + installTokenLength > sizeof(sendBuffer)) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
		}
		i += (DWORD)lenLen;
		memcpy(sendBuffer+i, installToken, installTokenLength);
		i+=installTokenLength;
	}
	else {
		if (i + 1 > sizeof(sendBuffer)) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
		}
		sendBuffer[i++] = 0x00; // Length of install token
	}

	status = send_chained_data(cardContext, cardInfo, secInfo, commandHeader, 5, sendBuffer, i,
		recvBuffer, &recvBufferLength, false, false, true, false);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	status = parse_receipt_from_response(recvBuffer, recvBufferLength, receiptData, receiptDataAvailable);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("install_for_make_selectable"), status);
	return status;
}

/**
 * If you are not the Card Issuer and do not know the token verification private key send this data to the
 * Card Issuer and obtain the RSA signature of the data, i.e. the Install Token.
 * volatileDataSpaceLimit can be 0, if the card does not need or support this tag.
 * The parameters must match the parameters of a later GP211_install_for_install() and GP211_install_for_make_selectable() method.
 * \param P1 [in] The parameter P1 in the APDU command.
 * <ul>
 * <li> 0x04 for a INSTALL [for install] command </li>
 * <li> 0x08 for an INSTALL [for make selectable] command </li>
 * <li> 0x0C for an INSTALL [for install and make selectable] </li>
 * </ul>
 * \param executableLoadFileAID [in] A buffer with AID of the Executable Load File to INSTALL [for load].
 * \param executableLoadFileAIDLength [in] The length of the Executable Load File AID.
 * \param executableModuleAID [in] The AID of the application class in the package.
 * \param executableModuleAIDLength [in] The length of the executableModuleAID buffer.
 * \param applicationAID [in] The AID of the installed application.
 * \param applicationAIDLength [in] The length of the application instance AID.
 * \param applicationPrivileges [in] The application privileges. Can be an OR of multiple privileges. See GP211_APPLICATION_PRIVILEGE_SECURITY_DOMAIN.
 * \param volatileDataSpaceLimit [in] The minimum amount of RAM space that must be available.
 * \param nonVolatileDataSpaceLimit [in] The minimum amount of space for objects of the application, i.e. the data allocated in its lifetime.
 * \param installParameters [in] Applet install parameters for the install() method of the application.
 * \param installParametersLength [in] The length of the installParameters buffer.
 * \param sdParameters [in] Security Domain install parameters encoded for tag C9 (e.g., tag 'C1' with tags '81','82','83','84','86','87').
 * \param sdParametersLength [in] The length of the sdParameters buffer.
 * \param uiccSystemSpecParams [in] UICC System Specific Parameters according to ETSI TS 102 226, sect. 8.2.1.3.2.2.
 * \param uiccSystemSpecParamsLength [in] The length of the uiccSystemSpecParams buffer.
 * \param simSpecParams [in] SIM File Access and Toolkit Application Specific Parameters according to ETSI TS 102 226, sect. 8.2.1.3.2.1.
 * \param simSpecParamsLength [in] The length of the simSpecParams buffer.
 * \param installTokenSignatureData [out] The data to sign in a Install Token.
 * \param installTokenSignatureDataLength [in, out] The length of the installTokenSignatureData buffer.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_get_install_token_signature_data(BYTE P1, PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength, PBYTE executableModuleAID,
										  DWORD executableModuleAIDLength, PBYTE applicationAID,
										  DWORD applicationAIDLength, DWORD applicationPrivileges,
										  DWORD volatileDataSpaceLimit, DWORD nonVolatileDataSpaceLimit,
									  PBYTE installParameters, DWORD installParametersLength,
										  PBYTE sdParameters, DWORD sdParametersLength,
										  PBYTE uiccSystemSpecParams, DWORD uiccSystemSpecParamsLength,
										  PBYTE simSpecParams, DWORD simSpecParamsLength,
										  PBYTE installTokenSignatureData, PDWORD installTokenSignatureDataLength) {
	return get_install_data(P1, executableLoadFileAID, executableLoadFileAIDLength, executableModuleAID,
										  executableModuleAIDLength, applicationAID,
										  applicationAIDLength, applicationPrivileges,
										  volatileDataSpaceLimit, nonVolatileDataSpaceLimit,
										  installParameters, installParametersLength,
										  sdParameters, sdParametersLength,
										  uiccSystemSpecParams, uiccSystemSpecParamsLength,
										  simSpecParams, simSpecParamsLength,
										  installTokenSignatureData, installTokenSignatureDataLength);
}

static OPGP_ERROR_STATUS build_uicc_toolkit_app_params(const GP211_UICC_TOOLKIT_APP_PARAMS *params, PBYTE out,
						       PDWORD outLength) {
	OPGP_ERROR_STATUS status;
	BYTE buf[256];
	DWORD i = 0;
	DWORD j = 0;
	DWORD mslLength = 0;
	DWORD requiredLength = 0;

	if (params == NULL || out == NULL || outLength == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}
	if (params->maxTimers > GP211_UICC_TOOLKIT_MAX_TIMERS ||
		params->maxChannels > GP211_UICC_TOOLKIT_MAX_CHANNELS ||
		params->maxServices > GP211_UICC_TOOLKIT_MAX_SERVICES) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}
	if (params->maxMenuEntries > GP211_UICC_MAX_MENU_ENTRIES) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}
	if ((params->tarValuesLength % 3) != 0 || params->tarValuesLength > (GP211_UICC_MAX_TAR_VALUES * 3)) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}

	if (params->mslData != 0) {
		mslLength = 2;
	}
	requiredLength = 8 + ((DWORD)params->maxMenuEntries * 2) + mslLength + params->tarValuesLength;
	if (requiredLength > 0xFF) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE));
		return status;
	}
	if (requiredLength > *outLength) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
		return status;
	}

	buf[i++] = params->priority;
	buf[i++] = params->maxTimers;
	buf[i++] = params->maxTextLength;
	buf[i++] = params->maxMenuEntries;
	for (j = 0; j < params->maxMenuEntries; j++) {
		buf[i++] = params->menuEntries[j].position;
		buf[i++] = params->menuEntries[j].identifier;
	}
	buf[i++] = params->maxChannels;
	buf[i++] = (BYTE)mslLength;
	if (mslLength > 0) {
		buf[i++] = GP211_UICC_MSL_PARAMETER_MINIMUM_SPI1;
		buf[i++] = params->mslData;
	}
	buf[i++] = (BYTE)params->tarValuesLength;
	if (params->tarValuesLength > 0) {
		memcpy(buf + i, params->tarValues, params->tarValuesLength);
		i += params->tarValuesLength;
	}
	buf[i++] = params->maxServices;

	if (i != requiredLength) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}
	memcpy(out, buf, i);
	*outLength = i;
	{ OPGP_ERROR_CREATE_NO_ERROR(status); return status; }
}

static OPGP_ERROR_STATUS build_uicc_access_params(const GP211_UICC_ACCESS_PARAMS *params, PBYTE out,
						  PDWORD outLength) {
	OPGP_ERROR_STATUS status;
	BYTE buf[256];
	DWORD i = 0;
	DWORD r = 0;

	if (params == NULL || out == NULL || outLength == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}
	if (params->rulesLength > GP211_UICC_MAX_ACCESS_RULES) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}

	for (r = 0; r < params->rulesLength; r++) {
		const GP211_UICC_ACCESS_RULE *rule = &params->rules[r];
		DWORD accessDomainLength = 0;

		if (rule->aidLength != 0 && (rule->aidLength < 5 || rule->aidLength > 16)) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			return status;
		}
		switch (rule->accessDomainParameter) {
			case GP211_UICC_ACCESS_DOMAIN_FULL_ACCESS:
			case GP211_UICC_ACCESS_DOMAIN_NO_ACCESS:
				accessDomainLength = 1;
				break;
			case GP211_UICC_ACCESS_DOMAIN_UICC_ACCESS:
				accessDomainLength = 4;
				break;
			default:
				OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
				return status;
		}
		if (rule->accessDomainDapLength > GP211_UICC_ACCESS_DOMAIN_DAP_MAX_LENGTH) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			return status;
		}

		if ((i + 1 + rule->aidLength + 1 + accessDomainLength + 1 + rule->accessDomainDapLength) > 0xFF) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE));
			return status;
		}

		buf[i++] = rule->aidLength;
		if (rule->aidLength > 0) {
			memcpy(buf + i, rule->aid, rule->aidLength);
			i += rule->aidLength;
		}
		buf[i++] = (BYTE)accessDomainLength;
		buf[i++] = rule->accessDomainParameter;
		if (accessDomainLength == 4) {
			memcpy(buf + i, rule->accessDomainData, 3);
			i += 3;
		}
		buf[i++] = (BYTE)rule->accessDomainDapLength;
		if (rule->accessDomainDapLength > 0) {
			memcpy(buf + i, rule->accessDomainDap, rule->accessDomainDapLength);
			i += rule->accessDomainDapLength;
		}
	}

	if (i > *outLength) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
		return status;
	}
	memcpy(out, buf, i);
	*outLength = i;
	{ OPGP_ERROR_CREATE_NO_ERROR(status); return status; }
}

static OPGP_ERROR_STATUS build_sim_toolkit_params(const GP211_SIM_TOOLKIT_PARAMS *params, PBYTE out,
						  PDWORD outLength) {
	OPGP_ERROR_STATUS status;
	BYTE buf[256];
	DWORD i = 0;
	DWORD j = 0;
	DWORD mslLength = 0;
	DWORD requiredLength = 0;

	if (params == NULL || out == NULL || outLength == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}
	if (params->maxTimers > GP211_UICC_TOOLKIT_MAX_TIMERS ||
		params->maxChannels > GP211_UICC_TOOLKIT_MAX_CHANNELS) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}
	if (params->maxMenuEntries > GP211_UICC_MAX_MENU_ENTRIES) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}
	if ((params->tarValuesLength % 3) != 0 || params->tarValuesLength > (GP211_UICC_MAX_TAR_VALUES * 3)) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}

	if (params->mslData != 0) {
		mslLength = 2;
	}
	requiredLength = 7 + ((DWORD)params->maxMenuEntries * 2) + mslLength + params->tarValuesLength;
	if (requiredLength > 0xFF) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE));
		return status;
	}
	if (requiredLength > *outLength) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
		return status;
	}

	buf[i++] = params->priority;
	buf[i++] = params->maxTimers;
	buf[i++] = params->maxTextLength;
	buf[i++] = params->maxMenuEntries;
	for (j = 0; j < params->maxMenuEntries; j++) {
		buf[i++] = params->menuEntries[j].position;
		buf[i++] = params->menuEntries[j].identifier;
	}
	buf[i++] = params->maxChannels;
	buf[i++] = (BYTE)mslLength;
	if (mslLength > 0) {
		buf[i++] = GP211_UICC_MSL_PARAMETER_MINIMUM_SPI1;
		buf[i++] = params->mslData;
	}
	buf[i++] = (BYTE)params->tarValuesLength;
	if (params->tarValuesLength > 0) {
		memcpy(buf + i, params->tarValues, params->tarValuesLength);
		i += params->tarValuesLength;
	}

	if (i != requiredLength) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		return status;
	}
	memcpy(out, buf, i);
	*outLength = i;
	{ OPGP_ERROR_CREATE_NO_ERROR(status); return status; }
}

/**
 * Builds the UICC System Specific Parameters value.
 * The output is the value of tag EA without the tag and length bytes.
 * \param params [in] The structured UICC system specific parameters to encode.
 * \param uiccSystemSpecParams [out] The output buffer for the encoded parameters.
 * \param uiccSystemSpecParamsLength [in, out] The length of uiccSystemSpecParams and, on success, the number of encoded bytes.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS GP211_build_uicc_system_specific_params(GP211_UICC_SYSTEM_SPECIFIC_PARAMS *params,
							  PBYTE uiccSystemSpecParams, PDWORD uiccSystemSpecParamsLength) {
	OPGP_ERROR_STATUS status;
	BYTE buf[256];
	DWORD i = 0;
	BYTE tmp[256];
	DWORD tmpLength = 0;

	OPGP_LOG_START(_T("GP211_build_uicc_system_specific_params"));
	if (params == NULL || uiccSystemSpecParams == NULL || uiccSystemSpecParamsLength == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		goto end;
	}

	if (params->toolkitParamsPresent) {
		tmpLength = sizeof(tmp);
		status = build_uicc_toolkit_app_params(&params->toolkitParams, tmp, &tmpLength);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
		if (tmpLength > 0) {
			if ((i + 2 + tmpLength) > 0xFF) {
				OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE));
				goto end;
			}
			buf[i++] = 0x80;
			buf[i++] = (BYTE)tmpLength;
			memcpy(buf + i, tmp, tmpLength);
			i += tmpLength;
		}
	}

	if (params->toolkitParametersDapLength > 0) {
		if (params->toolkitParametersDapLength > GP211_UICC_TOOLKIT_DAP_MAX_LENGTH) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			goto end;
		}
		if ((i + 2 + params->toolkitParametersDapLength) > 0xFF) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE));
			goto end;
		}
		buf[i++] = 0xC3;
		buf[i++] = (BYTE)params->toolkitParametersDapLength;
		memcpy(buf + i, params->toolkitParametersDap, params->toolkitParametersDapLength);
		i += params->toolkitParametersDapLength;
	}

	if (params->accessParamsPresent) {
		tmpLength = sizeof(tmp);
		status = build_uicc_access_params(&params->accessParams, tmp, &tmpLength);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
		if (tmpLength > 0) {
			if ((i + 2 + tmpLength) > 0xFF) {
				OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE));
				goto end;
			}
			buf[i++] = 0x81;
			buf[i++] = (BYTE)tmpLength;
			memcpy(buf + i, tmp, tmpLength);
			i += tmpLength;
		}
	}

	if (params->adminAccessParamsPresent) {
		tmpLength = sizeof(tmp);
		status = build_uicc_access_params(&params->adminAccessParams, tmp, &tmpLength);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
		if (tmpLength > 0) {
			if ((i + 2 + tmpLength) > 0xFF) {
				OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE));
				goto end;
			}
			buf[i++] = 0x82;
			buf[i++] = (BYTE)tmpLength;
			memcpy(buf + i, tmp, tmpLength);
			i += tmpLength;
		}
	}

	if (i > *uiccSystemSpecParamsLength) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
		goto end;
	}
	memcpy(uiccSystemSpecParams, buf, i);
	*uiccSystemSpecParamsLength = i;
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }

end:
	OPGP_LOG_END(_T("GP211_build_uicc_system_specific_params"), status);
	return status;
}

/**
 * Builds the SIM File Access and Toolkit Application Specific Parameters value.
 * The output is the value of tag CA without the tag and length bytes.
 * \param params [in] The structured SIM specific parameters to encode.
 * \param simSpecParams [out] The output buffer for the encoded parameters.
 * \param simSpecParamsLength [in, out] The length of simSpecParams and, on success, the number of encoded bytes.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS GP211_build_sim_specific_params(GP211_SIM_SPECIFIC_PARAMS *params,
						  PBYTE simSpecParams, PDWORD simSpecParamsLength) {
	OPGP_ERROR_STATUS status;
	BYTE buf[256];
	DWORD i = 0;
	BYTE tmp[256];
	DWORD tmpLength = sizeof(tmp);
	BYTE accessDomainLength = 0;

	OPGP_LOG_START(_T("GP211_build_sim_specific_params"));
	if (params == NULL || simSpecParams == NULL || simSpecParamsLength == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		goto end;
	}

	switch (params->accessDomainParameter) {
		case GP211_UICC_ACCESS_DOMAIN_FULL_ACCESS:
		case GP211_UICC_ACCESS_DOMAIN_NO_ACCESS:
			accessDomainLength = 1;
			break;
		case GP211_UICC_ACCESS_DOMAIN_UICC_ACCESS:
			accessDomainLength = 4;
			break;
		default:
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			goto end;
	}

	tmpLength = sizeof(tmp);
	status = build_sim_toolkit_params(&params->toolkitParams, tmp, &tmpLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	if ((1 + accessDomainLength + tmpLength) > 0xFF) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE));
		goto end;
	}

	buf[i++] = accessDomainLength;
	buf[i++] = params->accessDomainParameter;
	if (accessDomainLength == 4) {
		memcpy(buf + i, params->accessDomainData, 3);
		i += 3;
	}
	memcpy(buf + i, tmp, tmpLength);
	i += tmpLength;

	if (i > *simSpecParamsLength) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
		goto end;
	}
	memcpy(simSpecParams, buf, i);
	*simSpecParamsLength = i;
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }

end:
	OPGP_LOG_END(_T("GP211_build_sim_specific_params"), status);
	return status;
}

/**
 * Builds Security Domain install parameters.
 * The output is the value of tag C1, including the nested tags and lengths for
 * SCP entries, extradition/deletion policies, personalized-state marker, CASD
 * capability information and extradition-away policy.
 * \param params [in] The structured Security Domain install parameters to encode.
 * \param sdParameters [out] The output buffer for the encoded parameters.
 * \param sdParametersLength [in, out] The length of sdParameters and, on success, the number of encoded bytes.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS GP211_build_sd_parameters(GP211_SD_INSTALL_PARAMS *params,
						  PBYTE sdParameters, PDWORD sdParametersLength) {
	OPGP_ERROR_STATUS status;
	BYTE buf[256];
	DWORD i = 0;
	DWORD j = 0;

	OPGP_LOG_START(_T("GP211_build_sd_parameters"));
	if (params == NULL || sdParameters == NULL || sdParametersLength == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		goto end;
	}
	if (params->scpEntriesLength > GP211_SD_MAX_SCP) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		goto end;
	}
	for (j = 0; j < params->scpEntriesLength; j++) {
		if ((i + 4) > sizeof(buf)) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE));
			goto end;
		}
		buf[i++] = GP211_SD_PARAM_TAG_SCP;
		buf[i++] = GP211_SD_PARAM_LEN_SCP;
		buf[i++] = params->scpEntries[j].scpIdentifier;
		buf[i++] = params->scpEntries[j].scpImpl;
	}
	if (params->acceptExtraditionHereLength > 0) {
		if (params->acceptExtraditionHereLength != GP211_SD_PARAM_LEN_ACCEPT_MIN &&
			params->acceptExtraditionHereLength != GP211_SD_PARAM_LEN_ACCEPT_MAX) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			goto end;
		}
		if ((i + 2 + params->acceptExtraditionHereLength) > sizeof(buf)) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE));
			goto end;
		}
		buf[i++] = GP211_SD_PARAM_TAG_ACCEPT_EXTRACTION;
		buf[i++] = params->acceptExtraditionHereLength;
		memcpy(buf + i, params->acceptExtraditionHere, params->acceptExtraditionHereLength);
		i += params->acceptExtraditionHereLength;
	}
	if (params->acceptDeletionLength > 0) {
		if (params->acceptDeletionLength != GP211_SD_PARAM_LEN_ACCEPT_DELETE) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			goto end;
		}
		if ((i + 3) > sizeof(buf)) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE));
			goto end;
		}
		buf[i++] = GP211_SD_PARAM_TAG_ACCEPT_DELETE;
		buf[i++] = GP211_SD_PARAM_LEN_ACCEPT_DELETE;
		buf[i++] = params->acceptDeletion;
	}
	if (params->personalizedStatePresent) {
		if ((i + 2) > sizeof(buf)) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE));
			goto end;
		}
		buf[i++] = GP211_SD_PARAM_TAG_PERSONALIZED;
		buf[i++] = GP211_SD_PARAM_LEN_PERSONALIZED;
	}
	if (params->casdCapabilityInfo[0] != 0 || params->casdCapabilityInfo[1] != 0) {
		if ((i + 4) > sizeof(buf)) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE));
			goto end;
		}
		buf[i++] = GP211_SD_PARAM_TAG_CASD_CAPABILITY;
		buf[i++] = GP211_SD_PARAM_LEN_CASD_CAPABILITY;
		buf[i++] = params->casdCapabilityInfo[0];
		buf[i++] = params->casdCapabilityInfo[1];
	}
	if (params->acceptExtraditionAwayLength > 0) {
		if (params->acceptExtraditionAwayLength != GP211_SD_PARAM_LEN_ACCEPT_MIN &&
			params->acceptExtraditionAwayLength != GP211_SD_PARAM_LEN_ACCEPT_MAX) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			goto end;
		}
		if ((i + 2 + params->acceptExtraditionAwayLength) > sizeof(buf)) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE));
			goto end;
		}
		buf[i++] = GP211_SD_PARAM_TAG_ACCEPT_GLOBAL_DELETE;
		buf[i++] = params->acceptExtraditionAwayLength;
		memcpy(buf + i, params->acceptExtraditionAway, params->acceptExtraditionAwayLength);
		i += params->acceptExtraditionAwayLength;
	}

	if (i == 0) {
		*sdParametersLength = 0;
		{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
	}
	if (i > 0xFF) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE));
		goto end;
	}
	if (i > *sdParametersLength) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
		goto end;
	}
	memcpy(sdParameters, buf, i);
	*sdParametersLength = i;
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }

end:
	OPGP_LOG_END(_T("GP211_build_sd_parameters"), status);
	return status;
}

OPGP_ERROR_STATUS get_install_data(BYTE P1, PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength, PBYTE executableModuleAID,
										  DWORD executableModuleAIDLength, PBYTE applicationAID,
										  DWORD applicationAIDLength, DWORD applicationPrivileges,
										  DWORD volatileDataSpaceLimit, DWORD nonVolatileDataSpaceLimit,
										  PBYTE installParameters, DWORD installParametersLength,
									  PBYTE sdParameters, DWORD sdParametersLength,
									  PBYTE uiccSystemSpecParams, DWORD uiccSystemSpecParamsLength,
									  PBYTE simSpecParams, DWORD simSpecParamsLength,
									  PBYTE installData, PDWORD installDataLength) {
	BYTE buf[300];
	DWORD i=0;
	DWORD hiByte, loByte;
	DWORD installParameterFieldLengthSize;
	DWORD installParameterFieldLength;
	DWORD c9Length;
	DWORD bodyLength;
	DWORD encodedLength;
	OPGP_ERROR_STATUS status;

	OPGP_LOG_START(_T("get_install_data"));
	buf[i++] = P1;
	buf[i++] = 0x00;
	buf[i++] = 0x00; // Lc dummy (extended)
	buf[i++] = 0x00; // Lc dummy (extended)
	buf[i++] = 0x00; // Lc dummy (extended)
	buf[i++] = (BYTE)executableLoadFileAIDLength; // Executable Load File AID
	memcpy(buf+i, executableLoadFileAID, executableLoadFileAIDLength);
	i+=executableLoadFileAIDLength;
	buf[i++] = (BYTE)executableModuleAIDLength; // application class AID
	memcpy(buf+i, executableModuleAID, executableModuleAIDLength);
	i+=executableModuleAIDLength;
	buf[i++] = (BYTE)applicationAIDLength; // application instance AID
	memcpy(buf+i, applicationAID, applicationAIDLength);
	i+=applicationAIDLength;

	// Encode privileges with variable length (1, 2, or 3 bytes depending on which bits are set)
	BYTE privilegeLength = 1;
	if (applicationPrivileges & 0x0000FF) {
		privilegeLength = 3;
	} else if (applicationPrivileges & 0xFF00) {
		privilegeLength = 2;
	}
	buf[i++] = privilegeLength;
	if (privilegeLength == 3) {
		buf[i++] = (BYTE)((applicationPrivileges >> 16) & 0xFF);
		buf[i++] = (BYTE)((applicationPrivileges >> 8) & 0xFF);
		buf[i++] = (BYTE)(applicationPrivileges & 0xFF);
	} else if (privilegeLength == 2) {
		buf[i++] = (BYTE)((applicationPrivileges >> 16) & 0xFF);
		buf[i++] = (BYTE)((applicationPrivileges >> 8) & 0xFF);
	} else {
		buf[i++] = (BYTE)((applicationPrivileges >> 16) & 0xFF);
	}

	installParameterFieldLength = 0x02; // install parameter field length tag C9 + length byte (C9LL)
	c9Length = installParametersLength + sdParametersLength;
	if (c9Length > 0) {
		installParameterFieldLength += (BYTE)c9Length;
	}
	if (uiccSystemSpecParamsLength > 0) {
		installParameterFieldLength += 2;
		installParameterFieldLength += (BYTE)uiccSystemSpecParamsLength;
	}

	if (nonVolatileDataSpaceLimit > 0 || volatileDataSpaceLimit > 0 || simSpecParamsLength > 0) {
		installParameterFieldLength += 2; // 0xEF LL
	}
	if (nonVolatileDataSpaceLimit > 0) {
		installParameterFieldLength += 4;
	}
	if (volatileDataSpaceLimit > 0) {
		installParameterFieldLength += 4;
	}
	if (simSpecParamsLength > 0) {
		installParameterFieldLength += (BYTE)simSpecParamsLength + 2;
	}
	if (installParameterFieldLength < 128L) {
		installParameterFieldLengthSize=1;
	}
	else if (installParameterFieldLength < 256L) {
		installParameterFieldLengthSize=2;
	}
	else if (installParameterFieldLength < 65536L) {
		installParameterFieldLengthSize=3;
	}
	else {
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_INSTALL_PARAMETERS_TOO_LARGE)); goto end; }
	}
	switch (installParameterFieldLengthSize) {
		case 1: {
			buf[i++] = (BYTE)installParameterFieldLength;
			break;
			}
		case 2: {
			buf[i++] = 0x81;
			buf[i++] = (BYTE)installParameterFieldLength;
			break;
			}
		case 3: {
			buf[i++] = 0x82;
			buf[i++] = (BYTE)(installParameterFieldLength >> 8);
			buf[i++] = (BYTE)(installParameterFieldLength - (buf[i-1] << 8));
			break;
			}
	}
	buf[i++] = 0xC9; // application install parameters
 	buf[i++] = (BYTE)c9Length;
	if (installParametersLength > 0) {
		memcpy(buf+i, installParameters, installParametersLength);
		i+=installParametersLength;
	}
	if (sdParametersLength > 0) {
		memcpy(buf+i, sdParameters, sdParametersLength);
		i+=sdParametersLength;
	}
 
 	if (nonVolatileDataSpaceLimit > 0 || volatileDataSpaceLimit > 0 || simSpecParamsLength > 0) {
		buf[i++] = 0xEF;
		buf[i++] = (simSpecParamsLength > 0 ? simSpecParamsLength + 2 : 0) + (volatileDataSpaceLimit > 0 ? 4 : 0) + (nonVolatileDataSpaceLimit > 0 ? 4 : 0);
		if (volatileDataSpaceLimit != 0) {
			buf[i++] = 0xC7;
			buf[i++] = 0x02;
			hiByte = volatileDataSpaceLimit >> 8;
			loByte = volatileDataSpaceLimit - (hiByte << 8);
			buf[i++] = (BYTE)hiByte;
			buf[i++] = (BYTE)loByte;
		}
		if (nonVolatileDataSpaceLimit != 0) {
			buf[i++] = 0xC8;
			buf[i++] = 0x02;
			hiByte = nonVolatileDataSpaceLimit >> 8;
			loByte = nonVolatileDataSpaceLimit - (hiByte << 8);
			buf[i++] = (BYTE)hiByte;
			buf[i++] = (BYTE)loByte;
		}
		if (simSpecParamsLength > 0) {
			buf[i++] = 0xCA;
			buf[i++] = simSpecParamsLength;
			memcpy(buf+i, simSpecParams, simSpecParamsLength);
			i+=simSpecParamsLength;
		}
	}

	if (uiccSystemSpecParamsLength > 0) {
	 	buf[i++] = 0xEA;
		buf[i++] = uiccSystemSpecParamsLength;
		memcpy(buf+i, uiccSystemSpecParams, uiccSystemSpecParamsLength);
		i+=uiccSystemSpecParamsLength;
	}
	bodyLength = i - 5;
	if (bodyLength > 0xFF) {
		encodedLength = i;
		buf[2] = 0x00;
		buf[3] = (BYTE)(bodyLength >> 8);
		buf[4] = (BYTE)(bodyLength & 0xFF);
		if (encodedLength > *installDataLength) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
		}
		memcpy(installData, buf, encodedLength);
		*installDataLength = encodedLength;
	} else {
		encodedLength = bodyLength + 3;
		if (encodedLength > *installDataLength) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
		}
		installData[0] = buf[0];
		installData[1] = buf[1];
		installData[2] = (BYTE)bodyLength;
		memcpy(installData + 3, buf + 5, bodyLength);
		*installDataLength = encodedLength;
	}
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("get_install_data"), status);
	return status;
}

/**
 * If you are not the Card Issuer and do not know the token verification private key send this data to the
 * Card Issuer and obtain the RSA signature of the data, i.e. the Extradition Token.
 * The parameters must match the parameters of a later GP211_install_for_extradition() method.
 * \param securityDomainAID [in] A buffer containing the Security Domain AID.
 * \param securityDomainAIDLength [in] The length of the Security Domain AID.
 * \param applicationAID [in] The AID of the installed application.
 * \param applicationAIDLength [in] The length of the application instance AID.
 * \param extraditionTokenSignatureData [out] The data to sign in a Install Token.
 * \param extraditionTokenSignatureDataLength [in, out] The length of the installTokenSignatureData buffer.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_get_extradition_token_signature_data(PBYTE securityDomainAID,
										  DWORD securityDomainAIDLength,
										  PBYTE applicationAID, DWORD applicationAIDLength,
										  PBYTE extraditionTokenSignatureData,
										  PDWORD extraditionTokenSignatureDataLength) {
	BYTE buf[64];
	DWORD i=0;
	DWORD bodyLength;
	DWORD encodedLength;
	OPGP_ERROR_STATUS status;

	OPGP_LOG_START(_T("get_extradition_token_signature_data"));
	if (securityDomainAIDLength > 0xFF || applicationAIDLength > 0xFF ||
			securityDomainAIDLength + applicationAIDLength + 5 > 0xFF) {
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_COMMAND_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_COMMAND_TOO_LARGE)); goto end; }
	}
	buf[i++] = 0x10;
	buf[i++] = 0x00;
	buf[i++] = 0x00; // Lc dummy
	buf[i++] = (BYTE)securityDomainAIDLength; // Security Domain AID
	memcpy(buf+i, securityDomainAID, securityDomainAIDLength);
	i+=securityDomainAIDLength;
	buf[i++] = 0x00;
	buf[i++] = (BYTE)applicationAIDLength; // application instance AID
	memcpy(buf+i, applicationAID, applicationAIDLength);
	i+=applicationAIDLength;

	buf[i++] = 0x00;
	buf[i++] = 0x00;

	bodyLength = i - 3;
	encodedLength = i;
	if (encodedLength > *extraditionTokenSignatureDataLength) {
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
	}
	buf[2] = (BYTE)bodyLength;
	memcpy(extraditionTokenSignatureData, buf, encodedLength);
	*extraditionTokenSignatureDataLength = encodedLength;
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("get_extradition_token_signature_data"), status);
	return status;
}

/**
 * If you are not the Card Issuer and do not know the token verification private key send this data to the
 * Card Issuer and obtain the signature of the data, i.e., the Registry Update Token.
 * The parameters must match the parameters of a later GP211_install_for_registry_update() method.
 * \param securityDomainAID [in] A buffer containing the target Security Domain AID.
 * \param securityDomainAIDLength [in] The length of the target Security Domain AID.
 * \param applicationAID [in] The AID of the application.
 * \param applicationAIDLength [in] The length of the application instance AID.
 * \param applicationPrivileges [in] The application privileges.
 * \param registryUpdateParameters [in] The Registry Update Parameters.
 * \param registryUpdateParametersLength [in] The length of the Registry Update Parameters.
 * \param tokenSignatureData [out] The data to sign in a Registry Update Token.
 * \param tokenSignatureDataLength [in, out] The length of the tokenSignatureData buffer.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_get_registry_update_token_signature_data(PBYTE securityDomainAID,
										  DWORD securityDomainAIDLength,
										  PBYTE applicationAID, DWORD applicationAIDLength,
										  DWORD applicationPrivileges,
										  PBYTE registryUpdateParameters, DWORD registryUpdateParametersLength,
										  PBYTE tokenSignatureData,
										  PDWORD tokenSignatureDataLength) {
	BYTE buf[512];
	DWORD i=0;
	DWORD bodyLength;
	DWORD encodedLength;
	OPGP_ERROR_STATUS status;
	BYTE privilegeLength = 0;

	OPGP_LOG_START(_T("get_registry_update_token_signature_data"));

	buf[i++] = 0x40; // P1
	buf[i++] = 0x00; // P2
	buf[i++] = 0x00; // Lc dummy (extended)
	buf[i++] = 0x00; // Lc dummy (extended)
	buf[i++] = 0x00; // Lc dummy (extended)

	buf[i++] = (BYTE)securityDomainAIDLength; // Security Domain AID length
	if (securityDomainAIDLength > 0 && securityDomainAID != NULL) {
		memcpy(buf+i, securityDomainAID, securityDomainAIDLength);
		i+=securityDomainAIDLength;
	}

	buf[i++] = 0x00; // Length of data

	buf[i++] = (BYTE)applicationAIDLength; // Application AID length
	if (applicationAIDLength > 0 && applicationAID != NULL) {
		memcpy(buf+i, applicationAID, applicationAIDLength);
		i+=applicationAIDLength;
	}

	// Privileges
	if (applicationPrivileges & 0x00FFFF) {
		privilegeLength = 3;
	} else if (applicationPrivileges & 0xFF0000) {
		privilegeLength = 1;
	} else {
		privilegeLength = 0;
	}

	buf[i++] = privilegeLength;
	if (privilegeLength == 3) {
		buf[i++] = (BYTE)((applicationPrivileges >> 16) & 0xFF);
		buf[i++] = (BYTE)((applicationPrivileges >> 8) & 0xFF);
		buf[i++] = (BYTE)(applicationPrivileges & 0xFF);
	} else if (privilegeLength == 1) {
		buf[i++] = (BYTE)((applicationPrivileges >> 16) & 0xFF);
	}

	// Always skip Registry Update Parameters field and use 0 for its length as requested
	buf[i++] = 0x00;

	bodyLength = i - 5;
	if (bodyLength > 0xFF) {
		encodedLength = i;
		buf[2] = 0x00;
		buf[3] = (BYTE)(bodyLength >> 8);
		buf[4] = (BYTE)(bodyLength & 0xFF);
		if (encodedLength > *tokenSignatureDataLength) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
		}
		memcpy(tokenSignatureData, buf, encodedLength);
		*tokenSignatureDataLength = encodedLength;
	} else {
		encodedLength = bodyLength + 3;
		if (encodedLength > *tokenSignatureDataLength) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
		}
		tokenSignatureData[0] = buf[0];
		tokenSignatureData[1] = buf[1];
		tokenSignatureData[2] = (BYTE)bodyLength;
		memcpy(tokenSignatureData + 3, buf + 5, bodyLength);
		*tokenSignatureDataLength = encodedLength;
	}
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("get_registry_update_token_signature_data"), status);
	return status;
}

/**
 * If you are not the Card Issuer and do not know the token verification private key send this data to the
 * Card Issuer and obtain the signature of the data, i.e., the Delete Token.
 * The parameters must match the parameters of a later GP211_delete_application() method.
 * \param AIDs [in] A pointer to an array of OPGP_AID structures describing the applications and load files to delete.
 * \param AIDsLength [in] The number of OPGP_AID structures.
 * \param deleteTokenSignatureData [out] The data to sign in a Delete Token.
 * \param deleteTokenSignatureDataLength [in, out] The length of the deleteTokenSignatureData buffer.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_get_delete_token_signature_data(OPGP_AID *AIDs, DWORD AIDsLength,
										  PBYTE deleteTokenSignatureData,
										  PDWORD deleteTokenSignatureDataLength) {
	BYTE buf[255];
	DWORD i=0;
	DWORD j;
	DWORD bodyLength;
	DWORD encodedLength;
	OPGP_ERROR_STATUS status;

	OPGP_LOG_START(_T("get_delete_token_signature_data"));
	buf[i++] = 0x00; // P1
	buf[i++] = 0x80; // P2
	buf[i++] = 0x00; // Lc dummy

	for (j=0; j<AIDsLength; j++) {
		if (i + AIDs[j].AIDLength + 2 - 3 > sizeof(buf)) {
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_COMMAND_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_COMMAND_TOO_LARGE)); goto end; }
		}
		OPGP_LOG_HEX(_T("get_delete_token_signature_data: AID: "), AIDs[j].AID, AIDs[j].AIDLength);
		buf[i++] = 0x4F;
		buf[i++] = AIDs[j].AIDLength;
		memcpy(buf+i, AIDs[j].AID, AIDs[j].AIDLength);
		i+=AIDs[j].AIDLength;
	}

	bodyLength = i - 3;
	encodedLength = i;
	if (encodedLength > *deleteTokenSignatureDataLength) {
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
	}
	buf[2] = (BYTE)bodyLength;
	memcpy(deleteTokenSignatureData, buf, encodedLength);
	*deleteTokenSignatureDataLength = encodedLength;

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("get_delete_token_signature_data"), status);
	return status;
}

/**
 * The parameters must match the parameters of a later GP211_install_for_install(), GP211_install_for_make_selectable() and GP211_install_for_install_and_make_selectable() method.
 * \param P1 [in] The parameter P1 in the APDU command.
 * <ul>
 * <li> 0x04 for a INSTALL [for install] command </li>
 * <li> 0x08 for an INSTALL [for make selectable] command </li>
 * <li> 0x0C for an INSTALL [for install and make selectable] </li>
 * <li> 0x10 for an INSTALL [for extradiction] </li>
 * </ul>
 * \param executableLoadFileAID [in] A buffer with AID of the Executable Load File to INSTALL [for install].
 * \param executableLoadFileAIDLength [in] The length of the Executable Load File AID.
 * \param executableModuleAID [in] The AID of the application class in the package.
 * \param executableModuleAIDLength [in] The length of the executableModuleAID buffer.
 * \param applicationAID [in] The AID of the installed application.
 * \param applicationAIDLength [in] The length of the application instance AID.
 * \param applicationPrivileges [in] The application privileges. Can be an OR of multiple privileges. See GP211_APPLICATION_PRIVILEGE_SECURITY_DOMAIN.
 * \param volatileDataSpaceLimit [in] The minimum amount of RAM space that must be available.
 * \param nonVolatileDataSpaceLimit [in] The minimum amount of space for objects of the application, i.e. the data allocated in its lifetime.
 * \param installParameters [in] Applet install parameters for the install() method of the application.
 * \param installParametersLength [in] The length of the installParameters buffer.
 * \param sdParameters [in] Security Domain install parameters encoded for tag C9 (e.g., tag 'C1' with tags '81','82','83','84','86','87').
 * \param sdParametersLength [in] The length of the sdParameters buffer.
 * \param uiccSystemSpecParams [in] UICC System Specific Parameters according to ETSI TS 102 226, sect. 8.2.1.3.2.2.
 * \param uiccSystemSpecParamsLength [in] The length of the uiccSystemSpecParams buffer.
 * \param simSpecParams [in] SIM File Access and Toolkit Application Specific Parameters according to ETSI TS 102 226, sect. 8.2.1.3.2.1.
 * \param simSpecParamsLength [in] The length of the simSpecParams buffer.
 * \param installToken [out] The calculated Install Token. A 1024 bit RSA signature.
 * \param installTokenLength [in, out] The length of the installToken buffer.
 * \param PEMKeyFileName [in] A PEM file name with the private RSA key.
 * \param *passPhrase [in] The passphrase. Must be an ASCII string.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_calculate_install_token(BYTE P1, PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength,
							 PBYTE executableModuleAID,
							 DWORD executableModuleAIDLength, PBYTE applicationAID, DWORD applicationAIDLength,
							 DWORD applicationPrivileges, DWORD volatileDataSpaceLimit, DWORD nonVolatileDataSpaceLimit,
							 PBYTE installParameters, DWORD installParametersLength,
							 PBYTE sdParameters, DWORD sdParametersLength,
							 PBYTE uiccSystemSpecParams, DWORD uiccSystemSpecParamsLength,
							 PBYTE simSpecParams, DWORD simSpecParamsLength,
							 PBYTE installToken, PDWORD installTokenLength,
							 OPGP_STRING PEMKeyFileName, char *passPhrase) {
	return calculate_install_token(P1, executableLoadFileAID, executableLoadFileAIDLength,
							 executableModuleAID,
							 executableModuleAIDLength, applicationAID, applicationAIDLength,
							 applicationPrivileges, volatileDataSpaceLimit, nonVolatileDataSpaceLimit,
							 installParameters, installParametersLength,
							 sdParameters, sdParametersLength,
							 uiccSystemSpecParams, uiccSystemSpecParamsLength,
							 simSpecParams, simSpecParamsLength,
							 installToken, installTokenLength, PEMKeyFileName, passPhrase);
}

/**
 * The parameters must match the parameters of a later GP211_install_for_extradition() method.
 * \param securityDomainAID [in] A buffer containing the Security Domain AID.
 * \param securityDomainAIDLength [in] The length of the Security Domain AID.
 * \param applicationAID [in] The AID of the installed application.
 * \param applicationAIDLength [in] The length of the application instance AID.
 * \param extraditionToken [out] The calculated Extradition Token.
 * \param extraditionTokenLength [in, out] The length of the extraditionToken buffer.
 * \param PEMKeyFileName [in] A PEM file name with the private RSA key.
 * \param *passPhrase [in] The passphrase. Must be an ASCII string.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_calculate_extradition_token(PBYTE securityDomainAID,
										  DWORD securityDomainAIDLength,
										  PBYTE applicationAID, DWORD applicationAIDLength,
										  PBYTE extraditionToken, PDWORD extraditionTokenLength,
										  OPGP_STRING PEMKeyFileName, char *passPhrase) {
	OPGP_ERROR_STATUS status;
	BYTE signatureData[256];
	DWORD signatureDataLength = sizeof(signatureData);
	OPGP_LOG_START(_T("GP211_calculate_extradition_token"));
	status = GP211_get_extradition_token_signature_data(securityDomainAID, securityDomainAIDLength,
		applicationAID, applicationAIDLength, signatureData, &signatureDataLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	status = calculate_signature(signatureData, signatureDataLength, PEMKeyFileName,
									passPhrase, extraditionToken, extraditionTokenLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("GP211_calculate_extradition_token"), status);
	return status;
}

/**
 * The parameters must match the parameters of a later GP211_install_for_registry_update() method.
 * \param securityDomainAID [in] A buffer containing the target Security Domain AID.
 * \param securityDomainAIDLength [in] The length of the target Security Domain AID.
 * \param applicationAID [in] The AID of the application.
 * \param applicationAIDLength [in] The length of the application instance AID.
 * \param applicationPrivileges [in] The application privileges.
 * \param registryUpdateParameters [in] The Registry Update Parameters.
 * \param registryUpdateParametersLength [in] The length of the Registry Update Parameters.
 * \param registryUpdateToken [out] The calculated Registry Update Token.
 * \param registryUpdateTokenLength [in, out] The length of the registryUpdateToken buffer.
 * \param PEMKeyFileName [in] A PEM file name with the private RSA key.
 * \param *passPhrase [in] The passphrase. Must be an ASCII string.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_calculate_update_registry_token(PBYTE securityDomainAID,
										  DWORD securityDomainAIDLength,
										  PBYTE applicationAID, DWORD applicationAIDLength,
										  DWORD applicationPrivileges,
										  PBYTE registryUpdateParameters, DWORD registryUpdateParametersLength,
										  PBYTE registryUpdateToken, PDWORD registryUpdateTokenLength,
										  OPGP_STRING PEMKeyFileName, char *passPhrase) {
	OPGP_ERROR_STATUS status;
	BYTE signatureData[256];
	DWORD signatureDataLength = sizeof(signatureData);
	OPGP_LOG_START(_T("GP211_calculate_update_registry_token"));
	status = GP211_get_registry_update_token_signature_data(securityDomainAID, securityDomainAIDLength,
		applicationAID, applicationAIDLength, applicationPrivileges,
		registryUpdateParameters, registryUpdateParametersLength,
		signatureData, &signatureDataLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	status = calculate_signature(signatureData, signatureDataLength, PEMKeyFileName,
									passPhrase, registryUpdateToken, registryUpdateTokenLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("GP211_calculate_update_registry_token"), status);
	return status;
}

/**
 * The parameters must match the parameters of a later GP211_delete_application() method.
 * \param applicationOrExecutableLoadFileAID [in] A buffer containing the AID of the application or executable load file.
 * \param applicationOrExecutableLoadFileAIDLength [in] The length of the AID buffer.
 * \param deleteToken [out] The calculated Delete Token.
 * \param deleteTokenLength [in, out] The length of the deleteToken buffer.
 * \param PEMKeyFileName [in] A PEM file name with the private RSA key.
 * \param *passPhrase [in] The passphrase. Must be an ASCII string.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_calculate_delete_token(PBYTE applicationOrExecutableLoadFileAID,
										  DWORD applicationOrExecutableLoadFileAIDLength,
										  PBYTE deleteToken, PDWORD deleteTokenLength,
										  OPGP_STRING PEMKeyFileName, char *passPhrase) {
	OPGP_ERROR_STATUS status;
	BYTE signatureData[256];
	DWORD signatureDataLength = sizeof(signatureData);
	OPGP_AID aid;
	OPGP_LOG_START(_T("GP211_calculate_delete_token"));

	if (applicationOrExecutableLoadFileAIDLength > sizeof(aid.AID)) {
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_COMMAND_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_COMMAND_TOO_LARGE)); goto end; }
	}
	memset(&aid, 0, sizeof(aid));
	aid.AIDLength = (BYTE)applicationOrExecutableLoadFileAIDLength;
	memcpy(aid.AID, applicationOrExecutableLoadFileAID, applicationOrExecutableLoadFileAIDLength);

	status = GP211_get_delete_token_signature_data(&aid, 1, signatureData, &signatureDataLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	status = calculate_signature(signatureData, signatureDataLength, PEMKeyFileName,
									passPhrase, deleteToken, deleteTokenLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("GP211_calculate_delete_token"), status);
	return status;
}

/**
 * If you are not the Card Issuer and do not know the token verification private key send this data to the
 * Card Issuer and obtain the RSA signature of the data, i.e. the Load Token.
 * volatileDataSpaceLimit and nonVolatileDataSpaceLimit can be 0, if the card does not need or support this tags.
 * The parameters must match the parameters of a later GP211_install_for_load() command.
 * \param executableLoadFileAID [in] A buffer containing the Executable Load File AID.
 * \param executableLoadFileAIDLength [in] The length of the Executable Load File AID.
 * \param securityDomainAID [in] A buffer containing the Security Domain AID.
 * \param securityDomainAIDLength [in] The length of the Security Domain AID.
 * \param loadFileDataBlockHash [in] The Load File Data Block Hash. The same calculated as in GP211_install_for_load().
 * \param loadFileDataBlockHashLength [in] The length of the loadFileDataBlockHash buffer.
 * \param nonVolatileCodeSpaceLimit [in] The minimum space required to store the application code.
 * \param volatileDataSpaceLimit [in] The minimum amount of RAM space that must be available.
 * \param nonVolatileDataSpaceLimit [in] The minimum amount of space for objects of the application, i.e. the data allocated in its lifetime.
 * \param loadTokenSignatureData [out] The data to sign in a Load Token.
 * \param loadTokenSignatureDataLength [in, out] The length of the loadTokenSignatureData buffer.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_get_load_token_signature_data(PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength, PBYTE securityDomainAID,
									   DWORD securityDomainAIDLength, PBYTE loadFileDataBlockHash,
									   DWORD loadFileDataBlockHashLength,
									   DWORD nonVolatileCodeSpaceLimit, DWORD volatileDataSpaceLimit,
									   DWORD nonVolatileDataSpaceLimit, PBYTE loadTokenSignatureData,
									   PDWORD loadTokenSignatureDataLength) {
	OPGP_ERROR_STATUS status;
	OPGP_LOG_START(_T("GP211_get_load_token_signature_data"));
	if (loadFileDataBlockHash == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, GP211_ERROR_LOAD_FILE_DATA_BLOCK_HASH_NULL, OPGP_stringify_error(GP211_ERROR_LOAD_FILE_DATA_BLOCK_HASH_NULL));
		goto end;
	}
	status = get_load_data(executableLoadFileAID, executableLoadFileAIDLength,
		securityDomainAID, securityDomainAIDLength, loadFileDataBlockHash,
		loadFileDataBlockHashLength, 0, nonVolatileCodeSpaceLimit, volatileDataSpaceLimit,
		nonVolatileDataSpaceLimit, loadTokenSignatureData, loadTokenSignatureDataLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
#ifdef OPGP_DEBUG
	OPGP_LOG_HEX(_T("GP211_get_load_token_signature_data: Signature Data: "), loadTokenSignatureData, *loadTokenSignatureDataLength);
#endif
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("GP211_get_load_token_signature_data"), status);
	return status;
}


/**
 * The parameters must match the parameters of a later GP211_install_for_load() method.
 * \param executableLoadFileAID [in] A buffer containing the Executable Load File AID.
 * \param executableLoadFileAIDLength [in] The length of the Executable Load File AID.
 * \param securityDomainAID [in] A buffer containing the Security Domain AID.
 * \param securityDomainAIDLength [in] The length of the Security Domain AID.
 * \param loadFileDataBlockHash [in] The Load File DAP. The same calculated as in GP211_install_for_load().
 * \param loadFileDataBlockHashLength [in] The length of the loadFileDataBlockHash buffer.
 * \param nonVolatileCodeSpaceLimit [in] The minimum space required to store the package.
 * \param volatileDataSpaceLimit [in] The minimum amount of RAM space that must be available.
 * \param nonVolatileDataSpaceLimit [in] The minimum amount of space for objects of the application, i.e. the data allocated in its lifetime.
 * \param loadToken [out] The calculated Load Token. A 1024 bit RSA signature.
 * \param loadTokenLength [in, out] The length of the loadToken buffer.
 * \param PEMKeyFileName [in] A PEM file name with the private RSA key.
 * \param *passPhrase [in] The passphrase. Must be an ASCII string.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_calculate_load_token(PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength,
						  PBYTE securityDomainAID,
						  DWORD securityDomainAIDLength, PBYTE loadFileDataBlockHash,
						  DWORD loadFileDataBlockHashLength,
						  DWORD nonVolatileCodeSpaceLimit, DWORD volatileDataSpaceLimit,
						  DWORD nonVolatileDataSpaceLimit, PBYTE loadToken, PDWORD loadTokenLength,
						  OPGP_STRING PEMKeyFileName, char *passPhrase) {
	OPGP_ERROR_STATUS status;
	BYTE loadTokenSignatureData[512];
	DWORD loadTokenSignatureDataLength = 512;
	OPGP_LOG_START(_T("GP211_calculate_load_token"));
	status = GP211_get_load_token_signature_data(executableLoadFileAID, executableLoadFileAIDLength, securityDomainAID, securityDomainAIDLength,
		loadFileDataBlockHash, loadFileDataBlockHashLength, nonVolatileCodeSpaceLimit, volatileDataSpaceLimit,
		nonVolatileDataSpaceLimit, loadTokenSignatureData, &loadTokenSignatureDataLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	status = calculate_signature(loadTokenSignatureData, loadTokenSignatureDataLength, PEMKeyFileName,
									passPhrase, loadToken, loadTokenLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("GP211_calculate_load_token"), status);
	return status;
}

/**
 * This is a hash of the Load File Data Block with SHA-1 for SCP02 or SHA-256, SHA-384, SHA-512 or SM3 for SCP03.
 * \param executableLoadFileName [in] The name of the Executable Load File to hash.
 * \param hash [out] The hash value.
 * \param hashLength [in] The hash length for SCP03: 32 for AES-128, 48 for AES-192, 64 for AES-256, 32 for SM3.
 * \param hashType [in] The hash type. See GP211_HASH_SHA256.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_calculate_load_file_data_block_hash(OPGP_STRING executableLoadFileName,
                                                            PBYTE hash, DWORD hashLength, BYTE hashType) {
	OPGP_ERROR_STATUS status;
	PBYTE loadFileBuf = NULL;
	DWORD loadFileBufSize;
	OPGP_LOG_START(_T("GP211_calculate_load_file_data_block_hash"));
	if ((executableLoadFileName == NULL) || (_tcslen(executableLoadFileName) == 0))
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_FILENAME, OPGP_stringify_error(OPGP_ERROR_INVALID_FILENAME)); goto end; }
	status = handle_load_file((OPGP_CSTRING)executableLoadFileName, loadFileBuf, &loadFileBufSize);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	loadFileBuf = (PBYTE)malloc(sizeof(BYTE) * loadFileBufSize);
	if (loadFileBuf == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, ENOMEM, OPGP_stringify_error(ENOMEM));
		goto end;
	}
	status = handle_load_file((OPGP_CSTRING)executableLoadFileName, loadFileBuf, &loadFileBufSize);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	switch (hashType) {
		case GP211_HASH_SHA1:
			status = calculate_sha1_hash(loadFileBuf, loadFileBufSize, hash);
			break;
		case GP211_HASH_SHA256:
		case GP211_HASH_SHA384:
		case GP211_HASH_SHA512:
			status = calculate_sha2_hash(loadFileBuf, loadFileBufSize, hash, hashLength);
			break;
		case GP211_HASH_SM3:
			status = calculate_sm3_hash(loadFileBuf, loadFileBufSize, hash);
			break;
		default:
			OPGP_ERROR_CREATE_ERROR(status, GP211_ERROR_INVALID_SCP, OPGP_stringify_error(GP211_ERROR_INVALID_SCP));
			goto end;
	}
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	if (loadFileBuf != NULL) {
		free(loadFileBuf);
	}
	OPGP_LOG_END(_T("GP211_calculate_load_file_data_block_hash"), status);
	return status;
}

/**
 * This is used with SCP02 or SCP03. If a security domain has DAP verification privilege the security domain validates this DAP.
 * The loadFileDataBlockHash can be calculated using calculate_load_file_data_block_hash().
 * \param loadFileDataBlockHash [in] The Load File Data Block Hash. Must be a SHA-256, SHA-384 or SHA-512 hash.
 * \param hashLength [in] The length of the hash.
 * \param DAPCalculationKey [in] The key to calculate the DAP.
 * \param keyLength [in] The key length of the DAPCalculationKey.
 * \param signature [out] Output buffer for the raw DAP signature bytes.
 * \param signatureLength [in,out] Input: size of signature buffer. Output: generated signature length.
 * \param secureChannelProtocol [in] The Secure Channel Protocol.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_calculate_DAP(BYTE loadFileDataBlockHash[64], BYTE hashLength,
						BYTE DAPCalculationKey[32], DWORD keyLength,
						PBYTE signature, PDWORD signatureLength, BYTE secureChannelProtocol)
{
	OPGP_ERROR_STATUS status;
	DWORD requiredLength = (secureChannelProtocol == GP211_SCP02) ? 8 : 16;
	OPGP_LOG_START(_T("GP211_calculate_aes_DAP"));
	if (signature == NULL || signatureLength == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, _T("signature and signatureLength must be provided"));
		goto end;
	}
	if (*signatureLength < requiredLength) {
		*signatureLength = requiredLength;
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
		goto end;
	}
	if (secureChannelProtocol == GP211_SCP02) {
		calculate_MAC_des_3des(DAPCalculationKey, loadFileDataBlockHash, 20, NULL, signature);
		*signatureLength = 8;
	}
	else {
		calculate_CMAC_aes(DAPCalculationKey, keyLength, loadFileDataBlockHash, hashLength, NULL, signature);
		*signatureLength = 16;
	}

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("GP211_calculate_aes_DAP"), status);
	return status;
}

/**
 * If a security domain has DAP verification privilege the security domain validates this DAP.
 * The loadFileDataBlockHash can be calculated using calculate_load_file_data_block_hash().
 * \param loadFileDataBlockHash [in] The Load File Data Block Hash.
 * \param securityDomainAID [in] A buffer containing the Security Domain AID.
 * \param securityDomainAIDLength [in] The length of the Security Domain AID.
 * \param PEMKeyFileName [in] A PEM file name with the DAP Verification private RSA key.
 * \param *passPhrase [in] The passphrase. Must be an ASCII string.
 * \param *loadFileDataBlockSignature [out] A pointer to the returned GP211_DAP_BLOCK structure.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_calculate_rsa_DAP(BYTE loadFileDataBlockHash[20], PBYTE securityDomainAID,
					   DWORD securityDomainAIDLength,
					   OPGP_STRING PEMKeyFileName, char *passPhrase,
					   GP211_DAP_BLOCK *loadFileDataBlockSignature)
{
	OPGP_ERROR_STATUS status;
	DWORD signatureLength = sizeof(loadFileDataBlockSignature->signature);
	OPGP_LOG_START(_T("GP211_calculate_rsa_DAP"));

	status = calculate_signature(loadFileDataBlockHash, 20, PEMKeyFileName, passPhrase,
		loadFileDataBlockSignature->signature, &signatureLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	loadFileDataBlockSignature->signatureLength = signatureLength;
	memcpy(loadFileDataBlockSignature->securityDomainAID, securityDomainAID, securityDomainAIDLength);
	loadFileDataBlockSignature->securityDomainAIDLength = (BYTE)securityDomainAIDLength;

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("GP211_calculate_rsa_DAP"), status);
	return status;
}

/**
 * Calculates a Load File Data Block Signature according to the GlobalPlatform specification 2.1.1.
 * This function supports RSA keys larger than 1024 bits using RSA-PSS (Scheme 2).
 * Uses SHA-256 for RSA keys <= 2048 bits and SHA-512 for RSA keys > 2048 bits.
 * \param loadFileDataBlockHash [in] The Load File Data Block Hash to sign.
 * \param loadFileDataBlockHashLength [in] The length of the Load File Data Block Hash.
 * \param PEMKeyFileName [in] A PEM file name with the private RSA key.
 * \param *passPhrase [in] The passphrase. Must be an ASCII string.
 * \param signature [out] Output buffer for the raw DAP signature bytes.
 * \param signatureLength [in,out] Input: size of signature buffer. Output: generated signature length.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_calculate_rsa_schemeX_DAP(PBYTE loadFileDataBlockHash, DWORD loadFileDataBlockHashLength,
					   OPGP_STRING PEMKeyFileName, char *passPhrase,
					   PBYTE signature, PDWORD signatureLength)
{
	OPGP_ERROR_STATUS status;
	OPGP_LOG_START(_T("GP211_calculate_rsa_schemeX_DAP"));

	status = calculate_signature(loadFileDataBlockHash, loadFileDataBlockHashLength, PEMKeyFileName, passPhrase,
		signature, signatureLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("GP211_calculate_rsa_schemeX_DAP"), status);
	return status;
}

/**
 * Calculates an ECC Load File Data Block Signature according to GlobalPlatform Card Specification 2.3.1.
 * The returned signature is encoded in plain format according to BSI TR-03111 (r||s).
 * \param loadFileDataBlockHash [in] The Load File Data Block Hash to sign.
 * \param loadFileDataBlockHashLength [in] The length of the Load File Data Block Hash.
 * \param PEMKeyFileName [in] A PEM file name with the private ECC key.
 * \param *passPhrase [in] The passphrase. Must be an ASCII string.
 * \param signature [out] Output buffer for the raw DAP signature bytes.
 * \param signatureLength [in,out] Input: size of signature buffer. Output: generated signature length.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_calculate_ecc_DAP(PBYTE loadFileDataBlockHash, DWORD loadFileDataBlockHashLength,
					   OPGP_STRING PEMKeyFileName, char *passPhrase,
					   PBYTE signature, PDWORD signatureLength)
{
	OPGP_ERROR_STATUS status;
	OPGP_LOG_START(_T("GP211_calculate_ecc_DAP"));

	status = calculate_signature(loadFileDataBlockHash, loadFileDataBlockHashLength, PEMKeyFileName, passPhrase,
		signature, signatureLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("GP211_calculate_ecc_DAP"), status);
	return status;
}


/**
 * Validates a GlobalPlatform 2.3.1 Load Receipt with symmetric (DES/AES) or asymmetric (RSA/ECC) keys.
 */
OPGP_ERROR_STATUS GP211_validate_load_receipt(PBYTE receiptKey, DWORD keyLength, GP211_RECEIPT_DATA receiptData,
						   PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength,
						   PBYTE securityDomainAID, DWORD securityDomainAIDLength,
						   BYTE receiptKeyType, OPGP_STRING PEMKeyFileName, char *passPhrase) {
	return validate_load_receipt(receiptKey, keyLength, receiptData,
		executableLoadFileAID, executableLoadFileAIDLength,
		securityDomainAID, securityDomainAIDLength,
		receiptKeyType, PEMKeyFileName, passPhrase);
}

/**
 * Validates a GlobalPlatform 2.3.1 Install Receipt with symmetric (DES/AES) or asymmetric (RSA/ECC) keys.
 */
OPGP_ERROR_STATUS GP211_validate_install_receipt(PBYTE receiptKey, DWORD keyLength, GP211_RECEIPT_DATA receiptData,
							  PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength,
							  PBYTE applicationAID, DWORD applicationAIDLength,
							  BYTE receiptKeyType, OPGP_STRING PEMKeyFileName, char *passPhrase) {
	return validate_install_receipt(receiptKey, keyLength, receiptData,
		executableLoadFileAID, executableLoadFileAIDLength,
		applicationAID, applicationAIDLength,
		receiptKeyType, PEMKeyFileName, passPhrase);
}

/**
 * Validates a GlobalPlatform 2.3.1 Delete Receipt with symmetric (DES/AES) or asymmetric (RSA/ECC) keys.
 */
OPGP_ERROR_STATUS GP211_validate_delete_receipt(PBYTE receiptKey, DWORD keyLength, GP211_RECEIPT_DATA receiptData,
							 PBYTE AID, DWORD AIDLength,
							 BYTE receiptKeyType, OPGP_STRING PEMKeyFileName, char *passPhrase) {
	return validate_delete_receipt(receiptKey, keyLength, receiptData,
		AID, AIDLength, receiptKeyType, PEMKeyFileName, passPhrase);
}

/**
 * Validates a GlobalPlatform 2.3.1 Extradition Receipt with symmetric (DES/AES) or asymmetric (RSA/ECC) keys.
 */
OPGP_ERROR_STATUS GP211_validate_extradition_receipt(PBYTE receiptKey, DWORD keyLength, GP211_RECEIPT_DATA receiptData,
							  PBYTE oldSecurityDomainAID, DWORD oldSecurityDomainAIDLength,
							  PBYTE newSecurityDomainAID, DWORD newSecurityDomainAIDLength,
							  PBYTE applicationOrExecutableLoadFileAID,
							  DWORD applicationOrExecutableLoadFileAIDLength,
							  BYTE receiptKeyType, OPGP_STRING PEMKeyFileName, char *passPhrase) {
	return validate_extradition_receipt(receiptKey, keyLength, receiptData,
		oldSecurityDomainAID, oldSecurityDomainAIDLength,
		newSecurityDomainAID, newSecurityDomainAIDLength,
		applicationOrExecutableLoadFileAID, applicationOrExecutableLoadFileAIDLength,
		receiptKeyType, PEMKeyFileName, passPhrase);
}

/**
 * Validates a GlobalPlatform 2.3.1 Registry Update Receipt with symmetric (DES/AES) or asymmetric (RSA/ECC) keys.
 */
OPGP_ERROR_STATUS GP211_validate_registry_update_receipt(PBYTE receiptKey, DWORD keyLength, GP211_RECEIPT_DATA receiptData,
								  PBYTE oldSecurityDomainAID, DWORD oldSecurityDomainAIDLength,
								  PBYTE applicationAID, DWORD applicationAIDLength,
								  PBYTE newSecurityDomainAID, DWORD newSecurityDomainAIDLength,
								  DWORD applicationPrivileges,
								  PBYTE registryUpdateParameters, DWORD registryUpdateParametersLength,
								  BYTE receiptKeyType, OPGP_STRING PEMKeyFileName, char *passPhrase) {
	return validate_registry_update_receipt(receiptKey, keyLength, receiptData,
		oldSecurityDomainAID, oldSecurityDomainAIDLength,
		applicationAID, applicationAIDLength,
		newSecurityDomainAID, newSecurityDomainAIDLength,
		applicationPrivileges, registryUpdateParameters, registryUpdateParametersLength,
		receiptKeyType, PEMKeyFileName, passPhrase);
}

/**
  * E.g. GemXpresso cards, JCOP-10 cards or Palmera Protect V5 cards use this scheme.
  * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
  * \param cardInfo [in] The OPGP_CARD_INFO cardInfo, structure returned by OPGP_card_connect().
  * \param *secInfo [in, out] The pointer to the OP201_SECURITY_INFO structure returned by OP201_mutual_authentication().
  * \param AID [in] The AID of the Card Manager.
  * \param AIDLength [in] The length of the Card Manager AID / Issuer Security Domain AID.
  * \param masterKey [in] The master key.
  * \param S_ENC [out] The static Encryption key.
  * \param S_MAC [out] The static Message Authentication Code key.
  * \param DEK [out] The static Key Encryption Key.
  * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
  */
OPGP_ERROR_STATUS OP201_VISA2_derive_keys(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, OP201_SECURITY_INFO *secInfo, PBYTE AID, DWORD AIDLength, PBYTE masterKey,
							PBYTE S_ENC, PBYTE S_MAC, PBYTE DEK) {
    OPGP_ERROR_STATUS status;

    if (secInfo == NULL) {
        status = VISA2_derive_keys_get_data(cardContext, cardInfo, NULL, AID, AIDLength, masterKey, S_ENC, S_MAC, DEK);
    } else {
        GP211_SECURITY_INFO gp211secInfo;
        mapOP201ToGP211SecurityInfo(*secInfo, &gp211secInfo);
        status = VISA2_derive_keys_get_data(cardContext, cardInfo, &gp211secInfo, AID, AIDLength, masterKey, S_ENC, S_MAC, DEK);
        mapGP211ToOP201SecurityInfo(gp211secInfo, secInfo);
    }

    return status;
}

/**
  * E.g. GemXpresso cards, JCOP-10 cards or Palmera Protect V5 cards use this scheme.
  * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
  * \param cardInfo [in] The OPGP_CARD_INFO cardInfo, structure returned by OPGP_card_connect().
  * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
  * \param AID [in] The AID of the Card Manager.
  * \param AIDLength [in] The length of the Card Manager AID / Issuer Security Domain AID.
  * \param masterKey [in] The master key.
  * \param S_ENC [out] The static Encryption key.
  * \param S_MAC [out] The static Message Authentication Code key.
  * \param DEK [out] The static Key Encryption Key.
  * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
  */
OPGP_ERROR_STATUS GP211_VISA2_derive_keys(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo, PBYTE AID, DWORD AIDLength, PBYTE masterKey,
							PBYTE S_ENC, PBYTE S_MAC, PBYTE DEK) {
    return VISA2_derive_keys_get_data(cardContext, cardInfo, secInfo, AID, AIDLength, masterKey, S_ENC, S_MAC, DEK);
}

OPGP_ERROR_STATUS VISA2_derive_keys_get_data(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo, PBYTE AID, DWORD AIDLength, PBYTE masterKey,
							PBYTE S_ENC, PBYTE S_MAC, PBYTE DEK) {
	OPGP_ERROR_STATUS status;
	BYTE cardCPLCData[50];
	DWORD cplcDataLen = 50;
	BYTE keyDiversificationData[16];
	BYTE cardmanagerAID[16];
	DWORD cardmanagerAIDLength = 16;

	OPGP_LOG_START(_T("VISA2_derive_keys_get_data"));

	OPGP_LOG_HEX(_T("VISA2_derive_keys_get_data: Card Manager AID: "), AID, AIDLength);

	status = GP211_get_data(cardContext, cardInfo, secInfo, (PBYTE)OP201_GET_DATA_CPLC_WHOLE_CPLC,
		cardCPLCData, &cplcDataLen);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	memcpy(cardmanagerAID, AID, AIDLength);
	cardmanagerAIDLength = AIDLength;

	keyDiversificationData[0] = cardmanagerAID[cardmanagerAIDLength-2];
	keyDiversificationData[1] = cardmanagerAID[cardmanagerAIDLength-1];
	// we are using 13 here because VISA2_derive_keys skips the first 2 bytes after the card manager AID, ic serial starts at offset 15
 	memcpy(keyDiversificationData+2, cardCPLCData+13, 8);

	status = VISA2_derive_keys(keyDiversificationData, masterKey, S_ENC, S_MAC, DEK);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("VISA2_derive_keys_get_data"), status);
	return status;
}

/**
  * E.g. GemXpresso cards, JCOP-10 cards or Palmera Protect V5 cards use this scheme.
  * The baseKeyDiversificationData must contain the rightmost two bytes of the Card Manager AID as first 2 bytes and starting at position 4 the 4 bytes of the IC serial number.
  * \param baseKeyDiversificationData [in] The key diversification data. This is returned by INITIALIZE UPDATE or can be constructed.
  * \param masterKey [in] The master key.
  * \param S_ENC [out] The static Encryption key.
  * \param S_MAC [out] The static Message Authentication Code key.
  * \param DEK [out] The static Key Encryption Key.
  * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
  */
OPGP_ERROR_STATUS VISA2_derive_keys(BYTE baseKeyDiversificationData[10], PBYTE masterKey,
							PBYTE S_ENC, PBYTE S_MAC, PBYTE DEK) {
	OPGP_ERROR_STATUS status;
	DWORD outl;
	BYTE keyDiversificationData[16];

	OPGP_LOG_START(_T("VISA2_derive_keys"));

	OPGP_LOG_HEX(_T("VISA2_derive_keys: Base Key Diversification Data: "), baseKeyDiversificationData, 10);

	/* Key Diversification data VISA 2
	KDCAUTH/ENC xxh xxh || IC serial number || F0h 01h ||xxh xxh || IC serial number
	||0Fh 01h
	KDCMAC xxh xxh || IC serial number || F0h 02h ||xxh xxh || IC serial number
	|| 0Fh 02h
	KDCKEK xxh xxh || IC serial number || F0h 03h || xxh xxh || IC serial number
	|| 0Fh 03h

	xxh xxh is the last (rightmost) two bytes of the Card Manager AID.
	IC Serial Number is taken from the CPLC data.
	*/

 	// left
 	memcpy(keyDiversificationData, baseKeyDiversificationData, 2);
 	memcpy(keyDiversificationData+2, baseKeyDiversificationData+4, 4);
 	keyDiversificationData[6] = 0xF0;
 	keyDiversificationData[7] = 0x01;
 	// right
 	memcpy(keyDiversificationData+8, baseKeyDiversificationData, 2);
 	memcpy(keyDiversificationData+10, baseKeyDiversificationData+4, 4);
 	keyDiversificationData[14] = 0x0F;
 	keyDiversificationData[15] = 0x01;

	OPGP_LOG_HEX(_T("VISA2_derive_keys: Key Diversification Data for ENC: "), keyDiversificationData, 16);

	status = calculate_enc_ecb_two_key_triple_des(masterKey, keyDiversificationData, 16, S_ENC, &outl);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	// left for MAC
	keyDiversificationData[6] = 0xF0;
	keyDiversificationData[7] = 0x02;
	// right for MAC
	keyDiversificationData[14] = 0x0F;
	keyDiversificationData[15] = 0x02;

	OPGP_LOG_HEX(_T("VISA2_derive_keys: Key Diversification Data: for MAC "), keyDiversificationData, 16);

	status = calculate_enc_ecb_two_key_triple_des(masterKey, keyDiversificationData, 16, S_MAC, &outl);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	// DEK

	// left for DEK
	keyDiversificationData[6] = 0xF0;
	keyDiversificationData[7] = 0x03;
	// right for DEK
	keyDiversificationData[14] = 0x0F;
	keyDiversificationData[15] = 0x03;

	OPGP_LOG_HEX(_T("VISA2_derive_keys: Key Diversification Data for DEK: "), keyDiversificationData, 16);

	status = calculate_enc_ecb_two_key_triple_des(masterKey, keyDiversificationData, 16, DEK, &outl);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("VISA2_derive_keys"), status);
	return status;
}

/**
  * E.g. GemXpresso cards use this scheme.
  * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
  * \param cardInfo [in] The OPGP_CARD_INFO cardInfo, structure returned by OPGP_card_connect().
  * \param *secInfo [in, out] The pointer to the OP201_SECURITY_INFO structure returned by OP201_mutual_authentication().
  * \param masterKey [in] The master key.
  * \param S_ENC [out] The static Encryption key.
  * \param S_MAC [out] The static Message Authentication Code key.
  * \param DEK [out] The static Key Encryption Key.
  * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
  */
OPGP_ERROR_STATUS OP201_VISA1_derive_keys(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, OP201_SECURITY_INFO *secInfo, PBYTE masterKey,
							PBYTE S_ENC, PBYTE S_MAC, PBYTE DEK) {
    OPGP_ERROR_STATUS status;

    if (secInfo == NULL) {
        status = VISA1_derive_keys_get_data(cardContext, cardInfo, NULL, masterKey, S_ENC, S_MAC, DEK);
    } else {
        GP211_SECURITY_INFO gp211secInfo;
        mapOP201ToGP211SecurityInfo(*secInfo, &gp211secInfo);
        status = VISA1_derive_keys_get_data(cardContext, cardInfo, &gp211secInfo, masterKey, S_ENC, S_MAC, DEK);
        mapGP211ToOP201SecurityInfo(gp211secInfo, secInfo);
    }

    return status;
}

/**
  * E.g. GemXpresso cards use this scheme.
  * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
  * \param cardInfo [in] The OPGP_CARD_INFO cardInfo, structure returned by OPGP_card_connect().
  * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
  * \param masterKey [in] The master key.
  * \param S_ENC [out] The static Encryption key.
  * \param S_MAC [out] The static Message Authentication Code key.
  * \param DEK [out] The static Key Encryption Key.
  * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
  */
OPGP_ERROR_STATUS GP211_VISA1_derive_keys(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo, PBYTE masterKey,
							PBYTE S_ENC, PBYTE S_MAC, PBYTE DEK) {
    return VISA1_derive_keys_get_data(cardContext, cardInfo, secInfo, masterKey, S_ENC, S_MAC, DEK);
}

OPGP_ERROR_STATUS VISA1_derive_keys_get_data(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo, PBYTE masterKey,
							PBYTE S_ENC, PBYTE S_MAC, PBYTE DEK) {
	OPGP_ERROR_STATUS status;
	BYTE cardCPLCData[50];
	DWORD cplcDataLen = 50;
	BYTE serialNumber[8];

	OPGP_LOG_START(_T("VISA1_derive_keys_get_data"));

	status = GP211_get_data(cardContext, cardInfo, secInfo, (PBYTE)OP201_GET_DATA_CPLC_WHOLE_CPLC,
		cardCPLCData, &cplcDataLen);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	// ic serial starts at offset 15
 	memcpy(serialNumber, cardCPLCData+15, 8);

	status = VISA1_derive_keys(serialNumber, masterKey, S_ENC, S_MAC, DEK);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("VISA1_derive_keys_get_data"), status);
	return status;
}

/**
  * E.g. GemXpresso cards use this scheme.
  * \param cardSerialNumber [in] The card serial number.
  * \param masterKey [in] The master key.
  * \param S_ENC [out] The static Encryption key.
  * \param S_MAC [out] The static Message Authentication Code key.
  * \param DEK [out] The static Key Encryption Key.
  * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
  */
OPGP_ERROR_STATUS VISA1_derive_keys(BYTE cardSerialNumber[8], PBYTE masterKey,
							PBYTE S_ENC, PBYTE S_MAC, PBYTE DEK) {
	OPGP_ERROR_STATUS status;
	DWORD outl;
	BYTE keyDiversificationData[16];

	OPGP_LOG_START(_T("VISA1_derive_keys"));

	OPGP_LOG_HEX(_T("VISA1_derive_keys: Base Key Diversification Data: "), cardSerialNumber, 10);

	/*
	Key Diversification data VISA 1
	KDCAUTH/ENC FFh FFh || card serial number || 01h 00h 00h 00h 00h 00h
	KDCMAC 00h 00h || card serial number || 02h 00h 00h 00h 00h 00h
	KDCKEK F0h F0h || card serial number || 03h 00h 00h 00h 00h 00h
	*/

	// ENC
	keyDiversificationData[0] = 0xFF;
	keyDiversificationData[1] = 0xFF;
 	memcpy(keyDiversificationData, cardSerialNumber, 8);
 	keyDiversificationData[10] = 0x01;
	memset(keyDiversificationData+11, 0x00, 5);

	OPGP_LOG_HEX(_T("VISA1_derive_keys: Key Diversification Data for ENC: "), keyDiversificationData, 16);

	status = calculate_enc_ecb_two_key_triple_des(masterKey, keyDiversificationData, 16, S_ENC, &outl);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	// MAC
	keyDiversificationData[0] = 0x00;
	keyDiversificationData[1] = 0x00;
 	keyDiversificationData[10] = 0x02;

	OPGP_LOG_HEX(_T("VISA1_derive_keys: Key Diversification Data: for MAC "), keyDiversificationData, 16);

	status = calculate_enc_ecb_two_key_triple_des(masterKey, keyDiversificationData, 16, S_MAC, &outl);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	// DEK
	keyDiversificationData[0] = 0xF0;
	keyDiversificationData[1] = 0xF0;
 	keyDiversificationData[10] = 0x03;

	OPGP_LOG_HEX(_T("VISA1_derive_keys: Key Diversification Data for DEK: "), keyDiversificationData, 16);

	status = calculate_enc_ecb_two_key_triple_des(masterKey, keyDiversificationData, 16, DEK, &outl);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("VISA1_derive_keys"), status);
	return status;
}

/**
  * E.g. Sm\@rtCafe Expert 3.0 and later cards use this scheme.
  * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
  * \param cardInfo [in] The OPGP_CARD_INFO cardInfo, structure returned by OPGP_card_connect().
  * \param *secInfo [in, out] The pointer to the OP201_SECURITY_INFO structure returned by OP201_mutual_authentication().
  * \param masterKey [in] The master key.
  * \param S_ENC [out] The static Encryption key.
  * \param S_MAC [out] The static Message Authentication Code key.
  * \param DEK [out] The static Key Encryption Key.
  * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
  */
OPGP_ERROR_STATUS OP201_EMV_CPS11_derive_keys(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, OP201_SECURITY_INFO *secInfo, PBYTE masterKey,
							PBYTE S_ENC, PBYTE S_MAC, PBYTE DEK) {
    OPGP_ERROR_STATUS status;

    if (secInfo == NULL) {
        status = EMV_CPS11_derive_keys_get_data(cardContext, cardInfo, NULL, masterKey, S_ENC, S_MAC, DEK);
    } else {
        GP211_SECURITY_INFO gp211secInfo;
        mapOP201ToGP211SecurityInfo(*secInfo, &gp211secInfo);
        status = EMV_CPS11_derive_keys_get_data(cardContext, cardInfo, &gp211secInfo, masterKey, S_ENC, S_MAC, DEK);
        mapGP211ToOP201SecurityInfo(gp211secInfo, secInfo);
    }

    return status;
}

/**
  * E.g. Sm\@rtCafe Expert 3.0 cards use this scheme.
  * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
  * \param cardInfo [in] The OPGP_CARD_INFO cardInfo, structure returned by OPGP_card_connect().
  * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
  * \param masterKey [in] The master key.
  * \param S_ENC [out] The static Encryption key.
  * \param S_MAC [out] The static Message Authentication Code key.
  * \param DEK [out] The static Key Encryption Key.
  * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
  */
OPGP_ERROR_STATUS GP211_EMV_CPS11_derive_keys(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo, PBYTE masterKey,
							PBYTE S_ENC, PBYTE S_MAC, PBYTE DEK) {
    return EMV_CPS11_derive_keys_get_data(cardContext, cardInfo, secInfo, masterKey, S_ENC, S_MAC, DEK);
}

OPGP_ERROR_STATUS EMV_CPS11_derive_keys_get_data(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo, PBYTE masterKey,
							PBYTE S_ENC, PBYTE S_MAC, PBYTE DEK) {
	OPGP_ERROR_STATUS status;
	BYTE diversificationData[50] = {0};
	DWORD diversificationDataLen = 50;

	OPGP_LOG_START(_T("EMV_CPS11_derive_keys_get_data"));

	status = GP211_get_data(cardContext, cardInfo, secInfo, (PBYTE)GP211_GET_DATA_DIVERSIFICATION_DATA,
		diversificationData, &diversificationDataLen);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	// skip tag identifier 0xCF and length (10)
	status = EMV_CPS11_derive_keys(diversificationData + 2, masterKey, S_ENC, S_MAC, DEK);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("EMV_CPS11_derive_keys_get_data"), status);
	return status;
}

/**
 * Derives static Secure Channel keys using the EMV CPS 1.1 derivation scheme.
 * The baseKeyDiversificationData buffer must contain the 10 byte KEYDATA value
 * used by the card for INITIALIZE UPDATE.
 * \param baseKeyDiversificationData [in] The 10 byte EMV CPS 1.1 key diversification data.
 * \param masterKey [in] The master key.
 * \param S_ENC [out] The derived static Encryption key.
 * \param S_MAC [out] The derived static Message Authentication Code key.
 * \param DEK [out] The derived static Data Encryption Key.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS EMV_CPS11_derive_keys(BYTE baseKeyDiversificationData[10], PBYTE masterKey,
							PBYTE S_ENC, PBYTE S_MAC, PBYTE DEK) {
	OPGP_ERROR_STATUS status;
	DWORD outl;
	BYTE keyDiversificationData[16];

	OPGP_LOG_START(_T("EMV_CPS11_derive_keys"));

	/*
	The 6 byte KMCID (e.g. IIN right justified and left padded with 1111b per quartet)
	concatenated with the 4 byte CSN (least significant bytes) form the key
	diversification data that must be placed in tag ‘CF’. This same data must be used to
	form the response to the INITIALIZE UPDATE command.
	*/

	/* KEYDATA Key derivation data:
        - KMCID (6 bytes)
        - CSN (4 bytes)

        If the CSN does not ensure the uniqueness of KEYDATA across different batches of cards other unique data (e.g. 2
		right most bytes of IC serial number and 2 bytes of IC batch identifier) should be used instead.
    */

	/* KENC := DES3(KMC)[Six least
	significant bytes of the KEYDATA || ’F0’ || ‘01’ ]|| DES3(KMC)[ Six least
	significant bytes of the KEYDATA || ‘0F’ || ‘01’].
	*/


	/* KMAC := DES3(KMC)[ Six
	least significant bytes of the KEYDATA || ’F0’ || ‘02’ ]|| DES3(KMC)[ Six
	least significant bytes of the KEYDATA || ‘0F’ || ‘02’].
	*/

	/* KDEK := DES3(KMC)[ Six
	least significant bytes of the KEYDATA || ’F0’ || ‘03’ ]|| DES3(KMC)[ Six
	least significant bytes of the KEYDATA || ‘0F’ || ‘03’].
	*/

	// left
    memcpy(keyDiversificationData, baseKeyDiversificationData + 4, 6);
    keyDiversificationData[6] = 0xF0;
    keyDiversificationData[7] = 0x01;
    // right
    memcpy(keyDiversificationData+8, baseKeyDiversificationData + 4, 6);
    keyDiversificationData[14] = 0x0F;
    keyDiversificationData[15] = 0x01;

	OPGP_LOG_HEX(_T("EMV_CPS11_derive_keys: Key Diversification Data: "), keyDiversificationData, 16);

	status = calculate_enc_ecb_two_key_triple_des(masterKey, keyDiversificationData, 16, S_ENC, &outl);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	// left for MAC
	keyDiversificationData[6] = 0xF0;
	keyDiversificationData[7] = 0x02;
	// right for MAC
	keyDiversificationData[14] = 0x0F;
	keyDiversificationData[15] = 0x02;

	OPGP_LOG_HEX(_T("EMV_CPS11_derive_keys: Key Diversification Data: "), keyDiversificationData, 16);

	status = calculate_enc_ecb_two_key_triple_des(masterKey, keyDiversificationData, 16, S_MAC, &outl);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	// DEK

	// left for DEK
	keyDiversificationData[6] = 0xF0;
	keyDiversificationData[7] = 0x03;
	// right for DEK
	keyDiversificationData[14] = 0x0F;
	keyDiversificationData[15] = 0x03;

	OPGP_LOG_HEX(_T("EMV_CPS11_derive_keys: Key Diversification Data: "), keyDiversificationData, 16);

	status = calculate_enc_ecb_two_key_triple_des(masterKey, keyDiversificationData, 16, DEK, &outl);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("EMV_CPS11_derive_keys"), status);
	return status;
}

static OPGP_ERROR_STATUS normalize_scp11_sd_public_key_override(PBYTE sdPublicKeyOverride,
		DWORD sdPublicKeyOverrideLength, PBYTE sdStaticPublicKey, PDWORD sdStaticPublicKeyLength) {
	OPGP_ERROR_STATUS status;

	if (sdPublicKeyOverride == NULL || sdPublicKeyOverrideLength == 0
			|| sdStaticPublicKey == NULL || sdStaticPublicKeyLength == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_WRONG_KEY_TYPE, OPGP_stringify_error(OPGP_ERROR_WRONG_KEY_TYPE));
		return status;
	}
	if (sdPublicKeyOverrideLength == 65 && sdPublicKeyOverride[0] == 0x04) {
		if (*sdStaticPublicKeyLength < sdPublicKeyOverrideLength) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
			return status;
		}
		memcpy(sdStaticPublicKey, sdPublicKeyOverride, sdPublicKeyOverrideLength);
		*sdStaticPublicKeyLength = sdPublicKeyOverrideLength;
		OPGP_ERROR_CREATE_NO_ERROR(status);
		return status;
	}
	if (sdPublicKeyOverrideLength == 67
			&& sdPublicKeyOverride[0] == 0xB0
			&& sdPublicKeyOverride[1] == 0x41
			&& sdPublicKeyOverride[2] == 0x04) {
		if (*sdStaticPublicKeyLength < 65) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
			return status;
		}
		memcpy(sdStaticPublicKey, sdPublicKeyOverride + 2, 65);
		*sdStaticPublicKeyLength = 65;
		OPGP_ERROR_CREATE_NO_ERROR(status);
		return status;
	}

	OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_WRONG_KEY_TYPE, OPGP_stringify_error(OPGP_ERROR_WRONG_KEY_TYPE));
	return status;
}

/**
 * Performs the SCP11a mutual authentication flow.
 *
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context().
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param staticOcePrivateKey [in] The raw static OCE ECKA private key (SK.OCE.ECKA).
 * \param keyLength [in] Requested AES session key length. 16, 24 or 32 bytes.
 * \param keySetVersion [in] The key set version on the card to use for mutual authentication.
 * \param keyIndex [in] The key index in the key set version on the card to use for mutual authentication.
 * \param scpParameters [in] SCP11 parameter byte. The SCP11a variant bits are applied by this function.
 * \param securityLevel [in] The requested security level.
 * \param certOceEcka [in] Optional certificate chain data to submit before mutual authentication. The data must contain one or more 7F21 or X.509 DER certificates, optionally wrapped in BF21, and must end with CERT.OCE.ECKA.
 * \param certOceEckaLength [in] Length of \p certOceEcka.
 * \param sdPublicKeyOverride [in] Optional raw uncompressed or B0-wrapped PK.SD.ECKA public key.
 * \param sdPublicKeyOverrideLength [in] Length of \p sdPublicKeyOverride.
 * \param sdPublicKeyFileName [in] Optional PEM file containing PK.SD.ECKA. If set, this takes precedence over \p sdPublicKeyOverride.
 * \param secInfo [out] The returned GP211_SECURITY_INFO structure.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
static OPGP_ERROR_STATUS mutual_authentication_scp11a(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo,
					   PBYTE staticOcePrivateKey,
					   DWORD keyLength,
					   BYTE keySetVersion,
					   BYTE keyIndex,
					   BYTE scpParameters,
					   BYTE securityLevel,
					   PBYTE certOceEcka,
					   DWORD certOceEckaLength,
					   PBYTE sdPublicKeyOverride,
					   DWORD sdPublicKeyOverrideLength,
					   OPGP_STRING sdPublicKeyFileName,
					   GP211_SECURITY_INFO *secInfo) {
	OPGP_ERROR_STATUS status;
	BYTE sdCertificateStore[2048];
	DWORD sdCertificateStoreLength = sizeof(sdCertificateStore);
	BYTE sdStaticPublicKey[256];
	DWORD sdStaticPublicKeyLength = sizeof(sdStaticPublicKey);
	BYTE keyParameterReference = GP211_KEY_TYPE_ECC_KEY_PARAMETER_REFERENCE_P256;
	BYTE oceEphemeralPrivateKey[96];
	DWORD oceEphemeralPrivateKeyLength = sizeof(oceEphemeralPrivateKey);
	BYTE oceEphemeralPublicKey[256];
	DWORD oceEphemeralPublicKeyLength = sizeof(oceEphemeralPublicKey);
	BYTE sdEphemeralPublicKey[256];
	DWORD sdEphemeralPublicKeyLength = sizeof(sdEphemeralPublicKey);
	BYTE receipt[32];
	DWORD receiptLength = sizeof(receipt);
	BYTE commandData[512];
	DWORD commandDataLength = 0;
	BYTE crtValue[480];
	DWORD crtValueLength = 0;
	BYTE response[256];
	DWORD responseLength = sizeof(response);
	BYTE sendBuffer[256];
	DWORD sendBufferLength;
	DWORD i = 0;
	BYTE keyUsageQualifier;
	BYTE sharedInfo[64];
	DWORD sharedInfoLength = 0;
	BYTE receiptInput[1024];
	DWORD receiptInputLength = 0;
	DWORD expectedStaticPrivateKeyLength;
	BYTE keyType = GP211_KEY_TYPE_AES;
	BYTE scpIdentifierAndParameters[2];
	BYTE caKlocKeyVersionNumber = keySetVersion;
	BYTE caKlocKeyIdentifier = GP211_SCP11_DEFAULT_CA_KLOC_KEY_IDENTIFIER;

	OPGP_LOG_START(_T("mutual_authentication_scp11a"));

	if (secInfo == NULL || staticOcePrivateKey == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		goto end;
	}
	if (keyLength != 16 && keyLength != 24 && keyLength != 32) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_WRONG_KEY_TYPE, OPGP_stringify_error(OPGP_ERROR_WRONG_KEY_TYPE));
		goto end;
	}

	/* Force SCP11a coding in tag '90'. Host/Card ID inclusion is not used in this helper. */
	scpParameters = (BYTE)((scpParameters & 0xF8) | 0x01);
	keyUsageQualifier = map_scp11a_key_usage_qualifier(securityLevel);

	if (sdPublicKeyFileName != NULL && _tcslen(sdPublicKeyFileName) > 0) {
		BYTE eccKeyComponentType;

		sdStaticPublicKeyLength = sizeof(sdStaticPublicKey);
		status = read_public_ecc_key(sdPublicKeyFileName, NULL, sdStaticPublicKey, &sdStaticPublicKeyLength,
				&eccKeyComponentType, &keyParameterReference, NULL);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
		if (eccKeyComponentType != GP211_KEY_TYPE_ECC_PUBLIC_OR_PRIVATE
				|| sdStaticPublicKeyLength != 65 || sdStaticPublicKey[0] != 0x04) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_WRONG_KEY_TYPE, OPGP_stringify_error(OPGP_ERROR_WRONG_KEY_TYPE));
			goto end;
		}
	} else if (sdPublicKeyOverride != NULL && sdPublicKeyOverrideLength > 0) {
		sdStaticPublicKeyLength = sizeof(sdStaticPublicKey);
		status = normalize_scp11_sd_public_key_override(sdPublicKeyOverride, sdPublicKeyOverrideLength,
				sdStaticPublicKey, &sdStaticPublicKeyLength);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
		keyParameterReference = GP211_KEY_TYPE_ECC_KEY_PARAMETER_REFERENCE_P256;
	} else {
		status = GP211_get_ecka_certificate(cardContext, cardInfo, NULL, keySetVersion, keyIndex, sdCertificateStore, &sdCertificateStoreLength);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}

		sdStaticPublicKeyLength = sizeof(sdStaticPublicKey);
		status = extract_scp11_sd_public_key_from_certificate_store(sdCertificateStore, sdCertificateStoreLength,
				sdStaticPublicKey, &sdStaticPublicKeyLength, &keyParameterReference);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
	}

	expectedStaticPrivateKeyLength = scp11_private_key_length_from_key_parameter_reference(keyParameterReference);
	if (expectedStaticPrivateKeyLength == 0) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_WRONG_KEY_TYPE, OPGP_stringify_error(OPGP_ERROR_WRONG_KEY_TYPE));
		goto end;
	}

	if (certOceEcka != NULL && certOceEckaLength > 0) {
		BYTE caKlocIdentifier[64];
		DWORD caKlocIdentifierLength = sizeof(caKlocIdentifier);
		OPGP_ERROR_STATUS lookupStatus;

		lookupStatus = extract_scp11_ca_kloc_identifier_from_certificate_chain(certOceEcka, certOceEckaLength,
				caKlocIdentifier, &caKlocIdentifierLength);
		if (!OPGP_ERROR_CHECK(lookupStatus)) {
			BYTE resolvedKeyIdentifier = 0;
			BYTE resolvedKeyVersionNumber = 0;

			lookupStatus = GP211_ca_kloc_kid_kvn(cardContext, cardInfo, NULL,
					caKlocIdentifier, caKlocIdentifierLength,
					&resolvedKeyIdentifier, &resolvedKeyVersionNumber);
			if (OPGP_ERROR_CHECK(lookupStatus)
					&& lookupStatus.errorCode == OPGP_ISO7816_ERROR_DATA_NOT_FOUND) {
				lookupStatus = resolve_scp11_ca_kloc_from_supported_identifiers(cardContext, cardInfo,
						caKlocIdentifier, caKlocIdentifierLength,
						&resolvedKeyIdentifier, &resolvedKeyVersionNumber);
			}
			if (!OPGP_ERROR_CHECK(lookupStatus)) {
				caKlocKeyIdentifier = resolvedKeyIdentifier;
				caKlocKeyVersionNumber = resolvedKeyVersionNumber;
			}
		}

		status = GP211_perform_security_operation_certificate_chain(cardContext, cardInfo, NULL,
				caKlocKeyVersionNumber, caKlocKeyIdentifier, certOceEcka, certOceEckaLength);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
	}

	status = scp11a_generate_ephemeral_keypair(keyParameterReference, oceEphemeralPrivateKey, &oceEphemeralPrivateKeyLength,
			oceEphemeralPublicKey, &oceEphemeralPublicKeyLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	scpIdentifierAndParameters[0] = GP211_SCP11;
	scpIdentifierAndParameters[1] = scpParameters;
	status = append_tlv(crtValue, sizeof(crtValue), &crtValueLength, 0x90, scpIdentifierAndParameters, sizeof(scpIdentifierAndParameters));
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	status = append_tlv(crtValue, sizeof(crtValue), &crtValueLength, 0x95, &keyUsageQualifier, 1);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	status = append_tlv(crtValue, sizeof(crtValue), &crtValueLength, 0x80, &keyType, 1);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	{
		BYTE keyLengthByte = (BYTE)keyLength;
		status = append_tlv(crtValue, sizeof(crtValue), &crtValueLength, 0x81, &keyLengthByte, 1);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
	}
	status = append_tlv(commandData, sizeof(commandData), &commandDataLength, 0xA6, crtValue, crtValueLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	status = append_tlv(commandData, sizeof(commandData), &commandDataLength, 0x5F49,
			oceEphemeralPublicKey, oceEphemeralPublicKeyLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	sendBuffer[i++] = 0x80;
	sendBuffer[i++] = 0x82;
	sendBuffer[i++] = (BYTE)(keySetVersion & 0x7F);
	sendBuffer[i++] = (BYTE)(keyIndex & 0x7F);
	if (commandDataLength > sizeof(sendBuffer) - 6) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_COMMAND_TOO_LARGE, OPGP_stringify_error(OPGP_ERROR_COMMAND_TOO_LARGE));
		goto end;
	}
	sendBuffer[i++] = (BYTE)commandDataLength;
	memcpy(sendBuffer + i, commandData, commandDataLength);
	i += commandDataLength;
	sendBuffer[i++] = 0x00;
	sendBufferLength = i;

	status = OPGP_send_APDU(cardContext, cardInfo, NULL, sendBuffer, sendBufferLength, response, &responseLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	CHECK_SW_9000(response, responseLength, status);
	if (responseLength < 2) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		goto end;
	}

	sdEphemeralPublicKeyLength = sizeof(sdEphemeralPublicKey);
	receiptLength = sizeof(receipt);
	status = parse_scp11_mutual_authentication_response(response, responseLength - 2, sdEphemeralPublicKey,
			&sdEphemeralPublicKeyLength, receipt, &receiptLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	sharedInfo[sharedInfoLength++] = keyUsageQualifier;
	sharedInfo[sharedInfoLength++] = keyType;
	sharedInfo[sharedInfoLength++] = (BYTE)keyLength;

	memcpy(receiptInput + receiptInputLength, commandData, commandDataLength);
	receiptInputLength += commandDataLength;
	status = append_tlv(receiptInput, sizeof(receiptInput), &receiptInputLength, 0x5F49,
			sdEphemeralPublicKey, sdEphemeralPublicKeyLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	status = scp11a_derive_session_keys_and_verify_receipt(secInfo, (BYTE)keyLength, keyParameterReference,
			staticOcePrivateKey, expectedStaticPrivateKeyLength,
			sdStaticPublicKey, sdStaticPublicKeyLength,
			oceEphemeralPrivateKey, oceEphemeralPrivateKeyLength,
			sdEphemeralPublicKey, sdEphemeralPublicKeyLength,
			sharedInfo, sharedInfoLength, receiptInput, receiptInputLength, receipt, receiptLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	secInfo->secureChannelProtocol = GP211_SCP03;
	secInfo->secureChannelProtocolImpl = map_scp11a_scp03_impl(keyUsageQualifier);
	secInfo->securityLevel = map_scp11a_security_level(keyUsageQualifier);
	secInfo->keyLength = keyLength;
	secInfo->keySetVersion = keySetVersion;
	secInfo->keyIndex = keyIndex;
	secInfo->sessionEncryptionCounter = 1;

	OPGP_ERROR_CREATE_NO_ERROR(status);

end:
	OPGP_LOG_END(_T("mutual_authentication_scp11a"), status);
	return status;
}

/*
 * Performs GlobalPlatform mutual authentication. The public header owns the
 * Doxygen contract for this exported API.
 * For SCP01, SCP02 and SCP03 the first key parameter is the Secure Channel base
 * or master key when the selected implementation uses one key. For SCP11a it is
 * the raw static OCE ECKA private key (SK.OCE.ECKA). See the public header for
 * the full parameter contract.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context().
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param baseKeyOrStaticOcePrivateKey [in] SCP01/SCP02/SCP03 base or master key, or SCP11a static OCE ECKA private key.
 * \param S_ENC [in] Secure Channel Encryption Key for SCP01/SCP02/SCP03 implementations with separate static keys.
 * \param S_MAC [in] Secure Channel Message Authentication Code Key for SCP01/SCP02/SCP03 implementations with separate static keys.
 * \param DEK [in] Data Encryption Key for SCP01/SCP02/SCP03 implementations with separate static keys.
 * \param keyLength [in] Symmetric key length. 16, 24 or 32 bytes.
 * \param keySetVersion [in] The key set version on the card to use for mutual authentication.
 * \param keyIndex [in] The key index in the key set version on the card to use for mutual authentication.
 * \param secureChannelProtocol [in] The Secure Channel Protocol. Use GP211_SCP11 explicitly for SCP11a. Passing 0 only auto-detects SCP01/SCP02/SCP03 via INITIALIZE UPDATE; callers may use GP211_get_secure_channel_protocol_details() before authentication for card-recognition based discovery.
 * \param secureChannelProtocolImpl [in] The Secure Channel Protocol implementation, or SCP11 parameter byte for SCP11a.
 * \param securityLevel [in] The requested security level.
 * \param derivationMethod [in] The derivation method to use.
 * \param certOceEcka [in] Optional SCP11 certificate chain data to submit before mutual authentication. The data must contain one or more 7F21 or X.509 DER certificates, optionally wrapped in BF21, and must end with CERT.OCE.ECKA.
 * \param certOceEckaLength [in] Length of certOceEcka.
 * \param sdPublicKeyOverride [in] Optional SCP11 PK.SD.ECKA public key as raw uncompressed EC point or B0-wrapped point.
 * \param sdPublicKeyOverrideLength [in] Length of \p sdPublicKeyOverride.
 * \param sdPublicKeyFileName [in] Optional PEM file containing PK.SD.ECKA. If set, this takes precedence over \p sdPublicKeyOverride.
 * \param secInfo [out] The returned GP211_SECURITY_INFO structure.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS GP211_mutual_authentication(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, PBYTE baseKeyOrStaticOcePrivateKey,
                           PBYTE S_ENC, PBYTE S_MAC,
                           PBYTE DEK,
                           DWORD keyLength,
                           BYTE keySetVersion,
                           BYTE keyIndex, BYTE secureChannelProtocol,
                           BYTE secureChannelProtocolImpl, BYTE securityLevel,
                           BYTE derivationMethod,
                           PBYTE certOceEcka, DWORD certOceEckaLength,
                           PBYTE sdPublicKeyOverride, DWORD sdPublicKeyOverrideLength,
                           OPGP_STRING sdPublicKeyFileName,
                           GP211_SECURITY_INFO *secInfo) {
	return mutual_authentication(cardContext, cardInfo, baseKeyOrStaticOcePrivateKey,
						   S_ENC, S_MAC,
						   DEK, keyLength,
						   keySetVersion,
						   keyIndex, secureChannelProtocol,
						   secureChannelProtocolImpl, securityLevel,
						   derivationMethod,
						   certOceEcka, certOceEckaLength,
						   sdPublicKeyOverride, sdPublicKeyOverrideLength, sdPublicKeyFileName,
						   secInfo);
}

OPGP_ERROR_STATUS mutual_authentication(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, PBYTE baseKeyOrStaticOcePrivateKey,
                           PBYTE S_ENC, PBYTE S_MAC,
                           PBYTE DEK,
                           DWORD keyLength,
                           BYTE keySetVersion,
                           BYTE keyIndex, BYTE secureChannelProtocol,
                           BYTE secureChannelProtocolImpl, BYTE securityLevel,
                           BYTE derivationMethod,
                           PBYTE certOceEcka, DWORD certOceEckaLength,
                           PBYTE sdPublicKeyOverride, DWORD sdPublicKeyOverrideLength,
                           OPGP_STRING sdPublicKeyFileName,
                           GP211_SECURITY_INFO *secInfo) {
	OPGP_ERROR_STATUS status;
	DWORD i=0;

	BYTE hostChallenge[8];

	BYTE keyDiversificationData[10];
	BYTE keyInformationData[3];
	SCP_INITIALIZE_UPDATE_RESPONSE scpResponse;
	BYTE hostCryptogram[8];
    /* Philip Wendland: SCP03 appends the first 8 Bytes of 16 Byte CMAC output to the message,
	 * but the chaining value is the full 16 byte output. So 16 Bytes are needed.
	 */
    BYTE mac[16]; // Philip Wendland: Only the fist 8 byte used by SCP01/02

	BYTE sMac[32];
	BYTE sEnc[32];
	BYTE dek[32];

	BYTE recvBuffer[64];
	DWORD recvBufferLength = sizeof(recvBuffer);
	BYTE sendBuffer[128];
	DWORD sendBufferLength = sizeof(sendBuffer);

	// random for host challenge

	OPGP_LOG_START(_T("mutual_authentication"));

	if (secureChannelProtocol == GP211_SCP11) {
		status = mutual_authentication_scp11a(cardContext, cardInfo, baseKeyOrStaticOcePrivateKey, keyLength, keySetVersion, keyIndex,
				secureChannelProtocolImpl, securityLevel, certOceEcka, certOceEckaLength,
				sdPublicKeyOverride, sdPublicKeyOverrideLength, sdPublicKeyFileName, secInfo);
		goto end;
	}

	// copy keys to internal buffer
	if (S_MAC != NULL) {
		memcpy(sMac, S_MAC, keyLength);
	}
	if (S_ENC != NULL) {
		memcpy(sEnc, S_ENC, keyLength);
	}
	if (DEK != NULL) {
		memcpy(dek, DEK, keyLength);
	}

	status = get_random(hostChallenge, 8);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	secInfo->keyLength = keyLength;

	OPGP_LOG_HEX(_T("mutual_authentication: Generated Host Challenge: "), hostChallenge, 8);

	// INITIALIZE UPDATE
	i=0;
	sendBufferLength = 14;
	sendBuffer[i++] = 0x80;
	sendBuffer[i++] = 0x50;
	sendBuffer[i++] = keySetVersion;
	sendBuffer[i++] = keyIndex;
	sendBuffer[i++] = 0x08;
	memcpy(sendBuffer+i, hostChallenge, 8);
	i+=8;
	sendBuffer[i] = 0x00;

	status = OPGP_send_APDU(cardContext, cardInfo, NULL, sendBuffer, sendBufferLength, recvBuffer, &recvBufferLength);
	if ( OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	CHECK_SW_9000(recvBuffer, recvBufferLength, status);

	// check receive buffer length, including SW it must be 30 bytes
	// - SCP01/SCP02 = 30 bytes, SCP03 31 or 34 bytes
	if (recvBufferLength != 30 && recvBufferLength != 31 && recvBufferLength != 34) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		goto end;
	}

	// response of INITIALIZE UPDATE
	memcpy(keyDiversificationData, recvBuffer, 10);
	memcpy(keyInformationData, recvBuffer+10, 3); // Copy the 3rd byte regardless - ignored for SCP01/SCP02

	// set detected SCP
	if (cardInfo.specVersion == GP_211) {
		if (!secureChannelProtocol) {
			secureChannelProtocol = keyInformationData[1];
		}
		// test if reported SCP is consistent with passed SCP
		else if (secureChannelProtocol != keyInformationData[1]) {
			OPGP_ERROR_CREATE_ERROR(status, GP211_ERROR_INCONSISTENT_SCP, OPGP_stringify_error(GP211_ERROR_INCONSISTENT_SCP));
			goto end;
		}
		secInfo->secureChannelProtocol = secureChannelProtocol;
	}
	else {
		// OP201 does not contain secure channel protocol in keyInformationData[1]
		if (secureChannelProtocol != GP211_SCP01) {
			OPGP_ERROR_CREATE_ERROR(status, GP211_ERROR_INCONSISTENT_SCP, OPGP_stringify_error(GP211_ERROR_INCONSISTENT_SCP));
			goto end;
		}
		secInfo->secureChannelProtocol = secureChannelProtocol;
	}

	secInfo->keySetVersion = keyInformationData[0];
	// the key index is only reported in OP201
	if (cardInfo.specVersion == OP_201) {
		secInfo->keyIndex = keyInformationData[1];
	}
	else {
		// we set it to a dummy value
		secInfo->keyIndex = 0xFF;
	}
	/* end */

#ifdef OPGP_DEBUG
	OPGP_log_Msg(_T("mutual_authentication: Secure Channel Protocol: 0x%02X"), secureChannelProtocol);
#endif

	status = scp_parse_initialize_update_response(secInfo->secureChannelProtocol,
			&secureChannelProtocolImpl,
			secInfo->securityLevel,
			keyInformationData,
			recvBuffer,
			recvBufferLength,
			&scpResponse);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	secInfo->secureChannelProtocolImpl = secureChannelProtocolImpl;
#ifdef OPGP_DEBUG
	OPGP_log_Msg(_T("mutual_authentication: Secure Channel Protocol Implementation: 0x%02X"), secureChannelProtocolImpl);
#endif

#ifdef OPGP_DEBUG
	OPGP_LOG_HEX(_T("mutual_authentication: Key Diversification Data: "), keyDiversificationData, 10);

	OPGP_LOG_HEX(_T("mutual_authentication: Key Information Data: "), keyInformationData, scpResponse.keyInformationDataLength);
	OPGP_LOG_HEX(_T("mutual_authentication: Card Challenge: "), scpResponse.cardChallenge, scpResponse.cardChallengeLength);

	if (scpResponse.sequenceCounterLength == 2) {
		OPGP_LOG_HEX(_T("mutual_authentication: Sequence Counter: "), scpResponse.sequenceCounter, 2);
	}
	// only present when pseudo-random challenge generation is used
	if (scpResponse.sequenceCounterLength == 3) {
		OPGP_LOG_HEX(_T("mutual_authentication: Sequence Counter: "), scpResponse.sequenceCounter, 3);
	}

	OPGP_LOG_HEX(_T("mutual_authentication: Retrieved Card Cryptogram: "), scpResponse.cardCryptogram, 8);

#endif

	if (derivationMethod == OPGP_DERIVATION_METHOD_EMV_CPS11) {
		status = EMV_CPS11_derive_keys(keyDiversificationData, baseKeyOrStaticOcePrivateKey, sEnc, sMac, dek);
		if ( OPGP_ERROR_CHECK(status)) {
			goto end;
		}
	}

	if (derivationMethod == OPGP_DERIVATION_METHOD_VISA2) {
		status = VISA2_derive_keys(keyDiversificationData, baseKeyOrStaticOcePrivateKey, sEnc, sMac, dek);
		if ( OPGP_ERROR_CHECK(status)) {
			goto end;
		}
	}

	status = scp_derive_session_keys_and_verify_card_cryptogram(secInfo->secureChannelProtocol,
			secInfo, baseKeyOrStaticOcePrivateKey, sEnc, sMac, dek, keyLength, hostChallenge, &scpResponse);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

#ifdef OPGP_DEBUG
	OPGP_LOG_HEX(_T("mutual_authentication: S-ENC Session Key: "), secInfo->encryptionSessionKey, 16);
	OPGP_LOG_HEX(_T("mutual_authentication: S-MAC Session Key: "), secInfo->C_MACSessionKey, 16);

	if (secInfo->secureChannelProtocol == GP211_SCP01) {
		OPGP_LOG_HEX(_T("mutual_authentication: Data Encryption Key: "), secInfo->dataEncryptionSessionKey, 16);
	}
	if (secInfo->secureChannelProtocol == GP211_SCP02 ||
			secInfo->secureChannelProtocol == GP211_SCP03) {
		OPGP_LOG_HEX(_T("mutual_authentication: R-MAC Session Key: "), secInfo->R_MACSessionKey, 16);
	}
	if (secInfo->secureChannelProtocol == GP211_SCP02) {
		OPGP_LOG_HEX(_T("mutual_authentication: DEK Session Key: "), secInfo->dataEncryptionSessionKey, 16);
	}
#endif

	// EXTERNAL AUTHENTICATE
	secInfo->securityLevel = securityLevel;
	status = scp_prepare_external_authentication(secInfo->secureChannelProtocol,
			secInfo, keyLength, securityLevel, hostChallenge, &scpResponse, hostCryptogram, mac);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	sendBufferLength = 21;
	i=0;
	sendBuffer[i++] = 0x84;
	sendBuffer[i++] = 0x82;
	sendBuffer[i++] = securityLevel;
	sendBuffer[i++] = 0x00;
	sendBuffer[i++] = 0x10;
	memcpy(sendBuffer+i, hostCryptogram, 8);
	i+=8;

	// The SCP helper has already calculated MAC chaining values.
	memcpy(sendBuffer+i, mac, 8);
	i+=8;

	status = OPGP_send_APDU(cardContext, cardInfo, NULL, sendBuffer, sendBufferLength, recvBuffer, &recvBufferLength);
	if ( OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	switch (status.errorCode) {
		case OPGP_ISO7816_ERROR_6300:
			{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ISO7816_ERROR_HOST_CRYPTOGRAM_VERIFICATION, OPGP_stringify_error(OPGP_ISO7816_ERROR_HOST_CRYPTOGRAM_VERIFICATION)); goto end; }
	}
	CHECK_SW_9000(recvBuffer, recvBufferLength, status);

	// init SCP03 session counter
	if (secInfo->secureChannelProtocol == GP211_SCP03) {
		secInfo->sessionEncryptionCounter = 1;
	}
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("mutual_authentication"), status);
	return status;
}

/**
 * This is only supported in SCP02. It depends on the supported protocol implementation by the card what keys must be passed as parameters.
 * baseKey must be NULL if the protocol uses 3 Secure Channel Keys
 * (Secure Channel Encryption Key, Secure Channel Message Authentication Code Key and
 * Data Encryption Key) and vice versa.
 * Details about the supported Secure Channel Protocol and its implementation can be
 * obtained by a call to the function GP211_get_secure_channel_protocol_details().
 * New cards usually use the VISA default key for all DES keys. See OPGP_VISA_DEFAULT_KEY.
 * The current Sequence Counter can be obtained with a call to GP211_get_sequence_counter().
 * SCP02 is implicitly set and the security level is set to C-MAC only.
 * \param AID The AID needed for the calculation of the ICV.
 * \param AIDLength The length of the AID buffer.
 * \param baseKey [in] Secure Channel base key.
 * \param S_ENC [in] Secure Channel Encryption Key.
 * \param S_MAC [in] Secure Channel Message Authentication Code Key.
 * \param DEK [in] Data Encryption Key.
 * \param secureChannelProtocolImpl [in] The Secure Channel Protocol Implementation.
 * \param sequenceCounter [in] The sequence counter.
 * \param *secInfo [out] The returned GP211_SECURITY_INFO structure.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_init_implicit_secure_channel(PBYTE AID, DWORD AIDLength, BYTE baseKey[16],
								  BYTE S_ENC[16], BYTE S_MAC[16], BYTE DEK[16],
								  BYTE secureChannelProtocolImpl, BYTE sequenceCounter[2],
								  GP211_SECURITY_INFO *secInfo) {
	OPGP_ERROR_STATUS status;

	OPGP_LOG_START(_T("init_implicit_secure_channel"));

	secInfo->secureChannelProtocol = GP211_SCP02;
	secInfo->secureChannelProtocolImpl = secureChannelProtocolImpl;
	secInfo->securityLevel = GP211_SCP02_SECURITY_LEVEL_C_MAC;
		/* Secure Channel base key */
	if (secInfo->secureChannelProtocolImpl == GP211_SCP02_IMPL_i1A
			|| secInfo->secureChannelProtocolImpl == GP211_SCP02_IMPL_i1B) {
		// calculation of encryption session key
			status = create_session_key_SCP02(baseKey, ENCDerivationConstant, sequenceCounter, secInfo->encryptionSessionKey);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}

		// calculation of C-MAC session key
		status = create_session_key_SCP02(baseKey, C_MACDerivationConstant, sequenceCounter, secInfo->C_MACSessionKey);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}

		// calculation of R-MAC session key
		status = create_session_key_SCP02(baseKey, R_MACDerivationConstant, sequenceCounter, secInfo->C_MACSessionKey);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}

		// calculation of data encryption session key
		status = create_session_key_SCP02(baseKey, DEKDerivationConstant, sequenceCounter, secInfo->C_MACSessionKey);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}

	}
	/* 3 Secure Channel Keys */
	else if (secInfo->secureChannelProtocolImpl == GP211_SCP02_IMPL_i0A
		|| secInfo->secureChannelProtocolImpl == GP211_SCP02_IMPL_i0B) {
		// calculation of encryption session key
		status = create_session_key_SCP02(S_ENC, ENCDerivationConstant, sequenceCounter, secInfo->encryptionSessionKey);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}

		// calculation of C-MAC session key
		status = create_session_key_SCP02(S_MAC, C_MACDerivationConstant, sequenceCounter, secInfo->C_MACSessionKey);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}

		// calculation of R-MAC session key
		status = create_session_key_SCP02(S_MAC, R_MACDerivationConstant, sequenceCounter, secInfo->R_MACSessionKey);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}

		// calculation of data encryption session key
		status = create_session_key_SCP02(DEK, DEKDerivationConstant, sequenceCounter, secInfo->dataEncryptionSessionKey);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
	}
	else {
		OPGP_ERROR_CREATE_ERROR(status, GP211_ERROR_INVALID_SCP_IMPL, OPGP_stringify_error(GP211_ERROR_INVALID_SCP_IMPL));
		goto end;
	}

	status = calculate_MAC_des_3des(secInfo->C_MACSessionKey, AID, AIDLength, (PBYTE)ICV, secInfo->lastC_MAC);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("init_implicit_secure_channel"), status);
	return status;
}

/**
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param sequenceCounter [out] The sequence counter.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_get_sequence_counter(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo,
						  GP211_SECURITY_INFO *secInfo, DWORD *sequenceCounter) {
	OPGP_ERROR_STATUS status;
	BYTE recvBuffer[256];
	DWORD recvBufferLength = sizeof(recvBuffer);
	TLV tlv;
	LONG result;
	DWORD counterValue = 0;

	OPGP_LOG_START(_T("get_sequence_counter"));
	status = GP211_get_data(cardContext, cardInfo, secInfo, (PBYTE)GP211_GET_DATA_SEQUENCE_COUNTER_DEFAULT_KEY_VERSION,
		recvBuffer, &recvBufferLength);
	if ( OPGP_ERROR_CHECK(status) ) {
		goto end;
	}
	result = read_TLV(recvBuffer, recvBufferLength, &tlv);
	if (result == -1) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		goto end;
	}
	if (tlv.tag != 0xC1 || tlv.length != 3) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		goto end;
	}
	counterValue = 0;
	for (DWORD i = 0; i < tlv.length; i++) {
		counterValue = (counterValue << 8) | tlv.value[i];
	}
	*sequenceCounter = counterValue;
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("get_sequence_counter"), status);
	return status;
}

/**
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param confirmationCounter [out] The confirmation counter.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_get_confirmation_counter(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo,
						  GP211_SECURITY_INFO *secInfo, DWORD *confirmationCounter) {
	OPGP_ERROR_STATUS status;
	BYTE recvBuffer[256];
	DWORD recvBufferLength = sizeof(recvBuffer);
	TLV tlv;
	LONG result;
	DWORD counterValue = 0;

	OPGP_LOG_START(_T("get_confirmation_counter"));
	status = GP211_get_data(cardContext, cardInfo, secInfo, (PBYTE)GP211_GET_DATA_CONFIRMATION_COUNTER,
		recvBuffer, &recvBufferLength);
	if ( OPGP_ERROR_CHECK(status) ) {
		goto end;
	}
	result = read_TLV(recvBuffer, recvBufferLength, &tlv);
	if (result == -1) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		goto end;
	}
	if (tlv.tag != 0xC2 || tlv.length != 2) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		goto end;
	}
	counterValue = 0;
	for (DWORD i = 0; i < tlv.length; i++) {
		counterValue = (counterValue << 8) | tlv.value[i];
	}
	*confirmationCounter = counterValue;
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("get_confirmation_counter"), status);
	return status;
}

/**
 * \param *secInfo [out] The returned GP211_SECURITY_INFO structure.
 */
OPGP_ERROR_STATUS GP211_close_implicit_secure_channel(GP211_SECURITY_INFO *secInfo) {
	OPGP_ERROR_STATUS status;
	OPGP_LOG_START(_T("close_implicit_secure_channel"));
	secInfo->securityLevel = GP211_SCP02_SECURITY_LEVEL_NO_SECURE_MESSAGING;
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("close_implicit_secure_channel"), status);
	return status;
}

/**
 * The single numbers of the new PIN are encoded as single BYTEs in the newPIN buffer.
 * The tryLimit must be in the range of 0x03 and x0A.
 * The PIN must comprise at least 6 numbers and not exceeding 12 numbers.
 * To unblock the PIN use tryLimit with a value of 0x00. In this case newPIN buffer and newPINLength are ignored.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param tryLimit [in] The try limit for the PIN.
 * \param newPIN [in] The new PIN.
 * \param newPINLength [in] The length of the new PIN.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_pin_change(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo, BYTE tryLimit,
					  PBYTE newPIN, DWORD newPINLength) {
	return pin_change(cardContext, cardInfo, secInfo, tryLimit, newPIN, newPINLength);
}

OPGP_ERROR_STATUS pin_change(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo, BYTE tryLimit,
				PBYTE newPIN, DWORD newPINLength) {
	OPGP_ERROR_STATUS status;
	DWORD sendBufferLength;
	DWORD recvBufferLength=2;
	BYTE recvBuffer[2];
	BYTE sendBuffer[13];
	BYTE PINFormat[8];
	BYTE encryption[8];
	DWORD encryptionLength;
	DWORD j,i=0;
	OPGP_LOG_START(_T("pin_change"));
	if ((tryLimit != 0) && !((tryLimit >= 0x03) && (tryLimit <= 0x0a))) {
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_WRONG_TRY_LIMIT, OPGP_stringify_error(OPGP_ERROR_WRONG_TRY_LIMIT)); goto end; }
	}
	if ((newPINLength < 6) || (newPINLength > 12)) {
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_WRONG_PIN_LENGTH, OPGP_stringify_error(OPGP_ERROR_WRONG_PIN_LENGTH)); goto end; }
	}
	sendBuffer[i++] = 0x80;
	sendBuffer[i++] = 0x24;
	sendBuffer[i++] = 0x00;
	sendBuffer[i++] = tryLimit;
	if (tryLimit != 0) {
		sendBuffer[i++] = 0x08;
		memset ( PINFormat, 0, sizeof ( PINFormat ));
		PINFormat[0] = 0x20;
		PINFormat[0] |= newPINLength & 0x0f;
		for (j=0; j<newPINLength; j++) {
			PINFormat[1+j/2] |= (newPIN[j] & 0x0f) << 4*(1-j%2);
		}
		for (; j<12; j++) {
			PINFormat[1+j/2] |= (0x0f) << 4*(1-j%2);
		}
		PINFormat[7] = 0xFF;
		calculate_enc_ecb_two_key_triple_des(secInfo->dataEncryptionSessionKey, PINFormat, 8, encryption, &encryptionLength);
		memcpy(sendBuffer+i, encryption, 8);
		i+=8;
	}
	sendBufferLength = i;

	status = OPGP_send_APDU(cardContext, cardInfo, secInfo,sendBuffer, sendBufferLength, recvBuffer, &recvBufferLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	if (status.errorCode == OPGP_ISO7816_ERROR_WRONG_DATA)
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ISO7816_ERROR_WRONG_GLOBAL_PIN_FORMAT, OPGP_stringify_error(OPGP_ISO7816_ERROR_WRONG_GLOBAL_PIN_FORMAT)); goto end; }
	if (status.errorCode == OPGP_ISO7816_ERROR_INCORRECT_P1P2)
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ISO7816_ERROR_WRONG_PIN_TRY_LIMIT, OPGP_stringify_error(OPGP_ISO7816_ERROR_WRONG_PIN_TRY_LIMIT)); goto end; }
	CHECK_SW_9000(recvBuffer, recvBufferLength, status);

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("pin_change"), status);
	return status;
}

/**
 * Internal helper function for STORE DATA command.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param encryptionFlags [in] Flag defining the encryption settings.
 * \param formatFlags [in] Flag defining the format settings.
 * \param responseDataExpected [in] TRUE if response data is expected.
 * \param *data [in] Data to send.
 * \param dataLength [in] The length of the data buffer.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct
 */
static OPGP_ERROR_STATUS store_data(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
	BYTE encryptionFlags, BYTE formatFlags, BOOL responseDataExpected, PBYTE data, DWORD dataLength);

/**
 * If STORE DATA is used for personalizing an application, a GP211_install_for_personalization() must be called first.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param encryptionFlags [in] Flag defining the encryption settings. See STORE_DATA_ENCRYPTION_NO_INFORMATION.
 * \param formatFlags [in] Flag defining the format settings. See STORE_DATA_FORMAT_DGI.
 * \param responseDataExpected [in] TRUE if response data is expected. In this case a case 4 APDU is used. FALSE to use a case 3 APDU.
 * \param *data [in] Data to send to application or Security Domain.
 * \param dataLength [in] The length of the data buffer.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_store_data(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
	BYTE encryptionFlags, BYTE formatFlags, BOOL responseDataExpected, PBYTE data, DWORD dataLength) {
	OPGP_ERROR_STATUS status;
	OPGP_LOG_START(_T("GP211_store_data"));
	status = store_data(cardContext, cardInfo, secInfo, encryptionFlags, formatFlags, responseDataExpected, data, dataLength);
	OPGP_LOG_END(_T("GP211_store_data"), status);
	return status;
}

/**
 * Stores an SCP11 ECKA certificate store using STORE DATA.
 * The input file must contain either DER or PEM encoded certificate data.
 * For PEM input, CERTIFICATE blocks are converted to DER first.
 * The resulting data must contain either a complete BF21 certificate store TLV or
 * a certificate list value for BF21 according to section 7.2 table 7-4.
 * The certificate list must contain homogeneous certificates in either legacy
 * GP format (7F21) or X.509 DER format (30), but not both.
 * The command data also includes a Control Reference Template for KID/KVN.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context().
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param keyVersionNumber [in] Key Version Number (KVN) assigned to the certificate store.
 * \param keyIdentifier [in] Key Identifier (KID) assigned to the certificate store.
 * \param certificateStoreFileName [in] File name of the encoded SCP11 certificate store.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS GP211_store_data_ecka_certificate(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
				 BYTE keyVersionNumber, BYTE keyIdentifier, OPGP_STRING certificateStoreFileName) {
	OPGP_ERROR_STATUS status;
	BYTE certificateBuffer[4096];
	DWORD certificateBufferLength = sizeof(certificateBuffer);
	BYTE certificateDerBuffer[4096];
	PBYTE certificateData = certificateBuffer;
	DWORD certificateDataLength = 0;
	BYTE commandData[4600];
	DWORD commandDataLength = 0;
	BYTE crtValue[8];
	DWORD crtValueLength = 0;
	BYTE keyReference[2] = {keyIdentifier, keyVersionNumber};
	GP_SIMPLE_TLV certificateStoreTlv;

	OPGP_LOG_START(_T("GP211_store_data_ecka_certificate"));

	status = read_binary_file(certificateStoreFileName, certificateBuffer, &certificateBufferLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	certificateDataLength = certificateBufferLength;
	if (contains_pem_certificate_header(certificateBuffer, certificateBufferLength)) {
		certificateDataLength = sizeof(certificateDerBuffer);
		status = convert_pem_certificate_list_to_der(certificateBuffer, certificateBufferLength,
				certificateDerBuffer, &certificateDataLength);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
		certificateData = certificateDerBuffer;
	}

	status = append_tlv(crtValue, sizeof(crtValue), &crtValueLength, 0x83, keyReference, sizeof(keyReference));
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	status = append_tlv(commandData, sizeof(commandData), &commandDataLength, 0xA6, crtValue, crtValueLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	if (parse_simple_tlv(certificateData, certificateDataLength, &certificateStoreTlv) > 0
			&& certificateStoreTlv.tlvLength == certificateDataLength && certificateStoreTlv.tag == 0xBF21) {
		if (!is_scp11_certificate_list((PBYTE)certificateStoreTlv.value, certificateStoreTlv.length)) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			goto end;
		}
		status = append_tlv(commandData, sizeof(commandData), &commandDataLength, 0xBF21,
				(PBYTE)certificateStoreTlv.value, certificateStoreTlv.length);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
	} else if (is_scp11_certificate_list(certificateData, certificateDataLength)) {
		status = append_tlv(commandData, sizeof(commandData), &commandDataLength, 0xBF21,
				certificateData, certificateDataLength);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
	} else {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		goto end;
	}

	status = GP211_store_data(cardContext, cardInfo, secInfo, STORE_DATA_ENCRYPTION_NO_INFORMATION,
			STORE_DATA_FORMAT_BER_TLV, 0, commandData, commandDataLength);

end:
	OPGP_LOG_END(_T("GP211_store_data_ecka_certificate"), status);
	return status;
}

/**
 * Stores an SCP11 whitelist using STORE DATA.
 * The whitelist is linked to the KID/KVN in the Control Reference Template and
 * encoded in tag 70. Each Certificate Serial Number is encoded as a tag 93 TLV
 * inside tag 70. If present, the whitelist counter is encoded in tag 92.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context().
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param keyVersionNumber [in] Key Version Number (KVN) of the related CA-KLOC key.
 * \param keyIdentifier [in] Key Identifier (KID) of the related CA-KLOC key.
 * \param whitelistCounterPresent [in] TRUE to include the whitelist counter.
 * \param whitelistCounter [in] The whitelist counter value when whitelistCounterPresent is TRUE.
 * \param certificateSerialNumbers [in] Array of Certificate Serial Number byte arrays. May be NULL when certificateSerialNumberCount is 0.
 * \param certificateSerialNumberLengths [in] Lengths of the Certificate Serial Number byte arrays. Each length must be 1..16. May be NULL when certificateSerialNumberCount is 0.
 * \param certificateSerialNumberCount [in] Number of Certificate Serial Numbers. Use 0 to remove the whitelist.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS GP211_store_data_whitelist(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
				 BYTE keyVersionNumber, BYTE keyIdentifier, BOOL whitelistCounterPresent, USHORT whitelistCounter,
				 PBYTE *certificateSerialNumbers, PDWORD certificateSerialNumberLengths,
				 DWORD certificateSerialNumberCount) {
	OPGP_ERROR_STATUS status;
	BYTE commandData[2048];
	DWORD commandDataLength = 0;
	BYTE whitelistValue[1536];
	DWORD whitelistValueLength = 0;
	BYTE crtValue[8];
	DWORD crtValueLength = 0;
	BYTE keyReference[2] = {keyIdentifier, keyVersionNumber};
	BYTE counterValue[2];
	DWORD i;

	OPGP_LOG_START(_T("GP211_store_data_whitelist"));

	if (certificateSerialNumberCount > 0
			&& (certificateSerialNumbers == NULL || certificateSerialNumberLengths == NULL)) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		goto end;
	}
	for (i = 0; i < certificateSerialNumberCount; i++) {
		if (certificateSerialNumbers[i] == NULL
				|| certificateSerialNumberLengths[i] == 0
				|| certificateSerialNumberLengths[i] > GP211_SCP11_CERTIFICATE_MAX_ID_LENGTH) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
			goto end;
		}
		status = append_tlv(whitelistValue, sizeof(whitelistValue), &whitelistValueLength,
				0x93, certificateSerialNumbers[i], certificateSerialNumberLengths[i]);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
	}

	status = append_tlv(crtValue, sizeof(crtValue), &crtValueLength, 0x83, keyReference, sizeof(keyReference));
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	status = append_tlv(commandData, sizeof(commandData), &commandDataLength, 0xA6, crtValue, crtValueLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	if (whitelistCounterPresent) {
		counterValue[0] = (BYTE)(whitelistCounter >> 8);
		counterValue[1] = (BYTE)(whitelistCounter & 0xFF);
		status = append_tlv(commandData, sizeof(commandData), &commandDataLength, 0x92, counterValue, sizeof(counterValue));
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
	}

	status = append_tlv(commandData, sizeof(commandData), &commandDataLength, 0x70, whitelistValue, whitelistValueLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	status = GP211_store_data(cardContext, cardInfo, secInfo, STORE_DATA_ENCRYPTION_NO_INFORMATION,
			STORE_DATA_FORMAT_BER_TLV, 0, commandData, commandDataLength);

end:
	OPGP_LOG_END(_T("GP211_store_data_whitelist"), status);
	return status;
}

/**
 * Stores an SCP11 CA-KLOC Identifier to KID/KVN mapping using STORE DATA.
 * The CA-KLOC Identifier is encoded in tag 42 and the key reference in tag 83,
 * both inside a Control Reference Template.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context().
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param caKlocIdentifier [in] The CA-KLOC Identifier value.
 * \param caKlocIdentifierLength [in] The length of caKlocIdentifier.
 * \param keyIdentifier [in] Key Identifier (KID) to map.
 * \param keyVersionNumber [in] Key Version Number (KVN) to map.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct.
 */
OPGP_ERROR_STATUS GP211_store_data_ca_kloc_kid_kvn(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
				 PBYTE caKlocIdentifier, DWORD caKlocIdentifierLength, BYTE keyIdentifier, BYTE keyVersionNumber) {
	OPGP_ERROR_STATUS status;
	BYTE commandData[512];
	DWORD commandDataLength = 0;
	BYTE crtValue[260];
	DWORD crtValueLength = 0;
	BYTE keyReference[2] = {keyIdentifier, keyVersionNumber};

	OPGP_LOG_START(_T("GP211_store_data_ca_kloc_kid_kvn"));

	if (caKlocIdentifier == NULL || caKlocIdentifierLength == 0) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		goto end;
	}

	status = append_tlv(crtValue, sizeof(crtValue), &crtValueLength, 0x42, caKlocIdentifier, caKlocIdentifierLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	status = append_tlv(crtValue, sizeof(crtValue), &crtValueLength, 0x83, keyReference, sizeof(keyReference));
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	status = append_tlv(commandData, sizeof(commandData), &commandDataLength, 0xA6, crtValue, crtValueLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	status = GP211_store_data(cardContext, cardInfo, secInfo, STORE_DATA_ENCRYPTION_NO_INFORMATION,
			STORE_DATA_FORMAT_BER_TLV, 0, commandData, commandDataLength);

end:
	OPGP_LOG_END(_T("GP211_store_data_ca_kloc_kid_kvn"), status);
	return status;
}

/**
 * Internal helper function for STORE DATA command.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param encryptionFlags [in] Flag defining the encryption settings.
 * \param formatFlags [in] Flag defining the format settings.
 * \param responseDataExpected [in] TRUE if response data is expected.
 * \param *data [in] Data to send.
 * \param dataLength [in] The length of the data buffer.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct
 */
static OPGP_ERROR_STATUS store_data(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
	BYTE encryptionFlags, BYTE formatFlags, BOOL responseDataExpected, PBYTE data, DWORD dataLength) {
	OPGP_ERROR_STATUS status;
	BYTE recvBuffer[258];
	DWORD recvBufferLength = sizeof(recvBuffer);
	BYTE sendBuffer[5 + 255 + 1];
	DWORD offset = 0;
	BYTE blockNumber = 0;
	DWORD maxBlockDataLength = 255;

	OPGP_LOG_START(_T("store_data"));

	if (data == NULL && dataLength > 0) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_RESPONSE_DATA, OPGP_stringify_error(OPGP_ERROR_INVALID_RESPONSE_DATA));
		goto end;
	}

	do {
		DWORD remaining = dataLength - offset;
		DWORD blockDataLength = remaining > maxBlockDataLength ? maxBlockDataLength : remaining;
		BOOL lastBlock = (offset + blockDataLength) >= dataLength;
		DWORD sendBufferLength = 5 + blockDataLength;

		sendBuffer[0] = 0x80;
		sendBuffer[1] = 0xE2;
		sendBuffer[2] = encryptionFlags | formatFlags;
		if (responseDataExpected) {
			sendBuffer[2] |= 0x01;
		}
		if (lastBlock) {
			sendBuffer[2] |= 0x80;
		}
		sendBuffer[3] = blockNumber++;
		sendBuffer[4] = (BYTE)blockDataLength;
		if (blockDataLength > 0) {
			memcpy(sendBuffer + 5, data + offset, blockDataLength);
		}
		if (lastBlock && responseDataExpected) {
			sendBuffer[sendBufferLength++] = 0x00;
		}

		recvBufferLength = sizeof(recvBuffer);
		status = OPGP_send_APDU(cardContext, cardInfo, secInfo, sendBuffer, sendBufferLength, recvBuffer, &recvBufferLength);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
		CHECK_SW_9000(recvBuffer, recvBufferLength, status);

		offset += blockDataLength;
	} while (offset < dataLength);

	OPGP_ERROR_CREATE_NO_ERROR(status);

end:
	OPGP_LOG_END(_T("store_data"), status);
	return status;
}

/**
 * Builds STORE DATA encoding for symmetric keys according to GlobalPlatform v2.3.1.49 section 11.11.4.
 * \param scp [in] The secure channel protocol. See GP211_SCP03.
 * \param scpImpl [in] The secure channel protocol implementation. See GP211_SCP03_IMPL_i04.
 * \param dataEncryptionKey [in] The data encryption key for encrypting sensitive data.
 * \param dataEncryptionKeyLength [in] The length of the data encryption key in bytes.
 * \param keyType [in] The key type. See GP211_KEY_TYPE_AES.
 * \param keyPurpose [in] The key purpose. See GP211_KEY_PURPOSE_SCP03.
 * \param keys [in] Array of key data pointers.
 * \param keyIds [in] Array of key identifiers.
 * \param numKeys [in] Number of keys.
 * \param keyLength [in] The key length in bytes.
 * \param keyVersion [in] The key version number.
 * \param keyAccessCondition [in] The key access condition. See GP211_KEY_ACCESS_SD_AND_APPS.
 * \param output [out] The output buffer for the encoded data.
 * \param outputLength [in, out] The available and returned length of the output buffer.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_build_store_data_keys(BYTE scp,
                                        BYTE scpImpl,
                                        PBYTE dataEncryptionKey,
                                        DWORD dataEncryptionKeyLength,
                                        BYTE keyType,
                                        USHORT keyPurpose,
                                        PBYTE keys[],
                                        BYTE keyIds[],
                                        DWORD numKeys,
                                        DWORD keyLength,
                                        BYTE keyVersion,
                                        BYTE keyAccessCondition,
                                        PBYTE output,
                                        PDWORD outputLength) {
	OPGP_ERROR_STATUS status;
	DWORD i = 0;
	DWORD j, k;
	LONG result;
	BYTE keyCheckValue[3];
	BYTE encryptedKey[256];
	DWORD encryptedKeyLength;
	// Temp buffers to build content before writing with length
	BYTE b7Content[256];
	DWORD b7Len = 0;
	BYTE b9AllContent[1024];  // All B9 CRT entries
	DWORD b9AllLen = 0;

	OPGP_LOG_START(_T("build_store_data_keys"));

	if (numKeys == 0) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_COMBINATION_KEY_SET_VERSION_KEY_INDEX, OPGP_stringify_error(OPGP_ERROR_INVALID_COMBINATION_KEY_SET_VERSION_KEY_INDEX));
		goto end;
	}

	// Build B7 (Key Attributes) content
	b7Content[b7Len++] = 0x80;  // Tag: Key Type
	b7Content[b7Len++] = 0x01;  // Length
	b7Content[b7Len++] = keyType;

	// '81' Key Length
	b7Content[b7Len++] = 0x81;  // Tag
	if (keyLength <= 255) {
		b7Content[b7Len++] = 0x01;
		b7Content[b7Len++] = (BYTE)keyLength;
	} else {
		b7Content[b7Len++] = 0x02;
		b7Content[b7Len++] = (BYTE)(keyLength >> 8);
		b7Content[b7Len++] = (BYTE)(keyLength & 0xFF);
	}

	// '83' Key Version Number
	b7Content[b7Len++] = 0x83;  // Tag
	b7Content[b7Len++] = 0x01;  // Length
	b7Content[b7Len++] = keyVersion;

	// '96' Key Access Condition
	b7Content[b7Len++] = 0x96;  // Tag
	b7Content[b7Len++] = 0x01;  // Length
	b7Content[b7Len++] = keyAccessCondition;

	// '97' Key Purpose
	b7Content[b7Len++] = 0x97;  // Tag
	b7Content[b7Len++] = 0x02;  // Length
	b7Content[b7Len++] = (BYTE)(keyPurpose >> 8);
	b7Content[b7Len++] = (BYTE)(keyPurpose & 0xFF);

	// Build all B9 (CRT) entries
	for (j = 0; j < numKeys; j++) {
		BYTE b9CrtContent[128];
		DWORD b9CrtLen = 0;

		// '82' Key Identifier
		b9CrtContent[b9CrtLen++] = 0x82;
		b9CrtContent[b9CrtLen++] = 0x01;
		b9CrtContent[b9CrtLen++] = keyIds[j];

		// '84' Key Check Value
		// calculate_key_check_value doesn't actually use secInfo, pass NULL
		status = calculate_key_check_value(NULL, keyType, keys[j], keyLength, keyCheckValue);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
		b9CrtContent[b9CrtLen++] = 0x84;
		b9CrtContent[b9CrtLen++] = 0x03;
		memcpy(b9CrtContent + b9CrtLen, keyCheckValue, 3);
		b9CrtLen += 3;

		// Write B9 tag + length + content to b9AllContent
		b9AllContent[b9AllLen++] = 0xB9;
		result = write_TLV_length(b9AllContent, b9AllLen, sizeof(b9AllContent) - b9AllLen, (USHORT)b9CrtLen);
		if (result < 0) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
			goto end;
		}
		b9AllLen += result;
		memcpy(b9AllContent + b9AllLen, b9CrtContent, b9CrtLen);
		b9AllLen += b9CrtLen;
	}

	// Now write to output: DGI '00B9' + length + B7 + B9s + DGI '8113's
	if (i + 2 > *outputLength) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
		goto end;
	}
	output[i++] = 0x00;  // DGI '00B9' tag
	output[i++] = 0xB9;

	// Calculate total content length for DGI '00B9': tag(1) + len(1-3) + b7Content + b9AllContent
	DWORD dgi00B9ContentLen = 1 + 1 + b7Len + b9AllLen;  // Assuming short length for B7
	// Recalculate with actual B7 length field size
	LONG b7LenFieldSize = (b7Len <= 128) ? 1 : (b7Len <= 255) ? 2 : 3;
	dgi00B9ContentLen = 1 + b7LenFieldSize + b7Len + b9AllLen;

	result = write_TLV_length(output, i, *outputLength - i, (USHORT)dgi00B9ContentLen);
	if (result < 0) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
		goto end;
	}
	i += result;

	// Write B7
	if (i + 1 + b7LenFieldSize + b7Len > *outputLength) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
		goto end;
	}
	output[i++] = 0xB7;
	result = write_TLV_length(output, i, *outputLength - i, (USHORT)b7Len);
	if (result < 0) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
		goto end;
	}
	i += result;
	memcpy(output + i, b7Content, b7Len);
	i += b7Len;

	// Write all B9 entries
	if (i + b9AllLen > *outputLength) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
		goto end;
	}
	memcpy(output + i, b9AllContent, b9AllLen);
	i += b9AllLen;

	// Now write DGI '8113' for each encrypted key
	// Create temporary secInfo for encrypt_sensitive_data
	GP211_SECURITY_INFO tempSecInfo;
	memset(&tempSecInfo, 0, sizeof(tempSecInfo));
	tempSecInfo.secureChannelProtocol = scp;
	tempSecInfo.secureChannelProtocolImpl = scpImpl;
	memcpy(tempSecInfo.dataEncryptionSessionKey, dataEncryptionKey, dataEncryptionKeyLength);
	tempSecInfo.keyLength = dataEncryptionKeyLength;

	for (k = 0; k < numKeys; k++) {
		encryptedKeyLength = sizeof(encryptedKey);
		status = encrypt_sensitive_data(&tempSecInfo, keys[k], keyLength, encryptedKey, &encryptedKeyLength);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}

		if (i + 2 > *outputLength) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
			goto end;
		}
		output[i++] = 0x81;  // DGI '8113' tag
		output[i++] = 0x13;

		result = write_TLV_length(output, i, *outputLength - i, (USHORT)encryptedKeyLength);
		if (result < 0) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
			goto end;
		}
		i += result;

		if (i + encryptedKeyLength > *outputLength) {
			OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER));
			goto end;
		}
		memcpy(output + i, encryptedKey, encryptedKeyLength);
		i += encryptedKeyLength;
	}

	*outputLength = i;
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("build_store_data_keys"), status);
	return status;
}

/**
 * Stores secure channel keys using STORE DATA command with symmetric key format according to GlobalPlatform v2.3.1.49 section 11.11.4.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param AID [in] The AID of the Security Domain.
 * \param AIDLength [in] The length of the AID.
 * \param keyType [in] The key type. See GP211_KEY_TYPE_AES.
 * \param baseKey [in] The base key (optional, can be NULL).
 * \param newS_ENC [in] The new S-ENC key.
 * \param newS_MAC [in] The new S-MAC key.
 * \param newDEK [in] The new DEK key.
 * \param keyLength [in] The key length in bytes.
 * \param keySetVersion [in] The key set version.
 * \param keyAccessCondition [in] The key access condition. See GP211_KEY_ACCESS_SD_AND_APPS.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS GP211_store_secure_channel_keys(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, GP211_SECURITY_INFO *secInfo,
	PBYTE AID, DWORD AIDLength,
	BYTE keyType, PBYTE baseKey, PBYTE newS_ENC, PBYTE newS_MAC, PBYTE newDEK,
	DWORD keyLength, BYTE keySetVersion, BYTE keyAccessCondition) {
	OPGP_ERROR_STATUS status;
	BYTE encodedData[2048];
	DWORD encodedDataLength = sizeof(encodedData);
	PBYTE keys[4];
	BYTE keyIds[4];
	DWORD numKeys = 0;
	USHORT keyPurpose = secInfo->secureChannelProtocol;

	OPGP_LOG_START(_T("GP211_store_secure_channel_keys"));

	// Install for personalization
	status = GP211_install_for_personalization(cardContext, cardInfo, secInfo, AID, AIDLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	if (baseKey != NULL) {
		keys[numKeys] = baseKey;
		keyIds[numKeys] = 0x01;
		numKeys++;
	} else {
		if (newS_ENC != NULL) {
			keys[numKeys] = newS_ENC;
			keyIds[numKeys] = 0x01;
			numKeys++;
		}

		if (newS_MAC != NULL) {
			keys[numKeys] = newS_MAC;
			keyIds[numKeys] = 0x02;
			numKeys++;
		}

		if (newDEK != NULL) {
			keys[numKeys] = newDEK;
			keyIds[numKeys] = 0x03;
			numKeys++;
		}
	}

	if (numKeys == 0) {
		OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_COMBINATION_KEY_SET_VERSION_KEY_INDEX, OPGP_stringify_error(OPGP_ERROR_INVALID_COMBINATION_KEY_SET_VERSION_KEY_INDEX));
		goto end;
	}

	// Build the STORE DATA encoding
	status = GP211_build_store_data_keys(secInfo->secureChannelProtocol, secInfo->secureChannelProtocolImpl,
		secInfo->dataEncryptionSessionKey, secInfo->keyLength,
		keyType, keyPurpose, keys, keyIds, numKeys, keyLength, keySetVersion, keyAccessCondition, encodedData, &encodedDataLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	status = store_data(cardContext, cardInfo, secInfo, STORE_DATA_ENCRYPTION_ENCRYPTED, STORE_DATA_FORMAT_DGI, 0, encodedData, encodedDataLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("GP211_store_secure_channel_keys"), status);
	return status;
}

/**
 * You must track on your own, what channels are open.
 * \param *cardInfo [in, out] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param channelNumber [in] The Logical Channel number to select.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OPGP_select_channel(OPGP_CARD_INFO *cardInfo, BYTE channelNumber) {
	OPGP_ERROR_STATUS status;
	OPGP_LOG_START(_T("select_channel"));
	cardInfo->logicalChannel = channelNumber;
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("select_channel"), status);
	return status;
}

/**
 * For an OPEN command, the channelNumberToClose is ignored.
 * For an CLOSE command, the channelNumberOpened is returned.
 * After closing a Logical Channel the Basic Logical Channel is assumed for the next transmissions.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param *cardInfo [in, out] The OPGP_CARD_INFO structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param openClose [in] Logical Channel should be opened or closed. See GP211_MANAGE_CHANNEL_OPEN.
 * \param channelNumberToClose [in] The Logical Channel number to close.
 * \param channelNumberOpened [out] The Logical Channel number opened.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OPGP_manage_channel(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO *cardInfo, GP211_SECURITY_INFO *secInfo,
					BYTE openClose, BYTE channelNumberToClose,
					BYTE *channelNumberOpened) {

	OPGP_ERROR_STATUS status;
	DWORD sendBufferLength;
	DWORD recvBufferLength=3;
	BYTE recvBuffer[3];
	BYTE sendBuffer[5];
	DWORD i=0;
	OPGP_LOG_START(_T("OPGP_manage_channel"));
	sendBuffer[i++] = 0x00;
	sendBuffer[i++] = 0x70;
	sendBuffer[i++] = openClose;
	if (openClose == GP211_MANAGE_CHANNEL_CLOSE) {
		sendBuffer[i++] = channelNumberToClose;
	}
	else {
		sendBuffer[i++] = 0x00;
		sendBuffer[i++] = 0x00;
	}
	sendBufferLength = i;

	status = OPGP_send_APDU(cardContext, *cardInfo, secInfo, sendBuffer, sendBufferLength, recvBuffer, &recvBufferLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	CHECK_SW_9000(recvBuffer, recvBufferLength, status);

	if (openClose == GP211_MANAGE_CHANNEL_OPEN) {
		*channelNumberOpened = recvBuffer[0];
		cardInfo->logicalChannel = recvBuffer[0];
	OPGP_LOG_MSG(_T("OPGP_manage_channel: Logical Channel number opened: %d"), *channelNumberOpened);
	}
	else {
		*channelNumberOpened = 0;
		cardInfo->logicalChannel = 0;
	OPGP_LOG_MSG(_T("OPGP_manage_channel: Logical Channel closed: %d"), channelNumberToClose);
	}

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("OPGP_manage_channel"), status);
	return status;
}

/**
 * The secInfo pointer can also be null and so this function can be used for arbitrary cards.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO cardInfo, structure returned by OPGP_card_connect().
 * \param *secInfo [in] The pointer to the OP201_SECURITY_INFO structure returned by OP201_mutual_authentication().
 * \param capdu [in] The command APDU.
 * \param capduLength [in] The length of the command APDU.
 * \param rapdu [out] The response APDU.
 * \param rapduLength [in, out] The length of the response APDU.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_send_APDU(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, OP201_SECURITY_INFO *secInfo,
					 PBYTE capdu, DWORD capduLength, PBYTE rapdu, PDWORD rapduLength) {
	OPGP_ERROR_STATUS status;

	if (secInfo == NULL) {
	    status = OPGP_send_APDU(cardContext, cardInfo, NULL, capdu, capduLength, rapdu, rapduLength);
	} else {
	    GP211_SECURITY_INFO gp211secInfo;
	    mapOP201ToGP211SecurityInfo(*secInfo, &gp211secInfo);
	    status = OPGP_send_APDU(cardContext, cardInfo, &gp211secInfo, capdu, capduLength, rapdu, rapduLength);
	    mapGP211ToOP201SecurityInfo(gp211secInfo, secInfo);
	}

	return status;
}

/**
 * A keySetVersion value of 0x00 adds a new key.
 * Any other value between 0x01 and 0x7f must match an existing key set version.
 * The new key set version defines the key set version a new key belongs to.
 * This can be the same key version or a new not existing key set version.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO cardInfo, structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the OP201_SECURITY_INFO structure returned by OP201_mutual_authentication().
 * \param keySetVersion [in] An existing key set version.
 * \param keyIndex [in] The position of the key in the key set version.
 * \param newKeySetVersion [in] The new key set version.
 * \param PEMKeyFileName [in] A PEM file name with the public RSA key.
 * \param *passPhrase [in] The passphrase. Must be an ASCII string.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_put_rsa_key(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, OP201_SECURITY_INFO *secInfo,
				 BYTE keySetVersion, BYTE keyIndex, BYTE newKeySetVersion,
				 OPGP_STRING PEMKeyFileName, char *passPhrase) {
	OPGP_ERROR_STATUS status;
	GP211_SECURITY_INFO gp211secInfo;
	mapOP201ToGP211SecurityInfo(*secInfo, &gp211secInfo);
	status = put_asymmetric_keys(cardContext, cardInfo, &gp211secInfo, keySetVersion, keyIndex,
								 newKeySetVersion, PEMKeyFileName, passPhrase, GP211_KEY_TYPE_RSA, true);
	mapGP211ToOP201SecurityInfo(gp211secInfo, secInfo);
	return status;
}

/**
 * A keySetVersion value of 0x00 adds a new key.
 * Any other value between 0x01 and 0x7f must match an existing key set version.
 * The new key set version defines the key set version a new key belongs to.
 * This can be the same key version or a new not yet existing key set version.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO cardInfo, structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the OP201_SECURITY_INFO structure returned by OP201_mutual_authentication().
 * \param keySetVersion [in] An existing key set version.
 * \param keyIndex [in] The position of the key in the key set version.
 * \param newKeySetVersion [in] The new key set version.
 * \param _3desKey [in] The new 3DES key.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_put_3desKey(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, OP201_SECURITY_INFO *secInfo,
				  BYTE keySetVersion, BYTE keyIndex, BYTE newKeySetVersion, BYTE _3desKey[16]) {
	OPGP_ERROR_STATUS status;
	GP211_SECURITY_INFO gp211secInfo;
	mapOP201ToGP211SecurityInfo(*secInfo, &gp211secInfo);
	status = put_3des_key(cardContext, cardInfo, &gp211secInfo, keySetVersion, keyIndex, newKeySetVersion, _3desKey);
	mapGP211ToOP201SecurityInfo(gp211secInfo, secInfo);
	return status;
}

/**
 * A keySetVersion value of 0x00 adds a new secure channel key set.
 * Any other value between 0x01 and 0x7f must match an existing key set version.
 * The new key set version defines the key set version the new secure channel keys belongs to.
 * This can be the same key version or a new not existing key set version.
 * Sometimes a key derivation of the put keys might be necessary so it is necessary to call
 * OP201_EMV_CPS11_derive_keys() or any other derivation function.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO cardInfo, structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the OP201_SECURITY_INFO structure returned by OP201_mutual_authentication().
 * \param keySetVersion [in] An existing key set version.
 * \param newKeySetVersion [in] The new key set version.
 * \param new_encKey [in] The new Encryption key.
 * \param new_macKey [in] The new MAC key.
 * \param new_KEK [in] The new key encryption key.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_put_secure_channel_keys(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, OP201_SECURITY_INFO *secInfo,
        BYTE keySetVersion, BYTE newKeySetVersion, BYTE new_encKey[16], BYTE new_macKey[16], BYTE new_KEK[16]) {
	OPGP_ERROR_STATUS status;
	GP211_SECURITY_INFO gp211secInfo;
	mapOP201ToGP211SecurityInfo(*secInfo, &gp211secInfo);
	status = put_secure_channel_keys(cardContext, cardInfo, &gp211secInfo, keySetVersion, newKeySetVersion,
		NULL, new_encKey, new_macKey, new_KEK, 16, 0);
	mapGP211ToOP201SecurityInfo(gp211secInfo, secInfo);
	return status;
}

/**
 * A keySetVersion value of 0x00 adds a new secure channel key set.
 * Any other value between 0x01 and 0x7f must match an existing key set version.
 * The new key set version defines the key set version a the new secure channel keys belongs to.
 * This can be the same key version or a new not existing key set version.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO cardInfo, structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the OP201_SECURITY_INFO structure returned by OP201_mutual_authentication().
 * \param keySetVersion [in] An existing key set version.
 * \param newKeySetVersion [in] The new key set version.
 * \param PEMKeyFileName [in] A PEM file name with the public RSA key.
 * \param *passPhrase [in] The passphrase. Must be an ASCII string.
 * \param receiptGenerationKey [in] The new Receipt Generation key.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_put_delegated_management_keys(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, OP201_SECURITY_INFO *secInfo,
								   BYTE keySetVersion, BYTE newKeySetVersion,
								   OPGP_STRING PEMKeyFileName, char *passPhrase,
								   BYTE receiptGenerationKey[16]) {
	OPGP_ERROR_STATUS status;
	GP211_SECURITY_INFO gp211secInfo;
	BYTE currentKeySetVersion = keySetVersion;

	mapOP201ToGP211SecurityInfo(*secInfo, &gp211secInfo);

	if (PEMKeyFileName != NULL) {
		status = put_asymmetric_keys(cardContext, cardInfo, &gp211secInfo, currentKeySetVersion, 0x01,
									 newKeySetVersion, PEMKeyFileName, passPhrase, OP201_KEY_TYPE_RSA, true);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
		currentKeySetVersion = newKeySetVersion;
	}

	if (receiptGenerationKey != NULL) {
		status = put_delegated_management_receipt_keys(cardContext, cardInfo, &gp211secInfo, currentKeySetVersion, newKeySetVersion, receiptGenerationKey, 16, OP201_KEY_TYPE_DES_ECB);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
	}

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	mapGP211ToOP201SecurityInfo(gp211secInfo, secInfo);
	return status;
}

/**
 * If keyIndex is 0xFF all keys within a keySetVersion are deleted.
 * If keySetVersion is 0x00 all keys with the specified keyIndex are deleted.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO cardInfo, structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the OP201_SECURITY_INFO structure returned by OP201_mutual_authentication().
 * \param keySetVersion [in] An existing key set version.
 * \param keyIndex [in] An existing key index.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_delete_key(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, OP201_SECURITY_INFO *secInfo, BYTE keySetVersion, BYTE keyIndex) {
	OPGP_ERROR_STATUS status;
	GP211_SECURITY_INFO gp211secInfo;
	mapOP201ToGP211SecurityInfo(*secInfo, &gp211secInfo);
	status = delete_key(cardContext, cardInfo, &gp211secInfo, keySetVersion, keyIndex);
	mapGP211ToOP201SecurityInfo(gp211secInfo, secInfo);
	return status;
}

/**
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO cardInfo, structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the OP201_SECURITY_INFO structure returned by OP201_mutual_authentication().
 * \param AIDs [in] A pointer to the an array of OPGP_AID structures describing the applications and load files to delete.
 * \param AIDsLength [in] The number of OPGP_AID structures.
 * \param *receiptData [out] A OP201_RECEIPT_DATA array. If the deletion is performed by a
 * security domain with delegated management privilege
 * this structure contains the according data for each deleted application or package.
 * \param receiptDataLength [in, out] A pointer to the length of the receiptData array.
 * If no receiptData is available this length is 0;
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_delete_application(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, OP201_SECURITY_INFO *secInfo,
							  OPGP_AID *AIDs, DWORD AIDsLength, OP201_RECEIPT_DATA *receiptData, PDWORD receiptDataLength) {
	OPGP_ERROR_STATUS status;
	DWORD i;
	GP211_SECURITY_INFO gp211secInfo;
	GP211_RECEIPT_DATA *gp211receiptData;
	mapOP201ToGP211SecurityInfo(*secInfo, &gp211secInfo);
    gp211receiptData = (*receiptDataLength == 0) ?
        NULL :
        (GP211_RECEIPT_DATA *)malloc(sizeof(GP211_RECEIPT_DATA)* (*receiptDataLength));

    if ((*receiptDataLength != 0) && (gp211receiptData == NULL)) {
        OPGP_ERROR_CREATE_ERROR(status, ENOMEM, OPGP_stringify_error(ENOMEM));
        goto end;
    }

	status = delete_application(cardContext, cardInfo, &gp211secInfo, AIDs, AIDsLength,
		gp211receiptData, receiptDataLength, NULL, 0, OP_201);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	for (i=0; i<*receiptDataLength; i++) {
		mapGP211ToOP201ReceiptData(gp211receiptData[i], &(receiptData[i]));
	}
end:
	mapGP211ToOP201SecurityInfo(gp211secInfo, secInfo);
	if (gp211receiptData) {
		free(gp211receiptData);
	}
	return status;
}

/**
 * Puts a single card data object identified by identifier.
 * Some cards do not provide some data objects. Some possible identifiers are predefined.
 * See OP201_GET_DATA_ISSUER_BIN. For details about the coding of the dataObject see the programmer's manual
 * of your card.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO cardInfo, structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the OP201_SECURITY_INFO structure returned by OP201_mutual_authentication().
 * \param identifier [in] Two byte buffer with high and low order tag value for identifying card data object.
 * \param dataObject [in] The coded data object.
 * \param dataObjectLength [in] The length of the data object.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_put_data(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, OP201_SECURITY_INFO *secInfo,
					BYTE identifier[2], PBYTE dataObject, DWORD dataObjectLength) {
	OPGP_ERROR_STATUS status;
	GP211_SECURITY_INFO gp211secInfo;
	mapOP201ToGP211SecurityInfo(*secInfo, &gp211secInfo);
	status = put_data(cardContext, cardInfo, &gp211secInfo, identifier, dataObject, dataObjectLength);
	mapGP211ToOP201SecurityInfo(gp211secInfo, secInfo);
	return status;
}

/**
 * Retrieves a single card data object from the card identified by identifier.
 * Some cards do not provide some data objects. Some possible identifiers are predefined.
 * See OP201_GET_DATA_ISSUER_BIN and so on. For details about the coding of the response see the programmer's manual
 * of your card.
 * There is a convenience method get_key_information_templates() to get the key information template(s)
 * containing key set version, key index, key type and key length of the keys.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO cardInfo, structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the OP201_SECURITY_INFO structure returned by OP201_mutual_authentication().
 * \param identifier [in] Two byte buffer with high and low order tag value for identifying card data object.
 * \param recvBuffer [in] The buffer for the card data object.
 * \param recvBufferLength [in] The length of the received card data object.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_get_data(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, OP201_SECURITY_INFO *secInfo, BYTE identifier[2], PBYTE recvBuffer, PDWORD recvBufferLength) {
	OPGP_ERROR_STATUS status;
	GP211_SECURITY_INFO gp211secInfo;
	/* can be executed outside of a secret channel */
	if (secInfo != NULL) {
		mapOP201ToGP211SecurityInfo(*secInfo, &gp211secInfo);
		status = get_data(cardContext, cardInfo, &gp211secInfo, identifier, recvBuffer, recvBufferLength);
	}
	else {
		status = get_data(cardContext, cardInfo, NULL, identifier, recvBuffer, recvBufferLength);
	}
	if (secInfo != NULL) {
		mapGP211ToOP201SecurityInfo(gp211secInfo, secInfo);
	}
	return status;
}

/**
 * The card must support the optional report of key information templates.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO cardInfo, structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the OP201_SECURITY_INFO structure returned by OP201_mutual_authentication().
 * \param keyInformationTemplate [in] The number of the key information template.
 * \param *keyInformation [out] A pointer to an array of OP201_KEY_INFORMATION structures.
 * \param keyInformationLength [in, out] The number of OP201_KEY_INFORMATION structures.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_get_key_information_templates(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, OP201_SECURITY_INFO *secInfo,
								   BYTE keyInformationTemplate,
								   OP201_KEY_INFORMATION *keyInformation, PDWORD keyInformationLength) {
	OPGP_ERROR_STATUS status;
	DWORD i;
	GP211_SECURITY_INFO gp211secInfo;
	GP211_KEY_INFORMATION *gp211keyInformation;
	mapOP201ToGP211SecurityInfo(*secInfo, &gp211secInfo);
	gp211keyInformation =
		(GP211_KEY_INFORMATION *)malloc(sizeof(GP211_KEY_INFORMATION)* (*keyInformationLength));
	if (gp211keyInformation == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, ENOMEM, OPGP_stringify_error(ENOMEM));
		goto end;
	}

	status = get_key_information_templates(cardContext, cardInfo, &gp211secInfo, keyInformationTemplate,
		gp211keyInformation, keyInformationLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	for (i=0; i<*keyInformationLength; i++) {
		mapGP211ToOP201KeyInformation(gp211keyInformation[i], &(keyInformation[i]));
	}
end:
	mapGP211ToOP201SecurityInfo(gp211secInfo, secInfo);
	if (keyInformation && gp211keyInformation) {
		free(gp211keyInformation);
	}
	return status;
}

/**
 *
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO cardInfo, structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the OP201_SECURITY_INFO structure returned by OP201_mutual_authentication().
 * \param statusType [in] Identifier for Load Files, Applications or the Card Manager.
 * \param AID [in] The AID.
 * \param AIDLength [in] The length of the AID.
 * \param lifeCycleState [in] The new life cycle state.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_set_status(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, OP201_SECURITY_INFO *secInfo, BYTE statusType, PBYTE AID, DWORD AIDLength, BYTE lifeCycleState) {
	OPGP_ERROR_STATUS status;
	GP211_SECURITY_INFO gp211secInfo;
	mapOP201ToGP211SecurityInfo(*secInfo, &gp211secInfo);
	status = set_status(cardContext, cardInfo, &gp211secInfo, statusType, AID, AIDLength, lifeCycleState);
	mapGP211ToOP201SecurityInfo(gp211secInfo, secInfo);
	return status;
}

/**
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO cardInfo, structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the OP201_SECURITY_INFO structure returned by OP201_mutual_authentication().
 * \param cardElement [in] Identifier to retrieve data for Load Files, Applications or the Card Manager.
 * \param *applData [out] The OP201_APPLICATION_DATA structure containing AID, life cycle state and privileges.
 * \param applDataLength [in, out] The number of OP201_APPLICATION_DATA passed and returned.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_get_status(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, OP201_SECURITY_INFO *secInfo, BYTE cardElement, OP201_APPLICATION_DATA *applData, PDWORD applDataLength) {
	OPGP_ERROR_STATUS status;
	DWORD sendBufferLength=8;
	DWORD recvBufferLength=APDU_RESPONSE_LEN;
	BYTE recvBuffer[APDU_RESPONSE_LEN];
	BYTE sendBuffer[8];
	DWORD j,i=0;
	DWORD apduErrorCode;
	OPGP_LOG_START(_T("get_status"));
	sendBuffer[i++] = 0x80;
	sendBuffer[i++] = 0xF2;
	sendBuffer[i++] = cardElement;
	sendBuffer[i++] = 0x00;
	sendBuffer[i++] = 2;
	sendBuffer[i++] = 0x4F;
	sendBuffer[i++] = 0x00;
	sendBuffer[i] = 0x00;
	i=0;
	do {
		recvBufferLength=APDU_RESPONSE_LEN;
		status = OP201_send_APDU(cardContext, cardInfo, secInfo, sendBuffer, sendBufferLength, recvBuffer, &recvBufferLength);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
		if (status.errorCode != OPGP_ISO7816_ERROR_MORE_DATA_AVAILABLE) {
			CHECK_SW_9000(recvBuffer, recvBufferLength, status);
		}
		apduErrorCode = status.errorCode;
		for (j=0; j<recvBufferLength-2; ) {
			DWORD dataRead;
			GP211_APPLICATION_DATA gp211AppData;
			if (*applDataLength <= i ) {
				{ OPGP_ERROR_CREATE_ERROR(status, OP201_ERROR_MORE_APPLICATION_DATA, OPGP_stringify_error(OP201_ERROR_MORE_APPLICATION_DATA)); goto end; }
			}
			parse_application_data(recvBuffer + j, recvBufferLength - 2 - j, cardElement, GP211_STATUS_FORMAT_DEPRECATED, &gp211AppData, &dataRead);
			mapGP211ToOP201ApplicationData(gp211AppData, &applData[i]);
			j += dataRead;
			i++;
		}
		sendBuffer[3]=0x01;
	} while (apduErrorCode == OPGP_ISO7816_ERROR_MORE_DATA_AVAILABLE);

	*applDataLength = i;
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("get_status"), status);
	return status;
}

/**
 * An install_for_load() must precede.
 * The Load File Data Block DAP block(s) must be the same block(s) and in the same order like in calculate_load_file_DAP().
 * If no Load File Data Block DAP blocks are necessary the dapBlock must be NULL and the dapBlockLength 0.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO cardInfo, structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the OP201_SECURITY_INFO structure returned by OP201_mutual_authentication().
 * \param *dapBlock [in] A pointer to OP201_DAP_BLOCK structure(s).
 * \param dapBlockLength [in] The number of OP201_DAP_BLOCK structure(s).
 * \param executableLoadFileName [in] The name of the CAP or IJC file to load.
 * \param *receiptData [out] If the deletion is performed by a security domain with delegated management privilege
 * this structure contains the according data.
 * Can be validated with validate_load_receipt().
 * \param receiptDataAvailable [out] 0 if no receiptData is available.
 * \param *callback [in] A pointer to a OPGP_PROGRESS_CALLBACK defining the callback function and optional parameters for it.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_load(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, OP201_SECURITY_INFO *secInfo,
				 OP201_DAP_BLOCK *dapBlock, DWORD dapBlockLength, OPGP_STRING executableLoadFileName,
				 OP201_RECEIPT_DATA *receiptData, PDWORD receiptDataAvailable, OPGP_PROGRESS_CALLBACK *callback) {
	OPGP_ERROR_STATUS status;
	GP211_SECURITY_INFO gp211secInfo;
	GP211_RECEIPT_DATA gp211receiptData;
	GP211_DAP_BLOCK *gp211dapBlock = NULL;
	DWORD i;
	mapOP201ToGP211SecurityInfo(*secInfo, &gp211secInfo);
	gp211dapBlock = (GP211_DAP_BLOCK *)malloc(sizeof(GP211_DAP_BLOCK)*dapBlockLength);
	if (gp211dapBlock == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, ENOMEM, OPGP_stringify_error(ENOMEM));
		goto end;
	}

	for (i=0; i<dapBlockLength; i++) {
		mapOP201ToGP211DAPBlock(dapBlock[i], &(gp211dapBlock[i]));
	}
	status = load(cardContext, cardInfo, &gp211secInfo, gp211dapBlock, dapBlockLength,
		executableLoadFileName, &gp211receiptData, receiptDataAvailable, callback);
	if (*receiptDataAvailable) {
		mapGP211ToOP201ReceiptData(gp211receiptData, receiptData);
	}
	mapGP211ToOP201SecurityInfo(gp211secInfo, secInfo);
end:
	if (gp211dapBlock)
		free(gp211dapBlock);
	return status;
}

/**
 * An install_for_load() must precede.
 * The Load File Data Block DAP block(s) must be the same block(s) and in the same order like in calculate_load_file_DAP().
 * If no Load File Data Block DAP blocks are necessary the dapBlock must be NULL and the dapBlockLength 0.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO cardInfo, structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the OP201_SECURITY_INFO structure returned by OP201_mutual_authentication().
 * \param *dapBlock [in] A pointer to OP201_DAP_BLOCK structure(s).
 * \param dapBlockLength [in] The number of OP201_DAP_BLOCK structure(s).
 * \param loadFileBuf [in] buffer with the contents of a Executable Load File.
 * \param loadFileBufSize [in] size of loadFileBuf.
 * \param *receiptData [out] If the deletion is performed by a security domain with delegated management privilege
 * this structure contains the according data.
 * Can be validated with validate_load_receipt().
 * \param receiptDataAvailable [out] 0 if no receiptData is available.
 * \param *callback [in] A pointer to a OPGP_PROGRESS_CALLBACK defining the callback function and optional parameters for it.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_load_from_buffer(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, OP201_SECURITY_INFO *secInfo,
				 OP201_DAP_BLOCK *dapBlock, DWORD dapBlockLength,
				 PBYTE loadFileBuf, DWORD loadFileBufSize,
				 OP201_RECEIPT_DATA *receiptData, PDWORD receiptDataAvailable, OPGP_PROGRESS_CALLBACK *callback) {
	OPGP_ERROR_STATUS status;
	GP211_SECURITY_INFO gp211secInfo;
	GP211_RECEIPT_DATA gp211receiptData;
	GP211_DAP_BLOCK *gp211dapBlock = NULL;
	DWORD i;
	mapOP201ToGP211SecurityInfo(*secInfo, &gp211secInfo);
	gp211dapBlock = (GP211_DAP_BLOCK *)malloc(sizeof(GP211_DAP_BLOCK)*dapBlockLength);
	if (gp211dapBlock == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, ENOMEM, OPGP_stringify_error(ENOMEM));
		goto end;
	}

	for (i=0; i<dapBlockLength; i++) {
		mapOP201ToGP211DAPBlock(dapBlock[i], &(gp211dapBlock[i]));
	}
	status = load_from_buffer(cardContext, cardInfo, &gp211secInfo, gp211dapBlock, dapBlockLength,
		loadFileBuf, loadFileBufSize, &gp211receiptData, receiptDataAvailable, callback);
	if (*receiptDataAvailable)
		mapGP211ToOP201ReceiptData(gp211receiptData, receiptData);
	mapGP211ToOP201SecurityInfo(gp211secInfo, secInfo);
end:
	if (gp211dapBlock)
		free(gp211dapBlock);
	return status;
}

/**
 * The function assumes that the Card Manager or Security Domain
 * uses an optional load file DAP using the SHA-1 message digest algorithm.
 * The loadFileDAP can be calculated using calculate_load_file_DAP() or must be NULL, if the card does not
 * need or support a Load File DAP in this situation, e.g. if you want to load a Executable Load File to the Card
 * Manager Security Domain.
 * In the case of delegated management a Load Token authorizing the INSTALL [for load] must be included.
 * Otherwise loadToken must be NULL. See OP201_calculate_load_token().
 * The term Executable Load File is equivalent to the Open Platform term Load File Data Block.
 * volatileDataSpaceLimit and nonVolatileDataSpaceLimit can be 0, if the card does not need or support this tags.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO cardInfo, structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the OP201_SECURITY_INFO structure returned by OP201_mutual_authentication().
 * \param executableLoadFileAID [in] A buffer with AID of the Executable Load File to INSTALL [for load].
 * \param executableLoadFileAIDLength [in] The length of the Executable Load File AID.
 * \param securityDomainAID [in] A buffer containing the AID of the intended associated Security Domain.
 * \param securityDomainAIDLength [in] The length of the Security Domain AID.
 * \param loadFileDataBlockHash [in] The load file DAP of the Executable Load File to INSTALL [for load].
 * \param loadFileDataBlockHashLength [in] The length of the loadFileDataBlockHash buffer.
 * \param loadToken [in] The Load Token. This is an assymetric (e.g., ECC or RSA) Signature.
 * \param loadTokenLength [in] The length of the loadToken buffer.
 * \param nonVolatileCodeSpaceLimit [in] The minimum amount of space that must be available to store the package.
 * \param volatileDataSpaceLimit [in] The minimum amount of RAM space that must be available.
 * \param nonVolatileDataSpaceLimit [in] The minimum amount of space for objects of the application, i.e. the data allocated in its lifetime.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_install_for_load(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, OP201_SECURITY_INFO *secInfo,
					  PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength, PBYTE securityDomainAID,
					  DWORD securityDomainAIDLength, PBYTE loadFileDataBlockHash,
					  DWORD loadFileDataBlockHashLength, PBYTE loadToken, DWORD loadTokenLength,
					  DWORD nonVolatileCodeSpaceLimit, DWORD volatileDataSpaceLimit,
					  DWORD nonVolatileDataSpaceLimit) {
	OPGP_ERROR_STATUS status;
	GP211_SECURITY_INFO gp211secInfo;
	mapOP201ToGP211SecurityInfo(*secInfo, &gp211secInfo);
	status = install_for_load(cardContext, cardInfo, &gp211secInfo, executableLoadFileAID,
		executableLoadFileAIDLength, securityDomainAID, securityDomainAIDLength,
		loadFileDataBlockHash, loadFileDataBlockHashLength,
		loadToken, loadTokenLength, nonVolatileCodeSpaceLimit,
		volatileDataSpaceLimit, nonVolatileDataSpaceLimit);
	mapGP211ToOP201SecurityInfo(gp211secInfo, secInfo);
	return status;
}

/**
 * In the case of delegated management an Install Token authorizing the INSTALL [for install] must be included. See OP201_calculate_install_token().
 * Otherwise installToken must be NULL. See calculate_install_token().
 * volatileDataSpaceLimit and nonVolatileDataSpaceLimit can be 0, if the card does not need or support this tag.
 * For Security domains look in your manual what parameters are necessary.
 * If the tag for application install parameters is mandatory for your card, but you have no install parameters
 * for the install() method of the application anyway you have to use at least a dummy parameter.
 * If AIDWithinLoadFileAID is NULL and AIDWithinLoadFileAIDLength is 0 applicationInstanceAID is assumed for AIDWithinLoadFileAID
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO cardInfo, structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the OP201_SECURITY_INFO structure returned by OP201_mutual_authentication().
 * \param executableLoadFileAID [in] A buffer with AID of the Executable Load File to INSTALL [for install].
 * \param executableLoadFileAIDLength [in] The length of the Executable Load File AID.
 * \param AIDWithinLoadFileAID [in] The AID of the application class in the package.
 * \param AIDWithinLoadFileAIDLength [in] The length of the AIDWithinLoadFileAID buffer.
 * \param applicationInstanceAID [in] The AID of the installed application.
 * \param applicationInstanceAIDLength [in] The length of the application instance AID.
 * \param applicationPrivileges [in] The application privileges. Can be an OR of multiple privileges. See OP201_APPLICATION_PRIVILEGE_SECURITY_DOMAIN.
 * \param volatileDataSpaceLimit [in] The minimum amount of RAM space that must be available.
 * \param nonVolatileDataSpaceLimit [in] The minimum amount of space for objects of the application, i.e. the data allocated in its lifetime.
 * \param applicationInstallParameters [in] Applet install parameters for the install() method of the application.
 * \param applicationInstallParametersLength [in] The length of the applicationInstallParameters buffer.
 * \param sdParameters [in] Security Domain install parameters encoded for tag C9 (e.g., tag 'C1' with tags '81','82','83','84','86','87').
 * \param sdParametersLength [in] The length of the sdParameters buffer.
 * \param uiccSystemSpecParams [in] UICC System Specific Parameters according to ETSI TS 102 226, sect. 8.2.1.3.2.2.
 * \param uiccSystemSpecParamsLength [in] The length of the uiccSystemSpecParams buffer.
 * \param simSpecParams [in] SIM File Access and Toolkit Application Specific Parameters according to ETSI TS 102 226, sect. 8.2.1.3.2.1.
 * \param simSpecParamsLength [in] The length of the simSpecParams buffer.
 * \param installToken [in] The Install Token. This is an assymetric (e.g., ECC or RSA) Signature.
 * \param installTokenLength [in] The length of the installToken buffer.
 * \param *receiptData [out] If the deletion is performed by a security domain with delegated management privilege
 * this structure contains the according data.
 * \param receiptDataAvailable [out] 0 if no receiptData is available.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_install_for_install(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, OP201_SECURITY_INFO *secInfo,
						 PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength, PBYTE AIDWithinLoadFileAID,
						 DWORD AIDWithinLoadFileAIDLength, PBYTE applicationInstanceAID,
						 DWORD applicationInstanceAIDLength, DWORD applicationPrivileges,
						 DWORD volatileDataSpaceLimit, DWORD nonVolatileDataSpaceLimit,
						 PBYTE applicationInstallParameters, DWORD applicationInstallParametersLength,
						 PBYTE sdParameters, DWORD sdParametersLength,
						 PBYTE uiccSystemSpecParams, DWORD uiccSystemSpecParamsLength,
						 PBYTE simSpecParams, DWORD simSpecParamsLength,
						 PBYTE installToken, DWORD installTokenLength,
						 OP201_RECEIPT_DATA *receiptData, PDWORD receiptDataAvailable) {
	OPGP_ERROR_STATUS status;
	GP211_SECURITY_INFO gp211secInfo;
	GP211_RECEIPT_DATA gp211receiptData;
	mapOP201ToGP211SecurityInfo(*secInfo, &gp211secInfo);
	status = install_for_install(cardContext, cardInfo, &gp211secInfo, executableLoadFileAID,
		executableLoadFileAIDLength, AIDWithinLoadFileAID, AIDWithinLoadFileAIDLength,
		applicationInstanceAID, applicationInstanceAIDLength,
		applicationPrivileges, volatileDataSpaceLimit, nonVolatileDataSpaceLimit,
		applicationInstallParameters, applicationInstallParametersLength,
		sdParameters, sdParametersLength,
		uiccSystemSpecParams, uiccSystemSpecParamsLength,
		simSpecParams, simSpecParamsLength,
		installToken, installTokenLength,
		&gp211receiptData, receiptDataAvailable);
	if (*receiptDataAvailable)
		mapGP211ToOP201ReceiptData(gp211receiptData, receiptData);
	mapGP211ToOP201SecurityInfo(gp211secInfo, secInfo);
	return status;
}

/**
 * In the case of delegated management an Install Token authorizing the INSTALL [for install and make selectable] must be included. See OP201_calculate_install_token().
 * Otherwise installToken must be NULL. See calculate_install_token().
 * volatileDataSpaceLimit and nonVolatileDataSpaceLimit can be 0, if the card does not need or support this tag.
 * For Security domains look in your manual what parameters are necessary.
 * If the tag for application install parameters is mandatory for your card, but you have no install parameters
 * for the install() method of the application anyway you have to use at least a dummy parameter.
 * If AIDWithinLoadFileAID is NULL and AIDWithinLoadFileAIDLength is 0 applicationInstanceAID is assumed for AIDWithinLoadFileAID.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO cardInfo, structure returned by OPGP_card_connect().
 * \param *secInfo [in, out] The pointer to the OP201_SECURITY_INFO structure returned by OP201_mutual_authentication().
 * \param executableLoadFileAID [in] A buffer with AID of the Executable Load File to INSTALL [for install].
 * \param executableLoadFileAIDLength [in] The length of the Executable Load File AID.
 * \param AIDWithinLoadFileAID [in] The AID of the application class in the package.
 * \param AIDWithinLoadFileAIDLength [in] The length of the AIDWithinLoadFileAID buffer.
 * \param applicationInstanceAID [in] The AID of the installed application.
 * \param applicationInstanceAIDLength [in] The length of the application instance AID.
 * \param applicationPrivileges [in] The application privileges. Can be an OR of multiple privileges. See OP201_APPLICATION_PRIVILEGE_SECURITY_DOMAIN.
 * \param volatileDataSpaceLimit [in] The minimum amount of RAM space that must be available.
 * \param nonVolatileDataSpaceLimit [in] The minimum amount of space for objects of the application, i.e. the data allocated in its lifetime.
 * \param applicationInstallParameters [in] Applet install parameters for the install() method of the application.
 * \param applicationInstallParametersLength [in] The length of the applicationInstallParameters buffer.
 * \param sdParameters [in] Security Domain install parameters encoded for tag C9 (e.g., tag 'C1' with tags '81','82','83','84','86','87').
 * \param sdParametersLength [in] The length of the sdParameters buffer.
 * \param uiccSystemSpecParams [in] UICC System Specific Parameters according to ETSI TS 102 226, sect. 8.2.1.3.2.2.
 * \param uiccSystemSpecParamsLength [in] The length of the uiccSystemSpecParams buffer.
 * \param simSpecParams [in] SIM File Access and Toolkit Application Specific Parameters according to ETSI TS 102 226, sect. 8.2.1.3.2.1.
 * \param simSpecParamsLength [in] The length of the simSpecParams buffer.
 * \param installToken [in] The Install Token. This is an assymetric (e.g., ECC or RSA) Signature.
 * \param installTokenLength [in] The length of the installToken buffer.
 * \param *receiptData [out] If the deletion is performed by a security domain with delegated management privilege
 * this structure contains the according data.
 * \param receiptDataAvailable [out] 0 if no receiptData is available.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_install_for_install_and_make_selectable(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, OP201_SECURITY_INFO *secInfo,
						 PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength, PBYTE AIDWithinLoadFileAID,
						 DWORD AIDWithinLoadFileAIDLength, PBYTE applicationInstanceAID,
						 DWORD applicationInstanceAIDLength, DWORD applicationPrivileges,
						 DWORD volatileDataSpaceLimit, DWORD nonVolatileDataSpaceLimit,
						 PBYTE applicationInstallParameters, DWORD applicationInstallParametersLength,
						 PBYTE sdParameters, DWORD sdParametersLength,
						 PBYTE uiccSystemSpecParams, DWORD uiccSystemSpecParamsLength,
						 PBYTE simSpecParams, DWORD simSpecParamsLength,
						 PBYTE installToken, DWORD installTokenLength,
						 OP201_RECEIPT_DATA *receiptData, PDWORD receiptDataAvailable) {
	OPGP_ERROR_STATUS status;
	GP211_SECURITY_INFO gp211secInfo;
	GP211_RECEIPT_DATA gp211receiptData;
	mapOP201ToGP211SecurityInfo(*secInfo, &gp211secInfo);
	status = install_for_install_and_make_selectable(cardContext, cardInfo, &gp211secInfo, executableLoadFileAID,
		executableLoadFileAIDLength, AIDWithinLoadFileAID, AIDWithinLoadFileAIDLength,
		applicationInstanceAID, applicationInstanceAIDLength,
		applicationPrivileges, volatileDataSpaceLimit, nonVolatileDataSpaceLimit,
		applicationInstallParameters, applicationInstallParametersLength,
		sdParameters, sdParametersLength,
		uiccSystemSpecParams, uiccSystemSpecParamsLength,
		simSpecParams, simSpecParamsLength,
		installToken, installTokenLength,
		&gp211receiptData, receiptDataAvailable);
 	if (*receiptDataAvailable) {
		mapGP211ToOP201ReceiptData(gp211receiptData, receiptData);
	}
	mapGP211ToOP201SecurityInfo(gp211secInfo, secInfo);
	return status;
}

/**
 * In the case of delegated management an Install Token authorizing the INSTALL [for make selectable] must be included. See OP201_calculate_install_token().
 * Otherwise installToken must be NULL.
 * For Security domains look in your manual what parameters are necessary.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param *secInfo [in, out] The pointer to the OP201_SECURITY_INFO structure returned by OP201_mutual_authentication().
 * \param cardInfo [in] The OPGP_CARD_INFO cardInfo, structure returned by OPGP_card_connect().
 * \param applicationInstanceAID [in] The AID of the installed application or security domain.
 * \param applicationInstanceAIDLength [in] The length of the application instance AID.
 * \param applicationPrivileges [in] The application privileges. Can be an OR of multiple privileges. See OP201_APPLICATION_PRIVILEGE_SECURITY_DOMAIN.
 * \param installToken [in] The Install Token. This is an assymetric (e.g., ECC or RSA) Signature.
 * \param installTokenLength [in] The length of the installToken buffer.
 * \param *receiptData [out] If the deletion is performed by a security domain with delegated management privilege
 * this structure contains the according data.
 * \param receiptDataAvailable [out] 0 if no receiptData is available.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_install_for_make_selectable(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, OP201_SECURITY_INFO *secInfo,
								 PBYTE applicationInstanceAID,
								 DWORD applicationInstanceAIDLength, DWORD applicationPrivileges,
								 PBYTE installToken, DWORD installTokenLength,
								 OP201_RECEIPT_DATA *receiptData,
								 PDWORD receiptDataAvailable) {
	OPGP_ERROR_STATUS status;
	GP211_SECURITY_INFO gp211secInfo;
	GP211_RECEIPT_DATA gp211receiptData;
	mapOP201ToGP211SecurityInfo(*secInfo, &gp211secInfo);
	status = install_for_make_selectable(cardContext, cardInfo, &gp211secInfo, applicationInstanceAID, applicationInstanceAIDLength,
		applicationPrivileges, installToken, installTokenLength,
		&gp211receiptData, receiptDataAvailable);
	if (*receiptDataAvailable)
		mapGP211ToOP201ReceiptData(gp211receiptData, receiptData);
	mapGP211ToOP201SecurityInfo(gp211secInfo, secInfo);
	return status;
}

/**
 * If you are not the Card Issuer and do not know the token verification private key send this data to the
 * Card Issuer and obtain the RSA signature of the data, i.e. the Install Token.
 * volatileDataSpaceLimit can be 0, if the card does not need or support this tag.
 * The parameters must match the parameters of a later install_for_install() and install_for_make_selectable() method.
 * \param P1 [in] The parameter P1 in the APDU command.
 * <ul>
 * <li> 0x04 for a INSTALL [for install] command </li>
 * <li> 0x08 for an INSTALL [for make selectable] command </li>
 * <li> 0x0C for an INSTALL [for install and make selectable] </li>
 * </ul>
 * \param executableLoadFileAID [in] A buffer with AID of the Executable Load File to INSTALL [for load].
 * \param executableLoadFileAIDLength [in] The length of the Executable Load File AID.
 * \param AIDWithinLoadFileAID [in] The AID of the application class in the package.
 * \param AIDWithinLoadFileAIDLength [in] The length of the AIDWithinLoadFileAID buffer.
 * \param applicationInstanceAID [in] The AID of the installed application.
 * \param applicationInstanceAIDLength [in] The length of the application instance AID.
 * \param applicationPrivileges [in] The application privileges. Can be an OR of multiple privileges. See OP201_APPLICATION_PRIVILEGE_SECURITY_DOMAIN.
 * \param volatileDataSpaceLimit [in] The minimum amount of RAM space that must be available.
 * \param nonVolatileDataSpaceLimit [in] The minimum amount of space for objects of the application, i.e. the data allocated in its lifetime.
 * \param applicationInstallParameters [in] Applet install parameters for the install() method of the application.
 * \param applicationInstallParametersLength [in] The length of the applicationInstallParameters buffer.
 * \param sdParameters [in] Security Domain install parameters encoded for tag C9 (e.g., tag 'C1' with tags '81','82','83','84','86','87').
 * \param sdParametersLength [in] The length of the sdParameters buffer.
 * \param uiccSystemSpecParams [in] UICC System Specific Parameters according to ETSI TS 102 226, sect. 8.2.1.3.2.2.
 * \param uiccSystemSpecParamsLength [in] The length of the uiccSystemSpecParams buffer.
 * \param simSpecParams [in] SIM File Access and Toolkit Application Specific Parameters according to ETSI TS 102 226, sect. 8.2.1.3.2.1.
 * \param simSpecParamsLength [in] The length of the simSpecParams buffer.
 * \param installTokenSignatureData [out] The data to sign in a Install Token.
 * \param installTokenSignatureDataLength [in, out] The length of the installTokenSignatureData buffer.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_get_install_token_signature_data(BYTE P1, PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength, PBYTE AIDWithinLoadFileAID,
									  DWORD AIDWithinLoadFileAIDLength, PBYTE applicationInstanceAID,
									  DWORD applicationInstanceAIDLength, DWORD applicationPrivileges,
									  DWORD volatileDataSpaceLimit, DWORD nonVolatileDataSpaceLimit,
									  PBYTE applicationInstallParameters, DWORD applicationInstallParametersLength,
									  PBYTE sdParameters, DWORD sdParametersLength,
									  PBYTE uiccSystemSpecParams, DWORD uiccSystemSpecParamsLength,
									  PBYTE simSpecParams, DWORD simSpecParamsLength,
									  PBYTE installTokenSignatureData, PDWORD installTokenSignatureDataLength) {
	return get_install_data(P1, executableLoadFileAID, executableLoadFileAIDLength,
		AIDWithinLoadFileAID, AIDWithinLoadFileAIDLength, applicationInstanceAID,
		applicationInstanceAIDLength, applicationPrivileges, volatileDataSpaceLimit,
		nonVolatileDataSpaceLimit, applicationInstallParameters, applicationInstallParametersLength,
		sdParameters, sdParametersLength,
		uiccSystemSpecParams, uiccSystemSpecParamsLength,
		simSpecParams, simSpecParamsLength,
		installTokenSignatureData, installTokenSignatureDataLength);
}

/**
 * The parameters must match the parameters of a later install_for_install(), install_for_make_selectable() and install_for_install_and_make_selectable() method.
 * \param P1 [in] The parameter P1 in the APDU command.
 * <ul>
 * <li> 0x04 for a INSTALL [for install] command </li>
 * <li> 0x08 for an INSTALL [for make selectable] command </li>
 * <li> 0x0C for an INSTALL [for install and make selectable] </li>
 * </ul>
 * \param executableLoadFileAID [in] A buffer with AID of the Executable Load File to INSTALL [for install].
 * \param executableLoadFileAIDLength [in] The length of the Executable Load File AID.
 * \param AIDWithinLoadFileAID [in] The AID of the application class in the package.
 * \param AIDWithinLoadFileAIDLength [in] The length of the AIDWithinLoadFileAID buffer.
 * \param applicationInstanceAID [in] The AID of the installed application.
 * \param applicationInstanceAIDLength [in] The length of the application instance AID.
 * \param applicationPrivileges [in] The application privileges. Can be an OR of multiple privileges. See OP201_APPLICATION_PRIVILEGE_SECURITY_DOMAIN.
 * \param volatileDataSpaceLimit [in] The minimum amount of RAM space that must be available.
 * \param nonVolatileDataSpaceLimit [in] The minimum amount of space for objects of the application, i.e. the data allocated in its lifetime.
 * \param applicationInstallParameters [in] Applet install parameters for the install() method of the application.
 * \param applicationInstallParametersLength [in] The length of the applicationInstallParameters buffer.
 * \param sdParameters [in] Security Domain install parameters encoded for tag C9 (e.g., tag 'C1' with tags '81','82','83','84','86','87').
 * \param sdParametersLength [in] The length of the sdParameters buffer.
 * \param uiccSystemSpecParams [in] UICC System Specific Parameters according to ETSI TS 102 226, sect. 8.2.1.3.2.2.
 * \param uiccSystemSpecParamsLength [in] The length of the uiccSystemSpecParams buffer.
 * \param simSpecParams [in] SIM File Access and Toolkit Application Specific Parameters according to ETSI TS 102 226, sect. 8.2.1.3.2.1.
 * \param simSpecParamsLength [in] The length of the simSpecParams buffer.
 * \param installToken [out] The calculated Install Token. A 1024 bit RSA signature.
 * \param installTokenLength [in, out] The length of the installToken buffer.
 * \param PEMKeyFileName [in] A PEM file name with the private RSA key.
 * \param *passPhrase [in] The passphrase. Must be an ASCII string.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_calculate_install_token(BYTE P1, PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength, PBYTE AIDWithinLoadFileAID,
							 DWORD AIDWithinLoadFileAIDLength, PBYTE applicationInstanceAID, DWORD applicationInstanceAIDLength,
							 DWORD applicationPrivileges, DWORD volatileDataSpaceLimit, DWORD nonVolatileDataSpaceLimit,
							 PBYTE applicationInstallParameters, DWORD applicationInstallParametersLength,
							 PBYTE sdParameters, DWORD sdParametersLength,
							 PBYTE uiccSystemSpecParams, DWORD uiccSystemSpecParamsLength,
							 PBYTE simSpecParams, DWORD simSpecParamsLength,
							 PBYTE installToken, PDWORD installTokenLength,
							 OPGP_STRING PEMKeyFileName, char *passPhrase) {
	OPGP_ERROR_STATUS status;
	status = calculate_install_token(P1, executableLoadFileAID, executableLoadFileAIDLength,
		AIDWithinLoadFileAID, AIDWithinLoadFileAIDLength, applicationInstanceAID, applicationInstanceAIDLength,
		applicationPrivileges, volatileDataSpaceLimit, nonVolatileDataSpaceLimit,
		applicationInstallParameters, applicationInstallParametersLength,
		sdParameters, sdParametersLength,
		uiccSystemSpecParams, uiccSystemSpecParamsLength,
		simSpecParams, simSpecParamsLength,
		installToken, installTokenLength,
		PEMKeyFileName, passPhrase);
	return status;
}

/**
 * If you are not the Card Issuer and do not know the token verification private key send this data to the
 * Card Issuer and obtain the RSA signature of the data, i.e. the Load Token.
 * volatileDataSpaceLimit and nonVolatileDataSpaceLimit can be 0, if the card does not need or support this tags.
 * The parameters must match the parameters of a later install_for_load() command.
 * \param executableLoadFileAID [in] A buffer containing the Executable Load File AID.
 * \param executableLoadFileAIDLength [in] The length of the Executable Load File AID.
 * \param securityDomainAID [in] A buffer containing the Security Domain AID.
 * \param securityDomainAIDLength [in] The length of the Security Domain AID.
 * \param loadFileDataBlockHash [in] The Load File DAP. The same calculated as in install_for_load().
 * \param loadFileDataBlockHashLength [in] The length of the loadFileDataBlockHash buffer.
 * \param nonVolatileCodeSpaceLimit [in] The minimum space required to store the application code.
 * \param volatileDataSpaceLimit [in] The minimum amount of RAM space that must be available.
 * \param nonVolatileDataSpaceLimit [in] The minimum amount of space for objects of the application, i.e. the data allocated in its lifetime.
 * \param loadTokenSignatureData [out] The data to sign in a Load Token.
 * \param loadTokenSignatureDataLength [in, out] The length of the loadTokenSignatureData buffer.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_get_load_token_signature_data(PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength, PBYTE securityDomainAID,
								   DWORD securityDomainAIDLength, PBYTE loadFileDataBlockHash,
								   DWORD loadFileDataBlockHashLength,
								   DWORD nonVolatileCodeSpaceLimit, DWORD volatileDataSpaceLimit,
								   DWORD nonVolatileDataSpaceLimit, PBYTE loadTokenSignatureData,
								   PDWORD loadTokenSignatureDataLength) {
	BYTE buf[258];
	DWORD i=0;
#ifdef OPGP_DEBUG
	DWORD j=0;
#endif
	DWORD hiByte, loByte;
	DWORD staticSize;
	OPGP_ERROR_STATUS status;
	OPGP_LOG_START(_T("OP201_get_load_token_signature_data"));
	if (loadFileDataBlockHash == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, OP201_ERROR_LOAD_FILE_DAP_NULL, OPGP_stringify_error(OP201_ERROR_LOAD_FILE_DAP_NULL));
		goto end;
	}
	buf[i++] = 0x02;
	buf[i++] = 0x00;
	buf[i++] = 0x00; // Lc dummy
	buf[i++] = (BYTE)executableLoadFileAIDLength; // Executable Load File AID
	memcpy(buf+i, executableLoadFileAID, executableLoadFileAIDLength);
	i+=executableLoadFileAIDLength;
	buf[i++] = (BYTE)securityDomainAIDLength; // Security Domain AID
	memcpy(buf+i, securityDomainAID, securityDomainAIDLength);
	i+=securityDomainAIDLength;
	if ((volatileDataSpaceLimit != 0) || (nonVolatileCodeSpaceLimit != 0) ||
(nonVolatileDataSpaceLimit != 0)) {
		buf[i++] = 0x02; // load parameter field
		if (volatileDataSpaceLimit != 0)
			buf[i-1] += 4;
		if (nonVolatileDataSpaceLimit != 0)
			buf[i-1] += 4;
		if (nonVolatileCodeSpaceLimit != 0)
			buf[i-1] += 4;
		buf[i++] = 0xEF;
		buf[i++] = 0x00;
		if (volatileDataSpaceLimit != 0)
			buf[i-1] += 4;
		if (nonVolatileDataSpaceLimit != 0)
			buf[i-1] += 4;
		if (nonVolatileCodeSpaceLimit != 0)
			buf[i-1] += 4;
		if (nonVolatileCodeSpaceLimit != 0) {
			buf[i++] = 0xC6; // non-volatile code space limit.
			buf[i++] = 0x02; //
			staticSize = 8 - (nonVolatileCodeSpaceLimit % 8) + 8;
            nonVolatileCodeSpaceLimit += staticSize;
			hiByte = nonVolatileCodeSpaceLimit >> 8;
			loByte = nonVolatileCodeSpaceLimit - (hiByte << 8);
			buf[i++] = (BYTE)hiByte; // minimum amount
			buf[i++] = (BYTE)loByte; // of space needed
		}
		if (volatileDataSpaceLimit != 0) {
			buf[i++] = 0xC7;
			buf[i++] = 0x02;
			hiByte = volatileDataSpaceLimit >> 8;
			loByte = volatileDataSpaceLimit - (hiByte << 8);
			buf[i++] = (BYTE)hiByte;
			buf[i++] = (BYTE)loByte;
		}
		if (nonVolatileDataSpaceLimit != 0) {
			buf[i++] = 0xC8;
			buf[i++] = 0x02;
			hiByte = nonVolatileDataSpaceLimit >> 8;
			loByte = nonVolatileDataSpaceLimit - (hiByte << 8);
			buf[i++] = (BYTE)hiByte;
			buf[i++] = (BYTE)loByte;
		}
	}
	else buf[i++] = 0x00;

	memcpy(buf+i, loadFileDataBlockHash, loadFileDataBlockHashLength);
	i+=loadFileDataBlockHashLength;

	buf[2] = (BYTE)i-3;
	if (i > *loadTokenSignatureDataLength)
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INSUFFICIENT_BUFFER, OPGP_stringify_error(OPGP_ERROR_INSUFFICIENT_BUFFER)); goto end; }
	memcpy(loadTokenSignatureData, buf, i);
	*loadTokenSignatureDataLength = i;
#ifdef OPGP_DEBUG
	OPGP_log_Msg(_T("OP201_get_load_token_signature_data: P1: 0x%02x"), loadTokenSignatureData[j++]);
	OPGP_log_Msg(_T("OP201_get_load_token_signature_data: P2: 0x%02x"), loadTokenSignatureData[j++]);
	OPGP_log_Msg(_T("OP201_get_load_token_signature_data: Lc: 0x%02x"), loadTokenSignatureData[j++]);
	OPGP_log_Msg(_T("OP201_get_load_token_signature_data: Load file AID length indicator: 0x%02x"), loadTokenSignatureData[j++]);
	OPGP_LOG_HEX(_T("OP201_get_load_token_signature_data: Load file AID: "), loadTokenSignatureData+(j-1), *loadTokenSignatureDataLength-(j-1));
	j+=loadTokenSignatureData[j-1];
	OPGP_log_Msg(_T("OP201_get_load_token_signature_data: Security Domain AID length indicator: 0x%02x"), loadTokenSignatureData[j++]);
	OPGP_LOG_HEX(_T("OP201_get_load_token_signature_data: Security Domain AID: "), loadTokenSignatureData+(j-1), *loadTokenSignatureDataLength-(j-1));
	j+=loadTokenSignatureData[j-1];
	OPGP_log_Msg(_T("OP201_get_load_token_signature_data: Load parameters length indicator: 0x%02x"), loadTokenSignatureData[j++]);
	OPGP_LOG_HEX(_T("OP201_get_load_token_signature_data: Load parameters: "), loadTokenSignatureData+(j-1), *loadTokenSignatureDataLength-(j-1));
	j+=loadTokenSignatureData[j-1];
	OPGP_LOG_HEX(_T("OP201_get_load_token_signature_data: Hash of Load File: "), loadTokenSignatureData+j, loadFileDataBlockHashLength);
	j += loadFileDataBlockHashLength;
#endif
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("OP201_get_load_token_signature_data"), status);
	return status;
}

/**
 * The parameters must match the parameters of a later install_for_load() method.
 * \param executableLoadFileAID [in] A buffer containing the Executable Load File AID.
 * \param executableLoadFileAIDLength [in] The length of the Executable Load File AID.
 * \param securityDomainAID [in] A buffer containing the Security Domain AID.
 * \param securityDomainAIDLength [in] The length of the Security Domain AID.
 * \param loadFileDAP [in] The Load File DAP. The same calculated as in install_for_load().
 * \param loadFileDAPLength [in] The length of the loadFileDAP buffer.
 * \param nonVolatileCodeSpaceLimit [in] The minimum space required to store the package.
 * \param volatileDataSpaceLimit [in] The minimum amount of RAM space that must be available.
 * \param nonVolatileDataSpaceLimit [in] The minimum amount of space for objects of the application, i.e. the data allocated in its lifetime.
 * \param loadToken [out] The calculated Load Token. A 1024 bit RSA signature.
 * \param loadTokenLength [in, out] The length of the loadToken buffer.
 * \param PEMKeyFileName [in] A PEM file name with the private RSA key.
 * \param *passPhrase [in] The passphrase. Must be an ASCII string.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_calculate_load_token(PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength, PBYTE securityDomainAID,
						  DWORD securityDomainAIDLength, PBYTE loadFileDAP,
						  DWORD loadFileDAPLength,
						  DWORD nonVolatileCodeSpaceLimit, DWORD volatileDataSpaceLimit,
						  DWORD nonVolatileDataSpaceLimit, PBYTE loadToken, PDWORD loadTokenLength,
						  OPGP_STRING PEMKeyFileName, char *passPhrase) {
	OPGP_ERROR_STATUS status;
	BYTE loadTokenSignatureData[256];
	DWORD loadTokenSignatureDataLength = 256;
	OPGP_LOG_START(_T("calculate_load_token"));
	status = OP201_get_load_token_signature_data(executableLoadFileAID, executableLoadFileAIDLength, securityDomainAID, securityDomainAIDLength,
		loadFileDAP, loadFileDAPLength, nonVolatileCodeSpaceLimit, volatileDataSpaceLimit,
		nonVolatileDataSpaceLimit, loadTokenSignatureData, &loadTokenSignatureDataLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	status = calculate_signature(loadTokenSignatureData, loadTokenSignatureDataLength, PEMKeyFileName,
									passPhrase, loadToken, loadTokenLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("calculate_load_token"), status);
	return status;
}

/**
 * This is a hash of the Load File with SHA-1.
 * A Load File consists of 0 to n Load File Data Block DAP blocks and a mandatory
 * Load File Data Block, e.g. a CAP file.
 * If no Load File Data Block DAP blocks are necessary the dapBlock must be NULL and the dapBlockLength 0.
 * The dapBlock(s) can be calculated using calculate_3des_dap() or calculate_rsa_dap().
 * If the Load File Data Block DAP block(s) are already calculated they must be parsed into a OP201_DAP_BLOCK structure.
 * If the Load File Data Block DAP block(s) are already prefixing the CAPFile following the Open Platform Specification 2.0.1',
 * the whole CAPFile including the Load File Data Block DAP block(s) is sufficient, the dapBlock must be NULL and the dapBlockLength 0.
 * \param *dapBlock [in] A pointer to OP201_DAP_BLOCK structure(s).
 * \param dapBlockLength [in] The number of OP201_DAP_BLOCK structure(s).
 * \param executableLoadFileName [in] The name of the CAP or IJC file to hash.
 * \param hash [out] The hash value. This are 20 bytes.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_calculate_load_file_DAP(OP201_DAP_BLOCK *dapBlock, DWORD dapBlockLength, OPGP_STRING executableLoadFileName,
							 BYTE hash[20])
{
	OPGP_ERROR_STATUS status;
	int count;
	DWORD k,i;
	BYTE buf[4];

	BYTE dapBuf[256];
	DWORD dapBufSize=sizeof(dapBuf);

	PBYTE loadFileBuf = NULL;
	DWORD loadFileBufSize;

	PBYTE temp = NULL;
	DWORD tempSize = 0;

	OPGP_LOG_START(_T("OP201_calculate_load_file_DAP"));
	if ((executableLoadFileName == NULL) || (_tcslen(executableLoadFileName) == 0))
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_FILENAME, OPGP_stringify_error(OPGP_ERROR_INVALID_FILENAME)); goto end; }

	for (i=0; i<dapBlockLength; i++) {
		OPGP_LOG_MSG(_T("OP201_calculate_load_file_DAP: Hashing DAP block %lu."), i);
		k = dapBufSize;
		status = readDAPBlock(dapBuf, &k, dapBlock[i]);
		if (OPGP_ERROR_CHECK(status)) {
			goto end;
		}
		tempSize += k;
		temp = realloc(temp, tempSize);
		if (temp == NULL) {
			OPGP_ERROR_CREATE_ERROR(status, ENOMEM, OPGP_stringify_error(ENOMEM));
			goto end;
		}
		memcpy(temp+tempSize-k, dapBuf, k);
	}

	status = handle_load_file((OPGP_CSTRING)executableLoadFileName, loadFileBuf, &loadFileBufSize);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	loadFileBuf = (PBYTE)malloc(sizeof(BYTE) * loadFileBufSize);
	if (loadFileBuf == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, ENOMEM, OPGP_stringify_error(ENOMEM));
		goto end;
	}
	status = handle_load_file((OPGP_CSTRING)executableLoadFileName, loadFileBuf, &loadFileBufSize);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	if (loadFileBufSize < 128L) {
		buf[0] = 0xC4;
		buf[1] = (BYTE)loadFileBufSize;
		count=2;
	}
	else if (loadFileBufSize < 256L) {
		buf[0] = 0xC4;
		buf[1] = 0x81;
		buf[2] = (BYTE)loadFileBufSize;
		count=3;
	}
	else if (loadFileBufSize < 32536L) {
		buf[0] = 0xC4;
		buf[1] = 0x82;
		buf[2] = (BYTE)(loadFileBufSize >> 8);
		buf[3] = (BYTE)(loadFileBufSize - (buf[2] << 8));
		count=4;
	}
	else {
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_APPLICATION_TOO_BIG, OPGP_stringify_error(OPGP_ERROR_APPLICATION_TOO_BIG)); goto end; }
	}
	tempSize += count;
	temp = realloc(temp, tempSize);
	if (temp == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, ENOMEM, OPGP_stringify_error(ENOMEM));
		goto end;
	}
	memcpy(temp+tempSize-count, buf, count);

	tempSize += loadFileBufSize;
	temp = realloc(temp, tempSize);
	if (temp == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, ENOMEM, OPGP_stringify_error(ENOMEM));
		goto end;
	}
	memcpy(temp+tempSize-loadFileBufSize, loadFileBuf, loadFileBufSize);

	status = calculate_sha1_hash(temp, tempSize, hash);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	if (loadFileBuf != NULL) {
		free(loadFileBuf);
	}
	if (temp != NULL) {
		free(temp);
	}
	OPGP_LOG_END(_T("calculate_load_file_DAP"), status);
	return status;
}

/**
 * If a security domain has DAP verification privilege the security domain validates this DAP.
 * \param securityDomainAID [in] A buffer containing the Security Domain AID.
 * \param securityDomainAIDLength [in] The length of the Security Domain AID.
 * \param executableLoadFileName [in] The name of the CAP or IJC file to calculate the DAP for.
 * \param DAP_verification_key [in] The key to calculate the DAP.
 * \param *dapBlock [out] A pointer to the returned OP201_DAP_BLOCK structure.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_calculate_3des_DAP(PBYTE securityDomainAID, DWORD securityDomainAIDLength, OPGP_STRING executableLoadFileName,
						BYTE DAP_verification_key[16], OP201_DAP_BLOCK *dapBlock)
{
	OPGP_ERROR_STATUS status;
	PBYTE loadFileBuf = NULL;
	DWORD loadFileBufSize;
	OPGP_LOG_START(_T("calculate_3des_DAP"));
	if ((executableLoadFileName == NULL) || (_tcslen(executableLoadFileName) == 0))
		{ OPGP_ERROR_CREATE_ERROR(status, OPGP_ERROR_INVALID_FILENAME, OPGP_stringify_error(OPGP_ERROR_INVALID_FILENAME)); goto end; }

	status = handle_load_file((OPGP_CSTRING)executableLoadFileName, loadFileBuf, &loadFileBufSize);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	loadFileBuf = (PBYTE)malloc(sizeof(BYTE) * loadFileBufSize);
	if (loadFileBuf == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, ENOMEM, OPGP_stringify_error(ENOMEM));
		goto end;
	}
	status = handle_load_file((OPGP_CSTRING)executableLoadFileName, loadFileBuf, &loadFileBufSize);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	status = calculate_MAC_des_3des(DAP_verification_key, loadFileBuf, loadFileBufSize, NULL, dapBlock->signature);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	dapBlock->signatureLength = 8;
	memcpy(dapBlock->securityDomainAID, securityDomainAID, securityDomainAIDLength);
	dapBlock->securityDomainAIDLength = (BYTE)securityDomainAIDLength;

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	if (loadFileBuf != NULL) {
		free(loadFileBuf);
	}
	OPGP_LOG_END(_T("calculate_3des_DAP"), status);
	return status;
}

/**
 * If a security domain has DAP verification privilege the security domain validates this DAP.
 * \param securityDomainAID [in] A buffer containing the Security Domain AID.
 * \param securityDomainAIDLength [in] The length of the Security Domain AID.
 * \param executableLoadFileName [in] The name of the CAP or IJC file to calculate the DAP for.
 * \param PEMKeyFileName [in] A PEM file name with the private RSA key.
 * \param *passPhrase [in] The passphrase. Must be an ASCII string.
 * \param *dapBlock [out] A pointer to the returned OP201_DAP_BLOCK structure.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_calculate_rsa_DAP(PBYTE securityDomainAID, DWORD securityDomainAIDLength, OPGP_STRING executableLoadFileName,
					   OPGP_STRING PEMKeyFileName, char *passPhrase, OP201_DAP_BLOCK *dapBlock)
{
	OPGP_ERROR_STATUS status;
	PBYTE loadFileBuf = NULL;
	DWORD loadFileBufSize;

	OPGP_LOG_START(_T("calculate_rsa_DAP"));

	status = handle_load_file((OPGP_CSTRING)executableLoadFileName, loadFileBuf, &loadFileBufSize);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	loadFileBuf = (PBYTE)malloc(sizeof(BYTE) * loadFileBufSize);
	if (loadFileBuf == NULL) {
		OPGP_ERROR_CREATE_ERROR(status, ENOMEM, OPGP_stringify_error(ENOMEM));
		goto end;
	}
	status = handle_load_file((OPGP_CSTRING)executableLoadFileName, loadFileBuf, &loadFileBufSize);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	DWORD sigLen = sizeof(dapBlock->signature);
	status = calculate_signature(loadFileBuf, loadFileBufSize, PEMKeyFileName, passPhrase, dapBlock->signature, &sigLen);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}

	dapBlock->signatureLength = (BYTE)sigLen;
	memcpy(dapBlock->securityDomainAID, securityDomainAID, securityDomainAIDLength);
	dapBlock->securityDomainAIDLength = (BYTE)securityDomainAIDLength;

	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	if (loadFileBuf != NULL) {
		free(loadFileBuf);
	}
	OPGP_LOG_END(_T("calculate_rsa_DAP"), status);
	return status;
}

/**
 * Each time a receipt is generated the confirmation counter is incremented by the Card Manager.
 * You may keep track of it. Returns OPGP_ERROR_SUCCESS if receipt is valid.
 * \param confirmationCounter [in] The confirmation counter.
 * \param cardUniqueData [in] The card unique data (?).
 * \param receiptGenerationKey [in] The 3DES key to generate the receipt.
 * \param receiptData [in] The OP201_RECEIPT_DATA structure containing the receipt returned
 * from load_application() to verify.
 * \param executableLoadFileAID [in] A buffer with AID of the Executable Load File which was INSTALL [for load].
 * \param executableLoadFileAIDLength [in] The length of the Executable Load File AID.
 * \param securityDomainAID [in] A buffer containing the AID of the associated Security Domain.
 * \param securityDomainAIDLength [in] The length of the Security Domain AID.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_validate_load_receipt(DWORD confirmationCounter, BYTE cardUniqueData[10],
						   BYTE receiptGenerationKey[16], OP201_RECEIPT_DATA receiptData,
						   PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength,
						   PBYTE securityDomainAID, DWORD securityDomainAIDLength) {
	OPGP_ERROR_STATUS status;
	GP211_RECEIPT_DATA gp211receiptData;
	mapOP201ToGP211ReceiptData(receiptData, &gp211receiptData);
	gp211receiptData.confirmationCounterLength = 2;
	gp211receiptData.confirmationCounter[0] = (BYTE)((confirmationCounter >> 8) & 0xFF);
	gp211receiptData.confirmationCounter[1] = (BYTE)(confirmationCounter & 0xFF);
	gp211receiptData.cardUniqueDataLength = 10;
	memcpy(gp211receiptData.cardUniqueData, cardUniqueData, 10);
	status = validate_load_receipt(receiptGenerationKey, 16, gp211receiptData, executableLoadFileAID,
		executableLoadFileAIDLength, securityDomainAID, securityDomainAIDLength,
		GP211_KEY_TYPE_DES, NULL, NULL);
	return status;
}

/**
 * Each time a receipt is generated the confirmation counter is incremented by the Card Manager.
 * You may keep track of it. Returns OPGP_ERROR_SUCCESS if receipt is valid.
 * \param confirmationCounter [in] The confirmation counter.
 * \param cardUniqueData [in] The card unique data (?).
 * \param receiptGenerationKey [in] The 3DES key to generate the receipt.
 * \param receiptData [in] The OP201_RECEIPT_DATA structure containing the receipt returned
 * from install_for_install() to verify.
 * \param executableLoadFileAID [in] A buffer with AID of the Executable Load File which was INSTALL [for install].
 * \param executableLoadFileAIDLength [in] The length of the Executable Load File AID.
 * \param applicationInstanceAID [in] The AID of the installed application.
 * \param applicationInstanceAIDLength [in] The length of the application instance AID.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_validate_install_receipt(DWORD confirmationCounter, BYTE cardUniqueData[10],
						   BYTE receiptGenerationKey[16], OP201_RECEIPT_DATA receiptData,
						   PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength,
						   PBYTE applicationInstanceAID, DWORD applicationInstanceAIDLength) {
	OPGP_ERROR_STATUS status;
	GP211_RECEIPT_DATA gp211receiptData;
	mapOP201ToGP211ReceiptData(receiptData, &gp211receiptData);
	gp211receiptData.confirmationCounterLength = 2;
	gp211receiptData.confirmationCounter[0] = (BYTE)((confirmationCounter >> 8) & 0xFF);
	gp211receiptData.confirmationCounter[1] = (BYTE)(confirmationCounter & 0xFF);
	gp211receiptData.cardUniqueDataLength = 10;
	memcpy(gp211receiptData.cardUniqueData, cardUniqueData, 10);
	status = validate_install_receipt(receiptGenerationKey, 16, gp211receiptData, executableLoadFileAID,
		executableLoadFileAIDLength, applicationInstanceAID, applicationInstanceAIDLength,
		GP211_KEY_TYPE_DES, NULL, NULL);
	return status;
}

/**
 * Each time a receipt is generated the confirmation counter is incremented by the Card Manager.
 * You may keep track of it. Returns OPGP_ERROR_SUCCESS if receipt is valid.
 * \param confirmationCounter [in] The confirmation counter.
 * \param cardUniqueData [in] The card unique data (?).
 * \param receiptGenerationKey [in] The 3DES key to generate the receipt.
 * \param receiptData [in] The OP201_RECEIPT_DATA structure containing the receipt returned
 * from delete_application() to verify.
 * \param AID [in] A buffer with AID of the application which was deleted.
 * \param AIDLength [in] The length of the AID.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_validate_delete_receipt(DWORD confirmationCounter, BYTE cardUniqueData[10],
						   BYTE receiptGenerationKey[16], OP201_RECEIPT_DATA receiptData,
						   PBYTE AID, DWORD AIDLength) {
	OPGP_ERROR_STATUS status;
	GP211_RECEIPT_DATA gp211receiptData;
	mapOP201ToGP211ReceiptData(receiptData, &gp211receiptData);
	gp211receiptData.confirmationCounterLength = 2;
	gp211receiptData.confirmationCounter[0] = (BYTE)((confirmationCounter >> 8) & 0xFF);
	gp211receiptData.confirmationCounter[1] = (BYTE)(confirmationCounter & 0xFF);
	gp211receiptData.cardUniqueDataLength = 10;
	memcpy(gp211receiptData.cardUniqueData, cardUniqueData, 10);
	status = validate_delete_receipt(receiptGenerationKey, 16, gp211receiptData, AID, AIDLength,
		GP211_KEY_TYPE_DES, NULL, NULL);
	return status;
}

/**
 * The single numbers of the new PIN are encoded as single BYTEs in the newPIN buffer.
 * The tryLimit must be in the range of 0x03 and x0A.
 * The PIN must comprise at least 6 numbers and not exceeding 12 numbers.
 * To unblock the PIN use tryLimit with a value of 0x00. In this case newPIN buffer and newPINLength are ignored.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param *secInfo [in, out] The pointer to the OP201_SECURITY_INFO structure returned by OP201_mutual_authentication().
 * \param cardInfo [in] The OPGP_CARD_INFO cardInfo, structure returned by OPGP_card_connect().
 * \param tryLimit [in] The try limit for the PIN.
 * \param newPIN [in] The new PIN.
 * \param newPINLength [in] The length of the new PIN.
 * \param KEK [in] The Key Encryption key (KEK).
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_pin_change(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, OP201_SECURITY_INFO *secInfo, BYTE tryLimit,
				PBYTE newPIN, DWORD newPINLength, BYTE KEK[16]) {
	OPGP_ERROR_STATUS status;
	GP211_SECURITY_INFO gp211secInfo;
	mapOP201ToGP211SecurityInfo(*secInfo, &gp211secInfo);
	memcpy(gp211secInfo.dataEncryptionSessionKey, KEK, 16);
	status = pin_change(cardContext, cardInfo, &gp211secInfo, tryLimit, newPIN, newPINLength);
	mapGP211ToOP201SecurityInfo(gp211secInfo, secInfo);
	return status;
}

/**
 * A keySetVersion and keyIndex of 0x00 selects the first available key set version and key index.
 * If a derivation method is used the baseKey defines the master key.
 * \param cardContext [in] The valid OPGP_CARD_CONTEXT returned by OPGP_establish_context()
 * \param cardInfo [in] The OPGP_CARD_INFO cardInfo, structure returned by OPGP_card_connect().
 * \param baseKey [in] The master key used for the key derivation.
 * \param encKey [in] The static encryption key.
 * \param macKey [in] The static MAC key.
 * \param kekKey [in] The static Key Encryption key.
 * \param keySetVersion [in] The key set version on the card to use for mutual authentication.
 * \param keyIndex [in] The key index of the encryption key in the key set version on the card to use for mutual authentication.
 * \param securityLevel [in] The requested security level.
 * \param derivationMethod [in] The derivation method to use for. See OPGP_DERIVATION_METHOD_VISA2.
 * \param *secInfo [out] The returned OP201_SECURITY_INFO structure.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OP201_mutual_authentication(OPGP_CARD_CONTEXT cardContext, OPGP_CARD_INFO cardInfo, PBYTE baseKey, PBYTE encKey, PBYTE macKey,
								 PBYTE kekKey,
								 BYTE keySetVersion,
								 BYTE keyIndex, BYTE securityLevel,
								 BYTE derivationMethod,
								 OP201_SECURITY_INFO *secInfo) {
	OPGP_ERROR_STATUS status;
	GP211_SECURITY_INFO gp211secInfo;
	status = mutual_authentication(cardContext, cardInfo, baseKey, encKey, macKey, kekKey, 16, keySetVersion,
		keyIndex, GP211_SCP01, GP211_SCP01_IMPL_i05, securityLevel, derivationMethod, NULL, 0,
		NULL, 0, NULL, &gp211secInfo);
	mapGP211ToOP201SecurityInfo(gp211secInfo, secInfo);
	return status;
}

OPGP_ERROR_STATUS calculate_install_token(BYTE P1, PBYTE executableLoadFileAID, DWORD executableLoadFileAIDLength,
							 PBYTE executableModuleAID,
							 DWORD executableModuleAIDLength, PBYTE applicationAID, DWORD applicationAIDLength,
							 DWORD applicationPrivileges, DWORD volatileDataSpaceLimit, DWORD nonVolatileDataSpaceLimit,
							 PBYTE installParameters, DWORD installParametersLength,
							 PBYTE sdParameters, DWORD sdParametersLength,
							 PBYTE uiccSystemSpecParams, DWORD uiccSystemSpecParamsLength,
							 PBYTE simSpecParams, DWORD simSpecParamsLength,
							 PBYTE installToken, PDWORD installTokenLength,
							 OPGP_STRING PEMKeyFileName, char *passPhrase) {
	OPGP_ERROR_STATUS status;
	BYTE installTokenSignatureData[256];
	DWORD installTokenSignatureDataLength = 256;
	OPGP_LOG_START(_T("calculate_install_token"));
	status = get_install_data(P1, executableLoadFileAID, executableLoadFileAIDLength,
		executableModuleAID, executableModuleAIDLength, applicationAID,
		applicationAIDLength, applicationPrivileges, volatileDataSpaceLimit,
		nonVolatileDataSpaceLimit, installParameters, installParametersLength,
		sdParameters, sdParametersLength,
		uiccSystemSpecParams, uiccSystemSpecParamsLength,
		simSpecParams, simSpecParamsLength,
		installTokenSignatureData, &installTokenSignatureDataLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	status = calculate_signature(installTokenSignatureData, installTokenSignatureDataLength, PEMKeyFileName,
									passPhrase, installToken, installTokenLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:

	OPGP_LOG_END(_T("calculate_install_token"), status);
	return status;
}

/**
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param keyType [in] The key type. See GP211_KEY_TYPE_AES.
 * \param keyData [in] The key data.
 * \param keyDataLength [in] The key data length.
 * \param keyCheckValue [out] The key check value.
  * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OPGP_calculate_key_check_value(GP211_SECURITY_INFO *secInfo,
	BYTE keyType,
	PBYTE keyData,
	DWORD keyDataLength,
	BYTE keyCheckValue[3]) {
	OPGP_ERROR_STATUS status;
	OPGP_LOG_START(_T("OPGP_calculate_key_check_value"));
	status = calculate_key_check_value(secInfo, keyType, keyData, keyDataLength, keyCheckValue);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("OPGP_calculate_key_check_value"), status);
	return status;
}

/**
 * \param *secInfo [in, out] The pointer to the GP211_SECURITY_INFO structure returned by GP211_mutual_authentication().
 * \param data [in] The data to encrypt.
 * \param dataLength [in] The data length.
 * \param encryptedData [out] The encrypted data. No length checking is done. The buffer must have sufficient size, e.g. the next block size or twice the data size if unsure.
 * \param encryptedDataLength [out] The length of the encrypted data.
 * \return OPGP_ERROR_STATUS struct with error status OPGP_ERROR_STATUS_SUCCESS if no error occurs, otherwise error code  and error message are contained in the OPGP_ERROR_STATUS struct
 */
OPGP_ERROR_STATUS OPGP_encrypt_sensitive_data(GP211_SECURITY_INFO *secInfo,
	PBYTE data,
	DWORD dataLength,
	PBYTE encryptedData,
	PDWORD encryptedDataLength) {
	OPGP_ERROR_STATUS status;
	OPGP_LOG_START(_T("OPGP_encrypt_sensitive_data"));
	status = encrypt_sensitive_data(secInfo, data, dataLength, encryptedData, encryptedDataLength);
	if (OPGP_ERROR_CHECK(status)) {
		goto end;
	}
	{ OPGP_ERROR_CREATE_NO_ERROR(status); goto end; }
end:
	OPGP_LOG_END(_T("OPGP_encrypt_sensitive_data"), status);
	return status;
}
