Module Smartcard::Gp::GpCardMixin
In: lib/smartcard/gp/gp_card_mixin.rb

Module intended to be mixed into transport implementations to add commands for talking to GlobalPlatform smart-cards.

The module talks to the card exclusively via methods in Smartcard::Iso::IsoCardMixin, so the transport requirements are the same as for that module.

Methods

Included Modules

Smartcard::Iso::IsoCardMixin

Public Instance methods

The GlobalPlatform applications available on the card.

[Source]

     # File lib/smartcard/gp/gp_card_mixin.rb, line 231
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

Deletes a GlobalPlatform application.

Returns false if the application was not found on the card, or a true value if the application was deleted.

[Source]

     # File lib/smartcard/gp/gp_card_mixin.rb, line 259
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

The default application ID of the GlobalPlatform card manager.

[Source]

    # File lib/smartcard/gp/gp_card_mixin.rb, line 39
39:   def gp_card_manager_aid
40:     [0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00]
41:   end

Issues a GlobalPlatform DELETE command targeting an executable load file.

Args:

  aid:: the executable load file's AID

The return value is irrelevant.

[Source]

     # File lib/smartcard/gp/gp_card_mixin.rb, line 246
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

Secure channel keys for development GlobalPlatform cards.

Most importantly, the JCOP cards and simulator work with these keys.

[Source]

     # File lib/smartcard/gp/gp_card_mixin.rb, line 149
149:   def gp_development_keys
150:     key = (0x40..0x4F).to_a.pack('C*')
151:     { :senc => key, :smac => key, :dek => key }
152:   end

Issues a GlobalPlatform GET STATUS command.

Args:

  scope:: the information to be retrieved from the card, can be:
    :issuer_sd:: the issuer's security domain
    :apps:: applications and supplementary security domains
    :files:: executable load files
    :files_modules:: executable load files and executable modules
  query_aid:: the AID to look for (empty array to get everything)

Returns an array of application information data. Each element represents an application, and is a hash with the following keys:

  :aid:: the application or file's AID
  :lifecycle:: the state in the application's lifecycle (symbol)
  :permissions:: a Set of the application's permissions (symbols)
  :modules:: array of modules in an executable load file, each array element
             is a hash with the key :aid which has the module's AID

[Source]

     # File lib/smartcard/gp/gp_card_mixin.rb, line 171
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:         raise Smartcard::Iso::ApduException, 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

Issues a GlobalPlatform INSTALL command that loads an application‘s file.

The command should be followed by a LOAD command (see gp_load).

Args:

  file_aid:: the AID of the file to be loaded
  sd_aid:: the AID of the security domain handling the loading
  data_hash::
  params::
  token:: load token (needed by some SDs)

Returns a true value if the command returns a valid install confirmation.

[Source]

     # File lib/smartcard/gp/gp_card_mixin.rb, line 286
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

Issues a GlobalPlatform INSTALL command that installs an application and makes it selectable.

Args:

  file_aid:: the AID of the application's executable load file
  module_aid:: the AID of the application's module in the load file
  app_aid:: the application's AID (application will be selectable by it)
  privileges:: array of application privileges (e.g. :security_domain)
  params:: application install parameters
  token:: install token (needed by some SDs)

Returns a true value if the command returns a valid install confirmation.

[Source]

     # File lib/smartcard/gp/gp_card_mixin.rb, line 311
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

Issues a GlobalPlatform LOAD command.

Args:

  file_data:: the file's data
  max_apdu_length:: the maximum APDU length, returned from
                    select_application

Returns a true value if the loading succeeds.

[Source]

     # File lib/smartcard/gp/gp_card_mixin.rb, line 340
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
360:   end

Issues a GlobalPlatform EXTERNAL AUTHENTICATE command.

This should not be called directly. Call secure_session insteaad.

Args:

  host_auth:: 8-byte host authentication value
  security:: array of desired security flags (leave empty for the default
             of no security)

The return value is irrelevant. The card will fire an ISO exception if the authentication doesn‘t work out.

[Source]

     # File lib/smartcard/gp/gp_card_mixin.rb, line 95
 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

Issues a GlobalPlatform INITIALIZE UPDATE command.

This should not be called directly. Call secure_session insteaad.

Args:

  host_challenge:: 8-byte array with a unique challenge for the session
  key_version:: the key in the Security domain to be used (0 = any key)

Returns a hash containing the command‘s parsed response. The keys are:

  :key_diversification:: key diversification data
  :key_version:: the key in the Security domain chosen to be used
  :protocol_id:: numeric ID for the secure protocol to be used
  :counter:: counter for creating session keys
  :challenge:: the card's 6-byte challenge
  :auth:: the card's 8-byte authentication value

[Source]

    # File lib/smartcard/gp/gp_card_mixin.rb, line 58
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

Wrapper around iso_apdu! that adds a MAC to the APDU.

[Source]

    # File lib/smartcard/gp/gp_card_mixin.rb, line 70
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

Installs a JavaCard applet on the JavaCard.

Args:

  cap_file:: path to the applet's CAP file
  package_aid:: the applet's package AID; if nil, the AID in the CAP's
                header is used (should work all the time)
  applet_aid:: the AID used to select the applet; if nil, the first AID
               in the CAP's Applet section is used (this works pretty well)
  install_data:: data to be passed to the applet at installation time

[Source]

     # File lib/smartcard/gp/gp_card_mixin.rb, line 371
371:   def install_applet(cap_file, applet_aid = nil, package_aid = nil,
372:                      install_data = [])
373:     load_data = CapLoader.cap_load_data(cap_file)
374:     applet_aid ||= load_data[:applets].first[:aid]
375:     package_aid ||= load_data[:header][:package][:aid]
376: 
377:     delete_application applet_aid
378:     
379:     manager_data = select_application gp_card_manager_aid
380:     max_apdu = manager_data[:max_apdu_length]
381:     secure_channel
382:         
383:     gp_install_load package_aid, gp_card_manager_aid
384:     gp_load_file load_data[:data], max_apdu
385:     gp_install_selectable package_aid, applet_aid, applet_aid, [],
386:                           { :app => install_data }    
387:   end

Sets up a secure session with the current GlobalPlatform application.

Args:

  keys:: hash containing 3 3DES encryption keys, identified by the following
         keys:
           :senc:: channel encryption key
           :smac:: channel MAC key
           :dek:: data encryption key

[Source]

     # File lib/smartcard/gp/gp_card_mixin.rb, line 114
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

Selects a GlobalPlatform application.

[Source]

    # File lib/smartcard/gp/gp_card_mixin.rb, line 23
23:   def select_application(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

[Validate]