ctapi 0.2.2 → 0.2.3
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/CHANGES +9 -4
- data/{GPL → COPYING} +7 -7
- data/README +49 -0
- data/Rakefile +76 -57
- data/VERSION +1 -1
- data/{examples → bin}/cardinfo.rb +2 -2
- data/bin/ctsh.rb +21 -0
- data/ctapi.gemspec +31 -0
- data/doc-main.txt +39 -0
- data/ext/ctapicore.c +164 -163
- data/ext/extconf.rb +5 -2
- data/install.rb +9 -5
- data/lib/ctapi.rb +521 -560
- data/lib/ctapi/version.rb +8 -0
- data/tests/test.rb +36 -36
- metadata +68 -45
- data/README.en +0 -31
- data/examples/ctsh.rb +0 -10
- data/make_doc.rb +0 -6
data/ext/extconf.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
require 'mkmf'
|
2
2
|
require 'fileutils'
|
3
3
|
|
4
|
-
|
4
|
+
DEFAULT_LIBRARY = 'towitoko'
|
5
|
+
unless libraryname = ENV['CTAPI_LIBRARY']
|
6
|
+
libraryname = DEFAULT_LIBRARY
|
7
|
+
warn "Using default library #{libraryname}!"
|
8
|
+
end
|
5
9
|
if have_library(libraryname)
|
6
10
|
create_makefile('ctapicore')
|
7
11
|
end
|
8
|
-
# vim: set et sw=2 ts=2:
|
data/install.rb
CHANGED
@@ -6,16 +6,20 @@ include FileUtils::Verbose
|
|
6
6
|
|
7
7
|
include Config
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
unless ARGV.empty?
|
10
|
+
ENV['CTAPI_LIBRARY'] = ARGV.shift.to_s
|
11
|
+
end
|
12
|
+
cd 'ext' do
|
11
13
|
File.exist?('Makefile') and system("make clean")
|
12
|
-
system("ruby extconf.rb
|
14
|
+
system("ruby extconf.rb") or
|
13
15
|
fail "Could not make makefile for library!"
|
14
16
|
system("make") or fail "Could not make library"
|
15
17
|
end
|
16
18
|
|
17
19
|
libdir = CONFIG["sitelibdir"]
|
18
|
-
for file in
|
20
|
+
for file in ["ext/ctapicore.#{CONFIG['DLEXT']}", 'lib/ctapi.rb']
|
19
21
|
install(file, libdir)
|
20
22
|
end
|
21
|
-
|
23
|
+
subdir = File.join(libdir, 'ctapi')
|
24
|
+
mkdir subdir
|
25
|
+
install(file, File.join(subdir, 'version.rb'))
|
data/lib/ctapi.rb
CHANGED
@@ -1,563 +1,524 @@
|
|
1
|
-
# There are two possible ways to use this library:
|
2
|
-
#
|
3
|
-
# 1. You can require 'ctapicore' and directly use the ct_init, ct_data and
|
4
|
-
# ct_close methods . That will give you access to the very simple
|
5
|
-
# C-Interface in Ruby, but you have to send APDU commands and receive and
|
6
|
-
# interpret APDU Responses yourself:
|
7
|
-
#
|
8
|
-
# require 'ctapicore'
|
9
|
-
# include CTAPICore
|
10
|
-
# ct_init(PORT_COM1, 0)
|
11
|
-
# response = ct_data(0, CT, HOST, "\x12\x34\x56")
|
12
|
-
# p response
|
13
|
-
# ct_close(0)
|
14
|
-
#
|
15
|
-
# 2. You can require 'ctapi' and the module CTAPI has some nice abstractions to
|
16
|
-
# deal with cardterminals, cards and APDUs, which are much more the Ruby
|
17
|
-
# Way:
|
18
|
-
#
|
19
|
-
# require 'ctapi'
|
20
|
-
# include CTAPI
|
21
|
-
# Cardterminal.open(PORT_COM1) do |ct|
|
22
|
-
# puts "Cardterminal Manufacturer: " + ct.manufacturer.to_s
|
23
|
-
# if ct.card_inserted?
|
24
|
-
# card = ct.card
|
25
|
-
# puts "My Cardterminal object: #{ct.inspect}"
|
26
|
-
# puts "Current card status is: #{ct.card_status}"
|
27
|
-
# puts "Answer to Reset is #{card.atr}."
|
28
|
-
# puts "ATR ok? #{card.atr_ok?}."
|
29
|
-
# puts "Memory size of this card is #{card.memory_size} bytes" +
|
30
|
-
# (#{card.memory_blocks} blocks x #{card.memory_bits} bit)."
|
31
|
-
# puts "Structure of this card is #{card.structure}."
|
32
|
-
# puts "Supported protocol type of this card is #{card.protocol}."
|
33
|
-
# puts "Trying to read(0, 16):"
|
34
|
-
# data = ct.read(0, 16)
|
35
|
-
# puts "Have read #{data.size} bytes:"
|
36
|
-
# p data
|
37
|
-
# else
|
38
|
-
# puts "Please insert a card into your cardterminal!"
|
39
|
-
# end
|
40
|
-
# end
|
41
|
-
#
|
42
1
|
module CTAPI
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
2
|
+
require 'ctapicore'
|
3
|
+
require 'ctapi/version'
|
4
|
+
include CTAPICore
|
5
|
+
|
6
|
+
require 'sync'
|
7
|
+
|
8
|
+
# Represents a cardterminal. Mixes in CTAPI methods and constants.
|
9
|
+
class Cardterminal
|
10
|
+
include CTAPI
|
11
|
+
|
12
|
+
# Global lock for managment of cardterminals.
|
13
|
+
LOCK = Sync.new
|
14
|
+
|
15
|
+
# Returns the mapping of cardterminal numbers to Cardterminal instances as
|
16
|
+
# a hash.
|
17
|
+
def self.cardterminals
|
18
|
+
@cardterminals ||= {}
|
19
|
+
end
|
20
|
+
|
21
|
+
# Convenience method to access Cardterminal.cardterminals.
|
22
|
+
def cardterminals
|
23
|
+
self.class.cardterminals
|
24
|
+
end
|
25
|
+
|
26
|
+
# Creates a new Cardterminal object for this interface and initializes
|
27
|
+
# the card terminal that is connected. If no cardterminal number is
|
28
|
+
# given, a number (that is not currently in use) is assigned to this
|
29
|
+
# object. Otherwise this object is forced to use the given card
|
30
|
+
# terminal number. BTW: number has to be >= 0.
|
31
|
+
def initialize(interface, number = nil)
|
32
|
+
LOCK.synchronize do
|
33
|
+
@interface = interface
|
34
|
+
@slot = ICC1
|
35
|
+
@chunk_size = 255
|
36
|
+
if number
|
37
|
+
@number = number
|
38
|
+
else
|
39
|
+
@number = 0
|
40
|
+
while cardterminals.key?(@number)
|
41
|
+
@number += 1
|
42
|
+
end
|
43
|
+
end
|
44
|
+
ct_init(@number, interface)
|
45
|
+
cardterminals[@number] = true
|
46
|
+
@manufacturer = get_manufacturer
|
47
|
+
reset
|
48
|
+
select_file # master file
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class << self
|
53
|
+
alias init new
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns a Manufacturer object with information about the manufacturer
|
57
|
+
# of this cardterminal. Could return nil if the response was not
|
58
|
+
# successful.
|
59
|
+
def get_manufacturer
|
60
|
+
get_manufacturer_status = [ CTBCS_CLA, CTBCS_INS_STATUS,
|
61
|
+
CTBCS_P1_CT_KERNEL, CTBCS_P2_STATUS_MANUFACTURER, 0 ]
|
62
|
+
response = data(CT, HOST, get_manufacturer_status)
|
63
|
+
return nil unless response.successful?
|
64
|
+
Manufacturer.new(response)
|
65
|
+
end
|
66
|
+
private :get_manufacturer
|
67
|
+
|
68
|
+
# The cardterminal number assigned to this object.
|
69
|
+
attr_reader :number
|
70
|
+
|
71
|
+
# The interface this cardterminal is connected to. This is
|
72
|
+
# a value of PORT_COM1, PORT_COM2, PORT_COM3, PORT_COM4, PORT_LPT1,
|
73
|
+
# PORT_LPT2, PORT_Modem or PORT_Printer.
|
74
|
+
attr_reader :interface
|
75
|
+
|
76
|
+
# The slot of the card terminal where the card is inserted. This
|
77
|
+
# is a value in the range of ICC1, ICC2,..., ICC14. Default is
|
78
|
+
# ICC1.
|
79
|
+
attr_accessor :slot
|
80
|
+
|
81
|
+
# The maximum size of the chunks of data that are read from/written to
|
82
|
+
# the card. Can be set by calling the chunk_size= method.
|
83
|
+
attr_reader :chunk_size
|
84
|
+
|
85
|
+
# Sets the size of the chunks of data that are read from/written to
|
86
|
+
# the card to size: (0 < size <= 255).
|
87
|
+
def chunk_size=(size)
|
88
|
+
raise ArgumentError.new("size must be > 0") unless size > 0
|
89
|
+
raise ArgumentError.new("size must be <= 255") unless size <= 255
|
90
|
+
@chunk_size = size
|
91
|
+
end
|
92
|
+
|
93
|
+
# The Manufacturer object for this cardterminal.
|
94
|
+
attr_reader :manufacturer
|
95
|
+
|
96
|
+
# Card object of the inserted chipcard.
|
97
|
+
attr_reader :card
|
98
|
+
|
99
|
+
# Takes a block and yields to an initialized Cardterminal object.
|
100
|
+
# After the block has been called, the cardterminal is closed
|
101
|
+
# again.
|
102
|
+
def self.open(interface, number = nil)
|
103
|
+
cardterminal = self.new(interface, cardterminal)
|
104
|
+
yield cardterminal
|
105
|
+
ensure
|
106
|
+
cardterminal.close if cardterminal
|
107
|
+
end
|
108
|
+
|
109
|
+
# Sends a sequence of commands to the card or the cardterminal
|
110
|
+
# (depending on destination/dad and source address/sad) and returns the
|
111
|
+
# response (or responses) to the calling program. A command can be
|
112
|
+
# given as an array of bytes [ 0x12, 0x23 ] or as a string of the form
|
113
|
+
# '12:23:a4' or '12 23 a4' or as a Command object.
|
114
|
+
def data(dad, sad, *commands)
|
115
|
+
responses = []
|
116
|
+
commands.each do |command|
|
117
|
+
command =
|
118
|
+
case command
|
119
|
+
when String then Command.from_string(command)
|
120
|
+
when Array then Command.from_array(command)
|
121
|
+
else command
|
122
|
+
end
|
123
|
+
$DEBUG and debug(2, command)
|
124
|
+
data = ct_data(@number, dad, sad, command.data)
|
125
|
+
response = Response.new(data)
|
126
|
+
$DEBUG and debug(1, response)
|
127
|
+
responses << response
|
128
|
+
end
|
129
|
+
return *responses
|
130
|
+
end
|
131
|
+
|
132
|
+
# Terminates the communication with the cardterminal associated with
|
133
|
+
# this object and releases the assigned cardterminal number.
|
134
|
+
def close
|
135
|
+
LOCK.synchronize do
|
136
|
+
ct_close(@number)
|
137
|
+
cardterminals.delete @number
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Sends the select file byte sequence to the card with a default value
|
142
|
+
# for the master file. Returns true if the response was successful,
|
143
|
+
# false otherwise.
|
144
|
+
def select_file(fid = [ 0x3f, 0 ])
|
145
|
+
select_file = [ 0, 0xa4, 0, 0, 0x02, *fid ]
|
146
|
+
response = data(@slot, HOST, select_file)
|
147
|
+
response.successful?
|
148
|
+
end
|
149
|
+
|
150
|
+
def read_chunk(address, size)
|
151
|
+
unless address <= 65535
|
152
|
+
raise ArgumentError.new("address must be <= 65535")
|
153
|
+
end
|
154
|
+
unless size <= 255
|
155
|
+
raise ArgumentError.new("size must be <= 255")
|
156
|
+
end
|
157
|
+
read_file = [ 0, 0xb0, address >> 8, address & 0xff, size ]
|
158
|
+
response = data(@slot, HOST, read_file)
|
159
|
+
response.successful? or return
|
160
|
+
data = response.data
|
161
|
+
data.slice!(-2, 2)
|
162
|
+
data
|
163
|
+
end
|
164
|
+
private :read_chunk
|
165
|
+
|
166
|
+
# Attempts to read data of length size starting at address. If
|
167
|
+
# size is nil, an attempt is made to read the whole card memory.
|
168
|
+
def read(address = 0, size = nil)
|
169
|
+
if size == nil
|
170
|
+
if @card
|
171
|
+
size = @card.memory_size - address
|
172
|
+
else
|
173
|
+
size = chunk_size
|
174
|
+
end
|
175
|
+
elsif @card and address + size > @card.memory_size
|
176
|
+
size = @card.memory_size - address
|
177
|
+
end
|
178
|
+
return if size <= 0
|
179
|
+
data = ''
|
180
|
+
caught = catch(:break) do
|
181
|
+
while size >= chunk_size
|
182
|
+
d = read_chunk(address, chunk_size)
|
183
|
+
if d
|
184
|
+
data << d
|
185
|
+
address += chunk_size
|
186
|
+
size -= chunk_size
|
187
|
+
else
|
188
|
+
break :break
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
if caught != :break and size > 0
|
193
|
+
d = read_chunk(address, size) and data << d
|
194
|
+
end
|
195
|
+
data
|
196
|
+
end
|
197
|
+
|
198
|
+
def write_chunk(address, data)
|
199
|
+
unless address <= 65535
|
200
|
+
raise ArgumentError.new("address must be <= 65535")
|
201
|
+
end
|
202
|
+
unless data.size <= 255
|
203
|
+
raise ArgumentError.new("size of data must be <= 255")
|
204
|
+
end
|
205
|
+
data = data.unpack("C*") if data.is_a? String
|
206
|
+
write_file = [ 0, 0xd6, address >> 8, address & 0xff, data.size ] +
|
207
|
+
data
|
208
|
+
response = data(@slot, HOST, write_file)
|
209
|
+
response.successful? or return
|
210
|
+
true
|
211
|
+
end
|
212
|
+
private :write_chunk
|
213
|
+
|
214
|
+
# Attempts to write the string data to the card starting at address.
|
215
|
+
# On success returns a true value.
|
216
|
+
def write(data, address = 0)
|
217
|
+
size = data.size
|
218
|
+
if @card and address + size > @card.memory_size
|
219
|
+
size = @card.memory_size - address
|
220
|
+
end
|
221
|
+
return if size <= 0
|
222
|
+
offset = 0
|
223
|
+
caught = catch(:break) do
|
224
|
+
while size >= chunk_size
|
225
|
+
write_chunk(address, data[offset, chunk_size]) or break :break
|
226
|
+
address += chunk_size
|
227
|
+
offset += chunk_size
|
228
|
+
size -= chunk_size
|
229
|
+
end
|
230
|
+
end
|
231
|
+
if caught == :break
|
232
|
+
return false
|
233
|
+
elsif size > 0
|
234
|
+
write_chunk(address, data[offset, size])
|
235
|
+
end
|
236
|
+
true
|
237
|
+
end
|
238
|
+
|
239
|
+
# The pin is sent to the card. It can be given as a string or an array
|
240
|
+
# of characters. A true result is returned if the sending was
|
241
|
+
# successful.
|
242
|
+
def enter_pin(pin)
|
243
|
+
unless pin.size <= 255
|
244
|
+
raise ArgumentError.new("size of pin must be <= 255")
|
245
|
+
end
|
246
|
+
pin = pin.unpack("C*") if pin.is_a? String
|
247
|
+
enter_pin = [ 0, 0x20, 0, 0, pin.size ] + pin
|
248
|
+
response = data(@slot, HOST, enter_pin)
|
249
|
+
response.successful? or return
|
250
|
+
true
|
251
|
+
end
|
252
|
+
|
253
|
+
# The pin of this card is changed from old_pin to new_pin. They can be
|
254
|
+
# given as strings or arrays of characters. A true result is returned
|
255
|
+
# if the sending was successful.
|
256
|
+
def change_pin(old_pin, new_pin)
|
257
|
+
old_pin = old_pin.unpack("C*") if old_pin.is_a? String
|
258
|
+
new_pin = new_pin.unpack("C*") if new_pin.is_a? String
|
259
|
+
data = old_pin + new_pin
|
260
|
+
unless data.size <= 255
|
261
|
+
raise ArgumentError.new("size of old and new pin must be <= 255")
|
262
|
+
end
|
263
|
+
change_pin = [ 0, 0x24, 0, 0, data.size ] + data
|
264
|
+
response = data(@slot, HOST, change_pin)
|
265
|
+
response.successful? or return
|
266
|
+
true
|
267
|
+
end
|
268
|
+
|
269
|
+
# Requests the card status from the cardterminal. Returns the
|
270
|
+
# Response object or nil if the response wasn't successful.
|
271
|
+
# This method is called by the card_inserted? method to find
|
272
|
+
# out if a card is inserted into the terminal.
|
273
|
+
def request_card_status
|
274
|
+
get_card_status = [ CTBCS_CLA, CTBCS_INS_STATUS,
|
275
|
+
CTBCS_P1_CT_KERNEL, CTBCS_P2_STATUS_ICC, 0 ]
|
276
|
+
response = data(CT, HOST, get_card_status)
|
277
|
+
response.successful? ? response : nil
|
278
|
+
end
|
279
|
+
|
280
|
+
# Sends a byte sequence to the card to get the answer to reset
|
281
|
+
# Response object. This method is called by the Cardterminal#reset
|
282
|
+
# method.
|
283
|
+
def request_icc
|
284
|
+
get_atr = [ CTBCS_CLA, CTBCS_INS_REQUEST, CTBCS_P1_INTERFACE1,
|
314
285
|
CTBCS_P2_REQUEST_GET_ATR, 0 ]
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
# Returns adescription of this card including the ATR as a string.
|
555
|
-
def inspect
|
556
|
-
string = to_s
|
557
|
-
string[-1] = " #{atr}>"
|
558
|
-
string
|
559
|
-
end
|
560
|
-
|
561
|
-
end
|
562
|
-
|
286
|
+
@card_old = @card if @card
|
287
|
+
@card, atr = nil, nil
|
288
|
+
begin
|
289
|
+
if card_inserted?
|
290
|
+
atr = data(CT, HOST, get_atr)
|
291
|
+
if atr
|
292
|
+
if atr.not_changed?
|
293
|
+
@card = @card_old
|
294
|
+
return @card.atr
|
295
|
+
end
|
296
|
+
@card = Card.new(atr)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
rescue CTAPIError => e
|
300
|
+
STDERR.puts "Caught: #{e}."
|
301
|
+
end
|
302
|
+
@card.atr
|
303
|
+
end
|
304
|
+
|
305
|
+
# Sends the eject card byte sequence to the card terminal and
|
306
|
+
# returns the Response object. This method is called by the
|
307
|
+
# Cardterminal#reset method.
|
308
|
+
def eject_icc
|
309
|
+
eject = [ 0x20, 0x15, 0x01, 0x00, 0x00 ]
|
310
|
+
data(CT, HOST, eject)
|
311
|
+
end
|
312
|
+
|
313
|
+
# The cardterminal is reset by first calling eject_icc and
|
314
|
+
# then request_icc. If the reset was successful the Response
|
315
|
+
# object of request_icc is returned, otherwise a nontrue value is
|
316
|
+
# returned.
|
317
|
+
def reset
|
318
|
+
eject_icc
|
319
|
+
response = request_icc or return
|
320
|
+
response.successful? or return
|
321
|
+
response
|
322
|
+
end
|
323
|
+
|
324
|
+
# Returns the card status as symbol: :no_card, :card, :card_connect.
|
325
|
+
def card_status
|
326
|
+
response = request_card_status
|
327
|
+
case response[0]
|
328
|
+
when CTBCS_DATA_STATUS_NOCARD
|
329
|
+
:no_card
|
330
|
+
when CTBCS_DATA_STATUS_CARD
|
331
|
+
:card
|
332
|
+
when CTBCS_DATA_STATUS_CARD_CONNECT
|
333
|
+
:card_connect
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
# Returns true if a card is inserted in the cardterminal at the moment,
|
338
|
+
# false if the cardterminal is empty.
|
339
|
+
def card_inserted?
|
340
|
+
cs = card_status
|
341
|
+
cs == :card || cs == :card_connect ? true : false
|
342
|
+
end
|
343
|
+
|
344
|
+
# Returns true if the card has been changed in the cardterminal,
|
345
|
+
# false it is still the same card. If an error occured nil
|
346
|
+
# is returned.
|
347
|
+
def card_changed?
|
348
|
+
response = reset or return
|
349
|
+
response.changed? and return true
|
350
|
+
response.not_changed? and return false
|
351
|
+
return
|
352
|
+
end
|
353
|
+
private :card_changed?
|
354
|
+
|
355
|
+
# A little structure for manufacturer information.
|
356
|
+
class Manufacturer
|
357
|
+
|
358
|
+
# Creates a Manufacturer object for this cardterminal from
|
359
|
+
# a manufacturer status response.
|
360
|
+
def initialize(response)
|
361
|
+
@manufacturer, @model, @revision =
|
362
|
+
response[0, 5], response[5, 3], response[10, 5]
|
363
|
+
end
|
364
|
+
|
365
|
+
# The manufacturer of this cardterminal.
|
366
|
+
attr_reader :manufacturer
|
367
|
+
|
368
|
+
# The model name of this cardterminal.
|
369
|
+
attr_reader :model
|
370
|
+
|
371
|
+
# The revision number of this cardterminal.
|
372
|
+
attr_reader :revision
|
373
|
+
|
374
|
+
# A string of the form 'manufacturer model revision' is returned.
|
375
|
+
def to_s
|
376
|
+
[manufacturer, model, revision].join(" ")
|
377
|
+
end
|
378
|
+
|
379
|
+
end
|
380
|
+
|
381
|
+
# Prints data with indication of direction to STDERR.
|
382
|
+
def debug(direction, data)
|
383
|
+
direction =
|
384
|
+
case direction
|
385
|
+
when 1 then '>>> '
|
386
|
+
when 2 then '<<< '
|
387
|
+
else ''
|
388
|
+
end
|
389
|
+
STDERR.puts direction + data.to_s
|
390
|
+
end
|
391
|
+
private :debug
|
392
|
+
end
|
393
|
+
|
394
|
+
# APDU (Application Protocol Data Unit) that is sent to/received from the
|
395
|
+
# cardterminal. This is the parent class of Command and Response.
|
396
|
+
class APDU
|
397
|
+
# An APDU object is generated from the String data.
|
398
|
+
def initialize(data)
|
399
|
+
@data = data
|
400
|
+
end
|
401
|
+
|
402
|
+
# This is the data string.
|
403
|
+
attr_reader :data
|
404
|
+
|
405
|
+
# Access data string by indices.
|
406
|
+
def [](*args) @data[*args] end
|
407
|
+
|
408
|
+
# We use Ruby's inspect representation to inspect the data.
|
409
|
+
def inspect
|
410
|
+
@data.inspect
|
411
|
+
end
|
412
|
+
|
413
|
+
# To display the data, a string of the form '12:23:a4' is built.
|
414
|
+
def to_s
|
415
|
+
@data.unpack('C*').map { |x| sprintf("%02x", x) }.join(':')
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
# A command sent to the cardterminal.
|
420
|
+
class Command < APDU
|
421
|
+
# A Command is generated from a string of the form '12:23:a4' or
|
422
|
+
# '12 23 a4'.
|
423
|
+
def self.from_string(command)
|
424
|
+
command = command.split(/[ :]/).map { |x| Integer '0x' + x }
|
425
|
+
from_array(command)
|
426
|
+
end
|
427
|
+
|
428
|
+
# A Command is generated from an array of bytes of the form
|
429
|
+
# [ 0x12, 0x23, 0xa4].
|
430
|
+
def self.from_array(command)
|
431
|
+
new(command.pack('C*'))
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
# A response received from the cardterminal.
|
436
|
+
class Response < APDU
|
437
|
+
# If this response was successful true is returned, false otherwise.
|
438
|
+
# A response is successful if the last two bytes are '90:00'.
|
439
|
+
def successful?
|
440
|
+
return false if @data.size < 2
|
441
|
+
return true if @data[-2, 2] == "\x90\x00"
|
442
|
+
false
|
443
|
+
end
|
444
|
+
|
445
|
+
def changed?
|
446
|
+
@data[-2, 2] == "\x62\x00" ? true : false
|
447
|
+
end
|
448
|
+
|
449
|
+
def not_changed?
|
450
|
+
@data[-2, 2] == "\x62\x01" ? true : false
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
class Card
|
455
|
+
def initialize(atr)
|
456
|
+
@atr = atr
|
457
|
+
end
|
458
|
+
|
459
|
+
# The answer to reset (ATR) of the inserted chipcard.
|
460
|
+
attr_accessor :atr
|
461
|
+
|
462
|
+
# Check if the ATR is correct. If false, the data of this Card
|
463
|
+
# object could be wrong and the calculations based on this data wrong.
|
464
|
+
def atr_ok?
|
465
|
+
@atr[4, 2] == "\x90\x00" && @atr[2] == 0x10
|
466
|
+
end
|
467
|
+
|
468
|
+
# The bit width of this chipcard.
|
469
|
+
def memory_bits
|
470
|
+
1 << (@atr[1] & 0x07)
|
471
|
+
end
|
472
|
+
|
473
|
+
# Number of memory blocks on this chipcard.
|
474
|
+
def memory_blocks
|
475
|
+
1 << (((@atr[1] & 0x78) >> 3) + 6)
|
476
|
+
end
|
477
|
+
|
478
|
+
# Computes the memory size of the inserted chipcard in bytes.
|
479
|
+
def memory_size
|
480
|
+
(memory_blocks * memory_bits >> 3)
|
481
|
+
end
|
482
|
+
|
483
|
+
# The structure of this chipcard.
|
484
|
+
def structure
|
485
|
+
if (@atr[0] & 0x3) == 0
|
486
|
+
'ISO'
|
487
|
+
elsif (@atr[0] & 0x7) == 2
|
488
|
+
'common use'
|
489
|
+
elsif (@atr[0] & 0x7) == 6
|
490
|
+
'proprietary use'
|
491
|
+
else
|
492
|
+
'special use'
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
496
|
+
# Supported protocol of this chipcard.
|
497
|
+
def protocol
|
498
|
+
if (@atr[0] & 0x80) == 0x00
|
499
|
+
'ISO'
|
500
|
+
elsif (@atr[0] & 0xf0) == 0x80
|
501
|
+
'I2C'
|
502
|
+
elsif (@atr[0] & 0xf0) == 0x90
|
503
|
+
'3W'
|
504
|
+
elsif (@atr[0] & 0xf0) == 0xa0
|
505
|
+
'2W'
|
506
|
+
else
|
507
|
+
'unknown'
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
# Returns a short description of this card as a string.
|
512
|
+
def to_s
|
513
|
+
"<#{self.class}:Structure #{structure} Protocol #{protocol} " +
|
514
|
+
"(#{memory_blocks}x#{memory_bits}=#{memory_size}B)" + '>'
|
515
|
+
end
|
516
|
+
|
517
|
+
# Returns adescription of this card including the ATR as a string.
|
518
|
+
def inspect
|
519
|
+
string = to_s
|
520
|
+
string[-1] = " #{atr}>"
|
521
|
+
string
|
522
|
+
end
|
523
|
+
end
|
563
524
|
end
|