fe2a6db786
rust / build / test / clippy / fmt (push) Has been cancelled
Layout:
- src/ .NET 10 x64 reference: MxNativeCodec, MxNativeClient,
MxAsbClient, probes, tests, harnesses. Executable spec.
- design/ Architectural plan for the Rust port (M0–M6), error
model, protocol invariants, risks (R1–R16), adversarial
review log (review.md).
- rust/ Rust workspace. M0 skeleton + M1 codec parity.
mxaccess-codec: 215 unit tests + 2 cross-implementation
parity tests (byte-identical against .NET reference).
Other crates are M0 stubs awaiting M2+.
- captures/ Frida + netsh + pcap evidence per CLAUDE.md
("captures are evidence, not throwaway logs").
- analysis/ Decompiled C# (frida/proxy/decompiled-*),
Ghidra exports for native DLLs (`exports/` only —
working state at `projects/` and AVEVA's input
binaries at `input/` are gitignored).
- docs/ Reverse-engineering reference docs.
- tools/ Setup-LiveProbeEnv.ps1 (Infisical credential fetcher),
Compute-Crc.ps1 (.NET parity helper).
- .github/workflows/ Rust CI: fmt + build + test + clippy on Windows.
- LICENSE MIT (Joseph Doherty, 2026).
Verified:
- cargo test --workspace → 217 passed (215 unit + 2 .NET parity), 0 failed
- cargo clippy --workspace -- -D warnings → clean
- cargo fmt --all -- --check → clean
- cargo publish --dry-run -p mxaccess-codec → packages cleanly
Excluded from history (see .gitignore):
- **/bin, **/obj, **/target — build artifacts
- analysis/ghidra/projects/ — Ghidra working state (regenerable)
- analysis/ghidra/input/ — AVEVA proprietary DLLs (vendor IP)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
211 lines
9.7 KiB
C#
211 lines
9.7 KiB
C#
#define TRACE
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.Numerics;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using ArchestrAServices.Common;
|
|
|
|
namespace ArchestrAServices.Contract;
|
|
|
|
public class ClientAuthentication : EncryptionBase
|
|
{
|
|
private RNGCryptoServiceProvider m_Random = new RNGCryptoServiceProvider();
|
|
|
|
public ulong Timeout { get; set; }
|
|
|
|
public ConnectionId connectionId { get; private set; }
|
|
|
|
public bool SecureSessionEstablished { get; private set; }
|
|
|
|
public string ReasonSecureSessionNotEstablished { get; private set; }
|
|
|
|
public BigInteger ClientPrivateKey { get; private set; }
|
|
|
|
public BigInteger ClientPublicKey { get; private set; }
|
|
|
|
public BigInteger ServicePublicKey { get; private set; }
|
|
|
|
public ClientAuthentication()
|
|
{
|
|
Reset();
|
|
ReasonSecureSessionNotEstablished = "Constructed";
|
|
base.DH_passphrase = Constants.GetDHPassphrase();
|
|
base.hashAlgorithm = Constants.hashAlgorithm;
|
|
}
|
|
|
|
public void EstablishSecureSession(string application, string domain, string host, MakeCallToServiceConnect ConnectDelegate, MakeCallToServiceActivate ActivateDelegate)
|
|
{
|
|
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Information, 100, string.Format(CultureInfo.CurrentCulture, "ClientAuth: EstablishSecureSession '{0}', '{1}', '{2}' entering", new object[3] { application, domain, host }));
|
|
SecureSessionEstablished = false;
|
|
InitializeAuthentication();
|
|
PublicKey clientToken = new PublicKey
|
|
{
|
|
ApplicationName = application,
|
|
DomainName = domain,
|
|
HostName = host,
|
|
KeyValue = ClientPublicKey.ToByteArray()
|
|
};
|
|
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Information, 100, string.Format(CultureInfo.CurrentCulture, "ClientAuth: Sending Connect() with client public key of {0} bits", new object[1] { clientToken.KeyValue.Length * 8 }));
|
|
Connection connection = default(Connection);
|
|
ArchestrAResult archestrAResult = ConnectDelegate(out connection, application, domain, host, clientToken);
|
|
if (archestrAResult.ErrorCode == EnumASBFactory.ArchestrAErrorToInt(ArchestrAError.Success))
|
|
{
|
|
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Information, 100, string.Format(CultureInfo.CurrentCulture, "ClientAuth: Received successful response from service Connect() with service public key of {0} bits", new object[1] { connection.serviceKeyField.KeyValue.Length * 8 }));
|
|
connectionId = connection.idField;
|
|
ServicePublicKey = new BigInteger(connection.serviceKeyField.KeyValue);
|
|
byte[] ClientValidationData = null;
|
|
if (ProcessServiceNegotiation(connection.authenticationDataField.AuthenticationData, out ClientValidationData))
|
|
{
|
|
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Information, 100, "ClientAuth: Returning client validation data in call to service ActivateSession()");
|
|
archestrAResult = ActivateDelegate(Authentication: new ConnectionAuthenticationData
|
|
{
|
|
AuthenticationData = ClientValidationData
|
|
}, ConnectionId: connectionId, Timeout: Timeout);
|
|
if (archestrAResult.ErrorCode == EnumASBFactory.ArchestrAErrorToInt(ArchestrAError.Success))
|
|
{
|
|
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Information, 100, string.Format(CultureInfo.CurrentCulture, "ClientAuth: Service returned good result from ActivateSession(), secure session established"));
|
|
SecureSessionEstablished = true;
|
|
ReasonSecureSessionNotEstablished = "Secure session established";
|
|
}
|
|
else
|
|
{
|
|
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Information, 100, string.Format(CultureInfo.CurrentCulture, "ClientAuth: Service returned bad result from ActivateSession(), no secure session established"));
|
|
SvcTrace.DiagException.TraceEvent(TraceEventType.Error, 0, string.Format(CultureInfo.CurrentCulture, "ClientAuth: Service returned bad result from ActivateSession(), no secure session established"));
|
|
SecureSessionEstablished = false;
|
|
ReasonSecureSessionNotEstablished = $"Service ActivateSession() returned ArchestrAError '{EnumASBFactory.IntToArchestrAError(archestrAResult.ErrorCode).ToString()}'";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SvcTrace.DiagControl.TraceEvent(TraceEventType.Warning, 100, "ClientAuth: Service validation data could not be verified, no secure session established");
|
|
SvcTrace.DiagException.TraceEvent(TraceEventType.Error, 0, "ClientAuth: Service validation data could not be verified, no secure session established");
|
|
Reset();
|
|
SecureSessionEstablished = false;
|
|
ReasonSecureSessionNotEstablished = "Service validation data returned from Connect() was invalid";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Information, 100, "ClientAuth: Service returned bad result from Connect(), no secure session established");
|
|
SvcTrace.DiagException.TraceEvent(TraceEventType.Error, 0, "ClientAuth: Service returned bad result from Connect(), no secure session established");
|
|
SecureSessionEstablished = false;
|
|
ReasonSecureSessionNotEstablished = $"Service Connect() returned ArchestrAError '{EnumASBFactory.IntToArchestrAError(archestrAResult.ErrorCode).ToString()}'";
|
|
}
|
|
}
|
|
|
|
public void AbortSession()
|
|
{
|
|
Reset();
|
|
ReasonSecureSessionNotEstablished = "Session Aborted";
|
|
}
|
|
|
|
public void DisconnectSecureSession(MakeCallToServiceDisconnect DisconnectDelegate)
|
|
{
|
|
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Information, 100, "ClientAuth: Calling service Disconnect(), ending secure session");
|
|
DisconnectDelegate(connectionId);
|
|
Reset();
|
|
ReasonSecureSessionNotEstablished = "Session Disconnected normally";
|
|
}
|
|
|
|
private void Reset()
|
|
{
|
|
Timeout = 10000uL;
|
|
connectionId = new ConnectionId
|
|
{
|
|
Id = default(Guid)
|
|
};
|
|
SecureSessionEstablished = false;
|
|
ReasonSecureSessionNotEstablished = "Reset";
|
|
ClientPrivateKey = BigInteger.MinusOne;
|
|
ClientPublicKey = BigInteger.Zero;
|
|
base.NegotiatedKey = new byte[200];
|
|
m_Random.GetBytes(base.NegotiatedKey);
|
|
ServicePublicKey = BigInteger.Zero;
|
|
}
|
|
|
|
private void InitializeAuthentication()
|
|
{
|
|
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Information, 100, string.Format(CultureInfo.CurrentCulture, "ClientAuth: Generating D-H keys with size = {0}", new object[1] { Constants.DH_KeySize }));
|
|
Constants.GenerateKey(Constants.DH_KeySize, out DH_p, out DH_g);
|
|
BigInteger bigInteger = DH_p - new BigInteger(1);
|
|
ClientPrivateKey = new BigInteger(0);
|
|
while (ClientPrivateKey >= bigInteger || ClientPrivateKey <= 0L)
|
|
{
|
|
byte[] array = new byte[Constants.DH_SecretSize / 8];
|
|
m_Random.GetBytes(array);
|
|
ClientPrivateKey = new BigInteger(array);
|
|
}
|
|
ClientPublicKey = BigInteger.ModPow(DH_g, ClientPrivateKey, DH_p);
|
|
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Information, 100, "ClientAuth: Generated Client private key and public key");
|
|
}
|
|
|
|
private bool ProcessServiceNegotiation(byte[] ServiceValidationData, out byte[] ClientValidationData)
|
|
{
|
|
base.NegotiatedKey = Encoding.UTF8.GetBytes(base.DH_passphrase);
|
|
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Information, 100, string.Format(CultureInfo.CurrentCulture, "ClientAuth: Computed negotiated key [{0} {1} {2} {3} .. {4} {5}] {6} bytes", base.NegotiatedKey[0], base.NegotiatedKey[1], base.NegotiatedKey[2], base.NegotiatedKey[3], base.NegotiatedKey[base.NegotiatedKey.Length - 2], base.NegotiatedKey[base.NegotiatedKey.Length - 1], base.NegotiatedKey.Length));
|
|
byte[] array = ServicePublicKey.ToByteArray();
|
|
byte[] array2 = ClientPublicKey.ToByteArray();
|
|
byte[] array3 = new byte[array.Length + array2.Length];
|
|
Array.Copy(array, array3, array.Length);
|
|
Array.Copy(array2, 0, array3, array.Length, array2.Length);
|
|
byte[] array4 = Decrypt(ServiceValidationData, base.NegotiatedKey);
|
|
byte[] array5 = new byte[array4[0] + (array4[1] << 8)];
|
|
for (int i = 0; i < array5.Length; i++)
|
|
{
|
|
array5[i] = 0;
|
|
}
|
|
Array.Copy(array4, 2, array5, 0, array4.Length - 2);
|
|
byte[] bytes = Encoding.UTF8.GetBytes(base.DH_passphrase);
|
|
byte[] array6 = Decrypt(array5, bytes);
|
|
byte[] array7 = new byte[array6[0] + (array6[1] << 8)];
|
|
for (int j = 0; j < array7.Length; j++)
|
|
{
|
|
array7[j] = 0;
|
|
}
|
|
Array.Copy(array6, 2, array7, 0, array6.Length - 2);
|
|
bool flag = array3.Length == array7.Length;
|
|
if (flag)
|
|
{
|
|
for (int k = 0; k < array7.Length; k++)
|
|
{
|
|
if (array3[k] != array7[k])
|
|
{
|
|
flag = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
bool flag2 = false;
|
|
ClientValidationData = null;
|
|
if (flag)
|
|
{
|
|
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Information, 100, "ClientAuth: Decrypted and confirmed service validation data");
|
|
SecureSessionEstablished = true;
|
|
ReasonSecureSessionNotEstablished = "Secure session established";
|
|
array3 = new byte[array2.Length + array.Length + 2];
|
|
int num = array3.Length - 2;
|
|
array3[0] = (byte)((ulong)num & 0xFFuL);
|
|
array3[1] = (byte)(((ulong)num >> 8) & 0xFF);
|
|
Array.Copy(array2, 0, array3, 2, array2.Length);
|
|
Array.Copy(array, 0, array3, array2.Length + 2, array.Length);
|
|
byte[] array8 = Encrypt(array3, bytes);
|
|
byte[] array9 = new byte[array8.Length + 2];
|
|
int num2 = array9.Length - 2;
|
|
array9[0] = (byte)((ulong)num2 & 0xFFuL);
|
|
array9[1] = (byte)(((ulong)num2 >> 8) & 0xFF);
|
|
Array.Copy(array8, 0, array9, 2, array8.Length);
|
|
ClientValidationData = Encrypt(array9, base.NegotiatedKey);
|
|
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Information, 100, "ClientAuth: Generated and encrypted return client validation data");
|
|
return true;
|
|
}
|
|
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Information, 100, "ClientAuth: Service validation data is incorrect, cannot authenticate");
|
|
SvcTrace.DiagException.TraceEvent(TraceEventType.Error, 0, "ClientAuth: Service validation data is incorrect, cannot authenticate");
|
|
SecureSessionEstablished = false;
|
|
ReasonSecureSessionNotEstablished = "Service validation payload incorrect";
|
|
ClientValidationData = ServiceValidationData;
|
|
return false;
|
|
}
|
|
}
|