This tutorial shows how to create a very simple script to read the UID of a contactless card. To execute this script, you need a PC/SC 2.0 complicant contactless smart card reader and a contactless card.
This tutorial presumes that you are familiar with smart card APDUs as defined in ISO 7816-4.
Smart Card Scripter uses a Pascal-like syntax. As any standard Pascal application, each script starts with the keyword "begin" and ends with the keyword "end" followed by a single dot.
begin
end.
This most simple script does of course nothing yet. We need to add some more commands. All commands are inserted between the keywords begin and end. All variables andf constants are defined before the keyword begin.
APDUs
The main purpose of the Smart Card Scripter is to send commands to smart cards. These commands are called APDUs.
To send an APDU to a card, the command "APDU" can be used. The syntax of this command is quite simple. The only parameter is actually the APDU as a hexadecimal string. The APDU to read the UID of a contactless card for example is "FF CA 00 00 00". To send this APDU to a smart card, the script can be modified as follows:
begin
APDU('FF CA 00 00 00');
end.
The number of white spaces inside the APDU string doesn't matter. White spaces just make the APDU easier to read.
If we execute this script, we see the UID of the card in the log area of the Smart Card Scripter. The backdraw of the APDU command is, that it doesn't allow to process any card responses. We, can see the UID, but we cannot use it in subsequent commands.
If we want to process the card response, we have to use one of the Transmit commands, eg. as follows:
var
Response: TBuffer;
begin
TransmitAsString('FFCA000000', Response);
end.
The TransmitAsString command works much like the APDU command, but it takes a 2nd parameter in which the card response is stored. This second parameter is of type TBuffer. A TBuffer is basically an array of bytes with a dynamic lenght, much like a text string. Before we can use a varioable, we need to declare it at the top of the script using the keyword "var".
After executing this script, the variable "Response" contains the response of the card. In best case, this is the UID of the card followed by a status word. But it could also be a status word that indicates an error condition. Let's add another line of code to the script to print the content of the variable "response".
var
Response: TBuffer;
begin
TransmitAsString('FFCA000000', Response);
DumpBuffer(Response);
end.
The command "dumpbuffer" simply prints the content of any variable of type TBuffer. When we use our script with a contactless card, we will see, that the Response not only contains the UID, but also two additional bytes with the value 0x9000. This is the status word. To get the UID, we need to remove this status word. There are two ways to do this. We can either remove the last two bytes from the card response manually or we can use another type of the Transmit command that automatically cuts the status word off the card response. Let's do it the second way:
var
UID: TBuffer;
SW12: Word;
begin
TransmitAsStringSW('FFCA000000', UID, SW12);
DumpBuffer(UID);
end.
The modified script now uses the command TransmitAsStringSW, which has an additional parameter of type word in which the status word of the card response is stored.
Error handling
All Transmit commands can of course fail, e.g if no smart card is present. It is good programming style to check for errors, so let's modify the script as follows to check the result of the Transmit command:
var
UID: TBuffer;
SW12: Word;
begin
if TransmitAsStringSW('FFCA000000', UID, SW12) <> SCARD_S_SUCCESS then exit;
DumpBuffer(UID);
end.
All Transmit commands always return SCARD_S_SUCCESS (=0) on successful execution. If we get a different return value, we exit the script using the keyword exit.
If the transmit command returns SCARD_S_SUCCESS, this only means that the communication with the smart card was successful. It doesn't mean that the smart card was able to process the APDU. We also need to check the status word, maybe as follows:
var
UID: TBuffer;
SW12: Word;
begin
if TransmitAsStringSW('FFCA000000', UID, SW12) <> SCARD_S_SUCCESS then exit;
if SW12<>$9000 then exit;
DumpBuffer(UID);
end.
We assume in this example, that the status word ist 9000 on successful execution of the APDU. If we get a different status word, we exit the script. This is true for this particular APDU, but other APDUs can return other status words which also indicate a sucessfull execution of the APDU.
Good programming style
In PC/SC, an application needs to connect to a smart card first, before it can send APDUs to the card. Although Smart Card Scripter automatically and implicitely connects to a smart card, it is better programming style, to explicitely connect to the card at the beginning of the script and to disconnect from the card at the end of the script. Let's modify our script accordingly:
var
UID: TBuffer;
SW12: Word;
begin
if ConnectCard = SCARD_S_SUCCESS then try
if TransmitAsStringSW('FFCA000000', UID, SW12) <> SCARD_S_SUCCESS then exit;
if SW12<>$9000 then exit;
DumpBuffer(UID);
finally
Disconnect;
end;
end.
We now added the command ConnectCard at the beginning of the script to connect to the card and we added the command Disconnect at the end of the script to disconnect from the card. The APDUs are now included in a try finally block, so that the Disconnect command is always executed, even if the script exits on error.
Text output
In our sample script, we print the UID using the command DumpBuffer. There are more powerful commands for text output, especially the command WriteLnF. WriteLnF takes one parameter of type string as input and prints this string to the log area of Smart Card Scripter. WriteLnF supports some basic HTML-like style tags. Let's print the UID in bold and blue and with a caption:
var
UID: TBuffer;
SW12: Word;
begin
if ConnectCard = SCARD_S_SUCCESS then try
if TransmitAsStringSW('FFCA000000', UID, SW12) <> SCARD_S_SUCCESS then exit;
if SW12<>$9000 then exit;
WriteLnF('UID = <b><c:clBlue>' + BufferToHexString(UID));
finally
Disconnect;
end;
end.
The printed string is a concatentation of the caption ('UID ='), some style parameters and the UID. Since the UID is of type TBuffer, it must be converted into a hexadecimal string before it can be printed using WriteLnF. This is done by BufferToHexString. There are a number of other conversion functions available. See the function list in Smart Card Scripter for details.
The UID is printed in bold and in blue. The tag <b> causes all following text to be bold (until </b> or until the end of the line). The tag <c:clBlue> causes all following text to be blue (until </c> or until the end of the line). Instead of clBlue, a decimal or hexadecimal color value could also be specified in the <c> tag (e.g. <c:$FF>).
Constants
Constant values can be defined with the keyword const. In the following script, some of the previously hard coded values are now constants:
const
GET_UID = 'FFCA000000';
SW_SUCCESS = $9000;
UID_COLOR = 'clBlue';
var
UID: TBuffer;
SW12: Word;
begin
if ConnectCard = SCARD_S_SUCCESS then try
if TransmitAsStringSW(GET_UID, UID, SW12) <> SCARD_S_SUCCESS then exit;
if SW12<>SW_SUCCESS then exit;
WriteLnF('UID = <b><c:clBlue>' + BufferToHexString(UID));
finally
Disconnect;
end;
end.
The final script is much longer than the first version and it also seems to be more complex, but it still just reads the UID from a contactless card. Whether you create simple or complex scripts for simple tasks is up to you. Scripts will soon enough become complicated, when it comes to cryptography. But this is out of scope of this tutorial.