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

163 lines
7.4 KiB
C#

#define TRACE
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
using System.ServiceModel.Security;
using System.ServiceModel.Security.Tokens;
using System.Xml;
using ArchestrAServices.Common;
namespace ArchestrAServices.Contract;
public class TokenManager
{
public static UserToken RepackageSamlToken(UserToken userAuthentication, byte[] incomingSharedSecret, byte[] outgoingSharedSecret)
{
SamlSecurityToken samlReadToken = ExtractIncomingSamlToken(userAuthentication, incomingSharedSecret);
string tokenId = string.Empty;
return SerializeSamlToken(PackageOutgoingSamlToken(samlReadToken, outgoingSharedSecret, out tokenId), tokenId);
}
public static SamlSecurityToken ExtractIncomingSamlToken(UserToken userAuthentication, byte[] incomingSharedSecret)
{
if (incomingSharedSecret == null)
{
SvcTrace.DiagException.TraceEvent(TraceEventType.Warning, 0, "ExtractIncomingSamlToken: incomingSharedSecret cannot be null");
return null;
}
SamlSecurityToken samlSecurityToken = null;
try
{
SecurityToken item = new BinarySecretSecurityToken(userAuthentication.Password, incomingSharedSecret);
WSSecurityTokenSerializer wSSecurityTokenSerializer = new WSSecurityTokenSerializer(SecurityVersion.WSSecurity11, emitBspRequiredAttributes: false, new SamlSerializer());
XmlReader reader = XmlReader.Create(new MemoryStream(userAuthentication.SamlToken));
if (wSSecurityTokenSerializer.CanReadToken(reader))
{
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Information, 0, "Serializer is capable of reading SAML token from XML");
SecurityTokenResolver tokenResolver = SecurityTokenResolver.CreateDefaultSecurityTokenResolver(new List<SecurityToken> { item }.AsReadOnly(), canMatchLocalId: true);
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Information, 0, "Serializer is reading token");
SecurityToken securityToken = wSSecurityTokenSerializer.ReadToken(reader, tokenResolver);
if (securityToken != null)
{
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Information, 0, "Serializer successfully read a token");
samlSecurityToken = securityToken as SamlSecurityToken;
if (samlSecurityToken == null)
{
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Information, 0, "Serializer could not read a SAML token");
}
}
else
{
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Information, 0, "Serializer could not read the token");
}
}
else
{
SvcTrace.DiagException.TraceEvent(TraceEventType.Warning, 0, "Serializer is NOT capable of reading SAML token to XML");
}
}
catch (Exception ex)
{
SvcTrace.DiagException.TraceEvent(TraceEventType.Warning, 0, string.Format(CultureInfo.CurrentCulture, "Exception deserializing SAML token: {0}", new object[1] { ex.Message }));
Exception innerException = ex.InnerException;
if (innerException != null)
{
SvcTrace.DiagException.TraceEvent(TraceEventType.Warning, 0, string.Format(CultureInfo.CurrentCulture, "--> {0}", new object[1] { innerException.Message }));
Exception innerException2 = innerException.InnerException;
if (innerException2 != null)
{
SvcTrace.DiagException.TraceEvent(TraceEventType.Warning, 0, string.Format(CultureInfo.CurrentCulture, "--> {0}", new object[1] { innerException2.Message }));
}
}
}
return samlSecurityToken;
}
private static SamlSecurityToken PackageOutgoingSamlToken(SamlSecurityToken samlReadToken, byte[] outgoingSharedSecret, out string tokenId)
{
tokenId = string.Empty;
if (samlReadToken == null)
{
SvcTrace.DiagException.TraceEvent(TraceEventType.Warning, 0, "PackageOutgoingSamlToken: samlReadToken cannot be null");
return null;
}
if (outgoingSharedSecret == null)
{
SvcTrace.DiagException.TraceEvent(TraceEventType.Warning, 0, "PackageOutgoingSamlToken: outgoingSharedSecret cannot be null");
return null;
}
SamlSecurityToken result = null;
try
{
SecurityToken securityToken = new BinarySecretSecurityToken(outgoingSharedSecret);
tokenId = securityToken.Id;
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Information, 0, string.Format(CultureInfo.CurrentCulture, "PackageOutgoingSamlToken: creating skic from id='{0}'", new object[1] { securityToken.Id }));
SecurityKeyIdentifierClause securityKeyIdentifierClause = securityToken.CreateKeyIdentifierClause<LocalIdKeyIdentifierClause>();
SecurityKeyIdentifier signingKeyIdentifier = new SecurityKeyIdentifier(securityKeyIdentifierClause);
List<string> list = new List<string>(1);
SecurityKeyIdentifier securityKeyIdentifier = null;
list.Add(SamlConstants.SenderVouches);
new SamlSubject(null, null, null, list, null, securityKeyIdentifier);
SigningCredentials signingCredentials = new SigningCredentials(securityToken.SecurityKeys[0], "http://www.w3.org/2000/09/xmldsig#hmac-sha1", "http://www.w3.org/2000/09/xmldsig#sha1", signingKeyIdentifier);
SamlAssertion assertion = samlReadToken.Assertion;
result = new SamlSecurityToken(new SamlAssertion(assertion.AssertionId, assertion.Issuer, assertion.IssueInstant, assertion.Conditions, assertion.Advice, assertion.Statements)
{
SigningCredentials = signingCredentials
});
}
catch (Exception ex)
{
SvcTrace.DiagException.TraceEvent(TraceEventType.Error, 0, string.Format(CultureInfo.CurrentCulture, "Exception caught in CreateSamlToken: '{0}'", new object[1] { ex.Message }));
}
return result;
}
public static UserToken SerializeSamlToken(SamlSecurityToken samlToken, string tokenId)
{
UserToken result = default(UserToken);
if (samlToken != null)
{
WSSecurityTokenSerializer wSSecurityTokenSerializer = new WSSecurityTokenSerializer(SecurityVersion.WSSecurity11, emitBspRequiredAttributes: true, new SamlSerializer());
if (wSSecurityTokenSerializer.CanWriteToken(samlToken))
{
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Verbose, 0, "Serializer is capable of writing SAML token to XML");
try
{
using (MemoryStream memoryStream = new MemoryStream())
{
XmlWriter writer = XmlWriter.Create(memoryStream);
wSSecurityTokenSerializer.WriteToken(writer, samlToken);
result.IdType = EnumASBFactory.CredentialTypeToInt(CredentialType.SamlToken);
result.SamlToken = memoryStream.ToArray();
result.Password = tokenId;
}
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Verbose, 0, string.Format(CultureInfo.CurrentCulture, "Serialized SAML Token {0}:", new object[1] { samlToken.Id }));
}
catch (Exception ex)
{
SvcTrace.DiagException.TraceEvent(TraceEventType.Error, 0, string.Format(CultureInfo.CurrentCulture, "Exception during serialization: '{0}'", new object[1] { ex.Message }));
Exception innerException = ex.InnerException;
if (innerException != null)
{
SvcTrace.DiagException.TraceEvent(TraceEventType.Error, 0, string.Format(CultureInfo.CurrentCulture, "-> '{0}'", new object[1] { innerException.Message }));
Exception innerException2 = innerException.InnerException;
if (innerException2 != null)
{
SvcTrace.DiagException.TraceEvent(TraceEventType.Error, 0, string.Format(CultureInfo.CurrentCulture, "-> '{0}'", new object[1] { innerException2.Message }));
}
}
}
}
else
{
SvcTrace.DiagDiagnostics.TraceEvent(TraceEventType.Verbose, 0, "Serializer is NOT capable of writing SAML token to XML");
}
}
return result;
}
}