mfrc522 1.0.6 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6970a8238b5e32859472bec37f4a0c7327dd8bf7
4
- data.tar.gz: cbeac674373f82caa74e5fc91179176bb2c40c86
3
+ metadata.gz: 4c15749e6896a2e58865f9e6639bc829fa717068
4
+ data.tar.gz: e340cba7fdc0810a3d581b8a14d329bba72886e4
5
5
  SHA512:
6
- metadata.gz: edbb612b4d103e712eff156431ebdcc55b601cbf74a666d39a34b8246f027d5cfd5d2a938fcb4b33612f7970187bd68983e5d391c05b3eb586a33374e8b9f02e
7
- data.tar.gz: 3affc064c30c146115c0c0d1894fd1e71a519af1557700178b07ba1a5e3655e55a0102e989f6c3e4a3da04e1164dbe7a56604b997d46e0c5364aa3a6ba30a753
6
+ metadata.gz: 7b88ed0c68070b2bafe8e3bd7e25304852a24bd88c609b1688b2c5e1ce830114c3540801482c7aee5276cd357226dfb49884fea1c9c79fba56e6a9c47a73d293
7
+ data.tar.gz: 94e20de85a0185679b75a2902516406f6e5ed499433f5195f26493621ddac779932ca3c61b88a2c487a044db1e0ac56a489d9ec4a21aae47b842b190d782d98f
@@ -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
@@ -5,6 +5,7 @@ class IncorrectCRCError < CommunicationError; end
5
5
  class CollisionError < CommunicationError; end
6
6
 
7
7
  class UnexpectedDataError < StandardError; end
8
+ class UsageError < StandardError; end
8
9
 
9
10
  class MifareNakError < StandardError; end
10
11
 
@@ -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/des_fire'
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 if value != 0
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 = append_crc([PICC_HLTA, 0])
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 = append_crc(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 check_crc(received_data)
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 = append_crc(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.map{|x|x.to_s(16).rjust(2,'0').upcase}}" if ENV['DEBUG']
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 [] if status == :status_picc_timeout && accept_timeout
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.map{|x|x.to_s(16).rjust(2,'0').upcase}}" if ENV['DEBUG']
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 and return
471
- if received_data.size > 1
472
- raise IncorrectCRCError unless check_crc(received_data)
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
- raise UnexpectedDataError, 'Incorrect Mifare ACK format' if received_data.size != 1 || valid_bits != 4 # ACK is 4 bits long
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 :status_error if (error & 0x13) != 0 # BufferOvfl ParityErr ProtocolErr
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
@@ -1,4 +1,4 @@
1
- module Mifare
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 UnexpectedDataError, 'Missing key data'
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 UnexpectedDataError, "Expect 6 bytes auth key, got: #{key.size} byte"
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
- buffer = [CMD_READ, block_addr]
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 UnexpectedDataError, "Expect 16 bytes data, got: #{send_data.size} byte"
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
- @pcd.picc_transceive(buffer)
64
+ transceive([CMD_WRITE, block_addr])
52
65
 
53
66
  # Then start transfer our data
54
- @pcd.picc_transceive(send_data)
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] = ~block_addr
110
+ buffer[13] = ~buffer[12]
90
111
  buffer[14] = buffer[12]
91
- buffer[15] = buffer[13]
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
- buffer = [CMD_TRANSFER, block_addr]
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
- @pcd.picc_transceive(buffer)
145
+ transceive(buffer)
127
146
 
128
147
  # Then start transfer our data
129
- @pcd.picc_transceive(send_data, true) # Accept timeout
148
+ transceive(send_data, true) # Accept timeout
130
149
  end
131
150
  end
132
151
  end
@@ -1,5 +1,5 @@
1
- module Mifare
2
- class DESFire < ::ISO144434
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(0x00~0x0D) required for `change_key`
71
- # 0x0E means same key, 0x0F freezes all keys
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
- :mk_changeable,
75
- # Set if listing requires master key
76
- :listing_without_mk,
77
- # Set if create or delete requires master key
78
- :create_delete_without_mk,
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
- :configuration_changeable) do
81
- def initialize(*data)
88
+ :key_setting_changeable) do
89
+ def initialize(*)
82
90
  super
83
- default
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 default
87
- self[:privileged_key] = 0 unless privileged_key
88
- self[:mk_changeable] = true unless mk_changeable
89
- self[:listing_without_mk] = true unless listing_without_mk
90
- self[:create_delete_without_mk] = true unless create_delete_without_mk
91
- self[:configuration_changeable] = true unless configuration_changeable
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 import(byte)
95
- self[:privileged_key] = (byte >> 4) & 0x0F
96
- self[:mk_changeable] = byte & 0x01 != 0
97
- self[:listing_without_mk] = (byte >> 1) & 0x01 != 0
98
- self[:create_delete_without_mk] = (byte >> 2) & 0x01 != 0
99
- self[:configuration_changeable] = (byte >> 3) & 0x01 != 0
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[:change_access] = byte & 0x0F
124
- self[:read_write_access] = (byte >> 4) & 0x0F
125
- self[:write_access] = (byte >> 8) & 0x0F
126
- self[:read_access] = (byte >> 12) & 0x0F
127
- self
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 to_uint
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, receive_all: nil, receive_length: 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 (tx == :cmac || tx == :send_cmac) && cmd != CMD_ADDITIONAL_FRAME && @authed
198
- @cmac_buffer = buffer
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 == :send_cmac
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 = super(buffer.shift(@max_inf_size))
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? && !receive_all)
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.to_s(16).rjust(2, '0').upcase} - #{error_msg}"
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 UnexpectedDataError, 'Lack of receive length for removing padding'
242
+ raise UsageError, 'Lack of receive length for removing padding'
246
243
  end
247
- received_data = @session_key.decrypt(received_data)
248
- received_data = remove_padding_bytes(received_data, receive_length)
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
- end
255
-
256
- if expect
257
- if received_data.empty? && !return_data
258
- return true
259
- else
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
- return card_status, received_data
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(received_data.size).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, expect: ST_SUCCESS)
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, tx: :cmac, rx: :cmac, expect: ST_SUCCESS, return_data: true, receive_all: true)
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), expect: ST_SUCCESS)
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 UnexpectedDataError, 'An application can only hold up to 14 keys.' if key_count > 14
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.to_uint, KEY_TYPE.fetch(cipher_suite) | key_count]
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, tx: :cmac, rx: :cmac, expect: ST_SUCCESS)
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), tx: :cmac, rx: :cmac, expect: ST_SUCCESS)
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, tx: :cmac, rx: :cmac, expect: ST_SUCCESS, receive_all: true)
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, tx: :cmac, rx: :cmac, expect: ST_SUCCESS)
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, tx: :cmac, rx: :cmac, expect: ST_SUCCESS)
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 UnexpectedDataError, 'Invalid key number' if key_number > 13
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.zip(curr_key.key).map{|x, y| x ^ y }
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, tx: :cmac, rx: :cmac, expect: ST_SUCCESS)
453
+ received_data = transceive(cmd: CMD_GET_KEY_SETTING)
413
454
 
414
- { key_setting: KEY_SETTING.new.import(received_data[0]),
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.to_uint, tx: :encrypt, rx: :cmac, expect: ST_SUCCESS)
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, tx: :cmac, rx: :cmac, expect: ST_SUCCESS, return_data: true)
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, tx: :cmac, rx: :cmac, expect: ST_SUCCESS)
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.new.import(received_data.shift(2).to_uint)
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.to_uint, 2)
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, rx: :cmac, expect: ST_SUCCESS)
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.to_uint, 2)
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, tx: :cmac, rx: :cmac, expect: ST_SUCCESS)
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, tx: :cmac, rx: :cmac, expect: ST_SUCCESS)
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, tx: :cmac, rx: convert_file_communication(file_setting.communication), expect: ST_SUCCESS, receive_all: true, receive_length: length)
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: convert_file_communication(file_setting.communication), rx: :cmac, expect: ST_SUCCESS)
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 UnexpectedDataError, 'Negative number is not allowed.' if delta < 0
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 UnexpectedDataError, 'Negative number is not allowed.' if delta < 0
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 UnexpectedDataError, 'Negative number is not allowed.' if delta < 0
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, tx: :cmac, rx: :cmac, expect: ST_SUCCESS)
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, tx: :cmac, rx: :cmac, expect: ST_SUCCESS)
617
+ transceive(cmd: CMD_COMMIT_TRANSACTION)
577
618
  end
578
619
 
579
620
  def abort_transaction
580
- transceive(cmd: CMD_ABORT_TRANSACTION, tx: :cmac, rx: :cmac, expect: ST_SUCCESS)
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 UnexpectedDataError, 'Application ID overflow' if id < 0 || id >= (1 << 24)
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