smartcard 0.4.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,5 @@
1
+ v0.4.2. GlobalPlatform applet instalation.
2
+
1
3
  v0.4.1. Workaround ATR resend bug in JCOP simulator 3.2.7.
2
4
 
3
5
  v0.4.0. High-level functionality for ISO7816 cards.
data/Manifest CHANGED
@@ -1,5 +1,9 @@
1
1
  BUILD
2
2
  CHANGELOG
3
+ LICENSE
4
+ Manifest
5
+ README
6
+ Rakefile
3
7
  ext/smartcard_pcsc/extconf.rb
4
8
  ext/smartcard_pcsc/pcsc.h
5
9
  ext/smartcard_pcsc/pcsc_card.c
@@ -13,6 +17,10 @@ ext/smartcard_pcsc/pcsc_namespace.c
13
17
  ext/smartcard_pcsc/pcsc_reader_states.c
14
18
  ext/smartcard_pcsc/pcsc_surrogate_reader.h
15
19
  ext/smartcard_pcsc/pcsc_surrogate_wintypes.h
20
+ lib/smartcard.rb
21
+ lib/smartcard/gp/asn1_ber.rb
22
+ lib/smartcard/gp/cap_loader.rb
23
+ lib/smartcard/gp/des.rb
16
24
  lib/smartcard/gp/gp_card_mixin.rb
17
25
  lib/smartcard/iso/auto_configurator.rb
18
26
  lib/smartcard/iso/iso_card_mixin.rb
@@ -22,13 +30,12 @@ lib/smartcard/iso/jcop_remote_transport.rb
22
30
  lib/smartcard/iso/pcsc_transport.rb
23
31
  lib/smartcard/iso/transport.rb
24
32
  lib/smartcard/pcsc/pcsc_exception.rb
25
- lib/smartcard.rb
26
- LICENSE
27
- Manifest
28
- Rakefile
29
- README
30
- smartcard.gemspec
33
+ test/gp/asn1_ber_test.rb
34
+ test/gp/cap_loader_test.rb
35
+ test/gp/des_test.rb
31
36
  test/gp/gp_card_mixin_test.rb
37
+ test/gp/hello.apdu
38
+ test/gp/hello.cap
32
39
  test/iso/auto_configurator_test.rb
33
40
  test/iso/iso_card_mixin_test.rb
34
41
  test/iso/jcop_remote_test.rb
data/Rakefile CHANGED
@@ -10,13 +10,13 @@ Echoe.new('smartcard') do |p|
10
10
  p.summary = 'Interface with ISO 7816 smart cards.'
11
11
  p.url = 'http://www.costan.us/smartcard'
12
12
 
13
- p.need_tar_gz = !Platform.windows?
14
- p.need_zip = !Platform.windows?
13
+ p.need_tar_gz = !Gem.win_platform?
14
+ p.need_zip = !Gem.win_platform?
15
15
  p.clean_pattern += ['ext/**/*.manifest', 'ext/**/*_autogen.h']
16
16
  p.rdoc_pattern = /^(lib|bin|tasks|ext)|^BUILD|^README|^CHANGELOG|^TODO|^LICENSE|^COPYING$/
17
17
 
18
18
  p.eval = proc do |p|
19
- if Platform.windows?
19
+ if Gem.win_platform?
20
20
  p.files += ['lib/smartcard/pcsc.so']
21
21
  p.platform = Gem::Platform::CURRENT
22
22
 
data/lib/smartcard.rb CHANGED
@@ -12,4 +12,7 @@ require 'smartcard/iso/transport.rb'
12
12
  require 'smartcard/iso/auto_configurator.rb'
13
13
 
14
14
 
15
+ require 'smartcard/gp/asn1_ber.rb'
16
+ require 'smartcard/gp/cap_loader.rb'
17
+ require 'smartcard/gp/des.rb'
15
18
  require 'smartcard/gp/gp_card_mixin.rb'
@@ -0,0 +1,199 @@
1
+ # Encoding and decoding of ASN.1-BER data.
2
+ #
3
+ # Author:: Victor Costan
4
+ # Copyright:: Copyright (C) 2009 Massachusetts Institute of Technology
5
+ # License:: MIT
6
+
7
+ # :nodoc: namespace
8
+ module Smartcard::Gp
9
+
10
+
11
+ # Logic for encoding and decoding ASN.1-BER data as specified in X.690-0207.
12
+ module Asn1Ber
13
+ # Decodes a TLV tag (the data type).
14
+ #
15
+ # Args:
16
+ # data:: the array to decode from
17
+ # offset:: the position of the first byte containing the tag
18
+ #
19
+ # Returns the offset of the first byte after the tag, and the tag information.
20
+ # Tag information is a hash with the following keys.
21
+ # :class:: the tag's class (symbol, named after X690-0207)
22
+ # :primitive:: if +false+, the tag's value is a sequence of TLVs
23
+ # :number:: the tag's number
24
+ def self.decode_tag(data, offset)
25
+ class_bits = data[offset] >> 6
26
+ tag_class = [:universal, :application, :context, :private][class_bits]
27
+ tag_primitive = (data[offset] & 0x20) == 0
28
+ tag_number = (data[offset] & 0x1F)
29
+ if tag_number == 0x1F
30
+ tag_number = 0
31
+ loop do
32
+ offset += 1
33
+ tag_number <<= 7
34
+ tag_number |= (data[offset] & 0x7F)
35
+ break if (data[offset] & 0x80) == 0
36
+ end
37
+ end
38
+ return (offset + 1), { :class => tag_class, :primitive => tag_primitive,
39
+ :number => tag_number }
40
+ end
41
+
42
+ # Decodes a TLV length.
43
+ #
44
+ # Args:
45
+ # data:: the array to decode from
46
+ # offset:: the position of the first byte containing the length
47
+ #
48
+ # Returns the offset of the first byte after the length, and the length. The
49
+ # returned value might be +:indefinite+ if the encoding uses the indefinite
50
+ # length.
51
+ def self.decode_length(data, offset)
52
+ return (offset + 1), data[offset] if (data[offset] & 0x80) == 0
53
+ len_bytes = (data[offset] & 0x7F)
54
+ return (offset + 1), :indefinite if len_bytes == 0
55
+ length = 0
56
+ len_bytes.times do
57
+ offset += 1
58
+ length = (length << 8) | data[offset]
59
+ end
60
+ return (offset + 1), length
61
+ end
62
+
63
+
64
+ # Decodes a TLV value.
65
+ #
66
+ # Args:
67
+ # data:: the array to decode from
68
+ # offset:: the position of the first byte containing the length
69
+ #
70
+ # Returns the offset of the first byte after the value, and the value.
71
+ def self.decode_value(data, offset, length)
72
+ return offset + length, data[offset, length] unless length == :indefinite
73
+
74
+ length = 0
75
+ loop do
76
+ raise 'Unterminated data' if offset + length + 2 > data.length
77
+ break if data[offset + length, 2] == [0, 0]
78
+ length += 1
79
+ end
80
+ return (offset + length + 2), data[offset, length]
81
+ end
82
+
83
+ # Maps a TLV value with a known tag to a Ruby data type.
84
+ def self.map_value(value, tag)
85
+ # TODO(costan): map primitive types if necessary
86
+ value
87
+ end
88
+
89
+ # Decodes a TLV (tag-length-value).
90
+ #
91
+ # Returns a hash that contains tag and value information. See decode_tag for
92
+ # the keys containing the tag information. Value information is contained in
93
+ # the :value: tag.
94
+ def self.decode_tlv(data, offset)
95
+ offset, tag = decode_tag data, offset
96
+ offset, length = decode_length data, offset
97
+ offset, value = decode_value data, offset, length
98
+
99
+ tag[:value] = tag[:primitive] ? map_value(value, tag) : decode(value)
100
+ return offset, tag
101
+ end
102
+
103
+ # Decodes a sequence of TLVs (tag-length-value).
104
+ #
105
+ # Returns an array with one element for each TLV in the sequence. See
106
+ # decode_tlv for the format of each array element.
107
+ def self.decode(data, offset = 0, length = data.length - offset)
108
+ sequence = []
109
+ loop do
110
+ break if offset >= length
111
+ offset, tlv = decode_tlv data, offset
112
+ sequence << tlv
113
+ end
114
+ sequence
115
+ end
116
+
117
+ # Encodes a TLV tag (the data type).
118
+ #
119
+ # Args:
120
+ # tag:: a hash with the keys produced by decode_tag.
121
+ #
122
+ # Returns an array of byte values.
123
+ def self.encode_tag(tag)
124
+ tag_classes = { :universal => 0, :application => 1, :context => 2,
125
+ :private => 3 }
126
+ tag_lead = (tag_classes[tag[:class]] << 6) | (tag[:primitive] ? 0x00 : 0x20)
127
+ return [tag_lead | tag[:number]] if tag[:number] < 0x1F
128
+
129
+ number_bytes, number = [], tag[:number]
130
+ first = true
131
+ while number != 0
132
+ byte = (number & 0x7F)
133
+ number >>= 7
134
+ byte |= 0x80 unless first
135
+ first = false
136
+ number_bytes << byte
137
+ end
138
+ [tag_lead | 0x1F] + number_bytes.reverse
139
+ end
140
+
141
+ # Encodes a TLV length (the length of the data).
142
+ #
143
+ # Args::
144
+ # length:: the length to be encoded (number of :indefinite)
145
+ #
146
+ # Returns an array of byte values.
147
+ def self.encode_length(length)
148
+ return [0x80] if length == :indefinite
149
+ return [length] if length < 0x80
150
+ length_bytes = []
151
+ while length > 0
152
+ length_bytes << (length & 0xFF)
153
+ length >>= 8
154
+ end
155
+ [0x80 | length_bytes.length] + length_bytes.reverse
156
+ end
157
+
158
+ # Encodes a TLV (tag-length-value).
159
+ #
160
+ # Args::
161
+ # tlv:: hash with tag and value information, to be encoeded as TLV; see
162
+ # decode_tlv for the hash keys encoding the tag and value
163
+ #
164
+ # Returns an array of byte values.
165
+ def self.encode_tlv(tlv)
166
+ value = tlv[:primitive] ? tlv[:value] : encode(tlv[:value])
167
+ [encode_tag(tlv), encode_length(value.length), value].flatten
168
+ end
169
+
170
+ # Encodes a sequence of TLVs (tag-length-value).
171
+ #
172
+ # Args::
173
+ # tlvs:: an array of hashes to be encoded as TLV
174
+ #
175
+ # Returns an array of byte values.
176
+ def self.encode(tlvs)
177
+ tlvs.map { |tlv| encode_tlv tlv }.flatten
178
+ end
179
+
180
+ # Visitor pattern for decoded TLVs.
181
+ #
182
+ # Args:
183
+ # tlvs:: the TLVs to visit
184
+ # tag_path:: internal, do not use
185
+ #
186
+ # Yields: |tag_path, value| tag_path lists the numeric tags for the current
187
+ # value's tag, and all the parents' tags.
188
+ def self.visit(tlvs, tag_path = [], &block)
189
+ tlvs.each do |tlv|
190
+ tag_number = encode_tag(tlv).inject { |acc, v| (acc << 8) | v }
191
+ new_tag_path = tag_path + [tag_number]
192
+ yield new_tag_path, tlv[:value]
193
+ next if tlv[:primitive]
194
+ visit tlv[:value], new_tag_path, &block
195
+ end
196
+ end
197
+ end # module Smartcard::Gp::Asn1Ber
198
+
199
+ end # namespace
@@ -0,0 +1,88 @@
1
+ # Loads JavaCard CAP files.
2
+ #
3
+ # Author:: Victor Costan
4
+ # Copyright:: Copyright (C) 2009 Massachusetts Institute of Technology
5
+ # License:: MIT
6
+
7
+ require 'zip/zip'
8
+
9
+ # :nodoc: namespace
10
+ module Smartcard::Gp
11
+
12
+
13
+ # Logic for loading JavaCard CAP files.
14
+ module CapLoader
15
+ # Loads a CAP file.
16
+ #
17
+ # Returns a hash mapping component names to component data.
18
+ def self.load_cap(cap_file)
19
+ components = {}
20
+ Zip::ZipFile.open(cap_file) do |file|
21
+ file.each do |entry|
22
+ data = entry.get_input_stream { |io| io.read }
23
+ offset = 0
24
+ while offset < data.length
25
+ tag = TAG_NAMES[data[offset, 1].unpack('C').first]
26
+ length = data[offset + 1, 2].unpack('n').first
27
+ value = data[offset + 3, length]
28
+ components[tag] = value
29
+ offset += 3 + length
30
+ end
31
+ end
32
+ end
33
+ components
34
+ end
35
+
36
+ # Serializes CAP components for on-card loading.
37
+ #
38
+ # Returns an array of bytes.
39
+ def self.serialize_components(components)
40
+ [:header, :directory, :import, :applet, :class, :method, :static_field,
41
+ :export, :constant_pool, :reference_location].map { |name|
42
+ tag = TAG_NAMES.keys.find { |k| TAG_NAMES[k] == name }
43
+ if components[name]
44
+ length = [components[name].length].pack('n').unpack('C*')
45
+ data = components[name].unpack('C*')
46
+ [tag, length, data]
47
+ else
48
+ []
49
+ end
50
+ }.flatten
51
+ end
52
+
53
+ # Parses the Applet section in a CAP file, obtaining applet AIDs.
54
+ #
55
+ # Returns an array of hashes, one hash per applet. The hash has a key +:aid+
56
+ # that contains the applet's AID.
57
+ def self.parse_applets(components)
58
+ applets = []
59
+ return applets unless section = components[:applet]
60
+ offset = 1
61
+ section[0].times do
62
+ aid_length = section[offset]
63
+ install_method = section[offset + 1 + aid_length, 2].unpack('n').first
64
+ applets << { :aid => section[offset + 1, aid_length].unpack('C*'),
65
+ :install_method => install_method }
66
+ offset += 3 + aid_length
67
+ end
68
+ applets
69
+ end
70
+
71
+ # Loads a CAP file and serializes its components for on-card loading.
72
+ #
73
+ # Returns an array of bytes.
74
+ def self.cap_load_data(cap_file)
75
+ components = load_cap cap_file
76
+ { :data => serialize_components(components),
77
+ :applets => parse_applets(components) }
78
+ end
79
+
80
+ # Maps numeric tags to tag names.
81
+ TAG_NAMES = {
82
+ 1 => :header, 2 => :directory, 3 => :applet, 4 => :import,
83
+ 5 => :constant_pool, 6 => :class, 7 => :method, 8 => :static_field,
84
+ 9 => :reference_location, 10 => :export, 11 => :descriptor, 12 => :debug
85
+ }
86
+ end # module Smartcard::Gp::CapLoader
87
+
88
+ end # namespace
@@ -0,0 +1,68 @@
1
+ # DES and 3DES encryption and MAC logic for GlobalPlatform secure channels.
2
+ #
3
+ # Author:: Victor Costan
4
+ # Copyright:: Copyright (C) 2009 Massachusetts Institute of Technology
5
+ # License:: MIT
6
+
7
+ require 'openssl'
8
+
9
+ # :nodoc: namespace
10
+ module Smartcard::Gp
11
+
12
+
13
+ # DES and 3DES encryption and MAC logic for GlobalPlatform secure channels.
14
+ module Des
15
+ # Generates random bytes for session nonces.
16
+ #
17
+ # Args:
18
+ # bytes:: how many bytes are desired
19
+ #
20
+ # Returns a string of random bytes.
21
+ def self.random_bytes(bytes)
22
+ OpenSSL::Random.random_bytes bytes
23
+ end
24
+
25
+ # Perform DES or 3DES encryption.
26
+ #
27
+ # Args:
28
+ # key:: the encryption key to be used (8-byte or 16-byte)
29
+ # data:: the data to be encrypted or decrypted
30
+ # iv:: initialization vector
31
+ # decrypt:: if +false+ performs encryption, otherwise performs decryption
32
+ #
33
+ # Returns the encrypted / decrypted data.
34
+ def self.crypt(key, data, iv = nil, decrypt = false)
35
+ cipher_name = key.length == 8 ? 'DES-CBC' : 'DES-EDE-CBC'
36
+ cipher = OpenSSL::Cipher::Cipher.new cipher_name
37
+ decrypt ? cipher.decrypt : cipher.encrypt
38
+ cipher.key = key
39
+ cipher.iv = iv || ("\x00" * 8)
40
+ cipher.padding = 0
41
+ crypted = cipher.update data
42
+ crypted += cipher.final
43
+ crypted
44
+ end
45
+
46
+ # Computes a MAC using DES mixed with 3DES.
47
+ def self.mac_retail(key, data, iv = nil)
48
+ # Output transformation: add 80, then 00 until it's block-sized.
49
+ data = data + "\x80"
50
+ data += "\x00" * (8 - data.length % 8) unless data.length % 8 == 0
51
+
52
+ # DES-encrypt everything except for the last block.
53
+ iv = crypt(key[0, 8], data[0, data.length - 8], iv)[-8, 8]
54
+ # Take the chained block and supply it to a 3DES-encryption.
55
+ crypt(key, data[-8, 8], iv)
56
+ end
57
+
58
+ def self.mac_3des(key, data)
59
+ # Output transformation: add 80, then 00 until it's block-sized.
60
+ data = data + "\x80"
61
+ data += "\x00" * (8 - data.length % 8) unless data.length % 8 == 0
62
+
63
+ # The MAC is the last block from 3DES-encrypting the data.
64
+ crypt(key, data)[-8, 8]
65
+ end
66
+ end # module Smartcard::Gp::Des
67
+
68
+ end # namespace
@@ -4,24 +4,383 @@
4
4
  # Copyright:: Copyright (C) 2009 Massachusetts Institute of Technology
5
5
  # License:: MIT
6
6
 
7
+ require 'set'
8
+
7
9
  # :nodoc: namespace
8
10
  module Smartcard::Gp
9
11
 
10
12
 
13
+ # Module intended to be mixed into transport implementations to add commands for
14
+ # talking to GlobalPlatform smart-cards.
15
+ #
16
+ # The module talks to the card exclusively via methods in
17
+ # Smartcard::Iso::IsoCardMixin, so the transport requirements are the same as
18
+ # for that module.
11
19
  module GpCardMixin
12
20
  include Smartcard::Iso::IsoCardMixin
13
21
 
14
22
  # Selects a GlobalPlatform application.
15
23
  def select_application(app_id)
16
- iso_apdu! :ins => 0xA4, :p1 => 0x04, :p2 => 0x00, :data => app_id
24
+ ber_data = iso_apdu! :ins => 0xA4, :p1 => 0x04, :p2 => 0x00, :data => app_id
25
+ app_tags = Asn1Ber.decode ber_data
26
+ app_data = {}
27
+ Asn1Ber.visit app_tags do |path, value|
28
+ case path
29
+ when [0x6F, 0xA5, 0x9F65]
30
+ app_data[:max_apdu_length] = value.inject(0) { |acc, v| (acc << 8) | v }
31
+ when [0x6F, 0x84]
32
+ app_data[:aid] = value
33
+ end
34
+ end
35
+ app_data
36
+ end
37
+
38
+ # The default application ID of the GlobalPlatform card manager.
39
+ def gp_card_manager_aid
40
+ [0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00]
41
+ end
42
+
43
+ # Issues a GlobalPlatform INITIALIZE UPDATE command.
44
+ #
45
+ # This should not be called directly. Call secure_session insteaad.
46
+ #
47
+ # Args:
48
+ # host_challenge:: 8-byte array with a unique challenge for the session
49
+ # key_version:: the key in the Security domain to be used (0 = any key)
50
+ #
51
+ # Returns a hash containing the command's parsed response. The keys are:
52
+ # :key_diversification:: key diversification data
53
+ # :key_version:: the key in the Security domain chosen to be used
54
+ # :protocol_id:: numeric ID for the secure protocol to be used
55
+ # :counter:: counter for creating session keys
56
+ # :challenge:: the card's 6-byte challenge
57
+ # :auth:: the card's 8-byte authentication value
58
+ def gp_setup_secure_channel(host_challenge, key_version = 0)
59
+ raw = iso_apdu! :cla => 0x80, :ins => 0x50, :p1 => key_version, :p2 => 0,
60
+ :data => host_challenge
61
+ response = {
62
+ :key_diversification => raw[0, 10],
63
+ :key_version => raw[10], :protocol_id => raw[11],
64
+ :counter => raw[12, 2].pack('C*').unpack('n').first,
65
+ :challenge => raw[14, 6], :auth => raw[20, 8]
66
+ }
67
+ end
68
+
69
+ # Wrapper around iso_apdu! that adds a MAC to the APDU.
70
+ def gp_signed_apdu!(apdu_data)
71
+ apdu_data = apdu_data.dup
72
+ apdu_data[:cla] = (apdu_data[:cla] || 0) | 0x04
73
+ apdu_data[:data] = (apdu_data[:data] || []) + [0, 0, 0, 0, 0, 0, 0, 0]
74
+
75
+ apdu_bytes = Smartcard::Iso::IsoCardMixin.serialize_apdu(apdu_data)[0...-9]
76
+ mac = Des.mac_retail @gp_secure_channel_keys[:cmac], apdu_bytes.pack('C*'),
77
+ @gp_secure_channel_keys[:mac_iv]
78
+ @gp_secure_channel_keys[:mac_iv] = mac
79
+
80
+ apdu_data[:data][apdu_data[:data].length - 8, 8] = mac.unpack('C*')
81
+ iso_apdu! apdu_data
82
+ end
83
+
84
+ # Issues a GlobalPlatform EXTERNAL AUTHENTICATE command.
85
+ #
86
+ # This should not be called directly. Call secure_session insteaad.
87
+ #
88
+ # Args:
89
+ # host_auth:: 8-byte host authentication value
90
+ # security:: array of desired security flags (leave empty for the default
91
+ # of no security)
92
+ #
93
+ # The return value is irrelevant. The card will fire an ISO exception if the
94
+ # authentication doesn't work out.
95
+ def gp_lock_secure_channel(host_auth, security = [])
96
+ security_level = 0
97
+ security_flags = { :command_mac => 0x01, :response_mac => 0x10,
98
+ :command_encryption => 0x02 }
99
+ security.each do |flag|
100
+ security_level |= security_flags[flag]
101
+ end
102
+ gp_signed_apdu! :cla => 0x80, :ins => 0x82, :p1 => security_level, :p2 => 0,
103
+ :data => host_auth
104
+ end
105
+
106
+ # Sets up a secure session with the current GlobalPlatform application.
107
+ #
108
+ # Args:
109
+ # keys:: hash containing 3 3DES encryption keys, identified by the following
110
+ # keys:
111
+ # :senc:: channel encryption key
112
+ # :smac:: channel MAC key
113
+ # :dek:: data encryption key
114
+ def secure_channel(keys = gp_development_keys)
115
+ host_challenge = Des.random_bytes 8
116
+ card_info = gp_setup_secure_channel host_challenge.unpack('C*')
117
+ card_counter = [card_info[:counter]].pack('n')
118
+ card_challenge = card_info[:challenge].pack('C*')
119
+
120
+ # Compute session keys.
121
+ session_keys = {}
122
+ derivation_data = "\x01\x01" + card_counter + "\x00" * 12
123
+ session_keys[:cmac] = Des.crypt keys[:smac], derivation_data
124
+ derivation_data[0, 2] = "\x01\x02"
125
+ session_keys[:rmac] = Des.crypt keys[:smac], derivation_data
126
+ derivation_data[0, 2] = "\x01\x82"
127
+ session_keys[:senc] = Des.crypt keys[:senc], derivation_data
128
+ derivation_data[0, 2] = "\x01\x81"
129
+ session_keys[:dek] = Des.crypt keys[:dek], derivation_data
130
+ session_keys[:mac_iv] = "\x00" * 8
131
+ @gp_secure_channel_keys = session_keys
132
+
133
+ # Compute authentication cryptograms.
134
+ card_auth = Des.mac_3des session_keys[:senc],
135
+ host_challenge + card_counter + card_challenge
136
+ host_auth = Des.mac_3des session_keys[:senc],
137
+ card_counter + card_challenge + host_challenge
138
+
139
+ unless card_auth == card_info[:auth].pack('C*')
140
+ raise 'Card authentication invalid'
141
+ end
142
+
143
+ gp_lock_secure_channel host_auth.unpack('C*')
144
+ end
145
+
146
+ # Secure channel keys for development GlobalPlatform cards.
147
+ #
148
+ # Most importantly, the JCOP cards and simulator work with these keys.
149
+ def gp_development_keys
150
+ key = (0x40..0x4F).to_a.pack('C*')
151
+ { :senc => key, :smac => key, :dek => key }
152
+ end
153
+
154
+ # Issues a GlobalPlatform GET STATUS command.
155
+ #
156
+ # Args:
157
+ # scope:: the information to be retrieved from the card, can be:
158
+ # :issuer_sd:: the issuer's security domain
159
+ # :apps:: applications and supplementary security domains
160
+ # :files:: executable load files
161
+ # :files_modules:: executable load files and executable modules
162
+ # query_aid:: the AID to look for (empty array to get everything)
163
+ #
164
+ # Returns an array of application information data. Each element represents an
165
+ # application, and is a hash with the following keys:
166
+ # :aid:: the application or file's AID
167
+ # :lifecycle:: the state in the application's lifecycle (symbol)
168
+ # :permissions:: a Set of the application's permissions (symbols)
169
+ # :modules:: array of modules in an executable load file, each array element
170
+ # is a hash with the key :aid which has the module's AID
171
+ def gp_get_status(scope, query_aid = [])
172
+ scope_byte = { :issuer_sd => 0x80, :apps => 0x40, :files => 0x20,
173
+ :files_modules => 0x10 }[scope]
174
+ data = Asn1Ber.encode [{:class => :application, :primitive => true,
175
+ :number => 0x0F, :value => query_aid}]
176
+ apps = []
177
+ first = true # Set to false after the first GET STATUS is issued.
178
+ loop do
179
+ raw = iso_apdu :cla => 0x80, :ins => 0xF2, :p1 => scope_byte,
180
+ :p2 => (first ? 0 : 1), :data => [0x4F, 0x00]
181
+ if raw[:status] != 0x9000 && raw[:status] != 0x6310
182
+ Smartcard::Iso::IsoCardMixin.raise_response_exception raw
183
+ end
184
+
185
+ offset = 0
186
+ loop do
187
+ break if offset >= raw[:data].length
188
+ aid_length, offset = raw[:data][offset], offset + 1
189
+ app = { :aid => raw[:data][offset, aid_length] }
190
+ offset += aid_length
191
+
192
+ if scope == :issuer_sd
193
+ lc_states = { 1 => :op_ready, 7 => :initialized, 0x0F => :secured,
194
+ 0x7F => :card_locked, 0xFF => :terminated }
195
+ lc_mask = 0xFF
196
+ else
197
+ lc_states = { 1 => :loaded, 3 => :installed, 7 => :selectable,
198
+ 0x83 => :locked, 0x87 => :locked }
199
+ lc_mask = 0x87
200
+ end
201
+ app[:lifecycle] = lc_states[raw[:data][offset] & lc_mask]
202
+
203
+ permission_bits = raw[:data][offset + 1]
204
+ app[:permissions] = Set.new()
205
+ [[1, :mandated_dap], [2, :cvm_management], [4, :card_reset],
206
+ [8, :card_terminate], [0x10, :card_lock], [0x80, :security_domain],
207
+ [0xA0, :delegate], [0xC0, :dap_verification]].each do |mask, perm|
208
+ app[:permissions] << perm if (permission_bits & mask) == mask
209
+ end
210
+ offset += 2
211
+
212
+ if scope == :files_modules
213
+ num_modules, offset = raw[:data][offset], offset + 1
214
+ app[:modules] = []
215
+ num_modules.times do
216
+ aid_length = raw[:data][offset]
217
+ app[:modules] << { :aid => raw[:data][offset + 1, aid_length] }
218
+ offset += 1 + aid_length
219
+ end
220
+ end
221
+
222
+ apps << app
223
+ end
224
+ break if raw[:status] == 0x9000
225
+ first = false # Need more GET STATUS commands.
226
+ end
227
+ apps
228
+ end
229
+
230
+ # The GlobalPlatform applications available on the card.
231
+ def applications
232
+ select_application gp_card_manager_aid
233
+ secure_channel
234
+ gp_get_status :apps
235
+
236
+ # TODO(costan): there should be a way to query the AIDs without asking the
237
+ # SD, which requires admin keys.
238
+ end
239
+
240
+ # Issues a GlobalPlatform DELETE command targeting an executable load file.
241
+ #
242
+ # Args:
243
+ # aid:: the executable load file's AID
244
+ #
245
+ # The return value is irrelevant.
246
+ def gp_delete_file(aid)
247
+ data = Asn1Ber.encode [{:class => :application, :primitive => true,
248
+ :number => 0x0F, :value => aid}]
249
+ response = iso_apdu! :cla => 0x80, :ins => 0xE4, :p1 => 0x00, :p2 => 0x80,
250
+ :data => data
251
+ delete_confirmation = response[1, response[0]]
252
+ delete_confirmation
253
+ end
254
+
255
+ # Deletes a GlobalPlatform application.
256
+ #
257
+ # Returns +false+ if the application was not found on the card, or a true
258
+ # value if the application was deleted.
259
+ def delete_application(application_aid)
260
+ select_application gp_card_manager_aid
261
+ secure_channel
262
+
263
+ files = gp_get_status :files_modules
264
+ app_file_aid = nil
265
+ files.each do |file|
266
+ next unless modules = file[:modules]
267
+ next unless modules.any? { |m| m[:aid] == application_aid }
268
+ gp_delete_file file[:aid]
269
+ app_file_aid = file[:aid]
270
+ end
271
+ app_file_aid
272
+ end
273
+
274
+ # Issues a GlobalPlatform INSTALL command that loads an application's file.
275
+ #
276
+ # The command should be followed by a LOAD command (see gp_load).
277
+ #
278
+ # Args:
279
+ # file_aid:: the AID of the file to be loaded
280
+ # sd_aid:: the AID of the security domain handling the loading
281
+ # data_hash::
282
+ # params::
283
+ # token:: load token (needed by some SDs)
284
+ #
285
+ # Returns a true value if the command returns a valid install confirmation.
286
+ def gp_install_load(file_aid, sd_aid = nil, data_hash = [], params = {},
287
+ token = [])
288
+ ber_params = []
289
+
290
+ data = [file_aid.length, file_aid, sd_aid.length, sd_aid,
291
+ Asn1Ber.encode_length(data_hash.length), data_hash,
292
+ Asn1Ber.encode_length(ber_params.length), ber_params,
293
+ Asn1Ber.encode_length(token.length), token].flatten
294
+ response = iso_apdu! :cla => 0x80, :ins => 0xE6, :p1 => 0x02, :p2 => 0x00,
295
+ :data => data
296
+ response == [0x00]
297
+ end
298
+
299
+ # Issues a GlobalPlatform INSTALL command that installs an application and
300
+ # makes it selectable.
301
+ #
302
+ # Args:
303
+ # file_aid:: the AID of the application's executable load file
304
+ # module_aid:: the AID of the application's module in the load file
305
+ # app_aid:: the application's AID (application will be selectable by it)
306
+ # privileges:: array of application privileges (e.g. :security_domain)
307
+ # params:: application install parameters
308
+ # token:: install token (needed by some SDs)
309
+ #
310
+ # Returns a true value if the command returns a valid install confirmation.
311
+ def gp_install_selectable(file_aid, module_aid, app_aid, privileges = [],
312
+ params = {}, token = [])
313
+ privilege_byte = 0
314
+ privilege_bits = { :mandated_dap => 1, :cvm_management => 2,
315
+ :card_reset => 4, :card_terminate => 8, :card_lock => 0x10,
316
+ :security_domain => 0x80, :delegate => 0xA0, :dap_verification => 0xC0 }
317
+ privileges.each { |privilege| privilege_byte |= privilege_bits[privilege] }
318
+
319
+ param_tags = [{:class => :private, :primitive => true, :number => 9,
320
+ :value => params[:app] || []}]
321
+ ber_params = Asn1Ber.encode(param_tags)
322
+
323
+ data = [file_aid.length, file_aid, module_aid.length, module_aid,
324
+ app_aid.length, app_aid, 1, privilege_byte,
325
+ Asn1Ber.encode_length(ber_params.length), ber_params,
326
+ Asn1Ber.encode_length(token.length), token].flatten
327
+ response = iso_apdu! :cla => 0x80, :ins => 0xE6, :p1 => 0x0C, :p2 => 0x00,
328
+ :data => data
329
+ response == [0x00]
330
+ end
331
+
332
+ # Issues a GlobalPlatform LOAD command.
333
+ #
334
+ # Args:
335
+ # file_data:: the file's data
336
+ # max_apdu_length:: the maximum APDU length, returned from
337
+ # select_application
338
+ #
339
+ # Returns a true value if the loading succeeds.
340
+ def gp_load_file(file_data, max_apdu_length)
341
+ data_tag = { :class => :private, :primitive => true, :number => 4,
342
+ :value => file_data }
343
+ ber_data = Asn1Ber.encode [data_tag]
344
+
345
+ max_data_length = max_apdu_length - 5
346
+ offset = 0
347
+ block_number = 0
348
+ loop do
349
+ block_length = [max_data_length, ber_data.length - offset].min
350
+ last_block = (offset + block_length >= ber_data.length)
351
+ response = iso_apdu! :cla => 0x80, :ins => 0xE8,
352
+ :p1 => (last_block ? 0x80 : 0x00),
353
+ :p2 => block_number,
354
+ :data => ber_data[offset, block_length]
355
+ offset += block_length
356
+ block_number += 1
357
+ break if last_block
358
+ end
359
+ true
17
360
  end
18
361
 
19
362
  # Installs a JavaCard applet on the JavaCard.
20
363
  #
21
- # This would be really, really nice to have. Sadly, it's a far away TBD right
22
- # now.
23
- def install_applet(cap_contents)
24
- raise "Not implemeted; it'd be nice though, right?"
364
+ # Args:
365
+ # cap_file:: path to the applet's CAP file
366
+ # package_aid:: the applet's package AID
367
+ # applet_aid:: the AID used to select the applet; if nil, the first AID
368
+ # in the CAP's Applet section is used (this works pretty well)
369
+ # install_data:: data to be passed to the applet at installation time
370
+ def install_applet(cap_file, package_aid, applet_aid = nil, install_data = [])
371
+ load_data = CapLoader.cap_load_data(cap_file)
372
+ applet_aid ||= load_data[:applets].first[:aid]
373
+
374
+ delete_application applet_aid
375
+
376
+ manager_data = select_application gp_card_manager_aid
377
+ max_apdu = manager_data[:max_apdu_length]
378
+ secure_channel
379
+
380
+ gp_install_load package_aid, gp_card_manager_aid
381
+ gp_load_file load_data[:data], max_apdu
382
+ gp_install_selectable package_aid, applet_aid, applet_aid, [],
383
+ { :app => install_data }
25
384
  end
26
385
  end # module GpCardMixin
27
386