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.
- 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
|