From 5393db957bfb05f3aac5a66bca6a33d2bbb24b9c Mon Sep 17 00:00:00 2001 From: Thomas Wiedebusch Date: Thu, 19 Oct 2023 13:07:09 +0200 Subject: [PATCH] Common: - password file check extended --- .../Genesis/GenesisFile/MeterPwdFile.cs | 162 ++++++++++++++---- .../Genesis/GenesisPwd/MeterPwdDb.cs | 21 ++- .../Genesis/GenesisPwd/MeterPwdHandlerDb.cs | 5 +- .../Actions/ProductionCheckShippingState.cs | 1 + .../Actions/ProductionProcessPasswordFile.cs | 13 +- .../Ui/GenesisToolBox/frmPassword.Designer.cs | 18 +- Common/Ui/GenesisToolBox/frmPassword.cs | 15 +- 7 files changed, 182 insertions(+), 53 deletions(-) diff --git a/Common/Hardware/WaterMeter/Genesis/GenesisFile/MeterPwdFile.cs b/Common/Hardware/WaterMeter/Genesis/GenesisFile/MeterPwdFile.cs index bc6f2c21..69e0cfd1 100644 --- a/Common/Hardware/WaterMeter/Genesis/GenesisFile/MeterPwdFile.cs +++ b/Common/Hardware/WaterMeter/Genesis/GenesisFile/MeterPwdFile.cs @@ -12,19 +12,37 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile /// public class MeterPwdFile { + // The FwUpdateSw references this string public 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; + + /// + /// 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(GenesisMeter genesisMeter) @@ -45,11 +63,11 @@ 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]; - _hashedPasswordFile = hashedPwdFile; + HashedPasswordFile = new Byte[hashedPwdFile.Length]; + HashedPasswordFile = hashedPwdFile; return true; } @@ -113,37 +131,95 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile /// /// Build the password file out of the clear text passwords. - /// The Level 3 password is already preset. + /// 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. /// - public Boolean BuildPwdFile(List pwdLevels, UInt64 processorUid, String dbSkeletonKey) + /// + /// - 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) { - if (!Encoding.UTF8.GetBytes(dbSkeletonKey).SequenceEqual(pwdLevels[2])) + // The length of each password has to be 12 bytes + if (passwords.Any( x => x.Length != MeterPwdDb.ClearTextPwdLength)) { - throw new ApplicationException("Level 3 Password is unequal to the old Data Base Content!"); + throw new ApplicationException("Individual password length is out of range! " + + $"Expected length for each password: {MeterPwdDb.ClearTextPwdLength}"); } - var ret = new List(); + // 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 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; } @@ -177,12 +253,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 /// /// @@ -198,33 +295,36 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile if (!SetHashedPwdFile(hashedPwdFile)) return false; } - if (_hashedPasswordFile != null && _meterFile != null && _genesisMeter != null - && _genesisMeter.IsLoggedOn && _hashedPasswordFile.Length == MeterPwdDb.PwdFileLength) + if (HashedPasswordFile != null && _meterFile != null && _genesisMeter != null + && _genesisMeter.IsLoggedOn && HashedPasswordFile.Length == MeterPwdDb.PwdFileLength) { - return _meterFile.WriteMeterFile(StrPasswordFileName, _hashedPasswordFile); + return _meterFile.WriteMeterFile(StrPasswordFileName, HashedPasswordFile); } return false; } /// - /// Verify Meter password file - /// generated passowrd file has to be present + /// 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() + public Boolean VerifyMeterPwdFile(Byte[] hashedPwdFile = null) { - if (_hashedPasswordFile == null) + if (hashedPwdFile != null) + { + if (!SetHashedPwdFile(hashedPwdFile)) return false; + } + if (HashedPasswordFile == null) { return false; } - - return _meterFile.VerifyMeterFile(StrPasswordFileName, _hashedPasswordFile); - + return _meterFile.VerifyMeterFile(StrPasswordFileName, HashedPasswordFile); } /// diff --git a/Common/Hardware/WaterMeter/Genesis/GenesisPwd/MeterPwdDb.cs b/Common/Hardware/WaterMeter/Genesis/GenesisPwd/MeterPwdDb.cs index bfd7194b..591379e8 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 + /// + public 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/ProductionCheckShippingState.cs b/Common/ProductionUiCordonel/ProductionProcesses/Actions/ProductionCheckShippingState.cs index 163b4d9f..1921566b 100644 --- a/Common/ProductionUiCordonel/ProductionProcesses/Actions/ProductionCheckShippingState.cs +++ b/Common/ProductionUiCordonel/ProductionProcesses/Actions/ProductionCheckShippingState.cs @@ -154,6 +154,7 @@ namespace ProductionUiCordonel.ProductionProcesses.Actions throw; } //; + //TODO ROLAND only new meters can be tested which have a LUT installed if (Meter.LutCrc != $@"0x{int.Parse(res[8]):X4}") { currentProcessState = ProductionProcessState.Error; diff --git a/Common/ProductionUiCordonel/ProductionProcesses/Actions/ProductionProcessPasswordFile.cs b/Common/ProductionUiCordonel/ProductionProcesses/Actions/ProductionProcessPasswordFile.cs index 9d77e116..c9c7ba43 100644 --- a/Common/ProductionUiCordonel/ProductionProcesses/Actions/ProductionProcessPasswordFile.cs +++ b/Common/ProductionUiCordonel/ProductionProcesses/Actions/ProductionProcessPasswordFile.cs @@ -135,8 +135,17 @@ namespace ProductionUiCordonel.ProductionProcesses.Actions #region realWork public Boolean CreatePasswordFile() { - _meterPwdFile = new MeterPwdFile(Meter); - return _meterPwdFile.BuildPwdFile(_pwdContainer.ListOfPasswords, 1, _pwdContainer.Skeleton); + try + { + _meterPwdFile = new MeterPwdFile(Meter); + return _meterPwdFile.BuildPwdFile(_pwdContainer.ListOfPasswords, 1, _pwdContainer.Skeleton); + } + catch (Exception e) + { + // log error message of wrong generated password file + NewStatus(e.Message); + return false; + } } public Boolean WriteAndVerifyPasswordFileToMeter() { diff --git a/Common/Ui/GenesisToolBox/frmPassword.Designer.cs b/Common/Ui/GenesisToolBox/frmPassword.Designer.cs index fe2f68dd..c22ea105 100644 --- a/Common/Ui/GenesisToolBox/frmPassword.Designer.cs +++ b/Common/Ui/GenesisToolBox/frmPassword.Designer.cs @@ -32,7 +32,7 @@ this.btnGet = new System.Windows.Forms.Button(); this.btnSet = new System.Windows.Forms.Button(); this.txtPcbID = new System.Windows.Forms.TextBox(); - this.txtPassword = new System.Windows.Forms.TextBox(); + this.txtSkeletonKey = new System.Windows.Forms.TextBox(); this.lblPcbID = new System.Windows.Forms.Label(); this.label1 = new System.Windows.Forms.Label(); this.grpSetup = new System.Windows.Forms.GroupBox(); @@ -84,13 +84,13 @@ this.txtPcbID.TabIndex = 2; this.txtPcbID.TextChanged += new System.EventHandler(this.txtPcbID_TextChanged); // - // txtPassword + // txtSkeletonKey // - this.txtPassword.Location = new System.Drawing.Point(118, 67); - this.txtPassword.Name = "txtPassword"; - this.txtPassword.Size = new System.Drawing.Size(102, 20); - this.txtPassword.TabIndex = 3; - this.txtPassword.TextChanged += new System.EventHandler(this.txtPassword_TextChanged); + this.txtSkeletonKey.Location = new System.Drawing.Point(118, 67); + this.txtSkeletonKey.Name = "txtSkeletonKey"; + this.txtSkeletonKey.Size = new System.Drawing.Size(102, 20); + this.txtSkeletonKey.TabIndex = 3; + this.txtSkeletonKey.TextChanged += new System.EventHandler(this.txtPassword_TextChanged); // // lblPcbID // @@ -203,7 +203,7 @@ this.grpDataBase.Controls.Add(this.barProgressDb); this.grpDataBase.Controls.Add(this.label1); this.grpDataBase.Controls.Add(this.lblPcbID); - this.grpDataBase.Controls.Add(this.txtPassword); + this.grpDataBase.Controls.Add(this.txtSkeletonKey); this.grpDataBase.Controls.Add(this.txtPcbID); this.grpDataBase.Controls.Add(this.btnSet); this.grpDataBase.Controls.Add(this.btnGet); @@ -303,7 +303,7 @@ private System.Windows.Forms.Button btnGet; private System.Windows.Forms.Button btnSet; private System.Windows.Forms.TextBox txtPcbID; - private System.Windows.Forms.TextBox txtPassword; + private System.Windows.Forms.TextBox txtSkeletonKey; private System.Windows.Forms.Label lblPcbID; private System.Windows.Forms.Label label1; private System.Windows.Forms.GroupBox grpSetup; diff --git a/Common/Ui/GenesisToolBox/frmPassword.cs b/Common/Ui/GenesisToolBox/frmPassword.cs index 34cebccc..b403c0c2 100644 --- a/Common/Ui/GenesisToolBox/frmPassword.cs +++ b/Common/Ui/GenesisToolBox/frmPassword.cs @@ -104,14 +104,15 @@ namespace Xylem.Common.Ui.GenesisToolBox } var responseJson = sr.ReadToEnd(); //TODO THW hide skeleton key if read from DB - txtPassword.Text = responseJson.Trim('"'); + //txtSkeletonKey.Text = responseJson.Trim('"'); + txtSkeletonKey.Text = a.Skeleton; txtLvl8.Text = autoPassword; lblConnectDb.Text = @"Connected to PWD DB"; lblConnectDb.ForeColor = Color.Green; ////TODO TEST ONLY remove here _isReadyPasswordFile _isReadyPasswordFile = true; - btnWritePwdFile.Enabled = true; + //btnWritePwdFile.Enabled = true; ////TODO remove until here })); @@ -140,7 +141,7 @@ namespace Xylem.Common.Ui.GenesisToolBox var passwordSetServiceUrl = ServiceUrls.GenesisSetPasswordServiceUrl(); #pragma warning restore CS0618 // Type or member is obsolete var http = (HttpWebRequest)WebRequest.Create(passwordSetServiceUrl + txtPcbID.Text - + "&Password=" + txtPassword.Text); + + "&Password=" + txtSkeletonKey.Text); var response = (HttpWebResponse)http.GetResponse(); var responseStream = response.GetResponseStream(); if (responseStream == null) @@ -250,7 +251,7 @@ namespace Xylem.Common.Ui.GenesisToolBox lblConnectPcb.Text = StrNotConnected; lblConnectPcb.ForeColor = Color.Red; txtPcbID.Text = ""; - txtPassword.Text = ""; + txtSkeletonKey.Text = ""; btnGet.Enabled = false; btnSet.Enabled = false; if (!string.IsNullOrEmpty(cbComSlot.SelectedItem.ToString()) && @@ -324,7 +325,7 @@ namespace Xylem.Common.Ui.GenesisToolBox lblConnectDb.ForeColor = Color.Red; btnConnect.Enabled = true; txtPcbID.Text = ""; - txtPassword.Text = ""; + txtSkeletonKey.Text = ""; btnGet.Enabled = false; btnSet.Enabled = false; btnWritePwdFile.Enabled = false; @@ -343,13 +344,13 @@ namespace Xylem.Common.Ui.GenesisToolBox } lblConnectDb.Text = StrNotConnected; lblConnectDb.ForeColor = Color.Red; - txtPassword.Text = ""; + txtSkeletonKey.Text = ""; } private void txtPassword_TextChanged(Object sender, EventArgs e) { //setting is only allowed if genesis is not connected - if (txtPcbID.Text.Length > 7 && txtPassword.Text.Length == 12 + if (txtPcbID.Text.Length > 7 && txtSkeletonKey.Text.Length == 12 && lblConnectPcb.Text == StrNotConnected) { btnSet.Enabled = true;