borepin/NFC/NXP MIFARE DESFire/MIFARE_DESFire.cs
2020-10-05 18:39:45 +02:00

430 lines
13 KiB
C#

using NFC.Crypto;
using NFC.ISO7816_4;
using NFC.Mifare_DESFire.Enums;
using Org.BouncyCastle.Asn1.Crmf;
using PCSC.Iso7816;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
namespace NFC.Mifare_DESFire
{
public class MIFARE_DESFire
{
// Docs https://hackmd.io/qATu8uYdRnOC40aFrB9afg
#region Contructors
/// <summary>
/// Construct MIFRARE_DESFire Object with ICard Interface
/// </summary>
/// <param name="card">Implementation of ICard, only transmit is used</param>
public MIFARE_DESFire(ICard card)
{
_Card = card;
}
#endregion
#region Properties
/// <summary>
/// ICard Implementation used to transmit APDUCommands and recive APDUResponses
/// </summary>
private ICard _Card;
#endregion
#region Methods
/// <summary>
/// Generate Byte Array filled with 0
/// </summary>
/// <param name="size">Size of Array</param>
public byte[] GenerateDefaultKey(uint size)
{
byte[] key = new byte[size];
for (int i = 0; i < size; i++)
{
key.[i] = 0;
}
return key;
}
/// <summary>
/// Converts byte[] to string with HEX Code
/// No 0x is created
/// </summary>
/// <param name="data">Data</param>
public string ConvertToHexString(byte[] data)
{
return BitConverter.ToString(data).Replace("-", string.Empty);
}
public void CheckAPDUResponse(APDUResponse response)
{
}
#region Methods for Crypto Operation
/// <summary>
/// Return a copy of the last Block of data
/// </summary>
/// <param name="data">Data compatible to blocksize</param>
/// <param name="blocksize">Size of Block, default is 8</param>
public byte[] GetLastBlock(byte[] data, uint blocksize = 8)
{
if(data.Length % blocksize != 0)
{
throw new ArgumentException(string.Format("Data is not compatible with blocksize(data(length):{0}, blocksize:{1}", data.Length, blocksize));
}
byte[] block = new byte[blocksize];
for (int i = 0; i < blocksize; i++)
{
block[i] = data[data.Length - blocksize + i];
}
return block;
}
/// <summary>
/// Rotates Array to the left
/// </summary>
/// <param name="data">Data</param>
/// <returns>Copy of data</returns>
public byte[] rotateLeft(byte[] data)
{
byte[] rotate = new byte[data.Length];
data.CopyTo(rotate, 0);
byte tmp = rotate[0];
for (var i = 0; i < rotate.Length - 1; i++)
{
rotate[i] = rotate[i + 1];
}
rotate[rotate.Length - 1] = tmp;
return rotate;
}
/// <summary>
/// Rotates Array to the right
/// </summary>
/// <param name="data">Data</param>
/// <returns>Copy of data</returns>
public byte[] rotateRight(byte[] data)
{
byte[] rotate = new byte[data.Length];
data.CopyTo(rotate, 0);
byte tmp = rotate[rotate.Length - 1];
for (var i = rotate.Length - 1; i > 0; i--)
{
rotate[i] = rotate[i - 1];
}
rotate[0] = tmp;
return rotate;
}
/// <summary>
/// Concatenates two Arrays, Array A start at index 0
/// </summary>
/// <param name="a">Array A</param>
/// <param name="b">Array B</param>
/// <returns>Copy of Data</returns>
public byte[] concatenate(byte[] a, byte[] b)
{
byte[] c = new byte[a.Length + b.Length];
a.CopyTo(c, 0);
b.CopyTo(c, a.Length);
return c;
}
#endregion
#endregion
#region DESFire Commands
/// <summary>
/// Get Application IDs from Card
/// </summary>
/// <returns>AIDs as Array</returns>
public UInt32[] GetApplicationIDs()
{
APDUCommand cmd = new APDUCommand(IsoCase.Case2Short)
{
CLA = 0x90,
INS = (byte)APDUInstructions.GET_APPLICATION_IDS
};
APDUResponse response = _Card.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);
}
return applicationIDs.ToArray();
}
/// <summary>
/// Select Application by AID
/// </summary>
/// <param name="aid">3 Byte AID</param>
public void SelectApplication(UInt32 aid)
{
byte[] id_byte = BitConverter.GetBytes(aid);
APDUCommand cmd = new APDUCommand(IsoCase.Case3Short)
{
CLA = 0x90,
INS = (byte)APDUInstructions.SELECT_APPLICATION,
Data = new byte[]
{
id_byte[0],
id_byte[1],
id_byte[2]
},
Le = 0x00
};
APDUResponse response = _Card.Transmit(cmd);
CheckAPDUResponse(response);
}
#endregion
/// <summary>
/// Create new Application with AID
/// </summary>
/// <param name="aid">Appilication ID</param>
public APDUCommand CreateApplication(UInt64 aid)
{
throw new NotImplementedException();
}
public void Format()
{
throw new NotImplementedException();
}
/// <summary>
/// Authenticate to Card
/// </summary>
/// <param name="key_id">0x01 - 0x0D</param>
/// <param name="key"></param>
// TODO
public void AuthenticateDES(byte key_id, byte[] key)
{
APDUCommand cmd_challange_request = new APDUCommand(IsoCase.Case4Short)
{
CLA = 0x90,
INS = (byte)0x1A,
Data = new byte[]
{
key_id
}
};
APDUResponse response = _Card.Transmit(cmd_challange_request);
byte[] rndB_enc = response.Body;
Console.WriteLine("rndB_enc: {0}", ConvertToHexString(rndB_enc));
DES des = new DES();
byte[] rndB = des.Decrypt(rndB_enc, key, GenerateDefaultKey(8));
Console.WriteLine("rndB: {0}", ConvertToHexString(rndB));
byte[] iv = new byte[8];
rndB.CopyTo(iv, 0);
byte[] rndB_rl = rotateLeft(rndB);
Console.WriteLine("rndB_enc: {0}", ConvertToHexString(rndB_rl));
byte[] rndA = new byte[]
{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08
};
Console.WriteLine("rndA: {0}", ConvertToHexString(rndA));
byte[] rndAB = concatenate(rndA, rndB_rl);
Console.WriteLine("rndAB: {0}", ConvertToHexString(rndAB));
byte[] rndAB_enc = des.Encrypt(rndAB, key, rndB_enc);
Console.WriteLine("rndA_rndB_enc: {0}", ConvertToHexString(rndAB_enc));
iv = GetLastBlock(rndAB_enc);
APDUCommand cmd_challange_response = new APDUCommand(IsoCase.Case4Short)
{
CLA = 0x90,
INS = (byte)0xAF,
Data = rndAB_enc
};
Console.WriteLine("cmd_challange_response: {0}", ConvertToHexString(cmd_challange_response.ToArray()));
response = _Card.Transmit(cmd_challange_response);
byte[] encryptedRndAFromCard = response.Body;
Console.WriteLine("encryptedRndAFromCard: {0}", ConvertToHexString(encryptedRndAFromCard));
byte[] rotatedRndAFromCard = des.Decrypt(encryptedRndAFromCard, key, iv);
Console.WriteLine("rotatedRndAFromCard: {0}", ConvertToHexString(rotatedRndAFromCard));
byte[] rndAFromCard = rotateRight(rotatedRndAFromCard);
Console.WriteLine("rndAFromCard: {0}", ConvertToHexString(rndAFromCard));
if (!rndA.SequenceEqual(rndAFromCard))
{
throw new Exception("PICC Challenge is not correct answered.");
}
}
public void ChangeApplicationMasterKey(byte[] aPP_MasterKey)
{
throw new NotImplementedException();
}
/// <summary>
/// Delete Application by ID
/// </summary>
/// <param name="id">3 Byte ID</param>
public APDUCommand 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
};
return cmd;
}
public void ChangeApplicationKey(int v, byte[] aPP_Key_1)
{
throw new NotImplementedException();
}
/// <summary>
/// Select Application by ID
/// </summary>
/// <param name="id">3 Byte ID</param>
public APDUCommand CreateApplication(UInt32 id, byte keysetting1, byte keysetting2)
{
byte[] id_byte = BitConverter.GetBytes(id);
APDUCommand cmd = new APDUCommand(IsoCase.Case4Short)
{
CLA = 0x90,
INS = (byte)APDUInstructions.CREATE_APPLICATION,
Data = new byte[]
{
id_byte[0],
id_byte[1],
id_byte[2],
keysetting1,
keysetting2
},
Le = 0x00
};
return cmd;
}
public void CreateFile(byte fabAccessIdentFileID, FileCommunication pLAIN, ushort fileAccessRight, UInt32 v)
{
throw new NotImplementedException();
}
public ushort GenerateFileAccessRight(AccessRights fREE, int v1, int v2, int v3)
{
throw new NotImplementedException();
}
public void WirteData(byte fabAccessIdentFileID, int v1, int v2, byte[] vs)
{
throw new NotImplementedException();
}
public void CreateFile(byte fabAccessIdentFileID, object plain, AccessRights fREE, int v1, int v2, int v3)
{
throw new NotImplementedException();
}
public byte[] ReadData(byte identFileID, int v1, int v2)
{
throw new NotImplementedException();
}
/// <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(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);
}
}
}