smartcard 0.4.1 → 0.4.2

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