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/LICENSE
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
This package is copyrighted free software by Tim Becker <tim@kuriositaet.de>.
|
2
|
+
You can redistribute it and/or modify it under either the terms of the GPL
|
3
|
+
(see COPYING.txt file), or the conditions below:
|
4
|
+
|
5
|
+
1. You may make and give away verbatim copies of the source form of the
|
6
|
+
software without restriction, provided that you duplicate all of the
|
7
|
+
original copyright notices and associated disclaimers.
|
8
|
+
|
9
|
+
2. You may modify your copy of the software in any way, provided that
|
10
|
+
you do at least ONE of the following:
|
11
|
+
|
12
|
+
a) place your modifications in the Public Domain or otherwise
|
13
|
+
make them Freely Available, such as by posting said
|
14
|
+
modifications to Usenet or an equivalent medium, or by allowing
|
15
|
+
the author to include your modifications in the software.
|
16
|
+
|
17
|
+
b) use the modified software only within your corporation or
|
18
|
+
organization.
|
19
|
+
|
20
|
+
c) rename any non-standard executables so the names do not conflict
|
21
|
+
with standard executables, which must also be provided.
|
22
|
+
|
23
|
+
d) make other distribution arrangements with the author.
|
24
|
+
|
25
|
+
3. You may distribute the software in object code or executable
|
26
|
+
form, provided that you do at least ONE of the following:
|
27
|
+
|
28
|
+
a) distribute the executables and library files of the software,
|
29
|
+
together with instructions (in the manual page or equivalent)
|
30
|
+
on where to get the original distribution.
|
31
|
+
|
32
|
+
b) accompany the distribution with the machine-readable source of
|
33
|
+
the software.
|
34
|
+
|
35
|
+
c) give non-standard executables non-standard names, with
|
36
|
+
instructions on where to get the original software distribution.
|
37
|
+
|
38
|
+
d) make other distribution arrangements with the author.
|
39
|
+
|
40
|
+
4. You may modify and include the part of the software into any other
|
41
|
+
software (possibly commercial). But some files in the distribution
|
42
|
+
are not written by the author, so that they are not under this terms.
|
43
|
+
|
44
|
+
They are gc.c(partly), utils.c(partly), regex.[ch], st.[ch] and some
|
45
|
+
files under the ./missing directory. See each file for the copying
|
46
|
+
condition.
|
47
|
+
|
48
|
+
5. The scripts and library files supplied as input to or produced as
|
49
|
+
output from the software do not automatically fall under the
|
50
|
+
copyright of the software, but belong to whomever generated them,
|
51
|
+
and may be sold commercially, and may be aggregated with this
|
52
|
+
software.
|
53
|
+
|
54
|
+
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
55
|
+
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
56
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
57
|
+
PURPOSE.
|
58
|
+
|
data/README
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
require "rdoc/task"
|
2
|
+
require "rubygems/package_task"
|
3
|
+
require "rake/testtask"
|
4
|
+
require "rake/clean"
|
5
|
+
require "rubygems"
|
6
|
+
|
7
|
+
# Some definitions that you'll need to edit in case you reuse this
|
8
|
+
# Rakefile for your own project.
|
9
|
+
|
10
|
+
SHORTNAME ='iso7816' # this should be the rubyforge project name
|
11
|
+
DESC ='ruby smartcard access'
|
12
|
+
PKG_VERSION ='0.0.3'
|
13
|
+
LONG_DESC = <<END_DESC
|
14
|
+
Utilities to provided ISO 7816 smartcard functionality
|
15
|
+
END_DESC
|
16
|
+
RUBYFORGE_USER ='a2800276'
|
17
|
+
|
18
|
+
# Specifies the default task to execute.
|
19
|
+
task :default => [:test]
|
20
|
+
|
21
|
+
# The directory to generate +rdoc+ in.
|
22
|
+
RDOC_DIR="doc/html"
|
23
|
+
|
24
|
+
# This global variable contains files that will be erased by the `clean` task.
|
25
|
+
# The `clean` task itself is automatically generated by requiring `rake/clean`.
|
26
|
+
|
27
|
+
CLEAN << RDOC_DIR << "pkg"
|
28
|
+
|
29
|
+
|
30
|
+
# This is the task that generates the +rdoc+ documentation from the
|
31
|
+
# source files. Instantiating Rake::RDocTask automatically generates a
|
32
|
+
# task called `rdoc`.
|
33
|
+
|
34
|
+
Rake::RDocTask.new do |rd|
|
35
|
+
# Options for documenation generation are specified inside of
|
36
|
+
# this block. For example the following line specifies that the
|
37
|
+
# content of the README file should be the main page of the
|
38
|
+
# documenation.
|
39
|
+
rd.main = "README"
|
40
|
+
|
41
|
+
# The following line specifies all the files to extract
|
42
|
+
# documenation from.
|
43
|
+
rd.rdoc_files.include( "README", "AUTHORS", "LICENSE", "TODO",
|
44
|
+
"CHANGELOG", "bin/**/*", "lib/**/*.rb",
|
45
|
+
"examples/**/*rb","test/**/*.rb", "doc/*.rdoc")
|
46
|
+
# This one specifies the output directory ...
|
47
|
+
rd.rdoc_dir = "doc/html"
|
48
|
+
|
49
|
+
# Or the HTML title of the generated documentation set.
|
50
|
+
rd.title = "#{SHORTNAME}: #{DESC}"
|
51
|
+
|
52
|
+
# These are options specifiying how source code inlined in the
|
53
|
+
# documentation should be formatted.
|
54
|
+
|
55
|
+
rd.options = ["--line-numbers"]
|
56
|
+
|
57
|
+
# Check:
|
58
|
+
# `rdoc --help` for more rdoc options
|
59
|
+
# the {rdoc documenation home}[http://www.ruby-doc.org/stdlib/libdoc/rdoc/rdoc/index.html]
|
60
|
+
# or the documentation for the +Rake::RDocTask+ task[http://rake.rubyforge.org/classes/Rake/RDocTask.html]
|
61
|
+
end
|
62
|
+
|
63
|
+
# The GemPackageTask facilitates getting all your files collected
|
64
|
+
# together into gem archives. You can also use it to generate tarball
|
65
|
+
# and zip archives.
|
66
|
+
|
67
|
+
# First you'll need to assemble a gemspec
|
68
|
+
|
69
|
+
PKG_FILES = FileList['lib/**/*.rb', 'bin/**/*', 'examples/**/*', '[A-Z]*', 'test/**/*'].to_a
|
70
|
+
|
71
|
+
spec = Gem::Specification.new do |s|
|
72
|
+
s.platform = Gem::Platform::RUBY
|
73
|
+
s.summary = "#{SHORTNAME}: #{DESC}"
|
74
|
+
s.name = SHORTNAME
|
75
|
+
s.version = PKG_VERSION
|
76
|
+
s.files = PKG_FILES
|
77
|
+
s.author = "Tim Becker"
|
78
|
+
s.email = "tim.becker@kuriositaet.de"
|
79
|
+
s.homepage = "https://github.com/a2800276/7816"
|
80
|
+
s.requirements << "none"
|
81
|
+
s.require_path = 'lib'
|
82
|
+
s.add_dependency("hexy")
|
83
|
+
s.add_dependency("tlv")
|
84
|
+
s.add_dependency("smartcard")
|
85
|
+
#s.has_rdoc=true
|
86
|
+
s.description = LONG_DESC
|
87
|
+
end
|
88
|
+
|
89
|
+
# Adding a new GemPackageTask adds a task named `package`, which generates
|
90
|
+
# packages as gems, tarball and zip archives.
|
91
|
+
Gem::PackageTask.new(spec) do |pkg|
|
92
|
+
pkg.need_zip = true
|
93
|
+
pkg.need_tar_gz = true
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
# This task is used to demonstrate how to upload files to Rubyforge.
|
98
|
+
# Calling `upload_page` creates a current version of the +rdoc+
|
99
|
+
# documentation and uploads it to the Rubyforge homepage of the project,
|
100
|
+
# assuming it's hosted there and naming conventions haven't changed.
|
101
|
+
#
|
102
|
+
# This task uses `sh` to call the `scp` binary, which is plattform
|
103
|
+
# dependant and may not be installed on your computer if you're using
|
104
|
+
# Windows. I'm currently not aware of any pure ruby way to do scp
|
105
|
+
# transfers.
|
106
|
+
|
107
|
+
RubyForgeProject=SHORTNAME
|
108
|
+
|
109
|
+
desc "Upload the web pages to the web."
|
110
|
+
task :upload_pages => ["rdoc"] do
|
111
|
+
if RubyForgeProject then
|
112
|
+
path = "/var/www/gforge-projects/#{RubyForgeProject}"
|
113
|
+
sh "scp -r doc/html/* #{RUBYFORGE_USER}@rubyforge.org:#{path}"
|
114
|
+
sh "scp doc/images/*.png #{RUBYFORGE_USER}@rubyforge.org:#{path}/images"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# This task will run the unit tests provided in files called
|
119
|
+
# `test/test*.rb`. The task itself can be run with a call to `rake test`
|
120
|
+
|
121
|
+
Rake::TestTask.new do |t|
|
122
|
+
t.libs << "test"
|
123
|
+
t.libs << "lib"
|
124
|
+
t.ruby_opts = ["-rubygems"]
|
125
|
+
t.test_files = FileList['test/*.rb']
|
126
|
+
t.verbose = true
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
desc "generate iso apdu command classes"
|
131
|
+
task :iso_apdu do
|
132
|
+
ruby "-rubygems build/create_apdu.rb -i lib/apdu.spec -o lib/apdu_generated.rb -s lib/apdu_generated_impl.rb"
|
133
|
+
end
|
134
|
+
|
135
|
+
desc "delete generated apdu classes"
|
136
|
+
task :iso_apdu_clean do
|
137
|
+
File.delete "lib/apdu_generated.rb" if File.exist? "lib/apdu_generated.rb"
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
|
data/THANKS
ADDED
File without changes
|
data/TODO
ADDED
data/lib/7816.rb
ADDED
data/lib/7816/apdu.rb
ADDED
@@ -0,0 +1,554 @@
|
|
1
|
+
require 'hexy'
|
2
|
+
module ISO7816
|
3
|
+
def ISO7816.b2s bytestr
|
4
|
+
r = bytestr.unpack("H*")[0]
|
5
|
+
r.length > 1 ? r : " "
|
6
|
+
end
|
7
|
+
def ISO7816.make_binary value
|
8
|
+
value = [value].pack("C") if value.is_a? Numeric
|
9
|
+
end
|
10
|
+
|
11
|
+
def b2s bytestr
|
12
|
+
ISO7816.b2s bytestr
|
13
|
+
end
|
14
|
+
|
15
|
+
def s2b string
|
16
|
+
ISO7816.s2b string
|
17
|
+
end
|
18
|
+
|
19
|
+
def ISO7816.s2b string
|
20
|
+
string = string.gsub(/\s+/, "")
|
21
|
+
[string].pack("H*")
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
module APDU
|
26
|
+
|
27
|
+
#
|
28
|
+
# Models an ISO 7816 APDU (Application Protocol Data Unit) the basic
|
29
|
+
# "packet"/unit of communication sent from terminal/cardreader to the
|
30
|
+
# card attributes are: CLASS (.cla) INS (.ins), PARAM1 (p1), PARAM2
|
31
|
+
# (.p2) DATA (.data) and LE (.le), the length of the data expected in
|
32
|
+
# the response from the card.
|
33
|
+
|
34
|
+
class APDU
|
35
|
+
include ISO7816
|
36
|
+
# data is the transported data
|
37
|
+
attr_accessor :card, :name
|
38
|
+
attr_writer :data, :le
|
39
|
+
#attr_reader :cla, :ins, :p1, :p2
|
40
|
+
|
41
|
+
def initialize card=nil
|
42
|
+
@card=card
|
43
|
+
end
|
44
|
+
|
45
|
+
def cla
|
46
|
+
@cla || self.class._cla
|
47
|
+
end
|
48
|
+
|
49
|
+
def ins
|
50
|
+
@ins || self.class._ins
|
51
|
+
end
|
52
|
+
|
53
|
+
def p1
|
54
|
+
@p1 || self.class._p1
|
55
|
+
end
|
56
|
+
|
57
|
+
def p2
|
58
|
+
@p2 || self.class._p2
|
59
|
+
end
|
60
|
+
|
61
|
+
def le
|
62
|
+
@le || self.class._le
|
63
|
+
end
|
64
|
+
|
65
|
+
def data
|
66
|
+
@data || self.class._data
|
67
|
+
end
|
68
|
+
|
69
|
+
def cla= cla
|
70
|
+
@cla = "" << cla
|
71
|
+
end
|
72
|
+
def ins= ins
|
73
|
+
@ins = "" << ins
|
74
|
+
end
|
75
|
+
def p1= p1
|
76
|
+
@p1 = "" << p1
|
77
|
+
end
|
78
|
+
def p2= p2
|
79
|
+
@p2 = "" << p2
|
80
|
+
end
|
81
|
+
def lc
|
82
|
+
unless @lc
|
83
|
+
return "" if @data == "" || @data==nil
|
84
|
+
return [@data.length].pack("C")
|
85
|
+
end
|
86
|
+
@lc
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
# normally don't need to set lc, because it's calculated from the
|
91
|
+
# data's length, but for testing it may be necessary to set an
|
92
|
+
# correct value ...
|
93
|
+
def lc= val
|
94
|
+
if val.is_a? Numeric
|
95
|
+
@lc = [val].pack("C")
|
96
|
+
else
|
97
|
+
@lc=val
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def le= val
|
102
|
+
if val.is_a? Numeric
|
103
|
+
@le = [val].pack("C")
|
104
|
+
else
|
105
|
+
@le=val
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# add data field to the apdu, contrary to
|
110
|
+
# the +data+ accessor, this method takes a hex string
|
111
|
+
# and not a binary string. The following are identical:
|
112
|
+
#
|
113
|
+
# apdu.data = "\xaa\xaa\xaa"
|
114
|
+
#
|
115
|
+
# and
|
116
|
+
#
|
117
|
+
# apdu.hex_data = "aa aa aa"
|
118
|
+
def hex_data= val
|
119
|
+
self.data = s2b(val)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Indicate whether this is a case_4 APDU.
|
123
|
+
# In case of T=0, le field is NOT sent along for case 4 APDUs
|
124
|
+
def case_4?
|
125
|
+
data!="" && le!=""
|
126
|
+
end
|
127
|
+
|
128
|
+
# INS may not have values of 0x60..0x6f and 0x90..0x9f
|
129
|
+
def ins_valid?
|
130
|
+
invalid = APDU.in_range(@ins, "\x60", "\x6f") || APDU.in_range(@ins, "\x90", "\x9f")
|
131
|
+
!invalid
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.in_range byte, from, to
|
135
|
+
byte >= from && byte <= to
|
136
|
+
end
|
137
|
+
|
138
|
+
def to_b
|
139
|
+
bytes = ""
|
140
|
+
bytes << cla
|
141
|
+
bytes << ins
|
142
|
+
bytes << p1
|
143
|
+
bytes << p2
|
144
|
+
|
145
|
+
if data != "" || lc != nil
|
146
|
+
bytes << lc
|
147
|
+
bytes << data
|
148
|
+
end
|
149
|
+
if le != "" && le != nil
|
150
|
+
bytes << le
|
151
|
+
end
|
152
|
+
bytes
|
153
|
+
end
|
154
|
+
|
155
|
+
def send handle_more_data=true, card=nil
|
156
|
+
card = card || @card
|
157
|
+
raise "no card to send data to" unless card
|
158
|
+
|
159
|
+
data_to_send = self.to_b
|
160
|
+
data_to_send = data_to_send.chop if card.t0? && self.case_4?
|
161
|
+
|
162
|
+
if card.t0? && data_to_send.length == 4
|
163
|
+
#7816-3: ... case1 apdu mapped onto TPDU with P3=00
|
164
|
+
data_to_send << "\x00"
|
165
|
+
end
|
166
|
+
if card.respond_to? :comment
|
167
|
+
card.comment "#{self.class.to_s}\n#{self.to_s.strip}"
|
168
|
+
end
|
169
|
+
|
170
|
+
card.send data_to_send
|
171
|
+
|
172
|
+
to_receive = 2
|
173
|
+
to_receive += le.unpack("C")[0] if le && le != ""
|
174
|
+
if card.t0? && self.case_4?
|
175
|
+
to_receive = 2
|
176
|
+
end
|
177
|
+
|
178
|
+
r = card.receive(to_receive)
|
179
|
+
resp = Response.new(r, self)
|
180
|
+
|
181
|
+
if (handle_more_data && how_much_more = resp.more_data?)
|
182
|
+
gr = GET_RESPONSE.new
|
183
|
+
gr.le = how_much_more
|
184
|
+
resp = gr.send(false, card) # avoid infinite get_response loop... how to handle?
|
185
|
+
end
|
186
|
+
|
187
|
+
resp
|
188
|
+
end
|
189
|
+
|
190
|
+
def to_s
|
191
|
+
|
192
|
+
line1 = "#{@name}\n|CLA|INS| P1| P2|"
|
193
|
+
line2 = [cla, ins, p1, p2].map { |b|
|
194
|
+
"| "+ ( b.unpack("H*")[0] )
|
195
|
+
}.join+"|"
|
196
|
+
|
197
|
+
data_ = data
|
198
|
+
if data_.length > 16
|
199
|
+
data_ = ""
|
200
|
+
end
|
201
|
+
if data_ != "" || le != ""
|
202
|
+
field_size = data_.length*2>"Data".length ? data_.length*2 : "Data".length
|
203
|
+
if data_.length >=2
|
204
|
+
pad0 = " "*((data_.length*2 - 4)/2)
|
205
|
+
pad1 = ""
|
206
|
+
else
|
207
|
+
pad0 = ""
|
208
|
+
pad1 = " "
|
209
|
+
end
|
210
|
+
|
211
|
+
#line1 += "| LC|#{pad0}Data#{pad0}| LE|"
|
212
|
+
line1 += ("| LC|% #{field_size}s| LE|" % "Data")
|
213
|
+
#line2 += "| #{b2s(self.lc)}|#{pad1}#{b2s(@data)}#{pad1}| #{@le?b2s(@le):" "}|"
|
214
|
+
line2 += "| #{b2s(lc)}|%#{field_size}s| #{le ? b2s(le) : ' '}|" % b2s(data_)
|
215
|
+
end
|
216
|
+
txt = "#{line1}\n#{line2}"
|
217
|
+
if data.length > 16
|
218
|
+
h = Hexy.new @data
|
219
|
+
txt << "\n\n"
|
220
|
+
txt << h.to_s
|
221
|
+
end
|
222
|
+
txt
|
223
|
+
end
|
224
|
+
|
225
|
+
# parses a string of bytes into an APDU object.
|
226
|
+
def self.to_apdu bytes, sanity=true
|
227
|
+
apdu = APDU.new
|
228
|
+
apdu.cla = bytes[0,1]
|
229
|
+
apdu.ins = bytes[1,1]
|
230
|
+
apdu.p1 = bytes[2,1]
|
231
|
+
apdu.p2 = bytes[3,1]
|
232
|
+
if bytes.size > 4 # at least le
|
233
|
+
if bytes.size > 5 # weird, 0 size lc and no data
|
234
|
+
apdu.lc = bytes[4,1]
|
235
|
+
len = apdu.lc.unpack("C")[0]
|
236
|
+
apdu.data = bytes[5,len]
|
237
|
+
if bytes.size > 5+len
|
238
|
+
raise "Too many bytes!" if bytes.size > 6+len
|
239
|
+
apdu.le = bytes[5+len,1]
|
240
|
+
end
|
241
|
+
else # only le
|
242
|
+
apdu.le = bytes[4,1]
|
243
|
+
end #le
|
244
|
+
end # lc, data, le
|
245
|
+
apdu
|
246
|
+
end #to_apdu
|
247
|
+
|
248
|
+
class << self
|
249
|
+
|
250
|
+
def cla cla
|
251
|
+
@cla=""<<cla
|
252
|
+
end
|
253
|
+
def ins ins
|
254
|
+
@ins=""<<ins
|
255
|
+
end
|
256
|
+
def p1 p1
|
257
|
+
@p1=""<<p1
|
258
|
+
end
|
259
|
+
def p2 p2
|
260
|
+
@p2=""<<p2
|
261
|
+
end
|
262
|
+
def data data
|
263
|
+
@data=""<<data
|
264
|
+
end
|
265
|
+
def le le
|
266
|
+
@le=""<<le
|
267
|
+
end
|
268
|
+
|
269
|
+
def _cla
|
270
|
+
@cla ||= self == APDU ? "\x00" : superclass._cla
|
271
|
+
end
|
272
|
+
|
273
|
+
def _ins
|
274
|
+
@ins ||= self == APDU ? "\x00" : superclass._ins
|
275
|
+
end
|
276
|
+
|
277
|
+
def _p1
|
278
|
+
@p1 ||= self == APDU ? "\x00" : superclass._p1
|
279
|
+
end
|
280
|
+
|
281
|
+
def _p2
|
282
|
+
@p2 ||= self == APDU ? "\x00" : superclass._p2
|
283
|
+
end
|
284
|
+
|
285
|
+
def _data
|
286
|
+
@data ||= self == APDU ? "" : superclass._data
|
287
|
+
end
|
288
|
+
def _le
|
289
|
+
@le ||= self == APDU ? "" : superclass._le
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end # class APDU
|
293
|
+
|
294
|
+
|
295
|
+
# Each new APDU generated provides a random CLA, INS
|
296
|
+
class RandomAPDU < APDU
|
297
|
+
def initialize card=nil, rand_p1p2 = true, rand_le = true, rand_data=0
|
298
|
+
super card
|
299
|
+
@cla = [rand(256)].pack("C")
|
300
|
+
@ins = [rand(256)].pack("C")
|
301
|
+
until ins_valid?
|
302
|
+
@ins = [rand(256)].pack("C")
|
303
|
+
end
|
304
|
+
if (rand_p1p2)
|
305
|
+
@p1 = [rand(256)].pack("C")
|
306
|
+
@p2 = [rand(256)].pack("C")
|
307
|
+
end
|
308
|
+
|
309
|
+
if (rand_le)
|
310
|
+
if rand() > 0.5
|
311
|
+
@le = [rand(256)].pack("C")
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
if (rand_data && rand_data>0)
|
316
|
+
data=[]
|
317
|
+
1.upto(rand_data) {
|
318
|
+
data << [rand(256)].pack("C")
|
319
|
+
}
|
320
|
+
@data = data.join
|
321
|
+
end
|
322
|
+
|
323
|
+
end
|
324
|
+
|
325
|
+
def self.get_random card, num, seed=0, allow_invalid_ins = false
|
326
|
+
srand(seed)
|
327
|
+
arr = []
|
328
|
+
1.upto(num) {
|
329
|
+
apdu = RandomAPDU.new card
|
330
|
+
while allow_invalid_ins || !apdu.ins_valid?
|
331
|
+
apdu = RandomAPDU.new card
|
332
|
+
end
|
333
|
+
arr << apdu
|
334
|
+
}
|
335
|
+
arr
|
336
|
+
end
|
337
|
+
|
338
|
+
end
|
339
|
+
|
340
|
+
class Response
|
341
|
+
include ISO7816
|
342
|
+
attr_accessor :data, :sw1, :sw2
|
343
|
+
|
344
|
+
def initialize data, le=0
|
345
|
+
if le.is_a? ISO7816::APDU::APDU
|
346
|
+
le = le.le.unpack("C")[0]
|
347
|
+
le = le ? le : 0
|
348
|
+
end
|
349
|
+
@data = data[0,le]
|
350
|
+
@sw1 = data[-2,1]
|
351
|
+
@sw2 = data[-1,1]
|
352
|
+
|
353
|
+
end
|
354
|
+
|
355
|
+
def status
|
356
|
+
if @sw1 && @sw2
|
357
|
+
status = @sw1.dup
|
358
|
+
return b2s(status<<@sw2)
|
359
|
+
end
|
360
|
+
"????"
|
361
|
+
end
|
362
|
+
|
363
|
+
def normal?
|
364
|
+
(@sw1 == "\x90" && @sw2 == "\x00") || @sw1 == "\x61"
|
365
|
+
end
|
366
|
+
|
367
|
+
def warning?
|
368
|
+
["\x62","\x63"].include? @sw1
|
369
|
+
end
|
370
|
+
|
371
|
+
def execution_err?
|
372
|
+
["\x64", "\x65", "\x66"].include? @sw1
|
373
|
+
end
|
374
|
+
|
375
|
+
def checking_err?
|
376
|
+
["\x67", "\x68", "\x69", "\x6a", "\x6b", "\x6c", "\x6d", "\x6e", "\x6f"].include? @sw1
|
377
|
+
end
|
378
|
+
|
379
|
+
def more_data?
|
380
|
+
return @sw1 == "\x61" ? @sw2 : false
|
381
|
+
end
|
382
|
+
|
383
|
+
def to_s
|
384
|
+
str = ""
|
385
|
+
str << "APDU Response\n"
|
386
|
+
str << " Data(#{@data.length}): #{b2s(@data)}\n" unless @data == ""
|
387
|
+
str << " SW1: #{b2s(@sw1)} (#{sw1_explanation})\n"
|
388
|
+
str << " SW2: #{b2s(@sw2)} (#{sw2_explanation})\n"
|
389
|
+
str
|
390
|
+
end
|
391
|
+
|
392
|
+
def sw1_explanation
|
393
|
+
case @sw1
|
394
|
+
when "\x90"
|
395
|
+
return "OK: No further qualification"
|
396
|
+
when "\x61"
|
397
|
+
return "OK: SW2 indicates the number of response bytes still available"
|
398
|
+
# Warning processings
|
399
|
+
when "\x62"
|
400
|
+
return "WARN: State of non-volatile memory unchanged"
|
401
|
+
when "\x63" : return "WARN: State of non-volatile memory changed"
|
402
|
+
# Execution errors
|
403
|
+
when "\x64"
|
404
|
+
return "EXE ERR: State of non-volatile memory unchanged"
|
405
|
+
when "\x65"
|
406
|
+
return "EXE ERR: State of non-volatile memory changed"
|
407
|
+
when "\x66"
|
408
|
+
return "EXE ERR: Reserved for security-related issues"
|
409
|
+
# Checking errors
|
410
|
+
when "\x67"
|
411
|
+
return "CHK ERR: Wrong length"
|
412
|
+
when "\x68"
|
413
|
+
return "CHK ERR: Functions in CLA not supported"
|
414
|
+
when "\x69"
|
415
|
+
return "CHK ERR: Command not allowed"
|
416
|
+
when "\x6A"
|
417
|
+
return "CHK ERR: Wrong parameter(s) P1-P2"
|
418
|
+
when "\x6B"
|
419
|
+
return "CHK ERR: Wrong parameter(s) P1-P2"
|
420
|
+
when "\x6C"
|
421
|
+
return "CHK ERR: Wrong length Le: SW2 indicates the exact length"
|
422
|
+
when "\x6D"
|
423
|
+
return "CHK ERR: Instruction code not supported or invalid"
|
424
|
+
when "\x6E"
|
425
|
+
return "CHK ERR: Class not supported"
|
426
|
+
when "\x6F"
|
427
|
+
return "CHK ERR: No precise diagnosis"
|
428
|
+
else
|
429
|
+
return "UNKNOWN : #{b2s(@sw1)}"
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
def sw2_explanation
|
434
|
+
case @sw1
|
435
|
+
when "\x90", "\x64", "\x67", "\x6b", "\x6d", "\x6e", "\x6f"
|
436
|
+
sw2_check00
|
437
|
+
when "\x61"
|
438
|
+
"#{b2s(@sw2)} bytes remaining."
|
439
|
+
when "\x62"
|
440
|
+
sw2_62
|
441
|
+
when "\x63"
|
442
|
+
sw2_63
|
443
|
+
when "\x65"
|
444
|
+
sw2_65
|
445
|
+
when "\x66"
|
446
|
+
"security (undefined)"
|
447
|
+
when "\x68"
|
448
|
+
sw2_68
|
449
|
+
when "\x69"
|
450
|
+
sw2_69
|
451
|
+
when "\x6a"
|
452
|
+
sw2_6a
|
453
|
+
when "\x6c"
|
454
|
+
"wrong lenght. Exact len: #{b2s(@sw2)}"
|
455
|
+
else
|
456
|
+
"UNKNOWN : #{b2s(@sw2)}"
|
457
|
+
end
|
458
|
+
end
|
459
|
+
|
460
|
+
private
|
461
|
+
|
462
|
+
def sw2_check00
|
463
|
+
@sw2 == "\x00" ? "" : "sw2 should be 0x00, is #{b2s(@sw2)}"
|
464
|
+
end
|
465
|
+
def sw2_62
|
466
|
+
case @sw2
|
467
|
+
when "\x00": "No information given"
|
468
|
+
when "\x81": "Part of returned data may be corrupted"
|
469
|
+
when "\x82": "End of file/record reached before reading Le bytes"
|
470
|
+
when "\x83": "Selected file invalidated"
|
471
|
+
when "\x84": "FCI not formatted according to spec"
|
472
|
+
else "unknown sw2 for sw1=#{b2s(@sw1)}: #{b2s(@sw2)}"
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
def sw2_63
|
477
|
+
case @sw2
|
478
|
+
when "\x00": "No information given"
|
479
|
+
when "\x81": "File filled up by the last write"
|
480
|
+
when "\xC0","\xC1","\xC2","\xC3","\xC4","\xC5","\xC6","\xC7","\xC8","\xC9","\xCA","\xCB","\xCC","\xCD","\xCE","\xCF"
|
481
|
+
"Counter provided by '#{@sw2[0] & 0x0f}'"
|
482
|
+
else "unknown sw2 for sw1=#{b2s(@sw1)}: #{b2s(@sw2)}"
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
def sw2_65
|
487
|
+
case @sw2
|
488
|
+
when "\x00": "No information given"
|
489
|
+
when "\x81": "Memory failure"
|
490
|
+
else "unknown sw2 for sw1=#{b2s(@sw1)}: #{b2s(@sw2)}"
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
494
|
+
def sw2_68
|
495
|
+
case @sw2
|
496
|
+
when "\x00": "No information given"
|
497
|
+
when "\x81": "Logical channel not supported"
|
498
|
+
when "\x82": "Secure messaging not supported"
|
499
|
+
else "unknown sw2 for sw1=#{b2s(@sw1)}: #{b2s(@sw2)}"
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
def sw2_69
|
504
|
+
case @sw2
|
505
|
+
when "\x00": "No information given"
|
506
|
+
when "\x81": "Command incompatible with file structure"
|
507
|
+
when "\x82": "Security status not satisfied"
|
508
|
+
when "\x83": "Authentication method blocked"
|
509
|
+
when "\x84": "Referenced data invalidated"
|
510
|
+
when "\x85": "Conditions of use not satisfied"
|
511
|
+
when "\x86": "Command not allowed (no current EF)"
|
512
|
+
when "\x87": "Expected SM data objects missing"
|
513
|
+
when "\x88": "SM data objects incorrect"
|
514
|
+
else "unknown sw2 for sw1=#{b2s(@sw1)}: #{b2s(@sw2)}"
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
def sw2_6a
|
519
|
+
case @sw2
|
520
|
+
when "\x00": "No information given"
|
521
|
+
when "\x80": "Incorrect parameters in the data field"
|
522
|
+
when "\x81": "Function not supported"
|
523
|
+
when "\x82": "File not found"
|
524
|
+
when "\x83": "Record not found"
|
525
|
+
when "\x84": "Not enough memory space in the file"
|
526
|
+
when "\x85": "Lc inconsistent with TLV structure"
|
527
|
+
when "\x86": "Incorrect parameters P1-P2"
|
528
|
+
when "\x87": "Lc inconsistent with P1-P2"
|
529
|
+
when "\x88": "Referenced data not found"
|
530
|
+
else "unknown sw2 for sw1=#{b2s(@sw1)}: #{b2s(@sw2)}"
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|
534
|
+
end
|
535
|
+
|
536
|
+
|
537
|
+
end # module APDU
|
538
|
+
|
539
|
+
end # ISO7816
|
540
|
+
|
541
|
+
if __FILE__ == $0
|
542
|
+
puts 0x04.class
|
543
|
+
puts "here"
|
544
|
+
a = ISO7816::APDU::APDU.new
|
545
|
+
puts a
|
546
|
+
a.data = "\xde\xad\xbe\xef"
|
547
|
+
puts a
|
548
|
+
a = ISO7816::APDU::APDU.new
|
549
|
+
a.data = "\xde"
|
550
|
+
a.le = "\x01"
|
551
|
+
puts a
|
552
|
+
a = ISO7816::APDU::SELECT.new
|
553
|
+
puts a
|
554
|
+
end
|