iso7816 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|