smartcard 0.4.11 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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