Class: Smartcard::PCSC::Card

Inherits:
Object
  • Object
show all
Defined in:
lib/smartcard/pcsc/card.rb

Overview

Connects a smart-card in a PC/SC reader to the Ruby world.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(context, reader_name, sharing_mode = :exclusive, preferred_protocols = :any) ⇒ Card

Establishes a connection to the card in a PC/SC reader.

The first connection will power up the card and perform a reset on it.

Args:

context:: the Smartcard::PCSC::Context for the PC/SC resource manager
reader_name:: friendly name of the reader to connect to; reader names can
              be obtained from Smartcard::PCSC::Context#readers
sharing_mode:: whether a shared or exclusive lock will be requested on the
               reader; the possible values are +:shared+, +:exclusive+ and
               +:direct+ (see the SCARD_SHARE_ constants in the PC/SC API)
preferred_protocols:: the desired communication protocol; the possible
                      values are +:t0+, +:t1+, +:t15+, +:raw+, and +:any+
                      (see the SCARD_PROTOCOL_ constants in the PC/SC API)


29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/smartcard/pcsc/card.rb', line 29

def initialize(context, reader_name, sharing_mode = :exclusive,
               preferred_protocols = :any)
  handle_ptr = FFILib::WordPtr.new
  protocol_ptr = FFILib::WordPtr.new
  status = FFILib.card_connect context._handle, reader_name, sharing_mode,
                               preferred_protocols, handle_ptr, protocol_ptr
  raise Smartcard::PCSC::Exception, status unless status == :success

  @context = context
  @sharing_mode = sharing_mode
  @_handle = handle_ptr[:value]
  set_protocol FFILib::PROTOCOLS[protocol_ptr[:value]]
end

Instance Attribute Details

#_handleObject (readonly)

The low-level SCARDHANDLE data.

This should not be used by client code.



315
316
317
# File 'lib/smartcard/pcsc/card.rb', line 315

def _handle
  @_handle
end

#protocolObject (readonly)

The communication protocol in use with this smart-card.



318
319
320
# File 'lib/smartcard/pcsc/card.rb', line 318

def protocol
  @protocol
end

#sharing_modeObject (readonly)

The sharing mode for this smart-card session. (:shared or :exclusive)



321
322
323
# File 'lib/smartcard/pcsc/card.rb', line 321

def sharing_mode
  @sharing_mode
end

Instance Method Details

#[](attribute_name) ⇒ Object



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/smartcard/pcsc/card.rb', line 139

def [](attribute_name)
  length_ptr = FFILib::WordPtr.new
  status = FFILib.get_attrib @_handle, attribute_name, nil, length_ptr
  raise Smartcard::PCSC::Exception, status unless status == :success

  value_ptr = FFI::MemoryPointer.new :char, length_ptr[:value]
  begin
    status = FFILib.get_attrib @_handle, attribute_name, value_ptr,
                               length_ptr
    raise Smartcard::PCSC::Exception, status unless status == :success

    value_ptr.get_bytes 0, length_ptr[:value]
  ensure
    value_ptr.free
  end
end

#[]=(attribute_name, value) ⇒ Object

Sets the value of an attribute in the interface driver.

The interface driver may not implement all possible attributes.

Args:

attribute_name:: the attribute to be set; possible values are the members
                 of Smartcard::PCSC::FFILib::Attribute, for example
                 +:vendor_name+)
value:: string containing the value bytes to be assigned to the attribute


165
166
167
168
169
170
171
172
173
174
175
# File 'lib/smartcard/pcsc/card.rb', line 165

def []=(attribute_name, value)
  value_ptr = FFI::MemoryPointer.from_string value
  begin
    status = FFILib.set_attrib @_handle, attribute_name, value_ptr,
                               value.length
    raise Smartcard::PCSC::Exception, status unless status == :success
    value
  ensure
    value_ptr.free
  end
end

#begin_transactionObject

Starts a transaction, obtaining an exclusive lock on the smart-card.



107
108
109
110
# File 'lib/smartcard/pcsc/card.rb', line 107

def begin_transaction
  status = FFILib.begin_transaction @_handle
  raise Smartcard::PCSC::Exception, status unless status == :success
end

#control(code, data, receive_buffer_size = 4096) ⇒ Object

Sends a interface driver command for the smart-card reader.

This method is useful for creating client side reader drivers for functions like PIN pads, biometrics, or other smart card reader extensions that are not normally handled by the PC/SC API.

Args:

code:: control code for the operation; a driver-specific integer
data:: string containing the data bytes to be sent to the driver
receive_buffer_size:: the maximum number of bytes that can be received

Returns a string containg the response bytes.



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/smartcard/pcsc/card.rb', line 210

def control(code, data, receive_buffer_size = 4096)
  # NOTE: In general, I tried to avoid specifying receive buffer sizes. This
  #       is the only case where that is impossible to achieve, because there
  #       is no well-known maximum buffer size, and the SCardControl call is
  #       not guaranteed to be idempotent, so it's not OK to re-issue it after
  #       guessing a buffer size works out.
  send_ptr = FFI::MemoryPointer.from_string data
  recv_ptr = FFI::MemoryPointer.new receive_buffer_size
  recv_size_ptr = FFILib::WordPtr.new
  recv_size_ptr[:value] = receive_buffer_size
  begin
    status = FFILib.card_control @_handle, code, send_ptr, data.length,
                                 recv_ptr, receive_buffer_size, recv_size_ptr
    raise Smartcard::PCSC::Exception, status unless status == :success
    recv_ptr.get_bytes 0, recv_size_ptr[:value]
  ensure
    send_ptr.free
    recv_ptr.free
  end
end

#disconnect(disposition = :leave) ⇒ Object

Disconnects from the smart-card.

Future method calls on this object will raise PC/SC errors.

Args:

disposition:: what to do with the smart-card right before disconnecting;
              the possible values are +:leave+, +:reset+, +:unpower+, and
              +:eject+ (see the SCARD_*_CARD constants in the PC/SC API)


98
99
100
101
102
103
104
# File 'lib/smartcard/pcsc/card.rb', line 98

def disconnect(disposition = :leave)
  status = FFILib.card_disconnect @_handle, disposition
  raise Smartcard::PCSC::Exception, status unless status == :success

  @_handle = nil
  @protocol = nil
end

#end_transaction(disposition = :leave) ⇒ Object

Ends a transaction started with begin_transaction.

The calling application must be the owner of the previously started transaction or an error will occur.

Args:

disposition:: what to do with the smart-card after the transaction; the
              possible values are +:leave+, +:reset+, +:unpower+, and
              +:eject+ (see the SCARD_*_CARD constants in the PC/SC API)


121
122
123
124
# File 'lib/smartcard/pcsc/card.rb', line 121

def end_transaction(disposition = :leave)
  status = FFILib.end_transaction @_handle, disposition
  raise Smartcard::PCSC::Exception, status unless status == :success
end

#infoObject

Assorted information about this smart-card.

Returns a hash with the following keys:

:state:: reader/card status, as a Set of symbols; the possible values are
         +:present+, +:swallowed+, +:absent+, +:specific+, and +:powered+
         (see the SCARD_* constants in the PC/SC API)
:protocol:: the protocol established with the card
:atr:: the card's ATR bytes, wrapped in a string
:reader_names:: array of strings containing all the names of the reader
                connected to this smart-card


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
# File 'lib/smartcard/pcsc/card.rb', line 241

def info
  readers_length_ptr = FFILib::WordPtr.new
  state_ptr = FFILib::WordPtr.new
  protocol_ptr = FFILib::WordPtr.new
  atr_ptr = FFI::MemoryPointer.new FFILib::Consts::MAX_ATR_SIZE
  atr_length_ptr = FFILib::WordPtr.new
  atr_length_ptr[:value] = FFILib::Consts::MAX_ATR_SIZE

  begin
    status = FFILib.card_status @_handle, nil, readers_length_ptr, state_ptr,
        protocol_ptr, atr_ptr, atr_length_ptr
    raise Smartcard::PCSC::Exception, status unless status == :success

    readers_ptr = FFI::MemoryPointer.new :char, readers_length_ptr[:value]
    begin
      status = FFILib.card_status @_handle, readers_ptr, readers_length_ptr,
          state_ptr, protocol_ptr, atr_ptr, atr_length_ptr
      raise Smartcard::PCSC::Exception, status unless status == :success

      state_word = state_ptr[:value]
      state = Set.new
      FFILib::CardState.to_h.each do |key, mask|
        state << key if (state_word & mask) == mask && mask != 0
      end

      { :readers => Context.decode_multi_string(readers_ptr),
        :protocol => FFILib::PROTOCOLS[protocol_ptr[:value]],
        :atr => atr_ptr.get_bytes(0, atr_length_ptr[:value]),
        :state => state }
    ensure
      readers_ptr.free
    end
  ensure
    atr_ptr.free
  end
end

#reconnect(sharing_mode = :exclusive, preferred_protocols = :any, disposition = :leave) ⇒ Object

Reconnects to the smart-card, potentially using a different protocol.

Args:

sharing_mode:: whether a shared or exclusive lock will be requested on the
               reader; the possible values are +:shared+, +:exclusive+ and
               +:direct+ (see the SCARD_SHARE_ constants in the PC/SC API)
preferred_protocols:: the desired communication protocol; the possible
                      values are +:t0+, +:t1+, +:t15+, +:raw+, and +:any+
                      (see the SCARD_PROTOCOL_ constants in the PC/SC API)
disposition:: what to do with the smart-card right before disconnecting;
              the possible values are +:leave+, +:reset+, +:unpower+, and
              +:eject+ (see the SCARD_*_CARD constants in the PC/SC API)


79
80
81
82
83
84
85
86
87
88
# File 'lib/smartcard/pcsc/card.rb', line 79

def reconnect(sharing_mode = :exclusive, preferred_protocols = :any,
              disposition = :leave)
  protocol_ptr = FFILib::WordPtr.new
  status = FFILib.card_reconnect @_handle, sharing_mode,
      preferred_protocols, disposition, protocol_ptr
  raise Smartcard::PCSC::Exception, status unless status == :success

  @sharing_mode = sharing_mode
  set_protocol FFILib::Protocol[protocol_ptr[:value]]
end

#transaction(disposition = :leave) ⇒ Object

Performs a block inside a transaction, with an exclusive smart-card lock.

Args:

disposition:: what to do with the smart-card after the transaction; the
              possible values are +:leave+, +:reset+, +:unpower+, and
              +:eject+ (see the SCARD_*_CARD constants in the PC/SC API)


132
133
134
135
136
# File 'lib/smartcard/pcsc/card.rb', line 132

def transaction(disposition = :leave)
  begin_transaction
  yield
  end_transaction disposition
end

#transmit(data, receive_buffer_size = 65546) ⇒ Object

Sends an APDU to the smart card, and returns the card’s response.

Args:

send_data:: string containing the APDU bytes to be sent to the card
receive_buffer_size: the maximum number of bytes that can be received


182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/smartcard/pcsc/card.rb', line 182

def transmit(data, receive_buffer_size = 65546)
  send_ptr = FFI::MemoryPointer.from_string data
  recv_ptr = FFI::MemoryPointer.new receive_buffer_size
  recv_size_ptr = FFILib::WordPtr.new
  recv_size_ptr[:value] = receive_buffer_size
  begin
    status = FFILib.transmit @_handle, @send_pci, send_ptr, data.length,
                             nil, recv_ptr, recv_size_ptr
    raise Smartcard::PCSC::Exception, status unless status == :success
    recv_ptr.get_bytes 0, recv_size_ptr[:value]
  ensure
    send_ptr.free
    recv_ptr.free
  end
end