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.
- data/CHANGELOG +4 -0
- data/Manifest +13 -6
- data/Rakefile +6 -4
- data/lib/smartcard/gp/asn1_ber.rb +199 -0
- data/lib/smartcard/gp/cap_loader.rb +88 -0
- data/lib/smartcard/gp/des.rb +68 -0
- data/lib/smartcard/gp/gp_card_mixin.rb +364 -5
- data/lib/smartcard/iso/iso_card_mixin.rb +13 -3
- data/lib/smartcard/iso/jcop_remote_server.rb +14 -5
- data/lib/smartcard/iso/pcsc_transport.rb +8 -1
- data/lib/smartcard/pcsc.so +0 -0
- data/lib/smartcard.rb +3 -0
- data/smartcard.gemspec +8 -5
- data/test/gp/asn1_ber_test.rb +116 -0
- data/test/gp/cap_loader_test.rb +24 -0
- data/test/gp/des_test.rb +39 -0
- data/test/gp/gp_card_mixin_test.rb +194 -5
- data/test/gp/hello.apdu +1 -0
- data/test/gp/hello.cap +0 -0
- data/test/iso/iso_card_mixin_test.rb +9 -8
- data/test/iso/jcop_remote_test.rb +4 -1
- metadata +36 -13
@@ -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
|
-
#
|
22
|
-
#
|
23
|
-
|
24
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
#
|
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
|
165
|
-
#
|
166
|
-
send_message socket, :type => 0, :node => 0,
|
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
|
-
#
|
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,
|
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
|
data/lib/smartcard/pcsc.so
CHANGED
Binary file
|
data/lib/smartcard.rb
CHANGED
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.
|
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-
|
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/
|
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", "
|
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
|