0

I'm trying to requests a security token from a STS service. The service is 3rd party, so I can't modify it, check logs, etc.

The resulting request SOAP message looks almost identical to a sample request I have. It's only that there are two BinarySecurityToken elements added with identical values, while the proper request contains only one token.

The SOAP message looks like this:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
  <s:Header>
    <o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
      <u:Timestamp u:Id="uuid-4db59a59-9180-4efe-92d0-14aefcbf68d5-1">
        <u:Created>2014-08-22T07:51:45.763Z</u:Created>
        <u:Expires>2014-08-22T08:51:45.763Z</u:Expires>
      </u:Timestamp>
      <o:BinarySecurityToken u:Id="uuid-54e35db8-29dc-4d3c-bba2-c6eacb7cf4e9-4" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">...</o:BinarySecurityToken>
      <o:BinarySecurityToken u:Id="uuid-54e35db8-29dc-4d3c-bba2-c6eacb7cf4e9-2" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">...</o:BinarySecurityToken>
      <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
        <SignedInfo>
          <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
          <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
          <Reference URI="#_1">
            <Transforms>
              <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            </Transforms>
            <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
            <DigestValue>...</DigestValue>
          </Reference>
          <Reference URI="#uuid-4db59a59-9180-4efe-92d0-14aefcbf68d5-1">
            <Transforms>
              <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            </Transforms>
            <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
            <DigestValue>...</DigestValue>
          </Reference>
          <Reference URI="#uuid-54e35db8-29dc-4d3c-bba2-c6eacb7cf4e9-2">
            <Transforms>
              <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            </Transforms>
            <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
            <DigestValue>...</DigestValue>
          </Reference>
        </SignedInfo>
        <SignatureValue>.../SignatureValue>
        <KeyInfo>
          <o:SecurityTokenReference>
            <o:Reference ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" URI="#uuid-54e35db8-29dc-4d3c-bba2-c6eacb7cf4e9-4"/>
          </o:SecurityTokenReference>
        </KeyInfo>
      </Signature>
    </o:Security>
  </s:Header>
  <s:Body u:Id="_1">
    <trust:RequestSecurityToken xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">
      <trust:Claims Dialect="http://docs.oasis-open.org/wsfed/authorization/200706/authclaims" xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706">
        <auth:ClaimType Uri="custom claim" Optional="true">
          <auth:Value>...</auth:Value>
        </auth:ClaimType>
      </trust:Claims>
      <trust:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/PublicKey</trust:KeyType>
      <trust:RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</trust:RequestType>
      <trust:TokenType>http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1</trust:TokenType>
    </trust:RequestSecurityToken>
  </s:Body>
</s:Envelope>

The security binding element as well as the binding are configured like this:

AsymmetricSecurityBindingElement messageSecurity = SecurityBindingElement.CreateMutualCertificateDuplexBindingElement(
    MessageSecurityVersion.WSSecurity10WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10
);

messageSecurity.IncludeTimestamp = true;
messageSecurity.LocalClientSettings.TimestampValidityDuration = TimeSpan.FromHours(1);
messageSecurity.ProtectTokens = true;
messageSecurity.SecurityHeaderLayout = SecurityHeaderLayout.Lax;
messageSecurity.DefaultAlgorithmSuite = SecurityAlgorithmSuite.Basic256;
messageSecurity.EnableUnsecuredResponse = true;

X509SecurityTokenParameters initiatorParams = new X509SecurityTokenParameters
{
    InclusionMode = SecurityTokenInclusionMode.AlwaysToRecipient,
    ReferenceStyle = SecurityTokenReferenceStyle.Internal,
    RequireDerivedKeys = false,
    X509ReferenceStyle = X509KeyIdentifierClauseType.IssuerSerial
};

X509SecurityTokenParameters recipientParams = new X509SecurityTokenParameters
{
    InclusionMode = SecurityTokenInclusionMode.Never,
    ReferenceStyle = SecurityTokenReferenceStyle.Internal,
    RequireDerivedKeys = false,
    X509ReferenceStyle = X509KeyIdentifierClauseType.IssuerSerial
};

X509SecurityTokenParameters endpointParams = new X509SecurityTokenParameters
{
    InclusionMode = SecurityTokenInclusionMode.Once,
    ReferenceStyle = SecurityTokenReferenceStyle.Internal,
    RequireDerivedKeys = false,
    X509ReferenceStyle = X509KeyIdentifierClauseType.IssuerSerial
};

messageSecurity.InitiatorTokenParameters = initiatorParams;
messageSecurity.RecipientTokenParameters = recipientParams;
messageSecurity.EndpointSupportingTokenParameters.Signed.Add(endpointParams);

HttpsTransportBindingElement elem = new HttpsTransportBindingElement();
CustomBinding binding = new CustomBinding(messageSecurity, new TextMessageEncodingBindingElement(MessageVersion.Soap11, Encoding.UTF8), elem);

return binding;

I also have specifically configured the WSTrustChannelFactory to only sign messages (not sign & encrypt):

var factory = new WSTrustChannelFactory(binding, endpoint);
factory.Endpoint.Contract.ProtectionLevel = ProtectionLevel.Sign;

The only thing is that the service's security policy mentions that the initiator token should be a SAML token, while I'm using an X509SecurityTokenParameters. This is mostly because I have no idea how to add a SAML token, but I'm not sure what difference this would make.

For reference, the WS-Security policy is:

<wsp:Policy xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
    xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"> 
        <sp:AsymmetricBinding>
        <wsp:Policy> 
            <sp:InitiatorToken>
                <wsp:Policy>
                    <sp:SamlToken
                    sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient"> 
                        <wsp:Policy>
                            <sp:WssSamlV11Token10/>
                        </wsp:Policy>
                    </sp:SamlToken>
                </wsp:Policy>
            </sp:InitiatorToken>
        <sp:RecipientToken>
            <wsp:Policy>
                <sp:X509Token
                sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/Never">
            <wsp:Policy>
                <sp:WssX509V3Token10/>
            </wsp:Policy>
                </sp:X509Token>
            </wsp:Policy>
        </sp:RecipientToken>
        <sp:AlgorithmSuite>
            <wsp:Policy>
                <sp:Basic256/>
            </wsp:Policy>
        </sp:AlgorithmSuite>
        <sp:Layout>
            <wsp:Policy>
                <sp:Lax/>
            </wsp:Policy>
        </sp:Layout>
        <sp:IncludeTimestamp/>
        <sp:ProtectTokens/>
        <sp:OnlySignEntireHeadersAndBody/>
        </wsp:Policy>
    </sp:AsymmetricBinding>
    <sp:Wss10>
        <wsp:Policy>
            <sp:MustSupportRefKeyIdentifier/>
            <sp:MustSupportRefIssuerSerial/>
        </wsp:Policy>
    </sp:Wss10>
    <sp:SignedParts>
        <sp:Body/>
    </sp:SignedParts>
</wsp:Policy>

Ultimately, I'm after a way to have the message contain only one binary token. If the only way is to manually alter the SOAP XML just before it is being sent, then so be it.

Marcel N.
  • 13,726
  • 5
  • 47
  • 72
  • why do you add EndpointSupportingTokenParameters? This is adding the new binary token – Yaron Naveh Aug 22 '14 at 09:52
  • @YaronNaveh: I need to sign the Body, the Timestamp and the BinarySecurityToken. Without `EndpointSupportingTokenParameters` I only get signatures for Timestamp and Body. It's actually the only way I've found to sign all three of them. – Marcel N. Aug 22 '14 at 09:54
  • @YaronNaveh: Actually, what I'm doing now is working on a custom encoder so I can get the final SOAP XML right before it's sent out and remove one of the binary tokens. Not ideal, but might work. – Marcel N. Aug 22 '14 at 10:11
  • 1
    this will not work, the extra token you are adding is the one you sign, not the original token that signs the message. removing either of them will invalidate the message. I suggest to use CreateMutualCertificateBindingElement and not CreateMutualCertificateDuplexBindingElement. Then remove the extra supporting token. ProtectTokens=ture is supposed to ensure the token is signed. though in many cases the server does not care about that so maybe you actually have another problem. – Yaron Naveh Aug 22 '14 at 10:59
  • @YaronNaveh: Did it, but unfortunately I get the same (generic) error from the other party. I will contact and see what they have to say. I removed the token `uuid-54e35db8-29dc-4d3c-bba2-c6eacb7cf4e9-2` and also updated the `//SignedInfo//Reference` entry to point to the other token. I also tried with both bindings. Thanks! – Marcel N. Aug 22 '14 at 12:40

1 Answers1

0

I have successfully managed to get a token back from the STS service by using the following code for creating the binding.

It appears that the extra token was being added when X509SecurityTokenParameters.InclusionMode was set to anything else than SecurityTokenInclusionMode.Never on the either of InitiatorTokenParameters, RecipientTokenParameters or EndpointSupportingTokenParameters.Signed.

The code for creating the binding is below, in case anyone else needs it:

var messageSecurity = new AsymmetricSecurityBindingElement
{
    MessageSecurityVersion = MessageSecurityVersion.WSSecurity11WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10,
    InitiatorTokenParameters = new X509SecurityTokenParameters
    {
        InclusionMode = SecurityTokenInclusionMode.Never,
        ReferenceStyle = SecurityTokenReferenceStyle.Internal,
    },
    RecipientTokenParameters = new X509SecurityTokenParameters
    {
        InclusionMode = SecurityTokenInclusionMode.Never,
        ReferenceStyle = SecurityTokenReferenceStyle.Internal
    },
    MessageProtectionOrder = MessageProtectionOrder.SignBeforeEncrypt,
    SecurityHeaderLayout = SecurityHeaderLayout.Lax,
    EnableUnsecuredResponse = true,
    IncludeTimestamp = true
};
messageSecurity.SetKeyDerivation(false);
messageSecurity.DefaultAlgorithmSuite = SecurityAlgorithmSuite.Basic256;
messageSecurity.EndpointSupportingTokenParameters.Signed.Add(new X509SecurityTokenParameters());
messageSecurity.LocalClientSettings.TimestampValidityDuration = TimeSpan.FromHours(1);
messageSecurity.RequireSignatureConfirmation = true;
messageSecurity.AllowSerializedSigningTokenOnReply = true;

HttpsTransportBindingElement elem = new HttpsTransportBindingElement { RequireClientCertificate = true };
CustomBinding binding = new CustomBinding(messageSecurity, new TextMessageEncodingBindingElement(MessageVersion.Soap11, Encoding.UTF8), elem);

I also had an issue with the RequestSecurityToken serialization in the SOAP body. WCF was using a different namespace here:

<auth:ClaimType Uri="..." xmlns:auth="http://schemas.xmlsoap.org/ws/2006/12/authorization">

I solved this with a client message inspector, where I adjust the namespaces right before they are being signed and sent.

Marcel N.
  • 13,726
  • 5
  • 47
  • 72