smartcard 0.4.1-x86-mswin32-60 → 0.4.3-x86-mswin32-60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|