Class: MIFARE::Plus

Inherits:
PICC
  • Object
show all
Defined in:
lib/mifare/plus.rb

Constant Summary collapse

CMD_WRITE_PERSO =
0xA8
CMD_COMMIT_PERSO =
0xAA
CMD_MULTI_BLOCK_READ =
0x38
CMD_MULTI_BLOCK_WRITE =
0xA8
CMD_FIRST_AUTH =
0x70
CMD_SECOND_AUTH =
0x72
CMD_FOLLOWING_AUTH =
0x76
CMD_RESET_AUTH =
0x78
CMD_VC_DESELECT =
0x48
MF_ACK =
0x0A

Constants inherited from PICC

PICC::CMD_ADDITIONAL_FRAME, PICC::CMD_DESELECT, PICC::CMD_PPS, PICC::CMD_RATS, PICC::FSCI_to_FSC

Instance Attribute Summary

Attributes inherited from PICC

#sak, #uid

Instance Method Summary collapse

Methods inherited from PICC

#halt, identify_model, #iso_deselect, #iso_select, #iso_transceive, #picc_transceive, #restart_communication

Constructor Details

#initialize(pcd, uid, sak) ⇒ Plus

Returns a new instance of Plus.


14
15
16
17
18
# File 'lib/mifare/plus.rb', line 14

def initialize(pcd, uid, sak)
  super
  invalid_auth
  reset_counter
end

Instance Method Details

#auth(key_number, auth_key) ⇒ Object


29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/mifare/plus.rb', line 29

def auth(key_number, auth_key)
  cmd = authed? ? CMD_FOLLOWING_AUTH : CMD_FIRST_AUTH
  auth_key.padding_mode(2)

  buffer = [cmd].append_uint(key_number, 2)
  buffer << 0x00 unless authed? # No PCDCaps2 to send

  # Ask for authentication
  received_data = iso_transceive(buffer)

  # Receive challenge
  auth_key.clear_iv
  auth_key.set_iv(generate_iv(:decrypt)) if authed?
  challenge = auth_key.decrypt(received_data)
  challenge_rot = challenge.rotate

  # Generate random number and encrypt it with rotated challenge
  random_number = SecureRandom.random_bytes(received_data.size).bytes
  auth_key.clear_iv
  auth_key.set_iv(generate_iv(:encrypt)) if authed?
  response = auth_key.encrypt(random_number + challenge_rot)

  # Send challenge response
  received_data = iso_transceive([CMD_SECOND_AUTH, *response])

  # Check if verification matches rotated random_number
  auth_key.clear_iv
  auth_key.set_iv(generate_iv(:decrypt)) if authed?
  verification = auth_key.decrypt(received_data)
  @transaction_identifier = verification.shift(4)
  response_rot = verification.shift(16)

  if random_number.rotate != response_rot
    raise ReceiptIntegrityError, 'Authentication Failed'
  end

  byte_a = random_number[11..15]
  byte_b = challenge[11..15]
  byte_c = random_number[4..8]
  byte_d = challenge[4..8]
  byte_e = random_number[7..11]
  byte_f = challenge[7..11]
  byte_g = random_number[0..4]
  byte_h = challenge[0..4]
  byte_i = byte_c.xor(byte_d)
  byte_j = byte_g.xor(byte_h)

  enc_key_base = byte_a + byte_b + byte_i + [0x11]
  mac_key_base = byte_e + byte_f + byte_j + [0x22]

  auth_key.clear_iv
  auth_key.set_iv(generate_iv(:encrypt)) if authed?
  enc_key = auth_key.encrypt(enc_key_base)
  @enc_key = Key.new(:aes, enc_key)

  auth_key.clear_iv
  auth_key.set_iv(generate_iv(:encrypt)) if authed?
  mac_key = auth_key.encrypt(mac_key_base)
  @mac_key = Key.new(:aes, mac_key)

  reset_counter
end

#authed?Boolean

Returns:

  • (Boolean)

20
21
22
# File 'lib/mifare/plus.rb', line 20

def authed?
  !@transaction_identifier.empty?
end

#transceive(cmd:, plain_data: [], data: [], tx: nil, rx: nil) ⇒ Object

Raises:


24
25
26
27
# File 'lib/mifare/plus.rb', line 24

def transceive(cmd: , plain_data: [], data: [], tx: nil, rx: nil)
  raise UsageError, 'Call `iso_select` before using commands' unless @iso_selected
  iso_transceive(send_data)
end