Fix NegotiateStream connections between Linux clients and Windows servers - #99909

Merged
wfurt merged 7 commits into
dotnet:mainfrom
filipnavara:seal
Mar 26, 2024
Merged

Fix NegotiateStream connections between Linux clients and Windows servers#99909
wfurt merged 7 commits into
dotnet:mainfrom
filipnavara:seal

Conversation

@filipnavara

@filipnavarafilipnavara commented Mar 18, 2024

Copy link
Copy Markdown
Member

Fixes#99227

Managed NTLM: Send the NegotiateSeal NTLM flag when client asks for ProtectionLevel.EncryptAndSign.

NegotiateStream: Process the last handshake done message. In case of SPNEGO protocol it may contain message integrity check. Additionally, if the negotiated protocol is NTLM then we need to reset the encryption keys after the message integrity check is verified.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/ncl, @bartonjs, @vcsjones
See info in area-owners.md if you want to be subscribed.

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Mar 18, 2024
Comment on lines +886 to +891
if (message.Length > 0)
{
Debug.Assert(_context != null);
_context.GetOutgoingBlob(message, out NegotiateAuthenticationStatusCode statusCode);
_remoteOk = statusCode is NegotiateAuthenticationStatusCode.Completed;
}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review note:

This was likely broken for quite a long time and went unnoticed. Windows clients send raw NTLM instead of SPNEGO if NTLM is the only protocol available. Linux/macOS GSSAPI libraries are aware of this quirk and handle it too. In that case the last handshake message will be empty.

Skipping the processing of the last handshake message is mostly harmless. In SPNEGO it would contain the final message integrity check. Skipping this check could enable some man-in-the-middle attacks, at least in theory. In case of managed NTLM/SPNEGO, however, we reset the NTLM keys only after this very last message is processed. If this is skipped the client and server will have mismatched encryption keys. This is how the bug was discovered.

In theory we can align with GSSAPI by calling (_mechanism as ManagedNtlmNegotiateAuthenticationPal)?.ResetKeys(); here:

_mechanism.GetMIC(_spnegoMechList,micBuffer);
writer.WriteOctetString(micBuffer.WrittenSpan);

We would reset the key both after GetMIC and VerifyMIC instead of doing it only after VerifyMIC. It may not be desirable to do so though; when the API is used correctly and securely this is not an issue.

@filipnavara
filipnavara marked this pull request as ready for review March 19, 2024 11:34
@filipnavara
filipnavara requested review from rzikm and wfurtMarch 19, 2024 11:34

@rzikmrzikm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wfurtwfurt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Seems like test is failing on some platforms.

@filipnavara

Copy link
Copy Markdown
MemberAuthor

Seems like test is failing on some platforms.

I will have a look. Helix was rather unhealthy today so I forgot to check the test results.

ProtectionLevel.EncryptAndSign.
Process the last handshake done message in NegotiateStream. In case of
SPNEGO protocol it may contain message integrity check. Additionally,
if the negotiated protocol is NTLM then we need to reset the encryption
key after the message integrity check is verified.
@filipnavara

Copy link
Copy Markdown
MemberAuthor

Turns out that I had one check order bug and one incorrect assumption in the test. The incorrect assumption was that I tried to check the scenario where the server doesn't support the NegotiateSeal attribute. The specification doesn't allow that:

If set, requests session key negotiation for message confidentiality. If the client sends NTLMSSP_NEGOTIATE_SEAL to the server in the NEGOTIATE_MESSAGE, the server MUST return NTLMSSP_NEGOTIATE_SEAL to the client in the CHALLENGE_MESSAGE.

Linux and the managed implementation handle it by returning error. Windows implementation silently assumes that the specification is followed and ignores that the server stripped NTLMSSP_NEGOTIATE_SEAL. I dropped the part of test since that's not what we are trying to fix anyway.

@filipnavara

Copy link
Copy Markdown
MemberAuthor

Just when I thought the CI cannot be any more borken... it fails to start any pipelines at all.

@wfurt

Copy link
Copy Markdown
Member

thanks @filipnavara for digging deep into this.

 /repo/artifacts/bin/System.Net.Security.Enterprise.Tests/Debug/net9.0-unix /repo/src/libraries/System.Net.Security/tests/EnterpriseTests
Discovering: System.Net.Security.Enterprise.Tests (method display = ClassAndMethod, method display options = None)
Discovered: System.Net.Security.Enterprise.Tests (found 6 test cases)
Starting: System.Net.Security.Enterprise.Tests (parallel test collections = on [4 threads], stop on fail = off)
Process terminated. Assertion failed.
at System.Net.Security.NegotiateStream.WriteAsync[TIOAdapter](ReadOnlyMemory`1 buffer, CancellationToken cancellationToken) in /_/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs:line 507
at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine) in /_/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilderCore.cs:line 38
at System.Net.Security.NegotiateStream.WriteAsync[TIOAdapter](ReadOnlyMemory`1 buffer, CancellationToken cancellationToken)

while your chance did not touched WriteAsync I'm wondering something changed. The buffer seems to be initialized upon successful authentication (but I only skimmed the code)

 else if (statusCode == NegotiateAuthenticationStatusCode.Completed)
{
_writeBuffer = new ArrayBufferWriter<byte>();

@filipnavara

Copy link
Copy Markdown
MemberAuthor

while your chance did not touched WriteAsync I'm wondering something changed.

These correlate with a SecurityQosFailed error. I'll have a deeper look.

Also, the osx-x64 tests should be using Managed NTLM and should not fail like that.

…shakeComplete.
If HandshakeComplete is not true, then the authentication blob will get processed with the normal flow.
@filipnavara
filipnavara requested review from rzikm and wfurtMarch 20, 2024 12:15
@filipnavara

Copy link
Copy Markdown
MemberAuthor

Helix is in quite a disrepair for the past few days (auto-scaler fails to scale several queues; already reported). The relevant pipelines succeeded though (win-x86, osx-x64).

@rzikmrzikm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wfurtwfurt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wfurt
wfurt merged commit be1b035 into dotnet:mainMar 26, 2024
@filipnavara
filipnavara deleted the seal branch March 26, 2024 16:07
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Apr 26, 2024
@karelzkarelz added this to the 9.0.0 milestone May 14, 2024
@rzikm

Copy link
Copy Markdown
Member

/backport to release/8.0-staging

@github-actionsgithub-actionsBot unlocked this conversation May 14, 2024
@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/8.0-staging: https://github.com/dotnet/runtime/actions/runs/9084265804

@github-actions

Copy link
Copy Markdown
Contributor

@rzikm backporting to release/8.0-staging failed, the patch most likely resulted in conflicts:

$ git am --3way --ignore-whitespace --keep-non-patch changes.patch
Applying: Send the NegotiateSeal NTLM flag when client asked for ProtectionLevel.EncryptAndSign.
Applying: Add testfor the NegotiateSeal flag
Using index info to reconstruct a base tree...
M	src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs
Falling back to patching base and 3-way merge...
Auto-merging src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs
Applying: Fix the test
Patch is empty.
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To record the empty patch as an empty commit, run "git am --allow-empty".
To restore the original branch and stop patching, run "git am --abort".
Error: The process '/usr/bin/git' failed with exit code 128

Please backport manually!

@github-actions

Copy link
Copy Markdown
Contributor

@rzikm an error occurred while backporting to release/8.0-staging, please check the run log for details!

Error: git am failed, most likely due to a merge conflict.

@github-actionsgithub-actionsBot locked as resolved and limited conversation to collaborators May 14, 2024
@karelz

Copy link
Copy Markdown
Member

Backport to 8.0 in PR #102216

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Net.Securitycommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NET8 WcfClient (net-tcp binding) running on linux cannot be authenticated on WCF Server hosted on windows if UseManagedNtlm is set

4 participants

@filipnavara@wfurt@rzikm@karelz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Fix NegotiateStream connections between Linux clients and Windows servers - #99909

Merged
wfurt merged 7 commits into
dotnet:mainfrom
filipnavara:seal
Mar 26, 2024
Merged

Fix NegotiateStream connections between Linux clients and Windows servers#99909
wfurt merged 7 commits into
dotnet:mainfrom
filipnavara:seal

Conversation

@filipnavara

@filipnavarafilipnavara commented Mar 18, 2024

Copy link
Copy Markdown
Member

Fixes#99227

Managed NTLM: Send the NegotiateSeal NTLM flag when client asks for ProtectionLevel.EncryptAndSign.

NegotiateStream: Process the last handshake done message. In case of SPNEGO protocol it may contain message integrity check. Additionally, if the negotiated protocol is NTLM then we need to reset the encryption keys after the message integrity check is verified.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/ncl, @bartonjs, @vcsjones
See info in area-owners.md if you want to be subscribed.

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Mar 18, 2024
Comment on lines +886 to +891
if (message.Length > 0)
{
Debug.Assert(_context != null);
_context.GetOutgoingBlob(message, out NegotiateAuthenticationStatusCode statusCode);
_remoteOk = statusCode is NegotiateAuthenticationStatusCode.Completed;
}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review note:

This was likely broken for quite a long time and went unnoticed. Windows clients send raw NTLM instead of SPNEGO if NTLM is the only protocol available. Linux/macOS GSSAPI libraries are aware of this quirk and handle it too. In that case the last handshake message will be empty.

Skipping the processing of the last handshake message is mostly harmless. In SPNEGO it would contain the final message integrity check. Skipping this check could enable some man-in-the-middle attacks, at least in theory. In case of managed NTLM/SPNEGO, however, we reset the NTLM keys only after this very last message is processed. If this is skipped the client and server will have mismatched encryption keys. This is how the bug was discovered.

In theory we can align with GSSAPI by calling (_mechanism as ManagedNtlmNegotiateAuthenticationPal)?.ResetKeys(); here:

_mechanism.GetMIC(_spnegoMechList,micBuffer);
writer.WriteOctetString(micBuffer.WrittenSpan);

We would reset the key both after GetMIC and VerifyMIC instead of doing it only after VerifyMIC. It may not be desirable to do so though; when the API is used correctly and securely this is not an issue.

@filipnavara
filipnavara marked this pull request as ready for review March 19, 2024 11:34
@filipnavara
filipnavara requested review from rzikm and wfurtMarch 19, 2024 11:34

@rzikmrzikm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wfurtwfurt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Seems like test is failing on some platforms.

@filipnavara

Copy link
Copy Markdown
MemberAuthor

Seems like test is failing on some platforms.

I will have a look. Helix was rather unhealthy today so I forgot to check the test results.

ProtectionLevel.EncryptAndSign.
Process the last handshake done message in NegotiateStream. In case of
SPNEGO protocol it may contain message integrity check. Additionally,
if the negotiated protocol is NTLM then we need to reset the encryption
key after the message integrity check is verified.
@filipnavara

Copy link
Copy Markdown
MemberAuthor

Turns out that I had one check order bug and one incorrect assumption in the test. The incorrect assumption was that I tried to check the scenario where the server doesn't support the NegotiateSeal attribute. The specification doesn't allow that:

If set, requests session key negotiation for message confidentiality. If the client sends NTLMSSP_NEGOTIATE_SEAL to the server in the NEGOTIATE_MESSAGE, the server MUST return NTLMSSP_NEGOTIATE_SEAL to the client in the CHALLENGE_MESSAGE.

Linux and the managed implementation handle it by returning error. Windows implementation silently assumes that the specification is followed and ignores that the server stripped NTLMSSP_NEGOTIATE_SEAL. I dropped the part of test since that's not what we are trying to fix anyway.

@filipnavara

Copy link
Copy Markdown
MemberAuthor

Just when I thought the CI cannot be any more borken... it fails to start any pipelines at all.

@wfurt

Copy link
Copy Markdown
Member

thanks @filipnavara for digging deep into this.

 /repo/artifacts/bin/System.Net.Security.Enterprise.Tests/Debug/net9.0-unix /repo/src/libraries/System.Net.Security/tests/EnterpriseTests
Discovering: System.Net.Security.Enterprise.Tests (method display = ClassAndMethod, method display options = None)
Discovered: System.Net.Security.Enterprise.Tests (found 6 test cases)
Starting: System.Net.Security.Enterprise.Tests (parallel test collections = on [4 threads], stop on fail = off)
Process terminated. Assertion failed.
at System.Net.Security.NegotiateStream.WriteAsync[TIOAdapter](ReadOnlyMemory`1 buffer, CancellationToken cancellationToken) in /_/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs:line 507
at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine) in /_/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilderCore.cs:line 38
at System.Net.Security.NegotiateStream.WriteAsync[TIOAdapter](ReadOnlyMemory`1 buffer, CancellationToken cancellationToken)

while your chance did not touched WriteAsync I'm wondering something changed. The buffer seems to be initialized upon successful authentication (but I only skimmed the code)

 else if (statusCode == NegotiateAuthenticationStatusCode.Completed)
{
_writeBuffer = new ArrayBufferWriter<byte>();

@filipnavara

Copy link
Copy Markdown
MemberAuthor

while your chance did not touched WriteAsync I'm wondering something changed.

These correlate with a SecurityQosFailed error. I'll have a deeper look.

Also, the osx-x64 tests should be using Managed NTLM and should not fail like that.

…shakeComplete.
If HandshakeComplete is not true, then the authentication blob will get processed with the normal flow.
@filipnavara
filipnavara requested review from rzikm and wfurtMarch 20, 2024 12:15
@filipnavara

Copy link
Copy Markdown
MemberAuthor

Helix is in quite a disrepair for the past few days (auto-scaler fails to scale several queues; already reported). The relevant pipelines succeeded though (win-x86, osx-x64).

@rzikmrzikm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wfurtwfurt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wfurt
wfurt merged commit be1b035 into dotnet:mainMar 26, 2024
@filipnavara
filipnavara deleted the seal branch March 26, 2024 16:07
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Apr 26, 2024
@karelzkarelz added this to the 9.0.0 milestone May 14, 2024
@rzikm

Copy link
Copy Markdown
Member

/backport to release/8.0-staging

@github-actionsgithub-actionsBot unlocked this conversation May 14, 2024
@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/8.0-staging: https://github.com/dotnet/runtime/actions/runs/9084265804

@github-actions

Copy link
Copy Markdown
Contributor

@rzikm backporting to release/8.0-staging failed, the patch most likely resulted in conflicts:

$ git am --3way --ignore-whitespace --keep-non-patch changes.patch
Applying: Send the NegotiateSeal NTLM flag when client asked for ProtectionLevel.EncryptAndSign.
Applying: Add testfor the NegotiateSeal flag
Using index info to reconstruct a base tree...
M	src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs
Falling back to patching base and 3-way merge...
Auto-merging src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs
Applying: Fix the test
Patch is empty.
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To record the empty patch as an empty commit, run "git am --allow-empty".
To restore the original branch and stop patching, run "git am --abort".
Error: The process '/usr/bin/git' failed with exit code 128

Please backport manually!

@github-actions

Copy link
Copy Markdown
Contributor

@rzikm an error occurred while backporting to release/8.0-staging, please check the run log for details!

Error: git am failed, most likely due to a merge conflict.

@github-actionsgithub-actionsBot locked as resolved and limited conversation to collaborators May 14, 2024
@karelz

Copy link
Copy Markdown
Member

Backport to 8.0 in PR #102216

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Net.Securitycommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NET8 WcfClient (net-tcp binding) running on linux cannot be authenticated on WCF Server hosted on windows if UseManagedNtlm is set

4 participants

@filipnavara@wfurt@rzikm@karelz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix NegotiateStream connections between Linux clients and Windows servers - #99909

Merged
wfurt merged 7 commits into
dotnet:mainfrom
filipnavara:seal
Mar 26, 2024
Merged

Fix NegotiateStream connections between Linux clients and Windows servers#99909
wfurt merged 7 commits into
dotnet:mainfrom
filipnavara:seal

Conversation

@filipnavara

@filipnavarafilipnavara commented Mar 18, 2024

Copy link
Copy Markdown
Member

Fixes#99227

Managed NTLM: Send the NegotiateSeal NTLM flag when client asks for ProtectionLevel.EncryptAndSign.

NegotiateStream: Process the last handshake done message. In case of SPNEGO protocol it may contain message integrity check. Additionally, if the negotiated protocol is NTLM then we need to reset the encryption keys after the message integrity check is verified.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/ncl, @bartonjs, @vcsjones
See info in area-owners.md if you want to be subscribed.

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Mar 18, 2024
Comment on lines +886 to +891
if (message.Length > 0)
{
Debug.Assert(_context != null);
_context.GetOutgoingBlob(message, out NegotiateAuthenticationStatusCode statusCode);
_remoteOk = statusCode is NegotiateAuthenticationStatusCode.Completed;
}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review note:

This was likely broken for quite a long time and went unnoticed. Windows clients send raw NTLM instead of SPNEGO if NTLM is the only protocol available. Linux/macOS GSSAPI libraries are aware of this quirk and handle it too. In that case the last handshake message will be empty.

Skipping the processing of the last handshake message is mostly harmless. In SPNEGO it would contain the final message integrity check. Skipping this check could enable some man-in-the-middle attacks, at least in theory. In case of managed NTLM/SPNEGO, however, we reset the NTLM keys only after this very last message is processed. If this is skipped the client and server will have mismatched encryption keys. This is how the bug was discovered.

In theory we can align with GSSAPI by calling (_mechanism as ManagedNtlmNegotiateAuthenticationPal)?.ResetKeys(); here:

_mechanism.GetMIC(_spnegoMechList,micBuffer);
writer.WriteOctetString(micBuffer.WrittenSpan);

We would reset the key both after GetMIC and VerifyMIC instead of doing it only after VerifyMIC. It may not be desirable to do so though; when the API is used correctly and securely this is not an issue.

@filipnavara
filipnavara marked this pull request as ready for review March 19, 2024 11:34
@filipnavara
filipnavara requested review from rzikm and wfurtMarch 19, 2024 11:34

@rzikmrzikm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wfurtwfurt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Seems like test is failing on some platforms.

@filipnavara

Copy link
Copy Markdown
MemberAuthor

Seems like test is failing on some platforms.

I will have a look. Helix was rather unhealthy today so I forgot to check the test results.

ProtectionLevel.EncryptAndSign.
Process the last handshake done message in NegotiateStream. In case of
SPNEGO protocol it may contain message integrity check. Additionally,
if the negotiated protocol is NTLM then we need to reset the encryption
key after the message integrity check is verified.
@filipnavara

Copy link
Copy Markdown
MemberAuthor

Turns out that I had one check order bug and one incorrect assumption in the test. The incorrect assumption was that I tried to check the scenario where the server doesn't support the NegotiateSeal attribute. The specification doesn't allow that:

If set, requests session key negotiation for message confidentiality. If the client sends NTLMSSP_NEGOTIATE_SEAL to the server in the NEGOTIATE_MESSAGE, the server MUST return NTLMSSP_NEGOTIATE_SEAL to the client in the CHALLENGE_MESSAGE.

Linux and the managed implementation handle it by returning error. Windows implementation silently assumes that the specification is followed and ignores that the server stripped NTLMSSP_NEGOTIATE_SEAL. I dropped the part of test since that's not what we are trying to fix anyway.

@filipnavara

Copy link
Copy Markdown
MemberAuthor

Just when I thought the CI cannot be any more borken... it fails to start any pipelines at all.

@wfurt

Copy link
Copy Markdown
Member

thanks @filipnavara for digging deep into this.

 /repo/artifacts/bin/System.Net.Security.Enterprise.Tests/Debug/net9.0-unix /repo/src/libraries/System.Net.Security/tests/EnterpriseTests
Discovering: System.Net.Security.Enterprise.Tests (method display = ClassAndMethod, method display options = None)
Discovered: System.Net.Security.Enterprise.Tests (found 6 test cases)
Starting: System.Net.Security.Enterprise.Tests (parallel test collections = on [4 threads], stop on fail = off)
Process terminated. Assertion failed.
at System.Net.Security.NegotiateStream.WriteAsync[TIOAdapter](ReadOnlyMemory`1 buffer, CancellationToken cancellationToken) in /_/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs:line 507
at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine) in /_/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilderCore.cs:line 38
at System.Net.Security.NegotiateStream.WriteAsync[TIOAdapter](ReadOnlyMemory`1 buffer, CancellationToken cancellationToken)

while your chance did not touched WriteAsync I'm wondering something changed. The buffer seems to be initialized upon successful authentication (but I only skimmed the code)

 else if (statusCode == NegotiateAuthenticationStatusCode.Completed)
{
_writeBuffer = new ArrayBufferWriter<byte>();

@filipnavara

Copy link
Copy Markdown
MemberAuthor

while your chance did not touched WriteAsync I'm wondering something changed.

These correlate with a SecurityQosFailed error. I'll have a deeper look.

Also, the osx-x64 tests should be using Managed NTLM and should not fail like that.

…shakeComplete.
If HandshakeComplete is not true, then the authentication blob will get processed with the normal flow.
@filipnavara
filipnavara requested review from rzikm and wfurtMarch 20, 2024 12:15
@filipnavara

Copy link
Copy Markdown
MemberAuthor

Helix is in quite a disrepair for the past few days (auto-scaler fails to scale several queues; already reported). The relevant pipelines succeeded though (win-x86, osx-x64).

@rzikmrzikm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wfurtwfurt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wfurt
wfurt merged commit be1b035 into dotnet:mainMar 26, 2024
@filipnavara
filipnavara deleted the seal branch March 26, 2024 16:07
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Apr 26, 2024
@karelzkarelz added this to the 9.0.0 milestone May 14, 2024
@rzikm

Copy link
Copy Markdown
Member

/backport to release/8.0-staging

@github-actionsgithub-actionsBot unlocked this conversation May 14, 2024
@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/8.0-staging: https://github.com/dotnet/runtime/actions/runs/9084265804

@github-actions

Copy link
Copy Markdown
Contributor

@rzikm backporting to release/8.0-staging failed, the patch most likely resulted in conflicts:

$ git am --3way --ignore-whitespace --keep-non-patch changes.patch
Applying: Send the NegotiateSeal NTLM flag when client asked for ProtectionLevel.EncryptAndSign.
Applying: Add testfor the NegotiateSeal flag
Using index info to reconstruct a base tree...
M	src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs
Falling back to patching base and 3-way merge...
Auto-merging src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs
Applying: Fix the test
Patch is empty.
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To record the empty patch as an empty commit, run "git am --allow-empty".
To restore the original branch and stop patching, run "git am --abort".
Error: The process '/usr/bin/git' failed with exit code 128

Please backport manually!

@github-actions

Copy link
Copy Markdown
Contributor

@rzikm an error occurred while backporting to release/8.0-staging, please check the run log for details!

Error: git am failed, most likely due to a merge conflict.

@github-actionsgithub-actionsBot locked as resolved and limited conversation to collaborators May 14, 2024
@karelz

Copy link
Copy Markdown
Member

Backport to 8.0 in PR #102216

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Net.Securitycommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NET8 WcfClient (net-tcp binding) running on linux cannot be authenticated on WCF Server hosted on windows if UseManagedNtlm is set

4 participants

@filipnavara@wfurt@rzikm@karelz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix NegotiateStream connections between Linux clients and Windows servers - #99909

Merged
wfurt merged 7 commits into
dotnet:mainfrom
filipnavara:seal
Mar 26, 2024
Merged

Fix NegotiateStream connections between Linux clients and Windows servers#99909
wfurt merged 7 commits into
dotnet:mainfrom
filipnavara:seal

Conversation

@filipnavara

@filipnavarafilipnavara commented Mar 18, 2024

Copy link
Copy Markdown
Member

Fixes#99227

Managed NTLM: Send the NegotiateSeal NTLM flag when client asks for ProtectionLevel.EncryptAndSign.

NegotiateStream: Process the last handshake done message. In case of SPNEGO protocol it may contain message integrity check. Additionally, if the negotiated protocol is NTLM then we need to reset the encryption keys after the message integrity check is verified.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/ncl, @bartonjs, @vcsjones
See info in area-owners.md if you want to be subscribed.

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Mar 18, 2024
Comment on lines +886 to +891
if (message.Length > 0)
{
Debug.Assert(_context != null);
_context.GetOutgoingBlob(message, out NegotiateAuthenticationStatusCode statusCode);
_remoteOk = statusCode is NegotiateAuthenticationStatusCode.Completed;
}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review note:

This was likely broken for quite a long time and went unnoticed. Windows clients send raw NTLM instead of SPNEGO if NTLM is the only protocol available. Linux/macOS GSSAPI libraries are aware of this quirk and handle it too. In that case the last handshake message will be empty.

Skipping the processing of the last handshake message is mostly harmless. In SPNEGO it would contain the final message integrity check. Skipping this check could enable some man-in-the-middle attacks, at least in theory. In case of managed NTLM/SPNEGO, however, we reset the NTLM keys only after this very last message is processed. If this is skipped the client and server will have mismatched encryption keys. This is how the bug was discovered.

In theory we can align with GSSAPI by calling (_mechanism as ManagedNtlmNegotiateAuthenticationPal)?.ResetKeys(); here:

_mechanism.GetMIC(_spnegoMechList,micBuffer);
writer.WriteOctetString(micBuffer.WrittenSpan);

We would reset the key both after GetMIC and VerifyMIC instead of doing it only after VerifyMIC. It may not be desirable to do so though; when the API is used correctly and securely this is not an issue.

@filipnavara
filipnavara marked this pull request as ready for review March 19, 2024 11:34
@filipnavara
filipnavara requested review from rzikm and wfurtMarch 19, 2024 11:34

@rzikmrzikm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wfurtwfurt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Seems like test is failing on some platforms.

@filipnavara

Copy link
Copy Markdown
MemberAuthor

Seems like test is failing on some platforms.

I will have a look. Helix was rather unhealthy today so I forgot to check the test results.

ProtectionLevel.EncryptAndSign.
Process the last handshake done message in NegotiateStream. In case of
SPNEGO protocol it may contain message integrity check. Additionally,
if the negotiated protocol is NTLM then we need to reset the encryption
key after the message integrity check is verified.
@filipnavara

Copy link
Copy Markdown
MemberAuthor

Turns out that I had one check order bug and one incorrect assumption in the test. The incorrect assumption was that I tried to check the scenario where the server doesn't support the NegotiateSeal attribute. The specification doesn't allow that:

If set, requests session key negotiation for message confidentiality. If the client sends NTLMSSP_NEGOTIATE_SEAL to the server in the NEGOTIATE_MESSAGE, the server MUST return NTLMSSP_NEGOTIATE_SEAL to the client in the CHALLENGE_MESSAGE.

Linux and the managed implementation handle it by returning error. Windows implementation silently assumes that the specification is followed and ignores that the server stripped NTLMSSP_NEGOTIATE_SEAL. I dropped the part of test since that's not what we are trying to fix anyway.

@filipnavara

Copy link
Copy Markdown
MemberAuthor

Just when I thought the CI cannot be any more borken... it fails to start any pipelines at all.

@wfurt

Copy link
Copy Markdown
Member

thanks @filipnavara for digging deep into this.

 /repo/artifacts/bin/System.Net.Security.Enterprise.Tests/Debug/net9.0-unix /repo/src/libraries/System.Net.Security/tests/EnterpriseTests
Discovering: System.Net.Security.Enterprise.Tests (method display = ClassAndMethod, method display options = None)
Discovered: System.Net.Security.Enterprise.Tests (found 6 test cases)
Starting: System.Net.Security.Enterprise.Tests (parallel test collections = on [4 threads], stop on fail = off)
Process terminated. Assertion failed.
at System.Net.Security.NegotiateStream.WriteAsync[TIOAdapter](ReadOnlyMemory`1 buffer, CancellationToken cancellationToken) in /_/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs:line 507
at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine) in /_/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilderCore.cs:line 38
at System.Net.Security.NegotiateStream.WriteAsync[TIOAdapter](ReadOnlyMemory`1 buffer, CancellationToken cancellationToken)

while your chance did not touched WriteAsync I'm wondering something changed. The buffer seems to be initialized upon successful authentication (but I only skimmed the code)

 else if (statusCode == NegotiateAuthenticationStatusCode.Completed)
{
_writeBuffer = new ArrayBufferWriter<byte>();

@filipnavara

Copy link
Copy Markdown
MemberAuthor

while your chance did not touched WriteAsync I'm wondering something changed.

These correlate with a SecurityQosFailed error. I'll have a deeper look.

Also, the osx-x64 tests should be using Managed NTLM and should not fail like that.

…shakeComplete.
If HandshakeComplete is not true, then the authentication blob will get processed with the normal flow.
@filipnavara
filipnavara requested review from rzikm and wfurtMarch 20, 2024 12:15
@filipnavara

Copy link
Copy Markdown
MemberAuthor

Helix is in quite a disrepair for the past few days (auto-scaler fails to scale several queues; already reported). The relevant pipelines succeeded though (win-x86, osx-x64).

@rzikmrzikm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wfurtwfurt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wfurt
wfurt merged commit be1b035 into dotnet:mainMar 26, 2024
@filipnavara
filipnavara deleted the seal branch March 26, 2024 16:07
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Apr 26, 2024
@karelzkarelz added this to the 9.0.0 milestone May 14, 2024
@rzikm

Copy link
Copy Markdown
Member

/backport to release/8.0-staging

@github-actionsgithub-actionsBot unlocked this conversation May 14, 2024
@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/8.0-staging: https://github.com/dotnet/runtime/actions/runs/9084265804

@github-actions

Copy link
Copy Markdown
Contributor

@rzikm backporting to release/8.0-staging failed, the patch most likely resulted in conflicts:

$ git am --3way --ignore-whitespace --keep-non-patch changes.patch
Applying: Send the NegotiateSeal NTLM flag when client asked for ProtectionLevel.EncryptAndSign.
Applying: Add testfor the NegotiateSeal flag
Using index info to reconstruct a base tree...
M	src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs
Falling back to patching base and 3-way merge...
Auto-merging src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs
Applying: Fix the test
Patch is empty.
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To record the empty patch as an empty commit, run "git am --allow-empty".
To restore the original branch and stop patching, run "git am --abort".
Error: The process '/usr/bin/git' failed with exit code 128

Please backport manually!

@github-actions

Copy link
Copy Markdown
Contributor

@rzikm an error occurred while backporting to release/8.0-staging, please check the run log for details!

Error: git am failed, most likely due to a merge conflict.

@github-actionsgithub-actionsBot locked as resolved and limited conversation to collaborators May 14, 2024
@karelz

Copy link
Copy Markdown
Member

Backport to 8.0 in PR #102216

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Net.Securitycommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NET8 WcfClient (net-tcp binding) running on linux cannot be authenticated on WCF Server hosted on windows if UseManagedNtlm is set

4 participants

@filipnavara@wfurt@rzikm@karelz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Fix NegotiateStream connections between Linux clients and Windows servers - #99909

Merged
wfurt merged 7 commits into
dotnet:mainfrom
filipnavara:seal
Mar 26, 2024
Merged

Fix NegotiateStream connections between Linux clients and Windows servers#99909
wfurt merged 7 commits into
dotnet:mainfrom
filipnavara:seal

Conversation

@filipnavara

@filipnavarafilipnavara commented Mar 18, 2024

Copy link
Copy Markdown
Member

Fixes#99227

Managed NTLM: Send the NegotiateSeal NTLM flag when client asks for ProtectionLevel.EncryptAndSign.

NegotiateStream: Process the last handshake done message. In case of SPNEGO protocol it may contain message integrity check. Additionally, if the negotiated protocol is NTLM then we need to reset the encryption keys after the message integrity check is verified.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/ncl, @bartonjs, @vcsjones
See info in area-owners.md if you want to be subscribed.

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Mar 18, 2024
Comment on lines +886 to +891
if (message.Length > 0)
{
Debug.Assert(_context != null);
_context.GetOutgoingBlob(message, out NegotiateAuthenticationStatusCode statusCode);
_remoteOk = statusCode is NegotiateAuthenticationStatusCode.Completed;
}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review note:

This was likely broken for quite a long time and went unnoticed. Windows clients send raw NTLM instead of SPNEGO if NTLM is the only protocol available. Linux/macOS GSSAPI libraries are aware of this quirk and handle it too. In that case the last handshake message will be empty.

Skipping the processing of the last handshake message is mostly harmless. In SPNEGO it would contain the final message integrity check. Skipping this check could enable some man-in-the-middle attacks, at least in theory. In case of managed NTLM/SPNEGO, however, we reset the NTLM keys only after this very last message is processed. If this is skipped the client and server will have mismatched encryption keys. This is how the bug was discovered.

In theory we can align with GSSAPI by calling (_mechanism as ManagedNtlmNegotiateAuthenticationPal)?.ResetKeys(); here:

_mechanism.GetMIC(_spnegoMechList,micBuffer);
writer.WriteOctetString(micBuffer.WrittenSpan);

We would reset the key both after GetMIC and VerifyMIC instead of doing it only after VerifyMIC. It may not be desirable to do so though; when the API is used correctly and securely this is not an issue.

@filipnavara
filipnavara marked this pull request as ready for review March 19, 2024 11:34
@filipnavara
filipnavara requested review from rzikm and wfurtMarch 19, 2024 11:34

@rzikmrzikm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wfurtwfurt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Seems like test is failing on some platforms.

@filipnavara

Copy link
Copy Markdown
MemberAuthor

Seems like test is failing on some platforms.

I will have a look. Helix was rather unhealthy today so I forgot to check the test results.

ProtectionLevel.EncryptAndSign.
Process the last handshake done message in NegotiateStream. In case of
SPNEGO protocol it may contain message integrity check. Additionally,
if the negotiated protocol is NTLM then we need to reset the encryption
key after the message integrity check is verified.
@filipnavara

Copy link
Copy Markdown
MemberAuthor

Turns out that I had one check order bug and one incorrect assumption in the test. The incorrect assumption was that I tried to check the scenario where the server doesn't support the NegotiateSeal attribute. The specification doesn't allow that:

If set, requests session key negotiation for message confidentiality. If the client sends NTLMSSP_NEGOTIATE_SEAL to the server in the NEGOTIATE_MESSAGE, the server MUST return NTLMSSP_NEGOTIATE_SEAL to the client in the CHALLENGE_MESSAGE.

Linux and the managed implementation handle it by returning error. Windows implementation silently assumes that the specification is followed and ignores that the server stripped NTLMSSP_NEGOTIATE_SEAL. I dropped the part of test since that's not what we are trying to fix anyway.

@filipnavara

Copy link
Copy Markdown
MemberAuthor

Just when I thought the CI cannot be any more borken... it fails to start any pipelines at all.

@wfurt

Copy link
Copy Markdown
Member

thanks @filipnavara for digging deep into this.

 /repo/artifacts/bin/System.Net.Security.Enterprise.Tests/Debug/net9.0-unix /repo/src/libraries/System.Net.Security/tests/EnterpriseTests
Discovering: System.Net.Security.Enterprise.Tests (method display = ClassAndMethod, method display options = None)
Discovered: System.Net.Security.Enterprise.Tests (found 6 test cases)
Starting: System.Net.Security.Enterprise.Tests (parallel test collections = on [4 threads], stop on fail = off)
Process terminated. Assertion failed.
at System.Net.Security.NegotiateStream.WriteAsync[TIOAdapter](ReadOnlyMemory`1 buffer, CancellationToken cancellationToken) in /_/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs:line 507
at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine) in /_/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilderCore.cs:line 38
at System.Net.Security.NegotiateStream.WriteAsync[TIOAdapter](ReadOnlyMemory`1 buffer, CancellationToken cancellationToken)

while your chance did not touched WriteAsync I'm wondering something changed. The buffer seems to be initialized upon successful authentication (but I only skimmed the code)

 else if (statusCode == NegotiateAuthenticationStatusCode.Completed)
{
_writeBuffer = new ArrayBufferWriter<byte>();

@filipnavara

Copy link
Copy Markdown
MemberAuthor

while your chance did not touched WriteAsync I'm wondering something changed.

These correlate with a SecurityQosFailed error. I'll have a deeper look.

Also, the osx-x64 tests should be using Managed NTLM and should not fail like that.

…shakeComplete.
If HandshakeComplete is not true, then the authentication blob will get processed with the normal flow.
@filipnavara
filipnavara requested review from rzikm and wfurtMarch 20, 2024 12:15
@filipnavara

Copy link
Copy Markdown
MemberAuthor

Helix is in quite a disrepair for the past few days (auto-scaler fails to scale several queues; already reported). The relevant pipelines succeeded though (win-x86, osx-x64).

@rzikmrzikm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wfurtwfurt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wfurt
wfurt merged commit be1b035 into dotnet:mainMar 26, 2024
@filipnavara
filipnavara deleted the seal branch March 26, 2024 16:07
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Apr 26, 2024
@karelzkarelz added this to the 9.0.0 milestone May 14, 2024
@rzikm

Copy link
Copy Markdown
Member

/backport to release/8.0-staging

@github-actionsgithub-actionsBot unlocked this conversation May 14, 2024
@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/8.0-staging: https://github.com/dotnet/runtime/actions/runs/9084265804

@github-actions

Copy link
Copy Markdown
Contributor

@rzikm backporting to release/8.0-staging failed, the patch most likely resulted in conflicts:

$ git am --3way --ignore-whitespace --keep-non-patch changes.patch
Applying: Send the NegotiateSeal NTLM flag when client asked for ProtectionLevel.EncryptAndSign.
Applying: Add testfor the NegotiateSeal flag
Using index info to reconstruct a base tree...
M	src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs
Falling back to patching base and 3-way merge...
Auto-merging src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs
Applying: Fix the test
Patch is empty.
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To record the empty patch as an empty commit, run "git am --allow-empty".
To restore the original branch and stop patching, run "git am --abort".
Error: The process '/usr/bin/git' failed with exit code 128

Please backport manually!

@github-actions

Copy link
Copy Markdown
Contributor

@rzikm an error occurred while backporting to release/8.0-staging, please check the run log for details!

Error: git am failed, most likely due to a merge conflict.

@github-actionsgithub-actionsBot locked as resolved and limited conversation to collaborators May 14, 2024
@karelz

Copy link
Copy Markdown
Member

Backport to 8.0 in PR #102216

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Net.Securitycommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NET8 WcfClient (net-tcp binding) running on linux cannot be authenticated on WCF Server hosted on windows if UseManagedNtlm is set

4 participants

@filipnavara@wfurt@rzikm@karelz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix NegotiateStream connections between Linux clients and Windows servers - #99909

Merged
wfurt merged 7 commits into
dotnet:mainfrom
filipnavara:seal
Mar 26, 2024
Merged

Fix NegotiateStream connections between Linux clients and Windows servers#99909
wfurt merged 7 commits into
dotnet:mainfrom
filipnavara:seal

Conversation

@filipnavara

@filipnavarafilipnavara commented Mar 18, 2024

Copy link
Copy Markdown
Member

Fixes#99227

Managed NTLM: Send the NegotiateSeal NTLM flag when client asks for ProtectionLevel.EncryptAndSign.

NegotiateStream: Process the last handshake done message. In case of SPNEGO protocol it may contain message integrity check. Additionally, if the negotiated protocol is NTLM then we need to reset the encryption keys after the message integrity check is verified.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/ncl, @bartonjs, @vcsjones
See info in area-owners.md if you want to be subscribed.

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Mar 18, 2024
Comment on lines +886 to +891
if (message.Length > 0)
{
Debug.Assert(_context != null);
_context.GetOutgoingBlob(message, out NegotiateAuthenticationStatusCode statusCode);
_remoteOk = statusCode is NegotiateAuthenticationStatusCode.Completed;
}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review note:

This was likely broken for quite a long time and went unnoticed. Windows clients send raw NTLM instead of SPNEGO if NTLM is the only protocol available. Linux/macOS GSSAPI libraries are aware of this quirk and handle it too. In that case the last handshake message will be empty.

Skipping the processing of the last handshake message is mostly harmless. In SPNEGO it would contain the final message integrity check. Skipping this check could enable some man-in-the-middle attacks, at least in theory. In case of managed NTLM/SPNEGO, however, we reset the NTLM keys only after this very last message is processed. If this is skipped the client and server will have mismatched encryption keys. This is how the bug was discovered.

In theory we can align with GSSAPI by calling (_mechanism as ManagedNtlmNegotiateAuthenticationPal)?.ResetKeys(); here:

_mechanism.GetMIC(_spnegoMechList,micBuffer);
writer.WriteOctetString(micBuffer.WrittenSpan);

We would reset the key both after GetMIC and VerifyMIC instead of doing it only after VerifyMIC. It may not be desirable to do so though; when the API is used correctly and securely this is not an issue.

@filipnavara
filipnavara marked this pull request as ready for review March 19, 2024 11:34
@filipnavara
filipnavara requested review from rzikm and wfurtMarch 19, 2024 11:34

@rzikmrzikm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wfurtwfurt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Seems like test is failing on some platforms.

@filipnavara

Copy link
Copy Markdown
MemberAuthor

Seems like test is failing on some platforms.

I will have a look. Helix was rather unhealthy today so I forgot to check the test results.

ProtectionLevel.EncryptAndSign.
Process the last handshake done message in NegotiateStream. In case of
SPNEGO protocol it may contain message integrity check. Additionally,
if the negotiated protocol is NTLM then we need to reset the encryption
key after the message integrity check is verified.
@filipnavara

Copy link
Copy Markdown
MemberAuthor

Turns out that I had one check order bug and one incorrect assumption in the test. The incorrect assumption was that I tried to check the scenario where the server doesn't support the NegotiateSeal attribute. The specification doesn't allow that:

If set, requests session key negotiation for message confidentiality. If the client sends NTLMSSP_NEGOTIATE_SEAL to the server in the NEGOTIATE_MESSAGE, the server MUST return NTLMSSP_NEGOTIATE_SEAL to the client in the CHALLENGE_MESSAGE.

Linux and the managed implementation handle it by returning error. Windows implementation silently assumes that the specification is followed and ignores that the server stripped NTLMSSP_NEGOTIATE_SEAL. I dropped the part of test since that's not what we are trying to fix anyway.

@filipnavara

Copy link
Copy Markdown
MemberAuthor

Just when I thought the CI cannot be any more borken... it fails to start any pipelines at all.

@wfurt

Copy link
Copy Markdown
Member

thanks @filipnavara for digging deep into this.

 /repo/artifacts/bin/System.Net.Security.Enterprise.Tests/Debug/net9.0-unix /repo/src/libraries/System.Net.Security/tests/EnterpriseTests
Discovering: System.Net.Security.Enterprise.Tests (method display = ClassAndMethod, method display options = None)
Discovered: System.Net.Security.Enterprise.Tests (found 6 test cases)
Starting: System.Net.Security.Enterprise.Tests (parallel test collections = on [4 threads], stop on fail = off)
Process terminated. Assertion failed.
at System.Net.Security.NegotiateStream.WriteAsync[TIOAdapter](ReadOnlyMemory`1 buffer, CancellationToken cancellationToken) in /_/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs:line 507
at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine) in /_/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilderCore.cs:line 38
at System.Net.Security.NegotiateStream.WriteAsync[TIOAdapter](ReadOnlyMemory`1 buffer, CancellationToken cancellationToken)

while your chance did not touched WriteAsync I'm wondering something changed. The buffer seems to be initialized upon successful authentication (but I only skimmed the code)

 else if (statusCode == NegotiateAuthenticationStatusCode.Completed)
{
_writeBuffer = new ArrayBufferWriter<byte>();

@filipnavara

Copy link
Copy Markdown
MemberAuthor

while your chance did not touched WriteAsync I'm wondering something changed.

These correlate with a SecurityQosFailed error. I'll have a deeper look.

Also, the osx-x64 tests should be using Managed NTLM and should not fail like that.

…shakeComplete.
If HandshakeComplete is not true, then the authentication blob will get processed with the normal flow.
@filipnavara
filipnavara requested review from rzikm and wfurtMarch 20, 2024 12:15
@filipnavara

Copy link
Copy Markdown
MemberAuthor

Helix is in quite a disrepair for the past few days (auto-scaler fails to scale several queues; already reported). The relevant pipelines succeeded though (win-x86, osx-x64).

@rzikmrzikm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wfurtwfurt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wfurt
wfurt merged commit be1b035 into dotnet:mainMar 26, 2024
@filipnavara
filipnavara deleted the seal branch March 26, 2024 16:07
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Apr 26, 2024
@karelzkarelz added this to the 9.0.0 milestone May 14, 2024
@rzikm

Copy link
Copy Markdown
Member

/backport to release/8.0-staging

@github-actionsgithub-actionsBot unlocked this conversation May 14, 2024
@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/8.0-staging: https://github.com/dotnet/runtime/actions/runs/9084265804

@github-actions

Copy link
Copy Markdown
Contributor

@rzikm backporting to release/8.0-staging failed, the patch most likely resulted in conflicts:

$ git am --3way --ignore-whitespace --keep-non-patch changes.patch
Applying: Send the NegotiateSeal NTLM flag when client asked for ProtectionLevel.EncryptAndSign.
Applying: Add testfor the NegotiateSeal flag
Using index info to reconstruct a base tree...
M	src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs
Falling back to patching base and 3-way merge...
Auto-merging src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs
Applying: Fix the test
Patch is empty.
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To record the empty patch as an empty commit, run "git am --allow-empty".
To restore the original branch and stop patching, run "git am --abort".
Error: The process '/usr/bin/git' failed with exit code 128

Please backport manually!

@github-actions

Copy link
Copy Markdown
Contributor

@rzikm an error occurred while backporting to release/8.0-staging, please check the run log for details!

Error: git am failed, most likely due to a merge conflict.

@github-actionsgithub-actionsBot locked as resolved and limited conversation to collaborators May 14, 2024
@karelz

Copy link
Copy Markdown
Member

Backport to 8.0 in PR #102216

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Net.Securitycommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NET8 WcfClient (net-tcp binding) running on linux cannot be authenticated on WCF Server hosted on windows if UseManagedNtlm is set

4 participants

@filipnavara@wfurt@rzikm@karelz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix NegotiateStream connections between Linux clients and Windows servers - #99909

Merged
wfurt merged 7 commits into
dotnet:mainfrom
filipnavara:seal
Mar 26, 2024
Merged

Fix NegotiateStream connections between Linux clients and Windows servers#99909
wfurt merged 7 commits into
dotnet:mainfrom
filipnavara:seal

Conversation

@filipnavara

@filipnavarafilipnavara commented Mar 18, 2024

Copy link
Copy Markdown
Member

Fixes#99227

Managed NTLM: Send the NegotiateSeal NTLM flag when client asks for ProtectionLevel.EncryptAndSign.

NegotiateStream: Process the last handshake done message. In case of SPNEGO protocol it may contain message integrity check. Additionally, if the negotiated protocol is NTLM then we need to reset the encryption keys after the message integrity check is verified.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/ncl, @bartonjs, @vcsjones
See info in area-owners.md if you want to be subscribed.

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Mar 18, 2024
Comment on lines +886 to +891
if (message.Length > 0)
{
Debug.Assert(_context != null);
_context.GetOutgoingBlob(message, out NegotiateAuthenticationStatusCode statusCode);
_remoteOk = statusCode is NegotiateAuthenticationStatusCode.Completed;
}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review note:

This was likely broken for quite a long time and went unnoticed. Windows clients send raw NTLM instead of SPNEGO if NTLM is the only protocol available. Linux/macOS GSSAPI libraries are aware of this quirk and handle it too. In that case the last handshake message will be empty.

Skipping the processing of the last handshake message is mostly harmless. In SPNEGO it would contain the final message integrity check. Skipping this check could enable some man-in-the-middle attacks, at least in theory. In case of managed NTLM/SPNEGO, however, we reset the NTLM keys only after this very last message is processed. If this is skipped the client and server will have mismatched encryption keys. This is how the bug was discovered.

In theory we can align with GSSAPI by calling (_mechanism as ManagedNtlmNegotiateAuthenticationPal)?.ResetKeys(); here:

_mechanism.GetMIC(_spnegoMechList,micBuffer);
writer.WriteOctetString(micBuffer.WrittenSpan);

We would reset the key both after GetMIC and VerifyMIC instead of doing it only after VerifyMIC. It may not be desirable to do so though; when the API is used correctly and securely this is not an issue.

@filipnavara
filipnavara marked this pull request as ready for review March 19, 2024 11:34
@filipnavara
filipnavara requested review from rzikm and wfurtMarch 19, 2024 11:34

@rzikmrzikm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wfurtwfurt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Seems like test is failing on some platforms.

@filipnavara

Copy link
Copy Markdown
MemberAuthor

Seems like test is failing on some platforms.

I will have a look. Helix was rather unhealthy today so I forgot to check the test results.

ProtectionLevel.EncryptAndSign.
Process the last handshake done message in NegotiateStream. In case of
SPNEGO protocol it may contain message integrity check. Additionally,
if the negotiated protocol is NTLM then we need to reset the encryption
key after the message integrity check is verified.
@filipnavara

Copy link
Copy Markdown
MemberAuthor

Turns out that I had one check order bug and one incorrect assumption in the test. The incorrect assumption was that I tried to check the scenario where the server doesn't support the NegotiateSeal attribute. The specification doesn't allow that:

If set, requests session key negotiation for message confidentiality. If the client sends NTLMSSP_NEGOTIATE_SEAL to the server in the NEGOTIATE_MESSAGE, the server MUST return NTLMSSP_NEGOTIATE_SEAL to the client in the CHALLENGE_MESSAGE.

Linux and the managed implementation handle it by returning error. Windows implementation silently assumes that the specification is followed and ignores that the server stripped NTLMSSP_NEGOTIATE_SEAL. I dropped the part of test since that's not what we are trying to fix anyway.

@filipnavara

Copy link
Copy Markdown
MemberAuthor

Just when I thought the CI cannot be any more borken... it fails to start any pipelines at all.

@wfurt

Copy link
Copy Markdown
Member

thanks @filipnavara for digging deep into this.

 /repo/artifacts/bin/System.Net.Security.Enterprise.Tests/Debug/net9.0-unix /repo/src/libraries/System.Net.Security/tests/EnterpriseTests
Discovering: System.Net.Security.Enterprise.Tests (method display = ClassAndMethod, method display options = None)
Discovered: System.Net.Security.Enterprise.Tests (found 6 test cases)
Starting: System.Net.Security.Enterprise.Tests (parallel test collections = on [4 threads], stop on fail = off)
Process terminated. Assertion failed.
at System.Net.Security.NegotiateStream.WriteAsync[TIOAdapter](ReadOnlyMemory`1 buffer, CancellationToken cancellationToken) in /_/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs:line 507
at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine) in /_/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilderCore.cs:line 38
at System.Net.Security.NegotiateStream.WriteAsync[TIOAdapter](ReadOnlyMemory`1 buffer, CancellationToken cancellationToken)

while your chance did not touched WriteAsync I'm wondering something changed. The buffer seems to be initialized upon successful authentication (but I only skimmed the code)

 else if (statusCode == NegotiateAuthenticationStatusCode.Completed)
{
_writeBuffer = new ArrayBufferWriter<byte>();

@filipnavara

Copy link
Copy Markdown
MemberAuthor

while your chance did not touched WriteAsync I'm wondering something changed.

These correlate with a SecurityQosFailed error. I'll have a deeper look.

Also, the osx-x64 tests should be using Managed NTLM and should not fail like that.

…shakeComplete.
If HandshakeComplete is not true, then the authentication blob will get processed with the normal flow.
@filipnavara
filipnavara requested review from rzikm and wfurtMarch 20, 2024 12:15
@filipnavara

Copy link
Copy Markdown
MemberAuthor

Helix is in quite a disrepair for the past few days (auto-scaler fails to scale several queues; already reported). The relevant pipelines succeeded though (win-x86, osx-x64).

@rzikmrzikm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wfurtwfurt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wfurt
wfurt merged commit be1b035 into dotnet:mainMar 26, 2024
@filipnavara
filipnavara deleted the seal branch March 26, 2024 16:07
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Apr 26, 2024
@karelzkarelz added this to the 9.0.0 milestone May 14, 2024
@rzikm

Copy link
Copy Markdown
Member

/backport to release/8.0-staging

@github-actionsgithub-actionsBot unlocked this conversation May 14, 2024
@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/8.0-staging: https://github.com/dotnet/runtime/actions/runs/9084265804

@github-actions

Copy link
Copy Markdown
Contributor

@rzikm backporting to release/8.0-staging failed, the patch most likely resulted in conflicts:

$ git am --3way --ignore-whitespace --keep-non-patch changes.patch
Applying: Send the NegotiateSeal NTLM flag when client asked for ProtectionLevel.EncryptAndSign.
Applying: Add testfor the NegotiateSeal flag
Using index info to reconstruct a base tree...
M	src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs
Falling back to patching base and 3-way merge...
Auto-merging src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs
Applying: Fix the test
Patch is empty.
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To record the empty patch as an empty commit, run "git am --allow-empty".
To restore the original branch and stop patching, run "git am --abort".
Error: The process '/usr/bin/git' failed with exit code 128

Please backport manually!

@github-actions

Copy link
Copy Markdown
Contributor

@rzikm an error occurred while backporting to release/8.0-staging, please check the run log for details!

Error: git am failed, most likely due to a merge conflict.

@github-actionsgithub-actionsBot locked as resolved and limited conversation to collaborators May 14, 2024
@karelz

Copy link
Copy Markdown
Member

Backport to 8.0 in PR #102216

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Net.Securitycommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NET8 WcfClient (net-tcp binding) running on linux cannot be authenticated on WCF Server hosted on windows if UseManagedNtlm is set

4 participants

@filipnavara@wfurt@rzikm@karelz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Fix NegotiateStream connections between Linux clients and Windows servers - #99909

Merged
wfurt merged 7 commits into
dotnet:mainfrom
filipnavara:seal
Mar 26, 2024
Merged

Fix NegotiateStream connections between Linux clients and Windows servers#99909
wfurt merged 7 commits into
dotnet:mainfrom
filipnavara:seal

Conversation

@filipnavara

@filipnavarafilipnavara commented Mar 18, 2024

Copy link
Copy Markdown
Member

Fixes#99227

Managed NTLM: Send the NegotiateSeal NTLM flag when client asks for ProtectionLevel.EncryptAndSign.

NegotiateStream: Process the last handshake done message. In case of SPNEGO protocol it may contain message integrity check. Additionally, if the negotiated protocol is NTLM then we need to reset the encryption keys after the message integrity check is verified.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/ncl, @bartonjs, @vcsjones
See info in area-owners.md if you want to be subscribed.

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Mar 18, 2024
Comment on lines +886 to +891
if (message.Length > 0)
{
Debug.Assert(_context != null);
_context.GetOutgoingBlob(message, out NegotiateAuthenticationStatusCode statusCode);
_remoteOk = statusCode is NegotiateAuthenticationStatusCode.Completed;
}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review note:

This was likely broken for quite a long time and went unnoticed. Windows clients send raw NTLM instead of SPNEGO if NTLM is the only protocol available. Linux/macOS GSSAPI libraries are aware of this quirk and handle it too. In that case the last handshake message will be empty.

Skipping the processing of the last handshake message is mostly harmless. In SPNEGO it would contain the final message integrity check. Skipping this check could enable some man-in-the-middle attacks, at least in theory. In case of managed NTLM/SPNEGO, however, we reset the NTLM keys only after this very last message is processed. If this is skipped the client and server will have mismatched encryption keys. This is how the bug was discovered.

In theory we can align with GSSAPI by calling (_mechanism as ManagedNtlmNegotiateAuthenticationPal)?.ResetKeys(); here:

_mechanism.GetMIC(_spnegoMechList,micBuffer);
writer.WriteOctetString(micBuffer.WrittenSpan);

We would reset the key both after GetMIC and VerifyMIC instead of doing it only after VerifyMIC. It may not be desirable to do so though; when the API is used correctly and securely this is not an issue.

@filipnavara
filipnavara marked this pull request as ready for review March 19, 2024 11:34
@filipnavara
filipnavara requested review from rzikm and wfurtMarch 19, 2024 11:34

@rzikmrzikm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wfurtwfurt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Seems like test is failing on some platforms.

@filipnavara

Copy link
Copy Markdown
MemberAuthor

Seems like test is failing on some platforms.

I will have a look. Helix was rather unhealthy today so I forgot to check the test results.

ProtectionLevel.EncryptAndSign.
Process the last handshake done message in NegotiateStream. In case of
SPNEGO protocol it may contain message integrity check. Additionally,
if the negotiated protocol is NTLM then we need to reset the encryption
key after the message integrity check is verified.
@filipnavara

Copy link
Copy Markdown
MemberAuthor

Turns out that I had one check order bug and one incorrect assumption in the test. The incorrect assumption was that I tried to check the scenario where the server doesn't support the NegotiateSeal attribute. The specification doesn't allow that:

If set, requests session key negotiation for message confidentiality. If the client sends NTLMSSP_NEGOTIATE_SEAL to the server in the NEGOTIATE_MESSAGE, the server MUST return NTLMSSP_NEGOTIATE_SEAL to the client in the CHALLENGE_MESSAGE.

Linux and the managed implementation handle it by returning error. Windows implementation silently assumes that the specification is followed and ignores that the server stripped NTLMSSP_NEGOTIATE_SEAL. I dropped the part of test since that's not what we are trying to fix anyway.

@filipnavara

Copy link
Copy Markdown
MemberAuthor

Just when I thought the CI cannot be any more borken... it fails to start any pipelines at all.

@wfurt

Copy link
Copy Markdown
Member

thanks @filipnavara for digging deep into this.

 /repo/artifacts/bin/System.Net.Security.Enterprise.Tests/Debug/net9.0-unix /repo/src/libraries/System.Net.Security/tests/EnterpriseTests
Discovering: System.Net.Security.Enterprise.Tests (method display = ClassAndMethod, method display options = None)
Discovered: System.Net.Security.Enterprise.Tests (found 6 test cases)
Starting: System.Net.Security.Enterprise.Tests (parallel test collections = on [4 threads], stop on fail = off)
Process terminated. Assertion failed.
at System.Net.Security.NegotiateStream.WriteAsync[TIOAdapter](ReadOnlyMemory`1 buffer, CancellationToken cancellationToken) in /_/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs:line 507
at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine) in /_/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilderCore.cs:line 38
at System.Net.Security.NegotiateStream.WriteAsync[TIOAdapter](ReadOnlyMemory`1 buffer, CancellationToken cancellationToken)

while your chance did not touched WriteAsync I'm wondering something changed. The buffer seems to be initialized upon successful authentication (but I only skimmed the code)

 else if (statusCode == NegotiateAuthenticationStatusCode.Completed)
{
_writeBuffer = new ArrayBufferWriter<byte>();

@filipnavara

Copy link
Copy Markdown
MemberAuthor

while your chance did not touched WriteAsync I'm wondering something changed.

These correlate with a SecurityQosFailed error. I'll have a deeper look.

Also, the osx-x64 tests should be using Managed NTLM and should not fail like that.

…shakeComplete.
If HandshakeComplete is not true, then the authentication blob will get processed with the normal flow.
@filipnavara
filipnavara requested review from rzikm and wfurtMarch 20, 2024 12:15
@filipnavara

Copy link
Copy Markdown
MemberAuthor

Helix is in quite a disrepair for the past few days (auto-scaler fails to scale several queues; already reported). The relevant pipelines succeeded though (win-x86, osx-x64).

@rzikmrzikm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wfurtwfurt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wfurt
wfurt merged commit be1b035 into dotnet:mainMar 26, 2024
@filipnavara
filipnavara deleted the seal branch March 26, 2024 16:07
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Apr 26, 2024
@karelzkarelz added this to the 9.0.0 milestone May 14, 2024
@rzikm

Copy link
Copy Markdown
Member

/backport to release/8.0-staging

@github-actionsgithub-actionsBot unlocked this conversation May 14, 2024
@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/8.0-staging: https://github.com/dotnet/runtime/actions/runs/9084265804

@github-actions

Copy link
Copy Markdown
Contributor

@rzikm backporting to release/8.0-staging failed, the patch most likely resulted in conflicts:

$ git am --3way --ignore-whitespace --keep-non-patch changes.patch
Applying: Send the NegotiateSeal NTLM flag when client asked for ProtectionLevel.EncryptAndSign.
Applying: Add testfor the NegotiateSeal flag
Using index info to reconstruct a base tree...
M	src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs
Falling back to patching base and 3-way merge...
Auto-merging src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs
Applying: Fix the test
Patch is empty.
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To record the empty patch as an empty commit, run "git am --allow-empty".
To restore the original branch and stop patching, run "git am --abort".
Error: The process '/usr/bin/git' failed with exit code 128

Please backport manually!

@github-actions

Copy link
Copy Markdown
Contributor

@rzikm an error occurred while backporting to release/8.0-staging, please check the run log for details!

Error: git am failed, most likely due to a merge conflict.

@github-actionsgithub-actionsBot locked as resolved and limited conversation to collaborators May 14, 2024
@karelz

Copy link
Copy Markdown
Member

Backport to 8.0 in PR #102216

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Net.Securitycommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NET8 WcfClient (net-tcp binding) running on linux cannot be authenticated on WCF Server hosted on windows if UseManagedNtlm is set

4 participants

@filipnavara@wfurt@rzikm@karelz