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); } }
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); }
/// <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); }
/// <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); }
/// <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); }
/// <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); }
/// <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); }
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); }
/// <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); }
/// <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); }
/// <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); }
/// <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); }
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); }
/// <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); }
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(); }