-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Description
Description
We recently met failures from NegotiateStream when sending data from .NET 8 server to .NET framework client with ProtectionLevel.Sign. I made a repro as following code. I checked tcp dump, the data sent from server was very weird. Seems a bug. Especially it fails when ProtectionLevel is Sign, but succeeded when it's EncryptAndSign.
I tested serveral scenarios:
Net8 client -> Net8 server (ProtectionLevel.Sign). Failed.
Net8 server -> NetFx client (ProtectionLevel.Sign). Failed.
Net7 server ->NetFx client (ProtectionLevel.Sign). Succeed.
Net6 server ->NetFx client (ProtectionLevel.Sign). Succeed.
Net8 server -> NetFx client (ProtectionLevel.EncryptAndSign). Succeed.
Exception:
System.IO.IOException: The read operation failed, see inner exception.. -2146232800
at System.Net.Security.NegotiateStream.ProcessRead(Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.NegotiateStream.Read(Byte[] buffer, Int32 offset, Int32 count)
at NegotiateStreamNetFx.Program.Main(String[] args) in C:\Users\user\source\repos\NegotiateStreamNetFx\NegotiateStreamNetFx\Program.cs:line 77
InnerEx: System.ComponentModel.Win32Exception: The message or signature supplied for verification has been altered. -2146232800
at System.Net.NTAuthentication.DecryptNtlm(Byte[] payload, Int32 offset, Int32 count, Int32& newOffset, UInt32 expectedSeqNumber)
at System.Net.NTAuthentication.Decrypt(Byte[] payload, Int32 offset, Int32 count, Int32& newOffset, UInt32 expectedSeqNumber)
at System.Net.Security.NegoState.DecryptData(Byte[] buffer, Int32 offset, Int32 count, Int32& newOffset)
at System.Net.Security.NegotiateStream.ProcessFrameBody(Int32 readBytes, Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.NegotiateStream.StartFrameBody(Int32 readBytes, Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.NegotiateStream.StartFrameHeader(Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.NegotiateStream.ProcessRead(Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
Reproduction Steps
Client code:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Security;
using System.Net.Sockets;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
namespace NegotiateStreamNetFx
{
internal class Program
{
static TcpClient client = null;
static void Main(string[] args)
{
Console.WriteLine($"Runtime: {System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription}");
// Establish the remote endpoint for the socket.
// For this example, use the local machine.
IPHostEntry ipHostInfo = Dns.GetHostEntry("localhost");
IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
// Client and server use port 11000.
IPEndPoint remoteEP = new IPEndPoint(ipAddress, 11000);
// Create a TCP/IP socket.
client = new TcpClient();
// Connect the socket to the remote endpoint.
client.Connect(remoteEP);
Console.WriteLine("Client connected to {0}.", remoteEP.ToString());
// Ensure the client does not close when there is
// still data to be sent to the server.
client.LingerState = new LingerOption(true, 0);
// Request authentication.
NetworkStream clientStream = client.GetStream();
NegotiateStream authStream = new NegotiateStream(clientStream, false);
// Pass the NegotiateStream as the AsyncState object
// so that it is available to the callback delegate.
var ar = authStream.BeginAuthenticateAsClient(
CredentialCache.DefaultNetworkCredentials,
string.Empty,
ProtectionLevel.Sign, //ProtectionLevel.EncryptAndSign works if server is .NET 8
TokenImpersonationLevel.Identification,
null,
null);
Console.WriteLine("Client waiting for authentication...");
// Wait until the result is available.
ar.AsyncWaitHandle.WaitOne();
authStream.EndAuthenticateAsClient(ar);
// Display the properties of the authenticated stream.
AuthenticatedStreamReporter.DisplayProperties(authStream);
// Send a message to the server.
// Encode the test data into a byte array.
byte[] message = Encoding.UTF8.GetBytes("Hello from the client.");
Task writeTask = authStream
.WriteAsync(message, 0, message.Length)
.ContinueWith(task =>
{
Console.WriteLine("Client ending write operation...");
});
writeTask.Wait();
Console.WriteLine("Sent {0} bytes.", message.Length);
var buffer = new byte[2048];
int readBytes = 0;
try
{
readBytes = authStream.Read(buffer, 0, buffer.Length);
}
catch (IOException ex)
{
Console.WriteLine($"{ex.GetType()}: {ex.Message}. {ex.HResult}");
Console.WriteLine($"{ex.StackTrace}");
Console.WriteLine($"InnerEx: {ex.InnerException.GetType()}: {ex.InnerException.Message}. {ex.HResult}\n");
Console.WriteLine($"{ex.InnerException.StackTrace}");
return;
}
var readStr = Encoding.UTF8.GetChars(buffer, 0, readBytes);
Console.WriteLine("Read {0} bytes.", readBytes);
Console.WriteLine("Read {0} string.", new string(readStr));
// Close the client connection.
authStream.Close();
Console.WriteLine("Client closed.");
}
}
// The following class displays the properties of an authenticatedStream.
public class AuthenticatedStreamReporter
{
public static void DisplayProperties(AuthenticatedStream stream)
{
Console.WriteLine("IsAuthenticated: {0}", stream.IsAuthenticated);
Console.WriteLine("IsMutuallyAuthenticated: {0}", stream.IsMutuallyAuthenticated);
Console.WriteLine("IsEncrypted: {0}", stream.IsEncrypted);
Console.WriteLine("IsSigned: {0}", stream.IsSigned);
Console.WriteLine("IsServer: {0}", stream.IsServer);
}
}
}Server code:
using System;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Principal;
using System.Text;
using System.IO;
using System.Threading;
namespace Examples.NegotiateStreamExample
{
public class AsynchronousAuthenticatingTcpListener
{
public static void Main()
{
Console.WriteLine($"Runtime: {System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription}");
// Create an IPv4 TCP/IP socket.
TcpListener listener = new TcpListener(IPAddress.Any, 11000);
// Listen for incoming connections.
listener.Start();
while (true)
{
TcpClient clientRequest;
// Application blocks while waiting for an incoming connection.
// Type CNTL-C to terminate the server.
clientRequest = listener.AcceptTcpClient();
Console.WriteLine("Client connected.");
// A client has connected.
try
{
AuthenticateClient(clientRequest);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
public static void AuthenticateClient(TcpClient clientRequest)
{
NetworkStream stream = clientRequest.GetStream();
// Create the NegotiateStream.
NegotiateStream authStream = new NegotiateStream(stream, false);
// Save the current client and NegotiateStream instance
// in a ClientState object.
ClientState cState = new ClientState(authStream, clientRequest);
// Listen for the client authentication request.
// Any exceptions that occurred during authentication are
// thrown by the EndAuthenticateAsServer method.
try
{
// This call blocks until the authentication is complete.
authStream.AuthenticateAsServer(
CredentialCache.DefaultNetworkCredentials,
ProtectionLevel.None,
TokenImpersonationLevel.Identification);
IIdentity id = authStream.RemoteIdentity;
Console.WriteLine("{0} was authenticated using {1}.",
id.Name,
id.AuthenticationType);
}
catch (AuthenticationException e)
{
Console.WriteLine(e);
Console.WriteLine("Authentication failed - closing connection.");
return;
}
catch (Exception e)
{
Console.WriteLine(e);
Console.WriteLine("Closing connection.");
return;
}
Task<int> readTask = authStream
.ReadAsync(cState.Buffer, 0, cState.Buffer.Length);
readTask
.ContinueWith((task) => { EndReadCallback(cState, task.Result); })
.Wait();
// Finished with the current client.
authStream.Close();
clientRequest.Close();
}
private static void EndReadCallback(ClientState cState, int bytes)
{
NegotiateStream authStream = (NegotiateStream)cState.AuthenticatedStream;
// Read the client message.
try
{
cState.Message.Append(Encoding.UTF8.GetChars(cState.Buffer, 0, bytes));
if (bytes != 0)
{
Task<int> readTask = authStream.ReadAsync(cState.Buffer, 0, cState.Buffer.Length);
byte[] message = Encoding.UTF8.GetBytes("Hello from the server.");
authStream.Write(message, 0, message.Length);
readTask
.ContinueWith(task => { EndReadCallback(cState, task.Result); })
.Wait();
return;
}
}
catch (Exception e)
{
// A real application should do something
// useful here, such as logging the failure.
Console.WriteLine("Client message exception:");
Console.WriteLine(e);
return;
}
IIdentity id = authStream.RemoteIdentity;
Console.WriteLine("{0} says {1}", id.Name, cState.Message.ToString());
}
}
// ClientState is the AsyncState object.
internal class ClientState
{
private StringBuilder _message = null;
internal ClientState(AuthenticatedStream a, TcpClient theClient)
{
AuthenticatedStream = a;
Client = theClient;
}
internal TcpClient Client { get; }
internal AuthenticatedStream AuthenticatedStream { get; }
internal byte[] Buffer { get; } = new byte[2048];
internal StringBuilder Message
{
get { return _message ??= new StringBuilder(); }
}
}
}Output when .NET8 client -> .NET8 server
Client output (.NET8 client -> .NET8 server):
Runtime: .NET 8.0.1
Client connected to 127.0.0.1:11000.
Client waiting for authentication...
IsAuthenticated: True
IsMutuallyAuthenticated: False
IsEncrypted: False
IsSigned: True
IsServer: False
Client ending write operation...
Sent 22 bytes.
Server output (.NET8 client -> .NET8 server):
Runtime: .NET 8.0.1
Client connected.
DOMAIN\user was authenticated using NTLM.
System.AggregateException: One or more errors occurred. (One or more errors occurred. (The read operation failed, see inner exception.))
---> System.AggregateException: One or more errors occurred. (The read operation failed, see inner exception.)
---> System.IO.IOException: The read operation failed, see inner exception.
---> System.ComponentModel.Win32Exception (0x8009030F): The message or signature supplied for verification has been altered
at System.Net.NegotiateAuthenticationPal.WindowsNegotiateAuthenticationPal.VerifyMIC(ReadOnlySpan`1 message, ReadOnlySpan`1 signature)
at System.Net.Security.NegotiateStream.ReadAsync[TIOAdapter](Memory`1 buffer, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at System.Net.Security.NegotiateStream.ReadAsync[TIOAdapter](Memory`1 buffer, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
at Examples.NegotiateStreamExample.AsynchronousAuthenticatingTcpListener.<>c__DisplayClass1_0.<AuthenticateClient>b__0(Task`1 task) in Q:\source\NStreamServer\NStreamServer\Program.cs:line 83
at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
--- End of inner exception stack trace ---
at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
at System.Threading.Tasks.Task.Wait()
at Examples.NegotiateStreamExample.AsynchronousAuthenticatingTcpListener.AuthenticateClient(TcpClient clientRequest) in Q:\source\NStreamServer\NStreamServer\Program.cs:line 82
at Examples.NegotiateStreamExample.AsynchronousAuthenticatingTcpListener.Main() in Q:\source\NStreamServer\NStreamServer\Program.cs:line 33
TCP dump (.NET8 client -> .NET8 server);
(Negotiate segments removed)
00000108 16 01 00 00 ....
0000010C 01 00 00 00 4f 40 6e e7 88 45 2c 29 01 00 00 00 ....O@n. .E,)....
0000011C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
0000012C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
0000013C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
0000014C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
0000015C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
0000016C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
0000017C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
0000018C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
0000019C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
000001AC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
000001BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
000001CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
000001DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
000001EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
000001FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
0000020C 48 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 63 Hello fr om the c
0000021C 6c 69 65 6e 74 2e lient.
Output when .NET framework client -> .NET8 server
Client output (.NET framework client -> .NET8 server):
Runtime: .NET Framework 4.8.9181.0
Client connected to 127.0.0.1:11000.
Client waiting for authentication...
IsAuthenticated: True
IsMutuallyAuthenticated: False
IsEncrypted: False
IsSigned: True
IsServer: False
Client ending write operation...
Sent 22 bytes.
System.IO.IOException: The read operation failed, see inner exception.. -2146232800
at System.Net.Security.NegotiateStream.ProcessRead(Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.NegotiateStream.Read(Byte[] buffer, Int32 offset, Int32 count)
at NegotiateStreamNetFx.Program.Main(String[] args) in C:\Users\user\source\repos\NegotiateStreamNetFx\NegotiateStreamNetFx\Program.cs:line 77
InnerEx: System.ComponentModel.Win32Exception: The message or signature supplied for verification has been altered. -2146232800
at System.Net.NTAuthentication.DecryptNtlm(Byte[] payload, Int32 offset, Int32 count, Int32& newOffset, UInt32 expectedSeqNumber)
at System.Net.NTAuthentication.Decrypt(Byte[] payload, Int32 offset, Int32 count, Int32& newOffset, UInt32 expectedSeqNumber)
at System.Net.Security.NegoState.DecryptData(Byte[] buffer, Int32 offset, Int32 count, Int32& newOffset)
at System.Net.Security.NegotiateStream.ProcessFrameBody(Int32 readBytes, Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.NegotiateStream.StartFrameBody(Int32 readBytes, Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.NegotiateStream.StartFrameHeader(Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.NegotiateStream.ProcessRead(Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
Server output (.NET framework client -> .NET8 server):
Runtime: .NET 8.0.1
Client connected.
DOMAIN\user was authenticated using NTLM.
TCP dump (.NET framework client -> .NET8 server):
(Negotiate segments removed)
00000100 26 00 00 00 01 00 00 00 d8 12 6c a6 bb 9e 4a 6d &....... ..l...Jm
00000110 01 00 00 00 48 65 6c 6c 6f 20 66 72 6f 6d 20 74 ....Hell o from t
00000120 68 65 20 63 6c 69 65 6e 74 2e he clien t.
000000EE 16 01 00 00 ....
000000F2 01 00 00 00 02 38 40 f8 4a d1 1c c0 01 00 00 00 .....8@. J.......
00000102 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
00000112 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
00000122 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
00000132 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
00000142 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
00000152 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
00000162 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
00000172 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
00000182 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
00000192 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
000001A2 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
000001B2 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
000001C2 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
000001D2 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
000001E2 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
000001F2 48 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 73 Hello fr om the s
00000202 65 72 76 65 72 2e erver.
Expected behavior
Above example code succeeded.
Actual behavior
Got exception
Exception:
System.IO.IOException: The read operation failed, see inner exception.. -2146232800
at System.Net.Security.NegotiateStream.ProcessRead(Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.NegotiateStream.Read(Byte[] buffer, Int32 offset, Int32 count)
at NegotiateStreamNetFx.Program.Main(String[] args) in C:\Users\user\source\repos\NegotiateStreamNetFx\NegotiateStreamNetFx\Program.cs:line 77
InnerEx: System.ComponentModel.Win32Exception: The message or signature supplied for verification has been altered. -2146232800
at System.Net.NTAuthentication.DecryptNtlm(Byte[] payload, Int32 offset, Int32 count, Int32& newOffset, UInt32 expectedSeqNumber)
at System.Net.NTAuthentication.Decrypt(Byte[] payload, Int32 offset, Int32 count, Int32& newOffset, UInt32 expectedSeqNumber)
at System.Net.Security.NegoState.DecryptData(Byte[] buffer, Int32 offset, Int32 count, Int32& newOffset)
at System.Net.Security.NegotiateStream.ProcessFrameBody(Int32 readBytes, Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.NegotiateStream.StartFrameBody(Int32 readBytes, Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.NegotiateStream.StartFrameHeader(Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.NegotiateStream.ProcessRead(Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
Regression?
No response
Known Workarounds
No response
Configuration
No response
Other information
No response