From 1dd747d460730fb13d41323a24a2149c3dbd95cf Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Sat, 25 Jun 2022 13:36:49 +0200 Subject: [PATCH 1/5] Reduce buffer allocations during NTLM/Negotiate authentication --- .../Interop.NetSecurityNative.cs | 19 ++++++------- .../Interop/Windows/SspiCli/ISSPIInterface.cs | 2 +- .../Interop/Windows/SspiCli/SSPIAuthType.cs | 2 +- .../Windows/SspiCli/SSPISecureChannelType.cs | 2 +- .../Interop/Windows/SspiCli/SSPIWrapper.cs | 2 +- .../Windows/SspiCli/SecuritySafeHandles.cs | 27 ++++++++++--------- .../src/System/Net/NTAuthentication.Common.cs | 20 ++++++++++---- .../Net/Security/NegotiateStreamPal.Unix.cs | 16 ++++++++--- .../Security/NegotiateStreamPal.Windows.cs | 10 +++++-- 9 files changed, 61 insertions(+), 39 deletions(-) diff --git a/src/libraries/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurityNative.cs b/src/libraries/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurityNative.cs index ecb5adceb51b9e..79cde6ed0c8df1 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurityNative.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurityNative.cs @@ -210,10 +210,10 @@ private static unsafe partial Status Wrap( ref GssBuffer outBuffer); [LibraryImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_Unwrap")] - private static partial Status Unwrap( + private static unsafe partial Status Unwrap( out Status minorStatus, SafeGssContextHandle? contextHandle, - byte[] inputBytes, + byte* inputBytes, int offset, int count, ref GssBuffer outBuffer); @@ -231,19 +231,16 @@ internal static unsafe Status WrapBuffer( } } - internal static Status UnwrapBuffer( + internal static unsafe Status UnwrapBuffer( out Status minorStatus, SafeGssContextHandle? contextHandle, - byte[] inputBytes, - int offset, - int count, + ReadOnlySpan inputBytes, ref GssBuffer outBuffer) { - Debug.Assert(inputBytes != null, "inputBytes must be valid value"); - Debug.Assert(offset >= 0 && offset <= inputBytes.Length, "offset must be valid"); - Debug.Assert(count >= 0 && count <= inputBytes.Length, "count must be valid"); - - return Unwrap(out minorStatus, contextHandle, inputBytes, offset, count, ref outBuffer); + fixed (byte* inputBytesPtr = inputBytes) + { + return Unwrap(out minorStatus, contextHandle, inputBytesPtr, 0, inputBytes.Length, ref outBuffer); + } } } } diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/ISSPIInterface.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/ISSPIInterface.cs index a599342f449b41..8d793d976e28c4 100644 --- a/src/libraries/Common/src/Interop/Windows/SspiCli/ISSPIInterface.cs +++ b/src/libraries/Common/src/Interop/Windows/SspiCli/ISSPIInterface.cs @@ -25,7 +25,7 @@ internal interface ISSPIInterface int QueryContextChannelBinding(SafeDeleteContext phContext, Interop.SspiCli.ContextAttribute attribute, out SafeFreeContextBufferChannelBinding refHandle); int QueryContextAttributes(SafeDeleteContext phContext, Interop.SspiCli.ContextAttribute attribute, Span buffer, Type? handleType, out SafeHandle? refHandle); int QuerySecurityContextToken(SafeDeleteContext phContext, out SecurityContextTokenHandle phToken); - int CompleteAuthToken(ref SafeDeleteSslContext? refContext, in SecurityBuffer inputBuffer); + int CompleteAuthToken(ref SafeDeleteSslContext? refContext, in InputSecurityBuffer inputBuffer); int ApplyControlToken(ref SafeDeleteContext? refContext, in SecurityBuffer inputBuffer); } } diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIAuthType.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIAuthType.cs index bb1ea258d7c265..c28f6b99e129d8 100644 --- a/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIAuthType.cs +++ b/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIAuthType.cs @@ -168,7 +168,7 @@ public int QuerySecurityContextToken(SafeDeleteContext phContext, out SecurityCo return GetSecurityContextToken(phContext, out phToken); } - public int CompleteAuthToken(ref SafeDeleteSslContext? refContext, in SecurityBuffer inputBuffer) + public int CompleteAuthToken(ref SafeDeleteSslContext? refContext, in InputSecurityBuffer inputBuffer) { return SafeDeleteContext.CompleteAuthToken(ref refContext, in inputBuffer); } diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/SSPISecureChannelType.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/SSPISecureChannelType.cs index 9c9911d9ff8835..e11557fcc9534e 100644 --- a/src/libraries/Common/src/Interop/Windows/SspiCli/SSPISecureChannelType.cs +++ b/src/libraries/Common/src/Interop/Windows/SspiCli/SSPISecureChannelType.cs @@ -136,7 +136,7 @@ public int QuerySecurityContextToken(SafeDeleteContext phContext, out SecurityCo throw new NotSupportedException(); } - public int CompleteAuthToken(ref SafeDeleteSslContext? refContext, in SecurityBuffer inputBuffer) + public int CompleteAuthToken(ref SafeDeleteSslContext? refContext, in InputSecurityBuffer inputBuffer) { throw new NotSupportedException(); } diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIWrapper.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIWrapper.cs index 3eaa92f19abc00..077c7aed1eb53d 100644 --- a/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIWrapper.cs +++ b/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIWrapper.cs @@ -163,7 +163,7 @@ internal static int AcceptSecurityContext(ISSPIInterface secModule, SafeFreeCred return errorCode; } - internal static int CompleteAuthToken(ISSPIInterface secModule, ref SafeDeleteSslContext? context, in SecurityBuffer inputBuffer) + internal static int CompleteAuthToken(ISSPIInterface secModule, ref SafeDeleteSslContext? context, in InputSecurityBuffer inputBuffer) { int errorCode = secModule.CompleteAuthToken(ref context, in inputBuffer); diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs index 47de5f4e6b5784..8a8945f5dc515c 100644 --- a/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs +++ b/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs @@ -472,9 +472,13 @@ internal static unsafe int InitializeSecurityContext( // Get unmanaged buffer with index 0 as the only one passed into PInvoke. outSecBuffer.size = outUnmanagedBuffer.cbBuffer; outSecBuffer.type = outUnmanagedBuffer.BufferType; - outSecBuffer.token = outSecBuffer.size > 0 ? - new Span((byte*)outUnmanagedBuffer.pvBuffer, outUnmanagedBuffer.cbBuffer).ToArray() : - null; + + if (isSspiAllocated) + { + outSecBuffer.token = outSecBuffer.size > 0 ? + new Span((byte*)outUnmanagedBuffer.pvBuffer, outUnmanagedBuffer.cbBuffer).ToArray() : + null; + } if (inSecBuffers.Count > 1 && inUnmanagedBuffer[1].BufferType == SecurityBufferType.SECBUFFER_EXTRA && inSecBuffers._item1.Type == SecurityBufferType.SECBUFFER_EMPTY) { @@ -952,25 +956,22 @@ private static unsafe int MustRunAcceptSecurityContext_SECURITY( internal static unsafe int CompleteAuthToken( ref SafeDeleteSslContext? refContext, - in SecurityBuffer inSecBuffer) + in InputSecurityBuffer inSecBuffer) { - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, $"refContext = {refContext}, inSecBuffer = {inSecBuffer}"); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, $"refContext = {refContext}"); var inSecurityBufferDescriptor = new Interop.SspiCli.SecBufferDesc(1); int errorCode = (int)Interop.SECURITY_STATUS.InvalidHandle; Interop.SspiCli.SecBuffer inUnmanagedBuffer = default; inSecurityBufferDescriptor.pBuffers = &inUnmanagedBuffer; - fixed (byte* pinnedToken = inSecBuffer.token) + fixed (byte* pinnedToken = inSecBuffer.Token) { - inUnmanagedBuffer.cbBuffer = inSecBuffer.size; - inUnmanagedBuffer.BufferType = inSecBuffer.type; - - // Use the unmanaged token if it's not null; otherwise use the managed buffer. + Debug.Assert(inSecBuffer.UnmanagedToken != null); + inUnmanagedBuffer.cbBuffer = inSecBuffer.Token.Length; + inUnmanagedBuffer.BufferType = inSecBuffer.Type; inUnmanagedBuffer.pvBuffer = - inSecBuffer.unmanagedToken != null ? inSecBuffer.unmanagedToken.DangerousGetHandle() : - inSecBuffer.token == null || inSecBuffer.token.Length == 0 ? IntPtr.Zero : - (IntPtr)(pinnedToken + inSecBuffer.offset); + inSecBuffer.Token.IsEmpty ? IntPtr.Zero : (IntPtr)pinnedToken; Interop.SspiCli.CredHandle contextHandle = refContext != null ? refContext._handle : default; if (refContext == null || refContext.IsInvalid) diff --git a/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs b/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs index cdb7103cdcd8d2..bd1b4ae036a9a0 100644 --- a/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs +++ b/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs @@ -20,6 +20,7 @@ internal sealed partial class NTAuthentication private string? _spn; private int _tokenSize; + private byte[]? _tokenBuffer; private ContextFlagsPal _requestedContextFlags; private ContextFlagsPal _contextFlags; @@ -221,9 +222,10 @@ internal int MakeSignature(byte[] buffer, int offset, int count, [AllowNull] ref internal byte[]? GetOutgoingBlob(ReadOnlySpan incomingBlob, bool throwOnError, out SecurityStatusPal statusCode) { - byte[]? result = new byte[_tokenSize]; + _tokenBuffer ??= _tokenSize == 0 ? Array.Empty() : new byte[_tokenSize]; bool firstTime = _securityContext == null; + int resultBlobLength; try { if (!_isServer) @@ -236,18 +238,19 @@ internal int MakeSignature(byte[] buffer, int offset, int count, [AllowNull] ref _requestedContextFlags, incomingBlob, _channelBinding, - ref result, + ref _tokenBuffer, + out resultBlobLength, ref _contextFlags); if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"SSPIWrapper.InitializeSecurityContext() returns statusCode:0x{((int)statusCode.ErrorCode):x8} ({statusCode})"); if (statusCode.ErrorCode == SecurityStatusPalErrorCode.CompleteNeeded) { - statusCode = NegotiateStreamPal.CompleteAuthToken(ref _securityContext, result); + statusCode = NegotiateStreamPal.CompleteAuthToken(ref _securityContext, _tokenBuffer.AsSpan(0, resultBlobLength)); if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"SSPIWrapper.CompleteAuthToken() returns statusCode:0x{((int)statusCode.ErrorCode):x8} ({statusCode})"); - result = null; + resultBlobLength = 0; } } else @@ -259,7 +262,8 @@ internal int MakeSignature(byte[] buffer, int offset, int count, [AllowNull] ref _requestedContextFlags, incomingBlob, _channelBinding, - ref result, + ref _tokenBuffer, + out resultBlobLength, ref _contextFlags); if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"SSPIWrapper.AcceptSecurityContext() returns statusCode:0x{((int)statusCode.ErrorCode):x8} ({statusCode})"); @@ -284,6 +288,7 @@ internal int MakeSignature(byte[] buffer, int offset, int count, [AllowNull] ref { CloseContext(); _isCompleted = true; + _tokenBuffer = null; if (throwOnError) { throw NegotiateStreamPal.CreateExceptionFromError(statusCode); @@ -297,12 +302,17 @@ internal int MakeSignature(byte[] buffer, int offset, int count, [AllowNull] ref SSPIHandleCache.CacheCredential(_credentialsHandle); } + byte[]? result = + resultBlobLength == 0 || _tokenBuffer == null ? null : + (_tokenBuffer.Length == resultBlobLength ? _tokenBuffer : _tokenBuffer.AsSpan(0, resultBlobLength).ToArray()); + // The return value will tell us correctly if the handshake is over or not if (statusCode.ErrorCode == SecurityStatusPalErrorCode.OK || (_isServer && statusCode.ErrorCode == SecurityStatusPalErrorCode.CompleteNeeded)) { // Success. _isCompleted = true; + _tokenBuffer = null; } else { diff --git a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs index 47daaaf9872f62..aba5087a75284c 100644 --- a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs +++ b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs @@ -76,7 +76,7 @@ private static int GssUnwrap( try { Interop.NetSecurityNative.Status minorStatus; - Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.UnwrapBuffer(out minorStatus, context, buffer, offset, count, ref decryptedBuffer); + Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.UnwrapBuffer(out minorStatus, context, new ReadOnlySpan(buffer, offset, count), ref decryptedBuffer); if (status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE) { throw new Interop.NetSecurityNative.GssApiException(status, minorStatus); @@ -274,11 +274,13 @@ private static SecurityStatusPal EstablishSecurityContext( string? targetName, ContextFlagsPal inFlags, ReadOnlySpan incomingBlob, - ref byte[]? resultBuffer, + out byte[]? resultBuffer, ref ContextFlagsPal outFlags) { bool isNtlmOnly = credential.IsNtlmOnly; + resultBuffer = null; + if (context == null) { if (NetEventSource.Log.IsEnabled()) @@ -354,6 +356,7 @@ internal static SecurityStatusPal InitializeSecurityContext( ReadOnlySpan incomingBlob, ChannelBinding? channelBinding, ref byte[]? resultBlob, + out int resultBlobLength, ref ContextFlagsPal contextFlags) { SafeFreeNegoCredentials negoCredentialsHandle = (SafeFreeNegoCredentials)credentialsHandle; @@ -370,8 +373,9 @@ internal static SecurityStatusPal InitializeSecurityContext( spn, requestedContextFlags, incomingBlob, - ref resultBlob, + out resultBlob, ref contextFlags); + resultBlobLength = resultBlob?.Length ?? 0; return status; } @@ -383,6 +387,7 @@ internal static SecurityStatusPal AcceptSecurityContext( ReadOnlySpan incomingBlob, ChannelBinding? channelBinding, ref byte[] resultBlob, + out int resultBlobLength, ref ContextFlagsPal contextFlags) { if (securityContext == null) @@ -414,6 +419,7 @@ internal static SecurityStatusPal AcceptSecurityContext( contextFlags = ContextFlagsAdapterPal.GetContextFlagsPalFromInterop( (Interop.NetSecurityNative.GssFlags)outputFlags, isServer: true); + resultBlobLength = resultBlob.Length; SecurityStatusPalErrorCode errorCode; if (done) @@ -437,11 +443,13 @@ internal static SecurityStatusPal AcceptSecurityContext( catch (Interop.NetSecurityNative.GssApiException gex) { if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, gex); + resultBlobLength = 0; return new SecurityStatusPal(GetErrorCode(gex), gex); } catch (Exception ex) { if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, ex); + resultBlobLength = 0; return new SecurityStatusPal(SecurityStatusPalErrorCode.InternalError, ex); } } @@ -534,7 +542,7 @@ internal static SafeFreeCredentials AcquireCredentialsHandle(string package, boo internal static SecurityStatusPal CompleteAuthToken( ref SafeDeleteContext? securityContext, - byte[]? incomingBlob) + ReadOnlySpan incomingBlob) { return new SecurityStatusPal(SecurityStatusPalErrorCode.OK); } diff --git a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs index 29603b58d2ce55..44c3ce64df38f6 100644 --- a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs +++ b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs @@ -83,6 +83,7 @@ internal static SecurityStatusPal InitializeSecurityContext( ReadOnlySpan incomingBlob, ChannelBinding? channelBinding, ref byte[]? resultBlob, + out int resultBlobLength, ref ContextFlagsPal contextFlags) { @@ -113,18 +114,20 @@ internal static SecurityStatusPal InitializeSecurityContext( ref outSecurityBuffer, ref outContextFlags); securityContext = sslContext; + Debug.Assert(outSecurityBuffer.offset == 0); resultBlob = outSecurityBuffer.token; + resultBlobLength = outSecurityBuffer.size; contextFlags = ContextFlagsAdapterPal.GetContextFlagsPalFromInterop(outContextFlags); return SecurityStatusAdapterPal.GetSecurityStatusPalFromInterop(winStatus); } internal static SecurityStatusPal CompleteAuthToken( ref SafeDeleteContext? securityContext, - byte[]? incomingBlob) + ReadOnlySpan incomingBlob) { // There is only one SafeDeleteContext type on Windows which is SafeDeleteSslContext so this cast is safe. SafeDeleteSslContext? sslContext = (SafeDeleteSslContext?)securityContext; - var inSecurityBuffer = new SecurityBuffer(incomingBlob, SecurityBufferType.SECBUFFER_TOKEN); + var inSecurityBuffer = new InputSecurityBuffer(incomingBlob, SecurityBufferType.SECBUFFER_TOKEN); Interop.SECURITY_STATUS winStatus = (Interop.SECURITY_STATUS)SSPIWrapper.CompleteAuthToken( GlobalSSPI.SSPIAuth, ref sslContext, @@ -140,6 +143,7 @@ internal static SecurityStatusPal AcceptSecurityContext( ReadOnlySpan incomingBlob, ChannelBinding? channelBinding, ref byte[]? resultBlob, + out int resultBlobLength, ref ContextFlagsPal contextFlags) { InputSecurityBuffers inputBuffers = default; @@ -176,7 +180,9 @@ internal static SecurityStatusPal AcceptSecurityContext( winStatus = Interop.SECURITY_STATUS.InvalidToken; } + Debug.Assert(outSecurityBuffer.offset == 0); resultBlob = outSecurityBuffer.token; + resultBlobLength = outSecurityBuffer.size; securityContext = sslContext; contextFlags = ContextFlagsAdapterPal.GetContextFlagsPalFromInterop(outContextFlags); return SecurityStatusAdapterPal.GetSecurityStatusPalFromInterop(winStatus); From 49c1ed0982ce9121f80ee09fab24248386d31be9 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Sat, 25 Jun 2022 13:46:22 +0200 Subject: [PATCH 2/5] Update ReadWriteAdapter.WriteAsync prototype to use Memory instead of explicit offset/count --- .../System/Net/Security/NegotiateStream.cs | 2 +- .../System/Net/Security/ReadWriteAdapter.cs | 10 ++++---- .../src/System/Net/Security/SslStream.IO.cs | 23 ++++++++----------- .../src/System/Net/StreamFramer.cs | 4 ++-- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs index db3a21ce7b114c..e388afa9dfb1a9 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs @@ -501,7 +501,7 @@ private async Task WriteAsync(ReadOnlyMemory buffer, Cancellat throw new IOException(SR.net_io_encrypt, e); } - await TIOAdapter.WriteAsync(InnerStream, _writeBuffer, 0, encryptedBytes, cancellationToken).ConfigureAwait(false); + await TIOAdapter.WriteAsync(InnerStream, new ReadOnlyMemory(_writeBuffer, 0, encryptedBytes), cancellationToken).ConfigureAwait(false); buffer = buffer.Slice(chunkBytes); } } diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/ReadWriteAdapter.cs b/src/libraries/System.Net.Security/src/System/Net/Security/ReadWriteAdapter.cs index 10c689c1d12b14..14e676f6382176 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/ReadWriteAdapter.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/ReadWriteAdapter.cs @@ -11,7 +11,7 @@ internal interface IReadWriteAdapter { static abstract ValueTask ReadAsync(Stream stream, Memory buffer, CancellationToken cancellationToken); static abstract ValueTask ReadAtLeastAsync(Stream stream, Memory buffer, int minimumBytes, bool throwOnEndOfStream, CancellationToken cancellationToken); - static abstract ValueTask WriteAsync(Stream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken); + static abstract ValueTask WriteAsync(Stream stream, ReadOnlyMemory buffer, CancellationToken cancellationToken); static abstract Task FlushAsync(Stream stream, CancellationToken cancellationToken); static abstract Task WaitAsync(TaskCompletionSource waiter); } @@ -24,8 +24,8 @@ public static ValueTask ReadAsync(Stream stream, Memory buffer, Cance public static ValueTask ReadAtLeastAsync(Stream stream, Memory buffer, int minimumBytes, bool throwOnEndOfStream, CancellationToken cancellationToken) => stream.ReadAtLeastAsync(buffer, minimumBytes, throwOnEndOfStream, cancellationToken); - public static ValueTask WriteAsync(Stream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken) => - stream.WriteAsync(new ReadOnlyMemory(buffer, offset, count), cancellationToken); + public static ValueTask WriteAsync(Stream stream, ReadOnlyMemory buffer, CancellationToken cancellationToken) => + stream.WriteAsync(buffer, cancellationToken); public static Task FlushAsync(Stream stream, CancellationToken cancellationToken) => stream.FlushAsync(cancellationToken); @@ -40,9 +40,9 @@ public static ValueTask ReadAsync(Stream stream, Memory buffer, Cance public static ValueTask ReadAtLeastAsync(Stream stream, Memory buffer, int minimumBytes, bool throwOnEndOfStream, CancellationToken cancellationToken) => new ValueTask(stream.ReadAtLeast(buffer.Span, minimumBytes, throwOnEndOfStream)); - public static ValueTask WriteAsync(Stream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken) + public static ValueTask WriteAsync(Stream stream, ReadOnlyMemory buffer, CancellationToken cancellationToken) { - stream.Write(buffer, offset, count); + stream.Write(buffer.Span); return default; } diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.IO.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.IO.cs index 3b01821966e9d0..4917f87d36bd22 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.IO.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.IO.cs @@ -196,7 +196,7 @@ private async Task RenegotiateAsync(CancellationToken cancellationTo if (nextmsg is { Length: > 0 }) { - await TIOAdapter.WriteAsync(InnerStream, nextmsg, 0, nextmsg.Length, cancellationToken).ConfigureAwait(false); + await TIOAdapter.WriteAsync(InnerStream, nextmsg, cancellationToken).ConfigureAwait(false); await TIOAdapter.FlushAsync(InnerStream, cancellationToken).ConfigureAwait(false); } @@ -219,7 +219,7 @@ private async Task RenegotiateAsync(CancellationToken cancellationTo message = await ReceiveBlobAsync(cancellationToken).ConfigureAwait(false); if (message.Size > 0) { - await TIOAdapter.WriteAsync(InnerStream, message.Payload!, 0, message.Size, cancellationToken).ConfigureAwait(false); + await TIOAdapter.WriteAsync(InnerStream, new ReadOnlyMemory(message.Payload!, 0, message.Size), cancellationToken).ConfigureAwait(false); await TIOAdapter.FlushAsync(InnerStream, cancellationToken).ConfigureAwait(false); } } @@ -264,7 +264,7 @@ private async Task ForceAuthenticationAsync(bool receiveFirst, byte[ message = NextMessage(reAuthenticationData); if (message.Size > 0) { - await TIOAdapter.WriteAsync(InnerStream, message.Payload!, 0, message.Size, cancellationToken).ConfigureAwait(false); + await TIOAdapter.WriteAsync(InnerStream, new ReadOnlyMemory(message.Payload!, 0, message.Size), cancellationToken).ConfigureAwait(false); await TIOAdapter.FlushAsync(InnerStream, cancellationToken).ConfigureAwait(false); if (NetEventSource.Log.IsEnabled()) NetEventSource.Log.SentFrame(this, message.Payload); @@ -291,28 +291,25 @@ private async Task ForceAuthenticationAsync(bool receiveFirst, byte[ { message = await ReceiveBlobAsync(cancellationToken).ConfigureAwait(false); - byte[]? payload = null; - int size = 0; + ReadOnlyMemory payload = default; if (message.Size > 0) { - payload = message.Payload; - size = message.Size; + payload = new ReadOnlyMemory(message.Payload, 0, message.Size); } else if (message.Failed && (_lastFrame.Header.Type == TlsContentType.Handshake || _lastFrame.Header.Type == TlsContentType.ChangeCipherSpec)) { // If we failed without OS sending out alert, inject one here to be consistent across platforms. payload = TlsFrameHelper.CreateAlertFrame(_lastFrame.Header.Version, TlsAlertDescription.ProtocolVersion); - size = payload.Length; } - if (payload != null && size > 0) + if (!payload.IsEmpty) { // If there is message send it out even if call failed. It may contain TLS Alert. - await TIOAdapter.WriteAsync(InnerStream, payload!, 0, size, cancellationToken).ConfigureAwait(false); + await TIOAdapter.WriteAsync(InnerStream, payload, cancellationToken).ConfigureAwait(false); await TIOAdapter.FlushAsync(InnerStream, cancellationToken).ConfigureAwait(false); if (NetEventSource.Log.IsEnabled()) - NetEventSource.Log.SentFrame(this, payload); + NetEventSource.Log.SentFrame(this, payload.Span); } if (message.Failed) @@ -592,7 +589,7 @@ private ValueTask WriteSingleChunk(ReadOnlyMemory buffer, Canc return ValueTask.FromException(ExceptionDispatchInfo.SetCurrentStackTrace(new IOException(SR.net_io_encrypt, SslStreamPal.GetException(status)))); } - ValueTask t = TIOAdapter.WriteAsync(InnerStream, outBuffer, 0, encryptedBytes, cancellationToken); + ValueTask t = TIOAdapter.WriteAsync(InnerStream, new ReadOnlyMemory(outBuffer, 0, encryptedBytes), cancellationToken); if (t.IsCompletedSuccessfully) { ArrayPool.Shared.Return(rentedBuffer); @@ -626,7 +623,7 @@ async ValueTask WaitAndWriteAsync(ReadOnlyMemory buffer, Task waitTask, by } else if (status.ErrorCode == SecurityStatusPalErrorCode.OK) { - await TIOAdapter.WriteAsync(InnerStream, outBuffer, 0, encryptedBytes, cancellationToken).ConfigureAwait(false); + await TIOAdapter.WriteAsync(InnerStream, new ReadOnlyMemory(outBuffer, 0, encryptedBytes), cancellationToken).ConfigureAwait(false); } else { diff --git a/src/libraries/System.Net.Security/src/System/Net/StreamFramer.cs b/src/libraries/System.Net.Security/src/System/Net/StreamFramer.cs index 3dc39ae065786b..6689c01fe8193d 100644 --- a/src/libraries/System.Net.Security/src/System/Net/StreamFramer.cs +++ b/src/libraries/System.Net.Security/src/System/Net/StreamFramer.cs @@ -72,10 +72,10 @@ public async Task WriteMessageAsync(Stream stream, byte[] message, Can _writeHeader.PayloadSize = message.Length; _writeHeader.CopyTo(_writeHeaderBuffer, 0); - await TAdapter.WriteAsync(stream, _writeHeaderBuffer, 0, _writeHeaderBuffer.Length, cancellationToken).ConfigureAwait(false); + await TAdapter.WriteAsync(stream, _writeHeaderBuffer, cancellationToken).ConfigureAwait(false); if (message.Length != 0) { - await TAdapter.WriteAsync(stream, message, 0, message.Length, cancellationToken).ConfigureAwait(false); + await TAdapter.WriteAsync(stream, message, cancellationToken).ConfigureAwait(false); } } } From 2fe44914eb83ac88a2a137e0661299d5d5073bd3 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Sat, 25 Jun 2022 14:18:06 +0200 Subject: [PATCH 3/5] Spanify NTAuthentication.Decrypt and avoid couple of offset/count checks --- .../src/System/Net/NTAuthentication.Common.cs | 4 +- .../System/Net/NTAuthentication.Managed.cs | 2 +- .../Net/Security/NegotiateStreamPal.Unix.cs | 35 +--- .../Security/NegotiateStreamPal.Windows.cs | 156 ++++++++++-------- .../System/Net/Security/NegotiateStream.cs | 6 +- 5 files changed, 98 insertions(+), 105 deletions(-) diff --git a/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs b/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs index bd1b4ae036a9a0..44020f1fd90a51 100644 --- a/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs +++ b/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs @@ -345,13 +345,11 @@ internal int Encrypt(ReadOnlySpan buffer, [NotNull] ref byte[]? output, ui sequenceNumber); } - internal int Decrypt(byte[] payload, int offset, int count, out int newOffset, uint expectedSeqNumber) + internal int Decrypt(Span payload, out int newOffset, uint expectedSeqNumber) { return NegotiateStreamPal.Decrypt( _securityContext!, payload, - offset, - count, (_contextFlags & ContextFlagsPal.Confidentiality) != 0, IsNTLM, out newOffset, diff --git a/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs b/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs index 04b8bd8f2270b2..102cbbb6523866 100644 --- a/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs +++ b/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs @@ -1008,7 +1008,7 @@ internal int Encrypt(ReadOnlySpan buffer, [NotNull] ref byte[]? output, ui throw new PlatformNotSupportedException(); } - internal int Decrypt(byte[] payload, int offset, int count, out int newOffset, uint expectedSeqNumber) + internal int Decrypt(Span payload, out int newOffset, uint expectedSeqNumber) { throw new PlatformNotSupportedException(); } diff --git a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs index aba5087a75284c..4e5d8044256001 100644 --- a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs +++ b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs @@ -64,25 +64,20 @@ private static byte[] GssWrap( private static int GssUnwrap( SafeGssContextHandle? context, - byte[] buffer, - int offset, - int count) + Span buffer) { - Debug.Assert((buffer != null) && (buffer.Length > 0), "Invalid input buffer passed to Decrypt"); - Debug.Assert((offset >= 0) && (offset <= buffer.Length), "Invalid input offset passed to Decrypt"); - Debug.Assert((count >= 0) && (count <= (buffer.Length - offset)), "Invalid input count passed to Decrypt"); - Interop.NetSecurityNative.GssBuffer decryptedBuffer = default(Interop.NetSecurityNative.GssBuffer); try { Interop.NetSecurityNative.Status minorStatus; - Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.UnwrapBuffer(out minorStatus, context, new ReadOnlySpan(buffer, offset, count), ref decryptedBuffer); + Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.UnwrapBuffer(out minorStatus, context, buffer, ref decryptedBuffer); if (status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE) { throw new Interop.NetSecurityNative.GssApiException(status, minorStatus); } - return decryptedBuffer.Copy(buffer, offset); + decryptedBuffer.Span.CopyTo(buffer); + return decryptedBuffer.Span.Length; } finally { @@ -576,28 +571,14 @@ internal static int Encrypt( internal static int Decrypt( SafeDeleteContext securityContext, - byte[]? buffer, - int offset, - int count, + Span buffer, bool isConfidential, bool isNtlm, out int newOffset, uint sequenceNumber) { - if (offset < 0 || offset > (buffer == null ? 0 : buffer.Length)) - { - Debug.Fail("Argument 'offset' out of range"); - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - if (count < 0 || count > (buffer == null ? 0 : buffer.Length - offset)) - { - Debug.Fail("Argument 'count' out of range."); - throw new ArgumentOutOfRangeException(nameof(count)); - } - - newOffset = offset; - return GssUnwrap(((SafeDeleteNegoContext)securityContext).GssContext, buffer!, offset, count); + newOffset = 0; + return GssUnwrap(((SafeDeleteNegoContext)securityContext).GssContext, buffer); } internal static int VerifySignature(SafeDeleteContext securityContext, byte[] buffer, int offset, int count) @@ -614,7 +595,7 @@ internal static int VerifySignature(SafeDeleteContext securityContext, byte[] bu throw new ArgumentOutOfRangeException(nameof(count)); } - return GssUnwrap(((SafeDeleteNegoContext)securityContext).GssContext, buffer!, offset, count); + return GssUnwrap(((SafeDeleteNegoContext)securityContext).GssContext, buffer.AsSpan(offset, count)); } internal static int MakeSignature(SafeDeleteContext securityContext, byte[] buffer, int offset, int count, [AllowNull] ref byte[] output) diff --git a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs index 44c3ce64df38f6..3597510dabeb94 100644 --- a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs +++ b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs @@ -366,111 +366,125 @@ internal static int Encrypt( return resultSize + 4; } - internal static int Decrypt( + internal static unsafe int Decrypt( SafeDeleteContext securityContext, - byte[]? buffer, - int offset, - int count, + Span buffer, bool isConfidential, bool isNtlm, out int newOffset, uint sequenceNumber) { - if (offset < 0 || offset > (buffer == null ? 0 : buffer.Length)) - { - Debug.Fail("Argument 'offset' out of range."); - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - if (count < 0 || count > (buffer == null ? 0 : buffer.Length - offset)) - { - Debug.Fail("Argument 'count' out of range."); - throw new ArgumentOutOfRangeException(nameof(count)); - } - if (isNtlm) { - return DecryptNtlm(securityContext, buffer, offset, count, isConfidential, out newOffset, sequenceNumber); + return DecryptNtlm(securityContext, buffer, isConfidential, out newOffset, sequenceNumber); } // // Kerberos and up // - TwoSecurityBuffers buffers = default; - var securityBuffer = MemoryMarshal.CreateSpan(ref buffers._item0, 2); - securityBuffer[0] = new SecurityBuffer(buffer, offset, count, SecurityBufferType.SECBUFFER_STREAM); - securityBuffer[1] = new SecurityBuffer(0, SecurityBufferType.SECBUFFER_DATA); + fixed (byte* bufferPtr = buffer) + { + Interop.SspiCli.SecBuffer* unmanagedBuffer = stackalloc Interop.SspiCli.SecBuffer[2]; + Interop.SspiCli.SecBuffer* streamBuffer = &unmanagedBuffer[0]; + Interop.SspiCli.SecBuffer* dataBuffer = &unmanagedBuffer[1]; + streamBuffer->BufferType = SecurityBufferType.SECBUFFER_STREAM; + streamBuffer->pvBuffer = (IntPtr)bufferPtr; + streamBuffer->cbBuffer = buffer.Length; + dataBuffer->BufferType = SecurityBufferType.SECBUFFER_DATA; + dataBuffer->pvBuffer = IntPtr.Zero; + dataBuffer->cbBuffer = 0; + + Interop.SspiCli.SecBufferDesc sdcInOut = new Interop.SspiCli.SecBufferDesc(2) + { + pBuffers = unmanagedBuffer + }; - int errorCode = isConfidential ? - SSPIWrapper.DecryptMessage(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, sequenceNumber) : - SSPIWrapper.VerifySignature(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, sequenceNumber); + int errorCode = isConfidential ? + GlobalSSPI.SSPIAuth.DecryptMessage(securityContext, ref sdcInOut, sequenceNumber) : + GlobalSSPI.SSPIAuth.VerifySignature(securityContext, ref sdcInOut, sequenceNumber); - if (errorCode != 0) - { - Exception e = new Win32Exception(errorCode); - if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, e); - throw e; - } + if (errorCode != 0) + { + Exception e = new Win32Exception(errorCode); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, e); + throw new Win32Exception(errorCode); + } - if (securityBuffer[1].type != SecurityBufferType.SECBUFFER_DATA) - { - throw new InternalException(securityBuffer[1].type); - } + if (dataBuffer->BufferType != SecurityBufferType.SECBUFFER_DATA) + { + throw new InternalException(dataBuffer->BufferType); + } - newOffset = securityBuffer[1].offset; - return securityBuffer[1].size; + Debug.Assert((nint)dataBuffer->pvBuffer >= (nint)bufferPtr); + Debug.Assert((nint)dataBuffer->pvBuffer + dataBuffer->cbBuffer <= (nint)bufferPtr + buffer.Length); + newOffset = (int)((byte*)dataBuffer->pvBuffer - bufferPtr); + return dataBuffer->cbBuffer; + } } - private static int DecryptNtlm( + private static unsafe int DecryptNtlm( SafeDeleteContext securityContext, - byte[]? buffer, - int offset, - int count, + Span buffer, bool isConfidential, out int newOffset, uint sequenceNumber) { - const int ntlmSignatureLength = 16; + const int NtlmSignatureLength = 16; + // For the most part the arguments are verified in Decrypt(). - if (count < ntlmSignatureLength) + if (buffer.Length < NtlmSignatureLength) { Debug.Fail("Argument 'count' out of range."); - throw new ArgumentOutOfRangeException(nameof(count)); + throw new Win32Exception((int)Interop.SECURITY_STATUS.InvalidToken); } - TwoSecurityBuffers buffers = default; - var securityBuffer = MemoryMarshal.CreateSpan(ref buffers._item0, 2); - securityBuffer[0] = new SecurityBuffer(buffer, offset, ntlmSignatureLength, SecurityBufferType.SECBUFFER_TOKEN); - securityBuffer[1] = new SecurityBuffer(buffer, offset + ntlmSignatureLength, count - ntlmSignatureLength, SecurityBufferType.SECBUFFER_DATA); + fixed (byte* bufferPtr = buffer) + { + SecurityBufferType realDataType = SecurityBufferType.SECBUFFER_DATA; + Interop.SspiCli.SecBuffer* unmanagedBuffer = stackalloc Interop.SspiCli.SecBuffer[2]; + Interop.SspiCli.SecBuffer* tokenBuffer = &unmanagedBuffer[0]; + Interop.SspiCli.SecBuffer* dataBuffer = &unmanagedBuffer[1]; + tokenBuffer->BufferType = SecurityBufferType.SECBUFFER_TOKEN; + tokenBuffer->pvBuffer = (IntPtr)bufferPtr; + tokenBuffer->cbBuffer = NtlmSignatureLength; + dataBuffer->BufferType = SecurityBufferType.SECBUFFER_DATA; + dataBuffer->pvBuffer = (IntPtr)(bufferPtr + NtlmSignatureLength); + dataBuffer->cbBuffer = buffer.Length - NtlmSignatureLength; + + Interop.SspiCli.SecBufferDesc sdcInOut = new Interop.SspiCli.SecBufferDesc(2) + { + pBuffers = unmanagedBuffer + }; + int errorCode; - int errorCode; - SecurityBufferType realDataType = SecurityBufferType.SECBUFFER_DATA; + if (isConfidential) + { + errorCode = GlobalSSPI.SSPIAuth.DecryptMessage(securityContext, ref sdcInOut, sequenceNumber); + } + else + { + realDataType |= SecurityBufferType.SECBUFFER_READONLY; + dataBuffer->BufferType = realDataType; + errorCode = GlobalSSPI.SSPIAuth.VerifySignature(securityContext, ref sdcInOut, sequenceNumber); + } - if (isConfidential) - { - errorCode = SSPIWrapper.DecryptMessage(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, sequenceNumber); - } - else - { - realDataType |= SecurityBufferType.SECBUFFER_READONLY; - securityBuffer[1].type = realDataType; - errorCode = SSPIWrapper.VerifySignature(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, sequenceNumber); - } + if (errorCode != 0) + { + Exception e = new Win32Exception(errorCode); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, e); + throw new Win32Exception(errorCode); + } - if (errorCode != 0) - { - Exception e = new Win32Exception(errorCode); - if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, e); - throw new Win32Exception(errorCode); - } + if (dataBuffer->BufferType != realDataType) + { + throw new InternalException(dataBuffer->BufferType); + } - if (securityBuffer[1].type != realDataType) - { - throw new InternalException(securityBuffer[1].type); + Debug.Assert((nint)dataBuffer->pvBuffer >= (nint)bufferPtr); + Debug.Assert((nint)dataBuffer->pvBuffer + dataBuffer->cbBuffer <= (nint)bufferPtr + buffer.Length); + newOffset = (int)((byte*)dataBuffer->pvBuffer - bufferPtr); + return dataBuffer->cbBuffer; } - - newOffset = securityBuffer[1].offset; - return securityBuffer[1].size; } } } diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs index e388afa9dfb1a9..5ebd90b89a7ab9 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs @@ -394,7 +394,7 @@ private async ValueTask ReadAsync(Memory buffer, Cancella // Decrypt into internal buffer, change "readBytes" to count now _Decrypted Bytes_ // Decrypted data start from zero offset, the size can be shrunk after decryption. - _readBufferCount = readBytes = DecryptData(_readBuffer!, 0, readBytes, out _readBufferOffset); + _readBufferCount = readBytes = DecryptData(_readBuffer.AsSpan(0, readBytes), out _readBufferOffset); if (readBytes == 0 && buffer.Length != 0) { // Read again. @@ -965,14 +965,14 @@ private int EncryptData(ReadOnlySpan buffer, [NotNull] ref byte[]? outBuff return _context.Encrypt(buffer, ref outBuffer, _writeSequenceNumber); } - private int DecryptData(byte[] buffer, int offset, int count, out int newOffset) + private int DecryptData(Span buffer, out int newOffset) { Debug.Assert(_context != null); ThrowIfFailed(authSuccessCheck: true); // SSPI seems to ignore this sequence number. ++_readSequenceNumber; - return _context.Decrypt(buffer, offset, count, out newOffset, _readSequenceNumber); + return _context.Decrypt(buffer, out newOffset, _readSequenceNumber); } private static void ThrowCredentialException(long error) From 3b4aff60603037afcd36f7fc1a164cdbbf966159 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Sat, 25 Jun 2022 14:57:17 +0200 Subject: [PATCH 4/5] Spanify NegotiateStreamPal.VerifySignature/MakeSignature. Remove indirect Encrypt/Decrypt layer from SSPIWrapper, it is unnecessarily cumbersome to use and SslStreamPal already migrated away from it. --- .../Interop/Windows/SspiCli/SSPIWrapper.cs | 141 ----------- .../src/System/Net/NTAuthentication.Common.cs | 8 +- .../Net/Security/NegotiateStreamPal.Unix.cs | 31 ++- .../Security/NegotiateStreamPal.Windows.cs | 234 +++++++++--------- .../Net/Security/SecurityBuffer.Windows.cs | 21 -- .../Mail/SmtpNegotiateAuthenticationModule.cs | 4 +- .../tests/UnitTests/NTAuthenticationTests.cs | 4 +- 7 files changed, 149 insertions(+), 294 deletions(-) diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIWrapper.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIWrapper.cs index 077c7aed1eb53d..4dbd2c57f27771 100644 --- a/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIWrapper.cs +++ b/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIWrapper.cs @@ -186,147 +186,6 @@ public static int QuerySecurityContextToken(ISSPIInterface secModule, SafeDelete return secModule.QuerySecurityContextToken(context, out token); } - public static int EncryptMessage(ISSPIInterface secModule, SafeDeleteContext context, Span input, uint sequenceNumber) - { - return EncryptDecryptHelper(OP.Encrypt, secModule, context, input, sequenceNumber); - } - - public static int DecryptMessage(ISSPIInterface secModule, SafeDeleteContext context, Span input, uint sequenceNumber) - { - return EncryptDecryptHelper(OP.Decrypt, secModule, context, input, sequenceNumber); - } - - internal static int MakeSignature(ISSPIInterface secModule, SafeDeleteContext context, Span input, uint sequenceNumber) - { - return EncryptDecryptHelper(OP.MakeSignature, secModule, context, input, sequenceNumber); - } - - public static int VerifySignature(ISSPIInterface secModule, SafeDeleteContext context, Span input, uint sequenceNumber) - { - return EncryptDecryptHelper(OP.VerifySignature, secModule, context, input, sequenceNumber); - } - - private enum OP - { - Encrypt = 1, - Decrypt, - MakeSignature, - VerifySignature - } - - [StructLayout(LayoutKind.Sequential)] - private ref struct ThreeByteArrays - { - public const int NumItems = 3; - internal byte[] _item0; - private byte[] _item1; - private byte[] _item2; - } - - private static unsafe int EncryptDecryptHelper(OP op, ISSPIInterface secModule, SafeDeleteContext context, Span input, uint sequenceNumber) - { - Debug.Assert(Enum.IsDefined(op), $"Unknown op: {op}"); - Debug.Assert(input.Length <= 3, "The below logic only works for 3 or fewer buffers."); - - Interop.SspiCli.SecBufferDesc sdcInOut = new Interop.SspiCli.SecBufferDesc(input.Length); - Span unmanagedBuffer = stackalloc Interop.SspiCli.SecBuffer[input.Length]; - unmanagedBuffer.Clear(); - - fixed (Interop.SspiCli.SecBuffer* unmanagedBufferPtr = unmanagedBuffer) - fixed (byte* pinnedBuffer0 = input.Length > 0 ? input[0].token : null) - fixed (byte* pinnedBuffer1 = input.Length > 1 ? input[1].token : null) - fixed (byte* pinnedBuffer2 = input.Length > 2 ? input[2].token : null) - { - sdcInOut.pBuffers = unmanagedBufferPtr; - - ThreeByteArrays byteArrayStruct = default; - Span buffers = MemoryMarshal.CreateSpan(ref byteArrayStruct._item0!, ThreeByteArrays.NumItems).Slice(0, input.Length); - - for (int i = 0; i < input.Length; i++) - { - ref readonly SecurityBuffer iBuffer = ref input[i]; - unmanagedBuffer[i].cbBuffer = iBuffer.size; - unmanagedBuffer[i].BufferType = iBuffer.type; - if (iBuffer.token == null || iBuffer.token.Length == 0) - { - unmanagedBuffer[i].pvBuffer = IntPtr.Zero; - } - else - { - unmanagedBuffer[i].pvBuffer = Marshal.UnsafeAddrOfPinnedArrayElement(iBuffer.token, iBuffer.offset); - buffers[i] = iBuffer.token; - } - } - - // The result is written in the input Buffer passed as type=BufferType.Data. - int errorCode = op switch - { - OP.Encrypt => secModule.EncryptMessage(context, ref sdcInOut, sequenceNumber), - OP.Decrypt => secModule.DecryptMessage(context, ref sdcInOut, sequenceNumber), - OP.MakeSignature => secModule.MakeSignature(context, ref sdcInOut, sequenceNumber), - _ /* OP.VerifySignature */ => secModule.VerifySignature(context, ref sdcInOut, sequenceNumber), - }; - - // Marshalling back returned sizes / data. - for (int i = 0; i < input.Length; i++) - { - ref SecurityBuffer iBuffer = ref input[i]; - iBuffer.size = unmanagedBuffer[i].cbBuffer; - iBuffer.type = unmanagedBuffer[i].BufferType; - - if (iBuffer.size == 0) - { - iBuffer.offset = 0; - iBuffer.token = null; - } - else - { - - // Find the buffer this is inside of. Usually they all point inside buffer 0. - int j; - for (j = 0; j < input.Length; j++) - { - if (buffers[j] != null) - { - checked - { - byte* bufferAddress = (byte*)Marshal.UnsafeAddrOfPinnedArrayElement(buffers[j], 0); - if ((byte*)unmanagedBuffer[i].pvBuffer >= bufferAddress && - (byte*)unmanagedBuffer[i].pvBuffer + iBuffer.size <= bufferAddress + buffers[j].Length) - { - iBuffer.offset = (int)((byte*)unmanagedBuffer[i].pvBuffer - bufferAddress); - iBuffer.token = buffers[j]; - break; - } - } - } - } - - if (j >= input.Length) - { - Debug.Fail("Output buffer out of range."); - iBuffer.size = 0; - iBuffer.offset = 0; - iBuffer.token = null; - } - } - - // Backup validate the new sizes. - Debug.Assert(iBuffer.offset >= 0 && iBuffer.offset <= (iBuffer.token == null ? 0 : iBuffer.token.Length), $"'offset' out of range. [{iBuffer.offset}]"); - Debug.Assert(iBuffer.size >= 0 && iBuffer.size <= (iBuffer.token == null ? 0 : iBuffer.token.Length - iBuffer.offset), $"'size' out of range. [{iBuffer.size}]"); - } - - if (NetEventSource.Log.IsEnabled() && errorCode != 0) - { - NetEventSource.Error(null, errorCode == Interop.SspiCli.SEC_I_RENEGOTIATE ? - SR.Format(SR.event_OperationReturnedSomething, op, "SEC_I_RENEGOTIATE") : - SR.Format(SR.net_log_operation_failed_with_error, op, $"0x{0:X}")); - } - - return errorCode; - } - } - public static SafeFreeContextBufferChannelBinding? QueryContextChannelBinding(ISSPIInterface secModule, SafeDeleteContext securityContext, Interop.SspiCli.ContextAttribute contextAttribute) { SafeFreeContextBufferChannelBinding result; diff --git a/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs b/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs index 44020f1fd90a51..280572b33494f6 100644 --- a/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs +++ b/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs @@ -159,14 +159,14 @@ internal void CloseContext() _isCompleted = false; } - internal int VerifySignature(byte[] buffer, int offset, int count) + internal int VerifySignature(ReadOnlySpan buffer) { - return NegotiateStreamPal.VerifySignature(_securityContext!, buffer, offset, count); + return NegotiateStreamPal.VerifySignature(_securityContext!, buffer); } - internal int MakeSignature(byte[] buffer, int offset, int count, [AllowNull] ref byte[] output) + internal int MakeSignature(ReadOnlySpan buffer, [AllowNull] ref byte[] output) { - return NegotiateStreamPal.MakeSignature(_securityContext!, buffer, offset, count, ref output); + return NegotiateStreamPal.MakeSignature(_securityContext!, buffer, ref output); } internal string? GetOutgoingBlob(string? incomingBlob) diff --git a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs index 4e5d8044256001..2317354fef2b5c 100644 --- a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs +++ b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs @@ -581,27 +581,34 @@ internal static int Decrypt( return GssUnwrap(((SafeDeleteNegoContext)securityContext).GssContext, buffer); } - internal static int VerifySignature(SafeDeleteContext securityContext, byte[] buffer, int offset, int count) + internal static int VerifySignature(SafeDeleteContext securityContext, ReadOnlySpan buffer) { - if (offset < 0 || offset > (buffer == null ? 0 : buffer.Length)) + Interop.NetSecurityNative.GssBuffer decryptedBuffer = default(Interop.NetSecurityNative.GssBuffer); + try { - Debug.Fail("Argument 'offset' out of range"); - throw new ArgumentOutOfRangeException(nameof(offset)); - } + Interop.NetSecurityNative.Status minorStatus; + Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.UnwrapBuffer( + out minorStatus, + ((SafeDeleteNegoContext)securityContext).GssContext, + buffer, + ref decryptedBuffer); + if (status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE) + { + throw new Interop.NetSecurityNative.GssApiException(status, minorStatus); + } - if (count < 0 || count > (buffer == null ? 0 : buffer.Length - offset)) + return decryptedBuffer.Span.Length; + } + finally { - Debug.Fail("Argument 'count' out of range."); - throw new ArgumentOutOfRangeException(nameof(count)); + decryptedBuffer.Dispose(); } - - return GssUnwrap(((SafeDeleteNegoContext)securityContext).GssContext, buffer.AsSpan(offset, count)); } - internal static int MakeSignature(SafeDeleteContext securityContext, byte[] buffer, int offset, int count, [AllowNull] ref byte[] output) + internal static int MakeSignature(SafeDeleteContext securityContext, ReadOnlySpan buffer, [AllowNull] ref byte[] output) { SafeDeleteNegoContext gssContext = (SafeDeleteNegoContext)securityContext; - output = GssWrap(gssContext.GssContext, false, new ReadOnlySpan(buffer, offset, count)); + output = GssWrap(gssContext.GssContext, false, buffer); return output.Length; } } diff --git a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs index 3597510dabeb94..7e69df87adfabf 100644 --- a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs +++ b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers.Binary; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -193,97 +194,95 @@ internal static Win32Exception CreateExceptionFromError(SecurityStatusPal status return new Win32Exception((int)SecurityStatusAdapterPal.GetInteropFromSecurityStatusPal(statusCode)); } - internal static int VerifySignature(SafeDeleteContext securityContext, byte[] buffer, int offset, int count) + internal static unsafe int VerifySignature(SafeDeleteContext securityContext, ReadOnlySpan buffer) { - // validate offset within length - if (offset < 0 || offset > (buffer == null ? 0 : buffer.Length)) + fixed (byte* bufferPtr = buffer) { - NetEventSource.Info("Argument 'offset' out of range."); - throw new ArgumentOutOfRangeException(nameof(offset)); - } + Interop.SspiCli.SecBuffer* unmanagedBuffer = stackalloc Interop.SspiCli.SecBuffer[2]; + Interop.SspiCli.SecBuffer* streamBuffer = &unmanagedBuffer[0]; + Interop.SspiCli.SecBuffer* dataBuffer = &unmanagedBuffer[1]; + streamBuffer->BufferType = SecurityBufferType.SECBUFFER_STREAM; + streamBuffer->pvBuffer = (IntPtr)bufferPtr; + streamBuffer->cbBuffer = buffer.Length; + dataBuffer->BufferType = SecurityBufferType.SECBUFFER_DATA; + dataBuffer->pvBuffer = IntPtr.Zero; + dataBuffer->cbBuffer = 0; - // validate count within offset and end of buffer - if (count < 0 || - count > (buffer == null ? 0 : buffer.Length - offset)) - { - NetEventSource.Info("Argument 'count' out of range."); - throw new ArgumentOutOfRangeException(nameof(count)); - } + Interop.SspiCli.SecBufferDesc sdcInOut = new Interop.SspiCli.SecBufferDesc(2) + { + pBuffers = unmanagedBuffer + }; - // setup security buffers for ssp call - // one points at signed data - // two will receive payload if signature is valid -#if NETSTANDARD2_0 - Span securityBuffer = new SecurityBuffer[2]; -#else - TwoSecurityBuffers stackBuffer = default; - Span securityBuffer = MemoryMarshal.CreateSpan(ref stackBuffer._item0, 2); -#endif - securityBuffer[0] = new SecurityBuffer(buffer, offset, count, SecurityBufferType.SECBUFFER_STREAM); - securityBuffer[1] = new SecurityBuffer(0, SecurityBufferType.SECBUFFER_DATA); - - // call SSP function - int errorCode = SSPIWrapper.VerifySignature( - GlobalSSPI.SSPIAuth, - securityContext, - securityBuffer, - 0); - // throw if error - if (errorCode != 0) - { - NetEventSource.Info($"VerifySignature threw error: {errorCode:x}"); - throw new Win32Exception(errorCode); - } + int errorCode = GlobalSSPI.SSPIAuth.VerifySignature(securityContext, ref sdcInOut, 0); + if (errorCode != 0) + { + Exception e = new Win32Exception(errorCode); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, e); + throw new Win32Exception(errorCode); + } + + if (dataBuffer->BufferType != SecurityBufferType.SECBUFFER_DATA) + { + throw new InternalException(dataBuffer->BufferType); + } - // not sure why this is here - retained from Encrypt code above - if (securityBuffer[1].type != SecurityBufferType.SECBUFFER_DATA) - throw new InternalException(securityBuffer[1].type); + Debug.Assert((nint)dataBuffer->pvBuffer >= (nint)bufferPtr); + Debug.Assert((nint)dataBuffer->pvBuffer + dataBuffer->cbBuffer <= (nint)bufferPtr + buffer.Length); - // return validated payload size - return securityBuffer[1].size; + // return validated payload size + return dataBuffer->cbBuffer; + } } - internal static int MakeSignature(SafeDeleteContext securityContext, byte[] buffer, int offset, int count, [AllowNull] ref byte[] output) + internal static unsafe int MakeSignature(SafeDeleteContext securityContext, ReadOnlySpan buffer, [AllowNull] ref byte[] output) { SecPkgContext_Sizes sizes = default; bool success = SSPIWrapper.QueryBlittableContextAttributes(GlobalSSPI.SSPIAuth, securityContext, Interop.SspiCli.ContextAttribute.SECPKG_ATTR_SIZES, ref sizes); Debug.Assert(success); // alloc new output buffer if not supplied or too small - int resultSize = count + sizes.cbMaxSignature; + int resultSize = buffer.Length + sizes.cbMaxSignature; if (output == null || output.Length < resultSize) { output = new byte[resultSize]; } // make a copy of user data for in-place encryption - Buffer.BlockCopy(buffer, offset, output, sizes.cbMaxSignature, count); - - // setup security buffers for ssp call -#if NETSTANDARD2_0 - Span securityBuffer = new SecurityBuffer[2]; -#else - TwoSecurityBuffers stackBuffer = default; - Span securityBuffer = MemoryMarshal.CreateSpan(ref stackBuffer._item0, 2); -#endif - securityBuffer[0] = new SecurityBuffer(output, 0, sizes.cbMaxSignature, SecurityBufferType.SECBUFFER_TOKEN); - securityBuffer[1] = new SecurityBuffer(output, sizes.cbMaxSignature, count, SecurityBufferType.SECBUFFER_DATA); - - // call SSP Function - int errorCode = SSPIWrapper.MakeSignature(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, 0); - - // throw if error - if (errorCode != 0) + buffer.CopyTo(output.AsSpan(sizes.cbMaxSignature, buffer.Length)); + + fixed (byte* outputPtr = output) { - NetEventSource.Info($"MakeSignature threw error: {errorCode:x}"); - throw new Win32Exception(errorCode); - } + // Prepare buffers TOKEN(signature), DATA and Padding. + Interop.SspiCli.SecBuffer* unmanagedBuffer = stackalloc Interop.SspiCli.SecBuffer[2]; + Interop.SspiCli.SecBuffer* tokenBuffer = &unmanagedBuffer[0]; + Interop.SspiCli.SecBuffer* dataBuffer = &unmanagedBuffer[1]; + tokenBuffer->BufferType = SecurityBufferType.SECBUFFER_TOKEN; + tokenBuffer->pvBuffer = (IntPtr)(outputPtr); + tokenBuffer->cbBuffer = sizes.cbMaxSignature; + dataBuffer->BufferType = SecurityBufferType.SECBUFFER_DATA; + dataBuffer->pvBuffer = (IntPtr)(outputPtr + sizes.cbMaxSignature); + dataBuffer->cbBuffer = buffer.Length; + + Interop.SspiCli.SecBufferDesc sdcInOut = new Interop.SspiCli.SecBufferDesc(2) + { + pBuffers = unmanagedBuffer + }; + + int errorCode = GlobalSSPI.SSPIAuth.MakeSignature(securityContext, ref sdcInOut, 0); - // return signed size - return securityBuffer[0].size + securityBuffer[1].size; + if (errorCode != 0) + { + Exception e = new Win32Exception(errorCode); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, e); + throw new Win32Exception(errorCode); + } + + // return signed size + return tokenBuffer->cbBuffer + dataBuffer->cbBuffer; + } } - internal static int Encrypt( + internal static unsafe int Encrypt( SafeDeleteContext securityContext, ReadOnlySpan buffer, bool isConfidential, @@ -310,60 +309,71 @@ internal static int Encrypt( // Make a copy of user data for in-place encryption. buffer.CopyTo(output.AsSpan(4 + sizes.cbSecurityTrailer)); - // Prepare buffers TOKEN(signature), DATA and Padding. - ThreeSecurityBuffers buffers = default; - var securityBuffer = MemoryMarshal.CreateSpan(ref buffers._item0, 3); - securityBuffer[0] = new SecurityBuffer(output, 4, sizes.cbSecurityTrailer, SecurityBufferType.SECBUFFER_TOKEN); - securityBuffer[1] = new SecurityBuffer(output, 4 + sizes.cbSecurityTrailer, buffer.Length, SecurityBufferType.SECBUFFER_DATA); - securityBuffer[2] = new SecurityBuffer(output, 4 + sizes.cbSecurityTrailer + buffer.Length, sizes.cbBlockSize, SecurityBufferType.SECBUFFER_PADDING); - - int errorCode; - if (isConfidential) + fixed (byte* outputPtr = output) { - errorCode = SSPIWrapper.EncryptMessage(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, sequenceNumber); - } - else - { - if (isNtlm) + // Prepare buffers TOKEN(signature), DATA and Padding. + Interop.SspiCli.SecBuffer* unmanagedBuffer = stackalloc Interop.SspiCli.SecBuffer[3]; + Interop.SspiCli.SecBuffer* tokenBuffer = &unmanagedBuffer[0]; + Interop.SspiCli.SecBuffer* dataBuffer = &unmanagedBuffer[1]; + Interop.SspiCli.SecBuffer* paddingBuffer = &unmanagedBuffer[2]; + tokenBuffer->BufferType = SecurityBufferType.SECBUFFER_TOKEN; + tokenBuffer->pvBuffer = (IntPtr)(outputPtr + 4); + tokenBuffer->cbBuffer = sizes.cbSecurityTrailer; + dataBuffer->BufferType = SecurityBufferType.SECBUFFER_DATA; + dataBuffer->pvBuffer = (IntPtr)(outputPtr + 4 + sizes.cbSecurityTrailer); + dataBuffer->cbBuffer = buffer.Length; + paddingBuffer->BufferType = SecurityBufferType.SECBUFFER_PADDING; + paddingBuffer->pvBuffer = (IntPtr)(outputPtr + 4 + sizes.cbSecurityTrailer + buffer.Length); + paddingBuffer->cbBuffer = sizes.cbBlockSize; + + Interop.SspiCli.SecBufferDesc sdcInOut = new Interop.SspiCli.SecBufferDesc(3) + { + pBuffers = unmanagedBuffer + }; + + int errorCode; + + if (isConfidential) { - securityBuffer[1].type |= SecurityBufferType.SECBUFFER_READONLY; + errorCode = GlobalSSPI.SSPIAuth.EncryptMessage(securityContext, ref sdcInOut, sequenceNumber); } + else + { + if (isNtlm) + { + dataBuffer->BufferType |= SecurityBufferType.SECBUFFER_READONLY; + } - errorCode = SSPIWrapper.MakeSignature(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, 0); - } + errorCode = GlobalSSPI.SSPIAuth.MakeSignature(securityContext, ref sdcInOut, 0); + } - if (errorCode != 0) - { - Exception e = new Win32Exception(errorCode); - if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, e); - throw e; - } + if (errorCode != 0) + { + Exception e = new Win32Exception(errorCode); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, e); + throw new Win32Exception(errorCode); + } - // Compacting the result. - resultSize = securityBuffer[0].size; - bool forceCopy = false; - if (resultSize != sizes.cbSecurityTrailer) - { - forceCopy = true; - Buffer.BlockCopy(output, securityBuffer[1].offset, output, 4 + resultSize, securityBuffer[1].size); - } + // Compacting the result. + resultSize = tokenBuffer->cbBuffer; + bool forceCopy = false; + if (resultSize != sizes.cbSecurityTrailer) + { + forceCopy = true; + output.AsSpan(4 + sizes.cbSecurityTrailer, dataBuffer->cbBuffer).CopyTo(output.AsSpan(4 + resultSize, dataBuffer->cbBuffer)); + } - resultSize += securityBuffer[1].size; - if (securityBuffer[2].size != 0 && (forceCopy || resultSize != (buffer.Length + sizes.cbSecurityTrailer))) - { - Buffer.BlockCopy(output, securityBuffer[2].offset, output, 4 + resultSize, securityBuffer[2].size); - } + resultSize += dataBuffer->cbBuffer; + if (paddingBuffer->cbBuffer != 0 && (forceCopy || resultSize != (buffer.Length + sizes.cbSecurityTrailer))) + { + output.AsSpan(4 + sizes.cbSecurityTrailer + buffer.Length, paddingBuffer->cbBuffer).CopyTo(output.AsSpan(4 + resultSize, paddingBuffer->cbBuffer)); + } - resultSize += securityBuffer[2].size; - unchecked - { - output[0] = (byte)((resultSize) & 0xFF); - output[1] = (byte)(((resultSize) >> 8) & 0xFF); - output[2] = (byte)(((resultSize) >> 16) & 0xFF); - output[3] = (byte)(((resultSize) >> 24) & 0xFF); - } + resultSize += paddingBuffer->cbBuffer; + BinaryPrimitives.WriteInt32LittleEndian(output, resultSize); - return resultSize + 4; + return resultSize + 4; + } } internal static unsafe int Decrypt( diff --git a/src/libraries/Common/src/System/Net/Security/SecurityBuffer.Windows.cs b/src/libraries/Common/src/System/Net/Security/SecurityBuffer.Windows.cs index 07bac1000c4377..1866eae805fdef 100644 --- a/src/libraries/Common/src/System/Net/Security/SecurityBuffer.Windows.cs +++ b/src/libraries/Common/src/System/Net/Security/SecurityBuffer.Windows.cs @@ -7,27 +7,6 @@ namespace System.Net.Security { - // Until we have stackalloc Span support, these two - // structs allow us to do the equivalent of stackalloc SecurityBuffer[2] - // and stackalloc SecurityBuffer[3], with code like: - // TwoSecurityBuffers tmp = default; - // Span buffers = MemoryMarshal.CreateSpan Date: Tue, 28 Jun 2022 17:13:55 +0200 Subject: [PATCH 5/5] Update src/libraries/Common/src/System/Net/NTAuthentication.Common.cs Co-authored-by: Stephen Toub --- src/libraries/Common/src/System/Net/NTAuthentication.Common.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs b/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs index 280572b33494f6..af8eff8b35dd37 100644 --- a/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs +++ b/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs @@ -304,7 +304,8 @@ internal int MakeSignature(ReadOnlySpan buffer, [AllowNull] ref byte[] out byte[]? result = resultBlobLength == 0 || _tokenBuffer == null ? null : - (_tokenBuffer.Length == resultBlobLength ? _tokenBuffer : _tokenBuffer.AsSpan(0, resultBlobLength).ToArray()); + _tokenBuffer.Length == resultBlobLength ? _tokenBuffer : + _tokenBuffer[0..resultBlobLength]; // The return value will tell us correctly if the handshake is over or not if (statusCode.ErrorCode == SecurityStatusPalErrorCode.OK