smartcard 0.4.11 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGELOG CHANGED
@@ -1,3 +1,5 @@
1
+ v0.5.0. Dropped C extension in favor of FFI. Hello, jruby and rbx!
2
+
1
3
  v0.4.11. Support for GlobalPlatform's SC01 (Secure Channel 1) protocol.
2
4
 
3
5
  v0.4.10. Removed hardcoded card manager AID.
data/Manifest CHANGED
@@ -4,19 +4,6 @@ LICENSE
4
4
  Manifest
5
5
  README
6
6
  Rakefile
7
- ext/smartcard_pcsc/extconf.rb
8
- ext/smartcard_pcsc/pcsc.h
9
- ext/smartcard_pcsc/pcsc_card.c
10
- ext/smartcard_pcsc/pcsc_constants.c
11
- ext/smartcard_pcsc/pcsc_context.c
12
- ext/smartcard_pcsc/pcsc_exception.c
13
- ext/smartcard_pcsc/pcsc_io_request.c
14
- ext/smartcard_pcsc/pcsc_main.c
15
- ext/smartcard_pcsc/pcsc_multi_strings.c
16
- ext/smartcard_pcsc/pcsc_namespace.c
17
- ext/smartcard_pcsc/pcsc_reader_states.c
18
- ext/smartcard_pcsc/pcsc_surrogate_reader.h
19
- ext/smartcard_pcsc/pcsc_surrogate_wintypes.h
20
7
  lib/smartcard.rb
21
8
  lib/smartcard/gp/asn1_ber.rb
22
9
  lib/smartcard/gp/cap_loader.rb
@@ -30,7 +17,15 @@ lib/smartcard/iso/jcop_remote_server.rb
30
17
  lib/smartcard/iso/jcop_remote_transport.rb
31
18
  lib/smartcard/iso/pcsc_transport.rb
32
19
  lib/smartcard/iso/transport.rb
20
+ lib/smartcard/pcsc/card.rb
21
+ lib/smartcard/pcsc/context.rb
22
+ lib/smartcard/pcsc/ffi_autogen.rb
23
+ lib/smartcard/pcsc/ffi_functions.rb
24
+ lib/smartcard/pcsc/ffi_lib.rb
25
+ lib/smartcard/pcsc/ffi_structs.rb
33
26
  lib/smartcard/pcsc/pcsc_exception.rb
27
+ lib/smartcard/pcsc/reader_state_queries.rb
28
+ tasks/ffi_codegen.rb
34
29
  test/gp/asn1_ber_test.rb
35
30
  test/gp/cap_loader_test.rb
36
31
  test/gp/des_test.rb
@@ -42,6 +37,6 @@ test/iso/auto_configurator_test.rb
42
37
  test/iso/iso_card_mixin_test.rb
43
38
  test/iso/iso_exception_test.rb
44
39
  test/iso/jcop_remote_test.rb
45
- test/pcsc/containers_test.rb
46
- test/pcsc/smoke_test.rb
47
- tests/ts_pcsc_ext.rb
40
+ test/pcsc/card_test.rb
41
+ test/pcsc/context_test.rb
42
+ test/pcsc/reader_state_queries_test.rb
data/Rakefile CHANGED
@@ -1,7 +1,8 @@
1
1
  require 'rubygems'
2
- gem 'echoe'
3
2
  require 'echoe'
4
3
 
4
+ require 'tasks/ffi_codegen.rb'
5
+
5
6
  Echoe.new('smartcard') do |p|
6
7
  p.project = 'smartcard' # rubyforge project
7
8
 
@@ -9,7 +10,8 @@ Echoe.new('smartcard') do |p|
9
10
  p.email = 'victor@costan.us'
10
11
  p.summary = 'Interface with ISO 7816 smart cards.'
11
12
  p.url = 'http://www.costan.us/smartcard'
12
- p.dependencies = ['rubyzip >=0.9.1',
13
+ p.dependencies = ['ffi >=0.5.3',
14
+ 'rubyzip >=0.9.1',
13
15
  'zerg_support >=0.1.5']
14
16
  p.development_dependencies = ['echoe >=3.2',
15
17
  'flexmock >=0.8.6']
@@ -18,21 +20,12 @@ Echoe.new('smartcard') do |p|
18
20
  p.need_zip = !Gem.win_platform?
19
21
  p.clean_pattern += ['ext/**/*.manifest', 'ext/**/*_autogen.h']
20
22
  p.rdoc_pattern =
21
- /^(lib|bin|tasks|ext)|^BUILD|^README|^CHANGELOG|^TODO|^LICENSE|^COPYING$/
22
-
23
- p.eval = proc do |p|
24
- if Gem.win_platform?
25
- p.files += ['lib/smartcard/pcsc.so']
26
- p.platform = Gem::Platform::CURRENT
27
-
28
- # take out the extension info from the gemspec
29
- task :postcompile_hacks => [:compile] do
30
- p.extensions.clear
31
- end
23
+ /^(lib|bin|tasks|ext)|^BUILD|^README|^CHANGELOG|^TODO|^LICENSE|^COPYING$/
24
+ end
32
25
 
33
- task :package => [ :clean, :compile, :postcompile_hacks ]
34
- end
35
- end
26
+ unless FFI::Platform.windows?
27
+ task :package => :ffi_header
28
+ task :test => :ffi_header
36
29
  end
37
30
 
38
31
  if $0 == __FILE__
data/lib/smartcard.rb CHANGED
@@ -1,6 +1,15 @@
1
- require 'smartcard/pcsc'
2
- require 'smartcard/pcsc/pcsc_exception.rb'
1
+ # :nodoc: namespace
2
+ module Smartcard
3
+ end
3
4
 
5
+ require 'smartcard/pcsc/card.rb'
6
+ require 'smartcard/pcsc/context.rb'
7
+ require 'smartcard/pcsc/ffi_lib.rb'
8
+ require 'smartcard/pcsc/ffi_autogen.rb'
9
+ require 'smartcard/pcsc/ffi_structs.rb'
10
+ require 'smartcard/pcsc/ffi_functions.rb'
11
+ require 'smartcard/pcsc/pcsc_exception.rb'
12
+ require 'smartcard/pcsc/reader_state_queries.rb'
4
13
 
5
14
  require 'smartcard/iso/apdu_error.rb'
6
15
  require 'smartcard/iso/iso_card_mixin.rb'
@@ -24,7 +24,7 @@ class PcscTransport
24
24
 
25
25
  def exchange_apdu(apdu)
26
26
  xmit_apdu_string = apdu.pack('C*')
27
- result_string = @card.transmit xmit_apdu_string, @xmit_ioreq, @recv_ioreq
27
+ result_string = @card.transmit xmit_apdu_string
28
28
  return result_string.unpack('C*')
29
29
  end
30
30
 
@@ -33,51 +33,41 @@ class PcscTransport
33
33
  end
34
34
 
35
35
  def connect
36
- @context = PCSC::Context.new(PCSC::SCOPE_SYSTEM) if @context.nil?
36
+ @context = PCSC::Context.new if @context.nil?
37
37
 
38
38
  if @options[:reader_name]
39
39
  @reader_name = @options[:reader_name]
40
40
  else
41
- # get the first reader
42
- readers = @context.list_readers nil
41
+ # Get the first reader.
42
+ readers = @context.readers
43
43
  @reader_name = readers[@options[:reader_index] || 0]
44
44
  end
45
45
 
46
- # get the reader's status
47
- reader_states = PCSC::ReaderStates.new(1)
48
- reader_states.set_reader_name_of!(0, @reader_name)
49
- reader_states.set_current_state_of!(0, PCSC::STATE_UNKNOWN)
50
- @context.get_status_change reader_states, 100
51
- reader_states.acknowledge_events!
46
+ # Query the reader's status.
47
+ queries = PCSC::ReaderStateQueries.new 1
48
+ queries[0].reader_name = @reader_name
49
+ queries[0].current_state = :unknown
50
+ @context.wait_for_status_change queries, 100
51
+ queries.ack_changes
52
52
 
53
- # prompt for card insertion unless that already happened
54
- if (reader_states.current_state_of(0) & PCSC::STATE_PRESENT) == 0
55
- puts "Please insert TEM card in reader #{@reader_name}\n"
56
- while (reader_states.current_state_of(0) & PCSC::STATE_PRESENT) == 0 do
57
- @context.get_status_change reader_states, PCSC::INFINITE_TIMEOUT
58
- reader_states.acknowledge_events!
53
+ # Prompt for card insertion unless that already happened.
54
+ unless queries[0].current_state.include? :present
55
+ puts "Please insert smart-card card in reader #{@reader_name}\n"
56
+ until queries[0].current_state.include? :presentt do
57
+ @context.wait_for_status_change queries
58
+ queries.ack_changes
59
59
  end
60
60
  puts "Card detected\n"
61
61
  end
62
62
 
63
- # connect to card
64
- @card = PCSC::Card.new @context, @reader_name, PCSC::SHARE_EXCLUSIVE,
65
- PCSC::PROTOCOL_ANY
66
-
67
- # build the transmit / receive IoRequests
68
- status = @card.status
69
- @xmit_ioreq = @@xmit_iorequest[status[:protocol]]
70
- @atr = status[:atr]
71
- if RUBY_PLATFORM =~ /win/ and (not RUBY_PLATFORM =~ /darwin/)
72
- @recv_ioreq = nil
73
- else
74
- @recv_ioreq = PCSC::IoRequest.new
75
- end
63
+ # Connect to the card.
64
+ @card = @context.card @reader_name, :shared
65
+ @atr = @card.info[:atr]
76
66
  end
77
67
 
78
68
  def disconnect
79
69
  unless @card.nil?
80
- @card.disconnect PCSC::DISPOSITION_LEAVE
70
+ @card.disconnect
81
71
  @card = nil
82
72
  @atr = nil
83
73
  end
@@ -90,12 +80,7 @@ class PcscTransport
90
80
  def to_s
91
81
  "#<PC/SC Terminal: disconnected>" if @card.nil?
92
82
  "#<PC/SC Terminal: #{@reader_name}>"
93
- end
94
-
95
- @@xmit_iorequest = {
96
- Smartcard::PCSC::PROTOCOL_T0 => Smartcard::PCSC::IOREQUEST_T0,
97
- Smartcard::PCSC::PROTOCOL_T1 => Smartcard::PCSC::IOREQUEST_T1,
98
- }
83
+ end
99
84
  end # class PcscTransport
100
85
 
101
- end # module Smartcard::Iso
86
+ end # module Smartcard::Iso
@@ -0,0 +1,281 @@
1
+ # Connects Ruby to a smart-card in a PC/SC reader (wraps SCARDHANDLE).
2
+ #
3
+ # Author:: Victor Costan
4
+ # Copyright:: Copyright (C) 2009 Massachusetts Institute of Technology
5
+ # License:: MIT
6
+
7
+ require 'set'
8
+
9
+ # :nodoc: namespace
10
+ module Smartcard::PCSC
11
+
12
+
13
+ # Connects a smart-card in a PC/SC reader to the Ruby world.
14
+ class Card
15
+ # Establishes a connection to the card in a PC/SC reader.
16
+ #
17
+ # The first connection will power up the card and perform a reset on it.
18
+ #
19
+ # Args:
20
+ # context:: the Smartcard::PCSC::Context for the PC/SC resource manager
21
+ # reader_name:: friendly name of the reader to connect to; reader names can
22
+ # be obtained from Smartcard::PCSC::Context#readers
23
+ # sharing_mode:: whether a shared or exclusive lock will be requested on the
24
+ # reader; the possible values are +:shared+, +:exclusive+ and
25
+ # +:direct+ (see the SCARD_SHARE_ constants in the PC/SC API)
26
+ # preferred_protocols:: the desired communication protocol; the possible
27
+ # values are +:t0+, +:t1+, +:t15+, +:raw+, and +:any+
28
+ # (see the SCARD_PROTOCOL_ constants in the PC/SC API)
29
+ def initialize(context, reader_name, sharing_mode = :exclusive,
30
+ preferred_protocols = :any)
31
+ handle_ptr = FFILib::WordPtr.new
32
+ protocol_ptr = FFILib::WordPtr.new
33
+ status = FFILib.card_connect context._handle, reader_name, sharing_mode,
34
+ preferred_protocols, handle_ptr, protocol_ptr
35
+ raise Smartcard::PCSC::Exception, status unless status == :success
36
+
37
+ @context = context
38
+ set_protocol FFILib::Protocol[protocol_ptr[:value]]
39
+ @_handle = handle_ptr[:value]
40
+ end
41
+
42
+ # Updates internal buffers to reflect a change in the communication protocol.
43
+ def set_protocol(protocol)
44
+ @protocol = protocol
45
+
46
+ case protocol
47
+ when :t0
48
+ @send_pci = @recv_pci = FFILib::PCI_T0
49
+ when :t1
50
+ @send_pci = @recv_pci = FFILib::PCI_T1
51
+ when :raw
52
+ @send_pci = @recv_pci = FFILib::PCI_RAW
53
+ end
54
+
55
+ # Windows really doesn't like a receiving IoRequest.
56
+ if FFI::Platform.windows? || FFI::Platform.mac?
57
+ @recv_pci = nil
58
+ end
59
+ end
60
+ private :set_protocol
61
+
62
+ # Reconnects to the smart-card, potentially using a different protocol.
63
+ #
64
+ # Args:
65
+ # sharing_mode:: whether a shared or exclusive lock will be requested on the
66
+ # reader; the possible values are +:shared+, +:exclusive+ and
67
+ # +:direct+ (see the SCARD_SHARE_ constants in the PC/SC API)
68
+ # preferred_protocols:: the desired communication protocol; the possible
69
+ # values are +:t0+, +:t1+, +:t15+, +:raw+, and +:any+
70
+ # (see the SCARD_PROTOCOL_ constants in the PC/SC API)
71
+ # disposition:: what to do with the smart-card right before disconnecting;
72
+ # the possible values are +:leave+, +:reset+, +:unpower+, and
73
+ # +:eject+ (see the SCARD_*_CARD constants in the PC/SC API)
74
+ def reconnect(sharing_mode = :exclusive, preferred_protocols = :any,
75
+ disposition = :leave)
76
+ protocol_ptr = FFILib::WordPtr.new
77
+ status = FFILib.card_reconnect @_handle, sharing_mode,
78
+ preferred_protocols, disposition, protocol_ptr
79
+ raise Smartcard::PCSC::Exception, status unless status == :success
80
+
81
+ set_protocol FFILib::Protocol[protocol_ptr[:value]]
82
+ end
83
+
84
+ # Disconnects from the smart-card.
85
+ #
86
+ # Future method calls on this object will raise PC/SC errors.
87
+ #
88
+ # Args:
89
+ # disposition:: what to do with the smart-card right before disconnecting;
90
+ # the possible values are +:leave+, +:reset+, +:unpower+, and
91
+ # +:eject+ (see the SCARD_*_CARD constants in the PC/SC API)
92
+ def disconnect(disposition = :leave)
93
+ status = FFILib.card_disconnect @_handle, disposition
94
+ raise Smartcard::PCSC::Exception, status unless status == :success
95
+
96
+ @_handle = nil
97
+ @protocol = nil
98
+ end
99
+
100
+ # Starts a transaction, obtaining an exclusive lock on the smart-card.
101
+ def begin_transaction
102
+ status = FFILib.begin_transaction @_handle
103
+ raise Smartcard::PCSC::Exception, status unless status == :success
104
+ end
105
+
106
+ # Ends a transaction started with begin_transaction.
107
+ #
108
+ # The calling application must be the owner of the previously started
109
+ # transaction or an error will occur.
110
+ #
111
+ # Args:
112
+ # disposition:: what to do with the smart-card after the transaction; the
113
+ # possible values are +:leave+, +:reset+, +:unpower+, and
114
+ # +:eject+ (see the SCARD_*_CARD constants in the PC/SC API)
115
+ def end_transaction(disposition = :leave)
116
+ status = FFILib.end_transaction @_handle, disposition
117
+ raise Smartcard::PCSC::Exception, status unless status == :success
118
+ end
119
+
120
+ # Performs a block inside a transaction, with an exclusive smart-card lock.
121
+ #
122
+ # Args:
123
+ # disposition:: what to do with the smart-card after the transaction; the
124
+ # possible values are +:leave+, +:reset+, +:unpower+, and
125
+ # +:eject+ (see the SCARD_*_CARD constants in the PC/SC API)
126
+ def transaction(disposition = :leave)
127
+ begin_transaction
128
+ yield
129
+ end_transaction disposition
130
+ end
131
+
132
+
133
+ def [](attribute_name)
134
+ length_ptr = FFILib::WordPtr.new
135
+ status = FFILib.get_attrib @_handle, attribute_name, nil, length_ptr
136
+ raise Smartcard::PCSC::Exception, status unless status == :success
137
+
138
+ value_ptr = FFI::MemoryPointer.new :char, length_ptr[:value]
139
+ begin
140
+ status = FFILib.get_attrib @_handle, attribute_name, value_ptr,
141
+ length_ptr
142
+ raise Smartcard::PCSC::Exception, status unless status == :success
143
+
144
+ value_ptr.get_bytes 0, length_ptr[:value]
145
+ ensure
146
+ value_ptr.free
147
+ end
148
+ end
149
+
150
+ # Sets the value of an attribute in the interface driver.
151
+ #
152
+ # The interface driver may not implement all possible attributes.
153
+ #
154
+ # Args:
155
+ # attribute_name:: the attribute to be set; possible values are the members
156
+ # of Smartcard::PCSC::FFILib::Attribute, for example
157
+ # +:vendor_name+)
158
+ # value:: string containing the value bytes to be assigned to the attribute
159
+ def []=(attribute_name, value)
160
+ value_ptr = FFI::MemoryPointer.from_string value
161
+ begin
162
+ status = FFILib.set_attrib @_handle, attribute_name, value_ptr,
163
+ value.length
164
+ raise Smartcard::PCSC::Exception, status unless status == :success
165
+ value
166
+ ensure
167
+ value_ptr.free
168
+ end
169
+ end
170
+
171
+ # Sends an APDU to the smart card, and returns the card's response.
172
+ #
173
+ # Args:
174
+ # send_data:: string containing the APDU bytes to be sent to the card
175
+ # receive_buffer_size: the maximum number of bytes that can be received
176
+ def transmit(data, receive_buffer_size = 65546)
177
+ send_ptr = FFI::MemoryPointer.from_string data
178
+ recv_ptr = FFI::MemoryPointer.new receive_buffer_size
179
+ recv_size_ptr = FFILib::WordPtr.new
180
+ recv_size_ptr[:value] = receive_buffer_size
181
+ begin
182
+ status = FFILib.transmit @_handle, @send_pci, send_ptr, data.length,
183
+ @recv_pci, recv_ptr, recv_size_ptr
184
+ raise Smartcard::PCSC::Exception, status unless status == :success
185
+ recv_ptr.get_bytes 0, recv_size_ptr[:value]
186
+ ensure
187
+ send_ptr.free
188
+ recv_ptr.free
189
+ end
190
+ end
191
+
192
+ # Sends a interface driver command for the smart-card reader.
193
+ #
194
+ # This method is useful for creating client side reader drivers for functions
195
+ # like PIN pads, biometrics, or other smart card reader extensions that are
196
+ # not normally handled by the PC/SC API.
197
+ #
198
+ # Args:
199
+ # code:: control code for the operation; a driver-specific integer
200
+ # data:: string containing the data bytes to be sent to the driver
201
+ # receive_buffer_size:: the maximum number of bytes that can be received
202
+ #
203
+ # Returns a string containg the response bytes.
204
+ def control(code, data, receive_buffer_size = 4096)
205
+ # NOTE: In general, I tried to avoid specifying receive buffer sizes. This
206
+ # is the only case where that is impossible to achieve, because there
207
+ # is no well-known maximum buffer size, and the SCardControl call is
208
+ # not guaranteed to be idempotent, so it's not OK to re-issue it after
209
+ # guessing a buffer size works out.
210
+ send_ptr = FFI::MemoryPointer.from_string data
211
+ recv_ptr = FFI::MemoryPointer.new receive_buffer_size
212
+ recv_size_ptr = FFILib::WordPtr.new
213
+ recv_size_ptr[:value] = receive_buffer_size
214
+ begin
215
+ status = FFILib.card_control @_handle, code, send_ptr, data.length,
216
+ recv_ptr, receive_buffer_size, recv_size_ptr
217
+ raise Smartcard::PCSC::Exception, status unless status == :success
218
+ recv_ptr.get_bytes 0, recv_size_ptr[:value]
219
+ ensure
220
+ send_ptr.free
221
+ recv_ptr.free
222
+ end
223
+ end
224
+
225
+ # Assorted information about this smart-card.
226
+ #
227
+ # Returns a hash with the following keys:
228
+ # :state:: reader/card status, as a Set of symbols; the possible values are
229
+ # +:present+, +:swallowed+, +:absent+, +:specific+, and +:powered+
230
+ # (see the SCARD_* constants in the PC/SC API)
231
+ # :protocol:: the protocol established with the card
232
+ # :atr:: the card's ATR bytes, wrapped in a string
233
+ # :reader_names:: array of strings containing all the names of the reader
234
+ # connected to this smart-card
235
+ def info
236
+ readers_length_ptr = FFILib::WordPtr.new
237
+ state_ptr = FFILib::WordPtr.new
238
+ protocol_ptr = FFILib::WordPtr.new
239
+ atr_ptr = FFI::MemoryPointer.new FFILib::Consts::MAX_ATR_SIZE
240
+ atr_length_ptr = FFILib::WordPtr.new
241
+ atr_length_ptr[:value] = FFILib::Consts::MAX_ATR_SIZE
242
+
243
+ begin
244
+ status = FFILib.card_status @_handle, nil, readers_length_ptr, state_ptr,
245
+ protocol_ptr, atr_ptr, atr_length_ptr
246
+ raise Smartcard::PCSC::Exception, status unless status == :success
247
+
248
+ readers_ptr = FFI::MemoryPointer.new :char, readers_length_ptr[:value]
249
+ begin
250
+ status = FFILib.card_status @_handle, readers_ptr, readers_length_ptr,
251
+ state_ptr, protocol_ptr, atr_ptr, atr_length_ptr
252
+ raise Smartcard::PCSC::Exception, status unless status == :success
253
+
254
+ state_word = state_ptr[:value]
255
+ state = Set.new
256
+ FFILib::CardState.to_h.each do |key, mask|
257
+ state << key if (state_word & mask) == mask && mask != 0
258
+ end
259
+
260
+ { :readers => Context.decode_multi_string(readers_ptr),
261
+ :protocol => FFILib::Protocol[protocol_ptr[:value]],
262
+ :atr => atr_ptr.get_bytes(0, atr_length_ptr[:value]),
263
+ :state => state }
264
+ ensure
265
+ readers_ptr.free
266
+ end
267
+ ensure
268
+ atr_ptr.free
269
+ end
270
+ end
271
+
272
+ # The low-level _SCARDHANDLE_ data.
273
+ #
274
+ # This should not be used by client code.
275
+ attr_reader :_handle
276
+
277
+ # The communication protocol in use with this smart-card.
278
+ attr_reader :protocol
279
+ end # class Smartcard::PCSC::Card
280
+
281
+ end # namespace Smartcard::PCSC