smartcard 0.4.1-x86-mswin32-60 → 0.4.3-x86-mswin32-60

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.
@@ -4,24 +4,383 @@
4
4
  # Copyright:: Copyright (C) 2009 Massachusetts Institute of Technology
5
5
  # License:: MIT
6
6
 
7
+ require 'set'
8
+
7
9
  # :nodoc: namespace
8
10
  module Smartcard::Gp
9
11
 
10
12
 
13
+ # Module intended to be mixed into transport implementations to add commands for
14
+ # talking to GlobalPlatform smart-cards.
15
+ #
16
+ # The module talks to the card exclusively via methods in
17
+ # Smartcard::Iso::IsoCardMixin, so the transport requirements are the same as
18
+ # for that module.
11
19
  module GpCardMixin
12
20
  include Smartcard::Iso::IsoCardMixin
13
21
 
14
22
  # Selects a GlobalPlatform application.
15
23
  def select_application(app_id)
16
- iso_apdu! :ins => 0xA4, :p1 => 0x04, :p2 => 0x00, :data => app_id
24
+ ber_data = iso_apdu! :ins => 0xA4, :p1 => 0x04, :p2 => 0x00, :data => app_id
25
+ app_tags = Asn1Ber.decode ber_data
26
+ app_data = {}
27
+ Asn1Ber.visit app_tags do |path, value|
28
+ case path
29
+ when [0x6F, 0xA5, 0x9F65]
30
+ app_data[:max_apdu_length] = value.inject(0) { |acc, v| (acc << 8) | v }
31
+ when [0x6F, 0x84]
32
+ app_data[:aid] = value
33
+ end
34
+ end
35
+ app_data
36
+ end
37
+
38
+ # The default application ID of the GlobalPlatform card manager.
39
+ def gp_card_manager_aid
40
+ [0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00]
41
+ end
42
+
43
+ # Issues a GlobalPlatform INITIALIZE UPDATE command.
44
+ #
45
+ # This should not be called directly. Call secure_session insteaad.
46
+ #
47
+ # Args:
48
+ # host_challenge:: 8-byte array with a unique challenge for the session
49
+ # key_version:: the key in the Security domain to be used (0 = any key)
50
+ #
51
+ # Returns a hash containing the command's parsed response. The keys are:
52
+ # :key_diversification:: key diversification data
53
+ # :key_version:: the key in the Security domain chosen to be used
54
+ # :protocol_id:: numeric ID for the secure protocol to be used
55
+ # :counter:: counter for creating session keys
56
+ # :challenge:: the card's 6-byte challenge
57
+ # :auth:: the card's 8-byte authentication value
58
+ def gp_setup_secure_channel(host_challenge, key_version = 0)
59
+ raw = iso_apdu! :cla => 0x80, :ins => 0x50, :p1 => key_version, :p2 => 0,
60
+ :data => host_challenge
61
+ response = {
62
+ :key_diversification => raw[0, 10],
63
+ :key_version => raw[10], :protocol_id => raw[11],
64
+ :counter => raw[12, 2].pack('C*').unpack('n').first,
65
+ :challenge => raw[14, 6], :auth => raw[20, 8]
66
+ }
67
+ end
68
+
69
+ # Wrapper around iso_apdu! that adds a MAC to the APDU.
70
+ def gp_signed_apdu!(apdu_data)
71
+ apdu_data = apdu_data.dup
72
+ apdu_data[:cla] = (apdu_data[:cla] || 0) | 0x04
73
+ apdu_data[:data] = (apdu_data[:data] || []) + [0, 0, 0, 0, 0, 0, 0, 0]
74
+
75
+ apdu_bytes = Smartcard::Iso::IsoCardMixin.serialize_apdu(apdu_data)[0...-9]
76
+ mac = Des.mac_retail @gp_secure_channel_keys[:cmac], apdu_bytes.pack('C*'),
77
+ @gp_secure_channel_keys[:mac_iv]
78
+ @gp_secure_channel_keys[:mac_iv] = mac
79
+
80
+ apdu_data[:data][apdu_data[:data].length - 8, 8] = mac.unpack('C*')
81
+ iso_apdu! apdu_data
82
+ end
83
+
84
+ # Issues a GlobalPlatform EXTERNAL AUTHENTICATE command.
85
+ #
86
+ # This should not be called directly. Call secure_session insteaad.
87
+ #
88
+ # Args:
89
+ # host_auth:: 8-byte host authentication value
90
+ # security:: array of desired security flags (leave empty for the default
91
+ # of no security)
92
+ #
93
+ # The return value is irrelevant. The card will fire an ISO exception if the
94
+ # authentication doesn't work out.
95
+ def gp_lock_secure_channel(host_auth, security = [])
96
+ security_level = 0
97
+ security_flags = { :command_mac => 0x01, :response_mac => 0x10,
98
+ :command_encryption => 0x02 }
99
+ security.each do |flag|
100
+ security_level |= security_flags[flag]
101
+ end
102
+ gp_signed_apdu! :cla => 0x80, :ins => 0x82, :p1 => security_level, :p2 => 0,
103
+ :data => host_auth
104
+ end
105
+
106
+ # Sets up a secure session with the current GlobalPlatform application.
107
+ #
108
+ # Args:
109
+ # keys:: hash containing 3 3DES encryption keys, identified by the following
110
+ # keys:
111
+ # :senc:: channel encryption key
112
+ # :smac:: channel MAC key
113
+ # :dek:: data encryption key
114
+ def secure_channel(keys = gp_development_keys)
115
+ host_challenge = Des.random_bytes 8
116
+ card_info = gp_setup_secure_channel host_challenge.unpack('C*')
117
+ card_counter = [card_info[:counter]].pack('n')
118
+ card_challenge = card_info[:challenge].pack('C*')
119
+
120
+ # Compute session keys.
121
+ session_keys = {}
122
+ derivation_data = "\x01\x01" + card_counter + "\x00" * 12
123
+ session_keys[:cmac] = Des.crypt keys[:smac], derivation_data
124
+ derivation_data[0, 2] = "\x01\x02"
125
+ session_keys[:rmac] = Des.crypt keys[:smac], derivation_data
126
+ derivation_data[0, 2] = "\x01\x82"
127
+ session_keys[:senc] = Des.crypt keys[:senc], derivation_data
128
+ derivation_data[0, 2] = "\x01\x81"
129
+ session_keys[:dek] = Des.crypt keys[:dek], derivation_data
130
+ session_keys[:mac_iv] = "\x00" * 8
131
+ @gp_secure_channel_keys = session_keys
132
+
133
+ # Compute authentication cryptograms.
134
+ card_auth = Des.mac_3des session_keys[:senc],
135
+ host_challenge + card_counter + card_challenge
136
+ host_auth = Des.mac_3des session_keys[:senc],
137
+ card_counter + card_challenge + host_challenge
138
+
139
+ unless card_auth == card_info[:auth].pack('C*')
140
+ raise 'Card authentication invalid'
141
+ end
142
+
143
+ gp_lock_secure_channel host_auth.unpack('C*')
144
+ end
145
+
146
+ # Secure channel keys for development GlobalPlatform cards.
147
+ #
148
+ # Most importantly, the JCOP cards and simulator work with these keys.
149
+ def gp_development_keys
150
+ key = (0x40..0x4F).to_a.pack('C*')
151
+ { :senc => key, :smac => key, :dek => key }
152
+ end
153
+
154
+ # Issues a GlobalPlatform GET STATUS command.
155
+ #
156
+ # Args:
157
+ # scope:: the information to be retrieved from the card, can be:
158
+ # :issuer_sd:: the issuer's security domain
159
+ # :apps:: applications and supplementary security domains
160
+ # :files:: executable load files
161
+ # :files_modules:: executable load files and executable modules
162
+ # query_aid:: the AID to look for (empty array to get everything)
163
+ #
164
+ # Returns an array of application information data. Each element represents an
165
+ # application, and is a hash with the following keys:
166
+ # :aid:: the application or file's AID
167
+ # :lifecycle:: the state in the application's lifecycle (symbol)
168
+ # :permissions:: a Set of the application's permissions (symbols)
169
+ # :modules:: array of modules in an executable load file, each array element
170
+ # is a hash with the key :aid which has the module's AID
171
+ def gp_get_status(scope, query_aid = [])
172
+ scope_byte = { :issuer_sd => 0x80, :apps => 0x40, :files => 0x20,
173
+ :files_modules => 0x10 }[scope]
174
+ data = Asn1Ber.encode [{:class => :application, :primitive => true,
175
+ :number => 0x0F, :value => query_aid}]
176
+ apps = []
177
+ first = true # Set to false after the first GET STATUS is issued.
178
+ loop do
179
+ raw = iso_apdu :cla => 0x80, :ins => 0xF2, :p1 => scope_byte,
180
+ :p2 => (first ? 0 : 1), :data => [0x4F, 0x00]
181
+ if raw[:status] != 0x9000 && raw[:status] != 0x6310
182
+ Smartcard::Iso::IsoCardMixin.raise_response_exception raw
183
+ end
184
+
185
+ offset = 0
186
+ loop do
187
+ break if offset >= raw[:data].length
188
+ aid_length, offset = raw[:data][offset], offset + 1
189
+ app = { :aid => raw[:data][offset, aid_length] }
190
+ offset += aid_length
191
+
192
+ if scope == :issuer_sd
193
+ lc_states = { 1 => :op_ready, 7 => :initialized, 0x0F => :secured,
194
+ 0x7F => :card_locked, 0xFF => :terminated }
195
+ lc_mask = 0xFF
196
+ else
197
+ lc_states = { 1 => :loaded, 3 => :installed, 7 => :selectable,
198
+ 0x83 => :locked, 0x87 => :locked }
199
+ lc_mask = 0x87
200
+ end
201
+ app[:lifecycle] = lc_states[raw[:data][offset] & lc_mask]
202
+
203
+ permission_bits = raw[:data][offset + 1]
204
+ app[:permissions] = Set.new()
205
+ [[1, :mandated_dap], [2, :cvm_management], [4, :card_reset],
206
+ [8, :card_terminate], [0x10, :card_lock], [0x80, :security_domain],
207
+ [0xA0, :delegate], [0xC0, :dap_verification]].each do |mask, perm|
208
+ app[:permissions] << perm if (permission_bits & mask) == mask
209
+ end
210
+ offset += 2
211
+
212
+ if scope == :files_modules
213
+ num_modules, offset = raw[:data][offset], offset + 1
214
+ app[:modules] = []
215
+ num_modules.times do
216
+ aid_length = raw[:data][offset]
217
+ app[:modules] << { :aid => raw[:data][offset + 1, aid_length] }
218
+ offset += 1 + aid_length
219
+ end
220
+ end
221
+
222
+ apps << app
223
+ end
224
+ break if raw[:status] == 0x9000
225
+ first = false # Need more GET STATUS commands.
226
+ end
227
+ apps
228
+ end
229
+
230
+ # The GlobalPlatform applications available on the card.
231
+ def applications
232
+ select_application gp_card_manager_aid
233
+ secure_channel
234
+ gp_get_status :apps
235
+
236
+ # TODO(costan): there should be a way to query the AIDs without asking the
237
+ # SD, which requires admin keys.
238
+ end
239
+
240
+ # Issues a GlobalPlatform DELETE command targeting an executable load file.
241
+ #
242
+ # Args:
243
+ # aid:: the executable load file's AID
244
+ #
245
+ # The return value is irrelevant.
246
+ def gp_delete_file(aid)
247
+ data = Asn1Ber.encode [{:class => :application, :primitive => true,
248
+ :number => 0x0F, :value => aid}]
249
+ response = iso_apdu! :cla => 0x80, :ins => 0xE4, :p1 => 0x00, :p2 => 0x80,
250
+ :data => data
251
+ delete_confirmation = response[1, response[0]]
252
+ delete_confirmation
253
+ end
254
+
255
+ # Deletes a GlobalPlatform application.
256
+ #
257
+ # Returns +false+ if the application was not found on the card, or a true
258
+ # value if the application was deleted.
259
+ def delete_application(application_aid)
260
+ select_application gp_card_manager_aid
261
+ secure_channel
262
+
263
+ files = gp_get_status :files_modules
264
+ app_file_aid = nil
265
+ files.each do |file|
266
+ next unless modules = file[:modules]
267
+ next unless modules.any? { |m| m[:aid] == application_aid }
268
+ gp_delete_file file[:aid]
269
+ app_file_aid = file[:aid]
270
+ end
271
+ app_file_aid
272
+ end
273
+
274
+ # Issues a GlobalPlatform INSTALL command that loads an application's file.
275
+ #
276
+ # The command should be followed by a LOAD command (see gp_load).
277
+ #
278
+ # Args:
279
+ # file_aid:: the AID of the file to be loaded
280
+ # sd_aid:: the AID of the security domain handling the loading
281
+ # data_hash::
282
+ # params::
283
+ # token:: load token (needed by some SDs)
284
+ #
285
+ # Returns a true value if the command returns a valid install confirmation.
286
+ def gp_install_load(file_aid, sd_aid = nil, data_hash = [], params = {},
287
+ token = [])
288
+ ber_params = []
289
+
290
+ data = [file_aid.length, file_aid, sd_aid.length, sd_aid,
291
+ Asn1Ber.encode_length(data_hash.length), data_hash,
292
+ Asn1Ber.encode_length(ber_params.length), ber_params,
293
+ Asn1Ber.encode_length(token.length), token].flatten
294
+ response = iso_apdu! :cla => 0x80, :ins => 0xE6, :p1 => 0x02, :p2 => 0x00,
295
+ :data => data
296
+ response == [0x00]
297
+ end
298
+
299
+ # Issues a GlobalPlatform INSTALL command that installs an application and
300
+ # makes it selectable.
301
+ #
302
+ # Args:
303
+ # file_aid:: the AID of the application's executable load file
304
+ # module_aid:: the AID of the application's module in the load file
305
+ # app_aid:: the application's AID (application will be selectable by it)
306
+ # privileges:: array of application privileges (e.g. :security_domain)
307
+ # params:: application install parameters
308
+ # token:: install token (needed by some SDs)
309
+ #
310
+ # Returns a true value if the command returns a valid install confirmation.
311
+ def gp_install_selectable(file_aid, module_aid, app_aid, privileges = [],
312
+ params = {}, token = [])
313
+ privilege_byte = 0
314
+ privilege_bits = { :mandated_dap => 1, :cvm_management => 2,
315
+ :card_reset => 4, :card_terminate => 8, :card_lock => 0x10,
316
+ :security_domain => 0x80, :delegate => 0xA0, :dap_verification => 0xC0 }
317
+ privileges.each { |privilege| privilege_byte |= privilege_bits[privilege] }
318
+
319
+ param_tags = [{:class => :private, :primitive => true, :number => 9,
320
+ :value => params[:app] || []}]
321
+ ber_params = Asn1Ber.encode(param_tags)
322
+
323
+ data = [file_aid.length, file_aid, module_aid.length, module_aid,
324
+ app_aid.length, app_aid, 1, privilege_byte,
325
+ Asn1Ber.encode_length(ber_params.length), ber_params,
326
+ Asn1Ber.encode_length(token.length), token].flatten
327
+ response = iso_apdu! :cla => 0x80, :ins => 0xE6, :p1 => 0x0C, :p2 => 0x00,
328
+ :data => data
329
+ response == [0x00]
330
+ end
331
+
332
+ # Issues a GlobalPlatform LOAD command.
333
+ #
334
+ # Args:
335
+ # file_data:: the file's data
336
+ # max_apdu_length:: the maximum APDU length, returned from
337
+ # select_application
338
+ #
339
+ # Returns a true value if the loading succeeds.
340
+ def gp_load_file(file_data, max_apdu_length)
341
+ data_tag = { :class => :private, :primitive => true, :number => 4,
342
+ :value => file_data }
343
+ ber_data = Asn1Ber.encode [data_tag]
344
+
345
+ max_data_length = max_apdu_length - 5
346
+ offset = 0
347
+ block_number = 0
348
+ loop do
349
+ block_length = [max_data_length, ber_data.length - offset].min
350
+ last_block = (offset + block_length >= ber_data.length)
351
+ response = iso_apdu! :cla => 0x80, :ins => 0xE8,
352
+ :p1 => (last_block ? 0x80 : 0x00),
353
+ :p2 => block_number,
354
+ :data => ber_data[offset, block_length]
355
+ offset += block_length
356
+ block_number += 1
357
+ break if last_block
358
+ end
359
+ true
17
360
  end
18
361
 
19
362
  # Installs a JavaCard applet on the JavaCard.
20
363
  #
21
- # This would be really, really nice to have. Sadly, it's a far away TBD right
22
- # now.
23
- def install_applet(cap_contents)
24
- raise "Not implemeted; it'd be nice though, right?"
364
+ # Args:
365
+ # cap_file:: path to the applet's CAP file
366
+ # package_aid:: the applet's package AID
367
+ # applet_aid:: the AID used to select the applet; if nil, the first AID
368
+ # in the CAP's Applet section is used (this works pretty well)
369
+ # install_data:: data to be passed to the applet at installation time
370
+ def install_applet(cap_file, package_aid, applet_aid = nil, install_data = [])
371
+ load_data = CapLoader.cap_load_data(cap_file)
372
+ applet_aid ||= load_data[:applets].first[:aid]
373
+
374
+ delete_application applet_aid
375
+
376
+ manager_data = select_application gp_card_manager_aid
377
+ max_apdu = manager_data[:max_apdu_length]
378
+ secure_channel
379
+
380
+ gp_install_load package_aid, gp_card_manager_aid
381
+ gp_load_file load_data[:data], max_apdu
382
+ gp_install_selectable package_aid, applet_aid, applet_aid, [],
383
+ { :app => install_data }
25
384
  end
26
385
  end # module GpCardMixin
27
386
 
@@ -28,7 +28,16 @@ module IsoCardMixin
28
28
  def iso_apdu!(apdu_data)
29
29
  response = self.iso_apdu apdu_data
30
30
  return response[:data] if response[:status] == 0x9000
31
- raise "JavaCard response has error status 0x#{'%04x' % response[:status]}"
31
+ IsoCardMixin.raise_response_exception response
32
+ end
33
+
34
+ # Raises an exception in response to an error status in an APDU.
35
+ #
36
+ # :call_seq:
37
+ # IsoCardMixin.raise_response_exception(response)
38
+ def self.raise_response_exception(response)
39
+ raise "JavaCard response has error status 0x#{'%04x' % response[:status]}" +
40
+ " - #{response[:data].map { |ch| '%02x' % ch }.join(' ')}"
32
41
  end
33
42
 
34
43
  # Performs an APDU exchange with the ISO7816 card.
@@ -47,7 +56,7 @@ module IsoCardMixin
47
56
  # Serializes an APDU for wire transmission.
48
57
  #
49
58
  # :call-seq:
50
- # transport.wire_apdu(apdu_data) -> array
59
+ # IsoCardMixin.serialize_apdu(apdu_data) -> array
51
60
  #
52
61
  # The following keys are recognized in the APDU hash:
53
62
  # cla:: the CLA byte in the APDU (optional, defaults to 0)
@@ -73,13 +82,14 @@ module IsoCardMixin
73
82
  else
74
83
  apdu << 0
75
84
  end
85
+ apdu << (apdu_data[:le] || 0)
76
86
  apdu
77
87
  end
78
88
 
79
89
  # De-serializes a ISO7816 response APDU.
80
90
  #
81
91
  # :call-seq:
82
- # transport.deserialize_response(response) -> hash
92
+ # IsoCardMixin.deserialize_response(response) -> hash
83
93
  #
84
94
  # The response contains the following keys:
85
95
  # status:: the 2-byte status code (e.g. 0x9000 is OK)
@@ -42,6 +42,13 @@ module JcopRemoteServingStubs
42
42
  # Dumb implementation that always returns OK.
43
43
  [0x90, 0x00]
44
44
  end
45
+
46
+ # The smartcard's APDU.
47
+ def card_apdu
48
+ # ATR from the card simulator in JCOP 3.2.7.
49
+ [0x3B, 0xF8, 0x13, 0x00, 0x00, 0x81, 0x31, 0xFE, 0x45, 0x4A, 0x43, 0x4F,
50
+ 0x50, 0x76, 0x32, 0x34, 0x31, 0xB7].pack('C*')
51
+ end
45
52
  end # module JcopRemoteServingStubs
46
53
 
47
54
 
@@ -161,15 +168,17 @@ class JcopRemoteServer
161
168
 
162
169
  case request[:type]
163
170
  when 0
164
- # Wait for card; no-op, because that should have happen when the client
165
- # connected.
166
- send_message socket, :type => 0, :node => 0, :data => [3, 1, 4, 1, 5, 9]
171
+ # Wait for card (no-op, happened when the client connected) and return
172
+ # card ATR.
173
+ send_message socket, :type => 0, :node => 0,
174
+ :data => @logic.card_atr.unpack('C*')
167
175
  when 1
168
- # ATR exchange; the class' bread and butter
176
+ # APDU exchange; the class' bread and butter
169
177
  response = @logic.exchange_apdu request[:data]
170
178
  send_message socket, :type => 1, :node => 0, :data => response
171
179
  else
172
- send_message socket, :type => request[:type], :node => 0, :data => []
180
+ send_message socket, :type => request[:type], :node => 0,
181
+ :data => @logic.card_atr.unpack('C*')
173
182
  end
174
183
  end
175
184
  private :process_request
@@ -18,7 +18,8 @@ class PcscTransport
18
18
  def initialize(options)
19
19
  @options = options
20
20
  @context = nil
21
- @card = nil
21
+ @card = nil
22
+ @atr = nil
22
23
  end
23
24
 
24
25
  def exchange_apdu(apdu)
@@ -27,6 +28,10 @@ class PcscTransport
27
28
  return result_string.unpack('C*')
28
29
  end
29
30
 
31
+ def card_atr
32
+ @atr
33
+ end
34
+
30
35
  def connect
31
36
  @context = PCSC::Context.new(PCSC::SCOPE_SYSTEM) if @context.nil?
32
37
 
@@ -62,6 +67,7 @@ class PcscTransport
62
67
  # build the transmit / receive IoRequests
63
68
  status = @card.status
64
69
  @xmit_ioreq = @@xmit_iorequest[status[:protocol]]
70
+ @atr = status[:atr]
65
71
  if RUBY_PLATFORM =~ /win/ and (not RUBY_PLATFORM =~ /darwin/)
66
72
  @recv_ioreq = nil
67
73
  else
@@ -73,6 +79,7 @@ class PcscTransport
73
79
  unless @card.nil?
74
80
  @card.disconnect PCSC::DISPOSITION_LEAVE
75
81
  @card = nil
82
+ @atr = nil
76
83
  end
77
84
  unless @context.nil?
78
85
  @context.release
Binary file
data/lib/smartcard.rb CHANGED
@@ -12,4 +12,7 @@ require 'smartcard/iso/transport.rb'
12
12
  require 'smartcard/iso/auto_configurator.rb'
13
13
 
14
14
 
15
+ require 'smartcard/gp/asn1_ber.rb'
16
+ require 'smartcard/gp/cap_loader.rb'
17
+ require 'smartcard/gp/des.rb'
15
18
  require 'smartcard/gp/gp_card_mixin.rb'
data/smartcard.gemspec CHANGED
@@ -2,31 +2,34 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{smartcard}
5
- s.version = "0.4.1"
5
+ s.version = "0.4.3"
6
6
  s.platform = %q{x86-mswin32-60}
7
7
 
8
8
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
9
9
  s.authors = ["Victor Costan"]
10
- s.date = %q{2009-08-19}
10
+ s.date = %q{2009-11-01}
11
11
  s.description = %q{Interface with ISO 7816 smart cards.}
12
12
  s.email = %q{victor@costan.us}
13
- s.extra_rdoc_files = ["BUILD", "CHANGELOG", "ext/smartcard_pcsc/extconf.rb", "ext/smartcard_pcsc/pcsc.h", "ext/smartcard_pcsc/pcsc_card.c", "ext/smartcard_pcsc/pcsc_constants.c", "ext/smartcard_pcsc/pcsc_context.c", "ext/smartcard_pcsc/pcsc_exception.c", "ext/smartcard_pcsc/pcsc_io_request.c", "ext/smartcard_pcsc/pcsc_main.c", "ext/smartcard_pcsc/pcsc_multi_strings.c", "ext/smartcard_pcsc/pcsc_namespace.c", "ext/smartcard_pcsc/pcsc_reader_states.c", "ext/smartcard_pcsc/pcsc_surrogate_reader.h", "ext/smartcard_pcsc/pcsc_surrogate_wintypes.h", "lib/smartcard/gp/gp_card_mixin.rb", "lib/smartcard/iso/auto_configurator.rb", "lib/smartcard/iso/iso_card_mixin.rb", "lib/smartcard/iso/jcop_remote_protocol.rb", "lib/smartcard/iso/jcop_remote_server.rb", "lib/smartcard/iso/jcop_remote_transport.rb", "lib/smartcard/iso/pcsc_transport.rb", "lib/smartcard/iso/transport.rb", "lib/smartcard/pcsc/pcsc_exception.rb", "lib/smartcard.rb", "LICENSE", "README"]
14
- s.files = ["BUILD", "CHANGELOG", "ext/smartcard_pcsc/extconf.rb", "ext/smartcard_pcsc/pcsc.h", "ext/smartcard_pcsc/pcsc_card.c", "ext/smartcard_pcsc/pcsc_constants.c", "ext/smartcard_pcsc/pcsc_context.c", "ext/smartcard_pcsc/pcsc_exception.c", "ext/smartcard_pcsc/pcsc_io_request.c", "ext/smartcard_pcsc/pcsc_main.c", "ext/smartcard_pcsc/pcsc_multi_strings.c", "ext/smartcard_pcsc/pcsc_namespace.c", "ext/smartcard_pcsc/pcsc_reader_states.c", "ext/smartcard_pcsc/pcsc_surrogate_reader.h", "ext/smartcard_pcsc/pcsc_surrogate_wintypes.h", "lib/smartcard/gp/gp_card_mixin.rb", "lib/smartcard/iso/auto_configurator.rb", "lib/smartcard/iso/iso_card_mixin.rb", "lib/smartcard/iso/jcop_remote_protocol.rb", "lib/smartcard/iso/jcop_remote_server.rb", "lib/smartcard/iso/jcop_remote_transport.rb", "lib/smartcard/iso/pcsc_transport.rb", "lib/smartcard/iso/transport.rb", "lib/smartcard/pcsc/pcsc_exception.rb", "lib/smartcard.rb", "LICENSE", "Manifest", "Rakefile", "README", "smartcard.gemspec", "test/gp/gp_card_mixin_test.rb", "test/iso/auto_configurator_test.rb", "test/iso/iso_card_mixin_test.rb", "test/iso/jcop_remote_test.rb", "test/pcsc/containers_test.rb", "test/pcsc/smoke_test.rb", "tests/ts_pcsc_ext.rb", "lib/smartcard/pcsc.so"]
13
+ s.extra_rdoc_files = ["BUILD", "CHANGELOG", "LICENSE", "README", "ext/smartcard_pcsc/extconf.rb", "ext/smartcard_pcsc/pcsc.h", "ext/smartcard_pcsc/pcsc_card.c", "ext/smartcard_pcsc/pcsc_constants.c", "ext/smartcard_pcsc/pcsc_context.c", "ext/smartcard_pcsc/pcsc_exception.c", "ext/smartcard_pcsc/pcsc_io_request.c", "ext/smartcard_pcsc/pcsc_main.c", "ext/smartcard_pcsc/pcsc_multi_strings.c", "ext/smartcard_pcsc/pcsc_namespace.c", "ext/smartcard_pcsc/pcsc_reader_states.c", "ext/smartcard_pcsc/pcsc_surrogate_reader.h", "ext/smartcard_pcsc/pcsc_surrogate_wintypes.h", "lib/smartcard.rb", "lib/smartcard/gp/asn1_ber.rb", "lib/smartcard/gp/cap_loader.rb", "lib/smartcard/gp/des.rb", "lib/smartcard/gp/gp_card_mixin.rb", "lib/smartcard/iso/auto_configurator.rb", "lib/smartcard/iso/iso_card_mixin.rb", "lib/smartcard/iso/jcop_remote_protocol.rb", "lib/smartcard/iso/jcop_remote_server.rb", "lib/smartcard/iso/jcop_remote_transport.rb", "lib/smartcard/iso/pcsc_transport.rb", "lib/smartcard/iso/transport.rb", "lib/smartcard/pcsc/pcsc_exception.rb"]
14
+ s.files = ["BUILD", "CHANGELOG", "LICENSE", "Manifest", "README", "Rakefile", "ext/smartcard_pcsc/extconf.rb", "ext/smartcard_pcsc/pcsc.h", "ext/smartcard_pcsc/pcsc_card.c", "ext/smartcard_pcsc/pcsc_constants.c", "ext/smartcard_pcsc/pcsc_context.c", "ext/smartcard_pcsc/pcsc_exception.c", "ext/smartcard_pcsc/pcsc_io_request.c", "ext/smartcard_pcsc/pcsc_main.c", "ext/smartcard_pcsc/pcsc_multi_strings.c", "ext/smartcard_pcsc/pcsc_namespace.c", "ext/smartcard_pcsc/pcsc_reader_states.c", "ext/smartcard_pcsc/pcsc_surrogate_reader.h", "ext/smartcard_pcsc/pcsc_surrogate_wintypes.h", "lib/smartcard.rb", "lib/smartcard/gp/asn1_ber.rb", "lib/smartcard/gp/cap_loader.rb", "lib/smartcard/gp/des.rb", "lib/smartcard/gp/gp_card_mixin.rb", "lib/smartcard/iso/auto_configurator.rb", "lib/smartcard/iso/iso_card_mixin.rb", "lib/smartcard/iso/jcop_remote_protocol.rb", "lib/smartcard/iso/jcop_remote_server.rb", "lib/smartcard/iso/jcop_remote_transport.rb", "lib/smartcard/iso/pcsc_transport.rb", "lib/smartcard/iso/transport.rb", "lib/smartcard/pcsc/pcsc_exception.rb", "test/gp/asn1_ber_test.rb", "test/gp/cap_loader_test.rb", "test/gp/des_test.rb", "test/gp/gp_card_mixin_test.rb", "test/gp/hello.apdu", "test/gp/hello.cap", "test/iso/auto_configurator_test.rb", "test/iso/iso_card_mixin_test.rb", "test/iso/jcop_remote_test.rb", "test/pcsc/containers_test.rb", "test/pcsc/smoke_test.rb", "tests/ts_pcsc_ext.rb", "smartcard.gemspec", "lib/smartcard/pcsc.so"]
15
15
  s.homepage = %q{http://www.costan.us/smartcard}
16
16
  s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Smartcard", "--main", "README"]
17
17
  s.require_paths = ["lib", "ext"]
18
18
  s.rubyforge_project = %q{smartcard}
19
19
  s.rubygems_version = %q{1.3.5}
20
20
  s.summary = %q{Interface with ISO 7816 smart cards.}
21
- s.test_files = ["test/gp/gp_card_mixin_test.rb", "test/iso/auto_configurator_test.rb", "test/iso/iso_card_mixin_test.rb", "test/iso/jcop_remote_test.rb", "test/pcsc/containers_test.rb", "test/pcsc/smoke_test.rb"]
21
+ s.test_files = ["test/gp/asn1_ber_test.rb", "test/gp/cap_loader_test.rb", "test/gp/des_test.rb", "test/gp/gp_card_mixin_test.rb", "test/iso/auto_configurator_test.rb", "test/iso/iso_card_mixin_test.rb", "test/iso/jcop_remote_test.rb", "test/pcsc/containers_test.rb", "test/pcsc/smoke_test.rb"]
22
22
 
23
23
  if s.respond_to? :specification_version then
24
24
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
25
25
  s.specification_version = 3
26
26
 
27
27
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
28
+ s.add_runtime_dependency(%q<rubyzip>, [">= 0.9.1"])
28
29
  else
30
+ s.add_dependency(%q<rubyzip>, [">= 0.9.1"])
29
31
  end
30
32
  else
33
+ s.add_dependency(%q<rubyzip>, [">= 0.9.1"])
31
34
  end
32
35
  end