From 2f2ede5d3a6ef5e1eb41074ba73aaef602881fd0 Mon Sep 17 00:00:00 2001 From: Scott Pinkawa Date: Thu, 13 Jun 2024 08:33:17 +0200 Subject: [PATCH 1/2] Added USB Port detection for reader --- lib/smartcard/pcsc/context.rb | 31 +++++++++++++++++++++++++++++++ test/pcsc/context_test.rb | 17 +++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/lib/smartcard/pcsc/context.rb b/lib/smartcard/pcsc/context.rb index 6d0576a..98c5acd 100644 --- a/lib/smartcard/pcsc/context.rb +++ b/lib/smartcard/pcsc/context.rb @@ -95,6 +95,37 @@ def readers(groups = []) groups_ptr.free end end + + def usb_port_for_reader(reader) + begin + card = self.card(reader, :shared, :any) + begin + attrib = card[FFILib::Consts::SCARD_ATTR_CHANNEL_ID] + if attrib + ddddcccc = attrib.unpack('I')[0] + dddd = ddddcccc >> 16 + if dddd == 0x0020 + bus = (ddddcccc & 0xFF00) >> 8 + device_address = ddddcccc & 0xFF + # return bus and device_address + return {bus: bus, device_address: device_address} + else + puts "Not a USB reader" + end + else + puts "Unable to get SCARD_ATTR_CHANNEL_ID" + end + ensure + card.disconnect if card + end + rescue Smartcard::PCSC::Exception => e + if e.message.include?("no_smartcard") + nil + else + raise e + end + end + end # Queries smart-card readers, blocking until a state change occurs. # diff --git a/test/pcsc/context_test.rb b/test/pcsc/context_test.rb index d262047..c78b13f 100644 --- a/test/pcsc/context_test.rb +++ b/test/pcsc/context_test.rb @@ -42,6 +42,23 @@ def _check_readers(readers) 'each reader name should be a string' end end + + def test_usb_port_for_reader + readers = @context.readers + readers.each do |reader| + usb_port = @context.usb_port_for_reader(reader) + if usb_port + assert_operator usb_port, :kind_of?, Hash, 'usb_port should be a Hash' + assert_includes usb_port, :bus, 'usb_port should include :bus key' + assert_includes usb_port, :device_address, 'usb_port should include :device_address key' + + assert_operator usb_port[:bus], :kind_of?, Integer, ':bus should be an Integer' + assert_operator usb_port[:device_address], :kind_of?, Integer, ':device_address should be an Integer' + else + assert_nil usb_port, 'usb_port should be nil for non-USB reader or no card present' + end + end + end def test_wait_for_status_change readers = @context.readers From 2810c5421aa61ac9906b0ff8894148c93bbb68c3 Mon Sep 17 00:00:00 2001 From: Scott Pinkawa Date: Thu, 13 Jun 2024 08:34:07 +0200 Subject: [PATCH 2/2] Added ReaderStateQuery for usb_address --- lib/smartcard/pcsc/ffi_structs.rb | 3 ++- lib/smartcard/pcsc/reader_state_queries.rb | 14 ++++++++++++++ test/pcsc/reader_state_queries_test.rb | 8 ++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/smartcard/pcsc/ffi_structs.rb b/lib/smartcard/pcsc/ffi_structs.rb index 3f3b4cc..5a1b749 100644 --- a/lib/smartcard/pcsc/ffi_structs.rb +++ b/lib/smartcard/pcsc/ffi_structs.rb @@ -33,7 +33,8 @@ class ReaderStateQuery < FFI::Struct :current_state, Word, :event_state, Word, :atr_length, Word, - :atr, [:char, Consts::MAX_ATR_SIZE] + :atr, [:char, Consts::MAX_ATR_SIZE], + :usb_address, :pointer end # Low-level protocol information for APDU transmission and reception. diff --git a/lib/smartcard/pcsc/reader_state_queries.rb b/lib/smartcard/pcsc/reader_state_queries.rb index 2c81df8..706c462 100644 --- a/lib/smartcard/pcsc/reader_state_queries.rb +++ b/lib/smartcard/pcsc/reader_state_queries.rb @@ -5,6 +5,7 @@ # License:: MIT require 'set' +require 'json' # :nodoc: namespace module Smartcard::PCSC @@ -169,6 +170,19 @@ def reader_name=(new_name) self[:reader_name] = FFI::MemoryPointer.from_string new_name end + def usb_address + if self[:usb_address].null? + nil + else + JSON.parse(self[:usb_address].read_string, symbolize_names: true) + end + end + + def usb_address=(new_usb_address) + self[:usb_address].free if self[:usb_address].kind_of? FFI::MemoryPointer + self[:usb_address] = FFI::MemoryPointer.from_string(new_usb_address.to_json) + end + # Packs an unpacked card state (symbol or set of symbols) into a number. # # This should not be used by client code. diff --git a/test/pcsc/reader_state_queries_test.rb b/test/pcsc/reader_state_queries_test.rb index 3a6a5fd..48b139b 100644 --- a/test/pcsc/reader_state_queries_test.rb +++ b/test/pcsc/reader_state_queries_test.rb @@ -4,6 +4,7 @@ require 'rubygems' require 'smartcard' +require 'json' require 'test/unit' @@ -20,6 +21,8 @@ def setup @queries[0].atr = 'grreat success' @queries[0].reader_name = 'PC/SC Reader 0' @queries[1].reader_name = 'CCID Reader 1' + @queries[0].usb_address = { :bus => 1, :device_address => 2 } + @queries[1].usb_address = { :bus => 1, :device_address => 4 } end def teardown @@ -65,6 +68,11 @@ def test_reader_names assert_equal 'PC/SC Reader 0', @queries[0].reader_name assert_equal 'CCID Reader 1', @queries[1].reader_name end + + def test_usb_address + assert_equal({ :bus => 1, :device_address => 2 }, @queries[0].usb_address) + assert_equal({ :bus => 1, :device_address => 4 }, @queries[1].usb_address) + end def test_ack_changes @queries.ack_changes