Exemple #1
0
        private void TransmitToCard(CommandAPDU command)
        {
            try
            {
                var crp       = new CommandResponsePair(command);
                var errorCode = crp.Transmit(SharedData.CardChannel);

                if (errorCode != ErrorCode.Success)
                {
                    LogFailure($"ErrorCode={errorCode}");
                }
                else if (crp.RApdu.StatusWord != 0x9000)
                {
                    LogFailure($"SW={crp.RApdu.StatusWord:X4}");
                }
                else
                {
                    LogSuccess();
                }
            }
            catch (Exception exception)
            {
                LogException(exception);
            }
        }
Exemple #2
0
        public ErrorCode Transmit(ICardCommand command, ICardResponse response)
        {
            ErrorCode ret;
            var       cAPDU = (CommandAPDU)command;

            if ((cAPDU.Ins == 0xA4) && cAPDU.IsCc4)
            {
                var le = cAPDU.Le;
                cAPDU.HasLe = false;
                // As an example, direct use of the layer to transmit the command
                ret = stack.RequestLayer(this, SearchMode.Next).Transmit(command, response);
                var rAPDU = (ResponseAPDU)response;
                if ((ret == ErrorCode.Success) && (rAPDU.Sw1 == 0x61))
                {
                    if (le > rAPDU.Sw2)
                    {
                        le = rAPDU.Sw2;
                    }
                    // As an example, use of a CommandResponsePair object to manage the dialog
                    var crpGetResponse = new CommandResponsePair(new GetResponseCommand(le))
                    {
                        RApdu = rAPDU
                    };
                    ret = crpGetResponse.Transmit(stack.RequestLayer(this, SearchMode.Next));
                }
            }
            else
            {
                ret = stack.RequestLayer(this, SearchMode.Next).Transmit(command, response);
            }

            return(ret);
        }
Exemple #3
0
        /// <summary>
        /// Process the GET CHALLENGE of an EMV transaction.
        /// </summary>
        /// <returns>Last status word.</returns>
        public UInt16 GetChallenge()
        {
            BeforeGetChallengeEvent.Raise(this, new EmvEventArgs());

            // Clear previous challenge
            _iccChallenge = null;

            // Execute GET CHALLENGE
            var cAPDU = new CommandAPDU(0x00, 0x84, 0x00, 0x00, 0);
            var crp   = new CommandResponsePair(cAPDU);

            crp.Transmit(_cardChannel);
            _lastStatusWord = crp.RApdu.StatusWord;

            // If GET RESPONSE needed, do it
            if (crp.RApdu.Sw1 == 0x61)
            {
                crp             = new CommandResponsePair(new GetResponseCommand(crp.RApdu.Sw2));
                _lastStatusWord = crp.RApdu.StatusWord;
            }

            // Finally, store result
            if (crp.RApdu.StatusWord == 0x9000)
            {
                _iccChallenge = crp.RApdu.Udr;
            }

            AfterGetChallengeEvent.Raise(this, new EmvEventArgs());

            return(_lastStatusWord);
        }
Exemple #4
0
        /// <summary>
        /// Process the VERIFY PIN phase of an EMV transaction.
        /// </summary>
        /// <param name="pinBlock">PIN Block.</param>
        /// <returns>Last status word.</returns>
        public UInt16 VerifyPin(PINBlock pinBlock)
        {
            BeforeVerifyPinEvent.Raise(this, new EmvEventArgs());

            byte p2 = 0x00;

            if (pinBlock is PlaintextPINBlock)
            {
                p2 = 0x80;
            }

            // Execute the VERIFY instruction
            var cAPDU = new CommandAPDU(0x00, 0x20, 0x00, p2, (UInt32)pinBlock.PinBlock.Length, pinBlock.PinBlock);
            var crp   = new CommandResponsePair(cAPDU);

            crp.Transmit(_cardChannel);
            _lastStatusWord      = crp.RApdu.StatusWord;
            _verifyPinStatusWord = _lastStatusWord;

            if (_lastStatusWord != 0x9000)
            {
                Tvr.CardholderVerificationFailed = true;
            }

            AfterVerifyPinEvent.Raise(this, new EmvEventArgs());

            return(_lastStatusWord);
        }
        /// <summary>
        /// Reads the file pointed by the SFI found in the FCI of the application.
        /// </summary>
        /// <returns>Last status word.</returns>
        public UInt16 Read()
        {
            BeforeReadEvent.Raise(this, new EmvEventArgs());

            var tlvSfi = TlvFci.GetTag(0x88);

            if (tlvSfi == null)
            {
                throw new Exception(String.Format("PSE: no tag 88 (sfi) found in FCI [{0}]", TlvFci));
            }
            var sfi = new ShortFileIdentifier(tlvSfi);

            byte recordNumber = 0;

            do
            {
                recordNumber++;
                var crp = new CommandResponsePair {
                    CApdu = new EMVReadRecordCommand(recordNumber, sfi.Sfi, 0)
                };
                crp.Transmit(_cardChannel);
                _lastStatusWord = crp.RApdu.StatusWord;
                if (crp.RApdu.StatusWord == 0x9000)
                {
                    _tlvRecords.Add(new TlvData(crp.RApdu.Udr));
                }
            } while (_lastStatusWord == 0x9000);

            AfterReadEvent.Raise(this, new EmvEventArgs());

            return(_lastStatusWord);
        }
Exemple #6
0
        /// <summary>
        /// MasterCard cAPDU.
        /// Process the COMPUTE CRYPTOGRAPHIC CHECKSUM phase of a MasterCard transaction.
        /// </summary>
        /// <returns>Last status word.</returns>
        public UInt16 ComputeCryptographicChecksum()
        {
            BeforeComputeCryptographicChecksumEvent.Raise(this, new EmvEventArgs());

            // If UDOL 9F69 is not supplied in records, then use default UDOL = 9F6A04
            byte[] udolDataValue = null;
            var    tlvAll        = new List <TlvData>();

            foreach (var record in _tlvRecords)
            {
                tlvAll.Add(record);
                if (record.HasTag(0x9F69))
                {
                    var udol = new DataObjectList(record.GetTag(0x9F69).Value);
                    tlvAll.AddRange(TlvTerminalData);
                    udolDataValue = udol.BuildData(tlvAll);
                }
            }

            if (udolDataValue == null)
            {
                var tmp        = "";
                var seed       = new Random(10000).Next();
                var rndNumbers = new Random(seed);
                for (var i = 0; i < 8; i++)
                {
                    var rndNumber = rndNumbers.Next(10);
                    tmp += rndNumber.ToString(CultureInfo.InvariantCulture);
                }
                udolDataValue = tmp.FromHexa();
            }

            // Execute COMPUTE CRYPTOGRAPHIC CHECKSUM
            var cAPDU = new CommandAPDU(0x80, 0x2A, 0x8E, 0x80, (uint)udolDataValue.Length, udolDataValue, 0);
            var crp   = new CommandResponsePair(cAPDU);

            crp.Transmit(_cardChannel);
            _lastStatusWord = crp.RApdu.StatusWord;

            // If GET RESPONSE needed, do it
            if (crp.RApdu.Sw1 == 0x61)
            {
                _tlvCryptographicChecksum = new TlvData();
                crp = new CommandResponsePair(new GetResponseCommand(crp.RApdu.Sw2));
                crp.Transmit(_cardChannel);
                _lastStatusWord = crp.RApdu.StatusWord;
            }

            // Finally, store result
            if (crp.RApdu.StatusWord == 0x9000)
            {
                _tlvCryptographicChecksum = new TlvData(crp.RApdu.Udr);
            }

            AfterComputeCryptographicChecksumEvent.Raise(this, new EmvEventArgs());

            return(_lastStatusWord);
        }
Exemple #7
0
        /// <summary>
        /// Process the INTERNAL AUTHENTICATION of an EMV transaction.
        /// </summary>
        /// <param name="unpredictableNumber">Unpredictable number.</param>
        /// <returns>Last status word.</returns>
        public UInt16 InternalAuthenticate(byte[] unpredictableNumber)
        {
            BeforeInternalAuthenticateEvent.Raise(this, new EmvEventArgs());

            _tlvInternalAuthenticateUnpredictableNumber = new TlvData(0x9F37, 0x04, unpredictableNumber);

            // Build DDOL data
            byte[] ddolDataValue;
            if (Ddol != null)
            {
                // Use DDOL to build data
                var tlvAll = new List <TlvData>
                {
                    TlvFci,
                    TlvProcessingOptions,
                    _tlvInternalAuthenticateUnpredictableNumber
                };
                tlvAll.AddRange(TlvRecords);
                tlvAll.AddRange(TlvTerminalData);
                ddolDataValue = Ddol.BuildData(tlvAll);
            }
            else
            {
                ddolDataValue = new byte[0];
            }

            // Execute GET PROCESSING OPTIONS
            var cAPDU = new CommandAPDU(0x00, 0x88, 0x00, 0x00, (uint)ddolDataValue.Length, ddolDataValue, 0);
            var crp   = new CommandResponsePair(cAPDU);

            crp.Transmit(_cardChannel);
            _lastStatusWord = crp.RApdu.StatusWord;

            // If GET RESPONSE needed, do it
            if (crp.RApdu.Sw1 == 0x61)
            {
                _tlvSignedDynamicApplicationResponse = new TlvData();
                crp = new CommandResponsePair(new GetResponseCommand(crp.RApdu.Sw2));
                crp.Transmit(_cardChannel);
                _lastStatusWord = crp.RApdu.StatusWord;
            }

            // Finally, store result
            if (crp.RApdu.StatusWord == 0x9000)
            {
                _tlvSignedDynamicApplicationResponse = new TlvData(crp.RApdu.Udr);
            }

            AfterInternalAuthenticateEvent.Raise(this, new EmvEventArgs());

            return(_lastStatusWord);
        }
Exemple #8
0
        /// <summary>
        /// Process the GET PROCESSING OPTIONS phase of an EMV transaction.
        /// </summary>
        /// <returns>Last status word.</returns>
        public UInt16 GetProcessingOptions()
        {
            BeforeGetProcessingOptionsEvent.Raise(this, new EmvEventArgs());

            // If PDOL 9F38 is not supplied in FCI, then used 8300 as UDC; if supplied: build the PDOL in tag 83 L V
            byte[] pdolDataValue;
            if (TlvFci.HasTag(0x9F38))
            {
                // Use PDOL to build tag 83 value
                var pdol   = new DataObjectList(TlvFci.GetTag(0x9F38).Value);
                var tlvAll = new List <TlvData> {
                    TlvFci
                };
                tlvAll.AddRange(TlvTerminalData);
                pdolDataValue = pdol.BuildData(tlvAll);
            }
            else
            {
                pdolDataValue = new byte[0];
            }
            // Build tag 83 with computed value
            var tlvPdolData = new TlvData(0x83, (uint)pdolDataValue.Length, pdolDataValue);

            // Execute GET PROCESSING OPTIONS
            var cAPDU = new CommandAPDU(0x80, 0xA8, 0x00, 0x00, tlvPdolData.Length + 2, tlvPdolData.ToByteArray(), 0);
            var crp   = new CommandResponsePair(cAPDU);

            crp.Transmit(_cardChannel);

            _lastStatusWord = crp.RApdu.StatusWord;

            // If GET RESPONSE needed, do it
            if (crp.RApdu.Sw1 == 0x61)
            {
                _tlvProcessingOptions = new TlvData();

                crp = new CommandResponsePair(new GetResponseCommand(crp.RApdu.Sw2));
                crp.Transmit(_cardChannel);
                _lastStatusWord = crp.RApdu.StatusWord;
            }

            // Finally, store result
            if (crp.RApdu.StatusWord == 0x9000)
            {
                _tlvProcessingOptions = new TlvData(crp.RApdu.Udr);
            }

            AfterGetProcessingOptionsEvent.Raise(this, new EmvEventArgs());

            return(_lastStatusWord);
        }
Exemple #9
0
        private static CommandResponsePair ExecuteCrp(CommandAPDU cApdu)
        {
            PrintCommand(cApdu);

            var crp = new CommandResponsePair(cApdu);

            var errorCode = crp.Transmit(channel);

            if (errorCode != ErrorCode.Success)
            {
                Console.WriteLine($"ErrorCode: {errorCode}");

                return(crp);
            }

            PrintResponse(crp.RApdu);

            return(crp);
        }
Exemple #10
0
        /// <summary>
        /// Reads the file pointed by the SFI found in the FCI of the application.
        /// </summary>
        /// <returns>Last status word.</returns>
        public UInt16 ReadDataFile(AflEntry file)
        {
            for (var recordNumber = file.FirstRecord; recordNumber <= file.LastRecord; recordNumber++)
            {
                CommandAPDU cAPDU = new EMVReadRecordCommand(recordNumber, file.Sfi, 0);
                var         crp   = new CommandResponsePair(cAPDU);
                crp.Transmit(_cardChannel);

                if (crp.RApdu.StatusWord == 0x9000)
                {
                    var tlv = new TlvData(crp.RApdu.Udr);

                    if (tlv.Tag != 0x70)
                    {
                        throw new Exception(String.Format("EMVApplication.readData(): record is not TLV-coded with tag 70 [{0}]", tlv));
                    }

                    // Store data in list
                    _tlvRecords.Add(tlv);

                    // If used for offline, store it in dedicated list
                    if (recordNumber - file.FirstRecord < file.OfflineNumberOfRecords)
                    {
                        // For files with SFI in the range 1 to 10, the record tag ('70') and the record length are excluded from the offline data authentication process.
                        if (file.Sfi <= 10)
                        {
                            foreach (var tlvData in tlv.InnerTlvs)
                            {
                                _tlvOfflineRecords.Add(tlvData);
                            }
                        }
                        //For files with SFI in the range 11 to 30, the record tag ('70') and the record length are not excluded from the offline data authentication process.
                        else
                        {
                            _tlvOfflineRecords.Add(tlv);
                        }
                    }
                }
                _lastStatusWord = crp.RApdu.StatusWord;
            }
            return(_lastStatusWord);
        }
Exemple #11
0
        /// <summary>
        /// Selects the DF by its DF Name or AID.
        /// </summary>
        /// <returns>Last status word</returns>
        public UInt16 Select()
        {
            BeforeSelectEvent.Raise(this, new EmvEventArgs());

            // Execute the SELECT
            var crp = new CommandResponsePair(new EMVSelectByNameCommand(_adfName, 0));

            crp.Transmit(_cardChannel);
            _lastStatusWord = crp.RApdu.StatusWord;

            // Finally, store FCI
            if (crp.RApdu.StatusWord == 0x9000)
            {
                TlvFci = new TlvData(crp.RApdu.Udr);
            }

            AfterSelectEvent.Raise(this, new EmvEventArgs());

            return(_lastStatusWord);
        }
Exemple #12
0
        /// <summary>
        /// Reads log file of the EMV application. Records are stored.
        /// </summary>
        /// <returns>Last status word.</returns>
        public UInt16 ReadLogFile()
        {
            BeforeReadLogFileEvent.Raise(this, new EmvEventArgs());

            if (LogEntry == null)
            {
                throw new LogEntryNotFoundException(String.Format("EMVApplication.readLogFile(): logEntry ({0}) undefined.", _logEntry));
            }

            if (LogFormat == null)
            {
                throw new LogFormatNotFoundException(String.Format("EMVApplication.readLogFile(): logFormat ({0}) undefined.", _logFormat));
            }

            _logRecords = new List <List <TlvData> >();

            byte recordNumber = 0;

            do
            {
                recordNumber++;
                CommandAPDU cAPDU = new EMVReadRecordCommand(recordNumber, LogEntry.Sfi, 0);
                var         crp   = new CommandResponsePair(cAPDU);
                crp.Transmit(_cardChannel);
                _lastStatusWord = crp.RApdu.StatusWord;
                if (crp.RApdu.StatusWord == 0x9000)
                {
                    var record       = crp.RApdu.Udr;
                    var dataInRecord = _logFormat.ParseRawData(record);
                    _logRecords.Add(dataInRecord);
                }
            } while (_lastStatusWord == 0x9000 && recordNumber < _logEntry.CyclicFileSize);

            AfterReadLogFileEvent.Raise(this, new EmvEventArgs());

            return(_lastStatusWord);
        }
Exemple #13
0
        /// <summary>
        /// Process a GET DATA to retrieve one EMV information.
        /// </summary>
        /// <param name="tag">
        ///     <list type="bullet">
        ///         <item><c>0x9F36</c> for ATC</item>
        ///         <item><c>0x9F13</c> for Last Online ATC Register</item>
        ///         <item><c>0x9F17</c> for PIN Try Counter</item>
        ///         <item><c>0x9F4F</c> for Log Format</item>
        ///     </list>
        /// </param>
        /// <param name="tlv"></param>
        /// <returns>Last status word.</returns>
        protected UInt16 GetData(UInt32 tag, ref TlvData tlv)
        {
            // Execute GET DATA instruction
            var cAPDU = new GetDataCommand(tag, 0)
            {
                Cla = 0x80
            };
            var crp = new CommandResponsePair(cAPDU);

            if (crp.Transmit(_cardChannel) != ErrorCode.Success)
            {
                return(_lastStatusWord);
            }

            _lastStatusWord = crp.RApdu.StatusWord;

            // Finally store rAPDU
            if (crp.RApdu.StatusWord == 0x9000)
            {
                tlv = new TlvData(crp.RApdu.Udr);
            }

            return(_lastStatusWord);
        }
Exemple #14
0
        private ErrorCode TransmitT0(CommandAPDU cAPDU, ResponseAPDU rAPDU)
        {
            ErrorCode ret;

            // Adapt APDU for T=0 smartcards
            // If C-APDU is CC1: add P3=0
            if (cAPDU.IsCc1)
            {
                var crp = new CommandResponsePair(cAPDU)
                {
                    CApdu = { Le = 0 }, RApdu = rAPDU
                };
                ret = crp.Transmit(_cardChannel);
            }
            // If C-APDU is CC2: test SW1=61/6C
            else if (cAPDU.IsCc2)
            {
                var crp = new CommandResponsePair(cAPDU);
                // Let the crp create a new crp.rAPDU
                ret = crp.Transmit(_cardChannel);
                if (ret == ErrorCode.Success && crp.RApdu.Sw1 == 0x61)
                {
                    var crpGetResponse = new CommandResponsePair(new GetResponseCommand(crp.RApdu.Sw2))
                    {
                        RApdu = rAPDU
                    };
                    ret = crpGetResponse.Transmit(_cardChannel);
                }
                else if (ret == ErrorCode.Success && crp.RApdu.Sw1 == 0x6C)
                {
                    var crpWithLe = new CommandResponsePair {
                        CApdu = crp.CApdu
                    };
                    crpWithLe.CApdu.Le = crp.RApdu.Sw2;
                    crpWithLe.RApdu    = rAPDU;
                    ret = crpWithLe.Transmit(_cardChannel);
                }
                else
                {
                    // last rAPDU must be returned as is
                    rAPDU.Udr = crp.RApdu.Udr;
                    rAPDU.Sw1 = crp.RApdu.Sw1;
                    rAPDU.Sw2 = crp.RApdu.Sw2;
                }
            }
            // If C-APDU is CC3: nothing to do
            else if (cAPDU.IsCc3)
            {
                var crp = new CommandResponsePair(cAPDU)
                {
                    RApdu = rAPDU
                };
                ret = crp.Transmit(_cardChannel);
            }
            // If C-APDU is CC4: first CC3 then CC2 GET RESPONSE
            else
            {
                cAPDU.HasLe = false;
                var crp = new CommandResponsePair(cAPDU);
                // Let the crp create a new crp.rAPDU
                ret = crp.Transmit(_cardChannel);
                if (ret == ErrorCode.Success && crp.RApdu.Sw1 == 0x61)
                {
                    var crpGetResponse = new CommandResponsePair(new GetResponseCommand(crp.RApdu.Sw2))
                    {
                        RApdu = rAPDU
                    };
                    ret = crpGetResponse.Transmit(_cardChannel);
                }
                else if (ret == ErrorCode.Success && crp.RApdu.Sw1 == 0x6C)
                {
                    var crpWithLe = new CommandResponsePair {
                        CApdu = crp.CApdu
                    };
                    crpWithLe.CApdu.Le = crp.RApdu.Sw2;
                    crpWithLe.RApdu    = rAPDU;
                    ret = crpWithLe.Transmit(_cardChannel);
                }
                else
                {
                    // last rAPDU must be returned as is
                    rAPDU.Udr = crp.RApdu.Udr;
                    rAPDU.Sw1 = crp.RApdu.Sw1;
                    rAPDU.Sw2 = crp.RApdu.Sw2;
                }
                // Restore initial cAPDU for logs
                cAPDU.HasLe = true;
            }

            return(ret);
        }
Exemple #15
0
        /// <summary>
        /// Process the GENERATE APPLICATION CRYPTOGRAM 1 of an EMV transaction.
        /// </summary>
        /// <param name="cryptogramType">Type of the cryptogram to be generated.</param>
        /// <param name="unpredictableNumber">Unpredictable number.</param>
        /// <returns>Last status word.</returns>
        public UInt16 GenerateAc1(CryptogramType cryptogramType, byte[] unpredictableNumber)
        {
            BeforeGenerateAC1Event.Raise(this, new EmvEventArgs());

            _tlvGenerateAC1UnpredictableNumber = new TlvData(0x9F37, (uint)unpredictableNumber.Length, unpredictableNumber);

            _requestedAC1Type = cryptogramType;

            byte referenceControlParameter;

            switch (cryptogramType)
            {
            case CryptogramType.AAC:
                referenceControlParameter = 0x00;
                break;

            case CryptogramType.ARQC:
                referenceControlParameter = 0x80;
                break;

            case CryptogramType.TC:
                referenceControlParameter = 0x40;
                break;

            default:
                throw new Exception(String.Format("EMVApplication.generateAC1: bad Cryptogram Type [{0}]", cryptogramType));
            }

            // TODO: if CDA signature requested, set bit 5 to 1 (0x08)

            // Build CDOL1 data
            byte[] cdolDataValue;
            if (Cdol1 != null)
            {
                // Use CDOL1 to build data
                var tlvAll = new List <TlvData> {
                    TlvFci, TlvProcessingOptions, _tlvGenerateAC1UnpredictableNumber
                };
                tlvAll.AddRange(TlvRecords);
                tlvAll.AddRange(TlvTerminalData);
                tlvAll.Add(Tvr.Tlv);
                cdolDataValue = Cdol1.BuildData(tlvAll);
            }
            else
            {
                cdolDataValue = new byte[0];
            }

            // Execute GENERATE AC
            var cAPDU = new CommandAPDU(0x80, 0xAE, referenceControlParameter, 0x00, (UInt32)cdolDataValue.Length, cdolDataValue, 0); // Bad APDU !
            var crp   = new CommandResponsePair(cAPDU);

            crp.Transmit(_cardChannel);

            _lastStatusWord = crp.RApdu.StatusWord;

            // If GET RESPONSE needed, do it
            if (crp.RApdu.Sw1 == 0x61)
            {
                _tlvGenerateAC1Response = new TlvData();
                crp = new CommandResponsePair(new GetResponseCommand(crp.RApdu.Sw2));
                crp.Transmit(_cardChannel);
                _lastStatusWord = crp.RApdu.StatusWord;
            }

            // Finally, store result
            if (crp.RApdu.StatusWord == 0x9000)
            {
                _tlvGenerateAC1Response = new TlvData(crp.RApdu.Udr);
            }

            AfterGenerateAC1Event.Raise(this, new EmvEventArgs());

            return(_lastStatusWord);
        }
Exemple #16
0
        private static void Main(/*string[] args*/)
        {
            try
            {
                Console.WindowWidth  = 132;
                Console.WindowHeight = 50;
            }
            catch (Exception)
            {
            }
            Console.ForegroundColor = ConsoleColor.Gray;

            Console.WriteLine();
            Console.WriteLine("=========== S t a t u s W o r d D i c t i o n a r y");

            #region >> StatusWordDictionary

            //            StatusWordDictionary swd = SerializedObject<StatusWordDictionary>.loadFromXml(@"Dictionary.StatusWord.xml");
            //            Console.WriteLine("SW: {0:X4} Description: {1}", 0x9000, swd.getDescription(0x90, 0x00));
            //            Console.WriteLine("SW: {0:X4} Description: {1}", 0x6800, swd.getDescription(0x68, 0x00));
            //            Console.WriteLine("SW: {0:X4} Description: {1}", 0x6800, swd.getDescription(0x68, 0x84));
            //            Console.WriteLine("SW: {0:X4} Description: {1}", 0x6A00, swd.getDescription(0x6A, 0x83));

            #endregion

            Console.WriteLine();
            Console.WriteLine("=========== C o n s o l e O b s e r v e r");

            #region >> ConsoleObserver

            var logger = new ConsoleObserver();

            #endregion

            Console.WriteLine();
            Console.WriteLine("=========== C a r d C o n t e x t");

            #region >> CardContext

            ICardContext context = new CardContext();
            logger.ObserveContext((ICardContextObservable)context);

            context.Establish();
            context.ListReaderGroups();
            context.ListReaders(context.Groups[0]);

            #endregion

            Console.WriteLine();
            Console.WriteLine("=========== C a r d   i n s e r t i o n   d e t e c t i o n");

            #region >> StatusChangeMonitor

            var monitor = new StatusChangeMonitor(context);

            logger.ObserveMonitor(monitor);

            var readerState = monitor.WaitForCardPresence(0);
            if (readerState == null)
            {
                Console.ForegroundColor = ConsoleColor.Green;
                Console.WriteLine(">> Insert a card in one of the {0} readers (time out in 15s)", context.ReadersCount);
                readerState = monitor.WaitForCardPresence(15000);
            }

            if (readerState == null)
            {
                Console.WriteLine(">> Time Out! No card found");
                return;
            }

            #endregion

            Console.WriteLine();
            Console.WriteLine("=========== C a r d C h a n n e l");

            #region >> CardChannel

            ICardChannel cardChannel = new CardChannel(context, readerState.ReaderName);
            logger.ObserveChannel((ICardChannelObservable)cardChannel);

            cardChannel.Connect(ShareMode.Shared, Protocol.Any);

            //Console.WriteLine(cardChannel.getStatus());

            byte[] recvBuffer = null;
            cardChannel.GetAttrib(Attrib.AtrString, ref recvBuffer);

            recvBuffer = null;
            cardChannel.GetAttrib(Attrib.DeviceFriendlyName, ref recvBuffer);

            recvBuffer = null;
            cardChannel.GetAttrib(Attrib.AtrString, ref recvBuffer);

            cardChannel.Reconnect(ShareMode.Shared, Protocol.Any, Disposition.ResetCard);

            Console.WriteLine();

            var           cAPDU = new CommandAPDU("00A4040005A000000069");
            ICardResponse rAPDU = new ResponseAPDU();

            cardChannel.Transmit(cAPDU, rAPDU);
            if (((ResponseAPDU)rAPDU).Sw1 == 0x61)
            {
                cAPDU = new CommandAPDU(String.Format("00C00000{0:X2}", ((ResponseAPDU)rAPDU).Sw2));
                rAPDU = new ResponseAPDU();
                cardChannel.Transmit(cAPDU, rAPDU);
            }

            Console.WriteLine();

            cAPDU = new CommandAPDU("00A404000E315041592E5359532E4444463031");
            rAPDU = new ResponseAPDU();
            cardChannel.Transmit(cAPDU, rAPDU);
            if (((ResponseAPDU)rAPDU).Sw1 == 0x61)
            {
                cAPDU = new CommandAPDU(String.Format("00C00000{0:X2}", ((ResponseAPDU)rAPDU).Sw2));
                rAPDU = new ResponseAPDU();
                cardChannel.Transmit(cAPDU, rAPDU);
            }

            cAPDU = new CommandAPDU(0x00, 0xA4, 0x00, 0x00, 0x02, new byte[] { 0x3F, 0x00 });
            rAPDU = new ResponseAPDU();
            cardChannel.Transmit(cAPDU, rAPDU);
            if (((ResponseAPDU)rAPDU).Sw1 == 0x61)
            {
                cAPDU = new CommandAPDU(String.Format("00C00000{0:X2}", ((ResponseAPDU)rAPDU).Sw2));
                rAPDU = new ResponseAPDU();
                cardChannel.Transmit(cAPDU, rAPDU);
            }

            cardChannel.Disconnect(Disposition.UnpowerCard);

            #endregion

            Console.WriteLine();
            Console.WriteLine("=========== C a r d C h a n n e l S t a c k");

            #region >> CardChannelStack

            var layers = new List <ICardChannelLayer> {
                new CardChannelLayer61(), new CardChannelLayer()
            }
            .ToObservableLayers()
            .ForEach(l => logger.ObserveChannel(l));

            ICardChannelStack cardStack = new CardChannelStack(layers);

            cardStack.Attach(context, readerState.ReaderName);

            cardStack.Connect(ShareMode.Shared, Protocol.Any);

            cardStack.Reconnect(ShareMode.Shared, Protocol.Any, Disposition.ResetCard);

            // Use of a CommandResponsePair object to manage the dialog
            cAPDU = new SelectCommand(SelectCommand.SelectionMode.SelectDFName, SelectCommand.FileOccurrence.FirstOrOnly, SelectCommand.FileControlInformation.ReturnFci, "A000000069".FromHexa(), 0xFF);
            var crp = new CommandResponsePair(cAPDU);
            crp.Transmit(cardStack);

            cardStack.Disconnect(Disposition.UnpowerCard);

            #endregion

            Console.WriteLine();
            Console.WriteLine("=========== C a r d   r e m o v a l   d e t e c t i o n");

            #region >> StatusChangeMonitor

            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine(">> Waiting for a change since last call (time out in 10s)");
            // "unpower" change should be fired for the previously used reader
            monitor.WaitForChange(10000);

            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine(">> Remove the card in one of the readers {0} (time out in 10s)", readerState.ReaderName);
            // Wait for another change
            monitor.WaitForChange(10000);

            #endregion

            Console.WriteLine();

            context.Release();
        }