diff --git a/Common/Hardware/WaterMeter/Genesis/GenesisFile/MeterPwdFile.cs b/Common/Hardware/WaterMeter/Genesis/GenesisFile/MeterPwdFile.cs index 0011d012..b83e823e 100644 --- a/Common/Hardware/WaterMeter/Genesis/GenesisFile/MeterPwdFile.cs +++ b/Common/Hardware/WaterMeter/Genesis/GenesisFile/MeterPwdFile.cs @@ -12,13 +12,12 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile /// public class MeterPwdFile { - public const String StrPasswordFileName = "0\\password"; + private const String StrPasswordFileName = "0\\password"; private readonly GenesisMeter _genesisMeter; private readonly MeterFile _meterFile; private Byte[] _hashedPasswordFile; //seven passwords are needed for Level 1, 2, 4, 5, 6, 7, Level 3 will be generated //out of the ProcessorUID. - //private const Int32 PwdLevels = 7; /// /// Ctor @@ -45,7 +44,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile /// /// - Initial. /// - public Boolean SetHashedPwdFile(Byte[] hashedPwdFile) + private Boolean SetHashedPwdFile(Byte[] hashedPwdFile) { if (MeterPwdDb.PwdFileLength != hashedPwdFile.Length) return false; _hashedPasswordFile = new Byte[hashedPwdFile.Length]; @@ -114,43 +113,94 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile /// /// Build the password file out of the clear text passwords. /// The Level 3 password is already preset with skeletonKey. + /// + /// The skeleton key has to be unequal to the level 8 password, otherwise the skeleton key has been + /// overwritten with this password level 8 locking out all applications which need this key at the + /// level 3 to login in the configuration of the Cordonel. + /// + /// ATTENTION - PRECONDITIONS: + /// All passwords have to be preset in the list of passwords: + /// Level 1: random from password service + /// Level 2: random from password service + /// Level 3: skeletonKey, this is the initial password used in production, used by applications to + /// login to the configuration system of the Cordonel + /// Level 4: random from password service + /// Level 5: random from password service + /// Level 6: random from password service + /// Level 7: random from password service + /// Level 8: random from password service, this is the passwordLvl8 used in production after + /// password file installation + /// + /// Checks ( each failed check is going to throw an exception): + /// - Checks for individual clear text password length, + /// - Checks if list of clear text passwords are containing 8 levels, + /// - Checks that password level 3 is set to skeleton key, + /// - Checks that skeleton key is unequal to password level 8, + /// - Check file size of generated password file. + /// /// - /// 8 sorted passwords levels: 1, 2, 3, 4, 5, 6, 7, 8 + /// 8 sorted passwords levels: 1, 2, skeletonKey, 4, 5, 6, 7, 8 /// unique ID of processor used for password level 3 /// SkeletonKey for comparison /// /// /// - Initial. /// - /// + /// /// - All 8 password levels will be passed, /// - Password Level 3 will be compared with data base skeletonKey, /// - SHA 1 of passwords. /// - /// - /// - Check if skeletonKey is overwritten with passwordLvl8. + /// + /// - Throw exception if clear text password length is out of range. + /// - Throw exception if password list is out of range. + /// - Throw exception if password level3 does not contain the skeleton key. + /// - Throw exception if skeletonKey is overwritten with passwordLvl8. + /// - Throw exception if password file is out of range. /// - public Boolean BuildPwdFile(List pwdLevels, UInt64 processorUid, String dbSkeletonKey) + public Boolean BuildPwdFile(List passwords, UInt64 processorUid, String dbSkeletonKey) { - if (!Encoding.UTF8.GetBytes(dbSkeletonKey).SequenceEqual(pwdLevels[MeterPwdDb.SkeletonKeyIdx])) + // The length of each password has to be 12 bytes + if (passwords.Any( x => x.Length != MeterPwdDb.ClearTextPwdLength)) + { + throw new ApplicationException("Individual password length is out of range! " + + $"Expected for each password: {MeterPwdDb.ClearTextPwdLength}"); + } + // The password list has to contain 8 passwords + if (passwords.Count != MeterPwdDb.PwdLevels) + { + throw new ApplicationException("List of passwords is out of range! " + + $"Expected: {MeterPwdDb.PwdLevels}, " + + $"Transmitted: {passwords.Count }"); + } + // The skeleton key has to be preset on level 3 + if (!Encoding.UTF8.GetBytes(dbSkeletonKey).SequenceEqual(passwords[MeterPwdDb.SkeletonKeyIdx])) { throw new ApplicationException("SkeletonKey is not set on password file level 3!"); } - if (Encoding.UTF8.GetBytes(dbSkeletonKey).SequenceEqual(pwdLevels[MeterPwdDb.PwdLevel8Idx])) + // The skeleton key has to be unequal to the level 8 password + if (Encoding.UTF8.GetBytes(dbSkeletonKey).SequenceEqual(passwords[MeterPwdDb.PwdLevel8Idx])) { - throw new ApplicationException("SkeletonKey is overwritten with passwordLvl8!"); + throw new ApplicationException("SkeletonKey is overwritten with password level 8!"); } - var ret = new List(); + var hashedPasswords = new List(); - foreach (var keyLvl in pwdLevels) + foreach (var password in passwords) { using (var sha1 = new System.Security.Cryptography.SHA1Managed()) { - var hash = sha1.ComputeHash(keyLvl); - ret.AddRange(hash.ToList()); + var passwordHash = sha1.ComputeHash(password); + hashedPasswords.AddRange(passwordHash.ToList()); } } - _hashedPasswordFile = ret.ToArray(); + // The password file size has to be 160 bytes + if (hashedPasswords.ToArray().Length != MeterPwdDb.PwdFileLength) + { + throw new ApplicationException("Generated password file is out of range! " + + $"Expected: {MeterPwdDb.PwdFileLength}, " + + $"Transmitted: {hashedPasswords.ToArray().Length }"); + } + _hashedPasswordFile = hashedPasswords.ToArray(); return true; } @@ -184,12 +234,33 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile } /// - /// Write the meter password file, which was generated at or + /// This function combines the write, read back and verification of the password file. + /// Write password file and compare it + /// The hashed password file can be set or has been preprocessed using the + /// + /// optional hashed password file with constant length as byte array + /// true if password file could be written, read back byte by byte + /// and compared being identical + /// + /// - Initial, imported from ProductionProcessPasswordFile. + /// + public Boolean WriteAndVerifyMeterPwdFile(Byte[] hashedPwdFile = null) + { + if (UnlockEraseWriteMeterPwdFile()) + { + if (WriteMeterPwdFile(hashedPwdFile)) + { + return VerifyMeterPwdFile(); + } + } + return false; + } + /// + /// Write the meter password file which was generated at or /// use the already hashed password file if this is already pre-generated. /// The password file has a unique length of . /// - /// hashed password file with constant length as byte array - /// + /// optional hashed password file with constant length as byte array /// true if length matches the expectations and password file could be written /// /// diff --git a/Common/Hardware/WaterMeter/Genesis/GenesisPwd/MeterPwdDb.cs b/Common/Hardware/WaterMeter/Genesis/GenesisPwd/MeterPwdDb.cs index bfd7194b..9004555d 100644 --- a/Common/Hardware/WaterMeter/Genesis/GenesisPwd/MeterPwdDb.cs +++ b/Common/Hardware/WaterMeter/Genesis/GenesisPwd/MeterPwdDb.cs @@ -16,9 +16,24 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisPwd public class MeterPwdDb { /// - /// password file length is 160 bytes (8 passwords a 20 bytes SHA1) + /// 8 passwords needed for clear the text passwords ans the password file /// - public const Int32 PwdFileLength = 160; + public const Int32 PwdLevels = 8; + + /// + /// Each password will generate a 20 bytes hash constant due to SHA1 algorithm + /// + private const Int32 HashedPwdLength = 20; + + /// + /// password file length is 160 bytes (8 passwords a 20 bytes hashed) + /// + public const Int32 PwdFileLength = PwdLevels * HashedPwdLength; + + /// + /// 8 passwords needed for the password file + /// + public const Int32 ClearTextPwdLength = 12; /// /// Skeleton key index in password list. @@ -60,7 +75,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisPwd /// public List ListOfPasswords = new List(); /// - /// Ident for password server (BSI) can be radioaddress or pcbid + /// Ident for password server (BSI) can be radio address or pcbid /// public String PasswordFileIdent { get; set; } /// diff --git a/Common/Hardware/WaterMeter/Genesis/GenesisPwd/MeterPwdHandlerDb.cs b/Common/Hardware/WaterMeter/Genesis/GenesisPwd/MeterPwdHandlerDb.cs index 6e8d41e4..785ab928 100644 --- a/Common/Hardware/WaterMeter/Genesis/GenesisPwd/MeterPwdHandlerDb.cs +++ b/Common/Hardware/WaterMeter/Genesis/GenesisPwd/MeterPwdHandlerDb.cs @@ -56,7 +56,10 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisPwd } /// - /// Collect order number, radio address, skeleton key, password hashes and passwords from DB. + /// Overwrites the (production) password used for production in DB. + /// Initially this (production) password is set to the skeletonKey as the password file is not installed. + /// After installation of the password file the (production) password has to be set to the level 8 password + /// being able to access the meter with the production tools and the GTB. /// /// input the PcbId /// output of passwordContainer diff --git a/Common/ProductionUiCordonel/ProductionProcesses/Actions/ProductionProcessPasswordFile.cs b/Common/ProductionUiCordonel/ProductionProcesses/Actions/ProductionProcessPasswordFile.cs index 9d77e116..5e9b13e1 100644 --- a/Common/ProductionUiCordonel/ProductionProcesses/Actions/ProductionProcessPasswordFile.cs +++ b/Common/ProductionUiCordonel/ProductionProcesses/Actions/ProductionProcessPasswordFile.cs @@ -53,7 +53,7 @@ namespace ProductionUiCordonel.ProductionProcesses.Actions Meter.Logout(); ret = Meter.Login(); - + //If the password file is written, the "Password" from _pwdContainer is overwritten from Skeleton-key //to the "level 8 password" form the LuDB server. The DB password column is the current login password //for production. Initially the "Password" is the Skeleton-key which can not be part of the list of @@ -61,16 +61,17 @@ namespace ProductionUiCordonel.ProductionProcesses.Actions if (Encoding.UTF8.GetString(_pwdContainer.ListOfPasswords.Last()) == _pwdContainer.Password) { NewStatus("Passwörter schon geschrieben. Prozess wird übersprungen"); - + currentProcessState = ProductionProcessState.Done; return; } - + //Get the passwords from LuDB, insert the skeleton-kay and generate the password file if (ret) { - ret = CreatePasswordFile(); + _meterPwdFile = new MeterPwdFile(Meter); + ret = _meterPwdFile.BuildPwdFile(_pwdContainer.ListOfPasswords, 1, _pwdContainer.Skeleton); } updateImage(_userControl.CreateFile, ret); @@ -79,7 +80,8 @@ namespace ProductionUiCordonel.ProductionProcesses.Actions //Write the password file to the Cordonel, read it back and make a binary verification if (ret) { - ret = WriteAndVerifyPasswordFileToMeter(); + if (_meterPwdFile != null) + ret = _meterPwdFile.WriteAndVerifyMeterPwdFile(); } updateImage(_userControl.WriteToMeter, ret); @@ -131,25 +133,5 @@ namespace ProductionUiCordonel.ProductionProcesses.Actions { //clear } - - #region realWork - public Boolean CreatePasswordFile() - { - _meterPwdFile = new MeterPwdFile(Meter); - return _meterPwdFile.BuildPwdFile(_pwdContainer.ListOfPasswords, 1, _pwdContainer.Skeleton); - } - public Boolean WriteAndVerifyPasswordFileToMeter() - { - if (_meterPwdFile.UnlockEraseWriteMeterPwdFile()) - { - if (_meterPwdFile.WriteMeterPwdFile()) - { - return _meterPwdFile.VerifyMeterPwdFile(); - } - } - return false; - } - - #endregion } }