codeape
codeape

Reputation: 100896

Calling SOAP web service with SAML2 token using WCF

I am trying to do the following:

Exception:

SecurityNegotiationException

Inner exception: Fault Exception
The message with Action 'http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue' cannot be processed at the receiver, 
due to a ContractFilter mismatch at the EndpointDispatcher. This may be because of either a contract mismatch (mismatched Actions 
between sender and receiver) or a binding/security mismatch between the sender and the receiver.  Check that sender and receiver 
have the same contract and the same binding (including security requirements, e.g. Message, Transport, None).

Fiddler logs the following request:

POST /RespondingGateway/ HTTP/1.1
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
    <s:Header>
        <a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action>
        <a:MessageID>urn:uuid:3609d530-4b8d-4e9c-8907-bb346cfe0c91</a:MessageID>
        <a:ReplyTo>
            <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
        </a:ReplyTo>
        <a:To s:mustUnderstand="1">http://localhost:9000/RespondingGateway/</a:To>
    </s:Header>
    <s:Body>
        <t:RequestSecurityToken Context="uuid-e6928b4c-6100-4a1d-8818-8e7436f7a935-12" xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
            <t:TokenType>http://schemas.xmlsoap.org/ws/2005/02/sc/sct</t:TokenType>
            <t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
            <t:KeySize>256</t:KeySize>
            <t:BinaryExchange ValueType=" http://schemas.xmlsoap.org/ws/2005/02/trust/tlsnego" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">...</t:BinaryExchange>
        </t:RequestSecurityToken>
    </s:Body>
</s:Envelope>

So it looks to me as if WCF is trying to contact an STS to issue a token. Correct?

I don't want WCF to get a token from an STS, I want to provide my own token.

Questions/issues:

Here's the code I use for testing:

void SendRequest(XElement requestBody)
{
    var binding = new WSFederationHttpBinding();
    binding.Security.Mode = WSFederationHttpSecurityMode.Message;
    binding.Security.Message.IssuedTokenType = 
        "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0";

    var remoteAddress = 
        new EndpointAddress("http://localhost:9000/RespondingGateway/");
    var channelFactory = 
        new ChannelFactory<ICrossGatewayQueryITI38>(binding, remoteAddress);

    // if true I get a "Cardspace not installed" exception
    channelFactory.Credentials.SupportInteractive = false; 

    var channel = channelFactory.CreateChannelWithIssuedToken(
        CreateSaml2Token(
            GetCertificate(
                "thumbprint_of_certificate_i_have_private_key_of"
            )
        )
    );
    var response = channel.CrossGatewayQuery(CreateRequestMessage(requestBody));
    var body = XElement.ReadFrom(response.GetReaderAtBodyContents());
    Console.WriteLine(body.ToString());
}

Saml2SecurityToken CreateSaml2Token(X509Certificate2 certificate)
{
    var std = new SecurityTokenDescriptor();
    std.TokenIssuerName = "Foobar";
    std.TokenType = "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0";
    std.Lifetime = new Lifetime(DateTime.Now, DateTime.Now + TimeSpan.FromDays(10));
    std.Subject = new ClaimsIdentity(new[] { new Claim("User", "TheUserName") });

    if (certificate != null)
    {
        var ski = new SecurityKeyIdentifier(new SecurityKeyIdentifierClause[] { new X509SecurityToken(certificate).CreateKeyIdentifierClause<X509SubjectKeyIdentifierClause>() });
        std.SigningCredentials = new X509SigningCredentials(certificate, ski); ;
    }
    return (Saml2SecurityToken)new Saml2SecurityTokenHandler().CreateToken(std);
}

Message CreateRequestMessage(XElement requestBody)
{
    return Message.CreateMessage(
        MessageVersion.Soap12WSAddressing10,
        "urn:ihe:iti:2007:CrossGatewayQuery",
        requestBody
    );
}

[ServiceContract(Namespace = "urn:ihe:iti:xds-b:2007")]
public interface ICrossGatewayQueryITI38
{
    [OperationContract(Action = "urn:ihe:iti:2007:CrossGatewayQuery", ReplyAction = "urn:ihe:iti:2007:CrossGatewayQueryResponse")]
    Message CrossGatewayQuery(Message request);
}

Here is the stack trace:

Server stack trace: 
   at System.ServiceModel.Security.IssuanceTokenProviderBase`1.DoNegotiation(TimeSpan timeout)
   at System.ServiceModel.Security.SspiNegotiationTokenProvider.OnOpen(TimeSpan timeout)
   at System.ServiceModel.Security.TlsnegoTokenProvider.OnOpen(TimeSpan timeout)
   at System.ServiceModel.Security.WrapperSecurityCommunicationObject.OnOpen(TimeSpan timeout)
   at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
   at System.ServiceModel.Security.CommunicationObjectSecurityTokenProvider.Open(TimeSpan timeout)
   at System.ServiceModel.Security.SymmetricSecurityProtocol.OnOpen(TimeSpan timeout)
   at System.ServiceModel.Security.WrapperSecurityCommunicationObject.OnOpen(TimeSpan timeout)
   at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
   at System.ServiceModel.Channels.SecurityChannelFactory`1.ClientSecurityChannel`1.OnOpen(TimeSpan timeout)
   at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
   at System.ServiceModel.Security.SecuritySessionSecurityTokenProvider.DoOperation(SecuritySessionOperation operation, EndpointAddress target, Uri via, SecurityToken currentToken, TimeSpan timeout)
   at System.ServiceModel.Security.SecuritySessionSecurityTokenProvider.GetTokenCore(TimeSpan timeout)
   at System.IdentityModel.Selectors.SecurityTokenProvider.GetToken(TimeSpan timeout)
   at System.ServiceModel.Security.SecuritySessionClientSettings`1.ClientSecuritySessionChannel.OnOpen(TimeSpan timeout)
   at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannel.OnOpen(TimeSpan timeout)
   at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannel.CallOpenOnce.System.ServiceModel.Channels.ServiceChannel.ICallOnce.Call(ServiceChannel channel, TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannel.CallOnceManager.CallOnce(TimeSpan timeout, CallOnceManager cascade)
   at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
   at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)

Exception rethrown at [0]: 
   at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
   at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
   at XDSDotNet.ICrossGatewayQueryITI38.CrossGatewayQuery(Message request)
   at UserQuery.SendRequest(XElement requestBody)
   at UserQuery.Main()
   at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()

Update after tgolisch's comments

Forget the part about creating the SAML token in code.

The user authenticates and receives a SAML token from the authentication provider. A bit later the user visits an URL and the web app needs to do a SOAP call to a service to get the data the user needs.

So let's rephrase the question in this context: How can the web app (using WCF) make a SOAP call to the web service and include the SAML token that was provided when the user logged in?

See also

Call WCF service with issued token

Upvotes: 3

Views: 971

Answers (1)

tgolisch
tgolisch

Reputation: 6734

What you are trying to do fundamentally goes against the concept of federated identity. If you assemble your own token, then it will look like a forged token, because it is. The token needs to come from the auth server and (therefore) checked against the auth server (or member-server in the federation).

Edit: The same limit applies if you want the web server to reuse the identity, for a call to a web service. Think about the implications, if the web server could act as a proxy to relay the credentials, this would be a very serious security caveat. It would be equivalent to a man-in-the-middle attack. OAuth2 is supposed to prevent this sort of thing.

Therefore, the solution would be to write the app so the client machine (which is already authenticated) can contact the web service directly. From an architecture and design perspective, it is a hassle, but that is the way this works. It might explain the recent popularity of JS frameworks (JQuery, Angular, etc.).

Upvotes: 1

Related Questions