Skip to content

NegotiateStream with ProtectionLevel.Sign failed in .NET 8 #97942

@0x6c53a137

Description

@0x6c53a137

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions