Password handling: - additional checks, - WriteAndVerifyMeterPwdFile moved from ProductionProceePasswordFile to MeterPwdFile being able to reuse this in the CUST
This commit is contained in:
@@ -12,13 +12,12 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class MeterPwdFile
|
public class MeterPwdFile
|
||||||
{
|
{
|
||||||
public const String StrPasswordFileName = "0\\password";
|
private const String StrPasswordFileName = "0\\password";
|
||||||
private readonly GenesisMeter _genesisMeter;
|
private readonly GenesisMeter _genesisMeter;
|
||||||
private readonly MeterFile _meterFile;
|
private readonly MeterFile _meterFile;
|
||||||
private Byte[] _hashedPasswordFile;
|
private Byte[] _hashedPasswordFile;
|
||||||
//seven passwords are needed for Level 1, 2, 4, 5, 6, 7, Level 3 will be generated
|
//seven passwords are needed for Level 1, 2, 4, 5, 6, 7, Level 3 will be generated
|
||||||
//out of the ProcessorUID.
|
//out of the ProcessorUID.
|
||||||
//private const Int32 PwdLevels = 7;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ctor
|
/// Ctor
|
||||||
@@ -45,7 +44,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile
|
|||||||
/// <remarks date="2020-Dec-08" author="Thomas Wiedebusch">
|
/// <remarks date="2020-Dec-08" author="Thomas Wiedebusch">
|
||||||
/// - Initial.
|
/// - Initial.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public Boolean SetHashedPwdFile(Byte[] hashedPwdFile)
|
private Boolean SetHashedPwdFile(Byte[] hashedPwdFile)
|
||||||
{
|
{
|
||||||
if (MeterPwdDb.PwdFileLength != hashedPwdFile.Length) return false;
|
if (MeterPwdDb.PwdFileLength != hashedPwdFile.Length) return false;
|
||||||
_hashedPasswordFile = new Byte[hashedPwdFile.Length];
|
_hashedPasswordFile = new Byte[hashedPwdFile.Length];
|
||||||
@@ -114,43 +113,94 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Build the password file out of the clear text passwords.
|
/// Build the password file out of the clear text passwords.
|
||||||
/// The Level 3 password is already preset with skeletonKey.
|
/// 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.
|
||||||
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pwdLevels">8 sorted passwords levels: 1, 2, 3, 4, 5, 6, 7, 8</param>
|
/// <param name="passwords">8 sorted passwords levels: 1, 2, skeletonKey, 4, 5, 6, 7, 8</param>
|
||||||
/// <param name="processorUid">unique ID of processor used for password level 3</param>
|
/// <param name="processorUid">unique ID of processor used for password level 3</param>
|
||||||
/// <param name="dbSkeletonKey">SkeletonKey for comparison </param>
|
/// <param name="dbSkeletonKey">SkeletonKey for comparison </param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
/// <remarks date="2019-Mai-15" author="Thomas Wiedebusch">
|
/// <remarks date="2019-Mai-15" author="Thomas Wiedebusch">
|
||||||
/// - Initial.
|
/// - Initial.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <remarks date="2020-Mai-15" author="Roland Drahbesh">
|
/// <remarks date="2020-Mai-15" author="Roland Drabesch">
|
||||||
/// - All 8 password levels will be passed,
|
/// - All 8 password levels will be passed,
|
||||||
/// - Password Level 3 will be compared with data base skeletonKey,
|
/// - Password Level 3 will be compared with data base skeletonKey,
|
||||||
/// - SHA 1 of passwords.
|
/// - SHA 1 of passwords.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <remarks date="2023-Oct-10" author="Thomas Wiedebusch">
|
/// <remarks date="2023-Oct-11" author="Thomas Wiedebusch">
|
||||||
/// - 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.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public Boolean BuildPwdFile(List<Byte[]> pwdLevels, UInt64 processorUid, String dbSkeletonKey)
|
public Boolean BuildPwdFile(List<Byte[]> 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!");
|
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<Byte>();
|
var hashedPasswords = new List<Byte>();
|
||||||
|
|
||||||
foreach (var keyLvl in pwdLevels)
|
foreach (var password in passwords)
|
||||||
{
|
{
|
||||||
using (var sha1 = new System.Security.Cryptography.SHA1Managed())
|
using (var sha1 = new System.Security.Cryptography.SHA1Managed())
|
||||||
{
|
{
|
||||||
var hash = sha1.ComputeHash(keyLvl);
|
var passwordHash = sha1.ComputeHash(password);
|
||||||
ret.AddRange(hash.ToList());
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,12 +234,33 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Write the meter password file, which was generated at <see cref="BuildPwdFile"/> or
|
/// This function combines the write, read back and verification of the password file.
|
||||||
|
/// Write password file <see cref="WriteMeterPwdFile"/> and compare it <see cref="VerifyMeterPwdFile"/>
|
||||||
|
/// The hashed password file can be set or has been preprocessed using the <see cref="BuildPwdFile"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hashedPwdFile">optional hashed password file with constant length as byte array </param>
|
||||||
|
/// <returns>true if password file could be written, read back byte by byte
|
||||||
|
/// and compared being identical </returns>
|
||||||
|
/// <remarks date="2023-Oct-11" author="Thomas Wiedebusch">
|
||||||
|
/// - Initial, imported from ProductionProcessPasswordFile.
|
||||||
|
/// </remarks>
|
||||||
|
public Boolean WriteAndVerifyMeterPwdFile(Byte[] hashedPwdFile = null)
|
||||||
|
{
|
||||||
|
if (UnlockEraseWriteMeterPwdFile())
|
||||||
|
{
|
||||||
|
if (WriteMeterPwdFile(hashedPwdFile))
|
||||||
|
{
|
||||||
|
return VerifyMeterPwdFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Write the meter password file which was generated at <see cref="BuildPwdFile"/> or
|
||||||
/// use the already hashed password file if this is already pre-generated.
|
/// use the already hashed password file if this is already pre-generated.
|
||||||
/// The password file has a unique length of <see cref="MeterPwdDb.PwdFileLength"/>.
|
/// The password file has a unique length of <see cref="MeterPwdDb.PwdFileLength"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="hashedPwdFile">hashed password file with constant length as byte array
|
/// <param name="hashedPwdFile">optional hashed password file with constant length as byte array </param>
|
||||||
/// </param>
|
|
||||||
/// <returns>true if length matches the expectations and password file could be written
|
/// <returns>true if length matches the expectations and password file could be written
|
||||||
/// </returns>
|
/// </returns>
|
||||||
/// <remarks date="2019-Mai-02" author="Thomas Wiedebusch">
|
/// <remarks date="2019-Mai-02" author="Thomas Wiedebusch">
|
||||||
|
|||||||
@@ -16,9 +16,24 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisPwd
|
|||||||
public class MeterPwdDb
|
public class MeterPwdDb
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// password file length is 160 bytes (8 passwords a 20 bytes SHA1)
|
/// 8 passwords needed for clear the text passwords ans the password file
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const Int32 PwdFileLength = 160;
|
public const Int32 PwdLevels = 8;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Each password will generate a 20 bytes hash constant due to SHA1 algorithm
|
||||||
|
/// </summary>
|
||||||
|
private const Int32 HashedPwdLength = 20;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// password file length is 160 bytes (8 passwords a 20 bytes hashed)
|
||||||
|
/// </summary>
|
||||||
|
public const Int32 PwdFileLength = PwdLevels * HashedPwdLength;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 8 passwords needed for the password file
|
||||||
|
/// </summary>
|
||||||
|
public const Int32 ClearTextPwdLength = 12;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Skeleton key index in password list.
|
/// Skeleton key index in password list.
|
||||||
@@ -60,7 +75,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisPwd
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public List<Byte[]> ListOfPasswords = new List<Byte[]>();
|
public List<Byte[]> ListOfPasswords = new List<Byte[]>();
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ident for password server (BSI) can be radioaddress or pcbid
|
/// Ident for password server (BSI) can be radio address or pcbid
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public String PasswordFileIdent { get; set; }
|
public String PasswordFileIdent { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -56,7 +56,10 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisPwd
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pcbId">input the PcbId</param>
|
/// <param name="pcbId">input the PcbId</param>
|
||||||
/// <param name="passwordContainer">output of passwordContainer</param>
|
/// <param name="passwordContainer">output of passwordContainer</param>
|
||||||
|
|||||||
+7
-25
@@ -53,7 +53,7 @@ namespace ProductionUiCordonel.ProductionProcesses.Actions
|
|||||||
Meter.Logout();
|
Meter.Logout();
|
||||||
ret = Meter.Login();
|
ret = Meter.Login();
|
||||||
|
|
||||||
|
|
||||||
//If the password file is written, the "Password" from _pwdContainer is overwritten from Skeleton-key
|
//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
|
//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
|
//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)
|
if (Encoding.UTF8.GetString(_pwdContainer.ListOfPasswords.Last()) == _pwdContainer.Password)
|
||||||
{
|
{
|
||||||
NewStatus("Passwörter schon geschrieben. Prozess wird übersprungen");
|
NewStatus("Passwörter schon geschrieben. Prozess wird übersprungen");
|
||||||
|
|
||||||
currentProcessState = ProductionProcessState.Done;
|
currentProcessState = ProductionProcessState.Done;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//Get the passwords from LuDB, insert the skeleton-kay and generate the password file
|
//Get the passwords from LuDB, insert the skeleton-kay and generate the password file
|
||||||
if (ret)
|
if (ret)
|
||||||
{
|
{
|
||||||
ret = CreatePasswordFile();
|
_meterPwdFile = new MeterPwdFile(Meter);
|
||||||
|
ret = _meterPwdFile.BuildPwdFile(_pwdContainer.ListOfPasswords, 1, _pwdContainer.Skeleton);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateImage(_userControl.CreateFile, ret);
|
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
|
//Write the password file to the Cordonel, read it back and make a binary verification
|
||||||
if (ret)
|
if (ret)
|
||||||
{
|
{
|
||||||
ret = WriteAndVerifyPasswordFileToMeter();
|
if (_meterPwdFile != null)
|
||||||
|
ret = _meterPwdFile.WriteAndVerifyMeterPwdFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateImage(_userControl.WriteToMeter, ret);
|
updateImage(_userControl.WriteToMeter, ret);
|
||||||
@@ -131,25 +133,5 @@ namespace ProductionUiCordonel.ProductionProcesses.Actions
|
|||||||
{
|
{
|
||||||
//clear
|
//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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user