using System; using System.Collections.Generic; using System.Linq; using System.Text; using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore; using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisPwd; namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile { /// /// Password file operations /// public class MeterPwdFile { // The FwUpdateSw references this string public const String StrPasswordFileName = "0\\password"; private readonly IGenesisMeter _genesisMeter; private readonly MeterFile _meterFile; //seven passwords are needed for Level 1, 2, 4, 5, 6, 7, Level 3 will be generated //out of the ProcessorUID. /// /// Hashed password file return /// public Byte[] HashedPasswordFile { get; private set; } /// /// Ctor for usage of build of password file without writing it /// /// /// - Initial. /// public MeterPwdFile() { } /// /// Ctor /// /// /// /// - Initial. /// public MeterPwdFile(IGenesisMeter genesisMeter) { _genesisMeter = genesisMeter; _meterFile = new MeterFile(_genesisMeter); } /// /// Set the already hashed password file if this is already pre-generated. /// This is the preparation to the . /// The password file has a unique length of . /// /// hashed password file with constant length as byte array /// /// true if length matches the expectations /// /// - Initial. /// private Boolean SetHashedPwdFile(Byte[] hashedPwdFile) { if (MeterPwdDb.PwdFileLength != hashedPwdFile.Length) return false; HashedPasswordFile = new Byte[hashedPwdFile.Length]; HashedPasswordFile = hashedPwdFile; return true; } ///// ///// Build the password file out of the clear text passwords and the processor UID, ///// password Level 3 has to be build out of the Processor UID. ///// ///// 7 sorted passwords levels: 1, 2, 4, 5, 6, 7, 8 ///// unique ID of processor used for password level 3 ///// SkeletonKey from data base for comparison ///// ///// ///// - Initial. ///// ///// ///// - Insert of Level 3 password with skeletonKey, ///// - SHA 1 of passwords. ///// ///// ///// - . ///// ///// ///// - Set Level 3 password to modified from processor UID, ///// - Removed skeletonKey as this cannot be used here! ///// ///// ///// - Set Level 3 password to dbSkeletonKey if processorUid is unknown (processorUid == 1)! ///// //public Boolean BuildPwdFile(List pwdLevels, UInt64 processorUid, String dbSkeletonKey) //{ // if (pwdLevels.Count != PwdLevels || processorUid == 0 || // string.IsNullOrEmpty(dbSkeletonKey) && processorUid == 1) // { // return false; // } // // selective Level 3 password // if (processorUid == 1 && !string.IsNullOrEmpty(dbSkeletonKey)) // { // pwdLevels.Insert(2, Encoding.UTF8.GetBytes(dbSkeletonKey)); // } // else // { // pwdLevels.Insert(2,Encoding.UTF8.GetBytes(BuildLevel3Pwd(processorUid))); // } // var ret = new List(); // foreach (var keyLvl in pwdLevels) // { // using (var sha1 = new System.Security.Cryptography.SHA1Managed()) // { // var hash = sha1.ComputeHash(keyLvl); // ret.AddRange(hash.ToList()); // } // } // _hashedPasswordFile = ret.ToArray(); // return true; //} /// /// 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, 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. /// /// /// - 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 passwords, UInt64 processorUid, String dbSkeletonKey) { // 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 length for each password: {MeterPwdDb.ClearTextPwdLength}"); } // The password list has to contain 8 passwords if (passwords.Count != MeterPwdDb.PwdLevels) { throw new ApplicationException("Number 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!"); } // 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 password level 8!"); } var hashedPasswords = new List(); foreach (var password in passwords) { using (var sha1 = new System.Security.Cryptography.SHA1Managed()) { var passwordHash = sha1.ComputeHash(password); hashedPasswords.AddRange(passwordHash.ToList()); } } // 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; } /// /// Check the hashed password file: /// - Checks file size, /// - Checks if password level 8 is unequal to skeleton key /// - Checks if password level 3 is unequal to password level 8 (level 3 may be overwritten by level 8 /// password hash) /// - Builds skeleton key password hash and checks if this is placed at password level 3, /// - Builds password level 8 hash and checks if this is placed at level 8. /// /// /// SkeletonKey for comparison /// /// /// /// - Initial. /// /// /// - Check password file length. /// public Boolean CheckHashedPwdFile(Byte[] pwdFile, String skeletonKey, String pwdLevel8) { var retVal = false; // check the file size if (pwdFile.Length != MeterPwdDb.PwdFileLength) { return false; } // the skeletonKey has to be unequally to the passwordLvl8 if (skeletonKey.Equals(pwdLevel8)) { return false; } // compare hashed skeletonKey and hashed passwordLvl8 for (var c = 0; c < MeterPwdDb.HashedPwdLength; c++) { // hashed level 3 and level 8 have to be unequal on at least one byte position if (pwdFile[MeterPwdDb.SkeletonKeyIdx * MeterPwdDb.HashedPwdLength + c] != pwdFile[MeterPwdDb.PwdLevel8Idx * MeterPwdDb.HashedPwdLength + c]) { retVal = true; break; } } // hash the skeleton and check if equal to hashed level 3 // hash the passwordLvl8 and check if equal to hashed level 8 using (var sha1 = new System.Security.Cryptography.SHA1Managed()) { var skeletonHash = sha1.ComputeHash(Encoding.UTF8.GetBytes(skeletonKey)); var passwordLvl8Hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(pwdLevel8)); for (var c = 0; c < MeterPwdDb.HashedPwdLength; c++) { // if any byte is different the passwords do not match if (pwdFile[MeterPwdDb.SkeletonKeyIdx * MeterPwdDb.HashedPwdLength + c] != skeletonHash[c] || pwdFile[MeterPwdDb.PwdLevel8Idx * MeterPwdDb.HashedPwdLength + c] != passwordLvl8Hash[c]) { return false; } } } return retVal; } /// /// Build skeleton key out of unique processor Id /// /// /// /// /// - Initial. /// public static String BuildSkeletonKey(UInt64 processorUid) { return $"{(UInt16)((processorUid >> 48) ^ (processorUid >> 32)):X4}" + $"{(UInt32)((processorUid >> 16) ^ processorUid):X8}"; } /// /// Build level 3 password out of unique processor Id /// /// /// /// /// - Initial. /// public static String BuildLevel3Pwd(UInt64 processorUid) { //password level 3 algorithm return $"{(UInt16)((processorUid >> 48) ^ processorUid):X4}" + $"{(UInt32)((processorUid >> 32) ^ processorUid):X8}"; } /// /// 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 . /// /// optional hashed password file with constant length as byte array /// true if length matches the expectations and password file could be written /// /// /// - Initial. /// /// /// - Modified to take optional hashed password file as input. /// public Boolean WriteMeterPwdFile(Byte[] hashedPwdFile = null) { if (hashedPwdFile != null) { if (!SetHashedPwdFile(hashedPwdFile)) return false; } if (HashedPasswordFile != null && _meterFile != null && _genesisMeter != null && _genesisMeter.IsLoggedOn && HashedPasswordFile.Length == MeterPwdDb.PwdFileLength) { return _meterFile.WriteMeterFile(StrPasswordFileName, HashedPasswordFile); } return false; } /// /// Verify Meter password file. /// optional hashed password file with constant length as byte array /// true if the read value is identical with the preset or given hashed value /// /// /// /// - Initial. /// public Boolean VerifyMeterPwdFile(Byte[] hashedPwdFile = null) { if (hashedPwdFile != null) { if (!SetHashedPwdFile(hashedPwdFile)) return false; } if (HashedPasswordFile == null) { return false; } return _meterFile.VerifyMeterFile(StrPasswordFileName, HashedPasswordFile); } /// /// Erase the meter password file /// /// /// /// - Initial. /// public Boolean EraseMeterPwdFile() { if (_meterFile != null && _genesisMeter != null && _genesisMeter.IsLoggedOn) { return _meterFile.EraseMeterFile(StrPasswordFileName); } return false; } /// /// Unlock access to password file for write and erase /// /// /// /// - Initial. /// public Boolean UnlockEraseWriteMeterPwdFile() { if (_meterFile != null && _genesisMeter != null && _genesisMeter.IsLoggedOn) { return _meterFile.UnlockEraseWriteMeterFile(StrPasswordFileName); } return false; } } }