diff --git a/NFC/Crypto/AES.cs b/NFC/Crypto/AES.cs new file mode 100644 index 0000000..4c5be9a --- /dev/null +++ b/NFC/Crypto/AES.cs @@ -0,0 +1,45 @@ +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Paddings; +using Org.BouncyCastle.Crypto.Parameters; + +namespace NFC.Crypto +{ + public class AES + { + public byte[] Encrypt(byte[] data, byte[] key, byte[] iv) + { + AesEngine engine = new AesEngine(); + CbcBlockCipher blockCipher = new CbcBlockCipher(engine); + BufferedBlockCipher cipher = new BufferedBlockCipher(blockCipher); + KeyParameter keyParam = new KeyParameter(key); + ParametersWithIV keyParamWithIV = new ParametersWithIV(keyParam, iv); + + // Encrypt + cipher.Init(true, keyParamWithIV); + byte[] outputBytes = new byte[cipher.GetOutputSize(data.Length)]; + int length = cipher.ProcessBytes(data, outputBytes, 0); + cipher.DoFinal(outputBytes, length); + + return outputBytes; + } + + public byte[] Decrypt(byte[] data, byte[] key, byte[] iv) + { + AesEngine engine = new AesEngine(); + CbcBlockCipher blockCipher = new CbcBlockCipher(engine); + BufferedBlockCipher cipher = new BufferedBlockCipher(blockCipher); + KeyParameter keyParam = new KeyParameter(key); + ParametersWithIV keyParamWithIV = new ParametersWithIV(keyParam, iv); + + // Decrypt + cipher.Init(false, keyParamWithIV); + byte[] outputBytes = new byte[cipher.GetOutputSize(data.Length)]; + int length = cipher.ProcessBytes(data, outputBytes, 0); + cipher.DoFinal(outputBytes, length); + + return outputBytes; + } + } +} diff --git a/NFC/Crypto/CRC16.cs b/NFC/Crypto/CRC16.cs new file mode 100644 index 0000000..25f2a22 --- /dev/null +++ b/NFC/Crypto/CRC16.cs @@ -0,0 +1,44 @@ +using System; + +namespace NFC.Crypto +{ + public class CRC16 + { + public byte[] Calculate(byte[] data) + { + UInt16 crc16 = 0x6363; + + crc16 = Calculate(data, crc16); + + return BitConverter.GetBytes(crc16); + } + + public byte[] Calculate(byte[] cmd, byte[] data) + { + UInt16 crc16 = 0x6363; + + crc16 = Calculate(cmd, crc16); + crc16 = Calculate(data, crc16); + + return BitConverter.GetBytes(crc16); + } + + public UInt16 Calculate(byte[] data, UInt16 crc16) + { + for (int i = 0; i < data.Length; i++) + { + crc16 ^= data[i]; + for (int b = 0; b < 8; b++) + { + bool b_Bit = (crc16 & 0x01) > 0; + crc16 >>= 1; + if (b_Bit) + { + crc16 ^= 0x8408; + } + } + } + return crc16; + } + } +} diff --git a/NFC/Crypto/CRC32.cs b/NFC/Crypto/CRC32.cs index 6593d6e..e3e6f1d 100644 --- a/NFC/Crypto/CRC32.cs +++ b/NFC/Crypto/CRC32.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; namespace NFC.Crypto { diff --git a/NFC/ISO7816-4/APDUResponse.cs b/NFC/ISO7816-4/APDUResponse.cs index 6b01ab7..a52d62b 100644 --- a/NFC/ISO7816-4/APDUResponse.cs +++ b/NFC/ISO7816-4/APDUResponse.cs @@ -107,5 +107,28 @@ namespace NFC.ISO7816_4 } } #endregion + + #region Methodes + public byte[] ToArray() + { + byte[] array = null; + if (Body != null) + { + array = new byte[Body.Length + 2]; + Body.CopyTo(array, 0); + + array[Body.Length] = SW1; + array[Body.Length + 1] = SW2; + } + else + { + array = new byte[2]; + array[0] = SW1; + array[1] = SW2; + } + + return array; + } + #endregion } } diff --git a/NFC/NFC.csproj b/NFC/NFC.csproj index 59bc652..fdcc319 100644 --- a/NFC/NFC.csproj +++ b/NFC/NFC.csproj @@ -5,6 +5,7 @@ + diff --git a/NFC/NXP MIFARE DESFire/Exceptions/AuthenticationDelayException.cs b/NFC/NXP MIFARE DESFire/Exceptions/AuthenticationDelayException.cs new file mode 100644 index 0000000..160eceb --- /dev/null +++ b/NFC/NXP MIFARE DESFire/Exceptions/AuthenticationDelayException.cs @@ -0,0 +1,26 @@ +using System; + +namespace NFC.NXP_MIFARE_DESFire.Exceptions +{ + /// + /// Currently not allowed to authenticate. Keeptrying until full delay is spent. + /// 0x91AD + /// + public class AuthenticationDelayException : Exception + { + public AuthenticationDelayException() + { + + } + + public AuthenticationDelayException(string message) : base(message) + { + + } + + public AuthenticationDelayException(string message, Exception inner) : base(message, inner) + { + + } + } +} diff --git a/NFC/NXP MIFARE DESFire/Exceptions/AuthenticationErrorException.cs b/NFC/NXP MIFARE DESFire/Exceptions/AuthenticationErrorException.cs new file mode 100644 index 0000000..368e0eb --- /dev/null +++ b/NFC/NXP MIFARE DESFire/Exceptions/AuthenticationErrorException.cs @@ -0,0 +1,26 @@ +using System; + +namespace NFC.NXP_MIFARE_DESFire.Exceptions +{ + /// + /// Current authentication status does not allow there- quested command. + /// 0x91AE + /// + public class AuthenticationErrorException : Exception + { + public AuthenticationErrorException() + { + + } + + public AuthenticationErrorException(string message) : base(message) + { + + } + + public AuthenticationErrorException(string message, Exception inner) : base(message, inner) + { + + } + } +} diff --git a/NFC/NXP MIFARE DESFire/Exceptions/BoundaryErrorException.cs b/NFC/NXP MIFARE DESFire/Exceptions/BoundaryErrorException.cs new file mode 100644 index 0000000..f46e5ea --- /dev/null +++ b/NFC/NXP MIFARE DESFire/Exceptions/BoundaryErrorException.cs @@ -0,0 +1,26 @@ +using System; + +namespace NFC.NXP_MIFARE_DESFire.Exceptions +{ + /// + /// Attempt toread/write data from/to beyond thefile’s/record’s limits. Attempt to exceed the limitsof a value file. + /// 0x91BE + /// + public class BoundaryErrorException : Exception + { + public BoundaryErrorException() + { + + } + + public BoundaryErrorException(string message) : base(message) + { + + } + + public BoundaryErrorException(string message, Exception inner) : base(message, inner) + { + + } + } +} diff --git a/NFC/NXP MIFARE DESFire/Exceptions/CommandAbortedException.cs b/NFC/NXP MIFARE DESFire/Exceptions/CommandAbortedException.cs new file mode 100644 index 0000000..34e1534 --- /dev/null +++ b/NFC/NXP MIFARE DESFire/Exceptions/CommandAbortedException.cs @@ -0,0 +1,26 @@ +using System; + +namespace NFC.NXP_MIFARE_DESFire.Exceptions +{ + /// + /// Previous Command was not fully completed.Not all Frames were requested or provided bythe PCD. + /// 0x91CA + /// + public class CommandAbortedException : Exception + { + public CommandAbortedException() + { + + } + + public CommandAbortedException(string message) : base(message) + { + + } + + public CommandAbortedException(string message, Exception inner) : base(message, inner) + { + + } + } +} diff --git a/NFC/NXP MIFARE DESFire/Exceptions/DuplicateErrorException.cs b/NFC/NXP MIFARE DESFire/Exceptions/DuplicateErrorException.cs new file mode 100644 index 0000000..8b09b29 --- /dev/null +++ b/NFC/NXP MIFARE DESFire/Exceptions/DuplicateErrorException.cs @@ -0,0 +1,26 @@ +using System; + +namespace NFC.NXP_MIFARE_DESFire.Exceptions +{ + /// + /// Creation of file/application failed because file/application with same number already exists + /// 0x91DE + /// + public class DuplicateErrorException : Exception + { + public DuplicateErrorException() + { + + } + + public DuplicateErrorException(string message) : base(message) + { + + } + + public DuplicateErrorException(string message, Exception inner) : base(message, inner) + { + + } + } +} diff --git a/NFC/NXP MIFARE DESFire/Exceptions/FileNotFoundException.cs b/NFC/NXP MIFARE DESFire/Exceptions/FileNotFoundException.cs new file mode 100644 index 0000000..1de1e7c --- /dev/null +++ b/NFC/NXP MIFARE DESFire/Exceptions/FileNotFoundException.cs @@ -0,0 +1,26 @@ +using System; + +namespace NFC.NXP_MIFARE_DESFire.Exceptions +{ + /// + /// Specified file number does not exist. + /// 0x91F0 + /// + public class FileNotFoundException : Exception + { + public FileNotFoundException() + { + + } + + public FileNotFoundException(string message) : base(message) + { + + } + + public FileNotFoundException(string message, Exception inner) : base(message, inner) + { + + } + } +} diff --git a/NFC/NXP MIFARE DESFire/Exceptions/IllegalCommandCodeException.cs b/NFC/NXP MIFARE DESFire/Exceptions/IllegalCommandCodeException.cs new file mode 100644 index 0000000..18f4992 --- /dev/null +++ b/NFC/NXP MIFARE DESFire/Exceptions/IllegalCommandCodeException.cs @@ -0,0 +1,26 @@ +using System; + +namespace NFC.NXP_MIFARE_DESFire.Exceptions +{ + /// + /// Command code not supported. + /// 0x911C + /// + public class IllegalCommandCodeException : Exception + { + public IllegalCommandCodeException() + { + + } + + public IllegalCommandCodeException(string message) : base(message) + { + + } + + public IllegalCommandCodeException(string message, Exception inner) : base(message, inner) + { + + } + } +} diff --git a/NFC/NXP MIFARE DESFire/Exceptions/IntegrityErrorException.cs b/NFC/NXP MIFARE DESFire/Exceptions/IntegrityErrorException.cs new file mode 100644 index 0000000..6dce4f3 --- /dev/null +++ b/NFC/NXP MIFARE DESFire/Exceptions/IntegrityErrorException.cs @@ -0,0 +1,26 @@ +using System; + +namespace NFC.NXP_MIFARE_DESFire.Exceptions +{ + /// + /// CRC or MAC does not match data. Paddingbytes not valid. + /// 0x911E + /// + public class IntegrityErrorException : Exception + { + public IntegrityErrorException() + { + + } + + public IntegrityErrorException(string message) : base(message) + { + + } + + public IntegrityErrorException(string message, Exception inner) : base(message, inner) + { + + } + } +} diff --git a/NFC/NXP MIFARE DESFire/Exceptions/LengthErrorException.cs b/NFC/NXP MIFARE DESFire/Exceptions/LengthErrorException.cs new file mode 100644 index 0000000..b817242 --- /dev/null +++ b/NFC/NXP MIFARE DESFire/Exceptions/LengthErrorException.cs @@ -0,0 +1,26 @@ +using System; + +namespace NFC.NXP_MIFARE_DESFire.Exceptions +{ + /// + /// Length of command string invalid. + /// 0x917E + /// + public class LengthErrorException : Exception + { + public LengthErrorException() + { + + } + + public LengthErrorException(string message) : base(message) + { + + } + + public LengthErrorException(string message, Exception inner) : base(message, inner) + { + + } + } +} diff --git a/NFC/NXP MIFARE DESFire/Exceptions/NoSuchKeyException.cs b/NFC/NXP MIFARE DESFire/Exceptions/NoSuchKeyException.cs new file mode 100644 index 0000000..d534bbc --- /dev/null +++ b/NFC/NXP MIFARE DESFire/Exceptions/NoSuchKeyException.cs @@ -0,0 +1,26 @@ +using System; + +namespace NFC.NXP_MIFARE_DESFire.Exceptions +{ + /// + /// Invalid key number specified. + /// 0x9140 + /// + public class NoSuchKeyException : Exception + { + public NoSuchKeyException() + { + + } + + public NoSuchKeyException(string message) : base(message) + { + + } + + public NoSuchKeyException(string message, Exception inner) : base(message, inner) + { + + } + } +} diff --git a/NFC/NXP MIFARE DESFire/Exceptions/ParameterErrorException.cs b/NFC/NXP MIFARE DESFire/Exceptions/ParameterErrorException.cs new file mode 100644 index 0000000..c4e3d3f --- /dev/null +++ b/NFC/NXP MIFARE DESFire/Exceptions/ParameterErrorException.cs @@ -0,0 +1,26 @@ +using System; + +namespace NFC.NXP_MIFARE_DESFire.Exceptions +{ + /// + /// Value of the parameter(s) invalid. + /// 0x919E + /// + public class ParameterErrorException : Exception + { + public ParameterErrorException() + { + + } + + public ParameterErrorException(string message) : base(message) + { + + } + + public ParameterErrorException(string message, Exception inner) : base(message, inner) + { + + } + } +} diff --git a/NFC/NXP MIFARE DESFire/Exceptions/PermissionDeniedException.cs b/NFC/NXP MIFARE DESFire/Exceptions/PermissionDeniedException.cs new file mode 100644 index 0000000..12a0928 --- /dev/null +++ b/NFC/NXP MIFARE DESFire/Exceptions/PermissionDeniedException.cs @@ -0,0 +1,26 @@ +using System; + +namespace NFC.NXP_MIFARE_DESFire.Exceptions +{ + /// + /// Current configuration / status does not allow the requested command. + /// 0x919D + /// + public class PermissionDeniedException : Exception + { + public PermissionDeniedException() + { + + } + + public PermissionDeniedException(string message) : base(message) + { + + } + + public PermissionDeniedException(string message, Exception inner) : base(message, inner) + { + + } + } +} diff --git a/NFC/NXP MIFARE DESFire/MIFARE_DESFire.cs b/NFC/NXP MIFARE DESFire/MIFARE_DESFire.cs index 4fbe232..2c3840e 100644 --- a/NFC/NXP MIFARE DESFire/MIFARE_DESFire.cs +++ b/NFC/NXP MIFARE DESFire/MIFARE_DESFire.cs @@ -56,9 +56,10 @@ namespace NFC.Mifare_DESFire /// Data public string ConvertToHexString(byte[] data) { - return BitConverter.ToString(data).Replace("-", string.Empty); + return BitConverter.ToString(data).Replace("-", " "); } + public void CheckAPDUResponse(APDUResponse response) { @@ -297,7 +298,7 @@ namespace NFC.Mifare_DESFire return data; } - byte[] expand = new byte[data.Length + diff]; + byte[] expand = new byte[data.Length + bocksize - diff]; data.CopyTo(expand, 0); @@ -311,44 +312,43 @@ namespace NFC.Mifare_DESFire public void ChangeKeyDES(byte key_no, byte[] key_new, byte[] key_current) { - if(!CheckKey(key_new)) + Console.WriteLine("key_new: {0}", ConvertToHexString(key_new)); + key_new = concatenate(key_new, key_new); + Console.WriteLine("key_new_double: {0}", ConvertToHexString(key_new)); + + byte[] header = new byte[] { - throw new ArgumentException("key_new is invalid"); - } + 0xC4, key_no + }; + Console.WriteLine("header: {0}", ConvertToHexString(header)); - if (!CheckKey(key_current)) - { - throw new ArgumentException("key_new is invalid"); - } + byte[] cmd_ = concatenate(header, key_new); + Console.WriteLine("cmd_: {0}", ConvertToHexString(cmd_)); - if(GetKeyTypeDES(key_new) != GetKeyTypeDES(key_current)) - { - throw new ArgumentException("key_new and key_current are not same KeyType"); - } - - byte[] keys_xor = xor(key_new, key_current); - - keys_xor = concatenate(keys_xor, keys_xor); - - CRC32 crc32 = new CRC32(); - byte[] crc = crc32.Calculate(new byte[] { (byte)APDUInstructions.CHANGE_KEY, key_no }, keys_xor); - - byte[] key_xor_crc = concatenate(keys_xor, crc); - - byte[] key_xor_crc_block = expandToBlockSize(key_xor_crc, 8); + CRC32 crc = new CRC32(); + byte[] cmd_crc = crc.Calculate(cmd_); + Console.WriteLine("cmd_crc: {0}", ConvertToHexString(cmd_crc)); + byte[] cryptogram = concatenate(key_new, cmd_crc); + Console.WriteLine("cryptogram: {0}", ConvertToHexString(cryptogram)); + byte[] cryptogram_block = expandToBlockSize(cryptogram, 8); + Console.WriteLine("cryptogram_block: {0}", ConvertToHexString(cryptogram_block)); DES des = new DES(); - byte[] key_xor_crc_block_enc = des.Encrypt(key_xor_crc_block, _SessionKey, _IV); + byte[] cryptogram_block_enc = des.Encrypt(cryptogram_block, _SessionKey, _IV); + Console.WriteLine("cryptogram_block_enc: {0}", ConvertToHexString(cryptogram_block_enc)); + + byte[] data = concatenate(new byte[] { key_no }, cryptogram_block_enc); + Console.WriteLine("data: {0}", ConvertToHexString(data)); APDUCommand cmd = new APDUCommand(IsoCase.Case4Short) { CLA = 0x90, - INS = (byte)APDUInstructions.CHANGE_KEY, - Data = key_xor_crc_block_enc, - Le = 0x00 + INS = 0xC4, + Data = data }; + Console.WriteLine("cmd: {0}", ConvertToHexString(cmd.ToArray())); APDUResponse response = _Card.Transmit(cmd); @@ -592,33 +592,6 @@ namespace NFC.Mifare_DESFire 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); - } + } } diff --git a/NFC/NXP MIFARE DESFire/MIFARE_DESFire_V2.cs b/NFC/NXP MIFARE DESFire/MIFARE_DESFire_V2.cs new file mode 100644 index 0000000..6222665 --- /dev/null +++ b/NFC/NXP MIFARE DESFire/MIFARE_DESFire_V2.cs @@ -0,0 +1,575 @@ +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.Linq; + +namespace NFC.Mifare_DESFire +{ + public class MIFARE_DESFire_V2 + { + // 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_V2(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("-", " "); + } + + /// + /// 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; + } + + 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 two Arrays, Array A start at index 0 + /// + /// Array A + /// Array B + /// Copy of Data (a.Size + b.Size) + public byte[] Concatenate(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."); + } + + byte[] c = new byte[a.Length + b.Length]; + a.CopyTo(c, 0); + b.CopyTo(c, a.Length); + + return c; + } + + /// + /// 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 + private 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); + } + + /// + /// 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; + } + #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)); + + DES des = new DES(); + 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"); + } + #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); + } + #endregion + #endregion + } +} diff --git a/NFC_Test/AuthCrypto_Test.cs b/NFC_Test/AuthCrypto_Test.cs index 49fde2b..58e6b6f 100644 --- a/NFC_Test/AuthCrypto_Test.cs +++ b/NFC_Test/AuthCrypto_Test.cs @@ -126,5 +126,86 @@ namespace NFC_Test mifareDESFire.AuthenticateDES(0x00, mifareDESFire.GenerateDefaultKey(16)); } + + [Test] + public void ChangeKey() + { + ICard card = Substitute.For(); + MIFARE_DESFire mifareDESFire = new MIFARE_DESFire(card); + + mifareDESFire._SessionKey = new byte[] + { + 0xDC, 0xB0, 0x96, 0xC2, 0xA4, 0x0E, 0x78, 0xE0, 0xA0, 0xE4, 0x7A, 0x96, 0xF4, 0x2E, 0x62, 0xAE + }; + mifareDESFire._IV = new byte[] + { + 0x33, 0x45 , 0xAA , 0x95 , 0xF2 , 0xD9 , 0x56 , 0xCF + }; + + mifareDESFire.ChangeKeyDES(0x00, GenerateDefaultKey(16), GenerateDefaultKey(16)); + } + + [Test] + public void CRC() + { + byte[] data = StringToByteArrayFastest("c40045eeb8338ae8f49a032e85bb1114353010"); + + CRC32 crc32 = new CRC32(); + + byte[] crc = crc32.Calculate(data); + + MIFARE_DESFire dESFire = new MIFARE_DESFire(null); + + Console.WriteLine("data: {0}", dESFire.ConvertToHexString(data)); + Console.WriteLine("crc: {0}", dESFire.ConvertToHexString(crc)); + + byte[] data_crc = dESFire.concatenate(data, crc); + Console.WriteLine("data_crc: {0}", dESFire.ConvertToHexString(data_crc)); + } + + public static byte[] StringToByteArrayFastest(string hex) + { + if (hex.Length % 2 == 1) + throw new Exception("The binary key cannot have an odd number of digits"); + + byte[] arr = new byte[hex.Length >> 1]; + + for (int i = 0; i < hex.Length >> 1; ++i) + { + arr[i] = (byte)((GetHexVal(hex[i << 1]) << 4) + (GetHexVal(hex[(i << 1) + 1]))); + } + + return arr; + } + + public static 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)); + } + + [Test] + public void AES() + { + byte[] data = StringToByteArrayFastest("45eeb8338ae8f49a032e85bb111435301095c3894b0000000000000000000000"); + byte[] key = StringToByteArrayFastest("d99aca2b5b4de3a949fa2cf12b0eb673"); + byte[] iv = StringToByteArrayFastest("00000000000000000000000000000000"); + + MIFARE_DESFire dESFire = new MIFARE_DESFire(null); + + Console.WriteLine("data: {0}", dESFire.ConvertToHexString(data)); + Console.WriteLine("key: {0}", dESFire.ConvertToHexString(key)); + Console.WriteLine("iv: {0}", dESFire.ConvertToHexString(iv)); + + AES aes = new AES(); + + byte[] data_enc = aes.Encrypt(data, key, iv); + Console.WriteLine("data_enc: {0}", dESFire.ConvertToHexString(data_enc)); + } } } diff --git a/NFC_Test/MIFARE_DESFire_V2_Test.cs b/NFC_Test/MIFARE_DESFire_V2_Test.cs new file mode 100644 index 0000000..8ddbe05 --- /dev/null +++ b/NFC_Test/MIFARE_DESFire_V2_Test.cs @@ -0,0 +1,551 @@ +using NFC; +using NFC.ISO7816_4; +using NFC.Mifare_DESFire; +using NFC.NXP_MIFARE_DESFire.Exceptions; +using NSubstitute; +using NUnit.Framework; +using System; +using System.Net; + +namespace NFC_Test +{ + [TestFixture] + public class MIFARE_DESFire_V2_Test + { + #region Helper Methods + [Test] + public void GenerateDefaultKey() + { + uint i = 16; + + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + + byte[] data = desfire.GenerateEmptyKey(i); + + for(int e = 0; e < i; e++) + { + if(data[e] != 0x00) + { + Assert.Fail("Data is not 0x00"); + } + } + } + + [Test] + public void CheckAPDUResponse__NULL() + { + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + Assert.Throws( + delegate + { + desfire.CheckAPDUResponse(null); + }); + } + + [Test] + public void CheckAPDUResponse__UNKNOWN() + { + APDUResponse response = new APDUResponse() + { + SW1 = 0x00, + SW2 = 0x00 + }; + + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + Assert.Throws( + delegate + { + desfire.CheckAPDUResponse(response); + }); + } + + [Test] + public void CheckAPDUResponse__OPERATION_OK() + { + APDUResponse response = new APDUResponse() + { + SW1 = 0x91, + SW2 = 0x00 + }; + + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + + desfire.CheckAPDUResponse(response); + } + + [Test] + public void CheckAPDUResponse__NO_CHANGES() + { + APDUResponse response = new APDUResponse() + { + SW1 = 0x91, + SW2 = 0x0C + }; + + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + desfire.CheckAPDUResponse(response); + } + + [Test] + public void CheckAPDUResponse__ILLEGAL_COMMAND_CODE() + { + APDUResponse response = new APDUResponse() + { + SW1 = 0x91, + SW2 = 0x1C + }; + + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + + Assert.Throws( + delegate + { + desfire.CheckAPDUResponse(response); + }); + } + + [Test] + public void CheckAPDUResponse__INTEGRITY_ERROR() + { + APDUResponse response = new APDUResponse() + { + SW1 = 0x91, + SW2 = 0x1E + }; + + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + + Assert.Throws( + delegate + { + desfire.CheckAPDUResponse(response); + }); + } + + [Test] + public void CheckAPDUResponse__NO_SUCH_KEY() + { + APDUResponse response = new APDUResponse() + { + SW1 = 0x91, + SW2 = 0x40 + }; + + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + + Assert.Throws( + delegate + { + desfire.CheckAPDUResponse(response); + }); + } + + [Test] + public void CheckAPDUResponse__LENGTH_ERROR() + { + APDUResponse response = new APDUResponse() + { + SW1 = 0x91, + SW2 = 0x7E + }; + + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + + Assert.Throws( + delegate + { + desfire.CheckAPDUResponse(response); + }); + } + + [Test] + public void CheckAPDUResponse__PERMISSION_DENIED() + { + APDUResponse response = new APDUResponse() + { + SW1 = 0x91, + SW2 = 0x9D + }; + + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + + Assert.Throws( + delegate + { + desfire.CheckAPDUResponse(response); + }); + } + + + [Test] + public void CheckAPDUResponse__PARAMETER_ERROR() + { + APDUResponse response = new APDUResponse() + { + SW1 = 0x91, + SW2 = 0x9E + }; + + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + + Assert.Throws( + delegate + { + desfire.CheckAPDUResponse(response); + }); + } + + + [Test] + public void CheckAPDUResponse__AUTHENTICATION_DELAY() + { + APDUResponse response = new APDUResponse() + { + SW1 = 0x91, + SW2 = 0xAD + }; + + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + + Assert.Throws( + delegate + { + desfire.CheckAPDUResponse(response); + }); + } + + [Test] + public void CheckAPDUResponse__AUTHENTICATION_ERROR() + { + APDUResponse response = new APDUResponse() + { + SW1 = 0x91, + SW2 = 0xAE + }; + + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + + Assert.Throws( + delegate + { + desfire.CheckAPDUResponse(response); + }); + } + + [Test] + public void CheckAPDUResponse__ADDITIONAL_FRAME() + { + APDUResponse response = new APDUResponse() + { + SW1 = 0x91, + SW2 = 0xAF + }; + + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + + desfire.CheckAPDUResponse(response); + } + + [Test] + public void CheckAPDUResponse__BOUNDARY_ERROR() + { + APDUResponse response = new APDUResponse() + { + SW1 = 0x91, + SW2 = 0xBE + }; + + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + + Assert.Throws( + delegate + { + desfire.CheckAPDUResponse(response); + }); + } + + [Test] + public void CheckAPDUResponse__COMMAND_ABORTED() + { + APDUResponse response = new APDUResponse() + { + SW1 = 0x91, + SW2 = 0xCA + }; + + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + + Assert.Throws( + delegate + { + desfire.CheckAPDUResponse(response); + }); + } + + [Test] + public void CheckAPDUResponse__DUPLICATE_ERROR() + { + APDUResponse response = new APDUResponse() + { + SW1 = 0x91, + SW2 = 0xDE + }; + + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + + Assert.Throws( + delegate + { + desfire.CheckAPDUResponse(response); + }); + } + + [Test] + public void CheckAPDUResponse__FILE_NOT_FOUND() + { + APDUResponse response = new APDUResponse() + { + SW1 = 0x91, + SW2 = 0xF0 + }; + + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + + Assert.Throws( + delegate + { + desfire.CheckAPDUResponse(response); + }); + } + #endregion + + #region Crypto Operation + [Test] + public void ExtractLastBlock() + { + byte[] data = new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 + }; + + byte[] expected_lastblock = new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 + }; + + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + + byte[] lastblock = desfire.ExtractLastBlock(data, 8); + + Assert.AreEqual(expected_lastblock, lastblock); + } + + [Test] + public void ExtractLastBlock_WrongBlocksize() + { + byte[] data = new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 + }; + + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + + Assert.Throws( + delegate + { + byte[] lastblock = desfire.ExtractLastBlock(data, 7); + }); + } + + [Test] + public void ExtractLastBlock_Null() + { + byte[] data = null; + + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + + Assert.Throws( + delegate + { + byte[] lastblock = desfire.ExtractLastBlock(data, 7); + }); + } + + [Test] + public void RotateLeft() + { + byte[] data = new byte[] + { + 0x01, 0x02, 0x03, 0x04 + }; + + byte[] expected_data_left = new byte[] + { + 0x02, 0x03, 0x04, 0x01 + }; + + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + + byte[] data_left = desfire.RotateLeft(data); + + Assert.AreEqual(expected_data_left, data_left); + } + + [Test] + public void RotateLeft_Null() + { + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + + Assert.Throws( + delegate + { + byte[] lastblock = desfire.RotateLeft(null); + }); + } + + [Test] + public void RotateRight() + { + byte[] data = new byte[] + { + 0x01, 0x02, 0x03, 0x04 + }; + + byte[] expected_data_left = new byte[] + { + 0x04, 0x01, 0x02, 0x03 + }; + + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + + byte[] data_left = desfire.RotateRight(data); + + Assert.AreEqual(expected_data_left, data_left); + } + + [Test] + public void RotateRight_Null() + { + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + + Assert.Throws( + delegate + { + byte[] lastblock = desfire.RotateRight(null); + }); + } + + [Test] + public void Concatenate() + { + byte[] data_a = new byte[] + { + 0x01, 0x02, 0x03, 0x04 + }; + + byte[] data_b = new byte[] + { + 0x05, 0x06, 0x07, 0x08 + }; + + byte[] expected_data_c = new byte[] + { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 + }; + + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + + byte[] data_c = desfire.Concatenate(data_a, data_b); + + Assert.AreEqual(expected_data_c, data_c); + } + + [Test] + public void Concatenate_Null() + { + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + + Assert.Throws( + delegate + { + byte[] lastblock = desfire.Concatenate(null, null); + }); + } + + [Test] + public void XOR() + { + byte[] data_a = new byte[] + { + 0x00, 0xF0, 0x00, 0xF0 + }; + + byte[] data_b = new byte[] + { + 0x0F, 0x00, 0x0F, 0x00 + }; + + byte[] expected_data_c = new byte[] + { + 0x0F, 0xF0, 0x0F, 0xF0 + }; + + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + + byte[] data_c = desfire.XOR(data_a, data_b); + + Assert.AreEqual(expected_data_c, data_c); + } + + [Test] + public void XOR_null() + { + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null); + + Assert.Throws( + delegate + { + byte[] lastblock = desfire.XOR(null, null); + }); + } + #endregion + + #region DESFire Commands + [Test] + public void AuthenticateISO_DES() + { + ICard card = Substitute.For(); + + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(card); + + APDUResponse response_challenge_request = new APDUResponse() + { + SW1 = 0x91, + SW2 = 0xAF, + Body = desfire.ConvertFromHexString("5D994CE085F24089") + }; + + APDUResponse response_challenge_response = new APDUResponse() + { + SW1 = 0x91, + SW2 = 0x00, + Body = desfire.ConvertFromHexString("913C6DED84221C41") + }; + + byte[] rndA = desfire.ConvertFromHexString("849B36C5F8BF4A09"); + byte[] key = desfire.ConvertFromHexString("00000000000000000000000000000000"); + + card.Transmit(Arg.Is(x => x.INS == 0x1A)).Returns(response_challenge_request); + card.Transmit(Arg.Is(x => x.INS == 0xAF)).Returns(response_challenge_response); + + desfire.AuthenticateISO_DES(0x00, key, rndA); + + byte[] expected_sessionkey = desfire.ConvertFromHexString("849B36C54FD1B759849B36C54FD1B759"); + byte[] expected_iv = desfire.GenerateEmptyKey(8); + + Assert.AreEqual(expected_sessionkey, desfire._SessionKey); + Assert.AreEqual(expected_iv, desfire._IV); + } + #endregion + } +} diff --git a/NFC_Test/NamespaceSetUp.cs b/NFC_Test/NamespaceSetUp.cs new file mode 100644 index 0000000..4cb85ab --- /dev/null +++ b/NFC_Test/NamespaceSetUp.cs @@ -0,0 +1,18 @@ +using log4net; +using log4net.Config; +using NUnit.Framework; +using System; + +namespace NFC_Test +{ + [SetUpFixture] + public class NamespaceSetUp + { + private static readonly ILog log = LogManager.GetLogger(typeof(NamespaceSetUp)); + [OneTimeSetUp] + public void OneTimeSetUp() + { + BasicConfigurator.Configure(); + } + } +} diff --git a/NFC_Test/OTA.cs b/NFC_Test/OTA.cs new file mode 100644 index 0000000..8ff2752 --- /dev/null +++ b/NFC_Test/OTA.cs @@ -0,0 +1,60 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Text; +using NFC; +using NFC.Readers.PCSC; +using System.Threading; +using NFC.Mifare_DESFire; +using NFC.Mifare_DESFire.Enums; +using NFC.ISO7816_4; +using PCSC.Iso7816; +using log4net.Config; + +namespace NFC_Test +{ + [TestFixture, Explicit] + public class OTA + { + private string _ReaderID = "ACS ACR122U PICC Interface 0"; + + [Test] + public void Init() + { + IHardware hardware = new Hardware(); + IReader reader = hardware.OpenReader(_ReaderID); + + bool transmit_successfully = false; + + ReaderEventHandler handler = (sender, card) => + { + card.Connect(); + + MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(card); + + desfire.AuthenticateISO_DES(0x00, desfire.GenerateEmptyKey(16)); + desfire.Format(); + + desfire.AuthenticateISO_DES(0x00, desfire.GenerateEmptyKey(16)); + + byte keySetting1 = desfire.GenerateKeySetting1(ChangeApplicationKey.MASTERKEY, ChangeMasterKeySettings.WITHMASTERKEY, CreateDeleteFile.NOKEY, FileDirectoryAccess.NOKEY, ChangeMasterKey.CHANGEABLE); + byte keySetting2 = desfire.GenerateKeySetting2(CryptoOperationsType.AES, FileIdentifies.NOTUSED, 0x03); + desfire.CreateApplication(0xAAFFEE, keySetting1, keySetting2); + + desfire.SelectApplication(0xAAFFEE); + + transmit_successfully = true; + + card.Disconnect(); + }; + + reader.CardDiscovered += handler; + reader.Start(); + + Assert.AreEqual(true, transmit_successfully); + + reader.Stop(); + reader.CardDiscovered -= handler; + } + } +} diff --git a/NFC_Test/REAL_Windows.cs b/NFC_Test/REAL_Windows.cs index e6118b8..0e35f12 100644 --- a/NFC_Test/REAL_Windows.cs +++ b/NFC_Test/REAL_Windows.cs @@ -8,6 +8,7 @@ using System.Threading; using NFC.Mifare_DESFire; using NFC.Mifare_DESFire.Enums; using NFC.ISO7816_4; +using PCSC.Iso7816; namespace NFC_Test { @@ -140,41 +141,41 @@ namespace NFC_Test [TestCase("ACS ACR122U PICC Interface 0", (UInt32)0xAAFFEE)] public void CreateApplication(string readerID, UInt32 applicationID) { - IHardware hardware = new Hardware(); - IReader reader = hardware.OpenReader(readerID); + //IHardware hardware = new Hardware(); + //IReader reader = hardware.OpenReader(readerID); - bool transmit_successfully = false; + //bool transmit_successfully = false; - ReaderEventHandler handler = (sender, card) => - { - card.Connect(); + //ReaderEventHandler handler = (sender, card) => + //{ + // card.Connect(); - MIFARE_DESFire desfire = new MIFARE_DESFire(card); + // MIFARE_DESFire desfire = new MIFARE_DESFire(card); - byte keysetting1 = desfire.GenerateKeySetting1(ChangeApplicationKey.SAMEKEY, ChangeMasterKeySettings.WITHMASTERKEY, CreateDeleteFile.NOKEY, FileDirectoryAccess.NOKEY, ChangeMasterKey.CHANGEABLE); - byte keysetting2 = desfire.GenerateKeySetting2(CryptoOperationsType.TDES, FileIdentifies.NOTUSED, 0x03); + // byte keysetting1 = desfire.GenerateKeySetting1(ChangeApplicationKey.MASTERKEY, ChangeMasterKeySettings.WITHMASTERKEY, CreateDeleteFile.NOKEY, FileDirectoryAccess.NOKEY, ChangeMasterKey.CHANGEABLE); + // byte keysetting2 = desfire.GenerateKeySetting2(CryptoOperationsType.TDES, FileIdentifies.NOTUSED, 0x03); - APDUCommand cmd = desfire.CreateApplication(applicationID, keysetting1, keysetting2); + // APDUCommand cmd = desfire.CreateApplication(applicationID, keysetting1, keysetting2); - Console.WriteLine(cmd.ToArray()); + // Console.WriteLine(cmd.ToArray()); - APDUResponse response = card.Transmit(cmd); + // APDUResponse response = card.Transmit(cmd); - if (response.StatusWord == NFC.Mifare_DESFire.APDUStatusWords.OK) - { - transmit_successfully = true; - } + // if (response.StatusWord == NFC.Mifare_DESFire.APDUStatusWords.OK) + // { + // transmit_successfully = true; + // } - card.Disconnect(); - }; + // card.Disconnect(); + //}; - reader.CardDiscovered += handler; - reader.Start(); + //reader.CardDiscovered += handler; + //reader.Start(); - Assert.AreEqual(true, transmit_successfully); + //Assert.AreEqual(true, transmit_successfully); - reader.Stop(); - reader.CardDiscovered -= handler; + //reader.Stop(); + //reader.CardDiscovered -= handler; } [TestCase("ACS ACR122U PICC Interface 0", (UInt32)0xAAFFEE)] @@ -271,7 +272,48 @@ namespace NFC_Test desfire.SelectApplication(0xAAFFEE); desfire.AuthenticateDES(0x00, GenerateDefaultKey(8)); - desfire.ChangeKeyDES(0x01, GenerateDefaultKey(8), new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF }); + desfire.ChangeKeyDES(0x00, GenerateDefaultKey(8), new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF }); + + transmit_successfully = true; + + card.Disconnect(); + }; + + reader.CardDiscovered += handler; + reader.Start(); + + Assert.AreEqual(true, transmit_successfully); + + reader.Stop(); + reader.CardDiscovered -= handler; + } + + [TestCase("ACS ACR122U PICC Interface 0")] + public void ChangeKey_Test(string readerID) + { + IHardware hardware = new Hardware(); + IReader reader = hardware.OpenReader(readerID); + + bool transmit_successfully = false; + + ReaderEventHandler handler = (sender, card) => + { + card.Connect(); + + MIFARE_DESFire desfire = new MIFARE_DESFire(card); + + desfire.SelectApplication(0xAAFFEE); + desfire.AuthenticateDES(0x00, GenerateDefaultKey(8)); + + APDUCommand cmd = new APDUCommand(IsoCase.Case4Short) + { + CLA = 0x90, + INS = 0xC4, + Data = GenerateDefaultKey(1 + 16 + 4 + 4) + // KEY_NO, KEY, CRC32, PADDING + }; + + card.Transmit(cmd); transmit_successfully = true;