Tuesday, July 7, 2009

Smart Cards Hurt - 1

So here's the new toy I've got to play with - the DataCard CP40 Plus card printer with the SCM SCR331-DI Smart Card reader.

Datacard CP40 Plus

Developing the application for the printer consists mostly of two parts - communicating with the printer and communicating with the smart card reader. You tell the printer to pick up the card, you tell the printer to position the card for smart card processing, you tell the smart card reader to write data to the smart card, you tell the printer to encode the magstripe and print something on the card, you tell the printer to finish with the print job.

It does not look so easy when you read the manual. This is how the programming flow looks like:

In reality, though, the whole printer communication is mostly done by the following code:

printer.Hdc = PrintDoc.PrinterSettings.CreateMeasurementGraphics().GetHdc().ToInt32();

/* Set Interactive mode */
if (ICE_API.SetInteractiveMode(printer.Hdc, true) > 0)
{
ICE_API.DOCINFO di = new ICE_API.DOCINFO();
/* Initialize DOCINFO */
di.cbSize = 16;
di.lpszDocName = "Card Printer SDK Test";
di.lpszDataType = string.Empty;
di.lpszOutput = string.Empty;

/* Start document and page */
if (ICE_API.StartDoc(printer.Hdc, ref di) > 0)
{
if (ICE_API.StartPage(printer.Hdc) > 0)
{
/* Set card rotation on */
ICE_API.RotateCardSide(printer.Hdc, 1);
/* Feed the card into the smart card reader */
if (ICE_API.FeedCard(printer.Hdc, ICE_API.ICE_SMARTCARD_FRONT + ICE_API.ICE_GRAPHICS_FRONT) > 0)
{
/* Put any SmartCard processing/communication here */
TalkToSmartCard();
}
/* Remove the card from the reader and continue printing */
ICE_API.SmartCardContinue(printer.Hdc, ICE_API.ICE_SMART_CARD_GOOD);
/* End the page */
ICE_API.EndPage(printer.Hdc);
}
/* End job */
ICE_API.EndDoc(printer.Hdc);
}
}

The ICE_API mostly contains wrappings for the functions from the ICE_API.dll which comes with the printer and defines some constants and data structures, like this

[StructLayout(LayoutKind.Sequential)]
public struct DOCINFO
{
public int cbSize;
public string lpszDocName;
public string lpszOutput;
public string lpszDataType;
}

........

[DllImport("ICE_API.dll")]
public static extern int FeedCard(int hdc, int dwCardData);

[DllImport("ICE_API.dll")]
public static extern int GetCardId(int hdc, CARDIDTYPE pCardId);

[DllImport("ICE_API.dll")]
public static extern int SmartCardContinue(int hdc, int dwCommand);

.........

public const int ICE_SMARTCARD_FRONT = 0x10;
public const int ICE_GRAPHICS_FRONT = 0x1;

public const int ICE_SMART_CARD_GOOD = 0;
public const int ICE_SMART_CARD_ABORT = 1;

Now that was the easy part.

by . Also posted on my website

6 comments:

Dr. Sylvester said...

Very interesting article.

can we get a copy of the ICE_API wrapper class.

Evgeny said...

Surprisingly, I still have that file.

using System;
using System.Runtime.InteropServices;

namespace Devices
{
public class ICE_API
{
//API Errors
public const int AP00800 = 800;
public const int AP00801 = 801;
public const int AP00802 = 802;
public const int AP00803 = 803;
public const int AP00804 = 804;
public const int AP00805 = 805;
public const int AP00806 = 806;
public const int AP00807 = 807;
public const int AP00808 = 808;
public const int AP00809 = 809;
public const int AP00810 = 810;
public const int AP00811 = 811;
public const int AP00812 = 812;
public const int AP00813 = 813;
public const int AP00814 = 814;
public const int AP00815 = 815;
public const int AP00816 = 816;
public const int AP00817 = 817;
public const int AP00818 = 818;
public const int AP00819 = 819;
public const int AP00820 = 820;
public const int AP00821 = 821;
public const int AP00822 = 822;
public const int AP00823 = 823;
public const int AP00824 = 824;
public const int AP00825 = 825;

//Other errors
public const int ProcessingCompletedSuccessfully = ModWinsCard.SCARD_S_SUCCESS;
public const int PrinterNameNotSpecified = 10001;
public const int SmartCardReaderNameNotSpecified = 10002;
public const int NoSmartcardDataProvided = 10003;
public const int NoMagstripeDataProvided = 10004;

// Data types
public const int ICE_NO_DATA = 0x0;
public const int ICE_GRAPHICS_FRONT = 0x1;
public const int ICE_GRAPHICS_BACK = 0x2;
public const int ICE_MAGSTRIPE_FRONT = 0x4;
public const int ICE_MAGSTRIPE_BACK = 0x8;
public const int ICE_SMARTCARD_FRONT = 0x10;
public const int ICE_SMARTCARD_BACK = 0x20;
public const int ICE_READ_MAGSTRIPE_FRONT = 0x40;
public const int ICE_READ_MAGSTRIPE_BACK = 0x80;
public const int GAL_OPTION1_FRONT = 0x100;
public const int GAL_OPTION2_FRONT = 0x200;
public const int GAL_OPTION1_BACK = 0x400;
public const int GAL_OPTION2_BACK = 0x800;
public const int GAL_OPTION1_FRONT2 = 0x1000;
public const int GAL_OPTION2_FRONT2 = 0x2000;
public const int GAL_OPTION1_BACK2 = 0x4000;
public const int GAL_OPTION2_BACK2 = 0x8000;
public const int ICE_OPTICAL_FRONT = 0x10000;
public const int ICE_OPTICAL_BACK = 0x20000;
public const int ICE_MAG_BEFORE_SC_FRONT = 0x40000;
public const int ICE_MAG_BEFORE_SC_BACK = 0x80000;

public const int ICE_SMART_CARD_GOOD = 0;
public const int ICE_SMART_CARD_ABORT = 1;

public const int FILE_DEVICE_SMARTCARD = 0x31;

public const int IOCTL_SMARTCARD_POWER = FILE_DEVICE_SMARTCARD + 0x4;
public const int IOCTL_SMARTCARD_GET_ATTRIBUTE = FILE_DEVICE_SMARTCARD + 0x8;
public const int IOCTL_SMARTCARD_SET_ATTRIBUTE = FILE_DEVICE_SMARTCARD + 0xC;
public const int IOCTL_SMARTCARD_CONFISCATE = FILE_DEVICE_SMARTCARD + 0x10;
public const int IOCTL_SMARTCARD_TRANSMIT = FILE_DEVICE_SMARTCARD + 0x14;
public const int IOCTL_SMARTCARD_EJECT = FILE_DEVICE_SMARTCARD + 0x18;
public const int IOCTL_SMARTCARD_SWALLOW = FILE_DEVICE_SMARTCARD + 0x1C;
public const int IOCTL_SMARTCARD_IS_PRESENT = FILE_DEVICE_SMARTCARD + 0x28;
public const int IOCTL_SMARTCARD_IS_ABSENT = FILE_DEVICE_SMARTCARD + 0x2C;
public const int IOCTL_SMARTCARD_SET_PROTOCOL = FILE_DEVICE_SMARTCARD + 0x30;
public const int IOCTL_SMARTCARD_GET_STATE = FILE_DEVICE_SMARTCARD + 0x38;
public const int IOCTL_SMARTCARD_GET_LAST_ERROR = FILE_DEVICE_SMARTCARD + 0x3C;

(continued)

Evgeny said...

//These define mag stripe track types for the printer.
public const int ICE_MS_IAT = 0;
public const int ICE_MS_TRIPLE_IATA = 1;
public const int ICE_MS_TRIPLE_BINARY = 2;
public const int ICE_MS_AAMVA = 3;
public const int ICE_MS_NTT = 4;
public const int ICE_MS_RESERVED0 = 5;
public const int ICE_MS_RESERVED1 = 6;
public const int ICE_MS_CUSTOM = 7;
public const int ICE_MS_USE_DRIVER = 8;
public const int ICE_MS_USE_PRINTER = 9;
public const int ICE_MS_RESERVED2 = 10;
public const int ICE_MS_RESERVED3 = 11;

//These specify mag stripe coercivities.
public const int ICE_MS_COERCIVITY_NO_CHANGE = 0;
public const int ICE_MS_COERCIVITY_HICO = 1;
public const int ICE_MS_COERCIVITY_LOCO = 2;
public const int ICE_MS_COERCIVITY_50mA = 3;
public const int ICE_MS_COERCIVITY_75mA = 4;
public const int ICE_MS_COERCIVITY_100mA = 5;
public const int ICE_MS_COERCIVITY_RESERVED0 = 6;
public const int ICE_MS_COERCIVITY_RESERVED1 = 7;
public const int ICE_MS_COERCIVITY_RESERVED2 = 8;
public const int ICE_MS_COERCIVITY_RESERVED3 = 9;
public const int ICE_MS_COERCIVITY_USE_DRIVER = 10;

// Printer commands
public const int ICE_CMD_ABORT_CARD = 0;
public const int ICE_CMD_ABORT_CARD_NOEJECT = 1;
public const int ICE_CMD_ABORT_ALL_CARDS = 3;
public const int ICE_CMD_RESUME_CARD = 4;
public const int ICE_CMD_RESTART_PRINTER = 4;

[StructLayout(LayoutKind.Sequential)]
public struct DOCINFO
{
public int cbSize;
public string lpszDocName;
public string lpszOutput;
public string lpszDataType;
}

[StructLayout(LayoutKind.Sequential)]
public struct CARDIDTYPE
{
public int Jobid;
public int cardnum;
public int hPrinter;
}

[StructLayout(LayoutKind.Sequential)]
public struct CARD_INFO_1
{
public int Active;
public int Success;
}

public const int MAX_ICE_MS_TRACK_LENGTH = 256;
[StructLayout(LayoutKind.Sequential)]
public struct TRACKDATA
{
public UInt32 nLength;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string szTrackData;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class LOGFONT
{
public int lfHeight = 0;
public int lfWidth = 0;
public int lfEscapement = 0;
public int lfOrientation = 0;
public int lfWeight = 0;
public byte lfItalic = 0;
public byte lfUnderline = 0;
public byte lfStrikeOut = 0;
public byte lfCharSet = 0;
public byte lfOutPrecision = 0;
public byte lfClipPrecision = 0;
public byte lfQuality = 0;
public byte lfPitchAndFamily = 0;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string lfFaceName = string.Empty;
}

DllImport("ICE_API.dll", EntryPoint = "_ReadMagstripe@20", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.Winapi, SetLastError = true)]
return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ReadMagstripe(int hdc, ref TRACKDATA ptrack1, ref TRACKDATA ptrack2,
ref TRACKDATA ptrack3, ref TRACKDATA reserved);

[DllImport("ICE_API.dll", EntryPoint = "_EncodeMagstripe@20", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern bool EncodeMagstripe(int hdc, [In] ref TRACKDATA ptrack1, [In] ref TRACKDATA ptrack2,
[In] ref TRACKDATA ptrack3, [In] ref TRACKDATA reserved);

[DllImport("ICE_API.dll", EntryPoint = "_DisplayCardErrorW@8", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern bool DisplayCardError(string lpPrinterName, bool bDisplay);

[DllImport("ICE_API.dll", EntryPoint = "_ClearAllCardErrorsW@4", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern bool ClearAllCardErrors(string printerName);

(continued further)

Evgeny said...

[DllImport("ICE_API.dll", EntryPoint = "_GetCardPrinterErrorsW@24", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern bool GetCardPrinterErrors(string printerName, int level,
ref int lpbErrorInfo, int cbBuf, ref int lpdwNeeded, ref int lpdwError);

[DllImport("ICE_API.dll", EntryPoint = "_GetCardStatus@28", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern bool GetCardStatus(int jobnum, int cardnum, int printer,
int level, ref CARD_INFO_1 cardinfo, int cbBuf, ref int lpdwNeeded);

[DllImport("ICE_API.dll", EntryPoint = "_SendPrinterCommandW@12", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern bool SendPrinterCommand(string lpPrinterName, int JobID, int Command);

[DllImport("ICE_API.dll", EntryPoint = "_GetCardPrinterPollingStateW@8", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern bool GetCardPrinterPollingState(string lpPrinterName, ref int State);

[DllImport("ICE_API.dll", EntryPoint = "_ResumePrinterPollingW@8", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern bool ResumePrinterPooling(string lpPrinterName, bool resume);

[DllImport("ICE_API.dll")]
public static extern int SetMagstripeFormat(int hdc, int dwTrackFormat, int dwCoercivity);

[DllImport("ICE_API.dll")]
public static extern int PrinterAPIMajorVersion();

[DllImport("ICE_API.dll")]
public static extern int PrinterAPIMinorVersion();

[DllImport("ICE_API.dll", SetLastError = true)]
public static extern int SetInteractiveMode(int hdc, bool bEnable);

[DllImport("ICE_API.dll", SetLastError = true)]
public static extern bool FeedCard(int hdc, int dwCardData);

[DllImport("ICE_API.dll", EntryPoint = "_GetCardId@8", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern bool GetCardId(int hdc, ref CARDIDTYPE pCardId);


[DllImport("ICE_API.dll", EntryPoint = "_SmartCardContinue@8", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern bool SmartCardContinue(int hdc, int dwCommand);

[DllImport("ICE_API.dll")]
public static extern int RotateCardSide(int hdc, int bEnable);

[DllImport("gdi32")]
public static extern int StartDoc(int hdc, ref DOCINFO lpdi);

[DllImport("gdi32")]
public static extern int StartPage(int hdc);

[DllImport("gdi32")]
public static extern int EndDoc(int hdc);

[DllImport("gdi32")]
public static extern int EndPage(int hdc);

[DllImport("gdi32.dll", CharSet = CharSet.Auto)]
public static extern bool TextOut(int hdc, int nXStart, int nYStart,
string lpString, int cbString);

[DllImport("gdi32.dll")]
public static extern IntPtr CreateFont(int nHeight, int nWidth, int nEscapement,
int nOrientation, int fnWeight, uint fdwItalic, uint fdwUnderline, uint
fdwStrikeOut, uint fdwCharSet, uint fdwOutputPrecision, uint
fdwClipPrecision, uint fdwQuality, uint fdwPitchAndFamily, string lpszFace);

[DllImport("gdi32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr CreateFontIndirect([In, MarshalAs(UnmanagedType.LPStruct)]LOGFONT lplf);

[DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr handle);

[DllImport("gdi32.dll")]
public static extern bool SelectObject(int hdc, IntPtr handle);

}
}

(complete)

fulvio said...

Just wondering whether you can post code for creating the HDC? printer.Hdc = PrintDoc.PrinterSettings.CreateMeasurementGraphics().GetHdc().ToInt32();

Evgeny said...

There's not much code to write in that ...

PrintDoc.PrinterSettings.CreateMeasurementGraphics().GetHdc().ToInt32();

GetHdc() is a member of System.Graphics
Graphics.GetHdc Method

CreateMeasurementGraphics() too
PrinterSettings.CreateMeasurementGraphics Method

PrinterSettings is a member of System.Drawing.Printing
PrinterSettings Class