How can I read chip/smart cards using Xojo?

Hello,

I’ve got a ReinerSCT cyberJack RFID Standard Card Reader and my goal is to read German health insurance cards on Windows. As far as I know, I don’t need a special driver to use the card reader on Windows. It uses a standard Windows driver. How can I do that? Is there an API for Windows?

Alex

I contacted ReinerSCT support, and they provided a .dll file with an example.

Here’s the example:



////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
// 
//  Das folgende Beispiel zeigt das dynaische laden/entladen einer CTAPI Dll
// 
//  In einer Anwendung ist der logische Ablauf wie folgt:
//  
//  {
//		CCtapiDllSample  cCtApiDll;
//		cCtApiDll.Open (1,1) // Öffne Port 1 mit CTN 1
//		
//		..Befehle zm Leser und zur Chipkarte
//		..Beispiel siehe Funktion GetCardMonStatus ()
//		
//		cCtApiDll.Close ();
//	}


// Will man die Lib statisch binden so kann folgender header verwendet werden:

// #define CTAPI_RETURN	STDAPI_(char)
// 
// CTAPI_RETURN   CT_init (unsigned short ctn, 
// 						unsigned short pn);
// 
// CTAPI_RETURN   CT_data (unsigned short ctn, 
// 						unsigned char  *dad, 
// 						unsigned char  *sad, 
// 						unsigned short lenc,  
// 						const unsigned char *command, 
// 						unsigned short *lenr, 
// 						unsigned char  *response);
// 
// CTAPI_RETURN   CT_close(unsigned short ctn);
// 	
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////


// Spezifikation der Funtionspointer gemäß MKT

typedef char  (_stdcall *FUNC_CT_INIT)(	unsigned short ctn, 
										unsigned short pn);

typedef char  (_stdcall *FUNC_CT_DATA)(unsigned short ctn, 
										 unsigned char  *dad, 
										 unsigned char  *sad, 
										 unsigned short lenc,  
										 const unsigned char *command, 
										 unsigned short *lenr, 
										 unsigned char  *response);

typedef char  (_stdcall	*FUNC_CT_CLOSE)(unsigned short ctn);


// Name der Funktionen der CTAPI gemäß MKT

#define FUNCNAME_CT_INIT			"CT_init"
#define FUNCNAME_CT_DATA			"CT_data"
#define FUNCNAME_CT_CLOSE			"CT_close"


// Werte für SAD/DAD gemäß MKT

#define CT_DEST_CARD		0
#define CT_DEST_CT			1
#define CT_DEST_APPLICATION	2

#define CTAPI_FILENAME		"ctrsct32.dll"

class CCtapiDllSample  
{
public:

	// Kartenstatus
	enum ECardMonStatus
	{
		eCardReleased,
		eCardInserted,
		eCardError,
		eCardUnknown
	};


	CCtapiDllSample();
	virtual ~CCtapiDllSample();

	// Laden/Entladen der CTAPI DLL
	BOOL			LoadDll ();
	void			FreeDll();
	BOOL			IsLoaded ();

	// Beispielimplementierung CT_OPEN / CT_CLOSE

	char	Open  (USHORT p_nCtn, USHORT p_nPort);
	char	Close ();

	ECardMonStatus	GetCardMonStatus ();

	
	// Funktionspointer der CTAPI
	// Können direkt von ausßen verwendet werden,
	// oder über gekapselte Funktionen wie z.B. in 	ECardMonStatus	GetCardMonStatus ();

	FUNC_CT_INIT		m_pFuncCtInit;
	FUNC_CT_DATA		m_pFuncCtData;
	FUNC_CT_CLOSE		m_pFuncCtClose;

protected:
	// DLL Handel der CTAPI-DLL
	HINSTANCE		m_hDLL;
	USHORT			m_nCtn;
	USHORT			m_nPort;
};





CCtapiDllSample::CCtapiDllSample()
{
	m_pFuncCtInit=0;
	m_pFuncCtData=0;
	m_pFuncCtClose=0;
	m_hDLL=0;
	m_nCtn=1;
	m_nPort=0;
}

CCtapiDllSample::~CCtapiDllSample()
{
	FreeDll (); 
}

void CCtapiDllSample::FreeDll()
{
	try
	{
		if (m_hDLL )
		{
			FreeLibrary (m_hDLL);
		}
	}
	catch (...) 
	{
		ASSERT (0);
	};

	m_pFuncCtInit=0;
	m_pFuncCtData=0;
	m_pFuncCtClose=0;

	m_hDLL=0;
};
 
BOOL CCtapiDllSample::LoadDll ()
{
	INT nRet=0;
	
	m_hDLL = LoadLibraryA (CTAPI_FILENAME); 
	if (!m_hDLL) return false;

	m_pFuncCtInit= (FUNC_CT_INIT)::GetProcAddress(m_hDLL,FUNCNAME_CT_INIT);
	m_pFuncCtData= (FUNC_CT_DATA)::GetProcAddress(m_hDLL,FUNCNAME_CT_DATA);;
	m_pFuncCtClose=(FUNC_CT_CLOSE)::GetProcAddress(m_hDLL,FUNCNAME_CT_CLOSE);


	if (!m_pFuncCtInit || !m_pFuncCtData || !m_pFuncCtClose)
	{
		FreeDll();
		return false;
	}

	return true;
}


BOOL	CCtapiDllSample::IsLoaded ()
{
	return m_hDLL!=0;
}


char CCtapiDllSample::Open (USHORT p_nCtn, USHORT p_nPort)
{
	LoadDll ();

	ASSERT (m_pFuncCtInit);
	m_nPort=p_nPort;
	return m_pFuncCtInit (p_nCtn,p_nPort);
};

char CCtapiDllSample::Close ()
{
	ASSERT (m_pFuncCtClose);
	return m_pFuncCtClose (m_nCtn);
};

CCtapiDllSample::ECardMonStatus	CCtapiDllSample::GetCardMonStatus ()
{
	ASSERT (m_pFuncCtData);

	BYTE	Dad=CT_DEST_CT;
	BYTE	Sad=CT_DEST_APPLICATION;
	ULONG	nRelevantByte=0;

	BYTE	Cmd[]={0x20, 0x13, 0x00, 0x80, 0x00}; 
	USHORT	Lc=5;

	BYTE	Rsp[100];
	USHORT	Lr=sizeof (Rsp);

	INT nRet=-1; 

	nRet=m_pFuncCtData (m_nCtn,&Dad,&Sad,Lc,Cmd,&Lr,Rsp);

	if (nRet<0)
		return eCardError;

	if (Lr < 3)
		return eCardError;

	// Ermittle SW1SW2

	if (((USHORT) ((Rsp[Lr-2]*0x0100)+(Rsp[Lr-1]))) != 0x9000)
		return eCardError;

	// Ermittle nun Card Status

	switch (Lr)
	{
	case 3:
		// Nötig zur Abwärtskompatiblität V0.9
		// Eigentlich nur cyberjack KB
		nRelevantByte=0;
		break;
	case 4:
		nRelevantByte=0;
		break;
	case 5:
		if (Rsp[0] != 0x80 || Rsp[1]!=0x01)
			return eCardError;
		// Tag 80 gefunden
		nRelevantByte=2;
		break;
	default:
		return eCardError;
		break;
	};

	return (Rsp[nRelevantByte] & 0x01) ? eCardInserted : eCardReleased;;
};


I think it is possible to use a .dll file in Xojo with a Declare statement as far as I know.

Three different functions I can use: CT_init, CT_data and CT_close.

But I am not sure how to use them. I assume that CT_init and CT_close are to start and stop the connection and CT_data to get data from the smart card.

This is what I tried so far:

Declare Function init Lib "Ctrsct32" Alias "CT_init" (ctn As Integer, pn As Integer) as CString
Declare Function data Lib "Ctrsct32" Alias "CT_data" (ctn As Integer, dad As CString, sad As CString, lenc As Integer, command As CString, lenr As Integer, response As CString) as CString
Declare Function close Lib "Ctrsct32" Alias "CT_close" (ctn As Integer) as CString

Var value As CString

Try 
  value = init(1,1)
Catch e As FunctionNotFoundException
  MessageBox(e.Message)
End Try

After I execute this code by pressing a button, I get this error:
Bildschirmfoto 2022-08-22 um 18.49.22

Try

Declare Function init Lib "Ctrsct32" Alias "CT_init" (ctn As UInt16, pn As UInt16) As Int8
Declare Function data Lib "Ctrsct32" Alias "CT_data" (ctn As UInt16, ByRef dad As Int8, ByRef sad As Int8, lenc As UInt16, command As Ptr, ByRef lenr As UInt16, response As Ptr) As Int8
Declare Function close Lib "Ctrsct32" Alias "CT_close" (ctn As UInt16) As Int8

Also make sure you try both 32/64 bit exe as it will need to match the dll as one will not work with the other.

P.S. It looks like they have a bug on:

nRet=m_pFuncCtData (m_nCtn,&Dad,&Sad,Lc,Cmd,&Lr,Rsp);

They are not sending the address of Rsp over with an &Rsp

See https://blog.samphire.net/2017/01/22/windows-to-xojo-data-type-conversion/ if you need any more help with type conversions

2 Likes

Thank you.

I had to change the architecture to x86 32-bit and with your Declare statements it looks like it works.

Declare Function init Lib "Ctrsct32" Alias "CT_init" (ctn As UInt16, pn As UInt16) As Int8
Declare Function data Lib "Ctrsct32" Alias "CT_data" (ctn As UInt16, ByRef dad As Int8, ByRef sad As Int8, lenc As UInt16, command As Ptr, ByRef lenr As UInt16, response As Ptr) As Int8
Declare Function close1 Lib "Ctrsct32" Alias "CT_close" (ctn As UInt16) As Int8

Var value_init As Int8
Var value_close As Int8
Var value_data As Int8

Try 
  value_init = init(1,1)
  If value_init = 0 Then
    MessageBox("success")
    
  Else
    MessageBox(value_init.ToString)
  End If
Catch e As FunctionNotFoundException
  MessageBox(e.Message)
End Try

value_close = close1(1)

I also found this table here:

ASN.1 =AbstractSyntaxNotationOne
ATR = Answer-to-Reset
BCD = Binary Coded Digit (4 bits)
BER = Basic Encoding Rules
BU = Biometrical Unit
CHV = Card Holder Verification
CR = Carriage Return
CT = CardTerminal
CT-API = CT Application Programming Interf.
CT-BCS=CT Basic Command Set
DAD = Destination Address
DO = Data Object
FU = Functional Unit
HB = Historical Bytes
HBCI = Homebanking Computer Interface
ICC = Integrated Circuit(s) Card
PIN = Personal Identification Number
PUK = Personal Unblocking Key
RD = Reference Data
RID = Registered application provider ID
SAD = Source Address
TLV = Tag, Length, Value
VD = Verification Data
VR = Verification Requirement
|| = Concatenation

ICC Integrated Circuit(s) Card
HTSI Host Transport Service Interface
lenc LENgth Command
lenr LENgth Response
pn Port Number
res RESult of function
RFU Reserved for Future Use
RID Registered appl. provider IDentifier
wki Well Known Identifier

The parameters of CT_data function:

CardTerminalNumber As UInt16,
ByRef DestinationAddress As Int8, 
ByRef SourceAddress As Int8, 
LengthCommand As UInt16, 
command As Ptr, 
ByRef LengthResponse As UInt16, 
response As Ptr

But how can I use CT_data()? Anyone here who knows how to use CT_data()? What are these parameters?

Other useful links:

1 Like

Any ideas?

Sorry I didn’t notice the question in the middle there.

Did you manage to get the example from the code you posted to work in Xojo code? GetCardMonStatus?

I think this is completely wrong, but I tried.

Function GetCardMonStatus():

Declare Function init Lib "Ctrsct32" Alias "CT_init" (ctn As UInt16, pn As UInt16) As Int8
Declare Function data Lib "Ctrsct32" Alias "CT_data" (ctn As UInt16, dad As Int8, sad As Int8, lenc As UInt16, command As Ptr, ByRef lenr As UInt16, response As Ptr) As Int8
Declare Function close1 Lib "Ctrsct32" Alias "CT_close" (ctn As UInt16) As Int8

Var nRelevantByte As Integer
Var mb As New MemoryBlock(5)
mb.Int8Value(0) = 20
mb.Int8Value(1) = 13
mb.Int8Value(2) = 00
mb.Int8Value(3) = 80
mb.Int8Value(4) = 00
Var cmd As Ptr = mb
Var mb2 As New MemoryBlock(100)
Var rsp As Ptr = mb2
Var lr As UInt16 = mb2.Size
Var nRet As Integer = -1 

nRet = data(CType(1, UInt16), CType(1, Int8), CType(1, Int8), CType(5, UInt16), cmd, lr, rsp)

If nRet < 0 Then
  return "error"
End If

If lr < 3 Then
  return "error"
End If

If ( (mb2(lr-2)*100)+(mb2(lr-1)) <> 9000 ) Then // wrong?
  return "error";
End If

Select Case lr
Case 3
  nRelevantByte = 0
Case 4
  nRelevantByte = 0
Case 5
  If (rsp(0) <> 80 OR rsp(1) <> 1) // wrong?
    return "error"
  End If
  nRelevantByte = 2
Else
  return "error";
End Select

If rsp(nRelevantByte AND 1) Then // wrong?
  return "Card inserted"
Else
  return "Card released"
End If

Try this:

Const CT_DEST_CARD = 0
Const CT_DEST_CT = 1
Const CT_DEST_APPLICATION = 2

Declare Function init Lib "Ctrsct32" Alias "CT_init" (ctn As UInt16, pn As UInt16) As Int8
Declare Function data Lib "Ctrsct32" Alias "CT_data" (ctn As UInt16, dad As Int8, sad As Int8, lenc As UInt16, command As Ptr, ByRef lenr As UInt16, response As Ptr) As Int8
Declare Function close1 Lib "Ctrsct32" Alias "CT_close" (ctn As UInt16) As Int8

Var Dad As Int8 = CT_DEST_CT
Var Sad As Int8 = CT_DEST_APPLICATION
Var nRelevantByte As Integer

Var Cmd As New MemoryBlock(5)
Cmd.Int8Value(0) = &h20
Cmd.Int8Value(1) = &h13
Cmd.Int8Value(2) = &h00
Cmd.Int8Value(3) = &h80
Cmd.Int8Value(4) = &h00
Var Lc As UInt16 = Cmd.Size

Var Rsp As New MemoryBlock(100)
Var Lr As UInt16 = Rsp.Size

Var nRet As Integer = -1

nRet = data(1, Dad, Sad, Lc, Cmd, lr, Rsp)

If nRet < 0 Then
  return "error"
End If

If Lr < 3 Then
  return "error"
End If

If CType((Rsp.Int8Value(lr - 2) * &h0100) + (Rsp.Int8Value(Lr - 1)), UInt16) <> &h9000 Then
  Return "error"
End If

Select Case Lr
Case 3
  nRelevantByte = 0
Case 4
  nRelevantByte = 0
Case 5
  If ((Rsp.Int8Value(0) <> &h80) Or (Rsp.Int8Value(1) <> &h01)) Then
    return "error"
  End If
  nRelevantByte = 2
Else
  Return "error"
End Select

If CType(rsp.Int8Value(nRelevantByte) And 1, Boolean) Then
  return "Card inserted"
Else
  return "Card released"
End If
1 Like

So as soon as the application executes GetCardMonStatus() the application closes/exits without errors.

Step line by line, does it crash on the call to data ?

1 Like
Declare Function init Lib "Ctrsct32" Alias "CT_init" (ctn As UInt16, pn As UInt16) As Int8
Declare Function data Lib "Ctrsct32" Alias "CT_data" (ctn As UInt16, ByRef dad As Int8, ByRef sad As Int8, lenc As UInt16, command As Ptr, ByRef lenr As UInt16, response As Ptr) As Int8
Declare Function close1 Lib "Ctrsct32" Alias "CT_close" (ctn As UInt16) As Int8

Var value_init As Int8
Var value_close As Int8
Var value_data As Int8

Try 
  value_init = init(1,1)
  MessageBox(value_init.ToText)
  Var retval As String = App.GetCardMonStatus()
  MessageBox(retval)
Catch e As RuntimeException
  MessageBox(e.Message)
End Try

value_close = close1(1) 

I tried debugging.

init(1,1) returns 0 in a MessageBox and then the application just closes its window after executing GetCardMonStatus(). I can’t step line by line with the debugger, the debugger exits too after the application window closes.

If you put a breakpoint on this line

nRet = data(1, Dad, Sad, Lc, Cmd, lr, Rsp)

does it stop there or does it crash before it gets there?

If you don’t know how to breakpoint change it to the following:

break
nRet = data(1, Dad, Sad, Lc, Cmd, lr, Rsp)
1 Like

If I put a break there, it continues to run, but then if I click on resume it crashes.

Try changing this to:
Var Rsp As New MemoryBlock(65535)

which is what their c# example uses on the github you linked above.

If that doesn’t work, ask them to send you an example that works (preferably a visual studio project), because that example will not compile due to the error I mentioned in post #3 above which leads me to believe that the example is faulty.

1 Like

With the MemoryBlock of size 65535, it still doesn’t work. I’m contacting them, but I don’t think that they will respond today.

Done. I contacted the support. Now I guess we have to wait.

For everyone who’s wondering what &h means, here you go.

My mistake, the call to data looks correct, I’m not sure why its not working, maybe their support will shed some light onto the problem but without a working example its hard to say what the problem is.

1 Like

Try this, for some reason the ByRef’s were removed from dad and sad in the data declare when you pasted your example above and I fixed the lower portion of that version not noticing that they were changed from the code I posted previously:

Const CT_DEST_CARD = 0
Const CT_DEST_CT = 1
Const CT_DEST_APPLICATION = 2

Declare Function init Lib "Ctrsct32" Alias "CT_init" (ctn As UInt16, pn As UInt16) As Int8
Declare Function data Lib "Ctrsct32" Alias "CT_data" (ctn As UInt16, ByRef dad As Int8, ByRef sad As Int8, lenc As UInt16, command As Ptr, ByRef lenr As UInt16, response As Ptr) As Int8
Declare Function close1 Lib "Ctrsct32" Alias "CT_close" (ctn As UInt16) As Int8

Var Dad As Int8 = CT_DEST_CT
Var Sad As Int8 = CT_DEST_APPLICATION
Var nRelevantByte As Integer = 0

Var Cmd As New MemoryBlock(5)
Cmd.Int8Value(0) = &h20
Cmd.Int8Value(1) = &h13
Cmd.Int8Value(2) = &h00
Cmd.Int8Value(3) = &h80
Cmd.Int8Value(4) = &h00
Var Lc As UInt16 = Cmd.Size

Var Rsp As New MemoryBlock(65535)
Var Lr As UInt16 = Rsp.Size

Var nRet As Integer = -1

nRet = data(1, Dad, Sad, Lc, Cmd, lr, Rsp)

If nRet < 0 Then
  Return "error"
End If

If Lr < 3 Then
  return "error"
End If

If CType((Rsp.Int8Value(lr - 2) * &h0100) + (Rsp.Int8Value(Lr - 1)), UInt16) <> &h9000 Then
  Return "error"
End If

Select Case Lr
Case 3
  nRelevantByte = 0
Case 4
  nRelevantByte = 0
Case 5
  If ((Rsp.Int8Value(0) <> &h80) Or (Rsp.Int8Value(1) <> &h01)) Then
    return "error"
  End If
  nRelevantByte = 2
Else
  Return "error"
End Select

If CType(rsp.Int8Value(nRelevantByte) And 1, Boolean) Then
  return "Card inserted"
Else
  return "Card released"
End If
1 Like

At least I get “error” in a MessageBox without crashing. I have a card in my card reader and still get an error. Normally “Card inserted” should be returned, right?

Const CT_DEST_CARD = 0
Const CT_DEST_CT = 1
Const CT_DEST_APPLICATION = 2

Is this correct?

I assume that ByRef parameters passed to a method can be changed within a method and then used with the changed value. Is this correct?

Change the error messages to error1 error2 etc so you know where it is erroring and go from there, its a little hard for me to help you here as 1) I dont have the dll 2) I dont have the card reader 3) The docs are in greman and 4) I dont understand german :slight_smile:

I would assume card inserted would return, yes if it gets that far.

Yes, your byref question is correct.

1 Like