smartcard 0.4.1 → 0.4.2
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/CHANGELOG +2 -0
- data/Manifest +13 -6
- data/Rakefile +3 -3
- data/lib/smartcard.rb +3 -0
- data/lib/smartcard/gp/asn1_ber.rb +199 -0
- data/lib/smartcard/gp/cap_loader.rb +88 -0
- data/lib/smartcard/gp/des.rb +68 -0
- data/lib/smartcard/gp/gp_card_mixin.rb +364 -5
- data/lib/smartcard/iso/iso_card_mixin.rb +13 -3
- data/lib/smartcard/iso/jcop_remote_server.rb +14 -5
- data/lib/smartcard/iso/pcsc_transport.rb +8 -1
- data/smartcard.gemspec +5 -5
- data/test/gp/asn1_ber_test.rb +116 -0
- data/test/gp/cap_loader_test.rb +24 -0
- data/test/gp/des_test.rb +39 -0
- data/test/gp/gp_card_mixin_test.rb +194 -5
- data/test/gp/hello.apdu +1 -0
- data/test/gp/hello.cap +0 -0
- data/test/iso/iso_card_mixin_test.rb +9 -8
- data/test/iso/jcop_remote_test.rb +4 -1
- metadata +25 -11
@@ -28,7 +28,16 @@ module IsoCardMixin
|
|
28
28
|
def iso_apdu!(apdu_data)
|
29
29
|
response = self.iso_apdu apdu_data
|
30
30
|
return response[:data] if response[:status] == 0x9000
|
31
|
-
|
31
|
+
IsoCardMixin.raise_response_exception response
|
32
|
+
end
|
33
|
+
|
34
|
+
# Raises an exception in response to an error status in an APDU.
|
35
|
+
#
|
36
|
+
# :call_seq:
|
37
|
+
# IsoCardMixin.raise_response_exception(response)
|
38
|
+
def self.raise_response_exception(response)
|
39
|
+
raise "JavaCard response has error status 0x#{'%04x' % response[:status]}" +
|
40
|
+
" - #{response[:data].map { |ch| '%02x' % ch }.join(' ')}"
|
32
41
|
end
|
33
42
|
|
34
43
|
# Performs an APDU exchange with the ISO7816 card.
|
@@ -47,7 +56,7 @@ module IsoCardMixin
|
|
47
56
|
# Serializes an APDU for wire transmission.
|
48
57
|
#
|
49
58
|
# :call-seq:
|
50
|
-
#
|
59
|
+
# IsoCardMixin.serialize_apdu(apdu_data) -> array
|
51
60
|
#
|
52
61
|
# The following keys are recognized in the APDU hash:
|
53
62
|
# cla:: the CLA byte in the APDU (optional, defaults to 0)
|
@@ -73,13 +82,14 @@ module IsoCardMixin
|
|
73
82
|
else
|
74
83
|
apdu << 0
|
75
84
|
end
|
85
|
+
apdu << (apdu_data[:le] || 0)
|
76
86
|
apdu
|
77
87
|
end
|
78
88
|
|
79
89
|
# De-serializes a ISO7816 response APDU.
|
80
90
|
#
|
81
91
|
# :call-seq:
|
82
|
-
#
|
92
|
+
# IsoCardMixin.deserialize_response(response) -> hash
|
83
93
|
#
|
84
94
|
# The response contains the following keys:
|
85
95
|
# status:: the 2-byte status code (e.g. 0x9000 is OK)
|
@@ -42,6 +42,13 @@ module JcopRemoteServingStubs
|
|
42
42
|
# Dumb implementation that always returns OK.
|
43
43
|
[0x90, 0x00]
|
44
44
|
end
|
45
|
+
|
46
|
+
# The smartcard's APDU.
|
47
|
+
def card_apdu
|
48
|
+
# ATR from the card simulator in JCOP 3.2.7.
|
49
|
+
[0x3B, 0xF8, 0x13, 0x00, 0x00, 0x81, 0x31, 0xFE, 0x45, 0x4A, 0x43, 0x4F,
|
50
|
+
0x50, 0x76, 0x32, 0x34, 0x31, 0xB7].pack('C*')
|
51
|
+
end
|
45
52
|
end # module JcopRemoteServingStubs
|
46
53
|
|
47
54
|
|
@@ -161,15 +168,17 @@ class JcopRemoteServer
|
|
161
168
|
|
162
169
|
case request[:type]
|
163
170
|
when 0
|
164
|
-
# Wait for card
|
165
|
-
#
|
166
|
-
send_message socket, :type => 0, :node => 0,
|
171
|
+
# Wait for card (no-op, happened when the client connected) and return
|
172
|
+
# card ATR.
|
173
|
+
send_message socket, :type => 0, :node => 0,
|
174
|
+
:data => @logic.card_atr.unpack('C*')
|
167
175
|
when 1
|
168
|
-
#
|
176
|
+
# APDU exchange; the class' bread and butter
|
169
177
|
response = @logic.exchange_apdu request[:data]
|
170
178
|
send_message socket, :type => 1, :node => 0, :data => response
|
171
179
|
else
|
172
|
-
send_message socket, :type => request[:type], :node => 0,
|
180
|
+
send_message socket, :type => request[:type], :node => 0,
|
181
|
+
:data => @logic.card_atr.unpack('C*')
|
173
182
|
end
|
174
183
|
end
|
175
184
|
private :process_request
|
@@ -18,7 +18,8 @@ class PcscTransport
|
|
18
18
|
def initialize(options)
|
19
19
|
@options = options
|
20
20
|
@context = nil
|
21
|
-
@card = nil
|
21
|
+
@card = nil
|
22
|
+
@atr = nil
|
22
23
|
end
|
23
24
|
|
24
25
|
def exchange_apdu(apdu)
|
@@ -27,6 +28,10 @@ class PcscTransport
|
|
27
28
|
return result_string.unpack('C*')
|
28
29
|
end
|
29
30
|
|
31
|
+
def card_atr
|
32
|
+
@atr
|
33
|
+
end
|
34
|
+
|
30
35
|
def connect
|
31
36
|
@context = PCSC::Context.new(PCSC::SCOPE_SYSTEM) if @context.nil?
|
32
37
|
|
@@ -62,6 +67,7 @@ class PcscTransport
|
|
62
67
|
# build the transmit / receive IoRequests
|
63
68
|
status = @card.status
|
64
69
|
@xmit_ioreq = @@xmit_iorequest[status[:protocol]]
|
70
|
+
@atr = status[:atr]
|
65
71
|
if RUBY_PLATFORM =~ /win/ and (not RUBY_PLATFORM =~ /darwin/)
|
66
72
|
@recv_ioreq = nil
|
67
73
|
else
|
@@ -73,6 +79,7 @@ class PcscTransport
|
|
73
79
|
unless @card.nil?
|
74
80
|
@card.disconnect PCSC::DISPOSITION_LEAVE
|
75
81
|
@card = nil
|
82
|
+
@atr = nil
|
76
83
|
end
|
77
84
|
unless @context.nil?
|
78
85
|
@context.release
|
data/smartcard.gemspec
CHANGED
@@ -2,23 +2,23 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{smartcard}
|
5
|
-
s.version = "0.4.
|
5
|
+
s.version = "0.4.2"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Victor Costan"]
|
9
|
-
s.date = %q{2009-
|
9
|
+
s.date = %q{2009-11-01}
|
10
10
|
s.description = %q{Interface with ISO 7816 smart cards.}
|
11
11
|
s.email = %q{victor@costan.us}
|
12
12
|
s.extensions = ["ext/smartcard_pcsc/extconf.rb"]
|
13
|
-
s.extra_rdoc_files = ["BUILD", "CHANGELOG", "ext/smartcard_pcsc/extconf.rb", "ext/smartcard_pcsc/pcsc.h", "ext/smartcard_pcsc/pcsc_card.c", "ext/smartcard_pcsc/pcsc_constants.c", "ext/smartcard_pcsc/pcsc_context.c", "ext/smartcard_pcsc/pcsc_exception.c", "ext/smartcard_pcsc/pcsc_io_request.c", "ext/smartcard_pcsc/pcsc_main.c", "ext/smartcard_pcsc/pcsc_multi_strings.c", "ext/smartcard_pcsc/pcsc_namespace.c", "ext/smartcard_pcsc/pcsc_reader_states.c", "ext/smartcard_pcsc/pcsc_surrogate_reader.h", "ext/smartcard_pcsc/pcsc_surrogate_wintypes.h", "lib/smartcard/gp/
|
14
|
-
s.files = ["BUILD", "CHANGELOG", "ext/smartcard_pcsc/extconf.rb", "ext/smartcard_pcsc/pcsc.h", "ext/smartcard_pcsc/pcsc_card.c", "ext/smartcard_pcsc/pcsc_constants.c", "ext/smartcard_pcsc/pcsc_context.c", "ext/smartcard_pcsc/pcsc_exception.c", "ext/smartcard_pcsc/pcsc_io_request.c", "ext/smartcard_pcsc/pcsc_main.c", "ext/smartcard_pcsc/pcsc_multi_strings.c", "ext/smartcard_pcsc/pcsc_namespace.c", "ext/smartcard_pcsc/pcsc_reader_states.c", "ext/smartcard_pcsc/pcsc_surrogate_reader.h", "ext/smartcard_pcsc/pcsc_surrogate_wintypes.h", "lib/smartcard/gp/gp_card_mixin.rb", "lib/smartcard/iso/auto_configurator.rb", "lib/smartcard/iso/iso_card_mixin.rb", "lib/smartcard/iso/jcop_remote_protocol.rb", "lib/smartcard/iso/jcop_remote_server.rb", "lib/smartcard/iso/jcop_remote_transport.rb", "lib/smartcard/iso/pcsc_transport.rb", "lib/smartcard/iso/transport.rb", "lib/smartcard/pcsc/pcsc_exception.rb", "
|
13
|
+
s.extra_rdoc_files = ["BUILD", "CHANGELOG", "LICENSE", "README", "ext/smartcard_pcsc/extconf.rb", "ext/smartcard_pcsc/pcsc.h", "ext/smartcard_pcsc/pcsc_card.c", "ext/smartcard_pcsc/pcsc_constants.c", "ext/smartcard_pcsc/pcsc_context.c", "ext/smartcard_pcsc/pcsc_exception.c", "ext/smartcard_pcsc/pcsc_io_request.c", "ext/smartcard_pcsc/pcsc_main.c", "ext/smartcard_pcsc/pcsc_multi_strings.c", "ext/smartcard_pcsc/pcsc_namespace.c", "ext/smartcard_pcsc/pcsc_reader_states.c", "ext/smartcard_pcsc/pcsc_surrogate_reader.h", "ext/smartcard_pcsc/pcsc_surrogate_wintypes.h", "lib/smartcard.rb", "lib/smartcard/gp/asn1_ber.rb", "lib/smartcard/gp/cap_loader.rb", "lib/smartcard/gp/des.rb", "lib/smartcard/gp/gp_card_mixin.rb", "lib/smartcard/iso/auto_configurator.rb", "lib/smartcard/iso/iso_card_mixin.rb", "lib/smartcard/iso/jcop_remote_protocol.rb", "lib/smartcard/iso/jcop_remote_server.rb", "lib/smartcard/iso/jcop_remote_transport.rb", "lib/smartcard/iso/pcsc_transport.rb", "lib/smartcard/iso/transport.rb", "lib/smartcard/pcsc/pcsc_exception.rb"]
|
14
|
+
s.files = ["BUILD", "CHANGELOG", "LICENSE", "Manifest", "README", "Rakefile", "ext/smartcard_pcsc/extconf.rb", "ext/smartcard_pcsc/pcsc.h", "ext/smartcard_pcsc/pcsc_card.c", "ext/smartcard_pcsc/pcsc_constants.c", "ext/smartcard_pcsc/pcsc_context.c", "ext/smartcard_pcsc/pcsc_exception.c", "ext/smartcard_pcsc/pcsc_io_request.c", "ext/smartcard_pcsc/pcsc_main.c", "ext/smartcard_pcsc/pcsc_multi_strings.c", "ext/smartcard_pcsc/pcsc_namespace.c", "ext/smartcard_pcsc/pcsc_reader_states.c", "ext/smartcard_pcsc/pcsc_surrogate_reader.h", "ext/smartcard_pcsc/pcsc_surrogate_wintypes.h", "lib/smartcard.rb", "lib/smartcard/gp/asn1_ber.rb", "lib/smartcard/gp/cap_loader.rb", "lib/smartcard/gp/des.rb", "lib/smartcard/gp/gp_card_mixin.rb", "lib/smartcard/iso/auto_configurator.rb", "lib/smartcard/iso/iso_card_mixin.rb", "lib/smartcard/iso/jcop_remote_protocol.rb", "lib/smartcard/iso/jcop_remote_server.rb", "lib/smartcard/iso/jcop_remote_transport.rb", "lib/smartcard/iso/pcsc_transport.rb", "lib/smartcard/iso/transport.rb", "lib/smartcard/pcsc/pcsc_exception.rb", "test/gp/asn1_ber_test.rb", "test/gp/cap_loader_test.rb", "test/gp/des_test.rb", "test/gp/gp_card_mixin_test.rb", "test/gp/hello.apdu", "test/gp/hello.cap", "test/iso/auto_configurator_test.rb", "test/iso/iso_card_mixin_test.rb", "test/iso/jcop_remote_test.rb", "test/pcsc/containers_test.rb", "test/pcsc/smoke_test.rb", "tests/ts_pcsc_ext.rb", "smartcard.gemspec"]
|
15
15
|
s.homepage = %q{http://www.costan.us/smartcard}
|
16
16
|
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Smartcard", "--main", "README"]
|
17
17
|
s.require_paths = ["lib", "ext"]
|
18
18
|
s.rubyforge_project = %q{smartcard}
|
19
19
|
s.rubygems_version = %q{1.3.5}
|
20
20
|
s.summary = %q{Interface with ISO 7816 smart cards.}
|
21
|
-
s.test_files = ["test/gp/gp_card_mixin_test.rb", "test/iso/auto_configurator_test.rb", "test/iso/iso_card_mixin_test.rb", "test/iso/jcop_remote_test.rb", "test/pcsc/containers_test.rb", "test/pcsc/smoke_test.rb"]
|
21
|
+
s.test_files = ["test/gp/asn1_ber_test.rb", "test/gp/cap_loader_test.rb", "test/gp/des_test.rb", "test/gp/gp_card_mixin_test.rb", "test/iso/auto_configurator_test.rb", "test/iso/iso_card_mixin_test.rb", "test/iso/jcop_remote_test.rb", "test/pcsc/containers_test.rb", "test/pcsc/smoke_test.rb"]
|
22
22
|
|
23
23
|
if s.respond_to? :specification_version then
|
24
24
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# Author:: Victor Costan
|
2
|
+
# Copyright:: Copyright (C) 2008 Massachusetts Institute of Technology
|
3
|
+
# License:: MIT
|
4
|
+
|
5
|
+
require 'smartcard'
|
6
|
+
|
7
|
+
require 'test/unit'
|
8
|
+
|
9
|
+
class Asn1BerTest < Test::Unit::TestCase
|
10
|
+
Asn1Ber = Smartcard::Gp::Asn1Ber
|
11
|
+
|
12
|
+
def test_tag
|
13
|
+
prefix = [0x03, 0x14, 0x15]
|
14
|
+
[
|
15
|
+
[[0x82], {:primitive => true, :class => :context, :number => 2}],
|
16
|
+
[[0x29], {:primitive => false, :class => :universal, :number => 9}],
|
17
|
+
[[0xD9], {:primitive => true, :class => :private, :number => 0x19}],
|
18
|
+
[[0x9F, 0x65], {:primitive => true, :class => :context, :number => 0x65}],
|
19
|
+
[[0x5F, 0x81, 0x65], {:primitive => true, :class => :application,
|
20
|
+
:number => 0xE5}],
|
21
|
+
].each do |test_case|
|
22
|
+
offset, tag = Asn1Ber.decode_tag prefix + test_case.first, prefix.length
|
23
|
+
assert_equal((prefix + test_case.first).length, offset,
|
24
|
+
"Offset for #{test_case.inspect}")
|
25
|
+
assert_equal test_case.last, tag,
|
26
|
+
"Decoded tag information for #{test_case.inspect}"
|
27
|
+
assert_equal test_case.first, Asn1Ber.encode_tag(test_case.last),
|
28
|
+
"Encoded tag for #{test_case.inspect}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_length
|
33
|
+
prefix = [0x03, 0x14, 0x15]
|
34
|
+
[
|
35
|
+
[[0x12], 0x12],
|
36
|
+
[[0x82, 0x05, 0x39], 0x539],
|
37
|
+
[[0x80], :indefinite],
|
38
|
+
].each do |test_case|
|
39
|
+
offset, length = Asn1Ber.decode_length prefix + test_case.first,
|
40
|
+
prefix.length
|
41
|
+
assert_equal((prefix + test_case.first).length, offset,
|
42
|
+
"Offset for #{test_case.inspect}")
|
43
|
+
assert_equal test_case.last, length,
|
44
|
+
"Decoded length for #{test_case.inspect}"
|
45
|
+
assert_equal test_case.first, Asn1Ber.encode_length(test_case.last),
|
46
|
+
"Encoded length for #{test_case.inspect}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_value
|
51
|
+
data = (0...20).to_a
|
52
|
+
offset, value = Asn1Ber.decode_value data, 7, 4
|
53
|
+
assert_equal 7 + 4, offset, 'Offset with definite length'
|
54
|
+
assert_equal [7, 8, 9, 10], value, 'Value with definite length'
|
55
|
+
|
56
|
+
data = [0x03, 0x14, 0x15, 0x92, 0x65, 0x35, 0x00, 0x00, 0x01, 0x02, 0x03]
|
57
|
+
offset, value = Asn1Ber.decode_value data, 4, :indefinite
|
58
|
+
assert_equal 8, offset, 'Offset with indefinite length'
|
59
|
+
assert_equal [0x65, 0x35], value, 'Value with definite length'
|
60
|
+
offset, value = Asn1Ber.decode_value data, 6, :indefinite
|
61
|
+
assert_equal 8, offset, 'Offset for empty value with indefinite length'
|
62
|
+
assert_equal [], value, 'Empty value with indefinite length'
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_tlv
|
66
|
+
golden_tlv = {:primitive => true, :class => :context, :number => 4,
|
67
|
+
:value => [0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00]}
|
68
|
+
prefix = [0x03, 0x14, 0x15]
|
69
|
+
ber_tlv = [0x84, 0x08, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00]
|
70
|
+
offset, tlv = Asn1Ber.decode_tlv prefix + ber_tlv, prefix.length
|
71
|
+
assert_equal golden_tlv, tlv, 'Decoded TLV data'
|
72
|
+
assert_equal((prefix + ber_tlv).length, offset, 'Offset')
|
73
|
+
assert_equal ber_tlv, Asn1Ber.encode_tlv(golden_tlv), 'Encoded TLV data'
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_tlv_sequence
|
77
|
+
golden = [
|
78
|
+
{:number => 0x0F, :class => :application, :primitive => false,
|
79
|
+
:value => [
|
80
|
+
{:number => 4, :class => :context, :primitive => true,
|
81
|
+
:value => [0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00]},
|
82
|
+
{:number => 5, :class => :context, :primitive => false,
|
83
|
+
:value => [
|
84
|
+
{:number => 0x65, :class => :context, :primitive => true,
|
85
|
+
:value => [0xFF]}]}]}]
|
86
|
+
ber = [0x6F, 16, 0x84, 8, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
|
87
|
+
0xA5, 4, 0x9F, 0x65, 1, 0xFF]
|
88
|
+
assert_equal golden, Asn1Ber.decode(ber), 'Decoded sequence'
|
89
|
+
assert_equal ber, Asn1Ber.encode(golden), 'Encoded sequence'
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_visit
|
93
|
+
tlvs = [
|
94
|
+
{:number => 0x0F, :class => :application, :primitive => false,
|
95
|
+
:value => [
|
96
|
+
{:number => 4, :class => :context, :primitive => true,
|
97
|
+
:value => [0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00]},
|
98
|
+
{:number => 5, :class => :context, :primitive => false,
|
99
|
+
:value => [
|
100
|
+
{:number => 0x65, :class => :context, :primitive => true,
|
101
|
+
:value => [0xFF]}]}]}]
|
102
|
+
|
103
|
+
paths = []
|
104
|
+
Asn1Ber.visit tlvs do |path, value|
|
105
|
+
paths << path
|
106
|
+
case path
|
107
|
+
when [0x6F, 0xA5, 0x9F65]
|
108
|
+
assert_equal [0xFF], value
|
109
|
+
when [0x6F, 0x84]
|
110
|
+
assert_equal [0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00], value
|
111
|
+
end
|
112
|
+
end
|
113
|
+
assert_equal [[0x6F], [0x6F, 0x84], [0x6F, 0xA5], [0x6F, 0xA5, 0x9F65]],
|
114
|
+
paths, 'Visited paths'
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# Author:: Victor Costan
|
2
|
+
# Copyright:: Copyright (C) 2008 Massachusetts Institute of Technology
|
3
|
+
# License:: MIT
|
4
|
+
|
5
|
+
require 'smartcard'
|
6
|
+
|
7
|
+
require 'test/unit'
|
8
|
+
|
9
|
+
class CapLoaderTest < Test::Unit::TestCase
|
10
|
+
CapLoader = Smartcard::Gp::CapLoader
|
11
|
+
|
12
|
+
def setup
|
13
|
+
@cap_file = File.join(File.dirname(__FILE__), 'hello.cap')
|
14
|
+
@apdu_file = File.join(File.dirname(__FILE__), 'hello.apdu')
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_load_data
|
18
|
+
load_data = CapLoader.cap_load_data(@cap_file)
|
19
|
+
assert_equal File.read(@apdu_file),
|
20
|
+
load_data[:data].map { |ch| "%02x" % ch }.join(' ')
|
21
|
+
assert_equal [{:aid => [0x19, 0x83, 0x12, 0x29, 0x10, 0xDE, 0xAD],
|
22
|
+
:install_method => 8}], load_data[:applets]
|
23
|
+
end
|
24
|
+
end
|
data/test/gp/des_test.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# Author:: Victor Costan
|
2
|
+
# Copyright:: Copyright (C) 2008 Massachusetts Institute of Technology
|
3
|
+
# License:: MIT
|
4
|
+
|
5
|
+
require 'smartcard'
|
6
|
+
|
7
|
+
require 'test/unit'
|
8
|
+
|
9
|
+
class DesTest < Test::Unit::TestCase
|
10
|
+
Des = Smartcard::Gp::Des
|
11
|
+
|
12
|
+
def test_crypt
|
13
|
+
key = "@ABCDEFGHIJKLMNO"
|
14
|
+
data = "Quick brown fox "
|
15
|
+
iv = "0123456789ABCDEF"
|
16
|
+
golden = [0xC3, 0x21, 0xFE, 0x0D, 0xD1, 0x26, 0x34, 0xBE, 0xA6, 0x27, 0x7A,
|
17
|
+
0x00, 0x27, 0x26, 0xF0, 0xAA].pack('C*')
|
18
|
+
assert_equal golden, Des.crypt(key, data, iv)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_mac_3des
|
22
|
+
key = "@ABCDEFGHIJKLMNO"
|
23
|
+
data = "Quick brown fox "
|
24
|
+
golden = [0x99, 0xB8, 0x86, 0x86, 0x16, 0xBF, 0xDE, 0x01].pack('C*')
|
25
|
+
|
26
|
+
assert_equal golden, Des.mac_3des(key, data)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_mac_retail
|
30
|
+
data = [0x72, 0xC2, 0x9C, 0x23, 0x71, 0xCC, 0x9B, 0xDB, 0x65, 0xB7, 0x79,
|
31
|
+
0xB8, 0xE8, 0xD3, 0x7B, 0x29, 0xEC, 0xC1, 0x54, 0xAA, 0x56, 0xA8, 0x79,
|
32
|
+
0x9F, 0xAE, 0x2F, 0x49, 0x8F, 0x76, 0xED, 0x92, 0xF2].pack('C*')
|
33
|
+
key = [0x79, 0x62, 0xD9, 0xEC, 0xE0, 0x3D, 0x1A, 0xCD, 0x4C, 0x76, 0x08,
|
34
|
+
0x9D, 0xCE, 0x13, 0x15, 0x43].pack('C*')
|
35
|
+
golden = [0x5F, 0x14, 0x48, 0xEE, 0xA8, 0xAD, 0x90, 0xA7].pack('C*')
|
36
|
+
|
37
|
+
assert_equal golden, Des.mac_retail(key, data)
|
38
|
+
end
|
39
|
+
end
|
@@ -16,18 +16,207 @@ class GpCardMixinTest < Test::Unit::TestCase
|
|
16
16
|
# The sole purpose of this class is wrapping the mixin under test.
|
17
17
|
class MixinWrapper
|
18
18
|
include GpCardMixin
|
19
|
-
include Smartcard::Iso::IsoCardMixin
|
20
19
|
end
|
21
20
|
|
22
21
|
def setup
|
22
|
+
@file_aid = [0x19, 0x83, 0x12, 0x29, 0x10, 0xFA, 0xCE]
|
23
|
+
@app_aid = [0x19, 0x83, 0x12, 0x29, 0x10, 0xBA, 0xBE]
|
24
|
+
@host_auth = [0x00, 0x65, 0x07, 0x37, 0xD4, 0xB8, 0xDF, 0xDE, 0xD0, 0x7B,
|
25
|
+
0xAA, 0xA2, 0xDE, 0xDE, 0x82, 0x8B]
|
26
|
+
@host_challenge = [0x20, 0xBB, 0xE0, 0x4A, 0x1C, 0x6B, 0x6F, 0x50]
|
27
|
+
@file_data = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA,
|
28
|
+
0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x21, 0x32, 0x43, 0x54, 0x65,
|
29
|
+
0x76, 0x87, 0x98]
|
30
|
+
@max_apdu_length = 0x0F
|
31
|
+
end
|
32
|
+
|
33
|
+
def mock_card_manager_select(channel_mock)
|
34
|
+
flexmock(channel_mock).should_receive(:exchange_apdu).
|
35
|
+
with([0x00, 0xA4, 0x04, 0x00, 0x08, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x00,
|
36
|
+
0x00, 0x00, 0x00]).
|
37
|
+
and_return([0x6F, 16, 0x84, 8, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
|
38
|
+
0x00, 0xA5, 4, 0x9F, 0x65, 1, 0x0F, 0x90, 0x00])
|
23
39
|
end
|
24
40
|
|
25
41
|
def test_select_application
|
26
42
|
mock = MixinWrapper.new
|
43
|
+
mock_card_manager_select mock
|
44
|
+
app_data = mock.select_application mock.gp_card_manager_aid
|
45
|
+
|
46
|
+
golden = { :aid => mock.gp_card_manager_aid, :max_apdu_length => 0x0F }
|
47
|
+
assert_equal golden, app_data
|
48
|
+
end
|
49
|
+
|
50
|
+
def mock_channel_setup(channel_mock)
|
51
|
+
flexmock(channel_mock).should_receive(:exchange_apdu).
|
52
|
+
with([0x80, 0x50, 0x00, 0x00, 0x08, 0x20, 0xBB, 0xE0, 0x4A, 0x1C, 0x6B,
|
53
|
+
0x6F, 0x50, 0x00]).
|
54
|
+
and_return([0x00, 0x00, 0x81, 0x29, 0x00, 0x76, 0x76, 0x91, 0x36, 0x54,
|
55
|
+
0xFF, 0x02, 0x00, 0x02, 0x59, 0x8D, 0xD3, 0x96, 0x1B, 0xFD,
|
56
|
+
0x04, 0xB5, 0xCF, 0x5A, 0xD0, 0x08, 0x3C, 0x01, 0x90, 0x00])
|
57
|
+
flexmock(Smartcard::Gp::Des).should_receive(:random_bytes).with(8).
|
58
|
+
and_return(@host_challenge.pack('C*'))
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_gp_setup_secure_channel
|
62
|
+
mock = MixinWrapper.new
|
63
|
+
mock_channel_setup mock
|
64
|
+
golden = {
|
65
|
+
:key_diversification => [0x00, 0x00, 0x81, 0x29, 0x00, 0x76, 0x76, 0x91,
|
66
|
+
0x36, 0x54],
|
67
|
+
:key_version => 0xFF, :protocol_id => 2, :counter => 2,
|
68
|
+
:challenge => [0x59, 0x8D, 0xD3, 0x96, 0x1B, 0xFD],
|
69
|
+
:auth => [0x04, 0xB5, 0xCF, 0x5A, 0xD0, 0x08, 0x3C, 0x01]
|
70
|
+
}
|
71
|
+
assert_equal golden, mock.gp_setup_secure_channel(@host_challenge)
|
72
|
+
end
|
73
|
+
|
74
|
+
def mock_channel_lock(channel_mock)
|
75
|
+
flexmock(channel_mock).should_receive(:exchange_apdu).
|
76
|
+
with([0x84, 0x82, 0x00, 0x00, 0x10, 0x00, 0x65, 0x07, 0x37, 0xD4, 0xB8,
|
77
|
+
0xDF, 0xDE, 0xD0, 0x7B, 0xAA, 0xA2, 0xDE, 0xDE, 0x82, 0x8B, 0x00]).
|
78
|
+
and_return([0x90, 0x00])
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_secure_channel
|
82
|
+
mock = MixinWrapper.new
|
83
|
+
mock_channel_setup mock
|
84
|
+
mock_channel_lock mock
|
85
|
+
|
86
|
+
mock.secure_channel
|
87
|
+
end
|
88
|
+
|
89
|
+
def mock_get_status_files_modules(channel_mock)
|
90
|
+
flexmock(channel_mock).should_receive(:exchange_apdu).
|
91
|
+
with([0x80, 0xF2, 0x10, 0x00, 0x02, 0x4F, 0x00, 0x00]).
|
92
|
+
and_return([0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x53, 0x50, 0x01, 0x00,
|
93
|
+
0x01, 0x08, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x53, 0x50, 0x41,
|
94
|
+
0x63, 0x10])
|
95
|
+
flexmock(channel_mock).should_receive(:exchange_apdu).
|
96
|
+
with([0x80, 0xF2, 0x10, 0x01, 0x02, 0x4F, 0x00, 0x00]).
|
97
|
+
and_return([0x07, 0x19, 0x83, 0x12, 0x29, 0x10, 0xFA, 0xCE, 0x01, 0x00,
|
98
|
+
0x01, 0x07, 0x19, 0x83, 0x12, 0x29, 0x10, 0xBA, 0xBE, 0x90,
|
99
|
+
0x00])
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_gp_get_status
|
103
|
+
mock = MixinWrapper.new
|
104
|
+
flexmock(mock).should_receive(:exchange_apdu).once.
|
105
|
+
with([0x80, 0xF2, 0x80, 0x00, 0x02, 0x4F, 0x00, 0x00]).
|
106
|
+
and_return([0x08, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01,
|
107
|
+
0x9E, 0x90, 0x00])
|
108
|
+
golden = [{ :lifecycle => :op_ready, :aid => [0xA0, 0, 0, 0, 3, 0, 0, 0],
|
109
|
+
:permissions => Set.new([:cvm_management, :card_reset, :card_terminate,
|
110
|
+
:card_lock, :security_domain]) }]
|
111
|
+
assert_equal golden, mock.gp_get_status(:issuer_sd),
|
112
|
+
'Issuer security domain'
|
113
|
+
|
114
|
+
mock = MixinWrapper.new
|
115
|
+
mock_get_status_files_modules mock
|
116
|
+
golden = [
|
117
|
+
{ :aid => [0xA0, 0x00, 0x00, 0x00, 0x03, 0x53, 0x50],
|
118
|
+
:permissions => Set.new, :lifecycle => :loaded,
|
119
|
+
:modules => [
|
120
|
+
{:aid => [0xA0, 0x00, 0x00, 0x00, 0x03, 0x53, 0x50, 0x41]}]},
|
121
|
+
{ :aid => [0x19, 0x83, 0x12, 0x29, 0x10, 0xFA, 0xCE],
|
122
|
+
:permissions => Set.new, :lifecycle => :loaded,
|
123
|
+
:modules => [{:aid => [0x19, 0x83, 0x12, 0x29, 0x10, 0xBA, 0xBE]}]},
|
124
|
+
]
|
125
|
+
|
126
|
+
assert_equal golden, mock.gp_get_status(:files_modules),
|
127
|
+
'Executable load files and modules'
|
128
|
+
end
|
129
|
+
|
130
|
+
def test_gp_applications
|
131
|
+
mock = MixinWrapper.new
|
132
|
+
mock_card_manager_select mock
|
133
|
+
mock_channel_setup mock
|
134
|
+
mock_channel_lock mock
|
135
|
+
|
136
|
+
flexmock(mock).should_receive(:exchange_apdu).once.
|
137
|
+
with([0x80, 0xF2, 0x40, 0x00, 0x02, 0x4F, 0x00, 0x00]).
|
138
|
+
and_return([0x07, 0x19, 0x83, 0x12, 0x29, 0x10, 0xBA, 0xBE, 0x07, 0x00,
|
139
|
+
0x90, 0x00])
|
140
|
+
golden = [{ :aid => @app_aid,
|
141
|
+
:permissions => Set.new, :lifecycle => :selectable }]
|
142
|
+
assert_equal golden, mock.applications
|
143
|
+
end
|
144
|
+
|
145
|
+
def mock_delete_file(channel_mock)
|
146
|
+
flexmock(channel_mock).should_receive(:exchange_apdu).
|
147
|
+
with([0x80, 0xE4, 0x00, 0x80, 0x09, 0x4F, 0x07, 0x19, 0x83, 0x12, 0x29,
|
148
|
+
0x10, 0xFA, 0xCE, 0x00]).and_return([0x00, 0x90, 0x00])
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_gp_delete_file
|
152
|
+
mock = MixinWrapper.new
|
153
|
+
mock_delete_file mock
|
154
|
+
assert_equal [], mock.gp_delete_file(@file_aid)
|
155
|
+
end
|
156
|
+
|
157
|
+
def test_delete_application
|
158
|
+
mock = MixinWrapper.new
|
159
|
+
mock_card_manager_select mock
|
160
|
+
mock_channel_setup mock
|
161
|
+
mock_channel_lock mock
|
162
|
+
mock_get_status_files_modules mock
|
163
|
+
mock_delete_file mock
|
164
|
+
|
165
|
+
assert mock.delete_application([0x19, 0x83, 0x12, 0x29, 0x10, 0xBA, 0xBE])
|
166
|
+
end
|
167
|
+
|
168
|
+
def test_gp_install_load
|
169
|
+
mock = MixinWrapper.new
|
170
|
+
flexmock(mock).should_receive(:exchange_apdu).
|
171
|
+
with([0x80, 0xE6, 0x02, 0x00, 0x14, 0x07, 0x19, 0x83, 0x12, 0x29, 0x10,
|
172
|
+
0xFA, 0xCE, 0x08, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
|
173
|
+
0x00, 0x00, 0x00, 0x00]).and_return([0x00, 0x90, 0x00])
|
174
|
+
assert mock.gp_install_load(@file_aid, mock.gp_card_manager_aid)
|
175
|
+
end
|
176
|
+
|
177
|
+
def test_gp_install_install
|
178
|
+
mock = MixinWrapper.new
|
179
|
+
flexmock(mock).should_receive(:exchange_apdu).
|
180
|
+
with([0x80, 0xE6, 0x0C, 0x00, 0x1E, 0x07, 0x19, 0x83, 0x12, 0x29, 0x10,
|
181
|
+
0xFA, 0xCE, 0x07, 0x19, 0x83, 0x12, 0x29, 0x10, 0xBA, 0xBE, 0x07,
|
182
|
+
0x19, 0x83, 0x12, 0x29, 0x10, 0xBA, 0xBE, 0x01, 0x80, 0x02, 0xC9,
|
183
|
+
0x00, 0x00, 0x00]).and_return([0x00, 0x90, 0x00])
|
184
|
+
assert mock.gp_install_selectable(@file_aid, @app_aid, @app_aid,
|
185
|
+
[:security_domain])
|
186
|
+
end
|
187
|
+
|
188
|
+
def test_gp_load_file
|
189
|
+
mock = MixinWrapper.new
|
190
|
+
flexmock(mock).should_receive(:exchange_apdu).
|
191
|
+
with([0x80, 0xE8, 0x00, 0x00, 0x0A, 0xC4, 0x17, 0x11, 0x22, 0x33, 0x44,
|
192
|
+
0x55, 0x66, 0x77, 0x88, 0x00]).and_return([0x00, 0x90, 0x00])
|
27
193
|
flexmock(mock).should_receive(:exchange_apdu).
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
194
|
+
with([0x80, 0xE8, 0x00, 0x01, 0x0A, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE,
|
195
|
+
0xFF, 0x21, 0x32, 0x43, 0x00]).and_return([0x00, 0x90, 0x00])
|
196
|
+
flexmock(mock).should_receive(:exchange_apdu).
|
197
|
+
with([0x80, 0xE8, 0x80, 0x02, 0x05, 0x54, 0x65, 0x76, 0x87, 0x98, 0x00]).
|
198
|
+
and_return([0x00, 0x90, 0x00])
|
199
|
+
assert mock.gp_load_file @file_data, @max_apdu_length
|
200
|
+
end
|
201
|
+
|
202
|
+
def test_install_applet_live
|
203
|
+
# Establish transport to live card.
|
204
|
+
transport = Smartcard::Iso.auto_transport
|
205
|
+
class <<transport
|
206
|
+
include GpCardMixin
|
207
|
+
end
|
208
|
+
|
209
|
+
# Install applet.
|
210
|
+
applet_aid = [0x19, 0x83, 0x12, 0x29, 0x10, 0xDE, 0xAD]
|
211
|
+
cap_file = File.join File.dirname(__FILE__), 'hello.cap'
|
212
|
+
transport.install_applet cap_file,
|
213
|
+
[0x19, 0x83, 0x12, 0x29, 0x10, 0xDE, 0xAE]
|
214
|
+
|
215
|
+
# Ensure applet works.
|
216
|
+
transport.select_application applet_aid
|
217
|
+
assert_equal "Hello!", transport.iso_apdu!(:ins => 0x00).pack('C*')
|
218
|
+
|
219
|
+
# Uninstall applet.
|
220
|
+
transport.delete_application applet_aid
|
32
221
|
end
|
33
222
|
end
|