smartcard 0.4.1-x86-mswin32-60 → 0.4.3-x86-mswin32-60
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +4 -0
- data/Manifest +13 -6
- data/Rakefile +6 -4
- data/lib/smartcard/gp/asn1_ber.rb +199 -0
- data/lib/smartcard/gp/cap_loader.rb +88 -0
- data/lib/smartcard/gp/des.rb +68 -0
- data/lib/smartcard/gp/gp_card_mixin.rb +364 -5
- data/lib/smartcard/iso/iso_card_mixin.rb +13 -3
- data/lib/smartcard/iso/jcop_remote_server.rb +14 -5
- data/lib/smartcard/iso/pcsc_transport.rb +8 -1
- data/lib/smartcard/pcsc.so +0 -0
- data/lib/smartcard.rb +3 -0
- data/smartcard.gemspec +8 -5
- data/test/gp/asn1_ber_test.rb +116 -0
- data/test/gp/cap_loader_test.rb +24 -0
- data/test/gp/des_test.rb +39 -0
- data/test/gp/gp_card_mixin_test.rb +194 -5
- data/test/gp/hello.apdu +1 -0
- data/test/gp/hello.cap +0 -0
- data/test/iso/iso_card_mixin_test.rb +9 -8
- data/test/iso/jcop_remote_test.rb +4 -1
- metadata +36 -13
data/CHANGELOG
CHANGED
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
|
-
|
26
|
-
|
27
|
-
|
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
@@ -9,14 +9,16 @@ Echoe.new('smartcard') do |p|
|
|
9
9
|
p.email = 'victor@costan.us'
|
10
10
|
p.summary = 'Interface with ISO 7816 smart cards.'
|
11
11
|
p.url = 'http://www.costan.us/smartcard'
|
12
|
+
p.dependencies = ['rubyzip >=0.9.1']
|
12
13
|
|
13
|
-
p.need_tar_gz = !
|
14
|
-
p.need_zip = !
|
14
|
+
p.need_tar_gz = !Gem.win_platform?
|
15
|
+
p.need_zip = !Gem.win_platform?
|
15
16
|
p.clean_pattern += ['ext/**/*.manifest', 'ext/**/*_autogen.h']
|
16
|
-
p.rdoc_pattern =
|
17
|
+
p.rdoc_pattern =
|
18
|
+
/^(lib|bin|tasks|ext)|^BUILD|^README|^CHANGELOG|^TODO|^LICENSE|^COPYING$/
|
17
19
|
|
18
20
|
p.eval = proc do |p|
|
19
|
-
if
|
21
|
+
if Gem.win_platform?
|
20
22
|
p.files += ['lib/smartcard/pcsc.so']
|
21
23
|
p.platform = Gem::Platform::CURRENT
|
22
24
|
|
@@ -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
|