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