Files
Joseph Doherty fe2a6db786
rust / build / test / clippy / fmt (push) Has been cancelled
Initial project state: .NET reference, design, Rust port (M0+M1), evidence
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>
2026-05-05 06:21:00 -04:00

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);
}
}