ctapi 0.2.2 → 0.2.3

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