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

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