Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix TransportWithMessageCredentials with TransferMode.Streamed missing security header #4873

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion eng/SendToHelix.proj
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
</ItemGroup>

<ItemGroup Condition="'$(TestJob)' == 'Windows' AND '$(RunAsInternal)'" >
<HelixTargetQueue Include="windows.11.amd64" />
<HelixTargetQueue Include="windows.11.amd64.client" />
<HelixTargetQueue Include="(Debian.11.Amd64)ubuntu.2004.amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-11-helix-amd64-20220511124750-0ece9b3" />
<HelixTargetQueue Include="RedHat.7.Amd64" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.IO;
using System.Runtime;
using System.ServiceModel.Channels;
using System.Threading.Tasks;
using System.Xml;

using IPrefixGenerator = System.IdentityModel.IPrefixGenerator;
Expand Down Expand Up @@ -138,6 +139,33 @@ protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
}
}

protected override async Task OnWriteBodyContentsAsync(XmlDictionaryWriter writer)
{
switch (_state)
{
case BodyState.Created:
await InnerMessage.WriteBodyContentsAsync(writer);
return;
case BodyState.Signed:
case BodyState.EncryptedThenSigned:
XmlDictionaryReader reader = _fullBodyBuffer.GetReader(0);
reader.ReadStartElement();
while (reader.NodeType != XmlNodeType.EndElement)
{
await writer.WriteNodeAsync(reader, false);
}

reader.ReadEndElement();
reader.Close();
return;
case BodyState.Encrypted:
case BodyState.SignedThenEncrypted:
throw ExceptionHelper.PlatformNotSupported();
default:
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(CreateBadStateException(nameof(OnWriteBodyContentsAsync)));
}
}

protected override void OnWriteMessage(XmlDictionaryWriter writer)
{
// For Kerb one shot, the channel binding will be need to be fished out of the message, cached and added to the
Expand Down Expand Up @@ -197,6 +225,65 @@ protected override void OnWriteMessage(XmlDictionaryWriter writer)
writer.WriteEndElement();
}

public override async Task OnWriteMessageAsync(XmlDictionaryWriter writer)
{
// For Kerb one shot, the channel binding will be need to be fished out of the message, cached and added to the
// token before calling ISC.

AttachChannelBindingTokenIfFound();

EnsureUniqueSecurityApplication();

MessagePrefixGenerator prefixGenerator = new MessagePrefixGenerator(writer);
_securityHeader.StartSecurityApplication();

Headers.Add(_securityHeader);

InnerMessage.WriteStartEnvelope(writer);

Headers.RemoveAt(Headers.Count - 1);

_securityHeader.ApplyBodySecurity(writer, prefixGenerator);

InnerMessage.WriteStartHeaders(writer);
_securityHeader.ApplySecurityAndWriteHeaders(Headers, writer, prefixGenerator);

_securityHeader.RemoveSignatureEncryptionIfAppropriate();

_securityHeader.CompleteSecurityApplication();
_securityHeader.WriteHeader(writer, Version);
await writer.WriteEndElementAsync();

if (_fullBodyFragment != null)
{
((IFragmentCapableXmlDictionaryWriter)writer).WriteFragment(_fullBodyFragment, 0, _fullBodyFragmentLength);
}
else
{
if (_startBodyFragment != null)
{
((IFragmentCapableXmlDictionaryWriter)writer).WriteFragment(_startBodyFragment.GetBuffer(), 0, (int)_startBodyFragment.Length);
}
else
{
OnWriteStartBody(writer);
}

await OnWriteBodyContentsAsync(writer);

if (_endBodyFragment != null)
{
((IFragmentCapableXmlDictionaryWriter)writer).WriteFragment(_endBodyFragment.GetBuffer(), 0, (int)_endBodyFragment.Length);
}
else
{
writer.WriteEndElement();
}
}

await writer.WriteEndElementAsync();
}

private void AttachChannelBindingTokenIfFound()
{
// Only valid when using a TokenParameter of type KerberosSecurityTokenParameters which isn't currently supported
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class BasicHttpTransportWithMessageCredentialSecurityTests : ConditionalW
nameof(Client_Certificate_Installed),
nameof(SSL_Available))]
[OuterLoop]
public static void Https_SecModeTransWithMessCred_CertClientCredential_Succeeds()
public static void BasicHttps_SecModeTransWithMessCred_CertClientCredential_Succeeds()
{
string clientCertThumb = null;
EndpointAddress endpointAddress = null;
Expand Down Expand Up @@ -54,6 +54,56 @@ public static void Https_SecModeTransWithMessCred_CertClientCredential_Succeeds(
}
}

[WcfTheory]
[Condition(nameof(Root_Certificate_Installed),
nameof(SSL_Available))]
[OuterLoop]
[InlineData(TransferMode.Buffered)]
[InlineData(TransferMode.Streamed)]
public static void BasicHttps_SecModeTransWithMessCred_UserNameClientCredential_Succeeds(TransferMode transferMode)
{
EndpointAddress endpointAddress = null;
string testString = "Hello";
string username = null;
string password = null;
ChannelFactory<IWcfService> factory = null;
IWcfService serviceProxy = null;

try
{
// *** SETUP *** \\
BasicHttpBinding binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportWithMessageCredential);
binding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;
binding.TransferMode = transferMode;
endpointAddress = new EndpointAddress(new Uri(Endpoints.BasicHttps_SecModeTransWithMessCred_ClientCredTypeUserName + $"/{Enum.GetName(typeof(TransferMode), transferMode)}"));

factory = new ChannelFactory<IWcfService>(binding, endpointAddress);
username = Guid.NewGuid().ToString("n").Substring(0, 8);
char[] usernameArr = username.ToCharArray();
Array.Reverse(usernameArr);
password = new string(usernameArr);
factory.Credentials.UserName.UserName = username;
factory.Credentials.UserName.Password = password;

serviceProxy = factory.CreateChannel();

// *** EXECUTE *** \\
string result = serviceProxy.Echo(testString);

// *** VALIDATE *** \\
Assert.Equal(testString, result);

// *** CLEANUP *** \\
((ICommunicationObject)serviceProxy).Close();
factory.Close();
}
finally
{
// *** ENSURE CLEANUP *** \\
ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory);
}
}

[WcfFact]
[Condition(nameof(Root_Certificate_Installed),
nameof(SSL_Available))]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Channels;

Expand All @@ -11,13 +12,25 @@ namespace WcfService
[TestServiceDefinition(Schema = ServiceSchema.HTTPS, BasePath = "BasicHttpsTransSecMessCredsUserName.svc")]
internal class BasicHttpsTransportSecurityMessageCredentialsUserNameTestServiceHost : TestServiceHostBase<IWcfService>
{
protected override string Address { get { return "https-message-credentials-username"; } }
protected override IList<Binding> GetBindings()
{
return new List<Binding> { GetBufferedBinding(), GetStreamedBinding() };
}

protected override Binding GetBinding()
protected Binding GetBufferedBinding()
{
BasicHttpBinding binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportWithMessageCredential);
binding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;
binding.Name = "https-message-credentials-username/Buffered";
return binding;
}

protected Binding GetStreamedBinding()
{
BasicHttpBinding binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportWithMessageCredential);
binding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;
binding.TransferMode = TransferMode.Streamed;
binding.Name = "https-message-credentials-username/Streamed";
return binding;
}

Expand Down