Top > NFC > PC_SCサンプル

NFC/PC_SCサンプル

Last-modified: 2013-02-04 (月) 00:52:36

PC/SC経由でPaSoRi(RC-S380)を使い、リーダーのシリアル番号とFeliCa IDmを取得するC#のプログラム例。
PC/SCのプログラミング方法はEternalWindows セキュリティ/スマートカード を参考に作成。

DllImportの指定は、PINVOKE.NETのwinscardのカテゴリを参考にした。しかし、そのサイトでもfunction間で指定が統一されておらず、当方も仕組みが分かっていない上、CもC#も不慣れなため、宣言が適切かはかなりあやしい。
その他、かなり乱雑。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
 
 
 
 
 
 
 
 
 
-
|
|
|
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-
|
|
|
-
|
!
!
|
|
|
|
|
|
|
|
|
|
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-
|
|
!
|
|
-
|
|
|
-
|
|
|
|
|
|
-
|
!
|
|
|
|
|
|
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-
|
|
-
|
|
-
|
|
|
-
|
|
-
|
|
|
|
!
|
-
|
!
!
!
!
|
!
|
-
|
!
|
!
|
!
|
-
|
!
!
|
|
-
|
|
|
|
|
|
|
|
|
|
|
!
|
|
|
|
-
|
|
|
|
|
|
|
|
|
|
!
|
|
-
|
|
|
|
|
|
|
|
|
|
!
|
|
|
|
|
|
|
|
-
|
|
|
|
|
|
|
-
|
|
-
|
|
|
|
|
|
!
|
|
!
|
|
!
|
|
-
|
|
|
|
|
-
|
!
|
|
|
|
|
|
-
|
!
|
|
|
|
|
|
|
|
|
|
|
|
-
|
-
|
|
|
|
|
!
!
|
!
|
|
|
-
|
|
|
|
|
|
|
|
|
|
|
|
|
-
|
!
|
|
!
|
|
-
|
|
|
|
|
|
|
-
|
!
!
|
|
-
|
|
|
|
!
|
|
-
|
|
|
|
|
|
|
|
-
|
!
|
|
!
|
|
-
|
|
|
-
|
!
|
!
|
|
-
|
|
|
|
|
-
|
|
|
!
|
|
|
|
-
|
!
|
!
|
|
-
|
-
|
|
|
|
|
|
!
|
!
|
|
-
|
-
|
!
!
|
|
|
!
|
|
|
-
|
|
!
|
!
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
 
namespace SCardSample
{
 
 
    class Program
    {
 
        [DllImport("winscard.dll")]
        static extern uint SCardEstablishContext(uint dwScope, IntPtr pvReserved1, IntPtr pvReserved2, out IntPtr phContext);
 
        [DllImport("winscard.dll", EntryPoint = "SCardListReadersW", CharSet = CharSet.Unicode)]
        public static extern uint SCardListReaders(
          IntPtr hContext,
          byte[] mszGroups,
          byte[] mszReaders,
          ref UInt32 pcchReaders
          );
 
        [DllImport("WinScard.dll")]
        static extern uint SCardReleaseContext(IntPtr phContext);
 
        [DllImport("winscard.dll", EntryPoint = "SCardConnectW", CharSet = CharSet.Unicode)]
        static extern uint SCardConnect(
             IntPtr hContext,
             string szReader, 
             uint dwShareMode,
             uint dwPreferredProtocols,
             ref IntPtr phCard,
             ref IntPtr pdwActiveProtocol);
 
        [DllImport("WinScard.dll")]
        static extern uint SCardDisconnect(IntPtr hCard, int Disposition);
 
        [StructLayout(LayoutKind.Sequential)]
        internal class SCARD_IO_REQUEST
        {
            internal uint dwProtocol;
            internal int cbPciLength;
            public SCARD_IO_REQUEST()
            {
                dwProtocol = 0;
            }
        }
 
        [DllImport("winscard.dll")]
        public static extern uint SCardTransmit(IntPtr hCard, IntPtr pioSendRequest, byte[] SendBuff, int SendBuffLen, SCARD_IO_REQUEST pioRecvRequest,
                byte[] RecvBuff, ref int RecvBuffLen);
 
        [DllImport("winscard.dll")]
        public static extern uint SCardControl(IntPtr hCard, int controlCode, byte[] inBuffer, int inBufferLen, byte[] outBuffer, int outBufferLen, ref int bytesReturned);
 
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        internal struct SCARD_READERSTATE
        {
            /// <summary>
            /// Reader
            /// </summary>
            internal string szReader;
            /// <summary>
            /// User Data
            /// </summary>
            internal IntPtr pvUserData;
            /// <summary>
            /// Current State
            /// </summary>
            internal UInt32 dwCurrentState;
            /// <summary>
            /// Event State/ New State
            /// </summary>
            internal UInt32 dwEventState;
            /// <summary>
            /// ATR Length
            /// </summary>
            internal UInt32 cbAtr;
            /// <summary>
            /// Card ATR
            /// </summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 36)]
            internal byte[] rgbAtr;
        }
 
        [DllImport("winscard.dll", EntryPoint = "SCardGetStatusChangeW", CharSet = CharSet.Unicode)]
        public static extern uint SCardGetStatusChange(IntPtr hContext, int dwTimeout, [In, Out] SCARD_READERSTATE[] rgReaderStates, int cReaders);
 
        [DllImport("kernel32.dll", SetLastError = true)]
        private extern static IntPtr LoadLibrary(string lpFileName);
 
        [DllImport("kernel32.dll")]
        private extern static void FreeLibrary(IntPtr handle);
 
        [DllImport("kernel32.dll")]
        private extern static IntPtr GetProcAddress(IntPtr handle, string procName);
 
 
 
 
 
 
 
        volatile static bool quit = false;
 
        static void KeyCheck(object userState)
        {
            ConsoleKeyInfo keyInfo = Console.ReadKey(true);
            quit = true;
        }
 
        static void Main(string[] args)
        {
 
            IntPtr hContext = establishContext();
            try
            {
                ThreadPool.QueueUserWorkItem(new WaitCallback(KeyCheck), null);
 
                List<string> readersList = getReaders(hContext);
 
                Debug.WriteLine(readersList.Count + "台のリーダーがあります。");
                foreach (string readerName in readersList)
                {
                    Debug.WriteLine("リーダー:" + readerName);
                }
 
                ServerConnector serverConnector = new ServerConnector();
 
                SCARD_READERSTATE[] readerStateArray = initializeReaderState(hContext, readersList);
                updateCurrentState(readerStateArray);
                while(!quit)
                {
                    const int SCARD_STATE_CHANGED  =   0x00000002;// This implies that there is a
                                            // difference between the state
                                            // believed by the application, and
                                            // the state known by the Service
                                            // Manager.  When this bit is set,
                                            // the application may assume a
                                            // significant state change has
                                            // occurred on this reader.
                    const int SCARD_STATE_PRESENT = 0x00000020;// This implies that there is a card
                                            // in the reader.
                    const int SCARD_STATE_EMPTY = 0x00000010;  // This implies that there is not
                                            // card in the reader.  If this bit
                                            // is set, all the following bits
                                            // will be clear.
                    try
                    {
                        waitReaderStatusChange(hContext, readerStateArray, 1000);
                        for (int idx = 0; idx < readerStateArray.Length; idx++)
                        {
                            uint eventState = readerStateArray[idx].dwEventState;
                            if ((eventState & SCARD_STATE_CHANGED) != 0)
                            {
                                Debug.WriteLine("リーダー " + readerStateArray[idx].szReader + " 状態が変化しました。" + String.Format("{0:X}", eventState));
 
                                uint changedStateMask = eventState ^ readerStateArray[idx].dwCurrentState;
                                if ((changedStateMask & (SCARD_STATE_EMPTY | SCARD_STATE_PRESENT)) != 0) {
 
                                    if ((eventState & SCARD_STATE_PRESENT) != 0)
                                    {
                                        Debug.WriteLine("リーダー " + readerStateArray[idx].szReader + " カードがセットされました。");
                                        ReadResult result = readCard(hContext, readerStateArray[idx].szReader);
                                        // result.readerSerialNumber → リーダーのシリアル番号
                                        // result.cardId   → IDm
                                    }
                                    if ((eventState & SCARD_STATE_EMPTY) != 0)
                                    {
                                        Debug.WriteLine("リーダー " + readerStateArray[idx].szReader + " カードが外されました。");
                                    }
                                }
                            }
                        }
                        updateCurrentState(readerStateArray);
                    }
                    catch (TimeoutException e)
                    {
                        // 無視
                    }
 
                }
 
            }
            finally
            {
                uint ret = SCardReleaseContext(hContext);
            }
        }
 
        static ReadResult readCard(IntPtr hContext, string readerName)
        {
            IntPtr hCard = connect(hContext, readerName);
            string readerSerialNumber = readReaderSerialNumber(hCard);
            string cardId = readCardId(hCard);
            Debug.WriteLine(readerName + " (S/N " + readerSerialNumber + ") から、カードを読み取りました。" + cardId);
            disconnect(hCard);
 
            ReadResult result = new ReadResult();
            result.readerSerialNumber = readerSerialNumber;
            result.cardId = cardId;
            return result;
 
        }
 
 
 
        static string readCardId(IntPtr hCard)
        {
            byte maxRecvDataLen = 64;
            byte[] recvBuffer = new byte[maxRecvDataLen + 2];
            byte[] sendBuffer = new byte[] { 0xff, 0xca, 0x00, 0x00, maxRecvDataLen };
            int recvLength = transmit(hCard, sendBuffer, recvBuffer);
 
            // recvBuffer の最後の2バイトはステータスコードなので、応じた処理が必要。
            // http://eternalwindows.jp/security/scard/scard07.html
 
            string cardId = BitConverter.ToString(recvBuffer, 0, recvLength - 2).Replace("-", "");
            return cardId;
        }
 
        static string readReaderSerialNumber(IntPtr hCard)
        {
            int controlCode = 0x003136b0; // SCARD_CTL_CODE(3500) の値 
                                    // IOCTL_PCSC_CCID_ESCAPE
                                    // SONY SDK for NFC M579_PC_SC_2.1j.pdf 3.1.1 IOCTRL_PCSC_CCID_ESCAPE
            byte[] sendBuffer = new byte[] {0xc0, 0x08 }; // ESC_CMD_GET_INFO / Product Serial Number 
            byte[] recvBuffer = new byte[64];
            int recvLength = control(hCard, controlCode, sendBuffer, recvBuffer);
 
            ASCIIEncoding asciiEncoding = new ASCIIEncoding();
            string serialNumber = asciiEncoding.GetString(recvBuffer, 0, recvLength - 1); // recvBufferには\0で終わる文字列が取得されるので、長さを-1する。
            return serialNumber;
        }
 
 
 
        const uint SCARD_S_SUCCESS = 0;
        const uint SCARD_E_NO_SERVICE = 0x8010001D;
        const uint SCARD_E_TIMEOUT = 0x8010000A;
 
        static IntPtr establishContext()
        {
            IntPtr hContext = IntPtr.Zero;
            const uint SCARD_SCOPE_USER = 0;
            const uint SCARD_SCOPE_TERMINAL = 1;
            const uint SCARD_SCOPE_SYSTEM = 2;
 
            uint ret = SCardEstablishContext(SCARD_SCOPE_USER, IntPtr.Zero, IntPtr.Zero, out hContext);
            if (ret != SCARD_S_SUCCESS)
            {
                string message;
                switch (ret)
                {
                    case SCARD_E_NO_SERVICE:
                        message = "Smart Cardサービスが起動されていません。";
                        break;
                    default:
                        message= "Smart Cardサービスに接続できません。code = " + ret;
                        break;
                }
                Debug.WriteLine(message);
                throw new NotSupportedException(message);
            }
            Debug.WriteLine("Smart Cardサービスに接続しました。");
            return hContext;
        }
 
        static List<string> getReaders(IntPtr hContext)
        {
            uint pcchReaders = 0;
 
            // First call with 3rd parameter set to null gets readers buffer length.
            uint ret = SCardListReaders(hContext, null, null, ref pcchReaders);
            if (ret != SCARD_S_SUCCESS)
            {
                throw new ApplicationException("リーダーの情報が取得できません。code = " + ret);
            }
 
            byte[] mszReaders = new byte[pcchReaders * 2]; // 1文字2byte
 
            // Fill readers buffer with second call.
            ret = SCardListReaders(hContext, null, mszReaders, ref pcchReaders);
            if (ret != SCARD_S_SUCCESS)
            {
                throw new ApplicationException("リーダーの情報が取得できません。code = " + ret);
            }
 
            UnicodeEncoding unicodeEncoding = new UnicodeEncoding();
            string readerNameMultiString = unicodeEncoding.GetString(mszReaders);
 
            Debug.WriteLine("リーダー名を\\0で接続した文字列: " + readerNameMultiString);
            Debug.WriteLine(" ");
 
            int len = (int)pcchReaders;
            char nullchar = (char)0;
            List<string> readersList = new List<string>();
 
            if (len > 0)
            {
                while (readerNameMultiString[0] != nullchar)
                {
                    int nullindex = readerNameMultiString.IndexOf(nullchar);   // Get null end character.
                    string readerName = readerNameMultiString.Substring(0, nullindex);
                    readersList.Add(readerName);
                    len = len - (readerName.Length + 1);
                    readerNameMultiString = readerNameMultiString.Substring(nullindex + 1, len);
                }
            }
            return readersList;
        }
 
 
        static IntPtr connect(IntPtr hContext, string readerName)
        {
            const int SCARD_SHARE_SHARED = 0x00000002; // - This application will allow others to share the reader
            const int SCARD_SHARE_EXCLUSIVE = 0x00000001; // - This application will NOT allow others to share the reader
            const int SCARD_SHARE_DIRECT = 0x00000003; // - Direct control of the reader, even without a card
 
 
            const int SCARD_PROTOCOL_T0= 1; // - Use the T=0 protocol (value = 0x00000001)
            const int SCARD_PROTOCOL_T1= 2;// - Use the T=1 protocol (value = 0x00000002)
            const int SCARD_PROTOCOL_RAW = 4;// - Use with memory type cards (value = 0x00000004)
 
            IntPtr hCard = IntPtr.Zero;
            IntPtr activeProtocol = IntPtr.Zero;
            uint ret = SCardConnect(hContext, readerName, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T1, ref hCard, ref activeProtocol);
            if (ret != SCARD_S_SUCCESS)
            {
                throw new ApplicationException("カードに接続できません。code = " + ret);
            }
            return hCard;
 
        }
 
        static void disconnect(IntPtr hCard) 
        {
            const int SCARD_LEAVE_CARD =     0; // Don't do anything special on close
            const int SCARD_RESET_CARD =     1; // Reset the card on close
            const int SCARD_UNPOWER_CARD =   2; // Power down the card on close
            const int SCARD_EJECT_CARD = 3; // Eject the card on close
 
            uint ret = SCardDisconnect(hCard, SCARD_LEAVE_CARD);
            if (ret != SCARD_S_SUCCESS)
            {
                throw new ApplicationException("カードとの接続を切断できません。code = " + ret);
            }
        }
 
        private static IntPtr getPciT1()
        {
            IntPtr handle = LoadLibrary("Winscard.dll");
            IntPtr pci = GetProcAddress(handle, "g_rgSCardT1Pci");
            FreeLibrary(handle);
            return pci;
        }
 
        static int transmit(IntPtr hCard, byte[] sendBuffer, byte[] recvBuffer)
        {
            SCARD_IO_REQUEST ioRecv = new SCARD_IO_REQUEST();
            ioRecv.cbPciLength = 255;
 
            int pcbRecvLength = recvBuffer.Length;
            int cbSendLength = sendBuffer.Length;
            IntPtr SCARD_PCI_T1 = getPciT1();
            uint ret = SCardTransmit(hCard, SCARD_PCI_T1, sendBuffer, cbSendLength, ioRecv, recvBuffer, ref pcbRecvLength);
            if (ret != SCARD_S_SUCCESS)
            {
                throw new ApplicationException("カードへの送信に失敗しました。code = " + ret);
            }
            return pcbRecvLength; // 受信したバイト数(recvBufferに受け取ったバイト数)
 
        }
 
        static int control(IntPtr hCard, int controlCode, byte[] sendBuffer, byte[] recvBuffer)
        {
            int bytesReturned = 0;
            uint ret = SCardControl(hCard, controlCode, sendBuffer, sendBuffer.Length, recvBuffer, recvBuffer.Length, ref bytesReturned);
            if (ret != SCARD_S_SUCCESS)
            {
                throw new ApplicationException("カードへの制御命令送信に失敗しました。code = " + ret);
            }
            return bytesReturned;
        }
 
        static SCARD_READERSTATE[] initializeReaderState(IntPtr hContext, List<string> readerNameList)
        {
            const int SCARD_STATE_UNAWARE = 0x00000000;
 
            SCARD_READERSTATE[] readerStateArray = new SCARD_READERSTATE[readerNameList.Count];
            int i = 0;
            foreach (string readerName in readerNameList)
            {
                readerStateArray[i].dwCurrentState = SCARD_STATE_UNAWARE;
                readerStateArray[i].szReader = readerName;
                i++;
            }
 
 
            uint ret = SCardGetStatusChange(hContext, 100/*msec*/, readerStateArray, readerStateArray.Length);
            if (ret != SCARD_S_SUCCESS)
            {
                throw new ApplicationException("リーダーの初期状態の取得に失敗。code = " + ret);
            }
            return readerStateArray;
        }
 
        static void waitReaderStatusChange(IntPtr hContext, SCARD_READERSTATE[] readerStateArray, int timeoutMillis)
        {
            uint ret = SCardGetStatusChange(hContext, timeoutMillis/*msec*/, readerStateArray, readerStateArray.Length);
            switch(ret) {
                case SCARD_S_SUCCESS:
                    break;
                case SCARD_E_TIMEOUT:
                    throw new TimeoutException();
                default:
                    throw new ApplicationException("リーダーの状態変化の取得に失敗。code = " + ret);
            }
 
        }
 
        static void updateCurrentState(SCARD_READERSTATE[] readerStateArray)
        {
            for (int i = 0; i < readerStateArray.Length; i++)
            {
                readerStateArray[i].dwCurrentState = readerStateArray[i].dwEventState;
            }
        }
 
 
 
    }
 
 
    class ReadResult
    {
        public string readerSerialNumber;
        public string cardId;
    }
 
}