/************************************************************************************************\ * * This file is part of EMV Cardreader, a software Mastercard/VISA * CAP/DPA implementation - https://code.google.com/p/emv-cardreader-sec * For Brevity, EMV Cardreader (the software & all source code) shall * herein be referred to simply as 'EMVcr' * * Copyright 2011 by 'Olipro' * * This particular file is subject to a specific exception; it contains code * licensed under the Code Project Open License (http://www.codeproject.com/info/cpol10.aspx) * and therefore is exempt from any other incompatible licensing requirements * You may use this individual file in any manner that is compatible with the CPOL. * * EMVcr is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with EMVcr. If not, see <http://www.gnu.org/licenses/>. * \************************************************************************************************/ using System; using System.Runtime.InteropServices; using System.Text; using System.Threading; namespace GemCard { /// <summary> /// CARD_STATE enumeration, used by the PC/SC function SCardGetStatusChanged /// </summary> enum CARD_STATE { UNAWARE = 0x00000000, IGNORE = 0x00000001, CHANGED = 0x00000002, UNKNOWN = 0x00000004, UNAVAILABLE = 0x00000008, EMPTY = 0x00000010, PRESENT = 0x00000020, ATRMATCH = 0x00000040, EXCLUSIVE = 0x00000080, INUSE = 0x00000100, MUTE = 0x00000200, UNPOWERED = 0x00000400 } /// <summary> /// Wraps the SCARD_IO_STRUCTURE /// /// </summary> [StructLayout(LayoutKind.Sequential)] public struct SCard_IO_Request { public UInt32 m_dwProtocol; public UInt32 m_cbPciLength; } /// <summary> /// Wraps theSCARD_READERSTATE structure of PC/SC /// </summary> [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct SCard_ReaderState { public string m_szReader; public IntPtr m_pvUserData; public UInt32 m_dwCurrentState; public UInt32 m_dwEventState; public UInt32 m_cbAtr; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public byte[] m_rgbAtr; } /// <summary> /// Implementation of ICard using native (P/Invoke) interoperability for PC/SC /// </summary> public class CardNative : CardBase { private IntPtr m_hContext = IntPtr.Zero; private IntPtr m_hCard = IntPtr.Zero; private UInt32 m_nProtocol = (uint)PROTOCOL.T0; private int m_nLastError = 0; #region PCSC_FUNCTIONS /// <summary> /// Native SCardGetStatusChanged from winscard.dll /// </summary> /// <param name="hContext"></param> /// <param name="dwTimeout"></param> /// <param name="rgReaderStates"></param> /// <param name="cReaders"></param> /// <returns></returns> [DllImport("winscard", SetLastError = true)] internal static extern int SCardGetStatusChange(IntPtr hContext, UInt32 dwTimeout, [In, Out] SCard_ReaderState[] rgReaderStates, UInt32 cReaders); /// <summary> /// Native SCardListReaders function from winscard.dll /// </summary> /// <param name="hContext"></param> /// <param name="mszGroups"></param> /// <param name="mszReaders"></param> /// <param name="pcchGroups"></param> /// <returns></returns> [DllImport("winscard", SetLastError = true)] internal static extern int SCardListReaders(IntPtr hContext, [MarshalAs(UnmanagedType.LPTStr)] string mszGroups, IntPtr mszReaders, out UInt32 pcchReaders); /// <summary> /// Native SCardListReaderGroups function from winscard.dll /// </summary> /// <param name="hContext"></param> /// <param name="mszGroups"></param> /// <param name="pcchReaders"></param> /// <returns></returns> [DllImport("winscard", SetLastError = true)] internal static extern int SCardListReaderGroups(IntPtr hContext, IntPtr mszGroups, out UInt32 pcchReaders); /// <summary> /// Native SCardEstablishContext function from winscard.dll /// </summary> /// <param name="dwScope"></param> /// <param name="pvReserved1"></param> /// <param name="pvReserved2"></param> /// <param name="phContext"></param> /// <returns></returns> [DllImport("winscard", SetLastError = true)] internal static extern int SCardEstablishContext(UInt32 dwScope, IntPtr pvReserved1, IntPtr pvReserved2, IntPtr phContext); /// <summary> /// Native SCardReleaseContext function from winscard.dll /// </summary> /// <param name="hContext"></param> /// <returns></returns> [DllImport("winscard", SetLastError = true)] internal static extern int SCardReleaseContext(IntPtr hContext); /// <summary> /// Native SCardConnect function from winscard.dll /// </summary> /// <param name="hContext"></param> /// <param name="szReader"></param> /// <param name="dwShareMode"></param> /// <param name="dwPreferredProtocols"></param> /// <param name="phCard"></param> /// <param name="pdwActiveProtocol"></param> /// <returns></returns> [DllImport("winscard", SetLastError = true, CharSet = CharSet.Auto)] internal static extern int SCardConnect(IntPtr hContext, [MarshalAs(UnmanagedType.LPTStr)] string szReader, UInt32 dwShareMode, UInt32 dwPreferredProtocols, IntPtr phCard, IntPtr pdwActiveProtocol); /// <summary> /// Native SCardDisconnect function from winscard.dll /// </summary> /// <param name="hCard"></param> /// <param name="dwDisposition"></param> /// <returns></returns> [DllImport("winscard", SetLastError = true)] internal static extern int SCardDisconnect(IntPtr hCard, UInt32 dwDisposition); /// <summary> /// Native SCardTransmit function from winscard.dll /// </summary> /// <param name="hCard"></param> /// <param name="pioSendPci"></param> /// <param name="pbSendBuffer"></param> /// <param name="cbSendLength"></param> /// <param name="pioRecvPci"></param> /// <param name="pbRecvBuffer"></param> /// <param name="pcbRecvLength"></param> /// <returns></returns> [DllImport("winscard", SetLastError = true)] internal static extern int SCardTransmit(IntPtr hCard, [In] ref SCard_IO_Request pioSendPci, byte[] pbSendBuffer, UInt32 cbSendLength, IntPtr pioRecvPci, [Out] byte[] pbRecvBuffer, out UInt32 pcbRecvLength ); /// <summary> /// Native SCardBeginTransaction function of winscard.dll /// </summary> /// <param name="hContext"></param> /// <returns></returns> [DllImport("winscard", SetLastError = true)] internal static extern int SCardBeginTransaction(IntPtr hContext); /// <summary> /// Native SCardEndTransaction function of winscard.dll /// </summary> /// <param name="hContext"></param> /// <returns></returns> [DllImport("winscard", SetLastError = true)] internal static extern int SCardEndTransaction(IntPtr hContext, UInt32 dwDisposition); [DllImport("winscard", SetLastError = true)] internal static extern int SCardGetAttrib(IntPtr hCard, UInt32 dwAttribId, [Out] byte[] pbAttr, out UInt32 pcbAttrLen); #endregion WINSCARD_FUNCTIONS /// <summary> /// Default constructor /// </summary> public CardNative() { } /// <summary> /// Object destruction /// </summary> ~CardNative() { Disconnect(DISCONNECT.Unpower); ReleaseContext(); } #region ICard Members /// <summary> /// Wraps the PCSC function /// LONG SCardListReaders(SCARDCONTEXT hContext, /// LPCTSTR mszGroups, /// LPTSTR mszReaders, /// LPDWORD pcchReaders /// ); /// </summary> /// <returns>A string array of the readers</returns> public override string[] ListReaders() { EstablishContext(SCOPE.User); string[] sListReaders = null; UInt32 pchReaders = 0; IntPtr szListReaders = IntPtr.Zero; m_nLastError = SCardListReaders(m_hContext, null, szListReaders, out pchReaders); if (m_nLastError == 0) { szListReaders = Marshal.AllocHGlobal((int)pchReaders); m_nLastError = SCardListReaders(m_hContext, null, szListReaders, out pchReaders); if (m_nLastError == 0) { char[] caReadersData = new char[pchReaders]; int nbReaders = 0; for (int nI = 0; nI < pchReaders; nI++) { caReadersData[nI] = (char)Marshal.ReadByte(szListReaders, nI); if (caReadersData[nI] == 0) nbReaders++; } // Remove last 0 --nbReaders; if (nbReaders != 0) { sListReaders = new string[nbReaders]; char[] caReader = new char[pchReaders]; int nIdx = 0; int nIdy = 0; int nIdz = 0; // Get the nJ string from the multi-string while (nIdx < pchReaders - 1) { caReader[nIdy] = caReadersData[nIdx]; if (caReader[nIdy] == 0) { sListReaders[nIdz] = new string(caReader, 0, nIdy); ++nIdz; nIdy = 0; caReader = new char[pchReaders]; } else ++nIdy; ++nIdx; } } } Marshal.FreeHGlobal(szListReaders); } ReleaseContext(); return sListReaders; } /// <summary> /// Wraps the PCSC function /// LONG SCardEstablishContext( /// IN DWORD dwScope, /// IN LPCVOID pvReserved1, /// IN LPCVOID pvReserved2, /// OUT LPSCARDCONTEXT phContext /// ); /// </summary> /// <param name="Scope"></param> public void EstablishContext(SCOPE Scope) { IntPtr hContext = Marshal.AllocHGlobal(Marshal.SizeOf(m_hContext)); m_nLastError = SCardEstablishContext((uint)Scope, IntPtr.Zero, IntPtr.Zero, hContext); if (m_nLastError != 0) { string msg = "SCardEstablishContext error: " + m_nLastError; Marshal.FreeHGlobal(hContext); throw new Exception(msg); } m_hContext = Marshal.ReadIntPtr(hContext); Marshal.FreeHGlobal(hContext); } /// <summary> /// Wraps the PCSC function /// LONG SCardReleaseContext( /// IN SCARDCONTEXT hContext /// ); /// </summary> public void ReleaseContext() { if (m_hContext != IntPtr.Zero) { m_nLastError = SCardReleaseContext(m_hContext); if (m_nLastError != 0) { string msg = "SCardReleaseContext error: " + m_nLastError; throw new Exception(msg); } m_hContext = IntPtr.Zero; } } /// <summary> /// Wraps the PCSC function /// LONG SCardConnect( /// IN SCARDCONTEXT hContext, /// IN LPCTSTR szReader, /// IN DWORD dwShareMode, /// IN DWORD dwPreferredProtocols, /// OUT LPSCARDHANDLE phCard, /// OUT LPDWORD pdwActiveProtocol /// ); /// </summary> /// <param name="Reader"></param> /// <param name="ShareMode"></param> /// <param name="PreferredProtocols"></param> public override void Connect(string Reader, SHARE ShareMode, PROTOCOL PreferredProtocols) { EstablishContext(SCOPE.User); IntPtr hCard = Marshal.AllocHGlobal(Marshal.SizeOf(m_hCard)); IntPtr pProtocol = Marshal.AllocHGlobal(Marshal.SizeOf(m_nProtocol)); m_nLastError = SCardConnect(m_hContext, Reader, (uint)ShareMode, (uint)PreferredProtocols, hCard, pProtocol); if (m_nLastError != 0) { string msg = "SCardConnect error: " + m_nLastError; Marshal.FreeHGlobal(hCard); Marshal.FreeHGlobal(pProtocol); throw new Exception(msg); } m_hCard = Marshal.ReadIntPtr(hCard); m_nProtocol = (uint)Marshal.ReadInt32(pProtocol); Marshal.FreeHGlobal(hCard); Marshal.FreeHGlobal(pProtocol); b_isConnected = true; } /// <summary> /// Wraps the PCSC function /// LONG SCardDisconnect( /// IN SCARDHANDLE hCard, /// IN DWORD dwDisposition /// ); /// </summary> /// <param name="Disposition"></param> public override void Disconnect(DISCONNECT Disposition) { b_isConnected = false; if (m_hCard != IntPtr.Zero) { m_nLastError = SCardDisconnect(m_hCard, (uint)Disposition); m_hCard = IntPtr.Zero; if (m_nLastError != 0) { string msg = "SCardDisconnect error: " + m_nLastError; throw new Exception(msg); } ReleaseContext(); } } /// <summary> /// Wraps the PCSC function /// LONG SCardTransmit( /// SCARDHANDLE hCard, /// LPCSCARD_I0_REQUEST pioSendPci, /// LPCBYTE pbSendBuffer, /// DWORD cbSendLength, /// LPSCARD_IO_REQUEST pioRecvPci, /// LPBYTE pbRecvBuffer, /// LPDWORD pcbRecvLength /// ); /// </summary> /// <param name="ApduCmd">APDUCommand object with the APDU to send to the card</param> /// <returns>An APDUResponse object with the response from the card</returns> 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, (UInt32)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); } /// <summary> /// Wraps the PSCS function /// LONG SCardBeginTransaction( /// SCARDHANDLE hCard // ); /// </summary> public override void BeginTransaction() { if (m_hCard != IntPtr.Zero) { m_nLastError = SCardBeginTransaction(m_hCard); if (m_nLastError != 0) { string msg = "SCardBeginTransaction error: " + m_nLastError; throw new Exception(msg); } } } /// <summary> /// Wraps the PCSC function /// LONG SCardEndTransaction( /// SCARDHANDLE hCard, /// DWORD dwDisposition /// ); /// </summary> /// <param name="Disposition">A value from DISCONNECT enum</param> public override void EndTransaction(DISCONNECT Disposition) { if (m_hCard != IntPtr.Zero) { m_nLastError = SCardEndTransaction(m_hCard, (UInt32)Disposition); if (m_nLastError != 0) { string msg = "SCardEndTransaction error: " + m_nLastError; throw new Exception(msg); } } } /// <summary> /// Gets the attributes of the card /// </summary> /// <param name="AttribId">Identifier for the Attribute to get</param> /// <returns>Attribute content</returns> public override byte[] GetAttribute(UInt32 AttribId) { byte[] attr = null; UInt32 attrLen = 0; m_nLastError = SCardGetAttrib(m_hCard, AttribId, attr, out attrLen); if (m_nLastError == 0) { if (attrLen != 0) { attr = new byte[attrLen]; m_nLastError = SCardGetAttrib(m_hCard, AttribId, attr, out attrLen); if (m_nLastError != 0) { string msg = "SCardGetAttr error: " + m_nLastError; throw new Exception(msg); } } } else { string msg = "SCardGetAttr error: " + m_nLastError; throw new Exception(msg); } return attr; } public override bool IsConnected() { return b_isConnected; } #endregion /// <summary> /// This function must implement a card detection mechanism. /// /// When card insertion is detected, it must call the method CardInserted() /// When card removal is detected, it must call the method CardRemoved() /// /// </summary> protected override void RunCardDetection(object Reader) { lock (this) { bool bFirstLoop = true; IntPtr hContext = IntPtr.Zero; // Local context IntPtr phContext; phContext = Marshal.AllocHGlobal(Marshal.SizeOf(hContext)); if (SCardEstablishContext((uint)SCOPE.User, IntPtr.Zero, IntPtr.Zero, phContext) == 0) { hContext = Marshal.ReadIntPtr(phContext); Marshal.FreeHGlobal(phContext); UInt32 nbReaders = 1; SCard_ReaderState[] readerState = new SCard_ReaderState[nbReaders]; readerState[0].m_dwCurrentState = (UInt32)CARD_STATE.UNAWARE; readerState[0].m_szReader = (string)Reader; UInt32 eventState; UInt32 currentState = readerState[0].m_dwCurrentState; // Card detection loop lock (this) { do { if (SCardGetStatusChange(hContext, WAIT_TIME , readerState, nbReaders) == 0) { eventState = readerState[0].m_dwEventState; currentState = readerState[0].m_dwCurrentState; // Check state if (((eventState & (uint)CARD_STATE.CHANGED) == (uint)CARD_STATE.CHANGED) && !bFirstLoop) { // State has changed if ((eventState & (uint)CARD_STATE.EMPTY) == (uint)CARD_STATE.EMPTY) { // There is no card, card has been removed -> Fire CardRemoved event CardRemoved((string)Reader); bFirstLoop = true; Thread.Sleep(100); continue; } if (((eventState & (uint)CARD_STATE.PRESENT) == (uint)CARD_STATE.PRESENT) && ((eventState & (uint)CARD_STATE.PRESENT) != (currentState & (uint)CARD_STATE.PRESENT))) { // There is a card in the reader -> Fire CardInserted event CardInserted((string)Reader); } if ((eventState & (uint)CARD_STATE.ATRMATCH) == (uint)CARD_STATE.ATRMATCH) { // There is a card in the reader and it matches the ATR we were expecting-> Fire CardInserted event CardInserted((string)Reader); } } // The current stateis now the event state readerState[0].m_dwCurrentState = eventState; bFirstLoop = false; } Thread.Sleep(100); if (m_bRunCardDetection == false) break; } while (true); // Exit on request } } else { Marshal.FreeHGlobal(phContext); throw new Exception("PC/SC error"); } SCardReleaseContext(hContext); } } } }