using System.Buffers.Binary; namespace AVEVA.Historian.Client.Wcf; internal static class HistorianWcfAuthenticationProtocol { private const uint NativeNtlmNegotiateVersionFlag = 0x0010_0000; public static byte[] WrapValidateClientCredentialToken(bool isFirstRound, ReadOnlySpan token) { byte[] buffer = new byte[checked(1 + sizeof(uint) + token.Length)]; buffer[0] = isFirstRound ? (byte)1 : (byte)0; BinaryPrimitives.WriteUInt32LittleEndian(buffer.AsSpan(1, sizeof(uint)), checked((uint)token.Length)); token.CopyTo(buffer.AsSpan(1 + sizeof(uint))); return buffer; } public static bool TryApplyNativeNtlmNegotiateVersionFlag(Span token) { ReadOnlySpan ntlmSignature = "NTLMSSP\0"u8; if (token.Length < 16 || !token[..ntlmSignature.Length].SequenceEqual(ntlmSignature) || BinaryPrimitives.ReadUInt32LittleEndian(token.Slice(8, sizeof(uint))) != 1) { return false; } uint flags = BinaryPrimitives.ReadUInt32LittleEndian(token.Slice(12, sizeof(uint))); BinaryPrimitives.WriteUInt32LittleEndian( token.Slice(12, sizeof(uint)), flags | NativeNtlmNegotiateVersionFlag); return true; } public static ValidateClientCredentialToken? TryReadWrappedValidateClientCredentialToken(ReadOnlySpan buffer) { if (buffer.Length < 1 + sizeof(uint)) { return null; } uint tokenLength = BinaryPrimitives.ReadUInt32LittleEndian(buffer.Slice(1, sizeof(uint))); if (tokenLength > int.MaxValue || buffer.Length != 1 + sizeof(uint) + (int)tokenLength) { return null; } return new ValidateClientCredentialToken(buffer[0] != 0, buffer[(1 + sizeof(uint))..].ToArray()); } public static ValidateClientCredentialResponse? TryReadValidateClientCredentialResponse(ReadOnlySpan buffer) { if (buffer.Length == 0) { return null; } return new ValidateClientCredentialResponse(buffer[0] != 0, buffer[1..].ToArray()); } } internal sealed record ValidateClientCredentialToken(bool IsFirstRound, byte[] Token); internal sealed record ValidateClientCredentialResponse(bool Continue, byte[] Token);