iso7816 0.0.3
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/AUTHORS +1 -0
- data/CHANGELOG +13 -0
- data/COPYING +728 -0
- data/LICENSE +58 -0
- data/README +9 -0
- data/Rakefile +141 -0
- data/THANKS +0 -0
- data/TODO +2 -0
- data/lib/7816.rb +5 -0
- data/lib/7816/apdu.rb +554 -0
- data/lib/7816/atr.rb +173 -0
- data/lib/7816/card.rb +278 -0
- data/lib/7816/iso_apdu.rb +68 -0
- data/lib/7816/pcsc_helper.rb +102 -0
- data/lib/emv.rb +10 -0
- data/lib/emv/cps_apdu.rb +319 -0
- data/lib/emv/crypto/crypto.rb +108 -0
- data/lib/emv/data/cps_ini_update.rb +15 -0
- data/lib/emv/emv.rb +5 -0
- data/lib/emv/emv_apdu.rb +56 -0
- data/lib/iso7816.rb +9 -0
- data/lib/iso_7816.rb +5 -0
- data/test/apdu_resp_test.rb +63 -0
- data/test/apdu_test.rb +128 -0
- data/test/card_test.rb +36 -0
- data/test/cps_test.rb +35 -0
- data/test/crypto_test.rb +23 -0
- data/test/emv_apdu_test.rb +79 -0
- data/test/iso_apdu_test.rb +108 -0
- data/test/util_test.rb +30 -0
- metadata +135 -0
@@ -0,0 +1,68 @@
|
|
1
|
+
module ISO7816
|
2
|
+
|
3
|
+
module APDU
|
4
|
+
|
5
|
+
def APDU.create_class name, ins
|
6
|
+
cl=
|
7
|
+
%Q(
|
8
|
+
class #{name} < APDU
|
9
|
+
cla \"\\x00\"
|
10
|
+
ins \"\\x#{ins}\"
|
11
|
+
end
|
12
|
+
)
|
13
|
+
eval(cl)
|
14
|
+
end
|
15
|
+
|
16
|
+
[
|
17
|
+
["ACTIVATE_FILE", "44"],
|
18
|
+
["APPEND_RECORD", "E2"],
|
19
|
+
["CHANGE_REFERENCE_DATA", "24"],
|
20
|
+
["CREATE_FILE", "E0"],
|
21
|
+
["DEACTIVATE_FILE", "04"],
|
22
|
+
["DELETE_FILE", "E4"],
|
23
|
+
["DISABLE_VERIFICATION_REQUIREMENT", "26"],
|
24
|
+
["ENABLE_VERIFICATION_REQUIREMENT", "28"],
|
25
|
+
["ENVELOPE", "C2"],
|
26
|
+
["ERASE_BINARY", "0E"],
|
27
|
+
["ERASE_RECORD", "0C"],
|
28
|
+
["EXTERNAL_AUTHENTICATE", "82"],
|
29
|
+
["GENERAL_AUTHENTICATE", "86"],
|
30
|
+
["GENERATE_ASYMMETRIC_KEY_PAIR", "46"],
|
31
|
+
["GET_CHALLENGE", "84"],
|
32
|
+
["GET_DATA", "CA"],
|
33
|
+
["GET_RESPONSE", "C0"],
|
34
|
+
["INTERNAL_AUTHENTICATE", "88"],
|
35
|
+
["MANAGE_CHANNEL", "70"],
|
36
|
+
["MANAGE_SECURITY_ENVIRONMENT", "22"],
|
37
|
+
["PERFORM_SCQL_OPERATION", "10"],
|
38
|
+
["PERFORM_SECURITY_OPERATION", "2A"],
|
39
|
+
["PERFORM_TRANSACTION_OPERATION", "12"],
|
40
|
+
["PERFORM_USER_OPERATION", "14"],
|
41
|
+
["PUT_DATA", "DA"],
|
42
|
+
["READ_BINARY", "B0"],
|
43
|
+
["READ_RECORD", "B2"],
|
44
|
+
["RESET_RETRY_COUNTER", "2C"],
|
45
|
+
["SEARCH_BINARY", "A0"],
|
46
|
+
["SEARCH_RECORD", "A2"],
|
47
|
+
["SELECT", "A4"],
|
48
|
+
["TERMINATE_CARD_USAGE", "FE"],
|
49
|
+
["TERMINATE_DF", "E6"],
|
50
|
+
["TERMINATE_EF", "E8"],
|
51
|
+
["UPDATE_BINARY", "D6"],
|
52
|
+
["UPDATE_RECORD", "DC"],
|
53
|
+
["VERIFY", "20"],
|
54
|
+
["WRITE_BINARY", "D0"],
|
55
|
+
["WRITE_RECORD", "D2"]
|
56
|
+
].each{|entry|
|
57
|
+
APDU.create_class *entry
|
58
|
+
}
|
59
|
+
|
60
|
+
|
61
|
+
class GET_RESPONSE < APDU
|
62
|
+
ins "\xc0"
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
end # APDU
|
67
|
+
|
68
|
+
end #7816
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'smartcard'
|
2
|
+
module ISO7816
|
3
|
+
module PCSC
|
4
|
+
|
5
|
+
# adds default params to Smartcard to make them easier to work with.
|
6
|
+
|
7
|
+
class Context < Smartcard::PCSC::Context
|
8
|
+
def initialize scope=Smartcard::PCSC::SCOPE_SYSTEM
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def list_readers reader_groups=nil
|
13
|
+
super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Card < Smartcard::PCSC::Card
|
18
|
+
include Smartcard::PCSC
|
19
|
+
attr_accessor :ctx
|
20
|
+
def initialize( context = nil,
|
21
|
+
reader_name = nil,
|
22
|
+
share_mode = SHARE_EXCLUSIVE,
|
23
|
+
preferred_protocol = PROTOCOL_ANY)
|
24
|
+
|
25
|
+
@ctx = context || ISO7816::PCSC::Context.new
|
26
|
+
@r_name = reader_name || @ctx.list_readers()[0]
|
27
|
+
@share_mode = share_mode
|
28
|
+
@preferred_protocol = preferred_protocol
|
29
|
+
|
30
|
+
super(@ctx, @r_name, @share_mode, @preferred_protocol)
|
31
|
+
end
|
32
|
+
|
33
|
+
def disconnect disposition=DISPOSITION_UNPOWER, release_context=true
|
34
|
+
super(disposition)
|
35
|
+
@ctx.release if release_context
|
36
|
+
end
|
37
|
+
|
38
|
+
def reconnect share=nil, preferred_protocol=nil, ini= INITIALIZATION_UNPOWER
|
39
|
+
# use and remember passed params if set, else reuse last remembered params.
|
40
|
+
share ||= @share_mode
|
41
|
+
preferred_protocol ||= @preferred_protocol
|
42
|
+
|
43
|
+
@share_mode = share
|
44
|
+
@preferred_protocol = preferred_protocol
|
45
|
+
|
46
|
+
@status = nil
|
47
|
+
#puts @share_mode.class
|
48
|
+
#puts @preferred_protocol.class
|
49
|
+
#puts ini.class
|
50
|
+
super(@share_mode, @preferred_protocol, ini)
|
51
|
+
end
|
52
|
+
|
53
|
+
def _status
|
54
|
+
@status ||= self.status
|
55
|
+
end
|
56
|
+
|
57
|
+
def transmit send_data, send_io_request=nil, recv_io_request=nil
|
58
|
+
unless send_io_request
|
59
|
+
protocol = _status[:protocol]
|
60
|
+
send_io_request = case protocol
|
61
|
+
when PROTOCOL_T0:
|
62
|
+
IOREQUEST_T0
|
63
|
+
when PROTOCOL_T1:
|
64
|
+
puts "T1"
|
65
|
+
IOREQUEST_T1
|
66
|
+
when Smartcard::PCSC::PROTOCOL_RAW:
|
67
|
+
IOREQUEST_RAW
|
68
|
+
else
|
69
|
+
raise "weird protocol: #{protocol}"
|
70
|
+
end
|
71
|
+
end # send_io_request
|
72
|
+
|
73
|
+
recv_io_request ||= Smartcard::PCSC::IoRequest.new
|
74
|
+
super(send_data, send_io_request, recv_io_request)
|
75
|
+
end
|
76
|
+
|
77
|
+
def atr
|
78
|
+
_status[:atr]
|
79
|
+
end
|
80
|
+
def protocol?
|
81
|
+
case _status[:protocol]
|
82
|
+
when PROTOCOL_T0
|
83
|
+
"T0"
|
84
|
+
when PROTOCOL_T1
|
85
|
+
"T1"
|
86
|
+
when PROTOCOL_RAW
|
87
|
+
"RAW"
|
88
|
+
when PROTOCOL_T15
|
89
|
+
"T15"
|
90
|
+
when PROTOCOL_UNKNOWN
|
91
|
+
"UNKNOWN"
|
92
|
+
else
|
93
|
+
"UNKNOWN_UNKNOWN"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end #card
|
97
|
+
|
98
|
+
end # pcsc
|
99
|
+
end # iso7816
|
100
|
+
|
101
|
+
|
102
|
+
|
data/lib/emv.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'iso7816'
|
5
|
+
require 'emv/emv'
|
6
|
+
require 'emv/emv_apdu'
|
7
|
+
require 'emv/cps_apdu'
|
8
|
+
require 'emv/data/cps_ini_update'
|
9
|
+
require 'emv/crypto/crypto'
|
10
|
+
|
data/lib/emv/cps_apdu.rb
ADDED
@@ -0,0 +1,319 @@
|
|
1
|
+
module EMV
|
2
|
+
module APDU
|
3
|
+
module CPS
|
4
|
+
|
5
|
+
|
6
|
+
class SecureContext
|
7
|
+
# The Kenc key used in this session
|
8
|
+
attr_accessor :k_enc
|
9
|
+
|
10
|
+
# The Kmac key used in this session
|
11
|
+
attr_accessor :k_mac
|
12
|
+
|
13
|
+
# The Kdek key used in this session
|
14
|
+
attr_accessor :k_dek
|
15
|
+
|
16
|
+
# The challenge sent to the card, (Rterm)
|
17
|
+
attr_accessor :host_challenge
|
18
|
+
|
19
|
+
# The session key SKUenc
|
20
|
+
attr_accessor :sku_enc
|
21
|
+
|
22
|
+
attr_accessor :sku_mac
|
23
|
+
attr_accessor :sku_dek
|
24
|
+
|
25
|
+
# The card's response to initialize update containing:
|
26
|
+
# kmc_id (Identifier of the KMC)
|
27
|
+
# csn (Chip Serial Number)
|
28
|
+
# kmc_version (Version Number of Master key (KMC))
|
29
|
+
# sec_channel_proto_id (Identifier of Secure Channel Protocol)
|
30
|
+
# sequence_counter (Sequence Counter)
|
31
|
+
# challenge (Card challenge (r card))
|
32
|
+
# cryptogram (Card Cryptogram)
|
33
|
+
|
34
|
+
attr_reader :initialize_response
|
35
|
+
|
36
|
+
# The security level established by the EXTERNAL_AUTH command
|
37
|
+
# According to CPS Table 19
|
38
|
+
# One of:
|
39
|
+
# :enc_and_mac
|
40
|
+
# :mac
|
41
|
+
# :no_sec
|
42
|
+
attr_accessor :level
|
43
|
+
|
44
|
+
|
45
|
+
def initialize k_enc="\x00"*16, k_mac="\x00"*16, k_dek="\x00"*16, host_challenge="\x00"*8
|
46
|
+
@k_enc = k_enc
|
47
|
+
@k_mac = k_mac
|
48
|
+
@k_dek = k_dek
|
49
|
+
|
50
|
+
@host_challenge=host_challenge
|
51
|
+
@level = :no_sec
|
52
|
+
end
|
53
|
+
|
54
|
+
# Set the response returned by INITIALIZE UPDATE.
|
55
|
+
# * calculates the Session keys.
|
56
|
+
def initialize_response= resp
|
57
|
+
|
58
|
+
@initialize_response = resp
|
59
|
+
|
60
|
+
|
61
|
+
@sku_enc = EMV::Crypto.generate_session_key(k_enc,
|
62
|
+
@initialize_response.sequence_counter,
|
63
|
+
:enc)
|
64
|
+
@sku_mac = EMV::Crypto.generate_session_key(k_mac,
|
65
|
+
@initialize_response.sequence_counter,
|
66
|
+
:mac)
|
67
|
+
|
68
|
+
@sku_dek = EMV::Crypto.generate_session_key(k_dek,
|
69
|
+
@initialize_response.sequence_counter,
|
70
|
+
:dek)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Verify the cryptogram sent by the card according to: CPS 3.2.5.10
|
74
|
+
def check_card_cryptogram
|
75
|
+
mac_ = host_challenge +
|
76
|
+
initialize_response.sequence_counter +
|
77
|
+
initialize_response.challenge
|
78
|
+
|
79
|
+
mac = EMV::Crypto.mac_for_personalization(sku_enc, mac_)
|
80
|
+
unless mac == initialize_response.cryptogram
|
81
|
+
raise %Q{
|
82
|
+
Invalid MAC returned from card!
|
83
|
+
host challenge: #{ISO7816.b2s(host_challenge)}
|
84
|
+
card seq : #{ISO7816.b2s(initialize_response.sequence_counter)}
|
85
|
+
card challenge: #{ISO7816.b2s(initialize_response.challenge)}
|
86
|
+
expected mac : #{ISO7816.b2s(mac)}
|
87
|
+
recv mac : #{ISO7816.b2s(initialize_response.cryptogram)}
|
88
|
+
k_enc : #{ISO7816.b2s(k_enc)}
|
89
|
+
k_mac : #{ISO7816.b2s(k_mac)}
|
90
|
+
k_dek : #{ISO7816.b2s(k_dek)}
|
91
|
+
}
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Calculates the host cryptogram according to CPS 3.2.6.6
|
96
|
+
def calculate_host_cryptogram
|
97
|
+
mac_ = initialize_response.sequence_counter +
|
98
|
+
initialize_response.challenge +
|
99
|
+
host_challenge
|
100
|
+
EMV::Crypto.mac_for_personalization(sku_enc, mac_)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Calculate the C-MAC according to CP 5.4.2.2
|
104
|
+
def calculate_c_mac apdu
|
105
|
+
# data with placeholder for cmac
|
106
|
+
data = apdu.data
|
107
|
+
mac_ = apdu.cla +
|
108
|
+
apdu.ins +
|
109
|
+
apdu.p1 +
|
110
|
+
apdu.p2
|
111
|
+
|
112
|
+
mac_ << apdu.data.length+8
|
113
|
+
mac_ << data
|
114
|
+
if @c_mac # "prepend the c-mac computed for the previous command ..."
|
115
|
+
mac_ = @c_mac + mac_
|
116
|
+
end
|
117
|
+
@c_mac = EMV::Crypto.retail_mac(sku_mac, mac_)
|
118
|
+
@c_mac
|
119
|
+
end
|
120
|
+
|
121
|
+
# Retrieve the current seq number, this also increments the counter.
|
122
|
+
def store_data_seq_number
|
123
|
+
@store_data_seq_number ||= -1
|
124
|
+
@store_data_seq_number += 1
|
125
|
+
"" << @store_data_seq_number
|
126
|
+
end
|
127
|
+
|
128
|
+
def reset
|
129
|
+
@c_mac = nil
|
130
|
+
@store_data_seq_number = nil
|
131
|
+
@sku_enc = nil
|
132
|
+
@sku_mac = nil
|
133
|
+
@initialize_response = nil
|
134
|
+
end
|
135
|
+
|
136
|
+
# Encrypt data bytes according to CPS 5.5.2
|
137
|
+
def encrypt data
|
138
|
+
data = EMV::Crypto.pad data
|
139
|
+
cipher = OpenSSL::Cipher::Cipher.new("des-ede-cbc").encrypt
|
140
|
+
cipher.key = sku_enc
|
141
|
+
cipher.update data
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
class CPS_APDU < EMV::APDU::EMV_APDU
|
146
|
+
attr_accessor :secure_context
|
147
|
+
|
148
|
+
def initialize card, secure_context
|
149
|
+
super card
|
150
|
+
@secure_context= secure_context
|
151
|
+
end
|
152
|
+
end
|
153
|
+
class INITIALIZE_UPDATE < CPS_APDU
|
154
|
+
ins "\x50"
|
155
|
+
|
156
|
+
def key_version_number= kvn
|
157
|
+
self.p1= kvn
|
158
|
+
end
|
159
|
+
def send handle_more_data=true, card=nil
|
160
|
+
secure_context.reset
|
161
|
+
@data = secure_context.host_challenge
|
162
|
+
|
163
|
+
resp = super
|
164
|
+
if resp.status == "9000"
|
165
|
+
@secure_context.initialize_response = EMV::Data::InitializeUpdateData.new(resp.data)
|
166
|
+
@secure_context.check_card_cryptogram
|
167
|
+
end
|
168
|
+
resp
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
class C_MAC_APDU < CPS_APDU
|
173
|
+
# Provides the possibility to override the calculated c_mac
|
174
|
+
# with an arbitrary one for testing.
|
175
|
+
attr_writer :c_mac
|
176
|
+
|
177
|
+
def initialize card, secure_context
|
178
|
+
super
|
179
|
+
end
|
180
|
+
|
181
|
+
# calculate the c-mac according to 5.4.2.2
|
182
|
+
def c_mac
|
183
|
+
@c_mac ||= secure_context.calculate_c_mac(self)
|
184
|
+
@c_mac
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
class EXTERNAL_AUTHENTICATE < C_MAC_APDU
|
189
|
+
cla "\x84"
|
190
|
+
ins "\x82"
|
191
|
+
def initialize card, secure_context
|
192
|
+
super
|
193
|
+
self.security_level= secure_context.level if secure_context.level
|
194
|
+
end
|
195
|
+
|
196
|
+
#
|
197
|
+
# CPS 3.2.6
|
198
|
+
# :enc_and_mac
|
199
|
+
# :mac
|
200
|
+
# :no_sec
|
201
|
+
# or a sec level byte...
|
202
|
+
def security_level= level
|
203
|
+
case level
|
204
|
+
when :enc_and_mac
|
205
|
+
self.p1= 0x03
|
206
|
+
when :mac
|
207
|
+
self.p1= 0x01
|
208
|
+
when :no_sec
|
209
|
+
self.p1= 0x00
|
210
|
+
else
|
211
|
+
self.p1=level
|
212
|
+
end
|
213
|
+
@secure_context.level= level
|
214
|
+
end
|
215
|
+
|
216
|
+
#
|
217
|
+
# Sets the host cryptogram according to CPS 3.2.6.6
|
218
|
+
#
|
219
|
+
def cryptogram
|
220
|
+
@cryptogram ||= secure_context.calculate_host_cryptogram
|
221
|
+
@cryptogram
|
222
|
+
end
|
223
|
+
|
224
|
+
# Explicitly set a cryptogram, e.g. if an incorrect cryptogram is to be set
|
225
|
+
# for testing.
|
226
|
+
def cryptogram= bytes
|
227
|
+
@cryptogram= bytes
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
def send handle_more_data=true, card=nil
|
232
|
+
@data= self.cryptogram
|
233
|
+
@data+= c_mac # calculate the c_mac...
|
234
|
+
super
|
235
|
+
end
|
236
|
+
|
237
|
+
|
238
|
+
end
|
239
|
+
class STORE_DATA < C_MAC_APDU
|
240
|
+
ins "\xE2"
|
241
|
+
|
242
|
+
LAST_STORE_DATA_MASK = 0x80
|
243
|
+
ALL_DGI_ENC_MASK = 0x60
|
244
|
+
NO_DGI_ENC_MASK = 0x00
|
245
|
+
APP_DEPENDANT_MASK = 0x20
|
246
|
+
|
247
|
+
SECURE_MASK = 0x04
|
248
|
+
|
249
|
+
attr_reader :security_level
|
250
|
+
|
251
|
+
def initialize card, secure_context, data=""
|
252
|
+
super(card, secure_context)
|
253
|
+
#@security_level = secure_context.level
|
254
|
+
@cla= "\x84" if @security_level == :enc_and_mac
|
255
|
+
self.data= data
|
256
|
+
end
|
257
|
+
def security_level= level
|
258
|
+
@security_level = level
|
259
|
+
if [:enc_and_mac, :mac].include? level
|
260
|
+
secure
|
261
|
+
else
|
262
|
+
self.cla = 0x80
|
263
|
+
end
|
264
|
+
end
|
265
|
+
def secure
|
266
|
+
self.cla= cla[0] | SECURE_MASK
|
267
|
+
end
|
268
|
+
def secure?
|
269
|
+
(cla[0] & SECURE_MASK) == SECURE_MASK
|
270
|
+
end
|
271
|
+
def last_store_data
|
272
|
+
self.p1= (p1[0] | LAST_STORE_DATA_MASK)
|
273
|
+
end
|
274
|
+
def last_store_data?
|
275
|
+
(p1[0] & LAST_STORE_DATA_MASK) == LAST_STORE_DATA_MASK
|
276
|
+
end
|
277
|
+
def all_dgi_enc
|
278
|
+
self.p1= (p1[0] | ALL_DGI_ENC_MASK)
|
279
|
+
end
|
280
|
+
def all_dgi_enc?
|
281
|
+
(p1[0] & ALL_DGI_ENC_MASK) == ALL_DGI_ENC_MASK
|
282
|
+
end
|
283
|
+
def no_dgi_enc
|
284
|
+
self.p1= (p1[0] & ~ ALL_DGI_ENC_MASK)
|
285
|
+
end
|
286
|
+
def no_dgi_enc?
|
287
|
+
(p1[0] & NO_DGI_ENC_MASK) == NO_DGI_ENC_MASK
|
288
|
+
end
|
289
|
+
def app_dependant
|
290
|
+
self.p1= (p1[0] & ~ ALL_DGI_ENC_MASK) | APP_DEPENDANT_MASK
|
291
|
+
end
|
292
|
+
def app_dependant?
|
293
|
+
(p1[0] & APP_DEPENDANT_MASK) == APP_DEPENDANT_MASK
|
294
|
+
end
|
295
|
+
|
296
|
+
def send handle_more_data=true, card=nil
|
297
|
+
|
298
|
+
@p2 = secure_context.store_data_seq_number
|
299
|
+
|
300
|
+
# Secure Ctx security level may change in the course of a series of
|
301
|
+
# apdus, so we only no the current state just before sending.
|
302
|
+
self.security_level= @secure_context.level unless @security_level
|
303
|
+
|
304
|
+
unless @security_level == :no_sec
|
305
|
+
c_mac_ = self.c_mac # c_mac is calculated over unencrypted data
|
306
|
+
if @security_level == :enc_and_mac
|
307
|
+
@data= secure_context.encrypt(self.data)+c_mac_
|
308
|
+
else
|
309
|
+
@data= self.data+c_mac_
|
310
|
+
end
|
311
|
+
end
|
312
|
+
super
|
313
|
+
end
|
314
|
+
|
315
|
+
end
|
316
|
+
|
317
|
+
end #CPS
|
318
|
+
end # APDU
|
319
|
+
end #EMV
|