FreeRDP/libfreerdp/emu/scard/smartcard_virtual_gids.c
akallabeth 7f27e168b3
[emu,scard] flag allocator with nolint
The allocator keeps an internal list of allocated contexts. No manual
free required.
2024-11-12 16:46:01 +01:00

1638 lines
42 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* WinPR: Windows Portable Runtime
* Virtual GIDS implementation
*
* Copyright 2021 Martin Fleisz <martin.fleisz@thincast.com>
* Copyright 2023 Armin Novak <anovak@thincast.com>
* Copyright 2021,2023 Thincast Technologies GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <freerdp/config.h>
#include <winpr/wlog.h>
#include <winpr/stream.h>
#include <winpr/collections.h>
#include <freerdp/crypto/crypto.h>
#include <zlib.h>
#include "../../crypto/certificate.h"
#include "../../crypto/privatekey.h"
#include "smartcard_virtual_gids.h"
#define TAG CHANNELS_TAG("smartcard.vgids")
#define VGIDS_EFID_MASTER 0xA000
#define VGIDS_EFID_COMMON 0xA010
// #define VGIDS_EFID_CARDCF VGIDS_EFID_COMMON
// #define VGIDS_EFID_CARDAPPS VGIDS_EFID_COMMON
// #define VGIDS_EFID_CMAPFILE VGIDS_EFID_COMMON
#define VGIDS_EFID_CARDID 0xA012
// #define VGIDS_EFID_KXC00 VGIDS_EFID_COMMON
#define VGIDS_EFID_CURRENTDF 0x3FFF
#define VGIDS_DO_FILESYSTEMTABLE 0xDF1F
#define VGIDS_DO_KEYMAP 0xDF20
#define VGIDS_DO_CARDID 0xDF20
#define VGIDS_DO_CARDAPPS 0xDF21
#define VGIDS_DO_CARDCF 0xDF22
#define VGIDS_DO_CMAPFILE 0xDF23
#define VGIDS_DO_KXC00 0xDF24
#define VGIDS_CARDID_SIZE 16
#define VGIDS_MAX_PIN_SIZE 127
#define VGIDS_DEFAULT_RETRY_COUNTER 3
#define VGIDS_KEY_TYPE_KEYEXCHANGE 0x9A
// #define VGIDS_KEY_TYPE_SIGNATURE 0x9C
#define VGIDS_ALGID_RSA_1024 0x06
#define VGIDS_ALGID_RSA_2048 0x07
#define VGIDS_ALGID_RSA_3072 0x08
#define VGIDS_ALGID_RSA_4096 0x09
// #define VGIDS_SE_CRT_AUTH 0xA4
#define VGIDS_SE_CRT_SIGN 0xB6
#define VGIDS_SE_CRT_CONF 0xB8
#define VGIDS_SE_ALGOID_CT_PAD_PKCS1 0x40
#define VGIDS_SE_ALGOID_CT_PAD_OAEP 0x80
// #define VGIDS_SE_ALGOID_CT_RSA_1024 0x06
// #define VGIDS_SE_ALGOID_CT_RSA_2048 0x07
// #define VGIDS_SE_ALGOID_CT_RSA_3072 0x08
// #define VGIDS_SE_ALGOID_CT_RSA_4096 0x09
#define VGIDS_SE_ALGOID_DST_PAD_PKCS1 0x40
#define VGIDS_SE_ALGOID_DST_RSA_1024 0x06
#define VGIDS_SE_ALGOID_DST_RSA_2048 0x07
#define VGIDS_SE_ALGOID_DST_RSA_3072 0x08
#define VGIDS_SE_ALGOID_DST_RSA_4096 0x09
#define VGIDS_SE_ALGOID_DST_ECDSA_P192 0x0A
#define VGIDS_SE_ALGOID_DST_ECDSA_P224 0x0B
#define VGIDS_SE_ALGOID_DST_ECDSA_P256 0x0C
#define VGIDS_SE_ALGOID_DST_ECDSA_P384 0x0D
#define VGIDS_SE_ALGOID_DST_ECDSA_P512 0x0E
#define VGIDS_DEFAULT_KEY_REF 0x81
#define ISO_INS_SELECT 0xA4
#define ISO_INS_GETDATA 0xCB
#define ISO_INS_GETRESPONSE 0xC0
#define ISO_INS_MSE 0x22
#define ISO_INS_PSO 0x2A
#define ISO_INS_VERIFY 0x20
#define ISO_STATUS_MORE_DATA 0x6100
#define ISO_STATUS_VERIFYFAILED 0x6300
#define ISO_STATUS_WRONGLC 0x6700
#define ISO_STATUS_COMMANDNOTALLOWED 0x6900
#define ISO_STATUS_SECURITYSTATUSNOTSATISFIED 0x6982
#define ISO_STATUS_AUTHMETHODBLOCKED 0x6983
#define ISO_STATUS_INVALIDCOMMANDDATA 0x6A80
#define ISO_STATUS_FILENOTFOUND 0x6A82
#define ISO_STATUS_INVALIDP1P2 0x6A86
#define ISO_STATUS_INVALIDLC 0x6A87
#define ISO_STATUS_REFERENCEDATANOTFOUND 0x6A88
#define ISO_STATUS_SUCCESS 0x9000
#define ISO_AID_MAX_SIZE 16
#define ISO_FID_MF 0x3F00
struct vgids_ef
{
UINT16 id;
UINT16 dirID;
wStream* data;
};
typedef struct vgids_ef vgidsEF;
struct vgids_se
{
BYTE crt; /* control reference template tag */
BYTE algoId; /* Algorithm ID */
BYTE keyRef; /* Key reference */
};
typedef struct vgids_se vgidsSE;
struct vgids_context
{
UINT16 currentDF;
char* pin;
UINT16 curRetryCounter;
UINT16 retryCounter;
wStream* commandData;
wStream* responseData;
BOOL pinVerified;
vgidsSE currentSE;
rdpCertificate* certificate;
rdpPrivateKey* privateKey;
wArrayList* files;
};
/* PKCS 1.5 DER encoded digest information */
#define VGIDS_MAX_DIGEST_INFO 7
static const BYTE g_PKCS1_SHA1[] = { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e,
0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 };
static const BYTE g_PKCS1_SHA224[] = { 0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, 0x1c };
static const BYTE g_PKCS1_SHA256[] = { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20 };
static const BYTE g_PKCS1_SHA384[] = { 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30 };
static const BYTE g_PKCS1_SHA512[] = { 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40 };
static const BYTE g_PKCS1_SHA512_224[] = { 0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60,
0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02,
0x05, 0x05, 0x00, 0x04, 0x1c };
static const BYTE g_PKCS1_SHA512_256[] = { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60,
0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02,
0x06, 0x05, 0x00, 0x04, 0x20 };
/* Helper struct to map PKCS1.5 digest info to OpenSSL EVP_MD */
struct vgids_digest_info_map
{
const BYTE* info;
size_t infoSize;
const EVP_MD* digest;
};
typedef struct vgids_digest_info_map vgidsDigestInfoMap;
/* MS GIDS AID */
/* xx: Used by the Windows smart card framework for the GIDS version number. This byte must be set
* to the GIDS specification revision number which is either 0x01 or 0x02.
* yy: Reserved for use by the card application (set to 01)
*/
static const BYTE g_MsGidsAID[] = {
0xA0, 0x00, 0x00, 0x03, 0x97, 0x42, 0x54, 0x46, 0x59, 0x02, 0x01
};
/* GIDS APP File Control Parameter:
FD-Byte (82): 38 (not shareable-DF)
Sec Attr (8C): 03 30 30 Create/Delete File(03) Ext/User-Auth (30)
*/
static const BYTE g_GidsAppFCP[] = { 0x62, 0x08, 0x82, 0x01, 0x38, 0x8C, 0x03, 0x03, 0x30, 0x30 };
/* GIDS APP File Control Information:
AppID (4F, Len 0B): A0 00 00 03 97 42 54 46 59 02 01
Discretionary DOs (73, Len 03): 40 01 C0
Supported Auth Protocols (40, Len 01): C0 Mutual/External-Auth
*/
static const BYTE g_GidsAppFCI[] = { 0x61, 0x12, 0x4F, 0x0B, 0xA0, 0x00, 0x00, 0x03, 0x97, 0x42,
0x54, 0x46, 0x59, 0x02, 0x01, 0x73, 0x03, 0x40, 0x01, 0xC0 };
/*
typedef struct
{
BYTE bVersion; // Cache version
BYTE bPinsFreshness; // Card PIN
WORD wContainersFreshness;
WORD wFilesFreshness;
} CARD_CACHE_FILE_FORMAT, *PCARD_CACHE_FILE_FORMAT; */
static const BYTE g_CardCFContents[] = { 0x00, 0x00, 0x01, 0x00, 0x04, 0x00 };
/* {‘mscp’,0,0,0,0} */
static const BYTE g_CardAppsContents[] = { 0x6d, 0x73, 0x63, 0x70, 0x00, 0x00, 0x00, 0x00 };
#pragma pack(push, 1)
/* Type: CONTAINER_MAP_RECORD (taken from Windows Smart Card Minidriver Specification)
This structure describes the format of the Base CSP's
container map file, stored on the card. This is wellknown
logical file wszCONTAINER_MAP_FILE. The file consists of
zero or more of these records. */
#define MAX_CONTAINER_NAME_LEN 39
/* This flag is set in the CONTAINER_MAP_RECORD bFlags
member if the corresponding container is valid and currently
exists on the card. // If the container is deleted, its
bFlags field must be cleared. */
#define CONTAINER_MAP_VALID_CONTAINER 1
/* This flag is set in the CONTAINER_MAP_RECORD bFlags
member if the corresponding container is the default
container on the card. */
#define CONTAINER_MAP_DEFAULT_CONTAINER 2
struct vgids_container_map_entry
{
WCHAR wszGuid[MAX_CONTAINER_NAME_LEN + 1];
BYTE bFlags;
BYTE bReserved;
WORD wSigKeySizeBits;
WORD wKeyExchangeKeySizeBits;
};
typedef struct vgids_container_map_entry vgidsContainerMapEntry;
struct vgids_filesys_table_entry
{
char directory[9];
char filename[9];
UINT16 pad0;
UINT16 dataObjectIdentifier;
UINT16 pad1;
UINT16 fileIdentifier;
UINT16 unknown;
};
typedef struct vgids_filesys_table_entry vgidsFilesysTableEntry;
struct vgids_keymap_record
{
UINT32 state;
BYTE algid;
BYTE keytype;
UINT16 keyref;
UINT16 unknownWithFFFF;
UINT16 unknownWith0000;
};
typedef struct vgids_keymap_record vgidsKeymapRecord;
#pragma pack(pop)
static void vgids_ef_free(void* ptr);
static vgidsEF* vgids_ef_new(vgidsContext* ctx, USHORT id)
{
vgidsEF* ef = calloc(1, sizeof(vgidsEF));
ef->id = id;
ef->data = Stream_New(NULL, 1024);
if (!ef->data)
{
WLog_ERR(TAG, "Failed to create file data stream");
goto create_failed;
}
Stream_SetLength(ef->data, 0);
if (!ArrayList_Append(ctx->files, ef))
{
WLog_ERR(TAG, "Failed to add new ef to file list");
goto create_failed;
}
return ef;
create_failed:
vgids_ef_free(ef);
return NULL;
}
static BOOL vgids_write_tlv(wStream* s, UINT16 tag, const void* data, size_t dataSize)
{
WINPR_ASSERT(dataSize <= UINT16_MAX);
/* A maximum of 5 additional bytes is needed */
if (!Stream_EnsureRemainingCapacity(s, dataSize + 5))
{
WLog_ERR(TAG, "Failed to ensure capacity of DO stream");
return FALSE;
}
/* BER encoding: If the most-significant bit is set (0x80) the length is encoded in the
* remaining bits. So lengths < 128 bytes can be set directly, all others are encoded */
if (tag > 0xFF)
Stream_Write_UINT16_BE(s, tag);
else
Stream_Write_UINT8(s, (BYTE)tag);
if (dataSize < 128)
{
Stream_Write_UINT8(s, (BYTE)dataSize);
}
else if (dataSize < 256)
{
Stream_Write_UINT8(s, 0x81);
Stream_Write_UINT8(s, (BYTE)dataSize);
}
else
{
Stream_Write_UINT8(s, 0x82);
Stream_Write_UINT16_BE(s, (UINT16)dataSize);
}
Stream_Write(s, data, dataSize);
Stream_SealLength(s);
return TRUE;
}
static BOOL vgids_ef_write_do(vgidsEF* ef, UINT16 doID, const void* data, DWORD dataSize)
{
/* Write DO to end of file: 2-Byte ID, 1-Byte Len, Data */
return vgids_write_tlv(ef->data, doID, data, dataSize);
}
static BOOL vgids_ef_read_do(vgidsEF* ef, UINT16 doID, BYTE** data, DWORD* dataSize)
{
/* Read the given DO from the file: 2-Byte ID, 1-Byte Len, Data */
if (!Stream_SetPosition(ef->data, 0))
{
WLog_ERR(TAG, "Failed to seek to front of file");
return FALSE;
}
/* Look for the requested DO */
while (Stream_GetRemainingLength(ef->data) > 3)
{
BYTE len = 0;
size_t curPos = 0;
UINT16 doSize = 0;
UINT16 nextDOID = 0;
curPos = Stream_GetPosition(ef->data);
Stream_Read_UINT16_BE(ef->data, nextDOID);
Stream_Read_UINT8(ef->data, len);
if ((len & 0x80))
{
BYTE lenSize = len & 0x7F;
if (!Stream_CheckAndLogRequiredLength(TAG, ef->data, lenSize))
return FALSE;
switch (lenSize)
{
case 1:
Stream_Read_UINT8(ef->data, doSize);
break;
case 2:
Stream_Read_UINT16_BE(ef->data, doSize);
break;
default:
WLog_ERR(TAG, "Unexpected tag length %" PRIu8, lenSize);
return FALSE;
}
}
else
doSize = len;
if (!Stream_CheckAndLogRequiredLength(TAG, ef->data, doSize))
return FALSE;
if (nextDOID == doID)
{
BYTE* outData = NULL;
/* Include Tag and length in result */
doSize += (UINT16)(Stream_GetPosition(ef->data) - curPos);
outData = malloc(doSize);
if (!outData)
{
WLog_ERR(TAG, "Failed to allocate output buffer");
return FALSE;
}
Stream_SetPosition(ef->data, curPos);
Stream_Read(ef->data, outData, doSize);
*data = outData;
*dataSize = doSize;
return TRUE;
}
else
{
/* Skip DO */
if (!Stream_SafeSeek(ef->data, doSize))
return FALSE;
}
}
return FALSE;
}
void vgids_ef_free(void* ptr)
{
vgidsEF* ef = ptr;
if (ef)
{
Stream_Free(ef->data, TRUE);
free(ef);
}
}
static BOOL vgids_prepare_fstable(const vgidsFilesysTableEntry* fstable, DWORD numEntries,
BYTE** outData, DWORD* outDataSize)
{
/* Filesystem table:
BYTE unkonwn: 0x01
Array of vgidsFilesysTableEntry
*/
BYTE* data = malloc(sizeof(vgidsFilesysTableEntry) * numEntries + 1);
if (!data)
{
WLog_ERR(TAG, "Failed to allocate filesystem table data blob");
return FALSE;
}
*data = 0x01;
for (UINT32 i = 0; i < numEntries; ++i)
memcpy(data + 1 + (sizeof(vgidsFilesysTableEntry) * i), &fstable[i],
sizeof(vgidsFilesysTableEntry));
*outData = data;
*outDataSize = sizeof(vgidsFilesysTableEntry) * numEntries + 1;
return TRUE;
}
static BOOL vgids_prepare_certificate(const rdpCertificate* cert, BYTE** kxc, DWORD* kxcSize)
{
/* Key exchange container:
UINT16 compression version: 0001
UINT16 source size
ZLIB compressed cert
*/
uLongf destSize = 0;
wStream* s = NULL;
BYTE* comprData = NULL;
WINPR_ASSERT(cert);
size_t certSize = 0;
BYTE* certData = freerdp_certificate_get_der(cert, &certSize);
if (!certData || (certSize == 0))
{
WLog_ERR(TAG, "Failed to get certificate size");
goto handle_error;
}
comprData = malloc(certSize);
if (!comprData)
{
WLog_ERR(TAG, "Failed to allocate certificate buffer");
goto handle_error;
}
/* compress certificate data */
destSize = certSize;
if (compress(comprData, &destSize, certData, certSize) != Z_OK)
{
WLog_ERR(TAG, "Failed to compress certificate data");
goto handle_error;
}
/* Write container data */
s = Stream_New(NULL, destSize + 4);
Stream_Write_UINT16(s, 0x0001);
Stream_Write_UINT16(s, (UINT16)certSize);
Stream_Write(s, comprData, destSize);
Stream_SealLength(s);
*kxc = Stream_Buffer(s);
*kxcSize = (DWORD)Stream_Length(s);
Stream_Free(s, FALSE);
free(certData);
free(comprData);
return TRUE;
handle_error:
Stream_Free(s, TRUE);
free(certData);
free(comprData);
return FALSE;
}
static size_t get_rsa_key_size(const rdpPrivateKey* privateKey)
{
WINPR_ASSERT(privateKey);
return freerdp_key_get_bits(privateKey) / 8;
}
static BYTE vgids_get_algid(vgidsContext* p_Ctx)
{
WINPR_ASSERT(p_Ctx);
switch (get_rsa_key_size(p_Ctx->privateKey))
{
case (1024 / 8):
return VGIDS_ALGID_RSA_1024;
case (2048 / 8):
return VGIDS_ALGID_RSA_2048;
case (3072 / 8):
return VGIDS_ALGID_RSA_3072;
case (4096 / 8):
return VGIDS_ALGID_RSA_4096;
default:
WLog_ERR(TAG, "Failed to determine algid for private key");
break;
}
return 0;
}
static BOOL vgids_prepare_keymap(vgidsContext* context, BYTE** outData, DWORD* outDataSize)
{
/* Key map record table:
BYTE unkonwn (count?): 0x01
Array of vgidsKeymapRecord
*/
BYTE* data = NULL;
vgidsKeymapRecord record = {
1, /* state */
0, /* algo */
VGIDS_KEY_TYPE_KEYEXCHANGE, /* keytpe */
(0xB000 | VGIDS_DEFAULT_KEY_REF), /* keyref */
0xFFFF, /* unknown FFFF */
0x0000 /* unknown 0000 */
};
/* Determine algo */
BYTE algid = vgids_get_algid(context);
if (algid == 0)
return FALSE;
data = malloc(sizeof(record) + 1);
if (!data)
{
WLog_ERR(TAG, "Failed to allocate filesystem table data blob");
return FALSE;
}
*data = 0x01;
record.algid = algid;
memcpy(data + 1, &record, sizeof(record));
*outData = data;
*outDataSize = sizeof(record) + 1;
return TRUE;
}
static BOOL vgids_parse_apdu_header(wStream* s, BYTE* cla, BYTE* ins, BYTE* p1, BYTE* p2, BYTE* lc,
BYTE* le)
{
if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
return FALSE;
/* Read and verify APDU data */
if (cla)
Stream_Read_UINT8(s, *cla);
else
Stream_Seek(s, 1);
if (ins)
Stream_Read_UINT8(s, *ins);
else
Stream_Seek(s, 1);
if (p1)
Stream_Read_UINT8(s, *p1);
else
Stream_Seek(s, 1);
if (p2)
Stream_Read_UINT8(s, *p2);
else
Stream_Seek(s, 1);
/* If LC is requested - check remaining length and read as well */
if (lc)
{
if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
return FALSE;
Stream_Read_UINT8(s, *lc);
if (!Stream_CheckAndLogRequiredLength(TAG, s, *lc))
return FALSE;
}
/* read LE */
if (le)
{
if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
return FALSE;
Stream_Read_UINT8(s, *le);
}
return TRUE;
}
static BOOL vgids_create_response(UINT16 status, const BYTE* answer, DWORD answerSize,
BYTE** outData, DWORD* outDataSize)
{
BYTE* out = malloc(answerSize + 2);
if (!out)
{
WLog_ERR(TAG, "Failed to allocate memory for response data");
return FALSE;
}
*outData = out;
if (answer)
{
memcpy(out, answer, answerSize);
out += answerSize;
}
*out = (BYTE)((status >> 8) & 0xFF);
*(out + 1) = (BYTE)(status & 0xFF);
*outDataSize = answerSize + 2;
return TRUE;
}
static BOOL vgids_read_do_fkt(void* data, size_t index, va_list ap)
{
BYTE* response = NULL;
DWORD responseSize = 0;
vgidsEF* file = (vgidsEF*)data;
vgidsContext* context = va_arg(ap, vgidsContext*);
UINT16 efID = (UINT16)va_arg(ap, unsigned);
UINT16 doID = (UINT16)va_arg(ap, unsigned);
WINPR_UNUSED(index);
if (efID == 0x3FFF || efID == file->id)
{
/* If the DO was successfully read - abort file enum */
if (vgids_ef_read_do(file, doID, &response, &responseSize))
{
context->responseData = Stream_New(response, (size_t)responseSize);
return FALSE;
}
}
return TRUE;
}
static void vgids_read_do(vgidsContext* context, UINT16 efID, UINT16 doID)
{
ArrayList_ForEach(context->files, vgids_read_do_fkt, context, efID, doID);
}
static void vgids_reset_context_response(vgidsContext* context)
{
Stream_Free(context->responseData, TRUE);
context->responseData = NULL;
}
static void vgids_reset_context_command_data(vgidsContext* context)
{
Stream_Free(context->commandData, TRUE);
context->commandData = NULL;
}
static BOOL vgids_ins_select(vgidsContext* context, wStream* s, BYTE** response,
DWORD* responseSize)
{
BYTE p1 = 0;
BYTE p2 = 0;
BYTE lc = 0;
DWORD resultDataSize = 0;
const BYTE* resultData = NULL;
UINT16 status = ISO_STATUS_SUCCESS;
/* The only select operations performed are either select by AID or select 3FFF (return
* information about the currently selected DF) */
if (!vgids_parse_apdu_header(s, NULL, NULL, &p1, &p2, &lc, NULL))
return FALSE;
/* Check P1 for selection mode */
switch (p1)
{
/* Select by AID */
case 0x04:
{
/* read AID from APDU */
BYTE aid[ISO_AID_MAX_SIZE] = { 0 };
if (lc > ISO_AID_MAX_SIZE)
{
WLog_ERR(TAG, "The LC byte is greater than the maximum AID length");
status = ISO_STATUS_INVALIDLC;
break;
}
/* Check if we select MS GIDS App (only one we know) */
Stream_Read(s, aid, lc);
if (memcmp(aid, g_MsGidsAID, lc) != 0)
{
status = ISO_STATUS_FILENOTFOUND;
break;
}
/* Return FCI or FCP for MsGids App */
switch (p2)
{
/* Return FCI information */
case 0x00:
{
resultData = g_GidsAppFCI;
resultDataSize = sizeof(g_GidsAppFCI);
break;
}
/* Return FCP information */
case 0x04:
{
resultData = g_GidsAppFCP;
resultDataSize = sizeof(g_GidsAppFCP);
break;
}
default:
status = ISO_STATUS_INVALIDP1P2;
break;
}
if (resultData)
context->currentDF = ISO_FID_MF;
break;
}
/* Select by FID */
case 0x00:
{
/* read FID from APDU */
UINT16 fid = 0;
if (lc > 2)
{
WLog_ERR(TAG, "The LC byte for the file ID is greater than 2");
status = ISO_STATUS_INVALIDLC;
break;
}
Stream_Read_UINT16_BE(s, fid);
if (fid != VGIDS_EFID_CURRENTDF || context->currentDF == 0)
{
status = ISO_STATUS_FILENOTFOUND;
break;
}
break;
}
default:
{
/* P1 P2 combination not supported */
status = ISO_STATUS_INVALIDP1P2;
break;
}
}
return vgids_create_response(status, resultData, resultDataSize, response, responseSize);
}
static UINT16 vgids_handle_chained_response(vgidsContext* context, const BYTE** response,
DWORD* responseSize)
{
/* Cap to a maximum of 256 bytes and set status to more data */
UINT16 status = ISO_STATUS_SUCCESS;
DWORD remainingBytes = (DWORD)Stream_Length(context->responseData);
if (remainingBytes > 256)
{
status = ISO_STATUS_MORE_DATA;
remainingBytes = 256;
}
*response = Stream_Buffer(context->responseData);
*responseSize = remainingBytes;
Stream_Seek(context->responseData, remainingBytes);
/* Check if there are more than 256 bytes left or if we can already provide the remaining length
* in the status word */
remainingBytes = (DWORD)(Stream_Length(context->responseData) - remainingBytes);
if (remainingBytes < 256 && remainingBytes != 0)
status |= (remainingBytes & 0xFF);
return status;
}
static BOOL vgids_get_public_key(vgidsContext* context, UINT16 doTag)
{
BOOL rc = FALSE;
wStream* pubKey = NULL;
wStream* response = NULL;
WINPR_ASSERT(context);
/* Get key components */
size_t nSize = 0;
size_t eSize = 0;
char* n = freerdp_certificate_get_param(context->certificate, FREERDP_CERT_RSA_N, &nSize);
char* e = freerdp_certificate_get_param(context->certificate, FREERDP_CERT_RSA_E, &eSize);
if (!n || !e)
goto handle_error;
pubKey = Stream_New(NULL, nSize + eSize + 0x10);
if (!pubKey)
{
WLog_ERR(TAG, "Failed to allocate public key stream");
goto handle_error;
}
response = Stream_New(NULL, Stream_Capacity(pubKey) + 0x10);
if (!response)
{
WLog_ERR(TAG, "Failed to allocate response stream");
goto handle_error;
}
/* write modulus and exponent DOs */
if (!vgids_write_tlv(pubKey, 0x81, n, nSize))
goto handle_error;
if (!vgids_write_tlv(pubKey, 0x82, e, eSize))
goto handle_error;
/* write ISO public key template */
if (!vgids_write_tlv(response, doTag, Stream_Buffer(pubKey), (DWORD)Stream_Length(pubKey)))
goto handle_error;
/* set response data */
Stream_SetPosition(response, 0);
context->responseData = response;
response = NULL;
rc = TRUE;
handle_error:
free(n);
free(e);
Stream_Free(pubKey, TRUE);
Stream_Free(response, TRUE);
return rc;
}
static BOOL vgids_ins_getdata(vgidsContext* context, wStream* s, BYTE** response,
DWORD* responseSize)
{
UINT16 doId = 0;
UINT16 fileId = 0;
BYTE p1 = 0;
BYTE p2 = 0;
BYTE lc = 0;
DWORD resultDataSize = 0;
const BYTE* resultData = NULL;
UINT16 status = ISO_STATUS_SUCCESS;
/* GetData is called a lot!
- To retrieve DOs from files
- To retrieve public key information
*/
if (!vgids_parse_apdu_header(s, NULL, NULL, &p1, &p2, &lc, NULL))
return FALSE;
/* free any previous queried data */
vgids_reset_context_response(context);
/* build up file identifier */
fileId = (UINT16)(((UINT16)p1 << 8) | p2);
/* Do we have a DO reference? */
switch (lc)
{
case 4:
{
BYTE tag = 0;
BYTE length = 0;
Stream_Read_UINT8(s, tag);
Stream_Read_UINT8(s, length);
if (tag != 0x5C && length != 0x02)
{
status = ISO_STATUS_INVALIDCOMMANDDATA;
break;
}
Stream_Read_UINT16_BE(s, doId);
vgids_read_do(context, fileId, doId);
break;
}
case 0xA:
{
UINT16 pubKeyDO = 0;
BYTE tag = 0;
BYTE length = 0;
BYTE keyRef = 0;
/* We want to retrieve the public key? */
if (p1 != 0x3F && p2 != 0xFF)
{
status = ISO_STATUS_INVALIDP1P2;
break;
}
/* read parent tag/length */
Stream_Read_UINT8(s, tag);
Stream_Read_UINT8(s, length);
if (tag != 0x70 || length != 0x08)
{
status = ISO_STATUS_INVALIDCOMMANDDATA;
break;
}
/* read key reference TLV */
Stream_Read_UINT8(s, tag);
Stream_Read_UINT8(s, length);
Stream_Read_UINT8(s, keyRef);
if (tag != 0x84 || length != 0x01 || keyRef != VGIDS_DEFAULT_KEY_REF)
{
status = ISO_STATUS_INVALIDCOMMANDDATA;
break;
}
/* read key value template TLV */
Stream_Read_UINT8(s, tag);
Stream_Read_UINT8(s, length);
if (tag != 0xA5 || length != 0x03)
{
status = ISO_STATUS_INVALIDCOMMANDDATA;
break;
}
Stream_Read_UINT16_BE(s, pubKeyDO);
Stream_Read_UINT8(s, length);
if (pubKeyDO != 0x7F49 || length != 0x80)
{
status = ISO_STATUS_INVALIDCOMMANDDATA;
break;
}
if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
{
status = ISO_STATUS_INVALIDLC;
break;
}
/* Return public key value */
vgids_get_public_key(context, pubKeyDO);
break;
}
default:
status = ISO_STATUS_INVALIDCOMMANDDATA;
break;
}
/* If we have response data, make it ready for return */
if (context->responseData)
status = vgids_handle_chained_response(context, &resultData, &resultDataSize);
else if (status == ISO_STATUS_SUCCESS)
status = ISO_STATUS_REFERENCEDATANOTFOUND;
return vgids_create_response(status, resultData, resultDataSize, response, responseSize);
}
static BOOL vgids_ins_manage_security_environment(vgidsContext* context, wStream* s,
BYTE** response, DWORD* responseSize)
{
BYTE tag = 0;
BYTE length = 0;
BYTE p1 = 0;
BYTE p2 = 0;
BYTE lc = 0;
DWORD resultDataSize = 0;
const BYTE* resultData = NULL;
UINT16 status = ISO_STATUS_SUCCESS;
vgids_reset_context_command_data(context);
vgids_reset_context_response(context);
/* Manage security environment prepares the card for performing crypto operations. */
if (!vgids_parse_apdu_header(s, NULL, NULL, &p1, &p2, &lc, NULL))
return FALSE;
/* Check APDU params */
/* P1: Set Computation, decipherment, Internal Auth */
/* P2: Digital Signature (B6), Confidentiality (B8) */
if (p1 != 0x41 && p2 != 0xB6 && p2 != 0xB8)
{
status = ISO_STATUS_INVALIDP1P2;
goto create_response;
}
if (lc != 6)
{
status = ISO_STATUS_WRONGLC;
goto create_response;
}
context->currentSE.crt = p2;
/* parse command buffer */
/* Read algo ID */
Stream_Read_UINT8(s, tag);
Stream_Read_UINT8(s, length);
if (tag != 0x80 || length != 0x01)
{
status = ISO_STATUS_INVALIDCOMMANDDATA;
goto create_response;
}
Stream_Read_UINT8(s, context->currentSE.algoId);
/* Read private key reference */
Stream_Read_UINT8(s, tag);
Stream_Read_UINT8(s, length);
if (tag != 0x84 || length != 0x01)
{
status = ISO_STATUS_INVALIDCOMMANDDATA;
goto create_response;
}
Stream_Read_UINT8(s, context->currentSE.keyRef);
create_response:
/* If an error occured reset SE */
if (status != ISO_STATUS_SUCCESS)
memset(&context->currentSE, 0, sizeof(context->currentSE));
return vgids_create_response(status, resultData, resultDataSize, response, responseSize);
}
static BOOL vgids_perform_digital_signature(vgidsContext* context)
{
size_t sigSize = 0;
size_t msgSize = 0;
EVP_PKEY_CTX* ctx = NULL;
EVP_PKEY* pk = freerdp_key_get_evp_pkey(context->privateKey);
const vgidsDigestInfoMap gidsDigestInfo[VGIDS_MAX_DIGEST_INFO] = {
{ g_PKCS1_SHA1, sizeof(g_PKCS1_SHA1), EVP_sha1() },
{ g_PKCS1_SHA224, sizeof(g_PKCS1_SHA224), EVP_sha224() },
{ g_PKCS1_SHA256, sizeof(g_PKCS1_SHA256), EVP_sha256() },
{ g_PKCS1_SHA384, sizeof(g_PKCS1_SHA384), EVP_sha384() },
{ g_PKCS1_SHA512, sizeof(g_PKCS1_SHA512), EVP_sha512() },
#if OPENSSL_VERSION_NUMBER >= 0x10101000L
{ g_PKCS1_SHA512_224, sizeof(g_PKCS1_SHA512_224), EVP_sha512_224() },
{ g_PKCS1_SHA512_256, sizeof(g_PKCS1_SHA512_256), EVP_sha512_256() }
#endif
};
if (!pk)
{
WLog_ERR(TAG, "Failed to create PKEY");
return FALSE;
}
vgids_reset_context_response(context);
/* for each digest info */
Stream_SetPosition(context->commandData, 0);
for (int i = 0; i < VGIDS_MAX_DIGEST_INFO; ++i)
{
/* have we found our digest? */
const vgidsDigestInfoMap* digest = &gidsDigestInfo[i];
if (Stream_Length(context->commandData) >= digest->infoSize &&
memcmp(Stream_Buffer(context->commandData), digest->info, digest->infoSize) == 0)
{
/* skip digest info and calculate message size */
Stream_Seek(context->commandData, digest->infoSize);
if (!Stream_CheckAndLogRequiredLength(TAG, context->commandData, 2))
goto sign_failed;
msgSize = Stream_GetRemainingLength(context->commandData);
/* setup signing context */
ctx = EVP_PKEY_CTX_new(pk, NULL);
if (!ctx)
{
WLog_ERR(TAG, "Failed to create signing context");
goto sign_failed;
}
if (EVP_PKEY_sign_init(ctx) <= 0)
{
WLog_ERR(TAG, "Failed to init signing context");
goto sign_failed;
}
/* set padding and signature algo */
if (context->currentSE.algoId & VGIDS_SE_ALGOID_DST_PAD_PKCS1)
{
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0)
{
WLog_ERR(TAG, "Failed to set padding mode");
goto sign_failed;
}
}
if (EVP_PKEY_CTX_set_signature_md(ctx, digest->digest) <= 0)
{
WLog_ERR(TAG, "Failed to set signing mode");
goto sign_failed;
}
/* Determine buffer length */
if (EVP_PKEY_sign(ctx, NULL, &sigSize, Stream_Pointer(context->commandData), msgSize) <=
0)
{
WLog_ERR(TAG, "Failed to determine signature size");
goto sign_failed;
}
context->responseData = Stream_New(NULL, sigSize);
if (!context->responseData)
{
WLog_ERR(TAG, "Failed to allocate signing buffer");
goto sign_failed;
}
/* sign */
if (EVP_PKEY_sign(ctx, Stream_Buffer(context->responseData), &sigSize,
Stream_Pointer(context->commandData), msgSize) <= 0)
{
WLog_ERR(TAG, "Failed to create signature");
goto sign_failed;
}
Stream_SetLength(context->responseData, sigSize);
EVP_PKEY_CTX_free(ctx);
break;
}
}
EVP_PKEY_free(pk);
vgids_reset_context_command_data(context);
return TRUE;
sign_failed:
vgids_reset_context_command_data(context);
vgids_reset_context_response(context);
EVP_PKEY_CTX_free(ctx);
EVP_PKEY_free(pk);
return FALSE;
}
static BOOL vgids_perform_decrypt(vgidsContext* context)
{
EVP_PKEY_CTX* ctx = NULL;
BOOL rc = FALSE;
int res = 0;
int padding = RSA_NO_PADDING;
vgids_reset_context_response(context);
/* determine padding */
if (context->currentSE.algoId & VGIDS_SE_ALGOID_CT_PAD_PKCS1)
padding = RSA_PKCS1_PADDING;
else if (context->currentSE.algoId & VGIDS_SE_ALGOID_CT_PAD_OAEP)
padding = RSA_PKCS1_OAEP_PADDING;
/* init response buffer */
EVP_PKEY* pkey = freerdp_key_get_evp_pkey(context->privateKey);
if (!pkey)
goto decrypt_failed;
ctx = EVP_PKEY_CTX_new(pkey, NULL);
if (!ctx)
goto decrypt_failed;
if (EVP_PKEY_decrypt_init(ctx) <= 0)
goto decrypt_failed;
if (EVP_PKEY_CTX_set_rsa_padding(ctx, padding) <= 0)
goto decrypt_failed;
/* Determine buffer length */
const size_t inlen = Stream_Length(context->commandData);
size_t outlen = 0;
res = EVP_PKEY_decrypt(ctx, NULL, &outlen, Stream_Buffer(context->commandData), inlen);
if (res < 0)
{
WLog_ERR(TAG, "Failed to decrypt data");
goto decrypt_failed;
}
/* Prepare output buffer */
context->responseData = Stream_New(NULL, outlen);
if (!context->responseData)
{
WLog_ERR(TAG, "Failed to create decryption buffer");
goto decrypt_failed;
}
/* Decrypt */
res = EVP_PKEY_decrypt(ctx, Stream_Buffer(context->responseData), &outlen,
Stream_Buffer(context->commandData), inlen);
if (res < 0)
{
WLog_ERR(TAG, "Failed to decrypt data");
goto decrypt_failed;
}
Stream_SetLength(context->responseData, outlen);
rc = TRUE;
decrypt_failed:
EVP_PKEY_CTX_free(ctx);
EVP_PKEY_free(pkey);
vgids_reset_context_command_data(context);
if (!rc)
vgids_reset_context_response(context);
return rc;
}
static BOOL vgids_ins_perform_security_operation(vgidsContext* context, wStream* s, BYTE** response,
DWORD* responseSize)
{
BYTE cla = 0;
BYTE p1 = 0;
BYTE p2 = 0;
BYTE lc = 0;
DWORD resultDataSize = 0;
const BYTE* resultData = NULL;
UINT16 status = ISO_STATUS_SUCCESS;
/* Perform security operation */
if (!vgids_parse_apdu_header(s, &cla, NULL, &p1, &p2, &lc, NULL))
return FALSE;
if (lc == 0)
{
status = ISO_STATUS_WRONGLC;
goto create_response;
}
/* Is our default key referenced? */
if (context->currentSE.keyRef != VGIDS_DEFAULT_KEY_REF)
{
status = ISO_STATUS_SECURITYSTATUSNOTSATISFIED;
goto create_response;
}
/* is the pin protecting the key verified? */
if (!context->pinVerified)
{
status = ISO_STATUS_SECURITYSTATUSNOTSATISFIED;
goto create_response;
}
/* Append the data to the context command buffer (PSO might chain command data) */
if (!context->commandData)
{
context->commandData = Stream_New(NULL, lc);
if (!context->commandData)
return FALSE;
}
else if (!Stream_EnsureRemainingCapacity(context->commandData, lc))
return FALSE;
Stream_Write(context->commandData, Stream_Pointer(s), lc);
Stream_SealLength(context->commandData);
/* Check if the correct operation is requested for our current SE */
switch (context->currentSE.crt)
{
case VGIDS_SE_CRT_SIGN:
{
if (p1 != 0x9E || p2 != 0x9A)
{
status = ISO_STATUS_INVALIDP1P2;
break;
}
/* If chaining is over perform op */
if (!(cla & 0x10))
vgids_perform_digital_signature(context);
break;
}
case VGIDS_SE_CRT_CONF:
{
if ((p1 != 0x86 || p2 != 0x80) && (p1 != 0x80 || p2 != 0x86))
{
status = ISO_STATUS_INVALIDP1P2;
break;
}
/* If chaining is over perform op */
if (!(cla & 0x10))
vgids_perform_decrypt(context);
break;
}
default:
status = ISO_STATUS_INVALIDP1P2;
break;
}
/* Do chaining of response data if necessary */
if (status == ISO_STATUS_SUCCESS && context->responseData)
status = vgids_handle_chained_response(context, &resultData, &resultDataSize);
/* Check APDU params */
create_response:
return vgids_create_response(status, resultData, resultDataSize, response, responseSize);
}
static BOOL vgids_ins_getresponse(vgidsContext* context, wStream* s, BYTE** response,
DWORD* responseSize)
{
BYTE p1 = 0;
BYTE p2 = 0;
BYTE le = 0;
DWORD resultDataSize = 0;
const BYTE* resultData = NULL;
DWORD expectedLen = 0;
DWORD remainingSize = 0;
UINT16 status = ISO_STATUS_SUCCESS;
/* Get response continues data transfer after a previous get data command */
/* Check if there is any data to transfer left */
if (!context->responseData || !Stream_CheckAndLogRequiredLength(TAG, context->responseData, 1))
{
status = ISO_STATUS_COMMANDNOTALLOWED;
goto create_response;
}
if (!vgids_parse_apdu_header(s, NULL, NULL, &p1, &p2, NULL, &le))
return FALSE;
/* Check APDU params */
if (p1 != 00 || p2 != 0x00)
{
status = ISO_STATUS_INVALIDP1P2;
goto create_response;
}
/* LE = 0 means 256 bytes expected */
expectedLen = le;
if (expectedLen == 0)
expectedLen = 256;
/* prepare response size and update offset */
remainingSize = (DWORD)Stream_GetRemainingLength(context->responseData);
if (remainingSize < expectedLen)
expectedLen = remainingSize;
resultData = Stream_Pointer(context->responseData);
resultDataSize = expectedLen;
Stream_Seek(context->responseData, expectedLen);
/* If more data is left return 61XX - otherwise 9000 */
remainingSize = (DWORD)Stream_GetRemainingLength(context->responseData);
if (remainingSize > 0)
{
status = ISO_STATUS_MORE_DATA;
if (remainingSize < 256)
status |= (remainingSize & 0xFF);
}
create_response:
return vgids_create_response(status, resultData, resultDataSize, response, responseSize);
}
static BOOL vgids_ins_verify(vgidsContext* context, wStream* s, BYTE** response,
DWORD* responseSize)
{
BYTE ins = 0;
BYTE p1 = 0;
BYTE p2 = 0;
BYTE lc = 0;
UINT16 status = ISO_STATUS_SUCCESS;
char pin[VGIDS_MAX_PIN_SIZE + 1] = { 0 };
/* Verify is always called for the application password (PIN) P2=0x80 */
if (!vgids_parse_apdu_header(s, NULL, &ins, &p1, &p2, NULL, NULL))
return FALSE;
/* Check APDU params */
if (p1 != 00 && p2 != 0x80 && p2 != 0x82)
{
status = ISO_STATUS_INVALIDP1P2;
goto create_response;
}
/* shall we reset the security state? */
if (p2 == 0x82)
{
context->pinVerified = FALSE;
goto create_response;
}
/* Check if pin is not already blocked */
if (context->curRetryCounter == 0)
{
status = ISO_STATUS_AUTHMETHODBLOCKED;
goto create_response;
}
/* Read and verify LC */
if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
{
status = ISO_STATUS_INVALIDLC;
goto create_response;
}
Stream_Read_UINT8(s, lc);
if (!Stream_CheckAndLogRequiredLength(TAG, s, lc) || (lc > VGIDS_MAX_PIN_SIZE))
{
status = ISO_STATUS_INVALIDLC;
goto create_response;
}
/* read and verify pin */
Stream_Read(s, pin, lc);
if (strcmp(context->pin, pin) != 0)
{
/* retries are encoded in the lowest 4-bit of the status code */
--context->curRetryCounter;
context->pinVerified = FALSE;
status = (ISO_STATUS_VERIFYFAILED | (context->curRetryCounter & 0xFF));
}
else
{
/* reset retry counter and mark pin as verified */
context->curRetryCounter = context->retryCounter;
context->pinVerified = TRUE;
}
create_response:
return vgids_create_response(status, NULL, 0, response, responseSize);
}
vgidsContext* vgids_new(void)
{
wObject* obj = NULL;
vgidsContext* ctx = calloc(1, sizeof(vgidsContext));
ctx->files = ArrayList_New(FALSE);
if (!ctx->files)
{
WLog_ERR(TAG, "Failed to create files array list");
goto create_failed;
}
obj = ArrayList_Object(ctx->files);
obj->fnObjectFree = vgids_ef_free;
return ctx;
create_failed:
vgids_free(ctx);
return NULL;
}
BOOL vgids_init(vgidsContext* ctx, const char* cert, const char* privateKey, const char* pin)
{
DWORD kxcSize = 0;
DWORD keymapSize = 0;
DWORD fsTableSize = 0;
BOOL rc = FALSE;
BYTE* kxc = NULL;
BYTE* keymap = NULL;
BYTE* fsTable = NULL;
vgidsEF* masterEF = NULL;
vgidsEF* cardidEF = NULL;
vgidsEF* commonEF = NULL;
BYTE cardid[VGIDS_CARDID_SIZE] = { 0 };
vgidsContainerMapEntry cmrec = { { 'P', 'r', 'i', 'v', 'a', 't', 'e', ' ', 'K', 'e', 'y', ' ',
'0', '0' },
CONTAINER_MAP_VALID_CONTAINER |
CONTAINER_MAP_DEFAULT_CONTAINER,
0,
0,
0x00 /* key-size in bits - filled out later */ };
vgidsFilesysTableEntry filesys[] = {
{ "mscp", "", 0, 0, 0, 0xA000, 0 },
{ "", "cardid", 0, 0xDF20, 0, 0xA012, 0 },
{ "", "cardapps", 0, 0xDF21, 0, 0xA010, 0 },
{ "", "cardcf", 0, 0xDF22, 0, 0xA010, 0 },
{ "mscp", "cmapfile", 0, 0xDF23, 0, 0xA010, 0 },
{ "mscp", "kxc00", 0, 0xDF24, 0, 0xA010, 0 },
};
/* Check params */
if (!cert || !privateKey || !pin)
{
WLog_DBG(TAG, "Passed invalid NULL argument: cert=%p, privateKey=%p, pin=%p", cert,
privateKey, pin);
goto init_failed;
}
/* Convert PEM input to DER certificate/public key/private key */
ctx->certificate = freerdp_certificate_new_from_pem(cert);
if (!ctx->certificate)
goto init_failed;
ctx->privateKey = freerdp_key_new_from_pem(privateKey);
if (!ctx->privateKey)
goto init_failed;
/* create masterfile */
// NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
masterEF = vgids_ef_new(ctx, VGIDS_EFID_MASTER);
if (!masterEF)
goto init_failed;
/* create cardid file with cardid DO */
// NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
cardidEF = vgids_ef_new(ctx, VGIDS_EFID_CARDID);
if (!cardidEF)
goto init_failed;
winpr_RAND(cardid, sizeof(cardid));
if (!vgids_ef_write_do(cardidEF, VGIDS_DO_CARDID, cardid, sizeof(cardid)))
goto init_failed;
/* create user common file */
// NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
commonEF = vgids_ef_new(ctx, VGIDS_EFID_COMMON);
if (!commonEF)
goto init_failed;
/* write card cache DO */
if (!vgids_ef_write_do(commonEF, VGIDS_DO_CARDCF, g_CardCFContents, sizeof(g_CardCFContents)))
goto init_failed;
/* write container map DO */
const size_t size = get_rsa_key_size(ctx->privateKey);
if ((size == 0) || (size > UINT16_MAX / 8))
goto init_failed;
cmrec.wKeyExchangeKeySizeBits = (WORD)size * 8;
if (!vgids_ef_write_do(commonEF, VGIDS_DO_CMAPFILE, &cmrec, sizeof(cmrec)))
goto init_failed;
/* write cardapps DO */
if (!vgids_ef_write_do(commonEF, VGIDS_DO_CARDAPPS, g_CardAppsContents,
sizeof(g_CardAppsContents)))
goto init_failed;
/* convert and write certificate to key exchange container */
if (!vgids_prepare_certificate(ctx->certificate, &kxc, &kxcSize))
goto init_failed;
if (!vgids_ef_write_do(commonEF, VGIDS_DO_KXC00, kxc, kxcSize))
goto init_failed;
/* prepare and write file system table */
if (!vgids_prepare_fstable(filesys, ARRAYSIZE(filesys), &fsTable, &fsTableSize))
goto init_failed;
if (!vgids_ef_write_do(masterEF, VGIDS_DO_FILESYSTEMTABLE, fsTable, fsTableSize))
goto init_failed;
/* vgids_prepare_keymap and write to masterEF */
if (!vgids_prepare_keymap(ctx, &keymap, &keymapSize))
goto init_failed;
if (!vgids_ef_write_do(masterEF, VGIDS_DO_KEYMAP, keymap, keymapSize))
goto init_failed;
/* store user pin */
ctx->curRetryCounter = ctx->retryCounter = VGIDS_DEFAULT_RETRY_COUNTER;
ctx->pin = _strdup(pin);
if (!ctx->pin)
goto init_failed;
rc = TRUE;
init_failed:
// ArrayList_Append in vgids_ef_new takes ownership
// of cardidEF, commonEF, masterEF
// NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
free(kxc);
free(keymap);
free(fsTable);
return rc;
}
BOOL vgids_process_apdu(vgidsContext* context, const BYTE* data, DWORD dataSize, BYTE** response,
DWORD* responseSize)
{
wStream s;
static int x = 1;
/* Check params */
if (!context || !data || !response || !responseSize)
{
WLog_ERR(TAG, "Invalid NULL pointer passed");
return FALSE;
}
if (dataSize < 4)
{
WLog_ERR(TAG, "APDU buffer is less than 4 bytes: %" PRIu32, dataSize);
return FALSE;
}
/* Examine INS byte */
Stream_StaticConstInit(&s, data, dataSize);
if (x++ == 0xe)
x = 0xe + 1;
switch (data[1])
{
case ISO_INS_SELECT:
return vgids_ins_select(context, &s, response, responseSize);
case ISO_INS_GETDATA:
return vgids_ins_getdata(context, &s, response, responseSize);
case ISO_INS_GETRESPONSE:
return vgids_ins_getresponse(context, &s, response, responseSize);
case ISO_INS_MSE:
return vgids_ins_manage_security_environment(context, &s, response, responseSize);
case ISO_INS_PSO:
return vgids_ins_perform_security_operation(context, &s, response, responseSize);
case ISO_INS_VERIFY:
return vgids_ins_verify(context, &s, response, responseSize);
default:
break;
}
/* return command not allowed */
return vgids_create_response(ISO_STATUS_COMMANDNOTALLOWED, NULL, 0, response, responseSize);
}
void vgids_free(vgidsContext* context)
{
if (context)
{
freerdp_key_free(context->privateKey);
freerdp_certificate_free(context->certificate);
Stream_Free(context->commandData, TRUE);
Stream_Free(context->responseData, TRUE);
free(context->pin);
ArrayList_Free(context->files);
free(context);
}
}