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.
data/lib/mifare/key.rb CHANGED
@@ -1,4 +1,4 @@
1
- module Mifare
1
+ module MIFARE
2
2
  class Key
3
3
  attr_reader :type
4
4
  attr_reader :cipher_suite
@@ -12,25 +12,52 @@ module Mifare
12
12
  init_cipher
13
13
  end
14
14
 
15
+ # Padding Method according to ISO-9797
16
+ def padding_mode(mode)
17
+ if mode != 1 && mode != 2
18
+ raise UsageError, 'Unknown padding mode'
19
+ end
20
+ @padding_mode = mode
21
+ end
22
+
15
23
  def key
16
24
  @key.bytes
17
25
  end
18
26
 
19
- def encrypt(data, cbc_mode = :send)
27
+ def encrypt(data, cbc_mode: :send, data_length: nil)
20
28
  @cipher.encrypt
21
29
 
22
30
  # Add padding if not a complete block
23
- until data.size % @block_size == 0
24
- data << 0x00
31
+ if data.size % @block_size != 0
32
+ raise UsageError, 'Padding mode not set' unless @padding_mode
33
+ data << 0x80 if @padding_mode == 2
34
+ until data.size % @block_size == 0
35
+ data << 0x00
36
+ end
25
37
  end
26
38
 
27
39
  cbc_crypt(data, cbc_mode)
28
40
  end
29
41
 
30
- def decrypt(data, cbc_mode = :receive)
42
+ def decrypt(data, cbc_mode: :receive, data_length: nil)
31
43
  @cipher.decrypt
32
44
 
33
- cbc_crypt(data, cbc_mode)
45
+ data = cbc_crypt(data, cbc_mode)
46
+ if @padding_mode == 1
47
+ data[0...data_length]
48
+ elsif @padding_mode == 2
49
+ str = data.pack('C*')
50
+ str.sub! /#{0x80.chr}#{0x00.chr}*\z/, ''
51
+ str.bytes
52
+ else
53
+ raise UsageError, 'Padding mode not set'
54
+ end
55
+ end
56
+
57
+ def set_iv(iv)
58
+ iv = iv.pack('C*')
59
+ raise UsageError, 'Incorrect IV length' if iv.size != @block_size
60
+ @cipher_iv = iv
34
61
  end
35
62
 
36
63
  def clear_iv
@@ -42,7 +69,7 @@ module Mifare
42
69
  data = Array.new(@block_size, 0)
43
70
 
44
71
  clear_iv
45
- data = encrypt(data, :receive)
72
+ data = encrypt(data, cbc_mode: :receive)
46
73
 
47
74
  @cmac_subkey1 = bit_shift_left(data)
48
75
  @cmac_subkey1[-1] ^= r if data[0] & 0x80 != 0
@@ -53,7 +80,7 @@ module Mifare
53
80
 
54
81
  def calculate_cmac(data)
55
82
  if @cmac_subkey1.nil? || @cmac_subkey2.nil?
56
- raise 'Generate subkeys before calculating CMAC'
83
+ raise UsageError, 'Generate subkeys before calculating CMAC'
57
84
  end
58
85
 
59
86
  # Separate from input object
@@ -92,7 +119,7 @@ module Mifare
92
119
 
93
120
  if key_type == :des
94
121
  if @key_size != 8 && @key_size != 16 && @key_size != 24
95
- raise UnexpectedDataError, 'Incorrect key length'
122
+ raise UsageError, 'Incorrect key length'
96
123
  end
97
124
 
98
125
  # Data block size for DES is 8 bytes
@@ -114,7 +141,7 @@ module Mifare
114
141
 
115
142
  elsif key_type == :aes
116
143
  if @key_size != 16
117
- raise UnexpectedDataError, 'Incorrect key length'
144
+ raise UsageError, 'Incorrect key length'
118
145
  end
119
146
 
120
147
  # data block size for AES is 16 bytes
@@ -122,7 +149,7 @@ module Mifare
122
149
  @key = key
123
150
  @cipher_suite = 'aes-128-cbc'
124
151
  else
125
- raise UnexpectedDataError, 'Unknown key type'
152
+ raise UsageError, 'Unknown key type'
126
153
  end
127
154
 
128
155
  @key = @key.pack('C*')
@@ -162,7 +189,7 @@ module Mifare
162
189
 
163
190
  output_data.bytes
164
191
  else
165
- raise UnexpectedDataError, 'Unknown CBC mode'
192
+ raise UsageError, 'Unknown CBC mode'
166
193
  end
167
194
  end
168
195
 
@@ -0,0 +1,124 @@
1
+ module MIFARE
2
+ class Plus < ::PICC
3
+ CMD_WRITE_PERSO = 0xA8
4
+ CMD_COMMIT_PERSO = 0xAA
5
+ CMD_MULTI_BLOCK_READ = 0x38
6
+ CMD_MULTI_BLOCK_WRITE = 0xA8
7
+ CMD_FIRST_AUTH = 0x70
8
+ CMD_SECOND_AUTH = 0x72
9
+ CMD_FOLLOWING_AUTH = 0x76
10
+ CMD_RESET_AUTH = 0x78
11
+ CMD_VC_DESELECT = 0x48
12
+ MF_ACK = 0x0A
13
+
14
+ def initialize(pcd, uid, sak)
15
+ super
16
+ invalid_auth
17
+ reset_counter
18
+ end
19
+
20
+ def authed?
21
+ !@transaction_identifier.empty?
22
+ end
23
+
24
+ def transceive(cmd: , plain_data: [], data: [], tx: nil, rx: nil)
25
+ raise UsageError, 'Call `iso_select` before using commands' unless @iso_selected
26
+ iso_transceive(send_data)
27
+ end
28
+
29
+ def auth(key_number, auth_key)
30
+ cmd = authed? ? CMD_FOLLOWING_AUTH : CMD_FIRST_AUTH
31
+ auth_key.padding_mode(2)
32
+
33
+ buffer = [cmd].append_uint(key_number, 2)
34
+ buffer << 0x00 unless authed? # No PCDCaps2 to send
35
+
36
+ # Ask for authentication
37
+ received_data = iso_transceive(buffer)
38
+
39
+ # Receive challenge
40
+ auth_key.clear_iv
41
+ auth_key.set_iv(generate_iv(:decrypt)) if authed?
42
+ challenge = auth_key.decrypt(received_data)
43
+ challenge_rot = challenge.rotate
44
+
45
+ # Generate random number and encrypt it with rotated challenge
46
+ random_number = SecureRandom.random_bytes(received_data.size).bytes
47
+ auth_key.clear_iv
48
+ auth_key.set_iv(generate_iv(:encrypt)) if authed?
49
+ response = auth_key.encrypt(random_number + challenge_rot)
50
+
51
+ # Send challenge response
52
+ received_data = iso_transceive([CMD_SECOND_AUTH, *response])
53
+
54
+ # Check if verification matches rotated random_number
55
+ auth_key.clear_iv
56
+ auth_key.set_iv(generate_iv(:decrypt)) if authed?
57
+ verification = auth_key.decrypt(received_data)
58
+ @transaction_identifier = verification.shift(4)
59
+ response_rot = verification.shift(16)
60
+
61
+ if random_number.rotate != response_rot
62
+ raise ReceiptIntegrityError, 'Authentication Failed'
63
+ end
64
+
65
+ byte_a = random_number[11..15]
66
+ byte_b = challenge[11..15]
67
+ byte_c = random_number[4..8]
68
+ byte_d = challenge[4..8]
69
+ byte_e = random_number[7..11]
70
+ byte_f = challenge[7..11]
71
+ byte_g = random_number[0..4]
72
+ byte_h = challenge[0..4]
73
+ byte_i = byte_c.xor(byte_d)
74
+ byte_j = byte_g.xor(byte_h)
75
+
76
+ enc_key_base = byte_a + byte_b + byte_i + [0x11]
77
+ mac_key_base = byte_e + byte_f + byte_j + [0x22]
78
+
79
+ auth_key.clear_iv
80
+ auth_key.set_iv(generate_iv(:encrypt)) if authed?
81
+ enc_key = auth_key.encrypt(enc_key_base)
82
+ @enc_key = Key.new(:aes, enc_key)
83
+
84
+ auth_key.clear_iv
85
+ auth_key.set_iv(generate_iv(:encrypt)) if authed?
86
+ mac_key = auth_key.encrypt(mac_key_base)
87
+ @mac_key = Key.new(:aes, mac_key)
88
+
89
+ reset_counter
90
+ end
91
+
92
+ protected
93
+
94
+ def generate_iv(operation)
95
+ buffer = [].append_uint(@read_counter, 2).append_uint(@write_counter, 2)
96
+ buffer.concat(buffer, buffer)
97
+
98
+ if operation == :encrypt
99
+ buffer.unshift(@transaction_identifier)
100
+ elsif operation == :decrypt
101
+ buffer.concat(@transaction_identifier)
102
+ else
103
+ raise UsageError, 'Unknown operation mode'
104
+ end
105
+ end
106
+
107
+ def generate_mac_payload
108
+
109
+ end
110
+
111
+ def reset_counter
112
+ @session_read_counter = 0
113
+ @read_counter = 0
114
+ @write_counter = 0
115
+ end
116
+
117
+ def invalid_auth
118
+ reset_counter
119
+ @transaction_identifier = []
120
+ @enc_key = nil
121
+ @mac_key = nil
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,11 @@
1
+ module MIFARE
2
+ module PlusSL0
3
+ def write_perso(block_addr, data)
4
+ transceive(cmd: CMD_WRITE_PERSO, data: [block_addr, *data])
5
+ end
6
+
7
+ def commit_perso
8
+ transceive(cmd: CMD_COMMIT_PERSO)
9
+ end
10
+ end
11
+ end
File without changes
@@ -0,0 +1,11 @@
1
+ module MIFARE
2
+ module PlusSL3
3
+ def read(addr, tx, rx)
4
+
5
+ end
6
+
7
+ def write(addr, data, tx, rx)
8
+
9
+ end
10
+ end
11
+ end
@@ -1,35 +1,77 @@
1
- module Mifare
1
+ module MIFARE
2
2
  class Ultralight < ::PICC
3
- CMD_READ = 0x30 # Reads 4 pages(16 bytes) from the PICC.
4
- CMD_WRITE = 0xA2 # Writes 1 page(4 bytes) to the PICC.
5
- CMD_3DES_AUTH = 0x1A # Ultralight C 3DES Authentication.
3
+ CMD_READ = 0x30 # Reads 4 pages(16 bytes) from the PICC.
4
+ CMD_FAST_READ = 0x3A # Reads pages within requested range
5
+ CMD_WRITE = 0xA2 # Writes 1 page(4 bytes) to the PICC.
6
+ CMD_COMP_WRITE = 0xA0
7
+ CMD_READ_CNT = 0x39
8
+ CMD_INCR_CNT = 0xA5
9
+ CMD_PWD_AUTH = 0x1B
10
+ CMD_3DES_AUTH = 0x1A # Ultralight C 3DES Authentication.
11
+ CMD_GET_VERSION = 0x60
12
+ CMD_READ_SIG = 0x3C
13
+ CMD_VCSL = 0x4B
14
+ CMD_CHECK_TEARING_EVENT = 0x3E
15
+ MF_ACK = 0x0A # Mifare Acknowledge
16
+
17
+ CARD_VERSION = Struct.new(
18
+ :vendor_id, :type, :subtype, :major_ver, :minor_ver, :storage_size, :protocol_type
19
+ )
6
20
 
7
21
  def initialize(pcd, uid, sak)
8
22
  super
9
- @model_c = false
23
+ # Set transceive timeout to 15ms
24
+ @pcd.internal_timer(50)
25
+
26
+ # Maximum fast read range
27
+ @max_range = ((@pcd.buffer_size - 2) / 4).to_i
10
28
 
11
29
  # Check if Ultralight C
12
- if support_3des_auth?
30
+ if @model_c = support_3des_auth?
13
31
  extend UltralightC
14
- @model_c = true
32
+ end
33
+
34
+ unless @version = check_version
35
+ if version[:major_ver] == 0x01
36
+ extend UltralightEV1
37
+ end
15
38
  end
16
39
  end
17
40
 
18
- def read(block_addr)
19
- buffer = [CMD_READ, block_addr]
41
+ def transceive(send_data)
42
+ received_data, valid_bits = picc_transceive(send_data, false, true)
43
+ unless valid_bits == 0
44
+ raise UnexpectedDataError, 'Incorrect Mifare ACK format' if received_data.size != 1 || valid_bits != 4 # ACK is 4 bits long
45
+ raise MifareNakError, "Mifare NAK detected: 0x#{received_data[0].to_bytehex}" if received_data[0] != MF_ACK
46
+ end
47
+ received_data
48
+ end
20
49
 
21
- @pcd.picc_transceive(buffer)
50
+ def read(block_addr)
51
+ transceive([CMD_READ, block_addr])
22
52
  end
23
53
 
24
54
  def write(page, send_data)
25
55
  if send_data.size != 4
26
- raise UnexpectedDataError, "Expect 4 bytes data, got: #{send_data.size} byte"
56
+ raise UsageError, "Expect 4 bytes data, got: #{send_data.size} byte"
27
57
  end
28
58
 
29
- buffer = [CMD_WRITE, page]
30
- buffer.concat(send_data)
59
+ transceive([CMD_WRITE, page, *send_data])
60
+ end
61
+
62
+ def get_version
63
+ version = transceive([CMD_GET_VERSION])
64
+
65
+ expo = (version[6] >> 1) & 0x0F
66
+ if version[6] & 0x01 == 0
67
+ size = 1 << expo
68
+ else
69
+ size = (1 << expo) | (1 << (expo - 1))
70
+ end
31
71
 
32
- @pcd.picc_transceive(buffer)
72
+ CARD_VERSION.new(
73
+ version[1], version[2], version[3], version[4], version[5], size, version[7]
74
+ )
33
75
  end
34
76
 
35
77
  def model_c?
@@ -38,20 +80,29 @@ module Mifare
38
80
 
39
81
  private
40
82
 
83
+ def check_version
84
+ begin
85
+ version = get_version
86
+ rescue CommunicationError
87
+ restart_communication
88
+ return nil
89
+ end
90
+ version
91
+ end
92
+
41
93
  # Check if PICC support Ultralight 3DES command
42
94
  def support_3des_auth?
43
95
  # Ask for authentication
44
96
  buffer = [CMD_3DES_AUTH, 0x00]
45
97
 
46
98
  begin
47
- @pcd.picc_transceive(buffer)
99
+ transceive(buffer)
48
100
  result = true
49
101
  rescue CommunicationError
50
102
  result = false
51
103
  end
52
104
 
53
- resume_communication
54
-
105
+ restart_communication
55
106
  result
56
107
  end
57
108
  end
@@ -1,19 +1,18 @@
1
- module Mifare
1
+ module MIFARE
2
2
  module UltralightC
3
- CMD_3DES_AUTH = 0x1A # Ultralight C 3DES Authentication.
4
-
5
3
  def auth(auth_key)
6
4
  if auth_key.cipher_suite != 'des-ede-cbc'
7
- raise UnexpectedDataError, 'Incorrect Auth Key Type'
5
+ raise UsageError, 'Incorrect Auth Key Type'
8
6
  end
9
7
 
10
8
  auth_key.clear_iv
9
+ auth_key.padding_mode(1)
11
10
 
12
11
  # Ask for authentication
13
12
  buffer = [CMD_3DES_AUTH, 0x00]
14
- received_data = @pcd.picc_transceive(buffer)
13
+ received_data = transceive(buffer)
15
14
  card_status = received_data.shift
16
- raise UnexpectedDataError, 'Incorrect response' if card_status != 0xAF
15
+ raise UnexpectedDataError, 'Auth failed: stage 1' if card_status != 0xAF
17
16
 
18
17
  challenge = auth_key.decrypt(received_data)
19
18
  challenge_rot = challenge.rotate
@@ -24,15 +23,15 @@ module Mifare
24
23
 
25
24
  # Send challenge response
26
25
  buffer = [0xAF] + response
27
- received_data = @pcd.picc_transceive(buffer)
26
+ received_data = transceive(buffer)
28
27
  card_status = received_data.shift
29
- raise UnexpectedDataError, 'Incorrect response' if card_status != 0x00
28
+ raise UnexpectedDataError, 'Auth failed: stage 2' if card_status != 0x00
30
29
 
31
30
  # Check if verification matches rotated random_number
32
31
  verification = auth_key.decrypt(received_data)
33
32
 
34
33
  if random_number.rotate != verification
35
- halt
34
+ restart_communication
36
35
  return @authed = false
37
36
  end
38
37
 
@@ -40,14 +39,14 @@ module Mifare
40
39
  end
41
40
 
42
41
  def authed?
43
- @authed
42
+ @authed || false
44
43
  end
45
44
 
46
45
  def write_des_key(key)
47
46
  # key should be 16 bytes long
48
47
  bytes = [key].pack('H*').bytes
49
48
  if bytes.size != 16
50
- raise UnexpectedDataError, "Expect 16 bytes 3DES key, got: #{bytes.size} byte"
49
+ raise UsageError, "Expect 16 bytes 3DES key, got: #{bytes.size} byte"
51
50
  end
52
51
 
53
52
  # Key1
@@ -60,7 +59,7 @@ module Mifare
60
59
 
61
60
  def counter_increment(value)
62
61
  if value < 0
63
- raise UnexpectedDataError, 'Expect positive integer for counter'
62
+ raise UsageError, 'Expect positive integer for counter'
64
63
  end
65
64
  # you can set any value between 0x0000 to 0xFFFF on the first write (initialize)
66
65
  # after initialized, counter can only be incremented by 0x01 ~ 0x0F
@@ -68,8 +67,8 @@ module Mifare
68
67
  end
69
68
 
70
69
  def enable_protection_from(block_addr)
71
- if block_addr >= 0x03 && block_addr <= 0x30
72
- raise UnexpectedDataError, 'Requested block beyond memory limit'
70
+ if block_addr < 0x03 || block_addr > 0x30
71
+ raise UsageError, 'Requested block beyond memory limit'
73
72
  end
74
73
  # authentication will be required from `block_addr` to 0x2F
75
74
  # valid value are from 0x03 to 0x30