mfrc522 1.0.2 → 3.0.0
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.
- checksums.yaml +5 -5
- data/lib/core_ext.rb +38 -0
- data/lib/exceptions.rb +1 -0
- data/lib/mfrc522.rb +112 -160
- data/lib/mifare/classic.rb +38 -19
- data/lib/mifare/des_fire.rb +156 -157
- data/lib/mifare/key.rb +39 -12
- data/lib/mifare/plus.rb +124 -0
- data/lib/mifare/plus_sl0.rb +11 -0
- data/lib/mifare/plus_sl1.rb +0 -0
- data/lib/mifare/plus_sl3.rb +11 -0
- data/lib/mifare/ultralight.rb +68 -17
- data/lib/mifare/ultralight_c.rb +13 -14
- data/lib/mifare/ultralight_ev1.rb +40 -0
- data/lib/picc.rb +313 -9
- metadata +13 -15
- data/lib/iso144434.rb +0 -245
data/lib/mifare/classic.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
module
|
1
|
+
module MIFARE
|
2
2
|
class Classic < ::PICC
|
3
3
|
CMD_AUTH_KEY_A = 0x60 # Perform authentication with Key A
|
4
4
|
CMD_AUTH_KEY_B = 0x61 # Perform authentication with Key B
|
@@ -8,10 +8,27 @@ module Mifare
|
|
8
8
|
CMD_INCREMENT = 0xC1 # Increments the contents of a block and stores the result in the internal data register.
|
9
9
|
CMD_RESTORE = 0xC2 # Reads the contents of a block into the internal data register.
|
10
10
|
CMD_TRANSFER = 0xB0 # Writes the contents of the internal data register to a block.
|
11
|
+
MF_ACK = 0x0A # Mifare Acknowledge
|
12
|
+
|
13
|
+
def initialize(pcd, uid, sak)
|
14
|
+
super
|
15
|
+
# Set transceive timeout to 15ms
|
16
|
+
@pcd.internal_timer(50)
|
17
|
+
end
|
18
|
+
|
19
|
+
def transceive(send_data, accept_timeout = false)
|
20
|
+
received_data, valid_bits = picc_transceive(send_data, accept_timeout, true)
|
21
|
+
return if received_data.nil? && valid_bits.nil? && accept_timeout
|
22
|
+
unless valid_bits == 0
|
23
|
+
raise UnexpectedDataError, 'Incorrect Mifare ACK format' if received_data.size != 1 || valid_bits != 4 # ACK is 4 bits long
|
24
|
+
raise MifareNakError, "Mifare NAK detected: 0x#{received_data[0].to_bytehex}" if received_data[0] != MF_ACK
|
25
|
+
end
|
26
|
+
received_data
|
27
|
+
end
|
11
28
|
|
12
29
|
def auth(block_addr, key = {})
|
13
30
|
if key[:a].nil? && key[:b].nil?
|
14
|
-
raise
|
31
|
+
raise UsageError, 'Missing key data'
|
15
32
|
end
|
16
33
|
|
17
34
|
if key[:a]
|
@@ -24,7 +41,7 @@ module Mifare
|
|
24
41
|
|
25
42
|
key = [key].pack('H*').bytes
|
26
43
|
if key.size != 6
|
27
|
-
raise
|
44
|
+
raise UsageError, "Expect 6 bytes auth key, got: #{key.size} byte"
|
28
45
|
end
|
29
46
|
|
30
47
|
@pcd.mifare_crypto1_authenticate(cmd, block_addr, key, @uid)
|
@@ -35,29 +52,33 @@ module Mifare
|
|
35
52
|
end
|
36
53
|
|
37
54
|
def read(block_addr)
|
38
|
-
|
39
|
-
|
40
|
-
@pcd.picc_transceive(buffer)
|
55
|
+
transceive([CMD_READ, block_addr])
|
41
56
|
end
|
42
57
|
|
43
58
|
def write(block_addr, send_data)
|
44
59
|
if send_data.size != 16
|
45
|
-
raise
|
60
|
+
raise UsageError, "Expect 16 bytes data, got: #{send_data.size} byte"
|
46
61
|
end
|
47
62
|
|
48
|
-
buffer = [CMD_WRITE, block_addr]
|
49
|
-
|
50
63
|
# Ask PICC if we can write to block_addr
|
51
|
-
|
64
|
+
transceive([CMD_WRITE, block_addr])
|
52
65
|
|
53
66
|
# Then start transfer our data
|
54
|
-
|
67
|
+
transceive(send_data)
|
55
68
|
end
|
56
69
|
|
57
70
|
def read_value(block_addr)
|
58
71
|
received_data = read(block_addr)
|
59
72
|
|
60
|
-
received_data[0..3].to_sint
|
73
|
+
value = received_data[0..3].to_sint
|
74
|
+
value1 = ~(received_data[4..7].to_sint)
|
75
|
+
value2 = received_data[8..11].to_sint
|
76
|
+
|
77
|
+
if value != value1 || value != value2
|
78
|
+
raise UnexpectedDataError, 'Invalid value block'
|
79
|
+
end
|
80
|
+
|
81
|
+
value
|
61
82
|
end
|
62
83
|
|
63
84
|
def write_value(block_addr, value)
|
@@ -86,9 +107,9 @@ module Mifare
|
|
86
107
|
buffer[10] = buffer[2]
|
87
108
|
buffer[11] = buffer[3]
|
88
109
|
buffer[12] = block_addr
|
89
|
-
buffer[13] = ~
|
110
|
+
buffer[13] = ~buffer[12]
|
90
111
|
buffer[14] = buffer[12]
|
91
|
-
buffer[15] = buffer[
|
112
|
+
buffer[15] = ~buffer[12]
|
92
113
|
|
93
114
|
write(block_addr, buffer)
|
94
115
|
end
|
@@ -110,9 +131,7 @@ module Mifare
|
|
110
131
|
|
111
132
|
# Transfer: Writes the contents of the internal Transfer Buffer to a value block
|
112
133
|
def transfer(block_addr)
|
113
|
-
|
114
|
-
|
115
|
-
@pcd.picc_transceive(buffer)
|
134
|
+
transceive([CMD_TRANSFER, block_addr])
|
116
135
|
end
|
117
136
|
|
118
137
|
private
|
@@ -123,10 +142,10 @@ module Mifare
|
|
123
142
|
send_data = [].append_uint(value, 4)
|
124
143
|
|
125
144
|
# Ask PICC if we can write to block_addr
|
126
|
-
|
145
|
+
transceive(buffer)
|
127
146
|
|
128
147
|
# Then start transfer our data
|
129
|
-
|
148
|
+
transceive(send_data, true) # Accept timeout
|
130
149
|
end
|
131
150
|
end
|
132
151
|
end
|
data/lib/mifare/des_fire.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
module
|
2
|
-
class DESFire < ::
|
1
|
+
module MIFARE
|
2
|
+
class DESFire < ::PICC
|
3
3
|
# Security Related Commands
|
4
4
|
CMD_DES_AUTH = 0x1A # Authenticate with DES, 2K3DES, 3K3DES key
|
5
5
|
CMD_AES_AUTH = 0xAA # Authenticate with AES-128 key
|
@@ -7,14 +7,18 @@ module Mifare
|
|
7
7
|
CMD_CHANGE_KEY_SETTING = 0x54 # Changes the master key settings on PICC and application level.
|
8
8
|
CMD_GET_KEY_VERSION = 0x64 # Reads out the current key version of any key stored on the PICC.
|
9
9
|
CMD_CHANGE_KEY = 0xC4 # Changes any key stored on the PICC.
|
10
|
+
CMD_SET_CONFIGURATION = 0x5C # Configures and pre-personalizes the card with app default key, UID, and ATS string setup
|
10
11
|
|
11
12
|
# PICC Level Commands
|
12
13
|
CMD_CREATE_APP = 0xCA # Creates new applications on the PICC.
|
13
14
|
CMD_DELETE_APP = 0xDA # Permanently deactivates applications on the PICC.
|
14
15
|
CMD_GET_APP_IDS = 0x6A # Returns the Application IDentifiers of all applications on a PICC.
|
15
16
|
CMD_SELECT_APP = 0x5A # Selects one specific application for further access.
|
17
|
+
CMD_FREE_MEMORY = 0x6E # Returns the free memory available on the card
|
18
|
+
CMD_GET_DF_NAMES = 0x6D # Returns the DF names
|
16
19
|
CMD_GET_CARD_VERSION = 0x60 # Returns manufacturing related data of the PICC.
|
17
20
|
CMD_FORMAT_CARD = 0xFC # Releases the PICC user memory.
|
21
|
+
CMD_GET_CARD_UID = 0x51 # Returns the UID
|
18
22
|
|
19
23
|
# Application Level Commands
|
20
24
|
CMD_GET_FILE_IDS = 0x6F # Returns the File IDentifiers of all active files within the currently selected application.
|
@@ -67,45 +71,46 @@ module Mifare
|
|
67
71
|
KEY_TYPE = {'des-ede-cbc' => 0x00, 'des-ede3-cbc' => 0x40, 'aes-128-cbc' => 0x80}
|
68
72
|
|
69
73
|
KEY_SETTING = Struct.new(
|
70
|
-
# Key number
|
71
|
-
#
|
74
|
+
# Key number required for `change_key`
|
75
|
+
# 0x00 means only master key is accepted
|
76
|
+
# 0x01~0x0D chooses an app key to change any key
|
77
|
+
# 0x0E means keys can only change themselves
|
78
|
+
# 0x0F freezes all keys, except master key
|
79
|
+
# to freeze master key, use :masterkey_changeable
|
72
80
|
:privileged_key,
|
73
81
|
# Set if master key can be modified
|
74
|
-
:
|
75
|
-
# Set if
|
76
|
-
:
|
77
|
-
# Set if
|
78
|
-
:
|
82
|
+
:masterkey_changeable,
|
83
|
+
# Set if file management requires auth
|
84
|
+
:file_management_without_auth,
|
85
|
+
# Set if file configuration requires auth
|
86
|
+
:file_configurable_without_auth,
|
79
87
|
# Set if this setting can be modified
|
80
|
-
:
|
81
|
-
def initialize(*
|
88
|
+
:key_setting_changeable) do
|
89
|
+
def initialize(*)
|
82
90
|
super
|
83
|
-
|
91
|
+
self.privileged_key = 0 if self.privileged_key.nil?
|
92
|
+
self.masterkey_changeable = true if self.masterkey_changeable.nil?
|
93
|
+
self.file_management_without_auth = true if self.file_management_without_auth.nil?
|
94
|
+
self.file_configurable_without_auth = true if self.file_configurable_without_auth.nil?
|
95
|
+
self.key_setting_changeable = true if self.key_setting_changeable.nil?
|
84
96
|
end
|
85
97
|
|
86
|
-
def
|
87
|
-
self
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
98
|
+
def self.import(byte)
|
99
|
+
self.new(
|
100
|
+
(byte >> 4) & 0x0F,
|
101
|
+
byte & 0x01 != 0,
|
102
|
+
byte & 0x02 != 0,
|
103
|
+
byte & 0x04 != 0,
|
104
|
+
byte & 0x08 != 0
|
105
|
+
)
|
92
106
|
end
|
93
107
|
|
94
|
-
def
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
self
|
101
|
-
end
|
102
|
-
|
103
|
-
def to_uint
|
104
|
-
output = (privileged_key << 4)
|
105
|
-
output |= 0x01 if mk_changeable
|
106
|
-
output |= 0x02 if listing_without_mk
|
107
|
-
output |= 0x04 if create_delete_without_mk
|
108
|
-
output |= 0x08 if configuration_changeable
|
108
|
+
def export
|
109
|
+
output = (self.privileged_key << 4)
|
110
|
+
output |= 0x01 if self.masterkey_changeable
|
111
|
+
output |= 0x02 if self.file_management_without_auth
|
112
|
+
output |= 0x04 if self.file_configurable_without_auth
|
113
|
+
output |= 0x08 if self.key_setting_changeable
|
109
114
|
output
|
110
115
|
end
|
111
116
|
end
|
@@ -119,15 +124,16 @@ module Mifare
|
|
119
124
|
|
120
125
|
# value 0x00 ~ 0x0D are key numbers, 0x0E grants free access, 0x0F always denies access
|
121
126
|
FILE_PERMISSION = Struct.new(:read_access, :write_access, :read_write_access, :change_access) do
|
122
|
-
def import(byte)
|
123
|
-
self
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
127
|
+
def self.import(byte)
|
128
|
+
self.new(
|
129
|
+
(byte >> 12) & 0x0F,
|
130
|
+
(byte >> 8) & 0x0F,
|
131
|
+
(byte >> 4) & 0x0F,
|
132
|
+
byte & 0x0F
|
133
|
+
)
|
128
134
|
end
|
129
135
|
|
130
|
-
def
|
136
|
+
def export
|
131
137
|
(read_access << 12) | (write_access << 8) | (read_write_access << 4) | change_access
|
132
138
|
end
|
133
139
|
end
|
@@ -160,7 +166,6 @@ module Mifare
|
|
160
166
|
def initialize(pcd, uid, sak)
|
161
167
|
super
|
162
168
|
invalid_auth
|
163
|
-
@cmac_buffer = []
|
164
169
|
@selected_app = false
|
165
170
|
end
|
166
171
|
|
@@ -168,12 +173,16 @@ module Mifare
|
|
168
173
|
@authed.is_a? Numeric
|
169
174
|
end
|
170
175
|
|
176
|
+
def select
|
177
|
+
iso_select
|
178
|
+
end
|
179
|
+
|
171
180
|
def deselect
|
172
|
-
super
|
173
181
|
invalid_auth
|
182
|
+
iso_deselect
|
174
183
|
end
|
175
184
|
|
176
|
-
def transceive(cmd: , plain_data: [], data: [], tx: nil, rx: nil, expect: nil, return_data: nil,
|
185
|
+
def transceive(cmd: , plain_data: [], data: [], tx: nil, rx: nil, expect: nil, return_data: nil, receive_length: nil, encrypt_padding: nil)
|
177
186
|
# Session key is needed for encryption
|
178
187
|
if (tx == :encrypt || rx == :encrypt) && !@authed
|
179
188
|
raise UnauthenticatedError
|
@@ -185,6 +194,10 @@ module Mifare
|
|
185
194
|
|
186
195
|
buffer = [cmd] + plain_data
|
187
196
|
|
197
|
+
if @authed
|
198
|
+
@session_key.padding_mode(encrypt_padding || 1)
|
199
|
+
end
|
200
|
+
|
188
201
|
if tx == :encrypt
|
189
202
|
# Calculate CRC on whole frame
|
190
203
|
data.append_uint(crc32(buffer, data), 4)
|
@@ -194,22 +207,21 @@ module Mifare
|
|
194
207
|
|
195
208
|
buffer.concat(data)
|
196
209
|
|
197
|
-
if
|
198
|
-
|
199
|
-
cmac = @session_key.calculate_cmac(@cmac_buffer)
|
210
|
+
if tx != :encrypt && tx != :none && cmd != CMD_ADDITIONAL_FRAME && @authed
|
211
|
+
cmac = @session_key.calculate_cmac(buffer)
|
200
212
|
# Only first 8 bytes of CMAC are transmitted
|
201
|
-
buffer.concat(cmac[0..7]) if tx == :
|
213
|
+
buffer.concat(cmac[0..7]) if tx == :mac
|
202
214
|
end
|
203
215
|
|
204
216
|
received_data = []
|
205
217
|
card_status = nil
|
206
218
|
loop do
|
207
|
-
receive_buffer =
|
219
|
+
receive_buffer = iso_transceive(buffer.shift(@max_inf_size))
|
208
220
|
|
209
221
|
card_status = receive_buffer.shift
|
210
222
|
received_data.concat(receive_buffer)
|
211
223
|
|
212
|
-
break if card_status != ST_ADDITIONAL_FRAME || (buffer.empty? &&
|
224
|
+
break if card_status != ST_ADDITIONAL_FRAME || (buffer.empty? && expect == ST_ADDITIONAL_FRAME)
|
213
225
|
|
214
226
|
buffer.unshift(CMD_ADDITIONAL_FRAME)
|
215
227
|
end
|
@@ -218,72 +230,59 @@ module Mifare
|
|
218
230
|
|
219
231
|
unless error_msg.empty?
|
220
232
|
invalid_auth
|
221
|
-
raise ReceiptStatusError, "0x#{card_status.
|
233
|
+
raise ReceiptStatusError, "0x#{card_status.to_bytehex} - #{error_msg}"
|
222
234
|
end
|
223
235
|
|
224
236
|
if expect && expect != card_status
|
225
237
|
raise UnexpectedDataError, 'Card status does not match expected value'
|
226
238
|
end
|
227
239
|
|
228
|
-
if (rx == :cmac || rx == :send_cmac) && (card_status == ST_SUCCESS || card_status == ST_ADDITIONAL_FRAME) && @authed
|
229
|
-
@cmac_buffer = [] if cmd != CMD_ADDITIONAL_FRAME
|
230
|
-
@cmac_buffer.concat(received_data) if card_status == ST_ADDITIONAL_FRAME
|
231
|
-
|
232
|
-
if received_data.size >= 8 && card_status == ST_SUCCESS
|
233
|
-
received_cmac = received_data.pop(8)
|
234
|
-
@cmac_buffer.concat(received_data + [card_status])
|
235
|
-
cmac = @session_key.calculate_cmac(@cmac_buffer)
|
236
|
-
# Only first 8 bytes of CMAC are transmitted
|
237
|
-
if cmac[0..7] != received_cmac
|
238
|
-
raise ReceiptIntegrityError
|
239
|
-
end
|
240
|
-
end
|
241
|
-
end
|
242
|
-
|
243
240
|
if rx == :encrypt
|
244
241
|
if receive_length.nil?
|
245
|
-
raise
|
242
|
+
raise UsageError, 'Lack of receive length for removing padding'
|
246
243
|
end
|
247
|
-
|
248
|
-
|
244
|
+
@session_key.padding_mode((receive_length > 0) ? 1 : 2)
|
245
|
+
receive_length += 4 # CRC32
|
246
|
+
received_data = @session_key.decrypt(received_data, data_length: receive_length)
|
249
247
|
received_crc = received_data.pop(4).to_uint
|
250
248
|
crc = crc32(received_data, card_status)
|
251
249
|
if crc != received_crc
|
252
250
|
raise ReceiptIntegrityError
|
253
251
|
end
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
return received_data
|
252
|
+
elsif rx != :none && @authed && received_data.size >= 8 && card_status == ST_SUCCESS
|
253
|
+
received_cmac = received_data.pop(8)
|
254
|
+
cmac = @session_key.calculate_cmac(received_data + [card_status])
|
255
|
+
# Only first 8 bytes of CMAC are transmitted
|
256
|
+
if rx == :mac && cmac[0..7] != received_cmac
|
257
|
+
raise ReceiptIntegrityError
|
261
258
|
end
|
262
259
|
end
|
263
260
|
|
264
|
-
|
261
|
+
received_data
|
265
262
|
end
|
266
263
|
|
267
264
|
def auth(key_number, auth_key)
|
268
265
|
cmd = (auth_key.type == :des) ? CMD_DES_AUTH : CMD_AES_AUTH
|
266
|
+
rand_size = (auth_key.cipher_suite == 'des-ede-cbc') ? 8 : 16
|
269
267
|
auth_key.clear_iv
|
268
|
+
auth_key.padding_mode(1)
|
270
269
|
|
271
270
|
# Ask for authentication
|
272
|
-
received_data = transceive(cmd: cmd, data: key_number, expect: ST_ADDITIONAL_FRAME)
|
271
|
+
received_data = transceive(cmd: cmd, data: key_number, expect: ST_ADDITIONAL_FRAME, tx: :none, rx: :none)
|
273
272
|
|
274
273
|
# Receive challenge from DESFire
|
275
|
-
challenge = auth_key.decrypt(received_data)
|
274
|
+
challenge = auth_key.decrypt(received_data, data_length: rand_size)
|
276
275
|
challenge_rot = challenge.rotate
|
277
276
|
|
278
277
|
# Generate random number and encrypt it with rotated challenge
|
279
|
-
random_number = SecureRandom.random_bytes(
|
278
|
+
random_number = SecureRandom.random_bytes(rand_size).bytes
|
280
279
|
response = auth_key.encrypt(random_number + challenge_rot)
|
281
280
|
|
282
281
|
# Send challenge response
|
283
|
-
received_data = transceive(cmd: CMD_ADDITIONAL_FRAME, data: response,
|
282
|
+
received_data = transceive(cmd: CMD_ADDITIONAL_FRAME, data: response, tx: :none, rx: :none)
|
284
283
|
|
285
284
|
# Check if verification matches rotated random_number
|
286
|
-
verification = auth_key.decrypt(received_data)
|
285
|
+
verification = auth_key.decrypt(received_data, data_length: rand_size)
|
287
286
|
|
288
287
|
if random_number.rotate != verification
|
289
288
|
halt
|
@@ -314,7 +313,7 @@ module Mifare
|
|
314
313
|
end
|
315
314
|
|
316
315
|
def get_app_ids
|
317
|
-
ids = transceive(cmd: CMD_GET_APP_IDS,
|
316
|
+
ids = transceive(cmd: CMD_GET_APP_IDS, rx: :mac)
|
318
317
|
|
319
318
|
return ids if ids.empty?
|
320
319
|
|
@@ -329,7 +328,7 @@ module Mifare
|
|
329
328
|
end
|
330
329
|
|
331
330
|
def select_app(id)
|
332
|
-
transceive(cmd: CMD_SELECT_APP, data: convert_app_id(id)
|
331
|
+
transceive(cmd: CMD_SELECT_APP, data: convert_app_id(id))
|
333
332
|
|
334
333
|
invalid_auth
|
335
334
|
@selected_app = id
|
@@ -337,21 +336,21 @@ module Mifare
|
|
337
336
|
|
338
337
|
def create_app(id, key_setting, key_count, cipher_suite)
|
339
338
|
raise UnauthenticatedError unless @authed
|
340
|
-
raise
|
339
|
+
raise UsageError, 'An application can only hold up to 14 keys.' if key_count > 14
|
341
340
|
|
342
|
-
buffer = convert_app_id(id) + [key_setting.
|
341
|
+
buffer = convert_app_id(id) + [key_setting.export, KEY_TYPE.fetch(cipher_suite) | key_count]
|
343
342
|
|
344
|
-
transceive(cmd: CMD_CREATE_APP, data: buffer,
|
343
|
+
transceive(cmd: CMD_CREATE_APP, data: buffer, rx: :mac)
|
345
344
|
end
|
346
345
|
|
347
346
|
def delete_app(id)
|
348
347
|
raise UnauthenticatedError unless @authed
|
349
348
|
|
350
|
-
transceive(cmd: CMD_DELETE_APP, data: convert_app_id(id)
|
349
|
+
transceive(cmd: CMD_DELETE_APP, data: convert_app_id(id))
|
351
350
|
end
|
352
351
|
|
353
352
|
def get_card_version
|
354
|
-
version = transceive(cmd: CMD_GET_CARD_VERSION
|
353
|
+
version = transceive(cmd: CMD_GET_CARD_VERSION)
|
355
354
|
|
356
355
|
CARD_VERSION.new(
|
357
356
|
version[0], version[1], version[2], version[3], version[4], 1 << (version[5] / 2), version[6],
|
@@ -360,22 +359,63 @@ module Mifare
|
|
360
359
|
)
|
361
360
|
end
|
362
361
|
|
362
|
+
def get_free_memory
|
363
|
+
transceive(cmd: CMD_FREE_MEMORY)
|
364
|
+
end
|
365
|
+
|
366
|
+
def get_df_names
|
367
|
+
raise UsageError, 'App 0 should be selected before calling' unless @selected_app == 0
|
368
|
+
|
369
|
+
transceive(cmd: CMD_GET_DF_NAMES)
|
370
|
+
end
|
371
|
+
|
372
|
+
def get_card_uid
|
373
|
+
raise UnauthenticatedError unless @authed
|
374
|
+
|
375
|
+
transceive(cmd: CMD_GET_CARD_UID, rx: :encrypt, receive_length: 7)
|
376
|
+
end
|
377
|
+
|
363
378
|
def format_card
|
364
379
|
raise UnauthenticatedError unless @authed
|
365
380
|
|
366
|
-
transceive(cmd: CMD_FORMAT_CARD
|
381
|
+
transceive(cmd: CMD_FORMAT_CARD)
|
382
|
+
end
|
383
|
+
|
384
|
+
def set_configuration_byte(disable_format, enable_random_uid)
|
385
|
+
raise UnauthenticatedError unless @authed
|
386
|
+
|
387
|
+
flag = 0
|
388
|
+
flag |= 0x01 if disable_format
|
389
|
+
flag |= 0x02 if enable_random_uid
|
390
|
+
|
391
|
+
transceive(cmd: CMD_SET_CONFIGURATION, plain_data: 0x00, data: [flag], tx: :encrypt)
|
392
|
+
end
|
393
|
+
|
394
|
+
def set_default_key(key)
|
395
|
+
raise UnauthenticatedError unless @authed
|
396
|
+
|
397
|
+
buffer = key.key
|
398
|
+
buffer.append_uint(key.version, 1)
|
399
|
+
|
400
|
+
transceive(cmd: CMD_SET_CONFIGURATION, plain_data: 0x01, data: buffer, tx: :encrypt)
|
401
|
+
end
|
402
|
+
|
403
|
+
def set_ats(ats)
|
404
|
+
raise UnauthenticatedError unless @authed
|
405
|
+
|
406
|
+
transceive(cmd: CMD_SET_CONFIGURATION, plain_data: 0x02, data: ats, tx: :encrypt, encrypt_padding: 2)
|
367
407
|
end
|
368
408
|
|
369
409
|
def get_key_version(key_number)
|
370
|
-
received_data = transceive(cmd: CMD_GET_KEY_VERSION, data: key_number,
|
410
|
+
received_data = transceive(cmd: CMD_GET_KEY_VERSION, data: key_number, rx: :mac)
|
371
411
|
|
372
412
|
received_data[0]
|
373
413
|
end
|
374
414
|
|
375
415
|
def change_key(key_number, new_key, curr_key = nil)
|
376
416
|
raise UnauthenticatedError unless @authed
|
377
|
-
raise
|
378
|
-
|
417
|
+
raise UsageError, 'Invalid key number' if key_number > 13
|
418
|
+
|
379
419
|
cryptogram = new_key.key
|
380
420
|
|
381
421
|
same_key = (key_number == @authed)
|
@@ -385,7 +425,7 @@ module Mifare
|
|
385
425
|
|
386
426
|
# XOR new key if we're using different one
|
387
427
|
unless same_key
|
388
|
-
cryptogram = cryptogram.
|
428
|
+
cryptogram = cryptogram.xor(curr_key.key)
|
389
429
|
end
|
390
430
|
|
391
431
|
# AES stores key version separately
|
@@ -400,18 +440,19 @@ module Mifare
|
|
400
440
|
end
|
401
441
|
|
402
442
|
# Encrypt cryptogram
|
443
|
+
@session_key.padding_mode(1)
|
403
444
|
buffer = [key_number] + @session_key.encrypt(cryptogram)
|
404
445
|
|
446
|
+
transceive(cmd: CMD_CHANGE_KEY, data: buffer, tx: :none, rx: :mac)
|
447
|
+
|
405
448
|
# Change current used key will revoke authentication
|
406
449
|
invalid_auth if same_key
|
407
|
-
|
408
|
-
transceive(cmd: CMD_CHANGE_KEY, data: buffer, rx: :cmac, expect: ST_SUCCESS)
|
409
450
|
end
|
410
451
|
|
411
452
|
def get_key_setting
|
412
|
-
received_data = transceive(cmd: CMD_GET_KEY_SETTING
|
453
|
+
received_data = transceive(cmd: CMD_GET_KEY_SETTING)
|
413
454
|
|
414
|
-
{ key_setting: KEY_SETTING.
|
455
|
+
{ key_setting: KEY_SETTING.import(received_data[0]),
|
415
456
|
key_count: received_data[1] & 0x0F,
|
416
457
|
key_type: KEY_TYPE.key(received_data[1] & 0xF0) }
|
417
458
|
end
|
@@ -419,11 +460,11 @@ module Mifare
|
|
419
460
|
def change_key_setting(key_setting)
|
420
461
|
raise UnauthenticatedError unless @authed
|
421
462
|
|
422
|
-
transceive(cmd: CMD_CHANGE_KEY_SETTING, data: key_setting.
|
463
|
+
transceive(cmd: CMD_CHANGE_KEY_SETTING, data: key_setting.export, tx: :encrypt, rx: :mac)
|
423
464
|
end
|
424
465
|
|
425
466
|
def get_file_ids
|
426
|
-
transceive(cmd: CMD_GET_FILE_IDS
|
467
|
+
transceive(cmd: CMD_GET_FILE_IDS)
|
427
468
|
end
|
428
469
|
|
429
470
|
def file_exist?(id)
|
@@ -431,12 +472,12 @@ module Mifare
|
|
431
472
|
end
|
432
473
|
|
433
474
|
def get_file_setting(id)
|
434
|
-
received_data = transceive(cmd: CMD_GET_FILE_SETTING, data: id
|
475
|
+
received_data = transceive(cmd: CMD_GET_FILE_SETTING, data: id)
|
435
476
|
|
436
477
|
file_setting = FILE_SETTING.new
|
437
478
|
file_setting.type = FILE_TYPE.key(received_data.shift)
|
438
479
|
file_setting.communication = FILE_COMMUNICATION.key(received_data.shift)
|
439
|
-
file_setting.permission = FILE_PERMISSION.
|
480
|
+
file_setting.permission = FILE_PERMISSION.import(received_data.shift(2).to_uint)
|
440
481
|
|
441
482
|
case file_setting.type
|
442
483
|
when :std_data_file, :backup_data_file
|
@@ -458,15 +499,15 @@ module Mifare
|
|
458
499
|
def change_file_setting(id, file_setting)
|
459
500
|
buffer = []
|
460
501
|
buffer.append_uint(FILE_COMMUNICATION.fetch(file_setting.communication), 1)
|
461
|
-
buffer.append_uint(file_setting.permission.
|
502
|
+
buffer.append_uint(file_setting.permission.export, 2)
|
462
503
|
|
463
|
-
transceive(cmd: CMD_CHANGE_FILE_SETTING, plain_data: id, data: buffer, tx: :encrypt
|
504
|
+
transceive(cmd: CMD_CHANGE_FILE_SETTING, plain_data: id, data: buffer, tx: :encrypt)
|
464
505
|
end
|
465
506
|
|
466
507
|
def create_file(id, file_setting)
|
467
508
|
buffer = [id]
|
468
509
|
buffer.append_uint(FILE_COMMUNICATION.fetch(file_setting.communication), 1)
|
469
|
-
buffer.append_uint(file_setting.permission.
|
510
|
+
buffer.append_uint(file_setting.permission.export, 2)
|
470
511
|
|
471
512
|
case file_setting.type
|
472
513
|
when :std_data_file, :backup_data_file
|
@@ -483,22 +524,22 @@ module Mifare
|
|
483
524
|
|
484
525
|
cmd = self.class.const_get("CMD_CREATE_#{file_setting.type.to_s.upcase}")
|
485
526
|
|
486
|
-
transceive(cmd: cmd, data: buffer
|
527
|
+
transceive(cmd: cmd, data: buffer)
|
487
528
|
end
|
488
529
|
|
489
530
|
def delete_file(id)
|
490
|
-
transceive(cmd: CMD_DELETE_FILE, data: id
|
531
|
+
transceive(cmd: CMD_DELETE_FILE, data: id)
|
491
532
|
end
|
492
533
|
|
493
534
|
def read_file(id, cmd, data, length)
|
494
535
|
file_setting = get_file_setting(id)
|
495
536
|
length *= file_setting.record_size if file_setting.record_size
|
496
|
-
transceive(cmd: cmd, data: data,
|
537
|
+
transceive(cmd: cmd, data: data, rx: file_setting.communication, receive_length: length)
|
497
538
|
end
|
498
539
|
|
499
540
|
def write_file(id, cmd, plain_data, data)
|
500
541
|
file_setting = get_file_setting(id)
|
501
|
-
transceive(cmd: cmd, plain_data: plain_data, data: data, tx:
|
542
|
+
transceive(cmd: cmd, plain_data: plain_data, data: data, tx: file_setting.communication)
|
502
543
|
end
|
503
544
|
|
504
545
|
def read_data(id, offset, length)
|
@@ -524,7 +565,7 @@ module Mifare
|
|
524
565
|
end
|
525
566
|
|
526
567
|
def credit_value(id, delta)
|
527
|
-
raise
|
568
|
+
raise UsageError, 'Negative number is not allowed.' if delta < 0
|
528
569
|
|
529
570
|
buffer = []
|
530
571
|
buffer.append_sint(delta, 4)
|
@@ -533,7 +574,7 @@ module Mifare
|
|
533
574
|
end
|
534
575
|
|
535
576
|
def debit_value(id, delta)
|
536
|
-
raise
|
577
|
+
raise UsageError, 'Negative number is not allowed.' if delta < 0
|
537
578
|
|
538
579
|
buffer = []
|
539
580
|
buffer.append_sint(delta, 4)
|
@@ -542,7 +583,7 @@ module Mifare
|
|
542
583
|
end
|
543
584
|
|
544
585
|
def limited_credit_value(id, delta)
|
545
|
-
raise
|
586
|
+
raise UsageError, 'Negative number is not allowed.' if delta < 0
|
546
587
|
|
547
588
|
buffer = []
|
548
589
|
buffer.append_sint(delta, 4)
|
@@ -569,15 +610,15 @@ module Mifare
|
|
569
610
|
end
|
570
611
|
|
571
612
|
def clear_record(id)
|
572
|
-
transceive(cmd: CMD_CLEAR_RECORD_FILE, data: id
|
613
|
+
transceive(cmd: CMD_CLEAR_RECORD_FILE, data: id)
|
573
614
|
end
|
574
615
|
|
575
616
|
def commit_transaction
|
576
|
-
transceive(cmd: CMD_COMMIT_TRANSACTION
|
617
|
+
transceive(cmd: CMD_COMMIT_TRANSACTION)
|
577
618
|
end
|
578
619
|
|
579
620
|
def abort_transaction
|
580
|
-
transceive(cmd: CMD_ABORT_TRANSACTION
|
621
|
+
transceive(cmd: CMD_ABORT_TRANSACTION)
|
581
622
|
end
|
582
623
|
|
583
624
|
private
|
@@ -585,56 +626,14 @@ module Mifare
|
|
585
626
|
def invalid_auth
|
586
627
|
@authed = false
|
587
628
|
@session_key = nil
|
588
|
-
@cmac_buffer = []
|
589
629
|
end
|
590
630
|
|
591
631
|
def convert_app_id(id)
|
592
|
-
raise
|
632
|
+
raise UsageError, 'Application ID overflow' if id < 0 || id >= (1 << 24)
|
593
633
|
|
594
634
|
[].append_uint(id, 3)
|
595
635
|
end
|
596
636
|
|
597
|
-
def crc32(*datas)
|
598
|
-
crc = 0xFFFFFFFF
|
599
|
-
|
600
|
-
datas.each do |data|
|
601
|
-
data = [data] unless data.is_a? Array
|
602
|
-
data.each do |byte|
|
603
|
-
crc ^= byte
|
604
|
-
8.times do
|
605
|
-
flag = crc & 0x01 > 0
|
606
|
-
crc >>= 1
|
607
|
-
crc ^= 0xEDB88320 if flag
|
608
|
-
end
|
609
|
-
end
|
610
|
-
end
|
611
|
-
crc
|
612
|
-
end
|
613
|
-
|
614
|
-
# Remove trailing padding bytes
|
615
|
-
def remove_padding_bytes(data, length)
|
616
|
-
if length == 0
|
617
|
-
# padding format according to ISO 9797-1
|
618
|
-
str = data.pack('C*')
|
619
|
-
str.sub! /#{0x80.chr}#{0x00.chr}*\z/, ''
|
620
|
-
str.bytes
|
621
|
-
else
|
622
|
-
# data length + 4 bytes CRC
|
623
|
-
data[0...length + 4]
|
624
|
-
end
|
625
|
-
end
|
626
|
-
|
627
|
-
def convert_file_communication(communication)
|
628
|
-
case communication
|
629
|
-
when :plain
|
630
|
-
:cmac
|
631
|
-
when :mac
|
632
|
-
:send_cmac
|
633
|
-
when :encrypt
|
634
|
-
:encrypt
|
635
|
-
end
|
636
|
-
end
|
637
|
-
|
638
637
|
def check_status_code(code)
|
639
638
|
case code
|
640
639
|
when ST_SUCCESS, ST_ADDITIONAL_FRAME
|