private byte[] GenerateIv(MBusHeader header)
        {
            if (header.EncryptionScheme == EncryptionScheme.AesCtr)
            {
                return(GenerateCounterModeIV(header));
            }

            return(GenerateGenericIV(header));
        }
        private byte[] GenerateGenericIV(MBusHeader header)
        {
            var initializationVector = new List <byte>();

            initializationVector.AddRange(header.GetMAVDBytes);

            for (var i = 0; i < 8; i++)
            {
                initializationVector.Add(header.GetAccessNumber); //Access number is one byte
            }

            return(initializationVector.ToArray());
        }
        /// <summary>
        /// parse the payload.
        /// </summary>
        /// <param name="header">the mbus header. Used for manufacturer specific parsing.</param>
        /// <param name="payload">the payload.</param>
        /// <param name="decryptionKey">the decryption key.</param>
        /// <param name="initializationVector">the initialization vector.</param>
        /// <returns>the mbus telegram.</returns>
        public MBusTelegram ParsePayload(MBusHeader header, IList <byte> payload, byte[] decryptionKey = null)
        {
            double?rssi = null;

            // todo refactor to be able to return rssi

            if (header.IsWireless)
            {
                rssi    = -payload.Last() / 2;
                payload = payload.Take(payload.Count - 1).ToArray();
            }

            IList <VariableDataRecord>?records = this.ParseRecords(
                header,
                payload,
                decryptionKey);

            return(new MBusTelegram(header, records, rssi));
        }
        private byte[] GenerateCounterModeIV(MBusHeader header, byte[]?frameNumber = null, byte blockCounter = 0b00000000)
        {
            var extendedLinkLayer = header.ExtendedLinkLayer;

            if (frameNumber == null)
            {
                frameNumber = new byte[] { 0x00, 0x00 };
            }

            var initializationVector = new List <byte>();



            initializationVector.AddRange(header.GetMAVDBytes);
            byte commControl = extendedLinkLayer.GetCommunicationControlByte.Mask(0b1110_1111);

            initializationVector.Add(commControl);

            initializationVector.AddRange(extendedLinkLayer.GetSessionNumberBytes);
            initializationVector.AddRange(frameNumber);
            initializationVector.Add(blockCounter);

            return(initializationVector.ToArray());
        }
        /// <summary>
        /// parse the records.
        /// </summary>
        /// <param name="header">the mbus header. Useful for manufacturer specific parsing.</param>
        /// <param name="payload">the payload.</param>
        /// <param name="decryptionKey">the decryption key.</param>
        /// <param name="initializationVector">the initialization vector.</param>
        /// <returns>a IList<MbusRecord> containing the parse records.</returns>
        private IList <VariableDataRecord> ParseRecords(MBusHeader header, IList <byte> payload, byte[] decryptionKey)
        {
            List <byte> payloadBody;

            // Todo find out whats wrong with this.
            if (header.ExtendedLinkLayer != null)
            {
                payloadBody = payload.Skip(header.PayloadStartsAtIndex - 2).ToList();
            }
            else
            {
                payloadBody = payload.Skip(header.PayloadStartsAtIndex).ToList();
            }


            if (decryptionKey != null)
            {
                payloadBody = DecryptPayload(header, payloadBody, decryptionKey);
            }

            if (payloadBody == null)
            {
                return(null);
            }


            if (header.ExtendedLinkLayer != null)
            {
                var frameType = ControlInformationLookup.Find(payloadBody.First()).frameType;


                if (frameType != FrameType.Full)
                {
                    return(null);
                }

                payloadBody = payloadBody.Skip(1).ToList();
            }

            while (payloadBody.Last() == 0x2F)
            {
                payloadBody.RemoveAt(payloadBody.Count() - 1);
            }

            var list = new List <VariableDataRecord>();

            // MBus has a maximum length of 255.
            byte indexer = 0;

            while (indexer < payloadBody.Count)
            {
                var difFieldByte = payloadBody[indexer++];

                // TODO Handle special case stuff
                if (difFieldByte == 0x1F || difFieldByte == 0x0F)
                {
                    break;
                }

                var dataInformationField           = new DataInformationField(difFieldByte);
                var dataInformationFieldExtensions = new List <DataInformationExtensionField>();

                var difesAvailable = dataInformationField.HasExtensionBit;
                while (difesAvailable)
                {
                    var extension = new DataInformationExtensionField(payloadBody[indexer++]);
                    dataInformationFieldExtensions.Add(extension);

                    difesAvailable = extension.HasExtensionBit;
                }

                var valueInformationField           = new PrimaryValueInformationField(payloadBody[indexer++], dataInformationField.DataField);
                var valueInformationFieldExtensions = new List <ValueInformationExtensionField>();

                var vifesAvailable = valueInformationField.HasExtensionBit;
                while (vifesAvailable)
                {
                    ValueInformationExtensionField?extension = null;
                    if (!valueInformationFieldExtensions.Any())
                    {
                        switch (valueInformationField.Type)
                        {
                        case PrimaryValueInformation.FBValueInformationExtension:
                            extension = new FBValueInformationExtensionField(payloadBody[indexer++]);
                            break;

                        case PrimaryValueInformation.FDValueInformationExtension:
                            extension = new FDValueInformationExtensionField(payloadBody[indexer++]);
                            break;

                        case PrimaryValueInformation.ManufacturerSpecific:
                            switch (header.ManufacturerName.ToLowerInvariant())
                            {
                            case "kam":
                                extension = new KamstrupValueInformationExtensionField(payloadBody[indexer++]);
                                break;
                            }

                            break;

                        default:
                            extension = new UnknownValueInformationExtensionField(payloadBody[indexer++]);
                            break;
                        }
                    }
                    else
                    {
                        extension = new OrthogonalValueInformationExtensionField(payloadBody[indexer++]);
                    }

                    valueInformationFieldExtensions.Add(extension);

                    vifesAvailable = extension.HasExtensionBit;
                }

                // is LVAR, the length of the data is in the first byte of the "real" data.
                if (dataInformationField.DataLength == null && dataInformationField.DataField == DataField.VariableLength)
                {
                    dataInformationField.DataLength = payloadBody[indexer++];
                }

                var dataBlock = new DataBlock(payloadBody.Skip(indexer).Take(dataInformationField.DataLength.Value).ToArray(), dataInformationField, dataInformationFieldExtensions, valueInformationField, valueInformationFieldExtensions);
                indexer = (byte)(indexer + dataInformationField.DataLength.Value);

                list.Add(new VariableDataRecord(dataBlock));
            }


            return(list);
        }
        private List <byte> DecryptPayload(MBusHeader header, List <byte> payloadBody, byte[] decryptionKey)
        {
            var encryptionScheme = header.EncryptionScheme;

            if (encryptionScheme == EncryptionScheme.NoEncryption)
            {
                return(payloadBody);
            }

            var initializationVector = GenerateIv(header);

            List <byte>?       decryptionResult = null;
            IEnumerable <byte> bytesToBeDecrypted;
            var remainderBytes = new List <byte>();

            // For CTR we need to decrypt the entire payload from the Extended linklayer. Plus the two CRC bytes before the payload (these need to be removed after decryption)
            if (encryptionScheme == EncryptionScheme.AesCtr)
            {
                bytesToBeDecrypted = payloadBody;
            }
            else
            {
                // It is CBC, DES, or something else than CTR
                // These encryption schemes can be implemented in two ways:
                // 1 - All of the payload is encrypted, and if the payload isnt exactly a fit with encryption-block sizes, it puts "fill bytes" (2F) in the trailer of the (unencrypted) payload.
                // 2 - It encrypts what it can, and leaves a remainder of unencrypted bytes in the end of the payload.
                int decryptionBlockSize;

                // For 128-bit AES a decryption blocksize is 16
                // For 128-bit DES it is 8
                if (encryptionScheme == EncryptionScheme.DesCbcIvIsZero || encryptionScheme == EncryptionScheme.DesCbc)
                {
                    decryptionBlockSize = 8;
                }
                else
                {
                    decryptionBlockSize = 16;
                }

                // Find out how many encrypted blocks the payload contains
                var numberOfBlocks = header.Configuration.NumberOfEncryptedBlocks;

                // Find out how many of the bytes in the payload are actually encrypted
                var numberOfBytesToDecrypt = decryptionBlockSize * numberOfBlocks;

                // Retrieve the number of bytes to decrypted
                bytesToBeDecrypted = payloadBody.Take(numberOfBytesToDecrypt);

                // If there is a remainder, keep it for later use.
                remainderBytes = payloadBody.Skip(numberOfBytesToDecrypt).ToList();
            }

            decryptionResult = DecryptBytes(decryptionKey, initializationVector, bytesToBeDecrypted, encryptionScheme);

            int decryptionValidationSize = 2;

            // The first two bytes of CTR decryption is CRC bytes, and first two bytes of any other is "validation bytes" (2 x 0x2F), extract these in order to validate if decryption was successful.

            if (!DecryptionSuccessful(decryptionResult.ToArray(), encryptionScheme))
            {
                //throw new MBusDecryptionException($"Decryption-check failed. Key: {key.ToHexString()}, IV: {initializationVector.ToHexString()}");
                return(null);
            }

            // Remove the two validation bytes
            decryptionResult = decryptionResult.Skip(decryptionValidationSize).ToList();

            // Add the last bunch of bytes which were sent unencrypted, if there is any
            decryptionResult.AddRange(remainderBytes);

            return(decryptionResult);
        }
Exemple #7
0
 /// <summary>
 /// Initializes a new instance of the <see cref="MBusTelegram"/> class.
 /// </summary>
 /// <param name="header"><see cref="MBusHeader"/>.</param>
 /// <param name="records">A list of <see cref="MbusRecord"/>'s.</param>
 /// <param name="rssi">RSSI-value if the telegram was from a wireless mbus.</param>
 public MBusTelegram(MBusHeader header, IList <VariableDataRecord>?records, double?rssi)
 {
     Header  = header;
     Rssi    = rssi;
     Records = records;
 }