iso7816 0.0.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.
@@ -0,0 +1,173 @@
1
+
2
+ require 'hexy'
3
+
4
+ module ISO7816
5
+ class ATR
6
+ # require 'atr_generated'
7
+ attr_reader :atr_bytes
8
+ # provide a string of bytes to init this object.
9
+ def initialize atr
10
+ @atr_bytes = atr
11
+ @string_rep = ATR.stringify atr
12
+ @desc = ATR.get_description atr
13
+ @ta=Hash.new
14
+ @tb=Hash.new
15
+ @tc=Hash.new
16
+ @td=Hash.new
17
+ decode
18
+ end
19
+
20
+ # ATR spec:
21
+ # TS : initial
22
+ # 3F = inverse logic
23
+ # 3B = direct logic
24
+ # T0 : format
25
+ # |7|6|5|4||3|2|1|0|
26
+ # bits 4 through 7 indicate presence of TA1, TB1, TC1, TD1 respectively
27
+ # bits 0-3 are the length of the "historical data"
28
+ #
29
+ # TA1...TD1, if present follow T0
30
+ #
31
+ # TAi, TBi, and TCi have codified sematics, a possible TDi field signals the
32
+ # existance of further TAi+1... fields.
33
+ #
34
+ # |7|6|5|4||3|2|1|0|
35
+ #
36
+ # where 4, 5 and 6 signal he presesence of TAx, TBx, TCx, TDx.
37
+ #
38
+ # finally the last bytes are "historical data" followed by a single byte checksum.
39
+ def decode
40
+ pos = 0
41
+ @ts = @atr_bytes[pos]
42
+ case @ts
43
+ when 0x3F: @ts_d="inverse"
44
+ when 0x3B: @ts_d="direct"
45
+ else @ts_d = "invalid"
46
+ end
47
+
48
+ @t0 = @atr_bytes[pos+=1]
49
+
50
+ ta, tb, tc,td, @hist_data_len = decode_TD 0, pos
51
+
52
+ i=1
53
+ @max_i = 0
54
+ while ta || tb || tc || td
55
+ @max_i = i
56
+ decodeTA i, pos+=1 if ta
57
+ decodeTB i, pos+=1 if tb
58
+ decodeTC i, pos+=1 if tc
59
+ if td
60
+ ta, tb, tc, td = decode_TD i, pos+=1
61
+ else
62
+ break
63
+ end
64
+ i+=1
65
+ end
66
+
67
+ @historical_data = @atr_bytes[pos+=1, @hist_data_len]
68
+ #puts @historical_data.size
69
+
70
+ #puts ATR.stringify(@historical_data)
71
+ end
72
+
73
+ def decodeTA i, pos
74
+ @ta ||= Hash.new
75
+ @ta[i]=@atr_bytes[pos]
76
+ end
77
+ def decodeTB i, pos
78
+ @tb ||= Hash.new
79
+ @tb[i]=@atr_bytes[pos]
80
+ end
81
+
82
+ def decodeTC i, pos
83
+ @tc ||= Hash.new
84
+ @tc[i]=@atr_bytes[pos]
85
+ end
86
+
87
+ # decodes a byte in the form:
88
+ # |7|6|5|4||3|2|1|0|
89
+ # and returns
90
+ # [TA?, B?, TC?, TD?, num]
91
+ def decode_TD i, pos
92
+ byte = @atr_bytes[pos]
93
+ @td ||= Hash.new
94
+ @td[i]=byte
95
+
96
+ [
97
+ (byte & 0x10)!=0,
98
+ (byte & 0x20)!=0,
99
+ (byte & 0x40)!=0,
100
+ (byte & 0x80)!=0,
101
+ byte & 0x0F
102
+ ]
103
+ end
104
+
105
+ def to_s
106
+ str = <<HELLO
107
+ ATR #{@string_rep}
108
+ TS (#{"%X"%@ts}) : #{@ts_d}
109
+ T0 (#{"%X"%@t0}) : #{"%08b"%@t0}
110
+
111
+ histdatalen: #{@hist_data_len}
112
+
113
+ HELLO
114
+
115
+ 1.upto(@max_i) {|i|
116
+ str << "i=#{i}\n"
117
+ str << label_i_hex_bin("TA", i, @ta[i])
118
+ str << label_i_hex_bin("TB", i, @tb[i])
119
+ str << label_i_hex_bin("TC", i, @tc[i])
120
+ str << label_i_hex_bin("TD", i, @td[i])
121
+
122
+ }
123
+
124
+ if @historical_data
125
+ str << "Historical Data:\n"
126
+ str << Hexy.new(@historical_data).to_s
127
+ end
128
+
129
+ str
130
+ end
131
+
132
+
133
+ def label_i_hex_bin label, i, byte
134
+ byte == nil ? "" : " %s%d(%02x) = %08b\n" % [label, i, byte, byte]
135
+ end
136
+
137
+ def self.stringify atr
138
+ atr.unpack("H*")[0].upcase
139
+ end
140
+ def self.get_description atr, bytes=false
141
+ atr = stringify atr if bytes
142
+ #desc = ATR_HASH[atr]
143
+ #desc ? desc : "unknown"
144
+ "unknown"
145
+ end
146
+ end
147
+ end
148
+
149
+ if $0 == __FILE__
150
+ puts ISO7816::ATR.get_description("3B660000314B01010080")
151
+ puts ISO7816::ATR.get_description("3B66000;ldskjfalsdkj")
152
+ bytes = ["3B660000314B01010080"].pack("H*")
153
+ puts ISO7816::ATR.get_description(bytes , true)
154
+ atr = ISO7816::ATR.new bytes
155
+ puts atr
156
+
157
+
158
+ bytes = ["3BFF9500FFC00A1F438031E073F62113574A334861324147D6"].pack("H*")
159
+ atr = ISO7816::ATR.new bytes
160
+ puts atr
161
+
162
+
163
+ ISO7816::ATR::ATR_HASH.keys.each{|key|
164
+ puts key
165
+ bytes= [key].pack("H*")
166
+ puts ISO7816::ATR.new(bytes).to_s
167
+ puts "--------------------------------------"
168
+
169
+ }
170
+
171
+
172
+ end
173
+
@@ -0,0 +1,278 @@
1
+ require 'smartcard'
2
+ module ISO7816
3
+ module Card
4
+
5
+ # Dummy implementation of `Card` to describe the provided interface
6
+ class Card
7
+ attr_reader :atr
8
+ # Set up a connection to this card and return the ATR String.
9
+ # May be called with a block which will be passed `self`. Card
10
+ # will call `disconnect` at the end of the block
11
+ def connect
12
+ raise "not implemented, use a subclass of `Card`!"
13
+ end
14
+
15
+ # disconnect from the card and free all resources.
16
+ def disconnect
17
+ raise "not implemented, use a subclass of `Card`!"
18
+ end
19
+
20
+ # Send bytes to the card.
21
+ def send bytes=""
22
+ raise "not implemented, use a subclass of `Card`!"
23
+ end
24
+
25
+ def reconnect
26
+ disconnect
27
+ connect
28
+ end
29
+
30
+ # receive a response from the card, optionally pass in the
31
+ # number of expected bytes in the response, this value will be 1024
32
+ # by default, which should be enough to hold most responses sent by
33
+ # a card.
34
+ def receive le=1024
35
+ raise "not implemented, use a subclass of `Card`!"
36
+ end
37
+
38
+ def t0?
39
+ raise "not implemented, use a subclass of `Card`!"
40
+ end
41
+ end #class Card
42
+
43
+ class PCSCCard < Card
44
+ def initialize()
45
+ end
46
+
47
+ def connect
48
+ begin
49
+ if @connected
50
+ #puts "!!!!!!!!!!!!!! Already Connected!"
51
+ else
52
+ @card = ISO7816::PCSC::Card.new()
53
+ end
54
+ rescue
55
+ @card.disconnect if @card
56
+ raise $!
57
+ end
58
+
59
+ stat = @card.status
60
+ @connected = true
61
+ @atr = @card.atr
62
+ @t0 = stat[:protocol] == Smartcard::PCSC::PROTOCOL_T0
63
+ if block_given?
64
+ yield self
65
+ disconnect
66
+ end
67
+ @atr
68
+ end
69
+
70
+ def reconnect
71
+ #puts "calling reconn! #{ (@card && @connected)}"
72
+ @card.reconnect if (@card && @connected)
73
+ end
74
+
75
+ def disconnect
76
+ @card.disconnect() if @card
77
+ @card = nil
78
+ @connected = false
79
+ end
80
+
81
+ def send bytes = ""
82
+ @received = @card.transmit(bytes)
83
+ end
84
+
85
+ def receive len=0
86
+ ret = @received
87
+ @received = nil
88
+ return ret
89
+ end
90
+
91
+ def t0?
92
+ return @t0
93
+ end
94
+
95
+ end
96
+
97
+ class LoggingCard < Card
98
+ attr_accessor :card
99
+ def initialize card
100
+ @card = card
101
+ @log = []
102
+ end
103
+ def atr
104
+ @card.atr
105
+ end
106
+ def connect &block
107
+ if block_given?
108
+ @card.connect
109
+ @log.push [:atr, @card.atr]
110
+ yield self
111
+ disconnect
112
+ else
113
+ @card.connect
114
+ @log.push [:atr, @card.atr]
115
+ end
116
+ end
117
+
118
+ def disconnect
119
+ @card.disconnect
120
+ @log.push [:disco, ""]
121
+ end
122
+
123
+ def send bytes
124
+ @card.send bytes
125
+ @log.push [:send, bytes]
126
+ end
127
+
128
+ def receive le=1024
129
+ recv = @card.receive(le)
130
+ @log.push [:recv, recv]
131
+ recv
132
+ end
133
+
134
+ def t0?
135
+ @card.t0?
136
+ end
137
+
138
+ def comment txt
139
+ @log.push [:comment, txt]
140
+ end
141
+
142
+ DEFAULT_DUMP = lambda {|dir, b|
143
+ prefix = case dir
144
+ when :atr then "ATR"
145
+ when :send then " >"
146
+ when :recv then " <"
147
+ when :disco then "\nCLIENT DISCONNECT"
148
+ else :comment
149
+ end
150
+ if prefix == :comment
151
+ txt = "\n"
152
+ b.each_line{|line| txt << " #{line}"}
153
+ txt << "\n\n"
154
+ else
155
+ "%s %s" % [prefix, ISO7816.b2s(b)]
156
+ end
157
+ }
158
+ def dump io=STDOUT, formater=DEFAULT_DUMP
159
+ @log.each{ |line|
160
+ format = DEFAULT_DUMP.call(line[0], line[1])
161
+ io.puts(format)
162
+ }
163
+
164
+ end
165
+
166
+
167
+ end
168
+
169
+ # This implementation of card relies on
170
+ # socket communication, it expects an atr to be
171
+ # send on TCP connect
172
+ class TCPCard < Card
173
+ SLEEP_TIME = 0.1
174
+ attr_accessor :addr, :port
175
+ def initialize addr="127.0.0.1", port=1024
176
+ @addr = addr
177
+ @port = port
178
+ end
179
+
180
+ def connect
181
+ require 'socket'
182
+ @socket = TCPSocket.new(@addr, @port)
183
+ @connected = true
184
+
185
+ sleep SLEEP_TIME
186
+ @atr = @socket.recv 1024,0
187
+ if block_given?
188
+ yield self
189
+ disconnect
190
+ end
191
+ @atr
192
+ end
193
+
194
+ def disconnect
195
+ @socket.close if @socket && @connected
196
+ @connected = false
197
+ @expect_sw=nil
198
+ true
199
+ end
200
+
201
+ def connected?
202
+ return false unless @connected
203
+ # we're still connected, check if peer is still available
204
+ true
205
+ end
206
+
207
+ def send bytes, flags=0
208
+ raise "not connected" unless @connected
209
+ bytes1 = bytes[0,5]
210
+ bytes2 = bytes[5,bytes.length]
211
+ @socket.send bytes1, flags
212
+ # TCP2 echos back the ins byte, flags
213
+ # according to ISO 7816-3 10.3.3
214
+ proc_byte = @socket.recv 1, Socket::MSG_PEEK
215
+ # recv 90 60, INS, ...
216
+ if proc_byte == "\x60" # null byte
217
+ #
218
+ elsif in_range(proc_byte, "\x61", "\x6f") || in_range(proc_byte, "\x90", "\x9f")
219
+ @expect_sw=true
220
+ else # INS being echoed back
221
+ @socket.recv 1, 0
222
+ @socket.send bytes2,flags
223
+ end
224
+ end
225
+
226
+ def in_range byte, from, to
227
+ byte >= from && byte <= to
228
+ end
229
+
230
+ def receive le=nil
231
+ raise "not connected" unless @connected
232
+
233
+ if @expect_sw
234
+ le=2
235
+ @expect_sw=nil
236
+ end
237
+
238
+ if le && Socket.const_defined?("MSG_WAITALL")
239
+ # if we know the num bytes to receive, set MSG_WAIT_ALL
240
+ flags = Socket::MSG_WAITALL
241
+ elsif le
242
+ data = ""
243
+ data << @socket.recv(le, flags) while data.length < le
244
+ return data
245
+ else
246
+ # set le to 1024 (default) and wait a little bit
247
+ le = 1024
248
+ sleep SLEEP_TIME
249
+ flags = 0
250
+ end
251
+
252
+ #puts "Waiting to receive: #{le}"
253
+ @socket.recv(le, flags)
254
+ end
255
+
256
+ def t0?
257
+ true
258
+ end
259
+
260
+ end # class TCPCard
261
+ end # module Card
262
+ end # module ISO7816
263
+
264
+
265
+
266
+
267
+
268
+ # VERY VERY basic tests... Actual testing is in ../test/card_test.rb
269
+ if $0 == __FILE__
270
+ card = ISO7816::Card::TCPCard.new "172.18.4.27", 1024
271
+ puts "connecting"
272
+ atr = card.connect
273
+ puts "connected"
274
+ puts atr.unpack("H*")[0]
275
+ puts "disconnecting"
276
+ card.disconnect
277
+ puts "disconnected"
278
+ end