mirror of
https://gitlab.com/fabinfra/fabaccess/nfc.git
synced 2025-03-12 14:51:51 +01:00
921 lines
34 KiB
C#
921 lines
34 KiB
C#
using log4net;
|
|
using NFC.Cards.NXP_MIFARE_DESFire.Enums;
|
|
using NFC.Cards.NXP_MIFARE_DESFire.Exceptions;
|
|
using NFC.Helper;
|
|
using NFC.Helper.Crypto.Cipher;
|
|
using NFC.Helper.Crypto.CRC;
|
|
using NFC.Interfaces;
|
|
using Org.BouncyCastle.Crypto;
|
|
using Org.BouncyCastle.Crypto.Engines;
|
|
using Org.BouncyCastle.Crypto.Macs;
|
|
using Org.BouncyCastle.Crypto.Parameters;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Security.Cryptography;
|
|
|
|
namespace NFC.Cards.NXP_MIFARE_DESFire
|
|
{
|
|
public class NXP_MIFARE_DESFire
|
|
{
|
|
// Docs https://hackmd.io/qATu8uYdRnOC40aFrB9afg
|
|
|
|
#region Log
|
|
private static readonly ILog _Log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
|
|
#endregion
|
|
|
|
#region Contructors
|
|
/// <summary>
|
|
/// Construct MIFRARE_DESFire Object with ICard Interface
|
|
/// </summary>
|
|
/// <param name="nfcService">Implementation of ICard, only transmit is used</param>
|
|
public NXP_MIFARE_DESFire(INFCService nfcService)
|
|
{
|
|
_NFCService = nfcService;
|
|
}
|
|
#endregion
|
|
|
|
#region Properties
|
|
/// <summary>
|
|
/// ICard Implementation used to transmit APDUCommands and recive APDUResponses
|
|
/// </summary>
|
|
private readonly INFCService _NFCService;
|
|
|
|
/// <summary>
|
|
/// SessionKey, is set after Successfull Authentication
|
|
/// </summary>
|
|
public byte[] _SessionKey;
|
|
|
|
/// <summary>
|
|
/// Initialation Vector for CBC Encryption
|
|
/// Is 0 bytes after Successfull Authentication
|
|
/// </summary>
|
|
public byte[] _IV;
|
|
#endregion
|
|
|
|
#region Methods
|
|
#region Helper Methods
|
|
/// <summary>
|
|
/// Check APDU Response for DESFire Error Codes
|
|
/// https://www.nxp.com/docs/en/data-sheet/MF2DLHX0.pdf
|
|
/// Section: 11.3
|
|
/// </summary>
|
|
public void CheckAPDUResponse(APDUResponse response)
|
|
{
|
|
if (response == null)
|
|
{
|
|
throw new ArgumentNullException("Response cannot be null.");
|
|
}
|
|
|
|
if (response.SW1 == 0x91)
|
|
{
|
|
switch (response.SW2)
|
|
{
|
|
case 0x00: // OPERATION_OK
|
|
return;
|
|
case 0x0C: // NO_CHANGES
|
|
return;
|
|
case 0x1C: // ILLEGAL_COMMAND_CODE
|
|
throw new IllegalCommandCodeException();
|
|
case 0x1E: // INTEGRITY_ERROR
|
|
throw new IntegrityErrorException();
|
|
case 0x40: // NO_SUCH_KEY
|
|
throw new NoSuchKeyException();
|
|
case 0x7E: // LENGTH_ERROR
|
|
throw new LengthErrorException();
|
|
case 0x9D: // PERMISSION_DENIED
|
|
throw new PermissionDeniedException();
|
|
case 0x9E: // PARAMETER_ERROR
|
|
throw new ParameterErrorException();
|
|
case 0xAD: // AUTHENTICATION_DELAY
|
|
throw new AuthenticationDelayException();
|
|
case 0xAE: // AUTHENTICATION_ERROR
|
|
throw new AuthenticationErrorException();
|
|
case 0xAF: // ADDITIONAL_FRAME
|
|
return;
|
|
case 0xBE: // BOUNDARY_ERROR
|
|
throw new BoundaryErrorException();
|
|
case 0xCA: // COMMAND_ABORTED
|
|
throw new CommandAbortedException();
|
|
case 0xDE: // DUPLICATE_ERROR
|
|
throw new DuplicateErrorException();
|
|
case 0xF0: // FILE_NOT_FOUND
|
|
throw new FileNotFoundException();
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
throw new Exception(string.Format("Unknown Response Code: 0x{0}.", BitConverter.ToString(new byte[] { response.SW1, response.SW2 }).Replace("-", string.Empty)));
|
|
}
|
|
#endregion
|
|
|
|
#region Crypto Operation
|
|
/// <summary>
|
|
/// Generates SessionKey for DES Authentification
|
|
/// </summary>
|
|
/// <returns>16Byte SessionKey</returns>
|
|
public byte[] GenerateSesionKey_DES(byte[] rndA, byte[] rndB)
|
|
{
|
|
byte[] sesssionkey = new byte[8];
|
|
|
|
for (int i = 0; i < sesssionkey.Length; i++)
|
|
{
|
|
if (i < 4)
|
|
{
|
|
sesssionkey[i] = rndA[i];
|
|
}
|
|
else
|
|
{
|
|
sesssionkey[i] = rndB[i - 4];
|
|
}
|
|
}
|
|
|
|
// DES SessionKey is a double DES Key
|
|
return ByteOperation.Concatenate(sesssionkey, sesssionkey);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates SessionKey for AES Authentification
|
|
/// </summary>
|
|
/// <returns>16Byte SessionKey</returns>
|
|
public byte[] GenerateSesionKey_AES(byte[] rndA, byte[] rndB)
|
|
{
|
|
byte[] sesssionkey = new byte[16];
|
|
|
|
for (int i = 0; i < sesssionkey.Length; i++)
|
|
{
|
|
if (i < 4)
|
|
{
|
|
sesssionkey[i] = rndA[i];
|
|
}
|
|
else if (i >= 4 && i < 8)
|
|
{
|
|
sesssionkey[i] = rndB[i - 4];
|
|
}
|
|
else if (i >= 8 && i < 12)
|
|
{
|
|
sesssionkey[i] = rndA[i + 4];
|
|
}
|
|
else
|
|
{
|
|
sesssionkey[i] = rndB[i];
|
|
}
|
|
}
|
|
|
|
return sesssionkey;
|
|
}
|
|
#endregion
|
|
|
|
#region Configuration Generator
|
|
/// <summary>
|
|
/// Genearte KeySetting1 for Application Settings or PICC Setting
|
|
/// </summary>
|
|
public byte GenerateKeySetting1(ChangeApplicationKey changeKey, ChangeMasterKeySettings changeMasterKeySettings, CreateDeleteFile createDeleteFile, FileDirectoryAccess fileDirectoryAccess, ChangeMasterKey changeMasterKey)
|
|
{
|
|
return (byte)(((byte)changeKey << 4) | (byte)changeMasterKeySettings | (byte)createDeleteFile | (byte)fileDirectoryAccess | (byte)changeMasterKey);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Genearte KeySetting1 for Application Settings or PICC Setting
|
|
/// </summary>
|
|
/// <param name="changeKey">ID of Key for changing Application Keys</param>
|
|
/// <returns></returns>
|
|
public byte GenerateKeySetting1(byte changeKey, ChangeMasterKeySettings changeMasterKeySettings, CreateDeleteFile createDeleteFile, FileDirectoryAccess fileDirectoryAccess, ChangeMasterKey changeMasterKey)
|
|
{
|
|
if (changeKey < 0x01 || changeKey >= 0x0E)
|
|
{
|
|
throw new ArgumentOutOfRangeException();
|
|
}
|
|
return GenerateKeySetting1((ChangeApplicationKey)changeKey, changeMasterKeySettings, createDeleteFile, fileDirectoryAccess, changeMasterKey);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Genearte KeySetting2 for Application Creation
|
|
/// </summary>
|
|
/// <param name="numberOfKeys">Number of keys that can be stored within the application (0x01-0x0D)</param>
|
|
/// <returns></returns>
|
|
public byte GenerateKeySetting2(CryptoOperationsType cryptoOperations, FileIdentifies fileIdentifies, byte numberOfKeys)
|
|
{
|
|
if (numberOfKeys < 0x01 || numberOfKeys >= 0x0D)
|
|
{
|
|
throw new ArgumentOutOfRangeException();
|
|
}
|
|
|
|
return (byte)((byte)cryptoOperations | (byte)fileIdentifies | numberOfKeys);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generate FileAccess Rights for File Settings
|
|
/// Use enum AccesRights for Free or Never Option
|
|
/// </summary>
|
|
/// <param name="read">KeyID for Read Access</param>
|
|
/// <param name="write">KeyID for Write Access</param>
|
|
/// <param name="read_write">KeyID for Read and Write Access</param>
|
|
/// <param name="configure">KeyID for Configuration Access</param>
|
|
public UInt16 GenerateFileAccessRights(byte read, byte write, byte read_write, byte configure)
|
|
{
|
|
if (read > 0x0F || write > 0x0F || read_write > 0x0F || configure > 0x0F)
|
|
{
|
|
throw new ArgumentOutOfRangeException("One KeyID is not valid");
|
|
}
|
|
|
|
return (UInt16)((read << 12) | (write << 8) | (read_write << 4) | configure);
|
|
}
|
|
#endregion
|
|
|
|
#region DESFire Commands
|
|
/// <summary>
|
|
/// Select Application by ApplicationID (AID)
|
|
/// </summary>
|
|
/// <param name="aid">3 Byte AID</param>
|
|
public void SelectApplication(UInt32 aid)
|
|
{
|
|
if (aid > 0xFFFFFF)
|
|
{
|
|
throw new ArgumentOutOfRangeException("AID is too large");
|
|
}
|
|
|
|
byte[] id_byte = BitConverter.GetBytes(aid);
|
|
_Log.InfoFormat("Select Application: {0}", HexConverter.ConvertToHexString(id_byte.ToArray()));
|
|
|
|
APDUCommand cmd_SelectApplication = new APDUCommand(IsoCase.Case4Short)
|
|
{
|
|
CLA = 0x90,
|
|
INS = 0x5A,
|
|
Data = new byte[]
|
|
{
|
|
id_byte[0],
|
|
id_byte[1],
|
|
id_byte[2]
|
|
}
|
|
};
|
|
_Log.Debug(cmd_SelectApplication.ToString());
|
|
HexConverter.ConvertToHexString(cmd_SelectApplication.ToArray());
|
|
APDUResponse response = _NFCService.Transmit(cmd_SelectApplication);
|
|
_Log.DebugFormat(response.ToString());
|
|
|
|
CheckAPDUResponse(response);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Authenticate to PICC, with ISO Authenticate for DES Key
|
|
/// </summary>
|
|
/// <param name="key_id">0x01 - 0x0D</param>
|
|
/// <param name="key">Array of 8/16 Bytes</param>
|
|
/// <param name="rndA">!!! WARNING For Testing only !!!</param>
|
|
public void AuthenticateISO_DES(byte key_id, byte[] key, byte[] rndA = null)
|
|
{
|
|
if (key_id >= 0x0E)
|
|
{
|
|
throw new ArgumentOutOfRangeException("KeyID is invalid");
|
|
}
|
|
|
|
_Log.InfoFormat("Authenticate with DES Key No: 0x{0:x}", key_id);
|
|
|
|
// Sepearte Initialisation Vector for Authentication Process
|
|
byte[] iv = new byte[8];
|
|
|
|
APDUCommand cmd_challange_request = new APDUCommand(IsoCase.Case4Short)
|
|
{
|
|
CLA = 0x90,
|
|
INS = 0x1A,
|
|
Data = new byte[]
|
|
{
|
|
key_id
|
|
}
|
|
};
|
|
_Log.Debug(cmd_challange_request.ToString());
|
|
|
|
APDUResponse response = _NFCService.Transmit(cmd_challange_request);
|
|
_Log.Debug(response.ToString());
|
|
|
|
CheckAPDUResponse(response);
|
|
|
|
byte[] rndB_enc = response.Body;
|
|
_Log.DebugFormat("rndB_enc: {0}", HexConverter.ConvertToHexString(rndB_enc));
|
|
|
|
TDES des = new TDES();
|
|
byte[] rndB = des.Decrypt(rndB_enc, key, ByteOperation.GenerateEmptyArray(8));
|
|
_Log.DebugFormat("rndB: {0}", HexConverter.ConvertToHexString(rndB));
|
|
|
|
rndB.CopyTo(iv, 0);
|
|
|
|
byte[] rndB_rl = ByteOperation.RotateLeft(rndB);
|
|
_Log.DebugFormat("rndB_enc: {0}", HexConverter.ConvertToHexString(rndB_rl));
|
|
|
|
if (rndA == null)
|
|
{
|
|
rndA = new byte[8];
|
|
RNGCryptoServiceProvider cryptoProvider = new RNGCryptoServiceProvider();
|
|
cryptoProvider.GetBytes(rndA);
|
|
}
|
|
_Log.DebugFormat("rndA: {0}", HexConverter.ConvertToHexString(rndA));
|
|
|
|
byte[] rndAB = ByteOperation.Concatenate(rndA, rndB_rl);
|
|
_Log.DebugFormat("rndAB: {0}", HexConverter.ConvertToHexString(rndAB));
|
|
|
|
byte[] rndAB_enc = des.Encrypt(rndAB, key, rndB_enc);
|
|
_Log.DebugFormat("rndAB_enc: {0}", HexConverter.ConvertToHexString(rndAB_enc));
|
|
iv = ByteOperation.ExtractLastBlock(rndAB_enc, 8);
|
|
|
|
APDUCommand cmd_challange_response = new APDUCommand(IsoCase.Case4Short)
|
|
{
|
|
CLA = 0x90,
|
|
INS = 0xAF,
|
|
Data = rndAB_enc
|
|
};
|
|
_Log.Debug(cmd_challange_response.ToString());
|
|
|
|
response = _NFCService.Transmit(cmd_challange_response);
|
|
_Log.Debug(response.ToString());
|
|
|
|
CheckAPDUResponse(response);
|
|
|
|
byte[] encryptedRndAFromCard = response.Body;
|
|
_Log.DebugFormat("encryptedRndAFromCard: {0}", HexConverter.ConvertToHexString(encryptedRndAFromCard));
|
|
|
|
byte[] rotatedRndAFromCard = des.Decrypt(encryptedRndAFromCard, key, iv);
|
|
_Log.DebugFormat("rotatedRndAFromCard: {0}", HexConverter.ConvertToHexString(rotatedRndAFromCard));
|
|
|
|
byte[] rndAFromCard = ByteOperation.RotateRight(rotatedRndAFromCard);
|
|
_Log.DebugFormat("rndAFromCard: {0}", HexConverter.ConvertToHexString(rndAFromCard));
|
|
|
|
if (!rndA.SequenceEqual(rndAFromCard))
|
|
{
|
|
throw new Exception("Authentication failed, PICC Challenge is invalid.");
|
|
}
|
|
|
|
_Log.Info("Authenticated");
|
|
|
|
_SessionKey = GenerateSesionKey_DES(rndA, rndB);
|
|
_Log.DebugFormat("SessionKey: {0}", HexConverter.ConvertToHexString(_SessionKey));
|
|
|
|
_IV = ByteOperation.GenerateEmptyArray(8);
|
|
_Log.DebugFormat("IV: {0}", HexConverter.ConvertToHexString(_IV));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Format PICC
|
|
/// Need Authentication for PICC / Application 0x000000
|
|
/// </summary>
|
|
public void Format()
|
|
{
|
|
_Log.Info("Format PICC");
|
|
|
|
APDUCommand cmd_format = new APDUCommand(IsoCase.Case2Short)
|
|
{
|
|
CLA = 0x90,
|
|
INS = 0xFC,
|
|
};
|
|
_Log.Debug(cmd_format.ToString());
|
|
|
|
APDUResponse response = _NFCService.Transmit(cmd_format);
|
|
_Log.Debug(response.ToString());
|
|
|
|
CheckAPDUResponse(response);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create Application for ApplicationID
|
|
/// </summary>
|
|
/// <param name="aid">3 Byte ID</param>
|
|
public void CreateApplication(UInt32 aid, byte keysetting1, byte keysetting2)
|
|
{
|
|
if (aid > 0xFFFFFF)
|
|
{
|
|
throw new ArgumentOutOfRangeException("AID is too large");
|
|
}
|
|
|
|
byte[] id_byte = BitConverter.GetBytes(aid);
|
|
_Log.InfoFormat("Create Application: {0}", HexConverter.ConvertToHexString(id_byte.ToArray()));
|
|
|
|
APDUCommand cmd_CreateApplication = new APDUCommand(IsoCase.Case4Short)
|
|
{
|
|
CLA = 0x90,
|
|
INS = 0xCA,
|
|
Data = new byte[]
|
|
{
|
|
id_byte[0],
|
|
id_byte[1],
|
|
id_byte[2],
|
|
keysetting1,
|
|
keysetting2
|
|
}
|
|
};
|
|
_Log.Debug(cmd_CreateApplication.ToString());
|
|
|
|
APDUResponse response = _NFCService.Transmit(cmd_CreateApplication);
|
|
_Log.Debug(response.ToString());
|
|
|
|
CheckAPDUResponse(response);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Authenticate to PICC, with ISO Authenticate
|
|
/// </summary>
|
|
/// <param name="key_id">0x01 - 0x0D</param>
|
|
/// <param name="key">Array of 16 Bytes</param>
|
|
/// <param name="rndA">!!! WARNING For Testing only !!!</param>
|
|
public void AuthenticateISO_AES(byte key_id, byte[] key, byte[] rndA = null)
|
|
{
|
|
if (key_id >= 0x0E)
|
|
{
|
|
throw new ArgumentOutOfRangeException("KeyID is invalid");
|
|
}
|
|
|
|
_Log.InfoFormat("Authenticate with AES Key No: 0x{0:x}", key_id);
|
|
|
|
// Sepearte InitialisationVector for Authentication
|
|
byte[] iv = new byte[16];
|
|
|
|
APDUCommand cmd_challange_request = new APDUCommand(IsoCase.Case4Short)
|
|
{
|
|
CLA = 0x90,
|
|
INS = 0xAA,
|
|
Data = new byte[]
|
|
{
|
|
key_id
|
|
}
|
|
};
|
|
_Log.Debug(cmd_challange_request.ToString());
|
|
|
|
APDUResponse response = _NFCService.Transmit(cmd_challange_request);
|
|
_Log.Debug(response.ToString());
|
|
|
|
CheckAPDUResponse(response);
|
|
|
|
byte[] rndB_enc = response.Body;
|
|
_Log.DebugFormat("rndB_enc: {0}", HexConverter.ConvertToHexString(rndB_enc));
|
|
|
|
AES aes = new AES();
|
|
byte[] rndB = aes.Decrypt(rndB_enc, key, ByteOperation.GenerateEmptyArray(16));
|
|
_Log.DebugFormat("rndB: {0}", HexConverter.ConvertToHexString(rndB));
|
|
|
|
rndB.CopyTo(iv, 0);
|
|
|
|
byte[] rndB_rl = ByteOperation.RotateLeft(rndB);
|
|
_Log.DebugFormat("rndB_enc: {0}", HexConverter.ConvertToHexString(rndB_rl));
|
|
|
|
if (rndA == null)
|
|
{
|
|
rndA = new byte[16];
|
|
RNGCryptoServiceProvider cryptoProvider = new RNGCryptoServiceProvider();
|
|
cryptoProvider.GetBytes(rndA);
|
|
}
|
|
_Log.DebugFormat("rndA: {0}", HexConverter.ConvertToHexString(rndA));
|
|
|
|
byte[] rndAB = ByteOperation.Concatenate(rndA, rndB_rl);
|
|
_Log.DebugFormat("rndAB: {0}", HexConverter.ConvertToHexString(rndAB));
|
|
|
|
byte[] rndAB_enc = aes.Encrypt(rndAB, key, rndB_enc);
|
|
_Log.DebugFormat("rndAB_enc: {0}", HexConverter.ConvertToHexString(rndAB_enc));
|
|
iv = ByteOperation.ExtractLastBlock(rndAB_enc, 16);
|
|
|
|
APDUCommand cmd_challange_response = new APDUCommand(IsoCase.Case4Short)
|
|
{
|
|
CLA = 0x90,
|
|
INS = 0xAF,
|
|
Data = rndAB_enc
|
|
};
|
|
_Log.Debug(cmd_challange_response.ToString());
|
|
|
|
response = _NFCService.Transmit(cmd_challange_response);
|
|
_Log.Debug(response.ToString());
|
|
|
|
CheckAPDUResponse(response);
|
|
|
|
byte[] encryptedRndAFromCard = response.Body;
|
|
_Log.DebugFormat("encryptedRndAFromCard: {0}", HexConverter.ConvertToHexString(encryptedRndAFromCard));
|
|
|
|
byte[] rotatedRndAFromCard = aes.Decrypt(encryptedRndAFromCard, key, iv);
|
|
_Log.DebugFormat("rotatedRndAFromCard: {0}", HexConverter.ConvertToHexString(rotatedRndAFromCard));
|
|
|
|
byte[] rndAFromCard = ByteOperation.RotateRight(rotatedRndAFromCard);
|
|
_Log.DebugFormat("rndAFromCard: {0}", HexConverter.ConvertToHexString(rndAFromCard));
|
|
|
|
if (!rndA.SequenceEqual(rndAFromCard))
|
|
{
|
|
throw new Exception("Authentication failed, PICC Challenge is invalid.");
|
|
}
|
|
|
|
_SessionKey = GenerateSesionKey_AES(rndA, rndB);
|
|
_Log.DebugFormat("SessionKey: {0}", HexConverter.ConvertToHexString(_SessionKey));
|
|
|
|
_IV = ByteOperation.GenerateEmptyArray(16);
|
|
_Log.DebugFormat("IV: {0}", HexConverter.ConvertToHexString(_IV));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Change AES key, the same as Authenticated
|
|
/// </summary>
|
|
/// <param name="key_id">0x01 - 0x0D</param>
|
|
/// <param name="new_key">Array of 16 Bytes</param>
|
|
/// <param name="key_version">Version of Key(min. 0x10)</param>
|
|
public void ChangeKey_AES(byte key_id, byte[] new_key, byte key_version)
|
|
{
|
|
if (key_id >= 0x0E)
|
|
{
|
|
throw new ArgumentOutOfRangeException("KeyID is invalid");
|
|
}
|
|
|
|
_Log.InfoFormat("Change AES Key No: 0x{0:x}", key_id);
|
|
|
|
byte[] header = new byte[]
|
|
{
|
|
0xC4, key_id
|
|
};
|
|
_Log.DebugFormat("header: {0}", HexConverter.ConvertToHexString(header));
|
|
|
|
// AES Key Version is Append to Key
|
|
byte[] key_and_version = ByteOperation.Concatenate(new_key, new byte[] { key_version });
|
|
byte[] command = ByteOperation.Concatenate(header, key_and_version);
|
|
_Log.DebugFormat("command: {0}", HexConverter.ConvertToHexString(command));
|
|
|
|
CRC32 crc32 = new CRC32();
|
|
byte[] crc = crc32.Calculate(command);
|
|
_Log.DebugFormat("crc: {0}", HexConverter.ConvertToHexString(crc));
|
|
|
|
byte[] cryptogram = ByteOperation.Concatenate(key_and_version, crc);
|
|
_Log.DebugFormat("cryptogram: {0}", HexConverter.ConvertToHexString(cryptogram));
|
|
|
|
byte[] cryptogram_block = ByteOperation.ExpandToBlockSize(cryptogram, 16);
|
|
_Log.DebugFormat("cryptogram_block: {0}", HexConverter.ConvertToHexString(cryptogram_block));
|
|
|
|
AES aes = new AES();
|
|
byte[] cryptogram_enc = aes.Encrypt(cryptogram_block, _SessionKey, _IV);
|
|
_Log.DebugFormat("cryptogram_enc: {0}", HexConverter.ConvertToHexString(cryptogram_enc));
|
|
|
|
_IV = ByteOperation.ExtractLastBlock(cryptogram_enc, 16);
|
|
_Log.DebugFormat("_IV: {0}", HexConverter.ConvertToHexString(_IV));
|
|
|
|
byte[] data = ByteOperation.Concatenate(new byte[] { key_id }, cryptogram_enc);
|
|
_Log.DebugFormat("data: {0}", HexConverter.ConvertToHexString(data));
|
|
|
|
APDUCommand cmd_ChangeKey = new APDUCommand(IsoCase.Case4Short)
|
|
{
|
|
CLA = 0x90,
|
|
INS = 0xC4,
|
|
Data = data
|
|
};
|
|
_Log.Debug(cmd_ChangeKey.ToString());
|
|
|
|
APDUResponse response = _NFCService.Transmit(cmd_ChangeKey);
|
|
_Log.Debug(response.ToString());
|
|
|
|
CheckAPDUResponse(response);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Change AES key, other than Authenticated
|
|
/// </summary>
|
|
/// <param name="key_id">0x01 - 0x0D</param>
|
|
/// <param name="new_key">Array of 16 Bytes</param>
|
|
/// <param name="key_version">Version of Key(min. 0x10)</param>
|
|
public void ChangeOtherKey_AES(byte key_id, byte[] new_key, byte[] old_key, byte key_version)
|
|
{
|
|
if (key_id >= 0x0E)
|
|
{
|
|
throw new ArgumentOutOfRangeException("KeyID is invalid");
|
|
}
|
|
|
|
_Log.InfoFormat("Change AES Key No: 0x{0:x}", key_id);
|
|
|
|
byte[] header = new byte[]
|
|
{
|
|
0xC4, key_id
|
|
};
|
|
_Log.DebugFormat("header: {0}", HexConverter.ConvertToHexString(header));
|
|
|
|
byte[] key_xor = ByteOperation.XOR(new_key, old_key);
|
|
|
|
// AES Key Version is Append to Key
|
|
byte[] key_and_version = ByteOperation.Concatenate(key_xor, new byte[] { key_version });
|
|
byte[] command = ByteOperation.Concatenate(header, key_and_version);
|
|
_Log.DebugFormat("command: {0}", HexConverter.ConvertToHexString(command));
|
|
|
|
CRC32 crc32 = new CRC32();
|
|
byte[] crc_cmd = crc32.Calculate(command);
|
|
_Log.DebugFormat("crc_cmd: {0}", HexConverter.ConvertToHexString(crc_cmd));
|
|
byte[] crc_key = crc32.Calculate(new_key);
|
|
_Log.DebugFormat("crc_key: {0}", HexConverter.ConvertToHexString(crc_key));
|
|
|
|
byte[] cryptogram = ByteOperation.Concatenate(key_and_version, crc_cmd);
|
|
cryptogram = ByteOperation.Concatenate(cryptogram, crc_key);
|
|
_Log.DebugFormat("cryptogram: {0}", HexConverter.ConvertToHexString(cryptogram));
|
|
|
|
byte[] cryptogram_block = ByteOperation.ExpandToBlockSize(cryptogram, 16);
|
|
_Log.DebugFormat("cryptogram_block: {0}", HexConverter.ConvertToHexString(cryptogram_block));
|
|
|
|
AES aes = new AES();
|
|
byte[] cryptogram_enc = aes.Encrypt(cryptogram_block, _SessionKey, _IV);
|
|
_Log.DebugFormat("cryptogram_enc: {0}", HexConverter.ConvertToHexString(cryptogram_enc));
|
|
|
|
_IV = ByteOperation.ExtractLastBlock(cryptogram_enc, 16);
|
|
_Log.DebugFormat("_IV: {0}", HexConverter.ConvertToHexString(_IV));
|
|
|
|
byte[] data = ByteOperation.Concatenate(new byte[] { key_id }, cryptogram_enc);
|
|
_Log.DebugFormat("data: {0}", HexConverter.ConvertToHexString(data));
|
|
|
|
APDUCommand cmd_ChangeKey = new APDUCommand(IsoCase.Case4Short)
|
|
{
|
|
CLA = 0x90,
|
|
INS = 0xC4,
|
|
Data = data
|
|
};
|
|
_Log.Debug(cmd_ChangeKey.ToString());
|
|
|
|
APDUResponse response = _NFCService.Transmit(cmd_ChangeKey);
|
|
_Log.Debug(response.ToString());
|
|
|
|
CheckAPDUResponse(response);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create Standard Data File
|
|
/// </summary>
|
|
/// <param name="file_id">ID of File (0x00 - 0x20)</param>
|
|
/// <param name="communication">Type of File Communicaton</param>
|
|
/// <param name="accessRights">Access Rights for File</param>
|
|
/// <param name="size">Size of File in Bytes</param>
|
|
public void CreateFile_Standard(byte file_id, FileCommunication communication, UInt16 accessRights, UInt32 size)
|
|
{
|
|
if (file_id >= 0x20)
|
|
{
|
|
throw new ArgumentOutOfRangeException("FileID is to large");
|
|
}
|
|
_Log.DebugFormat("Create STD File: {0}", file_id);
|
|
|
|
byte[] accessRights_byte = BitConverter.GetBytes(accessRights);
|
|
byte[] size_byte_tolong = BitConverter.GetBytes(size);
|
|
// Use only 3 Bytes
|
|
byte[] size_byte = new byte[]
|
|
{
|
|
size_byte_tolong[0],
|
|
size_byte_tolong[1],
|
|
size_byte_tolong[2],
|
|
};
|
|
|
|
byte[] data = new byte[]
|
|
{
|
|
file_id,
|
|
(byte)communication
|
|
};
|
|
|
|
APDUCommand cmd_CreateFile_Standard = new APDUCommand(IsoCase.Case4Short)
|
|
{
|
|
CLA = 0x90,
|
|
INS = 0xCD,
|
|
Data = ByteOperation.Concatenate(data, accessRights_byte, size_byte)
|
|
};
|
|
_Log.Debug(cmd_CreateFile_Standard.ToString());
|
|
|
|
APDUResponse response = _NFCService.Transmit(cmd_CreateFile_Standard);
|
|
_Log.DebugFormat(response.ToString());
|
|
|
|
CheckAPDUResponse(response);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write Data to File
|
|
/// </summary>
|
|
/// <param name="file_id">ID of File (0x00 - 0x20)</param>
|
|
/// <param name="offset">Offset for File</param>
|
|
/// <param name="data">Data to write</param>
|
|
public void WriteData(byte file_id, UInt32 offset, byte[] data)
|
|
{
|
|
if (file_id >= 0x20)
|
|
{
|
|
throw new ArgumentOutOfRangeException("FileID is to large");
|
|
}
|
|
_Log.DebugFormat("Write Data to File: {0}", file_id);
|
|
|
|
int max_write_bytes_pre_transaction = 47;
|
|
byte[] write_buffer;
|
|
|
|
long bytes_writed = 0;
|
|
long length = data.Length;
|
|
|
|
while (bytes_writed != data.Length)
|
|
{
|
|
byte[] file_id_array = new byte[]
|
|
{
|
|
file_id
|
|
};
|
|
|
|
byte[] offset_byte_tolong = BitConverter.GetBytes(offset + bytes_writed);
|
|
// Use only 3 Bytes
|
|
byte[] offset_byte = new byte[]
|
|
{
|
|
offset_byte_tolong[0],
|
|
offset_byte_tolong[1],
|
|
offset_byte_tolong[2],
|
|
};
|
|
|
|
long bytes_towrite;
|
|
if (length - bytes_writed < max_write_bytes_pre_transaction)
|
|
{
|
|
bytes_towrite = length - bytes_writed;
|
|
}
|
|
else
|
|
{
|
|
bytes_towrite = max_write_bytes_pre_transaction;
|
|
}
|
|
|
|
byte[] length_byte_tolong = BitConverter.GetBytes(bytes_towrite);
|
|
|
|
write_buffer = ByteOperation.GetSubArray(data, bytes_writed, bytes_towrite);
|
|
bytes_writed += bytes_towrite;
|
|
|
|
// Use only 3 Bytes
|
|
byte[] length_byte = new byte[]
|
|
{
|
|
length_byte_tolong[0],
|
|
length_byte_tolong[1],
|
|
length_byte_tolong[2],
|
|
};
|
|
|
|
APDUCommand cmd_WriteData = new APDUCommand(IsoCase.Case4Short)
|
|
{
|
|
CLA = 0x90,
|
|
INS = 0x3D,
|
|
Data = ByteOperation.Concatenate(file_id_array, offset_byte, length_byte, write_buffer)
|
|
};
|
|
_Log.Debug(cmd_WriteData.ToString());
|
|
/*
|
|
|
|
|
|
|
|
|
|
IBlockCipher cipher = new AesEngine();
|
|
CMac mac = new CMac(cipher, 8 * 8);
|
|
KeyParameter keyParameter = new KeyParameter(_SessionKey);
|
|
|
|
mac.Init(keyParameter);
|
|
|
|
byte[] mac_buffer = ByteOperation.GenerateEmptyArray(8);
|
|
|
|
byte[] cmd_raw_buffer = cmd_WriteData.ToArray();
|
|
mac.BlockUpdate(ByteOperation.Concatenate(new byte[] { 0x90, 0x3D }, cmd_WriteData.Data), 0, cmd_WriteData.Data.Length);
|
|
mac.DoFinal(mac_buffer, 0);
|
|
|
|
cmd_WriteData.Data = ByteOperation.Concatenate(cmd_WriteData.Data, mac_buffer);
|
|
_Log.Debug(cmd_WriteData.ToString());
|
|
|
|
|
|
*/
|
|
|
|
APDUResponse response = _NFCService.Transmit(cmd_WriteData);
|
|
_Log.Debug(response.ToString());
|
|
|
|
CheckAPDUResponse(response);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read Data from File
|
|
/// </summary>
|
|
/// <param name="file_id">ID of File (0x00 - 0x20)</param>
|
|
/// <param name="offset">Offset for File</param>
|
|
/// <param name="length">Lenght of Data</param>
|
|
public byte[] ReadData(byte file_id, UInt32 offset, UInt32 length)
|
|
{
|
|
if (file_id >= 0x20)
|
|
{
|
|
throw new ArgumentOutOfRangeException("FileID is to large");
|
|
}
|
|
_Log.DebugFormat("Read Data from File: {0}", file_id);
|
|
|
|
int max_read_bytes_pre_transaction = 47;
|
|
long bytes_readed = 0;
|
|
|
|
List<byte> read_data = new List<byte>();
|
|
|
|
while (bytes_readed != length)
|
|
{
|
|
byte[] data = new byte[]
|
|
{
|
|
file_id
|
|
};
|
|
|
|
byte[] offset_byte_tolong = BitConverter.GetBytes(offset + bytes_readed);
|
|
// Use only 3 Bytes
|
|
byte[] offset_byte = new byte[]
|
|
{
|
|
offset_byte_tolong[0],
|
|
offset_byte_tolong[1],
|
|
offset_byte_tolong[2],
|
|
};
|
|
|
|
long bytes_toread;
|
|
if (length - bytes_readed < max_read_bytes_pre_transaction)
|
|
{
|
|
bytes_toread = length - bytes_readed;
|
|
}
|
|
else
|
|
{
|
|
bytes_toread = max_read_bytes_pre_transaction;
|
|
}
|
|
|
|
byte[] length_byte_tolong = BitConverter.GetBytes(bytes_toread);
|
|
bytes_readed += bytes_toread;
|
|
|
|
// Use only 3 Bytes
|
|
byte[] length_byte = new byte[]
|
|
{
|
|
length_byte_tolong[0],
|
|
length_byte_tolong[1],
|
|
length_byte_tolong[2],
|
|
};
|
|
|
|
APDUCommand cmd_ReadData = new APDUCommand(IsoCase.Case4Short)
|
|
{
|
|
CLA = 0x90,
|
|
INS = 0xBD,
|
|
Data = ByteOperation.Concatenate(data, offset_byte, length_byte)
|
|
};
|
|
_Log.Debug(cmd_ReadData.ToString());
|
|
|
|
APDUResponse response = _NFCService.Transmit(cmd_ReadData);
|
|
_Log.Debug(response.ToString());
|
|
|
|
CheckAPDUResponse(response);
|
|
|
|
// Remove CMAC from Body
|
|
read_data.AddRange(ByteOperation.GetSubArray(response.Body, 0, bytes_toread));
|
|
}
|
|
|
|
return read_data.ToArray();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get all ApplicationIDS from PICC
|
|
/// </summary>
|
|
/// <returns>AIDs (3 Byte) as Array</returns>
|
|
//public UInt32[] GetApplicationIDs()
|
|
//{
|
|
// _Log.Debug("Start GetApplicationIDs");
|
|
|
|
// APDUCommand cmd = new APDUCommand(IsoCase.Case2Short)
|
|
// {
|
|
// CLA = 0x90,
|
|
// INS = (byte)APDUInstructions.GET_APPLICATION_IDS
|
|
// };
|
|
|
|
// APDUResponse response = _NFCService.Transmit(cmd);
|
|
|
|
// CheckAPDUResponse(response);
|
|
|
|
// if (response.Body.Length % 3 != 0)
|
|
// {
|
|
// throw new Exception(string.Format("Invalid body length (was: {0}).", response.Body.Length));
|
|
// }
|
|
|
|
// if (response.Body.Length == 0)
|
|
// {
|
|
// throw new Exception("Missing PICC Entry 0x000000.");
|
|
// }
|
|
|
|
// List<UInt32> applicationIDs = new List<UInt32>();
|
|
|
|
// for (int i = 0; i < response.Body.Length; i += 3)
|
|
// {
|
|
// UInt32 new_applicationID = 0;
|
|
// new_applicationID = (UInt32)((response.Body[i] << 16) + (response.Body[i + 1] << 8) + response.Body[i + 2]);
|
|
// applicationIDs.Add(new_applicationID);
|
|
// }
|
|
|
|
// _Log.Debug("End GetApplicationIDs");
|
|
|
|
// return applicationIDs.ToArray();
|
|
//}
|
|
|
|
/// <summary>
|
|
/// Delete Application by ID
|
|
/// </summary>
|
|
/// <param name="id">3 Byte ID</param>
|
|
//public void DeleteApplication(UInt32 id)
|
|
//{
|
|
// byte[] id_byte = BitConverter.GetBytes(id);
|
|
|
|
// APDUCommand cmd = new APDUCommand(IsoCase.Case4Short)
|
|
// {
|
|
// CLA = 0x90,
|
|
// INS = (byte)APDUInstructions.DELETE_APPLICATION,
|
|
// Data = new byte[]
|
|
// {
|
|
// id_byte[0],
|
|
// id_byte[1],
|
|
// id_byte[2]
|
|
// },
|
|
// Le = 0x00
|
|
// };
|
|
|
|
// APDUResponse response = _NFCService.Transmit(cmd);
|
|
// CheckAPDUResponse(response);
|
|
//}
|
|
#endregion
|
|
#endregion
|
|
}
|
|
}
|