2
0
mirror of https://github.com/esiur/esiur-dotnet.git synced 2026-06-13 22:48:42 +00:00

removed unsafe

This commit is contained in:
2026-06-02 19:28:09 +03:00
parent 24cf15dec7
commit 3dc36149b7
31 changed files with 1155 additions and 338 deletions
@@ -9,10 +9,20 @@ using Esiur.Data.Types;
namespace Esiur.Security.Authority.Providers
{
/// <summary>
/// Implements the "hash" authentication protocol: a SHA3 nonce/challenge-response
/// handshake that mutually proves knowledge of a salted password hash without sending
/// the password, and derives a 512-bit session key. Supports initiator-only, responder-only
/// and dual identity modes. All challenge comparisons are constant-time and remote material
/// is validated, so malformed peer input fails the handshake closed rather than throwing.
/// </summary>
public class PasswordAuthenticationHandler : IAuthenticationHandler
{
public string Protocol => "hash";
// Length, in bytes, of the random nonces exchanged during the handshake.
// Remote nonces are validated against this to reject malformed or weak input.
const int NonceLength = 20;
byte[] _localNonce, _remoteNonce;
byte[] _localSalt, _remoteSalt;
@@ -33,6 +43,18 @@ namespace Esiur.Security.Authority.Providers
public IAuthenticationProvider Provider => _provider;
// Constant-time comparison of two byte arrays. Used for all challenge/MAC
// checks so that, unlike SequenceEqual, the time taken does not depend on how
// many leading bytes matched — closing a timing side channel on the secret.
// Returns false for null or length-mismatched inputs (challenges are fixed size).
static bool FixedTimeEquals(byte[] a, byte[] b)
{
if (a == null || b == null || a.Length != b.Length)
return false;
return Org.BouncyCastle.Utilities.Arrays.FixedTimeEquals(a, b);
}
public static byte[] ComputeSha3(byte[] data, int bitLength = 256)
{
// 1. Initialize the digest (supports 224, 256, 384, 512)
@@ -50,7 +72,22 @@ namespace Esiur.Security.Authority.Providers
public AuthenticationResult Process(object authData)
{
Console.WriteLine($"PasswordAuthenticationHandler: {this.GetHashCode()} Step {_step}, Mode {_mode}, Direction {_direction}");
// Process runs at the trust boundary on data supplied by a remote peer.
// Any malformed input (wrong types, null or short fields) must fail the
// handshake instead of throwing, so the exchange is wrapped to fail closed.
try
{
return ProcessInternal(authData);
}
catch
{
_step = -1;
return new AuthenticationResult(AuthenticationRuling.Failed, null);
}
}
private AuthenticationResult ProcessInternal(object authData)
{
var remoteAuthData = (object[])authData;
var localAuthData = new List<object>();
@@ -97,7 +134,7 @@ namespace Esiur.Security.Authority.Providers
var remoteChallenge = (byte[])remoteAuthData[2];
// prevent reply attack by checking if remote nonce is same as local nonce.
if (_remoteNonce.SequenceEqual(_localNonce))
if (_remoteNonce == null || _remoteNonce.Length != NonceLength || FixedTimeEquals(_remoteNonce, _localNonce))
{
_step = -1;
return new AuthenticationResult(AuthenticationRuling.Failed, null);
@@ -112,7 +149,7 @@ namespace Esiur.Security.Authority.Providers
.ToArray());
// compare remote challenge
if (!remoteChallenge.SequenceEqual(expectedRemoteChallenge))
if (!FixedTimeEquals(remoteChallenge, expectedRemoteChallenge))
{
_step = -1;
return new AuthenticationResult(AuthenticationRuling.Failed, null);
@@ -160,7 +197,7 @@ namespace Esiur.Security.Authority.Providers
_responderIdentity = (string)remoteAuthData[1];
// prevent reply attack by checking if remote nonce is same as local nonce.
if (_remoteNonce.SequenceEqual(_localNonce))
if (_remoteNonce == null || _remoteNonce.Length != NonceLength || FixedTimeEquals(_remoteNonce, _localNonce))
{
_step = -1;
return new AuthenticationResult(AuthenticationRuling.Failed, null);
@@ -203,7 +240,7 @@ namespace Esiur.Security.Authority.Providers
.Concat(_localNonce)
.ToArray());
if (!remoteChallenge.SequenceEqual(expectedRemoteChallenge))
if (!FixedTimeEquals(remoteChallenge, expectedRemoteChallenge))
{
_step = -1;
return new AuthenticationResult(AuthenticationRuling.Failed, null);
@@ -262,7 +299,7 @@ namespace Esiur.Security.Authority.Providers
_remoteSalt = (byte[])remoteAuthData[2];
// prevent reply attack by checking if remote nonce is same as local nonce.
if (_remoteNonce.SequenceEqual(_localNonce))
if (_remoteNonce == null || _remoteNonce.Length != NonceLength || FixedTimeEquals(_remoteNonce, _localNonce))
{
_step = -1;
return new AuthenticationResult(AuthenticationRuling.Failed, null);
@@ -313,7 +350,7 @@ namespace Esiur.Security.Authority.Providers
.Concat(_localNonce)
.ToArray());
if (!remoteChallenge.SequenceEqual(expectedRemoteChallenge))
if (!FixedTimeEquals(remoteChallenge, expectedRemoteChallenge))
{
_step = -1;
return new AuthenticationResult(AuthenticationRuling.Failed, null);
@@ -361,7 +398,7 @@ namespace Esiur.Security.Authority.Providers
// prevent reply attack by checking if remote nonce is same as local nonce.
// @TODO: We can change our localNonce then send it
if (_remoteNonce.SequenceEqual(_localNonce))
if (_remoteNonce == null || _remoteNonce.Length != NonceLength || FixedTimeEquals(_remoteNonce, _localNonce))
{
_step = -1;
return new AuthenticationResult(AuthenticationRuling.Failed, null);
@@ -403,7 +440,7 @@ namespace Esiur.Security.Authority.Providers
.Concat(_localNonce)
.ToArray());
// compare remote challenge
if (!expectedRemoteChallenge.SequenceEqual(remoteChallenge))
if (!FixedTimeEquals(expectedRemoteChallenge, remoteChallenge))
{
_step = -1;
return new AuthenticationResult(AuthenticationRuling.Failed, null);
@@ -442,7 +479,7 @@ namespace Esiur.Security.Authority.Providers
// prevent reply attack by checking if remote nonce is same as local nonce.
// @TODO: We can change our localNonce then send it
if (_remoteNonce.SequenceEqual(_localNonce))
if (_remoteNonce == null || _remoteNonce.Length != NonceLength || FixedTimeEquals(_remoteNonce, _localNonce))
{
_step = -1;
return new AuthenticationResult(AuthenticationRuling.Failed, null);
@@ -490,7 +527,7 @@ namespace Esiur.Security.Authority.Providers
.ToArray());
// compare remote challenge
if (!expectedRemoteChallenge.SequenceEqual(remoteChallenge))
if (!FixedTimeEquals(expectedRemoteChallenge, remoteChallenge))
{
_step = -1;
return new AuthenticationResult(AuthenticationRuling.Failed, null);
@@ -534,7 +571,7 @@ namespace Esiur.Security.Authority.Providers
// prevent reply attack by checking if remote nonce is same as local nonce.
// @TODO: We can change our localNonce then send it
if (_remoteNonce.SequenceEqual(_localNonce))
if (_remoteNonce == null || _remoteNonce.Length != NonceLength || FixedTimeEquals(_remoteNonce, _localNonce))
{
_step = -1;
return new AuthenticationResult(AuthenticationRuling.Failed, null);
@@ -597,7 +634,7 @@ namespace Esiur.Security.Authority.Providers
.ToArray());
// compare remote challenge
if (!expectedRemoteChallenge.SequenceEqual(remoteChallenge))
if (!FixedTimeEquals(expectedRemoteChallenge, remoteChallenge))
{
_step = -1;
return new AuthenticationResult(AuthenticationRuling.Failed, null);
@@ -646,7 +683,7 @@ namespace Esiur.Security.Authority.Providers
string domain,
PasswordAuthenticationProvider provider)
{
_localNonce = Global.GenerateBytes(20);
_localNonce = Global.GenerateBytes(NonceLength);
this._provider = provider;
this._initiatorIdentity = initiatorIdentity;