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>
170 lines
7.0 KiB
C#
170 lines
7.0 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 ServiceAuthentication : EncryptionBase
|
|
{
|
|
private RNGCryptoServiceProvider m_Random = new RNGCryptoServiceProvider();
|
|
|
|
public ConnectionId connectionId { get; private set; }
|
|
|
|
public bool SecureSessionEstablished { get; private set; }
|
|
|
|
public string ReasonSecureSessionNotEstablished { get; private set; }
|
|
|
|
public BigInteger ClientPublicKey { get; private set; }
|
|
|
|
public BigInteger ServicePrivateKey { get; private set; }
|
|
|
|
public BigInteger ServicePublicKey { get; private set; }
|
|
|
|
public ServiceAuthentication()
|
|
{
|
|
Reset();
|
|
ReasonSecureSessionNotEstablished = "Constructed";
|
|
base.DH_passphrase = Constants.GetDHPassphrase();
|
|
base.hashAlgorithm = Constants.hashAlgorithm;
|
|
}
|
|
|
|
public ArchestrAResult ProcessClientConnection(string application, string domain, string host, PublicKey ClientToken, out Connection connectionDescription)
|
|
{
|
|
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Information, 100, string.Format(CultureInfo.CurrentCulture, "SvcAuth: Processing client Connect() call '{0}', '{1}', '{2}'", new object[3] { application, domain, host }));
|
|
connectionId = new ConnectionId
|
|
{
|
|
Id = Guid.NewGuid()
|
|
};
|
|
ClientPublicKey = new BigInteger(ClientToken.KeyValue);
|
|
Constants.GenerateKey(Constants.DH_KeySize, out DH_p, out DH_g);
|
|
BigInteger bigInteger = DH_p - new BigInteger(1);
|
|
ServicePrivateKey = new BigInteger(0);
|
|
while (ServicePrivateKey >= bigInteger || ServicePrivateKey <= 0L)
|
|
{
|
|
byte[] array = new byte[Constants.DH_SecretSize / 8];
|
|
m_Random.GetBytes(array);
|
|
ServicePrivateKey = new BigInteger(array);
|
|
}
|
|
ServicePublicKey = BigInteger.ModPow(DH_g, ServicePrivateKey, DH_p);
|
|
base.NegotiatedKey = Encoding.UTF8.GetBytes(base.DH_passphrase);
|
|
connectionDescription = default(Connection);
|
|
connectionDescription.idField = connectionId;
|
|
connectionDescription.serviceKeyField.ApplicationName = application;
|
|
connectionDescription.serviceKeyField.DomainName = domain;
|
|
connectionDescription.serviceKeyField.HostName = host;
|
|
connectionDescription.serviceKeyField.KeyValue = ServicePublicKey.ToByteArray();
|
|
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Information, 100, string.Format(CultureInfo.CurrentCulture, "SvcAuth: Captured {0}-bit client public key, generated {1}-bit service public key, computed {2}-bit negotiated private key", new object[3]
|
|
{
|
|
ClientToken.KeyValue.Length * 8,
|
|
connectionDescription.serviceKeyField.KeyValue.Length * 8,
|
|
base.NegotiatedKey.Length * 8
|
|
}));
|
|
byte[] array2 = ServicePublicKey.ToByteArray();
|
|
byte[] array3 = ClientPublicKey.ToByteArray();
|
|
byte[] array4 = new byte[array2.Length + array3.Length + 2];
|
|
int num = array4.Length - 2;
|
|
array4[0] = (byte)((ulong)num & 0xFFuL);
|
|
array4[1] = (byte)(((ulong)num >> 8) & 0xFF);
|
|
Array.Copy(array2, 0, array4, 2, array2.Length);
|
|
Array.Copy(array3, 0, array4, array2.Length + 2, array3.Length);
|
|
byte[] bytes = Encoding.UTF8.GetBytes(base.DH_passphrase);
|
|
byte[] array5 = Encrypt(array4, bytes);
|
|
byte[] array6 = new byte[array5.Length + 2];
|
|
int num2 = array6.Length - 2;
|
|
array6[0] = (byte)((ulong)num2 & 0xFFuL);
|
|
array6[1] = (byte)(((ulong)num2 >> 8) & 0xFF);
|
|
Array.Copy(array5, 0, array6, 2, array5.Length);
|
|
byte[] array7 = Encrypt(array6, base.NegotiatedKey);
|
|
connectionDescription.authenticationDataField.AuthenticationData = array7;
|
|
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Information, 100, string.Format(CultureInfo.CurrentCulture, "SvcAuth: Generated {0}-bit service validation data, returning to client", new object[1] { array7.Length * 8 }));
|
|
return ResultFactory.MakeGoodResult();
|
|
}
|
|
|
|
public ArchestrAResult ProcessClientActivate(ConnectionId Id, ConnectionAuthenticationData Authentication, ulong Timeout)
|
|
{
|
|
if (Id.Id != connectionId.Id)
|
|
{
|
|
SvcTrace.DiagException.TraceEvent(TraceEventType.Error, 0, "SvcAuth: Client called ActivateSession() with invalid connection ID, no secure session created");
|
|
Reset();
|
|
ReasonSecureSessionNotEstablished = "ProcessClientActivate called with bad connection id";
|
|
return ResultFactory.MakeResult(ArchestrAError.ApplicationAuthenticationError, 0);
|
|
}
|
|
byte[] array = ClientPublicKey.ToByteArray();
|
|
byte[] array2 = ServicePublicKey.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(Authentication.AuthenticationData, 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;
|
|
}
|
|
}
|
|
}
|
|
ArchestrAResult result = ResultFactory.MakeGoodResult();
|
|
if (flag)
|
|
{
|
|
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Information, 100, string.Format(CultureInfo.CurrentCulture, "SvcAuth: Confirmed {0}-bit client validation data, secure session established", new object[1] { array7.Length * 8 }));
|
|
SecureSessionEstablished = true;
|
|
ReasonSecureSessionNotEstablished = "Secure session established";
|
|
}
|
|
else
|
|
{
|
|
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Information, 100, string.Format(CultureInfo.CurrentCulture, "SvcAuth: Could not confirm {0}-bit client validation data, secure session not established", new object[1] { array7.Length * 8 }));
|
|
SvcTrace.DiagException.TraceEvent(TraceEventType.Error, 0, string.Format(CultureInfo.CurrentCulture, "SvcAuth: Could not confirm {0}-bit client validation data, secure session not established", new object[1] { array7.Length * 8 }));
|
|
SecureSessionEstablished = false;
|
|
ReasonSecureSessionNotEstablished = "Client validation payload incorrect";
|
|
result = ResultFactory.MakeResult(ArchestrAError.ApplicationAuthenticationError, 0);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public ArchestrAResult ProcessClientDisconnect(ConnectionId Id)
|
|
{
|
|
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Information, 100, "SvcAuth: Processing Disconnect() call from client");
|
|
Reset();
|
|
ReasonSecureSessionNotEstablished = "Client disconnected";
|
|
return ResultFactory.MakeGoodResult();
|
|
}
|
|
|
|
private void Reset()
|
|
{
|
|
connectionId = new ConnectionId
|
|
{
|
|
Id = default(Guid)
|
|
};
|
|
SecureSessionEstablished = false;
|
|
ReasonSecureSessionNotEstablished = "Reset";
|
|
ClientPublicKey = BigInteger.Zero;
|
|
ServicePrivateKey = BigInteger.MinusOne;
|
|
ServicePublicKey = BigInteger.Zero;
|
|
base.NegotiatedKey = new byte[200];
|
|
m_Random.GetBytes(base.NegotiatedKey);
|
|
}
|
|
}
|