using log4net.Repository.Hierarchy; using NFC.Crypto; using NFC.ISO7816_4; using NFC.Mifare_DESFire.Enums; using NFC.NXP_MIFARE_DESFire.Exceptions; using PCSC.Iso7816; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace NFC.Mifare_DESFire { public class MIFARE_DESFire { // Docs https://hackmd.io/qATu8uYdRnOC40aFrB9afg #region Log private static readonly log4net.ILog _Log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); #endregion #region Contructors /// /// Construct MIFRARE_DESFire Object with ICard Interface /// /// Implementation of ICard, only transmit is used public MIFARE_DESFire(ICard card) { _Card = card; } #endregion #region Properties /// /// ICard Implementation used to transmit APDUCommands and recive APDUResponses /// private ICard _Card; /// /// SessionKey, is set after Successfull Authentication /// public byte[] _SessionKey; /// /// Initialation Vector for CBC Encryption /// Is 0 bytes after Successfull Authentication /// public byte[] _IV; #endregion #region Methods #region Helper Methods /// /// Generate Byte Array filled with 0 /// /// Size of Array public byte[] GenerateEmptyKey(uint size) { byte[] key = new byte[size]; for (int i = 0; i < size; i++) { key[i] = 0; } return key; } /// /// Converts byte[] to string with HEX Code /// No 0x is created /// /// Data public string ConvertToHexString(byte[] data) { return BitConverter.ToString(data).Replace("-", "").ToLower(); } /// /// Converts string with HEX Code to byte[] /// No 0x is requiered /// /// Data public byte[] ConvertFromHexString(string data) { if (data.Length % 2 == 1) throw new Exception("Data Length is uneven."); byte[] arr = new byte[data.Length >> 1]; for (int i = 0; i < data.Length >> 1; ++i) { arr[i] = (byte)((GetHexVal(data[i << 1]) << 4) + (GetHexVal(data[(i << 1) + 1]))); } return arr; } /// /// Get Range of Array /// /// /// /// /// public byte[] GetSubArray(byte[] array, long offset, long length) { byte[] subarray = new byte[length]; for (long i = offset; i < offset + length; i++) { subarray[i - offset] = array[i]; } return subarray; } private int GetHexVal(char hex) { int val = (int)hex; //For uppercase A-F letters: //return val - (val < 58 ? 48 : 55); //For lowercase a-f letters: //return val - (val < 58 ? 48 : 87); //Or the two combined, but a bit slower: return val - (val < 58 ? 48 : (val < 97 ? 55 : 87)); } /// /// Check APDU Response for DESFire Error Codes /// https://www.nxp.com/docs/en/data-sheet/MF2DLHX0.pdf /// Section: 11.3 /// 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 /// /// Return a copy of the last Block of data /// /// Data compatible to blocksize /// in byte public byte[] ExtractLastBlock(byte[] data, uint blocksize) { if (data == null) { throw new ArgumentNullException("Data cannot be null."); } 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[] lastblock = new byte[blocksize]; for (int i = 0; i < blocksize; i++) { lastblock[i] = data[data.Length - blocksize + i]; } return lastblock; } /// /// Rotates Array to the left /// /// Data /// Copy of data public byte[] RotateLeft(byte[] data) { if (data == null) { throw new ArgumentNullException("Data cannot be null."); } 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; } /// /// Rotates Array to the right /// /// Data /// Copy of data public byte[] RotateRight(byte[] data) { if (data == null) { throw new ArgumentNullException("Data cannot be null."); } 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; } /// /// Concatenates Arrays /// /// List of Byte Array public byte[] Concatenate(params byte[][] data) { if (data == null) { throw new ArgumentNullException("Data cannot be null."); } List cat = new List(); foreach(byte[] d in data) { cat.AddRange(d); } return cat.ToArray(); } /// /// Boolean Operation XOR on all Bytes /// /// Array A /// Array B /// Copy of Data public byte[] XOR(byte[] a, byte[] b) { if (a == null) { throw new ArgumentNullException("Array A cannot be null."); } if (b == null) { throw new ArgumentNullException("Array B cannot be null."); } if (a.Length != b.Length) { throw new ArgumentException(string.Format("Arrays are not same Length(Length A:{0}, Lenght B:{1})", a.Length, b.Length)); } byte[] c = new byte[a.Length]; for(int i = 0; i < a.Length; i++) { c[i] = (byte)(a[i] ^ b[i]); } return c; } /// /// Generates SessionKey for DES Authentification /// /// 16Byte SessionKey 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 Concatenate(sesssionkey, sesssionkey); } /// /// Generates SessionKey for AES Authentification /// /// 16Byte SessionKey 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; } /// /// Set KeyVersion in DES Key /// KeyVersion is stored in LSB of the first 8 Bytes of the DES Key /// public byte[] SetKeyVersion(byte[] key, byte keyversion) { byte[] pow2 = new byte[] { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 }; byte[] new_key = new byte[key.Length]; key.CopyTo(new_key, 0); for (int i = 0; i < 8; i++) { if ((keyversion & pow2[i]) > 0) { new_key[i] = (byte)(new_key[5] | 0x01); } else { new_key[i] = (byte)(new_key[5] & 0x7F); } } return new_key; } /// /// Expand Array to Block Size, fill with 0x00 /// /// public byte[] ExpandToBlockSize(byte[] data, uint bocksize) { int diff = data.Length % (int)bocksize; if (diff == 0) { return data; } byte[] expand = new byte[data.Length + bocksize - diff]; data.CopyTo(expand, 0); for (int i = expand.Length - 1; i > data.Length - 1; i--) { expand[i] = 0x00; } return expand; } #endregion #region DESFire Commands /// /// Authenticate to PICC, with ISO Authenticate /// /// 0x01 - 0x0D /// /// !!! WARNING For Testing only !!! /// Retry after short Time public void AuthenticateISO_DES(byte key_id, byte[] key, byte[] rndA = null) { _Log.Debug("Start AuthenticateISO_DES"); // Sepearte InitialisationVector for Authentication byte[] iv = new byte[8]; APDUCommand cmd_challange_request = new APDUCommand(IsoCase.Case4Short) { CLA = 0x90, INS = 0x1A, Data = new byte[] { key_id } }; _Log.DebugFormat("APDU_CMD(cmd_challange_request): {0}", ConvertToHexString(cmd_challange_request.ToArray())); APDUResponse response = _Card.Transmit(cmd_challange_request); _Log.DebugFormat("APDU_RES(cmd_challange_request): {0}", ConvertToHexString(response.ToArray())); CheckAPDUResponse(response); byte[] rndB_enc = response.Body; _Log.DebugFormat("rndB_enc: {0}", ConvertToHexString(rndB_enc)); TDES des = new TDES(); byte[] rndB = des.Decrypt(rndB_enc, key, GenerateEmptyKey(8)); _Log.DebugFormat("rndB: {0}", ConvertToHexString(rndB)); rndB.CopyTo(iv, 0); byte[] rndB_rl = RotateLeft(rndB); _Log.DebugFormat("rndB_enc: {0}", ConvertToHexString(rndB_rl)); if(rndA == null) { Random rnd = new Random(); rndA = new byte[8]; rnd.NextBytes(rndA); } _Log.DebugFormat("rndA: {0}", ConvertToHexString(rndA)); byte[] rndAB = Concatenate(rndA, rndB_rl); _Log.DebugFormat("rndAB: {0}", ConvertToHexString(rndAB)); byte[] rndAB_enc = des.Encrypt(rndAB, key, rndB_enc); _Log.DebugFormat("rndA_rndB_enc: {0}", ConvertToHexString(rndAB_enc)); iv = ExtractLastBlock(rndAB_enc, 8); APDUCommand cmd_challange_response = new APDUCommand(IsoCase.Case4Short) { CLA = 0x90, INS = 0xAF, Data = rndAB_enc }; _Log.DebugFormat("APDU_CMD(cmd_challange_response): {0}", ConvertToHexString(cmd_challange_response.ToArray())); response = _Card.Transmit(cmd_challange_response); _Log.DebugFormat("APDU_RES(cmd_challange_response): {0}", ConvertToHexString(cmd_challange_response.ToArray())); CheckAPDUResponse(response); byte[] encryptedRndAFromCard = response.Body; _Log.DebugFormat("encryptedRndAFromCard: {0}", ConvertToHexString(encryptedRndAFromCard)); byte[] rotatedRndAFromCard = des.Decrypt(encryptedRndAFromCard, key, iv); _Log.DebugFormat("rotatedRndAFromCard: {0}", ConvertToHexString(rotatedRndAFromCard)); byte[] rndAFromCard = RotateRight(rotatedRndAFromCard); _Log.DebugFormat("rndAFromCard: {0}", ConvertToHexString(rndAFromCard)); if (!rndA.SequenceEqual(rndAFromCard)) { throw new Exception("Authentication failed, PICC Challenge is not corret"); } _SessionKey = GenerateSesionKey_DES(rndA, rndB); _Log.DebugFormat("_SessionKey: {0}", ConvertToHexString(_SessionKey)); _IV = GenerateEmptyKey(8); _Log.DebugFormat("_IV: {0}", ConvertToHexString(_IV)); _Log.Debug("End AuthenticateISO_DES"); } /// /// Format PICC /// public void Format() { _Log.Debug("Start Format"); APDUCommand cmd_format = new APDUCommand(IsoCase.Case2Short) { CLA = 0x90, INS = 0xFC, }; _Log.DebugFormat("APDU_CMD(cmd_format): {0}", ConvertToHexString(cmd_format.ToArray())); APDUResponse response = _Card.Transmit(cmd_format); _Log.DebugFormat("APDU_RES(cmd_format): {0}", ConvertToHexString(response.ToArray())); CheckAPDUResponse(response); _Log.Debug("End Format"); } /// /// Create Application for ID /// /// 3 Byte ID public void CreateApplication(UInt32 aid, byte keysetting1, byte keysetting2) { _Log.Debug("Start CreateApplication"); byte[] id_byte = BitConverter.GetBytes(aid); _Log.DebugFormat("AID: {0}", 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.DebugFormat("APDU_CMD(cmd_CreateApplication): {0}", ConvertToHexString(cmd_CreateApplication.ToArray())); APDUResponse response = _Card.Transmit(cmd_CreateApplication); _Log.DebugFormat("APDU_RES(cmd_CreateApplication): {0}", ConvertToHexString(response.ToArray())); CheckAPDUResponse(response); _Log.Debug("End CreateApplication"); } /// /// Select Application by AID /// /// 3 Byte AID public void SelectApplication(UInt32 aid) { _Log.Debug("Start SelectApplication"); byte[] id_byte = BitConverter.GetBytes(aid); _Log.DebugFormat("AID: {0}", ConvertToHexString(id_byte.ToArray())); APDUCommand cmd_SelectApplication = new APDUCommand(IsoCase.Case4Short) { CLA = 0x90, INS = (byte)APDUInstructions.SELECT_APPLICATION, Data = new byte[] { id_byte[0], id_byte[1], id_byte[2] } }; _Log.DebugFormat("APDU_CMD(cmd_SelectApplication): {0}", ConvertToHexString(cmd_SelectApplication.ToArray())); APDUResponse response = _Card.Transmit(cmd_SelectApplication); _Log.DebugFormat("APDU_RES(cmd_SelectApplication): {0}", ConvertToHexString(response.ToArray())); CheckAPDUResponse(response); _Log.Debug("End SelectApplication"); } /// /// Authenticate to PICC, with ISO Authenticate /// /// 0x01 - 0x0D /// /// !!! WARNING For Testing only !!! /// Retry after short Time public void AuthenticateISO_AES(byte key_id, byte[] key, byte[] rndA = null) { _Log.Debug("Start AuthenticateISO_AES"); // 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.DebugFormat("APDU_CMD(cmd_challange_request): {0}", ConvertToHexString(cmd_challange_request.ToArray())); APDUResponse response = _Card.Transmit(cmd_challange_request); _Log.DebugFormat("APDU_RES(cmd_challange_request): {0}", ConvertToHexString(response.ToArray())); CheckAPDUResponse(response); byte[] rndB_enc = response.Body; _Log.DebugFormat("rndB_enc: {0}", ConvertToHexString(rndB_enc)); AES aes = new AES(); byte[] rndB = aes.Decrypt(rndB_enc, key, GenerateEmptyKey(16)); _Log.DebugFormat("rndB: {0}", ConvertToHexString(rndB)); rndB.CopyTo(iv, 0); byte[] rndB_rl = RotateLeft(rndB); _Log.DebugFormat("rndB_enc: {0}", ConvertToHexString(rndB_rl)); if (rndA == null) { Random rnd = new Random(); rndA = new byte[16]; rnd.NextBytes(rndA); } _Log.DebugFormat("rndA: {0}", ConvertToHexString(rndA)); byte[] rndAB = Concatenate(rndA, rndB_rl); _Log.DebugFormat("rndAB: {0}", ConvertToHexString(rndAB)); byte[] rndAB_enc = aes.Encrypt(rndAB, key, rndB_enc); _Log.DebugFormat("rndA_rndB_enc: {0}", ConvertToHexString(rndAB_enc)); iv = ExtractLastBlock(rndAB_enc, 16); APDUCommand cmd_challange_response = new APDUCommand(IsoCase.Case4Short) { CLA = 0x90, INS = 0xAF, Data = rndAB_enc }; _Log.DebugFormat("APDU_CMD(cmd_challange_response): {0}", ConvertToHexString(cmd_challange_response.ToArray())); response = _Card.Transmit(cmd_challange_response); _Log.DebugFormat("APDU_RES(cmd_challange_response): {0}", ConvertToHexString(cmd_challange_response.ToArray())); CheckAPDUResponse(response); byte[] encryptedRndAFromCard = response.Body; _Log.DebugFormat("encryptedRndAFromCard: {0}", ConvertToHexString(encryptedRndAFromCard)); byte[] rotatedRndAFromCard = aes.Decrypt(encryptedRndAFromCard, key, iv); _Log.DebugFormat("rotatedRndAFromCard: {0}", ConvertToHexString(rotatedRndAFromCard)); byte[] rndAFromCard = RotateRight(rotatedRndAFromCard); _Log.DebugFormat("rndAFromCard: {0}", ConvertToHexString(rndAFromCard)); if (!rndA.SequenceEqual(rndAFromCard)) { throw new Exception("Authentication failed, PICC Challenge is not corret"); } _SessionKey = GenerateSesionKey_AES(rndA, rndB); _Log.DebugFormat("_SessionKey: {0}", ConvertToHexString(_SessionKey)); _IV = GenerateEmptyKey(16); _Log.DebugFormat("_IV: {0}", ConvertToHexString(_IV)); _Log.Debug("End AuthenticateISO_DES"); } /// /// Change Same DES key as Authenticated /// /// /// /// public void ChangeKey_DES(byte key_id, byte[] new_key, byte key_version) { _Log.Debug("Start ChangeKey_DES"); byte[] header = new byte[] { 0xC4, key_id }; _Log.DebugFormat("header: {0}", ConvertToHexString(header)); byte[] key_and_version = new_key; byte[] command = Concatenate(header, key_and_version); _Log.DebugFormat("command: {0}", ConvertToHexString(command)); CRC32 crc32 = new CRC32(); byte[] crc = crc32.Calculate(command); _Log.DebugFormat("crc: {0}", ConvertToHexString(crc)); byte[] cryptogram = Concatenate(key_and_version, crc); _Log.DebugFormat("cryptogram: {0}", ConvertToHexString(cryptogram)); byte[] cryptogram_block = ExpandToBlockSize(cryptogram, 16); _Log.DebugFormat("cryptogram_block: {0}", ConvertToHexString(cryptogram_block)); TDES des = new TDES(); byte[] cryptogram_enc = des.Encrypt(cryptogram_block, _SessionKey, _IV); _Log.DebugFormat("cryptogram_enc: {0}", ConvertToHexString(cryptogram_enc)); _IV = ExtractLastBlock(cryptogram_enc, 8); _Log.DebugFormat("_IV: {0}", ConvertToHexString(_IV)); byte[] data = Concatenate(new byte[] { key_id }, cryptogram_enc); _Log.DebugFormat("data: {0}", ConvertToHexString(data)); APDUCommand cmd_ChangeKey = new APDUCommand(IsoCase.Case4Short) { CLA = 0x90, INS = 0xC4, Data = data }; _Log.DebugFormat("APDU_CMD(cmd_ChangeKey): {0}", ConvertToHexString(cmd_ChangeKey.ToArray())); APDUResponse response = _Card.Transmit(cmd_ChangeKey); _Log.DebugFormat("APDU_RES(cmd_ChangeKey): {0}", ConvertToHexString(response.ToArray())); CheckAPDUResponse(response); _Log.Debug("End ChangeKey_AES"); } /// /// Change Same AES key as Authenticated /// /// /// /// public void ChangeKey_AES(byte key_id, byte[] new_key, byte key_version) { _Log.Debug("Start ChangeKey_AES"); byte[] header = new byte[] { 0xC4, key_id }; _Log.DebugFormat("header: {0}", ConvertToHexString(header)); // AES Key Version is Append to Key byte[] key_and_version = Concatenate(new_key, new byte[] { key_version }); byte[] command = Concatenate(header, key_and_version); _Log.DebugFormat("command: {0}", ConvertToHexString(command)); CRC32 crc32 = new CRC32(); byte[] crc = crc32.Calculate(command); _Log.DebugFormat("crc: {0}", ConvertToHexString(crc)); byte[] cryptogram = Concatenate(key_and_version, crc); _Log.DebugFormat("cryptogram: {0}", ConvertToHexString(cryptogram)); byte[] cryptogram_block = ExpandToBlockSize(cryptogram, 16); _Log.DebugFormat("cryptogram_block: {0}", ConvertToHexString(cryptogram_block)); AES aes = new AES(); byte[] cryptogram_enc = aes.Encrypt(cryptogram_block, _SessionKey, _IV); _Log.DebugFormat("cryptogram_enc: {0}", ConvertToHexString(cryptogram_enc)); _IV = ExtractLastBlock(cryptogram_enc, 16); _Log.DebugFormat("_IV: {0}", ConvertToHexString(_IV)); byte[] data = Concatenate(new byte[] { key_id }, cryptogram_enc); _Log.DebugFormat("data: {0}", ConvertToHexString(data)); APDUCommand cmd_ChangeKey = new APDUCommand(IsoCase.Case4Short) { CLA = 0x90, INS = 0xC4, Data = data }; _Log.DebugFormat("APDU_CMD(cmd_ChangeKey): {0}", ConvertToHexString(cmd_ChangeKey.ToArray())); APDUResponse response = _Card.Transmit(cmd_ChangeKey); _Log.DebugFormat("APDU_RES(cmd_ChangeKey): {0}", ConvertToHexString(response.ToArray())); CheckAPDUResponse(response); _Log.Debug("End ChangeKey_AES"); } /// /// Change other AES key as Authenticated /// /// /// /// public void ChangeOtherKey_AES(byte key_id, byte[] new_key, byte[] old_key, byte key_version) { _Log.Debug("Start ChangeOtherKey_AES"); byte[] header = new byte[] { 0xC4, key_id }; _Log.DebugFormat("header: {0}", ConvertToHexString(header)); byte[] key_xor = XOR(new_key, old_key); // AES Key Version is Append to Key byte[] key_and_version = Concatenate(key_xor, new byte[] { key_version }); byte[] command = Concatenate(header, key_and_version); _Log.DebugFormat("command: {0}", ConvertToHexString(command)); CRC32 crc32 = new CRC32(); byte[] crc_cmd = crc32.Calculate(command); _Log.DebugFormat("crc_cmd: {0}", ConvertToHexString(crc_cmd)); byte[] crc_key = crc32.Calculate(new_key); _Log.DebugFormat("crc_key: {0}", ConvertToHexString(crc_key)); byte[] cryptogram = Concatenate(key_and_version, crc_cmd); cryptogram = Concatenate(cryptogram, crc_key); _Log.DebugFormat("cryptogram: {0}", ConvertToHexString(cryptogram)); byte[] cryptogram_block = ExpandToBlockSize(cryptogram, 16); _Log.DebugFormat("cryptogram_block: {0}", ConvertToHexString(cryptogram_block)); AES aes = new AES(); byte[] cryptogram_enc = aes.Encrypt(cryptogram_block, _SessionKey, _IV); _Log.DebugFormat("cryptogram_enc: {0}", ConvertToHexString(cryptogram_enc)); _IV = ExtractLastBlock(cryptogram_enc, 16); _Log.DebugFormat("_IV: {0}", ConvertToHexString(_IV)); byte[] data = Concatenate(new byte[] { key_id }, cryptogram_enc); _Log.DebugFormat("data: {0}", ConvertToHexString(data)); APDUCommand cmd_ChangeKey = new APDUCommand(IsoCase.Case4Short) { CLA = 0x90, INS = 0xC4, Data = data }; _Log.DebugFormat("APDU_CMD(cmd_ChangeKey): {0}", ConvertToHexString(cmd_ChangeKey.ToArray())); APDUResponse response = _Card.Transmit(cmd_ChangeKey); _Log.DebugFormat("APDU_RES(cmd_ChangeKey): {0}", ConvertToHexString(response.ToArray())); CheckAPDUResponse(response); _Log.Debug("End ChangeOtherKey_AES"); } /// /// Create Standard Data File /// /// ID of File (0x01 - 0x10) /// Type of File Communicaton /// Access Rights for File /// Size of File in Bytes public void CreateFile_Standard(byte file_id, FileCommunication communication, UInt16 accessRights, UInt32 size) { _Log.Debug("Start CreateFile_Standard"); _Log.DebugFormat("FID: {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 = (byte)APDUInstructions.CREATE_STDDATAFILE, Data = Concatenate(data, accessRights_byte, size_byte) }; _Log.DebugFormat("APDU_CMD(cmd_CreateFile_Standard): {0}", ConvertToHexString(cmd_CreateFile_Standard.ToArray())); APDUResponse response = _Card.Transmit(cmd_CreateFile_Standard); _Log.DebugFormat("APDU_RES(cmd_CreateFile_Standard): {0}", ConvertToHexString(response.ToArray())); CheckAPDUResponse(response); _Log.Debug("End CreateFile_Standard"); } /// /// Read Data from File /// /// ID of File (0x01 - 0x10) /// Offset for File /// Lenght of Data public byte[] ReadData(byte file_id, UInt32 offset, UInt32 length) { _Log.Debug("Start ReadData"); int max_read_bytes_pre_transaction = 58; long bytes_readed = 0; List read_data = new List(); 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 = 0; 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 = (byte)APDUInstructions.READ_DATA, Data = Concatenate(data, offset_byte, length_byte) }; _Log.DebugFormat("APDU_CMD(cmd_ReadData): {0}", ConvertToHexString(cmd_ReadData.ToArray())); APDUResponse response = _Card.Transmit(cmd_ReadData); _Log.DebugFormat("APDU_RES(cmd_ReadData): {0}", ConvertToHexString(response.ToArray())); CheckAPDUResponse(response); read_data.AddRange(response.Body); } _Log.Debug("End ReadData"); return read_data.ToArray(); } /// /// Write Data to File /// /// ID of File (0x01 - 0x10) /// Offset for File /// Data to write public void WriteData(byte file_id, UInt32 offset, byte[] data) { _Log.Debug("Start WriteData"); byte[] file_id_array = new byte[] { file_id }; byte[] offset_byte_tolong = BitConverter.GetBytes(offset); // Use only 3 Bytes byte[] offset_byte = new byte[] { offset_byte_tolong[0], offset_byte_tolong[1], offset_byte_tolong[2], }; byte[] lenght_byte_tolong = BitConverter.GetBytes(data.Length); // Use only 3 Bytes byte[] lenght_byte = new byte[] { lenght_byte_tolong[0], lenght_byte_tolong[1], lenght_byte_tolong[2], }; APDUCommand cmd_WriteData = new APDUCommand(IsoCase.Case4Short) { CLA = 0x90, INS = (byte)APDUInstructions.WRITE_DATA, Data = Concatenate(file_id_array, offset_byte, lenght_byte, data) }; _Log.DebugFormat("APDU_CMD(cmd_WriteData): {0}", ConvertToHexString(cmd_WriteData.ToArray())); APDUResponse response = _Card.Transmit(cmd_WriteData); _Log.DebugFormat("APDU_RES(cmd_WriteData): {0}", ConvertToHexString(response.ToArray())); CheckAPDUResponse(response); _Log.Debug("End WriteData"); } ///// ///// Write Data to File ///// ///// ID of File (0x01 - 0x10) ///// Offset for File ///// Data to write //public void WriteData(byte file_id, UInt32 offset, byte[] data) //{ // _Log.Debug("Start WriteData"); // int max_write_bytes_pre_transaction = 8; // 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 = 0; // 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 = 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 = (byte)APDUInstructions.WRITE_DATA, // Data = Concatenate(file_id_array, offset_byte, length_byte, write_buffer) // }; // _Log.DebugFormat("APDU_CMD(cmd_WriteData): {0}", ConvertToHexString(cmd_WriteData.ToArray())); // APDUResponse response = _Card.Transmit(cmd_WriteData); // _Log.DebugFormat("APDU_RES(cmd_WriteData): {0}", ConvertToHexString(response.ToArray())); // CheckAPDUResponse(response); // } // _Log.Debug("End WriteData"); //} /// /// Get all ApplicationIDS from PICC /// /// AIDs (3 Byte) as Array //public UInt32[] GetApplicationIDs() //{ // _Log.Debug("Start 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 applicationIDs = new List(); // 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(); //} /// /// Delete Application by ID /// /// 3 Byte ID //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 = _Card.Transmit(cmd); // CheckAPDUResponse(response); //} #endregion #region Configuration Generator /// /// Genearte KeySetting1 for Application Settings or PICC Setting /// 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); } /// /// Genearte KeySetting1 for Application Settings or PICC Setting /// /// ID of Key for changing Application Keys /// 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); } /// /// Genearte KeySetting2 for Application Creation /// /// Number of keys that can be stored within the application (0x01-0x0D) /// public byte GenerateKeySetting2(CryptoOperationsType cryptoOperations, FileIdentifies fileIdentifies, byte numberOfKeys) { if (numberOfKeys < 0x01 || numberOfKeys >= 0x0D) { throw new ArgumentOutOfRangeException(); } return (byte)((byte)cryptoOperations | (byte)fileIdentifies | numberOfKeys); } /// /// Generate FileAccess Rights for File Settings /// Use enum AccesRights for Free or Never Option /// /// KeyID for Read Access /// KeyID for Write Access /// KeyID for Read and Write Access /// KeyID for Configuration Access 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 #endregion } }