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