Vous êtes sur la page 1sur 14

Smart Card framework for .

NET - Part I

Introduction

The .NET framework has been introduced in 2002, and the version 3.0 has just been released in
November. So far, Microsoft hasn't included Smart Card classes in .NET, and if you want to develop a
Smart Card aware application, you have to develop your own classes. Fortunately, it is much easier to
reuse existing code in .NET than with Java. In Windows, if you need to use Smart Card, you just need to
use the PC/SC API in your program. This API comes in C functions or COM objects that wrap the PC/SC
functions. The .NET Framework offers two types of interoperabilities with the legacy code: the COM
interoperbility, and the P/Invoke feature for native code interoperability.

Background

This article demonstrates how to use the interoperability features of .NET and use them to write a simple
framework to use a Smart Card in your applications. A Smart Card is a small embedded device that
receives commands through a card reader using the PC/SC Win32 API. If you want to use this API, you
will need a Smart Card reader to use a Smart Card such as a SIM card.

A simple Smart Card framework

The SC framework I'm going to describe is composed of an interface to communicate with the Smart Card,
a few classes to wrap the different parameters of a Smart Card command, and the implementation classes
depending on the interop mode we are using.

The Smart Card interface provides a simple access to a Smart Card for .NET programs. We will see later
how to implement this interface using both the interoperability techniques.

spublic interface ICard


{
string[] ListReaders();
void Connect(string Reader, SHARE ShareMode,
PROTOCOL PreferredProtocols);
void Disconnect(DISCONNECT Disposition);
APDUResponse Transmit(APDUCommand ApduCmd);
void BeginTransaction();
void EndTransaction(DISCONNECT Disposition);
}

The classes APDUCommand and APDUResponse are used to send the command and get the response from
the card. SHARE, PROTOCOL, and DISCONNECT are constants used by PC/SC.

public class APDUCommand


{
public APDUCommand(byte bCla, byte bIns,
byte bP1, byte bP2, byte[] baData, byte bLe);
public void Update(APDUParam apduParam);
public override string ToString();
public byte Class;
public byte Ins;
public byte P1;
public byte P2;
public byte[] Data;
public byte Le;
}
public class APDUResponse
{
public APDUResponse(byte[] baData);
public byte[] Data;
public byte SW1;
public byte SW2;
public ushort Status;
public override string ToString();
}

Adding card events support

When a card is inserted in the reader or removed, PC/SC allows you to handle those events. So I added a
card event support to this framework based on the event model of .NET. The class CardBase, which
inherits from the interface ICard, implements support for two
events, CardInserted and CardRemoved. The mechanism of detection is implemented in the derived
class that implements ICard. So far I only implemented this support in CardNative. If you want to
support the event in your application program, you just need to implement
theCardInsertedEventHandler and the CardRemovedEventHandler.

abstract public class CardBase : ICard


{
public event CardInsertedEventHandler OnCardInserted = null;
public event CardRemovedEventHandler OnCardRemoved = null;

abstract public string[] ListReaders();


abstract public void Connect(string Reader,
SHARE ShareMode, PROTOCOL PreferredProtocols);
abstract public void Disconnect(DISCONNECT Disposition);
abstract public APDUResponse Transmit(APDUCommand ApduCmd);
abstract public void BeginTransaction();
abstract public void EndTransaction(DISCONNECT Disposition);

public void StartCardEvents(string Reader);


public void StopCardEvents();

abstract protected void RunCardDetection();

protected void CardInserted();


protected void CardRemoved();
}

I developed two implementations of the interface ICard. One is using COM interoperability, and the other
using native interoperability with P/Invoke. Both implementations behave the same way. In addition, I
ported the P/Invoke implementation for the Compact Framework so it is possible to develop Smart Card
applications for the Pocket PC.

CardCOM: a COM interoperability implementation class

COM interoperability is the simplest way to reuse legacy code in .NET framework. All versions of Visual
Studio .NET provide very good support for COM. You just need to add a reference to the COM object you
need to import, and it generates a wrapper DLL and all the necessary classes to use your COM interfaces.
Basically, you don't have to write any code. However, your COM components must respect a few rules,
specially regarding the parameters used in the methods. The different interfaces of the PC/SC COM
components were written long before .NET existed, and some of the interfaces are not totally compliant
with COM interoperability. This is why I had to develop my own COM interface to get the list of readers.
For the most important interface, ISCard and the interfaces it uses the import went fine.

interface ISCardDatabaseEx : IDispatch{


[id(1), helpstring("method ListReaders")]
HRESULT ListReaders([out,retval] VARIANT* ppReaders);
};

The COM component I developed replaces the ISCardDatabase interface, and only implements one of its
methods: ListReaders. In the Microsoft implementation, the return parameter is a SAFEARRAY of BSTR,
which unfortunately .NET is unable to import properly. The right way to do it is to use a VARIANT* that
contains the SAFEARRAY of BSTR. Then, .NET will generate a wrapper method that returns an object that
you just have to cast into a string[]. Using a COM object in .NET is as simple as adding a reference to
this object in your code. Visual Studio will then generate a wrapper class that you directly use in your
code. The following extract illustrates this.

using SCARDSSPLib; // use the SCard COM object

/// <summary>
/// Default constructor
/// </summary>
public CardCOM()
{
// Create the SCard object
m_itfCard = new CSCardClass();
}

public override void Connect(string Reader,


SHARE ShareMode, PROTOCOL PreferredProtocols)
{
// Calls AttachReader to connect to the card
m_itfCard.AttachByReader(Reader,
(SCARD_SHARE_MODES) ShareMode,
(SCARD_PROTOCOLS) PreferredProtocols);
}

The ISCardDatabase interface is provided in a DLL that must be registered with Regsvr32.

CardNative: A native interoperability implementation class using


P/Invoke

The Platform Invoke mechanism (P/Invoke) is a very powerful mechanism that gives total access to the
Win32 platform API. The .NET framework provides a complete set of classes that you can use to achieve
every marshaling operation necessary to call Win32 functions from .NET. Those classes are defined in
the System.Runtime.InteropServices assembly that you just need to import in your program. The
P/Invoke mechanism, even if it is a bit complex, is far more convenient than the JNI mechanism of Java.
All the atomic types like int, byte,long, etc... are automatically marshaled by the compiler itself.
The byte[] is also automatically marshaled as an input or output parameter. When you have to deal with
more complex parameters like strings, structures, or a pointer to a structure, .NET provides a set of
marshaling classes that can be used as attributes or objects in your program when marshaling
parameters. To develop the CardNative class, I didn't have to develop any extra code to use the PC/SC
API, all the code is in the source file CardNative.cs. When you want to use a Win32 API C function, you
need to declare the function in your class using the interoperability attributes provided by
the System.Runtime.InteropServices assembly. Once you have declared the function, you can just
use it as any C# method in your class. The following code sample illustrates this mechanism.

[DllImport("winscard.dll", SetLastError=true)]
internal static extern int SCardTransmit(UInt32 hCard,
[In] ref SCard_IO_Request pioSendPci,
byte[] pbSendBuffer,
UInt32 cbSendLength,
IntPtr pioRecvPci,
[Out] byte[] pbRecvBuffer,
out UInt32 pcbRecvLength
);

public override APDUResponse Transmit(APDUCommand ApduCmd)


{
uint RecvLength = (uint) (ApduCmd.Le + APDUResponse.SW_LENGTH);
byte[] ApduBuffer = null;
byte[] ApduResponse = new byte[ApduCmd.Le + APDUResponse.SW_LENGTH];
SCard_IO_Request ioRequest = new SCard_IO_Request();
ioRequest.m_dwProtocol = m_nProtocol;
ioRequest.m_cbPciLength = 8;

// Build the command APDU

if (ApduCmd.Data == null)
{
ApduBuffer = new byte[APDUCommand.APDU_MIN_LENGTH +
((ApduCmd.Le != 0) ? 1 : 0)];

if (ApduCmd.Le != 0)
ApduBuffer[4] = (byte) ApduCmd.Le;
}
else
{
ApduBuffer = new byte[APDUCommand.APDU_MIN_LENGTH + 1 +
ApduCmd.Data.Length];
for (int nI = 0; nI < ApduCmd.Data.Length; nI++)
ApduBuffer[APDUCommand.APDU_MIN_LENGTH + 1 + nI] =
ApduCmd.Data[nI];
ApduBuffer[APDUCommand.APDU_MIN_LENGTH] =
(byte) ApduCmd.Data.Length;
}

ApduBuffer[0] = ApduCmd.Class;
ApduBuffer[1] = ApduCmd.Ins;
ApduBuffer[2] = ApduCmd.P1;
ApduBuffer[3] = ApduCmd.P2;

m_nLastError = SCardTransmit(m_hCard,
ref ioRequest,
ApduBuffer,
(uint) ApduBuffer.Length,
IntPtr.Zero, ApduResponse, out RecvLength);

if (m_nLastError != 0)
{
string msg = "SCardTransmit error: " + m_nLastError;
throw new Exception(msg);
}

byte[] ApduData = new byte[RecvLength];

for (int nI = 0; nI < RecvLength; nI++)


ApduData[nI] = ApduResponse[nI];

return new APDUResponse(ApduData);


}
Demo application

The Smart Card API is very easy to use. However, if you want to write a Smart Card application, you must
know the commands to send to the card to perform operations such as selecting files, verifying PINs, or
reading data. Those commands are called APDU commands, and are described in the specification of the
Smart Card you want to access. The purpose of this article is not to explain how to use a Smart Card, but
to give you a simple C# API to play with any Smart Card you'd want.

Most people have a GSM phone so if you have a Smart Card reader on your PC (some laptops come with
an embedded Smart Card reader), you can use the little demo program to play with your SIM card. This
simple program presents the PIN (if your PIN is activated), selects the phone number file, and reads the
10 first records of this file. Everything you will get is in binary format like it is stored in the card. If your
PIN is activated, you must uncomment the line that verifies the PIN in my code. The PIN value is entered
in binary. If your PIN is 1234, you must enter 31323334 and pad with FF bytes until the PIN length is 8.

In the second part of this article, I will present a more advanced framework to write Smart Card
applications. This framework will use the classes I described in this article, and will make it easier writing
Smart Card applications.

Points of interest

In this first part, we have seen two methods to use legacy code with .NET. This is one of the very powerful
features of .NET that makes it the ideal managed framework to develop applications on the Windows
platform. This Smart Card framework can be very useful if you want to access a Smart Card application in
your .NET code. Of course, VB.NET users can use this framework as well.

However, this set of class still requires that you write dedicated code to access the card using the APDU
command which is not the most interesting code to write. In the second part of this article, I will give you
an XML framework that allows to write Smart Card applications with minimum code, using XML
declarations.
Smart Card framework for .NET - Part II

Introduction

In the first part, I described how to develop a set of classes with C# to wrap the PC/SC API of Windows.
PC/SC is a set of APIs used to communicate with a Smart Card. Even if the classes are easier to use than
the PC/SC functions, you still need to write code that is not always easy to understand. Communication
with a Smart Card uses a protocol named APDU to send the commands to the card, they are called APDU
commands. The framework I propose is designed to simplify the development of a Smart Card application.
Most of the time, you would have to chain several commands to perform an action like reading or writing
a file. With the framework I describe here, you can easily chain commands and then describe the Smart
Card elements of your application with an XML description.

Background

This article assumes that you have read the Part 1 of this article and that you have some knowledge of
XML. Even if not necessary, a basic knowledge of Smartcard programming can help.

The APDU command protocol

Most people have a mobile phone and so use a Smart Card every day. For example, when you enter your
PIN on your mobile phone or read a phone number in your SIM card directory, your mobile sends a set of
commands to the card to perform the action. An APDU command is a set of bytes that are send to the
card that will respond with a code and eventually with the data you want to read.

APDU command description

An APDU command is basically composed of the following bytes:

APDU Length in
Description
byte bytes

Class 1 Class byte

Ins 1 Instruction byte

P1 1 Parameter 1

P2 1 Parameter 2

This parameter is either the length of the sent data or the length of the
P3 1
expected data

Data N Data to send to the card

The card will respond to a command with an array of bytes.

Bytes Length Description


Data 0 to 255 Response data

SW1,
2 Command status
SW2

An APDU command always returns at least two bytes that are the status bytes. When the command
returns some data, the status bytes are the last two bytes returned by the command.

There are five different configurations to send commands to a card. They are categorized in two groups,
commands that send data and commands that receive data.

APDU to send data

No data to send, no data to receive

CLASS INS P1 P2 P3 SW1 SW2

lgth =
90 00
0

Sending data to the card

CLASS INS P1 P2 P3 DATA with length datalgth SW1 SW2

datalgth 90 00

If datalgth = 0, it sends 256 bytes to the card.

On a normal execution, those commands will answer with 9000 or with an error code.

APDU to receive data

Receiving data of known length

CLASS INS P1 P2 P3 DATA of length datalgth SW1 SW2

datalgth 90 00

Receiving data of unknown length

When a command requests data from the card with an unknown number of bytes, you must first send the
command with a length of 0 to get the number of bytes that the card will return. Then, you can call
the GET RESPONSE command with a request length lower or equal to the given length.

1 - Send the command with the requested length of 0.


CLASS INS P1 P2 P3 SW1 SW2

0 9F lgth

2 - Send the GET RESPONSE with req_lgth < lgth.

DATA of
CLASS INS P1 P2 P3 SW1 SW2
length req_lgth <= lgth

C0 0 0 req_lgth 90 00

Sending data and receiving data of known or unknown length

This case is very similar to the previous one. The difference is that you are first sending a command that
transmits data to the card. This command replies with a 9FXX code where XXindicates the length of the
maximum data you can read with the GET RESPONSE command.

1 - Send data to the card

CLASS INS P1 P2 P3 DATA with length datalgth SW1 SW2

datalgth 90 00

2 - Send the GET RESPONSE with req_lgth < lgth.

DATA of
CLASS INS P1 P2 P3 SW1 SW2
length req_lgth <= lgth

C0 0 0 req_lgth 90 00

SIM card commands

The commands that can be sent to a SIM card are described in a specification called 3GPP TS 11.11
(a.k.a. GSM 11.11). I won't describe here the whole set of commands and the files of the SIM card. If you
are interested in it, you can find a version of the GSM1111 here.

Here is a short list of the commands for a SIM card:

Class byte for GSM is A0, S indicates that data is sent to the card, and R that data is received from the
card. All byte values are given in hexadecimal.

COMMAND INS p1 P2 P3 S/R

SELECT A4 00 00 02 S/R

STATUS F2 00 00 lght R
READ BINARY B0 offset_high offset_low lgth R

UPDATE BINARY D6 offset_high offset_low lgth S

READ RECORD B2 rec_No mode lgth R

UPDATE RECORD DC rec_No mode lgth S

VERIFY CHV 20 00 CHV_No 08 S

CHANGE CHV 24 00 CHV_No 10 S

RUN GSM ALGORITHM 88 00 00 10 S/R

GET RESPONSE C0 00 00 lgth R

Now, let us see how it is possible to write an XML framework to simplify the writing of Smart Card
applications.

XML framework for APDU commands

The basic idea is to provide a simple and flexible framework in XML to describe APDU commands and write
an application which can be a sequence of several commands or of sequences. A command is the atomic
unit of description, it can be used alone or from a sequence if parameters need to be passed to the
command itself.

APDU command

An atomic APDU command is represented with an XML element. APDU commands are assembled in
a ApduList document. The ApduList and the Apdu elements are defined by the following schema:

<xs:schema attributeFormDefault="unqualified"
elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="ApduList">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" name="Apdu">
<xs:complexType>
<xs:attribute name="Name" type="xs:string" use="required" />
<xs:attribute name="Class" type="xs:string" use="required" />
<xs:attribute name="Ins" type="xs:string" use="required" />
<xs:attribute name="P1" type="xs:unsignedByte" use="required" />
<xs:attribute name="P2" type="xs:unsignedByte" use="required" />
<xs:attribute name="P3" type="xs:string" use="required" />
<xs:attribute name="Data" type="xs:string" use="optional" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
A command file contains a set of pre-defined APDU commands. A command can be played by its name
after the APDU command list has been loaded. There are different types of commands; some commands
can be played individually, and some need to use the result of a previous command.

When the P3 parameter represents the length of expected data from the command, its value can be the
result of the previous call of a command, or the command should have to be replayed to get this variable
value. Here is the syntax accepted by the parameter to handle this case:

P3 = "R,0:SW1?xx"

R indicates that the command must be replayed with a condition on the value of SW1 after a first call
with P3=0. If SW1 == xx, the command will be replayed with P3 = SW2.

<Apdu Name="Get OTP ID" Class="A0" Ins="1A"


P1="80" P2="2" Lc="0" Le="R,0:SW1?6C" Data="" />

P3 = "R,xx:DRyy"

R indicates that the command must be replayed, but without the condition this time. On the first
call, P3 = xx, then the command is replayed with P3 = xx + RespData[yy] (yyth data of the response,
the first data index is 1 in the norm).

<Apdu Name="Get Status" Class="A0" Ins="F2" P1="0"


P2="0" Lc="0" Le="R,13:DR13" Data="" />

P3 = "SW2"

If SW1 == 0x9F on the previous call to a command, then this command is played using P3 = SW2 of the
previous command.

<Apdu Name="Get Response" Class="A0" Ins="C0" P1="0" P2="0" P3="SW2" />

P3 = "DRxx"

If there were data from the previous command, xx is used as the index in the response data to get the
value of P3. Le = RespData[xx].

<Apdu Name="Read Binary" Class="A0" Ins="B0" P1="0" P2="0" P3="DR15" />

Sequence of commands

A sequence of commands is used to chain atomic APDU commands or sequences. A sequence can use
parameters that are given to commands that compose it. A SequenceList describes a set
of Sequence elements that can be called in an application. The following schema describes
a SequenceList of Sequence elements:

<xs:schema attributeFormDefault="unqualified"
elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="SequenceList">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" name="Sequence">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" name="Command">
<xs:complexType>
<xs:attribute name="Apdu"
type="xs:string" use="optional" />
<xs:attribute name="P2"
type="xs:unsignedByte" use="optional" />
<xs:attribute name="Data"
type="xs:string" use="optional" />
<xs:attribute
name="Sequence" type="xs:string" use="optional" />
<xs:attribute name="P1"
type="xs:string" use="optional" />
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="Name" type="xs:string" use="required" />
<xs:attribute name="PIN" type="xs:string" use="optional" />
<xs:attribute name="Record"
type="xs:unsignedByte" use="optional" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

The Sequence element allows you to pass parameters to a command or another Sequence.
The Sequence uses a subelement that is Command. A Command is used to call an APDU with or without
parameters. When a parameter is given in a Command, it overrides the default parameter of the APDU
element.

For example, if you want to verify a PIN code, you can use the following Sequence:

<Sequence Name="Verify CHV1" PIN="FFFFFFFFFFFFFFFF">


<Command Apdu="Verify CHV" P2="1" Data="PIN"/>
</Sequence>

The class that is used to call this sequence allows you to give a value to the PIN parameter. When
the Command is executed, the parameter Data will get the value given for the parameter
PIN. A Sequence parameter can take any name. In this version, the value of the parameter is a string
that represents a set of byte values.

The code to call this Sequence is the following:

SequenceParameter seqParam = new SequenceParameter();

// Process Apdu: VerifyCHV


Console.WriteLine("Sequence: Verify CHV1");

seqParam.Add("PIN", "31323334FFFFFFFF");
apduResp = player.ProcessSequence("Verify CHV1", seqParam);
Console.WriteLine(apduResp.ToString());
Now that we have seen how to declare the commands and chain them using the Sequence element, I'm
going to describe the code that takes advantage of this simple 'XML language'.

APDUPlayer: A C# class to be used with the XML description

In order to use the XML format previously described, I have developed a simple C# class which is able to
process APDU commands or APDU sequences.

The class APDUPlayer has three constructors, and exposes a few public methods. The two most
important methods are those that are used to execute an APDU command or a sequence of APDUs.

The following method is used to execute a single APDU command. The APDUParam parameter can be used
to modify a parameter of the APDU that was read from the XML description.

/// <summary>
/// Process a simple APDU command, Parameters
/// can be provided in the APDUParam object
/// </summary>
/// <param name="command">APDU command name</param>
/// <param name="apduParam">Parameters for the command</param>
/// <returns>An APDUResponse object with the response of the card </returns>
public APDUResponse ProcessCommand(string apduName, APDUParam apduParam);

This other method is used to execute a sequence of APDUs. The SequenceParameter class is a list of
parameter/value couple that are used as input parameters for the sequence to play.

/// <summary>
/// Process an APDU sequence and execute each
/// of its commands in the sequence order
/// </summary>
/// <param name="apduSequenceName">Name of the sequence to play</param>
/// <param name="seqParam">An array of SequenceParam
/// object used as parameters for the sequence</param>
/// <returns>APDUResponse object of the last command executed</returns>
public APDUResponse ProcessSequence(string apduSequenceName,
SequenceParameter seqParam);

Using this method has been shown previously.

A sample application to read the phone book of a SIM card

In my previous article, we read the phone book of a SIM card using the PC/SC wrapper I described, but
this application was just getting the raw content of the phone book file (6F3A). In this sample, we are
going to interpret the data of each record. The content of the phone record is the following:

This EF contains the Abbreviated Dialing Numbers (ADN). In addition, it contains identifiers of associated
network/bearer capabilities and identifiers of extension records. It may also contain an associated alpha
tagging.

Record length: X + 14 bytes


Identifier: 6F3A

Bytes Description Mandatory/Optional Length


1 to X Alpha identifier O X bytes

X+1 Length of phone number string M 1 byte

X+2 TON & NPI M 1 byte

X + 3 to X +
Phone number string M 10 bytes
12

X + 13 CCP identifier M 1 byte

X + 14 Extension 1 record

This specification has been taken from the GSM11.11 document that specifies the logic of the SIM card.
We are going to extract from this description the information we need to get the phone number and its
associated name.

The first bytes are the name that appears on your mobile phone with the phone number. It is a string
using a coding close to the ASCII coding by default.

The fourteenth bytes that follow contain the phone number itself and some description bytes. The first
byte is the length of the phone number in bytes, including the TON & NPI byte which indicates if the
number is national (Ax, 8x) or international (9x).

The ten last bytes of this group contain the phone number itself. The coding is BCD where the bytes are in
reverse order. If the number of digits is odd, then the last byte of number contains a F and the digit. For
example, the number 015648327 is coded the following way: 10658423F7.

The two last bytes of the record contain data that are rarely used.

The class PhoneNumber is a helper class provided to interpret the bytes of a phone number record. It is
used the following way:

PhoneNumber phone = new PhoneNumber(apduResp.Data);


Console.WriteLine("ADN n°" + nI.ToString());
Console.WriteLine(phone.ToString());

The ToString method gets a string of the content of the phone record in the following format, <name> :
<number>.

The program ReadPhonebook is a console application that reads the tenth first phone numbers of the ADN
file by default. You can read another number of records giving it as a parameter to the program.

Command line: ReadPhonebook P <pincode> <nbRecord>

The parameters are optional.

 P <pincode>: PIN code to present to the card. If you don't give this parameter, no PIN code will be
presented to the card.
 <nbRecord>: Number of records to read. By default, the program reads 10 records.

Examples:

 ReadPhonebook P 1234 25, will present 1234 as the PIN and read 25 records.
 ReadPhonebook 50, reads 50 records, doesn't present any PIN.
 ReadPhonebook P 4567, presents 4567 as PIN and reads 10 records.

APDExchange application

The APDUExchange Windows Forms application is a basic C# application that can be used to send any
APDU command to a card. It is possible to type the command or use an APDUListfile to preload
commands.

The application automatically detects card insertion or removal on demand. It uses the NativeCard
implementation of the Smart Card API described in the previous article.

The Exchange APDU form looks like this:

If you want to send a command that is not in the APDU list, you just need to add the command to the file
using the format previously described.

Points of Interest

I have been working in the Smart Card industry for quite some time, and I rarely find articles about this
topic on the internet. As Smart Cards are now largely adopted and that some computers even have an
embedded Smart Card reader, I thought it would be interesting to demonstrate how a simple XML
framework could simplify the development of a Smart Card enabled application. You can use this code and
extend the framework to your needs, and I hope that these articles have shown you that using Smart
Cards could be quite simple.

Vous aimerez peut-être aussi