From 3c43f83a89c604464e157c30d1cd5993f8bff2fe Mon Sep 17 00:00:00 2001 From: TheJoKlLa Date: Tue, 30 Mar 2021 22:51:02 +0200 Subject: [PATCH] Added: Files from Borepin Project --- NFC/APDUCommand.cs | 23 + NFC/APDUResponse.cs | 9 + .../Enums/ChangeApplicationKey.cs | 21 + .../Enums/ChangeMasterKey.cs | 18 + .../Enums/ChangeMasterKeySettings.cs | 19 + .../Enums/CreateDeleteFile.cs | 18 + .../Enums/CryptoOperationsType.cs | 12 + .../Enums/FileAccessRights.cs | 8 + .../Enums/FileCommunication.cs | 20 + .../Enums/FileDirectoryAccess.cs | 18 + .../Enums/FileIdentifies.cs | 11 + .../NXP MIFARE DESFire/Enums/FileTypes.cs | 30 + .../AuthenticationDelayException.cs | 26 + .../AuthenticationErrorException.cs | 26 + .../AuthenticationMissingException.cs | 26 + .../Exceptions/BoundaryErrorException.cs | 26 + .../Exceptions/CommandAbortedException.cs | 26 + .../Exceptions/DuplicateErrorException.cs | 26 + .../Exceptions/FileNotFoundException.cs | 26 + .../Exceptions/IllegalCommandCodeException.cs | 26 + .../Exceptions/IntegrityErrorException.cs | 26 + .../Exceptions/LengthErrorException.cs | 26 + .../Exceptions/NoSuchKeyException.cs | 26 + .../Exceptions/ParameterErrorException.cs | 26 + .../Exceptions/PermissionDeniedException.cs | 26 + .../NXP MIFARE DESFire/NXP_MIFARE_DESFire.cs | 1089 +++++++++++++++++ NFC/Exceptions/APDUException.cs | 9 + NFC/Exceptions/CardUnavailableException.cs | 9 + NFC/Exceptions/ReaderUnavailableException.cs | 9 + NFC/Helper/Crypto/CRC/CRC16.cs | 44 + NFC/Helper/Crypto/CRC/CRC32.cs | 44 + NFC/Helper/Crypto/Cipher/AES.cs | 48 + NFC/Helper/Crypto/Cipher/TDES.cs | 49 + NFC/Helper/Crypto/Cipher/TDES_2K.cs | 48 + NFC/Helper/Crypto/Cipher/TDES_3K.cs | 48 + NFC/Helper/Crypto/CipherKey.cs | 182 +++ NFC/Helper/Crypto/CipherType.cs | 25 + NFC/Helper/Crypto/ICipher.cs | 32 + NFC/Helper/HexConverter.cs | 51 + NFC/Interfaces/ICard.cs | 22 + NFC/Interfaces/IHardware.cs | 26 + NFC/Interfaces/IReader.cs | 24 + NFC/IsoCase.cs | 8 + NFC/NFC.csproj | 5 + 44 files changed, 2317 insertions(+) create mode 100644 NFC/APDUCommand.cs create mode 100644 NFC/APDUResponse.cs create mode 100644 NFC/Cards/NXP MIFARE DESFire/Enums/ChangeApplicationKey.cs create mode 100644 NFC/Cards/NXP MIFARE DESFire/Enums/ChangeMasterKey.cs create mode 100644 NFC/Cards/NXP MIFARE DESFire/Enums/ChangeMasterKeySettings.cs create mode 100644 NFC/Cards/NXP MIFARE DESFire/Enums/CreateDeleteFile.cs create mode 100644 NFC/Cards/NXP MIFARE DESFire/Enums/CryptoOperationsType.cs create mode 100644 NFC/Cards/NXP MIFARE DESFire/Enums/FileAccessRights.cs create mode 100644 NFC/Cards/NXP MIFARE DESFire/Enums/FileCommunication.cs create mode 100644 NFC/Cards/NXP MIFARE DESFire/Enums/FileDirectoryAccess.cs create mode 100644 NFC/Cards/NXP MIFARE DESFire/Enums/FileIdentifies.cs create mode 100644 NFC/Cards/NXP MIFARE DESFire/Enums/FileTypes.cs create mode 100644 NFC/Cards/NXP MIFARE DESFire/Exceptions/AuthenticationDelayException.cs create mode 100644 NFC/Cards/NXP MIFARE DESFire/Exceptions/AuthenticationErrorException.cs create mode 100644 NFC/Cards/NXP MIFARE DESFire/Exceptions/AuthenticationMissingException.cs create mode 100644 NFC/Cards/NXP MIFARE DESFire/Exceptions/BoundaryErrorException.cs create mode 100644 NFC/Cards/NXP MIFARE DESFire/Exceptions/CommandAbortedException.cs create mode 100644 NFC/Cards/NXP MIFARE DESFire/Exceptions/DuplicateErrorException.cs create mode 100644 NFC/Cards/NXP MIFARE DESFire/Exceptions/FileNotFoundException.cs create mode 100644 NFC/Cards/NXP MIFARE DESFire/Exceptions/IllegalCommandCodeException.cs create mode 100644 NFC/Cards/NXP MIFARE DESFire/Exceptions/IntegrityErrorException.cs create mode 100644 NFC/Cards/NXP MIFARE DESFire/Exceptions/LengthErrorException.cs create mode 100644 NFC/Cards/NXP MIFARE DESFire/Exceptions/NoSuchKeyException.cs create mode 100644 NFC/Cards/NXP MIFARE DESFire/Exceptions/ParameterErrorException.cs create mode 100644 NFC/Cards/NXP MIFARE DESFire/Exceptions/PermissionDeniedException.cs create mode 100644 NFC/Cards/NXP MIFARE DESFire/NXP_MIFARE_DESFire.cs create mode 100644 NFC/Exceptions/APDUException.cs create mode 100644 NFC/Exceptions/CardUnavailableException.cs create mode 100644 NFC/Exceptions/ReaderUnavailableException.cs create mode 100644 NFC/Helper/Crypto/CRC/CRC16.cs create mode 100644 NFC/Helper/Crypto/CRC/CRC32.cs create mode 100644 NFC/Helper/Crypto/Cipher/AES.cs create mode 100644 NFC/Helper/Crypto/Cipher/TDES.cs create mode 100644 NFC/Helper/Crypto/Cipher/TDES_2K.cs create mode 100644 NFC/Helper/Crypto/Cipher/TDES_3K.cs create mode 100644 NFC/Helper/Crypto/CipherKey.cs create mode 100644 NFC/Helper/Crypto/CipherType.cs create mode 100644 NFC/Helper/Crypto/ICipher.cs create mode 100644 NFC/Helper/HexConverter.cs create mode 100644 NFC/Interfaces/ICard.cs create mode 100644 NFC/Interfaces/IHardware.cs create mode 100644 NFC/Interfaces/IReader.cs create mode 100644 NFC/IsoCase.cs diff --git a/NFC/APDUCommand.cs b/NFC/APDUCommand.cs new file mode 100644 index 0000000..1630965 --- /dev/null +++ b/NFC/APDUCommand.cs @@ -0,0 +1,23 @@ +using System; + +namespace NFC +{ + public class APDUCommand + { + private IsoCase case4Short; + + public APDUCommand(IsoCase case4Short) + { + this.case4Short = case4Short; + } + + public int CLA { get; internal set; } + public byte INS { get; internal set; } + public byte[] Data { get; internal set; } + + internal byte[] ToArray() + { + throw new NotImplementedException(); + } + } +} diff --git a/NFC/APDUResponse.cs b/NFC/APDUResponse.cs new file mode 100644 index 0000000..8fc8bac --- /dev/null +++ b/NFC/APDUResponse.cs @@ -0,0 +1,9 @@ +namespace NFC +{ + public class APDUResponse + { + public byte SW1 { get; internal set; } + public byte SW2 { get; internal set; } + public byte[] Body { get; internal set; } + } +} diff --git a/NFC/Cards/NXP MIFARE DESFire/Enums/ChangeApplicationKey.cs b/NFC/Cards/NXP MIFARE DESFire/Enums/ChangeApplicationKey.cs new file mode 100644 index 0000000..8ea92ed --- /dev/null +++ b/NFC/Cards/NXP MIFARE DESFire/Enums/ChangeApplicationKey.cs @@ -0,0 +1,21 @@ +namespace NFC.Cards.NXP_MIFARE_DESFire.Enums +{ + /// + /// hold the Access Rights for changing application keys (Change Key command) + /// + public enum ChangeApplicationKey : byte + { + /// + /// Application master key authentication is necessary to change any key (default) + /// + MASTERKEY = 0x00, + /// + /// Authentication with the key to be changed (same Key#) is necessary to change a key + /// + SAMEKEY = 0x0E, + /// + /// All keys (except application master key, see Bit 0) within this application are frozen + /// + ALLKEYS = 0x0F + } +} diff --git a/NFC/Cards/NXP MIFARE DESFire/Enums/ChangeMasterKey.cs b/NFC/Cards/NXP MIFARE DESFire/Enums/ChangeMasterKey.cs new file mode 100644 index 0000000..6a96abd --- /dev/null +++ b/NFC/Cards/NXP MIFARE DESFire/Enums/ChangeMasterKey.cs @@ -0,0 +1,18 @@ +namespace NFC.Cards.NXP_MIFARE_DESFire.Enums +{ + /// + /// codes whether the application master key is changeable + /// + public enum ChangeMasterKey : byte + { + /// + /// Application master key is not changeable anymore (frozen) + /// + FROZEN = 0x00, + + /// + /// Application master key is changeable (authentication with the current application master key necessary, default) + /// + CHANGEABLE = 0x01, + } +} diff --git a/NFC/Cards/NXP MIFARE DESFire/Enums/ChangeMasterKeySettings.cs b/NFC/Cards/NXP MIFARE DESFire/Enums/ChangeMasterKeySettings.cs new file mode 100644 index 0000000..93c198f --- /dev/null +++ b/NFC/Cards/NXP MIFARE DESFire/Enums/ChangeMasterKeySettings.cs @@ -0,0 +1,19 @@ +namespace NFC.Cards.NXP_MIFARE_DESFire.Enums +{ + /// + /// codes whether a change of the application master key settings is allowed + /// + public enum ChangeMasterKeySettings : byte + { + + /// + /// configuration not changeable anymore (frozen) + /// + FROZEN = 0x00, + + /// + /// this configuration is changeable if authenticated with the application master key (default) + /// + WITHMASTERKEY = 0x08 + } +} diff --git a/NFC/Cards/NXP MIFARE DESFire/Enums/CreateDeleteFile.cs b/NFC/Cards/NXP MIFARE DESFire/Enums/CreateDeleteFile.cs new file mode 100644 index 0000000..22f236f --- /dev/null +++ b/NFC/Cards/NXP MIFARE DESFire/Enums/CreateDeleteFile.cs @@ -0,0 +1,18 @@ +namespace NFC.Cards.NXP_MIFARE_DESFire.Enums +{ + /// + /// codes whether application master key authentication is needed before “Create File” / “Delete File” + /// + public enum CreateDeleteFile : byte + { + /// + /// “Create File”/ “Delete File”is permitted only with application master key authentication + /// + ONLYMASTERKEY = 0x00, + + /// + /// “Create File”/ “Delete File”is permitted also without application master key authentication (default) + /// + NOKEY = 0x04, + } +} diff --git a/NFC/Cards/NXP MIFARE DESFire/Enums/CryptoOperationsType.cs b/NFC/Cards/NXP MIFARE DESFire/Enums/CryptoOperationsType.cs new file mode 100644 index 0000000..637fa28 --- /dev/null +++ b/NFC/Cards/NXP MIFARE DESFire/Enums/CryptoOperationsType.cs @@ -0,0 +1,12 @@ +namespace NFC.Cards.NXP_MIFARE_DESFire.Enums +{ + /// + /// Crypto method of the application + /// + public enum CryptoOperationsType : byte + { + TDES = 0x00, + TKTDES = 0x40, + AES = 0x80, + } +} \ No newline at end of file diff --git a/NFC/Cards/NXP MIFARE DESFire/Enums/FileAccessRights.cs b/NFC/Cards/NXP MIFARE DESFire/Enums/FileAccessRights.cs new file mode 100644 index 0000000..f91ab0d --- /dev/null +++ b/NFC/Cards/NXP MIFARE DESFire/Enums/FileAccessRights.cs @@ -0,0 +1,8 @@ +namespace NFC.Cards.NXP_MIFARE_DESFire.Enums +{ + public enum FileAccessRights : byte + { + FREE = 0x0E, + NEVER = 0x0F + } +} diff --git a/NFC/Cards/NXP MIFARE DESFire/Enums/FileCommunication.cs b/NFC/Cards/NXP MIFARE DESFire/Enums/FileCommunication.cs new file mode 100644 index 0000000..9cb17b7 --- /dev/null +++ b/NFC/Cards/NXP MIFARE DESFire/Enums/FileCommunication.cs @@ -0,0 +1,20 @@ +namespace NFC.Cards.NXP_MIFARE_DESFire.Enums +{ + public enum FileCommunication : byte + { + /// + /// "Plain communication" + /// + PLAIN = 0x00, + + /// + /// Plain communication secured by DES/3DES MACing + /// + MAC = 0x01, + + /// + /// Fully DES/3DES enciphered communication + /// + ENCRYPT = 0x03 + } +} diff --git a/NFC/Cards/NXP MIFARE DESFire/Enums/FileDirectoryAccess.cs b/NFC/Cards/NXP MIFARE DESFire/Enums/FileDirectoryAccess.cs new file mode 100644 index 0000000..70ae6be --- /dev/null +++ b/NFC/Cards/NXP MIFARE DESFire/Enums/FileDirectoryAccess.cs @@ -0,0 +1,18 @@ +namespace NFC.Cards.NXP_MIFARE_DESFire.Enums +{ + /// + /// codes whether application master key authentication is needed for file directory access + /// + public enum FileDirectoryAccess : byte + { + /// + /// Successful application master key authentication is required for executing the “Get FID List”, “Get File Settings”and “Get Key Settings”commands + /// + ONLYMASTERKEY = 0x00, + + /// + /// “Get FID List”, “Get File Settings” and “Get Key Settings” commands succeed independentlyof a preceding application master key authentication (default) + /// + NOKEY = 0x02, + } +} diff --git a/NFC/Cards/NXP MIFARE DESFire/Enums/FileIdentifies.cs b/NFC/Cards/NXP MIFARE DESFire/Enums/FileIdentifies.cs new file mode 100644 index 0000000..31c69f7 --- /dev/null +++ b/NFC/Cards/NXP MIFARE DESFire/Enums/FileIdentifies.cs @@ -0,0 +1,11 @@ +namespace NFC.Cards.NXP_MIFARE_DESFire.Enums +{ + /// + /// Indicates use of 2 byte ISO/IEC 7816-4 File Identifies for files within the Application + /// + public enum FileIdentifies : byte + { + NOTUSED = 0x00, + USED = 0x20 + } +} diff --git a/NFC/Cards/NXP MIFARE DESFire/Enums/FileTypes.cs b/NFC/Cards/NXP MIFARE DESFire/Enums/FileTypes.cs new file mode 100644 index 0000000..a858fdf --- /dev/null +++ b/NFC/Cards/NXP MIFARE DESFire/Enums/FileTypes.cs @@ -0,0 +1,30 @@ +namespace NFC.Cards.NXP_MIFARE_DESFire.Enums +{ + enum FileTypes : byte + { + /// + /// Standard Data File + /// + STANDARD = 0x00, + + /// + /// Backup Data Files + /// + BACKUP = 0x01, + + /// + /// Value Files with Backup + /// + VALUE = 0x02, + + /// + /// Linear Record Files with Backup + /// + LINEARRECORD = 0x03, + + /// + /// Cyclic Record Files with Backup + /// + CYCLICRECORD = 0x04 + } +} diff --git a/NFC/Cards/NXP MIFARE DESFire/Exceptions/AuthenticationDelayException.cs b/NFC/Cards/NXP MIFARE DESFire/Exceptions/AuthenticationDelayException.cs new file mode 100644 index 0000000..f463363 --- /dev/null +++ b/NFC/Cards/NXP MIFARE DESFire/Exceptions/AuthenticationDelayException.cs @@ -0,0 +1,26 @@ +using System; + +namespace NFC.Cards.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/Cards/NXP MIFARE DESFire/Exceptions/AuthenticationErrorException.cs b/NFC/Cards/NXP MIFARE DESFire/Exceptions/AuthenticationErrorException.cs new file mode 100644 index 0000000..8999755 --- /dev/null +++ b/NFC/Cards/NXP MIFARE DESFire/Exceptions/AuthenticationErrorException.cs @@ -0,0 +1,26 @@ +using System; + +namespace NFC.Cards.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/Cards/NXP MIFARE DESFire/Exceptions/AuthenticationMissingException.cs b/NFC/Cards/NXP MIFARE DESFire/Exceptions/AuthenticationMissingException.cs new file mode 100644 index 0000000..d6a789b --- /dev/null +++ b/NFC/Cards/NXP MIFARE DESFire/Exceptions/AuthenticationMissingException.cs @@ -0,0 +1,26 @@ +using System; + +namespace NFC.Cards.NXP_MIFARE_DESFire.Exceptions +{ + /// + /// Current authentication status does not allow there- quested command. + /// 0x91AE + /// + public class AuthenticationMissingException : Exception + { + public AuthenticationMissingException() + { + + } + + public AuthenticationMissingException(string message) : base(message) + { + + } + + public AuthenticationMissingException(string message, Exception inner) : base(message, inner) + { + + } + } +} diff --git a/NFC/Cards/NXP MIFARE DESFire/Exceptions/BoundaryErrorException.cs b/NFC/Cards/NXP MIFARE DESFire/Exceptions/BoundaryErrorException.cs new file mode 100644 index 0000000..4c0ed1f --- /dev/null +++ b/NFC/Cards/NXP MIFARE DESFire/Exceptions/BoundaryErrorException.cs @@ -0,0 +1,26 @@ +using System; + +namespace NFC.Cards.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/Cards/NXP MIFARE DESFire/Exceptions/CommandAbortedException.cs b/NFC/Cards/NXP MIFARE DESFire/Exceptions/CommandAbortedException.cs new file mode 100644 index 0000000..8d55f3b --- /dev/null +++ b/NFC/Cards/NXP MIFARE DESFire/Exceptions/CommandAbortedException.cs @@ -0,0 +1,26 @@ +using System; + +namespace NFC.Cards.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/Cards/NXP MIFARE DESFire/Exceptions/DuplicateErrorException.cs b/NFC/Cards/NXP MIFARE DESFire/Exceptions/DuplicateErrorException.cs new file mode 100644 index 0000000..7785ae8 --- /dev/null +++ b/NFC/Cards/NXP MIFARE DESFire/Exceptions/DuplicateErrorException.cs @@ -0,0 +1,26 @@ +using System; + +namespace NFC.Cards.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/Cards/NXP MIFARE DESFire/Exceptions/FileNotFoundException.cs b/NFC/Cards/NXP MIFARE DESFire/Exceptions/FileNotFoundException.cs new file mode 100644 index 0000000..bf5c861 --- /dev/null +++ b/NFC/Cards/NXP MIFARE DESFire/Exceptions/FileNotFoundException.cs @@ -0,0 +1,26 @@ +using System; + +namespace NFC.Cards.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/Cards/NXP MIFARE DESFire/Exceptions/IllegalCommandCodeException.cs b/NFC/Cards/NXP MIFARE DESFire/Exceptions/IllegalCommandCodeException.cs new file mode 100644 index 0000000..bbdbad0 --- /dev/null +++ b/NFC/Cards/NXP MIFARE DESFire/Exceptions/IllegalCommandCodeException.cs @@ -0,0 +1,26 @@ +using System; + +namespace NFC.Cards.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/Cards/NXP MIFARE DESFire/Exceptions/IntegrityErrorException.cs b/NFC/Cards/NXP MIFARE DESFire/Exceptions/IntegrityErrorException.cs new file mode 100644 index 0000000..316bdd2 --- /dev/null +++ b/NFC/Cards/NXP MIFARE DESFire/Exceptions/IntegrityErrorException.cs @@ -0,0 +1,26 @@ +using System; + +namespace NFC.Cards.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/Cards/NXP MIFARE DESFire/Exceptions/LengthErrorException.cs b/NFC/Cards/NXP MIFARE DESFire/Exceptions/LengthErrorException.cs new file mode 100644 index 0000000..4c2af58 --- /dev/null +++ b/NFC/Cards/NXP MIFARE DESFire/Exceptions/LengthErrorException.cs @@ -0,0 +1,26 @@ +using System; + +namespace NFC.Cards.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/Cards/NXP MIFARE DESFire/Exceptions/NoSuchKeyException.cs b/NFC/Cards/NXP MIFARE DESFire/Exceptions/NoSuchKeyException.cs new file mode 100644 index 0000000..6698459 --- /dev/null +++ b/NFC/Cards/NXP MIFARE DESFire/Exceptions/NoSuchKeyException.cs @@ -0,0 +1,26 @@ +using System; + +namespace NFC.Cards.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/Cards/NXP MIFARE DESFire/Exceptions/ParameterErrorException.cs b/NFC/Cards/NXP MIFARE DESFire/Exceptions/ParameterErrorException.cs new file mode 100644 index 0000000..e171c47 --- /dev/null +++ b/NFC/Cards/NXP MIFARE DESFire/Exceptions/ParameterErrorException.cs @@ -0,0 +1,26 @@ +using System; + +namespace NFC.Cards.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/Cards/NXP MIFARE DESFire/Exceptions/PermissionDeniedException.cs b/NFC/Cards/NXP MIFARE DESFire/Exceptions/PermissionDeniedException.cs new file mode 100644 index 0000000..b6d2b12 --- /dev/null +++ b/NFC/Cards/NXP MIFARE DESFire/Exceptions/PermissionDeniedException.cs @@ -0,0 +1,26 @@ +using System; + +namespace NFC.Cards.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/Cards/NXP MIFARE DESFire/NXP_MIFARE_DESFire.cs b/NFC/Cards/NXP MIFARE DESFire/NXP_MIFARE_DESFire.cs new file mode 100644 index 0000000..2a15df3 --- /dev/null +++ b/NFC/Cards/NXP MIFARE DESFire/NXP_MIFARE_DESFire.cs @@ -0,0 +1,1089 @@ +using log4net; +using NFC.Cards.NXP_MIFARE_DESFire.Enums; +using NFC.Cards.NXP_MIFARE_DESFire.Exceptions; +using NFC.Helper; +using NFC.Helper.Crypto.Cipher; +using NFC.Helper.Crypto.CRC; +using NFC.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace NFC.Cards.NXP_MIFARE_DESFire +{ + public class NXP_MIFARE_DESFire + { + // Docs https://hackmd.io/qATu8uYdRnOC40aFrB9afg + + #region Log + private static readonly ILog _Log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + #endregion + + #region Contructors + /// + /// Construct MIFRARE_DESFire Object with ICard Interface + /// + /// Implementation of ICard, only transmit is used + public NXP_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 = 0x5A, + Data = new byte[] + { + id_byte[0], + id_byte[1], + id_byte[2] + } + }; + _Log.Debug(cmd_SelectApplication.ToString()); + HexConverter.ConvertToHexString(cmd_SelectApplication.ToArray()); + APDUResponse response = _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 = 0xCD, + 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 = 0x3D, + 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 = 0xBD, + 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 + } +} diff --git a/NFC/Exceptions/APDUException.cs b/NFC/Exceptions/APDUException.cs new file mode 100644 index 0000000..85a1e08 --- /dev/null +++ b/NFC/Exceptions/APDUException.cs @@ -0,0 +1,9 @@ +using System; + +namespace NFC.Exceptions +{ + public class APDUException : Exception + { + public readonly byte ResponseCode; + } +} diff --git a/NFC/Exceptions/CardUnavailableException.cs b/NFC/Exceptions/CardUnavailableException.cs new file mode 100644 index 0000000..ec9dbd8 --- /dev/null +++ b/NFC/Exceptions/CardUnavailableException.cs @@ -0,0 +1,9 @@ +using System; + +namespace NFC.Exceptions +{ + public class CardUnavailableException : Exception + { + + } +} diff --git a/NFC/Exceptions/ReaderUnavailableException.cs b/NFC/Exceptions/ReaderUnavailableException.cs new file mode 100644 index 0000000..a685f0b --- /dev/null +++ b/NFC/Exceptions/ReaderUnavailableException.cs @@ -0,0 +1,9 @@ +using System; + +namespace NFC.Exceptions +{ + public class ReaderUnavailableException : Exception + { + + } +} diff --git a/NFC/Helper/Crypto/CRC/CRC16.cs b/NFC/Helper/Crypto/CRC/CRC16.cs new file mode 100644 index 0000000..5a46de5 --- /dev/null +++ b/NFC/Helper/Crypto/CRC/CRC16.cs @@ -0,0 +1,44 @@ +using System; + +namespace NFC.Helper.Crypto.CRC +{ + /// + /// CRC16 for DESFire Card + /// + public class CRC16 + { + public UInt16 Polynomial { get; } = 0x8408; + + public UInt16 InitValue { get; } = 0x6363; + + 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 ^= Polynomial; + } + } + } + return crc16; + } + + public byte[] Calculate(params byte[][] data) + { + UInt16 crc16 = InitValue; + + foreach(byte[] d in data) + { + crc16 = Calculate(d, crc16); + } + + return BitConverter.GetBytes(crc16); + } + } +} diff --git a/NFC/Helper/Crypto/CRC/CRC32.cs b/NFC/Helper/Crypto/CRC/CRC32.cs new file mode 100644 index 0000000..ec34a44 --- /dev/null +++ b/NFC/Helper/Crypto/CRC/CRC32.cs @@ -0,0 +1,44 @@ +using System; + +namespace NFC.Helper.Crypto.CRC +{ + /// + /// CRC32 for DESFire Card + /// + public class CRC32 + { + public UInt32 Polynomial { get; } = 0xEDB88320; + + public UInt32 InitValue { get; } = 0xFFFFFFFF; + + public UInt32 Calculate(byte[] data, UInt32 crc32) + { + for (int i = 0; i < data.Length; i++) + { + crc32 ^= data[i]; + for (int b = 0; b < 8; b++) + { + bool b_Bit = (crc32 & 0x01) > 0; + crc32 >>= 1; + if (b_Bit) + { + crc32 ^= Polynomial; + } + } + } + return crc32; + } + + public byte[] Calculate(params byte[][] data) + { + UInt32 crc32 = InitValue; + + foreach(byte[] d in data) + { + crc32 = Calculate(d, crc32); + } + + return BitConverter.GetBytes(crc32); + } + } +} diff --git a/NFC/Helper/Crypto/Cipher/AES.cs b/NFC/Helper/Crypto/Cipher/AES.cs new file mode 100644 index 0000000..df53170 --- /dev/null +++ b/NFC/Helper/Crypto/Cipher/AES.cs @@ -0,0 +1,48 @@ +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Parameters; + +namespace NFC.Helper.Crypto.Cipher +{ + public class AES : ICipher + { + public uint BlockSize { get; } = 16; + + public uint KeySize { get; } = 16; + + 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/Helper/Crypto/Cipher/TDES.cs b/NFC/Helper/Crypto/Cipher/TDES.cs new file mode 100644 index 0000000..798004a --- /dev/null +++ b/NFC/Helper/Crypto/Cipher/TDES.cs @@ -0,0 +1,49 @@ +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Parameters; + +namespace NFC.Helper.Crypto.Cipher +{ + public class TDES : ICipher + { + public uint BlockSize { get; } = 8; + + // Two times the DES Key + public uint KeySize { get; } = 16; + + public byte[] Encrypt(byte[] data, byte[] key, byte[] iv) + { + DesEngine engine = new DesEdeEngine(); + 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) + { + DesEngine engine = new DesEdeEngine(); + 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/Helper/Crypto/Cipher/TDES_2K.cs b/NFC/Helper/Crypto/Cipher/TDES_2K.cs new file mode 100644 index 0000000..1eb2631 --- /dev/null +++ b/NFC/Helper/Crypto/Cipher/TDES_2K.cs @@ -0,0 +1,48 @@ +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Parameters; + +namespace NFC.Helper.Crypto.Cipher +{ + public class TDES_2K : ICipher + { + public uint BlockSize { get; } = 8; + + public uint KeySize { get; } = 16; + + public byte[] Encrypt(byte[] data, byte[] key, byte[] iv) + { + DesEngine engine = new DesEdeEngine(); + 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) + { + DesEngine engine = new DesEdeEngine(); + 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/Helper/Crypto/Cipher/TDES_3K.cs b/NFC/Helper/Crypto/Cipher/TDES_3K.cs new file mode 100644 index 0000000..2e1ae5c --- /dev/null +++ b/NFC/Helper/Crypto/Cipher/TDES_3K.cs @@ -0,0 +1,48 @@ +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Parameters; + +namespace NFC.Helper.Crypto.Cipher +{ + public class TDES_3K : ICipher + { + public uint BlockSize { get; } = 8; + + public uint KeySize { get; } = 24; + + public byte[] Encrypt(byte[] data, byte[] key, byte[] iv) + { + DesEngine engine = new DesEdeEngine(); + 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) + { + DesEngine engine = new DesEdeEngine(); + 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/Helper/Crypto/CipherKey.cs b/NFC/Helper/Crypto/CipherKey.cs new file mode 100644 index 0000000..f6c0cf5 --- /dev/null +++ b/NFC/Helper/Crypto/CipherKey.cs @@ -0,0 +1,182 @@ +using System; + +namespace NFC.Helper.Crypto +{ + /// + /// Key for DESFire Card + /// + public class CipherKey + { + #region Constructors + /// + /// Creates Key from Array + /// + /// Key + /// Cipher for Key + /// Version of Key + public CipherKey(byte[] key, CipherType cipher, byte keyVersion) + { + _Cipher = cipher; + + if (cipher == CipherType.AES && keyVersion < 0x10) + { + throw new ArgumentOutOfRangeException("KeyVersion is to low for AES Key (Minimum = 0x10)"); + } + _KeyVersion = keyVersion; + + if (!CheckKey(key, cipher)) + { + throw new ArgumentException("Key is not vaild for CipherType"); + } + + if (cipher == CipherType.TDES || cipher == CipherType.TDES_2K || cipher == CipherType.TDES_3K) + { + _Key = SetKeyVersion(key, keyVersion); + } + else + { + _Key = key; + } + } + + /// + /// Creates Key from String + /// + /// Key + /// Cipher for Key + /// Version of Key + public CipherKey(string key, CipherType cipher, byte keyVersion) : this(HexConverter.ConvertFromHexString(key), cipher, keyVersion) + { + + } + + /// + /// Generates Empty Key + /// + /// Cipher for Key + /// + public CipherKey(CipherType cipher) + { + _Cipher = cipher; + _Key = GenerateEmptyKey(cipher); + + if (cipher == CipherType.AES) + { + _KeyVersion = 0x10; + } + else + { + _KeyVersion = 0x00; + } + } + #endregion + + #region Properties + /// + /// Key as Array + /// + public byte[] _Key { get; private set; } + + /// + /// CipherType of Key + /// + public CipherType _Cipher { get; private set; } + + /// + /// KeyVersion of Key + /// For AES 0x10 is minimum + /// + public byte _KeyVersion { get; private set; } + #endregion + + #region Methods + /// + /// Generate Empty Key for CipherType + /// + /// Type of Cipher + public byte[] GenerateEmptyKey(CipherType cipher) + { + uint size = GetKeySize(cipher); + + byte[] key = new byte[size]; + for (int i = 0; i < size; i++) + { + key[i] = 0; + } + + return key; + } + + /// + /// Check Key Array + /// + /// Key + /// Cipher Type of Key + public bool CheckKey(byte[] key, CipherType cipher) + { + if (key.Length != GetKeySize(cipher)) + { + return false; + } + else + { + return true; + } + } + + /// + /// Get KeySize for CipherType + /// + /// Type of Cipher + public uint GetKeySize(CipherType cipher) + { + switch (cipher) + { + case CipherType.TDES: + return 16; + case CipherType.TDES_2K: + return 16; + case CipherType.TDES_3K: + return 24; + case CipherType.AES: + return 16; + default: + throw new ArgumentOutOfRangeException("Unknown CipherType."); + } + } + + /// + /// Set Key Version for DES/TDES Keys + /// KeyVersion is stored in the LSBits of the first 8 Bytes + /// Parity Bits are not used from DESFire Cars + /// + /// + /// + /// + public byte[] SetKeyVersion(byte[] key, byte version) + { + 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 ((version & pow2[i]) > 0) + { + new_key[i] = (byte)(new_key[5] | 0x01); + } + else + { + new_key[i] = (byte)(new_key[5] & 0x7F); + } + } + + return new_key; + } + #endregion + } +} \ No newline at end of file diff --git a/NFC/Helper/Crypto/CipherType.cs b/NFC/Helper/Crypto/CipherType.cs new file mode 100644 index 0000000..d3dbf14 --- /dev/null +++ b/NFC/Helper/Crypto/CipherType.cs @@ -0,0 +1,25 @@ +namespace NFC.Helper.Crypto +{ + public enum CipherType + { + /// + /// DES / Triple DES + /// + TDES, + + /// + /// Triple DES with 2 DES Keys + /// + TDES_2K, + + /// + /// Triple DES with 3 DES Keys + /// + TDES_3K, + + /// + /// AES + /// + AES + } +} diff --git a/NFC/Helper/Crypto/ICipher.cs b/NFC/Helper/Crypto/ICipher.cs new file mode 100644 index 0000000..35afaff --- /dev/null +++ b/NFC/Helper/Crypto/ICipher.cs @@ -0,0 +1,32 @@ +namespace NFC.Helper.Crypto +{ + public interface ICipher + { + /// + /// Size of Cipher Block in Byte + /// + uint BlockSize { get; } + + /// + /// Size of Key in Byte + /// + uint KeySize { get; } + + /// + /// Encrypt Data + /// + /// Data in BlockSize + /// Key + /// Initialisation Vector + /// + byte[] Encrypt(byte[] data, byte[] key, byte[] IV); + + /// + /// Decrypt Data + /// + /// Data in BlockSize + /// Key + /// Initialisation Vector + byte[] Decrypt(byte[] data, byte[] key, byte[] IV); + } +} diff --git a/NFC/Helper/HexConverter.cs b/NFC/Helper/HexConverter.cs new file mode 100644 index 0000000..fd51446 --- /dev/null +++ b/NFC/Helper/HexConverter.cs @@ -0,0 +1,51 @@ +using System; + +namespace NFC.Helper +{ + /// + /// Converts to and from Byte Array from and to String + /// + public static class HexConverter + { + /// + /// Converts byte[] to string with HEX Code + /// No 0x is created + /// + /// Data + public static string ConvertToHexString(byte[] data) + { + return BitConverter.ToString(data).Replace("-", "").ToLower(); + } + + /// + /// Converts string with HEX Code to byte[] + /// No 0x is requiered + /// + /// Data + public static 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 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)); + } + } +} diff --git a/NFC/Interfaces/ICard.cs b/NFC/Interfaces/ICard.cs new file mode 100644 index 0000000..810624f --- /dev/null +++ b/NFC/Interfaces/ICard.cs @@ -0,0 +1,22 @@ +namespace NFC.Interfaces +{ + public interface ICard + { + /// + /// Connect to Smartcard + /// + void Connect(); + + /// + /// Disconnect from Smartcard + /// + void Disconnect(); + + /// + /// Transmit APDU Command to Smartcard + /// + /// Application Protocol Data Unit Command - ISO 7816 + /// Application Protocol Data Unit Response - ISO 7816 + APDUResponse Transmit(APDUCommand apdu_cmd); + } +} diff --git a/NFC/Interfaces/IHardware.cs b/NFC/Interfaces/IHardware.cs new file mode 100644 index 0000000..17e94a4 --- /dev/null +++ b/NFC/Interfaces/IHardware.cs @@ -0,0 +1,26 @@ +using System; + +namespace NFC.Interfaces +{ + /// + /// Abstract representation of the platform specific NFC Hardware + /// + public interface IHardware + { + /// + /// Check if the device has nfc support + /// + /// Returns true if the device supports NFC + bool IsAvailable(); + + /// Returns all available readers + string[] GetReaders(); + + /// + /// Create a new reader instance from the specified id + /// + /// Returns the spatform specific reader that corresponds to the id + /// Invalid reader id + IReader OpenReader(string readerID); + } +} diff --git a/NFC/Interfaces/IReader.cs b/NFC/Interfaces/IReader.cs new file mode 100644 index 0000000..cc3c9e5 --- /dev/null +++ b/NFC/Interfaces/IReader.cs @@ -0,0 +1,24 @@ +namespace NFC.Interfaces +{ + public delegate void ReaderEventHandler(object sender, ICard card); + + /// + /// Abstraction of a platform-specifc reader that can communicate with NFC cards + /// + public interface IReader + { + /// + /// Event that will be called when a new tag was discovered + /// + event ReaderEventHandler CardDiscovered; + + /// + /// Event that will be called when a tag that is in use gets disconnected + /// + event ReaderEventHandler CardLost; + + void Start(); + + void Stop(); + } +} diff --git a/NFC/IsoCase.cs b/NFC/IsoCase.cs new file mode 100644 index 0000000..7903691 --- /dev/null +++ b/NFC/IsoCase.cs @@ -0,0 +1,8 @@ +namespace NFC +{ + public enum IsoCase + { + Case4Short, + Case2Short + } +} diff --git a/NFC/NFC.csproj b/NFC/NFC.csproj index 9f5c4f4..bcc5c3f 100644 --- a/NFC/NFC.csproj +++ b/NFC/NFC.csproj @@ -4,4 +4,9 @@ netstandard2.0 + + + + +