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; 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[] GenerateEmptyArray(uint size) { byte[] key = new byte[size]; for (int i = 0; i < size; i++) { key[i] = 0; } return key; } /// /// Get Range of Array Elements /// /// Array /// Offset in Byte /// Lenght to read in Byte /// new Array with Range of Array Elements 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; } /// /// 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; } /// /// Expand Array to Block Size, fill with 0x00 /// /// public byte[] ExpandToBlockSize(byte[] data, uint bocksize) { if (data == null) { throw new ArgumentNullException("Data cannot be null."); } 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; } /// /// 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; } #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((ChangeApplicationKey)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 #region DESFire Commands /// /// Select Application by ApplicationID (AID) /// /// 3 Byte AID 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 = (byte)APDUInstructions.SELECT_APPLICATION, Data = new byte[] { id_byte[0], id_byte[1], id_byte[2] } }; _Log.Debug(cmd_SelectApplication.ToString()); HexConverter.ConvertToHexString(cmd_SelectApplication.ToArray()); APDUResponse response = _Card.Transmit(cmd_SelectApplication); _Log.DebugFormat(response.ToString()); CheckAPDUResponse(response); } /// /// Authenticate to PICC, with ISO Authenticate for DES Key /// /// 0x01 - 0x0D /// Array of 8/16 Bytes /// !!! WARNING For Testing only !!! 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 = _Card.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, GenerateEmptyArray(8)); _Log.DebugFormat("rndB: {0}", HexConverter.ConvertToHexString(rndB)); rndB.CopyTo(iv, 0); byte[] rndB_rl = RotateLeft(rndB); _Log.DebugFormat("rndB_enc: {0}", HexConverter.ConvertToHexString(rndB_rl)); if (rndA == null) { Random rnd = new Random(); rndA = new byte[8]; rnd.NextBytes(rndA); } _Log.DebugFormat("rndA: {0}", HexConverter.ConvertToHexString(rndA)); byte[] rndAB = 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 = 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 = _Card.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 = 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 = GenerateEmptyArray(8); _Log.DebugFormat("IV: {0}", HexConverter.ConvertToHexString(_IV)); } /// /// Format PICC /// Need Authentication for PICC / Application 0x000000 /// 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 = _Card.Transmit(cmd_format); _Log.Debug(response.ToString()); CheckAPDUResponse(response); } /// /// Create Application for ApplicationID /// /// 3 Byte ID 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 = _Card.Transmit(cmd_CreateApplication); _Log.Debug(response.ToString()); CheckAPDUResponse(response); } /// /// Authenticate to PICC, with ISO Authenticate /// /// 0x01 - 0x0D /// Array of 16 Bytes /// !!! WARNING For Testing only !!! 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 = _Card.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, GenerateEmptyArray(16)); _Log.DebugFormat("rndB: {0}", HexConverter.ConvertToHexString(rndB)); rndB.CopyTo(iv, 0); byte[] rndB_rl = RotateLeft(rndB); _Log.DebugFormat("rndB_enc: {0}", HexConverter.ConvertToHexString(rndB_rl)); if (rndA == null) { Random rnd = new Random(); rndA = new byte[16]; rnd.NextBytes(rndA); } _Log.DebugFormat("rndA: {0}", HexConverter.ConvertToHexString(rndA)); byte[] rndAB = 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 = 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 = _Card.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 = 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 = GenerateEmptyArray(16); _Log.DebugFormat("IV: {0}", HexConverter.ConvertToHexString(_IV)); } /// /// Change AES key, the same as Authenticated /// /// 0x01 - 0x0D /// Array of 16 Bytes /// Version of Key(min. 0x10) 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 = Concatenate(new_key, new byte[] { key_version }); byte[] command = 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 = Concatenate(key_and_version, crc); _Log.DebugFormat("cryptogram: {0}", HexConverter.ConvertToHexString(cryptogram)); byte[] cryptogram_block = 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 = ExtractLastBlock(cryptogram_enc, 16); _Log.DebugFormat("_IV: {0}", HexConverter.ConvertToHexString(_IV)); byte[] data = 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 = _Card.Transmit(cmd_ChangeKey); _Log.Debug(response.ToString()); CheckAPDUResponse(response); } /// /// Change AES key, other than Authenticated /// /// 0x01 - 0x0D /// Array of 16 Bytes /// Version of Key(min. 0x10) 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 = 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}", 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 = Concatenate(key_and_version, crc_cmd); cryptogram = Concatenate(cryptogram, crc_key); _Log.DebugFormat("cryptogram: {0}", HexConverter.ConvertToHexString(cryptogram)); byte[] cryptogram_block = 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 = ExtractLastBlock(cryptogram_enc, 16); _Log.DebugFormat("_IV: {0}", HexConverter.ConvertToHexString(_IV)); byte[] data = 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 = _Card.Transmit(cmd_ChangeKey); _Log.Debug(response.ToString()); CheckAPDUResponse(response); } /// /// Create Standard Data File /// /// ID of File (0x00 - 0x20) /// 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) { 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 = (byte)APDUInstructions.CREATE_STDDATAFILE, Data = Concatenate(data, accessRights_byte, size_byte) }; _Log.Debug(cmd_CreateFile_Standard.ToString()); APDUResponse response = _Card.Transmit(cmd_CreateFile_Standard); _Log.DebugFormat(response.ToString()); CheckAPDUResponse(response); } /// /// Write Data to File /// /// ID of File (0x00 - 0x20) /// Offset for File /// Data to write 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 = 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.Debug(cmd_WriteData.ToString()); APDUResponse response = _Card.Transmit(cmd_WriteData); _Log.Debug(response.ToString()); CheckAPDUResponse(response); } } /// /// Read Data from File /// /// ID of File (0x00 - 0x20) /// Offset for File /// Lenght of Data 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; byte[] readbuffer = new byte[47]; 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.Debug(cmd_ReadData.ToString()); APDUResponse response = _Card.Transmit(cmd_ReadData); _Log.Debug(response.ToString()); CheckAPDUResponse(response); // Remove CMAC from Body read_data.AddRange(GetSubArray(response.Body, 0, bytes_toread)); } return read_data.ToArray(); } /// /// 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 #endregion } }