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.
- data/AUTHORS +1 -0
- data/CHANGELOG +13 -0
- data/COPYING +728 -0
- data/LICENSE +58 -0
- data/README +9 -0
- data/Rakefile +141 -0
- data/THANKS +0 -0
- data/TODO +2 -0
- data/lib/7816.rb +5 -0
- data/lib/7816/apdu.rb +554 -0
- data/lib/7816/atr.rb +173 -0
- data/lib/7816/card.rb +278 -0
- data/lib/7816/iso_apdu.rb +68 -0
- data/lib/7816/pcsc_helper.rb +102 -0
- data/lib/emv.rb +10 -0
- data/lib/emv/cps_apdu.rb +319 -0
- data/lib/emv/crypto/crypto.rb +108 -0
- data/lib/emv/data/cps_ini_update.rb +15 -0
- data/lib/emv/emv.rb +5 -0
- data/lib/emv/emv_apdu.rb +56 -0
- data/lib/iso7816.rb +9 -0
- data/lib/iso_7816.rb +5 -0
- data/test/apdu_resp_test.rb +63 -0
- data/test/apdu_test.rb +128 -0
- data/test/card_test.rb +36 -0
- data/test/cps_test.rb +35 -0
- data/test/crypto_test.rb +23 -0
- data/test/emv_apdu_test.rb +79 -0
- data/test/iso_apdu_test.rb +108 -0
- data/test/util_test.rb +30 -0
- metadata +135 -0
data/lib/7816/atr.rb
ADDED
@@ -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
|
+
|
data/lib/7816/card.rb
ADDED
@@ -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
|