mfrc522 1.0.6 → 2.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 +4 -4
- data/lib/core_ext.rb +38 -0
- data/lib/exceptions.rb +1 -0
- data/lib/mfrc522.rb +37 -106
- 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 +7 -3
- data/lib/iso144434.rb +0 -247
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4c15749e6896a2e58865f9e6639bc829fa717068
|
4
|
+
data.tar.gz: e340cba7fdc0810a3d581b8a14d329bba72886e4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7b88ed0c68070b2bafe8e3bd7e25304852a24bd88c609b1688b2c5e1ce830114c3540801482c7aee5276cd357226dfb49884fea1c9c79fba56e6a9c47a73d293
|
7
|
+
data.tar.gz: 94e20de85a0185679b75a2902516406f6e5ed499433f5195f26493621ddac779932ca3c61b88a2c487a044db1e0ac56a489d9ec4a21aae47b842b190d782d98f
|
data/lib/core_ext.rb
CHANGED
@@ -32,4 +32,42 @@ class Array
|
|
32
32
|
sign = (self.last & 0x80 != 0) ? (-1 ^ ((1 << ((self.size * 8) - 1)) - 1)) : 0
|
33
33
|
sign | self.to_uint
|
34
34
|
end
|
35
|
+
|
36
|
+
def append_crc16
|
37
|
+
append_uint(crc16, 2)
|
38
|
+
end
|
39
|
+
|
40
|
+
def check_crc16(remove_after_check = false)
|
41
|
+
orig_crc = pop(2)
|
42
|
+
old_crc = (orig_crc[1] << 8) + orig_crc[0]
|
43
|
+
new_crc = crc16
|
44
|
+
concat(orig_crc) unless remove_after_check
|
45
|
+
old_crc == new_crc
|
46
|
+
end
|
47
|
+
|
48
|
+
def xor(array2)
|
49
|
+
zip(array2).map{|x, y| x ^ y }
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_bytehex
|
53
|
+
map{|x| x.to_bytehex}
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def crc16
|
59
|
+
crc = 0x6363
|
60
|
+
self.each do |byte|
|
61
|
+
bb = (byte ^ crc) & 0xFF
|
62
|
+
bb = (bb ^ (bb << 4)) & 0xFF
|
63
|
+
crc = (crc >> 8) ^ (bb << 8) ^ (bb << 3) ^ (bb >> 4)
|
64
|
+
end
|
65
|
+
crc & 0xFFFF
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class Numeric
|
70
|
+
def to_bytehex
|
71
|
+
self.to_s(16).rjust(2, '0').upcase
|
72
|
+
end
|
35
73
|
end
|
data/lib/exceptions.rb
CHANGED
data/lib/mfrc522.rb
CHANGED
@@ -7,13 +7,14 @@ require 'core_ext'
|
|
7
7
|
require 'exceptions'
|
8
8
|
|
9
9
|
require 'picc'
|
10
|
-
require 'iso144434'
|
11
10
|
|
12
11
|
require 'mifare/key'
|
13
12
|
require 'mifare/classic'
|
13
|
+
require 'mifare/plus'
|
14
|
+
require 'mifare/des_fire'
|
14
15
|
require 'mifare/ultralight'
|
15
16
|
require 'mifare/ultralight_c'
|
16
|
-
require 'mifare/
|
17
|
+
require 'mifare/ultralight_ev1'
|
17
18
|
|
18
19
|
include PiPiper
|
19
20
|
|
@@ -27,8 +28,6 @@ class MFRC522
|
|
27
28
|
PICC_SEL_CL2 = 0x95 # Anti collision/Select, Cascade Level 2
|
28
29
|
PICC_SEL_CL3 = 0x97 # Anti collision/Select, Cascade Level 3
|
29
30
|
PICC_HLTA = 0x50 # HaLT command, Type A. Instructs an ACTIVE PICC to go to state HALT.
|
30
|
-
# Mifare Acknowledge
|
31
|
-
PICC_MF_ACK = 0x0A
|
32
31
|
|
33
32
|
# PCD commands
|
34
33
|
PCD_Idle = 0x00 # no action, cancels current command execution
|
@@ -135,6 +134,12 @@ class MFRC522
|
|
135
134
|
|
136
135
|
# Reset PCD config to default
|
137
136
|
def pcd_config_reset
|
137
|
+
# Stop current command
|
138
|
+
write_spi(CommandReg, PCD_Idle)
|
139
|
+
|
140
|
+
# Stop crypto1 communication
|
141
|
+
mifare_crypto1_deauthenticate
|
142
|
+
|
138
143
|
# Clear ValuesAfterColl bit
|
139
144
|
write_spi_clear_bitmask(CollReg, 0x80)
|
140
145
|
|
@@ -159,10 +164,13 @@ class MFRC522
|
|
159
164
|
# value = 0: 106kBd, 1: 212kBd, 2: 424kBd, 3: 848kBd
|
160
165
|
def transceiver_baud_rate(direction, value = nil)
|
161
166
|
reg = {tx: TxModeReg, rx: RxModeReg}
|
167
|
+
mod = {0 => 0x26, 1 => 0x15, 2 => 0x0A, 3 => 0x05}
|
162
168
|
|
163
169
|
if value
|
170
|
+
@built_in_crc_disabled = (value == 0)
|
171
|
+
write_spi(ModWidthReg, mod.fetch(value))
|
164
172
|
value <<= 4
|
165
|
-
value |= 0x80
|
173
|
+
value |= 0x80 unless @built_in_crc_disabled
|
166
174
|
write_spi(reg.fetch(direction), value)
|
167
175
|
end
|
168
176
|
|
@@ -189,6 +197,10 @@ class MFRC522
|
|
189
197
|
(read_spi(RFCfgReg) & 0x70) >> 4
|
190
198
|
end
|
191
199
|
|
200
|
+
def buffer_size
|
201
|
+
64
|
202
|
+
end
|
203
|
+
|
192
204
|
# Wakes PICC from HALT or IDLE to ACTIVE state
|
193
205
|
# Accept PICC_REQA and PICC_WUPA command
|
194
206
|
def picc_request(picc_command)
|
@@ -201,7 +213,7 @@ class MFRC522
|
|
201
213
|
|
202
214
|
# Instruct PICC in ACTIVE state go to HALT state
|
203
215
|
def picc_halt
|
204
|
-
buffer =
|
216
|
+
buffer = [PICC_HLTA, 0].append_crc16
|
205
217
|
|
206
218
|
status, _received_data, _valid_bits = communicate_with_picc(PCD_Transceive, buffer)
|
207
219
|
|
@@ -213,7 +225,7 @@ class MFRC522
|
|
213
225
|
# Select PICC for further communication
|
214
226
|
#
|
215
227
|
# PICC must be in state ACTIVE
|
216
|
-
def picc_select
|
228
|
+
def picc_select(disable_anticollision = false)
|
217
229
|
# Description of buffer structure:
|
218
230
|
#
|
219
231
|
# Byte 0: SEL Indicates the Cascade Level: PICC_CMD_SEL_CL1, PICC_CMD_SEL_CL2 or PICC_CMD_SEL_CL3
|
@@ -281,7 +293,7 @@ class MFRC522
|
|
281
293
|
buffer[6] = (buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5]) # Block Check Character
|
282
294
|
|
283
295
|
# Append CRC to buffer
|
284
|
-
buffer
|
296
|
+
buffer.append_crc16
|
285
297
|
else
|
286
298
|
tx_last_bits = current_level_known_bits % 8
|
287
299
|
uid_full_byte = current_level_known_bits / 8
|
@@ -299,6 +311,8 @@ class MFRC522
|
|
299
311
|
|
300
312
|
if status != :status_ok && status != :status_collision
|
301
313
|
raise CommunicationError, status
|
314
|
+
elsif status == :status_collision && disable_anticollision
|
315
|
+
raise CollisionError
|
302
316
|
end
|
303
317
|
|
304
318
|
if received_data.empty?
|
@@ -356,7 +370,7 @@ class MFRC522
|
|
356
370
|
# Check the result of full select
|
357
371
|
# Select Acknowledge is 1 byte + CRC16
|
358
372
|
raise UnexpectedDataError, 'Unknown SAK format' if received_data.size != 3 || valid_bits != 0
|
359
|
-
raise IncorrectCRCError unless
|
373
|
+
raise IncorrectCRCError unless received_data.check_crc16(true)
|
360
374
|
|
361
375
|
sak = received_data[0]
|
362
376
|
break if (sak & 0x04) == 0 # No more cascade level
|
@@ -380,54 +394,6 @@ class MFRC522
|
|
380
394
|
status && uid == new_uid
|
381
395
|
end
|
382
396
|
|
383
|
-
# Lookup PICC name using sak
|
384
|
-
def identify_model(sak)
|
385
|
-
# SAK coding separation reference:
|
386
|
-
# http://cache.nxp.com/documents/application_note/AN10833.pdf
|
387
|
-
# http://www.nxp.com/documents/application_note/130830.pdf
|
388
|
-
if sak & 0x04 != 0
|
389
|
-
return :picc_uid_not_complete
|
390
|
-
end
|
391
|
-
|
392
|
-
if sak & 0x02 != 0
|
393
|
-
return :picc_reserved_future_use
|
394
|
-
end
|
395
|
-
|
396
|
-
if sak & 0x08 != 0
|
397
|
-
if sak & 0x10 != 0
|
398
|
-
return :picc_mifare_4k
|
399
|
-
end
|
400
|
-
|
401
|
-
if sak & 0x01 != 0
|
402
|
-
return :picc_mifare_mini
|
403
|
-
end
|
404
|
-
|
405
|
-
return :picc_mifare_1k
|
406
|
-
end
|
407
|
-
|
408
|
-
if sak & 0x10 != 0
|
409
|
-
if sak & 0x01 != 0
|
410
|
-
return :picc_mifare_plus_4k_sl2
|
411
|
-
end
|
412
|
-
|
413
|
-
return :picc_mifare_plus_2k_sl2
|
414
|
-
end
|
415
|
-
|
416
|
-
if sak == 0x00
|
417
|
-
return :picc_mifare_ultralight
|
418
|
-
end
|
419
|
-
|
420
|
-
if sak & 0x20 != 0
|
421
|
-
return :picc_iso_14443_4
|
422
|
-
end
|
423
|
-
|
424
|
-
if sak & 0x40 != 0
|
425
|
-
return :picc_iso_18092
|
426
|
-
end
|
427
|
-
|
428
|
-
return :picc_unknown
|
429
|
-
end
|
430
|
-
|
431
397
|
# Start Crypto1 communication between reader and Mifare PICC
|
432
398
|
#
|
433
399
|
# PICC must be selected before calling for authentication
|
@@ -455,29 +421,26 @@ class MFRC522
|
|
455
421
|
|
456
422
|
# Append CRC to buffer and check CRC or Mifare acknowledge
|
457
423
|
def picc_transceive(send_data, accept_timeout = false)
|
458
|
-
send_data =
|
424
|
+
send_data = send_data.dup
|
425
|
+
send_data.append_crc16 if @built_in_crc_disabled
|
459
426
|
|
460
|
-
puts "Sending Data: #{send_data.
|
427
|
+
puts "Sending Data: #{send_data.to_bytehex}" if ENV['DEBUG']
|
461
428
|
|
462
429
|
# Transfer data
|
463
430
|
status, received_data, valid_bits = communicate_with_picc(PCD_Transceive, send_data)
|
464
|
-
return
|
431
|
+
return if status == :status_picc_timeout && accept_timeout
|
465
432
|
raise PICCTimeoutError if status == :status_picc_timeout
|
466
433
|
raise CommunicationError, status if status != :status_ok
|
467
434
|
|
468
|
-
puts "Received Data: #{received_data.
|
435
|
+
puts "Received Data: #{received_data.to_bytehex}" if ENV['DEBUG']
|
436
|
+
puts "Valid bits: #{valid_bits}" if ENV['DEBUG']
|
469
437
|
|
470
|
-
# Data exists, check CRC
|
471
|
-
if received_data.size >
|
472
|
-
raise IncorrectCRCError unless
|
473
|
-
|
474
|
-
return received_data[0..-3]
|
438
|
+
# Data exists, check CRC
|
439
|
+
if received_data.size > 2 && @built_in_crc_disabled
|
440
|
+
raise IncorrectCRCError unless received_data.check_crc16(true)
|
475
441
|
end
|
476
442
|
|
477
|
-
|
478
|
-
raise MifareNakError, received_data[0] if received_data[0] != PICC_MF_ACK
|
479
|
-
|
480
|
-
received_data
|
443
|
+
return received_data, valid_bits
|
481
444
|
end
|
482
445
|
|
483
446
|
private
|
@@ -551,7 +514,10 @@ class MFRC522
|
|
551
514
|
|
552
515
|
# Check for error
|
553
516
|
error = read_spi(ErrorReg)
|
554
|
-
return :
|
517
|
+
return :status_buffer_overflow if (error & 0x10) != 0
|
518
|
+
return :status_crc_error if (error & 0x04) != 0
|
519
|
+
return :status_parity_error if (error & 0x02) != 0
|
520
|
+
return :status_protocol_error if (error & 0x01) != 0
|
555
521
|
|
556
522
|
# Receiving data
|
557
523
|
received_data = []
|
@@ -568,39 +534,4 @@ class MFRC522
|
|
568
534
|
|
569
535
|
return status, received_data, valid_bits
|
570
536
|
end
|
571
|
-
|
572
|
-
def calculate_crc(data)
|
573
|
-
write_spi(CommandReg, PCD_Idle) # Stop any active command.
|
574
|
-
write_spi(DivIrqReg, 0x04) # Clear the CRCIRq interrupt request bit
|
575
|
-
write_spi_set_bitmask(FIFOLevelReg, 0x80) # FlushBuffer = 1, FIFO initialization
|
576
|
-
write_spi(FIFODataReg, data) # Write data to the FIFO
|
577
|
-
write_spi(CommandReg, PCD_CalcCRC) # Start the calculation
|
578
|
-
|
579
|
-
# Wait for the command to complete
|
580
|
-
i = 5000
|
581
|
-
loop do
|
582
|
-
irq = read_spi(DivIrqReg)
|
583
|
-
break if (irq & 0x04) != 0
|
584
|
-
raise PCDTimeoutError, 'Error calculating CRC' if i == 0
|
585
|
-
i -= 1
|
586
|
-
end
|
587
|
-
|
588
|
-
write_spi(CommandReg, PCD_Idle) # Stop calculating CRC for new content in the FIFO.
|
589
|
-
|
590
|
-
[read_spi(CRCResultRegL), read_spi(CRCResultRegH)]
|
591
|
-
end
|
592
|
-
|
593
|
-
def append_crc(data)
|
594
|
-
data + calculate_crc(data)
|
595
|
-
end
|
596
|
-
|
597
|
-
def check_crc(data)
|
598
|
-
raise UnexpectedDataError, 'Data too short for CRC check' if data.size < 3
|
599
|
-
|
600
|
-
data = data.dup
|
601
|
-
crc = data.pop(2)
|
602
|
-
|
603
|
-
crc == calculate_crc(data)
|
604
|
-
end
|
605
|
-
|
606
537
|
end
|
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
|