Class: MIFARE::DESFire

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

Defined Under Namespace

Classes: CARD_VERSION, FILE_PERMISSION, FILE_SETTING, KEY_SETTING

Constant Summary collapse

CMD_DES_AUTH =

Security Related Commands

0x1A
CMD_AES_AUTH =

Authenticate with DES, 2K3DES, 3K3DES key

0xAA
CMD_GET_KEY_SETTING =

Authenticate with AES-128 key

0x45
CMD_CHANGE_KEY_SETTING =

Gets information on the PICC and application master key settings. In addition it returns the maximum number of keys which can be stored within the selected application.

0x54
CMD_GET_KEY_VERSION =

Changes the master key settings on PICC and application level.

0x64
CMD_CHANGE_KEY =

Reads out the current key version of any key stored on the PICC.

0xC4
CMD_SET_CONFIGURATION =

Changes any key stored on the PICC.

0x5C
CMD_CREATE_APP =

PICC Level Commands

0xCA
CMD_DELETE_APP =

Creates new applications on the PICC.

0xDA
CMD_GET_APP_IDS =

Permanently deactivates applications on the PICC.

0x6A
CMD_SELECT_APP =

Returns the Application IDentifiers of all applications on a PICC.

0x5A
CMD_FREE_MEMORY =

Selects one specific application for further access.

0x6E
CMD_GET_DF_NAMES =

Returns the free memory available on the card

0x6D
CMD_GET_CARD_VERSION =

Returns the DF names

0x60
CMD_FORMAT_CARD =

Returns manufacturing related data of the PICC.

0xFC
CMD_GET_CARD_UID =

Releases the PICC user memory.

0x51
CMD_GET_FILE_IDS =

Application Level Commands

0x6F
CMD_GET_FILE_SETTING =

Returns the File IDentifiers of all active files within the currently selected application.

0xF5
CMD_CHANGE_FILE_SETTING =

Get information on the properties of a specific file.

0x5F
CMD_CREATE_STD_DATA_FILE =

Changes the access parameters of an existing file.

0xCD
CMD_CREATE_BACKUP_DATA_FILE =

Creates files for the storage of plain unformatted user data within an existing application on the PICC.

0xCB
CMD_CREATE_VALUE_FILE =

Creates files for the storage of plain unformatted user data within an existing application on the PICC, additionally supporting the feature of an integrated backup mechanism.

0xCC
CMD_CREATE_LINEAR_RECORD_FILE =

Creates files for the storage and manipulation of 32bit signed integer values within an existing application on the PICC.

0xC1
CMD_CREATE_CYCLIC_RECORD_FILE =

Creates files for multiple storage of structural similar data, for example for loyalty programs, within an existing application on the PICC. Once the file is filled completely with data records, further writing to the file is not possible unless it is cleared.

0xC0
CMD_DELETE_FILE =

Creates files for multiple storage of structural similar data within an existing application on the PICC. Once the file is filled completely with data records, the PICC automatically overwrites the oldest record with the latest written one.

0xDF
CMD_READ_DATA =

Data Manipulation Commands

0xBD
CMD_WRITE_DATA =

Reads data from Standard Data Files or Backup Data Files.

0x3D
CMD_GET_VALUE =

Writes data to Standard Data Files or Backup Data Files.

0x6C
CMD_CREDIT =

Reads the currently stored value from Value Files.

0x0C
CMD_DEBIT =

Increases a value stored in a Value File.

0xDC
CMD_LIMITED_CREDIT =

Decreases a value stored in a Value File.

0x1C
CMD_WRITE_RECORD =

Allows a limited increase of a value stored in a Value File without having full Credit permissions to the file.

0x3B
CMD_READ_RECORDS =

Writes data to a record in a Cyclic or Linear Record File.

0xBB
CMD_CLEAR_RECORD_FILE =

Reads out a set of complete records from a Cyclic or Linear Record File.

0xEB
CMD_COMMIT_TRANSACTION =

Resets a Cyclic or Linear Record File to empty state.

0xC7
CMD_ABORT_TRANSACTION =

Validates all previous write access’ on Backup Data Files, Value Files and Record Files within one application.

0xA7
ST_SUCCESS =

Status code returned by DESFire

0x00
ST_NO_CHANGES =
0x0C
ST_OUT_OF_MEMORY =
0x0E
ST_ILLEGAL_COMMAND =
0x1C
ST_INTEGRITY_ERROR =
0x1E
ST_KEY_NOT_EXIST =
0x40
ST_WRONG_COMMAND_LEN =
0x7E
ST_PERMISSION_DENIED =
0x9D
ST_INCORRECT_PARAM =
0x9E
ST_APP_NOT_FOUND =
0xA0
ST_APPL_INTEGRITY_ERROR =
0xA1
ST_AUTHENTICATION_ERROR =
0xAE
ST_ADDITIONAL_FRAME =
0xAF
ST_BOUNDARY_ERROR =
0xBE
ST_PICC_INTEGRITY_ERROR =
0xC1
ST_COMMAND_ABORTED =
0xCA
ST_PICC_DISABLED_ERROR =
0xCD
ST_COUNT_ERROR =
0xCE
ST_DUPLICATE_ERROR =
0xDE
ST_EEPROM_ERROR =
0xEE
ST_FILE_NOT_FOUND =
0xF0
ST_FILE_INTEGRITY_ERROR =
0xF1
KEY_TYPE =
{'des-ede-cbc' => 0x00, 'des-ede3-cbc' => 0x40, 'aes-128-cbc' => 0x80}
FILE_TYPE =
{
  std_data_file: 0x00, backup_data_file: 0x01, value_file: 0x02,
  linear_record_file: 0x03, cyclic_record_file: 0x04
}
FILE_COMMUNICATION =
{plain: 0x00, mac: 0x01, encrypt: 0x03}

Constants inherited from PICC

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

Instance Attribute Summary collapse

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) ⇒ DESFire

Returns a new instance of DESFire.


166
167
168
169
170
# File 'lib/mifare/des_fire.rb', line 166

def initialize(pcd, uid, sak)
  super
  invalid_auth
  @selected_app = false
end

Instance Attribute Details

#selected_appObject (readonly)

Returns the value of attribute selected_app.


164
165
166
# File 'lib/mifare/des_fire.rb', line 164

def selected_app
  @selected_app
end

Instance Method Details

#abort_transactionObject


620
621
622
# File 'lib/mifare/des_fire.rb', line 620

def abort_transaction
  transceive(cmd: CMD_ABORT_TRANSACTION)
end

#app_exist?(id) ⇒ Boolean

Returns:

  • (Boolean)

326
327
328
# File 'lib/mifare/des_fire.rb', line 326

def app_exist?(id)
  get_app_ids.include?(id)
end

#auth(key_number, auth_key) ⇒ Object


264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/mifare/des_fire.rb', line 264

def auth(key_number, auth_key)
  cmd = (auth_key.type == :des) ? CMD_DES_AUTH : CMD_AES_AUTH
  rand_size = (auth_key.cipher_suite == 'des-ede-cbc') ? 8 : 16
  auth_key.clear_iv
  auth_key.padding_mode(1)

  # Ask for authentication
  received_data = transceive(cmd: cmd, data: key_number, expect: ST_ADDITIONAL_FRAME, tx: :none, rx: :none)

  # Receive challenge from DESFire
  challenge = auth_key.decrypt(received_data, data_length: rand_size)
  challenge_rot = challenge.rotate

  # Generate random number and encrypt it with rotated challenge
  random_number = SecureRandom.random_bytes(rand_size).bytes
  response = auth_key.encrypt(random_number + challenge_rot)

  # Send challenge response
  received_data = transceive(cmd: CMD_ADDITIONAL_FRAME, data: response, tx: :none, rx: :none)

  # Check if verification matches rotated random_number
  verification = auth_key.decrypt(received_data, data_length: rand_size)

  if random_number.rotate != verification
    halt
    @authed = false

    raise ReceiptIntegrityError, 'Authentication Failed'
  end

  # Generate session key from generated random number(RndA) and challenge(RndB)
  session_key = random_number[0..3] + challenge[0..3]

  if auth_key.key_size > 8
    if auth_key.cipher_suite == 'des-ede-cbc'
      session_key.concat(random_number[4..7] + challenge[4..7])
    elsif auth_key.cipher_suite == 'des-ede3-cbc'
      session_key.concat(random_number[6..9] + challenge[6..9])
      session_key.concat(random_number[12..15] + challenge[12..15])
    elsif auth_key.cipher_suite == 'aes-128-cbc'
      session_key.concat(random_number[12..15] + challenge[12..15])
    end
  end

  @session_key = Key.new(auth_key.type, session_key)
  @session_key.generate_cmac_subkeys
  @authed = key_number

  authed?
end

#authed?Boolean

Returns:

  • (Boolean)

172
173
174
# File 'lib/mifare/des_fire.rb', line 172

def authed?
  @authed.is_a? Numeric
end

#change_file_setting(id, file_setting) ⇒ Object


499
500
501
502
503
504
505
# File 'lib/mifare/des_fire.rb', line 499

def change_file_setting(id, file_setting)
  buffer = []
  buffer.append_uint(FILE_COMMUNICATION.fetch(file_setting.communication), 1)
  buffer.append_uint(file_setting.permission.export, 2)

  transceive(cmd: CMD_CHANGE_FILE_SETTING, plain_data: id, data: buffer, tx: :encrypt)
end

#change_key(key_number, new_key, curr_key = nil) ⇒ Object


415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
# File 'lib/mifare/des_fire.rb', line 415

def change_key(key_number, new_key, curr_key = nil)
  raise UnauthenticatedError unless @authed
  raise UsageError, 'Invalid key number' if key_number > 13

  cryptogram = new_key.key

  same_key = (key_number == @authed)

  # Only Master Key can change its key type
  key_number |= KEY_TYPE.fetch(new_key.cipher_suite) if @selected_app == 0

  # XOR new key if we're using different one
  unless same_key
    cryptogram = cryptogram.xor(curr_key.key)
  end

  # AES stores key version separately
  if new_key.type == :aes
    cryptogram.append_uint(new_key.version, 1)
  end

  cryptogram.append_uint(crc32([CMD_CHANGE_KEY, key_number], cryptogram), 4)

  unless same_key
    cryptogram.append_uint(crc32(new_key.key), 4)
  end

  # Encrypt cryptogram
  @session_key.padding_mode(1)
  buffer = [key_number] + @session_key.encrypt(cryptogram)

  transceive(cmd: CMD_CHANGE_KEY, data: buffer, tx: :none, rx: :mac)

  # Change current used key will revoke authentication
  invalid_auth if same_key
end

#change_key_setting(key_setting) ⇒ Object


460
461
462
463
464
# File 'lib/mifare/des_fire.rb', line 460

def change_key_setting(key_setting)
  raise UnauthenticatedError unless @authed

  transceive(cmd: CMD_CHANGE_KEY_SETTING, data: key_setting.export, tx: :encrypt, rx: :mac)
end

#clear_record(id) ⇒ Object


612
613
614
# File 'lib/mifare/des_fire.rb', line 612

def clear_record(id)
  transceive(cmd: CMD_CLEAR_RECORD_FILE, data: id)
end

#commit_transactionObject


616
617
618
# File 'lib/mifare/des_fire.rb', line 616

def commit_transaction
  transceive(cmd: CMD_COMMIT_TRANSACTION)
end

#create_app(id, key_setting, key_count, cipher_suite) ⇒ Object


337
338
339
340
341
342
343
344
# File 'lib/mifare/des_fire.rb', line 337

def create_app(id, key_setting, key_count, cipher_suite)
  raise UnauthenticatedError unless @authed
  raise UsageError, 'An application can only hold up to 14 keys.' if key_count > 14

  buffer = convert_app_id(id) + [key_setting.export, KEY_TYPE.fetch(cipher_suite) | key_count]

  transceive(cmd: CMD_CREATE_APP, data: buffer, rx: :mac)
end

#create_file(id, file_setting) ⇒ Object


507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
# File 'lib/mifare/des_fire.rb', line 507

def create_file(id, file_setting)
  buffer = [id]
  buffer.append_uint(FILE_COMMUNICATION.fetch(file_setting.communication), 1)
  buffer.append_uint(file_setting.permission.export, 2)

  case file_setting.type
  when :std_data_file, :backup_data_file
    buffer.append_uint(file_setting.size, 3) # PICC will allocate n * 32 bytes memory internally
  when :value_file
    buffer.append_sint(file_setting.lower_limit, 4)
    buffer.append_sint(file_setting.upper_limit, 4)
    buffer.append_sint(file_setting.limited_credit_value, 4)
    buffer.append_uint(file_setting.limited_credit, 1)
  when :linear_record_file, :cyclic_record_file
    buffer.append_uint(file_setting.record_size, 3)
    buffer.append_uint(file_setting.max_record_number, 3)
  end

  cmd = self.class.const_get("CMD_CREATE_#{file_setting.type.to_s.upcase}")

  transceive(cmd: cmd, data: buffer)
end

#credit_value(id, delta) ⇒ Object

Raises:


567
568
569
570
571
572
573
574
# File 'lib/mifare/des_fire.rb', line 567

def credit_value(id, delta)
  raise UsageError, 'Negative number is not allowed.' if delta < 0

  buffer = []
  buffer.append_sint(delta, 4)

  write_file(id, CMD_CREDIT, id, buffer)
end

#debit_value(id, delta) ⇒ Object

Raises:


576
577
578
579
580
581
582
583
# File 'lib/mifare/des_fire.rb', line 576

def debit_value(id, delta)
  raise UsageError, 'Negative number is not allowed.' if delta < 0

  buffer = []
  buffer.append_sint(delta, 4)

  write_file(id, CMD_DEBIT, id, buffer)
end

#delete_app(id) ⇒ Object


346
347
348
349
350
# File 'lib/mifare/des_fire.rb', line 346

def delete_app(id)
  raise UnauthenticatedError unless @authed

  transceive(cmd: CMD_DELETE_APP, data: convert_app_id(id))
end

#delete_file(id) ⇒ Object


530
531
532
# File 'lib/mifare/des_fire.rb', line 530

def delete_file(id)
  transceive(cmd: CMD_DELETE_FILE, data: id)
end

#deselectObject


180
181
182
183
# File 'lib/mifare/des_fire.rb', line 180

def deselect
  invalid_auth
  iso_deselect
end

#file_exist?(id) ⇒ Boolean

Returns:

  • (Boolean)

470
471
472
# File 'lib/mifare/des_fire.rb', line 470

def file_exist?(id)
  get_file_ids.include?(id)
end

#format_cardObject


378
379
380
381
382
# File 'lib/mifare/des_fire.rb', line 378

def format_card
  raise UnauthenticatedError unless @authed

  transceive(cmd: CMD_FORMAT_CARD)
end

#get_app_idsObject


315
316
317
318
319
320
321
322
323
324
# File 'lib/mifare/des_fire.rb', line 315

def get_app_ids
  ids = transceive(cmd: CMD_GET_APP_IDS, rx: :mac)

  return ids if ids.empty?

  ids = ids.each_slice(3).to_a
  ids.map do |id|
    id.to_uint
  end
end

#get_card_uidObject


372
373
374
375
376
# File 'lib/mifare/des_fire.rb', line 372

def get_card_uid
  raise UnauthenticatedError unless @authed

  transceive(cmd: CMD_GET_CARD_UID, rx: :encrypt, receive_length: 7)
end

#get_card_versionObject


352
353
354
355
356
357
358
359
360
# File 'lib/mifare/des_fire.rb', line 352

def get_card_version
  version = transceive(cmd: CMD_GET_CARD_VERSION)

  CARD_VERSION.new(
    version[0], version[1], version[2], version[3], version[4], 1 << (version[5] / 2), version[6],
    version[7], version[8], version[9], version[10], version[11], 1 << (version[12] / 2), version[13],
    version[14..20], version[21..25], version[26].to_s(16).to_i, 2000 + version[27].to_s(16).to_i
  )
end

#get_df_namesObject

Raises:


366
367
368
369
370
# File 'lib/mifare/des_fire.rb', line 366

def get_df_names
  raise UsageError, 'App 0 should be selected before calling' unless @selected_app == 0

  transceive(cmd: CMD_GET_DF_NAMES)
end

#get_file_idsObject


466
467
468
# File 'lib/mifare/des_fire.rb', line 466

def get_file_ids
  transceive(cmd: CMD_GET_FILE_IDS)
end

#get_file_setting(id) ⇒ Object


474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
# File 'lib/mifare/des_fire.rb', line 474

def get_file_setting(id)
  received_data = transceive(cmd: CMD_GET_FILE_SETTING, data: id)

  file_setting = FILE_SETTING.new
  file_setting.type = FILE_TYPE.key(received_data.shift)
  file_setting.communication = FILE_COMMUNICATION.key(received_data.shift)
  file_setting.permission = FILE_PERMISSION.import(received_data.shift(2).to_uint)

  case file_setting.type
  when :std_data_file, :backup_data_file
    file_setting.size = received_data.shift(3).to_uint
  when :value_file
    file_setting.lower_limit = received_data.shift(4).to_uint
    file_setting.upper_limit = received_data.shift(4).to_uint
    file_setting.limited_credit_value = received_data.shift(4).to_uint
    file_setting.limited_credit = received_data.shift & 0x01
  when :linear_record_file, :cyclic_record_file
    file_setting.record_size = received_data.shift(3).to_uint
    file_setting.max_record_number = received_data.shift(3).to_uint
    file_setting.current_record_number = received_data.shift(3).to_uint
  end

  file_setting
end

#get_free_memoryObject


362
363
364
# File 'lib/mifare/des_fire.rb', line 362

def get_free_memory
  transceive(cmd: CMD_FREE_MEMORY)
end

#get_key_settingObject


452
453
454
455
456
457
458
# File 'lib/mifare/des_fire.rb', line 452

def get_key_setting
  received_data = transceive(cmd: CMD_GET_KEY_SETTING)

  { key_setting: KEY_SETTING.import(received_data[0]),
    key_count: received_data[1] & 0x0F,
    key_type: KEY_TYPE.key(received_data[1] & 0xF0) }
end

#get_key_version(key_number) ⇒ Object


409
410
411
412
413
# File 'lib/mifare/des_fire.rb', line 409

def get_key_version(key_number)
  received_data = transceive(cmd: CMD_GET_KEY_VERSION, data: key_number, rx: :mac)

  received_data[0]
end

#limited_credit_value(id, delta) ⇒ Object

Raises:


585
586
587
588
589
590
591
592
# File 'lib/mifare/des_fire.rb', line 585

def limited_credit_value(id, delta)
  raise UsageError, 'Negative number is not allowed.' if delta < 0

  buffer = []
  buffer.append_sint(delta, 4)

  write_file(id, CMD_LIMITED_CREDIT, id, buffer)
end

#read_data(id, offset, length) ⇒ Object


545
546
547
548
549
550
551
552
# File 'lib/mifare/des_fire.rb', line 545

def read_data(id, offset, length)
  buffer = []
  buffer.append_uint(id, 1)
  buffer.append_uint(offset, 3)
  buffer.append_uint(length, 3)

  read_file(id, CMD_READ_DATA, buffer, length)
end

#read_file(id, cmd, data, length) ⇒ Object


534
535
536
537
538
# File 'lib/mifare/des_fire.rb', line 534

def read_file(id, cmd, data, length)
  file_setting = get_file_setting(id)
  length *= file_setting.record_size if file_setting.record_size
  transceive(cmd: cmd, data: data, rx: file_setting.communication, receive_length: length)
end

#read_records(id, offset, length) ⇒ Object


594
595
596
597
598
599
600
601
# File 'lib/mifare/des_fire.rb', line 594

def read_records(id, offset, length)
  buffer = []
  buffer.append_uint(id, 1)
  buffer.append_uint(offset, 3)
  buffer.append_uint(length, 3)

  read_file(id, CMD_READ_RECORDS, buffer, length)
end

#read_value(id) ⇒ Object


563
564
565
# File 'lib/mifare/des_fire.rb', line 563

def read_value(id)
  read_file(id, CMD_GET_VALUE, id, 4).to_sint
end

#selectObject


176
177
178
# File 'lib/mifare/des_fire.rb', line 176

def select
  iso_select
end

#select_app(id) ⇒ Object


330
331
332
333
334
335
# File 'lib/mifare/des_fire.rb', line 330

def select_app(id)
  transceive(cmd: CMD_SELECT_APP, data: convert_app_id(id))

  invalid_auth
  @selected_app = id
end

#set_ats(ats) ⇒ Object


403
404
405
406
407
# File 'lib/mifare/des_fire.rb', line 403

def set_ats(ats)
  raise UnauthenticatedError unless @authed

  transceive(cmd: CMD_SET_CONFIGURATION, plain_data: 0x02, data: ats, tx: :encrypt, encrypt_padding: 2)
end

#set_configuration_byte(disable_format, enable_random_uid) ⇒ Object


384
385
386
387
388
389
390
391
392
# File 'lib/mifare/des_fire.rb', line 384

def set_configuration_byte(disable_format, enable_random_uid)
  raise UnauthenticatedError unless @authed

  flag = 0
  flag |= 0x01 if disable_format
  flag |= 0x02 if enable_random_uid

  transceive(cmd: CMD_SET_CONFIGURATION, plain_data: 0x00, data: [flag], tx: :encrypt)
end

#set_default_key(key) ⇒ Object


394
395
396
397
398
399
400
401
# File 'lib/mifare/des_fire.rb', line 394

def set_default_key(key)
  raise UnauthenticatedError unless @authed

  buffer = key.key
  buffer.append_uint(key.version, 1)

  transceive(cmd: CMD_SET_CONFIGURATION, plain_data: 0x01, data: buffer, tx: :encrypt)
end

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


185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/mifare/des_fire.rb', line 185

def transceive(cmd: , plain_data: [], data: [], tx: nil, rx: nil, expect: nil, return_data: nil, receive_length: nil, encrypt_padding: nil)
  # Session key is needed for encryption
  if (tx == :encrypt || rx == :encrypt) && !@authed
    raise UnauthenticatedError
  end

  # Separate objects and be compatable with single byte input
  plain_data = plain_data.is_a?(Array) ? plain_data.dup : [plain_data]
  data = data.is_a?(Array) ? data.dup : [data]

  buffer = [cmd] + plain_data

  if @authed
    @session_key.padding_mode(encrypt_padding || 1)
  end

  if tx == :encrypt
    # Calculate CRC on whole frame
    data.append_uint(crc32(buffer, data), 4)
    # Encrypt data only
    data = @session_key.encrypt(data)
  end

  buffer.concat(data)

  if tx != :encrypt && tx != :none && cmd != CMD_ADDITIONAL_FRAME && @authed
    cmac = @session_key.calculate_cmac(buffer)
    # Only first 8 bytes of CMAC are transmitted
    buffer.concat(cmac[0..7]) if tx == :mac
  end

  received_data = []
  card_status = nil
  loop do
    receive_buffer = iso_transceive(buffer.shift(@max_inf_size))

    card_status = receive_buffer.shift
    received_data.concat(receive_buffer)

    break if card_status != ST_ADDITIONAL_FRAME || (buffer.empty? && expect == ST_ADDITIONAL_FRAME)

    buffer.unshift(CMD_ADDITIONAL_FRAME)
  end

  error_msg = check_status_code(card_status)

  unless error_msg.empty?
    invalid_auth
    raise ReceiptStatusError, "0x#{card_status.to_bytehex} - #{error_msg}"
  end

  if expect && expect != card_status
    raise UnexpectedDataError, 'Card status does not match expected value'
  end

  if rx == :encrypt
    if receive_length.nil?
      raise UsageError, 'Lack of receive length for removing padding'
    end
    @session_key.padding_mode((receive_length > 0) ? 1 : 2)
    receive_length += 4 # CRC32
    received_data = @session_key.decrypt(received_data, data_length: receive_length)
    received_crc = received_data.pop(4).to_uint
    crc = crc32(received_data, card_status)
    if crc != received_crc
      raise ReceiptIntegrityError
    end
  elsif rx != :none && @authed && received_data.size >= 8 && card_status == ST_SUCCESS
    received_cmac = received_data.pop(8)
    cmac = @session_key.calculate_cmac(received_data + [card_status])
    # Only first 8 bytes of CMAC are transmitted
    if rx == :mac && cmac[0..7] != received_cmac
      raise ReceiptIntegrityError
    end
  end

  received_data
end

#write_data(id, offset, data) ⇒ Object


554
555
556
557
558
559
560
561
# File 'lib/mifare/des_fire.rb', line 554

def write_data(id, offset, data)
  buffer = []
  buffer.append_uint(id, 1)
  buffer.append_uint(offset, 3)
  buffer.append_uint(data.size, 3)

  write_file(id, CMD_WRITE_DATA, buffer, data)
end

#write_file(id, cmd, plain_data, data) ⇒ Object


540
541
542
543
# File 'lib/mifare/des_fire.rb', line 540

def write_file(id, cmd, plain_data, data)
  file_setting = get_file_setting(id)
  transceive(cmd: cmd, plain_data: plain_data, data: data, tx: file_setting.communication)
end

#write_record(id, offset, data) ⇒ Object


603
604
605
606
607
608
609
610
# File 'lib/mifare/des_fire.rb', line 603

def write_record(id, offset, data)
  buffer = []
  buffer.append_uint(id, 1)
  buffer.append_uint(offset, 3)
  buffer.append_uint(data.size, 3)

  write_file(id, CMD_WRITE_RECORD, buffer, data)
end