Skip to content

Commit

Permalink
Support for installing JavaCard applets. Finally.
Browse files Browse the repository at this point in the history
  • Loading branch information
Victor Costan committed Nov 1, 2009
1 parent 33aacc9 commit 94774a0
Show file tree
Hide file tree
Showing 17 changed files with 967 additions and 72 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
v0.4.2. GlobalPlatform applet instalation.

v0.4.1. Workaround ATR resend bug in JCOP simulator 3.2.7.

v0.4.0. High-level functionality for ISO7816 cards.
Expand Down
19 changes: 13 additions & 6 deletions Manifest
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
BUILD
CHANGELOG
LICENSE
Manifest
README
Rakefile
ext/smartcard_pcsc/extconf.rb
ext/smartcard_pcsc/pcsc.h
ext/smartcard_pcsc/pcsc_card.c
Expand All @@ -13,6 +17,10 @@ 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
Expand All @@ -22,13 +30,12 @@ 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/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
Expand Down
2 changes: 2 additions & 0 deletions lib/smartcard.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@


require 'smartcard/gp/asn1_ber.rb'
require 'smartcard/gp/cap_loader.rb'
require 'smartcard/gp/des.rb'
require 'smartcard/gp/gp_card_mixin.rb'
96 changes: 87 additions & 9 deletions lib/smartcard/gp/asn1_ber.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ module Smartcard::Gp


# Logic for encoding and decoding ASN.1-BER data as specified in X.690-0207.
#
# TODO(costan): encoding routines, when necessary.
module Asn1Ber
# Decodes a TLV tag (the data type).
#
Expand Down Expand Up @@ -90,17 +88,16 @@ def self.map_value(value, tag)

# Decodes a TLV (tag-length-value).
#
# Returns a 2-element array, where the first element has tag information, and
# the second element is the value. See decode_tag for the format of the tag
# information.
# Returns a hash that contains tag and value information. See decode_tag for
# the keys containing the tag information. Value information is contained in
# the :value: tag.
def self.decode_tlv(data, offset)
offset, tag = decode_tag data, offset
offset, length = decode_length data, offset
offset, value = decode_value data, offset, length

value = tag[:primitive] ? map_value(value, tag) :
decode_tlv_sequence(value)
return offset, [tag, value]
tag[:value] = tag[:primitive] ? map_value(value, tag) : decode(value)
return offset, tag
end

# Decodes a sequence of TLVs (tag-length-value).
Expand All @@ -110,12 +107,93 @@ def self.decode_tlv(data, offset)
def self.decode(data, offset = 0, length = data.length - offset)
sequence = []
loop do
break if offset >= length
offset, tlv = decode_tlv data, offset
sequence << tlv
break if offset >= length
end
sequence
end

# Encodes a TLV tag (the data type).
#
# Args:
# tag:: a hash with the keys produced by decode_tag.
#
# Returns an array of byte values.
def self.encode_tag(tag)
tag_classes = { :universal => 0, :application => 1, :context => 2,
:private => 3 }
tag_lead = (tag_classes[tag[:class]] << 6) | (tag[:primitive] ? 0x00 : 0x20)
return [tag_lead | tag[:number]] if tag[:number] < 0x1F

number_bytes, number = [], tag[:number]
first = true
while number != 0
byte = (number & 0x7F)
number >>= 7
byte |= 0x80 unless first
first = false
number_bytes << byte
end
[tag_lead | 0x1F] + number_bytes.reverse
end

# Encodes a TLV length (the length of the data).
#
# Args::
# length:: the length to be encoded (number of :indefinite)
#
# Returns an array of byte values.
def self.encode_length(length)
return [0x80] if length == :indefinite
return [length] if length < 0x80
length_bytes = []
while length > 0
length_bytes << (length & 0xFF)
length >>= 8
end
[0x80 | length_bytes.length] + length_bytes.reverse
end

# Encodes a TLV (tag-length-value).
#
# Args::
# tlv:: hash with tag and value information, to be encoeded as TLV; see
# decode_tlv for the hash keys encoding the tag and value
#
# Returns an array of byte values.
def self.encode_tlv(tlv)
value = tlv[:primitive] ? tlv[:value] : encode(tlv[:value])
[encode_tag(tlv), encode_length(value.length), value].flatten
end

# Encodes a sequence of TLVs (tag-length-value).
#
# Args::
# tlvs:: an array of hashes to be encoded as TLV
#
# Returns an array of byte values.
def self.encode(tlvs)
tlvs.map { |tlv| encode_tlv tlv }.flatten
end

# Visitor pattern for decoded TLVs.
#
# Args:
# tlvs:: the TLVs to visit
# tag_path:: internal, do not use
#
# Yields: |tag_path, value| tag_path lists the numeric tags for the current
# value's tag, and all the parents' tags.
def self.visit(tlvs, tag_path = [], &block)
tlvs.each do |tlv|
tag_number = encode_tag(tlv).inject { |acc, v| (acc << 8) | v }
new_tag_path = tag_path + [tag_number]
yield new_tag_path, tlv[:value]
next if tlv[:primitive]
visit tlv[:value], new_tag_path, &block
end
end
end # module Smartcard::Gp::Asn1Ber

end # namespace
88 changes: 88 additions & 0 deletions lib/smartcard/gp/cap_loader.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Loads JavaCard CAP files.
#
# Author:: Victor Costan
# Copyright:: Copyright (C) 2009 Massachusetts Institute of Technology
# License:: MIT

require 'zip/zip'

# :nodoc: namespace
module Smartcard::Gp


# Logic for loading JavaCard CAP files.
module CapLoader
# Loads a CAP file.
#
# Returns a hash mapping component names to component data.
def self.load_cap(cap_file)
components = {}
Zip::ZipFile.open(cap_file) do |file|
file.each do |entry|
data = entry.get_input_stream { |io| io.read }
offset = 0
while offset < data.length
tag = TAG_NAMES[data[offset, 1].unpack('C').first]
length = data[offset + 1, 2].unpack('n').first
value = data[offset + 3, length]
components[tag] = value
offset += 3 + length
end
end
end
components
end

# Serializes CAP components for on-card loading.
#
# Returns an array of bytes.
def self.serialize_components(components)
[:header, :directory, :import, :applet, :class, :method, :static_field,
:export, :constant_pool, :reference_location].map { |name|
tag = TAG_NAMES.keys.find { |k| TAG_NAMES[k] == name }
if components[name]
length = [components[name].length].pack('n').unpack('C*')
data = components[name].unpack('C*')
[tag, length, data]
else
[]
end
}.flatten
end

# Parses the Applet section in a CAP file, obtaining applet AIDs.
#
# Returns an array of hashes, one hash per applet. The hash has a key +:aid+
# that contains the applet's AID.
def self.parse_applets(components)
applets = []
return applets unless section = components[:applet]
offset = 1
section[0].times do
aid_length = section[offset]
install_method = section[offset + 1 + aid_length, 2].unpack('n').first
applets << { :aid => section[offset + 1, aid_length].unpack('C*'),
:install_method => install_method }
offset += 3 + aid_length
end
applets
end

# Loads a CAP file and serializes its components for on-card loading.
#
# Returns an array of bytes.
def self.cap_load_data(cap_file)
components = load_cap cap_file
{ :data => serialize_components(components),
:applets => parse_applets(components) }
end

# Maps numeric tags to tag names.
TAG_NAMES = {
1 => :header, 2 => :directory, 3 => :applet, 4 => :import,
5 => :constant_pool, 6 => :class, 7 => :method, 8 => :static_field,
9 => :reference_location, 10 => :export, 11 => :descriptor, 12 => :debug
}
end # module Smartcard::Gp::CapLoader

end # namespace
68 changes: 68 additions & 0 deletions lib/smartcard/gp/des.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# DES and 3DES encryption and MAC logic for GlobalPlatform secure channels.
#
# Author:: Victor Costan
# Copyright:: Copyright (C) 2009 Massachusetts Institute of Technology
# License:: MIT

require 'openssl'

# :nodoc: namespace
module Smartcard::Gp


# DES and 3DES encryption and MAC logic for GlobalPlatform secure channels.
module Des
# Generates random bytes for session nonces.
#
# Args:
# bytes:: how many bytes are desired
#
# Returns a string of random bytes.
def self.random_bytes(bytes)
OpenSSL::Random.random_bytes bytes
end

# Perform DES or 3DES encryption.
#
# Args:
# key:: the encryption key to be used (8-byte or 16-byte)
# data:: the data to be encrypted or decrypted
# iv:: initialization vector
# decrypt:: if +false+ performs encryption, otherwise performs decryption
#
# Returns the encrypted / decrypted data.
def self.crypt(key, data, iv = nil, decrypt = false)
cipher_name = key.length == 8 ? 'DES-CBC' : 'DES-EDE-CBC'
cipher = OpenSSL::Cipher::Cipher.new cipher_name
decrypt ? cipher.decrypt : cipher.encrypt
cipher.key = key
cipher.iv = iv || ("\x00" * 8)
cipher.padding = 0
crypted = cipher.update data
crypted += cipher.final
crypted
end

# Computes a MAC using DES mixed with 3DES.
def self.mac_retail(key, data, iv = nil)
# Output transformation: add 80, then 00 until it's block-sized.
data = data + "\x80"
data += "\x00" * (8 - data.length % 8) unless data.length % 8 == 0

# DES-encrypt everything except for the last block.
iv = crypt(key[0, 8], data[0, data.length - 8], iv)[-8, 8]
# Take the chained block and supply it to a 3DES-encryption.
crypt(key, data[-8, 8], iv)
end

def self.mac_3des(key, data)
# Output transformation: add 80, then 00 until it's block-sized.
data = data + "\x80"
data += "\x00" * (8 - data.length % 8) unless data.length % 8 == 0

# The MAC is the last block from 3DES-encrypting the data.
crypt(key, data)[-8, 8]
end
end # module Smartcard::Gp::Des

end # namespace
Loading

0 comments on commit 94774a0

Please sign in to comment.