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.
@@ -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
- raise "JavaCard response has error status 0x#{'%04x' % response[:status]}"
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
- # transport.wire_apdu(apdu_data) -> array
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
- # transport.deserialize_response(response) -> hash
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; no-op, because that should have happen when the client
165
- # connected.
166
- send_message socket, :type => 0, :node => 0, :data => [3, 1, 4, 1, 5, 9]
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
- # ATR exchange; the class' bread and butter
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, :data => []
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.1"
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-08-19}
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/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", "lib/smartcard.rb", "LICENSE", "README"]
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", "lib/smartcard.rb", "LICENSE", "Manifest", "Rakefile", "README", "smartcard.gemspec", "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", "tests/ts_pcsc_ext.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
@@ -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
- with([0x00, 0xA4, 0x04, 0x00, 0x05,
29
- 0x19, 0x83, 0x12, 0x29, 0x10]).
30
- and_return([0x90, 0x00])
31
- mock.select_application([0x19, 0x83, 0x12, 0x29, 0x10])
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